Статья

Тёплый кэш — ещё не быстрый ответ

Тёплая реплика экономит вычисления, свободная — ожидание. Хороший маршрут учитывает обе цены.

Автор — Сергей Нотевский

Опубликовано

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

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

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

Я бы первым делом подумал о закреплении повторов за тёплой репликой. Она уже сделала часть работы, зачем начинать сначала? Но перед ней может стоять очередь из таких же повторов. И тогда свободный холодный сервер обработает вход раньше, хотя вычислений сделает больше. Вот эту границу между ожиданием и пересчётом и стоит найти.

Два роутера

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

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

Кроме выбора пути и реплики есть допуск: можем ли мы принять запрос с текущими лимитами и сколько ему осталось ждать. А перед сравнением реплик нужно отсеять несовместимые, неисправные и не подходящие по изоляции данных. Высокое попадание в кэш не возвращает их в список кандидатов.

От задачи к конкретной реплике

01 · Сценарий

Что нужно выполнить

Возможности, ограничения и допустимый пул.

02 · Допуск

Когда можно принять

Лимиты, очередь и оставшееся время.

03 · Реплика

Где выгоднее выполнить

Доступный префикс, нагрузка и память.

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

Почему свободный сервер может победить тёплый

У реплики A есть нужный префикс. У B его нет. Хочется сразу выбрать A: зачем второй раз платить за ту же работу?

Добавим очередь. В учебном примере A начнёт обработку через 900 мс и потратит ещё 80 мс на оставшийся вход. B освободится через 100 мс, а полный вход обработает за 600 мс. Получаются 980 и 700 мс. Свободная B заканчивает этот этап раньше, хотя вычислений делает больше.

Если очередь A сократить до 100 мс, результат меняется: 180 против 700 мс. Тёплая реплика снова выигрывает. Эти числа заданы для объяснения, не сняты с GPU. В интерактивном примере можно самостоятельно найти точку переключения.

Вот почему я бы не делал долю попаданий главной оценкой маршрутизатора. Можно старательно возить популярный префикс на один сервер, радоваться кэшу и собирать перед ним очередь. Дашборд с долей попаданий при этом выглядит отлично, а пользователь стоит в очереди к самой прогретой машине в пуле.

Кэш добавляет платформе состояние

Если балансировщик не учитывает кэш, для него две одинаково загруженные совместимые реплики выглядят равноценными. Хотя одна уже проделала часть работы, а другая ещё нет. Причём это преимущество временное: нужные блоки могут вытеснить из памяти или сервер перезапустится, а маршрутизатор ещё какое-то время будет считать префикс доступным.

В NVIDIA DynamoВнешняя ссылка, откроется в новой вкладке выбор учитывает и повторное использование блоков, и нагрузку. В SGLang Model GatewayВнешняя ссылка, откроется в новой вкладке предусмотрены пороги для балансировки при маршрутизации с учётом кэша. Оба учитывают один компромисс: сохранять локальность, пока ожидание не съело её выгоду. Но пороги из одного не обязаны подходить другому, даже если параметры называются похоже.

Состояние кэша тоже нужно наблюдать. Запись «отправили туда же» подтверждает маршрут, но ещё не чтение кэша. А более быстрый второй запрос сам по себе не объясняет причину: могла уменьшиться очередь или закончиться прогрев модели.

С чего я бы начал проверку

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

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

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

Иногда правильный маршрут ведёт на холодный сервер. Это нормальное решение, если оно помогает выполнить запрос вовремя. Но показать это должно сравнение на своём потоке, с измерением очереди и времени до первого токена.

Продолжение и источники

Связанные материалы