Продукты
VK Cloud

Как запускать LLM в Kubernetes: GPU, инференс, масштабирование и мониторинг

28 октября 2026 г.
_blog_head_178.png

Запустить LLM локально через ollama run llama3 — пять минут, и модель отвечает в терминале. Развернуть ту же модель в production Kubernetes с GPU, авторасштабированием и SLO по latency — совсем другая задача. Модель сама по себе — лишь часть истории. Вокруг неё нужна инфраструктура: GPU-узлы с правильными драйверами, инференс-сервер с батчингом запросов, маршрутизация нагрузки, мониторинг TTFT и throughput, стратегия масштабирования под переменную и не всегда предсказуемую нагрузку.

Инференс LLM в Kubernetes — это как раз про разрыв между демо-запуском и промышленной эксплуатацией. Production-развёртывание LLM в Kubernetes строится из пяти слоёв: GPU-инфраструктура, инференс-сервер, оркестрация модели и весов, масштабирование под нагрузку и наблюдаемость, то есть мониторинг метрик, специфичных именно для генеративных моделей, а не для типового веб-сервиса. Поднять один под с моделью можно за те же пять минут, что и ollama run, но выдержит ли сервис реальную нагрузку с приемлемым latency, определяет проработка каждого из этих слоёв.

Дальше разберём каждый слой по порядку: как устроена архитектура целиком, что делает GPU Operator и как устроены GPU-узлы, как выбрать инференс-сервер между vLLM, Triton и TGI, как масштабировать GPU-нагрузку с KEDA и на какие метрики смотреть дежурному инженеру, когда что-то идёт не так.

Архитектура production LLM-сервиса в Kubernetes

Независимо от конкретных инструментов, инференс LLM в Kubernetes укладывается в одни и те же пять слоёв.

Пять слоёв стека

Если смотреть на архитектуру сверху вниз, запрос проходит через пять уровней.

API Gateway / Router принимает входящий трафик: маршрутизация запросов, аутентификация, rate limiting, балансировка нагрузки между репликами инференс-сервера.

Inference Server — vLLM, Triton или TGI — принимает запрос, управляет батчингом и обращается к загруженной модели.

Model Storage хранит веса модели: в объектном хранилище S3 или в PersistentVolume, откуда они загружаются при старте пода.

GPU Worker Nodes — узлы кластера с GPU, подготовка и обслуживание которых автоматизирована через GPU Operator.

Observability собирает метрики, специфичные для LLM: TTFT, throughput, утилизация GPU, длина очереди ожидающих запросов.

Каждый слой решает свою задачу, и проблема на любом из них снижает качество обслуживания целиком: быстрый GPU с медленным роутером перед ним или идеальный мониторинг без масштабирования одинаково не дают приемлемого SLO.

Специфика LLM vs обычный ML-сервис

LLM-инференс принципиально отличается от классического ML-сервиса на Scikit-learn или XGBoost по четырём параметрам.

Размер модели измеряется гигабайтами VRAM, не мегабайтами: 7 миллиардов параметров в формате fp16 занимают около 14 ГБ видеопамяти, модель на 70 миллиардов параметров — порядка 140 ГБ, то есть несколько карт одновременно.

Генерация текста stateful и авторегрессивна: каждый следующий токен зависит от всех предыдущих, и для этого модель хранит промежуточное состояние в KV-cache, которое растёт с длиной диалога.

Длина вывода переменная и непредсказуема заранее: запрос на короткий ответ и запрос на развёрнутое объяснение занимают GPU совершенно разное время, и это нельзя оценить до начала генерации.

Батчинг запросов сложнее, чем в классическом ML: нельзя просто сложить несколько запросов в один батч без учёта того, что они находятся на разных этапах генерации и имеют разную длину уже сгенерированного текста.

Из этих четырёх отличий вытекает и разница в экономике сервиса. Классический ML-сервис на CPU масштабируется дёшево и почти линейно: добавили реплику, получили кратно больше RPS. Инференс LLM на GPU устроен иначе: сама карта стоит заметно дороже обычного вычислительного узла, а "простаивающая" карта всё равно тратит бюджет, даже если на ней выполняется один запрос из десяти возможных. Поэтому вопросы батчинга, утилизации GPU и масштабирования для LLM — это не только вопросы производительности, но и вопросы прямой экономики эксплуатации сервиса.

GPU-инфраструктура: GPU Operator и node groups

Нижний слой стека — физические GPU-узлы, и именно здесь чаще всего возникают операционные проблемы, если инфраструктуру настраивали вручную.

Что делает GPU Operator

GPU Operator автоматизирует всё, что нужно для работы видеокарт в кластере Kubernetes: установку NVIDIA-драйверов на узлы, установку NVIDIA Container Toolkit, то есть nvidia-docker runtime, настройку Device Plugin, который регистрирует GPU как ресурс nvidia.com/gpu, и настройку DCGM для мониторинга состояния карт.

Разница на практике ощутима сразу. Без GPU Operator каждый новый узел требует ручной установки драйверов и перезагрузки — операция, которую нужно повторять при каждом масштабировании кластера, причём версия драйвера на новом узле должна совпадать с версией на остальных, иначе поды с GPU-ресурсами не запланируются или будут падать с ошибками CUDA на старте. С GPU Operator новый GPU-узел становится готовым к работе автоматически, за несколько минут после присоединения к кластеру, без участия инженера: оператор сам ставит нужную версию драйвера, toolkit и device plugin по единому манифесту для всего кластера.

Отдельно стоит учитывать Node Feature Discovery — компонент, который GPU Operator обычно разворачивает вместе с собой. Он автоматически размечает узлы по обнаруженному оборудованию, и благодаря этим меткам планировщик понимает, какие узлы физически оснащены GPU, ещё до того, как на них попытаются запланировать под с запросом nvidia.com/gpu.

Запрос GPU в спецификации пода выглядит так:

resources: limits: nvidia.com/gpu: 1 # одна карта memory: "32Gi" cpu: "8" requests: nvidia.com/gpu: 1

GPU-группы в Managed Kubernetes

Отдельная группа GPU-узлов в Managed Kubernetes настраивается по той же логике, что и обычная группа узлов, с несколькими важными отличиями. Нужно выбрать конфигурацию GPU-узла, доступную в выбранном регионе — конкретные модели карт стоит уточнять в консоли или у поддержки провайдера на момент создания кластера, поскольку линейка оборудования меняется быстрее, чем обновляется документация. Нужно настроить минимум и максимум узлов для Cluster Autoscaler, чтобы кластер мог и расти под нагрузкой, и сокращаться в простое. И нужно поставить taint на GPU-узлы, чтобы обычные, не требующие GPU поды туда не попадали и не занимали дорогие ресурсы. Отдельного внимания стоит поведение Cluster Autoscaler на масштабировании вниз: по умолчанию он не удаляет узел, на котором есть хотя бы один работающий под, поэтому для GPU-группы стоит явно настраивать период ожидания перед скейл-даном и аккуратно выставлять PodDisruptionBudget для инференс-сервиса — иначе автоскейлер либо не сможет сократить группу, либо сократит её в момент пиковой нагрузки, оставив часть запросов без обработки:

# Toleration для GPU-подов tolerations: - key: "nvidia.com/gpu" operator: "Exists" effect: "NoSchedule" nodeSelector: accelerator: nvidia-gpu

Загрузка весов модели: S3 или PVC

Хранить веса модели можно двумя способами, и выбор зависит от сценария масштабирования.

PersistentVolumeClaim даёт быструю загрузку при старте с локального диска, но привязывает под к конкретному узлу в режиме ReadWriteOnce. Такой вариант подходит, когда число реплик фиксировано и не меняется динамически.

Объектное хранилище S3 не привязывает под к узлу: любая новая реплика загружает модель независимо от того, где её разместил планировщик. Загрузка при старте медленнее и зависит от размера модели и пропускной способности сети, но именно это нужно для автоскейлинга, когда новые поды появляются и исчезают по требованию.

Для кластеров с автоскейлингом веса обычно хранят в S3 и используют init-container для загрузки модели при старте пода. Альтернатива — ReadWriteMany PVC на основе NFS или CephFS, когда все реплики читают из одного общего тома: это снимает повторную загрузку одних и тех же весов, но добавляет зависимость от производительности сетевого хранилища под нагрузкой.

Третий вариант, который стоит знать, — запекать веса модели прямо в образ контейнера. Это увеличивает размер образа на десятки гигабайт и усложняет CI/CD, зато устраняет отдельный шаг загрузки при старте пода: модель готова к работе сразу после того, как контейнер поднялся, что заметно сокращает холодный старт при масштабировании. Такой подход оправдан, когда модель меняется редко и важнее именно скорость запуска новой реплики, а не скорость сборки и доставки образа.

Инференс-серверы: vLLM, Triton, TGI

Выбор инференс-сервера сильнее всего влияет на итоговую производительность: именно этот слой определяет, сколько запросов в секунду выдержит одна и та же видеокарта.

Почему нельзя просто запустить python app.py

Наивный вариант деплоя — обёртка на FastAPI, которая на каждый запрос вызывает model.generate(). На небольшой нагрузке это работает, но у подхода четыре системные проблемы: один запрос блокирует GPU для всех остальных, пока генерация не завершится; нет continuous batching, и GPU простаивает между токенами разных запросов, которые можно было бы считать параллельно; нет управления KV-cache, и каждый новый запрос начинает вычисления с нуля, даже если в сессии уже был контекст; нет PagedAttention, из-за чего видеопамять используется неэффективно и фрагментируется. Production-инференс-сервер решает все четыре проблемы сразу, за счёт архитектуры, спроектированной именно под LLM-нагрузку.

На практике это видно по утилизации GPU: наивный деплой редко прогревает карту выше 20–30%, даже под ощутимым потоком запросов, потому что большую часть времени GPU простаивает между обработкой отдельных запросов, а не считает их параллельно. Production-инференс-сервер при той же входящей нагрузке держит утилизацию в районе 60–80%, потому что continuous batching заполняет освобождающиеся вычислительные слоты новыми запросами, не дожидаясь, пока предыдущие полностью завершатся.

vLLM: оптимальный выбор для большинства задач

vLLM чаще всего выбирают для production-развёртываний LLM в Kubernetes, и у него для этого есть три ключевые технологии. PagedAttention управляет KV-cache по принципу виртуальной памяти операционной системы, снижая фрагментацию VRAM. Continuous Batching добавляет новые запросы в текущий батч по мере освобождения слотов, так что GPU не простаивает в ожидании завершения более длинных генераций. OpenAI-совместимый API позволяет сменить backend инференса без изменения клиентского кода, которое уже умеет работать с форматом запросов OpenAI.

Базовый Deployment с vLLM выглядит так:

apiVersion: apps/v1 kind: Deployment metadata: name: vllm-inference spec: replicas: 1 selector: matchLabels: app: vllm-inference template: metadata: labels: app: vllm-inference spec: tolerations: - key: "nvidia.com/gpu" operator: "Exists" effect: "NoSchedule" containers: - name: vllm image: vllm/vllm-openai:latest args: - "--model" - "/models/llama-3-8b" - "--tensor-parallel-size" - "1" - "--max-model-len" - "8192" - "--served-model-name" - "llama-3-8b" ports: - containerPort: 8000 resources: limits: nvidia.com/gpu: 1 memory: "32Gi" volumeMounts: - name: model-storage mountPath: /models volumes: - name: model-storage persistentVolumeClaim: claimName: llm-models-pvc

Tensor Parallelism: модель на нескольких GPU

Модели от 70 миллиардов параметров и крупнее не помещаются на одну карту уровня A100 80 ГБ. Tensor Parallelism делит матрицы весов модели между несколькими GPU, так что каждая карта хранит и считает только свою часть:

args: - "--tensor-parallel-size" - "4" # 4 GPU в одном поде resources: limits: nvidia.com/gpu: 4

Для tensor parallelism все GPU должны находиться на одном узле: обмен между картами идёт постоянно и интенсивно, и для этого важна высокая пропускная способность связи между ними, например NVLink. Pipeline Parallelism, при котором разные слои модели размещаются на разных узлах, даёт больший масштаб, но требует более сложной настройки и аккуратной балансировки нагрузки между стадиями.

NVIDIA Triton: когда нужна гибкость

Triton Inference Server стоит выбирать в нескольких конкретных случаях: когда из одного сервера нужно обслуживать разные типы моделей — компьютерное зрение, табличные модели, NLP; когда уже есть веса, оптимизированные под ONNX или TensorRT; когда требуется ensemble pipeline из нескольких моделей, выполняемых последовательно в рамках одного запроса. Для задач, ограниченных только LLM, triton inference server не даёт заметного преимущества: vLLM проще настраивается и показывает сопоставимый, а часто и более высокий throughput именно на генеративных моделях. Разумный критерий выбора — состав сервисов в самой платформе: если рядом с LLM уже работает отдельная команда, обслуживающая классические модели через Triton, может быть проще унифицировать весь инференс на одном сервере, даже с потерей части специфичных для LLM оптимизаций, чем поддерживать два разных стека параллельно.

Таблица сравнения инференс-серверов

Параметр vLLM TGI (HuggingFace) Triton
Специализация LLM LLM Любые модели
PagedAttention Да Да (FlashAttention) Нет
OpenAI-совместимый API Да Да Нет, нативный протокол
Tensor Parallelism Да Да Да
LoRA serving Да Частично Нет нативно
Сложность настройки Низкая Низкая Высокая
Throughput на LLM-задачах Высокий Высокий Зависит от бэкенда

Масштабирование: когда и как добавлять GPU

На этом слое сервис либо укладывается в SLO под переменной нагрузкой, либо начинает копить очередь запросов и терять в latency.

Проблема: стандартный HPA не работает для LLM

Horizontal Pod Autoscaler по умолчанию масштабирует по загрузке CPU или RAM. LLM-инференс нагружает GPU, а не CPU: в момент, когда видеокарта работает на пределе, процессор пода может оставаться практически в режиме ожидания. Формальные метрики CPU в этой ситуации покажут, что масштабировать ничего не нужно, хотя очередь запросов на GPU уже растёт. Решение — кастомные метрики через KEDA, Kubernetes Event-Driven Autoscaling, или через Prometheus Adapter, которые умеют масштабировать по любой метрике из Prometheus, а не ограничиваясь CPU и памятью.

KEDA и Prometheus: масштабирование по GPU-метрикам

apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: vllm-scaledobject spec: scaleTargetRef: name: vllm-inference minReplicaCount: 1 maxReplicaCount: 4 triggers: - type: prometheus metadata: serverAddress: http://prometheus:9090 metricName: vllm_requests_waiting threshold: "10" # масштабировать, если в очереди больше 10 запросов query: sum(vllm:num_requests_waiting)

Для масштабирования по GPU-метрикам у KEDA есть ещё два параметра, которые стоит задавать явно, не оставляя на значениях по умолчанию. pollingInterval определяет, как часто KEDA опрашивает Prometheus и принимает решение о масштабировании — слишком большой интервал замедляет реакцию на всплеск нагрузки, слишком маленький создаёт лишнюю нагрузку на сам Prometheus. cooldownPeriod определяет, сколько времени KEDA ждёт после падения метрики ниже порога, прежде чем начать сокращать количество реплик — без него под с загруженной в память моделью может быть удалён и пересоздан слишком быстро, на коротком провале нагрузки, что только увеличивает число холодных стартов.

Для масштабирования LLM-сервиса стоит ориентироваться на три метрики, в порядке приоритета. vllm:num_requests_waiting, то есть длина очереди запросов, реагирует быстрее всего и считается самым надёжным сигналом для принятия решения о масштабировании. DCGM_FI_DEV_GPU_UTIL, утилизация GPU, реагирует с заметной задержкой: карта может уже быть загружена на полную, пока очередь только начинает расти, поэтому ориентироваться только на эту метрику рискованно. Throughput в токенах в секунду, вычисляемый как скорость роста счётчика vllm:generation_tokens_total, хорошо показывает фактическую пропускную способность, но сам по себе не сигнализирует о необходимости масштабирования так явно, как длина очереди. На практике метрики комбинируют: основной триггер строят на длине очереди, а утилизацию GPU и throughput используют как вторичные сигналы для проверки, что добавленные реплики действительно разгрузили систему, а не просто увеличили число простаивающих карт.

Scale-to-zero: экономия на GPU в простое

GPU-узлы — самый дорогой ресурс в кластере, и при неравномерной нагрузке, например в batch-джобах или dev-окружениях, разумно держать минимум группы узлов равным нулю. Когда запросов нет, количество реплик vLLM падает до нуля, и Cluster Autoscaler удаляет ставший ненужным GPU-узел. Когда приходит новый запрос, KEDA поднимает первую реплику, а Cluster Autoscaler добавляет узел под неё. Холодный старт в такой схеме занимает от трёх до пяти минут: время на запуск нового узла плюс время на загрузку весов модели. Для production-сервиса с жёстким SLO по latency scale-to-zero, как правило, неприемлем — пользователь не должен ждать несколько минут первого ответа. Для dev- и staging-окружений экономия на простаивающих GPU-узлах обычно существенно перевешивает это ограничение.

Stale Requests и таймауты

Генерация длинного ответа может занимать десятки секунд, и стандартные таймауты веб-сервисов для LLM-инференса слишком короткие. Таймауты на уровне Ingress или API Gateway стоит увеличивать явно:

# Ingress/Gateway таймауты для LLM nginx.ingress.kubernetes.io/proxy-read-timeout: "300" nginx.ingress.kubernetes.io/proxy-send-timeout: "300"

Более надёжное решение — потоковая передача ответа через SSE или WebSocket: клиент получает токены по мере их генерации, соединение остаётся открытым всё время генерации, и проблема длинных таймаутов снимается архитектурно, а не подбором цифр в конфигурации. При таком подходе промежуточные узлы — балансировщик, Ingress-контроллер, сервис-меш — не воспринимают долгое отсутствие ответа как зависший запрос, поскольку байты от сервера идут непрерывно, просто небольшими порциями, и формально соединение остаётся активным от первого до последнего токена.

CTA: Managed Kubernetes в VK Cloud KZ с GPU-группами узлов: GPU Operator предустановлен, Cluster Autoscaler масштабирует GPU-группу до нуля и обратно по требованию → Создать GPU-кластер → vkcloud.kz/kubernetes/

Мониторинг LLM-инференса

Без наблюдаемости весь остальной стек работает вслепую: понять, что именно просело при жалобе на медленные ответы — сеть, GPU, очередь или сама модель — можно только по метрикам, специфичным для LLM, а не по общим метрикам веб-сервиса.

Ключевые метрики LLM vs обычного сервиса

У LLM-сервиса свой набор метрик, отличный от типового веб-сервиса, и latency вместе с throughput здесь нужно смотреть в комплексе, а не по одной метрике.

Метрика Описание Норма
TTFT (Time to First Token) Время до первого токена в ответе < 500 мс для интерактивных сценариев
TPOT (Time Per Output Token) Время генерации одного токена < 50 мс
E2E Latency Полное время ответа Зависит от длины вывода
Throughput (токенов/сек) Суммарная скорость генерации всего сервера Максимизировать при заданном latency
GPU Utilization Утилизация GPU 70–90% под нагрузкой
KV Cache Hit Rate Доля запросов с переиспользованием кэша Чем выше, тем меньше повторных вычислений
Requests Waiting Длина очереди запросов 0 в норме

vLLM и метрики Prometheus

vLLM экспортирует метрики нативно на эндпоинте /metrics, и для их сбора достаточно обычного ServiceMonitor:

# ServiceMonitor для Prometheus apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: vllm-monitor spec: selector: matchLabels: app: vllm-inference endpoints: - port: http path: /metrics interval: 15s

Ключевые метрики vLLM в Prometheus:

vllm:num_requests_running # активных запросов в обработке vllm:num_requests_waiting # запросов в очереди vllm:gpu_cache_usage_perc # использование KV cache, доля от 0 до 1 vllm:time_to_first_token_seconds_bucket # гистограмма TTFT vllm:generation_tokens_total # счётчик сгенерированных токенов

Throughput отдельной именованной метрикой не экспортируется: его считают как скорость роста счётчика, например запросом rate(vllm:generation_tokens_total[1m]), что даёт токены в секунду за последнюю минуту.

GPU-метрики через DCGM Exporter

NVIDIA DCGM Exporter устанавливается автоматически вместе с GPU Operator и отдаёт метрики физического состояния карт:

DCGM_FI_DEV_GPU_UTIL # утилизация GPU, % DCGM_FI_DEV_MEM_COPY_UTIL # утилизация памяти GPU, % DCGM_FI_DEV_FB_USED # использованная VRAM, МБ DCGM_FI_DEV_POWER_USAGE # потребление мощности, Вт DCGM_FI_DEV_SM_CLOCK # тактовая частота вычислительных блоков

Эти метрики дополняют метрики vLLM: vLLM показывает, что происходит на уровне запросов и очереди, DCGM — что происходит на уровне самого железа, и расхождение между ними часто подсказывает причину проблемы.

Grafana-дашборд: что должно быть на экране дежурного

Первый экран дашборда стоит собирать из трёх панелей. SLO-панель показывает TTFT по перцентилям P50, P95, P99 и долю запросов, укладывающихся в целевые 500 мс. Throughput-панель показывает токены в секунду, количество запросов в секунду и число активных запросов. GPU-панель показывает утилизацию GPU, занятую VRAM и длину очереди запросов.

Минимальный набор алертов: предупреждение при TTFT P95 выше 2 секунд, сигнал на масштабирование при очереди ожидания больше 20 запросов, и отдельный алерт на утилизацию GPU ниже 20% при наличии запросов в очереди — это обычно означает проблему в самом сервисе, а не нехватку вычислительных ресурсов. В виде правила для Prometheus Alertmanager первый из них может выглядеть так:

- alert: VLLMHighTTFT expr: histogram_quantile(0.95, rate(vllm:time_to_first_token_seconds_bucket[5m])) > 2 for: 5m labels: severity: warning annotations: summary: "TTFT P95 выше 2 секунд"

Такое правило реагирует не на единичный выброс, а на устойчивое ухудшение за последние пять минут, что снижает число ложных срабатываний от разовых пиков нагрузки. По такому же принципу стоит оформить и остальные алерты из минимального набора: с условием for, а не только с пороговым значением метрики, — это отделяет кратковременные колебания нагрузки от действительно деградирующего сервиса, на который должен реагировать дежурный инженер.

Типичные проблемы и их решение

Дальше — три проблемы, с которыми почти неизбежно сталкивается каждый, кто разворачивает LLM-сервис в первый раз, и которые проще предусмотреть заранее, чем разбирать на проде ночью.

OOM при загрузке модели

Симптом: под крашится с OOM при старте, видеопамять карты полностью исчерпана ещё до начала обработки запросов.

Разбираться стоит по трём направлениям. Слишком большой --max-model-len увеличивает размер KV-cache сильнее, чем кажется на первый взгляд: уменьшение контекста часто решает проблему без смены оборудования. Модель может быть физически больше VRAM карты — тогда помогает квантизация, например AWQ, GPTQ или fp8, либо добавление ещё одной карты с tensor parallelism. Параметр --gpu-memory-utilization по умолчанию равен 0.9, и в редких случаях стоит попробовать его снизить, например до 0.85, если на карте запущены и другие процессы.

Фактический размер модели в VRAM проверяется напрямую:

nvidia-smi --query-gpu=memory.used,memory.free --format=csv

Высокий TTFT при низкой нагрузке

Причина обычно в маленьком размере батча: при единичных запросах GPU не загружен полностью, и каждый запрос обрабатывается почти так же, как при наивном деплое без батчинга. Решение — параметр --max-num-seqs, максимальное число параллельных последовательностей: его стоит увеличить, если TTFT остаётся высоким при объективно низком throughput, то есть GPU простаивает, а не перегружен. Увеличивать этот параметр стоит постепенно и с проверкой на реальной нагрузке: слишком большое значение приведёт к тому, что при одновременном приходе множества запросов сервер попытается обработать их все сразу, упрётся в лимит VRAM и начнёт либо вытеснять часть последовательностей из обработки, либо падать с ошибкой нехватки памяти.

Несовпадение версий CUDA и драйвера

Симптом: под с инференс-сервером падает сразу при старте с ошибкой уровня CUDA, хотя ресурс nvidia.com/gpu успешно выделен, и планировщик разместил под на GPU-узле без проблем.

Чаще всего причина в том, что образ инференс-сервера собран под одну версию CUDA toolkit, а драйвер на узле, поставленный GPU Operator, — под другую, и минимальная требуемая версия драйвера для конкретного образа не совпадает с фактически установленной. Проверить версии можно прямо на узле:

nvidia-smi

Команда покажет версию драйвера и максимальную поддерживаемую версию CUDA. Решение — либо зафиксировать версию драйвера в конфигурации GPU Operator под требования используемого образа vLLM или Triton, либо выбрать версию образа инференс-сервера, совместимую с уже установленным в кластере драйвером. Смешивать узлы с разными версиями драйвера в одной GPU-группе не стоит: это гарантированно приведёт к тому, что часть подов будет падать непредсказуемо, в зависимости от того, на какой узел их разместил планировщик.

Модель не обновляется без downtime

Стратегия обновления весов без простоя строится в три шага: загрузить новую версию модели в S3 под новым путём, обновить ConfigMap или переменную окружения, указывающую на этот путь, и выполнить rolling update Deployment, при котором новые поды поднимаются с новой версией модели, а старые продолжают обслуживать трафик до завершения перехода.

Проверять откат тоже стоит заранее: если новая версия весов ведёт себя хуже прежней, возврат на старый путь в S3 должен занимать одну правку конфигурации, а не пересборку образа. Для GPU-подов стоит отдельно настроить readinessProbe, который проверяет готовность именно модели, а не только самого процесса инференс-сервера: модель должна быть полностью загружена в VRAM и готова принимать запросы, прежде чем под будет помечен готовым. Без этого Kubernetes может начать направлять трафик на новый под сразу после старта контейнера, пока загрузка модели ещё не завершена, и первые запросы к нему будут обрабатываться с задержкой или падать с ошибкой.

Заключение

Production LLM в Kubernetes — это пять слоёв, у каждого своя сложность: GPU-инфраструктура с Operator и правильно расставленными taint, инференс-сервер с continuous batching и PagedAttention, хранение весов в S3 для автоскейлинга, масштабирование через KEDA по метрике очереди запросов и мониторинг TTFT вместе с утилизацией GPU. Инференс LLM в Kubernetes в большинстве практических случаев закрывается связкой vLLM и Managed Kubernetes с GPU-группами узлов: vLLM решает задачи инференса без лишней сложности настройки, а управляемый кластер снимает операционную нагрузку по обслуживанию самих узлов. Любое production-развёртывание стоит начинать с одной реплики на фиксированном числе GPU-узлов, подключать мониторинг TTFT и очереди запросов до, а не после первого инцидента, и только затем переходить к автоскейлингу через KEDA — в таком порядке проще отличить проблему конфигурации от реальной нехватки ресурсов. Основное инженерное усилие в итоге уходит именно туда, где оно нужнее всего — в настройку инференс-сервера под требования конкретного сервиса по latency и throughput.

Вопросы и ответы

Какой GPU нужен для запуска LLM в Kubernetes?

Зависит от размера модели. 7 миллиардов параметров в формате fp16 требуют около 14 ГБ VRAM — хватает одной карты уровня A100 40 ГБ. 13 миллиардов параметров — около 26 ГБ, нужна карта на 80 ГБ. 70 миллиардов параметров — около 140 ГБ, то есть несколько карт на 80 ГБ одновременно с tensor parallelism. Квантизованные версии, AWQ, GPTQ или fp8, снижают требования к памяти в два-четыре раза при умеренной потере качества генерации.

Что такое vLLM и в чём его преимущество перед наивным деплоем?

vLLM — инференс-сервер для LLM с PagedAttention, эффективным управлением KV-cache, и Continuous Batching, при котором новые запросы добавляются в обработку без ожидания завершения текущих. По сравнению с наивным вызовом model.generate() на каждый запрос, vLLM заметно повышает утилизацию GPU и throughput на одинаковом оборудовании за счёт того, что карта почти не простаивает между токенами разных запросов.

Как масштабировать LLM-инференс в Kubernetes?

Стандартный HPA по CPU для LLM не подходит: нагрузка приходится на GPU, а не на процессор. Используйте KEDA с метрикой из Prometheus, например длиной очереди запросов vllm:num_requests_waiting. При росте очереди KEDA добавляет реплики инференс-сервера, а Cluster Autoscaler добавляет под них GPU-узлы.

Что такое TTFT и почему это главная метрика LLM?

TTFT, Time to First Token, — время от отправки запроса до получения первого токена ответа. Для интерактивных приложений это главная метрика пользовательского опыта: пользователь видит начало ответа уже через TTFT, а не ждёт полный ответ целиком. Типичная цель — TTFT P95 ниже 500 мс для интерактивных сценариев и ниже 2 секунд для фоновых batch-задач.

Можно ли запускать несколько LLM в одном кластере?

Да, каждая модель разворачивается как отдельный Deployment со своими GPU-ресурсами. Если GPU-узлов в кластере достаточно, несколько моделей работают параллельно без конфликтов. При нехватке GPU используется разделение одной карты между процессами: MIG, Multi-Instance GPU, на картах уровня A100 и H100, которые это поддерживают, либо MPS, Multi-Process Service, когда несколько процессов делят одну карту программно.

Оставьте заявку, чтобы получить консультацию

Оставьте заявку или напишите на почту digital.tech@corp.mail.ru, чтобы узнать больше о сервисах VK и получить коммерческое предложение.

section-subscribe_2x.png
            Ссылка скопирована
            Поделиться

            Почитать по теме

            _blog_head_5.png
            7 июля

            Что такое Kubernetes простыми словами: контейнеры, поды и кластер

            _blog_head_67.png
            7 июля

            Docker и Kubernetes: как они работают вместе и в чём разница

            _blog_head_73.png
            27 августа

            Контейнеризация приложений: что это такое и когда стоит использовать

            40+ готовых сервисов