Skip to content

Commit 7e6a69e

Browse files
committed
feat(h08): train-probe-text textarea forwards inference_probe_text
Closes V5-G20 from the UI side: TopBar's Train dropdown now exposes a `train-probe-text` textarea that App.handleRunPipeline forwards into `stage_options.train.inference_probe_text`. Backend stage_train pairs it with the active tokenizer to encode real token ids for the pre-vs- post forward divergence probe (instead of synthetic Gaussian noise). Tests: - vbgui vitest TopBar: H08 specs for the textarea + empty-omits-field paths — 22/22 TopBar tests passing. - vbgui e2e 62_probe_text.spec.ts: wire real tokenizer, fill probe text, Train → assert extras.inference_probe.real_tokens=true, text_len>0, top1_token_drift>=0. - vbgui e2e V6 suite (55–62): 10/10 passing — no regression.
1 parent 59a5f94 commit 7e6a69e

4 files changed

Lines changed: 106 additions & 1 deletion

File tree

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// H08: TopBar train-probe-text textarea threads opts.inference_probe_text
2+
// into stage_train (V5-G20). Backend pairs the text with the active
3+
// tokenizer to encode real token ids for the pre-vs-post forward
4+
// divergence probe, replacing the synthetic random Gaussian input.
5+
//
6+
// Assertions:
7+
// - extras.inference_probe.real_tokens === true
8+
// - extras.inference_probe.text_len > 0
9+
// - extras.inference_probe.top1_token_drift >= 0 (number, not "null")
10+
11+
import { test, expect } from "@playwright/test";
12+
import { gotoApp, selectPreset, clickTab, closeModal } from "../fixtures";
13+
import { loadMatrix } from "../utils/matrix";
14+
15+
const MATRIX = loadMatrix();
16+
const REAL_TOKENIZER = MATRIX.tokenizers.T2_gpt2_small.path;
17+
18+
test("H08: train-probe-text → extras.inference_probe.real_tokens=true",
19+
async ({ page }) => {
20+
test.setTimeout(120_000);
21+
await gotoApp(page);
22+
await selectPreset(page, "llama3_8b");
23+
24+
// Wire tokenizer so the backend can encode the probe text.
25+
await clickTab(page, "tokenizer");
26+
await page.getByTestId("tokenizer-playground").waitFor();
27+
await page.getByTestId("add-panel").click();
28+
await page.getByTestId("tokenizer-source-0").fill(REAL_TOKENIZER);
29+
await page.getByTestId("tokenizer-encode-0").click();
30+
await page.getByTestId("tokenizer-metrics-0").waitFor({ timeout: 8_000 });
31+
await page.getByTestId("tokenizer-use-for-train-0").click();
32+
33+
// Drive Train with probe text.
34+
await clickTab(page, "canvas");
35+
await page.getByTestId("run-pipeline-toggle").click();
36+
await page.getByTestId("train-num-steps").fill("2");
37+
await page.getByTestId("train-probe-text")
38+
.fill("hello world this is a probe sentence");
39+
await page.getByTestId("run-pipeline-train").click();
40+
await page.getByTestId("run-result-modal").waitFor({ timeout: 60_000 });
41+
await page.getByTestId("run-result-expand-train").click();
42+
await page.getByTestId("run-result-extras-row-train").waitFor();
43+
44+
const real = await page.getByTestId(
45+
"run-result-extras-train-inference_probe-real_tokens").textContent();
46+
expect(real?.trim().toLowerCase()).toBe("true");
47+
48+
const textLen = parseInt(await page.getByTestId(
49+
"run-result-extras-train-inference_probe-text_len")
50+
.textContent() ?? "0", 10);
51+
expect(textLen).toBeGreaterThan(0);
52+
53+
const drift = await page.getByTestId(
54+
"run-result-extras-train-inference_probe-top1_token_drift").textContent();
55+
expect(drift?.trim()).not.toBe("null");
56+
expect(parseFloat(drift!.trim())).toBeGreaterThanOrEqual(0);
57+
58+
await closeModal(page);
59+
});

vbgui/src/App.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ export function App(): JSX.Element {
302302
opts?: { num_steps?: number; warm_start?: boolean;
303303
checkpoint_save_path?: string;
304304
checkpoint_load_path?: string;
305+
inference_probe_text?: string;
305306
},
306307
) => {
307308
const snap = wireSpecRef.current;
@@ -338,6 +339,12 @@ export function App(): JSX.Element {
338339
if (opts?.checkpoint_load_path) {
339340
trainOpts.checkpoint_load_path = opts.checkpoint_load_path;
340341
}
342+
// H08: forward inference probe text (G20). Backend pairs it with
343+
// tokenizer_path (also forwarded above) to encode real tokens for
344+
// the pre-vs-post forward divergence probe.
345+
if (opts?.inference_probe_text) {
346+
trainOpts.inference_probe_text = opts.inference_probe_text;
347+
}
341348
if (trainParquetPath) trainOpts.parquet_path = trainParquetPath;
342349
if (trainTokenizerPath) trainOpts.tokenizer_path = trainTokenizerPath;
343350
// Forward SideChannelsTab train selection as synthetic int lists for the

vbgui/src/components/TopBar.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface TopBarProps {
1717
opts?: { num_steps?: number; warm_start?: boolean;
1818
checkpoint_save_path?: string;
1919
checkpoint_load_path?: string;
20+
inference_probe_text?: string;
2021
}) => void;
2122
/** H02: toggle callbacks. */
2223
onMixedPrecisionChange?: (enabled: boolean) => void;
@@ -45,6 +46,7 @@ export function TopBar(p: TopBarProps): JSX.Element {
4546
const [warmStart, setWarmStart] = useState<boolean>(false);
4647
const [ckptSavePath, setCkptSavePath] = useState<string>("");
4748
const [ckptLoadPath, setCkptLoadPath] = useState<string>("");
49+
const [probeText, setProbeText] = useState<string>("");
4850
return (
4951
<header data-testid="top-bar"
5052
style={{ height: 56, display: "flex", alignItems: "center",
@@ -201,6 +203,18 @@ export function TopBar(p: TopBarProps): JSX.Element {
201203
style={{ width: 200 }} />
202204
</label>
203205
</div>
206+
<label style={{ padding: "6px 12px", display: "flex",
207+
flexDirection: "column", gap: 3, fontSize: 11,
208+
color: "#374151" }}>
209+
<span style={{ color: "#6b7280" }}>probe text:</span>
210+
<textarea data-testid="train-probe-text"
211+
placeholder="Optional: encode this for inference probe"
212+
value={probeText}
213+
onChange={(e) => setProbeText(e.target.value)}
214+
rows={2}
215+
style={{ width: 280, fontFamily: "monospace",
216+
fontSize: 11 }} />
217+
</label>
204218
<button data-testid="run-pipeline-train"
205219
onClick={() => { setOpen(false);
206220
p.onRunPipeline("train",
@@ -209,7 +223,9 @@ export function TopBar(p: TopBarProps): JSX.Element {
209223
checkpoint_save_path:
210224
ckptSavePath || undefined,
211225
checkpoint_load_path:
212-
ckptLoadPath || undefined }); }}
226+
ckptLoadPath || undefined,
227+
inference_probe_text:
228+
probeText || undefined }); }}
213229
disabled={!!p.trainDisabled}
214230
title={p.trainDisabled?.reason ?? ""}
215231
style={{ ...menuItem,

vbgui/tests/TopBar.test.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,29 @@ describe("TopBar", () => {
148148
}));
149149
});
150150

151+
it("H08: train-probe-text textarea forwards inference_probe_text",
152+
() => {
153+
const onRunPipeline = vi.fn();
154+
render(<TopBar {...defaultTopProps({ onRunPipeline })} />);
155+
fireEvent.click(screen.getByTestId("run-pipeline-toggle"));
156+
fireEvent.change(screen.getByTestId("train-probe-text"),
157+
{ target: { value: "hello world from probe" } });
158+
fireEvent.click(screen.getByTestId("run-pipeline-train"));
159+
expect(onRunPipeline).toHaveBeenLastCalledWith("train",
160+
expect.objectContaining({
161+
inference_probe_text: "hello world from probe",
162+
}));
163+
});
164+
165+
it("H08: empty probe text omits field", () => {
166+
const onRunPipeline = vi.fn();
167+
render(<TopBar {...defaultTopProps({ onRunPipeline })} />);
168+
fireEvent.click(screen.getByTestId("run-pipeline-toggle"));
169+
fireEvent.click(screen.getByTestId("run-pipeline-train"));
170+
const opts = onRunPipeline.mock.calls.at(-1)?.[1];
171+
expect(opts.inference_probe_text).toBeUndefined();
172+
});
173+
151174
it("H05: empty checkpoint inputs omit fields", () => {
152175
const onRunPipeline = vi.fn();
153176
render(<TopBar {...defaultTopProps({ onRunPipeline })} />);

0 commit comments

Comments
 (0)