Модели и бэкенды
Сценарий
gigastt уже работает с головой rnnt по умолчанию на CPU-бэкенде, и теперь
нужно осознанно что-то поменять: другую голову распознавания (пунктуация «из
коробки» или языки помимо русского), более лёгкую загрузку модели, более
быстрый execution provider под ваше железо или больший пул сессий. Эта глава
отвечает на четыре вопроса проверяемыми рецептами: какую голову, INT8
или FP32, какой бэкенд и сколько RAM нужно пулу.
Цифры WER и RTF здесь не дублируются — канонические таблицы с доверительными
интервалами живут в
docs/benchmarks.md;
глава лишь ссылается на них. Флаги сверены с gigastt <command> --help; полный
справочник флагов —
docs/cli.md.
Предпосылки
- Установленный gigastt (бинарь, пакет или образ) — см. Начало работы.
- Диск: ~1,1 ГБ свободно для стандартного пути с FP32-загрузкой (FP32-набор плюс сгенерированный INT8-энкодер) или ~250 МБ для лёгкого pre-quantized пути.
- Для сборки нестандартного бэкенда из исходников: Rust 1.88+ и
protocвPATH(требования сборки: docs/architecture.md).
Рецепт
Выбор головы распознавания
Голова выбирается флагом --model-variant (env GIGASTT_MODEL_VARIANT) у
команд serve / download / transcribe. Все головы используют общий
mel-фронтенд и контракт входа 16 кГц моно; различаются ONNX-файлами,
словарём и декодированием.
| Голова | Размер на диске | Языки | Текст на выходе | Точность | Когда брать |
|---|---|---|---|---|---|
rnnt (по умолчанию) | энкодер 844 МБ FP32 → ~215 МБ INT8 (авто-квантизация) + decoder/joiner/vocab (несколько МБ) | русский | «Голый» lowercase; дополняйте --punctuation / --itn (включены по умолчанию в режиме auto) | Лучший русский WER из четырёх — таблица | Русскоязычные нагрузки; дефолт не случаен |
e2e_rnnt | Тот же класс размера, что у rnnt (~850 МБ FP32 → INT8 генерируется локально) | русский | Пунктуация / регистр / ITN встроены, один проход | WER выше, чем у rnnt, но лучший F1 пунктуации/регистра — сравнение | Нужен читаемый русский текст за один проход, без шага восстановления |
ml_ctc | ~225 МБ pre-quantized INT8, только энкодер (без decoder/joiner) | ru/en/kk/ky/uz | «Голый» lowercase | Мультиязычные таблицы | Мультиязычное аудио при малом футпринте |
ml_ctc_large | ~592 МБ pre-quantized INT8, только энкодер | ru/en/kk/ky/uz | «Голый» lowercase | Лучшая мультиязычная точность; на чистом русском чтении приближается к rnnt — таблица | Смешанные языки или английский/казахский/кыргызский/узбекский как таковые |
Два жёстких ограничения, следующих из таблицы:
- У
rnnt/e2e_rnntкириллический словарь — английский они не могут транскрибировать в принципе. Для английского (или kk/ky/uz) подходят только Multilingual-головы. - Multilingual-головы — это encoder-only CTC: они всегда поставляются и работают как pre-quantized INT8 — ни FP32-загрузки, ни шага квантизации на устройстве для них не существует.
Загрузка и запуск другой головы:
gigastt download --model-variant e2e_rnnt
gigastt serve --model-variant e2e_rnnt
Правила автоопределения (из crates/gigastt-core/src/model/mod.rs):
- Без
--model-variantдвижок определяет установленную голову по файлам в каталоге модели и использует полный комплект как есть (вообще без сетевых запросов). - Явный
--model-variant, отличный от установленной головы, → запрошенный набор скачивается рядом; головы никогда не смешиваются в одном инференсе, в лог пишется предупреждение. Если файлы нескольких голов сосуществуют, а вариант не задан, автоопределение предпочитаетrnnt. - Пустой каталог модели + отсутствие флага → скачивается дефолтный
rnnt.
Проверка:
curl -s http://127.0.0.1:9876/health
# {"status":"ok","model":"gigaam-v3-e2e-rnnt","variant":"e2e_rnnt",...}
curl -s http://127.0.0.1:9876/v1/models
# .id / .name отражают загруженную голову; .encoder показывает int8 или fp32
Читаемый текст: пунктуация, ITN и hotwords
Дефолтная голова rnnt выдаёт голый lowercase. Читаемый русский — это
пост-проход (или голова e2e_rnnt, где пунктуация/регистр/ITN уже внутри).
| Кноб | Дефолт (auto) | Принудительно on | Принудительно off |
|---|---|---|---|
--punctuation / GIGASTT_PUNCTUATION | on для rnnt, если есть RuPunct | on (докачает модель) | off |
--itn / GIGASTT_ITN | on для rnnt | on | off |
| REST / WS | политика сервера | ?punctuation=true / configure | ?punctuation=false |
# Явный читаемый пайплайн на дефолтной голове
gigastt serve --punctuation on --itn on
curl -s http://127.0.0.1:9876/health
# "punctuation":true,"itn":true
# Переопределение на запрос (409 punctuation_not_available, если модели нет и force on)
curl -s -X POST "http://127.0.0.1:9876/v1/transcribe?punctuation=true&itn=true" \
-H "Content-Type: application/octet-stream" --data-binary @clip.wav
Hotword bias поднимает бренды и доменные термины, которые модель иначе корежит. Одна фраза на строку; опционально встроенный русский lexicon брендов:
cat > /tmp/hotwords.txt <<'EOF'
гигачат
сбер
еком
EOF
gigastt serve --hotwords-file /tmp/hotwords.txt --hotwords-default \
--hotwords-boost 5.0
# Те же флаги работают на offline `transcribe` / `transcribe-batch` / `watch`.
Проверка: клип с брендом из списка — без файла гипотеза часто ломает
токен, с hotwords написание совпадает. Слишком большой boost может «выдумывать»
бренды из шума — начните с 5.0. Флаги:
docs/cli.md.
INT8 или FP32
Короткий ответ: всегда INT8, если только вы не отлаживаете саму модель.
INT8-энкодер работает как настоящие целочисленные вычисления (ядра
DynamicQuantizeLinear + MatMulInteger/ConvInteger), сжимает энкодер
844 МБ → 215 МБ (~3,9×) и даёт ~0% деградации WER — цифры и методология:
docs/benchmarks.md.
Что происходит на каждом пути (RNN-T-головы):
- По умолчанию —
gigastt download(или первыйserve/transcribe) скачивает FP32-набор с HuggingFace (istupakov/gigaam-v3-onnx), затем однократно (~2 мин) выполняет квантизацию нативным Rust-кодом и пишетv3_rnnt_encoder_int8.onnxрядом. Движок предпочитает INT8-энкодер, когда файл присутствует. - Лёгкий путь —
gigastt download --prequantizedскачивает готовый INT8-бандл (INT8-энкодер + decoder + joiner + vocab) из закреплённого GitHub Release: ни ~844 МБ FP32-загрузки, ни ~2-минутной квантизации. Это также запасной вариант, когда HuggingFace недоступен, а GitHub — нет. - Multilingual —
ml_ctc/ml_ctc_largeскачивают pre-quantized INT8-энкодер istupakov’а напрямую с HuggingFace;--prequantizedдля них — холостое уточнение (отдельного бандла нет). - Вручную —
gigastt quantize [--force]повторно запускает квантизацию для головы, определённой в--model-dir(например, после подмены FP32- энкодера своим файнтюном). - Отказ —
--skip-quantize(envGIGASTT_SKIP_QUANTIZE=1) пропускает шаг квантизации; движок тогда загружает FP32-энкодер с ~4× расходом RAM на слот пула и более медленными CPU-ядрами. Оставьте это только для отладки.
Проверка:
ls ~/.gigastt/models/
# v3_rnnt_encoder_int8.onnx присутствует рядом с decoder/joint/vocab
gigastt transcribe sample.wav 2>&1 | grep 'transcribe complete'
# ... encoder=int8/cpu ... rtf=0.1xx
Поле encoder=int8/<backend> в строке лога о завершении — истина о том, какой
файл энкодера был загружен.
Бэкенд под ваше железо
Бэкенд — это compile-time Cargo-фича, а не runtime-флаг: провайдер зашит в бинарь, который вы устанавливаете или собираете:
| Ваше железо | Фича | Провайдер | Примечания |
|---|---|---|---|
| Любое (по умолчанию) | — | CPU (ONNX Runtime) | Эталонная сборка; RTF заметно ниже 1.0 с INT8-энкодером |
| macOS ARM64 (M1–M4) | --features coreml | CoreML + Neural Engine | Готовые релизные бинари macOS уже собраны с этой фичей; ~3× энкодер на коротких клипах, ~5,6× на длинных файлах против CPU |
| Linux x86_64 + NVIDIA | --features cuda | CUDA 12+ | Готового tarball нет — берите Docker-образ -cuda или Dockerfile.cuda; при отсутствии GPU в рантайме откатывается на CPU |
| Android / ARM64 | --features nnapi | NNAPI (NPU/DSP) | Не является взаимоисключающей с остальными |
| macOS ARM64, экспериментально | --features ane | Нативный Apple Neural Engine (Core ML .mlpackage) | Только голова rnnt, ускорение файлового режима; см. ниже |
| Apple Silicon, экспериментально | --features candle | Чистый Rust Candle на Metal | Только голова rnnt, FP32; см. ниже |
Соберите подходящий вариант:
cargo build --release # CPU, любая платформа
cargo build --release --features coreml # macOS ARM64
cargo build --release --features cuda # Linux x86_64 + NVIDIA (CUDA 12+)
cargo build --release --features nnapi # Android / ARM64
Исключительность проверяется на этапе компиляции: coreml и cuda взаимно
исключают друг друга; ane конфликтует с coreml/cuda/nnapi/candle;
candle конфликтует с coreml/cuda. На плохой комбинации срабатывает
compile_error!.
Поведение в рантайме, о котором стоит знать:
- Откат на CPU — намеренный, никогда не падение. CoreML-сборка при старте
выполняет ~1-секундную warmup-проверку; при неудаче пишет в лог
falling back to CPU execution providerи пересобирает сессии на CPU. CUDA-бинарь аналогично работает на CPU, когда GPU не виден (например, контейнер запущен без--gpus all). - Упаковка CUDA. Готового CUDA-tarball нет — релизная матрица собирает
CPU-бинари для Linux (x86_64, aarch64), Windows и CoreML-сборку для macOS
ARM64. Для GPU берите опубликованный образ
ghcr.io/ekhodzitsky/gigastt:<ver>-cuda(рецепты: Развёртывание и эксплуатация) или собирайте через Dockerfile.cuda. - Нативный ANE-бэкенд (
--features ane, macOS ARM64): выполняет энкодерrnntна Neural Engine через пакеты Core ML с фиксированными формами по бакетам, ~10× тёплого end-to-end ускорения против CPU-сборки (упор в декодирование). Пакеты скачиваются командойgigastt download --ane(в~/.gigastt/models/ane/); модельe2e_rnntпрозрачно откатывается наort-энкодер, а стриминговые окна всегда идут по CPU-пути — ANE является ускорителем файлового режима. Полный дизайн и честные цифры: docs/ane-backend.md. - Candle-бэкенд (
--features candle, экспериментально): чисто-Rust инференс на Metal GPU, побайтовая идентичность сort, только головаrnnt, FP32-веса, однократно конвертируемые скриптомscripts/convert_gigaam_candle.py. Подробности: docs/candle-backend.md.
Проверка:
gigastt transcribe sample.wav 2>&1 | grep 'transcribe complete'
# encoder=int8/coreml → активна CoreML-сборка (int8/cuda, int8/cpu, ...)
# encoder=int8/ane → энкодер обработал нативный ANE-бэкенд
На CoreML-сборке также смотрите стартовый лог: отсутствие строки falling back to CPU execution provider означает, что warmup-проверка прошла.
Файлы модели в замкнутом контуре
Всё, что нужно движку, живёт в одном каталоге — ~/.gigastt/models/ по
умолчанию, переопределяется флагом --model-dir у serve / download /
transcribe / quantize. Поэтому набор моделей — это обычный файловый
артефакт, который можно подготовить и переносить:
# На машине с сетью:
gigastt download --prequantized --model-dir /srv/gigastt-models
# Любым способом скопируйте на целевой хост (rsync, USB, хранилище артефактов):
rsync -a /srv/gigastt-models/ offline-host:/srv/gigastt-models/
# На офлайн-хосте — запретить любые сетевые обращения, падать сразу, а не висеть:
gigastt --offline serve --model-dir /srv/gigastt-models
Свойства, которые делают это безопасным:
- Целостность проверяет бинарь, а не вы. Каждая загрузка проверяется по
SHA-256 против контрольных сумм, закреплённых в коде, складывается в
.partialи атомарно переименовывается на место; повреждённый файл удаляется, а не принимается. Несовпадение контрольной суммы завершает процесс с кодом65(сеть69, диск74, Ctrl-C130) — пригодно для скриптов. - Полный набор модели = ноль сети. При наличии полного комплекта головы
(энкодер — INT8 или FP32 — плюс decoder/joiner/vocab для RNN-T-голов или
INT8-энкодер + vocab для Multilingual) запуск не выполняет ни одного
сетевого запроса, даже без
--offline.--offline/GIGASTT_OFFLINE=1превращает любую недостающую опциональную модель (пунктуация, VAD, диаризация) в немедленную ошибку с именем файла, который нужно предоставить. - Голова автоопределяется по файлам — скопированному каталогу не нужен
флаг
--model-variantна целевом хосте. - При копировании включайте
*_int8.onnx-энкодер (или скопируйте также FP32- энкодер и выполнитеgigastt quantize --model-dir …на целевом хосте), vocab и — дляrnnt/e2e_rnnt— decoder и joiner.
Для полностью упакованной офлайн-установки (tarball с бинарём + INT8-моделью + моделью пунктуации + systemd-юнит или вариант из двух deb-пакетов) берите релизный offline-бандл — рецепт и шаги проверки в Развёртывание и эксплуатация; состав — README-OFFLINE.md.
Проверка:
gigastt --offline transcribe sample.wav --model-dir /srv/gigastt-models
# транскрибирует без сети; недостающий файл — немедленная ошибка с именем файла
Память под пул сессий
Каждый слот пула десериализует свою копию энкодера, поэтому RSS растёт
линейно с --pool-size (по умолчанию 2). Движок закладывает на слот примерно
2 × размер-файла-энкодера резидентной памяти (измерено ~1,9× на INT8-
энкодере rnnt, CPU-провайдер, release-сборка):
| Голова (как загружена) | Файл энкодера | ≈ RAM на слот пула | Дефолтный пул 2 |
|---|---|---|---|
rnnt / e2e_rnnt INT8 | ~215 МБ | ~0,4 ГБ | ~790 МБ суммарного RSS |
rnnt / e2e_rnnt FP32 (--skip-quantize) | 844 МБ | ~1,6 ГБ | ~3,3 ГБ — никогда в проде |
ml_ctc INT8 | ~225 МБ | ~0,45 ГБ | ~0,9 ГБ |
ml_ctc_large INT8 | ~592 МБ | ~1,2 ГБ | ~2,4 ГБ |
Встроены две защиты:
- Авто-ограничение по RAM. При загрузке запрошенный пул урезается так,
чтобы энкодеры пула оставались в пределах половины общей RAM — при срабатывании
пишется предупреждение
Capping pool size N -> M. Ограничение никогда не повышает ваш запрос и никогда не опускается ниже 1. - Деградированный запуск.
--pool-min-size 1(по умолчанию) позволяет серверу стартовать на частично загруженном пуле вместо падения, когда память заканчивается посреди загрузки.
Эмпирическое правило: RAM ≥ pool_size × расход-на-слот + ~1 ГБ на ОС и пики запросов. На машине с 4 ГБ это означает --pool-size 1–2 с INT8-энкодером —
тот же вывод, что и в пункте про OOM в
Развёртывание и эксплуатация.
Проверка:
# нет предупреждения "Capping pool size" при старте, и:
curl -s http://127.0.0.1:9876/ready
# {"status":"ready","pool_available":2,"pool_total":2}
Проверка результата
Сквозной смоук после любого изменения из этой главы:
ls ~/.gigastt/models/ # полный набор файлов головы, включая *_int8.onnx + vocab
gigastt transcribe sample.wav 2>&1 | grep 'transcribe complete'
# encoder=<int8|fp32>/<cpu|coreml|cuda|ane|candle>, rtf заметно ниже 1.0
curl -s http://127.0.0.1:9876/health # "model"/"variant" соответствуют выбранной голове
curl -s http://127.0.0.1:9876/ready # ready, pool_available >= 1
Затем сверьте ожидания по точности с docs/benchmarks.md, а не с кустарными замерами — харнесс, манифесты и нормализация значат больше, чем секундомер.
Частые ошибки
- Английское аудио превращается в мусор с головой по умолчанию. Это не
баг: у
rnnt/e2e_rnntкириллический словарь, и латиницу они выдавать не могут в принципе. Переключитесь на--model-variant ml_ctc(илиml_ctc_large). --model-variantсловно игнорируется после первой загрузки. Движок автоопределяет голову по каталогу модели; явный вариант, отличный от установленного, инициирует вторую загрузку рядом (с предупреждениемvariants are never mixed), а следующий запуск без флага снова предпочтётrnnt. Удалите файлы неиспользуемой головы, чтобы каталог был однозначным, и верните место на диске.- Первый
serveвыглядит зависшим. Это одноразовая загрузка ~850 МБ FP32- ~2 мин квантизации;
/healthотвечает200сmodel:"loading", пока/readyостаётся503 initializing. Уберите это окно командойgigastt download --prequantizedи стробируйте клиентов по/ready, никогда по/health.
- ~2 мин квантизации;
- OOM после переключения на
ml_ctc_large. Каждый слот теперь стоит ~1,2 ГБ. Снизьте--pool-size, держите--pool-min-size 1, чтобы тесный хост загружался деградированно, и следите за предупреждениемCapping pool sizeпри старте. - CoreML-сборка не быстрее CPU. Ищите в стартовом логе
falling back to CPU execution provider— warmup-проверка не прошла, и движок (намеренно) работает на CPU. Полеencoder=int8/cpuв логе завершения это подтверждает. - CUDA-контейнер работает на скорости CPU. GPU не виден: контейнеру нужны
NVIDIA Container Toolkit и
--gpus all. Бинарь молча откатывается на CPU — проверяйтеencoder=int8/cudaв логе завершения. error: ane and coreml are mutually exclusive(или похожая) при сборке. Бэкенд-фичи конфликтуют по дизайну; собирайте ровно одну изcoreml/cuda/ane/candle(nnapi— исключение).SHA-256 mismatchво времяgigastt download. Стейджинговая загрузка повреждена или подменена; она удаляется, а не принимается, CLI завершается с кодом65. Просто перезапустите команду — не переименовывайте.partialвручную на место.
Ссылки
- Начало работы — установка и первая транскрипция
- CLI и пакетная обработка — рецепт по пропускной способности и памяти для офлайн-CLI
- Развёртывание и эксплуатация — offline-бандл, systemd, пункты ранбука про OOM
- docs/benchmarks.md — канонические таблицы WER / RTF / футпринта
- docs/architecture.md — пайплайн, провайдеры, внутренности квантизации
- docs/cli.md — полный справочник флагов
- docs/ane-backend.md — нативный бэкенд Apple Neural Engine
- docs/candle-backend.md — бэкенд Candle/Metal
- docs/verifying-releases.md — проверка релизных артефактов
- packaging/offline/README-OFFLINE.md — состав offline-бандла