- Зачем: - решения по неопределённым словам и CPU-стоимости должны быть проверяемыми до публикации реализации. - Что: - задокументировано отклонение post-hoc сглаживания Speaker ? по соседним кластерам. - добавлено исследование стоимости каскада и облегчённых embedding-моделей. - Проверка: - `git diff --cached --check` — без ошибок.
200 lines
17 KiB
Markdown
200 lines
17 KiB
Markdown
# Стоимость 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](https://k2-fsa.github.io/sherpa/onnx/c-api/html/speaker_diarization.html),
|
||
так и [реализации конвейера
|
||
Pyannote](https://github.com/k2-fsa/sherpa-onnx/blob/v1.13.5/sherpa-onnx/csrc/offline-speaker-diarization-pyannote-impl.h).
|
||
|
||
### 1. Перекрывающаяся сегментация
|
||
|
||
Pyannote segmentation 3.0 принимает 10 секунд mono 16 кГц и различает до трёх
|
||
локальных говорящих в окне, включая пары одновременно говорящих. Это явно
|
||
зафиксировано в [карточке исходной
|
||
модели](https://huggingface.co/pyannote/segmentation-3.0).
|
||
|
||
В официальном ONNX-файле `window_size=160000`, то есть те же 10 секунд при
|
||
16 кГц. В sherpa-onnx 1.13.5 `window_shift_ratio` по умолчанию равен 0,1, а
|
||
число окон вычисляется из размера окна и этого шага. Поэтому обычная длинная
|
||
запись проходит через segmentation-модель примерно десятикратно
|
||
перекрывающимися окнами: новое окно начинается каждую секунду. См.
|
||
[конфигурацию шага](https://github.com/k2-fsa/sherpa-onnx/blob/v1.13.5/sherpa-onnx/csrc/offline-speaker-segmentation-pyannote-model-config.h)
|
||
и [цикл обработки
|
||
окон](https://github.com/k2-fsa/sherpa-onnx/blob/v1.13.5/sherpa-onnx/csrc/offline-speaker-diarization-pyannote-impl.h).
|
||
|
||
Это не признак «огромной» модели: FP32-файл сегментации весит лишь 5,7 МБ.
|
||
Стоимость создаёт прежде всего частота её запуска. Официальная поставка также
|
||
содержит `model.int8.onnx` размером 1,5 МБ
|
||
([документация sherpa-onnx](https://k2-fsa.github.io/sherpa/onnx/speaker-diarization/models.html)).
|
||
|
||
### 2. Embedding для каждого локального говорящего каждого окна
|
||
|
||
После сегментации sherpa-onnx исключает перекрывающиеся кадры из материала для
|
||
эмбеддинга, собирает пары `(окно, локальный говорящий)` и для каждой пары создаёт
|
||
отдельный stream и запускает embedding extractor. Это видно непосредственно в
|
||
[методах `GetChunkSpeakerSampleIndexes` и
|
||
`ComputeEmbeddings`](https://github.com/k2-fsa/sherpa-onnx/blob/v1.13.5/sherpa-onnx/csrc/offline-speaker-diarization-pyannote-impl.h).
|
||
|
||
При одном активном голосе это уже примерно один embedding на секунду записи;
|
||
если в окне модель видит несколько локальных голосов, вызовов становится больше.
|
||
Текущий extractor — английский VoxCeleb `ResNet34_LM`; WeSpeaker поясняет, что
|
||
суффикс LM означает дополнительную large-margin донастройку, полезную на
|
||
фрагментах длиннее трёх секунд
|
||
([официальный список моделей](https://github.com/wenet-e2e/wespeaker/blob/master/docs/pretrained.md)).
|
||
|
||
### 3. Кластеризация почти бесплатна
|
||
|
||
В [первичном разведочном
|
||
замере](../benchmarks/2026-08-12-diarization-feasibility.md) смена порога не
|
||
изменила время: 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](../benchmarks/2026-08-12-diarization-feasibility.md), [калибровка
|
||
эмбеддингов](../benchmarks/2026-08-14-diarization-calibration.md) и [Intel
|
||
baseline](../benchmarks/2026-08-14-diarization-intel-i7.md). Минутный профиль —
|
||
диагностический одиночный прогон той же 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. Все числа опубликованы на одной странице
|
||
[официальных примеров и
|
||
замеров](https://k2-fsa.github.io/sherpa/onnx/speaker-diarization/models.html).
|
||
|
||
Trade-off: это минимальное архитектурное изменение, но INT8 меняет границы
|
||
интервалов даже в официальном примере. Перед заменой нужно повторить на трёх
|
||
контрольных записях число кластеров, purity, малые кластеры, неназначенные слова
|
||
и полный runtime. На текущем минутном профиле segmentation занимает лишь около
|
||
19% времени, поэтому одной квантизацией нельзя ожидать кратного ускорения всей
|
||
диаризации.
|
||
|
||
### 3D-Speaker CAMPPlus zh/en
|
||
|
||
Модель уже входит в официальный релиз speaker-recognition моделей sherpa-onnx,
|
||
а 3D-Speaker публикует CAM++ как штатную архитектуру своего набора
|
||
([репозиторий и таблица
|
||
моделей](https://github.com/modelscope/3D-Speaker)). Её ONNX-файл занимает
|
||
28,3 МБ — немного больше текущих 26,5 МБ, поэтому размер файла здесь плохо
|
||
предсказывает вычислительную стоимость.
|
||
|
||
Локально CAMPPlus дала RTF 0,083 против 0,118 у WeSpeaker, то есть была примерно
|
||
на 30% быстрее. При известном числе участников она также улучшила proxy-purity
|
||
на двух разговорах. Но в автоматическом режиме не нашлось единого порога: один
|
||
порог оставлял лишние кластеры, следующий уже склеивал реальные голоса. Полные
|
||
данные находятся в [отчёте о
|
||
калибровке](../benchmarks/2026-08-14-diarization-calibration.md).
|
||
|
||
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
|
||
выигрыш среди проверенных на одной странице комбинаций
|
||
([официальные замеры](https://k2-fsa.github.io/sherpa/onnx/speaker-diarization/models.html)).
|
||
TitaNet использует 1D depth-wise separable convolutions и channel-attention
|
||
statistics pooling
|
||
([документация NVIDIA](https://docs.nvidia.com/nemo-framework/user-guide/25.02/nemotoolkit/asr/speaker_recognition/models.html)).
|
||
|
||
Trade-off: upstream-цифры сняты на другой embedding-модели сравнения, другом
|
||
материале и оборудовании, поэтому коэффициент нельзя переносить на этот ноутбук.
|
||
Кроме того, TitaNet small не проходила локальную калибровку на русской речи:
|
||
неизвестны подходящий clustering threshold, стабильность числа голосов и
|
||
качество коротких реплик. Сначала нужен тот же свип, который уже выполнен для
|
||
WeSpeaker и CAMPPlus.
|
||
|
||
### Увеличение шага окна
|
||
|
||
В C++-конфигурации sherpa-onnx 1.13.5 есть `window_shift_ratio`, поэтому на уровне
|
||
движка можно уменьшить число перекрывающихся окон ценой более грубой разметки.
|
||
Но [Python binding этой
|
||
версии](https://github.com/k2-fsa/sherpa-onnx/blob/v1.13.5/sherpa-onnx/python/csrc/offline-speaker-diarization.cc)
|
||
экспортирует только путь `model`, а не `window_shift_ratio`. Для текущего Python
|
||
приложения это не штатная ручка без изменения upstream binding или собственного
|
||
нативного слоя. Даже после появления ручки потребуется отдельная калибровка:
|
||
более редкие окна могут ухудшить границы смены голоса и короткие ответы — как раз
|
||
самую чувствительную часть текущего результата.
|
||
|
||
## Практические следующие шаги
|
||
|
||
1. Не считать текущую стоимость дефектом реализации: выбранный результат
|
||
соответствует устройству sherpa-onnx и остаётся быстрее realtime даже на
|
||
старом Intel.
|
||
2. Первым отдельным экспериментом прогнать INT8 Pyannote на тех же трёх файлах.
|
||
Это наименьший по масштабу вариант, хотя ожидаемый выигрыш умеренный.
|
||
3. Отдельно откалибровать TitaNet small. У неё лучший опубликованный потенциал
|
||
скорости, но пока нет локальных данных о качестве.
|
||
4. CAMPPlus предлагать только как кандидат для режима с известным числом
|
||
участников, если 30% экономии оправдывает второй production-профиль.
|
||
5. Параллельный 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 заметного выигрыша не даст.
|