Задержка синтеза речи в API: почему растет latency системы
Задержка синтеза речи в API почти никогда не равна времени, которое указывает провайдер в документации.

TTFB может выглядеть небольшим, но пользователь всё ещё не слышит начало фразы: соединение только установлено, пришёл заголовок контейнера, клиент готовит аудиопоток, а плеер ждёт буфер для стабильного воспроизведения.
Для голосового интерфейса важен не момент получения первых байтов, а время от окончания реплики пользователя до первого слышимого фрагмента ответа. Это end-to-end latency. В неё входят работа VAD и распознавания, генерация ответа языковой моделью, подготовка текста для TTS, сетевые операции, инференс синтезатора, декодирование и запуск воспроизведения на клиенте.
Разница между этими метриками и создаёт большинство ложных оптимизаций. Команда сокращает TTFB на сервере, но диалог по-прежнему ощущается медленным.
Ловушка метрики TTFB: почему первые байты не означают начало речи
TTFB, или Time to First Byte, показывает, когда клиент получил первые байты ответа. Для обычного HTTP-запроса это может быть начало JSON или бинарного тела. В WebSocket-сценарии метрика обычно привязана к получению первого фрейма или его части.
Ни один из этих моментов не гарантирует, что звук уже можно отправлять в аудиовыход.
Между первым байтом и воспроизведением могут находиться:
- заголовок и служебные данные контейнера;
- сборка неполного аудиочанка;
- парсинг формата на клиенте;
- декодирование кодека;
- создание или активация аудиопотока;
- начальное заполнение буфера плеера;
- синхронизация с аудиотрактом операционной системы или браузера.
У WAV структура относительно прозрачна: классический PCM-файл начинается с RIFF-заголовка, часто размером 44 байта. Эти байты описывают поток, но не содержат слышимого сэмпла. Если сервер отправил только заголовок, TTFB уже зафиксирован, а воспроизводить ещё нечего.
С Ogg/Opus и MP3 картина сложнее. Клиент получает служебные страницы или заголовки фреймов, после чего должен собрать достаточно данных для декодирования. Конкретное поведение зависит от библиотеки, браузера, способа упаковки и размера чанков. Поэтому нельзя автоматически считать первый полученный пакет началом речи.
К этой задержке добавляется буфер воспроизведения. Плеер может начать играть сразу после получения минимально достаточного объёма данных, а может накопить небольшой запас, чтобы не остановиться при первом сетевом колебании. Чем меньше буфер, тем ниже задержка, но тем выше риск прерываний и щелчков при нестабильном потоке.
TTFB фиксирует приход первых данных от сервера. Пользователь слышит речь только после того, как эти данные превратились в декодированный и готовый к воспроизведению аудиосигнал.
Внутри системы полезно разделять как минимум четыре момента:
1. отправка запроса на синтез;
2. получение первых байтов или первого фрейма;
3. получение первого валидного аудиофрагмента;
4. первый слышимый сэмпл на клиенте.
Последний показатель ближе всего к пользовательскому восприятию. Его можно измерять на стороне приложения, например через Web Audio API, AudioWorklet или инструменты нативного аудиостека. Серверный лог сам по себе не показывает, когда звук действительно начал воспроизводиться.
Есть и ещё одна ловушка: разные поставщики называют TTFB разные события. Один считает началом ответа отправку HTTP-заголовков, другой — первый аудиофрейм, третий — момент завершения подготовки синтезатора. Сравнивать такие значения напрямую нельзя, пока не определена методика.
Для корректного аудита стоит отдельно фиксировать:
- время отправки текста;
- время получения HTTP-ответа или открытия потока;
- время первого аудиобайта;
- время первого декодируемого блока;
- время первого воспроизводимого сэмпла;
- длительность стартового буфера;
- количество прерываний и underrun во время проигрывания.
Тогда станет видно, где именно появляется разрыв между серверной метрикой и реальной паузой в диалоге.
Сетевые накладные расходы: TLS, WebSocket и стоимость холодного старта
Если соединение открывается заново для каждого запроса, задержка синтеза речи в API увеличивается ещё до запуска модели. На холодном старте клиенту нужно установить транспортное соединение, выполнить TLS-согласование, пройти HTTP-этап и, если используется WebSocket, завершить upgrade.
Упрощённо путь выглядит так:
1. установление TCP-соединения либо согласование QUIC;
2. TLS-handshake;
3. HTTP-запрос или переход на WebSocket;
4. передача авторизационных данных;
5. отправка текста;
6. постановка запроса в очередь синтезатора.
Каждый этап зависит от сетевого RTT, маршрута до дата-центра, прокси, балансировщика и настроек клиента. Поэтому задержка холодного соединения не является постоянной характеристикой самого TTS. В мобильной сети она может заметно меняться даже у одного и того же пользователя.
При постоянном соединении часть работы выполняется один раз. Keep-Alive снижает стоимость повторных HTTP-запросов, а долгоживущий WebSocket позволяет отправлять несколько фраз по уже открытому каналу. Но у persistent connection есть собственные требования: нужно поддерживать heartbeat, обрабатывать разрыв, повторно авторизовываться при необходимости и не оставлять бесконтрольный пул соединений.
Для коротких голосовых реплик полезно различать два сценария:
- соединение открывается перед каждой фразой;
- соединение заранее прогрето и используется для серии реплик.
Во втором случае сетевой вклад в задержку обычно становится предсказуемее. Однако это не означает, что WebSocket всегда быстрее обычного HTTP. Если запрос редкий, короткий и не требует двунаправленного стриминга, постоянный канал может добавить сложности без заметного выигрыша. Если же текст и аудио передаются частями, WebSocket или другой потоковый транспорт даёт архитектурное преимущество.
HTTP/2 позволяет переиспользовать соединение и мультиплексировать запросы. HTTP/3 на базе QUIC иначе работает с потерями пакетов и установлением соединения. Но выбор протокола не отменяет задержку на стороне модели и клиента. Протокол способен убрать сетевой излишек, а не сделать инференс мгновенным.
Отдельно стоит проверить, где расположен балансировщик. Даже при близком к клиенту edge-узле запрос может отправляться на удалённый inference-сервис. Тогда в измерение попадают дополнительные переходы между точками присутствия, балансировщиком и GPU-инстансом.
Практика показывает, что холодный старт нужно измерять не одним средним значением, а распределением:
- первый запрос после простоя;
- повторный запрос по тому же соединению;
- запрос после переподключения;
- запрос при потере пакетов;
- запрос из разных сетей и регионов.
Среднее значение здесь может скрыть длинный хвост. Для голосового интерфейса особенно важны p95 и p99: именно редкие долгие запросы пользователь воспринимает как зависание ассистента.
Архитектурные узкие места: VAD и пофразовая передача данных
В каскаде STT → LLM → TTS синтез речи — только один участок цепочки. Если система ждёт завершения реплики пользователя слишком долго, оптимизация TTS почти не изменит общее ощущение скорости.
Главный источник задержки на этом этапе — VAD, детектор голосовой активности. Он определяет, где заканчивается речь, и решает, когда можно передавать распознанный текст дальше. Слишком короткий таймер окончания реплики обрывает фразы и отправляет в LLM незавершённые мысли. Слишком длинный заставляет ассистента ждать после естественной паузы пользователя.
Фиксированное окно тишины плохо подходит для всех сценариев. Темп речи, микрофон, акустика помещения и характер диалога различаются. В колл-центре пользователь может делать паузу внутри длинной фразы; в голосовом управлении короткая команда обычно заканчивается быстрее.
Поэтому VAD лучше рассматривать не как один переключатель, а как часть политики завершения хода. На решение могут влиять:
- длительность тишины;
- уверенность распознавания;
- признаки окончания синтаксической конструкции;
- наличие ключевого слова или команды;
- история предыдущих пауз;
- возможность немедленно прервать ответ при новом голосе.
End-of-turn prediction помогает оценить, закончил ли пользователь реплику, до истечения максимального таймера. Но такая модель тоже ошибается. Слишком агрессивное завершение хода ухудшает качество диалога, поэтому оптимизация должна учитывать не только миллисекунды, но и число преждевременных перебивок.
Barge-in решает обратную задачу: если ассистент уже говорит, пользователь должен иметь возможность его остановить. Для этого TTS-клиенту нужна команда мгновенной остановки, а аудиобуфер не должен быть настолько большим, чтобы старый ответ продолжал звучать после начала новой реплики.
Sentence streaming и границы фраз
Передача текста в TTS по мере генерации ответа обычно полезнее, чем ожидание полного сообщения от LLM. Но отправлять в синтезатор каждый появившийся токен тоже не стоит. Слишком мелкие части создают лишние запросы, ломают интонацию и заставляют модель многократно обрабатывать контекст.
Практический компромисс — накапливать текст до естественной границы:
- конец предложения;
- запятая или другая сильная пауза;
- завершённая короткая команда;
- ограничение по длине, если LLM генерирует слишком длинную фразу.
При этом граница для TTS не обязана совпадать с типографической точкой. Для диалога иногда лучше отправить короткий смысловой фрагмент раньше, чем ждать окончания большого предложения. Важны произносимость, интонация и возможность продолжить фразу без заметного шва.
Нельзя универсально утверждать, что конкретная архитектура или модель непригодна для sentence streaming. Поддержка потоковой генерации зависит от реализации, режима инференса, декодера, API и того, как модель обрабатывает контекст. Одна и та же модель может работать с пофразовой передачей через внешний слой, но не уметь выдавать аудио после каждого токена.
У end-to-end-систем тоже различается поведение. Одни могут начинать генерацию после получения части текста, другие требуют дополнительного контекста для устойчивой просодии, третьи формально поддерживают стриминг, но отдают первый фрагмент только после внутреннего накопления. Поэтому проверять нужно не название модели, а фактическое поведение конкретного endpoint:
- принимает ли он неполный текст;
- когда появляется первый аудиофрагмент;
- сохраняется ли голос и темп между чанками;
- как обрабатывается пунктуация на границе частей;
- можно ли остановить генерацию без ожидания полного ответа.
Sentence streaming сокращает не только время ожидания TTS. Он меняет сам порядок работы системы: синтез начинается до того, как LLM закончила весь ответ.
Буферизация, формат аудиопотока и client-side flush
Даже быстрый сервер не спасает приложение, если клиент неправильно собирает поток. Частая ошибка — использовать проигрыватель, рассчитанный на завершённый файл, для данных, которые приходят постепенно. Такой плеер может ждать метаданные, конец контейнера или достаточный объём аудио, хотя первые фреймы уже доступны.
Формат нужно выбирать вместе со способом доставки. PCM проще декодировать и удобно использовать в низколатентном тракте, но он требует больше полосы. Сжатые форматы экономят трафик, однако добавляют работу кодека и ограничения по фрагментации. Ogg/Opus хорошо подходит для потоковой передачи в подходящем клиентском стеке, но не каждый аудиоплеер одинаково быстро начинает воспроизведение частично полученного контейнера.
Ключевые параметры на стороне клиента:
- размер стартового буфера;
- размер последующих аудиочанков;
- частота вызова flush;
- время декодирования;
- поведение при пропуске пакетов;
- возможность остановить старый поток;
- синхронизация нескольких фрагментов речи.
Маленький буфер уменьшает задержку до первого звука, но повышает вероятность underrun. Слишком большой буфер делает речь устойчивой к сетевым скачкам, зато пользователь дольше ждёт ответа. Оптимальное значение зависит от транспорта и устройства, поэтому его следует подбирать по реальному распределению задержек, а не по одному лабораторному запуску.
Передискретизация — ещё один источник лишней работы. Если синтезатор естественно выдаёт аудио в одной частоте дискретизации, а клиент или сервер запрашивает другую, появляется дополнительный этап преобразования. Он может быть недорогим, но в цепочке с жёстким latency-бюджетом его всё равно стоит учитывать. По возможности формат вывода согласуют с последующим аудиотрактом и не делают лишних конвертаций.
Автоматическое определение языка также не является бесплатным. Если язык уже известен из настроек сессии, его лучше передавать явно. В мультиязычном продукте auto-detect может быть оправдан, но его вклад нужно измерять отдельно, особенно если определение выполняется перед каждым коротким запросом.
Проблемы возникают и при слишком частом client-side flush. Маленькие фрагменты текста могут запускать повторную обработку контекста, ухудшать просодию и создавать очередь из множества коротких задач. Если каждый чанк обрабатывается почти независимо, суммарные накладные расходы иногда перекрывают выигрыш от раннего старта.
Таблица ниже — не набор универсальных нормативов, а направления для настройки:
| Параметр | Что проверять | Возможный компромисс |
|---|---|---|
| Размер текстового чанка | Естественность фразы и время до первого ответа | Меньше задержка против большего числа запусков |
| Стартовый аудиобуфер | Время до первого слышимого сэмпла | Быстрый старт против риска прерываний |
| Формат аудио | Скорость декодирования и объём трафика | Простота PCM против экономии сжатого формата |
| Частота дискретизации | Совпадение с серверным и клиентским трактом | Меньше конвертаций против совместимости |
| Язык | Передаётся явно или определяется автоматически | Предсказуемость против универсальности |
| Транспорт | Повторное использование соединения и восстановление | Низкая задержка против сложности поддержки |
Влияние нагрузки на инференс и стратегии префиллинга моделей
Даже при постоянном соединении и хорошо настроенном VAD TTS может замедляться под нагрузкой. Запрос проходит через очередь, ждёт доступный вычислительный ресурс, конкурирует за память и пропускную способность GPU. При этом задержка не обязана расти линейно вместе с числом пользователей.
На поведение системы влияют:
- размер и состав батча;
- длина входного текста;
- выбранный голос и режим управления стилем;
- наличие референсного аудио;
- свободная память GPU;
- параллельность декодера;
- политика очереди;
- частота появления коротких и длинных запросов одновременно.
Префилл — подготовка внутреннего состояния модели по входному тексту и контексту — особенно важен для первого аудиочанка. Генерация может быть быстрой после того, как состояние подготовлено, но именно начальная обработка часто определяет time to first audio.
Мелкая нарезка текста не всегда ускоряет этот процесс. Если каждый фрагмент запускается как отдельная задача, модель снова обрабатывает служебный контекст, настройки голоса и часть входной последовательности. Поэтому потоковая схема должна уменьшать время до первого звука, а не просто делить один большой запрос на множество маленьких.
В зависимости от реализации применяются разные стратегии:
- держать модель загруженной и не допускать повторной инициализации воркера;
- заранее прогревать часто используемые голоса и конфигурации;
- кэшировать эмбеддинги говорящего для фиксированных голосов;
- разделять короткие интерактивные запросы и длинную фоновую озвучку;
- ограничивать размер очереди и корректно сообщать о перегрузке;
- масштабировать inference-воркеры по реальной очереди, а не только по загрузке CPU;
- измерять latency отдельно для первого чанка и для последующих.
Для zero-shot-клонирования голоса к TTS добавляется обработка референсного аудио. Система может извлекать признаки говорящего, подготавливать conditioning и только затем запускать синтез. Если один и тот же голос используется в течение всей сессии, результат такой подготовки разумно кэшировать. Для разовых референсов эта часть является неизбежным элементом задержки.
Универсального соответствия между числом параметров модели, объёмом VRAM и временем ответа нет. На результат влияют точность вычислений, версия движка, оптимизации, длина текста, вокодер, размер батча и конкретное оборудование. Поэтому таблицы вида «модель такого размера всегда отвечает за столько-то миллисекунд» вводят в заблуждение.
Тестировать нужно собственную конфигурацию. Минимальный набор сценариев:
1. короткая команда без клонирования голоса;
2. длинная фраза с потоковой передачей;
3. повторное использование одного голоса;
4. новый голос с референсным аудио;
5. один запрос и серия параллельных запросов;
6. холодный и прогретый worker;
7. нормальная и перегруженная очередь.
Для каждого запуска фиксируют не только среднее время. Нужны медиана, p95, p99, время до первого аудиочанка, время до завершения всей фразы и доля неудачных или прерванных потоков.
Как проводить аудит задержки
Аудит лучше начинать с единой временной шкалы. Все компоненты должны записывать события с согласованными timestamp, иначе сетевые и клиентские измерения нельзя будет сопоставить.
Минимальная последовательность выглядит так:
1. Зафиксировать момент окончания речи пользователя по сигналу VAD.
2. Зафиксировать окончание распознавания или момент, когда текст стал доступен LLM.
3. Записать начало генерации ответа.
4. Отметить передачу первой фразы в TTS.
5. Измерить первый байт и первый аудиофрейм.
6. Отметить первый декодированный блок на клиенте.
7. Измерить первый слышимый сэмпл.
8. Отдельно записать завершение всей реплики.
Такой журнал показывает, где теряется время. Если задержка между концом речи и отправкой текста в TTS велика, проблему нужно искать в VAD, STT или LLM. Если TTS получает текст быстро, но долго не выдаёт аудио, внимание переключается на очередь, префилл и конфигурацию синтезатора. Если первый аудиофрейм приходит вовремя, а звук слышен поздно, узкое место находится в декодере или буферизации клиента.
Для сравнения поставщиков важно использовать одинаковые условия:
- один и тот же текст;
- одинаковый язык и режим голоса;
- одинаковый формат аудио;
- одинаковый тип соединения;
- одинаковое состояние прогрева;
- одинаковый способ измерения TTFA;
- одинаковая модель нагрузки.
Нельзя сопоставлять TTFB одного API с TTFA другого и делать вывод о качестве системы. Как нельзя сравнивать локальный синтезатор без сетевого пути с облачным сервисом, если пользовательский сценарий предполагает удалённый API.
Целевой бюджет зависит от продукта. Для голосового управления допустима одна задержка, для перебивочного диалога — другая, для озвучки длинного текста — третья. Важно заранее решить, какой показатель является критичным: быстрый первый звук, отсутствие пауз между чанками или скорость завершения всей реплики.
Задержка синтеза речи в API — это не одна цифра в карточке провайдера. Она складывается из сетевого старта, очереди, подготовки текста, работы модели, кодирования, доставки, декодирования и буферизации. TTFB полезен как диагностический сигнал, но не как итоговая оценка пользовательского опыта.
Сначала измеряют путь до первого слышимого сэмпла, затем разбирают его на составляющие. После этого уже имеет смысл оптимизировать persistent connection, VAD, sentence streaming, формат аудио, client-side flush и прогрев inference-воркеров. Такой порядок защищает от самой дорогой ошибки: ускорить участок, который пользователь вообще не воспринимает как источник задержки.