feat(diarization): добавлено разделение транскрипта по говорящим #26
@@ -45,6 +45,39 @@ WeSpeaker ResNet34 LM проверена на трёх записях с изв
|
|||||||
[смешении говорящих](../benchmarks/2026-08-14-asr-segment-speaker-mixing.md) и
|
[смешении говорящих](../benchmarks/2026-08-14-asr-segment-speaker-mixing.md) и
|
||||||
[производительности на Intel](../benchmarks/2026-08-14-diarization-intel-i7.md).
|
[производительности на Intel](../benchmarks/2026-08-14-diarization-intel-i7.md).
|
||||||
|
|
||||||
|
### Сглаживание неизвестного говорящего
|
||||||
|
|
||||||
|
После приёмки отдельно проверена идея автоматически назначать `Speaker ?`
|
||||||
|
известному говорящему, если короткий неизвестный фрагмент находится между двумя
|
||||||
|
репликами одного и того же `Speaker N`. На трёх контрольных транскриптах найдено
|
||||||
|
143 таких неизвестных фрагмента, содержащих 309 слов. У 79 фрагментов соседи
|
||||||
|
имели одинаковую метку, у 64 — разные.
|
||||||
|
|
||||||
|
Для T2 BDMA и Yantar результат грубо сопоставлен с независимыми Hypescribe-
|
||||||
|
транскриптами. Это не gold-разметка: их границы округлены до секунды, а готовый
|
||||||
|
speaker Markdown хранит только начало реплики. Тем не менее эвристика не показала
|
||||||
|
достаточной селективности:
|
||||||
|
|
||||||
|
| Эвристика | Изменённых фрагментов на трёх записях | Совпадение на двух записях с опорой |
|
||||||
|
|---|---:|---:|
|
||||||
|
| Все неизвестные между одинаковыми соседями | 79 | 22 / 42 (52%) |
|
||||||
|
| Только один неизвестный word | 35 | 11 / 20 (55%) |
|
||||||
|
| Не больше двух words | 50 | 15 / 30 (50%) |
|
||||||
|
| Одинаковый отображённый timestamp | 42 | 14 / 22 (64%) |
|
||||||
|
|
||||||
|
Короткие `Да`, `Нет` и `Угу` часто являются самостоятельной репликой другого
|
||||||
|
участника. В одном из контрольных случаев неизвестный фрагмент `— Угу. — А`
|
||||||
|
даже содержал границу двух голосов, хотя с обеих сторон находился один и тот же
|
||||||
|
кластер. Поэтому post-hoc сглаживание по соседям скрывает полезную
|
||||||
|
неопределённость и не применяется. `Speaker ?` остаётся явным результатом ничьей
|
||||||
|
или отсутствия временного перекрытия.
|
||||||
|
|
||||||
|
Если к этой задаче возвращаться, проверять нужно исходные границы `Word` и
|
||||||
|
`SpeakerInterval` до группировки: отдельно различать нулевой timestamp, реальный
|
||||||
|
зазор между интервалами и ничью перекрытий. По готовому Markdown такая проверка
|
||||||
|
невозможна, потому что в нём уже потеряны доли секунды, конец слова и причина
|
||||||
|
неопределённости.
|
||||||
|
|
||||||
## Решение
|
## Решение
|
||||||
|
|
||||||
Диаризацию реализуем как явно включаемый пост-процессинг через `sherpa-onnx`.
|
Диаризацию реализуем как явно включаемый пост-процессинг через `sherpa-onnx`.
|
||||||
@@ -97,3 +130,4 @@ ASR-бэкенд приводит свой результат к общему н
|
|||||||
| `pyannote.audio` | Тянет PyTorch и требует Hugging Face token с принятием лицензии |
|
| `pyannote.audio` | Тянет PyTorch и требует Hugging Face token с принятием лицензии |
|
||||||
| Сборка поверх приватных деталей `onnx-asr` | Экономит небольшую отдельную зависимость ценой нестабильного внутреннего API и собственной кластеризации |
|
| Сборка поверх приватных деталей `onnx-asr` | Экономит небольшую отдельную зависимость ценой нестабильного внутреннего API и собственной кластеризации |
|
||||||
| Параллельные проходы в первой версии | Нет прямого benchmark и измеренного общего пика памяти; сначала нужен корректный последовательный baseline |
|
| Параллельные проходы в первой версии | Нет прямого benchmark и измеренного общего пика памяти; сначала нужен корректный последовательный baseline |
|
||||||
|
| Автоматически назначать `Speaker ?` одинаковому соседнему кластеру | На двух записях с независимой опорой совпало только 52% назначений; лимиты по словам и округлённому времени не отделили короткие ответы другого участника |
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
# Стоимость 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 заметного выигрыша не даст.
|
||||||
Reference in New Issue
Block a user