Сеня девелопер уходит → CNER Developer приходит: как мы дообучили GigaAM-v3


Наша программа для диктовки Glas работает на GigaAM-v3. С обычной русской речью модель справлялась хорошо, а на IT-терминах спотыкалась. Docker превращался в «докер», Python — в «Пайтон». Иногда исчезали целые слова: живую фразу «Мой ИИ редко ошибается» модель записала как «Мои редко ошибается».
Мы дообучили рабочую модель на 25 часах живой речи. На 525 отложенных коротких записях WER снизился с 8,81% до 5,76% — примерно на 35% относительно исходного значения. Доля правильно написанных терминов выросла с 43,8% до 86,7%. Скорость почти не изменилась.
Это версия 0.01, пока бета. Есть и ухудшения: модель чаще пропускает слова, а на обычной речи хуже воспроизводит регистр и пунктуацию. К тому же тест размечен тем же автоматическим конвейером, что и обучение, с выборочной ручной проверкой. Часть выигрыша может объясняться совпадением стиля разметки.
Что хотели исправить
GigaAM-v3 — семейство моделей распознавания речи на Conformer. Мы используем v3_e2e_rnnt: она сразу выдаёт текст с регистром и пунктуацией. На сервере энкодер работает в FP16, RNNT-голова — в FP32. Длинные записи режет по паузам Silero VAD, короткие сегменты распознаются пачками. Декодирование жадное, без внешней языковой модели; GPU — RTX A2000 12 ГБ.
Нас интересовали три группы ошибок: английские названия, освоенные англицизмы вроде «дашборд» и аббревиатура «ИИ». Словарь замен помог бы лишь частично. Один Kubernetes в тестах превращался в «Кьюбернит», «Куберните», «кубернетис», «Gubernitis» и «Qubernit». А отличить «ИИ» от затянутого союза «и» по одному тексту бывает трудно — нужен звук.
Менять токенизатор не пришлось: его словарь на 1024 токена уже содержит латиницу, и нужные названия кодируются без <unk>. Мы решили сохранить архитектуру, код модели и формат весов, чтобы дообученную версию можно было поставить на место прежней.
Первая попытка: хороший результат на плохом тесте
Сначала взяли CTC-вариант GigaAM и собрали 4,4 часа данных: около 1100 синтетических фраз с IT-терминами и примерно столько же записей из русской и английской частей FLEURS. Заморозили нижние 12 слоёв энкодера, обучали верхние четыре и CTC-голову.
На стенде из 12 синтетических фраз доля правильно написанных терминов выросла с 6% до 86%. Но сравнение с рабочей RNNT на 150 живых записях FLEURS выглядело иначе:
FLEURS ru, 150 живых записей | Прод (RNNT) | Дообученная CTC |
|---|---|---|
WER без пунктуации | 4,65% | 5,39% |
WER с регистром и пунктуацией | 9,69% | 28,75% |
Модель потеряла регистр и пунктуацию. В скрипте подготовки данных мы взяли transcription — нормализованный текст — вместо raw_transcription. Эти поля описаны в карточке FLEURS. Ни в одном из 150 ответов дообученной CTC не было заглавной буквы и точки.
К этому добавились «deadloin» вместо «дедлайн» и слабое распознавание терминов с русским акцентом. От модели отказались. Для следующей попытки выбрали рабочую RNNT той же ревизии, живую речь и проверку с учётом пунктуации. Ещё до разметки договорились, как писать термины.
Как собрали и разметили данные
Короткие записи отбирали по текстам исходной модели: искали латиницу, варианты терминов из глоссария и похожие транслитерации. Добавили обычную речь и публичные IT-подкасты. Дикторов короткого теста отложили целиком; для проверки подкастов взяли отдельный выпуск, которого не было в обучении.
Набор | Записей | Часов | Состав |
|---|---|---|---|
Обучение | 10 071 | 25,1 | 8 301 короткая запись, 1 770 сегментов подкастов |
Валидация | 290 | 0,7 | те же источники |
Тест «короткие записи» | 525 | 1,3 | отложенные дикторы |
Тест «подкаст» | 57 | 0,2 | подкаст, которого нет в обучении |
Тест «известное» | 318 | 0,7 | 150 записей FLEURS, 168 синтетических фраз |
В обучающем наборе синтетики не было. Глоссарий содержал 126 терминов с правильными написаниями и типичными ошибками. Правила разметки задали отдельно:
Что | Как пишем | Примеры |
|---|---|---|
Продукты, технологии, компании, форматы, аббревиатуры | латиницей | Docker, PostgreSQL, Jira, CRM, API |
Освоенные заимствования, которые склоняются | кириллицей | дедлайн, релиз, деплой, бэкенд, дашборд |
Глаголы от англицизмов | кириллицей | задеплоить, закоммитить, зашарить |
Падеж у латинского названия | не пишем | «в Докере» → «в Docker» |
Российские названия и аббревиатуры | кириллицей | Яндекс, Сбер, ВОЗ, ИИ |
Английское «эй-ай» | латиницей | AI |
Составные с «ИИ» | через дефис | ИИ-агент, ИИ-ассистент |
Запинки | не пишем | «э-э», «м-м», протяжное «и-и-и» → «и» |
Две расшифровки и аудиокорректор
Все 25 часов вручную не расшифровывали. Каждую запись обрабатывали GigaAM и Whisper large-v3-turbo. В наших проверках первая лучше сохраняла русский текст и пунктуацию, вторая — английские названия. Whisper запускали дважды: во второй проход передавали подсказку с терминами-кандидатами.
За основу брали текст GigaAM и заменяли участки с терминами вариантом Whisper. Запись получала статус ok, dispute или drop. Сначала разногласия отдавали текстовой LLM, но она выбирала правдоподобную фразу, не слыша записи. Перешли на мультимодальную модель: давали ей аудио и обе расшифровки, просили исправить расхождения.
На 132 синтетических записях с известным текстом такой корректор получил WER 4,8% и 94,6% правильных терминов. Это проверка конвейера на синтетике, а не оценка качества всей живой разметки. Две гипотезы помогают ограничить выдумки, но не исключают их.
Обычные записи, где GigaAM и Whisper соглашались, не отправляли в LLM. Ответы корректора с fuzz.ratio < 45 относительно гипотезы GigaAM отбрасывали и повторяли запрос: сильное расхождение могло означать выдуманный текст или перепутанный файл. Такой фильтр тоже не доказывает, что оставшаяся метка верна.
В конце убирали запинки, приводили названия и дефисы к общим правилам, отбрасывали записи с <unk>. Разметку проверяли вручную выборочно.
Как дефис испортил метрику «ИИ»
Для «ИИ» считали и долю найденных вхождений, и лишние срабатывания. Первые замеры показали девять лишних «ИИ» у новой модели против нуля у исходной. Разобрали примеры: шесть из девяти оказались расхождениями «ИИ-агент» и «ИИ агент». Подсчёт по словам принимал разницу в дефисе за ошибку распознавания.
Унифицировали написание составных слов в обучении и тесте, а счётчик научили видеть «ИИ» внутри «ИИ-агент». Для обучения набралось 278 записей-кандидатов, для теста — 132. В зависимости от варианта обучения после исправления оставалось от одного до трёх лишних «ИИ» на 525 коротких записей.
Как обучали RNNT
GigaAM-v3 опубликована под MIT: эта лицензия допускает модификацию, включая дообучение, и коммерческое использование. Публиковать свои дообученные веса она не обязывает; в копиях модели и кода нужно сохранять уведомление об авторских правах и текст лицензии. MIT указана в карточке используемого варианта e2e_rnnt, условия — в официальном репозитории.
Обучали на одной арендованной A100 40 ГБ, в PyTorch. Модель загружали через AutoModel с trust_remote_code, использовали torchaudio.functional.rnnt_loss.
В выбранном варианте заморозили препроцессор, субдискретизацию и первые десять слоёв энкодера. Обучались верхние шесть слоёв и RNNT-голова — 83,4 млн из 222,5 млн параметров. Первые 200 шагов обновляли только голову.
Параметры обучения:
AdamW: базовый learning rate 1·10⁻⁴, для энкодера — в пять раз меньше; weight decay 1·10⁻³;
линейный прогрев 300 шагов, затем косинусное снижение до 10% базового learning rate;
FP16 с
GradScaler, обрезка градиента по норме 1,0;SpecAugment: две частотные маски до 10 полос и две временные до 4% длины;
четыре эпохи, 1332 шага; чекпойнт выбирали по WER на валидации.
Две детали потребовали отдельного внимания.
Размер батча. В нашей реализации материализуется тензор логитов B × T × (U+1) × V, где T — число кадров энкодера, U — длина текста в токенах, V = 1025 с учётом blank. Поэтому памяти может не хватить даже на короткие записи, если текст длинный. Батчи ограничивали и по суммарной длительности — 400 секунд, и по размеру тензора — 8·10⁸ элементов.
Первый вход предсказателя. При штатном декодировании GigaAM начинает с нулевого эмбеддинга. Обучение должно начинаться так же. Ниже фрагмент нашего кода, зависящий от внутреннего устройства модели:
def joint_logits(inner, encoded, targets, blank):
dec, joint = inner.head.decoder, inner.head.joint
sos = torch.full((targets.size(0), 1), blank, dtype=torch.long, device=targets.device)
emb = dec.embed(torch.cat([sos, targets.long()], 1)) # blank → нулевой эмбеддинг
g, _ = dec.lstm(emb.transpose(0, 1))
f = joint.enc(encoded.transpose(1, 2))
p = joint.pred(g.transpose(0, 1))
return joint.joint_net(f.unsqueeze(2) + p.unsqueeze(1)) # (B, T, U+1, V)
После обучения сверяли ключи весов с исходным файлом и сохраняли каждый тензор в прежнем типе: энкодер FP16, голова FP32. config.json, код модели и токенизатор оставили без изменений.
Всего сравнили три варианта. Выбрали C: десять замороженных слоёв и меньший learning rate энкодера. Он дал одно лишнее «ИИ» вместо трёх у A и B, но распознал 93,5% эталонных «ИИ» против 95,4% у A. Для диктовки мы сочли ложное срабатывание более неприятной ошибкой.
Как проверяли
Сначала проверили сам код оценки на исходной модели. Он должен повторять серверный конвейер: лог-мел признаки считаются отдельно для каждой записи, затем дополняются нулями; энкодер работает в FP16, голова — в FP32; декодирование штатное.
В среде обучения с ответами рабочего сервиса совпали 513 текстов из 525, на прод-сервере — 524 из 525. Полного совпадения не получили, поэтому итоговое сравнение обеих моделей провели на одном сервере, с одинаковыми GPU, PyTorch и кодом распознавания.
Считали четыре показателя:
WER — доля замен, пропусков и вставок относительно числа слов в эталоне; перед подсчётом убирали регистр и пунктуацию, заменяли «ё» на «е».
WER e2e — тот же расчёт без этой нормализации.
Термины — доля латинских терминов из глоссария, записанных точно как в эталоне.
«ИИ» — доля распознанных вхождений и отдельный счётчик лишних.
Эталоны получены тем же конвейером, что и обучающие метки. Поэтому модель могла улучшить метрику, переняв его стиль: например, перестать писать запинки и тире перед репликами. Отложенные дикторы защищают от пересечения голосов, но не устраняют эту зависимость разметки.
Результаты и цена улучшения
Здесь «прод» — исходная модель, «новая» — выбранный вариант C. Все значения получены на прод-сервере.
Набор | Метрика | Прод | Новая |
|---|---|---|---|
Короткие записи, 525 | WER | 8,81 | 5,76 |
WER e2e | 10,54 | 8,83 | |
Термины, % | 43,8 | 86,7 | |
«ИИ», % | 72,5 | 93,5 | |
Лишние «ИИ» | 0 | 1 | |
Подкаст (не было в обучении), 57 | WER | 5,03 | 3,06 |
WER e2e | 7,90 | 5,52 | |
Термины, % | 44,4 | 100 | |
FLEURS ru, 150 | WER | 5,43 | 5,04 |
WER e2e | 9,58 | 9,41 | |
Чистая русская речь (синтетика), 36 | WER / WER e2e | 2,92 / 5,01 | 2,92 / 5,01 |
Скорость | секунд на секунду аудио | 0,0339 | 0,0342 |
Память GPU | пик, МиБ | 874 | 874 |
В этом замере обе модели обрабатывали минуту аудио примерно за две секунды. Одинаковая архитектура сама по себе не гарантирует одинаковую скорость — её мы измерили отдельно.
На 194 обычных коротких записях WER улучшился с 6,46% до 6,01%, а WER e2e ухудшился с 7,84% до 8,56%. Значит, выигрыш в словах сопровождался ухудшением по метрике, учитывающей регистр и пунктуацию.
На всех трёх группах теста суммарно стало на 19 пропущенных слов больше — примерно на 14 тысяч слов эталона. На коротких записях вставки сократились со 151 до 38, замены — с 464 до 325, но пропуски выросли с 85 до 95. Снижение вставок отчасти связано с тем, что модель перестала воспроизводить запинки, которых нет в эталоне.
Редкие названия остаются проблемой. На 48 синтетических фразах с редкими терминами доля правильных написаний выросла лишь с 4,9% до 12,3%. Этот тест отличается от живой речи, но показывает: дообучение не сделало модель надёжной на всех словах из глоссария.
Что слышно на живых записях
Эти фразы надиктовали в Glas после выкладки. Затем те же записи прогнали через прежнюю модель; в основной тест они не входят.
Сказано | Прежняя модель | Новая (0.01) |
|---|---|---|
Мой ИИ редко ошибается. | Мои редко ошибается. | Мой ИИ редко ошибается. |
Промты для ИИ могут быть разные. | Промты для могут быть разными. | Промты для ИИ могут быть разные. |
Хорошие промты для ИИ отличаются точностью и краткостью. | Хорошие пранты для ИИ отличаются точностью и краткостью. | Хорошие пранты для ИИ отличаются точностью и краткостью. |
Обнови конфиг Nginx и перезапусти Docker. | Обнови Config Jinks и перезапусти докер. | Обнови Confly Jings и перезапусти Docker. |
Обнови конфиг Nginx и перезапусти Docker. (второй дубль) | Обнови Config Jins и перепусти дакер. | Обнови Configins и перепусти Ducker. |
В первых двух фразах новая модель вернула пропущенное «ИИ». В третьей обе сохранили «ИИ» и обе ошиблись в слове «промты». Nginx не распознала ни одна; Docker новая модель записала правильно только в одном из двух дублей.
Есть и более неприятные ошибки. Ниже — примеры из публичного подкаста и синтетических записей; ответы моделей оставлены как есть.
Эталон | Прод | Новая | Что случилось |
|---|---|---|---|
Какой-нибудь современный SOC или задачи ИБ… | …современный Сока или задачи ИБ… | …современный SOC или задачи ИИ… | SOC исправлен, но «ИБ» стало «ИИ» — ложное срабатывание |
…зловредных файлов. Антифрод туда же. | …зловредных файлов. Антифрод туда же. | …зловредных файлов туда же. | пропущено слово |
На staging всё работает, а на production почему-то падает health check. | …падает Hells Check. | …падает health. | половина термина пропала |
Мы сделали апдейт приложения и выложили его в стор. | Мы сделали апдит… | Мы сделали аудит… | ошибка со сменой смысла |
Задачу я завёл в Jira, а описание положил в Confluence. | …в джира… | …в GRA… | было понятно — стало хуже |
Да? Закрываем контору. Ну нет, конечно же. | — Да? Закрываем кат. — Ну, нет, конечно же. | Да? Закрываем кат. Ну, нет, конечно же. | пропали тире перед репликами. По метрике это не ошибка — в эталонах тире нет, — но читать диалог стало труднее |
Как заменили модель на сервере
Дообученные веса мы не публикуем. Сервер берёт их из закрытой папки и сверяет SHA-256 файлов. Новую версию разместили в отдельном Docker-томе, старый сохранили для отката.
Перед переключением обе модели проверили в отдельном контейнере, не подключённом к очереди. Кроме тестовых наборов, прогнали тишину, шум и запись длиной 73 секунды. Тишину и шум отсеял VAD — это проверка всего конвейера, а не доказательство, что сама модель никогда не выдаёт на них текст.
Воркеры переключали по одному: дождаться освобождения, перезапустить на новом томе, проверить типы весов и пробное распознавание. При неудаче скрипт возвращал прежний образ и том. Метку версии добавили в ответ API и журнал, чтобы связывать жалобы с конкретной моделью.
Перед выкладкой обнаружилась отдельная проблема: после автоматического обновления драйвера NVIDIA новые GPU-контейнеры не запускались, хотя старые работали. Устранение заняло две с половиной минуты остановки распознавания ночью при пустой очереди. Поэтому всю подготовку к релизу назвать работой без простоя нельзя. Проверку запуска нового GPU-контейнера оставили обязательным шагом перед переключением воркеров.
Качество на живом потоке автоматически не посчитать без эталонных текстов. Для него собираем отзывы и разбираем присланные записи.
Что исправляем дальше
Сейчас приоритет — обычная речь с выверенной пунктуацией и примеры пропусков слов. Также собираем живые записи с редкими терминами: в текущем наборе даже «промты» встретились лишь в 42 записях из 10 071.
Первый прогон показал, сколько решений скрывается в разметке. Удалять ли запинки, как писать «ИИ-агент», сохранять ли тире в диалоге — всё это влияет и на поведение модели, и на оценку её качества. Прежде чем расширять датасет, нужно проверить, чему именно мы её учим.
Протестить работу новой модели можно в программе Glas, там никакие карты вводить не надо и 2 000 слов в неделю бесплатно.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.