Skip to content

Add tap::dsp::nn: dense and GRU inference kernels - #15

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

Add tap::dsp::nn: dense and GRU inference kernels#15
tap merged 1 commit into
mainfrom
claude/mutap-wake-word-plan-2i63pe

Conversation

@tap

@tap tap commented Sep 5, 2026

Copy link
Copy Markdown
Owner

What this changes

The seventh primitive, include/tap/dsp/nn.h: basic_dense<Sample> (y = act(W x + b), row-major [out x in], linear / tanh / sigmoid) and basic_gru<Sample> (Cho et al. 2014 in PyTorch's nn.GRU convention, gates r, z, n), lifted from MuTap's learned residual suppressor at its wake-word plan's milestone M3. Typed battery in tests/test_nn.cpp, README section, primitive count in CLAUDE.md.

Why

The keyword spotter (MuTap's mutap.wake~) is a different head on the same layers the suppressor already runs, and the audit of the plan asked for those layers to be promoted into DspTap after M2 gave them oracles in both profiles, so that "no behaviour change" is testable. That is now the case: MuTap's refactor onto these kernels (its companion PR) is bit-identical in both profiles on the shipping v2 model, its Python parity job passes at the same depths, and its instruction-count ratchet moved by −0.1 to −0.2 % on m55 and m33.

The contract is stated as numbers in the header: float32 weight storage converted to Sample at the point of use (so the double profile computes what a float64 numpy reference over the same model file computes), bias-first ascending-j accumulation in Sample with no double arithmetic in the float profile, the GRU formula and gate order, zero state after construction and reset(), and the per-step cost. Weight vectors are moved in and owned, so a copied layer is a deep copy that stays valid; apply() / step() are noexcept and allocation-free. Fixed-point profiles are deliberately not provided: a Q-format design for these layers is a documented per-primitive decision, not a template instantiation.

Verification

Built and run here on Linux (GCC 13, Release, -DTAP_DSP_WERROR=ON): 156 tests, all passing. clang-tidy clean the way CI runs it.

Pinned by tests/test_nn.cpp, measured on this branch:

Check Measured Pinned
GRU vs an independent long-double restatement that sums in the opposite order, 50 steps double 2.2e-16, float 1.0e-7 1e-14, 1e-6
float vs double at the suppressor geometry (56 → 64 tanh → GRU 96 → 26 sigmoid, 200 steps), state / gains 6.3e-7 / 2.5e-7 3e-6 / 1e-6

Also pinned: the row-major layout on hand-computed numbers, the three activation forms, the gate order by isolating each block of the 3*hidden axis through its biases, reset and deep-copy semantics, and noexcept on the processing path.

Not run here, so CI is the first real gate for: Windows MSVC, macOS AppleClang, the Cortex-M55 compile leg, and the drift check. The end-to-end oracle is MuTap's: its suppressor tests, Python parity job and ratchet on the refactored header, in the companion PR.

Notes for the reviewer

  • Contract change. None to existing primitives; one new contract (tap::dsp::nn::k_contract_version 1).
  • Submodule pin moved. No. MuTap's companion PR pins this branch's commit and is repointed at the identical tree on main after this merges by rebase/squash.
  • Notebooks re-executed. Not applicable; no notebook measures these, and the capi / bridge are unchanged.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JuUg1ZBxm3fyBqWgQv6H1G


Generated by Claude Code

The seventh primitive, lifted from MuTap's learned residual suppressor at
its wake-word plan's M3 so the keyword spotter can be a different head on
the same arithmetic. basic_dense<Sample> (y = act(W x + b), row-major
[out x in], linear / tanh / sigmoid) and basic_gru<Sample> (Cho et al.
2014 in PyTorch's nn.GRU convention, gates r, z, n), with the contract as
numbers: float32 weight storage converted to Sample at the point of use,
bias-first ascending-j accumulation in Sample (no double arithmetic in the
float profile), zero state after construction and reset(), moved-in and
owned weights so a copied layer stays valid, noexcept and allocation-free
processing. The loop order is exactly the suppressor's, so MuTap's
refactor onto these kernels is bit-identical.

tests/test_nn.cpp pins the layout on hand-computed numbers, the activation
forms, the gate order through each block's biases, the GRU formula against
an independent long-double restatement that sums in the opposite order
(measured: double 2.2e-16, float 1.0e-7), reset and deep-copy semantics,
noexcept, and float-tracks-double at the suppressor's geometry (measured
state 6.3e-7, gains 2.5e-7). 156 tests green with -DTAP_DSP_WERROR=ON;
clang-tidy clean. README section and the primitive count in CLAUDE.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JuUg1ZBxm3fyBqWgQv6H1G
tap pushed a commit to tap/MuTap that referenced this pull request Sep 5, 2026
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
tap merged commit 58210ae into main Sep 5, 2026
12 checks passed
tap pushed a commit to tap/MuTap that referenced this pull request Sep 5, 2026
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 added a commit to tap/MuTap that referenced this pull request Sep 5, 2026
…h accumulates in Sample (#46)

* M3: nn_suppressor on tap::dsp::nn; pitch search accumulates in Sample

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

* Repoint the DspTap pin at the merged tap::dsp::nn tree on main

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
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