Repository navigation
fix: apply BitNet's ffn_sub_norm on the processed-weights MLP path - #2004
flyingdoubleG wants to merge 1 commit into
Conversation
GatedMLPBridge's processed-weights forward went straight from act(gate) * up to down_proj, so BitNet in compatibility mode skipped ffn_sub_norm. Add a _pre_output_projection seam (identity by default), as on the attention bridge, and override it in an adapter-local BitNet MLP bridge.
jlarson4
left a comment
There was a problem hiding this comment.
Thanks for putting this together @flyingdoubleG, just a couple comments before we can approve and merge this
| # standard LN folding and W_O centering do not model them. | ||
| self.supports_fold_ln = False | ||
| self.supports_center_writing_weights = False | ||
| blocks = self.components["blocks"] |
There was a problem hiding this comment.
The attention path changes its class using _attention_bridge_cls. Here, the MLP is built as a plain GatedMLPBridge and then replaced, so a reader of _gated_mlp cannot see that BitNet overrides it and the next gated MLP will copy the swap. Can we create a mirror path for the MLP class, either by overriding _gated_mlp or by giving ArchitectureAdapter a _gated_mlp_bridge_cls to mirror the attention one?
| down_proj; BitNet inserts an RMSNorm in between. | ||
| """ | ||
|
|
||
| def _pre_output_projection(self, hidden: torch.Tensor) -> torch.Tensor: |
There was a problem hiding this comment.
I tested parity with microsoft/bitnet-b1.58-2B-4T-bf16. For this model, HF ternarizes the weight and int8-quantizes the activation on each call. The processed-weights path this bridge inherits does F.linear on the fp32 master weights, meaning after this fix compatibility mode on that checkpoint disagrees with HF by about 24 in log-prob, starting at mlp.hook_pre.
The attention bridge goes through its LinearBridges rather than F.linear, could the MLP do the same through gate, in and out with no loss? Nothing is folded for BitNet, so this should be safe. Take a look and let me know what you think
| _testing_eager = "config" | ||
|
|
||
| # Sub-layer norms are incompatible with HT-style processed-weight | ||
| # attention, so compatibility-mode equivalence (Phase 3) is out of scope. |
There was a problem hiding this comment.
This comment is now out of date, can we update it to reflect the updated reasoning for Phase 3 being out of scope?
Description
BitNet applies an RMSNorm right before each output projection:
_BitNetAttentionBridgealready re-appliesattn_sub_normthrough_pre_output_projectionon the processed-weights path. The MLP has no such step:GatedMLPBridge's processed-weights forward goes straight fromact(gate) * uptodown_proj. So afterenable_compatibility_mode(), every BitNet MLP skipsffn_sub_norm, and nothing reports it:devblocks.0.mlp.hook_postvs the unprocessed bridge (rel.)blocks.1.hook_resid_postvs the unprocessed bridge (rel.)Fix.
GatedMLPBridgegets a_pre_output_projection(hidden)step between the gating product andouton the processed-weights path. It is the identity by default, like the attention bridge's. An adapter-local_BitNetMLPBridgeoverrides it to apply the HF MLP'sffn_sub_norm.hook_post(out.hook_in) sees the normalized activation, as on the unprocessed path, where it is thedown_projinput. Other gated MLPs are unchanged.I left
applicable_phases = [1, 2, 4]as it is: I did not run the Phase 3 benchmark on a real checkpoint.Type of change
Checklist:
I have commented my code, particularly in hard-to-understand areas
I have made corresponding changes to the documentation
My changes generate no new warnings
I have added tests that prove my fix is effective or that my feature works
New and existing unit tests pass locally with my changes
I have not rewritten tests relating to key interfaces which would affect backward compatibility
New
tests/integration/model_bridge/test_bitnet_compatibility_mode.py. It builds a tiny randomBitNetForCausalLMwithout hub access and checks that:mlp.hook_post,hook_mlp_outandresid_postmatch the unprocessed bridge;get_full_resid_decomposition(expand_neurons=True)sums to the residual stream.The first four fail on
dev.test_bitnet_adapter.py: the MLP is a_BitNetMLPBridgewith the usualgate_proj/up_proj/down_projmapping, its_pre_output_projectionappliesffn_sub_norm, and the generic one is the identity.Local runs (Windows, CPU, torch 2.9.1, transformers 5.20.2,
--jaxtyping-packages=transformer_lens,beartype.beartype):test_bitnet_adapter.py,test_bitnet_compatibility_mode.pyandtests/unit/model_bridge/generalized_components: 636 passed.tests/unit -m "not slow": 8175 passed, 14 failed. The same 14 fail ondevin my environment (ten Gemma 3n adapter tests need the optionaltimm, threetest_sparse_probing.pytests check optimizer stop reasons, andtest_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).black(the locked 23.12.1),isort --check-onlyandmypypass on the changed files. In my environment mypy reports three errors in other, unchanged files (_model_state.py,moe.py,olmo_hybrid.py); they also appear ondev.This was prepared with AI assistance (Claude); I reviewed and tested the change.