Замена моделей — обычная практика
Если вы используете модель в продакшене, вы уже знаете о необходимости заменить чекпоинт или целое семейство моделей: экосистема открытых моделей развивается быстро, и кандидат обычно отлично выглядит на бенчмарках или обещает лучшую пропускную способность. Вы хотите показать его каждому пользователю без сбоев, а также иметь возможность откатиться, если результаты разочаруют. Обычные варианты заставляют идти на компромисс:
- Жесткая замена: укажите эндпоинт на новую модель, и все пользователи сразу получают к ней доступ. Если p95 вырастет вдвое, вы узнаете об этом из дашборда или, что еще хуже, от ваших клиентов, после чего начнете откат в условиях стресса на «холодно» запущенную старую модель.
- Самодельный поэтапный переход: второй деплой плюс скрипт, который постепенно увеличивает процент трафика, пока вы смотрите в Grafana, не забывая снова масштабировать старый деплой перед тем, как вернуть трафик назад.
Оба этих варианта предполагают участие человека в качестве механизма безопасности. Развертывания переносят этот механизм в саму платформу: вы описываете источник, цель, шаги и то, что означает «нормальное состояние», и платформа выполняет этот план на каждом этапе.
Как работают развертывания
Развертывание переносит трафик между двумя деплоями на одном эндпоинте: источником (тем, что обслуживает запросы сейчас) и целью (тем, что вы хотите запустить завтра). Вы выбираете одну из трех стратегий:
- Canary (Канареечная): трафик перемещается по заданным вами процентным шагам (например, 10% → 50% → 100%; стандартная шкала: 5% → 25% → 50% → 100%) с периодом ожидания и опциональной проверкой метрик между шагами.
- Blue-green (Сине-зеленая): единый контролируемый переход 0% → 100%. Это можно рассматривать как одношаговый канареечный релиз.
- Rolling (Скользящая): замена «на месте» по принципу «реплика за репликой», сохраняющая общую емкость. Лучше всего подходит при ограниченных ресурсах, особенно для изменений конфигурации той же модели, не требующих перенаправления трафика.
Вот что происходит внутри каждого канареечного шага:
Мы выбрали этот порядок намеренно; каждый пункт предотвращает определенный класс инцидентов:
- Целевой деплой масштабируется до того, как перенаправляется какой-либо трафик. Отсутствие емкости для нового деплоя означает отсутствие перенаправленных запросов: развертывание сначала ждет.
- Проверка здоровья (health gate) выполняется до перенаправления трафика. Трафик поступает только на те реплики, чей движок загружен и отвечает, а не просто запущен.
- Период распространения (propagation wait) находится между перенаправлением и высвобождением ресурсов. Кэши маршрутизации успевают обновиться до того, как емкость источника будет сокращена.
- Ресурсы источника высвобождаются после перемещения трафика. При росте емкость опережает трафик; при уменьшении трафик опережает емкость.
- Период ожидания и проверка метрик происходят до того, как шаг будет отмечен как завершенный. Шаг, на котором произошло ухудшение показателей, никогда не помечается как пройденный.
Через API или консоль развертывание создается в состоянии PENDING и ничего не делает до тех пор, пока вы не запустите его явно (команда rollout в CLI создает и запускает его за один шаг). Этот двухэтапный процесс создания и запуска сделан намеренно, чтобы вы могли создать развертывание, проверить его (или попросить коллегу проверить) и запустить тогда, когда вы действительно наблюдаете за процессом.
Два состояния на диаграмме выше заслуживают отдельного упоминания:
- PAUSED означает, что вы нажали паузу. Развертывание замирает ровно в текущем состоянии и возобновляется с того же шага.
- SYSTEM_PAUSED означает, что платформа обнаружила проблему (например, неудачную проверку метрик, нехватку емкости или отсутствие метрик) и остановилась в ожидании одобрения человека. Процесс приостанавливается, уведомляет вас и ждет; решение об отмене всегда остается за вами.
Не существует конечного состояния FAILED, оставляющего трафик в подвешенном состоянии: развертывание завершается либо как COMPLETED (целевая модель обслуживает запросы), либо как CANCELED (распределение трафика фиксируется на текущем моменте, и для возврата назад вы запускаете развертывание в обратном порядке).
Анатомия шага
Ниже приводится детальный разбор того, что происходит на одном канареечном шаге, измеренном во время тестового запуска в конце этой статьи (Qwen2.5-7B → Qwen3.5-9B на одном H100 для каждого).
Период распространения — это то, что мешает устаревшим глобальным кэшам маршрутизации отправлять запросы на уменьшающийся в размерах источник. Период ожидания увеличивается на время окна метрик плюс задержку сбора. Холодный старт доминирует на первом шаге; на последующих шагах реплики добавляются к уже работающей и прогретой цели.
Краткий обзор выбора стратегии
Все три стратегии работают через один и тот же движок и используют одинаковые проверки здоровья; они различаются тем, как перемещается трафик, сколько дополнительных ресурсов требует наложение деплоев и есть ли окно ожидания для проверки метрик.
Создание развертывания
Вот трехшаговый канареечный релиз от деплоя, обслуживающего вашу текущую модель, к деплою с кандидатом, с проверкой деградации задержки. CLI поставляется как tg в составе пакета Python together (версии 2.34.0 или новее). Вы передаете целевой деплой; источник определяется автоматически, если трафик получает ровно один деплой, в противном случае передайте --source:
Источник сокращается до нуля реплик и останавливается по завершении развертывания (--final-source-replicas по умолчанию равен 0), а цель принимает количество реплик источника в качестве минимального значения (--final-target-replicas). CLI добавляет одно правило метрик на одно развертывание; для нескольких правил используйте консоль или API.
То же самое через REST API, где создание и запуск являются раздельными вызовами, а развертывание может содержать несколько правил метрик:
Несколько важных требований API: перцентиль является целым числом (95, а не "p95"), значения перечислений содержат полный префикс (METRIC_STAT_TYPE_*, REGRESSION_DIRECTION_*, THRESHOLD_OPERATOR_*), длительности указываются в виде строк protobuf, например "600s", а имя метрики вне каталога отклоняется с кодом 400 и списком поддерживаемых имен. Высвобождение ресурсов источника используется по умолчанию, поэтому для него ничего передавать не нужно.
Проверку деградации можно понимать так: на каждом этапе сравнить p95 задержки маршрутизатора цели (продолжительность одного запроса, измеренная на маршрутизаторе в миллисекундах) за последние 5 минут с показателями источника. Если показатели цели хуже более чем на 10%, процесс не продолжается.
Python SDK
То же развертывание из Python с использованием пакета together (2.34.0 или новее). Имена полей здесь записаны в snake_case, а при передаче по сети — в camelCase; SDK выполняет этот перевод автоматически.
Управление развертыванием
Каждое развертывание принимает одни и те же четыре управляющие команды. Эндпоинт имеет не более одного активного развертывания, поэтому CLI принимает ID эндпоинта, и вам редко понадобится ID самого развертывания. Каждая команда возвращает результат сразу после принятия; опрашивайте tg beta endpoints get (или соответствующий GET-эндпоинт), пока развертывание не достигнет ожидаемого состояния. Пока развертывание активно (включая состояние паузы), распределение трафика заблокировано, а его источник и цель не могут быть остановлены или удалены.
Пауза (Pause)
Развертывание переходит в состояние PAUSING, дожидается завершения текущих активных шагов, а затем фиксирует текущее распределение трафика и количество реплик в состоянии PAUSED. Оба деплоя продолжают обслуживать запросы. Пауза может длиться днями; платформа никогда автоматически не возобновляет работу после паузы оператора.
CLI
REST
Возобновление (Resume)
Продолжение с того же шага как для PAUSED, так и для SYSTEM_PAUSED. Если проверка не прошла, она повторяется на свежих данных; шаг не пропускается.
CLI
REST
Продвижение (Promote)
Пропуск оставшихся канареечных шагов и выполнение финального 100%-го шага в полном объеме: цель масштабируется до конечного размера, трафик переключается, выполняются период распространения и стабилизация (soak), после чего источник высвобождается. Пропущенные шаги записываются как SKIPPED. Процесс не мгновенный: во время тестового запуска с 10-минутным интервалом шагов вызов promote на шаге 0 все равно ожидал полного периода стабилизации финального шага.
CLI
REST
Отмена (Cancel)
Фиксирует текущее разделение трафика на постоянные веса эндпоинта и завершает раскатку со статусом CANCELED. Масштабирование вниз не происходит; оба развертывания продолжают обслуживать свои зафиксированные доли, пока вы не измените распределение или не запустите обратную раскатку. Цель, отмененная на 0%, остается запущенной без трафика; масштабируйте ее до нуля или удалите, если она вам больше не нужна.
CLI
REST
Обратная раскатка
Команды отката (rollback) не существует. Чтобы вернуть трафик обратно после отмены или завершения, создайте новую раскатку с поменявленными местами источником и целью, а затем запустить ее. Подходит любая стратегия, и действуют те же правила (gates). После отмены стандартная канареечная лестница пропускает шаги, которые новая цель уже прошла, а стандартное финальное количество реплик равно суммарному количеству пары.
CLI
REST
Управление завершенной раскаткой (COMPLETED или CANCELED) запрещено. Удалите завершенную или так и не запущенную раскатку из истории с помощью команды tg beta endpoints rm $ROLLOUT_ID; удаление записи не меняет оставленное ею распределение трафика.
Под капотом: настройка проверок (gates)
Метрические проверки
Метрические проверки — это канареечная функция: сине-зеленые и скользящие (rolling) обновления по-прежнему выполняют проверки работоспособности (health gates), но поэтапное сравнение метрик требует структуры шагов канарейки, чтобы иметь смысл. Проверки оцениваются по закрытому каталогу из трех метрик на стороне роутера, которые измеряются одинаково для источника и цели (любое другое имя метрики отклоняется при создании):
- router_error_rate: ответы 5xx роутера, деленные на все ответы инференса, в виде соотношения от 0 до 1 (0.02 означает 2%)
- router_latency: длительность одного запроса, измеренная на роутере, в миллисекундах. Она имеет бимодальное распределение (средняя попытка часто представляет собой быстрый отказ), поэтому используйте в качестве проверки p95 или выше, а не среднее значение
- inflight_requests: параллельные запросы на одну готовую реплику, усредненные за окно (пороговые значения размера на одну реплику, а не для всего пула)
Каждое правило использует одну из двух проверок:
- regressionCheck (относительная): «цель должна быть не хуже источника более чем на N%». Это правильный выбор по умолчанию для задержки, поскольку он самокалибруется: вам не нужно знать ваш абсолютный p95, достаточно того, чтобы новая модель его не ухудшала. Установите направление (direction), чтобы платформа знала, какое значение является лучшим или худшим.
- thresholdCheck (абсолютная): «цель должна удовлетворять оператору и значению» (например, коэффициент ошибок < 0.01). Используйте это, когда у вас есть жесткое соглашение об уровне обслуживания (SLO) или когда сам источник может быть нездоров, и относительное сравнение будет оценивать по кривой.
Два рецепта охватывают большинство сервисов:
Временные окна
Три временных интервала взаимодействуют друг с другом, поэтому учитывайте их все:
- window (по умолчанию 5 м): как далеко назад заглядывает проверка при сравнении метрик.
- stepInterval (по умолчанию 3 м): сколько времени каждый шаг ожидает на своем уровне трафика перед запуском проверки.
- Задержка поступления метрик (~90 с): время между обслуживанием запроса и возможностью запроса его точки данных.
Вы должны подождать как минимум в течение периода window + задержка поступления, чтобы весь период ретроспективного анализа проверки попадал в устоявшееся состояние текущего шага. Если вы подождете меньше, чем ваше окно, проверка будет сравнивать метрики, которые частично описывают предыдущее распределение трафика. Платформа принудительно контролирует это: если вы запросите слишком короткий период ожидания для вашего окна, она автоматически увеличит его. Тем не менее, лучше проектировать систему с учетом этого: для окна в 5 минут требуется период ожидания 6,5 минут. При окне по умолчанию в 5 м платформа увеличивает интервал по умолчанию в 3 м до 390 с (6 м 30 с); если вы задаете собственный stepInterval, сделайте его равным по крайней мере window + 90 с.
Что происходит при регрессии
По умолчанию сработавшая проверка перенаправляет в статус SYSTEM_PAUSED, что означает приостановку системы для проверки. Раскатка удерживается на текущем распределении (радиус поражения остается на уровне вашего канареечного процента), и вы принимаете решение: возобновить (проверка переоценивается), продвинуть (promote) или отменить (cancel).
Автоматического прерывания нет: подтвержденная регрессия всегда приостанавливает раскатку для вмешательства человека, поскольку возвращение трафика назад — это само по себе изменение, за которым кто-то должен следить. Устранимые причины, такие как нехватка ресурсов или сбой в конвейере метрик, — это другое: платформа повторяет попытки каждые 15 минут в течение максимум 3 часов, прежде чем оставить раскатку приостановленной для вас. Платформа также защищает от ложных срабатываний: перед приостановкой из-за регрессии система делает несколько повторных запросов в течение примерно 90 секунд, чтобы убедиться, что она не имеет дело с задержкой поступления или временными сбоями, а проверка, не получающая достоверных данных, приостанавливается со статусом METRICS_UNAVAILABLE, а не засчитывается как регрессия.
Что гарантирует платформа
Все три стратегии работают через один и тот же движок шагов, поэтому эти правила одинаково применимы к канареечным, сине-зеленым и скользящим (rolling) обновлениям.
1. Емкость никогда не округляется в меньшую сторону. Целевые реплики округляются вверх, а истощение источника округляется вниз, поэтому замена одинакового размера никогда не имеет меньше реплик, чем было в начале. Скользящее обновление добавляет одну реплику в середине шага; сине-зеленое ненадолго запускает оба развертывания в полном размере. Реплики, добавленные вашим автоскейлером сверх плана, сохраняются.
2. Трафик никогда не попадает на неподготовленные мощности. Каждый шаг выполняется в определенном порядке: масштабирование цели, проверка работоспособности, перенос трафика, ожидание 30 секунд для схождения маршрутизации, истощение (drain) источника, ожидание, оценка проверки, запись шага. Шаг, в котором произошла регрессия во время ожидания, никогда не записывается как пройденный.
3. Каждая сторона всегда имеет реплики, необходимые для ее доли. Трафик перемещается только тогда, когда у цели достаточно готовых реплик для новой доли, и источник никогда не истощается ниже своей оставшейся доли. Если реплика умирает и разделение больше не может обслуживаться, раскатка удерживает максимально возможное разделение и приостанавливается со статусом UNDER_SERVED.
4. Раскатка повышает минимальные ограничения (floors); она не борется с вашим автоскейлером. Каждый шаг записывает минимальное количество реплик для каждого развертывания и ничего больше, за одним исключением: максимум цели поднимается один раз, чтобы она могла выдержать весь эндпоинт, и остается поднятым. Максимум источника уменьшается вместе с его долей во время истощения. Снижение максимума ниже того, что требуется для шага, приводит к тому, что раскатка приостанавливается со статусом POLICY_INFEASIBLE вместо того, чтобы переопределять вас.
5. Проверка всегда возвращает вердикт. Проверка регрессии проходит успешно, когда цель находится в пределах вашего процентного бюджета относительно источника. Данные источника отсутствуют: нулевой источник против ненулевой цели по метрике «чем больше, тем хуже» терпит неудачу; любой другой нулевой источник проходит. Пороговая проверка игнорирует источник и сравнивает цель с вашим значением.
6. Проверки считывают только трафик текущего шага и только в необходимом объеме. Период ожидания составляет по меньшей мере окно метрик плюс около 90 секунд задержки поступления, поэтому окно в 300 с означает ожидание в 390 с. Для p95 требуется 20 запросов в окне, а для p99 — 100; при меньшем количестве раскатка приостанавливается со статусом METRICS_UNAVAILABLE. Для частоты ошибок и запросов в полете (in-flight) требуется один запрос.
Краевые случаи (Edge cases)
1. Что делать, если для цели нет емкости графического процессора (GPU)?
Раскатка заранее проверяет осуществимость всего процесса, ничего не трогая, а также повторно проверяет ее при каждом масштабировании вверх. Нехватка приостанавливает раскатку в состоянии SYSTEM_PAUSED с категорией CAPACITY_EXHAUSTED. В этот момент ничего не сдвинулось с места, и ваш источник не затронут. Возобновление (Resume) повторно проверяет емкость и продолжается, если она освободилась. Проблемы с емкостью обычно носят временный характер, поэтому пауза лучше, чем сбой.
2. Могу ли я сделать паузу на неопределенный срок?
Да. Пауза — это не удержание соединения, а полноценное состояние. Развертывания спроектированы так, чтобы выдерживать многодневные паузы и возобновляться ровно с того места, где они остановились.
Когда платформа приостанавливает развертывание, поле status.condition содержит типизированную категорию сбоя и понятное человеку сообщение. К ним относятся:
В документации перечислены остальные категории и указано, что с каждой из них делать: Устранение неполадок при развертываниях.
Демонстрация реального развертывания
Все написанное выше проще принять на веру после того, как увидишь это в действии, поэтому вот запуск на текущей платформе (сентябрь 2026 г.). Мы обновили работающую эндпоинт-точку с Qwen2.5-7B-Instruct до Qwen3.5-9B, каждую на отдельной H100, в то время как стабильный поток в 5 запросов чат-комплитов в секунду проходил через эндпоинт все это время, и каждый код ответа регистрировался в логах. Новая модель — это именно то, что нам было нужно; вопрос, на который отвечает развертывание, заключается в том, укладывается ли она в бюджет задержки, установленный старой моделью. Мы выделили ей 25% бюджета p95.
Настройка. Модель 7B уже обслуживала запросы. Мы добавили 9B в качестве второго развертывания на ту же эндпоинт-точку без трафика и без реплик; развертывание запускает ее, когда это необходимо.
Запуск. Одна команда создает и запускает канареечное обновление: 10% → 50% → 100%, с проверкой (gate), которая сравнивает p95 задержку маршрутизатора целевой модели с исходной в течение 5-минутного окна после каждого шага.
Что произошло, по часам (время с момента начала развертывания):
- +3:57 модель 9B завершила холодный старт, прошла проверки работоспособности, и 10% запросов начали поступать на нее. Развертывание подождало 30 с для стабилизации маршрутизации, перенаправило соответствующую долю с 7B, а затем перешло в режим стабилизации (soak). Мы оставили интервал шага по умолчанию, поэтому платформа увеличила его до 390 с, чтобы учесть 300-секундное окно плюс задержку поступления данных.
- +11:00 проверка сработала и зафиксировала сбой. Задержка p95 маршрутизатора для 9B составила 1,740 мс по сравнению с 734 мс у 7B, что означает регрессию на 137% при бюджете в 25%. Развертывание перешло в состояние SYSTEM_PAUSED, причем 10% трафика все еще оставалось на целевой модели и ничего не было удалено. Вот что возвращает команда tg beta endpoints get $ROLLOUT_ID --json (значения в миллисекундах):
Решение. Регрессия реальна, а не случайна: 9B — это модель рассуждений (reasoning model) и при том же max_tokens генерирует больше данных на один запрос. Это продуктовое решение, а не то, что стоит возобновлять, поэтому мы отменили процесс и откатились назад.
- +11:09 CANCELED (ОТМЕНЕНО). Разделение трафика зафиксировалось на отметке 90/10 в течение 0.2 с после выполнения команды.
- +17:00 обратное развертывание завершено, через 5 минут 49 секунд после его начала: 100% трафика возвращено на 7B, модель 9B разгружена до нуля и остановлена. Большая часть этого времени ушла на холодный старт второй реплики 7B, так как после отмены стандартное финальное количество реплик равно их суммарному числу для пары.
Вердикт зонда за весь проход, включая переключение, паузу, отмену и возврат: 6,800 запросов, 0 ответов отличных от 200.
Журнал аудита (Audit trail). Каждый шаг выше зафиксирован в ленте событий эндпоинта, с возможностью фильтрации по ID развертывания:
Более ранний запуск в июле, с заведомо невыполнимым пороговым условием, дал тот же результат: сбой на 10% трафика и 1,198 запросов зонда без единой ошибки на протяжении всего восстановления.
Попробуйте сами!
1. Два развертывания на одном эндпоинте. Оставьте текущее развертывание в качестве источника и добавьте кандидата в качестве цели с нулевым трафиком. Команда pip install -U together (версии 2.34.0 или новее) предоставляет CLI-инструмент tg:
2. Создайте и запустите канареечное обновление со стандартной шкалой (5% → 25% → 50% → 100%) и одним условием регрессии router_latency:
3. Наблюдайте за процессом с помощью tg beta endpoints get $ENDPOINT_ID (или на вкладке Rollouts эндпоинта в консоли) по мере прохождения шагов. Приостановите, повысьте или отмените его с помощью tg beta endpoints rollout $ENDPOINT_ID --pause | --promote | --cancel.
Все это время URL эндпоинта и ваши клиенты остаются неизменными; меняется только модель, стоящая за ними.
📚 Документация: Запуск развертывания · Контроль развертываний с помощью метрик · Справочник CLI · Справочник API: Создание развертывания









