Skip to content

Commit 7095689

Browse files
committed
feat(v5-g10): optimizer state warm-start across sequential train runs
Closes V5-G10 / cppmega-mlx-jrp. Backend opts.run_id + LRU _RUN_CACHE (size 8) of opt.state. opts.continue_from_run_id loads cached state pre-train. extras.opt_state_carried + extras.run_id. UI checkbox surface deferred — backend exposure first, train-warm-start control TopBar follow-up. 3/3 pytest green.
1 parent 49baa0c commit 7095689

2 files changed

Lines changed: 85 additions & 0 deletions

File tree

cppmega_v4/runner/stages.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,19 @@ def loss_fn(model: nn.Module, emb: mx.array, tgt: mx.array) -> mx.array:
561561
pass
562562
all_modules = nn.Sequential(*modules, *lm_heads)
563563
opt, optimizer_kind = _build_optimizer(spec_optim, lr)
564+
# G10: optional optimizer state warm-start across sequential
565+
# Train runs. opts.continue_from_run_id refers to a prior
566+
# run cached in _RUN_CACHE. If hit, restore opt.state so the
567+
# second Train's losses[0] picks up where the first left off.
568+
opt_state_carried = False
569+
run_id = str(opts.get("run_id") or id(opt))
570+
continue_from = opts.get("continue_from_run_id")
571+
if continue_from and continue_from in _RUN_CACHE:
572+
try:
573+
opt.state = _RUN_CACHE[continue_from]
574+
opt_state_carried = True
575+
except Exception:
576+
pass
564577
# V4-9: when hybrid, count params routed to each bucket so e2e can
565578
# prove the split predicate actually saw 2D vs 1D/3D parameters.
566579
muon_group_size: int | None = None
@@ -725,6 +738,14 @@ def _count(tree: Any) -> int:
725738
except Exception:
726739
pass
727740

741+
# G10: cache opt.state for future warm-start lookups (capped LRU)
742+
try:
743+
_RUN_CACHE[run_id] = opt.state
744+
if len(_RUN_CACHE) > 8:
745+
_RUN_CACHE.pop(next(iter(_RUN_CACHE)))
746+
except Exception:
747+
pass
748+
728749
after_flat = dict(nn.utils.tree_flatten(all_modules.parameters()))
729750
delta = 0.0
730751
if probe_key is not None and probe_before is not None:
@@ -814,6 +835,8 @@ def _count(tree: Any) -> int:
814835
"train_dtype": train_dtype,
815836
"master_dtype": master_dtype,
816837
"fp8_active": fp8_active,
838+
"opt_state_carried": opt_state_carried,
839+
"run_id": run_id,
817840
"mtp": _compute_mtp_extras(
818841
all_modules, mtp_k, mtp_betas, vocab_size,
819842
batch, seq, hidden, targets,
@@ -877,6 +900,11 @@ def _tokenize_parquet_text(
877900
return [], None
878901

879902

903+
# G10: in-process LRU cache of opt.state by run_id. Used for warm-start
904+
# across sequential Train clicks in the same backend session.
905+
_RUN_CACHE: dict[str, Any] = {}
906+
907+
880908
_REWRITER_FACTORIES: dict[str, Callable[..., Any]] = {}
881909

882910

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""G10: optimizer state warm-start across sequential train runs."""
2+
3+
from __future__ import annotations
4+
5+
import pytest
6+
7+
from cppmega_v4.jsonrpc.schema import VerifyParams
8+
from cppmega_v4.runner import Pipeline, run_pipeline
9+
10+
11+
def _spec() -> VerifyParams:
12+
return VerifyParams.model_validate({
13+
"graph": {
14+
"nodes": [
15+
{"id": "attn", "kind": "attention", "params": {}},
16+
{"id": "mlp", "kind": "mlp",
17+
"params": {"intermediate_size": 64, "activation": "swiglu"}},
18+
],
19+
"edges": [{"src": "attn", "dst": "mlp"}],
20+
},
21+
"dim_env": {"B": 1, "S": 8, "H": 32, "nh": 2, "nkv": 1, "head_dim": 16},
22+
"loss": {"kind": "cross_entropy", "head_outputs": ["mlp"]},
23+
"optim": {"kind": "adamw",
24+
"groups": [{"matcher": "all", "lr": 1e-3,
25+
"weight_decay": 0.01, "betas": [0.9, 0.95]}]},
26+
})
27+
28+
29+
def _run(opts: dict) -> dict:
30+
report = run_pipeline(_spec(), Pipeline.from_dict({
31+
"stages": ["parse", "verify_build_spec", "build_model", "train"],
32+
"stage_options": {"train": opts},
33+
}))
34+
train = next(s for s in report.stages if s.name == "train")
35+
assert train.status == "ok", f"stage_train failed: {train.error}"
36+
return train.extras
37+
38+
39+
def test_first_run_no_carry():
40+
extras = _run({"num_steps": 2, "run_id": "run-A"})
41+
assert extras["opt_state_carried"] is False
42+
assert extras["run_id"] == "run-A"
43+
44+
45+
def test_continue_from_loads_carried_state():
46+
"""Two sequential runs: second sets continue_from_run_id=A → carried."""
47+
a = _run({"num_steps": 2, "run_id": "run-A2"})
48+
b = _run({"num_steps": 2, "run_id": "run-B2",
49+
"continue_from_run_id": "run-A2"})
50+
assert a["opt_state_carried"] is False
51+
assert b["opt_state_carried"] is True
52+
53+
54+
def test_continue_from_unknown_id_no_carry():
55+
extras = _run({"num_steps": 2, "run_id": "run-C",
56+
"continue_from_run_id": "nonexistent-id"})
57+
assert extras["opt_state_carried"] is False

0 commit comments

Comments
 (0)