vLLM, Ray Serve, TGI, On-prem ВМ + Kubernetes + cloud, команды, конфиги и процессы
// 01
В 2026 году стек устоялся. vLLM — производственный стандарт для high-throughput inference. TGI — для экосистемы HuggingFace. Ray — оркестрация multi-node и multi-model. Выбор определяется не предпочтениями, а требованиями к нагрузке.
| Инструмент | Для чего | Сильные стороны | Ограничения | Когда брать |
|---|---|---|---|---|
| vLLM | High-throughput inference, production API, multi-GPU | PagedAttention, continuous batching, OpenAI-compatible API, tensor/pipeline parallel из коробки | Только NVIDIA CUDA (+ AMD ROCm beta). Не для CPU | Default |
| TGI (HF) | HuggingFace ecosystem, streaming, production с HF моделями | Глубокая интеграция с HF Hub, Flash Attention, широкий список моделей | Чуть ниже throughput чем vLLM. Менее гибкий параллелизм | HF ecosystem |
| Ray Serve | Оркестрация multi-node, multi-model, routing, autoscaling | Fault tolerance, autoscaling, pipeline между моделями, cloud-agnostic | Overhead на простые single-node деплои. Сложнее в настройке | Multi-node / Multi-model |
| SGLang | Structured generation, сложные agent pipelines | Оптимизация для multi-step generation, RadixAttention для KV reuse | Менее зрелая экосистема. Требует специфичной настройки | Agent / Chain |
| Ollama / llama.cpp | Dev-среда, CPU inference, прототипы | CPU offload через GGUF, простой запуск, Apple Silicon | Не production throughput. Нет distributed serving | Dev только |
Правило 2026: один GPU на одной ноде → standalone vLLM. Несколько GPU на одной ноде → vLLM с tensor parallelism. Несколько нод → vLLM + Ray. Несколько моделей или сложные пайплайны → Ray Serve как оркестратор над vLLM backends.
vLLM напрямую или через Docker. Ray для multi-node. NFS/Object storage для моделей. Фиксированный baseline.
vLLM в Pod с GPU resource limits. KubeRay для Ray cluster. KEDA для autoscaling по queue depth. NVIDIA GPU Operator.
Spot GPU ВМ для burst. Ray head on-prem, workers в cloud. Hybrid: модель в on-prem, overflow в cloud.
Ray cluster: head node on-prem, worker pool: on-prem ВМ + K8s pods + cloud ВМ. Единый API endpoint.
// 02
Самый частый вопрос: сколько GPU нужно? Ответ: параметры модели × байт на параметр + KV cache + overhead. KV cache может превышать размер весов при большом batch или длинном контексте.
# VRAM = weights + KV cache + activations + framework overhead # Weights = params_B * bytes_per_param # KV cache = 2 * num_layers * num_heads * head_dim * seq_len * batch * bytes # Быстрый расчёт весов модели: # FP16/BF16 → params_B * 2 GB # INT8 → params_B * 1 GB # INT4/AWQ → params_B * 0.5 GB # FP8 → params_B * 1 GB (лучше INT8 по качеству) model_sizes = { "7B-FP16": 14, # GB "7B-INT4": 4, # GB (AWQ/GPTQ) "13B-FP16": 26, "13B-INT4": 7, "70B-FP16": 140, "70B-INT4": 35, # влезает на 2×A100 40GB "405B-FP8": 405, # требует 6-8 нод с A100 80GB } # Правило: добавить 30-50% к весам для KV cache при production нагрузке # gpu_memory_utilization=0.90 — безопасный baseline для H100 # gpu_memory_utilization=0.95 — допустимо на B200/H200
| Модель | Precision | Min GPU config | Рекомендуемо |
|---|---|---|---|
| Llama-3.1-8B | FP16 | 1× A100 40GB | 1× A100 80GB |
| Llama-3.1-8B | INT4 AWQ | 1× RTX 4090 24GB | 1× A100 40GB |
| Llama-3.1-70B | FP16 | 4× A100 80GB (TP=4) | 8× A100 80GB |
| Llama-3.1-70B | INT4 AWQ | 2× A100 40GB (TP=2) | 2× A100 80GB |
| Mistral-7B | FP16 | 1× A100 40GB | 1× A100 80GB |
| Qwen2.5-32B | FP16 | 2× A100 80GB | 4× A100 80GB |
| Llama-3.1-405B | FP8 | 8× A100 80GB (TP=8,PP=2) | 16× A100 80GB |
| DeepSeek-R1-70B | INT4 | 2× A100 40GB | 4× A100 80GB |
GPU-native, почти нет потери качества vs FP16. Нативная поддержка в vLLM. Лучший выбор для Hopper+ архитектур.
95% качества FP16 при 4× меньшем VRAM. Отличная поддержка в vLLM (Marlin kernels: 10× speedup над baseline AWQ). Лучший GPU INT4.
Чуть ниже качества AWQ, но огромное количество готовых моделей. С Marlin kernels в vLLM — 2.6× speedup. Допустимо для production.
Только для dev-сред или CPU offload сценариев. 92% качества FP16. Единственный вариант для CPU inference. Не для production GPU.
Ключевой принцип квантизации: большая модель при INT4 почти всегда лучше меньшей модели при FP16. Llama-3.1-70B INT4 (35GB) значительно превосходит Llama-3.1-7B FP16 (14GB) при похожем VRAM footprint.
// 03
Три вида параллелизма решают разные задачи. Неправильный выбор стоит throughput.
Делит матрицы весов между GPU. Требует высокоскоростного interconnect (NVLink). Для GPU в одной ноде — первый выбор. tensor_parallel_size = число GPU в ноде.
Делит модель по слоям между нодами. Допустима медленная сеть (Ethernet). Для multi-node. pipeline_parallel_size = число нод.
Несколько полных копий модели обрабатывают разные запросы. Для throughput при условии что модель влезает. Классический horizontal scale.
Модель влезает на 1 GPU? └── Да → Data Parallelism (несколько реплик vLLM) Нет → Модель влезает на 1 ноду с N GPU? └── Да → Tensor Parallelism (TP=N) └── GPU с NVLink? → Предпочесть TP └── GPU без NVLink (L40S)? → Предпочесть PP Нет → Multi-node: TP=GPU_per_node, PP=num_nodes └── Для MoE моделей (Mixtral/DeepSeek): TP + Expert Parallelism (EP) # Золотое правило vLLM 2025+: # TP размер должен быть степенью 2 и делить attention heads без остатка # Total GPUs = tensor_parallel_size × pipeline_parallel_size # Пример: 2 ноды по 4 GPU = 8 GPU total vllm serve model \ --tensor-parallel-size 4 \ # TP в пределах ноды --pipeline-parallel-size 2 \ # PP между нодами --distributed-executor-backend ray
GPU без NVLink (например L40S, RTX серия) — используйте Pipeline Parallelism вместо Tensor Parallelism между нодами. Tensor Parallelism требует high-bandwidth interconnect: без NVLink коммуникационные расходы съедают всю выгоду.
// 04
Самый прямолинейный путь. Минимум зависимостей, максимум контроля. Хорошо подходит как постоянная база для больших моделей.
# 1. Prerequisites pip install vllm # CUDA 12.1+ required # 2. Запуск — 4 GPU, модель 70B INT4 vllm serve meta-llama/Llama-3.1-70B-Instruct \ --tensor-parallel-size 4 \ --quantization awq_marlin \ --gpu-memory-utilization 0.90 \ --max-model-len 32768 \ --enable-prefix-caching \ --enable-chunked-prefill \ --max-num-seqs 256 \ --port 8000 \ --host 0.0.0.0 \ --api-key $VLLM_API_KEY # 3. Проверка curl http://localhost:8000/v1/models \ -H "Authorization: Bearer $VLLM_API_KEY" # 4. Тест генерации curl http://localhost:8000/v1/chat/completions \ -H "Authorization: Bearer $VLLM_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"meta-llama/Llama-3.1-70B-Instruct", "messages":[{"role":"user","content":"Hello"}], "max_tokens":100}'
# === НОДА 1 (head) — запустить первой === pip install vllm ray[default] # Запуск Ray head ray start --head \ --port 6379 \ --dashboard-host 0.0.0.0 # === НОДА 2 (worker) — подключиться к head === ray start \ --address 'NODE1_IP:6379' \ --block # === НА HEAD НОДЕ: запуск vLLM === vllm serve meta-llama/Llama-3.1-405B-Instruct-FP8 \ --tensor-parallel-size 4 \ # GPU на ноду --pipeline-parallel-size 2 \ # число нод --distributed-executor-backend ray \ --gpu-memory-utilization 0.90 \ --enable-prefix-caching \ --port 8000 # Проверить Ray cluster: ray status
services: vllm: image: vllm/vllm-openai:v0.8.5 # Пинить конкретный тег! runtime: nvidia environment: NVIDIA_VISIBLE_DEVICES: all HUGGING_FACE_HUB_TOKEN: ${HF_TOKEN} VLLM_API_KEY: ${VLLM_API_KEY} volumes: - /mnt/models:/models:ro # NFS или local path - vllm-cache:/root/.cache command: > --model /models/llama-3.1-70b-instruct-awq --tensor-parallel-size 4 --quantization awq_marlin --gpu-memory-utilization 0.90 --max-model-len 32768 --enable-prefix-caching --served-model-name llama-70b ports: - "8000:8000" healthcheck: test: ["CMD","curl","-f","http://localhost:8000/health"] interval: 30s timeout: 10s start_period: 120s # Модели загружаются долго retries: 3 restart: unless-stopped deploy: resources: reservations: devices: - driver: nvidia count: all capabilities: [gpu] volumes: vllm-cache:
// 05
K8s добавляет autoscaling, health management и multi-replica управление. Основная сложность — GPU scheduling и KV-cache-aware autoscaling. С 2026 года Gateway API Inference Extension (GA) добавляет model-aware routing из коробки.
# Установка NVIDIA GPU Operator (обязателен для GPU scheduling) helm repo add nvidia https://helm.ngc.nvidia.com/nvidia helm repo update helm install gpu-operator nvidia/gpu-operator \ --namespace gpu-operator \ --create-namespace \ --set driver.enabled=false \ # Если драйвер уже установлен --set toolkit.enabled=true # Проверить GPU resource kubectl get nodes -o json | jq '.items[].status.capacity | select(."nvidia.com/gpu")'
apiVersion: apps/v1 kind: Deployment metadata: name: vllm-llama70b namespace: inference spec: replicas: 2 selector: matchLabels: app: vllm-llama70b template: metadata: labels: app: vllm-llama70b model: llama-3.1-70b spec: nodeSelector: nvidia.com/gpu.product: A100-SXM4-80GB topologySpreadConstraints: - maxSkew: 1 topologyKey: "kubernetes.io/hostname" whenUnsatisfiable: DoNotSchedule containers: - name: vllm image: vllm/vllm-openai:v0.8.5 args: - --model=/models/llama-3.1-70b-awq - --tensor-parallel-size=4 - --quantization=awq_marlin - --gpu-memory-utilization=0.90 - --max-model-len=32768 - --enable-prefix-caching - --port=8000 env: - name: VLLM_API_KEY valueFrom: secretKeyRef: name: vllm-secrets key: api-key resources: requests: nvidia.com/gpu: "4" memory: 64Gi cpu: "8" limits: nvidia.com/gpu: "4" memory: 128Gi cpu: "16" volumeMounts: - name: model-storage mountPath: /models readOnly: true readinessProbe: httpGet: {path: /health, port: 8000} initialDelaySeconds: 120 periodSeconds: 10 livenessProbe: httpGet: {path: /health, port: 8000} initialDelaySeconds: 180 periodSeconds: 30 failureThreshold: 3 volumes: - name: model-storage persistentVolumeClaim: claimName: model-pvc
apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: vllm-scaler namespace: inference spec: scaleTargetRef: name: vllm-llama70b minReplicaCount: 1 maxReplicaCount: 4 triggers: - type: prometheus metadata: serverAddress: http://prometheus:9090 # Scale по queue depth (in-flight requests) query: avg(vllm:num_requests_waiting) threshold: "10" # >10 waiting → добавить реплику - type: prometheus metadata: serverAddress: http://prometheus:9090 # Также scale по KV cache utilization query: avg(vllm:gpu_cache_usage_perc) * 100 threshold: "80" # >80% KV cache → scale up
// 06
Ray — оркестратор для вашего кейса: on-prem ВМ + Kubernetes + cloud ВМ как единый кластер. Head node on-prem, worker pool гетерогенный.
# ray up ray_hybrid_cluster.yaml cluster_name: hybrid-inference provider: type: local # Head на on-prem ВМ head_ip: 192.168.1.10 head_node_type: node_config: {} resources: CPU: 32 GPU: 4 available_node_types: # On-prem ВМ workers onprem_gpu_worker: min_workers: 1 max_workers: 3 resources: CPU: 32 GPU: 4 node_config: NodeType: onprem # Cloud burst workers (spot) cloud_gpu_worker: min_workers: 0 # Scale to zero когда не нужно max_workers: 5 resources: CPU: 48 GPU: 8 node_config: InstanceType: p4d.24xlarge # 8× A100 40GB SpotInstances: true setup_commands: - pip install vllm ray[default] - pip install ray[serve]
import ray from ray import serve from vllm.engine.arg_utils import AsyncEngineArgs from vllm.engine.async_llm_engine import AsyncLLMEngine ray.init("ray://head-node-ip:10001") @serve.deployment( num_replicas=2, ray_actor_options={ "num_gpus": 4, "resources": {"NodeType": 0.001} # Предпочесть on-prem } ) class LLMDeployment: def __init__(self, model_id: str, tp_size: int): args = AsyncEngineArgs( model=model_id, tensor_parallel_size=tp_size, gpu_memory_utilization=0.90, enable_prefix_caching=True, quantization="awq_marlin", ) self.engine = AsyncLLMEngine.from_engine_args(args) async def generate(self, prompt: str, params: dict): # OpenAI-compatible generation results = [] async for output in self.engine.generate(prompt, ...): results.append(output) return results # Роутер: направлять запросы по model_name @serve.deployment class ModelRouter: def __init__(self): self.models = { "llama-70b": LLMDeployment.bind("llama-70b-awq", 4), "llama-8b": LLMDeployment.bind("llama-8b", 1), } async def __call__(self, request): model_name = request.json()["model"] return await self.models[model_name].remote(request) serve.run(ModelRouter.bind())
// 07
PagedAttention — фундаментальная инновация vLLM: делит KV cache на страницы как виртуальная память ОС. Устраняет 60–80% фрагментации памяти. Prefix caching переиспользует KV cache для повторяющихся системных промптов.
--enable-prefix-caching--enable-chunked-prefill (снижает TTFT для длинных промптов)--gpu-memory-utilization 0.90 — безопасный базовый уровень для A100; 0.95 для H100/H200vllm:gpu_cache_usage_percВ отличие от статического батчинга, continuous batching добавляет новые запросы в batch как только завершаются старые. Это единственно правильная стратегия для production LLM inference.
--max-num-seqs 256 — максимум одновременных последовательностей--max-num-batched-tokens 32768 — регулировать под GPU memoryvllm:num_requests_waiting — если растёт, нужно больше реплик или снизить max-model-lenМаленькая draft-модель генерирует несколько токенов, большая верифицирует. Снижает TTFT и inter-token latency без потери качества при правильной настройке.
--speculative-model, --num-speculative-tokens 5При нескольких нодах модели должны быть доступны на каждой. Варианты: NFS mount, pre-pull на каждую ноду, object storage (S3/MinIO).
--model /local/pathНе expose vLLM напрямую. Нужен слой маршрутизации, rate limiting, auth, и routing между репликами.
// 08
vLLM экспортирует Prometheus метрики из коробки. Мониторить не CPU/RAM, а LLM-специфичные метрики: TTFT, inter-token latency, KV cache, queue depth.
| Метрика Prometheus | Норма | Тревога | Что значит |
|---|---|---|---|
| vllm:time_to_first_token_seconds | P99 < 2s | P99 > 5s — перегрузка | Время до первого токена. Главная latency метрика. |
| vllm:time_per_output_token_seconds | P99 < 100ms | > 200ms — KV cache или batch переполнен | Inter-token latency. Критично для streaming UX. |
| vllm:num_requests_waiting | < 5 | > 20 стабильно — нужно масштабировать | Запросы в очереди. Главный сигнал для autoscaling. |
| vllm:gpu_cache_usage_perc | 60–85% | > 90% → preemption → latency spikes | Использование KV cache. При overflow начинаются preemption. |
| vllm:num_requests_running | Зависит от config | = max_num_seqs постоянно | Активные запросы. Если всегда на максимуме — бутылочное горло. |
| vllm:request_success_total | Error rate < 0.1% | > 1% ошибок | Успешные vs failed запросы. |
| nvidia_smi_utilization_gpu | 70–95% | < 50% при нагрузке — неэффективно | GPU utilization. Должен быть высоким при нагрузке. |
scrape_configs: - job_name: vllm scrape_interval: 15s static_configs: - targets: ['vllm-service:8000'] # vLLM metrics on /metrics # Ключевые PromQL запросы для дашборда: # TTFT P99 histogram_quantile(0.99, rate(vllm:time_to_first_token_seconds_bucket[5m])) # Requests waiting (autoscaling trigger) avg(vllm:num_requests_waiting) # KV cache utilization avg(vllm:gpu_cache_usage_perc) * 100 # Throughput (tokens/sec) sum(rate(vllm:generation_tokens_total[1m]))
🔴 Critical — немедленное действие
🟡 Warning — разобраться в течение часа
✅ Что мониторить как норму
📊 Business метрики
// 09
Выбор модели под бизнес-задачи. Определение SLA по latency. Бюджет GPU (on-prem vs cloud). Приоритизация optimization vs новых фич.
Настройка vLLM/Ray. Выбор квантизации. Конфигурация parallelism. Production деплой, мониторинг, тюнинг параметров.
K8s конфигурация. GPU Operator. KEDA autoscaling. CI/CD для model updates. Network / storage для моделей.
Engineer: поднять vLLM на одной ноде, протестировать модель, настроить мониторинг. Manager: зафиксировать SLA требования (TTFT P99, throughput target). Выбрать квантизацию и сделать benchmark.
Добавить Ray для multi-node если нужно. Настроить K8s деплой если инфра готова. KEDA autoscaling по queue depth. Нагрузочное тестирование.
Оптимизация gpu_memory_utilization, max-num-seqs. Prefix caching эффективность. KV cache sizing. Runbook для инцидентов.
Алерты на critical метрики: TTFT > threshold, OOM events, requests waiting > N. Никакого ручного труда в штатном режиме.
Просмотр throughput trend, P99 TTFT за неделю, KV cache utilization. Обновление vLLM если есть security/bug fixes. Проверка cloud GPU costs (spot ВМ).
Cost per token анализ. Нужна ли другая модель или другая квантизация. GPU capacity plan на следующий месяц. Есть ли смысл переходить на более новое железо.
--enable-prefix-caching --enable-chunked-prefill — это всегда включать:latestterminationGracePeriodSeconds: 300 (5 мин для завершения активных генераций)// 10
Самый быстрый path to production: одна ВМ → vLLM + Docker → Prometheus → нагрузочный тест → зафиксировать метрики. Только после этого добавлять Ray, K8s, autoscaling. Сложность должна следовать за реальной нагрузкой, а не предшествовать ей.