diff --git a/docs/benchmarks/2026-08-31-cpu-diarization-ryzen.md b/docs/benchmarks/2026-08-31-cpu-diarization-ryzen.md index a30a53e..46edb03 100644 --- a/docs/benchmarks/2026-08-31-cpu-diarization-ryzen.md +++ b/docs/benchmarks/2026-08-31-cpu-diarization-ryzen.md @@ -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, конфигурационный контракт и модель по умолчанию остаются без изменений.