From fde4c4885930407cc9132d7d12b5aa1244699506 Mon Sep 17 00:00:00 2001 From: Dmitry Dementiev Date: Mon, 31 Aug 2026 00:16:09 +0300 Subject: [PATCH 1/2] =?UTF-8?q?docs(diarization):=20=D1=81=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=20?= =?UTF-8?q?=D1=81=D0=BB=D0=B5=D0=B4=D1=83=D1=8E=D1=89=D0=B8=D0=B9=20CPU-?= =?UTF-8?q?=D1=8D=D0=BA=D1=81=D0=BF=D0=B5=D1=80=D0=B8=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - нужен ограниченный эксперимент для отсева явных проигрышей без полного перебора и лишней точности. - Что: - зафиксирована поэтапная матрица проверок на Intel и Ryzen. - определены пороги скорости, условия качества, контроль памяти и приватности. - описаны воспроизводимость стенда, QDQ-происхождение данных и правила решения. - Проверка: - `.venv/bin/pytest -q tests/test_diarization_calibration.py tests/test_speaker_diarizer.py tests/test_diarization.py` (61 passed). --- .../2026-08-30-cpu-diarization-experiment.md | 474 ++++++++++++++++++ 1 file changed, 474 insertions(+) create mode 100644 docs/specs/2026-08-30-cpu-diarization-experiment.md diff --git a/docs/specs/2026-08-30-cpu-diarization-experiment.md b/docs/specs/2026-08-30-cpu-diarization-experiment.md new file mode 100644 index 0000000..969a944 --- /dev/null +++ b/docs/specs/2026-08-30-cpu-diarization-experiment.md @@ -0,0 +1,474 @@ +# Следующий CPU-эксперимент диаризации + +**Дата:** 2026-08-30 + +**Статус:** согласованная постановка эксперимента. Production-конфигурация не +менялась. + +## Проблема + +Текущий профиль Pyannote FP32 + WeSpeaker тратит большую часть времени +диаризации на эмбеддинги для сильно перекрывающихся окон. Отдельные исследования +нашли три независимых рычага: увеличить шаг окна, удешевить эмбеддинги и изменить +их исполнение. Полный перебор всех сочетаний слишком дорог и смешивает проверку +механизма с выбором итогового профиля. + +Нужен один эксперимент с последовательным отсевом. Он должен показать, есть ли +комбинация, которая сокращает время диаризации на Ryzen минимум на 30%, сохраняет +принятую проверку качества и остается работоспособной на старом Intel. Ориентир +для сильного результата - ускорение примерно в два раза. + +## Сначала прочитать + +- [ADR-007](../adr/007-word-level-speaker-diarization.md) - когда работа касается + действующего профиля и проверки качества. +- [Предыдущий CPU-тест](../benchmarks/2026-08-30-lightweight-diarization-cpu.md) - + при работе с корпусом, метриками и приватными результатами. +- [Шаг окна](../research/2026-08-30-sherpa-window-shift.md) - при добавлении + `window_shift_ratio` и счетчиков работы. +- [Оценка числа говорящих](../research/2026-08-30-diarization-speaker-count.md) - + при реализации `Prepare/Finalize`, eigengap и NME. +- [Embedding-инференс](../research/2026-08-30-diarization-embedding-inference.md) - + при квантизации, конкурентных stream и batch TitaNet. +- [Решение об аппаратной матрице](https://git.dementev.space/ddmitry/local-transcriber/issues/35#issuecomment-2243) + - при подготовке машин и разборе памяти. + +## Цель и границы + +Эксперимент должен: + +- расширить исследовательский стенд для новых ручек, повторов и счетчиков; +- независимо проверить сокращение работы, модель с надежной оценкой числа + говорящих и способы ускорить embedding-инференс; +- собрать не больше трех комбинаций на Intel и перенести не больше двух на + Ryzen; +- выбрать один итоговый вариант либо зафиксировать, почему ни один вариант не + прошел; +- выпустить публичный обезличенный отчет и сохранить полную переносимую капсулу + в закрытой папке Яндекс Диска. + +В эксперимент не входят: + +- изменение CLI, `.transcriber.toml` или модели по умолчанию; +- автоматический профиль по ISA или отдельные пользовательские профили; +- параллельный запуск ASR и диаризации, GPU, NPU и Mac; +- новая полная спектральная кластеризация, NeMo runtime и k-means; +- CAMPPlus batch без маски и несколько сессий как самостоятельное направление; +- production-переход на победителя по результату одного эксперимента. + +Если каскад не достигает нужной скорости из-за качества, следующий шаг - +отдельная карта по end-to-end диаризации. Этот эксперимент ее не начинает. + +## Совпадающий baseline + +Каждое сравнение использует baseline, запущенный на той же машине, в том же +режиме питания и на том же стенде: + +- `sherpa-onnx` 1.13.6; +- Pyannote Segmentation 3.0 FP32; +- WeSpeaker ResNet34 LM FP32; +- `window_shift_ratio=0.1`; +- `FastClustering` с порогом `0.89` и автоматическим числом кластеров; +- последовательный embedding-инференс; +- не больше `P` потоков, где `P` - число физических ядер машины. + +Версию `sherpa-onnx`, uv override и `sherpa-onnx-core` обновляют синхронно. +Значение `0.1` сохраняет действующее поведение. Изменение зависимости само по +себе не считается ускорением. На тех же трех фрагментах W0 сравнивается со +свежим запуском 1.13.5 на той же машине. После канонической нумерации кластеров +сверяются интервалы, назначение говорящих словам и прежние диагностические +метрики. Различия направляются в слуховую проверку отмеченных мест. Baseline +1.13.6 принимается только при `3 / 2 / 2` и отсутствии подтвержденной регрессии. + +Время диаризации во всей спецификации означает wall time одного вызова +`diarizer.process(samples)`. ASR, загрузка моделей, декодирование и подготовка +данных в него не входят. + +Три порога используются во всех этапах: + +- разница до 5% считается равенством по скорости; +- 10% - порог продолжения после дешевого отсева; +- 30% - итоговый порог `go` на Ryzen. + +## Стенд и данные + +Существующий `scripts/benchmarks/diarization_calibration.py` остается точкой +входа, но получает новую версию manifest. Прежний формат жестко закрепляет +матрицу FP32/INT8 x WeSpeaker/TitaNet, обязательный свип порогов и +`sherpa-onnx` 1.13.5, поэтому старые результаты с новым экспериментом не +продолжаются. + +Новая версия manifest и идентификатор ячейки должны учитывать: + +- вариант шага окна, модель, счетчик `N` и способ embedding-инференса; +- число outer workers, вид session и бюджеты intra-op/inter-op; +- размер batch, где он применим; +- машину, число физических ядер, CPUID/ISA и версии библиотек; +- номер повтора и позицию в расписании; +- хеш экспериментальной сборки или патча sherpa-onnx; +- хеши моделей и приватных входов по прежнему контракту. + +Для QDQ идентичность также включает хеши калибровочных источников, +препроцессинг, reader, параметры `quant_pre_process` и `quantize_static`, список +исключенных узлов, версии инструментов и хеш готового графа. Для каждого +калибровочного элемента manifest хранит идентификатор и SHA-256 исходной записи, +а также использованный временной интервал. Вырезанный или перекодированный файл +наследует эти данные от исходника. Ни один калибровочный элемент не может +происходить из трех контрольных записей, даже из другого интервала. +`--validate-only` отклоняет общий исходник; если происхождение нельзя подтвердить +или отдельного материала нет, ветвь QDQ пропускается и не блокирует остальные +направления. Проверка показывает только хеши и рецепт без приватных путей. + +Каждая измеряемая ячейка выполняется в новом дочернем процессе. Стенд сохраняет +сырые результаты каждого повтора и не заменяет их одной агрегированной строкой. +При продолжении запуска успешные ячейки пропускаются по стабильному +идентификатору, а расписание оставшихся сохраняет заданный A/B-порядок. Режим +`--validate-only` показывает полный план без приватных путей и текста. + +### Приватность + +За пределами Git остаются manifest, записи, WAV-кеш, ASR-sidecar, опорные +транскрипты, сырые интервалы, результаты, worker requests, журналы и материалы +слуховой проверки. Рабочий каталог стенда нельзя размещать в обычной +`.scratch`: репозиторий игнорирует там только `.scratch/handoffs/`. + +Публичный отчет содержит хеши, агрегаты, конфигурацию и выводы. В него не входят +локальные пути, распознанный текст, временные интервалы, имена участников и +фрагменты приватных записей. Перед внешним ревью используется только публичный +отчет и код стенда. + +## Воронка вариантов + +### Этап 0. Проверить механизмы без дорогих замеров + +До CPU-матрицы стенд должен пройти дешевые проверки: + +1. `sherpa-onnx` 1.13.6 импортируется, принимает пять значений шага и сохраняет + прежнее поведение при `0.1`. +2. Счетчик eigengap и NME проходят синтетические фикстуры из исследования. + Затем оба считаются на независимых копиях трех матриц CAMPPlus и трех матриц + TitaNet, подготовленных без повторного инференса. Для каждой модели отдельно + остается счетчик, который дает точные `3 / 2 / 2`. Если проходят оба, при + разнице времени до 10% выбирается более простой eigengap, иначе более + быстрый. Модель, для которой не проходит ни один счетчик, выбывает из + автоматического режима. +3. `Prepare/Finalize` сохраняет пары строк и штатную финализацию. Для известного + `N` метки строк и итоговая разметка совпадают с отдельным штатным вызовом на + той же матрице. +4. WeSpeaker S8S8 QDQ сохраняет входы, выходы, opset, пользовательские метаданные + и загружается через `SpeakerEmbeddingExtractor`. +5. Shared-session возвращает по одному результату на каждый вход без гонок, + пропусков и нечисловых значений. Сравнение ведется с последовательными + вызовами на том же наборе входов. + +Числовой дрейф эмбеддингов записывается, но отдельный произвольный порог для него +не вводится. Решение принимается по одинаковому числу кластеров, меткам строк и +итоговой разметке. + +### Этап 1. Найти безопасный шаг окна на Intel + +На Core i7-6820HQ проверяется текущая связка WeSpeaker с сеткой: + +| Ячейка | `window_shift_ratio` | Роль | +|---|---:|---| +| W0 | `0.10` | совпадающий baseline | +| W1 | `0.15` | осторожное сокращение окон | +| W2 | `0.20` | примерно половина исходных окон | +| W3 | `0.25` | агрессивный кандидат | +| W4 | `0.50` | граничная проверка | + +Для каждой ячейки выполняется один прогон трех пятиминутных фрагментов. Стенд +записывает фактический шаг в сэмплах, расчетное число segmentation-окон и число +embedding-заданий. Ускорение без соответствующего изменения счетчиков считается +необъясненным и не проходит дальше до разбора. + +Все значения, прошедшие обязательные условия, ранжируются по времени +диаризации. При равенстве по скорости выбирается меньший шаг: он меньше отходит +от принятой разметки. Если лучший по времени шаг ухудшает purity или число слов +без говорящего, до сборки комбинаций прослушиваются отмеченные пятиминутные +случаи. Рабочим становится лучший шаг, достигший порога продолжения. Если такого +нет, рабочим остается `0.1`, а ветвь шага считается проигравшей. + +### Этап 2. Выбрать модель и счетчик говорящих на Intel + +При `window_shift_ratio=0.1` сравниваются: + +| Ячейка | Эмбеддинги | Оценка числа говорящих | +|---|---|---| +| M0 | WeSpeaker FP32 | текущий `FastClustering`, порог `0.89` | +| M2 | CAMPPlus | счетчик, выбранный для CAMPPlus, + `FastClustering(N)` | +| M3 | TitaNet small | счетчик, выбранный для TitaNet, + `FastClustering(N)` | + +M0, M2 и M3 получают по одному прогону трех фрагментов. Быстрая модель проходит +дальше, если выдерживает обязательные условия и достигает порога продолжения +относительно M0. Малая разница не уточняется дополнительными повторами. +Регрессия диагностических метрик требует прослушать отмеченные пятиминутные +случаи до сборки комбинации. + +### Этап 3. Проверить исполнение эмбеддингов + +#### QDQ WeSpeaker + +S8S8 QDQ сравнивается с FP32 на обеих машинах при шаге `0.1`. На Intel +достаточно одного A/B по трем фрагментам. Изолированный Ryzen A/B выполняется +до сборки комбинаций этапа 4: по одному прогону baseline и кандидата на трех +фрагментах. Проверка на Ryzen обязательна даже при проигрыше на Intel: выигрыш +зависит от VNNI/AVX-512. + +U8U8 допускается как единственный запасной вариант, если S8S8 достигает порога +продолжения по времени диаризации, но не проходит качество. Другие форматы +квантизации в матрицу не добавляются. + +#### Shared-session streams + +На Intel и Ryzen сравниваются четыре конфигурации при общем бюджете `P`. На +Intel достаточно одного A/B по трем фрагментам. Изолированный Ryzen A/B также +выполняется до этапа 4 и состоит из одного прогона baseline и одного прогона +кандидата на трех фрагментах. Порядок baseline и кандидата меняется между +парами. Эти прогоны в +этапе 5 не повторяются. + +| Ячейка | Outer workers | Intra-op | Session | +|---|---:|---:|---| +| E0 | `1` | `P` | одна, текущий внутренний параллелизм | +| E1 | `2` | общий pool `P` | одна | +| E2 | `P` | `1` | одна | +| E3 | `P` | `1` на session | отдельные session, отрицательный контроль | + +`inter_op_threads=1` во всех ячейках. Измеряется время полного набора +embedding-вызовов. Latency одного удачного stream для решения не используется. +E3 не становится кандидатом, если только не опровергает ожидаемый рост RSS и не +выигрывает у shared-session по времени диаризации. + +#### Batch TitaNet + +Batch TitaNet открывается только после прохождения M3. Сначала сравниваются batch +`1` и `4` на одинаковом наборе переменных длин. Batch проходит к CPU-замеру, +если сохраняет число кластеров, метки строк и итоговую разметку на всех трех +контрольных матрицах. CAMPPlus batch и подбор размера больше `4` отложены. + +### Этап 4. Собрать комбинации на Intel + +Полного декартова перебора нет. На Intel собираются максимум три явно заданные +комбинации: + +| Ячейка | Состав | +|---|---| +| C0 | рабочий шаг + WeSpeaker FP32 + последовательный инференс | +| C1 | рабочий шаг + одна сильнейшая прошедшая ветвь | +| C2 | рабочий шаг + все совместимые прошедшие ветви | + +Сильнейшая ветвь определяется по сокращению времени диаризации: для быстрой +модели используется Intel, для QDQ и shared-session - обязательный изолированный +Ryzen A/B из этапа 3. Это намеренно грубое межмашинное ранжирование: Intel +дешево отсеивает быстрые модели, а ISA-чувствительные ветви оцениваются там, где +их выигрыш проявляется. Оно не заменяет финальный Ryzen A/B. QDQ совместим +только с WeSpeaker. Batch совместим только с TitaNet. Shared-session добавляется +к выбранной модели после проверки сохранности всех входов и результатов на этой +модели. Если C1 и C2 совпадают, дубликат не запускается. C0 совпадает с baseline +и тоже не запускается, если рабочим остался шаг `0.1`. + +Каждая существующая комбинация получает один прогон трех фрагментов. Если C0 +отличается от baseline, он всегда переносится на Ryzen после обязательных +условий, а из C1 и C2 туда идет максимум один вариант с лучшим временем +диаризации. Если C0 совпадает с baseline, на Ryzen переносятся максимум два +разных варианта C1/C2. При равенстве по скорости выбирается меньший RSS и +меньшее изменение sherpa-onnx. Если прошел только новый шаг окна, воронка +продолжается с одним C0. Если порога продолжения не достигла ни одна ветвь, +эксперимент фиксирует `stop` до финального Ryzen A/B. + +### Этап 5. Принять финалиста на Ryzen + +На Ryzen выполняются: + +- совпадающий baseline; +- не больше двух комбинаций из этапа 4. + +Изолированные QDQ и shared-session A/B уже выполнены в этапе 3 и здесь не +повторяются. + +Первичный A/B состоит из одного прогона baseline и одного прогона кандидата на +трех фрагментах. Порядок меняется для следующей пары. Вариант, который не +достигает порога продолжения, выбывает без дополнительных прогонов. + +Не больше двух вариантов, прошедших первичный A/B, получают по пять чередующихся +A/B-циклов: C0, если он отличается от baseline, и лучший по времени диаризации +из C1/C2; иначе два лучших варианта C1/C2. Один цикл содержит все три фрагмента; +результат цикла - сумма времени диаризации. Итогом служат медиана пяти парных +ускорений и MAD. Доверительные интервалы и новые повторы после пяти циклов не +требуются. + +Итоговая комбинация проходит условие скорости, если достигает итогового порога +на Ryzen. Примерно двукратное ускорение остается ориентиром. На Intel процент +допустимого замедления заранее не задается. Если финалист медленнее Intel +baseline минимум на порог продолжения, эксперимент возвращает `needs-decision` +вместо автоматического `go`: приемлемость фактической регрессии решает владелец +после замеров. + +### Этап 6. Проверить полные записи + +Один победитель и совпадающий baseline 1.13.6 однократно обрабатывают три полные +записи на Ryzen. Эти прогоны проверяют перенос результата за границы +пятиминутных фрагментов; единственное время полного прогона не заменяет +итоговый порог скорости. + +В слуховой проверке baseline и кандидат сопоставляются по пяти группам: + +- короткие ответы и перебивки; +- смены говорящего; +- перекрывающуюся речь; +- устойчивость соответствия голоса кластеру; +- слова без говорящего и малые остаточные кластеры. + +Вердикт и краткие причины сохраняются в приватной капсуле. Публичный отчет +содержит только итог `пройдено` или `не пройдено` по каждой категории. + +## Метрики + +### Работа и скорость + +Для каждой ячейки сохраняются: + +- wall time, user/system/total CPU, средняя загрузка ядер и RTF; +- времена segmentation, embeddings, clustering и всего вызова; +- фактический шаг, число segmentation-окон и embedding-заданий; +- число строк подготовленной матрицы, выбранные `p` и `N`, время счетчика; +- число embedding-вызовов, batch, workers, session и бюджеты потоков. + +### Качество + +Обязательные условия: + +- точное число кластеров `3 / 2 / 2` на контрольных фрагментах; +- неизменный распознанный текст с точностью до прежней нормализации пробелов; +- повторяемые `p`, `N`, метки строк и финализация на одной матрице; +- конечные эмбеддинги и отсутствие потерь или дублей входов. + +Диагностика: + +- `mapped speaker purity` на двух записях с опорной разметкой; +- число слов без говорящего; +- число и длительность малых кластеров; +- доля времени речи в кластерах сверх ожидаемых; +- доля назначенных слов в тех же кластерах. + +Последние два показателя получают разные имена и единицы. Дополнительными +считаются кластеры после `N` крупнейших по времени речи. Доля времени использует +общее время речи в знаменателе, доля слов - все слова, назначенные известным +кластерам. + +Прокси-метрики не получают произвольных жестких порогов. Ухудшение purity или +рост слов без говорящего относительно совпадающего baseline помечает случаи для +слуховой проверки. Победитель не принимается, если слуховая проверка +подтверждает регрессию коротких реплик, смен говорящего или устойчивости +кластеров. + +ASR выполняется один раз при подготовке sidecar и не входит в измеряемые ячейки. +Каждая ячейка использует тот же набор слов. Проверка неизменности текста +сравнивает сборку слов после назначения говорящих с sidecar и ловит потерю, +дубль или перестановку слов; она не запускает ASR повторно. + +### Память и машина + +Для каждого запуска фиксируются точный SKU, физические и логические ядра, RAM, +CPUID/ISA, ОС, версии библиотек, питание, power mode, governor, turbo, power +limits, температуры, throttling, swap и число потоков. + +Обе машины работают от сети в стабильном `balanced`-режиме. A/B чередуются, +чтобы прогрев не давал преимущества одному варианту. Прогон с throttling +или сменой режима питания не учитывается и повторяется один раз после +стабилизации. Абсолютный температурный порог не задается. + +Peak RSS записывается для каждой ячейки и сравнивается с baseline той же машины. +RSS выше 2 ГиБ на машине с 16 ГиБ RAM требует разбора, но сам по себе не +отбрасывает вариант. Эксперимент останавливает ячейку при OOM, активной +подкачке, росте RSS между повторами без плато или линейном росте памяти с числом +workers или session. + +## Правила решения + +Результаты рассматриваются в таком порядке: + +1. обязательные условия качества и корректности; +2. медианное сокращение времени диаризации на Ryzen; +3. память; +4. сложность будущей реализации и сопровождения. + +Практически незначимая разница не получает дополнительных прогонов. При +равенстве по скорости выбирается более простой и экономный по памяти вариант. +Ни один экспериментальный результат автоматически не меняет production-профиль. + +Возможны четыре исхода: + +- `go`: один вариант достигает итогового порога на Ryzen и проходит все + проверки; +- `needs-decision`: Ryzen проходит итоговый порог, но Intel показывает + практически значимую регрессию; +- `stop`: ни один вариант не проходит скорость, качество или память; причины + сохраняются по каждой ветви; +- `new map`: сокращение окон и цены эмбеддинга упирается в качество раньше + порога скорости, поэтому отдельно рассматривается end-to-end архитектура. + +## Проверка реализации стенда + +Минимальная автоматическая проверка: + +```bash +uv run pytest -q \ + tests/test_diarization_calibration.py \ + tests/test_speaker_diarizer.py \ + tests/test_diarization.py +``` + +Тесты должны покрывать новую схему manifest и идентичность ячеек, расписание и +повторы, смешанные результаты счетчиков, ветвь только с шагом окна, совместимый +стек, наследование происхождения QDQ-материала и запрет общего исходника с +контрольными записями, обязательные условия, два показателя остаточных кластеров, +контроль общего бюджета потоков, продолжение незавершенного запуска и отсутствие +приватного текста в ошибках. + +До моделей выполняются: + +```bash +uv run python scripts/benchmarks/diarization_calibration.py --help + +uv run python scripts/benchmarks/diarization_calibration.py \ + --manifest /private/path/manifest.json \ + --validate-only +``` + +После эксперимента публичный отчет должен позволять восстановить все решения по +агрегатам и хешам. Полное воспроизведение использует приватную капсулу из +закрытой папки Яндекс Диска. + +## Отклоненные варианты + +- Полный декартов перебор отклонен: большая часть ячеек повторяет уже + проигравшие механизмы и не влияет на выбор. +- Пять повторов каждой ячейки отклонены: дорогая точность нужна только для + финального A/B. Первичный отсев использует порог продолжения. +- Только шаг окна отклонен: он не проверяет более дешевые модели и исполнение, + которые могут складываться с сокращением работы. +- Только современный CPU отклонен: он скрывает регрессии на AVX2 и не дает + понять, зависит ли выигрыш от ISA. +- Полная матрица на Ryzen отклонена: старый Intel дешевле для первичного отсева, + а Ryzen нужен для ISA-чувствительных A/B и финальной комбинации. +- Сохранение текущего профиля остается допустимым итогом, если практического + победителя нет. + +## Критерии завершения задачи эксперимента + +- [ ] Стенд и тесты реализуют описанную воронку и не меняют пользовательский + контракт диаризации. +- [ ] Совпадающий baseline воспроизведен на Intel и Ryzen. +- [ ] Все пройденные и остановленные ветви имеют явную причину и ссылку на + ячейки результата. +- [ ] Финальный A/B завершен либо зафиксировано, почему до него не дошел ни один + вариант. +- [ ] При наличии победителя выполнена проверка полных записей и слуховая + приемка. +- [ ] Публичный отчет свободен от приватных данных; полная капсула сохранена + только в закрытой папке Яндекс Диска. +- [ ] Итог формулирует следующее решение: готовить production-изменение, + вынести измеренную регрессию на Intel на решение владельца, сохранить текущий + профиль или открыть новую карту end-to-end диаризации. -- 2.54.0 From 558c1baf89a9ddbc41e1daa3e6667f468d3fd126 Mon Sep 17 00:00:00 2001 From: Dmitry Dementiev Date: Mon, 31 Aug 2026 00:59:23 +0300 Subject: [PATCH 2/2] =?UTF-8?q?docs(diarization):=20=D1=80=D0=B0=D0=B7?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D0=B5=D0=BD=20CPU-=D1=8D=D0=BA=D1=81=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B8=D0=BC=D0=B5=D0=BD=D1=82=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D0=BC=D0=B0=D1=88=D0=B8=D0=BD=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - один агент не может последовательно работать на двух ноутбуках. - Что: - этапы 0-4 закреплены за Intel, этапы 5-6 - за зависимой Ryzen-задачей. - добавлен проверяемый handoff через отчет, commit и приватную ZIP-капсулу. - устранен штатный возврат с Ryzen на Intel и ограничен финальный бюджет. - Проверка: - `.venv/bin/pytest -q tests/test_diarization_calibration.py tests/test_speaker_diarizer.py tests/test_diarization.py` (61 passed). --- .../2026-08-30-cpu-diarization-experiment.md | 202 ++++++++++++------ 1 file changed, 139 insertions(+), 63 deletions(-) diff --git a/docs/specs/2026-08-30-cpu-diarization-experiment.md b/docs/specs/2026-08-30-cpu-diarization-experiment.md index 969a944..042dde1 100644 --- a/docs/specs/2026-08-30-cpu-diarization-experiment.md +++ b/docs/specs/2026-08-30-cpu-diarization-experiment.md @@ -40,8 +40,8 @@ - расширить исследовательский стенд для новых ручек, повторов и счетчиков; - независимо проверить сокращение работы, модель с надежной оценкой числа говорящих и способы ускорить embedding-инференс; -- собрать не больше трех комбинаций на Intel и перенести не больше двух на - Ryzen; +- собрать не больше трех комбинаций на Intel, передать их в Ryzen-этап и + оставить там не больше двух финалистов после первичного A/B; - выбрать один итоговый вариант либо зафиксировать, почему ни один вариант не прошел; - выпустить публичный обезличенный отчет и сохранить полную переносимую капсулу @@ -105,6 +105,7 @@ - размер batch, где он применим; - машину, число физических ядер, CPUID/ISA и версии библиотек; - номер повтора и позицию в расписании; +- этап `intel` или `ryzen`; для Ryzen - исходный commit handoff; - хеш экспериментальной сборки или патча sherpa-onnx; - хеши моделей и приватных входов по прежнему контракту. @@ -125,18 +126,78 @@ идентификатору, а расписание оставшихся сохраняет заданный A/B-порядок. Режим `--validate-only` показывает полный план без приватных путей и текста. +### Приватная капсула + +Капсула передается одним ZIP-архивом; `capsule_sha256` означает SHA-256 этого +файла. В корне лежит `capsule.json` с идентификатором капсулы, исходным commit, +хешем публичного Intel-отчета и SHA-256 каждого файла внутри. Все пути в manifest +относительны корня распакованной капсулы. + +Архив содержит: + +- manifest и полный набор входов для трех пятиминутных и трех полных прогонов; +- ASR-sidecar, опорные транскрипты и разметку, нужную для метрик и прослушивания; +- калибровочный материал и готовый QDQ-граф, если QDQ передается в Ryzen; +- сырые Intel-результаты, интервалы, worker requests и журналы, нужные для + продолжения и разбора; +- локально созданные или измененные модельные артефакты. + +Публично загружаемые базовые модели можно не дублировать в архиве, если manifest +фиксирует репозиторий, revision и SHA-256. После распаковки `--validate-only` +сверяет внешний хеш архива, `capsule.json`, относительные пути и все внутренние +хеши до запуска моделей. + ### Приватность За пределами Git остаются manifest, записи, WAV-кеш, ASR-sidecar, опорные транскрипты, сырые интервалы, результаты, worker requests, журналы и материалы слуховой проверки. Рабочий каталог стенда нельзя размещать в обычной -`.scratch`: репозиторий игнорирует там только `.scratch/handoffs/`. +`.scratch`: репозиторий игнорирует там только служебный каталог +`.scratch/handoffs/`, не связанный с переносом эксперимента между ноутбуками. Публичный отчет содержит хеши, агрегаты, конфигурацию и выводы. В него не входят локальные пути, распознанный текст, временные интервалы, имена участников и фрагменты приватных записей. Перед внешним ревью используется только публичный отчет и код стенда. +## Разделение работы между ноутбуками + +Эксперимент выполняют две зависимые задачи. Один агент не меняет машину в ходе +работы: + +| Задача | Машина | Этапы | Результат | +|---|---|---|---| +| [Intel #41](https://git.dementev.space/ddmitry/local-transcriber/issues/41) | Core i7-6820HQ | 0-4 | стенд, Intel-отчет и handoff | +| [Ryzen #42](https://git.dementev.space/ddmitry/local-transcriber/issues/42) | Ryzen 7 8845H | 5-6 | финальный отчет и решение | + +Intel-агент коммитит в обычную ветку своей задачи код стенда и обезличенный +отчет. Специальная общая ветка для handoff не создается. Manifest, сырые +результаты и другие приватные материалы в Git не попадают. В финальном +комментарии к Intel-задаче агент оставляет только индекс: + +- `intel_outcome`: `handoff` или `stop-before-ryzen`; +- ветку, точный commit и PR; +- путь и SHA-256 публичного Intel-отчета; +- число кандидатов и ссылку на их рецепты в отчете; +- непрозрачный идентификатор приватной капсулы и ее SHA-256; +- ссылку на ограничения в отчете. + +При расхождении источником истины служит файл отчета в указанном commit, +комментарий остается указателем. Он не содержит приватных путей, текста или +интервалов. Пользователь передает капсулу на второй ноутбук отдельно и сливает +Intel-PR обычным merge, без squash или rebase. После этого #41 закрывается и +средствами Gitea разблокирует #42. Ryzen-агент сначала читает обе задачи и все +комментарии к первой, обновляет `master` и проверяет присутствие handoff-commit +в его истории. Затем он сверяет SHA-256 капсулы и запускает `--validate-only`. +При наличии кандидатов агент создает обычную ветку #42 от `master`. +Несовпадение commit, капсулы или плана переводит задачу в `needs-info`; +недостающие данные агент не восстанавливает догадками. + +Штатный путь идет только с Intel на Ryzen. Если на Ryzen обнаружена ошибка +стенда, которая делает Intel-результаты недействительными, эксперимент +останавливается и требует отдельного исправления. Обычный отсев или проигрыш +кандидата не возвращает работу на Intel. + ## Воронка вариантов ### Этап 0. Проверить механизмы без дорогих замеров @@ -199,21 +260,24 @@ embedding-заданий. Ускорение без соответствующе | M2 | CAMPPlus | счетчик, выбранный для CAMPPlus, + `FastClustering(N)` | | M3 | TitaNet small | счетчик, выбранный для TitaNet, + `FastClustering(N)` | +M1 намеренно отсутствует: отдельный контроль `Prepare/Finalize` перенесен в +дешевую проверку этапа 0 и не получает CPU-ячейку. + M0, M2 и M3 получают по одному прогону трех фрагментов. Быстрая модель проходит дальше, если выдерживает обязательные условия и достигает порога продолжения относительно M0. Малая разница не уточняется дополнительными повторами. Регрессия диагностических метрик требует прослушать отмеченные пятиминутные случаи до сборки комбинации. -### Этап 3. Проверить исполнение эмбеддингов +### Этап 3. Проверить исполнение эмбеддингов на Intel #### QDQ WeSpeaker -S8S8 QDQ сравнивается с FP32 на обеих машинах при шаге `0.1`. На Intel -достаточно одного A/B по трем фрагментам. Изолированный Ryzen A/B выполняется -до сборки комбинаций этапа 4: по одному прогону baseline и кандидата на трех -фрагментах. Проверка на Ryzen обязательна даже при проигрыше на Intel: выигрыш -зависит от VNNI/AVX-512. +S8S8 QDQ сравнивается с FP32 на Intel при шаге `0.1`; достаточно одного A/B по +трем фрагментам. Если QDQ проходит обязательные условия и проверку памяти, он +остается допустимым для C2 даже без порога продолжения на Intel: возможный +выигрыш на Ryzen зависит от VNNI/AVX-512. Изолированный Ryzen A/B выполняется +уже после handoff, в этапе 5. U8U8 допускается как единственный запасной вариант, если S8S8 достигает порога продолжения по времени диаризации, но не проходит качество. Другие форматы @@ -221,12 +285,10 @@ U8U8 допускается как единственный запасной в #### Shared-session streams -На Intel и Ryzen сравниваются четыре конфигурации при общем бюджете `P`. На -Intel достаточно одного A/B по трем фрагментам. Изолированный Ryzen A/B также -выполняется до этапа 4 и состоит из одного прогона baseline и одного прогона -кандидата на трех фрагментах. Порядок baseline и кандидата меняется между -парами. Эти прогоны в -этапе 5 не повторяются. +На Intel сравниваются четыре конфигурации при общем бюджете `P`; достаточно +одного A/B по трем фрагментам. Лучшая shared-session конфигурация, прошедшая +обязательные условия и проверку памяти, остается допустимой для C2 даже без +порога продолжения. Изолированный Ryzen A/B выполняется в этапе 5. | Ячейка | Outer workers | Intra-op | Session | |---|---:|---:|---| @@ -255,48 +317,51 @@ Batch TitaNet открывается только после прохожден | Ячейка | Состав | |---|---| | C0 | рабочий шаг + WeSpeaker FP32 + последовательный инференс | -| C1 | рабочий шаг + одна сильнейшая прошедшая ветвь | -| C2 | рабочий шаг + все совместимые прошедшие ветви | +| C1 | рабочий шаг + сильнейшая ветвь по Intel | +| C2 | рабочий шаг + заранее определенный ISA-стек | -Сильнейшая ветвь определяется по сокращению времени диаризации: для быстрой -модели используется Intel, для QDQ и shared-session - обязательный изолированный -Ryzen A/B из этапа 3. Это намеренно грубое межмашинное ранжирование: Intel -дешево отсеивает быстрые модели, а ISA-чувствительные ветви оцениваются там, где -их выигрыш проявляется. Оно не заменяет финальный Ryzen A/B. QDQ совместим -только с WeSpeaker. Batch совместим только с TitaNet. Shared-session добавляется -к выбранной модели после проверки сохранности всех входов и результатов на этой -модели. Если C1 и C2 совпадают, дубликат не запускается. C0 совпадает с baseline -и тоже не запускается, если рабочим остался шаг `0.1`. +Для C1 рассматриваются ветви, достигшие порога продолжения на Intel; сильнейшая +определяется по сокращению времени диаризации. C2 начинается с рабочего шага и +WeSpeaker FP32. Если QDQ выдержал обязательные условия и память, он заменяет +FP32 независимо от скорости на Intel. Затем добавляется лучшая допустимая +shared-session конфигурация для WeSpeaker. Если ни QDQ, ни shared-session не +допустимы, C2 собирается как совместимый стек прошедших Intel-ветвей. Так +ISA-кандидаты доходят до Ryzen без предварительного Ryzen-замера. Batch совместим +только с TitaNet. Если C1 и C2 совпадают, дубликат не запускается. C0 совпадает +с baseline и тоже не запускается, если рабочим остался шаг `0.1`. -Каждая существующая комбинация получает один прогон трех фрагментов. Если C0 -отличается от baseline, он всегда переносится на Ryzen после обязательных -условий, а из C1 и C2 туда идет максимум один вариант с лучшим временем -диаризации. Если C0 совпадает с baseline, на Ryzen переносятся максимум два -разных варианта C1/C2. При равенстве по скорости выбирается меньший RSS и -меньшее изменение sherpa-onnx. Если прошел только новый шаг окна, воронка -продолжается с одним C0. Если порога продолжения не достигла ни одна ветвь, -эксперимент фиксирует `stop` до финального Ryzen A/B. +Каждая существующая комбинация получает один прогон трех фрагментов на Intel. +Все комбинации, выдержавшие обязательные условия и память, передаются в handoff; +их остается не больше трех. Если прошел только новый шаг окна, передается один +C0. Если новых комбинаций нет, Intel-агент фиксирует `stop-before-ryzen`: +Ryzen-задача подтверждает итог по handoff и не запускает модели. ### Этап 5. Принять финалиста на Ryzen -На Ryzen выполняются: +Ryzen-агент начинает с восстановления handoff. Если передано ноль кандидатов и +Intel-итог равен `stop-before-ryzen`, после проверки commit, SHA-256 капсулы и +`--validate-only` он фиксирует итог `stop` и закрывает задачу без запуска +моделей. При наличии кандидатов агент воспроизводит совпадающий baseline и +выполняет изолированные QDQ и shared-session A/B для используемых механизмов. +Каждый такой A/B состоит из одного прогона baseline и одного прогона кандидата +на трех фрагментах. Порядок baseline и кандидата меняется между парами. Эти A/B +проверяют обязательные условия и объясняют вклад механизма. Проигрыш по скорости +сам по себе не снимает переданную комбинацию; до первичного A/B она выбывает +только при нарушении обязательных условий или правила памяти. -- совпадающий baseline; -- не больше двух комбинаций из этапа 4. +Затем первичный A/B получают все переданные комбинации, но не больше трех. +Каждый A/B состоит из одного прогона baseline и одного прогона кандидата на трех +фрагментах. Порядок меняется для следующей пары. Вариант, который нарушает +обязательные условия или не достигает порога продолжения, выбывает без +дополнительных прогонов. -Изолированные QDQ и shared-session A/B уже выполнены в этапе 3 и здесь не -повторяются. - -Первичный A/B состоит из одного прогона baseline и одного прогона кандидата на -трех фрагментах. Порядок меняется для следующей пары. Вариант, который не -достигает порога продолжения, выбывает без дополнительных прогонов. - -Не больше двух вариантов, прошедших первичный A/B, получают по пять чередующихся -A/B-циклов: C0, если он отличается от baseline, и лучший по времени диаризации -из C1/C2; иначе два лучших варианта C1/C2. Один цикл содержит все три фрагмента; -результат цикла - сумма времени диаризации. Итогом служат медиана пяти парных -ускорений и MAD. Доверительные интервалы и новые повторы после пяти циклов не -требуются. +Не больше двух лучших вариантов, прошедших первичный A/B, получают по пять +чередующихся A/B-циклов. Они выбираются по времени диаризации, при равенстве по +скорости - по меньшему RSS, затем по меньшему числу отличий от baseline. При +полном равенстве порядок C0, C1, C2 служит последним тай-брейком. Один цикл +содержит все три фрагмента; результат цикла - сумма времени диаризации. Итогом +служат медиана пяти парных ускорений и MAD. Доверительные интервалы и новые +повторы после пяти циклов не требуются. Итоговая комбинация проходит условие скорости, если достигает итогового порога на Ryzen. Примерно двукратное ускорение остается ориентиром. На Intel процент @@ -381,7 +446,7 @@ limits, температуры, throttling, swap и число потоков. Peak RSS записывается для каждой ячейки и сравнивается с baseline той же машины. RSS выше 2 ГиБ на машине с 16 ГиБ RAM требует разбора, но сам по себе не -отбрасывает вариант. Эксперимент останавливает ячейку при OOM, активной +отбрасывает вариант. Правило памяти останавливает ячейку при OOM, активной подкачке, росте RSS между повторами без плато или линейном росте памяти с числом workers или session. @@ -398,14 +463,18 @@ workers или session. равенстве по скорости выбирается более простой и экономный по памяти вариант. Ни один экспериментальный результат автоматически не меняет production-профиль. -Возможны четыре исхода: +`stop-before-ryzen` - промежуточный итог Intel-этапа; после проверки handoff он +становится итогом `stop`. `needs-info` - состояние заблокированной задачи, а не +результат эксперимента. + +Возможны четыре итоговых исхода: - `go`: один вариант достигает итогового порога на Ryzen и проходит все проверки; - `needs-decision`: Ryzen проходит итоговый порог, но Intel показывает практически значимую регрессию; -- `stop`: ни один вариант не проходит скорость, качество или память; причины - сохраняются по каждой ветви; +- `stop`: baseline 1.13.6 не принят либо ни один вариант не проходит скорость, + качество или память; причины сохраняются по каждой ветви; - `new map`: сокращение окон и цены эмбеддинга упирается в качество раньше порога скорости, поэтому отдельно рассматривается end-to-end архитектура. @@ -424,8 +493,9 @@ uv run pytest -q \ повторы, смешанные результаты счетчиков, ветвь только с шагом окна, совместимый стек, наследование происхождения QDQ-материала и запрет общего исходника с контрольными записями, обязательные условия, два показателя остаточных кластеров, -контроль общего бюджета потоков, продолжение незавершенного запуска и отсутствие -приватного текста в ошибках. +контроль общего бюджета потоков, продолжение незавершенного запуска, границу +этапов Intel/Ryzen, не больше трех handoff-кандидатов и отсутствие приватного +текста в ошибках. До моделей выполняются: @@ -437,9 +507,10 @@ uv run python scripts/benchmarks/diarization_calibration.py \ --validate-only ``` -После эксперимента публичный отчет должен позволять восстановить все решения по -агрегатам и хешам. Полное воспроизведение использует приватную капсулу из -закрытой папки Яндекс Диска. +Intel-отчет должен позволять восстановить отсев и точные рецепты переданных +комбинаций по агрегатам и хешам. Финальный Ryzen-отчет ссылается на него и +добавляет первичный A/B, финальные циклы и решение. Полное воспроизведение +использует приватную капсулу из закрытой папки Яндекс Диска. ## Отклоненные варианты @@ -451,16 +522,21 @@ uv run python scripts/benchmarks/diarization_calibration.py \ которые могут складываться с сокращением работы. - Только современный CPU отклонен: он скрывает регрессии на AVX2 и не дает понять, зависит ли выигрыш от ISA. -- Полная матрица на Ryzen отклонена: старый Intel дешевле для первичного отсева, - а Ryzen нужен для ISA-чувствительных A/B и финальной комбинации. +- Полная матрица на Ryzen отклонена: старый Intel дешевле для первичного отсева. + Ryzen получает максимум три комбинации для одного первичного A/B и оставляет + максимум две для дорогих циклов. - Сохранение текущего профиля остается допустимым итогом, если практического победителя нет. -## Критерии завершения задачи эксперимента +## Критерии завершения задач эксперимента - [ ] Стенд и тесты реализуют описанную воронку и не меняют пользовательский контракт диаризации. -- [ ] Совпадающий baseline воспроизведен на Intel и Ryzen. +- [ ] Intel-задача принимает baseline и завершает этапы 0-4 либо фиксирует + подтвержденную регрессию как `stop-before-ryzen`; в обоих случаях коммитит + обезличенный отчет и публикует полный handoff-комментарий. +- [ ] Ryzen-задача начинает с указанного commit и проверенной капсулы, затем + воспроизводит свой baseline без возврата к обычным Intel-замерам. - [ ] Все пройденные и остановленные ветви имеют явную причину и ссылку на ячейки результата. - [ ] Финальный A/B завершен либо зафиксировано, почему до него не дошел ни один -- 2.54.0