vLLM не запускается из-за памяти: причины и решение
Карта пустая, модель по паспорту в неё влезает, а vllm serve падает на старте с torch.OutOfMemoryError. Дело почти никогда не в весах: vLLM резервирует память заранее, одним куском, и не хватает её обычно не под модель, а под KV-кэш на паспортную длину контекста. Ниже — как прочитать лог профилирования, посчитать бюджет карты и какие флаги реально двигают цифру.
Содержание
- Как vLLM тратит память и почему падает именно на старте
- Причина 1: max-model-len, то есть паспортный контекст модели
- Причина 2: gpu-memory-utilization считается от всей карты, а не от свободной
- Причина 3: пик активаций, CUDA-графы и мультимодальность
- Причина 4: веса. Квантование двигает цифру в разы, а не в проценты
- Причина 5: кончилась не VRAM, а обычная память хоста
- Какой сервер брать под vLLM в MAATRIX
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Как vLLM тратит память и почему падает именно на старте
В отличие от llama.cpp и Ollama, vLLM не выделяет память по мере надобности. Он грузит веса, прогоняет профилирующий прогон фиктивным батчем максимального размера, замеряет пик активаций — и весь остаток отдаёт под KV-кэш одним пулом. Отсюда ошибка до первого запроса, зато детерминированная и объяснимая четырьмя числами лога.
INFO 08-28 10:12:38 [gpu_model_runner.py:1892] Model loading took 14.9878 GiB and 22.4 seconds
INFO 08-28 10:12:44 [gpu_worker.py:276] Memory profiling takes 6.52 seconds
INFO 08-28 10:12:44 [gpu_worker.py:276] the current vLLM instance can use total_gpu_memory (23.64GiB) x gpu_memory_utilization (0.90) = 21.28GiB
INFO 08-28 10:12:44 [gpu_worker.py:276] model weights take 14.99GiB; non_torch_memory takes 0.12GiB; PyTorch activation peak memory takes 1.22GiB; the rest of the memory reserved for KV Cache is 4.95GiB.
INFO 08-28 10:12:45 [kv_cache_utils.py:864] GPU KV cache size: 40,544 tokens
INFO 08-28 10:12:45 [kv_cache_utils.py:868] Maximum concurrency for 8,192 tokens per request: 4.95x
Бюджет 21,28 ГиБ: веса 14,99, буферы вне PyTorch 0,12, пик активаций 1,22 — под кэш остаётся 4,95 ГиБ, то есть 40 544 токена на всех. Последняя строка самая полезная: сколько диалогов вашей длины поместится одновременно. 4,95x — это пять пользователей, а не пятьдесят.
Если этих строк нет вовсе, падение случилось раньше профилирования — на загрузке весов, и виновата хостовая RAM. Сортировка по симптому:
| Что в логе | Что кончилось | Причина |
|---|---|---|
torch.OutOfMemoryError: CUDA out of memory | VRAM при выделении | 2–4 |
To serve at least one request with the models's max seq len... | VRAM под KV-кэш | 1 |
Free memory on device (2.31/23.64 GiB) on startup... | VRAM занял кто-то другой | 2 |
Процесс исчез без traceback, status=9/KILL | хостовая RAM, OOM killer | 5 |
Unexpected bus error encountered in worker | /dev/shm в контейнере | 5 |
journalctl -u vllm -n 200 --no-pager | grep -iE 'memory|oom|cache size'
nvidia-smi --query-gpu=memory.total,memory.used,memory.free --format=csv
sudo dmesg -T | grep -iE 'out of memory|killed process'
Нужно поднять сервис прямо сейчас — стартуйте с тесными параметрами и отпускайте по одному:
vllm serve Qwen/Qwen2.5-7B-Instruct --max-model-len 4096 \
--max-num-seqs 32 --max-num-batched-tokens 2048 \
--gpu-memory-utilization 0.85 --enforce-eager
Причина 1: max-model-len, то есть паспортный контекст модели
Первая по частоте, с отрывом. Llama 3.1 умеет 131 072 токена, и vLLM берёт это число из config.json как требование: обслужить хотя бы один запрос такой длины. Не может — не стартует.
ValueError: To serve at least one request with the models's max seq len (131072),
(16.00 GiB KV cache is needed, which is larger than the available KV cache memory
(4.95 GiB). Based on the available memory, you can try increasing
`gpu_memory_utilization` or decreasing `max_model_len` when initializing the engine.
Опечатка models's и незакрытая скобка — прямо в исходниках; по ним опознаётся движок V1, стоящий по умолчанию с версии 0.8. В старом V0 текст был другой, про maximum number of tokens that can be stored in KV cache, — советы под ту формулировку могут быть неактуальны.
Откуда 16 ГиБ, видно из формулы — размер кэша считается точно, без эмпирики:
байт на токен = 2 (ключи и значения) × число слоёв × число KV-голов × размер головы × байт на элемент
Для Llama 3.1 8B в bf16: 2 × 32 × 8 × 128 × 2 = 131 072 байта, то есть 128 КиБ на токен; на 131 072 токена — ровно 16 ГиБ. Числа берутся из config.json:
python3 - <<'PY'
import json, glob
p = glob.glob('/root/.cache/huggingface/hub/models--*/snapshots/*/config.json')[0]
c = json.load(open(p))
L = c['num_hidden_layers']
kv = c.get('num_key_value_heads', c['num_attention_heads'])
d = c.get('head_dim') or c['hidden_size'] // c['num_attention_heads']
b = 2 * L * kv * d * 2
print(f'{b/1024:.0f} КиБ/токен | 8192 ток = {b*8192/2**30:.2f} ГиБ | '
f'{c["max_position_embeddings"]} ток = {b*c["max_position_embeddings"]/2**30:.1f} ГиБ')
PY
Разброс огромный, число параметров ничего не предсказывает:
| Модель | Слоёв | KV-голов | Размер головы | КиБ на токен | ГиБ на 8192 токена |
|---|---|---|---|---|---|
| Qwen2.5-7B-Instruct | 28 | 4 | 128 | 56 | 0,44 |
| Llama 3.1 8B Instruct | 32 | 8 | 128 | 128 | 1,00 |
| Qwen2.5-32B-Instruct | 64 | 8 | 128 | 256 | 2,00 |
| Gemma 2 9B | 42 | 8 | 256 | 336 | 2,63 |
| Llama 2 13B (без GQA) | 40 | 40 | 128 | 800 | 6,25 |
У Gemma 2 кэш втрое дороже, чем у Llama 3.1 8B, — из-за головы в 256 (у половины слоёв там скользящее окно 4096, так что реальный расход ниже расчётного). А 800 КиБ на токен у Llama 2 13B без GQA — то самое, из-за чего старые модели не лезут в карту, куда помещаются модели крупнее.
Лечение почти бесплатное: --max-model-len 8192. Стотысячный контекст на каждый диалог не нужен никому, а платите вы за него всей свободной VRAM. Под документы берите 16384 и следите за Maximum concurrency: она должна остаться больше числа одновременных пользователей.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверПричина 2: gpu-memory-utilization считается от всей карты, а не от свободной
--gpu-memory-utilization 0.9 означает «занять 90% полного объёма карты», а не оставшегося свободным. Если на GPU что-то уже висит — второй экземпляр vLLM, Jupyter с забытым ядром, X-сервер, — движок всё равно потянется к 21,28 ГиБ из 23,64 и упрётся:
ValueError: Free memory on device (2.31/23.64 GiB) on startup is less than desired GPU
memory utilization (0.9, 21.28 GiB). Decrease GPU memory utilization or reduce GPU
memory used by other processes.
Лечится не понижением utilization, а выключением лишнего.
nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv
sudo fuser -v /dev/nvidia*
pid, process_name, used_gpu_memory [MiB]
3184, VLLM::EngineCore, 21504 MiB
Осиротевшие воркеры — классика. Когда родителя снимают через kill -9, дочерние VLLM::EngineCore остаются и держат VRAM: systemctl stop их не трогает, а nvidia-smi показывает занятую карту без единого живого сервиса. Лечится sudo pkill -f 'VLLM::EngineCore', а чтобы не повторялось — KillMode=control-group в юните.
Обратная ошибка — крутить утилизацию вверх. Значение 0,96–0,98 обычно даёт старт, но переносит падение в рантайм: без запаса на фрагментацию torch.OutOfMemoryError прилетает через час на длинном запросе. Рабочий коридор — 0,85–0,92, и выигрыш там скромный: разница между 0,85 и 0,92 на карте 24 ГБ равна 1,65 ГиБ, около 13 тысяч токенов кэша. Квантование весов даёт в семь раз больше.
А если в ошибке видно 412.19 MiB is reserved by PyTorch but unallocated, аллокатор держит пустые зарезервированные куски — помогает PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True.
Причина 3: пик активаций, CUDA-графы и мультимодальность
Строка PyTorch activation peak memory takes 1.22GiB — не константа: её задаёте вы, тремя флагами.
--max-num-batched-tokens. Профилирующий прогон собирает фиктивный батч ровно такого размера, и пик активаций растёт вместе с ним почти линейно. В V1 по умолчанию 8192; снижение до 2048 отдаёт под кэш заметный кусок, но длинные промпты дробятся на большее число шагов префилла и время до первого токена на большом документе вырастет.
--max-num-seqs. Верхняя граница числа последовательностей в батче, в V1 по умолчанию 1024 — почти всегда число фантастическое. Реально пять-десять пользователей — ставьте 64.
--enforce-eager. Отключает захват CUDA-графов: освобождает от одного до трёх гигабайт и ускоряет холодный старт, но отнимает 10–20% пропускной на коротких ответах. Тактика — поднять с ним, потом убрать и посмотреть, влезает ли: не влезает — значит, запаса на всплеск нет.
Мультимодальные модели — отдельная ловушка. Для Qwen2-VL, Llava и родственников vLLM профилирует худший случай: максимум картинок максимального разрешения в запросе. Пик улетает за 10 ГиБ, и 7B не стартует там, где текстовая модель того же размера живёт вольготно:
vllm serve Qwen/Qwen2-VL-7B-Instruct \
--max-model-len 8192 --limit-mm-per-prompt '{"image": 1}'
Причина 4: веса. Квантование двигает цифру в разы, а не в проценты
Всё, что выше, отыгрывает проценты. Веса — гигабайты: модель на 8 миллиардов параметров в bf16 занимает 8 × 2 = 16 гигабайт (14,99 ГиБ), в 4-битном AWQ или GPTQ — около 5,7 ГиБ, в FP8 — около 8 ГиБ. Бюджет карты 24 ГБ при утилизации 0,90 для Llama 3.1 8B, расчёт по формуле выше:
| Статья бюджета | FP16 | AWQ 4-бит | AWQ + fp8-кэш |
|---|---|---|---|
| Доступно движку | 21,28 ГиБ | 21,28 ГиБ | 21,28 ГиБ |
| Веса | 14,99 | ~5,7 | ~5,7 |
| Активации и графы | ~1,3 | ~1,3 | ~1,3 |
| Остаток под KV-кэш | ~4,95 | ~14,3 | ~14,3 |
| Байт на токен | 128 КиБ | 128 КиБ | 64 КиБ |
| Влезает токенов | ~40 500 | ~117 000 | ~234 000 |
| Диалогов по 8192 токена | 4–5 | 14 | 28 |
Разница между колонками — не проценты, а порядок по числу пользователей. Вывод: на карте 24 ГБ модель 7–8B имеет смысл держать в AWQ, если у вас больше пяти одновременных диалогов; готовые сборки лежат на HuggingFace с суффиксами -AWQ и -GPTQ-Int4. Где начинает падать качество — какое квантование выбрать.
--kv-cache-dtype fp8 режет кэш вдвое. Нужна вычислительная способность 8.0 и выше — на T4 и V100 флаг не заработает. И честно: восьмибитный кэш не бесплатен, на длинных диалогах и строгом формате вывода деградация заметна.
--tensor-parallel-size 2 делит и веса, и кэш между картами: 32B в 4 битах на паре 24-гигабайтных карт живёт спокойно. Требование, о которое спотыкаются, — число голов внимания должно делиться на размер группы:
ValueError: Total number of attention heads (32) must be divisible by tensor parallel size (3).
--cpu-offload-gb 8 выносит часть весов в хостовую RAM: технически спасает, практически плохая идея — каждый токен тянет выгруженные слои через PCIe. И признаем прямо: если модель в 4 битах с минимальным кэшем в карту не лезет, флаги не помогут — нужна модель поменьше или карта побольше. Как считать заранее — выбор GPU-сервера.
Причина 5: кончилась не VRAM, а обычная память хоста
Класс падений, который легко принять за проблему с картой: строк профилирования нет, traceback отсутствует, процесс просто исчезает. Или так:
ERROR [core.py:588] EngineCore failed to start.
RuntimeError: Engine core initialization failed. See root cause above. Failed core proc(s): {}
Смотрите в ядро и systemd:
sudo dmesg -T | grep -iE 'out of memory|killed process'
journalctl -u vllm --no-pager | grep -iE 'oom|status=9'
[Fri Aug 28 10:14:02 2026] Out of memory: Killed process 5312 (VLLM::EngineCor)
total-vm:41285632kB, anon-rss:16283904kB, oom_score_adj:0
systemd[1]: vllm.service: Main process exited, code=killed, status=9/KILL
systemd[1]: vllm.service: Failed with result 'oom-kill'.
Загрузка весов идёт через хостовую RAM. Safetensors читаются с диска и переливаются в карту; на машине с 16 ГБ RAM загрузка 16-гигабайтной модели упирается в своп ещё до того, как дело дойдёт до GPU. Правило: хостовой памяти не меньше двойного размера весов на диске (du -sh ~/.cache/huggingface/hub/models--*). Памяти не добавить — спасает своп, веса читаются один раз:
sudo fallocate -l 16G /swapfile && sudo chmod 600 /swapfile
sudo mkswap /swapfile && sudo swapon /swapfile
Лимиты контейнера и юнита. В Docker с --memory=16g контейнер убивается cgroup-ом молча, с кодом выхода 137; проверка однозначная — docker inspect --format='{{.State.OOMKilled}}' vllm вернёт true. В systemd ту же роль играет MemoryMax=.
/dev/shm в контейнере. Docker по умолчанию даёт 64 МБ разделяемой памяти, а vLLM обменивается через неё между воркерами, особенно при тензорном параллелизме:
ERROR: Unexpected bus error encountered in worker. This might be caused by insufficient
shared memory (shm).
docker run --gpus all --ipc=host -p 8000:8000 \
-v ~/.cache/huggingface:/root/.cache/huggingface \
vllm/vllm-openai:v0.11.0 --model Qwen/Qwen2.5-7B-Instruct-AWQ \
--max-model-len 8192 --gpu-memory-utilization 0.90
--ipc=host или --shm-size=8g — любой из двух, не оба. И помните: при --tensor-parallel-size 4 воркеров четыре, каждый просит свою долю хостовой памяти, а --swap-space (по умолчанию 4) задаёт гигабайты RAM под выгрузку блоков.
Кончившийся диск маскируется под нехватку памяти. Скачивание обрывается с OSError: [Errno 28] No space left on device, а один pip install vllm тянет PyTorch с библиотеками CUDA — 8–10 ГБ ещё до первой модели. Логика диагностики: что делать при нехватке RAM.
Какой сервер брать под vLLM в MAATRIX
vLLM без видеокарты — не рабочий вариант. CPU-бэкенд собирается из исходников с VLLM_TARGET_DEVICE=cpu, объём кэша задаётся руками через VLLM_CPU_KVCACHE_SPACE=8, а скорость — единицы токенов в секунду. Нет GPU — ставьте Ollama, она для CPU и делалась. Наш замер на VPS без карты, AMD EPYC 9554, 16 vCPU, Ollama 0.33.1: qwen2.5:7b в Q4_K_M занимает 5,1 ГБ и даёт 7,6 токена в секунду, llama3.1:8b — 5,6 ГБ и 12,8 ток/с, qwen2.5:3b — 2,2 ГБ и 34,1 ток/с. Генерация выходит на полку уже при четырёх потоках (упирается в память, не в процессор), а num_thread 32 на 16 vCPU роняет её до 0,35 ток/с. Сравнение движков — Ollama против vLLM.
Минимум под модель 7–8B: GPU с 24 ГБ VRAM, 8 vCPU, 32 ГБ RAM, 100 ГБ NVMe. Граница видна из таблицы выше: в FP16 останется около 5 ГиБ кэша — это --max-model-len 8192 и четыре-пять диалогов, дальше очередь. Длинный контекст или больше пользователей на той же карте — только через AWQ.
Комфортная конфигурация: 48 ГБ VRAM (одной картой или две по 24 с --tensor-parallel-size 2), 16 vCPU, 64 ГБ RAM, 200 ГБ NVMe. Здесь 8B живёт в FP16 с контекстом 32 тысячи токенов, а 32B в AWQ помещается без ухищрений. Диск берите с запасом: зависимости плюс две-три модели в кэше — уже под сотню гигабайт. И помните правило по RAM, а не по VRAM: удвоенный размер весов. Экономия здесь даёт падение на старте, похожее на проблему с картой.
Локация — Великобритания, Лондон. Причина практическая: HuggingFace и индексы pip оттуда открываются напрямую, без прокси, а скачивание 16-гигабайтных весов из России регулярно обрывается на середине. Плюс низкий пинг до Европы. Берите US, если рядом держите прокси к зарубежным API, и RU — если персональные данные обязаны оставаться в России по 152-ФЗ. Подробнее: GPU-сервер в Великобритании.
Про установку честно: vLLM в каталог готовых приложений не входит — сервер приезжает чистым, движок ставите сами (python3 -m venv /opt/vllm, pip install "vllm==0.11.0" — версию фиксируйте, флаги между минорными релизами меняются). Зато в каталоге есть готовые сборки родственных сервисов — Ollama, LiteLLM, AnythingLLM, Dify: они разворачиваются автоматически при заказе, доступы появляются в кабинете. Связка «vLLM руками, LiteLLM из каталога перед ним» работает хорошо: шлюз берёт ключи, лимиты и учёт. Оплата — картой российского банка, по СБП, криптой или токеном MAAT.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверОбсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Частые вопросы
Модель весит 16 ГБ, карта 24 ГБ — почему не запускается?
Кроме весов нужен KV-кэш на паспортный контекст: Llama 3.1 требует 16 ГиБ на 131 072 токена, вместе с весами это 31 ГиБ. Поставьте --max-model-len 8192 — запросит она уже 1 ГиБ.
Помогает ли поднять gpu-memory-utilization до 0.98?
Старт обычно получается, но проблема переезжает в рантайм: без запаса на фрагментацию torch.OutOfMemoryError придёт через час работы. Держитесь 0,85–0,92, а память отыгрывайте квантованием.
nvidia-smi показывает занятую карту, хотя сервис остановлен. Что это?
Осиротевшие воркеры после жёсткого убийства процесса: найдите их через nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv и снимите pkill -f 'VLLM::EngineCore'.
Нужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.