Почему «модель помещается в память» — не расчёт мощности

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

Рабочая нагрузка состоит из запросов, которые приходят неравномерно, имеют разную длину и одновременно занимают память и вычисления. Утром десять менеджеров могут открыть помощника почти в одну минуту. Один отправит короткий вопрос по инструкции, другой приложит длинную переписку, третий запустит RAG с несколькими фрагментами. Даже если каждый запрос по отдельности выполняется быстро, вместе они создают очередь.

Поэтому мощность локального контура определяется не числом параметров, а сочетанием пяти величин:

  • скорость поступления запросов и всплески;
  • число одновременно выполняющихся запросов;
  • распределение длины входа и выхода;
  • допустимые p95 для первого токена и полного ответа;
  • доля ответов, которые действительно приняты пользователем или процессом.

Последний показатель связывает инфраструктуру с бизнесом. Быстро сгенерированный, но отклонённый ответ расходует мощность, не создавая результата.

Что происходит внутри сервера

Генерация состоит как минимум из двух фаз. На prefill сервер обрабатывает входной контекст; длинный документ или большой RAG-пакет заметно увеличивает эту часть. На decode модель выдаёт токены последовательно, а для каждого активного запроса хранится KV-кэш. Чем больше одновременных последовательностей и их контекстов, тем больше памяти требуется под этот кэш.

Современные движки пытаются использовать ускоритель эффективнее. llama.cpp включает continuous batching и поддерживает несколько параллельных слотов: токены активных запросов объединяются в общий batch. vLLM использует планировщик и блочное управление KV-кэшем. В исходной работе о PagedAttention авторы показали, что более эффективное управление памятью может существенно увеличить пропускную способность по сравнению с рассмотренными тогда системами.

Но batching не создаёт бесплатную мощность. Когда запросов становится слишком много, растут ожидание в очереди и хвостовая задержка. Длинный prefill может мешать коротким интерактивным запросам; нехватка KV-кэша приводит к вытеснению и повторным вычислениям. Параметр максимального контекста также не означает, что все параллельные пользователи смогут одновременно использовать этот максимум.

Отсюда важный вывод: сравнивать серверы по «токенам в секунду» одного запроса недостаточно. Нужно воспроизвести рабочую смесь запросов и измерить, сколько из них укладывается в договорённый SLA.

Какие метрики нужны руководителю

Для интерактивного помощника полезно разделить задержку:

  • queue time — сколько запрос ждал начала обработки;
  • TTFT — время от старта обработки до первого токена;
  • inter-token latency — пауза между последующими токенами;
  • end-to-end latency — время до завершения ответа;
  • error и reject rate — ошибки и отказы по перегрузке.

vLLM публикует эти показатели как метрики уровня запроса и агрегированные гистограммы. Это позволяет смотреть не только среднее, но и p50, p95 и p99. Для бизнеса p95 обычно полезнее среднего: оно показывает опыт пользователя в плохой, но регулярный момент, а не в идеальную минуту.

К техническим метрикам добавьте две продуктовые:

  • accepted outcome rate — доля ответов, принятых без повторного запроса или существенной правки;
  • cost per SLA-compliant accepted outcome — полная стоимость принятого ответа, который уложился в SLA.

Вторая метрика не даёт скрыть перегрузку за высокой средней скоростью. Если сервер дешёвый, но четверть пользователей ждёт слишком долго, экономия существует только в таблице.

Как собрать профиль нагрузки

До закупки оборудования выгрузите обезличенную статистику пилота или аналогичного процесса за две–четыре недели. Для каждого запроса нужны время прихода, размер входа, размер ответа, тип операции и итог. Затем постройте распределения, а не одну среднюю строку.

Минимальный профиль включает:

  • медиану и p95 входных токенов;
  • медиану и p95 выходных токенов;
  • число запросов за минуту в обычный час и в пиковые 15 минут;
  • максимальное число одновременно активных запросов;
  • долю RAG, длинных документов и tool calls;
  • долю интерактивных и фоновых задач.

Интерактивные и фоновые задачи лучше тестировать отдельно. Пользовательский чат чувствителен к TTFT и плавности вывода. Ночная классификация документов может терпеть очередь, зато ценит максимальную суммарную пропускную способность. Если смешать их без приоритетов, фоновый batch способен испортить p95 рабочего чата.

Нагрузочный тест, который отвечает на вопрос о деньгах

Официальный `vllm bench serve` позволяет задавать request rate, burstiness и max concurrency. Документация прямо разделяет максимальную пропускную способность, реалистичное тестирование, стресс и проверку SLA. У llama.cpp есть серверный benchmark с заданным числом виртуальных пользователей, количеством итераций, размером batch и числом параллельных слотов.

Для выбора мощности проведите матрицу, а не один прогон:

1. Зафиксируйте модель, квантизацию, длины входа и выхода.
2. Прогрейте сервер и прогоните контрольный одиночный запрос.
3. Повторите тест при concurrency 1, 2, 4, 8 и далее до нарушения SLA.
4. Для каждого уровня сохраните p50/p95 TTFT, p95 полного ответа, throughput, ошибки, очередь и пиковую память.
5. Повторите с длинным p95-профилем и с короткими запросами.
6. Добавьте всплеск, похожий на начало рабочего дня.
7. Проверьте деградацию при одновременной индексации RAG или фоновой задаче.

Точка мощности — не максимальное значение, при котором сервер ещё отвечает. Это последняя ступень, где p95, ошибки и память остаются внутри заранее заданных границ с резервом. Для продакшена полезно оставить 15–25% запаса на изменение длины запросов, обновление движка и необычные пики.

Независимые сравнительные работы подчёркивают зависимость результата от режима. В одном опубликованном сравнении vLLM показал преимущество при высокой конкуренции, а TGI — меньшую хвостовую задержку в интерактивном режиме с умеренной нагрузкой. Такие цифры нельзя переносить на другую модель и GPU, но вывод устойчив: лучший движок определяется профилем сервиса, а не общим рейтингом.

Модельный расчёт: один сервер или два

Рассмотрим условный внутренний RAG-помощник. Это не рыночная смета, а пример метода.

Допущения:

  • 18 000 запросов в месяц;
  • 70% ответов принимаются без существенной правки — 12 600 результатов;
  • целевой p95 TTFT не больше 3 секунд, p95 полного ответа — не больше 25 секунд;
  • полная месячная стоимость одного узла — 150 000 ₽, включая амортизацию, электричество, резервирование и сопровождение;
  • нагрузочный тест показал, что один узел держит SLA до concurrency 6;
  • рабочий пик достигает concurrency 10;
  • на одном узле только 80% принятых ответов укладываются в SLA, на двух — 97%.

Для одного узла число SLA-совместимых принятых результатов составит:

`12 600 × 0,80 = 10 080`.

Стоимость такого результата:

`150 000 / 10 080 ≈ 14,9 ₽`.

Для двух одинаковых узлов:

`12 600 × 0,97 = 12 222`,

`300 000 / 12 222 ≈ 24,5 ₽`.

Второй вариант дороже на результат, но выполняет SLA почти для всего потока. Покупать второй узел стоит не автоматически. Сначала проверьте, можно ли ограничить concurrency шлюзом, перенести фоновые задачи, сократить контекст, разделить длинные и короткие очереди или сгладить утренний всплеск.

Если бизнес допускает ожидание, один узел может быть рациональнее. Если ответ встроен в обслуживание клиента или работу оператора в реальном времени, хвостовая задержка имеет денежную цену: простои сотрудников, повторные запросы и отказ от инструмента.

Когда сравнивать с внешним API

Локальный контур имеет высокую фиксированную стоимость и низкую предельную стоимость дополнительного запроса до насыщения. API чаще превращает затраты в переменные. Поэтому точка безубыточности зависит не только от токенов, но и от загрузки.

Сравнивайте два сценария по одинаковой формуле:

`полная месячная стоимость / SLA-совместимые принятые результаты`.

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

Бурстовый сервис с несколькими часами нагрузки в неделю часто выгоднее начинать через API или гибрид. Стабильный дневной поток, чувствительные данные и высокая утилизация делают локальный вариант интереснее. Но низкая загрузка способна превратить собственный GPU в дорогой обогреватель с отличным бенчмарком.

Управленческий чек-лист перед закупкой

До запроса коммерческого предложения зафиксируйте:

  • рабочие p50 и p95 длины входа и выхода;
  • пиковый request rate и concurrency;
  • SLA по TTFT и полному ответу;
  • долю фоновых задач и правила приоритета;
  • accepted outcome rate и допустимую цену результата;
  • требования к данным, отказоустойчивости и времени восстановления;
  • сценарий перегрузки: очередь, ранний отказ или переход на внешний API.

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

Следующий шаг

Соберите 200–500 реальных обезличенных запросов и воспроизведите один рабочий пик. Начните с одного узла или даже арендованного тестового сервера, прогоните лестницу concurrency и найдите первую ступень, где p95 выходит за границу. Затем рассчитайте цену не токена и не запроса, а принятого результата внутри SLA.

Так решение о локальной LLM превращается из спора о видеокартах в обычное capacity planning: известная нагрузка, измеримый уровень сервиса, резерв и понятная экономика.