Почему «процесс жив» ещё не означает «модель готова»
Локальная 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 — не очень.
