- Зачем: - локальные оптимизации каскада не достигли 30%-го порога ускорения; - старые закрытые хранилища и капсулы не должны стать будущей зависимостью. - Что: - собран единый реестр результатов задач #41 и #42; - опубликовано разложение Ryzen baseline и выбрана архитектурная гипотеза; - подготовлены самодостаточная спецификация и ready-for-agent задача #46. - Проверка: - пройдены self-review и две независимые линии ревью; - проверены 44 строки реестра, 42 арифметических инварианта и ссылки; - выполнены ai-text-lint, privacy scan и post-lint guard. Refs: #45
32 KiB
Эксперимент Streaming Sortformer v2 Q8_0 на Ryzen CPU
Дата: 2026-09-01
Статус: готовый контракт следующего эксперимента, исполняемый в задаче #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 и модель по
умолчанию сохраняются.
Сначала прочитать
- Прочитать карту архитектур,
особенно разделы "Граница выводов", A1 и формулу полной замены.
Критерий завершения: агент может объяснить, почему старые значения
3 / 2 / 2, unmatched words и purity не являются порогами нового опыта. - Прочитать ADR-007 и старую спецификацию. Критерий завершения: сохранены каноническое сведение слов, правила 5%/10%/30% и memory gate.
- Прочитать
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;
- автоматическое включение - это задача #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 и
останавливается при любом несовпадении.
Sortformer
Runtime:
- репозиторий 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, точный preset); - preset использует Release/Ninja, включает только diarization и отключает
patched GGML; runtime запускается с
gpu=-1, одним процессом и одним stream; - CPU graph использует закрепленные в этом commit четыре внутренних потока; runtime knob для их изменения отсутствует (вызов scheduler);
- 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. После подготовки байтов стенд работает без сети.
Кандидат вызывается через standalone C ABI, чтобы сохранить 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).
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).
В закрытом 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 и расчет метрик находятся вне
таймера.
Полная замена оценивается только измерением:
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 в пять финальных циклов не входит. Порядок закреплен заранее и чередуется так:
- final 1:
candidate -> W0; - final 2:
W0 -> candidate; - final 3:
candidate -> W0; - final 4:
W0 -> candidate; - 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 и остается достаточным для разбора собственного результата. Удаление старых хранилищ не блокирует ни один шаг.
Публичный отчет содержит 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 ячеек.
Минимальные детерминированные тесты выполняются без модели и аудио:
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 или модель по умолчанию.