Skip to content

Commit d844270

Browse files
committed
docs(v7-q): VisualBuilderPlan + Spec v7 — UI→API→Training E2E closure
Companion docs to docs/UI-TO-TRAIN-AUDIT-2026-05-23.md following the same V5/V6 plan+spec convention. Plan v7 (8 Q-gaps, ~24 sub-tickets): Q01 (P0) DataInspector file picker → cppmega-mlx-zcbs Q02 (P0) Edge validation in FlowCanvas → cppmega-mlx-ofd2 Q03 (P1) Checkpoint history UI + toggles → cppmega-mlx-9avx Q04 (P1) 5 Raschka gallery preset gaps → cppmega-mlx-2d47 Q05 (P1) smoke_zero1 CLI → cppmega-mlx-bfbi Q06 (P2) Train UI polish (B/S, nesting) → cppmega-mlx-sbz8 Q07 (P2) E2E coverage gaps → cppmega-mlx-x4vd Q08 (P3) Late warnings + multi-node note → cppmega-mlx-bpge Spec v7 covers: - RPC contract deltas: POST /upload/parquet, ckpt.list_history, ckpt.trigger WS, catalog.list_options('compatible_edges'), preview_parquet +tokenizer_source/roundtrip_pass_rate - stage_train extras additions (loss_scaler_overflows flat, plasticity/mtp/ifim/mhc styled keys) - Backend code stubs for each gap - Frontend testid contract (13 new testids) - Test file layout (9 pytest + 7 vitest + 6 Playwright) - Honest categorisation criteria (same as v6) Target counters: pytest 2540→2570, vitest 428→440, Playwright 1170→1175, Raschka 66/71→71/71, e2e extras 7/16→16/16, P0/P1 gaps 2/3→0/0.
1 parent faeab31 commit d844270

2 files changed

Lines changed: 690 additions & 0 deletions

File tree

VisualBuilderPlan-v7.md

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# VisualBuilderPlan-v7 — UI → API → Training End-to-End Closure
2+
3+
**Status**: planned 2026-05-23 (epic `cppmega-mlx-v7-q`)
4+
5+
**Driver**: 8-lane parallel audit (Lanes 1–8: Gallery, Canvas, Tokenizer, Data,
6+
Training, Checkpoints, Optim, Distributed) against the operator promise from
7+
the Raschka LLM gallery walkthrough. Audit report at
8+
`docs/UI-TO-TRAIN-AUDIT-2026-05-23.md`. Found 5 production-blocking gaps + 3
9+
polish + 2 coverage; this plan closes them.
10+
11+
## bd ticket ID mapping (post-creation)
12+
13+
| Q# | bd id | Title |
14+
|------|-----------------|-------------------------------------------------------|
15+
| Q01 | cppmega-mlx-zcbs | DataInspector file picker (P0 BLOCKING, Lane 4) |
16+
| Q02 | cppmega-mlx-ofd2 | Edge validation in FlowCanvas (P0 BLOCKING, Lane 2) |
17+
| Q03 | cppmega-mlx-9avx | Checkpoint history UI + compress/strict (P1, Lane 6) |
18+
| Q04 | cppmega-mlx-2d47 | Close 5 Raschka gallery preset gaps (P1, Lane 1) |
19+
| Q05 | cppmega-mlx-bfbi | smoke_zero1 CLI implementation (P1, Lane 8) |
20+
| Q06 | cppmega-mlx-sbz8 | Train UI polish — B/S + nesting + plasticity (P2) |
21+
| Q07 | cppmega-mlx-x4vd | E2E coverage gaps — extras + edge + adapter (P2) |
22+
| Q08 | cppmega-mlx-bpge | Late warnings + multi-node receipt (P3) |
23+
24+
V7-Q is intentionally smaller than V5/V6 (8 epics, ~24 sub-tickets vs V6's
25+
25 gaps / 190 sub-tickets) because the closure surface is narrower —
26+
V5/V6 wired the *math*; V7-Q wires the *operator UX* on top.
27+
28+
## 1. Why v7-Q exists
29+
30+
V6 closed the backend-ready → UI-wired → real-math triangle. The honest
31+
audit pass that followed (`docs/UI-TO-TRAIN-AUDIT-2026-05-23.md`) walked
32+
the Raschka-gallery operator promise step-by-step and found that the
33+
*math* paths work end-to-end, but several *operator surfaces* are stub
34+
or text-only:
35+
36+
### Block A — File system access (P0 BLOCKING, 1 gap)
37+
The promise "user uploads a parquet file (or picks one from the
38+
matrix)" is half-shipped: matrix-pick works, upload doesn't.
39+
40+
| Q# | Audit lane | UI work needed |
41+
|------|-----------|----------------------------------------------------------|
42+
| Q01 | Lane 4 | `<input type="file">` + `POST /upload/parquet` endpoint |
43+
44+
### Block B — Compatibility-at-edit (P0 BLOCKING, 1 gap)
45+
`FlowCanvas` accepts `isValidConnection?: IsValidConnection` (line 20)
46+
but `App.tsx:1215` never passes it. Incompatible drops succeed silently
47+
and fail at server verify. Client-side rejection contract exists in the
48+
React Flow prop but is unused.
49+
50+
| Q# | Audit lane | UI work needed |
51+
|------|-----------|----------------------------------------------------------|
52+
| Q02 | Lane 2 | Pass `isValidConnection` from App, derive set from `catalog.list_options('compatible_edges')` |
53+
54+
### Block C — Operator ergonomics (P1, 3 gaps)
55+
Backend supports save/load with arch-hash, compression, strict modes,
56+
mid-run trigger; UI surfaces only the two raw text paths. 5 of 71
57+
Raschka entries have bricks but no preset factory. `smoke_zero1` CLI
58+
documented but unimplemented.
59+
60+
| Q# | Audit lane | UI/CLI work needed |
61+
|------|-----------|-----------------------------------------------------------|
62+
| Q03 | Lane 6 | `ckpt.list_history` RPC + dropdown + 3 toggles + V7-H05 WS |
63+
| Q04 | Lane 1 | 4 preset factories: abs_pos_embed / mlstm / per_layer_embed; tiny_aya wire-in |
64+
| Q05 | Lane 8 | `cppmega_mlx.cli.smoke_zero1` + mlx.launch wrapper + receipt regression |
65+
66+
### Block D — UX polish (P2, 2 gaps)
67+
B and S not exposed as train controls. `loss_scaler.overflow_steps`
68+
nests under `loss_scaler` dict in backend but UI reads flat
69+
`extras.loss_scaler_overflows` — silent mismatch, overflow markers
70+
don't render. Plasticity / MTP / IFIM / MHC extras flow through but
71+
have no styled panel.
72+
73+
| Q# | Audit lane | Work |
74+
|------|-----------|-----------------------------------------------------------|
75+
| Q06 | Lane 5 | B + S inputs; fix nesting at `stages.py:2245`; add 4 styled sub-panels |
76+
77+
### Block E — Coverage gaps (P2, 1 gap)
78+
`readTrainExtras` reads 7/16 promised keys; no e2e for manual edge
79+
connect or GotchasTab adapter suggestions.
80+
81+
| Q# | Audit lane | Work |
82+
|------|-----------|-----------------------------------------------------------|
83+
| Q07 | Lane 2+5 | Extend readTrainExtras + 2 new Playwright specs |
84+
85+
### Block F — Defer / external blocker (P3, 1 gap)
86+
Tokenizer compatibility check at preview time, peak-RSS regression at
87+
real >1 GiB checkpoint, multi-node throughput receipt. The last one is
88+
**BLOCKED** on peer-48 hardware (external).
89+
90+
| Q# | Audit lane | Work |
91+
|------|-----------|-----------------------------------------------------------|
92+
| Q08 | Lane 4+6+8 | tokenizer-at-preview, >1GB peak-RSS test, multi-node note |
93+
94+
## 2. Goal
95+
96+
After v7-Q every step of the operator promise from
97+
`docs/UI-TO-TRAIN-AUDIT-2026-05-23.md §"happy path"` works without
98+
copy-paste of paths, without silent shape-contract failures, and with
99+
71/71 Raschka gallery coverage. The honest audit is re-runnable and
100+
returns zero P0/P1 gaps.
101+
102+
## 3. Stages — 8 Q-epics × 3–5 sub-tasks ≈ 24 sub-tickets
103+
104+
```
105+
cppmega-mlx-v7-q (epic)
106+
├── Q01 file picker (P0, 3 sub)
107+
├── Q02 edge validation (P0, 3 sub)
108+
├── Q03 ckpt history (P1, 4 sub)
109+
├── Q04 5 gallery presets (P1, 4 sub)
110+
├── Q05 smoke_zero1 CLI (P1, 3 sub)
111+
├── Q06 train UI polish (P2, 3 sub)
112+
├── Q07 e2e coverage (P2, 3 sub)
113+
└── Q08 late warnings (P3, 3 sub)
114+
115+
Total: ~24 sub-tickets (much smaller than V6 because closure surface
116+
is narrow — V7-Q is the "polish on top of V6's wiring" pass)
117+
```
118+
119+
## 4. Per-gap acceptance + sub-task breakdown
120+
121+
### Q01 — DataInspector file picker (P0 BLOCKING, 3)
122+
- Q01.1 Backend `POST /upload/parquet` endpoint in `cppmega_v4/jsonrpc/server.py` with size-cap 2 GiB, suffix-validate, write to `${VBGUI_UPLOAD_DIR:-/tmp/vbgui_uploads}/<uuid>.parquet`, return `{"path": str}`. TTL cleanup 24 h.
123+
- Q01.2 UI: `<input type="file" accept=".parquet">` in `DataInspector.tsx:194-198` with testid `data-inspector-file-upload`. On change: `FileReader.readAsArrayBuffer` → POST → set text field to returned path.
124+
- Q01.3 Tests: vitest mock fetch + path-set; pytest `TestClient` POST a 1 KiB synthetic parquet; Playwright `page.setInputFiles` on the testid, assert preview RPC fires with the returned path.
125+
126+
### Q02 — Edge validation in FlowCanvas (P0 BLOCKING, 3)
127+
- Q02.1 Backend `catalog.list_options("compatible_edges")` returns `[{src_kind, dst_kind}]` derived from `cppmega_v4/buildspec/shape_contract.SHAPE_CONTRACTS`.
128+
- Q02.2 UI: `App.tsx` on preset/brick changes pulls compatible-edges set once; closures `validateEdge({source, target})``contractSet.has(`${srcKind}→${dstKind}`)`. Pass `isValidConnection={validateEdge}` to FlowCanvas at line 1215.
129+
- Q02.3 Tests: vitest renders FlowCanvas with `isValidConnection=()=>false`, attempts onConnect, asserts edge not added; pytest catalog endpoint coverage for all 25 bricks; Playwright drops `attention` + `embed_lookup`, attempts connect, asserts no React Flow edge node appears.
130+
131+
### Q03 — Checkpoint history UI + compress/strict (P1, 4)
132+
- Q03.1 RPC `ckpt.list_history(directory: str = ".") → list[CkptEntry]`. Scans recursively for `*.safetensors`, reads metadata via `read_ckpt_metadata` (header-only, no full load), sorts by mtime desc, caps at 100. CkptEntry: `{path, mtime, size_bytes, arch_hash, opt_kind, global_step, has_opt_sidecar}`.
133+
- Q03.2 UI: new `CheckpointHistoryDropdown.tsx` component in `vbgui/src/components/`, wire into `TopBar.tsx` next to ckpt path input. Each row: `<basename> · arch_hash[:8] · step N · mtime`. Click → fills `ckpt_load_path` text field.
134+
- Q03.3 UI: `TrainOptionsPanel.tsx` adds 3 controls — `compress` dropdown (none / weights-int8 / opt-fp16 / both), `ckpt_strict` checkbox, `opt_state_strict` checkbox. Forward via `stage_options.train`.
135+
- Q03.4 V7-H05 mid-run trigger: backend adds `_TRIGGER_CHECKPOINT_TOKENS` set + WS message handler `ckpt.trigger {token, path}` → train loop checks each step → saves via existing `_save_compressed` + sidecar. UI: existing `TrainLiveControls` button's `onScheduleCheckpoint` callback rewired to send the WS message.
136+
137+
### Q04 — 5 Raschka gallery preset gaps (P1, 4)
138+
- Q04.1 `gpt2_xl_specs(...)` in `cppmega_v4/architectures/presets.py` using `abs_pos_embed` brick. Closes Raschka #1.
139+
- Q04.2 Wire existing `tiny_aya_parallel_specs()` into `PRESETS` dict with key `"tiny_aya"`. Closes Raschka #44.
140+
- Q04.3 `xlstm_7b_specs(...)` using `mlstm` brick. Closes Raschka #50.
141+
- Q04.4 `gemma_4_e2b_specs(...)` + `gemma_4_e4b_specs(...)` using `per_layer_embed` brick. Closes Raschka #57+58. Remove 5 xfail markers from `tests/v4/test_galcov_stage_d.py:204-211`. Acceptance: 71/71 (100%).
142+
143+
### Q05 — smoke_zero1 CLI (P1, 3)
144+
- Q05.1 `cppmega_mlx/cli/smoke_zero1.py` entrypoint with argparse `--hosts H1,H2,...`, `--world-size W`, `--num-steps N`, `--out path`. Wraps `mlx.launch -n W --hosts H1,H2,...` around the `bench/bench_zero1_loopback` payload.
145+
- Q05.2 Each rank runs ZeRO-1 wrapper over a tiny MLP; captures per-rank loss + `gradient_reduce_ms`. Rank 0 collects + writes receipt JSON `{world_size, hosts, losses, reduce_ms, parity_max_delta}`.
146+
- Q05.3 Regression: receipt regeneratable via `python -m cppmega_mlx.cli.smoke_zero1 --hosts 127.0.0.1,127.0.0.1` matches existing `bench/baselines/zero1_loopback_2proc_m4.json` within tolerances. pytest hook for loopback receipt.
147+
148+
### Q06 — Train UI polish (P2, 3)
149+
- Q06.1 `TrainOptionsPanel.tsx` adds `train-opt-B` (number 1-32) and `train-opt-S` (number 1-8192) inputs. App overrides `spec.dim_env.B/S` before `pipeline.run`.
150+
- Q06.2 Fix nesting at `cppmega_v4/runner/stages.py:2245`: also flatten `extras["loss_scaler_overflows"] = loss_scaler_overflow_steps` (keep nested `loss_scaler.overflow_steps` for back-compat). vitest pins both.
151+
- Q06.3 `TrainExtrasOverlay.tsx`: add conditional sub-panels — `PlasticityPanel` (FIRE/DASH/ReDo), `MTPPanel` (k-head losses), `IFIMPanel` (instr-FIM loss), `MHCPanel` (multi-head consistency). Each renders only when its key present in extras.
152+
153+
### Q07 — E2E coverage (P2, 3)
154+
- Q07.1 Extend `vbgui/e2e/utils/train_extras.ts:readTrainExtras` to all 16 promised keys (losses, losses_smoothed, val_losses, lr_trajectory, perplexity, bpb, dtype_state, fp8_active, fim_active, sharding_applied, side_channels_observed, per_brick_grad_norms, moe.*, optimizer_kind, gradient_reduce_ms, weight_delta_norm). `12_train_convergence.spec.ts` asserts all 16 present + finite where applicable.
155+
- Q07.2 New `vbgui/e2e/scenarios/74_manual_edge_connect.spec.ts`: drop two compatible bricks, manually draw edge via React Flow `connectionLine`, verify edge appears, assert verify RPC accepts.
156+
- Q07.3 New `vbgui/e2e/scenarios/75_adapter_suggestions.spec.ts`: load preset with intentional shape mismatch, navigate to GotchasTab, click "Suggest adapters", assert chain renders and "Apply" inserts adapter nodes on canvas.
157+
158+
### Q08 — Late warnings + perf + external blocker note (P3, 3)
159+
- Q08.1 Extend `data.preview_parquet` RPC: accept optional `tokenizer_source` arg → returns `roundtrip_pass_rate: float` field → DataInspector shows badge inline with preview, not waiting until train.
160+
- Q08.2 pytest `test_checkpoint_streaming_peak_rss.py` generates 1.5 GiB synthetic checkpoint → `load_auto` → asserts `mx.metal.get_peak_memory()` < 200 MiB during load.
161+
- Q08.3 Multi-node throughput receipt: file a `BLOCKED` note in bd ticket — peer-48 TB5 + macOS 26.2 + M4 family hardware required (per `docs/multimac_training.md:114-129`). Carry hardware-blocker notes; not implementable without external resource.
162+
163+
## 5. Workflow per gap (same as v5/v6)
164+
165+
1. `bd update <id> --claim`
166+
2. Implement (each sub-task atomic)
167+
3. Code review (self if <100 LOC, gsd-code-reviewer otherwise)
168+
4. Perf check (no >5% regression on affected suite)
169+
5. Regression (pytest + vitest + e2e affected)
170+
6. `git add` specific files → commit → push → verify on origin/main
171+
7. `bd close <id>` only after push verified
172+
173+
## 6. Acceptance counters
174+
175+
| Surface | Before v7-Q | Target v7-Q |
176+
|----------------------------------------|-------------|-------------|
177+
| pytest | ~2540 | ≥2570 |
178+
| vitest | ~428 | ≥440 |
179+
| Playwright deep e2e cells | ~1170 | ≥1175 |
180+
| Raschka gallery coverage | 66/71 (93%) | 71/71 (100%) |
181+
| extras keys read by e2e | 7/16 | 16/16 |
182+
| P0 audit gaps | 2 | 0 |
183+
| P1 audit gaps | 3 | 0 |
184+
| Hardware-blocked gaps | 1 | 1 (external) |
185+
186+
## 7. Out of scope (defer to v8+)
187+
188+
- Real cross-Mac ZeRO-1 receipt (BLOCKED on peer-48 hardware)
189+
- 1B+ param training matrix on Mac
190+
- A/B hyperparameter search UI
191+
- LoRA / PEFT adapter wiring
192+
- Tokenizer mutation UI (read-only by design per Lane 3)
193+
- Sophia / Adafactor / Tiger / AdEMAMix optimizers (deferred per Lane 7)
194+
- Live inference serving from trained checkpoint
195+
- Model weight quantisation post-train
196+
197+
## 8. Done definition
198+
199+
All 8 Q-gaps closed (Q08.3 carried as hardware-blocker note). Zero P0/P1
200+
audit findings on re-run. Closure report at
201+
`tests/fixtures/e2e_matrix_v7_q_report.md`. Re-run audit
202+
(`docs/UI-TO-TRAIN-AUDIT-2026-05-23.md` re-execution) returns 0 BLOCKING
203+
gaps.

0 commit comments

Comments
 (0)