A2 loads and runs, and a lot about it is genuinely well tested. What does not exist is any executable comparison of A2 against the reference implementation — the thing FR-NAM-030 names and FR-NAM-150 imports by reference. This is also, specifically, the dimension risk R-9 was raised to protect.
1. No A2 golden exists
crates/namir-nam/tests/golden/ holds exactly five files: input_10s.wav, wavenet_nano.nam + its reference render, and lstm_tiny.nam + its reference render. There is no A2 model and no A2 reference render. wavenet_nano.nam is a pure A1 file — scalar kernel_size: 3, activation: "Tanh", no bottleneck, no kernel_sizes, no nested head, no layer1x1.
2. The comment saying an A2 golden isn't needed is wrong on the facts
crates/namir-nam/tests/golden_reference.rs justifies its plain // trace: FR-NAM-030 with:
"D-9.12 keeps A2 inside the 'WaveNet' architecture this test already covers, so a third variant isn't needed"
D-9.12 is a dispatch decision — A2 files declare architecture: "WaveNet" — not a claim of shared execution. wavenet.rs's own module doc says A2's structural additions are "provably inert when the file is A1", which is precisely why an A1 golden cannot reach them. Each is a distinct branch the A1 fixture takes the other side of:
| A2-only path |
Branch |
What the A1 golden does instead |
per-layer kernel_sizes |
wavenet.rs:1124 |
scalar kernel_size |
bottleneck ≠ channels |
wavenet.rs:1138 |
absent → defaults to channels |
nested convolutional head |
wavenet.rs:1152 |
kernel=1, dilation=1 |
| per-layer activation array |
wavenet.rs:1167 |
single ActivationSpec::One |
LeakyReLU/SiLU/Hardswish/Softsign/LeakyHardtanh/PReLU |
wavenet.rs:139-158 |
Tanh only — 6 of 10 variants unreached |
k-tap causal head conv (head_history/head_padded) |
wavenet.rs:683-712 |
hits the hl == 0 fast path; head_padded allocated zero-length |
Real A2 is one 23-layer array, kernel sizes 6/15, dilations to 239, LeakyReLU, head kernel 16. The golden pair reaches none of it.
Stated fairly: A1's dilated conv does exercise the general Conv1D path at kernel 3, and A2's head reuses that same code, so the [out][in][k] tap-flatten convention is validated against the real reference for the dilated conv at least.
3. A2's only executable parity is Rust-vs-Rust
a2_fixtures.rs compares namir-nam against namir_fixtures::nam::reference_infer_a2 — a second in-house port. That is the exact position the A1 and LSTM parity tests were in before golden_reference.rs existed, and it is why M9a demoted both to trace-partial:.
Two aggravating details:
- The probe is
deterministic_signal(99, 4_000) — the same ~83 ms sine+noise the demoted tests used, not FR-NAM-030's specified 10-second clean/transient/saturated signal that FR-NAM-150's "to the accuracy of FR-NAM-030" clause imports.
- The real A2 shape's receptive field is roughly 6 300 samples — longer than the 4 000-sample probe. The comparison never leaves the zero-padded startup transient.
4. R-9's retirement rests on a figure nobody can reproduce, and two documents disagree about it
R-9 is recorded retired partly on "a real NeuralAmpModelerCore render … measured A2 Full/A2 Lite at −90.31 dB each". There is no committed A2 fixture, no reference WAV, no xtask subcommand that can write an A2 fixture for a reference render, and no manual-test write-up (LSTM has one; A2 has none).
And the two records contradict each other. The roadmap attributes −90.31/−90.31 to A2 Full and A2 Lite. golden_reference.rs restates the same measurement — same −90.3 to −90.9 range, same "flat regardless of signal length or complexity", same non-associativity explanation — as "a much larger WaveNet shape (Standard, ten times the layers)", with A2 unmentioned. WaveNetShape::Standard is an A1-only enum; A2 shapes are A2Shape::Full/Lite. Two structurally different models (channels 8 vs 3, different weights, different layer counts) also would not normally report the same figure to two decimal places.
R-9's specific concern was a silently-wrong flat weight order. Bit-exact agreement between two in-house ports proves they derived the same order, not the right one — so the other two legs cannot substitute for the one that is unverifiable.
5. No genuine A2 export has ever been loaded, and both sides share one reading of the C++
Every A2 test input is namir_fixtures::nam::generate_a2. Both the generator and the parser derive the A2 JSON schema from the same reading of the same upstream C++. A shared misreading would be bit-exactly invisible to every test in the tree. Two amplifiers:
- Every A2 field in
file.rs is #[serde(default)] Option<_> and the struct is not deny_unknown_fields, so a real file carrying a feature under an unanticipated key is silently ignored rather than rejected — which undercuts FR-NAM-140's guarantee for real files even though its test is honest.
head_dilation absent defaults to 1, so a name mismatch there is silent and wrong rather than loud.
AGENTS.md warns about exactly this class, citing the post-M6 null-vs-omitted parser bug. Note the generated A2 fixtures never emit loudness, input_level_dbu or output_level_dbu — the very A2-era metadata D-9.12 cited as the reason A2 unblocks FR-NAM-090.
6. Real A2 models in the wild take a path Namir has never been compared against
NAM_ENABLE_A2_FAST defaults to ON upstream, and its is_a2_shape detector matches exactly the two shapes FR-NAM-150 names. So a host built with upstream defaults runs a real A2-standard/A2-nano model through a2_fast.cpp, while the one (unrecorded) Namir comparison used -DNAM_ENABLE_A2_FAST=OFF — the generic path.
Choosing the generic path as the definitional reference is defensible. But the choice has no recorded rationale: golden_reference.rs attributes the flag to "D-9.12's PR #264 consequence note", and that note discusses only NAM_USE_INLINE_GEMM and never mentions A2-Fast.
What would close this
- A committed A2 golden — a generated A2-Full (and ideally A2-Lite) fixture rendered through the pinned
NeuralAmpModelerCore build over the same input_10s.wav, asserted in-process beside the two existing golden tests. This is the single most important missing thing, and it is the same shape of work golden_reference.rs already did twice.
- One recorded run against a genuine A2 export, written up the way
docs/manual-tests/fr-nam-020-real-lstm-models.md was for LSTM.
- Record why the fast path is excluded, or compare against it.
- Until (1) exists, R-9 should not read as retired.
Related: the probe for (1) should be long enough to clear A2's ~6 300-sample receptive field.
A2 loads and runs, and a lot about it is genuinely well tested. What does not exist is any executable comparison of A2 against the reference implementation — the thing FR-NAM-030 names and FR-NAM-150 imports by reference. This is also, specifically, the dimension risk R-9 was raised to protect.
1. No A2 golden exists
crates/namir-nam/tests/golden/holds exactly five files:input_10s.wav,wavenet_nano.nam+ its reference render, andlstm_tiny.nam+ its reference render. There is no A2 model and no A2 reference render.wavenet_nano.namis a pure A1 file — scalarkernel_size: 3,activation: "Tanh", nobottleneck, nokernel_sizes, no nestedhead, nolayer1x1.2. The comment saying an A2 golden isn't needed is wrong on the facts
crates/namir-nam/tests/golden_reference.rsjustifies its plain// trace: FR-NAM-030with:D-9.12 is a dispatch decision — A2 files declare
architecture: "WaveNet"— not a claim of shared execution.wavenet.rs's own module doc says A2's structural additions are "provably inert when the file is A1", which is precisely why an A1 golden cannot reach them. Each is a distinct branch the A1 fixture takes the other side of:kernel_sizeswavenet.rs:1124kernel_sizebottleneck≠channelswavenet.rs:1138channelsheadwavenet.rs:1152kernel=1, dilation=1wavenet.rs:1167ActivationSpec::OneLeakyReLU/SiLU/Hardswish/Softsign/LeakyHardtanh/PReLUwavenet.rs:139-158Tanhonly — 6 of 10 variants unreachedhead_history/head_padded)wavenet.rs:683-712hl == 0fast path;head_paddedallocated zero-lengthReal A2 is one 23-layer array, kernel sizes 6/15, dilations to 239,
LeakyReLU, head kernel 16. The golden pair reaches none of it.Stated fairly: A1's dilated conv does exercise the general
Conv1Dpath at kernel 3, and A2's head reuses that same code, so the[out][in][k]tap-flatten convention is validated against the real reference for the dilated conv at least.3. A2's only executable parity is Rust-vs-Rust
a2_fixtures.rscomparesnamir-namagainstnamir_fixtures::nam::reference_infer_a2— a second in-house port. That is the exact position the A1 and LSTM parity tests were in beforegolden_reference.rsexisted, and it is why M9a demoted both totrace-partial:.Two aggravating details:
deterministic_signal(99, 4_000)— the same ~83 ms sine+noise the demoted tests used, not FR-NAM-030's specified 10-second clean/transient/saturated signal that FR-NAM-150's "to the accuracy of FR-NAM-030" clause imports.4. R-9's retirement rests on a figure nobody can reproduce, and two documents disagree about it
R-9 is recorded retired partly on "a real
NeuralAmpModelerCorerender … measured A2 Full/A2 Lite at −90.31 dB each". There is no committed A2 fixture, no reference WAV, noxtasksubcommand that can write an A2 fixture for a reference render, and no manual-test write-up (LSTM has one; A2 has none).And the two records contradict each other. The roadmap attributes −90.31/−90.31 to A2 Full and A2 Lite.
golden_reference.rsrestates the same measurement — same −90.3 to −90.9 range, same "flat regardless of signal length or complexity", same non-associativity explanation — as "a much larger WaveNet shape (Standard, ten times the layers)", with A2 unmentioned.WaveNetShape::Standardis an A1-only enum; A2 shapes areA2Shape::Full/Lite. Two structurally different models (channels 8 vs 3, different weights, different layer counts) also would not normally report the same figure to two decimal places.R-9's specific concern was a silently-wrong flat weight order. Bit-exact agreement between two in-house ports proves they derived the same order, not the right one — so the other two legs cannot substitute for the one that is unverifiable.
5. No genuine A2 export has ever been loaded, and both sides share one reading of the C++
Every A2 test input is
namir_fixtures::nam::generate_a2. Both the generator and the parser derive the A2 JSON schema from the same reading of the same upstream C++. A shared misreading would be bit-exactly invisible to every test in the tree. Two amplifiers:file.rsis#[serde(default)] Option<_>and the struct is notdeny_unknown_fields, so a real file carrying a feature under an unanticipated key is silently ignored rather than rejected — which undercuts FR-NAM-140's guarantee for real files even though its test is honest.head_dilationabsent defaults to 1, so a name mismatch there is silent and wrong rather than loud.AGENTS.md warns about exactly this class, citing the post-M6
null-vs-omitted parser bug. Note the generated A2 fixtures never emitloudness,input_level_dbuoroutput_level_dbu— the very A2-era metadata D-9.12 cited as the reason A2 unblocks FR-NAM-090.6. Real A2 models in the wild take a path Namir has never been compared against
NAM_ENABLE_A2_FASTdefaults to ON upstream, and itsis_a2_shapedetector matches exactly the two shapes FR-NAM-150 names. So a host built with upstream defaults runs a real A2-standard/A2-nano model througha2_fast.cpp, while the one (unrecorded) Namir comparison used-DNAM_ENABLE_A2_FAST=OFF— the generic path.Choosing the generic path as the definitional reference is defensible. But the choice has no recorded rationale:
golden_reference.rsattributes the flag to "D-9.12's PR #264 consequence note", and that note discusses onlyNAM_USE_INLINE_GEMMand never mentions A2-Fast.What would close this
NeuralAmpModelerCorebuild over the sameinput_10s.wav, asserted in-process beside the two existing golden tests. This is the single most important missing thing, and it is the same shape of workgolden_reference.rsalready did twice.docs/manual-tests/fr-nam-020-real-lstm-models.mdwas for LSTM.Related: the probe for (1) should be long enough to clear A2's ~6 300-sample receptive field.