Skip to content

Commit d71c482

Browse files
committed
docs(v3): deep e2e verification plan + spec (epic cppmega-mlx-y7f)
v2 audit found vacuous expect(["ok","fail"]).toContain(status) assertions in 11_ui_to_train.spec.ts + 3 backend bugs: B1 stage_train hardcodes optim.AdamW (UI optimizer choice ignored) B2 stage_train uses synthetic Gaussian embeds (tokenizer/parquet ignored) B3 RunResultModal hides extras (tests cannot assert against losses/lr/delta) v3 spec: every UI mutation that claims to change training behavior must produce an observable difference in training math, asserted from Playwright test reading UI-surfaced extras. 13 stages, no monkeypatch, work direct on main.
1 parent 4d55487 commit d71c482

2 files changed

Lines changed: 467 additions & 0 deletions

File tree

VisualBuilderPlan-v3.md

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
# VisualBuilderPlan-v3 — Deep E2E Verification (UI → API → Real Training Math)
2+
3+
**Status**: in-progress 2026-05-21 (epic `cppmega-mlx-y7f`)
4+
**Driver**: honest audit of v2 closure (bb0). v2 declared "1188 cells, 0 hard failures"
5+
but the 5 "deep" UI→train scenarios used vacuous `expect(["ok","fail"]).toContain(status)`
6+
assertions. The status was returned by `stage_train`; nothing proved the UI mutation
7+
**affected the trained model**. v3 closes that gap.
8+
9+
This plan supersedes v2 §8 acceptance criteria ONLY for the UI→training assertions;
10+
v2 stage delivery + 1188-cell breadth remains valid.
11+
12+
## 1. Why v3 exists
13+
14+
Audit on 2026-05-21 found 3 categories of unproven claims and 3 confirmed backend bugs:
15+
16+
### Confirmed backend bugs (v2 ignored these)
17+
18+
| ID | Bug | File | Effect |
19+
|-----|---------------------------------------------------------------|--------------------------------------------|-----------------------------------------------------------------------|
20+
| B1 | `stage_train` hardcodes `optim.AdamW` | `cppmega_v4/runner/stages.py:425` | UI OptimTab choice (Lion/Muon/Adam8bit) is silently ignored at train |
21+
| B2 | `stage_train` uses synthetic Gaussian embeddings | `cppmega_v4/runner/stages.py:447` | tokenizer + parquet selection in UI has zero effect on training input |
22+
| B3 | `RunResultModal` does not surface `extras` (losses, lr, delta)| `vbgui/src/components/RunResultModal.tsx` | UI hides training math from user; tests cannot assert against extras |
23+
24+
### Vacuous assertions in `11_ui_to_train.spec.ts`
25+
26+
| Test | Old assertion | Why vacuous |
27+
|--------------------------------|----------------------------------------------|------------------------------------------|
28+
| activation glu→swiglu | `expect(["ok","fail"]).toContain(status)` | passes even if activation ignored |
29+
| schedule linear_warmup w=4 | `await stage-train.waitFor()` (no content) | no check that lr_trajectory shape matches|
30+
| pre_norm rmsnorm→layernorm | `expect(["ok","fail"]).toContain(status)` | passes even if norm ignored |
31+
| Auto-group + Train | `expect(["ok","fail"]).toContain(status)` | passes even though B1 ignores optimizer |
32+
| AblationsTab Run | `rows >= 2` | passes even if all variants same loss |
33+
34+
### Cross-cut gaps not tested at all
35+
36+
- **Multi-step convergence**: matrix uses N=2 (proves non-NaN, not learning).
37+
- **Cross-architecture**: deep UI→train only run on `llama3_8b` (1/57 presets).
38+
- **Sharding application**: UI shows proposals, never asserted that train uses them.
39+
- **Memory match**: UI estimate vs real `mx.metal.get_peak_memory()` never compared.
40+
- **Gotcha gating**: critical gotcha should disable Train; not verified.
41+
- **Validator gating**: verify=error should block Train; not verified.
42+
- **Lifecycle**: WS reconnect mid-train, spec save/load roundtrip.
43+
44+
## 2. Goal
45+
46+
Every UI mutation that claims to change training behavior **must produce an observable
47+
difference in training math**, asserted from a Playwright test that reads the value
48+
out of UI-surfaced extras.
49+
50+
Test pattern (the only one acceptable from v3 onward):
51+
52+
```ts
53+
const { extras } = await trainResult(page);
54+
55+
expect(extras.model_summary.mlp_activation).toBe("swiglu"); // propagated
56+
expect(extras.losses.length).toBeGreaterThanOrEqual(5); // multi-step
57+
expect(extras.losses[0]).toBeGreaterThan(extras.losses[N-1]); // learned
58+
expect(extras.weight_delta_norm).toBeGreaterThan(1e-3); // moved
59+
expect(extras.lr_trajectory[0]).toBeCloseTo(base_lr * 0.25, 4); // schedule shape
60+
expect(extras.optimizer_kind).toBe("lion"); // B1 fixed
61+
```
62+
63+
No more `["ok","fail"]`. Status check is a sanity gate only; the **content** is what
64+
proves the claim.
65+
66+
## 3. Stages
67+
68+
13 stages in epic `cppmega-mlx-y7f`, each with bd ticket. Run in dependency order;
69+
do not advance to next until current is green.
70+
71+
| Stage | Ticket | Title | Type | Pri | Depends |
72+
|-------|-----------------|--------------------------------------------------------------------|---------|-----|---------|
73+
| V3-1 | cppmega-mlx-wly | stage_train honors OptimKind (B1 fix + extras.optimizer_kind) | bugfix | P0 ||
74+
| V3-2 | cppmega-mlx-4pp | stage_train consumes tokenizer+parquet from spec (B2 fix) | bugfix | P0 ||
75+
| V3-3 | cppmega-mlx-0js | stage_train extras includes model_summary | feature | P1 | V3-1 |
76+
| V3-4 | cppmega-mlx-xzp | RunResultModal surfaces extras with testids (B3 fix) | feature | P0 ||
77+
| V3-5 | cppmega-mlx-7g9 | Rewrite 11_ui_to_train.spec.ts with strict content assertions | task | P0 | V3-1,3,4|
78+
| V3-6 | cppmega-mlx-9wj | Multi-step convergence (12_train_convergence.spec.ts) | task | P1 | V3-3,4 |
79+
| V3-7 | cppmega-mlx-bws | Ablation loss-divergence (13_ablation_math.spec.ts) | task | P1 | V3-5 |
80+
| V3-8 | cppmega-mlx-cwz | Critical gotcha disables Train + assertion | feature | P1 | V3-4 |
81+
| V3-9 | cppmega-mlx-7ut | verify=error disables Train + assertion | feature | P1 | V3-4 |
82+
| V3-10 | cppmega-mlx-e0t | Roundtrip FAIL warning, does not block train | task | P2 | V3-4 |
83+
| V3-11 | cppmega-mlx-2ar | Cross-architecture deep verify (14_cross_arch_deep.spec.ts) | task | P1 | V3-5 |
84+
| V3-12 | cppmega-mlx-1wm | Spec save/load roundtrip → identical train extras | task | P2 | V3-5 |
85+
| V3-13 | cppmega-mlx-7yd | Closure report `tests/fixtures/e2e_matrix_v3_report.md` | doc | P3 | all |
86+
87+
Deferred (not in this epic, follow-up):
88+
- Sharding apply → real distributed train (needs multi-device, scope creep)
89+
- Memory peak comparison (requires actual `mx.metal.get_peak_memory` calls + tolerance)
90+
- WS reconnect mid-train (lifecycle, not "training math")
91+
92+
## 4. Per-stage acceptance criteria
93+
94+
### V3-1 stage_train honors OptimKind
95+
96+
- `stages.py` reads `spec.optim.groups[0].kind` and instantiates corresponding optimizer
97+
from `cppmega_mlx.training.optimizers` (AdamW / Lion / Lion8bit / Adam8bit / Muon /
98+
MuonAdamWHybrid / SGD)
99+
- extras gains `optimizer_kind: str` field
100+
- pytest: 7 new tests in `tests/v4/test_stage_train_optimizers.py`, one per OptimKind,
101+
asserts loss is finite and weight_delta > 0
102+
- regression: existing 2306+ pytest stays green
103+
104+
### V3-2 stage_train consumes data
105+
106+
- If `spec.data` has `tokenizer_path` + `parquet_path`, train loads first N tokens
107+
from parquet, tokenizes via the configured tokenizer, uses those token IDs as
108+
targets (and a learned `nn.Embedding(vocab, hidden)` as input embeds)
109+
- Falls back to synthetic embeds + random targets only when `spec.data` is None
110+
(backwards compat for matrix tests)
111+
- extras gains `data_source: "synthetic"|"parquet"` and `token_count: int`
112+
- pytest: 4 new tests in `tests/v4/test_stage_train_data.py` — synthetic path,
113+
parquet path, mismatched vocab raises clear error, tokenizer load failure surfaces
114+
115+
### V3-3 model_summary in extras
116+
117+
- `stage_train.extras["model_summary"]` is `{mlp_activation, attention_pre_norm,
118+
attention_post_norm, mlp_pre_norm, mlp_post_norm, optimizer_kind, schedule_kind,
119+
num_brick_kinds}`
120+
- Helper `_summarize_model(spec, optimizer)` in stages.py
121+
- pytest: 5 new tests asserting summary correctness for various configs
122+
123+
### V3-4 RunResultModal extras display
124+
125+
- For each stage with non-error extras, add a clickable disclosure row
126+
- Inside expand: data-testid'd cells for every extras key:
127+
- `run-result-extras-{stage}-{key}` for primitives
128+
- `run-result-extras-{stage}-losses-{i}` for arrays
129+
- `run-result-extras-{stage}-model-{field}` for model_summary
130+
- vitest: 6 new tests covering shape rendering
131+
132+
### V3-5 Rewrite 11_ui_to_train.spec.ts
133+
134+
Five scenarios, each with **at least 3 content assertions** beyond status row presence:
135+
136+
1. **Activation glu→swiglu**: extras.model_summary.mlp_activation === "swiglu",
137+
losses[0] !== losses[-1] (learning happened with the new activation).
138+
2. **Schedule linear_warmup w=4**: extras.lr_trajectory[0] === base_lr * 0.25,
139+
extras.lr_trajectory[3] === base_lr (warmup completed), extras.schedule_kind ===
140+
"linear_warmup".
141+
3. **pre_norm switch attention**: extras.model_summary.attention_pre_norm ===
142+
"layernorm", weight_delta_norm > 0.
143+
4. **Auto-group + optimizer change to Lion**: extras.optimizer_kind === "lion",
144+
losses finite (B1 fix verified end-to-end).
145+
5. **Optimizer change to Muon**: extras.optimizer_kind === "muon", weight_delta > 0
146+
(Muon != AdamW math regression).
147+
148+
### V3-6 multi-step convergence
149+
150+
- New test `12_train_convergence.spec.ts`
151+
- Drive N=8 step training via stage_options.train.num_steps in OptimTab
152+
- Assert `losses[0] > losses[7] * 1.05` (5% loss reduction floor)
153+
- 4 preset variants: llama3_8b, gpt2_small_124m, mistral_7b, tiny_aya_parallel
154+
155+
### V3-7 ablation divergence
156+
157+
- `13_ablation_math.spec.ts`
158+
- Activation axis: glu vs swiglu vs gelu, 4 steps each
159+
- Assert pairwise `|final_loss_a - final_loss_b| > 1e-3` for at least one pair
160+
- Optimizer axis: adamw vs lion → assert weight_delta_norm differs by >10%
161+
162+
### V3-8 Critical gotcha gates Train
163+
164+
- Force a gotcha-critical config (e.g., loss type incompatible with brick output)
165+
- UI: `top-bar-train` button becomes disabled, tooltip "critical gotcha blocks train"
166+
- Test asserts disabled state + tooltip
167+
168+
### V3-9 verify=error gates Train
169+
170+
- Same pattern with a validator error (e.g., parallel-block requires pre_norm)
171+
- UI: train disabled, banner shows verify error count
172+
173+
### V3-10 roundtrip FAIL warning
174+
175+
- Force a parquet/tokenizer roundtrip mismatch
176+
- UI shows yellow banner, but Train remains enabled (training is robust to roundtrip
177+
fail since synthetic targets bypass it)
178+
- Assert banner + train still works (status="ok")
179+
180+
### V3-11 cross-architecture deep verify
181+
182+
- 6 family-reps × 3 mutations = 18 scenarios in `14_cross_arch_deep.spec.ts`
183+
- Family reps: llama3_8b, mistral_7b, gpt2_small_124m, qwen3_8b, gemma2_2b, deepseek_v3
184+
- Mutations: activation, schedule, optimizer
185+
- Assert same strict content as V3-5
186+
187+
### V3-12 save/load roundtrip
188+
189+
- Build spec in UI → Save → Load → Train → assert extras identical to first run
190+
- Tests load → save → diff JSON should be empty
191+
192+
### V3-13 Closure report
193+
194+
- ≤150 lines markdown report mirroring v2 layout
195+
- Stage table with commits, before/after assertion counts, regression totals
196+
197+
## 5. Workflow per stage (Goal directive)
198+
199+
1. `bd update <id> --claim` → in_progress
200+
2. Implement (smallest atomic change)
201+
3. Code review pass (Self-review via diff; gsd-code-reviewer only if >100 LOC or
202+
touches >3 files)
203+
4. Perf check (no >5% regression in pytest wall-clock for affected suite)
204+
5. Regression (pytest + vitest + playwright affected files)
205+
6. `git add` specific files (no `-A`) → commit → push to origin/main
206+
7. `bd close <id>` only after push verified
207+
208+
## 6. Pre-existing red (excluded from regression gates)
209+
210+
- `tests/v4/test_path_d_runtime_adapter.py::test_gdn_chunk_o_metal_threadgroup_memory_fits_device_limit`
211+
(61440 > 32K limit, parallel agent's WIP in `path_c_fusion.py`, not owned by this work)
212+
213+
## 7. Done definition
214+
215+
All 13 V3 tickets closed. Playwright `11/12/13/14_*.spec.ts` green with strict
216+
assertions (no `["ok","fail"]` in `vbgui/e2e/scenarios/1[1-4]_*.spec.ts`).
217+
Closure doc at `tests/fixtures/e2e_matrix_v3_report.md`.

0 commit comments

Comments
 (0)