Мне нужен был офлайновый голосовой ввод для Windows — push‑to‑talk, без облака, с хорошим распознаванием русского. Звучит просто? Я тоже так думал. За два месяца перепробовал три ASR‑движка, кучу оптимизаций, и большая часть идей оказалась тупиком. Но в итоге — 3.3% WER на CPU, в 2.4 раза лучше Whisper large‑v3-turbo на RTX 4090.
Зачем это вообще понадобилось
Голосовой ввод на русском в 2026 году — грустная история. Встроенный в Windows работает через облако и плохо понимает русскую речь. Google Cloud STT — платный и требует интернет.
Задача казалась простой: зажал горячую клавишу, сказал фразу, отпустил — текст вставился в позицию курсора. Как диктофон, только сразу в текст и без интернета.
Микрофон → VAD → ASR → Постобработка → Буфер обмена → Вставка (Ctrl+V)
Весь вопрос — чем заполнить блок «ASR».
Методология
Пять аудиофрагментов из русских аудиокниг, TTS‑генерация (воспроизводимо, но не совсем реальные условия):
short_1, short_2 (6–7с) — простая лексика
medium_1 (15с) — литературный текст
long_1, long_2 (25–30с) — редкие слова («обезматочивший», «Очумелов»)
Метрика — WER (jiwer), базовый уровень — Whisper base на CPU (33%). GPU — RTX 4090 Laptop 16 ГБ. Каждый замер ×3, среднее. Whisper через faster‑whisper + CTranslate2, int8.
Этап 1: Whisper — разочарование по нарастающей
Whisper — очевидный первый кандидат. OpenAI, 99 языков, хайп. Начал с base на CPU — единственная модель, которая укладывается в бюджет 1 секунды.
Модель | Устройство | WER | Задержка | Укладывается в 1с? |
tiny | CPU | 56.2% | 0.30с | Да, но бесполезная |
base | CPU | 32.6% | 0.45с | Да |
small | CPU | 23.4% | 1.23с | Нет (+230мс) |
medium | GPU | 10.7% | 1.75с | Нет |
large‑v3 | GPU | 8.8% | 2.30с | Нет |
large‑v3-turbo | GPU | 7.9% | 0.44с | Да, но нужен GPU |
На CPU реально использовать только base. Small не влезает в секунду, а дальше — только GPU.
Тупик 1: параметры декодинга
Ладно, может дело не в модели, а в настройках? Beam size 1/2/3, temperature 0.0/0.5/1.0 — WER одинаковый (32.6%). У Whisper base узкое место — энкодер, не декодер. Крутить ручки декодинга бесполезно.
Тупик 2: адаптивный фоллбэк (base → small)
Идея казалась красивой: если confidence у base низкий — переделать через small. На синтетике с шумом — 0.0% WER, я уже праздновал. На тестовом аудио — 34.6%, хуже чистого base. Классический случай, когда бенчмарк на синтетике врёт.
Тупик 3: агрессивная предобработка
noisereduce (2-pass), тяжёлая шумоподавка — стало хуже. Деноизинг разрушает форманты, Whisper путает слова ещё сильнее.
Этап 2: T5-коррекция — палка о двух концах
Раз Whisper ошибается — может, подчистить за ним языковой моделью? Взял [UrukHan/t5-russian‑spell](https://huggingface.co/UrukHan/t5-russian‑spell), ускорил через CTranslate2.
Проблема: нефильтрованная T5 ухудшает WER. Модель «креативит» — заменяет слова на семантически похожие, удаляет куски текста.
Решение: пословная фильтрация (Smart T5)
Сравниваем каждое слово до и после коррекции через difflib.SequenceMatcher. Принимаем исправление, только если слово изменилось незначительно:
def smart_correct(self, text: str) → str: corrected = self._correct(text) # прогоняем через T5 original_words = text.split() corrected_words = corrected.split() result_words = [] for orig, corr in zip(original_words, corrected_words): ratio = SequenceMatcher(None, orig, corr).ratio() threshold = 0.80 if len(orig.split()) == 1 else 0.85 result_words.append(corr if ratio >= threshold else orig) return ’ “.join(result_words)”
ASR выдал «привед», T5 исправил на «привет» — ratio ≈ 0.83, принимаем. T5 заменил «кошка» на «собака» — ratio ≈ 0.33, отклоняем.
Тупики 4–5: перебор порогов и confidence‑based T5
Пороги 0.60–0.85 дают одинаковый WER (32.6%), выше 0.90 — хуже (35.4%). Применение T5 только к низко/высококонфидентным словам — тоже без эффекта. T5 лишь удерживает Whisper base от совсем позорных результатов (без неё — 41%). Потолок 33%, до цели далеко. Нужен другой движок.
Этап 3: Vosk — быстрый, но ограниченный
Vosk — офлайн ASR на базе Kaldi. Модель 50 МБ, и у меня были на него большие надежды.
Длина аудио | WER | Задержка |
6–7 сек | 0–6% | 0.71–0.80с |
25–30 сек | 20–27% | — |
Среднее | 13.0% | 0.75с |
На коротких фразах — прилично. Но стоит подкинуть что‑то подлиннее с редкой лексикой — Vosk сыпется. «Обезматочивший» и «Очумелов» ему неизвестны, словарь слишком мал. 60% снижения — неплохо, но до цели (80%) не дотягивает.
Тупик 6: чанкование длинного аудио
Разбить длинное аудио на куски, распознать по частям — WER стал хуже. И у Vosk, и позже у GigaAM. Модели используют внутренний контекст, и чем короче сегмент — тем больше ошибок. Контринтуитивно, но целое аудио распознаётся лучше, чем сумма его частей.
Этап 4: GigaAM — и тут всё изменилось
К этому моменту я почти смирился, что 80% снижения на CPU — утопия. И тут наткнулся на GigaAM — ASR от SberDevices, обученную на 700 000 часов русской речи (MIT). Whisper обучен на ~680K часов всех языков, русский — лишь часть.
Интеграция через onnx‑asr:
import onnx_asr model = onnx_asr.load_model(“gigaam‑v3-e2e‑rnnt”) text = model.recognize(audio_float32, sample_rate=16000)
Результаты
Модель | Тип | WER | Снижение | Задержка (CPU) |
gigaam‑v3-e2e‑rnnt | RNNT | 3.3% | 90.0% | 0.66с |
gigaam‑v3-rnnt | RNNT | 3.3% | 90.0% | 0.82с |
gigaam‑v3-e2e‑ctc | CTC | 4.2% | 87.2% | 1.08с |
gigaam‑v2-ctc | CTC | 4.7% | 85.7% | 1.18с |
Я перепроверил трижды. Лучший Whisper на RTX 4090 — 7.9%. GigaAM на CPU — 3.3%. В 2.4 раза лучше, без видеокарты.
RNNT vs CTC
На сложных текстах разница огромная: RNNT 2.6% vs CTC 13.2% WER. RNNT‑декодер учитывает контекст ранее сгенерированных токенов, CTC — нет. Для литературного русского это критично.
Подводные камни
Float32 — обязательно. Стоило мне часа отладки. Передал int16 — на выходе пустая строка, ни ошибки, ни предупреждения. GigaAM молча глотает неправильный формат. Нужен строго numpy.float32.
Warmup. Первые 2 вызова ONNX Runtime в 3–5 раз медленнее. Прогреваю тихим аудио при запуске.
Пунктуация из коробки. Модель v3-e2e-rnnt расставляет точки и запятые сама.
T5 не нужна. Ирония: при 3.3% WER та самая T5, которую я так долго допиливал, только мешает — «исправляет» правильный текст.
Что НЕ сработало с GigaAM
Предобработка аудио (pre‑emphasis, нормализация) — WER не меняется, модель и так робастная.
Ансамблевое голосование — модели ошибаются в одних и тех же местах. Голосование трёх согласных дурачков не даёт одного умного ответа.
Английский — пустой вывод или мусор. GigaAM — только русский.
Мультиязычность: гибридная схема
Для английского — Whisper large‑v3-turbo на GPU:
Конфигурация | WER (рус.) | WER (англ.) | Среднее | Снижение |
GigaAM + Whisper GPU | 3.3% | 7.3% | 5.6% | 82.8% |
Whisper auto‑detect | 7.9% | 7.3% | 7.5% | 76.9% |
Режим auto: GigaAM для русского, Whisper для английского, язык определяется автоматически.
Итоговая архитектура
Микрофон
→ Silero VAD (ONNX)
→ GigaAM (рус.) / Whisper (мульт.) / Vosk (лёгкий)
→ Smart T5 (если не GigaAM)
→ Ctrl+V
asr_backend: «gigaam» # или «whisper», «vosk», «auto» gigaam_model: «v3-e2e‑rnnt» # лучшее качество + скорость use_t5_correction: false # не нужна для GigaAM
Выводы
Специализация бьёт универсальность. 700K часов русской речи vs 680K часов всех языков — результат: 3.3% vs 7.9%, без GPU.
Параметры декодинга не спасут слабый энкодер. Beam search и temperature для Whisper base — строго 0% улучшения.
Языковая коррекция — осторожно. Нефильтрованная T5 ухудшает результат. Пословная фильтрация помогает слабым ASR, но бесполезна для сильных.
Чанкование вредит. Целое аудио распознаётся лучше суммы частей — модели теряют контекст.
RNNT > CTC. На сложных текстах — пятикратная разница (2.6% vs 13.2%).
Ансамбли однотипных моделей бесполезны. Для ансамбля нужны разные ошибки.
Ограничения
Тестовый датасет — TTS, не живая запись. На реальном аудио с шумами WER будет выше.
Пять фрагментов — мало для академической строгости, но достаточно для выбора движка.
GigaAM — только русский. Для мультиязычных задач нужен гибрид.
Скрытый текст
Код проекта на GitHub (MIT). Если воспроизведёте бенчмарки на живом аудио — буду признателен.
