MAATRIX / Блог / Распознавание речи в реальном времени на своём сервере

Распознавание речи в реальном времени на своём сервере

MAATRIX

Живые субтитры на трансляции, голосовой ассистент, который не заставляет ждать после каждой фразы, транскрипция звонка прямо во время разговора — всё это задачи другого класса, чем «расшифруйте мне файл». Модель, которая отлично справляется с уже записанной аудиодорожкой, на потоке от микрофона может начать заикаться, путать слова на границах фраз или просто не успевать за живой речью. Разберём, почему реальное время — это не «тот же Whisper, только побыстрее», а отдельная архитектурная задача, и как её решать на собственном сервере.

Чем реальное время принципиально отличается от офлайн-транскрипции

При офлайн-транскрипции у модели есть весь файл целиком. Она видит фразу до того, как начинает её распознавать, видит контекст после неё, может опираться на паузы, интонацию завершения мысли, даже на содержание всего разговора. Faster-whisper или whisper.cpp, запущенные на сервере для пакетной обработки, режут аудио на сегменты по своему усмотрению — по паузам, по 30-секундным окнам внутренней логики модели — и это не создаёт проблем, потому что результат всё равно собирается в готовый текст уже после того, как всё посчитано.

В реальном времени этой роскоши нет. Система обязана выдать текст с минимальной задержкой по мере поступления звука — часто ещё до того, как говорящий закончил фразу. Это фундаментальный компромисс: чем меньше задержка, тем меньше аудиоконтекста доступно модели для анализа, и тем выше риск ошибок распознавания, особенно на омонимах и словах, чей смысл проясняется только следующим словом («замок» — это существительное на двери или в поле, до конца фразы не всегда понятно). Дальше речь пойдёт именно о том, как выстроить архитектуру вокруг этого компромисса, а не о том, как заставить офлайн-модель работать «побыстрее» — это разные задачи, и вторая не решает первую.

Важно сразу развести две вещи, которые в описаниях моделей часто смешивают: throughput (сколько часов аудио модель обработает за час работы, real-time factor) и per-chunk latency (сколько времени пройдёт между тем, как в буфер попал звук, и тем, как из модели вышел текст для этого куска). Для офлайн-обработки на очереди из сотен файлов важен throughput. Для живого потока — почти исключительно per-chunk latency, и высокий throughput сам по себе её не гарантирует.

Обработка звука перекрывающимися фрагментами вместо ожидания всей фразы

Первый архитектурный приём — не ждать, пока говорящий замолчит, а резать входящий поток на короткие фрагменты и скармливать их модели по мере поступления. Наивный вариант — фиксированные окна без учёта речи (например, ровно каждые 2 секунды) — работает, но режет слова прямо посередине, и модель на границе окна регулярно ошибается или теряет слово целиком.

Практичнее сочетать два механизма:

  • VAD (voice activity detection) — отдельная лёгкая модель (например, Silero VAD), которая в реальном времени определяет, где есть речь, а где тишина или шум. Именно VAD решает, когда закрывать фрагмент — по паузе в речи, а не по таймеру.
  • Перекрытие (overlap) между соседними фрагментами — например, окно в 2-4 секунды с перехлёстом в 0.5-1 секунду с предыдущим. Это даёт модели немного контекста «до» текущего фрагмента, чтобы не терять слово, которое пришлось на стык.

Псевдо-конфигурация типичного стримингового пайплайна на базе Silero VAD и модели распознавания:

audio:
  sample_rate: 16000
  channels: 1
  format: pcm16

vad:
  min_speech_duration_ms: 250
  min_silence_duration_ms: 500   # пауза, после которой фрагмент считается законченным
  speech_pad_ms: 200              # запас по краям, чтобы не резать слово

chunking:
  max_chunk_seconds: 8            # принудительная нарезка, если говорящий не молчит долго
  overlap_seconds: 0.5

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

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть ИИ на сервере

Модели, спроектированные под потоковый режим, и инкрементальное уточнение текста

Второй архитектурный вопрос — какая модель декодирует эти фрагменты. Тут есть развилка.

Модели, изначально спроектированные для стриминга (streaming-native): архитектуры вроде потоковых Conformer/RNN-T (используются, например, в NVIDIA NeMo), потоковых Zipformer-моделей в проекте sherpa-onnx, или Kaldi-based Vosk. Они устроены так, что обрабатывают каждый новый маленький кусок аудио почти независимо от длины уже накопленного разговора — вычислительная стоимость не растёт с длиной сессии. Это архитектурно самый чистый вариант для низкой задержки, но такие модели, как правило, точнее на английском и хуже документированы для русского языка, а качество распознавания у многих из них уступает топовым офлайн-моделям вроде Whisper large.

Офлайн-модели, адаптированные под псевдо-стриминг: Whisper и его производные (faster-whisper) изначально не проектировались для потока — они ждут сегмент целиком. Чтобы использовать их в реальном времени, применяют приём повторного декодирования растущего буфера: при поступлении новой порции звука модель заново прогоняет весь накопленный с начала фразы буфер (а не только новый кусок), и на выходе сравнивают новую гипотезу с предыдущей. Тот текст, который совпал в обеих гипотезах (например, по алгоритму LocalAgreement из проекта whisper_streaming), фиксируется как окончательный и больше не меняется; хвост, который ещё «гуляет» между прогонами, показывается как черновой (в интерфейсе живых субтитров это обычно и есть серый неподтверждённый текст, который на глазах у пользователя превращается в чёрный подтверждённый). Это и есть инкрементальное уточнение уже выданного распознанного текста по мере поступления дополнительного контекста.

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

Практический выбор для проекта на своём сервере часто такой: если система уже работает на faster-whisper для офлайн-задач (например, для расшифровки звонков) и требования к задержке умеренные — проще взять обёртку с псевдо-стримингом поверх той же модели, а не поднимать отдельный стек. Если задержка критична (доли секунды, живой переводчик, интерактивный ассистент) — стоит присмотреться к streaming-native моделям в sherpa-onnx или Vosk, у них ниже архитектурный потолок задержки именно потому, что они не пересчитывают весь буфер заново на каждом шаге.

Транспорт: как звук доезжает до модели, а текст — обратно

Для батч-обработки файл просто лежит на диске. В реальном времени нужен постоянный канал: клиент (браузер, телефон, IoT-устройство) непрерывно шлёт аудио на сервер, а сервер шлёт обратно частичные и финальные гипотезы. На практике это почти всегда WebSocket (реже gRPC-стрим): одно соединение на сессию, бинарные фреймы PCM в одну сторону, JSON-сообщения с текстом — в другую.

Минимальная схема сообщения от сервера клиенту:

{"type": "partial", "text": "включи свет на кух", "is_final": false}
{"type": "partial", "text": "включи свет на кухне", "is_final": true}

На сервере под каждую активную сессию держится своё состояние: буфер аудио, VAD-состояние, последняя зафиксированная гипотеза. Это важно для планирования нагрузки — модель обслуживает не «поток аудио вообще», а N параллельных сессий, каждая со своим состоянием, и упирается либо в память под эти состояния, либо в то, что декодер физически не успевает обработать N фрагментов за отведённое на задержку время. Проект Vosk-server и sherpa-onnx поставляются с готовым websocket-сервером именно такой формы — не обязательно писать транспортный слой с нуля.

Требования к железу: считать нужно не throughput, а задержку одного фрагмента

Здесь чаще всего ошибаются: смотрят на паспортную характеристику модели «столько-то часов аудио в реальном времени на такой-то видеокарте» (real-time factor) и считают, что этого достаточно. Для батч-очереди это правильная метрика. Для живого потока — нет, и вот почему.

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

  • накладные расходы на запуск инференса (kernel launch overhead на GPU, инициализация состояния) — на большом батче они размазываются на много секунд аудио и незаметны, на одном коротком чанке составляют заметную долю всей задержки;
  • при батче размером 1 (а в живом диалоге вы почти всегда декодируете по одному чанку одной сессии) GPU часто недогружен — параллелизм, ради которого он и ценен, просто не из чего собрать;
  • при нескольких одновременных сессиях фрагменты можно собирать в мини-батч динамически, но это добавляет свою логику буферизации и свою добавочную задержку ожидания, пока наберётся батч.

Отсюда практический методический совет: не полагайтесь на общие метрики throughput или на бенчмарки модели, найденные в чужой статье. Тестируйте конкретную модель и конкретную конфигурацию на характерных для вашего сценария коротких фрагментах, измеряя именно время от момента, когда фрагмент "дошёл" до сервера, до момента, когда из него вышел текст — на своём железе, со своей длиной чанка, со своим числом одновременных сессий. Разница между «эта модель распознаёт X часов аудио в реальном времени» и «этот конкретный чанк в 2 секунды даёт ответ за 300 мс или за 1.5 секунды при пяти параллельных звонках» — это разница, которая решает, годится ли конфигурация для живого голосового ассистента или нет.

Ориентировочно (это именно ориентир, а не измеренное число — на вашем железе и с вашей моделью может быть иначе) для стриминга полезны:

РесурсНа что влияет в стриминге
Частота CPU / GPU-тактЗадержка декодирования одного короткого чанка, а не общая пропускная способность
Объём VRAM / RAMСколько параллельных сессий (каждая со своим состоянием и буфером) можно держать одновременно
int8/int4-квантование модели (например, через CTranslate2 для faster-whisper)Снижает задержку и память на чанк ценой небольшой потери точности — почти всегда стоит того в стриминге
Дисковый I/OПочти не важен для стриминга — модель загружена в память один раз, диск участвует только на старте

Для CPU-инференса (Vosk, faster-whisper в int8-режиме) важна не столько частота, сколько количество ядер, доступных под параллельные сессии — каждая сессия декодируется в своём потоке, и число одновременных звонков или чатов, которое сервер выдержит с приемлемой задержкой, растёт вместе с числом свободных ядер, а не линейно с «мощностью» одного ядра.

Как выбрать компромисс задержка/точность под конкретную задачу

Универсального правильного значения задержки не существует — оно зависит от того, что произойдёт, если ответ придёт на полсекунды позже.

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

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

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

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть ИИ на сервере

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Можно ли использовать faster-whisper для настоящего реального времени, а не с задержкой в несколько секунд?

Да, через обёртку с повторным декодированием буфера и политикой стабилизации гипотез (LocalAgreement), но задержка у неё выше, чем у streaming-native архитектур, потому что модель каждый раз пересчитывает накопленный буфер, а не только новый кусок. Для мягких требований к задержке (субтитры с отставанием в пару секунд) — рабочий вариант, для миллисекундной реакции голосового ассистента — не всегда.

Нужна ли GPU для потокового распознавания или хватит CPU?

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

Как понять, что моя задержка приемлема для конкретного сценария?

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

VAD обязателен, или можно резать по фиксированному таймеру?

Технически можно, но качество распознавания на границах фрагментов заметно просядет, потому что таймер режет слова и фразы без учёта того, где реально идёт речь. VAD (например, Silero) — недорогое по вычислениям решение именно этой проблемы, и почти всегда себя оправдывает.

Чем стриминговое распознавание отличается от синтеза речи в реальном времени?

Это разные задачи в одном пайплайне голосового ассистента: распознавание речи (STT) превращает входящий звук в текст, синтез речи (TTS) — наоборот, генерирует звук из текста ответа. У обеих есть свои требования к задержке, но архитектурные решения для них разные.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →