dimuon-lite vs AdamW, LoRA SFT on Qwen3.5-2B, no_robots held-out NLL
Redo
Preflight only, no training: the shared Spindle returns HTTP 400 'optimizer family dimuon is not supported' when creating a model with DimuonOptimizerConfig, and rejects per-model LoRA seeds. A fork could add dimuon-lite (unmerged spindle#85 plus miles#17), but the spec's required correctness preflight has no pinned reference (Aaron's DiMuon reconstruction), so no sweep was started.
Result
No arm results yet.
Changes from the spec 0 · original hypothesis not tested
Original hypothesis tested: no
Tested instead: Nothing was trained. Only the optimizer and creation preflight ran on the shared Spindle.
Checks · activation unverified · evaluator unknown · feasibility unknown
Parity errors and accepted tolerances
[
{
"check": "baseline_update",
"measured": null,
"path": "spindle_dimuon_lite_optimizer",
"status": "unverified",
"tolerance": {
"atol": 1e-06,
"min_cosine": 0.99999,
"rtol": 0.001
}
},
{
"check": "reference_update",
"measured": null,
"path": "spindle_dimuon_lite_optimizer",
"status": "unverified",
"tolerance": {
"atol": 1e-05,
"min_cosine": 0.999,
"rtol": 0.01
}
}
]- executed_base_model
- not reported needs one of Qwen/Qwen3.5-2B · missing
- executed_optimizer_step_type
- not reported needs one of DimuonParams, AdamParams · missing
- dimuon_B_update_normalized_stable_rank_step1
- not reported needs >= 0.6 and <= 1.05 · missing
- batch_hash_match_across_arms
- not reported needs >= 1 and <= 1 · missing
- pilot_train_nll_drop_dimuon_10steps
- not reported needs >= 0 · missing
- Projected wall time
- unknown
- Projected GPU allocation
- unknown
- Measured feasibility not recorded
Timing, sample counts and allocation evidence
{
"gpu_hours": null,
"known_gpu_hours": 0,
"phases": {},
"problems": [
"Measured feasibility not recorded"
],
"record": null,
"recorded_at": 1793818407.728404,
"status": "unknown",
"wall_seconds": null
}Spec exp_71911f0fb9
Hypothesis
With per-optimizer learning rates picked on a validation split, LoRA SFT with dimuon-lite cuts held-out no_robots token_mean NLL by at least 0.02 nats versus AdamW. Both arms use the same 100 steps, batches and seeds.
Success criterion
Validated if the simultaneous 95% paired_t interval, across 3 training seeds, for (AdamW NLL − DiMuon NLL) on no_robots test token_mean NLL at step 100 lies entirely above 0.02 nats. Failed if it lies entirely below 0.02. Otherwise inconclusive. IFEval strict accuracy is reported descriptively and does not decide.
Method
Baseline
Full spec
{
"activation_gate": {
"applicable": true,
"checks": [
{
"measures": "model name passed to create_lora_training_client in every arm",
"metric": "executed_base_model",
"one_of": [
"Qwen/Qwen3.5-2B"
]
},
{
"measures": "type of params object passed to optim_step in each arm (logged client-side)",
"metric": "executed_optimizer_step_type",
"one_of": [
"DimuonParams",
"AdamParams"
]
},
{
"max": 1.05,
"measures": "||dB||_F^2 / (||dB||_2^2 * r) for the first DiMuon update of LoRA B, averaged over layers, from downloaded checkpoints at step 0 and 1",
"metric": "dimuon_B_update_normalized_stable_rank_step1",
"min": 0.6
},
{
"max": 1.0,
"measures": "fraction of the 100 training batches whose token-id hash is identical between paired arms within a seed",
"metric": "batch_hash_match_across_arms",
"min": 1.0
},
{
"measures": "train token_mean NLL at step 0 minus step 10 in the DiMuon pilot at the middle LR; must be finite and positive",
"metric": "pilot_train_nll_drop_dimuon_10steps",
"min": 0.0
}
],
"reason": "The paired comparison only tests DiMuon if the executed optimizer really orthogonalizes LoRA B updates and both arms ran the declared configuration. Polar Express makes the first-step B update near-orthogonal (flat singular spectrum); AdamW's is not. This is measured from saved weight diffs, not SDK introspection."
},
"arms": [
{
"lr": 5e-05,
"name": "lrsearch_adamw_5e-5_s0",
"optimizer": "adam",
"phase": "selection",
"seed": 0,
"steps": 40
},
{
"lr": 0.0001,
"name": "lrsearch_adamw_1e-4_s0",
"optimizer": "adam",
"phase": "selection",
"seed": 0,
"steps": 40
},
{
"lr": 0.0002,
"name": "lrsearch_adamw_2e-4_s0",
"optimizer": "adam",
"phase": "selection",
"seed": 0,
"steps": 40
},
{
"lr": 0.0003,
"name": "lrsearch_dimuon_3e-4_s0",
"optimizer": "dimuon-lite",
"phase": "selection",
"seed": 0,
"steps": 40
},
{
"lr": 0.001,
"name": "lrsearch_dimuon_1e-3_s0",
"optimizer": "dimuon-lite",
"phase": "selection",
"seed": 0,
"steps": 40
},
{
"lr": 0.003,
"name": "lrsearch_dimuon_3e-3_s0",
"optimizer": "dimuon-lite",
"phase": "selection",
"seed": 0,
"steps": 40
},
{
"lr": "frozen from selection",
"name": "baseline_adamw_s0",
"optimizer": "adam",
"phase": "confirmation",
"seed": 0,
"steps": 100
},
{
"lr": "frozen from selection",
"name": "baseline_adamw_s1",
"optimizer": "adam",
"phase": "confirmation",
"seed": 1,
"steps": 100
},
{
"lr": "frozen from selection",
"name": "baseline_adamw_s2",
"optimizer": "adam",
"phase": "confirmation",
"seed": 2,
"steps": 100
},
{
"lr": "frozen from selection",
"name": "treatment_dimuon_s0",
"optimizer": "dimuon-lite",
"phase": "confirmation",
"seed": 0,
"steps": 100
},
{
"lr": "frozen from selection",
"name": "treatment_dimuon_s1",
"optimizer": "dimuon-lite",
"phase": "confirmation",
"seed": 1,
"steps": 100
},
{
"lr": "frozen from selection",
"name": "treatment_dimuon_s2",
"optimizer": "dimuon-lite",
"phase": "confirmation",
"seed": 2,
"steps": 100
}
],
"base_model": "Qwen/Qwen3.5-2B",
"baseline": "AdamW (tinker.AdamParams, beta1 0.9, beta2 0.95, eps 1e-8) through the same SDK script, with the same model, LoRA rank, init seed, batches and order, 100 steps, and eval. Its LR is chosen by the same 3-point validation search.",
"batch_size": 32,
"budget": {
"gpu_hours": 2.0,
"max_wall_minutes": 150
},
"command": "# Custom part: one SDK SFT loop shared by both optimizers (cookbook chat_sl is AdamW-only).\nimport os, sys, tinker, datasets, random\nfrom tinker_cookbook import renderers, tokenizer_utils\nfrom tinker_cookbook.supervised.data import conversation_to_datum\nOPT, LR, SEED, STEPS = sys.argv[1], float(sys.argv[2]), int(sys.argv[3]), int(sys.argv[4])\nMODEL='Qwen/Qwen3.5-2B'; BS=32; MAXLEN=2048\ntok=tokenizer_utils.get_tokenizer(MODEL); rend=renderers.get_renderer(renderers.get_recommended_renderer_name(MODEL) if hasattr(renderers,'get_recommended_renderer_name') else 'qwen3', tok)\nds=datasets.load_dataset('HuggingFaceH4/no_robots'); tr=list(ds['train']); random.Random(1234).shuffle(tr)\nval, train = tr[:500], tr[500:]; random.Random(SEED).shuffle(train)\nmk=lambda r: conversation_to_datum(r['messages'], rend, MAXLEN)\nsc=tinker.ServiceClient(base_url=os.environ['TINKER_BASE_URL'])\nkw={'optimizer': tinker.types.DimuonOptimizerConfig()} if OPT=='dimuon' else {}\ntc=sc.create_lora_training_client(base_model=MODEL, rank=32, seed=SEED, **kw)\ntc.save_weights_for_sampler(name=f'init-{OPT}-s{SEED}').result() # hash for restoration preflight\nstep_params = (lambda: tinker.types.DimuonParams(learning_rate=LR, beta1=0.9, grad_clip_norm=0.0)) if OPT=='dimuon' else (lambda: tinker.AdamParams(learning_rate=LR, beta1=0.9, beta2=0.95, eps=1e-8))\nfor s in range(STEPS):\n batch=[mk(r) for r in train[s*BS:(s+1)*BS]]\n tc.forward_backward(batch, loss_fn='cross_entropy').result(); tc.optim_step(step_params()).result()\ndef nll(rows): # token_mean from per-example totals, never batch loss:mean\n tot=0.0; n=0.0\n for i in range(0,len(rows),32):\n d=[mk(r) for r in rows[i:i+32]]; out=tc.forward(d, loss_fn='cross_entropy').result()\n for di,o in zip(d,out.loss_fn_outputs):\n w=di.loss_fn_inputs['weights'].to_numpy(); lp=o['logprobs'].to_numpy(); tot+=-(lp*w).sum(); n+=w.sum()\n return tot/n\nsplit = val if STEPS==40 else list(ds['test'])\nprint({'opt':OPT,'lr':LR,'seed':SEED,'steps':STEPS,'token_mean_nll':nll(split)})\ntc.save_weights_for_sampler(name=f'final-{OPT}-lr{LR}-s{SEED}').result()\n# run: python sft_opt.py {adam|dimuon} <lr> <seed> {40|100}",
"correctness_preflight": {
"notes": "Tolerances are fixed for bf16 LoRA weights with fp32 optimizer math before measuring. reference_update rtol 1e-2 leaves headroom over the reported 0.09% error. Feed the same LoRA state and gradient to the Spindle DiMuon step and to the reference for one step. With grad_clip_norm=0 and beta1 momentum seeded from the first gradient, the update should scale linearly in LR across a 100x span.",
"paths": [
{
"baseline_revision": "UNRESOLVED: pin the modal-projects/spindle commit deployed at the shared Spindle URL (post-#85). AdamW updates on this deployment must match the pre-#85 AdamW update on identical grads/state.",
"id": "spindle_dimuon_lite_optimizer",
"kind": "optimizer",
"lr_scaling": {
"learning_rates": [
0.0001,
0.001,
0.01
],
"rtol": 0.01
},
"reference_revision": "UNRESOLVED: pin the commit of Aaron's DiMuon reconstruction (0.09% relative update error vs Tinker) and the modal-projects/miles commit of #17",
"tolerances": {
"baseline_update": {
"atol": 1e-06,
"min_cosine": 0.99999,
"rtol": 0.001
},
"reference_update": {
"atol": 1e-05,
"min_cosine": 0.999,
"rtol": 0.01
}
}
}
],
"required": true,
"status": "unresolved: spindle and miles commits for #85/#17 and the commit of Aaron's DiMuon reconstruction must be pinned before any full sweep"
},
"dataset": {
"hf_path": "HuggingFaceH4/no_robots",
"n_examples": 9500,
"name": "no_robots",
"split": "train (9000 for training, 500 held out for LR selection); test (500) for confirmation"
},
"decision_rule": {
"adaptive_selection": {
"confirmation_item_ids": "all 500 rows of no_robots split=test",
"kind": "hyperparameter",
"rationale": "Muon-family and AdamW LoRA optimizers have very different good LRs. Each gets a prespecified 3-point grid, scored only on validation items held out from train, and the choice is frozen before the single confirmation eval on the test split.",
"search_item_ids": "no_robots train rows held out by seeded shuffle (seed 1234), first 500 after shuffle; never used for training"
},
"alpha": 0.05,
"assumptions": {
"identically_distributed_seed_effects": true,
"independent_seed_effects": true,
"normal_seed_effects": true
},
"criteria": [
{
"id": "primary",
"margin": 0.02,
"metric": "no_robots_test_token_mean_nll_at_step_100",
"weights": {
"baseline": 1,
"treatment": -1
}
}
],
"estimand": "Reduction in held-out no_robots test token_mean NLL at step 100 from replacing AdamW with dimuon-lite (each at its validation-selected LR) for rank-32 LoRA SFT on Qwen3.5-2B; larger is better for dimuon-lite.",
"method": "paired_t",
"seeds": [
0,
1,
2
],
"unit": "training_seed",
"version": 1
},
"eval": {
"aggregation": "token_mean",
"benchmark": "no_robots test split (HuggingFaceH4/no_robots, split=test)",
"metric": "held-out token_mean NLL (nats per assistant loss token)",
"n_examples": 500
},
"experiment_features": {
"adaptive_selection": true,
"checkpoint_restore": false,
"custom_loss": false,
"custom_optimizer": true,
"generated_training_data": false,
"heldout_loss": true,
"learned_generator": false,
"paired_comparison": true,
"paired_training_arms": true,
"warmup_adaptation": false
},
"feasibility": {
"config": null,
"downsizing_order": "drop IFEval secondary, then shrink LR grid to 2 points per optimizer, then shorten selection runs to 25 steps; keep 3 seeds and the 100-step test NLL. If that still does not fit, report inconclusive.",
"effective_config": {
"arms": 12,
"base_model": "Qwen/Qwen3.5-2B",
"batch_size": 32,
"confirmation_steps": 100,
"max_seq_len": 2048,
"n_eval_test": 500,
"n_eval_validation": 500,
"n_ifeval_secondary": 541,
"planned_arm_steps": 840,
"selection_steps": 40
},
"generation": null,
"measurements": null,
"primary_endpoint": "no_robots_test_token_mean_nll_at_step_100",
"recovery": null,
"source": null
},
"feasibility_check": {
"generation_applicability": {
"check": "generation_audit",
"enforce": false,
"features": {
"generated_training_data": false
},
"reasons": [
"spec.generation_audit opts out and no feature it covers is true"
],
"status": "not_applicable"
},
"generation_counts": null,
"gpu_hours": null,
"known_gpu_hours": 0,
"phases": {},
"problems": [
"Effective configuration not recorded",
"Pilot evidence source not recorded",
"Representative training timing not recorded",
"Representative setup timing not recorded",
"Representative evaluation timing not recorded",
"Positive recovery reserve not recorded"
],
"status": "unknown",
"wall_seconds": null
},
"feasibility_required": true,
"generation_audit": {
"answer_comparison": null,
"max_eval_overlaps": 0,
"max_label_errors": 0,
"min_sample_size": 0,
"reason": "fixed public dataset; no generated training data",
"required": false
},
"hypothesis": "With per-optimizer learning rates picked on a validation split, LoRA SFT with dimuon-lite cuts held-out no_robots token_mean NLL by at least 0.02 nats versus AdamW. Both arms use the same 100 steps, batches and seeds.",
"lora_rank": 32,
"max_seq_len": 2048,
"method": "Train a rank-32 LoRA on Qwen/Qwen3.5-2B over HuggingFaceH4/no_robots, using one tinker SDK script for both arms so the only difference is the optimizer. The cookbook chat_sl recipe is AdamW-only, so running AdamW through the same script removes any recipe difference. DiMuon is selected at creation with optimizer=tinker.types.DimuonOptimizerConfig() and stepped with DimuonParams(learning_rate, beta1=0.9, grad_clip_norm=0.0). AdamW uses tinker.AdamParams(learning_rate, beta1=0.9, beta2=0.95, eps=1e-8). dimuon-lite is a recently landed reverse-engineered update rule: Polar Express orthogonalization with about 5 iterations, rank_lr_correction_exponent 0.175, Muon on LoRA B, and no beta2. Its update is checked against the trusted DiMuon reconstruction before the comparison runs. Phase 1, LR selection: each optimizer runs 40 steps at seed 0 on a 3-point LR grid fixed now (AdamW {5e-5, 1e-4, 2e-4}; DiMuon {3e-4, 1e-3, 3e-3}). Each run is scored by token_mean NLL on 500 validation items held out from the train split, and the best LR per optimizer is frozen. Phase 2, confirmation: each optimizer trains 100 steps at its frozen LR for seeds {0,1,2}. Batches are identical across arms within a seed: the same shuffled order of the remaining 9,000 train rows, batch_size 32, max_seq_len 2048. Data order and LoRA init seed are shared per seed. Primary eval is token_mean NLL on the full 500-example no_robots test split, computed from per-example summed logprobs times loss weights via forward(). Secondary, descriptive only: IFEval prompt-level strict accuracy (541 prompts, greedy, max_tokens 1024) on each final checkpoint. IFEval is dropped first if feasibility does not fit.",
"optimizer": {
"lr": 0.001,
"name": "dimuon-lite"
},
"primary_endpoint": "no_robots_test_token_mean_nll_at_step_100",
"restoration_preflight": {
"applicable": true,
"fixed_batch_required": true,
"groups": [
{
"arms": [
{
"name": "baseline_adamw_s0",
"optimizer": "adam",
"optimizer_config": {
"beta1": 0.9,
"beta2": 0.95,
"eps": 1e-08,
"lr": "frozen"
}
},
{
"name": "treatment_dimuon_s0",
"optimizer": "dimuon-lite",
"optimizer_config": {
"beta1": 0.9,
"grad_clip_norm": 0.0,
"lr": "frozen"
}
}
]
},
{
"arms": [
{
"name": "baseline_adamw_s1",
"optimizer": "adam",
"optimizer_config": {
"beta1": 0.9,
"beta2": 0.95,
"eps": 1e-08,
"lr": "frozen"
}
},
{
"name": "treatment_dimuon_s1",
"optimizer": "dimuon-lite",
"optimizer_config": {
"beta1": 0.9,
"grad_clip_norm": 0.0,
"lr": "frozen"
}
}
]
},
{
"arms": [
{
"name": "baseline_adamw_s2",
"optimizer": "adam",
"optimizer_config": {
"beta1": 0.9,
"beta2": 0.95,
"eps": 1e-08,
"lr": "frozen"
}
},
{
"name": "treatment_dimuon_s2",
"optimizer": "dimuon-lite",
"optimizer_config": {
"beta1": 0.9,
"grad_clip_norm": 0.0,
"lr": "frozen"
}
}
]
}
],
"reason": "AdamW and DiMuon arms are trained from a shared fresh-LoRA start within each seed. Before the first update, each arm calls save_weights and downloads the adapter. The SHA-256 of the LoRA tensors must match across the paired arms; B=0 at init, and A comes from the seeded init. Base-model logprobs on a fixed tokenized batch must also match. A hash mismatch makes that seed inconclusive. Optimizer type is verified by the activation-gate step-1 spectrum check, not by invented introspection."
},
"steps": 100,
"success_criterion": "Validated if the simultaneous 95% paired_t interval, across 3 training seeds, for (AdamW NLL \u2212 DiMuon NLL) on no_robots test token_mean NLL at step 100 lies entirely above 0.02 nats. Failed if it lies entirely below 0.02. Otherwise inconclusive. IFEval strict accuracy is reported descriptively and does not decide.",
"title": "dimuon-lite vs AdamW, LoRA SFT on Qwen3.5-2B, no_robots held-out NLL",
"training_type": "sft"
}Evidence
Interim updates from agentcore (1, first Nov 4, 18:59 UTC)
- Nov 4, 18:59 UTC · failed · Preflight only, no training: the shared Spindle returns HTTP 400 'optimizer family dimuon is not supported' when creating a model with DimuonOptimizerConfig, and rejects per-model LoRA seeds. A fork …
Artifacts
- correctness_validation
- {'status': 'unverified', 'required': True, 'checks': [{'path': 'spindle_dimuon_lite_optimizer', 'check': 'baseline_update', 'status': 'unverified', 'tolerance': {'atol': 1e-06, 'rtol': 0.001, 'min_cosine': 0.99999}, 'measured': None}, {'path': 'spindle_dimuon_lite_optimizer', 'check': 'reference_update', 'status': 'unverified', 'tolerance': {'atol': 1e-05, 'rtol': 0.01, 'min_cosine': 0.999}, 'measured': None}], 'reason': 'spindle_dimuon_lite_optimizer: fixed input description/hash; spindle_dimuon_lite_optimizer: matching reference_revision; spindle_dimuon_lite_optimizer: matching baseline_revision; spindle_dimuon_lite_optimizer: implementation_revision; spindle_dimuon_lite_optimizer: precision; spindle_dimuon_lite_optimizer: normalization; spindle_dimuon_lite_optimizer: measured baseline_update errors; spindle_dimuon_lite_optimizer: measured reference_update errors; spindle_dimuon_lite_optimizer: LR scaling needs at least two probes', 'evidence_source': 'executor report; remote execution is not attested', 'applicability': 'required', 'reported_verdict': 'inconclusive'}
- decision_validation
- {'status': 'uncertain', 'issues': ['Decision evidence was not recorded.'], 'criteria': [], 'outcome_reason': 'insufficient_evidence', 'unit': 'training_seed', 'n_seeds': 3, 'alpha': 0.05, 'method': 'paired_t', 'assumptions': {'independent_seed_effects': True, 'identically_distributed_seed_effects': True, 'normal_seed_effects': True}, 'applicability': {'check': 'decision_rule', 'status': 'required', 'enforce': True, 'reasons': ['experiment_features: paired_comparison, adaptive_selection is true'], 'features': {'paired_comparison': True, 'adaptive_selection': True}}}
- feasibility
- {'status': 'unknown', 'problems': ['Measured feasibility not recorded'], 'wall_seconds': None, 'gpu_hours': None, 'known_gpu_hours': 0, 'phases': {}, 'record': None, 'recorded_at': 1793818407.728404}
- feasibility_required
- True
- generation_audit_validation
- {'status': 'not_applicable', 'reasons': ['spec.generation_audit opts out and no feature it covers is true'], 'applicability': {'check': 'generation_audit', 'status': 'not_applicable', 'enforce': False, 'reasons': ['spec.generation_audit opts out and no feature it covers is true'], 'features': {'generated_training_data': False}}}
- launch
- {'experiment_id': 'exp_71911f0fb9', 'context_status': 'snapshotted', 'superseded': False, 'mode': 'ordinary', 'purpose': 'Execute the selected saved experiment version.'}
- progress_updated_at
- 1793818753.5838022
- reported_outcome_reason
- incomplete_execution
- reported_verdict
- inconclusive
- restoration_validation
- {'status': 'unverified', 'required': True, 'checks': [{'check': 'format', 'status': 'unverified', 'reason': 'Missing initialization_provenance version 1.'}, {'check': 'baseline_adamw_s0', 'status': 'unverified', 'reason': 'baseline_adamw_s0: missing or duplicate arm evidence.'}, {'check': 'treatment_dimuon_s0', 'status': 'unverified', 'reason': 'treatment_dimuon_s0: missing or duplicate arm evidence.'}, {'check': 'baseline_adamw_s1', 'status': 'unverified', 'reason': 'baseline_adamw_s1: missing or duplicate arm evidence.'}, {'check': 'treatment_dimuon_s1', 'status': 'unverified', 'reason': 'treatment_dimuon_s1: missing or duplicate arm evidence.'}, {'check': 'baseline_adamw_s2', 'status': 'unverified', 'reason': 'baseline_adamw_s2: missing or duplicate arm evidence.'}, {'check': 'treatment_dimuon_s2', 'status': 'unverified', 'reason': 'treatment_dimuon_s2: missing or duplicate arm evidence.'}], 'reason': 'Missing initialization_provenance version 1.', 'applicability': 'required'}
- started_at
- 1793818347.1238482
- verdict_report
- {'margin': 0.02}
Agent trace · 1 turn
Details
- run
- run_b4a6a042e6
- backend
- agentcore
- conversation
- run_b4a6a042e6
- experiment
- exp_71911f0fb9
- created
- Nov 4, 17:27 UTC
- started
- Nov 4, 18:52 UTC
- finished
- Nov 4, 18:59 UTC
- duration
- 7m 14s
- turns
- 1
- GPU-h budget
- 2.0
- wall budget
- 150 min
- base model
- Qwen/Qwen3.5-2B
- training
- SFT
- dataset
- no_robots
- result
- final
- outcome reason
- insufficient evidence
- run_07e69074dc weak evidence against 14h ago
- run_739abc9a8b weak evidence against 1d ago
- run_774cc21400 no result 2d ago
- run_38487faf1f no result 2d ago
- run_0ea02f3aa6 no result 2d ago