feat(init): coupled component initialization (weight_init: kaiming | coupled) - #991
Open
Antovigo wants to merge 5 commits into
Open
feat(init): coupled component initialization (weight_init: kaiming | coupled)#991Antovigo wants to merge 5 commits into
Antovigo wants to merge 5 commits into
Conversation
…coupled) JAX port of the torch-lineage coupled init (experiment/8B_targeted e5cdcbc): unit-norm seed on the narrow side, wide side its raw W-image (d_in <= d_out: v_c ~ unit norm, U_c <- (W v_c)^T; else u_c ~ unit norm, V_c <- W^T u_c). No C-dependent rescale — components sit at W's natural scale and the component sum equals W restricted to the seed span, the delta carrying the complement. pd.weight_init selects the init inside init_decomposition (match dispatch, kaiming default keeps the eval_shape restore path and stored configs untouched). W is recovered protocol-only via weight_deltas on zero V/U — no new DecomposedModel method, no per-target changes. The draw is vmapped per shape group like init_stack_arrays so the compiled init doesn't scale with site count, and the placed init grew an optional target_weights arg (riding as jit arguments, not closure constants) instead of a second placement helper. Verified: unit tests (seed determinism, W-image/span properties, stacked same-shape sites), make check, and TMS 5-2 engine smokes for both modes. The full core suite segfaults at test_llama8b under this box's memory pressure with or without this change; the test passes in isolation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UVg2f69eq8mhDrpZZ3ZEmM
…t helper The optional-target_weights overload made init_component_stacks_placed select the init implicitly (None => kaiming) — too implicit, and a dead end for future weight_init options. Back to named entry points (init_component_stacks_placed / init_coupled_component_stacks_placed), each a thin wrapper over _init_component_stacks_via, which owns the one eval_shape -> component_stacks_shardings -> jit(out_shardings) dance. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UVg2f69eq8mhDrpZZ3ZEmM
…weight_init selector Per review: per-mode entry points (init_kaiming_* / init_coupled_*) made every call site import one name per option and left the bare init_component_stacks name falsely suggesting universality. Now there is ONE public init_component_stacks(sites, key, weight_init, target_weights) — mode is an explicit argument (match-dispatched; target_weights required iff coupled, asserted), the per-mode draws are private _kaiming/_coupled stack-array helpers, and the placed variant carries the same signature. The WeightInit literal lives in components.py and PDConfig reuses it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UVg2f69eq8mhDrpZZ3ZEmM
Drop the component-sum test (an algebraic corollary of the exact U = (W V)^T assertion) and the seed-determinism test (a property of JAX's keyed PRNG, not of this code). What remains: the defining W-image property on both orientation branches, and the multi-site shape-group stacking wiring. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UVg2f69eq8mhDrpZZ3ZEmM
…t recovery DecomposedModel grows target_weight(name) -> frozen W in stored dtype — the torch lineage's ComponentModel.target_weight, restored. weight_deltas in every target is rewritten on top of it, and the coupled init reads W directly instead of the weight_deltas(zeros) trick, which cost a full zero V/U tree, an eager fp32 copy of every site's W, and all-site zero matmuls at startup. The fp32 upcast now happens inside the jitted init, per shape group. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UVg2f69eq8mhDrpZZ3ZEmM
Antovigo
force-pushed
the
feature/coupled_init
branch
from
July 25, 2026 01:31
1848ab5 to
eb3b5bc
Compare
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.
Summary
Adds the coupled component initialization to the JAX trainer as
pd.weight_init: kaiming | coupled(kaiming stays the default).With the "coupled" initialization scheme:
Concretely:
Testing
This was tested on the targeted decomposition of Llama-8B L18 on arithmetic tasks, with 3 different random seeds per condition.
Black is normal kaiming init, red is the coupled init introduced in this PR. Green initializes the components within the row/column space of the matrices without coupling. It outperforms the baseline, but is not as good as the coupled init, so I didn’t include it in the PR.
In practice this saves about 1000 steps on the arithmetic task, i.e. 5% of training.
Validation on Layer 17 for the same task (evals taken directly at init):

Validation on a full-data decomposition: initialization + 100 steps of faithfulness warmup + 200 steps of training for the "Jose" decomposition (Pile llama 4L):

The coupled initialization is also in the right direction for full-data decomposition, but it’s less clear whether this changes anything in practice. On the other hand, it runs only once and has no practical cost, so it probably doesn’t hurt to use it.
Note that these tests where done using the old Pytorch implementation, which should behave the same as the Jax version.
For the new Jax implementation:
param_decomp/tests/test_component_init.py: the defining W-image property on both orientation branches (unit-norm seeds, span membership), and independent per-site draws within a stacked shape group.weight_initmodes (init → faith warmup → training → eval → checkpoint)