Modal Research Exchange

you are

dimuon-lite vs AdamW for LoRA SFT on no_robots (Qwen3.5-0.8B, rank 32, 100 steps)

Failed no result Devin Qwen/Qwen3.5-0.8B SFT 1.5 GPU-h budget started 23h ago 46m 07s
Redo
Pick where the agent works.
View session in Devin ↗

A Spindle fork (app spindle-dimuon-fork, Qwen/Qwen3.5-2B because 0.8B is not served) accepted and ran dimuon-lite optim_steps, but the 6-run LR sweep was stopped at step 5-6 of 100 because the measured ~37 s per step on a 4-GPU trainer far exceeds the 1.5 GPU-hour budget, so no test NLL or IFEval numbers exist. In the first steps dimuon train NLL barely changed across a 10x LR range while AdamW's fell; the apps, secrets, volumes, and proxy tokens were cleaned up, but deleting the empty spindle-dimuon-fork environment needs a workspace admin.

No result No result: the run finished without measuring the effect.

Result

No arm results yet.

steps_completed_adam_lr2e-4
6
steps_completed_dimuon_lr1e-4
7
train_nll_step0_all
2.436
train_nll_step5_adam_lr5e-5
2.237
train_nll_step5_adam_lr1e-4
2.12
train_nll_step5_adam_lr2e-4
2.1
train_nll_step5_dimuon_lr1e-4
2.765
train_nll_step5_dimuon_lr3e-4
2.761
train_nll_step5_dimuon_lr1e-3
2.72
sweep_wall_seconds_when_stopped
687
Changes from the spec 0

Not recorded: this run didn't report its changes from the spec, so they are unknown.

Checks · activation unverified · evaluator unknown · feasibility unknown
Custom implementation correctness: unverified
Correctness preflight was not recorded.
Validates executor-reported evidence; autonomous training calls are not blocked by this server.
Treatment activation: unverified
The spec has no activation gate or experiment features (it predates them), so whether the treatment engaged is unknown.
Initialization / optimizer verification: unverified
Initialization and optimizer evidence was not required by this saved spec.
Research decision: unknown
No prespecified decision rule was recorded.
Adaptation batches across paired arms: unknown (not verified)
The spec does not declare whether this check applies, and the run did not verify it. The run did not report adaptation batch manifests.
Evaluator comparability: unknown
Evaluator provenance was not recorded.
Generator reward audit
Generator reward audit: Unknown whether this run needed a teacher reward audit: its spec does not declare whether a teacher or task generator is trained. Nothing was recorded, so reward contributions are unknown.
Held-out loss aggregation
Spec asks for token mean (summed NLL / loss tokens) · run reported no record, so the aggregation behind its loss numbers is unknown
Feasibility projection: unknown
Projected costs, not measured GPU usage.
Measured feasibility was not recorded for this run.
Generated task audit: unverified: no audit recorded
Answer agreement with recorded independent solutions does not establish semantic correctness. Normalized exact overlap does not detect paraphrases.
Spec exp_1e07d824d1
Instructions given to Devin
The previous attempt (run_38487faf1f) found that the shared Spindle server rejects dimuon-lite: optim_step accepts only adam_params. You may fork Spindle to add it: - Clone https://github.com/modal-projects/spindle and add dimuon-lite as an optimizer in your local copy (see src/spindle/backends/megatron_runtime/lora/optimizer.py and engine/operations.py for where adam_params is handled). Keep the change small. - Deploy your fork as your own throwaway Modal app with a distinct name (e.g. spindle-dimuon-fork), following the Spindle README ("Set up the server", `spindle deploy`), and point only this run at it: run BOTH arms (AdamW and dimuon-lite) on the fork so the comparison is fair. - Do not push to, redeploy, reconfigure, or stop the shared Spindle deployment, and do not open PRs or issues on the Spindle repo. If dimuon support looks worth upstreaming, propose it in your report instead. - The score-centering redo failed on "trainer capacity exhausted"; if your fork hits a capacity limit too, say so in the summary rather than guessing. - In your report, include the fork's diff (or commit), the app name you deployed, and the commands you ran. - When the run is done (success or failure), stop your forked app.

Hypothesis

With each optimizer at its best learning rate from a 3-point sweep, dimuon-lite reaches a held-out no_robots test NLL at least 0.02 nats/token lower than AdamW after 100 steps of LoRA SFT, without lowering IFEval prompt-level strict accuracy by more than 2 points.

Success criterion

Validated if the two-seed mean test NLL of best-LR dimuon-lite is at least 0.02 nats/token below best-LR AdamW, the seed-to-seed spread of each arm is below 0.01, and IFEval strict accuracy for dimuon-lite is no more than 2.0 points below AdamW. Failed if the NLL gap is under 0.02 in either direction (no meaningful difference) or AdamW is better. Inconclusive if seed spread exceeds the gap.

no_robots 100 steps batch 64 LoRA r32 lr 0.0003 120 min wall budget
Method
Train a rank-32 LoRA on Qwen/Qwen3.5-0.8B with next-token cross-entropy on assistant tokens of HuggingFaceH4/no_robots train. Use one fixed shuffle (seed 0) and the cookbook's recommended renderer for the model. Max length is 2048, batch size 64, and training runs 100 optimizer steps at a constant learning rate. The single decision under test is the optimizer. Because the two optimizers have different natural LR scales, each gets a 3-point LR sweep: AdamW at {5e-5, 1e-4, 2e-4} and dimuon-lite at {1e-4, 3e-4, 1e-3}. That is 6 runs on identical batches in identical order. After step 100, compute token-weighted NLL on the first 500 examples of the no_robots test split via forward with cross_entropy. Pick the best LR per optimizer by test NLL, re-run that config once with a second data-shuffle seed (seed 1) to estimate seed noise, and report the mean. For the two best checkpoints, sample greedily (max_tokens 1024) on all 541 google/IFEval prompts and score prompt-level strict accuracy with the standard IFEval checker, client-side. Also log train-loss curves for both arms to compare convergence speed.
Baseline
AdamW via tinker.AdamParams (default betas), LoRA rank 32 on Qwen/Qwen3.5-0.8B, best of LR {5e-5, 1e-4, 2e-4}. Same data order, batch size 64, 100 steps, constant LR, max length 2048, renderer, eval set and second-seed rerun as the dimuon-lite arm.
Full spec
{
  "base_model": "Qwen/Qwen3.5-0.8B",
  "baseline": "AdamW via tinker.AdamParams (default betas), LoRA rank 32 on Qwen/Qwen3.5-0.8B, best of LR {5e-5, 1e-4, 2e-4}. Same data order, batch size 64, 100 steps, constant LR, max length 2048, renderer, eval set and second-seed rerun as the dimuon-lite arm.",
  "batch_size": 64,
  "budget": {
    "gpu_hours": 1.5,
    "max_wall_minutes": 120
  },
  "command": "# Custom part: optimizer selection (DimuonOptimizerConfig/DimuonParams vs AdamParams). Everything else is stock tinker SDK + cookbook data utils.\nimport os, random, datasets, tinker\nfrom tinker_cookbook import renderers, model_info\nfrom tinker_cookbook.tokenizer_utils import get_tokenizer\nfrom tinker_cookbook.supervised.data import conversation_to_datum\nM = 'Qwen/Qwen3.5-0.8B'; tok = get_tokenizer(M)\nr = renderers.get_renderer(model_info.get_recommended_renderer_name(M), tok)\nds = datasets.load_dataset('HuggingFaceH4/no_robots')\ntest = [conversation_to_datum(x['messages'], r, 2048) for x in ds['test'].select(range(500))]\nsc = tinker.ServiceClient(base_url=os.environ['TINKER_BASE_URL'])\ndef run(opt, lr, seed=0, steps=100, bs=64):\n    rows = list(ds['train']); random.Random(seed).shuffle(rows)\n    train = [conversation_to_datum(x['messages'], r, 2048) for x in rows[:steps*bs]]\n    if opt == 'dimuon':\n        tc = sc.create_lora_training_client(base_model=M, rank=32, optimizer=tinker.types.DimuonOptimizerConfig())\n        p = tinker.types.DimuonParams(learning_rate=lr, beta1=0.9, grad_clip_norm=0.0)\n    else:\n        tc = sc.create_lora_training_client(base_model=M, rank=32)\n        p = tinker.AdamParams(learning_rate=lr)\n    for s in range(steps):\n        tc.forward_backward(train[s*bs:(s+1)*bs], 'cross_entropy').result()\n        tc.optim_step(p).result()\n    out = tc.forward(test, 'cross_entropy').result()\n    num = sum(-(o['logprobs'].to_torch() * d.loss_fn_inputs['weights'].to_torch()).sum().item() for o, d in zip(out.loss_fn_outputs, test))\n    den = sum(d.loss_fn_inputs['weights'].to_torch().sum().item() for d in test)\n    path = tc.save_weights_for_sampler(name=f'{opt}-{lr}-s{seed}').result().path\n    print(opt, lr, seed, 'test_nll', num/den, path)\nfor lr in [5e-5, 1e-4, 2e-4]: run('adam', lr)\nfor lr in [1e-4, 3e-4, 1e-3]: run('dimuon', lr)\n# then: rerun best LR per optimizer with seed=1; sample IFEval (google/IFEval, 541 prompts, greedy, max_tokens=1024) from the saved sampler paths via sc.create_sampling_client(model_path=path) and score prompt-level strict accuracy",
  "dataset": {
    "hf_path": "HuggingFaceH4/no_robots",
    "n_examples": 6400,
    "name": "no_robots",
    "split": "train (eval on test, first 500)"
  },
  "eval": {
    "benchmark": "no_robots test held-out NLL (primary); IFEval (secondary)",
    "metric": "token-weighted NLL in nats/token on 500 test examples; IFEval prompt-level strict accuracy on 541 prompts",
    "n_examples": 500
  },
  "hypothesis": "With each optimizer at its best learning rate from a 3-point sweep, dimuon-lite reaches a held-out no_robots test NLL at least 0.02 nats/token lower than AdamW after 100 steps of LoRA SFT, without lowering IFEval prompt-level strict accuracy by more than 2 points.",
  "lora_rank": 32,
  "max_seq_len": 2048,
  "method": "Train a rank-32 LoRA on Qwen/Qwen3.5-0.8B with next-token cross-entropy on assistant tokens of HuggingFaceH4/no_robots train. Use one fixed shuffle (seed 0) and the cookbook's recommended renderer for the model. Max length is 2048, batch size 64, and training runs 100 optimizer steps at a constant learning rate. The single decision under test is the optimizer. Because the two optimizers have different natural LR scales, each gets a 3-point LR sweep: AdamW at {5e-5, 1e-4, 2e-4} and dimuon-lite at {1e-4, 3e-4, 1e-3}. That is 6 runs on identical batches in identical order. After step 100, compute token-weighted NLL on the first 500 examples of the no_robots test split via forward with cross_entropy. Pick the best LR per optimizer by test NLL, re-run that config once with a second data-shuffle seed (seed 1) to estimate seed noise, and report the mean. For the two best checkpoints, sample greedily (max_tokens 1024) on all 541 google/IFEval prompts and score prompt-level strict accuracy with the standard IFEval checker, client-side. Also log train-loss curves for both arms to compare convergence speed.",
  "optimizer": {
    "lr": 0.0003,
    "name": "dimuon-lite"
  },
  "steps": 100,
  "success_criterion": "Validated if the two-seed mean test NLL of best-LR dimuon-lite is at least 0.02 nats/token below best-LR AdamW, the seed-to-seed spread of each arm is below 0.01, and IFEval strict accuracy for dimuon-lite is no more than 2.0 points below AdamW. Failed if the NLL gap is under 0.02 in either direction (no meaningful difference) or AdamW is better. Inconclusive if seed spread exceeds the gap.",
  "title": "dimuon-lite vs AdamW for LoRA SFT on no_robots (Qwen3.5-0.8B, rank 32, 100 steps)",
  "training_type": "sft"
}
Evidence

Unknown: this run has no recorded evidence bundle.

All metrics
steps_completed_adam_lr2e-4
6
steps_completed_dimuon_lr1e-4
7
train_nll_step0_all
2.436
train_nll_step5_adam_lr5e-5
2.237
train_nll_step5_adam_lr1e-4
2.12
train_nll_step5_adam_lr2e-4
2.1
train_nll_step5_dimuon_lr1e-4
2.765
train_nll_step5_dimuon_lr3e-4
2.761
train_nll_step5_dimuon_lr1e-3
2.72
sweep_wall_seconds_when_stopped
687
Agent trace full trace with commands in Devin ↗
Details
run
run_774cc21400
backend
Devin
session
devin-1b0dc5e586364d578aa9e5a2f6c87937
experiment
exp_1e07d824d1
created
Nov 3, 04:42 UTC
started
Nov 3, 04:42 UTC
finished
Nov 3, 05:28 UTC
duration
46m 07s
GPU-h budget
1.5
wall budget
120 min
base model
Qwen/Qwen3.5-0.8B
training
SFT
dataset
no_robots
Other runs of this idea

Submit an idea

Most ideas come in from Slack threads. Use this when yours is not in one yet; the triage agent picks it up.