- Зачем: - решения по неопределённым словам и CPU-стоимости должны быть проверяемыми до публикации реализации. - Что: - задокументировано отклонение post-hoc сглаживания Speaker ? по соседним кластерам. - добавлено исследование стоимости каскада и облегчённых embedding-моделей. - Проверка: - `git diff --cached --check` — без ошибок.
17 KiB
Стоимость CPU-диаризации и облегчённые альтернативы
Дата: 2026-08-14
Статус: исследовательская записка. Production-конфигурация не менялась.
Краткий вывод
Измеренная стоимость ожидаема для выбранного каскада, но не является
неизбежным минимумом. WeSpeaker ResNet34 LM — не аномально большая модель:
ONNX-файл занимает 26,5 МБ, а сама архитектура находится в младшей части
семейства WeSpeaker ResNet. Основная цена возникает из способа применения
моделей: Pyannote проходит запись перекрывающимися 10-секундными окнами с шагом
1 секунда, после чего sherpa-onnx отдельно считает speaker embedding для каждого
локального говорящего каждого окна.
На минутном профиле текущей связки на этом ноутбуке эмбеддинги заняли 5,150 с из 6,395 с, сегментация — 1,243 с, кластеризация — меньше измеримой миллисекунды. То есть около 81% времени в этом прогоне пришлось на многократные вызовы WeSpeaker. Ускорять только кластеризацию или менять её порог бессмысленно.
Более лёгкие пути есть. Самый безопасный для исследования — официальная INT8
версия той же Pyannote-сегментации. Самый большой подтверждённый upstream
потенциал даёт nemo_en_titanet_small, но её качество и порог кластеризации на
русских созвонах проекта ещё не проверялись. Локально проверенная CAMPPlus zh/en
быстрее WeSpeaker примерно на 30%, однако не прошла требование автоматического
определения числа говорящих с единым порогом.
Откуда берётся стоимость
sherpa-onnx строит результат из трёх вычислительных стадий: speaker
segmentation, speaker embeddings и clustering. Это соответствует как
официальному API,
так и реализации конвейера
Pyannote.
1. Перекрывающаяся сегментация
Pyannote segmentation 3.0 принимает 10 секунд mono 16 кГц и различает до трёх локальных говорящих в окне, включая пары одновременно говорящих. Это явно зафиксировано в карточке исходной модели.
В официальном ONNX-файле window_size=160000, то есть те же 10 секунд при
16 кГц. В sherpa-onnx 1.13.5 window_shift_ratio по умолчанию равен 0,1, а
число окон вычисляется из размера окна и этого шага. Поэтому обычная длинная
запись проходит через segmentation-модель примерно десятикратно
перекрывающимися окнами: новое окно начинается каждую секунду. См.
конфигурацию шага
и цикл обработки
окон.
Это не признак «огромной» модели: FP32-файл сегментации весит лишь 5,7 МБ.
Стоимость создаёт прежде всего частота её запуска. Официальная поставка также
содержит model.int8.onnx размером 1,5 МБ
(документация sherpa-onnx).
2. Embedding для каждого локального говорящего каждого окна
После сегментации sherpa-onnx исключает перекрывающиеся кадры из материала для
эмбеддинга, собирает пары (окно, локальный говорящий) и для каждой пары создаёт
отдельный stream и запускает embedding extractor. Это видно непосредственно в
методах GetChunkSpeakerSampleIndexes и
ComputeEmbeddings.
При одном активном голосе это уже примерно один embedding на секунду записи;
если в окне модель видит несколько локальных голосов, вызовов становится больше.
Текущий extractor — английский VoxCeleb ResNet34_LM; WeSpeaker поясняет, что
суффикс LM означает дополнительную large-margin донастройку, полезную на
фрагментах длиннее трёх секунд
(официальный список моделей).
3. Кластеризация почти бесплатна
В первичном разведочном
замере смена порога не
изменила время: 153–157 с, а кластеризация занимала доли секунды. Минутный профиль
текущей production-конфигурации также показал 0.000 s для clustering. Явное
число участников полезно для качества и стабильности количества кластеров, но не
является существенной оптимизацией CPU.
Что показывают локальные измерения
| Среда и материал | Конфигурация | Результат |
|---|---|---|
| Ryzen 7 8845H, Data Test 26:00 | Pyannote FP32 + WeSpeaker, 8 потоков | 140,7 с; 11,1× realtime |
| Ryzen 7 8845H, три 5-минутных фрагмента | WeSpeaker | средний RTF 0,118 |
| Ryzen 7 8845H, те же фрагменты | CAMPPlus zh/en | средний RTF 0,083; 0,70× от WeSpeaker |
| Intel Core i7-6820HQ, 61:13 аудио | WeSpeaker, 8 потоков | 832,3 с; 4,4× realtime |
| Текущий ноутбук, 60 с T2 BDMA | WeSpeaker, 8 потоков, debug profile | segmentation 1,243 с; embeddings 5,150 с; clustering 0,000 с; всего 6,395 с |
Источники полных воспроизводимых замеров: разведка на Ryzen, калибровка эмбеддингов и Intel baseline. Минутный профиль — диагностический одиночный прогон той же production-конфигурации на первых 60 секундах T2 BDMA; его следует использовать для распределения стоимости по стадиям, а не как новый общий benchmark.
Увеличение num_threads не решает проблему. На Data Test переход с четырёх
потоков (154 с) на восемь (141 с) дал только 9%. Это согласуется с устройством
конвейера: он выполняет много последовательных ONNX-вызовов для отдельных окон
и локальных говорящих, поэтому добавление потоков внутри одного вызова быстро
перестаёт масштабироваться.
Официально поддерживаемые облегчённые варианты
INT8 Pyannote segmentation 3.0
Sherpa-onnx официально поставляет FP32 и INT8 варианты одной сегментации. На его контрольной записи замена только segmentation-модели при 3D-Speaker embedding снизила RTF с 0,297 до 0,241, то есть примерно на 19%. С TitaNet small разница меньше: 0,119 против 0,110. Все числа опубликованы на одной странице официальных примеров и замеров.
Trade-off: это минимальное архитектурное изменение, но INT8 меняет границы интервалов даже в официальном примере. Перед заменой нужно повторить на трёх контрольных записях число кластеров, purity, малые кластеры, неназначенные слова и полный runtime. На текущем минутном профиле segmentation занимает лишь около 19% времени, поэтому одной квантизацией нельзя ожидать кратного ускорения всей диаризации.
3D-Speaker CAMPPlus zh/en
Модель уже входит в официальный релиз speaker-recognition моделей sherpa-onnx, а 3D-Speaker публикует CAM++ как штатную архитектуру своего набора (репозиторий и таблица моделей). Её ONNX-файл занимает 28,3 МБ — немного больше текущих 26,5 МБ, поэтому размер файла здесь плохо предсказывает вычислительную стоимость.
Локально CAMPPlus дала RTF 0,083 против 0,118 у WeSpeaker, то есть была примерно на 30% быстрее. При известном числе участников она также улучшила proxy-purity на двух разговорах. Но в автоматическом режиме не нашлось единого порога: один порог оставлял лишние кластеры, следующий уже склеивал реальные голоса. Полные данные находятся в отчёте о калибровке.
Trade-off: хороший кандидат для режима с обязательным --speakers N, но не
готовая замена общего автоматического режима.
NeMo TitaNet small
Sherpa-onnx официально показывает nemo_en_titanet_small как совместимый
embedding extractor. На его контрольной записи Pyannote FP32 + TitaNet small
дала RTF 0,119 вместо 0,297 у Pyannote FP32 + 3D-Speaker ERes2Net; с INT8
сегментацией — 0,110 вместо 0,241. Это самый большой опубликованный upstream
выигрыш среди проверенных на одной странице комбинаций
(официальные замеры).
TitaNet использует 1D depth-wise separable convolutions и channel-attention
statistics pooling
(документация NVIDIA).
Trade-off: upstream-цифры сняты на другой embedding-модели сравнения, другом материале и оборудовании, поэтому коэффициент нельзя переносить на этот ноутбук. Кроме того, TitaNet small не проходила локальную калибровку на русской речи: неизвестны подходящий clustering threshold, стабильность числа голосов и качество коротких реплик. Сначала нужен тот же свип, который уже выполнен для WeSpeaker и CAMPPlus.
Увеличение шага окна
В C++-конфигурации sherpa-onnx 1.13.5 есть window_shift_ratio, поэтому на уровне
движка можно уменьшить число перекрывающихся окон ценой более грубой разметки.
Но Python binding этой
версии
экспортирует только путь model, а не window_shift_ratio. Для текущего Python
приложения это не штатная ручка без изменения upstream binding или собственного
нативного слоя. Даже после появления ручки потребуется отдельная калибровка:
более редкие окна могут ухудшить границы смены голоса и короткие ответы — как раз
самую чувствительную часть текущего результата.
Практические следующие шаги
- Не считать текущую стоимость дефектом реализации: выбранный результат соответствует устройству sherpa-onnx и остаётся быстрее realtime даже на старом Intel.
- Первым отдельным экспериментом прогнать INT8 Pyannote на тех же трёх файлах. Это наименьший по масштабу вариант, хотя ожидаемый выигрыш умеренный.
- Отдельно откалибровать TitaNet small. У неё лучший опубликованный потенциал скорости, но пока нет локальных данных о качестве.
- CAMPPlus предлагать только как кандидат для режима с известным числом участников, если 30% экономии оправдывает второй production-профиль.
- Параллельный ASR и diarization исследовать независимо от выбора модели. Он не уменьшает CPU-работу, но может сократить wall-clock latency. Обязательно измерить конкуренцию за ядра и общий peak RSS: на Intel последовательные пики составляли 900–1035 МБ для ASR и 366–469 МБ для диаризации, а совместный пик пока не измерен.
Вердикт
Текущая диаризация тяжёлая в основном из-за каскада и плотного перекрытия окон, а не потому, что случайно выбрана гигантская модель. Но выбранный WeSpeaker ResNet34 LM не самый быстрый extractor. Реалистичный резерв — умеренное ускорение через INT8 segmentation, около 30% по локальным данным через CAMPPlus при известном числе участников и потенциально более крупное ускорение через TitaNet small после обязательной русскоязычной калибровки. Простое добавление потоков или настройка clustering заметного выигрыша не даст.