feat(diarization): завершить Ryzen-этап CPU-эксперимента #44

Merged
ddmitry merged 2 commits from issue-42-ryzen-cpu-experiment into master 2026-09-01 12:54:00 +03:00
Showing only changes of commit 249b970cc9 - Show all commits
@@ -9,23 +9,23 @@ Production-профиль не менялся.
На AMD Ryzen 7 8845H выполнен этап 5 эксперимента из
[спецификации](../specs/2026-08-30-cpu-diarization-experiment.md) по единственному
кандидату C2 из [Intel-этапа](2026-08-31-cpu-diarization-intel.md). Этап 6
остановлен до полных прогонов: победителя нет. C2 сохраняет baseline-рецепт
диаризации, но выполняет embedding-вызовы через два outer
worker и одну общую ONNX Runtime session с общим intra-op пулом из восьми
потоков; `inter_op_threads=1`.
кандидату C2 из [Intel-этапа](2026-08-31-cpu-diarization-intel.md). Этап 6 не
запускали: победителя нет. C2 сохраняет `window_shift_ratio=0.1`, Pyannote
Segmentation 3.0 FP32, WeSpeaker ResNet34 LM FP32 и `FastClustering(0.89)`.
Embedding-вызовы выполняют два outer worker через одну общую ONNX Runtime
session с общим пулом из восьми intra-op потоков; `inter_op_threads=1`.
Изолированный A/B механизма shared-session дал ускорение 24,05%. Первичный A/B
C2 показал 22,90%, поэтому кандидат прошел порог продолжения 10%, и для него
запустили пять чередующихся циклов. Они дали медиану 23,57% при MAD 0,62
процентного пункта, то есть ниже итогового порога 30%; по одному Ryzen-кандидату
C2 итог был бы `stop`.
Полная воронка, однако, соответствует отдельному исходу `new map`: все шаги
окна W1-W4 потеряли кластер, а C1 с CAMPPlus + NME ускорилась на Intel на 33,65%,
но добавила 2 слова без говорящего и осталась без требуемой слуховой проверки.
C1 на Ryzen не передавали, победителем она не считается; возвращать ее в матрицу
без подготовленной приватной слуховой проверки нельзя, и автоматической оценкой
эта проверка не подменялась.
Вся воронка дала `new map`. W1-W4 потеряли обязательное число кластеров. C1 с
CAMPPlus + NME ускорила Intel-прогон на 33,65%, но оставила два дополнительных
слова без говорящего; требуемую слуховую проверку не проводили и автоматической
оценкой не подменяли. Поэтому C1 не передавали на Ryzen и не считаем победителем.
Вернуть ее в матрицу можно только после приватной слуховой проверки.
Все принятые ячейки прошли обязательное качество: `3 / 2 / 2` кластера,
неизменный текст, `51 / 13 / 9` слов без говорящего, те же значения
@@ -34,8 +34,120 @@ C1 на Ryzen не передавали, победителем она не сч
Пиковый RSS C2 оставался в диапазоне 410,3-418,1 МиБ; OOM, активности swap и
линейного роста памяти не наблюдалось.
Полные записи и пятикатегорийная слуховая приемка по правилам этапа 6 не
запускались.
Полные записи и слуховая приемка по пяти категориям на этапе 6 не запускались.
## Что именно проверяли
Отчет охватывает всю воронку: этапы 0-4 на Intel и этап 5 на Ryzen. На Intel
проверили шаг окна, модели эмбеддингов, счетчик говорящих, квантизацию, способы
параллельного исполнения и две комбинации. Таблицы ниже собирают этот путь в
одном месте. Подробные хеши и исходные идентификаторы ячеек остаются в
[Intel-отчете](2026-08-31-cpu-diarization-intel.md).
Intel-ячейки W/M/E/C были разведочными одиночными прогонами трех пятиминутных
фрагментов. Они показывают направление и причину отсева, но не дисперсию.
Исключение - соседняя пара W0/C2 под runtime guard. На Ryzen сначала выполнялась
одна диагностическая пара, затем первичная пара C2 и только после прохождения
порога - пять чередующихся циклов.
### Этап 0: пригодность механизмов
До дорогих CPU-ячеек проверили, что экспериментальный стенд вообще измеряет
задуманные механизмы, не теряя строки и не меняя пользовательский результат.
| Гипотеза | Что проверяли | Что получилось | Решение |
|---|---|---|---|
| `Prepare/Finalize` | Подготовка один раз, кластеризация копии матрицы и финализация | Результат совпал со штатным `process()`; пропусков и дублей строк нет | Использовать в стенде |
| Счетчик eigengap | CAMPPlus и TitaNet на матрицах `531 / 414 / 440` строк | CAMPPlus дал `6 / 1 / 7`, TitaNet - `7 / 2 / 4` вместо `3 / 2 / 2` | Исключить |
| Счетчик NME | Те же шесть матриц без повторного embedding-инференса | Обе модели дали `3 / 2 / 2`; метки повторяемы, потерянных строк нет | Использовать для M2 и M3 |
| WeSpeaker S8S8 QDQ | Схема, opset, метаданные, динамические длины и загрузка через sherpa-onnx | Граф технически корректен; качество и скорость проверены отдельно в Q1 | Допустить к CPU-ячейке |
| Shared-session worker | Один и два worker на одном наборе prepared-входов | Строки, метки и интервалы совпали; гонок и нечисловых значений нет | Допустить E1/E2 |
Задача этапа 0 - подтвердить, что последующие CPU-ячейки сравнивают одинаковые
вычисления и используют исправный экспериментальный API. Скорость измеряли на
следующих этапах.
### Этап 1: можно ли считать меньше окон
Гипотеза: увеличить `window_shift_ratio`, сократить число segmentation-окон и
вместе с ними число embedding-заданий. Все варианты использовали Pyannote
Segmentation 3.0 FP32, WeSpeaker FP32 и текущий `FastClustering(0.89)`.
| Ячейка | Шаг | Окна по трем фрагментам | Embedding-задания | Время, с | Кластеры | Решение |
|---|---:|---|---|---:|---|---|
| W0 | 0,10 | `291 / 291 / 291` | `531 / 414 / 440` | 148,220 | `3 / 2 / 2` | baseline |
| W1 | 0,15 | `195 / 195 / 195` | `357 / 274 / 302` | 100,799 | `2 / 2 / 2` | исключена: потерян кластер |
| W2 | 0,20 | `146 / 146 / 146` | `264 / 206 / 222` | 76,127 | `2 / 2 / 2` | исключена: потерян кластер |
| W3 | 0,25 | `117 / 117 / 117` | `211 / 165 / 175` | 61,369 | `2 / 2 / 2` | исключена: потерян кластер |
| W4 | 0,50 | `59 / 59 / 59` | `105 / 82 / 89` | 31,291 | `1 / 2 / 2` | исключена: потеряны два кластера |
Скорость росла именно вместе с сокращением работы, но даже осторожный W1
нарушил обязательное качество на первой записи. Рабочим остался шаг `0.1`.
Комбинация C0 совпала бы с baseline, поэтому ее не запускали.
### Этап 2: можно ли удешевить модель эмбеддингов
Гипотеза: сохранить полный набор окон, но заменить WeSpeaker и при необходимости
счетчик говорящих. В M2 и M3 сохраняли `window_shift_ratio=0.1`, Pyannote
Segmentation 3.0 FP32 и последовательный инференс. Менялись модель эмбеддингов и
счетчик говорящих. Проценты ниже посчитаны относительно W0 148,220 с.
| Ячейка | Рецепт | Время, с | Изменение | RSS, МиБ | Кластеры | Решение |
|---|---|---:|---:|---:|---|---|
| W0 (рецепт M0) | WeSpeaker FP32 + `FastClustering(0.89)` | 148,220 | baseline | 362,6 | `3 / 2 / 2` | точка сравнения |
| M2 | CAMPPlus + NME + `FastClustering(N)` | 94,550 | -36,21% | 358,6 | `3 / 2 / 2` | самый быстрый, собрать C1 |
| M3 | TitaNet small + NME + `FastClustering(N)` | 107,205 | -27,67% | 511,9 | `3 / 2 / 2` | прошла, но медленнее и тяжелее M2 |
M2 и M3 прошли обязательное число кластеров. Окончательная диагностика M2
появилась в C1: purity выросла, но число слов без говорящего на первой записи
изменилось с 51 до 53. По спецификации это требует прослушивания отмеченных мест.
### Этап 3: квантизация и исполнение эмбеддингов
Для QDQ проверяли, даст ли INT8 выигрыш старому Intel без VNNI. Q1 и E1-E3
сохраняли `window_shift_ratio=0.1`, Pyannote Segmentation 3.0 FP32 и текущий
`FastClustering(0.89)`. Q1 меняла модель WeSpeaker, E1-E3 - только топологию
исполнения FP32-модели. Во всех вариантах `inter_op_threads=1`; E1-E3 использовали
общий бюджет четырех физических ядер.
| Ячейка | Рецепт | Время, с | Изменение к W0 | RSS, МиБ | Кластеры | Решение |
|---|---|---:|---:|---:|---|---|
| Q1 | WeSpeaker S8S8 QDQ, sequential | 177,537 | +19,78% | 367,7 | `2 / 2 / 2` | исключена: медленнее и потерян кластер |
| E1 | 2 outer worker, одна session, общий pool 4 | 139,219 | -6,07% | 398,6 | `3 / 2 / 2` | передать как аппаратный кандидат |
| E2 | 4 outer worker, одна session, `intra_op=1` | 152,167 | +2,66% | 551,2 | `3 / 2 / 2` | исключена по времени и памяти |
| E3 | 4 отдельные session, `intra_op=1` | 159,038 | +7,30% | 732,3 | `3 / 2 / 2` | отрицательный контроль подтвердился |
E3 измерялась составным интервалом на заранее подготовленных заданиях, поэтому
ее wall time нельзя сравнивать с `process()` так же строго, как E1/E2. Для
решения хватило отрицательного результата одновременно по времени и памяти.
TitaNet batch проверили отдельно на тех же трех матрицах:
| Контроль | Строк | Batch 1, с | Batch 4, с | Число кластеров | Метки строк совпали |
|---:|---:|---:|---:|---:|:---:|
| 1 | 531 | 27,175 | 37,588 | 3 | нет |
| 2 | 414 | 25,871 | 32,443 | 2 | нет |
| 3 | 440 | 22,877 | 28,565 | 2 | нет |
Batch 4 сохранил число кластеров, но оказался медленнее batch 1, поменял метки
строк на всех трех матрицах и дал максимальный дрейф эмбеддингов `0,277-0,317`.
Ветвь остановили до отдельной CPU-ячейки. SHA-256 закрытого batch-result:
`f12bfdb5cadf80c62871c0725eac744a99825cb5d06b70fcf11e4e538ff6df72`.
### Этап 4: комбинации перед Ryzen
Полного декартова перебора не было. C0 совпала с baseline; из оставшихся
собрали самый быстрый модельный вариант C1 и переносимый аппаратный вариант C2.
| Комбинация | Точный рецепт | Время, с | Ускорение | RSS, МиБ | Диагностика | Решение |
|---|---|---:|---:|---:|---|---|
| C1 | `0.1` + Pyannote Segmentation 3.0 FP32 + CAMPPlus + NME + `FastClustering(N)` + sequential | 98,347 | 33,65% | 399,5 | `3 / 2 / 2`; purity выше; `+2` слова без говорящего | не передавать без слуховой проверки |
| C2 | `0.1` + Pyannote Segmentation 3.0 FP32 + WeSpeaker ResNet34 LM FP32 + `FastClustering(0.89)` + 2 outer worker + 1 shared session + pool 4 | 140,632 | 4,32% | 406,0 | точное совпадение с соседним W0 | передать на Ryzen |
У C1 процент считается от разведочного W0 148,220 с. У C2 был соседний W0
146,978 с под тем же runtime guard. C2 не достигла Intel-порога продолжения,
но shared-session заранее считалась аппаратной гипотезой: Ryzen имеет восемь
физических ядер и другой ISA, поэтому спецификация требовала отдельный A/B.
## Handoff и воспроизводимость
@@ -53,10 +165,10 @@ C1 на Ryzen не передавали, победителем она не сч
| SHA-256 Ryzen-wheel | `baaaa2532606f1e1411e5c84655afa70107ad4b1676420953f5e67233c46a484` |
Входная Intel-капсула прошла полную проверку: schema v3, 199 файлов,
QDQ-артефакт доступен, предупреждений нет. Ryzen-manifest без изменений закрепил
из Intel-капсулы handoff, артефакты, контрольные записи, calibration, QDQ, патч,
версии runtime и интервал RSS. Baseline и candidate каждой запускаемой
combination-пары совпали с переносимыми Intel-рецептами.
QDQ-артефакт доступен, предупреждений нет. Ryzen-manifest без изменений
зафиксировал данные Intel-капсулы: handoff, артефакты, контрольные записи,
calibration, QDQ, патч, версии runtime и интервал RSS. Рецепты baseline и
кандидата во всех запущенных парах совпали с переносимыми Intel-рецептами.
Ryzen-manifest, сырые результаты, рабочие артефакты стенда, guard-свидетельства
и журналы сохранены в закрытой папке Яндекс Диска. Чтобы восстановить
@@ -94,7 +206,7 @@ Ryzen-manifest отдельно связывается с handoff и входа
температурный датчик или аппаратные лимиты мощности. Температура
записана как недоступная; это ограничивает тепловой guard;
- нативные WSL-интерфейсы не отдали supply, platform profile и throttle count,
поэтому для принятых ячеек стенд взял override
поэтому для принятых ячеек использовали override
`LOCAL_TRANSCRIBER_RUNTIME_GUARD_JSON`, заполненный проверенными Windows-фактами:
AC, `balanced`, turbo, power policy, нулевая подкачка, отсутствие throttling.
До и после принятых пар Windows-проверка подтверждала AC, неизменный `balanced`
@@ -134,6 +246,44 @@ E1 и C2 сохранили все обязательные и диагност
медиана недобрала 6,43 процентного пункта. На Intel тот же механизм давал только
4,32%; Ryzen ускоряет shared-session заметнее, но недостаточно для `go`.
## Что уже не нужно прогонять заново
Полный replay этапов 0-5 для следующей гипотезы не нужен. Результаты закреплены
публичными агрегатами, хешами и закрытой капсулой.
| Результат | Что можно переиспользовать | Когда повтор действительно нужен |
|---|---|---|
| W1-W4 | На текущей Pyannote-модели увеличение шага теряет кластер раньше порога скорости | Изменилась segmentation-модель, логика сборки окон или обязательный quality gate |
| M2/M3 | CAMPPlus и TitaNet с NME сохраняют `3 / 2 / 2`; M2 быстрее и экономнее | Изменилась модель, счетчик, clustering или контрольные входы |
| Q1 | Этот S8S8 QDQ-граф на Intel медленнее FP32 и дает `2 / 2 / 2` | Новая калибровка, формат квантизации, модель или runtime |
| E2/E3 | Больше worker и отдельные session не окупились при прежнем бюджете потоков | Изменилась топология session, runtime или бюджет ядер |
| E1/C2 на Ryzen | Для точного рецепта shared-session медиана равна 23,57%, качество и память проходят | Изменилась реализация механизма, runtime, машина или режим питания |
| C1 | Замер скорости уже есть; открыт только диагностический слуховой gate | Сначала прослушать подготовленные места; новый CPU-замер нужен лишь после положительного решения |
Есть три разных вида проверки, и они не заменяют друг друга:
1. `--validate-only` сверяет commit, схему, хеши, handoff и план. Модели он не
запускает.
2. `uv run pytest` проверяет код стенда на mock-данных. Полный Python-suite перед
PR нужен, но он не повторяет аудиозамеры и не скачивает модели.
3. Измерительная ячейка запускает реальные модели. Для новой гипотезы нужен
только затронутый минимальный срез, один совпадающий baseline и первичный A/B.
Пять циклов и полные записи нужны лишь кандидату, прошедшему записанные пороги.
## Что осталось неизвестным
- C1 не получила слухового решения. Из автоматических метрик нельзя вывести,
допустимы ли два дополнительных слова без говорящего.
- На Ryzen не опубликовано разложение wall time на segmentation, embeddings,
clustering и orchestration. По общему времени нельзя надежно назвать следующий
доминирующий компонент.
- Полные записи не запускались: C2 не достигла порога, а C1 не дошла до Ryzen.
- Температурный датчик в WSL и Windows недоступен. Внешняя проверка питания и
throttling снижает риск дрейфа, но не заменяет измерение температуры во время
каждой ячейки.
- Эксперимент проверял локальные замены внутри прежнего каскада. End-to-end
архитектуры в эту матрицу не входили.
## Индекс публичных доказательств
Хеши связывают обезличенные агрегаты с приватными JSON-результатами и не
@@ -160,7 +310,9 @@ E1 и C2 сохранили все обязательные и диагност
диагностическим слуховым gate: сокращение окон и цены эмбеддинга уперлось в
качество раньше итогового порога.
4. По правилу спецификации следующий шаг - отдельная карта end-to-end
диаризации; этот отчет ее не открывает и новых замеров не назначает.
диаризации, заведенная как
[задача #45](https://git.dementev.space/ddmitry/local-transcriber/issues/45).
Ее гипотезы и замеры относятся к отдельному эксперименту.
5. Полные записи и Ryzen-слуховая приемка не требуются без победителя.
6. Production-профиль, CLI, конфигурационный контракт и модель по умолчанию
остаются без изменений.