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

Зачем вообще масштабировать LLM-сервис
Пока запросов мало, один сервер с GPU обычно справляется. Но как только пользователей становится больше, сервис начинает "плыть": растет время ответа, появляются очереди, а в пиковые минуты возможны ошибки и таймауты. В итоге люди видят не умную модель, а медленную и нестабильную кнопку.
LLM-нагрузка почти никогда не растет ровно. Чаще это всплески: утром, перед дедлайнами, после рассылки, при запуске новой функции. Если планировать "на глаз", легко попасть в одну из двух ловушек: либо купить лишнее железо, либо недооценить пик и получить сбои в самый важный момент. Оба варианта стоят дорого, просто по-разному.
Норму лучше определить заранее и проверять регулярно. Смотрите не на "среднее", а на то, что чувствует пользователь и что платит бизнес:
- p95/p99 времени ответа (особенно для длинных ответов)
- длину очереди и время ожидания до старта генерации
- стабильность (таймауты, ошибки, перезапуски)
- стоимость одного запроса и загрузку GPU
Под эти цели и выбирают стратегию роста. Вертикальное масштабирование - усилить один узел (больше GPU, памяти, быстрее диски). Горизонтальное - добавить узлы и распределять запросы. На практике чаще всего получается гибрид: сначала "дотянуть" один сервер до разумного предела, а затем подключить второй-третий узел и навести порядок в очередях.
Простой пример: пилотный чат-бот для внутренней поддержки может жить на одном сервере. Но после подключения нескольких отделов очередь на генерацию начнет расти даже при той же модели. Это сигнал, что пора переходить от "одного мощного узла" к системе, которая держит пики без паники.
Какие метрики нужны, чтобы не гадать
Чтобы принимать решения о росте инференса, сначала договоритесь, что для вас значит "нормально". Без цифр легко переплатить за железо или, наоборот, получить очереди и жалобы.
Начните с базовых метрик, которые показывают реальную нагрузку и качество сервиса. Их стоит собирать по каждому эндпоинту и по каждому типу модели (если моделей несколько):
- RPS и количество одновременных запросов: сколько приходит и сколько "висит" в работе
- задержка p50 и p95: типичный опыт пользователя и "хвосты", которые обычно и болят
- длина очереди и время ожидания в очереди: главный сигнал, что уперлись в мощность, а не в сеть
- ошибки и отмены: 5xx, таймауты, а также клиентские отмены при долгом ожидании
- использование GPU и CPU: загрузка, память, а также стабильность (скачки важнее среднего процента)
Для LLM отдельно фиксируйте производственные метрики, иначе p95 так и останется загадкой. Считайте токены в секунду на генерации, среднюю длину промпта и ответа, долю длинных запросов. Часто именно редкие длинные промпты делают очередь, даже если RPS выглядит скромно.
Полезно разделить трафик по сценариям: чат (длинные диалоги), суммаризация (длинный вход), классификация (коротко и быстро), RAG (зависимость от поиска и базы). Тогда вы увидите, что "медленно" не всегда из-за модели: RAG может тормозить на данных.
Отдельно отмечайте окна пиков. Например, в организациях с отчетными периодами или массовыми рассылками нагрузка резко растет в определенные часы. В проектах системной интеграции это часто видно на этапе пилота: дневной RPS стабильный, а p95 взлетает в моменты пакетной обработки. Именно эти пики должны быть основой решения о переходе от одного узла к пулу GPU.
Когда достаточно одного сервера
Один узел часто закрывает пилот и даже небольшой продакшен, если нагрузка предсказуемая, а модель одна. Для масштабирования важно сначала убедиться, что вы не "масштабируете на всякий случай".
Признаки, что одного сервера пока хватает:
- p95 стабильно укладывается в цель даже в пиковые часы
- очередь почти всегда пустая (или короткая и быстро рассасывается)
- GPU не упирается в 100% на длинных отрезках, есть запас по VRAM
- есть запас по RAM и диску, нет активного свопа
- скорость генерации (токены в секунду) не падает со временем
Если показатели на грани, часто помогает не кластер, а усиление узла: GPU с большей VRAM, больше RAM, быстрый NVMe для логов и кешей, а также более подходящий профиль инференса (например, меньший контекст или ограничение максимальных токенов ответа). Обычно это дешевле и проще, чем сразу тянуть оркестрацию.
Понять, что вы уперлись именно в GPU, можно по сочетанию симптомов: загрузка высокая, VRAM почти полностью занята, при росте параллельности резко падают токены в секунду, а p95 "плывет". Если при этом появляется своп или растет I/O, проблема может быть не в GPU, а в памяти и диске.
Риски одного узла понятные: это единая точка отказа и сложные обновления (выкатка новой модели или драйвера часто означает простой). Например, даже надежный сервер уровня GSE S200 придется останавливать на обслуживание, если нет второго узла для переключения нагрузки.
Когда пора переходить к пулу GPU и нескольким узлам
Один мощный сервер часто закрывает пилот: одна модель, понятный трафик, небольшая очередь в пиковые минуты терпима. Но как только нагрузка становится "неровной", а ожидания пользователей строже, почти всегда упираетесь в параллельность и предсказуемость задержек.
Главный сигнал - пик не помещается в один узел без очереди. Если в спокойное время все быстро, но в часы активности запросы копятся, растет время до первого токена, а p95 скачет, добавление второго GPU на том же сервере не всегда спасает. Распределение по нескольким узлам чаще дает более ровный результат: вы добавляете вычисления и снижаете риск, что один процесс или одна модель "забьет" весь ресурс.
Практические признаки, что пора в несколько узлов
Решение обычно назревает, когда совпадает несколько пунктов:
- в пике очередь стабильно растет, и вы не удерживаете p95 на приемлемом уровне
- параллельных запросов много, и сервис начинает дергаться от небольших всплесков
- у вас несколько моделей или режимов (например, чат и суммаризация), и они мешают друг другу на одном GPU
- появилось требование высокой доступности: обслуживание без простоя при обновлениях и при отказе железа
Если вы обслуживаете разные группы пользователей, выгодно разделить потоки: критичные запросы (например, внутренняя поддержка или операционные сценарии) отделить от фоновых задач. В кластере это проще сделать очередями и выделенными узлами под разные модели.
Несколько узлов - это не только про скорость, но и про надежность. Для организаций, где простои недопустимы (госструктуры, финсектор, крупные предприятия), возможность выводить узел в ремонт и продолжать работу сервиса становится отдельным, вполне бизнесовым аргументом.
Как разбить сервис на потоки и приоритеты
Когда трафик растет, главный риск не в том, что модель станет медленной, а в том, что все начнет мешать всему: длинные запросы забивают очередь, один клиент съедает GPU, а срочные задачи ждут вместе с фоновыми. Разделение потоков часто дает эффект раньше, чем добавление железа, и заметно упрощает дальнейший рост.
Начните с разделения по типам задач. Если у вас есть и короткие ответы (чат, подсказки оператору), и тяжелые задачи (суммаризация отчетов, анализ больших документов), не гоните их через один и тот же коридор. Самый простой шаг - разные эндпоинты или очереди: "быстрый режим" с жесткими лимитами и "точный режим" с более длинным контекстом и временем ожидания.
Иногда выгодно держать не одну модель на весь трафик, а несколько профилей: меньшую и быструю для типовых вопросов и более точную для сложных. Например, запросы "помоги сформулировать ответ клиенту" идут в быстрый режим, а "подготовь разбор обращения с цитатами" - в точный.
Чтобы мульти-тенантность не превратилась в хаос, задайте приоритеты и лимиты заранее. Обычно достаточно:
- разделить клиентов/сервисы на классы и дать им разные очереди
- ввести квоты по ключу API или проекту: максимум одновременных запросов и бюджет токенов в минуту
- ограничить тяжелые параметры: длину промпта, max_tokens, число параллельных генераций
- добавить таймауты и понятные ответы об ограничениях, чтобы клиенты могли повторить запрос позже
- для крупных клиентов выделить отдельный пул или фиксированную долю мощности
Такой подход особенно полезен, когда один LLM-сервис обслуживает несколько подразделений (например, поддержку, аналитику и внутренние базы знаний) и всем нужен предсказуемый отклик.
Пошаговый план масштабирования под реальную нагрузку
Расти проще, когда вы опираетесь на цифры, а не на ощущения.
-
Зафиксируйте SLO. Запишите целевые p95 по задержке, требуемую доступность и сколько вы готовы платить за один запрос. Иначе легко улучшать сервис, который уже достаточно хорош.
-
Соберите профиль нагрузки. Важны пики: утренние всплески, пакетные задачи, сезонность. Отдельно отметьте долю коротких и длинных запросов - они по-разному забивают GPU и очередь.
-
Выберите стратегию роста. Вертикальное усиление (больше GPU и памяти в одном узле) обычно проще на старте. Горизонтальное (несколько узлов) нужно, когда вы упираетесь в лимиты одного сервера или хотите устойчивость. Часто получается гибрид: сильный основной узел плюс несколько дополнительных.
-
Внедрите очереди и лимиты до покупки новых GPU. Ограничьте параллелизм, разделите интерактивные запросы и фоновые, добавьте таймауты и понятные правила деградации (например, меньше контекста или более короткий ответ при перегрузе).
-
Добавьте второй узел ради отказоустойчивости, и только затем расширяйте пул. Два узла - практичный баланс: обновления без простоя и возможность пережить падение одного сервера.
Пример: пилот на одном узле (например, на rack-сервере уровня GSE S200) выдерживает будни, но в дни отчетности p95 резко растет. Сначала вводите приоритеты и очередь для пакетных задач, затем добавляете второй узел, и только когда пики стабильно упираются в лимит, расширяете пул GPU.
Оркестрация: когда нужна, и что выбрать на практике
Оркестрация реально помогает, когда у вас больше одного узла и важны обновления без простоя. Пока сервис живет на одном сервере и релизы редкие, часто проще держать все вручную: меньше движущихся частей, меньше неожиданных отказов из-за настроек.
Переход обычно оправдан, когда появляется пул GPU, несколько инстансов инференса, отдельные очереди и разные версии модели. Тогда вы занимаетесь не только "запустить", но и "держать в строю" 24/7, и здесь вопрос упирается в надежность.
Что дает Kubernetes на практике
Kubernetes полезен тем, что закрывает типовые проблемы эксплуатации:
- размещение сервисов по узлам и учет ресурсов (CPU, RAM, GPU)
- перезапуск при сбое и самовосстановление после падений
- горизонтальное масштабирование (HPA) по метрикам нагрузки
- роллинг-обновления без простоя и быстрый откат версии
- политики доступа и изоляция между командами и сервисами
Когда достаточно более простого подхода
Если у вас 2-3 сервера, редкие релизы и понятная нагрузка, часто хватает systemd, Docker Compose или простого оркестратора на уровне VM. Это подходит, например, для внутреннего ассистента, где модель обновляют раз в месяц и нет строгих SLA.
Какой бы вариант вы ни выбрали, продумайте базовую "гигиену": хранение секретов (ключи, пароли), управление конфигами по средам (dev, test, prod) и версионирование образов. В проектах системной интеграции, типичных для GSE.kz, эти детали часто важнее выбора конкретной платформы: они снижают риск простоя и ошибок при обновлениях.
Балансировка и контроль очередей
Балансировка нужна не ради красоты, а чтобы пользователи получали предсказуемое время ответа, даже когда нагрузка прыгает. Здесь важно разделять две вещи: внешнее распределение запросов между узлами и внутреннюю маршрутизацию внутри сервиса.
Внешняя балансировка отвечает за то, куда попадет запрос: на какой сервер, в какую зону, на какой пул GPU. Если один узел перегружен, балансировщик должен увести трафик на другой, но только после проверки, что он жив и готов принимать новые запросы.
Внутренняя маршрутизация часто дает больший эффект. Один и тот же сервис может обслуживать разные модели, разные размеры контекста и разные классы пользователей. Например, короткие запросы поддержки можно отправлять в отдельный пул, а длинные аналитические промпты - в пул, где больше памяти и ниже конкуренция.
Sticky или non-sticky? Привязка к узлу полезна, когда вы активно используете кеши (например, повторяющиеся префиксы, прогретая модель, локальный кеш токенизации). Но sticky усложняет восстановление при сбоях и может усиливать перекос, если один клиент генерирует много трафика.
Чтобы переживать всплески, заранее зафиксируйте простые правила:
- rate limit по ключам или пользователям, чтобы один источник не забил очередь
- ограничение длины запроса и максимальных токенов ответа
- очередь с понятным лимитом и деградацией (например, 429 или более короткий ответ)
- таймауты на генерацию и на ожидание в очереди
- circuit breaker, чтобы не тянуть всех вниз, когда один узел или модель подвисли
Это особенно важно, если вы разворачиваете инференс на нескольких серверах (например, в стойке на базе GPU-серверов уровня GSE S200 Series) и хотите ровную работу без ручного "дежурства".
Кеши, память и данные: что чаще всего тормозит
Частая причина, почему кажется, что не хватает GPU, на деле в памяти и данных. Перед тем как расширять пул, проверьте, сколько времени уходит не на генерацию, а на подготовку запроса: сбор контекста, чтение истории диалога, поиск документов, запись логов.
Кеширование результатов помогает, когда запросы повторяются и ответ не зависит от "живых" данных. Например, справочные вопросы, шаблонные письма, ответы на типовые регламенты. Но если ответ должен учитывать текущие цены, остатки или статус заявки, кеш быстро становится источником ошибок.
KV-cache и кеш префиксов дают эффект, когда у многих запросов общий старт: одинаковая системная инструкция, один и тот же скелет промпта, единый шаблон чата. Тогда модель меньше пересчитывает одно и то же. Цена этого - более сложная эксплуатация: больше потребление памяти и выше риск ошибок, если неправильно разделять сессии.
Хранение контекста диалогов часто незаметно раздувает задержку. Лучше заранее договориться о правилах: что хранить, как долго и где.
Полезный минимум, чтобы не упереться в данные:
- ограничьте длину истории (например, последние N сообщений или N токенов)
- введите срок хранения и политику удаления для разных типов чатов
- разделяйте контекст пользователя и служебные подсказки (их проще кешировать)
- следите за памятью: большие промпты и длинные ответы быстрее съедают RAM и GPU
Логи и трассировка нужны не для отчетности, а чтобы видеть, где теряется время: очередь, чтение контекста, токенизация, генерация, постобработка. Без этого легко купить второй сервер, а узким местом останется медленное хранилище или слишком подробные синхронные логи.
Пример сценария: рост сервиса от пилота до кластера
Пилот часто начинается просто: один сервер с одной моделью и одним GPU обслуживает внутренний чат для сотрудников. Запросов мало, пиков почти нет, а задержку можно терпеть, потому что главное - проверить пользу и качество ответов.
Через пару месяцев сервис попадает в рабочие процессы, и нагрузка внезапно растет в 10 раз. Пики появляются утром и после обеда, а p95 становится заметной проблемой: очередь копится, пользователи повторяют запросы, и это еще сильнее нагружает GPU. В этот момент рост начинается не с покупки железа, а с наведения порядка в потоке запросов.
Сначала добавляют очередь и простые правила приоритетов: интерактивный чат выше, чем пакетная обработка. Затем появляется второй узел, чтобы переживать пики и обновления без простоя. Параллельно разделяют модели по ролям: небольшая модель для быстрых ответов и классификации, большая - только для сложных запросов.
Чтобы прикинуть, сколько GPU нужно, опирайтесь на измеримое:
- цель по p95, например 3-5 секунд на ответ
- средняя длина: сколько токенов на вход и на выход
- реальная скорость модели: токенов в секунду на одном GPU
- пиковый поток: запросов в секунду в худшие 15 минут
Дальше оценка простая: (пиковые запросы) x (токены на запрос) делите на (токены в секунду на GPU) и закладывайте запас, иначе p95 поплывет при любом всплеске.
Когда узлов становится несколько, обновления планируют заранее. Практичный вариант - канареечный релиз: 5-10% трафика уходит на новую версию, сравниваются p95 и ошибки, и только потом идет полный раскат. Откат должен быть быстрым: отдельный тег образа, старая конфигурация под рукой и возможность вернуть трафик на стабильные узлы за минуты. На таких этапах удобны серверы класса GSE S200 под кластер и поддержка 24/7, чтобы не останавливать сервис в критичные часы.
Частые ошибки при масштабировании LLM-инференса
Первая ошибка - смотреть на среднюю нагрузку и успокаиваться. LLM-сервисы ломаются на пиках: утро понедельника, массовая рассылка, отчеты в конце месяца. Если планировать по среднему, в реальности получите очереди, таймауты и жалобы, даже когда графики "в целом нормальные".
Вторая типовая проблема - добавлять узлы для мощности, но не вводить правила игры. Без лимитов на параллельность, без явной очереди и без контроля времени выполнения запросы начинают мешать друг другу. В итоге кластер GPU выглядит большим, а пользователи видят нестабильность.
Часто недооценивают разницу между легкими и тяжелыми запросами. Один пользователь просит короткий ответ, другой запускает длинную генерацию с большим контекстом. Если все идет в одну очередь, тяжелые запросы вытесняют остальные, и задержки растут для всех.
Самые дорогие ошибки обычно такие:
- планирование по среднему, без учета пиков и хвостов задержек
- добавление узлов без очередей, лимитов и понятного backpressure
- одна общая очередь для разной сложности запросов, без приоритетов
- отсутствие расчета себестоимости на 1000 запросов и лимитов по бюджету
- выбор оркестрации "потому что так делают все", без конкретной задачи
Простой пример: вы подняли сервис на одном сервере, потом добавили второй и включили балансировку. В пике оба узла уходят в длинные ответы, а короткие запросы поддержки ждут по 40 секунд. Решение часто не в "еще одном GPU", а в раздельных очередях, квотах и понятных SLO. Если вы собираете инфраструктуру в локальном контуре на оборудовании GSE.kz, эти правила особенно важны: железо дает мощность, а предсказуемость дает дисциплина в очередях и лимитах.
Короткий чеклист перед переходом к кластеру
Переход к кластеру часто делают слишком рано: сначала добавляют сложность, а потом выясняют, что проблема была в таймаутах или очереди. Этот короткий список помогает понять, готовы ли вы к следующему шагу.
Перед тем как вкладываться в несколько узлов и оркестрацию, проверьте пять вещей:
- есть понятные SLO (например, p95 задержки и доля ошибок) и замеры на реальном трафике, а не на синтетике
- настроены лимиты и защита от всплесков: таймауты, ограничение параллелизма, ретраи только там, где они безопасны, и понятное поведение при перегрузке
- вы точно знаете узкое место: GPU, CPU (токенизация), сеть, диск (логирование) или память (контекст и кеши)
- продуман отказоустойчивый минимум: второй узел или резерв, план обновлений без простоя, сценарий на случай зависания GPU или процесса
- есть расчет емкости и денег на 3-6 месяцев: ожидаемые запросы в минуту, средний размер контекста, токены на ответ, GPU-часы
Практичный тест: если вы уже сейчас вынуждены резать длину промпта или отключать части функций, чтобы держать p95, кластер может быть оправдан. Если же p95 плавает из-за всплесков и отсутствия контроля очередей, сначала исправьте это на одном сервере.
Когда дойдете до закупки железа, заранее проверьте, что у поставщика есть понятная поддержка и сроки. Для критичных систем удобнее, когда есть локальное производство и сервисная сеть, как у GSE.kz: это снижает риски простоя при росте нагрузки.
Следующие шаги: как перейти к кластеру без лишних затрат
Соберите профиль нагрузки хотя бы за неделю. Важно видеть пики по часам, долю длинных запросов, время ожидания в очереди и реальные требования пользователей. После этого зафиксируйте SLO с бизнесом: например, 95% ответов быстрее N секунд и не более M ошибок на 1000 запросов.
Дальше часто имеет смысл идти не в большой кластер сразу, а в минимальный и понятный. Нередко достаточно конфигурации из 2 узлов, где роли простые: один узел держит основной инференс, второй страхует и подхватывает пики или обновления.
Практичный порядок действий:
- определите, какие модели нужны в проде и какие можно оставить для тестов
- разнесите очереди по типам запросов: интерактивные, пакетные, внутренние
- выберите простую маршрутизацию: по приоритету и по доступной памяти GPU
- добавьте лимиты на длину контекста и таймауты, чтобы один запрос не блокировал остальных
- проведите нагрузочный тест на реальных промптах и зафиксируйте новые пороги
Заранее решите, что именно будете разделять при росте: модели по узлам, очереди по узлам или и то и другое. Например, чат для операторов можно держать на быстрых интерактивных очередях, а ночные отчеты отправлять в отдельную очередь, чтобы они не забивали сервис днем.
Если нужно подобрать серверы, сеть, схему отказоустойчивости и питание в стойке, подключайте интегратора. В качестве базы можно рассмотреть локально произведенные серверы с поддержкой на месте, например стойковые серверы GSE.kz серии S200, а рабочие станции и ПК использовать для вспомогательных задач. Удобно, когда рядом есть системная интеграция и 24/7 поддержка, особенно на этапе первых инцидентов и тюнинга.
FAQ
По каким признакам понять, что LLM-сервис уже пора масштабировать?
Ориентируйтесь на пользовательский опыт и пики. Если в часы активности p95/п99 задержки растут, появляется очередь и увеличиваются таймауты, значит вы уперлись в предел узла, даже если «средняя» нагрузка выглядит нормально.
Какие метрики обязательно собирать, чтобы не гадать?
Для старта обычно достаточно RPS и одновременных запросов, p50/p95 задержки, длины очереди и времени ожидания, доли ошибок и отмен. Для LLM добавьте токены в секунду, среднюю длину промпта и ответа и долю длинных запросов, иначе причины «плохого p95» останутся неясными.
Почему смотреть на среднее время ответа опасно?
Хвосты показывают то, что больше всего раздражает пользователей: редкие, но очень медленные ответы. Среднее время ответа может быть «красивым», пока небольшая доля длинных запросов делает очередь и провоцирует повторные попытки, из-за чего сервис начинает деградировать каскадом.
Когда одного сервера действительно достаточно?
Если p95 стабильно укладывается в цель даже в пиковые окна, очередь почти всегда пустая, а у GPU есть запас по памяти и загрузке, один сервер обычно справится. В этом случае выгоднее улучшить профиль инференса и лимиты, чем сразу усложнять систему кластером.
Что выбрать: вертикальное или горизонтальное масштабирование?
Вертикальное масштабирование — когда вы усиливаете один узел: больше VRAM, больше RAM, быстрее диски, иногда второй GPU в том же сервере. Горизонтальное — когда добавляете узлы и распределяете запросы, чтобы держать пики и обновляться без простоя; на практике часто начинают с вертикального и переходят к горизонтальному, когда упираются в предсказуемость задержек и доступность.
Какие самые частые причины перехода к нескольким узлам и пулу GPU?
Чаще всего — когда пик не помещается без очереди и p95 «плывет», хотя в спокойные часы все быстро. Еще один частый повод — несколько моделей или режимов, которые мешают друг другу на одном GPU, и требование обслуживать сервис без простоя во время обновлений и ремонтов.
Как не дать длинным запросам забивать очередь и ломать p95?
Разделите потоки хотя бы на «быстрые» интерактивные и «тяжелые» фоновые, иначе длинные генерации будут вытеснять все остальное. Дальше добавьте приоритеты и лимиты по ключу API или проекту, чтобы один клиент не мог забить очередь и ухудшить сервис для всех.
Когда стоит внедрять Kubernetes, а когда можно обойтись проще?
Если у вас 2–3 сервера, редкие релизы и нет строгих требований по обновлениям без простоя, иногда достаточно простого управления процессами и контейнерами. Kubernetes имеет смысл, когда нужно устойчиво держать несколько инстансов, делать безопасные обновления и автоскейл по метрикам, а также изолировать разные сервисы и команды.
Почему сервис может тормозить, даже если GPU вроде бы хватает?
Потому что узким местом может быть не генерация, а подготовка запроса: RAG-поиск, чтение истории диалога, токенизация, логирование и запись в хранилище. Перед покупкой GPU полезно разложить задержку по этапам, иначе вы можете расширить вычисления, но оставить прежнюю «пробку» в данных или I/O.
Какие ошибки чаще всего делают при масштабировании LLM-инференса?
Обычно ошибаются, планируя по среднему вместо пиков и хвостов, и добавляя железо без правил очередей и backpressure. Еще одна проблема — одна общая очередь для разной сложности запросов и отсутствие лимитов по длине промпта и max_tokens, из-за чего дорогие запросы вытесняют быстрые и создают ощущение нестабильности.