Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Модели и бэкенды

Сценарий

gigastt уже работает с головой rnnt по умолчанию на CPU-бэкенде, и теперь нужно осознанно что-то поменять: другую голову распознавания (пунктуация «из коробки» или языки помимо русского), более лёгкую загрузку модели, более быстрый execution provider под ваше железо или больший пул сессий. Эта глава отвечает на четыре вопроса проверяемыми рецептами: какую голову, INT8- установку, какой бэкенд и сколько RAM нужно пулу.

Цифры WER и RTF здесь не дублируются — канонические таблицы с доверительными интервалами живут в docs/benchmarks.md; глава лишь ссылается на них. Флаги сверены с gigastt <command> --help; полный справочник флагов — docs/cli.md.

Предпосылки

  • Установленный gigastt (бинарь, пакет или образ) — см. Начало работы.
  • Диск: ~250–400 МБ свободно для lean INT8-установки (единственный runtime-путь; опциональные punct/VAD — отдельно).
  • Для сборки нестандартного бэкенда из исходников: Rust 1.94+ и protoc в PATH (требования сборки: docs/architecture.md).

Рецепт

Выбор головы распознавания

Голова выбирается флагом --model-variant (env GIGASTT_MODEL_VARIANT) у команд serve / download / transcribe. Все головы используют общий mel-фронтенд и контракт входа 16 кГц моно; различаются ONNX-файлами, словарём и декодированием.

ГоловаРазмер на дискеЯзыкиТекст на выходеТочностьКогда брать
rnnt (по умолчанию)~225 МБ lean INT8 (энкодер ~215 МБ + decoder/joiner/vocab)русский«Голый» lowercase; дополняйте --punctuation / --itn (включены по умолчанию в режиме auto)Лучший русский WER из четырёх — таблицаРусскоязычные нагрузки; дефолт не случаен
e2e_rnntТот же lean INT8 (~225 МБ)русскийПунктуация / регистр / ITN встроены, один проходWER выше, чем у rnnt, но лучший F1 пунктуации/регистра — сравнениеНужен читаемый русский текст за один проход, без шага восстановления
ml_ctc~225 МБ pre-quantized INT8, только энкодер (без decoder/joiner)ru/en/kk/ky/uz«Голый» lowercaseМультиязычные таблицыМультиязычное аудио или ~1,5× RTF vs rnnt (ready RSS ≈ rnnt, не lean-RAM SKU)
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/variant.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 на пути ORT — int8
# (candle сообщает fp32 — FP32 safetensors, is_int8()==false)

Читаемый текст: пунктуация, ITN и hotwords

Дефолтная голова rnnt выдаёт голый lowercase. Читаемый русский — это пост-проход (или голова e2e_rnnt, где пунктуация/регистр/ITN уже внутри).

КнобДефолт (auto)Принудительно onПринудительно off
--punctuation / GIGASTT_PUNCTUATIONon для rnnt, если есть RuPuncton (докачает модель)off
--itn / GIGASTT_ITNon для rnntonoff
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-установка (runtime)

Короткий ответ: runtime — только INT8. Поставляемый энкодер работает как настоящие целочисленные вычисления (ядра DynamicQuantizeLinear + MatMulInteger/ConvInteger), ~215 МБ на диске, ~0% деградации WER относительно FP32-исходника — цифры и методология: docs/benchmarks.md.

Что происходит на каждом пути:

  • По умолчанию (RNN-T)gigastt download (или первый serve / transcribe) скачивает pre-quantized INT8-бандл (INT8-энкодер + decoder + joiner + vocab) из закреплённого GitHub Release. Runtime никогда не загружает FP32.
  • Multilingualml_ctc / ml_ctc_large скачивают pre-quantized INT8-энкодер istupakov’а напрямую с HuggingFace.
  • Упаковкаgigastt quantize [--force] нужен локальный FP32 ONNX как исходник (например, после своего файнтюна). Это не runtime-путь.

Проверка:

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 coremlCoreML + Neural EngineГотовые релизные бинари macOS уже собраны с этой фичей; ~3× энкодер на коротких клипах, ~5,6× на длинных файлах против CPU
Linux x86_64 + NVIDIA--features cudaCUDA 12+Готового tarball нет — берите Docker-образ -cuda или Dockerfile.cuda; при отсутствии GPU в рантайме откатывается на CPU
Android / ARM64--features nnapiNNAPI (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/nnapi. На плохой комбинации срабатывает 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 --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-C 130) — пригодно для скриптов.
  • Полный INT8-набор модели = ноль сети. При наличии полного INT8-комплекта головы (INT8-энкодер + decoder/joiner/vocab для RNN-T или INT8-энкодер + vocab для Multilingual) запуск не выполняет ни одного сетевого запроса, даже без --offline. Только-FP32 деревья не usable. --offline / GIGASTT_OFFLINE=1 превращает любую недостающую опциональную модель (пунктуация, VAD, диаризация) в немедленную ошибку с именем файла.
  • Голова автоопределяется по файлам — скопированному каталогу не нужен флаг --model-variant на целевом хосте.
  • При копировании включайте *_int8.onnx (или CTC *.int8.onnx) энкодер, vocab и — для rnnt/e2e_rnnt — decoder и joiner. gigastt quantize — только упаковка (нужен локальный FP32-исходник); на runtime на него не рассчитывайте.

Для полностью упакованной офлайн-установки (tarball с бинарём + INT8-моделью + моделью пунктуации + systemd-юнит или вариант из двух deb-пакетов) берите релизный offline-бандл — рецепт и шаги проверки в Развёртывание и эксплуатация; состав — README-OFFLINE.md.

Проверка:

gigastt --offline transcribe sample.wav --model-dir /srv/gigastt-models
# транскрибирует без сети; недостающий файл — немедленная ошибка с именем файла

Память под пул сессий

INT8-энкодер memory-mapped и общий. Дополнительные слоты добавляют состояние decoder/joiner и арены ORT — не вторую копию энкодера. RAM процесса: ~46 / ~66 МБ resident при пуле 1 / 2 (~277 / ~510 МБ ps RSS); слот сверх первого ~20 МБ. --pool-size 1 — выбор ради RTF / edge, а не из-за нехватки RAM. При загрузке по-прежнему закладывается 2 × размер-файла-энкодера на слот. Цифры: docs/benchmarks.md.

Проверка:

# нет предупреждения "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> — fp32 только у 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 выглядит зависшим. Это одноразовая загрузка lean INT8 (~225 МБ), если каталог модели пуст; /health отвечает 200 с model:"loading", пока /ready остаётся 503 initializing. Предзагрузите gigastt download и стробируйте клиентов по /ready, никогда по /health.
  • OOM после переключения на ml_ctc_large. Resident после mmap не измерен (файл энкодера ~592 МБ; не выдумывайте 2 × размер файла). Пока нет замера — --pool-size 1. Ограничение при загрузке по-прежнему закладывает 2 × размер-файла-энкодера, поэтому пул может урезаться, даже если resident влез бы. Держите --pool-min-size 1, чтобы тесный хост загружался деградированно.
  • 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 вручную на место.

Ссылки