Почему «процесс жив» ещё не означает «модель готова»

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

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

Надёжный контур разделяет три вопроса:

  • запустился ли сервер и загрузил ли модель;
  • способен ли процесс продолжать работу;
  • готов ли он принять новый запрос прямо сейчас.

Kubernetes называет эти проверки startup, liveness и readiness probes. Startup даёт тяжёлому сервису время загрузить веса. Liveness предназначен для состояния, из которого нужен перезапуск, например зависания. Readiness временно убирает экземпляр из балансировки, не убивая его. В документации Kubernetes отдельно предупреждается: неверная liveness-проверка под нагрузкой может вызвать каскадные перезапуски.

Архитектура с управляемым входом

Минимально устойчивый контур выглядит так:

1. Клиент обращается не к серверу модели, а к единому шлюзу.
2. Шлюз проверяет размер запроса, права, дедлайн и идентификатор операции.
3. Admission control решает: принять запрос, отправить в ограниченную очередь, перевести в асинхронный режим или быстро отказать.
4. Балансировщик выбирает только готовую реплику.
5. Сервер модели публикует метрики очереди, KV-кэша, TTFT и ошибок.
6. После превышения порога circuit breaker прекращает накапливать работу быстрее, чем сервис способен её обработать.
7. При длительной деградации включается заранее разрешённый резервный сценарий.

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

Envoy документирует ограничения на активные соединения, ожидающие запросы, одновременные запросы и повторы. Эти механизмы работают как circuit breaker на сетевом уровне. Для локального LLM особенно полезны лимиты ожидающих запросов и retry budget: повтор не должен удваивать нагрузку во время аварии.

Какие сигналы использовать для readiness

Проверка готовности должна быть дешёвой и отражать способность принять новую работу. Один HTTP 200 от процесса недостаточен.

Практичная readiness-политика может учитывать:

  • модель полностью загружена и прошла короткий контрольный вызов;
  • число ожидающих запросов ниже порога;
  • KV-кэш не находится у критического предела;
  • нет серии ошибок выделения памяти;
  • зависимые локальные сервисы доступны;
  • реплика не находится в режиме дренирования;
  • температура и аппаратные ошибки GPU не требуют вывода узла.

vLLM публикует Prometheus-метрики через `/metrics`: число running и waiting requests, использование KV-кэша, queue time, time to first token, inter-token latency, число preemption и результаты запросов. Они полезнее для управления трафиком, чем абстрактная загрузка GPU.

Readiness не обязана меняться по одному мгновенному значению. Лучше использовать два порога и выдержку времени: например, убрать реплику после устойчивого превышения очереди и вернуть только после снижения нагрузки. Иначе экземпляр начнёт мигать между состояниями быстрее, чем оператор успеет открыть график.

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

Что делать с запросом, когда мест нет

У системы должно быть несколько заранее определённых ответов на перегрузку:

  • быстрый 429 или 503 с временем безопасного повторения;
  • ограниченная очередь с дедлайном;
  • асинхронное задание с идентификатором и статусом;
  • сокращённый контекст или лимит генерации для некритичного сценария;
  • меньшая локальная модель для разрешённых классов задач;
  • детерминированный шаблон без генерации;
  • передача человеку.

Выбор зависит от процесса. Черновик письма может подождать, поиск по базе знаний — перейти на более короткий ответ, а платёжное действие не должно незаметно сменить модель или повториться. Резервный сценарий обязан сохранять права, журналирование и бизнес-ограничения.

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

Почему повторы опаснее, чем кажутся

Когда клиент получает тайм-аут, он часто отправляет запрос снова. Прокси может сделать то же самое. Если первая операция продолжает выполняться, нагрузка удваивается. Для генерации это лишние токены; для агента с инструментами — риск повторного письма, заявки или изменения в CRM.

Поэтому нужны:

  • стабильный idempotency key для логической операции;
  • ограничение числа повторов и общий дедлайн;
  • случайная задержка между повторными попытками;
  • запрет автоматического retry после начала побочного действия;
  • журнал состояния операции, доступный через отдельный запрос;
  • retry budget на уровне шлюза.

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

Две реплики — не всегда высокая доступность

Если обе реплики стоят на одном GPU-узле, используют один источник питания и одновременно загружают веса с одного диска, отказоустойчивость ограничена. Если они обслуживают разные версии модели или токенизатора, ответы становятся непредсказуемыми.

Для реальной изоляции проверьте:

  • разные узлы или хотя бы независимые GPU;
  • закреплённые и проверенные артефакты модели;
  • одинаковые токенизаторы, шаблоны и параметры;
  • доступность весов без внешнего интернета;
  • разнесение по питанию и сети там, где простой дорог;
  • прогрев новой реплики до включения в трафик;
  • дренирование старой реплики до остановки;
  • достаточную ёмкость оставшегося контура при потере одного узла.

Последний пункт чаще всего забывают. Две реплики по 70% загрузки не дают N+1: после отказа одна получает 140% спроса и героически падает следом.

Модельная экономика доступности

Допустим, локальный помощник обрабатывает 1 000 запросов в рабочий день, а час недоступности создаёт 120 ручных операций по 8 минут. При внутренней стоимости часа сотрудника 800 рублей простой стоит примерно 12 800 рублей в час, без учёта сорванных сроков.

Резервная реплика стоимостью 80 000 рублей в месяц окупается только при предотвращении более 6,25 часа такого ущерба. Но если шлюз, ограниченная очередь и корректная readiness-политика сокращают простой с четырёх часов до тридцати минут без второй полной GPU-мощности, архитектурная дисциплина выгоднее покупки железа.

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

Пилот устойчивости за неделю

1. Запишите целевые p95 TTFT, максимальную очередь и допустимое время отказа.
2. Разделите startup, readiness и liveness; не используйте один endpoint для трёх смыслов.
3. Ограничьте число ожидающих запросов и повторов на шлюзе.
4. Настройте дренирование и прогрев реплик.
5. Определите резервный режим для каждого класса задач.
6. Проведите контролируемые сбои: медленная загрузка, заполнение KV-кэша, остановка GPU-узла, недоступность весов, всплеск длинных запросов.
7. Проверьте, что важные операции не дублируются, а запрещённые данные не уходят во внешний контур.
8. Посчитайте время восстановления и потерянные принятые результаты.

Что считать успехом

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

Для малого и среднего бизнеса разумный порядок такой: сначала метрики и конечная очередь, затем readiness и circuit breaker, после этого — резервная реплика. Иначе Внутрик получит второй сервер только для того, чтобы складывать на него вторую очередь. Стажёр доволен, SLA — не очень.