Repository navigation
[BUGFIX] Add activation output to residual when layer1x1 is inactive - #343
Open
andrenepomuceno wants to merge 1 commit into
Open
andrenepomuceno wants to merge 1 commit into
andrenepomuceno wants to merge 1 commit into
Conversation
With layer1x1 inactive, the trainer computes residual = x + post_activation, but the layer passed the input through unchanged. Add the activation output (top bottleneck rows of _z) to the input in both the Eigen and inline GEMM paths. Fixes sdatkinson#341
dfernandes83
added a commit
to dfernandes83/NeuralAmpModelerCore
that referenced
this pull request
Oct 6, 2026
…s() larger than max buffer), sdatkinson#343 (keep activation in the residual when layer1x1 is inactive), sdatkinson#344 (honor Hardtanh min_val/max_val)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #341
Problem
When
layer1x1is inactive,detail::Layer::Processcopied the layer input straight to_output_next_layer, so the residual path dropped the layer's activation. The trainer computesresidual = x + layer_output, andlayer_outputispost_activationwhen there is nolayer1x1(nam/models/wavenet/_layer_array.py,Layer.forward, ~L755–773 in neural-amp-modeler 0.13). Any model exported with"layer1x1": {"active": false}therefore rendered differently from the trainer.Change
NAM/wavenet/model.cpp: withlayer1x1inactive,_output_next_layer = input + _z.topRows(bottleneck). This is the activation output after the optionalactivation_post_film, the same tensor the trainer adds.NAM_USE_INLINE_GEMMpath: thememcpybecomes a per-column add. It uses_z's real row stride because_zhas2 * bottleneckrows under gated/blended activations.NAM/wavenet/detail.h: updated the doc comments that described the old identity residual.No new validation is needed.
detail::Layeralready throws whenlayer1x1is inactive andbottleneck != channels, andtest_layer1x1_inactive_bottleneck_mismatchcovers that. Neither the A2 fast path (it requires an activelayer1x1) nor the slimmable WaveNet (it reusesdetail::Layer) needs changes.Tests
test_layer1x1_inactive: the expected value now comes from the trainer's formula (1 + ReLU(2) = 3). It used to be1, which asserted the bug.test_layer1x1_inactive_gated: a gated layer with per-frame values and constant gates. It checks that only the gated (top) rows of_zreach the residual, which exercises the strided copy in the inline GEMM build.test_layer1x1_inactive_full_model: a two-layer, single-channel WaveNet loaded throughget_dspfrom a JSON config withlayer1x1inactive. The expected output is computed in the test fromx1 = x + ReLU(a0*x + b0 + m0*x), so the second layer (and the output) depend on the residual.Cross-check: a randomly initialised neural-amp-modeler 0.13 WaveNet with inactive
layer1x1and a bounded Hardtanh, rendered withtools/render, matches the PyTorch forward pass (pad_start=True, 4096 samples) to a max abs error of 7.5e-8 with this PR and #342 (Hardtanh bounds) applied, down from 1.9e-3 onmain.run_testspasses in both the default and-DNAM_USE_INLINE_GEMMbuilds (g++ 11, Linux). Before the fix, the updatedtest_layer1x1_inactivefails in both builds.Note
This changes the output of existing
.namfiles that havelayer1x1inactive: they now match the trainer, but the difference can be audible for models that were only ever heard through the core.