Skip to content
This repository was archived by the owner on Jul 17, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion param_decomp/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ faithfulness, sources/PPGD, the squashings (S5/S6), the imp-min reduction, the g
| S17 | `faithfulness_loss` is the global mean of squared delta entries over all sites' parameters (Σ‖Δ‖² / Σ numel), recomputed from live V/U each step. |
| S18 | Each training step consumes a fresh token batch (a pure function of `(seed, step)` for O(1) resume); the model embeds it internally. **AMENDED 2026-06-24** (Oli-approved): removed the prefix harvest (residual-start) — there is no separate prefix forward; `clean_output` / `read_activations` / `masked_output` take the token batch directly. |
| S19 | Components gradients are global-norm-clipped at `0.01`, before the optimizer step. CI fn is unclipped (production). The clip coefficient uses torch's eps convention: `clip_coef = min(1, max_norm / (total_norm + 1e-6))` (`torch.nn.utils.clip_grad_norm_`). This `+1e-6` is canonical and matched JAX-side (#643); plain `optax.clip_by_global_norm` divides by `max(total_norm, max_norm)` with NO eps, which at `clip=0.01` (clip fires almost every step) gives a ~1e-4 relative component-grad difference each step — a real per-step deviation the JAX clip must avoid by reproducing the `+1e-6`. |
| S20 | Both main optimizers are AdamW, `wd=0`, betas `(0.9, 0.999)`, eps `1e-8`; LR cosine to `0.1×` start, no warmup, stepped per training step. Cosine convention is canonical-torch: `progress = (step − warmup) / (decay_steps − 1)`, so LR reaches `0.1×` at `step = steps − 1` (`schedule.py`). This is matched JAX-side (#642); plain `optax.cosine_decay_schedule(lr, steps, alpha=0.1)` divides by `steps` and reaches `0.1×` one update LATER (at `count = steps`), a genuine formula divergence of ~O(1/steps) ≈ 2.5e-6/step at 400k that the JAX schedule must avoid by using the `decay_steps − 1` denominator. **AMENDED 2026-07-02** (config-gated, default off = oracle parity): `type: muon` on a main optimizer config swaps that group's update rule for `optax.contrib.muon` (Newton-Schulz-orthogonalized momentum for the group's matrix leaves, Adam fallback for the rest), under the same cosine schedule and the same S19 clip chain. Every config without `type: muon` deserializes to `type: adamw` and keeps the canonical trajectory bit-identical. **AMENDED 2026-07-11** (muon × ci-fn matrix experiment): the muon matrix-leaf labeling is per-group (`run_state.build_optimizers`) — V/U and the MLP CI fns use optax's default 2D rule; the chunkwise CI fn passes `MuonDimensionNumbers` marking its 3D leaves as chunk-batched matrices (trailing two axes orthogonalized) and its 2D bias stacks as Adam-fallback, since the default 2D rule would label that tree exactly backwards. |
| S20 | Both main optimizers are AdamW, `wd=0`, betas `(0.9, 0.999)`, eps `1e-8`; LR cosine to `0.1×` start, no warmup, stepped per training step. Cosine convention is canonical-torch: `progress = (step − warmup) / (decay_steps − 1)`, so LR reaches `0.1×` at `step = steps − 1` (`schedule.py`). This is matched JAX-side (#642); plain `optax.cosine_decay_schedule(lr, steps, alpha=0.1)` divides by `steps` and reaches `0.1×` one update LATER (at `count = steps`), a genuine formula divergence of ~O(1/steps) ≈ 2.5e-6/step at 400k that the JAX schedule must avoid by using the `decay_steps − 1` denominator. **AMENDED 2026-07-02** (config-gated, default off = oracle parity): `type: muon` on a main optimizer config swaps that group's update rule for `optax.contrib.muon` (Newton-Schulz-orthogonalized momentum for the group's matrix leaves, Adam fallback for the rest), under the same cosine schedule and the same S19 clip chain. Every config without `type: muon` deserializes to `type: adamw` and keeps the canonical trajectory bit-identical. **AMENDED 2026-07-11** (muon × ci-fn matrix experiment): the muon matrix-leaf labeling is per-group (`run_state.build_optimizers`) — V/U and the MLP CI fns use optax's default 2D rule; the chunkwise CI fn passes `MuonDimensionNumbers` marking its 3D leaves as chunk-batched matrices (trailing two axes orthogonalized) and its 2D bias stacks as Adam-fallback, since the default 2D rule would label that tree exactly backwards. **AMENDED 2026-07-12** (fast muon): `impl: stacked` on a muon config swaps the per-leaf NS for `muon_stacked.py` — same-shape leaves batched into one NS with the stack axis sharded over `(replicate, fsdp)`, making each orthogonalization device-local (GSPMD otherwise lowers per-leaf NS on the ÷N masters into per-iteration full-Gram all-reduces with the largest matmul replicated on every device — the measured 3.3× muon-ci step hit). Same `MuonState` pytree (checkpoints round-trip across impls); trajectories match `impl: optax` up to float reassociation (the D4 tolerance class). `ns_steps` (default 5) and `ns_dtype` (default float32; bfloat16 = the Kimi recipe, stacked-only, masters/momentum stay fp32 per N1) are config knobs. Default `impl: optax` keeps the 07-02/07-11 experiment arms' exact semantics. |
| S21 | Faithfulness warmup (400 × AdamW lr `1e-3` on `faithfulness_loss` alone) precedes step 0; its optimizer is discarded. |
| S22 | Checkpoints round-trip ALL trajectory state of §3 — including every persistent term's sources + SRC_STEP moments + step/schedule counters — such that a resumed run continues the same trajectory (modulo RNG streams and kernel nondeterminism, cf. D4). **SIGTERM lifecycle (decision):** a SLURM-delivered SIGTERM sets a flag that is serviced at the main train-step boundary (synchronous save of the completed step, then exit for requeue), inside the faith-warmup loop (clean exit WITHOUT a save — no valid checkpoint exists pre-step-0, and resume skips warmup whenever any checkpoint is present, so a partial step-0 save would resume as if fully warmed; the requeue redoes warmup), and inside the in-loop eval pass (abandon the partial pass unlogged, fall through to the step-boundary save). The **first-jit-compile window remains unserviced** — a SIGTERM there is honored only once compilation finishes and control reaches the next servicing point. Periodic `save_every` is the BACKSTOP guarantee for every window; the SIGTERM→save path is the low-latency fast path, not the sole guarantee (lore `jsp_sigterm_save_never_fired`: 6/6 historical preemptions fell back to periodic ckpts; warmup/eval servicing closes the two largest unserviced windows). |
| S23 | A persistent source bundle feeds exactly ONE loss term. (The fused-backward S14′ unscaling divides that term's coeff out of the source gradient; a bundle shared across terms would make the division wrong.) |
Expand Down
20 changes: 20 additions & 0 deletions param_decomp/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,26 @@ class MuonOptimizerConfig(BaseConfig):
default=None,
description="If set, clip the grad norm of this group's parameters to this value",
)
impl: Literal["optax", "stacked"] = Field(
default="optax",
description=(
"NS implementation. `optax` = per-leaf `optax.contrib.muon` (the 2026-07-02/11"
" experiment arms' exact semantics). `stacked` = same-shape leaves batched into"
" one NS with the stack axis sharded over (replicate, fsdp) — device-local"
" orthogonalization, no per-iteration collectives (`muon_stacked.py`); same"
" trajectory up to float reassociation (the SPEC D4 tolerance class)."
),
)
ns_steps: PositiveInt = Field(
default=5, description="Newton-Schulz iterations (optax default 5; fewer = cheaper/looser)"
)
ns_dtype: Literal["float32", "bfloat16"] = Field(
default="float32",
description=(
"Dtype of the NS orthogonalization only (masters/momentum stay fp32 per N1);"
" bfloat16 halves NS compute+comm (the Kimi recipe). `stacked` impl only."
),
)


def _default_optimizer_type_adamw(data: object) -> object:
Expand Down
164 changes: 164 additions & 0 deletions param_decomp/configs/muon/pile4l_ppgd_40k_both_muon_stacked.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# fast-muon acceptance (task muon-sharding-design): both-muon 40k cell with
# stacked NS (fp32) on BOTH optimizer groups. Byte-identical to
# pile4l_ppgd_40k_both_muon.yaml (run p-c01f5833) otherwise — quality curves should
# match it up to reassociation; step time is the acceptance metric (0.44 s today).
run_name: jax-pile4l-ppgd-both-muon-stacked
cadence:
keep_last_n_checkpoints: 2
save_every: 5000
train_log_every: 200
data:
buffer_size: 1000
column_name: input_ids
data_files: /mnt/data/artifacts/mechanisms/param-decomp/datasets/pile_neox_tok_512/*.parquet
dataset_name: parquet
eval_split: train
is_tokenized: true
max_seq_len: 512
revision: null
shuffle_each_epoch: true
streaming: false
tokenizer_name: EleutherAI/gpt-neox-20b
train_split: train
eval:
batch_size: 128
every: 1000
metrics:
- n_batches_accum: 1
type: CIHistograms
- ci_alive_threshold: 0.0
type: ComponentActivationDensity
- ci_alive_threshold: 0.0
groups:
layer_0:
- h.0.*
layer_1:
- h.1.*
layer_2:
- h.2.*
layer_3:
- h.3.*
total:
- '*'
type: CI_L0
- rounding_threshold: 0.0
type: CEandKLLosses
- type: CIMeanPerComponent
- coeff: null
type: StochasticHiddenActsReconLoss
- type: CIHiddenActsReconLoss
- coeff: null
init: random
mask_scope: shared_across_batch
n_steps: 20
name: PGDReconLoss_20step
step_size: 0.1
type: PGDReconLoss
n_steps: 1
slow_every: 10000
slow_on_first_step: true
pd:
batch_size: 64
ci_config:
type: chunkwise_transformer
blocks_per_chunk: 1
d_model: 2048
n_blocks: 8
n_heads: 16
mlp_hidden: 8192
ci_fn_optimizer:
type: muon
consistent_rms: 0.2
impl: stacked
grad_clip_norm: null
lr_schedule:
final_val_frac: 0.1
fn_type: cosine
start_val: 5.0e-05
warmup_pct: 0.0
weight_decay: 0.0
components_optimizer:
type: muon
consistent_rms: 0.2
impl: stacked
grad_clip_norm: 0.01
lr_schedule:
final_val_frac: 0.1
fn_type: cosine
start_val: 5.0e-05
weight_decay: 0.0
decomposition_targets:
- C: 3072
module_pattern: h.*.mlp.c_fc
- C: 3584
module_pattern: h.*.mlp.down_proj
- C: 512
module_pattern: h.*.attn.q_proj
- C: 512
module_pattern: h.*.attn.k_proj
- C: 1024
module_pattern: h.*.attn.v_proj
- C: 1024
module_pattern: h.*.attn.o_proj
faithfulness_warmup_lr: 0.001
faithfulness_warmup_steps: 400
faithfulness_warmup_weight_decay: 0.0
loss_metrics:
- frequency:
coeff: 0.0001
reference_token_count: 65536
coeff: 0.0002
eps: 1.0e-12
p_anneal_end_frac: 1.0
p_anneal_final_p: 0.4
p_anneal_start_frac: 0.0
pnorm: 2.0
type: ImportanceMinimalityLoss
- coeff: 0.5
routing:
type: uniform_k_subset
type: StochasticReconSubsetLoss
- coeff: 0.5
n_samples: 1
n_warmup_steps: 2
optimizer:
beta1: 0.5
beta2: 0.99
eps: 1.0e-08
lr_schedule:
final_val_frac: 1.0
fn_type: constant
start_val: 0.01
warmup_pct: 0.025
type: adam
scope:
type: per_batch_per_position
type: PersistentPGDReconLoss
- coeff: 10000000.0
type: FaithfulnessLoss
seed: 0
steps: 40000
runtime:
remat_recon_forwards: false
dp: 8
launch_env:
env:
# The shared xla_gpu_per_fusion_autotune_cache_dir was created group-unwritable
# (2026-07-11, PERMISSION_DENIED at first step for any non-owner run); the main
# executable cache is unaffected. The 2026-07-03 sibling arms predate that dir,
# so disabling it also matches their autotune behavior.
JAX_PERSISTENT_CACHE_ENABLE_XLA_CACHES: none
target:
output_extract: 0
weights_dtype: bfloat16
spec:
kind: pretrained
model_class: param_decomp_lab.experiments.lm.pretrain.models.llama_simple_mlp.LlamaSimpleMLP
run_path: goodfire/spd/runs/t-9d2b8f02
wandb:
entity: null
project: param-decomp
group: fast-muon-accept
tags:
- fast-muon-accept
- muon-cifn-matrix
166 changes: 166 additions & 0 deletions param_decomp/configs/muon/pile4l_ppgd_40k_both_muon_stacked_bf16.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# fast-muon acceptance (task muon-sharding-design): both-muon 40k cell with
# stacked NS + bf16 orthogonalization on BOTH optimizer groups. Byte-identical to
# pile4l_ppgd_40k_both_muon.yaml (run p-c01f5833) otherwise — quality curves should
# match it up to reassociation; step time is the acceptance metric (0.44 s today).
run_name: jax-pile4l-ppgd-both-muon-stacked-bf16
cadence:
keep_last_n_checkpoints: 2
save_every: 5000
train_log_every: 200
data:
buffer_size: 1000
column_name: input_ids
data_files: /mnt/data/artifacts/mechanisms/param-decomp/datasets/pile_neox_tok_512/*.parquet
dataset_name: parquet
eval_split: train
is_tokenized: true
max_seq_len: 512
revision: null
shuffle_each_epoch: true
streaming: false
tokenizer_name: EleutherAI/gpt-neox-20b
train_split: train
eval:
batch_size: 128
every: 1000
metrics:
- n_batches_accum: 1
type: CIHistograms
- ci_alive_threshold: 0.0
type: ComponentActivationDensity
- ci_alive_threshold: 0.0
groups:
layer_0:
- h.0.*
layer_1:
- h.1.*
layer_2:
- h.2.*
layer_3:
- h.3.*
total:
- '*'
type: CI_L0
- rounding_threshold: 0.0
type: CEandKLLosses
- type: CIMeanPerComponent
- coeff: null
type: StochasticHiddenActsReconLoss
- type: CIHiddenActsReconLoss
- coeff: null
init: random
mask_scope: shared_across_batch
n_steps: 20
name: PGDReconLoss_20step
step_size: 0.1
type: PGDReconLoss
n_steps: 1
slow_every: 10000
slow_on_first_step: true
pd:
batch_size: 64
ci_config:
type: chunkwise_transformer
blocks_per_chunk: 1
d_model: 2048
n_blocks: 8
n_heads: 16
mlp_hidden: 8192
ci_fn_optimizer:
type: muon
consistent_rms: 0.2
impl: stacked
ns_dtype: bfloat16
grad_clip_norm: null
lr_schedule:
final_val_frac: 0.1
fn_type: cosine
start_val: 5.0e-05
warmup_pct: 0.0
weight_decay: 0.0
components_optimizer:
type: muon
consistent_rms: 0.2
impl: stacked
ns_dtype: bfloat16
grad_clip_norm: 0.01
lr_schedule:
final_val_frac: 0.1
fn_type: cosine
start_val: 5.0e-05
weight_decay: 0.0
decomposition_targets:
- C: 3072
module_pattern: h.*.mlp.c_fc
- C: 3584
module_pattern: h.*.mlp.down_proj
- C: 512
module_pattern: h.*.attn.q_proj
- C: 512
module_pattern: h.*.attn.k_proj
- C: 1024
module_pattern: h.*.attn.v_proj
- C: 1024
module_pattern: h.*.attn.o_proj
faithfulness_warmup_lr: 0.001
faithfulness_warmup_steps: 400
faithfulness_warmup_weight_decay: 0.0
loss_metrics:
- frequency:
coeff: 0.0001
reference_token_count: 65536
coeff: 0.0002
eps: 1.0e-12
p_anneal_end_frac: 1.0
p_anneal_final_p: 0.4
p_anneal_start_frac: 0.0
pnorm: 2.0
type: ImportanceMinimalityLoss
- coeff: 0.5
routing:
type: uniform_k_subset
type: StochasticReconSubsetLoss
- coeff: 0.5
n_samples: 1
n_warmup_steps: 2
optimizer:
beta1: 0.5
beta2: 0.99
eps: 1.0e-08
lr_schedule:
final_val_frac: 1.0
fn_type: constant
start_val: 0.01
warmup_pct: 0.025
type: adam
scope:
type: per_batch_per_position
type: PersistentPGDReconLoss
- coeff: 10000000.0
type: FaithfulnessLoss
seed: 0
steps: 40000
runtime:
remat_recon_forwards: false
dp: 8
launch_env:
env:
# The shared xla_gpu_per_fusion_autotune_cache_dir was created group-unwritable
# (2026-07-11, PERMISSION_DENIED at first step for any non-owner run); the main
# executable cache is unaffected. The 2026-07-03 sibling arms predate that dir,
# so disabling it also matches their autotune behavior.
JAX_PERSISTENT_CACHE_ENABLE_XLA_CACHES: none
target:
output_extract: 0
weights_dtype: bfloat16
spec:
kind: pretrained
model_class: param_decomp_lab.experiments.lm.pretrain.models.llama_simple_mlp.LlamaSimpleMLP
run_path: goodfire/spd/runs/t-9d2b8f02
wandb:
entity: null
project: param-decomp
group: fast-muon-accept
tags:
- fast-muon-accept
- muon-cifn-matrix
Loading