Skip to content

Commit 629e3e7

Browse files
committed
feat(v7-h09): loss-surface explorer backend (lr × wd grid sweep)
Closes V7-H09 (cppmega-mlx-rzyz) backend: cppmega_v4.jsonrpc. loss_surface_method.loss_surface_run runs an N×M grid of short k-step Train evals, mutating per-cell (lr_mult, wd_mult) around the current schedule, returns LossSurfaceResult{rows: N×M of LossSurfaceCell{final_loss, throughput_tok_s, mem_mb, elapsed_ms, status}, best_lr_mult, best_wd_mult, best_loss}. Tests (tests/v4/test_loss_surface.py): 2/2 — * 3×3 grid returns 9 cells all status="ok" with positive elapsed_ms + final_loss + best cell surfaced. * Throughput positive on ok cells. LossSurfaceModal UI heatmap + iso-contours + Apply-best button + Playwright scenario + WS progress streaming (H05 composition) + H06 cancel composition are V7-H09-UI follow-up sub-tasks.
1 parent be6306b commit 629e3e7

2 files changed

Lines changed: 187 additions & 0 deletions

File tree

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
"""V7-H09: explore.loss_surface RPC — N×M (lr_delta × wd_delta) sweep.
2+
3+
Runs a grid of short k-step Train evaluations around the current
4+
schedule's lr / wd, returns a 2D matrix of {final_loss, throughput,
5+
mem_mb} cells the UI renders as a heatmap.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
import copy
11+
import time
12+
from typing import Any
13+
14+
from pydantic import BaseModel, ConfigDict, Field
15+
16+
from cppmega_v4.jsonrpc.schema import VerifyParams
17+
from cppmega_v4.runner import Pipeline, run_pipeline
18+
19+
20+
class LossSurfaceParams(BaseModel):
21+
model_config = ConfigDict(extra="forbid")
22+
23+
spec: VerifyParams
24+
lr_deltas: list[float] = Field(default_factory=lambda: [0.5, 1.0, 2.0])
25+
wd_deltas: list[float] = Field(default_factory=lambda: [0.5, 1.0, 2.0])
26+
k_steps: int = Field(2, ge=1, le=64)
27+
28+
29+
class LossSurfaceCell(BaseModel):
30+
model_config = ConfigDict(extra="allow")
31+
32+
lr_mult: float
33+
wd_mult: float
34+
status: str
35+
final_loss: float | None = None
36+
throughput_tok_s: float | None = None
37+
mem_mb: float | None = None
38+
elapsed_ms: float = 0.0
39+
40+
41+
class LossSurfaceResult(BaseModel):
42+
model_config = ConfigDict(extra="allow")
43+
44+
rows: list[list[LossSurfaceCell]]
45+
lr_deltas: list[float]
46+
wd_deltas: list[float]
47+
best_lr_mult: float | None = None
48+
best_wd_mult: float | None = None
49+
best_loss: float | None = None
50+
51+
52+
def _mutate(spec_dict: dict, *, lr_mult: float,
53+
wd_mult: float) -> dict:
54+
out = copy.deepcopy(spec_dict)
55+
opt = out.get("optim", {})
56+
for g in opt.get("groups", []):
57+
if "lr" in g:
58+
g["lr"] = float(g["lr"]) * lr_mult
59+
if "weight_decay" in g:
60+
g["weight_decay"] = float(g["weight_decay"]) * wd_mult
61+
return out
62+
63+
64+
def loss_surface_run(params: LossSurfaceParams,
65+
*, cache: Any | None = None
66+
) -> LossSurfaceResult:
67+
spec_dict = params.spec.model_dump()
68+
rows: list[list[LossSurfaceCell]] = []
69+
best: tuple[float, float, float] | None = None
70+
for lr_m in params.lr_deltas:
71+
row: list[LossSurfaceCell] = []
72+
for wd_m in params.wd_deltas:
73+
t0 = time.perf_counter()
74+
try:
75+
mutated = VerifyParams.model_validate(
76+
_mutate(spec_dict, lr_mult=lr_m, wd_mult=wd_m))
77+
rep = run_pipeline(mutated, Pipeline.from_dict({
78+
"stages": ["parse", "verify_build_spec",
79+
"build_model", "train"],
80+
"stage_options": {"train": {
81+
"num_steps": params.k_steps}},
82+
}))
83+
tr = next(s for s in rep.stages if s.name == "train")
84+
if tr.status != "ok":
85+
row.append(LossSurfaceCell(
86+
lr_mult=lr_m, wd_mult=wd_m, status="fail",
87+
elapsed_ms=(time.perf_counter() - t0) * 1000))
88+
continue
89+
losses = tr.extras.get("losses", [])
90+
final = float(losses[-1]) if losses else None
91+
mem = tr.extras.get("memory_peak_bytes")
92+
mem_mb = (round(int(mem) / (1024 * 1024), 4)
93+
if mem else None)
94+
elapsed_ms = (time.perf_counter() - t0) * 1000
95+
throughput = (
96+
params.k_steps * 1000.0 / max(elapsed_ms, 1e-3))
97+
cell = LossSurfaceCell(
98+
lr_mult=lr_m, wd_mult=wd_m, status="ok",
99+
final_loss=final, throughput_tok_s=throughput,
100+
mem_mb=mem_mb, elapsed_ms=elapsed_ms)
101+
row.append(cell)
102+
if final is not None and (
103+
best is None or final < best[2]):
104+
best = (lr_m, wd_m, final)
105+
except Exception as exc:
106+
row.append(LossSurfaceCell(
107+
lr_mult=lr_m, wd_mult=wd_m,
108+
status=f"fail:{type(exc).__name__}",
109+
elapsed_ms=(time.perf_counter() - t0) * 1000))
110+
rows.append(row)
111+
return LossSurfaceResult(
112+
rows=rows,
113+
lr_deltas=params.lr_deltas,
114+
wd_deltas=params.wd_deltas,
115+
best_lr_mult=best[0] if best else None,
116+
best_wd_mult=best[1] if best else None,
117+
best_loss=best[2] if best else None,
118+
)
119+
120+
121+
__all__ = ["LossSurfaceParams", "LossSurfaceResult",
122+
"LossSurfaceCell", "loss_surface_run"]

tests/v4/test_loss_surface.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""V7-H09: loss-surface (lr × wd) sweep."""
2+
3+
from __future__ import annotations
4+
5+
import pytest
6+
7+
from cppmega_v4.jsonrpc.loss_surface_method import (
8+
LossSurfaceParams, loss_surface_run,
9+
)
10+
from cppmega_v4.jsonrpc.schema import VerifyParams
11+
12+
13+
def _spec() -> VerifyParams:
14+
return VerifyParams.model_validate({
15+
"graph": {
16+
"nodes": [
17+
{"id": "attn", "kind": "attention",
18+
"params": {"num_heads": 4, "head_dim": 64}},
19+
{"id": "mlp", "kind": "mlp", "params": {}},
20+
],
21+
"edges": [{"src": "attn", "dst": "mlp"}],
22+
},
23+
"dim_env": {"B": 1, "S": 8, "H": 128,
24+
"nh": 2, "nkv": 1, "head_dim": 64},
25+
"loss": {"kind": "cross_entropy", "head_outputs": ["mlp"]},
26+
"optim": {"kind": "adamw",
27+
"groups": [{"matcher": "all", "lr": 1e-3,
28+
"weight_decay": 0.01,
29+
"betas": [0.9, 0.95]}]},
30+
})
31+
32+
33+
def test_v7_h09_3x3_grid_returns_matrix_with_best():
34+
r = loss_surface_run(LossSurfaceParams(
35+
spec=_spec(),
36+
lr_deltas=[0.5, 1.0, 2.0],
37+
wd_deltas=[0.5, 1.0, 2.0],
38+
k_steps=2,
39+
))
40+
assert len(r.rows) == 3
41+
for row in r.rows:
42+
assert len(row) == 3
43+
for cell in row:
44+
assert cell.lr_mult in (0.5, 1.0, 2.0)
45+
assert cell.wd_mult in (0.5, 1.0, 2.0)
46+
assert cell.status == "ok"
47+
assert cell.final_loss is not None
48+
assert cell.elapsed_ms > 0
49+
# Best cell surfaced.
50+
assert r.best_lr_mult in (0.5, 1.0, 2.0)
51+
assert r.best_wd_mult in (0.5, 1.0, 2.0)
52+
assert r.best_loss is not None
53+
54+
55+
def test_v7_h09_throughput_positive_on_ok_cells():
56+
r = loss_surface_run(LossSurfaceParams(
57+
spec=_spec(),
58+
lr_deltas=[1.0],
59+
wd_deltas=[1.0],
60+
k_steps=2,
61+
))
62+
cell = r.rows[0][0]
63+
assert cell.status == "ok"
64+
assert cell.throughput_tok_s is not None
65+
assert cell.throughput_tok_s > 0

0 commit comments

Comments
 (0)