Repository navigation
fix: carry the output logit scale into logit_attrs and DLA - #2013
Conversation
Granite, MiniCPM, Falcon-H1 and Muse Glimmer scale lm_head's output by a constant that W_U does not contain, so logit_attrs (and therefore direct_logit_attribution) summed to the logit difference divided by that factor. Expose the factor as ArchitectureAdapter.output_logits_scale() and apply it to the logit directions in logit_attrs.
| """ | ||
| return apply_softcap(logits, getattr(self.cfg, "output_logits_soft_cap", None)) | ||
|
|
||
| def output_logits_scale(self) -> float: |
There was a problem hiding this comment.
Every adapter now states its factor twice, here and in its apply_output_logits_transform. If a future adapter only overrides the transform this bug will resurface. Can the base transform apply output_logits_scale() before the soft cap, in order to eliminate the per-adapter transform overrides?
There was a problem hiding this comment.
Good call, done in dac9ee3. The base apply_output_logits_transform now multiplies by output_logits_scale() before the soft cap, and the Granite, Falcon-H1, MiniCPM, Muse Glimmer and Cohere transform overrides are gone; each adapter only overrides output_logits_scale(). The existing per-adapter transform tests (including Muse Glimmer's multiplier-before-softcap and Cohere's folded-scale cases) still pass unchanged.
Adapters now only declare their constant factor via output_logits_scale(); the base apply_output_logits_transform multiplies by it before the soft cap, so the forward path and linear readouts like logit_attrs cannot drift apart.
|
Looks good, thanks for making those updates @YHC66 |
Description
ActivationCache.logit_attrs(and sodirect_logit_attribution) dots each component withW_Udirections. Some adapters apply a constant factor after the unembedding, inapply_output_logits_transform: Granite divides bylogits_scaling, MiniCPM byd_model / dim_model_base, Falcon-H1 and Muse Glimmer multiply bylm_head_multiplier/output_multiplier. That factor is not inW_U, so on these models the attributions sum to the logit difference times the inverse of the factor. Released Granite checkpoints setlogits_scalingwell above 1, and MiniCPM4-0.5B has 1024 / 256 = 4, so DLA comes out several times too large.The DLA readout check does not catch this: it applies the transform before comparing with the model's logits, so it passes. Cohere is not affected, because its adapter folds
logit_scaleintoW_Uin compatibility mode.Changes:
ArchitectureAdapter.output_logits_scale()returns the constant factor the output transform applies before any soft cap (1.0 by default). Granite, MiniCPM, Falcon-H1, Muse Glimmer and Cohere return the same factor theirapply_output_logits_transformuses (for Cohere, 1.0 once the scale is folded). Granite MoE and Granite MoE Hybrid inherit it from Granite. The existing transforms are unchanged, except that Cohere's now calls the new method.logit_attrsmultiplies the logit directions by this factor.tokens_to_residual_directionsstill returns the plainW_Ucolumns. HookedTransformer has no adapter, so it is not affected.logits_scaling=8(component and head units) next to the existing one, which useslogits_scaling=1.0; and a contract test thatoutput_logits_scale()matchesapply_output_logits_transformfor Granite, Falcon-H1 and MiniCPM.With a soft cap (Gemma 2, Muse Glimmer) the logits are not linear in the residual stream, so DLA still attributes the pre-cap logits. This PR does not change that.
Type of change
Checklist:
Tested on macOS (Apple Silicon), Python 3.12, CPU, torch 2.11, transformers 5.13. Without the fix the 5 new tests fail; with it they pass.
tests/unit -m "not slow": 8360 passed, 0 failed.test_granite_embedding_decomposition.py,test_minicpm_tiny.pyandtest_ssm_hybrid_resid_decomposition.py: 213 passed. I also checked DLA sums against the logit difference on tiny GraniteMoe, MiniCPM (4 and 1/2 code), Cohere and Llama models in compatibility mode. black, isort, pycln and mypy pass on the changed files.