MAATRIX / Блог / Автомасштабирование ИИ-сервиса по нагрузке

Автомасштабирование ИИ-сервиса по нагрузке

MAATRIX

Автомасштабирование обычного API-сервера — решённая задача: нагрузка выросла, оркестратор поднял новый под, тот стартовал за секунды и уже принимает трафик. Примените ту же логику к сервису ИИ-инференса на GPU — и получите пользователей, которые ждут ответа заметно дольше обычного именно в момент всплеска, то есть ровно тогда, когда автомасштабирование должно было спасти положение. Дело не в том, что автомасштабирование ИИ-сервиса настроено криво: у него есть фундаментальное ограничение, которое не лечится тюнингом порогов, и его нужно закладывать в архитектуру заранее, а не выяснять постфактум по жалобам пользователей.

Почему обычное автомасштабирование не работает для ИИ так же хорошо

Стандартная модель горизонтального автомасштабирования (HPA в Kubernetes, Auto Scaling Group в облаках) строится на одном неявном допущении: новый экземпляр сервиса готов принимать трафик почти сразу после запуска. Для веб-приложения это допущение обычно верно — интерпретатор стартует, приложение слушает порт, проходит readiness-проверку за секунды. Оркестратор реагирует на рост метрики (CPU, RPS, очередь запросов), поднимает реплику, и она встаёт в строй быстрее, чем пользователь успевает заметить задержку.

ИИ-инференс на GPU ломает это допущение в самом основании. Прежде чем новый экземпляр сможет обработать хотя бы один запрос, он должен:

  • поднять контейнер и инициализировать драйверы GPU;
  • запустить сервер инференса (vLLM, TGI, Triton, llama.cpp-server и подобные);
  • скопировать веса модели с диска или сетевого хранилища в системную память;
  • перенести веса в видеопамять GPU и выполнить внутреннюю инициализацию (построение CUDA-графов, компиляцию кернелов, прогрев kv-cache);
  • иногда — выполнить один-два холостых прогона, чтобы прогреть JIT-компиляцию и убедиться, что первый реальный запрос не получает аномальную задержку.

Это и называется холодным стартом модели: интервал между командой «поднять экземпляр» и моментом, когда экземпляр реально готов отвечать. Его длительность зависит от размера модели, скорости хранилища, пропускной способности между CPU и GPU и от того, использует ли сервис оптимизации вроде ленивой загрузки весов — но в любом сценарии это не секунды, а заметное, измеримое в разы большее время, чем старт обычного веб-процесса. Точную цифру для вашей модели и инфраструктуры даст только замер на своём железе — она сильно разнится в зависимости от размера модели и скорости диска/сети, и разумно ожидать десятки секунд, а для тяжёлых моделей — больше.

Что в реальности происходит внутри окна холодного старта

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

Загрузка контейнера и рантайма. Если образ с сервером инференса тяжёлый (CUDA-тулкит, драйверы, python-зависимости), скачивание и распаковка на новом узле может занять существенное время, если образа ещё нет в локальном кэше. Лечится заранее запечённым образом на узлах-кандидатах или локальным registry mirror — но это оптимизация инфраструктуры, а не самого масштабирования.

Чтение весов с диска/сети. Модель на несколько десятков гигабайт нужно физически прочитать. Скорость упирается в пропускную способность диска (NVMe значительно быстрее сетевого хранилища) и в то, читаете ли вы с локального кэша модели на узле или тянете её заново из объектного хранилища при каждом холодном старте — это самая частая причина, почему холодный старт «внезапно» оказывается намного дольше ожидаемого.

Перенос в VRAM и инициализация движка инференса. Даже когда веса уже в памяти узла, их нужно скопировать в видеопамять GPU через шину PCIe (или NVLink), и сервер инференса должен построить внутренние структуры: страничный kv-cache, скомпилировать кернелы под конкретную конфигурацию батчей и длин контекста. У некоторых движков первая компиляция кернелов под новую конфигурацию — заметный источник задержки, отдельный от самой загрузки весов.

Прогрев. Многие команды дополнительно прогоняют один-два синтетических запроса перед тем, как пометить под как ready для балансировщика — иначе первый реальный пользовательский запрос ловит на себе всю стоимость ленивой инициализации.

Здесь и кроется коренное отличие от веб-приложения: у API-сервера почти весь «холодный старт» — это запуск интерпретатора и подключение к базе, операции с константной, обычно небольшой стоимостью независимо от размера приложения. У ИИ-инференса стоимость холодного старта растёт вместе с размером модели, а масштабирование в первую очередь применяется именно к тяжёлым моделям — проблема острее всего именно там, где автомасштабирование нужнее всего.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть ИИ на сервере

Практическое следствие: задержка реакции на всплеск

Разберём хронологию всплеска нагрузки на условном примере, чтобы стало видно, где теряется время.

  1. Нагрузка начинает расти — пользователи присылают больше запросов, чем текущие реплики успевают обработать.
  2. Метрика (очередь запросов, задержка p95, загрузка GPU) пересекает порог автомасштабирования — но не мгновенно: оркестратор опрашивает метрики с некоторым интервалом, и нужно несколько последовательных измерений выше порога, чтобы не реагировать на случайный шум.
  3. Оркестратор командует поднять новую реплику.
  4. Новая реплика проходит холодный старт целиком — контейнер, загрузка весов, инициализация.
  5. Реплика проходит readiness-проверку и получает трафик от балансировщика.

Шаги 2 и 4 — это и есть накопленная задержка реакции. Пока она идёт, весь избыточный трафик обслуживают уже существующие (перегруженные) реплики — именно в этом окне пользователи получают задержки выше обычных или ошибки таймаута. К моменту, когда новая реплика с загруженной моделью готова, исходный всплеск может уже частично схлынуть сам — новая мощность приходит с опозданием, которое для веб-приложения было бы незаметным, а для ИИ-инференса становится ощутимой частью пользовательского опыта именно в пиковые моменты.

Это не баг конкретной настройки автомасштабирования, а системное свойство архитектуры «холодный экземпляр по требованию». Убрать его совсем нельзя — можно только смягчить одним из трёх подходов ниже, у каждого свой компромисс.

Подход 1: минимальный резерв готовых экземпляров

Самый простой и надёжный способ смягчить проблему — не масштабироваться до нуля даже при простое. Держите minReplicas больше нуля: N экземпляров с уже загруженной моделью работают постоянно, готовые принять трафик без всякой задержки холодного старта, а автомасштабирование добавляет дополнительные экземпляры только сверх этого минимума при росте нагрузки.

Пример конфигурации HPA в Kubernetes с ненулевым минимумом:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: llm-inference-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: llm-inference
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Pods
      pods:
        metric:
          name: inference_queue_depth
        target:
          type: AverageValue
          averageValue: "5"
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
        - type: Pods
          value: 2
          periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300

Обратите внимание на асимметрию в behavior: масштабирование вверх реагирует быстро (без окна стабилизации), вниз — медленно, с окном в 5 минут. Цена лишнего простаивающего экземпляра на несколько минут почти всегда меньше цены преждевременного схлопывания мощности перед следующим всплеском.

Плата прямая и предсказуемая: N экземпляров с GPU оплачиваются постоянно, даже без реального трафика. Для сервисов, где GPU-время — основная статья расходов, это ощутимо увеличивает счёт по сравнению с гипотетическим масштабированием до нуля. Решение оправдано, если задержка холодного старта неприемлема для сценария (интерактивный чат, голосовой ассистент в реальном времени) — и не оправдано, если у сервиса есть длинные окна гарантированного простоя (например, ночью), когда содержание резерва — чистые потери.

Подход 2: предиктивное масштабирование по известным закономерностям

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

Практически это реализуется расписанием, а не только реактивной метрикой. Например, cron-задача или scheduled scaling в облачном автоскейлере:

# CronJob, поднимающий minReplicas перед утренним пиком
apiVersion: batch/v1
kind: CronJob
metadata:
  name: scale-up-before-peak
spec:
  schedule: "30 7 * * 1-5"  # в 07:30 по будням, за 30 минут до пика в 08:00
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: kubectl-scale
              image: bitnami/kubectl
              command:
                - kubectl
                - patch
                - hpa
                - llm-inference-hpa
                - --type=merge
                - -p
                - '{"spec":{"minReplicas":6}}'
          restartPolicy: OnFailure

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

Слабое место подхода честное: он работает ровно настолько хорошо, насколько предсказуема ваша нагрузка. Если закономерность нарушается (нестандартный день, вирусный всплеск, маркетинговая кампания без предупреждения инженеров), предиктивная схема не спасает — по необычному всплеску она реагирует так же, как обычное реактивное автомасштабирование, со всей той же задержкой холодного старта. Это дополнение к реактивному масштабированию, а не его замена: расписание закрывает известные пики, а реактивный HPA поверх него остаётся настроенным на случай отклонения от паттерна.

Подход 3: тёплый резерв — модель загружена, но не на трафике

Промежуточный вариант между «держать полностью активные экземпляры» и «поднимать с нуля» — тёплый резерв: экземпляры, где модель уже загружена в VRAM и сервер инференса полностью инициализирован, но которые не получают трафик от балансировщика, пока не понадобятся. Перевод такого экземпляра из тёплого резерва в рабочий пул — это не холодный старт целиком, а всего лишь регистрация в балансировщике и снятие с паузы, что на порядки быстрее полной загрузки весов.

Практически это реализуется через readiness-gate, отдельный от liveness: под жив, ресурсы GPU заняты моделью, но помечен как not-ready в балансировщике до явной команды. В Kubernetes это можно выразить через отдельный readiness-эндпоинт, управляемый внешним сигналом, а не автоматической проверкой готовности модели:

readinessProbe:
  httpGet:
    path: /readiness-gate  # возвращает 200 только после явного "разбужен"
    port: 8000
  periodSeconds: 5
livenessProbe:
  httpGet:
    path: /health  # проверяет, что модель загружена и процесс жив
    port: 8000
  periodSeconds: 15

Компромисс здесь тоньше, чем «деньги против скорости»: тёплый резервный экземпляр всё ещё занимает GPU (веса модели никуда не делись из VRAM), то есть стоит почти столько же, сколько активный, но не обрабатывает трафик. Экономия против подхода 1 минимальна или отсутствует. Плюс не в деньгах, а в гибкости — можно держать резерв под несколько моделей и быстро переключать, какая обслуживает трафик. Для сценария с одной моделью подход 1 обычно даёт тот же эффект проще, без лишнего слоя оркестрации.

Как выбрать стратегию под профиль вашей нагрузки

Сравнение трёх подходов по ключевым параметрам:

ПодходЗадержка при всплескеСтоимость простояСложность внедрения
Минимальный резерв (minReplicas > 0)Устранена в пределах резерва, есть только для трафика сверх негоПостоянная, пропорциональна размеру резерваНизкая — одна настройка HPA
Предиктивное масштабированиеУстранена для known-паттернов, полная для непредвиденных всплесковТолько в спланированные окна пикаСредняя — расписание + мониторинг отклонений
Тёплый резервМинимальная — только регистрация в балансировщикеБлизка к стоимости активных репликВысокая — отдельный readiness-gate, управление жизненным циклом

Прежде чем инвестировать в сложную схему автомасштабирования, честно оцените профиль своей нагрузки. Если она предсказуемая и относительно ровная — например, внутренний корпоративный сервис с известным числом пользователей и рабочими часами — автомасштабирование, возможно, вообще не первоочередная задача. Проще и надёжнее рассчитать фиксированную конфигурацию сервера под ожидаемый пик с разумным запасом (обычно 20-30% сверху), не усложняя архитектуру там, где динамическое масштабирование не решает реальную проблему, а лишь добавляет точки отказа.

Если нагрузка реально непредсказуема и пики резкие — публичный сервис, вирусный рост, сезонность без чёткого паттерна, — инвестиция в продуманное автомасштабирование оправдана, но требует именно продуманного проектирования, а не переноса шаблонов веб-автомасштабирования один в один. Рабочая комбинация на практике: ненулевой минимальный резерв под базовый трафик, реактивное масштабирование сверху для скачков, расписание для известных пиков. Отдельно стоит бороться за эффективность каждого GPU-инстанса батчингом запросов — чем выше пропускная способность одного экземпляра, тем реже приходится упираться в масштабирование вообще. И заранее продумайте, какой класс GPU-сервера закладывать под реплику: чем компактнее модель помещается в VRAM конкретной карты, тем короче холодный старт там, где он всё же случается.

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

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть ИИ на сервере

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Можно ли вообще устранить задержку холодного старта, а не просто смягчить?

Нет, полностью убрать нельзя — загрузка весов модели в VRAM физически требует времени. Можно только сократить его (быстрое хранилище, локальный кэш весов, меньшая модель) или спрятать за резервом готовых экземпляров.

Что произойдёт, если поставить minReplicas в 0 для экономии?

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

Как понять, какой размер минимального резерва достаточен?

Отталкивайтесь от базовой (не пиковой) нагрузки в обычные часы — резерв должен покрывать типичный трафик, а автомасштабирование сверху обрабатывать только отклонения. Точную цифру даёт наблюдение за реальным трафиком за несколько недель, не универсальная формула.

Стоит ли использовать облачные serverless-GPU платформы с масштабированием до нуля?

Они снимают операционную сложность настройки HPA и readiness-gate самостоятельно, но не устраняют физику холодного старта — та же загрузка весов происходит под капотом платформы. Смотрите документацию конкретной платформы на реальные цифры, а не на формулировки вроде «мгновенное масштабирование».

Нужно ли одно и то же автомасштабирование для инференса и для дообучения модели?

Нет. Обучение — обычно долгая предсказуемая нагрузка на фиксированном наборе ресурсов, где вопрос не «масштабировать по трафику», а «сколько GPU-часов выделить под прогон» — реактивное автомасштабирование здесь обычно не нужно.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →