Перейти к содержимому
23 июл. 2024 г.·8 мин чтения

Хеджированные запросы к двум моделям: когда падает p95

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

Хеджированные запросы к двум моделям: когда падает p95

Где возникает проблема с p95

Проблема с p95 появляется в тот момент, когда метрики выглядят терпимо, а пользователи все равно жалуются на "тормоза". Среднее время ответа может быть 2,4 секунды, и на дашборде это выглядит нормально. Но если часть запросов иногда уходит в 8-12 секунд, человек запоминает именно эти паузы.

Среднее скрывает хвост задержки. Если 90 запросов завершаются за 2 секунды, а 10 - за 10 секунд, среднее не покажет, насколько неприятны эти редкие провалы. p50 тоже успокаивает: медиана останется около 2 секунд. А p95 сразу показывает, что заметная доля трафика уже ощущается медленной.

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

Пользователи не думают категориями p50 и p95. Они просто видят, что интерфейс иногда думает слишком долго. Этого уже хватает, чтобы доверие к системе просело. В чате это раздражает. В рабочем окне оператора банка, телекома или ритейла это сразу бьет по темпу работы.

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

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

В этот момент и появляются хеджированные запросы. Не потому, что средняя задержка плохая, а потому, что редкие медленные ответы начинают ломать весь пользовательский опыт.

Что такое хеджированный запрос

Хеджированный запрос - это схема, в которой сервис иногда отправляет один и тот же промпт в две модели, чтобы не ждать медленный хвост по p95. Слово "иногда" тут главное. Вы не дублируете каждый запрос подряд, а запускаете второй вызов по правилу: например, если первая модель не начала отвечать за 400 мс или если запрос попал в класс с высоким риском задержки.

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

Второй вариант обычно полезнее на практике: сначала сервис отправляет запрос в основную модель, а вторую запускает с небольшой задержкой. Такой отложенный старт нужен, когда вы хотите снизить p95, но не готовы удваивать затраты на инференс на каждом вызове.

Смысл схемы не в том, чтобы вернуть любой первый ответ. Нужно вернуть первый пригодный ответ. Это важная разница. Если одна модель отдала пустой текст, сломанный JSON или ответ не по формату, сервис не должен считать такой результат победителем. Он ждет другой ответ, если тот проходит простые проверки качества.

Обычно проверка выглядит просто: ответ пришел без ошибки, формат валиден, текст не пустой и не оборванный. После этого шлюз отдает клиенту тот результат, который первым прошел проверку. В нормальной продакшен-схеме побеждает не тот, кто быстрее прислал хоть что-то, а тот, кто быстрее прислал ответ, который можно безопасно отдать дальше.

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

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

Когда второй вызов реально помогает

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

Лучший эффект получается, когда запасная модель близка по качеству и форме ответа. Ей не нужно быть лучше. Ей нужно достаточно похоже решать ту же задачу: держать нужный формат, не ломать tool calling и не давать другой тон там, где это заметят пользователи. Если обе модели проходят один и тот же набор запросов почти с одинаковым результатом, пара уже подходит.

Обычно схема работает при четырех условиях. У основной модели есть редкие зависания, а не просто стабильно высокая задержка. Запасная модель живет на другом провайдере или другом стеке и не повторяет тот же сбой. Дубль стартует по таймеру, например через 300-800 мс, а не одновременно. И цена лишнего вызова на части трафика ниже, чем цена сорванного SLA.

Представьте чат-помощник для контакт-центра. В 93-97% случаев основная модель отвечает быстро. Но часть запросов подвисает на несколько секунд, и оператор сидит без подсказки. Если через полсекунды отправить тот же запрос второй модели и взять первый пригодный ответ, p95 часто падает заметно сильнее, чем среднее время. При этом расход растет только на той доле запросов, где таймер успел сработать.

Если команда уже работает через единый LLM-шлюз, такую схему проще проверить. В AI Router можно держать две модели за одним OpenAI-совместимым эндпоинтом и менять только логику таймера и выбора победителя. Это удобно для короткого теста на небольшой доле трафика.

Если бизнес платит за хвост задержки деньгами, ожиданием операторов или просадкой конверсии, второй вызов на 3-7% запросов часто оправдан. Если такой цены нет, дубль обычно не нужен.

Когда схема просто сжигает бюджет

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

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

Еще хуже, когда обе модели тормозят в один и тот же момент. Это частая история, если они сидят у одного провайдера, в одном регионе или упираются в общий пик нагрузки. Тогда параллельные вызовы LLM не дают страховки: обе очереди растут вместе, и второй запрос не спасает хвост.

Отдельная ловушка - длинные ответы. На коротком классификаторе лишний параллельный вызов может стоить копейки. На генерации ответа в 800-1500 токенов картина другая: основная цена сидит в выходе, а не во входе. Если хеджировать длинные генерации без жесткого контроля, затраты на инференс растут очень быстро.

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

Есть и более неприятный сценарий: вторая модель отвечает быстрее, но хуже. Пользователь или оператор видит слабый результат и жмет retry, либо система сама делает повтор. Тогда вы платите за два параллельных запроса, а потом еще за третий. Формально p95 снижается. По деньгам и качеству это провал.

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

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

Как выбрать порог запуска

Проверьте хедж без переделок
Подключите AI Router через OpenAI-совместимый эндпоинт и быстро сравните пару моделей.

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

Среднее время ответа почти всегда врет. Для такой схемы смотрят хотя бы на p95 и p99. Если среднее равно 2 секундам, а p95 прыгает к 8, проблема не в общей скорости, а в длинном хвосте. Именно его и должен срезать второй параллельный вызов.

Сначала разделите трафик

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

Достаточно разбить трафик хотя бы на четыре группы: короткие ответы до 100-150 токенов, средние рабочие запросы, длинную генерацию и суммаризацию, а также задачи с жестким SLA. После этого считайте p95 и p99 по каждой группе отдельно. Часто оказывается, что хедж нужен только в одной категории, а остальной трафик лучше оставить без него.

Ищите точку окупаемости

Порог запуска - это время ожидания, после которого вы отправляете второй запрос. Слишком ранний порог почти удваивает расходы. Слишком поздний не успевает срезать p95.

Рабочий подход простой: возьмите историю задержек по одной группе задач и прогоните несколько порогов, например 800, 1200 и 1800 мс. Для каждого варианта посмотрите три числа: новый p95, долю запросов со вторым вызовом и рост затрат на инференс.

Если порог 800 мс снижает p95 на 300 мс, но второй вызов срабатывает у 35% трафика, схема может оказаться слишком дорогой. Если порог 1400 мс режет p95 почти так же, а дубль нужен только в 8% случаев, это уже похоже на нормальный компромисс.

Одинаковый порог для коротких и длинных ответов обычно не работает. Для короткой задачи 1200 мс - уже провал, а для длинной генерации это еще нормальное ожидание. Если вы маршрутизируете модели через один шлюз, такие пороги удобнее задавать по типам маршрутов, а не одной цифрой на весь трафик. В AI Router это особенно просто, потому что команды меняют только base_url на api.airouter.kz и продолжают использовать тот же SDK и код.

Хороший порог выглядит скучно. Он не дает рекордный p95 на графике, зато заметно срезает хвост и не раздувает счет в конце месяца.

Как выбрать пару моделей

Пара моделей для хеджирования не должна хорошо смотреться только на бумаге. Сначала прогоните один и тот же набор реальных промптов через обе модели и сравните не среднюю задержку, а хвост. Часто модель А быстрее по медиане, но регулярно проваливается в p95 на длинных ответах. Модель Б может быть чуть медленнее в обычных случаях, зато ровнее под нагрузкой. Такая пара работает лучше, чем две "быстрые" модели с одинаковыми скачками.

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

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

Еще один фильтр - совместимость ответа. Если первая модель возвращает аккуратный JSON по схеме, а вторая любит добавлять текст до или после объекта, вы получите не страховку, а лишние ошибки в парсинге. То же касается tool calling, имен полей, длины ответа и стиля отказов. Чем ближе контракт, тем меньше сюрпризов.

Не ставьте рядом две модели с общей точкой отказа. Если они сидят у одного провайдера, в одном регионе или на одной очереди, их задержки часто растут одновременно. Тогда второй вызов не снижает p95, а просто удваивает счет. Лучше брать модели из разных доменов отказа.

Качество проверяйте на своих задачах, а не на общих бенчмарках. Возьмите 100-200 живых запросов: извлечение полей, классификацию обращений, короткие ответы для поддержки, сводки документов. Если резервная модель ошибается хотя бы в нескольких заметных кейсах, выигрыш по задержке быстро съестся ручными проверками и повторами.

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

Как внедрить схему шаг за шагом

Соберите тест за день
AI Router дает одну точку подключения к моделям разных провайдеров без смены промптов.

Сначала нужен честный базовый замер. Без него легко решить, что схема помогает, хотя p95 почти не меняется, а счет растет заметно. Соберите хотя бы несколько дней обычного трафика и сохраните не только p95, но и p50, p99, долю таймаутов, среднее число токенов и стоимость на 1000 запросов.

Дальше запускайте схему узко. Не включайте второй вызов сразу для всего потока. Начните с 1-5% трафика и задайте простой порог: вторая ветка стартует только если первая не начала отвечать, например, за 600-800 мс. Для коротких запросов порог обычно ниже, для длинных - выше.

  1. Выберите один сценарий. Лучше брать не весь продукт, а один тип запросов, где p95 уже мешает пользователю: чат поддержки, поиск по базе знаний или генерацию короткого ответа.
  2. Настройте победителя. Как только одна модель вернула годный ответ или начала поток, считайте ее победителем и сразу отменяйте вторую ветку.
  3. Поставьте жесткие лимиты. Ограничьте долю хеджирования, дневной расход и максимальное число параллельных дублей на один ключ или сервис.
  4. Разведите метрики. Храните отдельно данные по первой и второй ветке: кто победил, кто отменился, сколько занял старт ответа, сколько токенов сгорело и где были ошибки.
  5. Сравните неделю к неделе. Смотрите не только на снижение p95, но и на цену этого выигрыша.

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

Смотрите на результат трезво. Если p95 упал на 25-30%, а недельные затраты выросли на 5-8%, схема, скорее всего, оправдана. Если выигрыш меньше 10%, а расходы выросли на 20% и выше, второй вызов лучше оставить только для самых важных запросов.

Простой сценарий из продакшена

У команды поддержки есть простое правило: клиент не должен ждать дольше двух секунд. Если ответ от бота приходит позже, оператор уже видит раздраженного пользователя, а иногда и повторное сообщение вроде "Вы тут?".

В обычный день основная модель закрывает большую часть обращений сама. Простые вопросы про статус заказа, возврат или смену тарифа она отдает быстро, часто меньше чем за секунду. Проблема сидит не в среднем времени, а в длинном хвосте: часть запросов уходит в p95 и портит весь опыт.

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

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

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

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

Частые ошибки

Нужна низкая задержка локально
Попробуйте модели AI Router на собственной GPU-инфраструктуре, если важны низкая задержка и хранение данных в стране.

Самая частая ошибка - смотреть на среднее время ответа и радоваться: было 2,1 секунды, стало 1,8. Но пользователи злятся не из-за среднего. Их раздражает хвост: редкие ответы по 8-12 секунд, которые ломают чат, выбивают таймауты и держат воркеры занятыми. Если не считать p95 и p99 отдельно, хеджирование легко кажется удачной идеей даже там, где оно почти ничего не меняет.

Вторая ошибка - слишком ранний порог запуска. Если вторая модель стартует через 200-300 мс, вы не страхуете хвост, а почти всегда дублируете трафик. Задержка на графике может стать лучше, но счет за инференс растет почти линейно. Для обычного чата это часто плохая сделка.

Третья ошибка - сравнивать модели в разных условиях. Команда меняет системный промпт, шаблон ответа или параметры генерации, а потом решает, что одна модель быстрее или стабильнее. Такой тест ничего не доказывает. Сначала зафиксируйте одну версию промпта, одинаковую температуру, одинаковый max tokens и один набор запросов. Иначе вы сравниваете не модели, а два разных сценария.

Еще один промах - забыть про лимиты провайдера. Параллельные вызовы дают всплеск именно в тот момент, когда основная модель уже тормозит. Если у провайдера жесткий rate limit, вторая волна запросов упрется в 429, очередь или деградацию канала. Тогда p95 не падает, а растет.

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

Перед запуском полезно проверить пять вещей:

  • считайте p50, p95 и p99, а не только среднее время;
  • смотрите долю запросов, где хедж реально включился;
  • фиксируйте один и тот же промпт для обеих моделей;
  • отдельно считайте стоимость завершенных и отмененных вызовов;
  • проверяйте rate limits под пиковой, а не спокойной нагрузкой.

Если после этого p95 падает заметно, а доля дублей остается умеренной, схема имеет смысл. Если нет, второй вызов просто сжигает бюджет.

Быстрая проверка и следующие шаги

Эксперимент с хеджированием легко уводит команду не туда, если сначала не договориться о цели. Одним нужен p95 не выше 2,5 секунды при росте цены не более чем на 10%. Другим важнее p99 или доля таймаутов. Зафиксируйте SLA и потолок по расходам до первого теста, иначе второй вызов быстро становится дорогой привычкой.

Дальше нужна простая логика запуска дубля. Обычно его не стоит включать на каждый запрос. Чаще всего он нужен только после задержки в 300-700 мс, для длинных промптов или для провайдеров с неровным хвостом. Если правило нельзя описать в двух строках, его будет трудно проверить и еще труднее вовремя отключить.

Короткий план выглядит так:

  • запишите целевой SLA: p95, p99, долю таймаутов и предел по цене на тестовый трафик;
  • определите правило запуска дубля: по таймеру, по типу запроса, по размеру входа или по конкретной модели;
  • проверьте механику: отмену проигравшего запроса, дедупликацию ответа, единый идентификатор запроса и бюджетный лимит;
  • запустите схему на 5-10% трафика на несколько дней и сравните p95, p99, среднюю цену и долю отмененных дублей.

Полезно заранее решить, какое действие вы примете по итогам теста. Если p95 упал на 20%, а цена выросла на 4%, схему можно расширять. Если p95 почти не изменился, а расходы выросли вдвое, тест уже дал ответ: дубль не нужен.

Когда нужно быстро проверить пары моделей от разных провайдеров через один OpenAI-совместимый эндпоинт, такой тест удобно собрать в AI Router. Достаточно сменить base_url на api.airouter.kz и оставить те же SDK, код и промпты. Для короткого прогона это удобно: вы быстрее сравните маршруты, увидите, где падает хвост, и не потратите неделю на обвязку вместо измерений.

Часто задаваемые вопросы

Что такое p95 простыми словами?

p95 показывает время, в которое укладываются 95% запросов. Если p95 равен 8 секундам, это значит, что заметная часть пользователей ждет очень долго, даже если среднее время выглядит нормально.

Для чатов, copilot-интерфейсов и цепочек из нескольких LLM-вызовов эта метрика часто ближе к реальному опыту, чем среднее или p50.

Когда хеджирование действительно имеет смысл?

Эта схема нужна там, где основная модель обычно быстрая, но иногда зависает на несколько секунд. Тогда второй вызов с задержкой может срезать хвост и убрать самые неприятные паузы.

Если один пользовательский запрос тянет несколько LLM-шагов подряд, смысл еще выше: один медленный шаг портит весь ответ.

Нужно ли запускать обе модели одновременно?

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

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

В каких случаях второй вызов просто сжигает деньги?

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

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

Как выбрать порог запуска дубля?

Берите не случайное число, а данные по своим запросам. Посмотрите p95 и p99 по группам задач и прогоните несколько порогов, например 800, 1200 и 1400 мс.

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

Как выбрать запасную модель?

Ищите модель, которая дает похожее качество и тот же формат ответа, но живет в другом домене отказа. Если резерв ломает JSON, tool calling или стиль ответа, она создаст новые ошибки вместо страховки.

Лучше сравнивать пару на живых промптах с одинаковыми настройками: тот же системный текст, те же max tokens и та же температура.

Можно ли брать просто первый пришедший ответ?

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

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

Что делать с проигравшим запросом?

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

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

Как понять, что схема окупается?

Смотрите не только на новый p95. Сравните еще p99, долю таймаутов, долю запросов со вторым вызовом и цену на 1000 запросов.

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

Как безопасно проверить хеджирование в продакшене?

Начните с одного сценария, где хвост уже мешает людям, и включите хедж только на небольшой доле трафика. Обычно хватает 5-10% на несколько дней, если вы заранее задали цель по p95 и предел по расходам.

Проверьте механику до старта: отмену лишней ветки, единый ID запроса, учет отмененных вызовов и лимит на дневной бюджет. Так вы получите чистый тест без лишних сюрпризов.