Files
local-transcriber/docs/specs/2026-09-01-streaming-sortformer-cpu-experiment.md
T
Dmitry Dementiev 15f88b1de8 docs(diarization): отложено удаление архивов
- Зачем:
  - старые результаты решено сохранить до завершения и приемки задачи #46.
- Что:
  - архивы задач #41/#42 закреплены как временные и доступные только для чтения;
  - стенду #46 запрещено читать архивы или использовать их как основу A/B;
  - удаление перенесено на момент после приемки нового отчета.
- Проверка:
  - выполнены git diff --check и проверка формулировки в задаче #46.
2026-09-01 16:36:11 +03:00

33 KiB
Raw Blame History

Эксперимент 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 и модель по умолчанию сохраняются.

Сначала прочитать

  1. Прочитать карту архитектур, особенно разделы "Граница выводов", A1 и формулу полной замены. Критерий завершения: агент может объяснить, почему старые значения 3 / 2 / 2, unmatched words и purity не являются порогами нового опыта.
  2. Прочитать ADR-007 и старую спецификацию. Критерий завершения: сохранены каноническое сведение слов, правила 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;
  • автоматическое включение - это задача #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;
  • 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 в пять финальных циклов не входит. Порядок закреплен заранее и чередуется так:

  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 ячеек.

Минимальные детерминированные тесты выполняются без модели и аудио:

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 или модель по умолчанию.