Открытые модели против облака: T-one, GigaAM или Deepgram: что реально тянет обычный CPU для живых субтитров в звонке

Число vCPU ничего не говорит: как я считал, нужен ли свой ASR-сервер вместо Deepgram

Мне понадобились живые субтитры для созвонов один на один на русском языке. Проще всего решить эту задачу через облачный API: буквально одним вызовом. Это удобный вариант, если разговоров немного, а их содержание можно передавать внешнему сервису.
В моём случае всё не так однозначно: часов общения может быть много, а в разговорах встречаются чувствительные данные, которые не хотелось бы отправлять в облако. Поэтому я развернул тестовый стенд и сравнил облачное распознавание речи в реальном времени с открытыми моделями на обычном сервере, только с CPU, без видеокарты.
Главный результат оказался про железо, а модели тут вторичны. Один чанк акустической модели на ядре Xeon 2013 года без AVX2 стоит в 2,5 раза дороже, чем на современном Xeon с AVX2, а под полной нагрузкой разница доходит до пяти раз. В пересчёте на дорожки это 32 против 5, но эту цифру надо читать с оговоркой о методике, о ней ниже. Считать окупаемость своего ASR по числу ядер в тарифе хостера бессмысленно.
Что было нужно
Сейчас я тестирую и использую облачный Deepgram, и стенд нужен, чтобы понять, можно ли с него уйти. Звонок идёт в браузере, у каждого участника свой микрофон, поэтому на сервер приходят две отдельные дорожки. Это сильно упрощает жизнь: диаризация не нужна, роль говорящего известна по дорожке. Требования к распознаванию простые:
фраза должна появляться не позже чем через 2 секунды после того, как её договорили (p95);
цена часа должна быть предсказуемой;
аудио не должно покидать сервер в России.
Контекст: делаю сервис для онлайн-встреч 1-на-1 с расшифровкой: 121ai.online
Кандидаты

Инструмент | Где работает | Режим | Зачем в сравнении |
|---|---|---|---|
T-one (Т-Банк, Apache-2.0, 71M параметров) | свой CPU | потоковый, 8 кГц, чанк 300 мс | основной кандидат на онлайн |
GigaAM-v3 (Сбер, MIT, 220–240M) | свой сервер | пакетный, вариант | чистовой текст и верхняя планка качества |
Vosk (ru-0.42 и small-ru-0.22) | свой CPU | потоковый | baseline |
Deepgram nova-2 и nova-3 | облако | оба режима | контроль качества и цены |
Из облаков в сравнении только Deepgram: для замеров я брал то, что доступно сразу, без договора и согласований. Внутри РФ есть Yandex SpeechKit, и если аудио не должно покидать страну, а свой сервер поднимать не хочется, присмотритесь к нему.
Chrome Web Speech с processLocally работает только в Chrome и только на машине пользователя, это другой класс решения.
По той же причине мимо прошли две свежие on-device-новинки на базе Google EmbeddingGemma 2.
Google AI Edge Foresight: экспериментальный помощник для созвонов, который всё распознаёт локально. Я поставил его себе: он работает только на Mac с Apple Silicon и на практике понимает только английский.
Embedding Gemma Transcribe, сторонний проект на той же модели, сводит распознавание к поиску ближайшего слова в словаре примерно из 229 тысяч английских слов, поэтому русского там нет и быть не может. Направление интересное, для приватных заметок на ноутбуке это будущее, но для русской речи на сервере пока не вариант.
Whistle от Cactus Compute, репо. Модель на 17 МБ, работает на CPU и в WASM, но русского языка в ней нет. Я проверил: пять русских фраз из пяти превратились в псевдопольский и псевдонемецкий. Плюс телеметрия, включённая по умолчанию. Для русской речи вопрос закрыт.
Что обещают авторы
Цифры WER у разных моделей получены на разных наборах, сравнивать их между строками нельзя, но порядок величин полезен.
Что такое WER?
WER (word error rate) показывает долю слов, которые модель распознала неправильно. Ошибкой считается замена, пропуск или лишнее слово, а сумма ошибок делится на число слов в эталоне. WER 10% значит, что ошибочно примерно каждое десятое слово. Чем меньше, тем лучше. Перед подсчётом убирают пунктуацию и регистр, числа приводят к одному виду. Цифры, полученные на разных наборах записей, сравнивать между собой нельзя.
T-one: call-center 8,63, прочая телефония 6,20, Common Voice 5,32. В той же таблице авторов Whisper large-v3 на call-center даёт 19,39.
GigaAM-v3 RNNT: в среднем 8,3 по десяти наборам, call-center 9,5; e2e-вариант с пунктуацией 11,2.
Vosk ru-0.42: openstt calls 36,0, youtube 19,5. Small-модель на Golos crowd 11,79.
Deepgram: публичных цифр WER для русского нет. Я их не нашёл, если найдёте, напишите в комментариях.
Стенд
Я прогнал один и тот же код на двух машинах.
Машина A: Xeon с AVX2, 8 vCPU, 15 ГБ RAM. Это первый замер, он мерил только акустическую модель через onnxruntime, батч собирался из одинаковых чанков, декодер был greedy.
Машина B: Xeon E5-2680 v2 на 2,8 ГГц, 2013 год, 6 vCPU под KVM, 11,6 ГБ RAM. Из векторных расширений только SSE4.2 и AVX, без AVX2 и FMA. Steal около 1%, соседи по хосту не мешали. Здесь мерился полный пайплайн: акустика, сплиттер по паузам и декодер, у каждой дорожки своё смещение в аудио, два декодера и два режима работы.
Сразу честно: методики на A и B разные. На A я мерил только акустику, батчем одинаковых чанков, а на B гонял полный пайплайн. Поэтому строго сравнима только цена одного чанка (таблица ниже, тот же код и та же модель). Ёмкость в дорожках сравнивается грубо: на A полный пайплайн я не гонял, и реальное число там, скорее всего, ниже 32.
Методика на машине B такая. Раз в 300 мс все активные дорожки получают очередной чанк. Измеряется время акустики, время декода и их сумма, а также отставание от расписания: если тик занял больше 300 мс, следующий начинается позже, и это отставание копится. Машина «держит» N дорожек, если p95 времени тика меньше 300 мс и отставание к концу минуты меньше секунды. На каждую точку 60 секунд замера после 6 секунд прогрева.
Два режима:
batch: один вызов модели на все дорожки, onnxruntime с тремя потоками. Так я изначально собирался строить сервис, и так советует документация T-one для Triton.threads: каждая дорожка в своём рабочем потоке Python, у onnxruntime один поток (intra_op_num_threads=1). Акустика отпускает GIL, параллельность даёт пул.
Аудио для нагрузки: образцы из пакета T-one, колл-центр, 8 кГц, зациклены.
Код стенда открыт в репозитории github и gitverse. Самое полезное там, по-моему, не бенчмарк, а зонд acoustic.py: он зависит только от onnxruntime, ставится за минуту и за ещё одну отвечает, сколько стоит один чанк на вашем сервере. Дальше станет понятно, зачем это нужно.
Результаты
Цена одного чанка
Машина A (AVX2) | Машина B (AVX) | |
|---|---|---|
1 поток onnxruntime | 44 мс | 109–117 мс |
все потоки onnxruntime | 12 мс (8 потоков) | 60 мс (3 потока) |
прирост от потоков | ×3,7 | ×1,8 |
батч из 2 дорожек | 24 мс | 126 мс |
RTF одной дорожки, 1 поток onnxruntime | 0,147 | 0,36–0,39 |
RTF одной дорожки, все потоки onnxruntime | 0,049 (8 потоков, p50 чанка 13,6 мс) | 0,20 (3 потока) |
Разница на одно ядро в 2,5 раза, при всех потоках onnxruntime в 4–5 раз. Важно сравнивать одинаковые режимы. 44 мс на машине A получены в один поток onnxruntime, а RTF 0,049 из прошлого отчёта на всех восьми. Это не противоречие, просто разные настройки.
Профиль на машине B: половина времени в MatMul, пятая часть в Conv. Это честное умножение матриц на AVX без FMA, ускорять тут нечего.
Два наблюдения, которые я не ожидал. Первое: на машине B шесть потоков onnxruntime хуже трёх (64 мс против 60), а под нагрузкой p95 начинает расти уже при 41% загрузки машины. Похоже, 6 vCPU это 3 физических ядра с гипертредингом, и в потолок упирается общий кэш. Второе: батч из двух дорожек стоит ровно вдвое дороже одной. Батчинг, на котором держится вся идея «один процесс на всех», на этой машине не даёт ничего.
Сколько дорожек держит машина

Машина A по прошлому замеру (только акустика, батч): 16 дорожек за 72 мс, 32 за 128 мс, 64 за 223 мс на тик. С двойным запасом это 32 дорожки, то есть 16 одновременных встреч. Это верхняя оценка: сплиттер и декодер в неё не вошли.
Машина B, кривая по четырём конфигурациям:
Конфигурация | Держит дорожек | p95 на пределе | Следующая точка |
|---|---|---|---|
batch, 3 потока ORT | 2 | 292 мс | 3 дорожки: 336 мс |
threads, 3 рабочих | 3 | 268 мс | 4: 322 мс |
threads, 6 рабочих | 5 | 286 мс | 6: 345 мс |
threads, 6 рабочих, KenLM | 4 | 286 мс | 6: 360 мс |
Итого: машина B держит 4–5 дорожек, это две встречи против верхней оценки в 16 на машине A. Режим «дорожка на поток» оказался лучше батча в два с половиной раза, и он же проще в реализации.
KenLM
У T-one два декодера: greedy и beam search с языковой моделью KenLM на 5,4 ГБ. Я ожидал, что KenLM сожрет процессор. Но не нет, не съел: декод фразы стоит 5–11 мс на секунду речи, на записи в 51 секунду весь декод занял 0,1 секунды. Он просто спайками ложится в конец каждой фразы. Зато память: процесс с KenLM занимает 5,5 ГБ против 0,6 ГБ с greedy. На образце тексты greedy и KenLM совпали дословно, а на получасовом живом диалоге KenLM оказался лучше на 0,9 п.п. на дорожке клиента и на 1,7 на дорожке ведущей.
Что не помогло
Я попробовал два очевидных ускорения, оба мимо.
Динамическое квантование в int8. Выяснилось, что из 158 матричных умножений в модели 157 работают в float16, а квантователь onnxruntime умеет только float32. Квантовать нечего.
Перевод всей модели в float32, раз уж она в fp16, а на x86 без AVX512-FP16 быстрых fp16-ядер нет. RTF не изменился: 0,392 против 0,395, значит onnxruntime и так считал в fp32. Зато модель стала загружаться за 3 секунды вместо 9 и занимать вдвое меньше памяти: до этого рантайм переводил веса из fp16 при каждой загрузке. Для сервиса fp32-вариант всё равно полезен.
Качество на одном образце
Это 51 секунда колл-центра из пакета T-one, восемь килогерц. Эталон я собрал как согласие движков, это не вычитка человеком, так что числа ниже стоит считать ориентиром, а не выводом.
Движок | WER, % | Скорость на машине B | RAM |
|---|---|---|---|
Deepgram nova-3 | 0,0 | 51 с за 1,0 с | облако |
GigaAM-v3 rnnt | 0,0 | RTF 1,25, то есть 75 минут на час аудио | 1,6 ГБ |
GigaAM-v3 e2e_rnnt (с пунктуацией) | 2,9 | RTF 1,12 | 1,6 ГБ |
Deepgram nova-2 | 2,9 | 51 с за 1,4 с | облако |
Vosk ru-0.42 | 5,9 | RTF 0,59, загрузка модели 85 с | 6,2 ГБ |
T-one, greedy и KenLM | 11,8 | RTF 0,35 | 0,6 / 5,5 ГБ |
Vosk small-ru-0.22 | 23,5 | RTF 0,17 | 0,27 ГБ |
Четыре ошибки T-one на 34 слова: дважды «на сумму» вместо «но сумма». Это телефонная речь, на которой модель обучена, и всё равно она слабее всех, кроме маленького Vosk. GigaAM на уровне облака, но работает только пакетно. На этом коротком образце она шла медленнее реального времени (RTF 1,25), а на получасовом диалоге ниже уложилась в 0,62–0,86. Скорее всего, на 51 секунде заметную долю времени съедает разогрев, и на длинной записи цифра честнее. Вывод: на машине B GigaAM успевает обработать встречу после её окончания, но онлайн не годится.
Живой диалог: 29 минут коучинговой сессии

Две дорожки, эталоном служит боевая расшифровка Deepgram nova-3.
Движок | Клиент, 3028 слов, WER % | Ведущая, 539 слов, WER % | Скорость на B | RAM |
|---|---|---|---|---|
Deepgram nova-3 | 0 (эталон) | 0 (эталон) | облако | облако |
GigaAM-v3 e2e_rnnt | 8,1 | 19,1 | RTF 0,62–0,86 (37–52 мин на час) | 1,7 ГБ |
T-one KenLM, офлайн | 10,6 | 24,5 | RTF 0,27–0,29 | 5,5 ГБ |
T-one greedy, офлайн | 11,5 | 26,2 | RTF 0,30–0,42 | 0,6 ГБ |
T-one greedy через сервис (48→8 кГц, реальное время) | 11,9 | 25,6 | p95 тика 272 мс при 2 дорожках | 0,5 ГБ |
Vosk ru-0.42 | 25,9 | 21,2 | RTF 0,63–0,94; чанк p95 376–516 мс | 5,4 ГБ |
Задержка фразы через сервис: ведущая 126 фраз, p50 1110 / p95 1200 мс; клиент 322 фразы, p50 1080 / p95 1200 мс.
Что видно за цифрами:
KenLM на живой речи даёт минус 0,9–1,7 п.п. к greedy на обеих дорожках (на образце разницы не было).
Путь через сервис (ресемплинг 48→8 кГц) на дорожке клиента хуже офлайна на 0,4 п.п., на дорожке ведущей даже чуть лучше (25,6 против 26,2). На таком объёме это шум: ресемплинг почти ничего не стоит.
Высокий WER ведущей объясняется утечкой звука между каналами. У T-one и GigaAM на 539 слов 60–69 вставок. Ведущая 89% времени молчит, в её микрофон долетает голос клиента из динамиков, и модели его записывают, а Deepgram в multichannel этого не записал.
Но и Deepgram здесь не безупречен: в 00:02 и 00:10 T-one и GigaAM сошлись против него, он приписал ведущей реплики клиента. Так что WER ведущей завышен, и точно скажет только прослушивание спорных мест.
Vosk ru-0.42 на широкополосной живой речи развалился: 326 пропусков из 3028 слов на клиенте (на телефонном образце был 5,9%).
Спорных мест для прослушивания набралось 91 на дорожке ведущей и 561 на дорожке клиента.
Оговорка про эталон, и почему она не меняет вывод. Эталоном здесь служит текст Deepgram nova-3, поэтому его строка равна нулю по построению, а цифры остальных показывают расстояние до Deepgram, а не WER в строгом смысле. Я посчитал, насколько это может исказить картину. На дорожке клиента есть 74 места из 3028 слов, где движки разных семейств сошлись против Deepgram. Если во всех 74 ошибся именно Deepgram, его WER станет 2,4%, а у T-one снизится на те же 2,4 п.п.: разрыв сожмётся с 10,6 до 5,7 п.п., но не исчезнет. Порядок движков этот сдвиг тоже не меняет. Всерьёз он мог бы подвинуть только строку GigaAM: 8,1% «расстояния» может означать и реальное отставание, и просто другие ошибки в других местах.
Как устроен сервис
Кривая подсказала устройство сервиса. Один процесс, одна загруженная модель, у каждой дорожки своё состояние, onnxruntime с одним потоком, дорожки параллелятся пулом рабочих потоков по числу ядер.
Браузер отдаёт PCM 48 кГц кадрами по 20 мс, сервис понижает до 8 кГц потоковым FIR-фильтром со срезом 3,6 кГц и режет на чанки по 300 мс. Контракт короткий: создать встречу (резервируются две дорожки, при перегрузе сразу 429), два WebSocket с ролью, события вида {"type":"phrase","role":"host","text":"…","start_ms":…,"end_ms":…,"lag_ms":…}, закрытие с finalize и хвостом фраз. Частичных гипотез нет, и это надо прямо писать в контракте: T-one отдаёт текст фразой по паузе, пословного потока от него не дождаться.
Живая проверка на машине B, отдельный прогон на синтетическом потоке: загрузка движка 4,2 с, фразы приходят с отставанием 990–1200 мс от живого края, p95 тика 229 мс при двух дорожках, 517 МБ памяти. На живом диалоге выше было 272 мс и 1080–1200 мс. Требование «p95 не больше 2 с» выполняется с запасом. Лимит дорожек задаётся конфигом по результату кривой; сверх лимита новая встреча получает отказ, а переключаться на облако или выключать субтитры решает вызывающая сторона. В логах нет ни текста, ни аудио, только идентификаторы и время.
Деньги

Час встречи в Deepgram nova-3 streaming, две дорожки: около 49 ₽ по промо-цене ($0,0048 за минуту) и около 79 ₽ по обычной ($0,0077), курс 84,9. Нюанс, который легко пропустить: стерео с multichannel тарифицируется поканально, десять минут записи это двадцать минут биллинга.
Дальше расчёт условный. Какой процессор стоит за тарифами ниже, я не замерял, и считаю так, будто они класса машины A. Сервер под T-one: Timeweb 8 vCPU / 12 ГБ за 2 790 ₽ в месяц окупается примерно с 57 часов встреч в месяц против промо-цены и с 36 против обычной. Yandex Cloud 8 vCPU / 16 ГБ за 10 944 ₽: 224 и 139 часов. Резервный сервер удваивает пороги.
И вот тут возвращается главный тезис. Эти расчёты предполагают, что 8 vCPU держат до 16 встреч. На машине класса B те же деньги дают две, то есть в восемь раз меньше. Считать окупаемость имеет смысл только после того, как зонд прогнан на конкретном тарифе конкретного хостера. Это минута работы, а ошибиться без неё можно в разы.
Что в расчёт не вошло: разработка и поддержка сервиса, дежурства, резерв и чистовой текст после встречи, потому что T-one пунктуацию не ставит.
Грабли
Поколение процессора важнее числа vCPU. AVX без AVX2 и FMA: в 2,5 раза медленнее на ядро, в 4–5 раз при всех потоках onnxruntime и в 6–8 раз по числу дорожек, которые держит сервер.
6 vCPU не равны шести ядрам. Признак: больше потоков onnxruntime делают хуже, p95 растёт при половинной загрузке.
Веса T-one в fp16. int8 не применить, fp32 не ускоряет, но вдвое экономит память при загрузке.
Батчинг дорожек на слабом CPU бесполезен. Поток на дорожку проще и держит больше.
8 кГц против широкополосного WebRTC. Ресемплинг меняет распознавание, на образце он добавил одну ошибку. Качество мерить по боевому пути.
Нет пунктуации и заглавных. Фраза приходит по паузе с задержкой 1–1,2 с. Интерфейс не должен ждать пословного текста.
KenLM почти не грузит процессор, но занимает 5,5 ГБ памяти.
WER между движками нельзя сравнивать без нормализации чисел.
Шумные соседи. На другой виртуалке с тем же E5-2680 v2, но со steal 22–31%, один чанк стоил 218 мс против 109 на чистой машине. Перед замером смотрите колонку
stвvmstat.Golos мог быть в обучении T-one. Пять правильно распознанных фраз из него ничего не доказывают.
Боевого сервера у T-one нет, есть демо на FastAPI. Серверную часть пишете сами.
Выводы
Облако выигрывает, когда часов встреч немного, нужна пунктуация и качество без возни. Свой сервер выигрывает, когда аудио не должно покидать страну, часов много, и у вас есть процессор с AVX2, что вы проверили зондом, а не прочитали в тарифе. T-one на CPU честно даёт фразы с отставанием около секунды, но по словам уступает и облаку, и GigaAM; для чистового текста после встречи нужна другая модель.
Вопрос к тем, кто дошёл до конца: кто гонял T-one или GigaAM в проде на WebRTC-аудио и какой WER получил на живой широкополосной речи?
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.