Skip to content

Commit bdc5d3f

Browse files
committed
feat(h23): fp16 dtype option alongside bf16/fp32
Closes V6 H23: TopBar Train dropdown gains a precision-mode selector with three options (fp32 / bf16 / fp16) that App.handleRunPipeline forwards via stage_options.train.master_dtype. Backend stage_train recognises master_dtype in ("fp32", "bf16", "fp16") and overrides the mixed_precision-derived default. When fp16 is selected, train_dtype is also coerced to fp16, and the H16 set_dtype cast covers the float16 path. Tests: - pytest tests/v4/test_stage_train_fp16.py: 3/3 — * fp16 reported in extras.{master_dtype, train_dtype, dtype_actual.master_dtype_actual}. * fp16 losses are finite and within 50% relative of bf16. * fp16 weight_delta ≤ 1.5×fp32 (precision loss caps it). - vbgui e2e 76_fp16_dtype.spec.ts: select "fp16" → assert extras.train_dtype == master_dtype == "fp16".
1 parent 3c4bc69 commit bdc5d3f

5 files changed

Lines changed: 123 additions & 1 deletion

File tree

cppmega_v4/runner/stages.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,12 @@ def _count(tree: Any) -> int:
974974
if precision_optim is not None else True)
975975
master_dtype = "fp32" if mixed_precision else "bf16"
976976
train_dtype = "bf16"
977+
# H23: opts.master_dtype overrides — accepts "fp32", "bf16", "fp16".
978+
_opt_master = opts.get("master_dtype")
979+
if _opt_master in ("fp32", "bf16", "fp16"):
980+
master_dtype = str(_opt_master)
981+
if master_dtype == "fp16":
982+
train_dtype = "fp16"
977983
fp8_active = False
978984
# Wire fp8_enabled from spec.sharding (payload pydantic model)
979985
ws_sharding = getattr(ctx.spec, "sharding", None)

tests/v4/test_stage_train_fp16.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""H23: fp16 dtype option alongside bf16/fp32."""
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",
16+
"params": {"num_heads": 4, "head_dim": 64}},
17+
{"id": "mlp", "kind": "mlp", "params": {}},
18+
],
19+
"edges": [{"src": "attn", "dst": "mlp"}],
20+
},
21+
"dim_env": {"B": 1, "S": 8, "H": 128,
22+
"nh": 2, "nkv": 1, "head_dim": 64},
23+
"loss": {"kind": "cross_entropy", "head_outputs": ["mlp"]},
24+
"optim": {"kind": "adamw",
25+
"groups": [{"matcher": "all", "lr": 1e-3,
26+
"weight_decay": 0.01,
27+
"betas": [0.9, 0.95]}]},
28+
})
29+
30+
31+
def _train(**opts) -> dict:
32+
rep = run_pipeline(_spec(), Pipeline.from_dict({
33+
"stages": ["parse", "verify_build_spec", "build_model", "train"],
34+
"stage_options": {"train": {"num_steps": 2, **opts}},
35+
}))
36+
tr = next(s for s in rep.stages if s.name == "train")
37+
assert tr.status == "ok"
38+
return tr.extras
39+
40+
41+
def test_h23_fp16_option_accepted_and_reported():
42+
e = _train(master_dtype="fp16")
43+
assert e["master_dtype"] == "fp16"
44+
assert e["train_dtype"] == "fp16"
45+
assert "float16" in e["dtype_actual"]["master_dtype_actual"]
46+
47+
48+
def test_h23_fp16_losses_finite_and_close_to_bf16():
49+
bf16 = _train(master_dtype="bf16")["losses"]
50+
fp16 = _train(master_dtype="fp16")["losses"]
51+
assert all(x == x and -1e10 < x < 1e10 for x in fp16)
52+
# fp16 and bf16 share the same exponent range; loss should be in
53+
# the same ballpark for a 2-step run (within 50%).
54+
for a, b in zip(bf16, fp16):
55+
assert abs(a - b) / max(abs(a), abs(b), 1e-9) < 0.5
56+
57+
58+
def test_h23_fp16_weight_delta_smaller_than_fp32():
59+
"""fp16 has fewer mantissa bits → tiny updates round to zero →
60+
expected smaller weight_delta_norm than fp32."""
61+
fp32 = _train(master_dtype="fp32")["weight_delta_norm"]
62+
fp16 = _train(master_dtype="fp16")["weight_delta_norm"]
63+
# On a 2-step run noise is large; allow generous gate that fp16
64+
# weight delta is no MORE than 1.5x fp32 (precision loss caps it).
65+
assert fp16 <= fp32 * 1.5 + 1e-6, (
66+
f"fp16 delta {fp16} > 1.5×fp32 {fp32}")
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// H23: TopBar precision dropdown → fp16 → Train extras report
2+
// master_dtype="fp16" and train_dtype="fp16".
3+
4+
import { test, expect } from "@playwright/test";
5+
import { gotoApp, selectPreset, closeModal } from "../fixtures";
6+
7+
test("H23: top-bar-precision-mode=fp16 → extras.train_dtype=='fp16'",
8+
async ({ page }) => {
9+
test.setTimeout(60_000);
10+
await gotoApp(page);
11+
await selectPreset(page, "llama3_8b");
12+
await page.getByTestId("run-pipeline-toggle").click();
13+
await page.getByTestId("train-num-steps").fill("2");
14+
await page.getByTestId("top-bar-precision-mode").selectOption("fp16");
15+
await page.getByTestId("run-pipeline-train").click();
16+
await page.getByTestId("run-result-modal").waitFor({ timeout: 60_000 });
17+
await page.getByTestId("run-result-expand-train").click();
18+
await page.getByTestId("run-result-extras-row-train").waitFor();
19+
20+
const td = ((await page.getByTestId(
21+
"run-result-extras-train-train_dtype").textContent()) ?? "").trim();
22+
expect(td).toBe("fp16");
23+
const md = ((await page.getByTestId(
24+
"run-result-extras-train-master_dtype").textContent()) ?? "").trim();
25+
expect(md).toBe("fp16");
26+
await closeModal(page);
27+
});

vbgui/src/App.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ export function App(): JSX.Element {
303303
checkpoint_save_path?: string;
304304
checkpoint_load_path?: string;
305305
inference_probe_text?: string;
306+
master_dtype?: "fp32" | "bf16" | "fp16";
306307
},
307308
) => {
308309
const snap = wireSpecRef.current;
@@ -345,6 +346,10 @@ export function App(): JSX.Element {
345346
if (opts?.inference_probe_text) {
346347
trainOpts.inference_probe_text = opts.inference_probe_text;
347348
}
349+
// H23: master_dtype override (fp32/bf16/fp16).
350+
if (opts?.master_dtype) {
351+
trainOpts.master_dtype = opts.master_dtype;
352+
}
348353
// H20: when the spec carries sharding axes, derive fake_ranks
349354
// from the product of their degrees so a Train run simulates a
350355
// mean-reduced multi-rank backward (extras.fake_ranks +

vbgui/src/components/TopBar.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface TopBarProps {
1818
checkpoint_save_path?: string;
1919
checkpoint_load_path?: string;
2020
inference_probe_text?: string;
21+
master_dtype?: "fp32" | "bf16" | "fp16";
2122
}) => void;
2223
/** H02: toggle callbacks. */
2324
onMixedPrecisionChange?: (enabled: boolean) => void;
@@ -47,6 +48,8 @@ export function TopBar(p: TopBarProps): JSX.Element {
4748
const [ckptSavePath, setCkptSavePath] = useState<string>("");
4849
const [ckptLoadPath, setCkptLoadPath] = useState<string>("");
4950
const [probeText, setProbeText] = useState<string>("");
51+
const [masterDtype, setMasterDtype] =
52+
useState<"fp32" | "bf16" | "fp16">("fp32");
5053
return (
5154
<header data-testid="top-bar"
5255
style={{ height: 56, display: "flex", alignItems: "center",
@@ -209,6 +212,20 @@ export function TopBar(p: TopBarProps): JSX.Element {
209212
style={{ width: 200 }} />
210213
</label>
211214
</div>
215+
<label style={{ padding: "6px 12px", display: "flex",
216+
alignItems: "center", gap: 6, fontSize: 11,
217+
color: "#374151" }}>
218+
<span style={{ color: "#6b7280" }}>master_dtype:</span>
219+
<select data-testid="top-bar-precision-mode"
220+
value={masterDtype}
221+
onChange={(e) =>
222+
setMasterDtype(e.target.value as
223+
"fp32" | "bf16" | "fp16")}>
224+
<option value="fp32">fp32</option>
225+
<option value="bf16">bf16</option>
226+
<option value="fp16">fp16</option>
227+
</select>
228+
</label>
212229
<label style={{ padding: "6px 12px", display: "flex",
213230
flexDirection: "column", gap: 3, fontSize: 11,
214231
color: "#374151" }}>
@@ -231,7 +248,8 @@ export function TopBar(p: TopBarProps): JSX.Element {
231248
checkpoint_load_path:
232249
ckptLoadPath || undefined,
233250
inference_probe_text:
234-
probeText || undefined }); }}
251+
probeText || undefined,
252+
master_dtype: masterDtype }); }}
235253
// H22: disable while a Train is already running so
236254
// double-clicks don't spawn a parallel pipeline.
237255
disabled={!!p.trainDisabled || !!p.trainInFlight}

0 commit comments

Comments
 (0)