Что такое батчинг запросов и почему один пользователь тратит GPU впустую
Вы арендовали GPU-сервер под свою LLM, открыли nvidia-smi и увидели, что карта во время генерации ответа загружена на несколько процентов, хотя память забита под завязку. Это не баг и не кривая настройка — так себя ведёт любой GPU при обработке одного запроса за раз. Разбираемся, откуда берётся эта неэффективность и почему тот же самый GPU у сервиса с сотней одновременных пользователей внезапно "оживает" и начинает отрабатывать вложенные в него деньги.
Содержание
- Что происходит внутри GPU, когда он считает нейросеть
- Почему один запрос не нагружает видеокарту
- Что такое батчинг простыми словами
- Continuous batching в vLLM и TGI: как запросы объединяются на лету
- Почему для одного пользователя GPU "простаивает", а для сервиса — работает в полную силу
- Как посмотреть и настроить батчинг у себя на сервере
Что происходит внутри GPU, когда он считает нейросеть
GPU — это тысячи вычислительных ядер, которые эффективны ровно тогда, когда им есть чем заняться одновременно. Инференс LLM на уровне матанализа — это цепочка умножений матриц: входной тензор (ваш запрос, представленный как последовательность векторов) умножается на матрицы весов слоя за слоем.
Ключевой момент: умножение матрицы 1×d на матрицу весов d×d и умножение матрицы 32×d на ту же матрицу весов — операции почти одинаковой стоимости по времени на GPU, если карта не упирается в объём вычислений. Почему так: за один такт GPU способен обработать огромное количество умножений параллельно, и пока в батче мало строк (мало запросов или мало токенов одновременно), большая часть вычислительных блоков карты просто простаивает — им нечего делать, они ждут, пока подготовятся данные для следующего такта.
Формально это называется задачей, ограниченной пропускной способностью памяти (memory-bound), а не вычислениями (compute-bound). При батче в один запрos GPU тратит время в основном на то, чтобы протащить веса модели из видеопамяти к вычислительным блокам — а сами вычисления после этого выполняются почти мгновенно. Добавление в батч ещё нескольких запросов почти не увеличивает время на протаскивание весов (они те же самые), но увеличивает полезную вычислительную работу. Отсюда и вывод: GPU становится вычислительно эффективным (compute-bound) именно тогда, когда одновременно обрабатывает много строк данных.
Почему один запрос не нагружает видеокарту
Если вы запустили Ollama или vLLM локально и общаетесь с моделью в одиночку, происходит следующее:
- Токены генерируются последовательно — каждый новый токен зависит от предыдущего, распараллелить это внутри одного диалога нельзя (авторегрессивная природа LLM).
- Между генерацией токенов GPU почти всё время ждёт данные из памяти, а не считает.
- Матричные операции выполняются с батчем размера 1 — то есть в буквальном смысле недогружают железо, которое физически способно обработать в разы больше строк за то же время.
Это объясняет парадокс, с которым сталкивается почти каждый, кто арендовал дорогую карту под личный чат-бот: nvidia-smi показывает низкую загрузку по вычислениям (столбец GPU-Util не забит на 100%), при этом видеопамять (VRAM) занята почти полностью — потому что там лежат веса модели и KV-кеш активного диалога. Загрузка памяти и загрузка вычислений — разные метрики, и для одного пользователя они расходятся: память забита, вычисления — нет. Подробнее о том, куда уходит память диалога, — в статье про KV-кеш и почему диалог ест VRAM.
Итог: для одного человека мощная карта работает примерно как менее мощная, потому что её реальный потенциал раскрывается только при параллельной нагрузке — а параллелить внутри одного диалога нечего.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть ИИ на сервереЧто такое батчинг простыми словами
Батчинг (batching) — это объединение нескольких независимых запросов в один общий проход через модель, чтобы использовать ту же самую вычислительную операцию (умножение на матрицу весов) сразу для всех них.
Представьте очередь из десяти человек, каждый из которых просит бухгалтера посчитать свою зарплату по одной и той же формуле. Бухгалтер может считать по одному — десять отдельных подходов к калькулятору. А может собрать все десять наборов цифр в одну таблицу и посчитать формулу сразу для всей таблицы — операция та же самая, но выполняется один раз вместо десяти. Для GPU "калькулятор" — это матричное умножение, а "таблица" — это батч запросов.
Практически это означает: сервер инференса не отправляет в GPU каждый запрос по отдельности сразу, как только тот пришёл. Вместо этого он:
- Принимает входящие запросы от разных пользователей.
- Группирует их в батч (набор), который помещается в доступную видеопамять.
- Прогоняет весь батч через модель за один вычислительный проход на каждом шаге генерации токена.
- Раздаёт результат — каждому пользователю его собственный, следующий токен его собственного ответа.
Ключевая тонкость: батчинг не "смешивает" ответы разных людей друг с другом — каждый запрос в батче обрабатывается независимо, просто параллельно с другими на уровне железа. Пользователь А получает ответ на свой вопрос, пользователь Б — на свой, но вычислительно это происходит одной и той же операцией.
Continuous batching в vLLM и TGI: как запросы объединяются на лету
Наивный батчинг — собрать N запросов, подождать, пока все N закончат генерацию, и только потом взять новую партию — плохо работает для чат-ботов: ответы разной длины, кто-то заканчивает за пару токенов, кто-то генерирует длинный текст, и весь батч простаивает, ожидая самого медленного участника.
Современные серверы инференса — vLLM, TGI (Text Generation Inference), и похожие движки — используют так называемый continuous batching (иногда его называют iteration-level scheduling). Суть:
- Батч пересобирается не после завершения всех запросов, а на каждом шаге генерации токена.
- Как только один запрос в батче завершился (сгенерировал
<eos>или упёрся в лимит токенов), его место в батче сразу занимает следующий запрос из очереди — не дожидаясь остальных. - Это держит батч постоянно "полным", насколько позволяет память, вместо того чтобы то расширяться до максимума, то опустошаться.
Если вы разворачивали vLLM, это поведение управляется параметрами вроде --max-num-seqs (сколько запросов одновременно может быть в батче) и --gpu-memory-utilization (какую долю VRAM выделить под веса и KV-кеш, а не оставлять как резерв). Мы разбирали установку и базовую настройку в статье про развёртывание vLLM на VPS — там же видно, почему для батчинга важен именно объём свободной VRAM под KV-кеш каждого запроса, а не только под сами веса модели.
Важный нюанс, о котором часто забывают: Ollama в базовой конфигурации ориентирован на однопользовательский локальный сценарий и исторически не делает continuous batching так же агрессивно, как vLLM — это осознанный компромисс в сторону простоты и низкого порога входа, а не недоработка. Если вам нужен именно параллельный сервис на многих пользователей, сравнение подходов есть в статье Ollama против vLLM.
Почему для одного пользователя GPU "простаивает", а для сервиса — работает в полную силу
Сведём картину воедино на двух сценариях с одним и тем же железом.
Сценарий 1: локальная модель для одного человека. Вы держите модель на своём GPU-сервере, обращаетесь к ней время от времени — набросать код, что-то перевести, задать вопрос. Между вашими запросами GPU простаивает вообще (нет никакой нагрузки), а во время самого запроса — простаивает частично (батч размера 1, упор в память, а не в вычисления). Суммарно карта используется малую долю времени и малую долю потенциала даже в момент использования. Это нормально и ожидаемо для личного инструмента — вы платите за отзывчивость и приватность, а не за утилизацию железа.
Сценарий 2: сервис с множеством одновременных пользователей. Десятки или сотни людей одновременно шлют запросы в тот же самый инстанс модели. Сервер инференса группирует их в батч — вместо простоя между чужими запросами GPU почти постоянно занят полезной работой, а вместо батча размера 1 обрабатывает сразу пачку строк, используя тот самый параллелизм, ради которого GPU и покупали. Та же карта на том же чипе отрабатывает кардинально эффективнее — не потому что стала мощнее, а потому что её наконец загружают так, как она устроена работать.
Отсюда практический вывод для планирования сервера: если вы разворачиваете модель под одного-двух человек, экономически осмысленнее не гнаться за самой мощной картой, а взять GPU по размеру задачи (или вообще присмотреться к CPU-инференсу для младших моделей) — избыточная мощность будет простаивать. Если же вы строите сервис на много пользователей — сама архитектура батчинга станет вашим главным рычагом эффективности, и тут уже оправдана более мощная карта, потому что она сможет держать больший батч. Обзор конфигураций под несколько пользователей — в статье про GPU-сервер на несколько пользователей.
Есть и третий, промежуточный случай — несколько независимых сервисов или ботов на одном сервере, обращающихся к одной модели через общий эндпоинт (например, через vLLM с OpenAI-совместимым API). Тогда даже без "реальных" тысяч пользователей вы получаете эффект батчинга просто за счёт того, что запросы разных ботов физически накладываются друг на друга по времени.
Как посмотреть и настроить батчинг у себя на сервере
Проверить, реально ли ваша нагрузка батчится, можно без специальных инструментов.
Смотрим утилизацию GPU в реальном времени во время запроса:
watch -n 0.5 nvidia-smi --query-gpu=utilization.gpu,utilization.memory,memory.used --format=csv
Если при одном активном диалоге utilization.gpu низкий (условно не близко к верхней границе), а memory.used большой — это и есть картина из раздела выше: память занята весами и KV-кешем, а вычислительные блоки простаивают. Как отслеживать нагрузку локальной LLM подробнее — в статье про мониторинг нагрузки.
Проверить, что сервер инференса действительно объединяет запросы, можно нагрузочным тестом — отправить несколько параллельных запросов и посмотреть на утилизацию:
for i in 1 2 3 4 5 6 7 8; do
curl -s http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{"model": "your-model", "prompt": "Расскажи короткую историю", "max_tokens": 200}' \
-o /dev/null &
done
wait
Пока эти восемь запросов летят параллельно, nvidia-smi в соседнем терминале должен показать заметно более высокую загрузку по вычислениям, чем при одном таком же запросе в одиночку — это и есть эффект батчинга на практике, без домыслов и точных цифр (конкретный прирост зависит от модели, длины промптов и объёма свободной VRAM у вас, поэтому измеряйте на своём железе, а не ориентируйтесь на чужие бенчмарки).
Из настроек, которые реально влияют на батчинг в vLLM:
| Параметр | Что делает | Когда трогать |
|---|---|---|
--max-num-seqs | Верхний предел одновременных запросов в батче | Увеличивать, если видите, что запросы стоят в очереди при свободной VRAM |
--gpu-memory-utilization | Доля VRAM, отдаваемая под веса + KV-кеш | Поднимать осторожно, оставляя запас под пики |
--max-model-len | Максимальная длина контекста на запрос | Меньшее значение освобождает VRAM под больший батч |
--enable-chunked-prefill | Разбивает обработку длинного промпта на части, чтобы не блокировать батч | Включать при смеси коротких и очень длинных запросов |
Если после увеличения --max-num-seqs запросы начинают падать по нехватке памяти или сильно замедляются — вы упёрлись в объём VRAM под KV-кеш, и решение здесь не "добавить параметр", а либо уменьшить --max-model-len, либо перейти на карту с большим объёмом памяти, либо включить квантование модели, чтобы освободить место под кеш конкурентных диалогов.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть ИИ на сервереНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Батчинг ухудшает качество или скорость ответа для одного конкретного пользователя?
В большинстве реализаций — практически нет: continuous batching устроен так, что каждый запрос в батче считается независимо, и добавление соседей в батч не меняет содержание его ответа. Небольшая задержка теоретически возможна, если сервер сильно перегружен и запрос ждёт своей очереди на вход в батч, но это вопрос очереди, а не самого батчинга.
Можно ли включить батчинг в Ollama так же, как в vLLM?
Ollama постепенно добавляет параллельную обработку запросов, но исторически это не основной сценарий использования инструмента — он проектировался под локальный однопользовательский запуск. Если батчинг для вас критичен (сервис на много пользователей), практичнее сразу смотреть на vLLM или TGI, которые спроектированы вокруг этой задачи с нуля.
Если я один использую сервер, стоит ли вообще думать о батчинге?
Нет смысла настраивать батчинг ради одного пользователя — вы просто не создадите нагрузку, которую есть смысл группировать. Здесь важнее правильно подобрать размер GPU под задачу, а не гнаться за настройками, рассчитанными на высокую параллельную нагрузку.
Как батчинг связан с ценой инференса для сервиса?
Чем эффективнее используется GPU (то есть чем больше запросов помещается в батч на каждый вычислительный такт), тем ниже себестоимость обработки одного запроса — та же карта обслуживает больше людей за то же время аренды. Это одна из причин, почему у сервисов с большим количеством пользователей экономика инференса выглядит иначе, чем у личного локального запуска.
Батчинг работает только на GPU или на CPU тоже есть смысл?
Эффект от батчинга на CPU тоже есть (амортизация накладных расходов на каждый проход), но он ощутимо меньше, чем на GPU, — у CPU изначально гораздо меньше параллельных вычислительных блоков, которые можно догрузить батчем.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →