Вы можете скопировать один бинарный файл и один файл GGUF на машину без графического процессора и получить расшифровку NVIDIA NeMo Parakeet в среднем в 1,40 раза быстрее собственной скорости PyTorch CPU от NeMo, с тем же результатом расшифровки, который производит NeMo. Это parakeet.cpp, порт семейств распознавания речи Parakeet на C++17, созданный на базе ggml.
Мы проверили точность, прежде чем браться за скорость, потому что транскрибатор, который расходится с эталоном, не является его портом.
WER 0 по сравнению с NeMo
Каждая опубликованная контрольная точка проверяется на WER 0 по сравнению с NeMo. На тестовом наборе LibriSpeech test-clean средний показатель совпадения f32 по WER, то есть уровень словарных ошибок между нашей расшифровкой и расшифровкой NeMo на одном и том же аудио, составляет 0,0155%. На семи из десяти моделей он равен ровно 0,0000%, что означает побуквенно идентичную расшифровку.
Оба движка проделали одинаковую работу и выдали одинаковый результат, поэтому единственным оставшимся различием является затраченное время.
ЦП, в сравнении с собственной средой выполнения NeMo
Измерения проводились на 20-ядерном хосте x86 с 8 потоками для обоих движков, на наборе LibriSpeech test-clean, размер батча 1 с обеих сторон. Эталоном служит NeMo 2.7.3 на PyTorch CPU. RTFx — это секунды аудио, деленные на секунды обработки, поэтому чем выше значение, тем выше скорость.
Диапазон для всех десяти моделей составляет от 1,11x до 1,69x в формате f32, со средним значением 1,40x. Квантование до q8_0 уменьшает файл до 37% от размера f32 и увеличивает лучший результат до 1,86x, сохраняя при этом практически нулевые потери качества. f16 составляет 57% от исходного размера и достигает 1,70x. K-квантования ниже этого уровня продолжают уменьшать модель ценой небольшого монотонного снижения точности, что подробно описано в таблицах для каждой модели в репозитории.
Пиковое потребление резидентной памяти составляет примерно половину от показателей NeMo для каждой модели, а после квантования становится еще ниже. Модель на 110 млн параметров работает при 563 МБ против 1650 МБ у NeMo, что и определяет разницу между возможностью запуска на небольшом периферийном устройстве и невозможностью.
По сравнению с whisper.cpp turbo на том же фрагменте и с той же точностью (1.6% WER на этом аудио), parakeet.cpp работает примерно в 27 раз быстрее на ЦП и примерно в 12 раз быстрее на ГП. Этот разрыв в основном обусловлен архитектурой: Whisper — это энкодер-декодер, обрабатывающий фиксированные 30-секундные окна, в то время как FastConformer и трансдьюсер Parakeet декодируют только те кадры, которые у них есть.
Откуда взялась скорость на ЦП
Решающим фактором стал успех на стороне декодирования. Трансдьюсер декодирует авторегрессионно, и профилирование показало, что LSTM предсказательной сети забирает около 97% времени декодирования RNN-T, выдавая при этом один и тот же результат снова и снова: на некадровом (негенерирующем) кадре входные данные предсказательной сети не меняются, поэтому ее прямой проход избыточен. Кэширование этого прямого прохода для некадровых кадров устранило большую часть затрат на декодирование.
Сторона энкодера представляет собой совокупность небольших улучшений: постоянный бэкенд ggml с gallocr, веса с нулевым копированием прямо из маппинга GGUF, один слитный граф вместо построения графа для каждого слоя, а также tinyBLAS через GGML_LLAMAFILE.
На графическом процессоре (GPU)
На чипе NVIDIA GB10 (Grace-Blackwell) parakeet.cpp побеждает на всех десяти моделях со средним показателем 1,25x и доходит до 4,3x на больших моделях TDT и гибридных моделях. В качестве эталона здесь используется NeMo-GPU внутри контейнера nvcr.io/nvidia/nemo, поскольку NeMo не может запускаться напрямую на стеке torch и CUDA этого хоста.
Случаи с ускорением в 4,3x имеют конкретную причину. Жадное декодирование TDT в NeMo не ускоряется с помощью CUDA-графов и возвращается к циклу Python для каждого шага, в то время как наш вариант представляет собой компактный цикл на C++. Там, где декодирование NeMo ускорено CUDA-графами, как в случае с RNN-T, разрыв сокращается примерно до 1,16x для f32 и 1,30x для q8_0. На чисто энкодерных моделях CTC разрыв составляет около 1,2x, поскольку стандартные ядра свёртки и внимания CUDA от ggml по-прежнему уступают оптимизированным cuDNN от NVIDIA. Это основной резерв производительности для GPU, оставшийся в проекте, и в README приведены данные по каждой модели.
Батчинг нескольких фрагментов через декодер позволяет достичь примерно 10x–12x при размере батча 16 на GB10 и примерно 3x–5x на ЦП. Это применимо только к моделям-трансдьюсерам, так как CTC не имеет авторегрессионного декодирования для батчинга, а батчевый путь выдает тот же результат, что и последовательная обработка фрагментов по одному.
На чипах Apple M4 через Metal-бэкенд ggml более крупные модели работают примерно в 3–5 раз быстрее, чем те же модели на ЦП этой же машины.
Потоковая передача с учетом кэша и определение конца реплики
Автономная (offline) транскрипция принимает файл и ждет завершения. Голосовой помощник не может так работать, поэтому parakeet_realtime_eou_120m-v1 использует потоковый режим с учетом кэша: вы передаете ему 16 кГц моно PCM по мере поступления, а он возвращает вновь финализированный текст по мере его стабилизации.
Учет кэша означает, что затраты на обработку каждого чанка остаются неизменными. Прямой проход каждого чанка переносит кэши свёртки и внимания для каждого слоя, а также состояние декодера трансдьюсера вперед, поэтому ничто из того, что было до текущего чанка, не пересчитывается. Без этого каждый чанк заново запускал бы энкодер для всей сессии целиком, и затраты на чанк росли бы вместе с длиной разговора, пока цикл не начал бы отставать. Реализация охватывает нормирование слоев с причинной свёрткой, причинное прореживание и внимание с ограничением по чанкам, а ее результаты расшифровки точно совпадают с собственной потоковой передачей NeMo с учетом кэша.
Определение конца реплики меняет восприятие работы помощника. Модель выдает <EOU>, когда говорящий завершил реплику, и <EOB> для подтверждения приема (backchannel) в виде событий, сопровождающих текст. Голосовой цикл может начать генерацию ответа в момент поступления <EOU>, не дожидаясь истечения таймера фиксированной тишины, из-за которого и возникает большая часть воспринимаемой задержки в разговорном помощнике. Альтернативный вариант — детектор активности речи (VAD) с затуханием 700 мс — либо обрывает людей на полуслове, либо создает ощущение медлительности помощника, и он не может отличить «угу» от завершения мысли. Функция finalize очищает хвост в конце потока, не создавая искусственный тег <EOU>, который NeMo не стала бы генерировать.
Потоковый режим демонстрирует RTFx 3.80 на фрагменте длиной 7,43 секунды. Это значение намеренно ниже показателя автономного режима, поскольку потоковая передача выполняет множество небольших пакетных проходов вместо одного крупного, и тем не менее она в несколько раз быстрее реального времени на ЦП.
Существует также многоязычная потоковая модель nemotron-3.5-asr-streaming-0.6b, поддерживающая 40 и более локалей с подсказкой для каждого языка. На ЦП она работает со скоростью 2,40x от NeMo для f32 и 2,52x для q8_0, при этом показатель совпадения WER составляет 0,0000% в обоих случаях — как в автономном, так и в потоковом режимах.
Длинное аудио без обрыва памяти
Энкодер FastConformer использует глобальное относительное позиционное самовнимание (global relative-position self-attention), сложность которого по времени и памяти составляет O(T в квадрате). Файл длительностью 16,6 минут прореживается примерно до 12 000 кадров энкодера, и одни только тензоры оценок и масок достигают десятков гигабайт, чего достаточно, чтобы уронить узел.
В parakeet.cpp портирована функция rel_pos_local_attn из NeMo — это полосовое внимание, где каждый запрос соотносится только с ключами в пределах определенного окна, что делает внимание равным O(T умножить на W). Оно включается автоматически при превышении 8192 кадров энкодера, а параметр PARAKEET_ATT_CONTEXT позволяет принудительно задать конкретное окно.
При полном окне NeMo W=128 это примерно в 4 раза быстрее и требует примерно в 5,7 раза меньше пиковой памяти, чем глобальный путь. Полоса создается с помощью чанкового умножения матриц (chunk-matmul), где перекрывающиеся чанки ключей и значений поступают на один пакетный GEMM плюс диагональное смещенное представление, поэтому количество узлов графа не зависит от размера окна. Широкое окно стоит столько же, сколько и узкое. Короткие фрагменты остаются на глобальном пути и выдают тот же результат, что и раньше.
Использование
parakeet.cpp поставляет предварительно собранные пакеты parakeet-cli для Linux x64 (CPU, Vulkan, CUDA), Linux arm64, macOS arm64 с поддержкой Metal, macOS x64 и Windows x64. В LocalAI это бэкенд parakeet-cpp, который динамически загружает libparakeet.so через purego и напрямую вызывает C ABI, поэтому в пути обслуживания нет процесса Python, а транскрипция возвращается через стандартный конечный пункт, совместимый с OpenAI.
Полный набор тестов, методология и графики для каждой модели находятся в benchmarks/BENCHMARK.md, а матрица соответствия для каждой контрольной точки — в docs/parity.md.







