docs / storage / chunked-transfer · API v1 · обновлено 2026-10-12
Чанковая передача файлов .safetensors
На этой странице описано, как TensorBase хранит, принимает и отдаёт многогигабайтные файлы весов, и как обращаться к эндпоинту /media/chunks напрямую, когда CLI использовать нельзя (собственные загрузчики, зеркала в изолированных контурах, скрипты прогрева инференса).
Обзор
Ревизия репозитория — это неизменяемый манифест: список файлов, каждый из которых описан размером, SHA-256 и упорядоченным списком хэшей чанков. На уровне хранилища байты файлов никогда не адресуются по пути — только по хэшу чанка. Это даёт три свойства, важных для чекпойнтов размером 10–500 ГБ:
- Дедупликация. При дообучении, меняющем только слои со слитым LoRA, повторно загружаются лишь изменившиеся чанки.
- Докачка. Передача — это набор независимых запросов чанков; обрыв соединения стоит не больше одного чанка.
- Проверяемость. Клиент сверяет каждый чанк с его хэшем перед записью, а затем весь файл — с манифестом.
Аутентификация
Каждый запрос передаёт токен рабочего пространства в заголовке Authorization. Токены создаются в разделе Настройки → Токены доступа и имеют область действия: токены read позволяют только скачивать, токены write — также создавать загрузки и коммиты. В корпоративных пространствах токены выдаются только пользователям, вошедшим через SSO (SAML 2.0 / OIDC).
export TB_ENDPOINT="https://<your-workspace-host>" # см. Настройки → API
export TB_TOKEN="tb_live_…" # не коммитьте это значение
Модель хранения
Файлы режутся на чанки фиксированного размера 64 МиБ (67 108 864 байта); последний чанк файла обычно короче. Чанк идентифицируется SHA-256 своих байтов в шестнадцатеричной записи в нижнем регистре.
{
"repo": "tb-labs/RuVision-7B-Instruct",
"revision": "a91f3c0d7e1b4f62",
"chunk_size": 67108864,
"files": [
{
"path": "model-00001-of-00003.safetensors",
"size": 4983451184,
"sha256": "0f6c2d…e91a",
"chunks": ["5b9e04…71cd", "c013aa…0b42", "…"] // 75 хэшей
}
]
}
Загрузка
- Разделите каждый файл на чанки по 64 МиБ и вычислите SHA-256 каждого чанка.
POST /api/v1/repos/{repo}/uploadsс путём файла, размером и упорядоченным списком хэшей. В ответе —upload_idи подмножество хэшей, которых на сервере ещё нет (missing).- Для каждого отсутствующего хэша выполните
PUT /media/chunks/{sha256}с сырыми байтами чанка и заголовкомX-TB-Upload-Id. Сервер хэширует тело и отклоняет его с400 chunk_digest_mismatch, если хэш не совпадает с URL. Запросы независимы и могут выполняться параллельно (до 16 на токен). POST /api/v1/repos/{repo}/uploads/{upload_id}/commit. Сервер проверяет наличие всех чанков и создаёт новую ревизию. Сессия загрузки истекает после 24 часов бездействия; загруженные чанки хранятся 7 дней и переиспользуются следующей сессией.
PUT для уже существующего чанка возвращает 200 OK, не дочитывая тело до конца, поэтому повтор всей загрузки «вслепую» обходится дёшево.
Скачивание
- Получите манифест ветки, тега или хэша ревизии:
GET /api/v1/repos/{repo}/manifest?revision=main. - Заранее выделите целевой файл размером
sizeбайт. - Для каждого чанка i выполните
GET /media/chunks/{sha256}и запишите тело по смещениюi × chunk_size. Ответы содержатETag: "<sha256>"иCache-Control: immutableи поддерживаютRangeдля докачки частично полученного чанка (206 Partial Content). - Проверяйте каждый чанк по хэшу перед записью, а в конце — весь файл по
sha256.
Большие ответы могут отдаваться через 307 Temporary Redirect на ближайший узел хранения с подписанным URL, действующим 15 минут. Клиент обязан следовать редиректам и не должен передавать заголовок Authorization на адрес редиректа.
Чтение метаданных тензоров без скачивания весов
Файл .safetensors начинается с 8-байтового целого N в little-endian, за которым следует JSON-заголовок длиной N байт с именами тензоров, типами, формами и смещениями. Поскольку заголовок лежит в первом чанке, для просмотра 30-гигабайтного чекпойнта достаточно двух Range-запросов:
FIRST=$(curl -s -H "Authorization: Bearer $TB_TOKEN" \
"$TB_ENDPOINT/api/v1/repos/tb-labs/RuVision-7B-Instruct/manifest" | jq -r '.files[0].chunks[0]')
N=$(curl -s -L -r 0-7 -H "Authorization: Bearer $TB_TOKEN" "$TB_ENDPOINT/media/chunks/$FIRST" \
| od -An -t u8 --endian=little | tr -d ' ')
curl -s -L -r 8-$((8 + N - 1)) -H "Authorization: Bearer $TB_TOKEN" \
"$TB_ENDPOINT/media/chunks/$FIRST" | jq 'del(."__metadata__") | to_entries[:3]'
Пример на Python
Фрагмент ниже использует только requests и стандартную библиотеку. Это эталонная реализация протокола; в продакшене используйте tensorbase-cli — в нём есть повторы с экспоненциальной задержкой, индикатор прогресса и локальный кэш чанков.
import hashlib
import os
from concurrent.futures import ThreadPoolExecutor
import requests
ENDPOINT = os.environ["TB_ENDPOINT"]
CHUNK_SIZE = 64 * 1024 * 1024
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['TB_TOKEN']}"
def read_chunk(path, index):
with open(path, "rb") as f:
f.seek(index * CHUNK_SIZE)
return f.read(CHUNK_SIZE)
def chunk_digests(path):
digests = []
with open(path, "rb") as f:
while block := f.read(CHUNK_SIZE):
digests.append(hashlib.sha256(block).hexdigest())
return digests
def upload(path, repo, target_path):
digests = chunk_digests(path)
r = session.post(f"{ENDPOINT}/api/v1/repos/{repo}/uploads", json={
"path": target_path,
"size": os.path.getsize(path),
"chunks": digests,
})
r.raise_for_status()
plan = r.json()
missing = set(plan["missing"])
# первый индекс каждого отсутствующего хэша (одинаковые чанки загружаются один раз)
todo = {d: i for i, d in reversed(list(enumerate(digests))) if d in missing}
def put(item):
digest, index = item
resp = session.put(
f"{ENDPOINT}/media/chunks/{digest}",
data=read_chunk(path, index),
headers={"Content-Type": "application/octet-stream",
"X-TB-Upload-Id": plan["upload_id"]},
timeout=300,
)
resp.raise_for_status()
with ThreadPoolExecutor(max_workers=8) as pool:
list(pool.map(put, todo.items()))
commit = session.post(f"{ENDPOINT}/api/v1/repos/{repo}/uploads/{plan['upload_id']}/commit")
commit.raise_for_status()
return commit.json()["revision"]
def download(repo, file_path, dest, revision="main"):
r = session.get(f"{ENDPOINT}/api/v1/repos/{repo}/manifest", params={"revision": revision})
r.raise_for_status()
manifest = r.json()
entry = next(f for f in manifest["files"] if f["path"] == file_path)
size = manifest["chunk_size"]
with open(dest, "ab") as f: # выделяем место, сохраняя уже скачанное
f.truncate(entry["size"])
def fetch(item):
index, digest = item
with open(dest, "r+b") as f:
f.seek(index * size)
if hashlib.sha256(f.read(size)).hexdigest() == digest:
return # уже скачан: докачка
resp = session.get(f"{ENDPOINT}/media/chunks/{digest}", timeout=300)
resp.raise_for_status()
if hashlib.sha256(resp.content).hexdigest() != digest:
raise IOError(f"chunk {index}: digest mismatch")
with open(dest, "r+b") as f:
f.seek(index * size)
f.write(resp.content)
with ThreadPoolExecutor(max_workers=8) as pool:
list(pool.map(fetch, enumerate(entry["chunks"])))
h = hashlib.sha256()
with open(dest, "rb") as f:
while block := f.read(CHUNK_SIZE):
h.update(block)
if h.hexdigest() != entry["sha256"]:
raise IOError(f"{file_path}: file digest mismatch")
if __name__ == "__main__":
download("tb-labs/RuVision-7B-Instruct",
"model-00001-of-00003.safetensors",
"model-00001-of-00003.safetensors")
Пример на curl
Полезно для отладки и в окружениях без Python. $SHA — хэш одного чанка, $UPLOAD_ID берётся из ответа на шаг 2 раздела Загрузка.
# 1. Есть ли этот чанк на сервере? (200 — да, 404 — нужно загрузить)
curl -I -H "Authorization: Bearer $TB_TOKEN" "$TB_ENDPOINT/media/chunks/$SHA"
# 2. Загрузка чанка №0 файла (байты 0 … 64 МиБ − 1)
dd if=model-00001-of-00003.safetensors bs=64M skip=0 count=1 status=none \
| curl --fail -X PUT --data-binary @- \
-H "Authorization: Bearer $TB_TOKEN" \
-H "Content-Type: application/octet-stream" \
-H "X-TB-Upload-Id: $UPLOAD_ID" \
"$TB_ENDPOINT/media/chunks/$SHA"
# 3. Скачивание чанка; -C - докачивает прерванную передачу через Range
curl --fail -L -C - -o "chunks/$SHA" \
-H "Authorization: Bearer $TB_TOKEN" \
"$TB_ENDPOINT/media/chunks/$SHA"
# 4. Проверка
echo "$SHA chunks/$SHA" | sha256sum -c -
Лимиты
| Параметр | Значение |
|---|---|
| Размер чанка | 64 MiB (фиксированный; последний чанк может быть короче) |
| Максимальный размер файла | 5 TiB (81 920 чанков) |
| Параллельные запросы чанков | 16 на токен, 64 на пространство (Business: 256) |
| Время жизни сессии загрузки | 24 ч бездействия |
| Срок действия подписанного URL | 15 мин |
| Частота запросов к API | 600 запросов/мин на токен (передача чанков не учитывается) |
Ошибки
Ошибки возвращаются в JSON: {"error": "chunk_not_found", "message": "…", "request_id": "…"}. При обращении в поддержку указывайте request_id.
| Статус | Код | Значение |
|---|---|---|
400 | chunk_digest_mismatch | Хэш тела не совпадает с хэшем в URL. Перечитайте чанк с диска и повторите. |
401 | invalid_token | Токен отсутствует, истёк или отозван. |
403 | insufficient_scope | Токен read использован для записи или репозиторий вне области действия токена. |
404 | chunk_not_found | Чанк не хранится (или не виден этому пространству). |
409 | upload_expired | Сессия загрузки истекла. Начните новую — уже загруженные чанки будут переиспользованы. |
413 | chunk_too_large | Тело больше 64 МиБ. |
416 | range_not_satisfiable | Запрошенный диапазон выходит за пределы чанка. |
429 | rate_limited | Слишком много параллельных запросов. Подождите Retry-After секунд. |
507 | quota_exceeded | Достигнута квота хранилища пространства. См. тарифы и дополнительные услуги. |
Что-то непонятно или не хватает информации?
Написать в поддержку