Детский сад на GPU: почему стандартный K8s не тянет LLM
Ты собрал кластер, натыкал подами с nvidia.com/gpu: 1 и думаешь, что дело в шляпе? Поздравляю, ты только что создал очередь из моделей, где каждый под занимает целый GPU, а остальная память простаивает. LLM инференс — это не микросервис на Go, где можно накинуть ещё один экземпляр. Здесь каждая модель весит десятки гигабайт, время старта — минуты, а фрагментация памяти убивает утилизацию.
Проблема классическая: стандартный Device Plugin видит GPU как неделимый ресурс. Одна модель на чип. А если модель занимает 30 ГБ из 80 ГБ H100? Остальные 50 ГБ летят в трубу. В 2026 году, когда H200 и B200 стали мейнстримом, так работать нельзя — слишком дорого. На помощь приходят три зверя: DRA (Dynamic Resource Allocation), GIE (GPU Inference Engine) и LLM-D. Разберёмся, как их скрестить, чтобы кластер выдавал тысячи токенов в секунду без простоев.
Если ты всё ещё веришь, что статические requests/limits спасут мир, прочитай сначала гайд по сборке мощной станции — там я в деталях объясняю, почему GPU не резиновый.
DRA: когда GPU перестаёт быть чёрным ящиком
Dynamic Resource Allocation — фича, которая наконец-то появилась в стабильном виде в Kubernetes 1.31+ и стала боевой к 1.32. В 2026 году уже нет смысла писать кастомные Device Plugin, если тебе нужно выделять не целый GPU, а его часть (MIG, время, полосу памяти). DRA позволяет запрашивать ресурсы в виде абстрактных слотов, а драйвер на узле сам решает, как их удовлетворить.
Для LLM это золото: модель может запросить не «один H100», а «24 ГБ памяти + 80% SM» или, скажем, «100 ГБ/с bandwidth». DRA-драйвер от NVIDIA (входит в GPU Operator v24.9+) умеет нарезать Tensor Core под конкретную задачу. Ниже — пример ресурсного запроса.
apiVersion: resource.k8s.io/v1alpha3
kind: ResourceClaim
metadata:
name: llm-gpu-claim
spec:
resourceClassName: nvidia.com-gpu
parametersRef:
kind: GpuClaimParameters
name: llm-gpu-params
---
apiVersion: nvidia.com/v1
kind: GpuClaimParameters
metadata:
name: llm-gpu-params
spec:
memory: 30Gi
computeRate: 50% # доля SM
Подаешь Claim — и K8s сам находит узел, где можно выделить именно такой кусок GPU. Без ручного маппинга, без запутанных селекторов.
Ошибка №1: Пытаться использовать DRA с кастомными Device Plugin, не удалив старые. DRA и Device Plugin конфликтуют на уровне kubelet. Полностью мигрируй или не трогай. Я сам однажды оставил оба — получил двойное резервирование и падение ноды.
GIE: кто будет выполнять модель?
GPU Inference Engine — прослойка между K8s и рантаймом модели. Это может быть TensorRT-LLM, vLLM или LMDeploy. В 2026 году стандартом стал NVIDIA GIE 3.2 — универсальный движок, который сам выбирает, компилировать ли граф под TensorRT или использовать динамическое биндинг CUDA.
GIE решает главную боль: после выделения ресурсов через DRA нужно быстро запустить модель и начать отвечать. GIE умеет «горячий старт» — держит пул процессов с уже загруженными весами. Когда K8s отправляет запрос через KServe, GIE просто переключает поток на готовый экземпляр.
Как это выглядит в Pod'е?
containers:
- name: llm-server
image: nvcr.io/nvidia/gie:3.2-cuda12.8
env:
- name: MODEL_NAME
value: "meta-llama-4-100b"
resources:
claims:
- name: gpu
volumeMounts:
- name: model-storage
mountPath: /models
args: ["--engine-path=/models/llama4.engine", "--pool-size=4"]
Пул из четырёх процессов на одном H200 — это 4 параллельных инференса с батчем до 32. Без GIE пришлось бы ставить 4 отдельных пода и делить GPU через MIG, а это лишний оверхед.
Про то, как встроить GIE в архитектуру с KServe и правильно считать latency, я писал в статье Архитектура платформы Nova AI. Там же разобраны типовые ошибки с очередями.
LLM-D: демон, который не даёт моделям умирать
Теперь о самом тонком месте — управлении жизненным циклом моделей. LLM-D (LLM Daemon) — это control loop внутри кластера, который следит за активностью моделей, перезагружает упавшие, чистит кеш и перераспределяет модели по узлам.
Звучит как обычный оператор? Не совсем. LLM-D понимает внутреннюю структуру GIE: он знает, сколько процессов загружено, какая у них память, какой TTL последнего запроса. Если модель не используется 10 минут — LLM-D выгружает её из GIE, освобождая GPU под другую задачу. Когда запрос приходит — загрузка происходит за 2-3 секунды (благодаря snapshot памяти).
Вот минимальная конфигурация LLM-D (Custom Resource):
apiVersion: llm.nvidia.com/v1
kind: LLMDaemon
metadata:
name: main-inference
spec:
engine: gie:3.2
model: hf://meta-llama/Llama-4-70b
replicas: 2
scaling:
minIdle: 1
maxIdle: 4
idleTTL: 10m
strategy: canary # поддержка canary-деплоя моделей
LLM-D + GIE + DRA образуют треугольник: DRA выделяет, GIE выполняет, LLM-D управляет. Без любого из звеньев цепочка рвётся.
Пошаговый план: ставим всё вместе
1 Включаем DRA в кластере
Убедись, что K8s версии 1.32+, на всех нодах установлен NVIDIA GPU Operator v24.9. В Operator'е есть встроенный DRA драйвер. Активируй его в Helm values:
dra:
enabled: true
resourceClass: nvidia.com-gpu
2 Устанавливаем GIE
Установи пакет gie-server через RPM или контейнер, подготовь engine для модели. Для Llama-4 это делается через gie-build-engine:
gie-build-engine --model hf://meta-llama/Llama-4-70b --output /models/llama4.engine --dtype fp8 --kv-cache 48
3 Развёртываем LLM-D
Установи оператор LLM-D (Helm-чарт nvidia/llm-daemon версии 2.5+). Создай ресурс LLMDaemon как в примере выше. Оператор сам сгенерирует ResourceClaim и под для GIE.
4 Интегрируем с KServe
Создай InferenceService, который ссылается на LLM-D как на runtime. Пример:
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: llama4
spec:
predictor:
model:
modelFormat:
name: llm-daemon
storageUri: "s3://models/llama4"
args:
- --daemon-endpoint=llm-daemon.nvidia-system.svc.cluster.local:9000
Теперь каждый запрос к KServe попадает в LLM-D, который выбирает свободный слот GIE, и ответ летит обратно.
Пять ошибок, которые я видел в продакшене
- Не настроили NUMA affinity. DRA выделяет память, но если CPU и GPU висят на разных NUMA-нодах, latency растёт в 2 раза. Всегда ставь
topologyManagerPolicy: single-numa-nodeв Kubelet. - Забыли про snapshot памяти. LLM-D умеет сохранять состояние модели в RAM, но если не выделить
hugepages, время восстановления будет 10+ секунд. Выдели 20-30 ГБ hugepages на ноду. - Смешали модели с разной длиной контекста. GIE плохо тюнится под переменные batch size. Лучше группировать запросы по длине.
- Не обновили GPU Operator. Старые версии (до 24.9) имели баги с DRA при одновременном запуске нескольких Claim'ов. Обновляйся, не будь мамонтом.
- Решили, что одной модели хватит. Когда нагрузка вырастает до 1000 RPS, LLM-D обязан уметь переключаться между несколькими движками. Подробнее про скейлинг — в статье Масштабирование LLM: расчёт инфраструктуры для 1000 запросов.
Бонус: как заставить это работать на разнородном железе
В 2026 году мало у кого кластер состоит из одинаковых H100. У тебя могут быть DGX Spark, старые A100, а кто-то воткнул Mac Studio. DRA позволяет абстрагироваться от типа GPU: ты просто запрашиваешь «40 ГБ памяти», а драйвер на узле решает, как это выдать. GIE на Mac работает через Metal Performance Shaders, а на Linux — через CUDA. LLM-D вообще не знает о железе — он просто дёргает GIE.
Я экспериментировал с кластером, где часть нод была на ARM, и это работало (медленнее, но работало). Если интересно, как скрестить утюг с холодильником — читай статью про кластеризацию разных платформ.
Короче: не строй велосипед
Три кита современного LLM инференса — DRA (честное деление GPU), GIE (быстрый рантайм) и LLM-D (автоматизация жизни модели). В 2026 году это уже не фичи early adopters, а стандарт. Если ты до сих пор планируешь вручную нарезать MIG части и ставить cron для перезапуска подов — остановись. Возьми GPU Operator, поставь чарты, запили пару манифестов — и получишь кластер, который сам себя оптимизирует.
И да, не забудь про мониторинг: dcgm-exporter с дашбордами в Grafana обязателен. Иначе как ты узнаешь, что LLM-D выгрузил не ту модель?