- Зачем: - старые результаты решено сохранить до завершения и приемки задачи #46. - Что: - архивы задач #41/#42 закреплены как временные и доступные только для чтения; - стенду #46 запрещено читать архивы или использовать их как основу A/B; - удаление перенесено на момент после приемки нового отчета. - Проверка: - выполнены git diff --check и проверка формулировки в задаче #46.
479 lines
33 KiB
Markdown
479 lines
33 KiB
Markdown
# Эксперимент Streaming Sortformer v2 Q8_0 на Ryzen CPU
|
||
|
||
**Дата:** 2026-09-01
|
||
|
||
**Статус:** готовый контракт следующего эксперимента, исполняемый в
|
||
[задаче #46](https://git.dementev.space/ddmitry/local-transcriber/issues/46).
|
||
Запуск модели и аудио начинается после подготовки свежей закрытой основы.
|
||
|
||
## Цель и исходы
|
||
|
||
Эксперимент отвечает на один вопрос: сокращает ли Streaming Sortformer v2
|
||
Q8_0 через NeMo-Speech.cpp время построения разметки говорящих на целевом Ryzen
|
||
минимум на 30% относительно свежего последовательного W0 при приемлемом
|
||
качестве и памяти.
|
||
|
||
Допустимы три исхода:
|
||
|
||
- `go`: медиана пяти финальных парных ускорений не ниже 30%, автоматические и
|
||
слуховые gates пройдены;
|
||
- `stop`: нарушен обязательный gate, первичное ускорение ниже 10%, финальная
|
||
медиана ниже 30% или слуховая проверка подтвердила регрессию;
|
||
- `needs-user`: отсутствует подтверждение свежей опоры или требуемое решение
|
||
по прослушиванию. Это состояние ожидания, его нельзя публиковать как результат
|
||
кандидата.
|
||
|
||
`go` разрешает подготовку отдельного production-решения. Экспериментальный
|
||
runtime остается изолированным, а production-профиль, CLI, config и модель по
|
||
умолчанию сохраняются.
|
||
|
||
## Сначала прочитать
|
||
|
||
1. Прочитать [карту архитектур](../research/2026-09-01-end-to-end-cpu-diarization-map.md),
|
||
особенно разделы "Граница выводов", A1 и формулу полной замены.
|
||
Критерий завершения: агент может объяснить, почему старые значения
|
||
`3 / 2 / 2`, unmatched words и purity не являются порогами нового опыта.
|
||
2. Прочитать [ADR-007](../adr/007-word-level-speaker-diarization.md) и
|
||
[старую спецификацию](2026-08-30-cpu-diarization-experiment.md). Критерий
|
||
завершения: сохранены каноническое сведение слов, правила 5%/10%/30% и
|
||
memory gate.
|
||
3. Прочитать `src/local_transcriber/benchmark_experiment.py` и
|
||
`tests/test_benchmark_experiment.py` перед реализацией. Критерий завершения:
|
||
новый стенд использует отдельный модуль и schema, не принимает старый
|
||
manifest или закрытые хранилища задач #41/#42.
|
||
|
||
## Границы
|
||
|
||
В scope входят свежая закрытая основа, точный W0/Sortformer A/B на Ryzen,
|
||
проверка трех полных записей после прохождения 30%-го порога на фрагментах и
|
||
слуховая приемка.
|
||
|
||
За пределами scope остаются:
|
||
|
||
- replay W/M/Q/E/C и восстановление старых patch/wheel;
|
||
- C1; ее повторное открытие требует отдельной свежей W0/C1 пары и нового
|
||
слухового материала;
|
||
- Intel и другие CPU;
|
||
- production integration, dependency lock, упаковка runtime и изменение ADR;
|
||
- параллельное планирование ASR/диаризации - это
|
||
[задача #22](https://git.dementev.space/ddmitry/local-transcriber/issues/22);
|
||
- автоматическое включение - это
|
||
[задача #23](https://git.dementev.space/ddmitry/local-transcriber/issues/23);
|
||
- joint ASR+diarization и shared-ASR-encoder. ASR создает только quality
|
||
sidecar до измерений.
|
||
|
||
## Закрепленные реализации
|
||
|
||
### W0
|
||
|
||
W0 использует действующий последовательный путь на восьми физических ядрах:
|
||
|
||
- `sherpa-onnx/core 1.13.6`;
|
||
- Pyannote Segmentation 3.0 FP32, SHA-256
|
||
`220ad67ca923bef2fa91f2390c786097bf305bceb5e261d4af67b38e938e1079`;
|
||
- WeSpeaker ResNet34 LM FP32, SHA-256
|
||
`e9848563da86f263117134dfd7ad63c92355b37de492b55e325400c9d9c39012`;
|
||
- `window_shift_ratio=0.1`;
|
||
- `FastClustering(auto, 0.89)`;
|
||
- `provider=cpu` для segmentation и embeddings;
|
||
- `debug=True` для обеих model configs: stage times читаются из штатного
|
||
debug-журнала и входят в измерительный рецепт;
|
||
- `min_duration_on=0.3`, `min_duration_off=0.5`;
|
||
- один worker, одна session, `intra_op_threads=8`, `inter_op_threads=1`, batch 1;
|
||
- штатный непатченный `diarizer.process(samples)`.
|
||
|
||
Старые experimental patch/wheel не загружаются и не восстанавливаются. До
|
||
первого запуска стенд проверяет версии, хеши и все effective values рецепта.
|
||
Критерий завершения: `--validate-only` показывает рецепт `W0-FRESH` и
|
||
останавливается при любом несовпадении.
|
||
|
||
Закрытые хранилища задач #41/#42 временно сохраняются до завершения и приемки
|
||
этого эксперимента как архив только для чтения. Стенд не получает их пути, не
|
||
читает старые manifest и результаты и отклоняет их ID при валидации. Архив не
|
||
является входом, базой сравнения или условием получения нового результата.
|
||
|
||
### Sortformer
|
||
|
||
Runtime:
|
||
|
||
- репозиторий [NVIDIA/NeMo-Speech.cpp](https://github.com/NVIDIA/NeMo-Speech.cpp);
|
||
- commit `4f9676226f667d14608487df744f375db87127f8`;
|
||
- license Apache-2.0;
|
||
- source build штатным CPU-only preset:
|
||
`git submodule update --init ggml`, затем `scripts/configure.sh cpu-diar` и
|
||
`cmake --build --preset cpu-diar`
|
||
([инструкция build](https://github.com/NVIDIA/NeMo-Speech.cpp/blob/4f9676226f667d14608487df744f375db87127f8/docs/build.md),
|
||
[точный preset](https://github.com/NVIDIA/NeMo-Speech.cpp/blob/4f9676226f667d14608487df744f375db87127f8/CMakePresets.json));
|
||
- preset использует Release/Ninja, включает только diarization и отключает
|
||
patched GGML; runtime запускается с `gpu=-1`, одним процессом и одним stream;
|
||
- CPU graph использует закрепленные в этом commit четыре внутренних потока;
|
||
runtime knob для их изменения отсутствует
|
||
([вызов scheduler](https://github.com/NVIDIA/NeMo-Speech.cpp/blob/4f9676226f667d14608487df744f375db87127f8/src/runtime/ggml/session.cpp#L903));
|
||
- submodule commits, compiler, CMake options и SHA-256 готового executable и
|
||
shared library сохраняются в новом manifest.
|
||
|
||
Модель:
|
||
|
||
- repo `nvidia/diar_streaming_sortformer_4spk-v2`;
|
||
- revision `5240a64075176943f677d30fa2171c780229f341`;
|
||
- файл `diar_streaming_sortformer_4spk-v2.q8_0.gguf`;
|
||
- размер `147075776` байт;
|
||
- SHA-256 `0679cfeb1ce356d0dea9470b31274f4bfc7eb927497d82005483770666da998a`;
|
||
- license CC BY 4.0.
|
||
|
||
Эти revision, размер и хеш также закреплены в
|
||
[официальном model index выбранного runtime](https://github.com/NVIDIA/NeMo-Speech.cpp/blob/4f9676226f667d14608487df744f375db87127f8/models/index.json#L75-L88).
|
||
После подготовки байтов стенд работает без сети.
|
||
|
||
Кандидат вызывается через
|
||
[standalone C ABI](https://github.com/NVIDIA/NeMo-Speech.cpp/blob/4f9676226f667d14608487df744f375db87127f8/include/nemo_speech/diar.h),
|
||
чтобы сохранить effective config и покадровую диагностику. Stateful streaming
|
||
получает точную геометрию NVIDIA в 80-мс кадрах:
|
||
|
||
| Поле | Значение |
|
||
|---|---:|
|
||
| `chunk_frames` | `340` |
|
||
| `right_context_frames` | `40` |
|
||
| `left_context_frames` | `0` |
|
||
| `fifo_frames` | `40` |
|
||
| `spkcache_frames` | `188` |
|
||
| `update_period_frames` | `300` |
|
||
|
||
Сумма chunk и right context дает input buffer latency 30,4 с. Опубликованный
|
||
RTF этой конфигурации измерен на RTX 6000 Ada и в CPU-гипотезу не переносится
|
||
([model card](https://huggingface.co/nvidia/diar_streaming_sortformer_4spk-v2#setting-up-streaming-configuration)).
|
||
Full-attention `--offline` не используется.
|
||
|
||
Post-processing передается явно при каждом запросе:
|
||
|
||
| Поле | Значение |
|
||
|---|---:|
|
||
| `onset` | `0.641` |
|
||
| `offset` | `0.561` |
|
||
| `pad_onset_sec` | `0.229` |
|
||
| `pad_offset_sec` | `0.079` |
|
||
| `min_duration_sec` | `0.511` |
|
||
| `min_gap_sec` | `0.296` |
|
||
|
||
Это callhome-tuned конфигурация закрепленного runtime. Она остается quality
|
||
риском для русских рабочих встреч и не настраивается по трем контрольным
|
||
фрагментам. Любое изменение геометрии, post-processing, quantization или
|
||
runtime commit создает другую гипотезу и требует новой спецификации.
|
||
|
||
Критерий завершения подготовки кандидата: сборка выполняет синтетический
|
||
ABI-тест без аудио, `nemo_speech_diar_num_speakers()` возвращает 4,
|
||
`nemo_speech_diar_seconds_per_frame()` возвращает 0,08, а manifest содержит все
|
||
закрепленные значения и хеши. Синтетический тест не заменяет smoke-проверку с
|
||
аудио.
|
||
|
||
## Свежая закрытая основа
|
||
|
||
### Шаг 1. Выбрать источники и фрагменты
|
||
|
||
Владелец выбирает три сохраненные исходные записи и по одной 300-секундной
|
||
области в каждой. Набор должен включать русскую рабочую речь, смену говорящего,
|
||
короткий ответ и перекрытие хотя бы где-то в совокупности. В каждой области и
|
||
каждой полной записи должно быть от двух до четырех слышимых голосов. Запись с
|
||
пятью голосами закрывает кандидат до измерения: модель выдает не более четырех
|
||
выходных голосовых кластеров
|
||
([technical limitations](https://huggingface.co/nvidia/diar_streaming_sortformer_4spk-v2#technical-limitations)).
|
||
|
||
В закрытом manifest сохраняются source hash, точные границы и recipe
|
||
mono/16 kHz float32. Публичные документы получают только число фрагментов,
|
||
длительность и агрегаты.
|
||
|
||
Критерий завершения: три source hash уникальны, три WAV имеют ровно 300 секунд
|
||
после единственного закрепленного преобразования, а ни один путь, ID или
|
||
интервал не попадает в stdout режима `--validate-only`.
|
||
|
||
### Шаг 2. Создать ASR-sidecar и опорную разметку
|
||
|
||
Действующий ASR выполняется один раз для каждой полной записи до измерений.
|
||
Sidecar хранит канонические слова с временной привязкой и нормализованный текст;
|
||
fragment-sidecar получается детерминированным срезом той же версии. Затем
|
||
человек прослушивает каждый фрагмент целиком, фиксирует ожидаемое число
|
||
голосовых кластеров для фрагментов и полных записей и создает опорную разметку,
|
||
достаточную для mapped purity и выбора мест прослушивания. Опорная разметка
|
||
остается приближенной и не объявляется истиной границ.
|
||
|
||
Критерий завершения: для каждого фрагмента и каждой полной записи есть
|
||
подтвержденное человеком `expected_cluster_count` в диапазоне `2..4`, один
|
||
неизменяемый sidecar и SHA-256; для фрагментов также есть опорная разметка.
|
||
Числа старых контролей не копируются.
|
||
|
||
### Шаг 3. Пройти пользовательский gate
|
||
|
||
До первого модельного запуска владелец подтверждает:
|
||
|
||
- право использовать выбранные записи в закрытом локальном опыте;
|
||
- пригодность трех областей и полных записей, а также
|
||
`expected_cluster_count` каждого фрагмента и каждой полной записи;
|
||
- доступность исходных полных записей для этапа после 30%-го порога на
|
||
фрагментах;
|
||
- готовность выполнить безусловный финальный слуховой gate, если медиана
|
||
достигнет 30%. Diagnostics только добавляют обязательные места прослушивания.
|
||
|
||
Критерий завершения: в закрытом manifest есть timestamped decision `approved`.
|
||
Без него исход `needs-user`; стенд не загружает модели.
|
||
|
||
## Измеряемая величина и A/B
|
||
|
||
Время W0 - wall вокруг одного `diarizer.process(samples)`. Время кандидата -
|
||
wall вокруг `stream_open -> push all prepared samples -> finish -> segment
|
||
post-processing`. Для обеих ветвей модель уже загружена, WAV уже декодирован в
|
||
один и тот же mono/16 kHz float32 buffer. ASR, скачивание, build, model load,
|
||
декодирование, чтение sidecar, запись artifacts и расчет метрик находятся вне
|
||
таймера.
|
||
|
||
Полная замена оценивается только измерением:
|
||
|
||
```text
|
||
B = measured W0 wall
|
||
A = measured Sortformer wall
|
||
speedup_percent = 100 * (B - A) / B
|
||
```
|
||
|
||
Восьмиклеточное историческое Ryzen-разложение не подставляется вместо свежего
|
||
`B`. CPU prediction для `A` отсутствует.
|
||
|
||
Каждая ячейка запускается новым дочерним процессом на одной машине, от сети, в
|
||
стабильном power mode. Process affinity разрешает те же восемь физических ядер
|
||
обеим ветвям. W0 получает `intra_op_threads=8`; закрепленный candidate runtime
|
||
исполняет CPU graph четырьмя внутренними потоками. Это часть сравниваемого
|
||
runtime recipe. Изменение hardcoded числа потоков образует новую гипотезу.
|
||
Фиксируются logical/physical cores, CPUID/ISA, RAM, ОС, compiler/runtime
|
||
versions, affinity, effective threads, power mode, governor, turbo, throttling
|
||
и swap. Прогон со сменой режима или throttling не учитывается и повторяется
|
||
один раз после стабилизации.
|
||
|
||
### Шаг 4. Принять свежий W0
|
||
|
||
Один `W0-FRESH` обрабатывает три фрагмента последовательно. Для каждого
|
||
сохраняются raw wall/CPU/RSS и stage times; публично выводится сумма. W0 должен
|
||
дать ровно подтвержденные `expected_cluster_count`, сохранить sidecar-текст и
|
||
пройти memory gate.
|
||
|
||
Критерий завершения: W0 прошел все обязательные gates и его закрытый result
|
||
имеет SHA-256. При провале итог `stop`: база сравнения непригодна.
|
||
|
||
### Шаг 5. Выполнить первичный A/B
|
||
|
||
Сразу после принятого W0 запускается одна ячейка кандидата на тех же buffers и в
|
||
том же порядке фрагментов. Primary order закреплен как `W0 -> candidate`; пять
|
||
дорогих циклов ниже устраняют постоянный порядок чередованием. Один primary
|
||
cycle равен сумме трех wall times каждой ветви.
|
||
|
||
Критерий завершения: обе ячейки имеют raw result, одинаковый список input
|
||
hashes и один pair ID; рассчитаны ускорение, CPU и RSS delta.
|
||
|
||
Решение после primary:
|
||
|
||
- `abs(speedup) <= 5%` - результат фиксируется как равенство по скорости;
|
||
- `speedup < 10%` - `stop`: причина `equal-speed` для равенства по скорости,
|
||
иначе `below-continuation`;
|
||
- `speedup >= 10%` при всех gates - перейти к пяти финальным циклам;
|
||
- любой обязательный quality/memory/Sortformer gate - немедленный `stop`.
|
||
|
||
### Шаг 6. Выполнить пять финальных циклов
|
||
|
||
Каждый цикл содержит свежую ячейку W0 и свежую ячейку кандидата на всех трех
|
||
фрагментах. Primary pair в пять финальных циклов не входит. Порядок закреплен
|
||
заранее и чередуется так:
|
||
|
||
1. final 1: `candidate -> W0`;
|
||
2. final 2: `W0 -> candidate`;
|
||
3. final 3: `candidate -> W0`;
|
||
4. final 4: `W0 -> candidate`;
|
||
5. final 5: `candidate -> W0`.
|
||
|
||
Вместе с primary `W0 -> candidate` это исключает выбор порядка по уже видимому
|
||
результату. Дополнительные варианты, модели и настройки не добавляются.
|
||
|
||
Критерий завершения: получены пять парных ускорений, их медиана и MAD; все
|
||
десять ячеек прошли gates. Медиана не ниже 30% открывает проверку полных записей.
|
||
Медиана ниже 30% дает `stop`. Дополнительные повторы не назначаются.
|
||
|
||
### Шаг 7. Проверить полные записи и пройти слуховой gate
|
||
|
||
После медианы не ниже 30% W0 и candidate однократно обрабатывают три полные
|
||
исходные записи. Скорость полных прогонов остается диагностикой и не заменяет
|
||
пятипарную медиану.
|
||
|
||
Владелец получает blinded A/B материал по пяти категориям:
|
||
|
||
- короткие ответы и перебивки;
|
||
- смены говорящего;
|
||
- перекрывающаяся речь;
|
||
- устойчивость соответствия голоса кластеру;
|
||
- слова без говорящего и малые остаточные кластеры.
|
||
|
||
Материал включает все места, отмеченные сравнительными diagnostics, и
|
||
фиксированную случайную выборку совпадающих мест. Порядок A/B и имя кандидата
|
||
раскрываются после вердикта. По каждой категории владелец записывает `pass` или
|
||
`fail` и короткую причину в закрытом хранилище; публично остается только
|
||
вердикт.
|
||
|
||
Критерий завершения: три пары на полных записях прошли автоматические gates,
|
||
все пять слуховых категорий имеют решение. Любой подтвержденный `fail` дает
|
||
`stop`; полный `pass` завершает эксперимент как `go`.
|
||
|
||
## Gates и диагностика
|
||
|
||
### Обязательное качество и корректность
|
||
|
||
Каждая ячейка W0 и кандидата обязана выполнить все условия:
|
||
|
||
- число голосовых кластеров каждого обрабатываемого контрольного объекта -
|
||
фрагмента или полной записи - точно равно свежему, подтвержденному человеком
|
||
`expected_cluster_count`;
|
||
- нормализованный текст и порядок слов побайтно совпадают с одним sidecar;
|
||
- нет потерянных, продублированных или переставленных слов;
|
||
- интервалы конечны, имеют положительную длительность, лежат в пределах audio
|
||
duration с учетом явно заданного padding и отсортированы по началу;
|
||
- каноническая нумерация меток детерминирована на повторном расчете метрик;
|
||
- все inputs, параметры и output artifacts имеют SHA-256.
|
||
|
||
Purity и число слов без говорящего сравниваются с новым `W0-FRESH`. Ухудшение
|
||
не дает автоматического `fail`, но обязательно добавляет все затронутые места в
|
||
слуховой gate. Старые абсолютные значения не участвуют.
|
||
|
||
Открытый диагностикой слуховой gate не прерывает шаги 5-6. Стенд накапливает
|
||
затронутые места и добавляет их в материал шага 7. Состояние `needs-user` из-за
|
||
прослушивания возможно только после достижения 30%-го порога и подготовки
|
||
полного blinded-материала.
|
||
|
||
### Sortformer-specific gate
|
||
|
||
Для каждой ячейки кандидата сохраняются и проверяются:
|
||
|
||
- `num_speakers=4`, `seconds_per_frame=0.08` и точная geometry/post-processing
|
||
из этой спецификации;
|
||
- `frame_count`, `frame_probs_start` и размер доступной матрицы `T x 4`;
|
||
- каждое сохраненное probability конечно и принадлежит `[0, 1]`;
|
||
- каждая итоговая speaker label принадлежит `1..4`;
|
||
- число и суммарная длительность интервалов по каждому каналу;
|
||
- число коротких интервалов, overlap ratio, unmatched words, малые/остаточные
|
||
кластеры и mapped purity относительно свежей опорной разметки;
|
||
- wall отдельно для model inference и segment post-processing.
|
||
|
||
Любой неверный shape/value/label или эффективная конфигурация, отличная от
|
||
закрепленной, дает `stop`. Появление лишнего/пропажа ожидаемого кластера дает
|
||
`stop`. Изменение channel stability, short-turn, overlap, unmatched-word или
|
||
purity diagnostics открывает слуховую проверку; числовой порог для этих proxy
|
||
не вводится.
|
||
|
||
Жесткий предел четырех выходных голосовых кластеров, риск записей с пятью и
|
||
более слышимыми голосами и риск non-English/out-of-domain сохраняются в
|
||
публичном решении даже после `go`; опыт не доказывает применимость к другим
|
||
встречам.
|
||
|
||
### Память
|
||
|
||
Peak RSS измеряется process-wide для каждой ячейки. Также сохраняются swap до и
|
||
после, page faults и RSS после каждого фрагмента.
|
||
|
||
Memory gate дает `stop` при OOM, активной подкачке во время ячейки, RSS без плато
|
||
между повторами или линейном росте RSS с числом обработанных фрагментов. Peak
|
||
RSS выше 2 ГиБ на машине с 16 ГиБ требует отдельного разбора владельца и
|
||
состояния `needs-user`; одно превышение не является автоматическим `fail`.
|
||
|
||
## Артефакты и приватность
|
||
|
||
Checkout и дерево сборки NeMo-Speech.cpp, скачанные веса и новое закрытое
|
||
хранилище размещаются вне рабочей копии local-transcriber. Обычная `.scratch`
|
||
внутри репозитория не подходит: `.gitignore` покрывает там только
|
||
`.scratch/handoffs/`. Ни один runtime/model/build artifact или закрытый вход не
|
||
попадает в Git.
|
||
|
||
Новое закрытое хранилище содержит:
|
||
|
||
- versioned manifest со schema, clean local-transcriber source commit,
|
||
source/model/runtime/build hashes и расписанием;
|
||
- исходные ссылки, подготовленные WAV и recipe преобразования;
|
||
- ASR-sidecar и опорную разметку;
|
||
- raw per-fragment results, intervals, word assignments и probability
|
||
diagnostics;
|
||
- machine/power/runtime guards и stderr/stdout logs;
|
||
- материал и решения слуховой проверки;
|
||
- байты runtime/model либо проверяемый download recipe с revision, size и hash.
|
||
|
||
Хранилище не ссылается на закрытые хранилища задач #41/#42 и остается
|
||
достаточным для разбора собственного результата. После приемки отчета задачи
|
||
#46 старые архивы можно удалить; их удаление не блокирует ни один шаг и не
|
||
лишает новый результат доказательной основы.
|
||
|
||
Публичный отчет содержит `clean local-transcriber source commit`, точные
|
||
рецепты, хеши, агрегаты, вердикты gates и итоговое решение. В Git, комментарии
|
||
Gitea, задания на ревью и вывод `--validate-only` не попадают пути,
|
||
`source/recording IDs`, текст, временные интервалы, сырые строки, содержимое
|
||
manifest, логи, имена участников или аудио.
|
||
|
||
При нарушении приватности стенд останавливается до публикации. Уже
|
||
сформированный публичный артефакт удаляется из рабочей ветки и создается заново
|
||
из безопасных агрегатов.
|
||
|
||
## Реализация стенда и тесты
|
||
|
||
Новый стенд реализуется отдельным модулем
|
||
`src/local_transcriber/streaming_sortformer_experiment.py`; существующий
|
||
`benchmark_experiment.py` остается источником общих проверенных механизмов.
|
||
Стенд получает отдельную schema и только две recipe: `W0-FRESH` и
|
||
`SORTFORMER-V2-Q8`. Manifest validation выполняется до загрузки моделей.
|
||
`--validate-only` проверяет:
|
||
|
||
- три уникальных source hashes, 300-секундные фрагменты и fresh-basis marker;
|
||
- human approval и `expected_cluster_count` каждого фрагмента и каждой полной
|
||
записи в диапазоне `2..4`;
|
||
- W0 model/runtime hashes и effective values: CPU provider, `debug=True`,
|
||
`min_duration_on=0.3`, `min_duration_off=0.5`, threads и clustering;
|
||
- clean local-transcriber worktree и source commit из manifest;
|
||
- Sortformer revision, size, SHA-256, runtime commit, submodules, build hashes,
|
||
CPU backend, geometry и post-processing;
|
||
- одинаковые input/sidecar hashes обеих ветвей;
|
||
- заранее созданные primary/final schedules;
|
||
- отсутствие корней старых хранилищ и legacy ID ячеек.
|
||
|
||
Минимальные детерминированные тесты выполняются без модели и аудио:
|
||
|
||
```bash
|
||
uv run pytest -q \
|
||
tests/test_streaming_sortformer_experiment.py \
|
||
tests/test_benchmark_experiment.py \
|
||
tests/test_speaker_diarizer.py \
|
||
tests/test_diarization.py
|
||
```
|
||
|
||
Новый модуль тестов покрывает отклонение `schema`, несовпадение пинов и хешей,
|
||
требование свежей опоры, обезличенный вывод `--validate-only`, правило
|
||
`expected_cluster_count`, точное расписание A/B, границы 5%/10%/30%, gates
|
||
качества, памяти и Sortformer, переход к прослушиванию, безопасную сериализацию
|
||
и остановку при первом отказе. Стенд C ABI имеет отдельный синтетический тест
|
||
размеров структур, выбора CPU и эффективной конфигурации; модель в модульных
|
||
тестах не загружается.
|
||
|
||
Критерий завершения реализации стенда: все детерминированные тесты проходят,
|
||
`--validate-only` проходит на закрытом manifest и публичный вывод проходит
|
||
privacy scan. Реализация стенда фиксируется чистым коммитом исходников,
|
||
совпадающим с manifest. Только после этого разрешен первый запуск модели W0.
|
||
|
||
## Критерии завершения эксперимента
|
||
|
||
- [ ] Свежая опора подтверждена владельцем и не наследует старые контрольные
|
||
значения.
|
||
- [ ] Runtime, модель, сборка и оба рецепта закреплены и проверены до моделей.
|
||
- [ ] Clean local-transcriber source commit сохранен в закрытом хранилище и
|
||
публичном отчете, а `--validate-only` проверяет его до моделей.
|
||
- [ ] Свежий W0 принят либо итог `stop` объясняет провал базы сравнения.
|
||
- [ ] Первичный A/B завершен; решение 10% применено без дополнительных ветвей.
|
||
- [ ] При продолжении завершены ровно пять финальных циклов, медиана и MAD.
|
||
- [ ] Каждая выполненная ячейка имеет вердикты качества, текста, памяти и
|
||
Sortformer.
|
||
- [ ] При медиане 30% выполнены три пары на полных записях и пять слуховых
|
||
решений.
|
||
- [ ] Все остановленные шаги имеют первую точную причину и хеш результата.
|
||
- [ ] Закрытое хранилище самодостаточно; публичный отчет содержит только безопасные
|
||
агрегаты и SHA-256.
|
||
- [ ] Итог равен `go` или `stop`; `needs-user` остается открытым до решения.
|
||
- [ ] Ни один результат автоматически не меняет production-зависимости,
|
||
профиль, CLI, config или модель по умолчанию.
|