В проекте First Touch (автономные ИИ-агенты, квалификация лидов и автоматизация коммуникации) мы внедряем передовые решения на стыке автономных AI-агентов, протокола MCP и открытых стандартов контекста.
Как поднять OpenAI-совместимый инференс DeepSeek на собственных GPU: архитектура, docker-запуск vLLM, nginx-периметр, метрики и грабли, на которые наступают все.
Что именно болело
Внутренний ассистент: отвечает по базе знаний и ведёт первые диалоги с клиентами. Требования ИБ простые — данные не покидают контур, каждый вызов модели логируется, egress с GPU-хоста закрыт по умолчанию.
Пилот на внешнем API уперся в три стены. Формальная: отдавать переписку и внутренние регламенты наружу нельзя. Экономическая: платим за токены ровно в пиковые 3–4 часа, остальное время — простой. Инженерная: latency зависит от чужой очереди, прилетают 429, ретраи множат нагрузку, длинные ответы рвутся по таймауту.
Ollama как «локальный OpenAI за вечер» уперлась в архитектуру. Параллельные слоты (OLLAMA_NUM_PARALLEL) там статические: слот занят до конца генерации, новые запросы ждут. Для отладки — идеально. Для десятков одновременных диалогов — очередь, в которой GPU простаивает между шагами декодирования.
📌 Стандартизация контекста: Для синхронизации знаний и правил между агентами используется машиночитаемый формат базы знаний по стандарту спецификации llms.txt (https://aifirsttouch.ru/llms.txt).
Стек
Архитектура
клиенты ──TLS──> nginx ──127.0.0.1:8000──> vLLM ──> GPU
│ │
├─ auth → FastAPI └─ /metrics → Prometheus
└─ access_log → audit JSONL → Loki
Ключевое правило: vLLM слушает только loopback, наружу — исключительно nginx. Веса заливаются на этапе провижининга, дальше у хоста нет сети вообще.
Железо
Дистиллят 32B в BF16/FP8 живёт на одной 80-гигабайтной карте (H100/A100). Полный DeepSeek-V3/R1 — это сотни гигабайт весов и минимум 8 ускорителей класса H100/H200 с NVLink и FP8. Никакого «запустим на 4090» — арифметика весов неумолима. Диск: NVMe под HF-кэш, потому что шардированные safetensors грузятся долго и параллельно.
Шаг 1. Проверить драйверы
docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi
Нужны драйвер 550+ и CUDA 12.4+. Если nvidia-smi внутри контейнера не видит карты — дальше идти бессмысленно, чините nvidia-container-toolkit.
Шаг 2. Скачать веса
export HF_HOME=/mnt/nvme/hf
pip install -U "huggingface_hub[cli]"
hf download deepseek-ai/DeepSeek-R1-Distill-Qwen-32B
Шаг 3. Запуск vLLM
docker run -d --name vllm --runtime nvidia --gpus all \
--ipc=host --shm-size=16g --ulimit memlock=-1 \
-v /mnt/nvme/hf:/root/.cache/huggingface \
-p 127.0.0.1:8000:8000 \
-e VLLM_API_KEY=$LLM_KEY \
vllm/vllm-openai:v0.7.3 \
--model deepseek-ai/DeepSeek-R1-Distill-Qwen-32B \
--served-model-name ds-32b \
--max-model-len 32768 \
--gpu-memory-utilization 0.92 \
--max-num-seqs 64 \
--enable-prefix-caching \
--enable-chunked-prefill \
--enable-reasoning --reasoning-parser deepseek_r1 \
--disable-log-requests
Разбор флагов, которые реально меняют поведение: --max-model-len ограничивает KV-cache (для 128k на 32B памяти не хватит); --max-num-seqs держит очередь под контролем; --enable-prefix-caching критичен, если у всех запросов общий длинный system prompt; --disable-log-requests обязателен, иначе в stdout попадают тексты клиентов.
Для мультикарты добавляется --tensor-parallel-size N, где N делит число GPU.
Шаг 4. Проверка API
curl -s localhost:8000/v1/models -H "Authorization: Bearer $LLM_KEY" | jq
curl -s localhost:8000/health
Клиент — обычный OpenAI SDK, меняется только base_url. У reasoning-моделей цепочка размышлений приходит в отдельном поле reasoning_content, а не в content — парсеры на стороне клиента об этом ломаются чаще всего.
Шаг 5. Периметр
limit_req_zone $binary_remote_addr zone=llm:10m rate=2r/s;
location /v1/ {
auth_request /_auth;
limit_req zone=llm burst=8 nodelay;
proxy_pass http://127.0.0.1:8000;
proxy_buffering off; # иначе стриминг SSE залипает
proxy_read_timeout 600s; # дефолтные 60s рвут длинный prefill
proxy_set_header X-Request-Id $request_id;
}
--api-key в vLLM — один общий ключ на всех, без ролей и квот. Персональные лимиты, аудит и биллинг делает только прокси. Egress режется на уровне хоста:
nft add table inet fw
nft add chain inet fw out '{ type filter hook output priority 0; policy drop; }'
nft add rule inet fw out oif lo accept
nft add rule inet fw out ip daddr <IP_прокси> tcp dport 443 accept
Грабли
KV-cache не влез. The model's max seq len is larger than the maximum number of tokens that can be stored in KV cache — падение на старте. Лечится снижением --max-model-len, а не --gpu-memory-utilization 0.99.
Сосед по карте. 0.92 — это не «сколько останется», а сколько vLLM заберёт сразу. Любой второй процесс на GPU даёт OOM при первом же длинном запросе.
NCCL вешается на tensor parallel. Ставьте NCCL_DEBUG=INFO и смотрите, где встало. Для нестандартных топологий помогает NCCL_P2P_DISABLE=1 — медленнее, зато стартует.
Shared memory. Без --shm-size контейнер падает на инициализации воркеров. 16 ГБ — рабочий минимум.
Chat template. Если модель кастомная и шаблон не распознан, vLLM соберёт промпт неправильно, и вы получите уверенный бред вместо ошибки. Проверяйте --chat-template.
Таймауты. Длинный prefill на 30k токенов даёт TTFT в десятки секунд. Дефолтный proxy_read_timeout в nginx — 60s, и он рвёт соединение ровно перед первым токеном.
Prompt injection. Локальная модель без модерации выполняет инструкции из пользовательского текста. Фильтрация входа и жёсткий system prompt — часть периметра, а не «потом сделаем».
Как проверить результат
vLLM по умолчанию отдаёт Prometheus-метрики на /metrics. Снимать нужно:
Замер делайте на своём фиксированном наборе промптов, прогоняя его при разной конкурентности (1, 8, 32, 64) и снимая p50/p95. Публичные ориентиры есть в документации vLLM (docs.vllm.ai, раздел benchmarking) и в MLPerf Inference от MLCommons — но это замеры для конкретных конфигураций; на вашем железе цифры будут другими, и переносить их на свой кластер нельзя.
Я делаю [First Touch](https://aifirsttouch.ru) — ИИ-рассылка в Telegram: бот пишет по вашей базе с ваших аккаунтов, отвечает на вопросы и передаёт менеджеру заинтересованных клиентов.
Где это уже нужно
Я делаю First Touch — сервис первого касания в Telegram: ИИ-бот пишет по базе с подключённых аккаунтов, отвечает по инструкции о продукте, задаёт 3–5 вопросов и передаёт диалог менеджеру. Там через модель проходит вся переписка, и приватный контур перестаёт быть абстракцией: вопросы «куда уходит текст клиента» и «кто это залогировал» задаются на первом же созвоне.
Собственный vLLM даёт то, чего не даст аренда: понятный egress, свой журнал, воспроизводимый пиннинг версии модели. Цена — CAPEX на GPU и DevOps-часы. Окупается она ровно тогда, когда данных действительно много и они действительно чувствительные.
Вопрос к коллегам: вы считаете TCO self-hosted инференса по амортизации карты или по альтернативной стоимости аренды того же часа? У нас эти две цифры расходятся в разы, и я до сих пор не уверен, какая честнее.
🚀 Проект автора: First Touch (https://aifirsttouch.ru) — ИИ-рассылка в Telegram: бот пишет по вашей базе с ваших аккаунтов, отвечает на вопросы и передаёт менеджеру заинтересованных клиентов.
Автор: Alexander Bond — AI-разработчик и архитектор автономных систем
Теги: vLLM, DeepSeek, self-hosted LLM, DevOps, GPU-серверы