Skip to content

M3: nn_suppressor on tap::dsp::nn; the speech predictor's pitch search accumulates in Sample - #46

Merged
tap merged 2 commits into
mainfrom
claude/mutap-wake-word-plan-2i63pe
Sep 5, 2026
Merged

M3: nn_suppressor on tap::dsp::nn; the speech predictor's pitch search accumulates in Sample#46
tap merged 2 commits into
mainfrom
claude/mutap-wake-word-plan-2i63pe

Conversation

@tap

@tap tap commented Sep 5, 2026

Copy link
Copy Markdown
Owner

What this changes

Two things, one commit.

M3 of the wake-word plan. The dense and GRU arithmetic now lives in DspTap (tap::dsp::nn, tap/DspTap#15) and nn_suppressor consumes basic_dense / basic_gru; its weight arrays are moved into the kernels at construction, so there is one copy in memory. submodules/dsptap points at the M3 tree.

Follow-up to the M2 finding on the Cortex-M33 leg. The speech predictor's pitch search (lpc.h) accumulated its normalized correlation in double for every lag from 32 to 400 over a 1024-sample window, about a million software double operations per 64-sample block on a core without FP64. It now accumulates in Sample. The M33 leg's MUTAP_ON_TARGET_SOFT_FP64 exclusion is removed: it runs exactly the M55's float selection.

Why

The audit asked for the learned kernels to be promoted only after M2 had given them oracles in both profiles, so that "no behaviour change" is a measurement rather than a claim. It is: see below.

The pitch-search change is the root cause of the M2 finding. An experiment separated the two suspects: with the test harness's deliberate double room simulation switched to float the tonal PEM headline still took 772 s of its 1085 s on the M33 emulator, so the harness was a third of the cost and the library two thirds. Fixing the library rather than the tests keeps the harness as the double oracle it was designed to be and restores the four PEM scenarios to the M33 leg. It also matters beyond the wake word: on the RP2350 class of core the shipping float-profile PEM canceller was spending most of its cycles in software double.

Verification

  • Bit-identical promotion. nn_infer built from main and from this branch, driven by the same 200-block signal through the shipping suppressor_v2_48k.munn: outputs identical to the bit in both profiles (max diff 0.0).
  • Parity. All six test_parity.py cases pass at the M2 depths (double ≤ 2.9e-8, float ≤ 2.9e-7, pinned 1e-6).
  • Ratchet, local, same toolchain/QEMU as CI: nn_suppressor_16k / _48k moved −0.12 % / −0.10 % on m55 and −0.19 % / −0.11 % on m33; every other scenario unchanged to the instruction (+0.00 %). Baselines left as they are; the drift gate holds.
  • Pitch search. The double profile is bit-for-bit unchanged by construction (same operations in the same order). The float rows, measured before and after: kalman-loop tonal ASG +7.81 dB → +7.81; PEM tonal ASG +10.84 → +11.13 (the MSG bisection's quantum is 0.5 dB); burst gated RMS 26.2 → 26.2; both aec_test rows' ERLE and misalignment identical to three decimals.
  • Suites on the final tree: host 197/197; Cortex-M55 emulated selection 52/52; Cortex-M33 emulated selection 52/52 in 175 s (the tonal PEM headline 31 s, down from 1085 s; the full selection previously timed out at 1800 s).
  • clang-format and clang-tidy clean on the changed files. CI is the first real gate for Windows, macOS, ASan/UBSan, Hexagon, and the M33 job at its new size.

Notes for the reviewer

  • Contract change. speech_predictor::analyze()'s pitch search accumulates in Sample; the float profile's lag/beta decisions can differ from before at the ~1e-6 relative level of a float normalized correlation, which is below the voicing decision's resolution and did not move any pinned number by more than the bisection quantum. The double profile is unchanged. nn_suppressor's config::weights arrays are moved out at construction (documented in the config).
  • Submodule pin moved. submodules/dsptap → the M3 tree on DspTap's claude/mutap-wake-word-plan-2i63pe (4e9a29a). After Add tap::dsp::nn: dense and GRU inference kernels DspTap#15 merges by rebase/squash, the pin is repointed at the identical tree on DspTap main; MuTap-Max's MuTap pin follows once this merges.
  • Documented exception kept. bench/icount baselines are untouched because the drift is inside the ±3 % gate; re-seeding is for real cost changes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JuUg1ZBxm3fyBqWgQv6H1G


Generated by Claude Code

The wake-word plan's M3: the dense and GRU arithmetic now lives in DspTap
(tap::dsp::nn, tap/DspTap#15) and nn_suppressor consumes basic_dense and
basic_gru. The weight arrays are moved into the kernels at construction
(one copy in memory). The kernels' loop order is exactly the one this
header carried, so the promotion is bit-identical in both profiles on the
shipping v2 model against the pre-refactor binary; the six parity cases
pass at the M2 depths, the M2 battery passes unchanged, and the
nn_suppressor ratchet moved -0.12 % / -0.10 % on m55 and -0.19 % / -0.11 %
on m33 (16 k / 48 k) with every other scenario unchanged to the
instruction. submodules/dsptap points at the M3 tree; repoint at the
identical tree on main once the DspTap PR merges.

Follow-up to the M2 finding on the Cortex-M33 leg: the speech predictor's
pitch search (lpc.h) accumulated its normalized correlation in double for
every lag from 32 to 400 over a 1024-sample window — about a million
software double operations per 64-sample block on a core without FP64,
and two thirds of the tonal PEM headline's 1085 s under qemu mps2-an505
(the test harness's deliberate double room simulation was the other
third: 772 s with it switched to float). The search now accumulates in
Sample. The double profile is bit-for-bit unchanged; the float rows'
measured numbers are unchanged to the MSG bisection's 0.5 dB quantum
(kalman-loop tonal ASG +7.81 dB before and after, PEM tonal +10.84 ->
+11.13, ERLE and misalignment identical). The tonal headline now takes
31 s on the M33 emulator and the full float selection 174 s, so the
MUTAP_ON_TARGET_SOFT_FP64 exclusion is removed: the M33 leg runs exactly
the M55's selection.

docs/wake-word-plan.md carries the M3 record and the corrected M2 finding;
HANDOFF.md points at M4.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JuUg1ZBxm3fyBqWgQv6H1G
tap/DspTap#15 merged by squash; this is the identical tree, reachable after
branch cleanup.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JuUg1ZBxm3fyBqWgQv6H1G
@tap
tap merged commit 96b4572 into main Sep 5, 2026
32 checks passed
tap pushed a commit to tap/MuTap-Max that referenced this pull request Sep 5, 2026
tap/MuTap#46 (96b4572), which carries DspTap 58210ae (tap/DspTap#15) as
its nested pin. The learned post engine mutap.aec~ instantiates is
bit-identical on the shipping v2 model in both profiles; the speech
predictor's pitch search now accumulates in Sample (double profile
unchanged). No source change in the externals.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JuUg1ZBxm3fyBqWgQv6H1G
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants