Files
local-transcriber/docs/benchmarks/2026-08-30-lightweight-diarization-cpu.md
Dmitry Dementiev 5b5fd535d6 feat(diarization): добавлена проверка переносимого бенчмарка
- Зачем:
  - новый агент должен проверить приватный набор до дорогого CPU-прогона.
- Что:
  - добавлены режим validate-only, относительные пути и ранняя проверка зависимостей.
  - описано воспроизведение; временные handoff исключены из Git.
- Проверка:
  - .venv/bin/pytest -q: 334 passed, 2 skipped.
  - ruff check, ruff format --check и git diff --check: успешно.
2026-08-30 13:26:16 +03:00

18 KiB
Raw Permalink Blame History

Облегчённые модели диаризации на старом Intel Core i7

Дата: 2026-08-30

Статус: сравнительный CPU-тест. Конфигурация по умолчанию не менялась.

Решение

Сохранить модель по умолчанию: Pyannote Segmentation 3.0 FP32, WeSpeaker ResNet34 LM, общий порог автоматической кластеризации 0,89. Новый прогон подтвердил для этой связки точное число голосовых кластеров 3 / 2 / 2 на всех трёх контрольных фрагментах.

Pyannote INT8 с WeSpeaker не заменяет FP32. Среднее ускорение составило только 2,1%, средний пиковый RSS по ячейкам снизился примерно на 4 МиБ, а средняя чистота сопоставленных говорящих и число слов без говорящего стали хуже.

TitaNet small в среднем быстрее WeSpeaker на 24,3%, но требует примерно на 147–149 МиБ больше RSS и не находит правильное число кластеров ни в одной проверенной точке общего автоматического порога. Его можно рассматривать только как кандидата для режима с явно известным числом говорящих. Следующий эксперимент должен завершиться отдельной слуховой проверкой. По этому тесту менять код, модели или пороги нельзя.

Границы вывода

Проверялись только четыре CPU-сочетания Pyannote FP32/INT8 × WeSpeaker/TitaNet на трёх пятиминутных русскоязычных рабочих созвонах из исходной калибровки. Число говорящих в них известно как 3 / 2 / 2. Это сравнительный прогон моделей диаризации, а не новая приёмка готовых транскриптов.

В частности:

  • новой слуховой проверки не было;
  • полные записи не прогонялись;
  • у каждой ячейки один запуск, без повторов и случайного перемешивания порядка;
  • влияние порядка, прогрева и температуры отдельно не измерено;
  • ASR выполнен один раз на запись и не входит во время ячеек диаризации;
  • параллельные ASR и диаризация, GPU, NPU и другие CPU не проверялись.

Происхождение, лицензии и закреплённые SHA-256 моделей описаны в исследовательской записке, а смысл mapped speaker purity и ограничения опорной разметки — в исходной калибровке. Здесь эти сведения не дублируются.

Методика

Каждая измеряемая ячейка запускалась в новом дочернем процессе. До измеряемого интервала загружались WAV, модели и сэмплы; полное время, CPU и пиковый RSS считались вокруг одного вызова diarizer.process(samples). Отладочный журнал sherpa-onnx 1.13.5 дал время сегментации, извлечения эмбеддингов, кластеризации и всего вызова. Декодирование, загрузка моделей, сериализация и ASR во время не входят.

Автоматический перебор прямоугольный: каждый порог сочетания проверен на всех трёх записях с num_clusters=-1. Для сочетания выбирался один общий порог. Условие приёмки считалось выполненным только при полном совпадении общего числа кластеров с 3 / 2 / 2; число содержательных кластеров оставалось диагностикой и не могло оправдать лишний кластер. После выбора порога каждое сочетание ещё раз прошло три записи с известным числом кластеров.

Итого сохранено 108 успешных ячеек без ошибок:

  • 96 с автоматическим числом кластеров: по 18 для каждого сочетания с WeSpeaker и по 30 для каждого сочетания с TitaNet;
  • 12 с известным числом кластеров: по три для каждого из четырёх сочетаний.

Отдельно сохранены четыре результата выбора общего порога. Они описывают агрегацию автоматических ячеек и в число 108 не входят.

Во всех 108 ячейках совпала последовательность 1853 распознанных слов после удаления Unicode-пробелов. Инвариантность текста не нарушена.

Воспроизведение

Стенд diarization_calibration.py закрепляет корпус и матрицу задачи #25. Файл manifest, ASR-sidecar, интервалы и полный результат содержат приватные данные и хранятся отдельно от Git. Относительные пути в manifest считаются от каталога самого файла, поэтому приватный набор можно перенести на другую машину целиком. Готовый файл результата при этом не переносится: продолжение допустимо только на той же платформе, CPU и том же числе логических ядер.

Перед дорогим запуском нужно проверить набор без декодирования и моделей:

uv run python scripts/benchmarks/diarization_calibration.py \
  --manifest /path/to/manifest.json \
  --validate-only

Полный запуск и его продолжение используют одну команду и один файл результата:

uv run python scripts/benchmarks/diarization_calibration.py \
  --manifest /path/to/manifest.json \
  --output /path/to/results.json \
  --work-dir /path/to/work

Совместимый файл продолжится с первой незавершенной ячейки. Код возврата 0 означает, что завершены все автоматические ячейки и ячейки с известным числом говорящих. Число кластеров и инвариантность текста нужно сверить с условиями конкретного исследования. Полный контракт аргументов и режим подготовки ASR-sidecar описаны в --help. Результат включает исходный manifest, приватные пути и интервалы. Поэтому его нельзя добавлять в Git или передавать внешнему ревьюеру.

Оборудование и условия

  • Intel Core i7-6820HQ, 4 ядра / 8 логических процессоров;
  • Linux 7.0.0-30-generic x86_64;
  • питание от сети, системный профиль balanced;
  • Python 3.13.13, sherpa-onnx 1.13.5, onnxruntime 1.28.0, NumPy 2.4.3;
  • CPU provider и 8 потоков для segmentation и embedding;
  • mono PCM16, 16 кГц, длительность каждого фрагмента 300 секунд.

Это тот же ноутбук, что в предыдущей Intel-приёмке, но условия нельзя считать побайтовым повтором: там отдельно зафиксированы governor powersave и урезанная доступная мощность, а версия ядра была 7.0.0-29. Во время нового перебора ночная пауза произошла до сохранения текущей ячейки. Продолжение началось после возврата профиля balanced; ячеек, измеренных в энергосберегающем профиле, в итоговом результате нет.

Производительность полного автоматического перебора

Числа ниже — средние по всем автоматическим ячейкам сочетания. Это не повторы одной ячейки. «Средний пиковый RSS» — среднее пикового RSS каждой ячейки; соседний столбец показывает наибольший такой максимум во всём переборе.

Сегментация + эмбеддинги Ячеек Время, с RTF CPU, ядер Средний пиковый RSS, МиБ Максимальный пиковый RSS, МиБ Сегментация, с Эмбеддинги, с
FP32 + WeSpeaker 18 72,486 0,24162 6,84 254,55 265,61 15,295 56,837
INT8 + WeSpeaker 18 70,969 0,23656 6,94 250,40 263,00 17,094 53,524
FP32 + TitaNet 30 52,797 0,17599 6,80 401,80 428,11 14,724 37,723
INT8 + TitaNet 30 55,792 0,18597 6,70 399,10 419,71 17,125 38,278

INT8 ускорил сочетание с WeSpeaker на 2,1%, но сегментация стала медленнее, а выигрыш возник на извлечении эмбеддингов. Такой результат не даёт практического основания заменять FP32. У TitaNet FP32 оказался быстрее INT8. Относительно соответствующей связки WeSpeaker ускорение TitaNet составило 27,2% для FP32 и 21,4% для INT8. Среднее для двух вариантов точности — 24,3%.

Опубликованное ожидание ускорения от INT8 локально не подтвердилось. Вместо 18,9% с 3D-Speaker получено 2,1% с WeSpeaker, а с TitaNet INT8 оказался на 5,7% медленнее FP32. Условия исходного примера и границы такого сравнения разобраны в исследовательской записке.

За скорость TitaNet платит памятью: средний пиковый RSS выше WeSpeaker на 147,25 МиБ с FP32 и на 148,70 МиБ с INT8. Поэтому на слабом CPU его нельзя считать безусловно облегчённым вариантом.

Автоматический порог и диагностика качества

Mapped speaker purity усреднена только по T2 BDMA и Yantar, для которых есть грубая опорная разметка. Это сравнительная прокси-метрика, не DER и не точность по выверенной разметке.

Сочетание Выбранный порог Приёмка 3 / 2 / 2 Кластеры Средняя purity Слов без говорящего, из 1853 Максимальный остаток сверх ожидаемых
FP32 + WeSpeaker 0,85 да 3 / 2 / 2 0,82950 71 0%
INT8 + WeSpeaker 0,85 да 3 / 2 / 2 0,81028 104 0%
FP32 + TitaNet 1,10 нет 5 / 3 / 3 0,82287 76 3,89%
INT8 + TitaNet 1,10 нет 3 / 2 / 3 0,79315 109 12,12%

У FP32 + WeSpeaker условие приёмки проходит на плато 0,85–0,89, у INT8 + WeSpeaker — на 0,85–0,90. Автоматический выбор дал 0,85 по общей лексикографической оценке, но это не аргумент менять рабочий порог 0,89: новый перебор не содержит повторов или слуховой проверки, тогда как действующее решение опирается также на предыдущую калибровку полных записей и слуховую проверку плотного диалога.

TitaNet не прошёл условие приёмки ни в одной точке проверенной сетки 0,50–1,10. Порог 1,10 — лишь наименее плохая точка перебора; допустимой автоматической конфигурацией она не является. Содержательные 3 / 2 / 3 кластера у FP32 TitaNet не отменяют лишние голосовые кластеры: диагностический фильтр не заменяет условие приёмки.

«Максимальный остаток сверх ожидаемых» — наибольшая по трём записям доля слов, которые попали в кластеры сверх известного числа говорящих. Метрика описывает объём лишних кластеров, а не их количество: один крупный лишний кластер может дать больший процент, чем несколько малых.

Режим с известным числом говорящих

Ячейки с известным числом говорящих использовали тот же выбранный порог и меняли только num_clusters на известные 3 / 2 / 2. Все четыре сочетания вернули точное число кластеров на всех трёх записях.

Сочетание Точное число кластеров Точное число содержательных кластеров Средняя purity
FP32 + WeSpeaker 3 / 3 записей 3 / 3 записей 0,82950
INT8 + WeSpeaker 3 / 3 записей 3 / 3 записей 0,81028
FP32 + TitaNet 3 / 3 записей 2 / 3 записей 0,83057
INT8 + TitaNet 3 / 3 записей 3 / 3 записей 0,79721

Близкая средняя purity FP32 TitaNet и FP32 WeSpeaker не доказывает равноценное качество. У FP32 TitaNet на Data Test один из трёх принудительных кластеров не достиг диагностического порога содержательности, а новой слуховой проверки не было. Поэтому TitaNet остаётся только направлением будущего эксперимента с обязательным известным числом говорящих.

Ограничения и следующий шаг

  • Три записи относятся к одному типу рабочих созвонов; результат не переносится автоматически на другие языки, микрофоны и акустические условия.
  • Две опорные разметки имеют секундную точность и не позволяют считать DER.
  • Средние значения полного времени и RSS объединяют разные записи и пороги. Без повторов они не оценивают дисперсию и не отделяют порядок, прогрев или влияние температуры.
  • Новый прогон не заменяет прежнюю слуховую проверку и не даёт слуховых данных для TitaNet или INT8.
  • Результат относится к последовательной CPU-диаризации на одном Core i7-6820HQ и не предсказывает конкретный Core i5 11-го поколения.

Если возвращаться к TitaNet, следующий эксперимент должен заранее требовать известное число говорящих, включать повторные прогоны с контролем порядка и температуры и завершаться слуховой проверкой всех трёх записей. До этого конфигурацию и модель по умолчанию не менять.