Текущий профиль Pyannote segmentation 3.0 FP32 + WeSpeaker ResNet34 LM качественно прошёл калибровку, но диаризация занимает примерно 60% полного времени обработки и увеличивает wall-clock транскрипции в 2,2–2,6 раза.
Минутный debug-профиль показал:
segmentation: 1,243 с (~19%);
speaker embeddings: 5,150 с (~81%);
clustering: около 0 с.
Высокая стоимость ожидаема из-за 10-секундных окон Pyannote с шагом 1 с и отдельного embedding для каждого локального говорящего каждого окна. При этом sherpa-onnx официально поддерживает INT8-сегментацию и nemo_en_titanet_small, которые потенциально быстрее.
Найти более лёгкий CPU-профиль диаризации без недокументированной потери качества либо зафиксировать, что текущий WeSpeaker остаётся оправданным default.
Что проверить
На одних и тех же трёх контрольных записях сравнить:
Pyannote FP32 + WeSpeaker ResNet34 LM — baseline.
Pyannote INT8 + WeSpeaker ResNet34 LM.
Pyannote FP32 + NeMo TitaNet small.
Pyannote INT8 + NeMo TitaNet small.
CAMPPlus zh/en не пересвипывать с нуля, а использовать существующие результаты как дополнительный кандидат только для режима с известным --speakers N.
Для каждой новой комбинации:
подобрать единый clustering threshold для автоматического режима;
зафиксировать число всех и содержательных кластеров, малые кластеры и неназначенные слова;
посчитать mapped speaker purity для T2 BDMA и Yantar по существующим references;
выборочно прослушать границы коротких реплик и смен говорящего;
убедиться, что ASR-текст не меняется и не теряется.
Критерии приёмки
Все новые модели и их SHA-256, лицензии и источники зафиксированы.
Есть воспроизводимый benchmark на Data Test, T2 BDMA и Yantar с одинаковыми условиями.
Автоматический кандидат оценивается одним общим threshold, а не отдельным значением на каждый файл.
Отчёт явно сравнивает скорость, purity, 3/2/2 ожидаемых содержательных голоса, малые кластеры и unknown words с baseline.
Если облегчённый профиль проигрывает по качеству, результат всё равно считается завершённым: кандидат отклоняется с измеренным обоснованием.
Если кандидат проходит, отдельно описаны минимальные изменения кеша, manifest, CLI/конфига и миграции текущего default; production-код в рамках benchmark-этапа не переключается.
Итог сохранён отдельным Markdown-отчётом в docs/benchmarks/ и отражён в ADR-007.
Вне scope
Параллельный запуск ASR и диаризации. Он уменьшает wall-clock, но не CPU-работу и требует отдельного benchmark конкуренции за ядра и общего peak RSS.
GPU/NPU-провайдеры.
Изменение шага окна через нестандартный Python binding.
Связано с реализацией диаризации: #24.
## Контекст
Текущий профиль `Pyannote segmentation 3.0 FP32 + WeSpeaker ResNet34 LM` качественно прошёл калибровку, но диаризация занимает примерно 60% полного времени обработки и увеличивает wall-clock транскрипции в 2,2–2,6 раза.
Минутный debug-профиль показал:
- segmentation: 1,243 с (~19%);
- speaker embeddings: 5,150 с (~81%);
- clustering: около 0 с.
Высокая стоимость ожидаема из-за 10-секундных окон Pyannote с шагом 1 с и отдельного embedding для каждого локального говорящего каждого окна. При этом sherpa-onnx официально поддерживает INT8-сегментацию и `nemo_en_titanet_small`, которые потенциально быстрее.
Исходные данные:
- `docs/research/2026-08-14-diarization-cpu-cost.md`;
- `docs/benchmarks/2026-08-14-diarization-calibration.md`;
- `docs/benchmarks/2026-08-14-diarization-intel-i7.md`;
- официальный benchmark sherpa-onnx: https://k2-fsa.github.io/sherpa/onnx/speaker-diarization/models.html.
## Цель
Найти более лёгкий CPU-профиль диаризации без недокументированной потери качества либо зафиксировать, что текущий WeSpeaker остаётся оправданным default.
## Что проверить
На одних и тех же трёх контрольных записях сравнить:
1. Pyannote FP32 + WeSpeaker ResNet34 LM — baseline.
2. Pyannote INT8 + WeSpeaker ResNet34 LM.
3. Pyannote FP32 + NeMo TitaNet small.
4. Pyannote INT8 + NeMo TitaNet small.
5. CAMPPlus zh/en не пересвипывать с нуля, а использовать существующие результаты как дополнительный кандидат только для режима с известным `--speakers N`.
Для каждой новой комбинации:
- подобрать единый clustering threshold для автоматического режима;
- отдельно проверить известное число участников;
- измерить segmentation / embeddings / clustering / total, RTF, peak RSS и загрузку CPU;
- зафиксировать число всех и содержательных кластеров, малые кластеры и неназначенные слова;
- посчитать mapped speaker purity для T2 BDMA и Yantar по существующим references;
- выборочно прослушать границы коротких реплик и смен говорящего;
- убедиться, что ASR-текст не меняется и не теряется.
## Критерии приёмки
- Все новые модели и их SHA-256, лицензии и источники зафиксированы.
- Есть воспроизводимый benchmark на Data Test, T2 BDMA и Yantar с одинаковыми условиями.
- Автоматический кандидат оценивается одним общим threshold, а не отдельным значением на каждый файл.
- Отчёт явно сравнивает скорость, purity, 3/2/2 ожидаемых содержательных голоса, малые кластеры и unknown words с baseline.
- Если облегчённый профиль проигрывает по качеству, результат всё равно считается завершённым: кандидат отклоняется с измеренным обоснованием.
- Если кандидат проходит, отдельно описаны минимальные изменения кеша, manifest, CLI/конфига и миграции текущего default; production-код в рамках benchmark-этапа не переключается.
- Итог сохранён отдельным Markdown-отчётом в `docs/benchmarks/` и отражён в ADR-007.
## Вне scope
- Параллельный запуск ASR и диаризации. Он уменьшает wall-clock, но не CPU-работу и требует отдельного benchmark конкуренции за ядра и общего peak RSS.
- GPU/NPU-провайдеры.
- Изменение шага окна через нестандартный Python binding.
Вердикт: задача готова к работе; метка ready-for-agent обоснована. Связанная #24 закрыта, нативных блокирующих зависимостей у #25 нет. Тело задачи сейчас уточнять не требуется.
Понимаю работу так:
это исследовательский CPU-only benchmark через sherpa-onnx / ORT CPU; CUDA, GPU и NPU вне scope. Неисправная CUDA на ноутбуке не является блокером и в эксперименте не участвует;
на трёх тех же контрольных записях сравниваются baseline и три новые комбинации Pyannote FP32/INT8 × WeSpeaker/TitaNet;
для каждой новой комбинации в автоматическом режиме выбирается один общий threshold на все три записи, затем отдельно выполняется режим с известным числом участников;
baseline следует перепрогнать той же обвязкой и в тех же условиях, а не только перенести старые цифры: прежние результаты не содержат всего нового набора метрик (стадии, CPU, peak RSS, unknown words) и не обеспечат строго одинаковые условия;
CAMPPlus заново не свипается: существующие результаты используются как дополнительное сравнение только для --speakers N;
benchmark не переключает production-default. Если кандидат проходит, отчёт лишь описывает будущие изменения кеша, manifest, CLI/конфига и миграции;
результатом являются отдельный отчёт в docs/benchmarks/ и обновление ADR-007.
Текущей обвязки scripts/benchmarks/diarization_calibration.py достаточно как основы, но её нужно расширить: сейчас она не варьирует segmentation-модель и не снимает per-stage timing, CPU, peak RSS, unknown words и проверку неизменности ASR-текста. FP32/INT8 Pyannote и baseline WeSpeaker уже лежат локально; TitaNet нужно скачать и зафиксировать с SHA-256, источником и лицензией.
Единственная практическая предпосылка перед фактическим прогоном: в текущем Linux-окружении нет трёх конфиденциальных исходных записей и двух Hypescribe references, а /mnt пуст. Они намеренно не хранятся в репозитории. Для benchmark-фазы понадобится доступный путь/монтирование этих пяти файлов. Это блокирует запуск замеров в данном окружении, но не означает, что постановка задачи неполна.
Дополнительных продуктовых или архитектурных вопросов у меня нет.
## Проверка готовности
**Вердикт:** задача готова к работе; метка `ready-for-agent` обоснована. Связанная #24 закрыта, нативных блокирующих зависимостей у #25 нет. Тело задачи сейчас уточнять не требуется.
Понимаю работу так:
- это исследовательский CPU-only benchmark через `sherpa-onnx` / ORT CPU; CUDA, GPU и NPU вне scope. Неисправная CUDA на ноутбуке не является блокером и в эксперименте не участвует;
- на трёх тех же контрольных записях сравниваются baseline и три новые комбинации Pyannote FP32/INT8 × WeSpeaker/TitaNet;
- для каждой новой комбинации в автоматическом режиме выбирается один общий threshold на все три записи, затем отдельно выполняется режим с известным числом участников;
- baseline следует перепрогнать той же обвязкой и в тех же условиях, а не только перенести старые цифры: прежние результаты не содержат всего нового набора метрик (стадии, CPU, peak RSS, unknown words) и не обеспечат строго одинаковые условия;
- CAMPPlus заново не свипается: существующие результаты используются как дополнительное сравнение только для `--speakers N`;
- benchmark не переключает production-default. Если кандидат проходит, отчёт лишь описывает будущие изменения кеша, manifest, CLI/конфига и миграции;
- результатом являются отдельный отчёт в `docs/benchmarks/` и обновление ADR-007.
Текущей обвязки `scripts/benchmarks/diarization_calibration.py` достаточно как основы, но её нужно расширить: сейчас она не варьирует segmentation-модель и не снимает per-stage timing, CPU, peak RSS, unknown words и проверку неизменности ASR-текста. FP32/INT8 Pyannote и baseline WeSpeaker уже лежат локально; TitaNet нужно скачать и зафиксировать с SHA-256, источником и лицензией.
**Единственная практическая предпосылка перед фактическим прогоном:** в текущем Linux-окружении нет трёх конфиденциальных исходных записей и двух Hypescribe references, а `/mnt` пуст. Они намеренно не хранятся в репозитории. Для benchmark-фазы понадобится доступный путь/монтирование этих пяти файлов. Это блокирует запуск замеров в данном окружении, но не означает, что постановка задачи неполна.
Дополнительных продуктовых или архитектурных вопросов у меня нет.
После этих результатов я бы сменил главный вопрос. Замена одной модели эмбеддингов на другую уже дала нам 24-30% на этой стадии, но сама по себе задачу не закрыла. В профиле FP32 WeSpeaker из 72.5 с на диаризацию 56.8 с уходят в эмбеддинги, 15.3 с в сегментацию и меньше 0.4 с во все остальное. Причина видна в устройстве конвейера: Pyannote берет окно 10 с с шагом 1 с, а затем для каждого локального говорящего в каждом окне отдельно считается эмбеддинг. Большая часть CPU-времени уходит на повторную обработку сильно перекрывающегося звука.
Отсюда мой порядок исследований.
1. Сначала сократить число окон
Это сейчас самый сильный рычаг. В свежем sherpa-onnx параметр window_shift_ratio уже есть в Python binding и C API, хотя в используемой у нас версии 1.13.5 Python его не показывал. Поэтому сначала стоит найти первый релиз с этим параметром и оценить обычное обновление зависимости. Если подходящего wheel еще нет, нужен маленький backport или изменение upstream. Постоянный частный binding в приложении здесь лишний.
Я бы проверил 0.1 (нынешние 1 с), 0.15, 0.2, 0.25; 0.5 оставить как заведомо агрессивную границу. При 0.2 число окон должно примерно уполовиниться. Если сегментация и эмбеддинги масштабируются вслед за ним, грубая оптимистичная оценка для нынешних 72.5 с будет около 36-40 с. Это гипотеза: редкие короткие реплики, перебивки и склейка говорящих могут испортиться раньше.
До опыта надо добавить в замер число окон, число вызовов extractor, сумму и квантили длительности поданных фрагментов. Одних трех времен стадии мало: без счетчиков мы опять не отличим экономию работы от более быстрого исполнения той же работы.
2. Отвязать быструю модель эмбеддингов от подсчета числа говорящих
TitaNet и CAMPPlus хорошо выглядят при известном числе говорящих. Значит, их провал в автоматическом режиме еще не доказывает плохое пространство эмбеддингов. Вероятнее, фиксированный порог FastClustering, подобранный для WeSpeaker, плохо переносится на другое распределение расстояний.
Следующий стенд должен один раз сохранить эмбеддинги и затем гонять кластеризацию без повторного запуска моделей. На нем я бы сравнил:
отдельный порог для каждой embedding-модели как дешевую контрольную точку;
спектральную оценку числа говорящих по eigengap / NME;
двухшаговый вариант: оценить N, затем отдать его нынешнему FastClustering как известное число кластеров.
У NeMo каскадная диаризация уже использует spectral clustering и отдельные параметры оценки числа говорящих. Тащить NeMo в продукт ради этого не нужно: сперва достаточно воспроизвести алгоритм в исследовательском стенде и понять, спасает ли он наши записи. Кластеризация сейчас занимает доли секунды, поэтому здесь можно позволить себе более умный алгоритм без заметного ущерба времени.
Если этот опыт пройдет, первым кандидатом на комбинацию со свободным шагом окна я бы взял CAMPPlus: на Ryzen он был примерно на 30% быстрее WeSpeaker и не показал заметного ухудшения качества при известном числе говорящих. TitaNet на текущем Intel уже дал 24% ускорения, но прибавил около 150 MiB RSS, так что для легкого профиля он хуже по памяти.
3. Проверить пакетное вычисление эмбеддингов в sherpa-onnx
Текущая реализация sherpa-onnx проходит пары (окно, локальный говорящий) последовательно и вызывает Compute() для одного stream за раз; публичный SpeakerEmbeddingExtractor тоже имеет только одиночный Compute. Это хороший кандидат на оптимизацию без изменения математики результата.
Сначала надо проверить, допускают ли наши ONNX-графы пакет переменного размера. Если да, сделать прототип с batch 4/8/16 и, при выигрыше, нести изменение upstream. Если batch зафиксирован в 1, запасной опыт - несколько независимых stream/session с одним intra-op thread на задачу. В обоих случаях надо следить за oversubscription и RSS: просто умножить нынешние 4 потока на несколько параллельных сессий почти наверняка будет плохой конфигурацией для старого ноутбука.
4. Квантовать именно embedding-модель
В нынешней матрице INT8 относится к Pyannote segmentation. Она занимает около пятой части времени, поэтому большой выигрыш от нее и не ожидался. Следующий INT8-опыт должен затронуть WeSpeaker/CAMPPlus/TitaNet.
Для CNN ONNX Runtime советует статическую квантизацию с калибровочными данными. Там же есть важная оговорка: на старых CPU без VNNI квантование нередко не ускоряет модель из-за стоимости Q/DQ и слабых INT8-инструкций. Поэтому этот опыт надо повторить на двух классах CPU: нынешнем AVX2-ноутбуке и более новом x86 с VNNI. Помимо времени и RSS, сравнивать косинусный дрейф эмбеддингов, итоговые кластеры и качество на слух.
Что пока отложить
sherpa-onnx-reverb-diarization-v1 выглядит слабым кандидатом: в официальном примере sherpa-onnx он медленнее Pyannote с тем же TitaNet и имеет non-commercial лицензию.
End-to-end Sortformer полезен как контроль другой архитектуры, но это отдельный spike: тяжелее runtime, другой профиль качества и ограничение готовых моделей по числу говорящих. До него есть три более дешевых опыта внутри нынешнего ONNX-конвейера.
Параллельные ASR и диаризация остаются хорошей задачей #22 для сочетания GPU ASR + CPU-диаризации. Такой режим уменьшает wall time, но CPU-работу диаризации не лечит и на старом четырехъядерном CPU может только добавить конкуренцию.
Прогон на современном процессоре нужен как проверка переносимости вывода, особенно для INT8. Сам по себе такой прогон не заменит поиск причины: даже быстрый новый CPU продолжит считать почти одинаковые окна.
Как я бы поставил серию опытов
Инструментировать число окон и эмбеддингов, затем прогнать только FP32 WeSpeaker с разными window_shift_ratio.
Сохранить один набор эмбеддингов и отдельно решить автоматический подсчет говорящих для WeSpeaker, CAMPPlus и TitaNet.
Скрестить лучший безопасный шаг окна с лучшей парой модели эмбеддингов и кластеризации.
После этого отдельно проверить batch и INT8 embedding на старом и новом CPU.
К Sortformer и другим end-to-end моделям идти, только если каскад упрется в качество раньше нужной скорости.
Порог go для направления я бы поставил выше нынешних 9-10%: хотя бы 30% сокращения времени самой диаризации при прохождении нынешней проверки качества; цель всей комбинации - примерно двукратное ускорение диаризации. Проверять надо те же 3/2/2 говорящих в автоматическом режиме, mapped word purity, короткие реплики и перебивки на слух, неизменность текста ASR, медиану минимум пяти прогонов и пиковый RSS. Режим питания, частоты/температуру CPU и набор ISA стоит писать в результат каждого запуска.
Если этот план сработает, наиболее правдоподобный новый профиль будет состоять из трех вещей: более редкие окна, CAMPPlus или TitaNet и отдельная надежная оценка числа говорящих. По отдельности каждая дает ограниченный выигрыш; вместе они сокращают число вычислений и цену одного эмбеддинга, заодно снимая нынешний блокер качества.
### Куда копать после эксперимента #25
После этих результатов я бы сменил главный вопрос. Замена одной модели эмбеддингов на другую уже дала нам 24-30% на этой стадии, но сама по себе задачу не закрыла. В профиле FP32 WeSpeaker из 72.5 с на диаризацию 56.8 с уходят в эмбеддинги, 15.3 с в сегментацию и меньше 0.4 с во все остальное. Причина видна в устройстве конвейера: Pyannote берет окно 10 с с шагом 1 с, а затем для каждого локального говорящего в каждом окне отдельно считается эмбеддинг. Большая часть CPU-времени уходит на повторную обработку сильно перекрывающегося звука.
Отсюда мой порядок исследований.
#### 1. Сначала сократить число окон
Это сейчас самый сильный рычаг. В свежем sherpa-onnx параметр `window_shift_ratio` уже есть в [Python binding](https://github.com/k2-fsa/sherpa-onnx/blob/master/sherpa-onnx/python/csrc/offline-speaker-diarization.cc) и [C API](https://k2-fsa.github.io/sherpa/onnx/c-api/html/speaker_diarization.html), хотя в используемой у нас версии 1.13.5 Python его не показывал. Поэтому сначала стоит найти первый релиз с этим параметром и оценить обычное обновление зависимости. Если подходящего wheel еще нет, нужен маленький backport или изменение upstream. Постоянный частный binding в приложении здесь лишний.
Я бы проверил `0.1` (нынешние 1 с), `0.15`, `0.2`, `0.25`; `0.5` оставить как заведомо агрессивную границу. При `0.2` число окон должно примерно уполовиниться. Если сегментация и эмбеддинги масштабируются вслед за ним, грубая оптимистичная оценка для нынешних 72.5 с будет около 36-40 с. Это гипотеза: редкие короткие реплики, перебивки и склейка говорящих могут испортиться раньше.
До опыта надо добавить в замер число окон, число вызовов extractor, сумму и квантили длительности поданных фрагментов. Одних трех времен стадии мало: без счетчиков мы опять не отличим экономию работы от более быстрого исполнения той же работы.
#### 2. Отвязать быструю модель эмбеддингов от подсчета числа говорящих
TitaNet и CAMPPlus хорошо выглядят при известном числе говорящих. Значит, их провал в автоматическом режиме еще не доказывает плохое пространство эмбеддингов. Вероятнее, фиксированный порог FastClustering, подобранный для WeSpeaker, плохо переносится на другое распределение расстояний.
Следующий стенд должен один раз сохранить эмбеддинги и затем гонять кластеризацию без повторного запуска моделей. На нем я бы сравнил:
1. отдельный порог для каждой embedding-модели как дешевую контрольную точку;
2. спектральную оценку числа говорящих по eigengap / NME;
3. двухшаговый вариант: оценить `N`, затем отдать его нынешнему FastClustering как известное число кластеров.
У NeMo каскадная диаризация уже использует [spectral clustering и отдельные параметры оценки числа говорящих](https://github.com/NVIDIA-NeMo/Speech/blob/main/docs/source/asr/speaker_diarization/configs.rst). Тащить NeMo в продукт ради этого не нужно: сперва достаточно воспроизвести алгоритм в исследовательском стенде и понять, спасает ли он наши записи. Кластеризация сейчас занимает доли секунды, поэтому здесь можно позволить себе более умный алгоритм без заметного ущерба времени.
Если этот опыт пройдет, первым кандидатом на комбинацию со свободным шагом окна я бы взял CAMPPlus: на Ryzen он был примерно на 30% быстрее WeSpeaker и не показал заметного ухудшения качества при известном числе говорящих. TitaNet на текущем Intel уже дал 24% ускорения, но прибавил около 150 MiB RSS, так что для легкого профиля он хуже по памяти.
#### 3. Проверить пакетное вычисление эмбеддингов в sherpa-onnx
Текущая [реализация sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx/blob/master/sherpa-onnx/csrc/offline-speaker-diarization-pyannote-impl.h) проходит пары `(окно, локальный говорящий)` последовательно и вызывает `Compute()` для одного stream за раз; публичный [SpeakerEmbeddingExtractor](https://github.com/k2-fsa/sherpa-onnx/blob/master/sherpa-onnx/csrc/speaker-embedding-extractor.h) тоже имеет только одиночный `Compute`. Это хороший кандидат на оптимизацию без изменения математики результата.
Сначала надо проверить, допускают ли наши ONNX-графы пакет переменного размера. Если да, сделать прототип с batch 4/8/16 и, при выигрыше, нести изменение upstream. Если batch зафиксирован в 1, запасной опыт - несколько независимых stream/session с одним intra-op thread на задачу. В обоих случаях надо следить за oversubscription и RSS: просто умножить нынешние 4 потока на несколько параллельных сессий почти наверняка будет плохой конфигурацией для старого ноутбука.
#### 4. Квантовать именно embedding-модель
В нынешней матрице INT8 относится к Pyannote segmentation. Она занимает около пятой части времени, поэтому большой выигрыш от нее и не ожидался. Следующий INT8-опыт должен затронуть WeSpeaker/CAMPPlus/TitaNet.
Для CNN ONNX Runtime советует [статическую квантизацию с калибровочными данными](https://onnxruntime.ai/docs/performance/model-optimizations/quantization.html). Там же есть важная оговорка: на старых CPU без VNNI квантование нередко не ускоряет модель из-за стоимости Q/DQ и слабых INT8-инструкций. Поэтому этот опыт надо повторить на двух классах CPU: нынешнем AVX2-ноутбуке и более новом x86 с VNNI. Помимо времени и RSS, сравнивать косинусный дрейф эмбеддингов, итоговые кластеры и качество на слух.
#### Что пока отложить
- `sherpa-onnx-reverb-diarization-v1` выглядит слабым кандидатом: в [официальном примере sherpa-onnx](https://k2-fsa.github.io/sherpa/onnx/speaker-diarization/models.html#sherpa-onnx-reverb-diarization-v1) он медленнее Pyannote с тем же TitaNet и имеет non-commercial лицензию.
- End-to-end [Sortformer](https://github.com/NVIDIA-NeMo/Speech/blob/main/docs/source/asr/speaker_diarization/models.rst) полезен как контроль другой архитектуры, но это отдельный spike: тяжелее runtime, другой профиль качества и ограничение готовых моделей по числу говорящих. До него есть три более дешевых опыта внутри нынешнего ONNX-конвейера.
- Параллельные ASR и диаризация остаются хорошей задачей #22 для сочетания GPU ASR + CPU-диаризации. Такой режим уменьшает wall time, но CPU-работу диаризации не лечит и на старом четырехъядерном CPU может только добавить конкуренцию.
- Прогон на современном процессоре нужен как проверка переносимости вывода, особенно для INT8. Сам по себе такой прогон не заменит поиск причины: даже быстрый новый CPU продолжит считать почти одинаковые окна.
#### Как я бы поставил серию опытов
1. Инструментировать число окон и эмбеддингов, затем прогнать только FP32 WeSpeaker с разными `window_shift_ratio`.
2. Сохранить один набор эмбеддингов и отдельно решить автоматический подсчет говорящих для WeSpeaker, CAMPPlus и TitaNet.
3. Скрестить лучший безопасный шаг окна с лучшей парой модели эмбеддингов и кластеризации.
4. После этого отдельно проверить batch и INT8 embedding на старом и новом CPU.
5. К Sortformer и другим end-to-end моделям идти, только если каскад упрется в качество раньше нужной скорости.
Порог `go` для направления я бы поставил выше нынешних 9-10%: хотя бы 30% сокращения времени самой диаризации при прохождении нынешней проверки качества; цель всей комбинации - примерно двукратное ускорение диаризации. Проверять надо те же 3/2/2 говорящих в автоматическом режиме, mapped word purity, короткие реплики и перебивки на слух, неизменность текста ASR, медиану минимум пяти прогонов и пиковый RSS. Режим питания, частоты/температуру CPU и набор ISA стоит писать в результат каждого запуска.
Если этот план сработает, наиболее правдоподобный новый профиль будет состоять из трех вещей: более редкие окна, CAMPPlus или TitaNet и отдельная надежная оценка числа говорящих. По отдельности каждая дает ограниченный выигрыш; вместе они сокращают число вычислений и цену одного эмбеддинга, заодно снимая нынешний блокер качества.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Связано с реализацией диаризации: #24.
Контекст
Текущий профиль
Pyannote segmentation 3.0 FP32 + WeSpeaker ResNet34 LMкачественно прошёл калибровку, но диаризация занимает примерно 60% полного времени обработки и увеличивает wall-clock транскрипции в 2,2–2,6 раза.Минутный debug-профиль показал:
Высокая стоимость ожидаема из-за 10-секундных окон Pyannote с шагом 1 с и отдельного embedding для каждого локального говорящего каждого окна. При этом sherpa-onnx официально поддерживает INT8-сегментацию и
nemo_en_titanet_small, которые потенциально быстрее.Исходные данные:
docs/research/2026-08-14-diarization-cpu-cost.md;docs/benchmarks/2026-08-14-diarization-calibration.md;docs/benchmarks/2026-08-14-diarization-intel-i7.md;Цель
Найти более лёгкий CPU-профиль диаризации без недокументированной потери качества либо зафиксировать, что текущий WeSpeaker остаётся оправданным default.
Что проверить
На одних и тех же трёх контрольных записях сравнить:
--speakers N.Для каждой новой комбинации:
Критерии приёмки
docs/benchmarks/и отражён в ADR-007.Вне scope
Проверка готовности
Вердикт: задача готова к работе; метка
ready-for-agentобоснована. Связанная #24 закрыта, нативных блокирующих зависимостей у #25 нет. Тело задачи сейчас уточнять не требуется.Понимаю работу так:
sherpa-onnx/ ORT CPU; CUDA, GPU и NPU вне scope. Неисправная CUDA на ноутбуке не является блокером и в эксперименте не участвует;--speakers N;docs/benchmarks/и обновление ADR-007.Текущей обвязки
scripts/benchmarks/diarization_calibration.pyдостаточно как основы, но её нужно расширить: сейчас она не варьирует segmentation-модель и не снимает per-stage timing, CPU, peak RSS, unknown words и проверку неизменности ASR-текста. FP32/INT8 Pyannote и baseline WeSpeaker уже лежат локально; TitaNet нужно скачать и зафиксировать с SHA-256, источником и лицензией.Единственная практическая предпосылка перед фактическим прогоном: в текущем Linux-окружении нет трёх конфиденциальных исходных записей и двух Hypescribe references, а
/mntпуст. Они намеренно не хранятся в репозитории. Для benchmark-фазы понадобится доступный путь/монтирование этих пяти файлов. Это блокирует запуск замеров в данном окружении, но не означает, что постановка задачи неполна.Дополнительных продуктовых или архитектурных вопросов у меня нет.
Куда копать после эксперимента #25
После этих результатов я бы сменил главный вопрос. Замена одной модели эмбеддингов на другую уже дала нам 24-30% на этой стадии, но сама по себе задачу не закрыла. В профиле FP32 WeSpeaker из 72.5 с на диаризацию 56.8 с уходят в эмбеддинги, 15.3 с в сегментацию и меньше 0.4 с во все остальное. Причина видна в устройстве конвейера: Pyannote берет окно 10 с с шагом 1 с, а затем для каждого локального говорящего в каждом окне отдельно считается эмбеддинг. Большая часть CPU-времени уходит на повторную обработку сильно перекрывающегося звука.
Отсюда мой порядок исследований.
1. Сначала сократить число окон
Это сейчас самый сильный рычаг. В свежем sherpa-onnx параметр
window_shift_ratioуже есть в Python binding и C API, хотя в используемой у нас версии 1.13.5 Python его не показывал. Поэтому сначала стоит найти первый релиз с этим параметром и оценить обычное обновление зависимости. Если подходящего wheel еще нет, нужен маленький backport или изменение upstream. Постоянный частный binding в приложении здесь лишний.Я бы проверил
0.1(нынешние 1 с),0.15,0.2,0.25;0.5оставить как заведомо агрессивную границу. При0.2число окон должно примерно уполовиниться. Если сегментация и эмбеддинги масштабируются вслед за ним, грубая оптимистичная оценка для нынешних 72.5 с будет около 36-40 с. Это гипотеза: редкие короткие реплики, перебивки и склейка говорящих могут испортиться раньше.До опыта надо добавить в замер число окон, число вызовов extractor, сумму и квантили длительности поданных фрагментов. Одних трех времен стадии мало: без счетчиков мы опять не отличим экономию работы от более быстрого исполнения той же работы.
2. Отвязать быструю модель эмбеддингов от подсчета числа говорящих
TitaNet и CAMPPlus хорошо выглядят при известном числе говорящих. Значит, их провал в автоматическом режиме еще не доказывает плохое пространство эмбеддингов. Вероятнее, фиксированный порог FastClustering, подобранный для WeSpeaker, плохо переносится на другое распределение расстояний.
Следующий стенд должен один раз сохранить эмбеддинги и затем гонять кластеризацию без повторного запуска моделей. На нем я бы сравнил:
N, затем отдать его нынешнему FastClustering как известное число кластеров.У NeMo каскадная диаризация уже использует spectral clustering и отдельные параметры оценки числа говорящих. Тащить NeMo в продукт ради этого не нужно: сперва достаточно воспроизвести алгоритм в исследовательском стенде и понять, спасает ли он наши записи. Кластеризация сейчас занимает доли секунды, поэтому здесь можно позволить себе более умный алгоритм без заметного ущерба времени.
Если этот опыт пройдет, первым кандидатом на комбинацию со свободным шагом окна я бы взял CAMPPlus: на Ryzen он был примерно на 30% быстрее WeSpeaker и не показал заметного ухудшения качества при известном числе говорящих. TitaNet на текущем Intel уже дал 24% ускорения, но прибавил около 150 MiB RSS, так что для легкого профиля он хуже по памяти.
3. Проверить пакетное вычисление эмбеддингов в sherpa-onnx
Текущая реализация sherpa-onnx проходит пары
(окно, локальный говорящий)последовательно и вызываетCompute()для одного stream за раз; публичный SpeakerEmbeddingExtractor тоже имеет только одиночныйCompute. Это хороший кандидат на оптимизацию без изменения математики результата.Сначала надо проверить, допускают ли наши ONNX-графы пакет переменного размера. Если да, сделать прототип с batch 4/8/16 и, при выигрыше, нести изменение upstream. Если batch зафиксирован в 1, запасной опыт - несколько независимых stream/session с одним intra-op thread на задачу. В обоих случаях надо следить за oversubscription и RSS: просто умножить нынешние 4 потока на несколько параллельных сессий почти наверняка будет плохой конфигурацией для старого ноутбука.
4. Квантовать именно embedding-модель
В нынешней матрице INT8 относится к Pyannote segmentation. Она занимает около пятой части времени, поэтому большой выигрыш от нее и не ожидался. Следующий INT8-опыт должен затронуть WeSpeaker/CAMPPlus/TitaNet.
Для CNN ONNX Runtime советует статическую квантизацию с калибровочными данными. Там же есть важная оговорка: на старых CPU без VNNI квантование нередко не ускоряет модель из-за стоимости Q/DQ и слабых INT8-инструкций. Поэтому этот опыт надо повторить на двух классах CPU: нынешнем AVX2-ноутбуке и более новом x86 с VNNI. Помимо времени и RSS, сравнивать косинусный дрейф эмбеддингов, итоговые кластеры и качество на слух.
Что пока отложить
sherpa-onnx-reverb-diarization-v1выглядит слабым кандидатом: в официальном примере sherpa-onnx он медленнее Pyannote с тем же TitaNet и имеет non-commercial лицензию.Как я бы поставил серию опытов
window_shift_ratio.Порог
goдля направления я бы поставил выше нынешних 9-10%: хотя бы 30% сокращения времени самой диаризации при прохождении нынешней проверки качества; цель всей комбинации - примерно двукратное ускорение диаризации. Проверять надо те же 3/2/2 говорящих в автоматическом режиме, mapped word purity, короткие реплики и перебивки на слух, неизменность текста ASR, медиану минимум пяти прогонов и пиковый RSS. Режим питания, частоты/температуру CPU и набор ISA стоит писать в результат каждого запуска.Если этот план сработает, наиболее правдоподобный новый профиль будет состоять из трех вещей: более редкие окна, CAMPPlus или TitaNet и отдельная надежная оценка числа говорящих. По отдельности каждая дает ограниченный выигрыш; вместе они сокращают число вычислений и цену одного эмбеддинга, заодно снимая нынешний блокер качества.