Перейти к содержимому
27 авг. 2025 г.·7 мин чтения

Бюджет задержки для LLM: где уходит время в запросе

Разберём, как считать бюджет задержки для LLM: сеть, маршрутизация, модель и постобработка, чтобы искать узкие места по данным, а не по ощущениям.

Бюджет задержки для LLM: где уходит время в запросе

Почему общее время ответа почти ничего не объясняет

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

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

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

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

Общая метрика чаще всего сбивает с толку в четырех ситуациях:

  • среднее время выглядит нормальным, но длинный хвост ломает диалог;
  • p95 вырос, хотя проблема появилась только у части моделей;
  • один провайдер тормозит, а общий дашборд прячет это в общей массе;
  • постобработка съедает секунду, и все винят LLM API.

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

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

Из чего состоит запрос

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

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

Сеть - это путь запроса от клиента до API и путь ответа обратно. Ее часто недооценивают. Если включен стриминг, сеть влияет не только на старт ответа, но и на то, насколько ровно приходят токены. Большой промпт, медленное мобильное соединение, лишний TLS-handshake или удаленный регион легко добавляют заметные сотни миллисекунд.

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

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

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

Как собрать бюджет задержки по шагам

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

Начните с таймстемпов в одном и том же месте для всех запросов. Обычно хватает четырех меток:

  • запрос вошел в ваш бэкенд;
  • код закончил проверки, маскирование PII и отправил вызов в шлюз или провайдеру;
  • пришел первый токен от модели;
  • пришел последний токен, и код закончил постобработку.

Из этих точек легко собрать интервалы. Первый показывает, сколько времени уходит до модели. Второй дает TTFT. Третий показывает саму генерацию. Последний относится к постобработке, форматированию, проверкам и отправке ответа клиенту.

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

TTFT и полное время ответа считайте раздельно. Для чата это две разные метрики. Если первый токен приходит за 700 мс, пользователь видит быстрый старт, даже если весь ответ собирается 6 секунд. Если TTFT внезапно вырос с 800 мс до 3 секунд, ищите очередь, сеть или маршрутизацию, а не длину вывода.

Чтобы сравнение не вралo, держите условия одинаковыми. Используйте один и тот же набор промптов, фиксируйте лимит выходных токенов, не меняйте модель, температуру и tool calling между сериями. Холодные запросы тоже лучше считать отдельно от обычных.

Если чат-ассистент отвечает медленно, не спешите винить модель. Иногда сеть съедает 200 мс, маршрутизация еще 150 мс, а две лишние проверки после ответа добавляют целую секунду. Такая разбивка сразу показывает, что чинить первым.

Если команда использует AI Router, удобно ставить метки и в приложении, и на границе вызова api.airouter.kz. Так проще отделить задержку в своем коде от задержки на маршруте до провайдера и на стороне самой модели.

Что считать на стороне сети

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

Для LLM-фичи это две разные дороги. Первая зависит от вашего приложения, мобильной сети, офисного интернета, DNS и TLS. Вторая зависит от того, где стоит шлюз, в каком регионе отвечает провайдер и как ведет себя внешнее API в конкретный момент.

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

Какие метрики писать в лог

Сохраняйте замеры так, чтобы потом можно было разложить каждый запрос на части. Обычно хватает пяти полей:

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

Этого уже достаточно, чтобы увидеть перекосы. Например, если клиент до шлюза ходит за 80 мс, а шлюз до провайдера прыгает с 300 мс до 1,8 с, искать проблему надо не во фронтенде.

Отдельно сравните холодные и теплые соединения. Новый коннект почти всегда медленнее: DNS, TCP, TLS, иногда еще прокси по пути. На серии запросов разница бывает заметной. Если теплый запрос идет 200 мс, а холодный 700 мс, вы нашли не "медленную модель", а цену отсутствия keep-alive.

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

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

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

Если после такого разбора сеть дает 150-250 мс, не пытайтесь выжать из нее секунду. Смотрите дальше, на маршрутизацию и саму модель.

Где модель съедает секунды

Сравните скорость и цену
Прогоняйте маршруты через AI Router по ставкам провайдеров без наценки на API

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

Длинный системный промпт кажется безобидным, потому что его пишут один раз. Но модель читает его на каждом запросе. Если в системной части 2-3 страницы правил, примеров и запретов, вы легко добавляете лишние сотни миллисекунд еще до первого токена. Иногда и больше, если модель большая, а запросов много.

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

Поэтому входные и выходные токены лучше считать отдельно. Эти два числа почти всегда объясняют больше, чем абстрактное "модель медленная".

Большой max_tokens тоже портит картину. Даже если пользователю обычно хватает 120 токенов, лимит на 2000 держит длинный хвост ответа как допустимый сценарий. Некоторые модели из-за этого дольше завершают запрос, а команда потом смотрит только на среднее время и не понимает, почему хвосты такие тяжелые.

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

Полезно отдельно смотреть на TTFT, полное время ответа, число входных и выходных токенов и ожидание в очереди у провайдера. Очередь особенно часто ломает картину в часы пика. Модель сама может быть быстрой, но запрос стоит 700-900 мс до старта генерации. Rate limits дают похожий эффект: часть запросов замедляется, часть уходит в повторные попытки, а на графиках это выглядит как "непредсказуемая модель".

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

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

Простой сценарий с чат-ассистентом

Банк запускает чат-ассистента для клиентов в мобильном приложении. Команда ждала ответ примерно за 2 секунды, но по логам среднее время выросло до 4,2 секунды. Пользователь видит только паузу, поэтому разработчики сначала начали резать все подряд.

Когда запрос разложили по этапам, картина стала простой. Сеть забирала около 250 мс, маршрутизация 120 мс, сама модель 3,4 с, а постобработка занимала еще примерно 430 мс. Это уже не спор про ощущения, а понятный бюджет по шагам.

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

Разбивка выглядела так:

  • сеть: 250 мс;
  • маршрутизация: 120 мс;
  • модель: 3,4 с;
  • постобработка: 430 мс.

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

После этого команда почти не трогала сеть и код после ответа. Она сократила системный промпт, убрала повторяющиеся инструкции, стала отправлять не всю историю, а только нужные реплики, и ограничила длину вывода. Время модели упало заметно сильнее, чем ожидали: с 3,4 до 1,9-2,1 секунды на похожих запросах.

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

Частые ошибки при разборе задержки

Поймайте длинный хвост
Посмотрите, какой маршрут тянет p95 вверх, пока среднее выглядит нормально

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

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

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

Еще одна ошибка - смотреть только на среднее время ответа. Среднее почти всегда выглядит спокойнее, чем реальный опыт пользователя. Если 80% запросов укладываются в 2 секунды, а 20% висят по 9 секунд, среднее не покажет, насколько неприятен хвост. Смотрите хотя бы p95 и p99. Именно там часто прячется проблема.

Есть и старая привычка винить сеть во всем. Она и правда может добавить сотни миллисекунд, но секунды чаще съедает модель, особенно когда ответ стал длиннее. Команда видит, что запрос пошел вдвое дольше, и сразу проверяет канал, DNS или прокси. А потом оказывается, что модель начала отдавать не 200 токенов, а 1200.

Полезно быстро проверить четыре вещи:

  • один ли и тот же промпт вы сравниваете;
  • не попали ли cold start запросы в общую выборку;
  • видите ли вы p95 и p99, а не только среднее;
  • не выросла ли длина ответа вместе с задержкой.

Если честно ответить на эти вопросы, половина "сетевых" проблем исчезает еще до дебага инфраструктуры.

Быстрая проверка перед выводами

Сведите замеры в одно место
AI Router помогает сравнивать модели и провайдеров без переписывания интеграции

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

Сначала зафиксируйте условия теста. Один и тот же промпт, одинаковый max_tokens, та же температура и один формат ответа. Если в одном замере модель должна выдать 80 токенов, а в другом 800, сравнение уже ломается.

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

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

  • сравнивайте только одинаковые запросы с одинаковым max_tokens;
  • снимайте TTFT и полное время ответа отдельно;
  • делите замеры по провайдерам, регионам и типам запросов;
  • проверяйте ретраи, очереди, rate limits и внутренние лимиты;
  • после каждого изменения делайте новый прогон, а не меняйте все сразу.

Разбивка по сегментам быстро отрезвляет. Один и тот же запрос может идти по-разному через разных провайдеров. Для команд в Казахстане и Центральной Азии это особенно заметно, если часть трафика уходит во внешний регион, а часть остается ближе к пользователю. Даже при OpenAI-совместимом API задержка может отличаться не из-за модели, а из-за маршрута.

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

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

Что делать дальше

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

Хорошее правило простое: сначала фиксируйте целевой ответ для обычного сценария, а потом делите его по шагам. Например, если чат-ассистент должен отвечать за 2,5 секунды, можно заранее решить, что сеть забирает до 150 мс, маршрутизация до 100 мс, модель до 1,8 секунды, а постобработка до 450 мс. Это не универсальная истина, но такая рамка быстро показывает, где система уже вышла за предел.

Дальше режьте самый дорогой участок, а не самый заметный. Команды часто начинают с того, что проще обсуждать: логирование, формат ответа, мелкие правки промпта. Но если модель стабильно тратит 70% времени, а сеть 5%, оптимизация сети почти ничего не даст. Смотрите на медиану и на 95-й перцентиль по каждому шагу. Именно там прячутся реальные потери.

Для повседневной работы достаточно нескольких простых правил:

  • оставить один эталонный сценарий и мерить его каждый день;
  • сравнивать время первого токена и полного ответа;
  • проверять, не растет ли задержка после смены модели или провайдера;
  • отделять время бизнес-логики от времени самого LLM API;
  • записывать метрики по этапам в одном формате для всех запросов.

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

Для команд в Казахстане есть и еще один практичный шаг. Если важны data residency и предсказуемая задержка, стоит отдельно сравнить внешние модели с open-weight моделями, которые AI Router хостит на собственной GPU-инфраструктуре внутри страны. В некоторых задачах это убирает лишний сетевой путь и делает время ответа ровнее.

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

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

Почему общего времени ответа мало для диагностики?

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

Какие этапы нужно измерять в LLM-запросе?

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

Что важнее для чата: TTFT или полное время ответа?

Для чата сначала смотрите на TTFT. Если первый токен приходит быстро, пользователь видит живой отклик даже при длинном хвосте ответа.

Как понять, что тормозит именно сеть?

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

Как промпт влияет на задержку?

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

Зачем ограничивать max_tokens?

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

Помогает ли streaming сделать ответ быстрее?

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

Почему среднее время часто вводит в заблуждение?

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

Как честно сравнивать модели или провайдеров по задержке?

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

Как AI Router помогает найти, где уходит время?

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