Вы можете просто скопировать один исполняемый файл и файл GGUF на компьютер без GPU и получить транскрипцию NVIDIA NeMo Parakeet со средней скоростью 1,40x от скорости PyTorch CPU самого NeMo, при этом транскрипт будет идентичен тому, что выдает NeMo. Это и есть parakeet.cpp — порт семейства моделей распознавания речи Parakeet на C++17, построенный на базе ggml.
Мы проверили точность до того, как занялись скоростью, потому что транскрибатор, который не совпадает с эталоном, нельзя считать его портом.
WER 0 по сравнению с NeMo
Каждая опубликованная контрольная точка проверена на WER 0 по сравнению с NeMo. По всему набору LibriSpeech test-clean средний показатель согласия WER в формате f32 (то есть частота ошибок в словах между нашим транскриптом и транскриптом NeMo на одном и том же аудио) составляет 0,0155%. На семи из десяти моделей он составляет ровно 0,0000%, что означает побайтово идентичный транскрипт.
Оба движка проделали одну и ту же работу и выдали одинаковый результат, поэтому единственное оставшееся различие — это время выполнения.
CPU, в сравнении с собственным рантаймом NeMo
Измерения проводились на 20-ядерном хосте x86 с 8 потоками для обоих движков, на наборе LibriSpeech test-clean, размер пакета (batch size) 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 для каждой модели, и становится еще ниже после квантования. Модель 110M потребляет 563 МБ против 1650 МБ у NeMo — это разница между возможностью запуска на небольшом периферийном устройстве и невозможностью.
По сравнению с whisper.cpp turbo на том же клипе и при той же точности (1,6% WER на этом аудио), parakeet.cpp примерно в 27 раз быстрее на CPU и примерно в 12 раз быстрее на GPU. Этот разрыв в основном обусловлен архитектурой: Whisper — это энкодер-декодер, который обрабатывает фиксированные 30-секундные окна, а FastConformer и трансдьюсер в Parakeet декодируют только те кадры, которые у них есть.
Откуда взялась скорость на CPU
Решающее преимущество было достигнуто на стороне декодирования. Трансдьюсер декодирует авторегрессионно, и профилирование показало, что 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 на CPU. Это применимо только к моделям-трансдьюсерам, так как у CTC нет авторегрессионного декодирования для пакетирования, а пакетный путь выдает тот же результат, что и при обработке клипов по одному.
На Apple M4 через бэкенд Metal в ggml более крупные модели работают примерно в 3–5 раз быстрее, чем те же модели на CPU этого компьютера.
Потоковая передача с учетом кэша и обнаружение конца фразы
Оффлайн-транскрипция получает файл и ждет. Голосовой помощник так работать не может, поэтому parakeet_realtime_eou_120m-v1 использует потоковый путь с учетом кэша: вы подаете ему 16 кГц моно PCM по мере поступления, и он возвращает окончательный текст, как только он становится стабильным.
Учет кэша означает, что стоимость обработки каждого фрагмента остается неизменной. Прямой проход каждого фрагмента переносит кэши свертки и внимания для каждого слоя, а также состояние декодера трансдьюсера, поэтому ничего из того, что было до текущего фрагмента, не пересчитывается. Без этого каждый фрагмент заставлял бы энкодер пересчитывать всю сессию целиком, и стоимость обработки фрагмента росла бы вместе с длиной разговора, пока цикл не начал бы отставать. Реализация включает нормализацию слоев с причинно-следственной сверткой, причинно-следственную субдискретизацию и внимание с ограничением по фрагментам, а транскрипт в точности совпадает с потоковой передачей NeMo с учетом кэша.
Обнаружение конца фразы (End-of-utterance) меняет восприятие помощника. Модель выдает <EOU>, когда говорящий закончил мысль, и <EOB> для обратной связи, как события параллельно с текстом. Голосовой цикл может начать генерировать ответ в момент появления <EOU>, а не ждать фиксированного таймера тишины, что является основной причиной воспринимаемой задержки в голосовых помощниках. Альтернатива — VAD с задержкой 700 мс — либо обрывает людей на полуслове, либо делает помощника «медленным», и он не может отличить «мм-хм» от завершения мысли. Функция finalize очищает хвост в конце потока, не создавая искусственный <EOU>, который NeMo не выдал бы.
Потоковый путь показывает RTFx 3,80 на 7,43-секундном клипе. Это значение намеренно ниже оффлайн-показателя, так как потоковая передача выполняет множество небольших фрагментированных проходов вместо одного большого, но она все равно в несколько раз быстрее реального времени на CPU.
Существует также многоязычная потоковая модель nemotron-3.5-asr-streaming-0.6b, охватывающая 40 и более локалей с промптом для каждого языка. На CPU она работает в 2,40 раза быстрее NeMo при f32 и в 2,52 раза быстрее при q8_0, с показателем согласия WER 0,0000% в обоих случаях, как в оффлайн, так и в потоковом режиме.
Длинное аудио без проблем с памятью
Энкодер FastConformer использует глобальное относительное позиционное self-attention, которое имеет сложность O(T в квадрате) по времени и памяти. 16,6-минутный файл при субдискретизации дает примерно 12 000 кадров энкодера, и только тензоры оценок и масок достигают десятков гигабайт, чего достаточно, чтобы вывести узел из строя.
parakeet.cpp портирует rel_pos_local_attn из NeMo — это ленточное внимание, где каждый запрос обращается только к ключам внутри окна, что делает внимание O(T умножить на W). Оно включается автоматически после 8192 кадров энкодера, а переменная PARAKEET_ATT_CONTEXT задает конкретное окно.
При полном окне W=128 в NeMo это примерно в 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.
