Перейти к содержимому

Перенос моделей Hugging Face в локальное S3 для запуска vLLM в закрытом контуре ​

Инструкция описывает загрузку модели с Hugging Face на машину с доступом в Интернет, перенос полного снапшота модели в закрытый контур и загрузку его в локальное S3-совместимое хранилище. После этого vLLM запускается внутри закрытого контура и получает модель из локального S3 либо из предварительно загруженного каталога на PVC.

Когда это пригодится ​

  • В закрытом контуре без доступа к сети Интернет необходимо запустить инференс модели через vLLM
  • Требуется контролируемая поставка моделей: фиксация версии, контроль целостности, исключение обращений к внешним ресурсам
  • Требуется развернуть модель в Kubernetes-кластере с GPU-нодами, где модель хранится в локальном S3

Основной сценарий:

text
                 INTERNET
                    │
                    ▼
          ┌───────────────────┐
          │ Internet Host     │
          │ Hugging Face      │
          │ huggingface_hub   │
          └─────────┬─────────┘
                    │ download
                    ▼
          ┌───────────────────┐
          │ Model Package     │
          │ config.json       │
          │ tokenizer.*       │
          │ *.safetensors     │
          │ generation_config │
          └─────────┬─────────┘
                    │ SHA256
                    ▼
          ┌───────────────────┐
          │ Transfer Package  │
          └─────────┬─────────┘
                    │ USB / SSD / контролируемый канал
                    ▼
          ┌───────────────────┐
          │ Air-gapped Host   │
          └─────────┬─────────┘
                    │ upload
                    ▼
          ┌───────────────────┐
          │ Local S3          │
          │ s3://models/...   │
          └─────────┬─────────┘
                    ▼
          ┌───────────────────┐
          │ Kubernetes / vLLM │
          │ model from S3     │
          └───────────────────┘

Обозначения в документе ​

ПлейсхолдерЗначение
<COMMIT-SHA>SHA-1 коммита модели в репозитории Hugging Face; фиксируется на шаге 1 и используется как immutable-префикс в S3
s3.example.localАдрес S3-совместимого хранилища в закрытом контуре
registry.example.localАдрес локального container registry в закрытом контуре
Qwen/Qwen3-8BПример репозитория модели; заменяйте на вашу модель
vllm.example.localАдрес сервиса vLLM для проверки API

Предварительные требования ​

Internet-хост (машина с доступом к Hugging Face):

  • Python 3
  • Пакет huggingface_hub (CLI hf)
  • Токен Hugging Face для gated/private-моделей (для публичных моделей не требуется)
  • Опционально: GPU для полной проверки модели через vLLM до переноса

Закрытый контур:

  • S3-совместимое хранилище: endpoint, bucket, credentials
  • Kubernetes-кластер с GPU-нодами (см. GPU-оператор Metax)
  • vLLM-образ в локальном registry
  • StorageClass и PVC для хранения модели (см. Longhorn и Velero — контекст S3-хранилищ)
  • Достаточный объём VRAM и дискового пространства под модель

Быстрый путь: минимальный вариант для разовой поставки ​

Если требуется максимально простой сценарий без полных проверок — выполните следующее. Детальный сценарий с фиксацией commit SHA, inventory, контрольными суммами и промышленной схемой описан в шагах 1–5.

На Internet-хосте:

bash
hf download \
    Qwen/Qwen3-8B \
    --revision <COMMIT-SHA> \
    --local-dir ./Qwen3-8B

Проверьте структуру:

bash
find Qwen3-8B -type f | sort

Создайте контрольные суммы:

bash
find Qwen3-8B \
    -type f \
    -exec sha256sum {} \; \
> SHA256SUMS

Заархивируйте и посчитайте SHA-256 архива:

bash
tar -cf Qwen3-8B.tar Qwen3-8B
sha256sum Qwen3-8B.tar > Qwen3-8B.tar.sha256

Перенесите архив на USB.

В закрытом контуре:

bash
sha256sum -c Qwen3-8B.tar.sha256
bash
tar -xf Qwen3-8B.tar

Загрузите в S3:

bash
aws s3 sync \
    Qwen3-8B/ \
    s3://models/qwen/Qwen3-8B/<COMMIT-SHA>/ \
    --endpoint-url https://s3.example.local

Проверьте содержимое:

bash
aws s3 ls \
    s3://models/qwen/Qwen3-8B/<COMMIT-SHA>/ \
    --recursive \
    --endpoint-url https://s3.example.local

После этого модель можно использовать для vLLM deployment.

Ограничения быстрого пути

В быстром пути не фиксируются commit SHA модели, inventory и контрольные суммы файлов до переноса, не проверяется повторно целостность после загрузки в S3. Для воспроизводимой и проверяемой поставки используйте шаги 1–5.

Шаг 1. Скачивание снапшота модели на Internet-хосте ​

1.1. Подготовка Internet-хоста ​

Проверьте Python и установите huggingface_hub:

bash
python3 --version
bash
python3 -m pip install -U huggingface_hub

Проверьте CLI. В современных версиях huggingface_hub основным CLI является hf:

bash
huggingface-cli --help
bash
hf --help

1.2. Авторизация в Hugging Face ​

Для публичной модели авторизация может не потребоваться. Для gated/private-моделей необходим токен:

bash
hf auth login

Введите Hugging Face Access Token. Проверьте авторизацию:

bash
hf auth whoami

1.3. Создание рабочего каталога ​

bash
export WORKDIR=/opt/model-transfer
bash
mkdir -p "$WORKDIR"/{models,manifests,checksums,logs,scripts}

Результат:

text
/opt/model-transfer/
├── models/
├── manifests/
├── checksums/
├── logs/
└── scripts/

1.4. Фиксация модели: repository, revision, commit SHA ​

Не используйте только имя репозитория (Qwen/Qwen3-8B) как идентификатор поставки — ветка main может измениться. Фиксируйте три параметра:

ПараметрЗначение
RepositoryQwen/Qwen3-8B
Revisionmain
Commit SHA<COMMIT-SHA>

Commit SHA однозначно идентифицирует состояние модели.

1.5. Получение информации о модели ​

Посмотрите, какие файлы будут скачаны, и оцените размер:

bash
hf download Qwen/Qwen3-8B --dry-run

Для конкретного revision:

bash
hf download \
    Qwen/Qwen3-8B \
    --revision <COMMIT-SHA> \
    --dry-run

1.6. Скачивание снапшота целиком ​

Скачивайте снапшот целиком, а не только веса

vLLM требуется как минимум config.json + веса + tokenizer. В зависимости от модели могут понадобиться generation_config.json, tokenizer_config.json, special_tokens_map.json, chat_template.jinja, индекс весов (model.safetensors.index.json) и custom-код модели. Правильная единица поставки — весь снапшот (Model Release), а не отдельные *.safetensors.

bash
hf download \
    Qwen/Qwen3-8B \
    --revision <COMMIT-SHA> \
    --local-dir "$WORKDIR/models/Qwen3-8B"

Используйте --local-dir, а не стандартный cache ~/.cache/huggingface/: модель сразу лежит в самостоятельном каталоге, который можно проверить, заархивировать, посчитать checksum и перенести.

Почему нельзя скачать только *.safetensors

Распространённая ошибка — «мне нужны веса модели, скачаю *.safetensors». Для vLLM этого недостаточно: без config.json и tokenizer модель не загрузится. Правильная единица поставки — Model Release, а не model.safetensors.

1.7. Проверка снапшота на Internet-хосте ​

Проверьте структуру и размер:

bash
find "$WORKDIR/models/Qwen3-8B" -type f | sort
bash
du -sh "$WORKDIR/models/Qwen3-8B"

Пример вывода:

text
42G    /opt/model-transfer/models/Qwen3-8B

Проверьте обязательные файлы — конфигурацию, токенизатор и веса:

bash
test -f "$WORKDIR/models/Qwen3-8B/config.json"
bash
find "$WORKDIR/models/Qwen3-8B" -maxdepth 1 -type f | grep tokenizer
bash
find "$WORKDIR/models/Qwen3-8B" \
    -type f \
    \( -name '*.safetensors' -o -name '*.bin' -o -name '*.pt' \)

Посмотрите содержимое config.json — особенно поля architectures, model_type, torch_dtype, hidden_size, num_hidden_layers, num_attention_heads:

bash
jq '{ architectures, model_type, torch_dtype, hidden_size, num_hidden_layers, num_attention_heads }' \
    "$WORKDIR/models/Qwen3-8B/config.json"

Убедитесь, что снапшот корректно загружается через Transformers. Установите зависимости:

bash
python3 -m pip install transformers torch

Выполните тест офлайн-загрузки:

python
from transformers import AutoTokenizer, AutoConfig

path = "/opt/model-transfer/models/Qwen3-8B"

config = AutoConfig.from_pretrained(
    path,
    local_files_only=True
)

tokenizer = AutoTokenizer.from_pretrained(
    path,
    local_files_only=True
)

print(config)
print(tokenizer)

local_files_only=True

Параметр local_files_only=True гарантирует, что библиотека не попытается скачать недостающие файлы из Интернета. Используйте его во всех офлайн-проверках.

Если на Internet-хосте есть GPU, выполните полный тест vLLM до переноса:

bash
vllm serve \
    /opt/model-transfer/models/Qwen3-8B \
    --served-model-name qwen3-8b

Зафиксируйте версии окружения (vLLM, CUDA, NVIDIA driver, PyTorch, архитектура модели и GPU) — они понадобятся для environment manifest.

Если Internet Host не имеет GPU

Это не проблема: можно проверить tokenizer, config и файлы модели офлайн. Полный тест vLLM выполните уже в закрытом контуре.

python
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained(
    "/opt/model-transfer/models/Qwen3-8B",
    local_files_only=True
)

print("Tokenizer OK")

1.8. Особенности отдельных моделей ​

  • Квантованные модели. Если модель квантована (AWQ, GPTQ, GGUF, FP8, INT8), явно отразите это в manifest: Qwen3-8B и Qwen3-8B-AWQ — разные артефакты, нельзя считать их одной моделью.
  • Custom-код. Некоторые модели требуют дополнительных Python-файлов в снапшоте (modeling_xxx.py, configuration_xxx.py, tokenization_xxx.py). Эти файлы должны быть перенесены вместе со снапшотом.

trust_remote_code

Флаг --trust-remote-code означает выполнение кода, поставляемого вместе с моделью. В защищённом контуре включайте его только для заранее проверенных и утверждённых моделей.

  • Python-зависимости. Custom-код может требовать пакеты (einops, sentencepiece, protobuf, tiktoken и др.). В air-gapped среде они тоже должны быть доступны — при необходимости перенесите их в локальный PyPI/package repository.
  • Токенизатор. Проверьте наличие файлов токенизатора — в зависимости от модели это tokenizer.json, tokenizer.model, vocab.json, merges.txt, special_tokens_map.json, tokenizer_config.json:
bash
find "$WORKDIR/models/Qwen3-8B" -maxdepth 1 -type f | grep -Ei 'token|vocab|merges|spiece'
  • Chat template. Для instruct/chat-моделей важен chat_template.jinja (или chat template внутри tokenizer_config.json). Без него модель может технически загрузиться, но поведение chat API будет отличаться от ожидаемого. Chat template — часть Model Release.

Шаг 2. Фиксация версии и подготовка transfer package ​

2.1. Получение commit SHA ​

Зафиксируйте commit SHA модели через Python:

python
from huggingface_hub import model_info

info = model_info(
    "Qwen/Qwen3-8B"
)

print(info.sha)

Или для конкретной revision:

python
from huggingface_hub import model_info

info = model_info(
    "Qwen/Qwen3-8B",
    revision="main"
)

print(info.sha)

Сохраните результат в manifests/model-revision.txt:

text
repository=Qwen/Qwen3-8B
revision=main
commit=<COMMIT-SHA>

2.2. Inventory и контрольные суммы ​

Создайте inventory всех файлов модели:

bash
find \
    "$WORKDIR/models/Qwen3-8B" \
    -type f \
    -printf '%P\n' \
    | sort \
> "$WORKDIR/manifests/model-files.txt"

Посчитайте SHA-256 каждого файла:

bash
cd "$WORKDIR/models/Qwen3-8B"
find . \
    -type f \
    -exec sha256sum {} \; \
> "$WORKDIR/checksums/model-SHA256SUMS"

Проверьте контрольные суммы:

bash
cd "$WORKDIR/models/Qwen3-8B"
sha256sum -c "$WORKDIR/checksums/model-SHA256SUMS"

Все файлы должны вернуть OK.

Сохраните размер и количество файлов:

bash
du -sh "$WORKDIR/models/Qwen3-8B" > "$WORKDIR/manifests/model-size.txt"
bash
find "$WORKDIR/models/Qwen3-8B" -type f | wc -l > "$WORKDIR/manifests/model-file-count.txt"

2.3. Model manifest ​

Пример manifests/model.yaml:

yaml
model:
  name: Qwen3-8B
  repository: Qwen/Qwen3-8B
  revision: main
  commit: "<COMMIT-SHA>"

files:
  count: 42
  size_bytes: 123456789012

format:
  weights: safetensors

source:
  type: huggingface
  url: https://huggingface.co/Qwen/Qwen3-8B

target:
  storage: s3
  bucket: models
  prefix: qwen/Qwen3-8B/<COMMIT-SHA>/

2.4. Сборка transfer package ​

Рекомендуемая структура пакета:

text
Qwen3-8B-<COMMIT-SHA>/
├── README.md
├── manifest.yaml
├── model/
│   ├── config.json
│   ├── generation_config.json
│   ├── tokenizer.json
│   ├── tokenizer_config.json
│   ├── model-00001-of-00004.safetensors
│   └── ...
├── manifests/
│   ├── model-files.txt
│   ├── model-revision.txt
│   ├── model-size.txt
│   └── environment.txt
└── checksums/
    └── model-SHA256SUMS

Соберите пакет:

bash
mkdir -p "$WORKDIR/package"
cp -r "$WORKDIR/models/Qwen3-8B" "$WORKDIR/package/model"
cp -r "$WORKDIR/manifests" "$WORKDIR/package/"
cp -r "$WORKDIR/checksums" "$WORKDIR/package/"

Положите в пакет README.txt с информацией о модели:

text
Model: Qwen3-8B
Hugging Face repository: Qwen/Qwen3-8B
Revision: main
Commit: <COMMIT-SHA>
Download date: 2026-08-31
Weight format: safetensors
Total size: XX GB
Files: XX
SHA256: checksums/model-SHA256SUMS

2.5. Создание архива и его SHA-256 ​

bash
tar \
    -czf \
    Qwen3-8B-<COMMIT-SHA>-airgap.tar.gz \
    -C "$WORKDIR/package" \
    model \
    manifests \
    checksums

gzip для больших моделей

Сжатие safetensors обычно даёт небольшой выигрыш — веса уже не являются хорошо сжимаемыми текстовыми данными. Для моделей на десятки-сотни гигабайт используйте обычный tar без сжатия:

bash
tar \
    -cf \
    Qwen3-8B-<COMMIT-SHA>-airgap.tar \
    -C "$WORKDIR/package" \
    model \
    manifests \
    checksums

Можно переносить каталог и без архива, если транспорт это позволяет.

Создайте SHA-256 архива:

bash
sha256sum \
    Qwen3-8B-<COMMIT-SHA>-airgap.tar \
> \
    Qwen3-8B-<COMMIT-SHA>-airgap.tar.sha256

2.6. Перенос через USB ​

bash
cp Qwen3-8B-<COMMIT-SHA>-airgap.tar /media/usb/
cp Qwen3-8B-<COMMIT-SHA>-airgap.tar.sha256 /media/usb/
sync
umount /media/usb

Шаг 3. Проверка и распаковка в закрытом контуре ​

3.1. Проверка SHA-256 архива ​

После подключения USB в закрытом контуре:

bash
sha256sum -c Qwen3-8B-<COMMIT-SHA>-airgap.tar.sha256

Ожидаемый результат:

text
Qwen3-8B-<COMMIT-SHA>-airgap.tar: OK

НЕ ПРОДОЛЖАТЬ ИМПОРТ

Если checksum не совпадает — не продолжайте импорт. Повторите перенос или разберитесь с причиной повреждения до загрузки в S3.

3.2. Распаковка ​

bash
mkdir -p /opt/model-import
tar -xf Qwen3-8B-<COMMIT-SHA>-airgap.tar -C /opt/model-import

Результат:

text
/opt/model-import/
├── model/
├── manifests/
└── checksums/

3.3. Повторная проверка checksum файлов ​

bash
cd /opt/model-import/model
sha256sum -c ../checksums/model-SHA256SUMS

Все файлы должны пройти проверку.

Шаг 4. Загрузка модели в локальное S3 ​

4.1. Подготовка AWS CLI и credentials ​

Пример параметров:

  • Endpoint: https://s3.example.local
  • Bucket: models

Проверьте наличие AWS CLI:

bash
aws --version

Настройте credentials:

bash
aws configure

Укажите:

text
AWS Access Key ID:
AWS Secret Access Key:
Default region:

Для S3-совместимого хранилища endpoint задаётся отдельно через --endpoint-url.

4.2. Проверка доступа к S3 ​

bash
aws s3 \
    --endpoint-url https://s3.example.local \
    ls

Проверьте bucket:

bash
aws s3 \
    --endpoint-url https://s3.example.local \
    ls \
    s3://models/

4.3. Если используется внутренний CA ​

Если S3 использует сертификат внутреннего CA — не отключайте проверку TLS.

Не используйте --no-verify-ssl в production

Флаг --no-verify-ssl отключает проверку TLS. В production вместо него установите внутренний CA:

bash
export AWS_CA_BUNDLE=/etc/pki/ca-trust/source/anchors/internal-ca.crt

(или соответствующий путь для конкретной ОС). После этого проверьте доступ:

bash
aws s3 \
    --endpoint-url https://s3.example.local \
    ls

4.4. Выбор immutable prefix ​

Рекомендуемый префикс:

text
s3://models/qwen/Qwen3-8B/<COMMIT-SHA>/

Не перезаписывайте существующую модель

Не загружайте новую версию поверх s3://models/qwen/Qwen3-8B/. Используйте immutable prefix <COMMIT-SHA> — это позволяет хранить несколько релизов модели (Model Release 1/2/3) и выполнять rollback.

Почему в S3 лучше использовать commit SHA

Хранение только по имени модели (s3://models/qwen/Qwen3-8B/) не позволяет отличить версии. Префикс с commit SHA — неизменяемый артефакт. Для нескольких моделей структура выглядит так:

text
s3://models/
│
├── qwen/
│   ├── Qwen3-8B/
│   │   ├── <COMMIT-1>/
│   │   └── <COMMIT-2>/
│   │
│   └── Qwen3-32B/
│       └── <COMMIT-1>/
│
├── meta-llama/
│   └── Llama-3.3-70B-Instruct/
│       └── <COMMIT>/
│
└── mistralai/
    └── Mistral-Small/
        └── <COMMIT>/

При необходимости можно вести логический alias current:

text
qwen/Qwen3-8B/
├── abc123/
├── def456/
└── current/   # current → def456

current — логический alias на утверждённую версию, а не единственное хранилище модели.

4.5. Загрузка модели в S3 ​

bash
aws s3 sync \
    /opt/model-import/model/ \
    s3://models/qwen/Qwen3-8B/<COMMIT-SHA>/ \
    --endpoint-url https://s3.example.local

AWS CLI рекурсивно загрузит весь каталог.

4.6. Проверка содержимого S3 ​

bash
aws s3 ls \
    s3://models/qwen/Qwen3-8B/<COMMIT-SHA>/ \
    --recursive \
    --endpoint-url https://s3.example.local

Должны присутствовать config.json, tokenizer.json, tokenizer_config.json, *.safetensors и остальные файлы модели.

Проверьте, что количество файлов совпадает. Локально:

bash
find /opt/model-import/model -type f | wc -l

В S3:

bash
aws s3 ls \
    s3://models/qwen/Qwen3-8B/<COMMIT-SHA>/ \
    --recursive \
    --endpoint-url https://s3.example.local \
    | wc -l

Количество должно совпадать.

Проверьте размер. Локально:

bash
du -sb /opt/model-import/model

В S3:

bash
aws s3 ls \
    s3://models/qwen/Qwen3-8B/<COMMIT-SHA>/ \
    --recursive \
    --summarize \
    --human-readable \
    --endpoint-url https://s3.example.local

Сравните общий размер.

4.7. Строгая проверка: повторная SHA-256 из S3 ​

Количество файлов и размер — недостаточная проверка: содержимое могло измениться. Скачайте модель из S3 во временный каталог и сверьте SHA-256:

bash
mkdir -p /tmp/model-verification
aws s3 sync \
    s3://models/qwen/Qwen3-8B/<COMMIT-SHA>/ \
    /tmp/model-verification/ \
    --endpoint-url https://s3.example.local
bash
cd /tmp/model-verification
sha256sum -c /opt/model-import/checksums/model-SHA256SUMS

Если все файлы вернули OK — модель успешно перенесена в S3.

ETag ≠ MD5

Не используйте ETag == MD5 как универсальную проверку целостности: для multipart-загрузки ETag обычно не является простым MD5 всего файла. Источник истины — собственные SHA-256 из checksums/model-SHA256SUMS.

Шаг 5. Подготовка PVC и запуск vLLM ​

5.1. Рекомендуемая схема: initContainer + PVC ​

Для production Kubernetes модель загружается из S3 в PVC через initContainer:

text
Local S3
   │ aws s3 sync
   ▼
initContainer
   │
   ▼
PVC /models
   │
   ▼
vLLM

S3 используется как master storage, PVC — как локальный cache. Это удобно для моделей размером 10–500 ГБ.

Не используйте emptyDir для больших моделей

При использовании emptyDir модель исчезает при пересоздании Pod — при каждом рестарте пришлось бы скачивать модель заново:

text
Pod restart
    ↓
emptyDir lost
    ↓
100 GB download
    ↓
vLLM start

Используйте PVC как cache: S3 → PVC → vLLM.

Альтернатива: скачать модель на PVC заранее

Можно один раз вручную скопировать модель из S3 на PVC (без initContainer). Этот вариант проще, но обновление модели требует ручных операций.

5.2. Credentials и права доступа ​

Не прописывайте AWS_ACCESS_KEY_ID и AWS_SECRET_ACCESS_KEY непосредственно в Deployment. Создайте Secret:

bash
kubectl create secret generic s3-model-credentials \
    --from-literal=AWS_ACCESS_KEY_ID='...' \
    --from-literal=AWS_SECRET_ACCESS_KEY='...' \
    -n inference

Отдельный пользователь S3

Для vLLM/model downloader не используйте административный S3-аккаунт. Создайте отдельного пользователя vllm-model-reader с правами только s3:GetObject и s3:ListBucket на models/qwen/Qwen3-8B/*. Запретите PutObject и DeleteObject.

Модель — immutable-артефакт: после утверждения s3://models/qwen/Qwen3-8B/<COMMIT-SHA>/ vLLM имеет доступ только на чтение (READ, не WRITE). Это разделяет процессы загрузки (Model ingestion) и обслуживания (Model serving) модели.

5.3. Манифест Deployment ​

5.4. Запуск vLLM ​

После того как модель появилась на PVC (/models/Qwen3-8B), vLLM запускается обычным образом:

bash
vllm serve \
    /models/Qwen3-8B \
    --served-model-name qwen3-8b

В Kubernetes аргументы задаются в манифесте Deployment (см. шаг 5.3).

5.5. Офлайн-режим и сетевая изоляция ​

vLLM должен запускаться без доступа к huggingface.co, github.com, pypi.org, docker.io. Если модель полностью локальная, vllm serve /models/Qwen3-8B не должен обращаться к Hugging Face.

HF_HUB_OFFLINE=1

Переменная HF_HUB_OFFLINE=1 запрещает Hugging Face Hub client обращаться в Интернет за отсутствующими файлами. Задайте её в контейнере:

yaml
env:
  - name: HF_HUB_OFFLINE
    value: "1"

Это особенно полезно для тестирования air-gapped-развёртывания.

Не полагайтесь только на переменные окружения: ограничьте сетевой доступ namespace/Pod через NetworkPolicy (см. шаг 5.3, промышленный вариант).

Промышленный вариант: рекомендуемая архитектура ​

Для production рекомендуемая архитектура:

text
                 INTERNET
                    │
                    ▼
          ┌──────────────────┐
          │ Model Collector  │  huggingface_hub
          └────────┬─────────┘
                   ▼
          ┌──────────────────┐
          │ Model Validator  │  commit SHA, SHA256, inventory, tokenizer, config
          └────────┬─────────┘
                   ▼
          ┌──────────────────┐
          │ Transfer Package │
          └────────┬─────────┘
                USB
                   ▼
          ┌──────────────────┐
          │ Airgap Importer  │
          └────────┬─────────┘
                   ▼
          ┌──────────────────┐
          │ Local S3         │  model releases
          └────────┬─────────┘
            read-only access
                   ▼
          ┌──────────────────┐
          │ Model Downloader │  initContainer
          └────────┬─────────┘
                   ▼
          ┌──────────────────┐
          │ PersistentVolume │
          └────────┬─────────┘
                   ▼
          ┌──────────────────┐
          │ vLLM (GPU)       │
          └────────┬─────────┘
                   ▼
          OpenAI-compatible API

Главная идея: Hugging Face — внешний источник модели, S3 — внутренний master storage, PVC — локальный cache для inference, а vLLM не должен знать о существовании Интернета.

Единица поставки — Model Release, а не отдельный файл весов:

text
Qwen3-8B Model Release
├── Hugging Face repository
├── Commit SHA
├── Model configuration
├── Tokenizer
├── Chat template
├── Weight files
├── Weight index
├── Generation configuration
├── Custom code
├── Python dependencies
├── File inventory
├── SHA256
└── Validation information

После импорта модель живёт в S3 с immutable prefix:

text
s3://models/
└── qwen/
    └── Qwen3-8B/
        └── <COMMIT-SHA>/
            ├── config.json
            ├── tokenizer.json
            ├── tokenizer_config.json
            ├── model-00001-of-00004.safetensors
            ├── ...
            └── model.safetensors.index.json

Сохраните также environment manifest — сведения об окружении, на котором модель проверялась:

text
Python: 3.x
huggingface_hub: x.y.z
transformers: x.y.z
torch: x.y.z
vLLM: x.y.z
CUDA: 12.x
NVIDIA Driver: xxx.xx

Это позволит через год ответить на вопрос: «На каком окружении эта модель была проверена?»

Проверка результата ​

После загрузки модели в S3 и запуска Deployment выполните итоговые проверки.

  1. Целостность архива и файлов — шаги 3.1 и 3.3: sha256sum -c для архива и для файлов модели.
  2. Повторная SHA-256 из S3 — шаг 4.7: скачайте модель из S3 и сверьте контрольные суммы.
  3. Запуск vLLM:
bash
vllm serve /models/Qwen3-8B --served-model-name qwen3-8b
  1. Проверка API vLLM. Список моделей:
bash
curl http://vllm.example.local/v1/models

Ожидаемый ответ:

json
{
  "data": [
    {
      "id": "qwen3-8b"
    }
  ]
}

Тестовый запрос:

bash
curl http://vllm.example.local/v1/chat/completions \
    -H 'Content-Type: application/json' \
    -d '{
      "model": "qwen3-8b",
      "messages": [
        {
          "role": "user",
          "content": "Hello"
        }
      ]
    }'
  1. Тест air-gap: запустите vLLM, убедитесь, что модель загружается, полностью запретите Интернет, перезапустите Pod и убедитесь, что vLLM снова успешно стартует. Если это работает — deployment действительно независим от Интернета.
text
Internet OFF
      ▼
Pod restart
      ▼
S3 local
      ▼
Model
      ▼
vLLM READY

Частые ошибки ​

СимптомВероятная причинаРешение
vLLM не загружает модель, ошибка на стартеПеренесены только веса, отсутствует tokenizer или config.jsonСкачивайте снапшот целиком (шаг 1.6), проверяйте обязательные файлы (шаг 1.7)
vLLM обращается в Интернет при стартеНе задан HF_HUB_OFFLINE, нет NetworkPolicyЗадайте HF_HUB_OFFLINE=1 и ограничьте сеть (шаг 5.5)
В S3 новая версия модели перезаписала старуюНарушение immutable: загрузка без префикса <COMMIT-SHA>Всегда используйте immutable prefix (шаг 4.4)
Проверка целостности по ETag даёт неверный результатДля multipart-upload ETag ≠ MD5 всего файлаИспользуйте собственные SHA-256 (шаг 4.7)
initContainer не скачивает модельНеверные credentials, endpoint или права пользователя S3Проверьте Secret, --endpoint-url, права s3:GetObject/s3:ListBucket (шаг 5.2)
Модель скачивается заново при каждом рестарте PodИспользуется emptyDir вместо PVCИспользуйте PVC как cache (шаг 5.1)
Не воспроизводимый образ initContainerИспользуется latest-тегЗафиксируйте версию или digest образа (шаг 5.3)
Модель с custom code не запускается в закрытом контуреНе перенесены Python-зависимостиПеренесите зависимости в локальный PyPI (шаг 1.8)

Приложения ​

Контрольный чек-лист перед переносом ​

Контрольный чек-лист после переноса ​

Команда Боцман