Repository navigation
fix: decompose Falcon-H1's parallel Mamba output and inline multipliers - #2002
flyingdoubleG wants to merge 3 commits into
Conversation
Falcon-H1 adds a Mamba-2 output beside attention in every block and scales
the attention, Mamba, MLP-down and embedding outputs inline. The residual
decompositions skipped the Mamba output and none of the multipliers, so
their sums missed the residual stream.
- ScaledResidualBlockBridge takes mlp_contribution_scale and
mixer_contribution_scales; hook_mlp_out uses the MLP scale.
- ActivationCache adds mixers that run beside attention as {l}_mamba_out
rows, and scales neuron results and b_out by the MLP's
_neuron_contribution_scale.
- The Falcon-H1 adapter scales hook_attn_out by attention_out_multiplier,
the Mamba rows by ssm_out_multiplier, neurons by mlp_multipliers[1] and
hook_embed by embedding_multiplier.
jlarson4
left a comment
There was a problem hiding this comment.
This PR is approved! Once v4.4.0 goes live, this will be merged into dev for inclusion in the next release
SBrouck
left a comment
There was a problem hiding this comment.
Thanks for this! I wrote #1987, and you're right: it assumed a block writes through either attention or a sequence mixer, so Falcon-H1's parallel Mamba output was dropped. Listing the mixers that sit beside attention, each with its own scale, is the right fix.
Checked locally at 0f586cd (CPU, fp32) with an independent tiny Falcon-H1: 4 layers, batch 2, all biases on, and every multiplier different from 1 and from the ones in the test.
- Logits match HF exactly.
decompose_resid()(andlayer=lagainst eachresid_pre) andget_full_resid_decomposition()with and withoutexpand_neurons: 2e-7 to 4e-7, against 0.27 / 0.53 ondev.stack_head_results()plusb_O * attention_out_multiplierequals the sum ofhook_attn_out(3e-7).stack_neuron_results()plusb_out * down_multiplierequals the sum ofhook_mlp_out(5e-7).- Doubling
blocks.0.hook_attn_outmoveshook_resid_midby exactlyhook_attn_out(2e-7), so the scaled hook writes in residual units. - Emptying
mixer_contribution_scalesbrings the error back to 0.37, so the test would catch a regression.
Since _mixer_out_names now lists other variant slots on blocks that have attention, I also checked the other adapters that declare attn plus another variant slot. On Bamba, Jamba and OLMo-Hybrid the layers alternate, so no block has both, and on tiny random models the decompositions and labels are identical to dev. GraniteMoeHybrid is in the test file. The existing decomposition, Granite, parallel-residual and ActivationCache tests pass unchanged.
One small, non-blocking thought: _neuron_contribution_scale is set from the adapter as a private attribute on the MLP bridge. A constructor argument on the gated MLP bridge, like output_contribution_scale on EmbeddingBridge, would make it easier to find for the next architecture with a multiplier after down_proj. Happy either way.
Description
Falcon-H1 runs attention and a Mamba-2 mixer in parallel in every block, and HF scales several outputs inline before their residual adds:
The bridge hooks see the outputs before these multipliers, and #1987 skips mixers in blocks that have attention. So on Falcon-H1,
decompose_resid,get_full_resid_decomposition(with or withoutexpand_neurons) andstack_neuron_resultssilently miss part of the residual stream:hook_attn_out, the head results and the attention bias are not multiplied byattention_out_multiplier;hook_embedis not multiplied byembedding_multiplier;post @ W_out) andb_outare not multiplied bymlp_multipliers[1](hook_mlp_outalready is, since it is the module output).The released checkpoints set all of these to values other than 1. On the seeded tiny random Falcon-H1 of the new test (3 layers, d_model 64, multipliers set to values other than 1, all biases on), comparing the sum with
resid_post(max abs error relative to max absresid_post):devdecompose_resid()get_full_resid_decomposition(expand_neurons=False)get_full_resid_decomposition(expand_neurons=True)With all multipliers at 1 the Mamba output is still missing (3e-3 on random weights, where Mamba writes little; more on trained weights).
Fix.
ScaledResidualBlockBridgetakes an optionalmlp_contribution_scale(default:residual_contribution_scale, so Granite is unchanged) andmixer_contribution_scalesfor a mixer that runs beside attention.hook_mlp_outuses the MLP scale.ActivationCachelists a mixer that sits beside attention in the same block (Falcon-H1'smamba) as a mixer row,{l}_mamba_out, indecompose_residandget_full_resid_decomposition. It is scaled by itsmixer_contribution_scalesentry. Its bias is not subtracted, sinceaccumulated_biasonly counts the attention bias for such a block.b_outinaccumulated_bias, the post-norm bias correction) use the MLP scale times an optional_neuron_contribution_scaleon the MLP bridge. It is the multiplier betweenpost @ W_out + b_outandhook_mlp_out, in the same way as_result_contribution_scaleon attention.ScaledResidualBlockBridgewithattention_out_multiplier, MLP scale 1 and{"mamba": ssm_out_multiplier}, sets the MLP's_neuron_contribution_scaletomlp_multipliers[1], and gives the embeddingoutput_contribution_scale=embedding_multiplier, as Granite does.attention_out_multiplier,ssm_out_multiplierandmlp_multipliersare added to the config attributes copied from HF.User-visible change: on Falcon-H1,
hook_attn_outandhook_embednow carry the scaled contributions that are added to the residual stream (as on Granite), and the decompositions gain{l}_mamba_outrows. The attribution-patching check that refuses blocks with a parallel mixer is unchanged.Type of change
Checklist:
tests/integration/model_bridge/test_ssm_hybrid_resid_decomposition.pygets afalcon_h1factory: a seeded tinyFalconH1Configwith every multiplier set to a value other than 1 and nonzero biases, built locally without hub access. It runs through the existing checks (decompositions sum toresid_pre/resid_mid/ the final residual, the attn/mlp modes partition the stream, full decomposition with and without neurons, LN + projection), plus:{l}_attn_out,{l}_mamba_outand{l}_mlp_out;hook_attn_out + mamba.hook_out * ssm_out_multiplier == resid_mid - resid_prein every block;b_Osum tohook_attn_out(the existing test now applies the block's scale tob_O).11 of these fail on
devand pass here.Local runs (Windows, CPU, torch 2.9.1,
--jaxtyping-packages=transformer_lens,beartype.beartype,PYTHONUTF8=1):tests/integration/model_bridge/test_ssm_hybrid_resid_decomposition.py: 82 passed, 20 skipped.tests/unit -m "not slow": 8173 passed, 14 failed. The same 14 fail ondevin my environment, with the same errors. Ten Gemma 3n adapter tests need the optionaltimm. Threetest_sparse_probing.pytests check optimizer stop reasons.test_faithfulness_recovery_is_graded_not_binaryis [Bug Report] test_faithfulness_recovery_is_graded_not_binary passes or fails on the sign of float32 rounding #1993.tests/integration/model_bridge/test_{direct_logit_attribution,granite_embedding_decomposition,granite_hook_semantics,granite_moe_hybrid_adapter,parallel_mlp_input_decomposition,parallel_residual_identities,post_norm_head_decomposition,residual_decomposition_identities,scaled_hook_autograd}.pyandtests/unit/test_activation_cache.py: 650 passed, 8 failed. The 8 failures are intest_granite_moe_hybrid_adapter.py(SSD reconstruction finds nomixer.in_proj.hook_outin the cache) and fail the same way ondevhere, which I take to be my local transformers version.I also ran a sweep of tiny random models, comparing each decomposition's sum with the residual stream. For Jamba, Bamba, OLMo-Hybrid, Qwen3-Next, Mamba and FalconMamba nothing changes (all at 1e-7, except Qwen3-Next with
expand_neurons=True, which raises on its MoE as before). For 38 dense architectures, including Granite and GraniteMoE, the results matchdev. The sweep also showed that Zamba2, RecurrentGemma and RWKV raiseKeyErrorin these APIs (LFM2 is handled in #2001); that is not changed here.black(the locked 23.12.1) andisort --check-onlypass.mypyreports no issues in the changed files.This was prepared with AI assistance (Claude); I reviewed and tested the change.