Логи агента бодро мелькают, вентиляторы шуршат, а ответ появляется через восемнадцать секунд. В этот момент очень хочется обвинить модель. Я тоже начинал с неё.
Потом разложил один запуск на части и обнаружил, что модель честно работала меньше половины времени. Остальное съели три похода в инструменты, повторное чтение контекста и один особенно задумчивый API.
Из чего складывается ожидание
Для грубой оценки мне хватило пяти величин:
- Время до первого токена. Модель принимает контекст и готовит начало ответа.
- Скорость генерации. Количество выходных токенов делю на токены в секунду.
- Вызовы инструментов. База данных, браузер, shell или внешний API отвечают со своей задержкой.
- Повторные проходы. После результата инструмента модель снова читает контекст и принимает решение.
- Служебный запас. Сеть, сериализация и очередь редко спрашивают разрешения перед тем, как откусить ещё полсекунды.
Упрощённая формула выглядит так:
total = rounds × (first_token + output_tokens / tokens_per_second)
+ tool_calls × tool_latency
+ network_overhead
Она не заменяет трассировку. Зато быстро показывает порядок величин и помогает выбрать первый замер.
Если полоса инструментов длиннее полосы модели, смена LLM почти ничего не ускорит. Проверено способом «потратил вечер и вернулся к тому же API» :D
Покрутить задержку руками
Ниже живёт небольшой калькулятор. Я добавил несколько пресетов: чат без инструментов, RAG-поиск и многошаговый агент. Ползунки пересчитывают диаграмму сразу в браузере.
Что измерять в настоящем сервисе
Калькулятор даёт гипотезу. В рабочем агенте я отдельно записываю четыре времени:
- запрос ушёл к модели;
- появился первый токен;
- начался и закончился каждый инструмент;
- завершился весь запуск.
Этого хватает, чтобы увидеть длинную паузу без гадания по общему времени запроса. Для стриминга дополнительно полезно считать скорость уже после первого токена: пользователь воспринимает эти две задержки совершенно по-разному.
Самый дешёвый способ ускориться
Сначала убираю лишний проход агента. Потом параллелю независимые инструменты и кеширую стабильные ответы. Модель меняю уже после этих двух шагов.
Иногда результат получается немного обидным: самая дорогая оптимизация была открыта в соседней вкладке, а самая полезная помещалась в один Promise.all.