Skip to content

Commit 70792af

Browse files
committed
fix(v7-q11): inspect.histogram Invalid params + brick_<kind> alias for adapter help
Two issues surfaced on the canvas rmsnorm node: 1. Inspect-weight-histogram → "Invalid params" The UI was sending the full buildVerifyParams payload as the spec arg to inspect.histogram. On certain canvas states (custom tiny_aya parallel composition, missing loss head outputs, etc.) the full payload failed VerifyParams' extra='forbid' validation before the backend handler ran. Backend works fine on a minimal spec — the inspect call only needs spec.graph.nodes[*].{id,kind, params} + spec.dim_env.H to instantiate the brick. Fix: build a minimal spec containing JUST the target brick + the current dim_env + a placeholder loss/optim that satisfies the schema. Verified end-to-end on rmsnorm/attention/mlp via the existing pytest histogram suite (10/10 PASS). 2. BrickContextPanel HelpModal → "(No explanation for brick_rmsnorm yet.)" Q09 shipped 25 brick_<kind> entries (covering the BRICKS array) and 6 adapter_<kind> entries (rmsnorm/residual/merge_heads/etc.). But adapter kinds also appear as nodes on the canvas, and the BrickContextPanel looks up help under brick_<kind> regardless of array origin. Fix: alias every adapter_<kind> entry to brick_<kind> at module load. No content duplication — both keys reference the same HelpTopic object. Tests (all green): - vbgui/tests/HelpIconAdapterAlias.test.tsx: 3 cases — alias map populated, brick_rmsnorm modal opens with RMSNorm content - pytest tests/v4/ -k histogram: 10/10 PASS (no backend regression) - vbgui vitest full: 483 -> 509 (+26) PASS
1 parent d6efa99 commit 70792af

3 files changed

Lines changed: 94 additions & 10 deletions

File tree

vbgui/src/App.tsx

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1933,20 +1933,56 @@ export function App(): JSX.Element {
19331933
: n));
19341934
}}
19351935
onInspectHistogram={async (brickId) => {
1936-
// V7-H08: build current spec on the fly and call
1937-
// inspect.histogram so the panel renders the
1938-
// brick's fresh-init weight distribution.
1936+
// V7-H08 + V7-Q11: send a MINIMAL spec containing
1937+
// just the target brick + dim_env so the call
1938+
// doesn't trip schema validation on canvas
1939+
// states that lack head_outputs / loss head / etc.
1940+
// Backend reads spec.graph.nodes[i].{id,kind,
1941+
// params} + spec.dim_env.H to instantiate the
1942+
// brick — everything else is filler that has to
1943+
// pass extra='forbid'.
19391944
const snap = wireSpecRef.current;
1940-
const spec_payload = buildVerifyParams(
1941-
snap.nodes, snap.edges, snap.spec,
1942-
snap.availableSideChannels);
1945+
const node = snap.nodes.find(
1946+
(n) => n.id === brickId);
1947+
if (!node) {
1948+
throw new Error(
1949+
`brick ${brickId} not in canvas`);
1950+
}
1951+
const kind = (node.data as { kind?: string })
1952+
?.kind ?? brickId;
1953+
const params = (node.data as {
1954+
params?: Record<string, unknown> })
1955+
?.params ?? {};
1956+
const dim_env = snap.spec.dim_env;
1957+
const minimal_spec = {
1958+
graph: {
1959+
nodes: [
1960+
{ id: brickId, kind, params },
1961+
],
1962+
edges: [],
1963+
},
1964+
dim_env: dim_env,
1965+
loss: {
1966+
kind: "cross_entropy",
1967+
head_outputs: [brickId],
1968+
},
1969+
optim: {
1970+
kind: "adamw",
1971+
groups: [{
1972+
matcher: "all",
1973+
lr: 1e-3,
1974+
weight_decay: 0.01,
1975+
betas: [0.9, 0.95],
1976+
}],
1977+
},
1978+
};
19431979
return rpc.call<{
19441980
brick_id: string; buckets: number;
19451981
bins: number[]; counts: number[];
19461982
min: number; max: number; mean: number;
19471983
n_values: number;
19481984
}>("inspect.histogram", {
1949-
spec: spec_payload, brick_id: brickId,
1985+
spec: minimal_spec, brick_id: brickId,
19501986
kind: "weight", buckets: 32,
19511987
});
19521988
}}

vbgui/src/components/HelpIcon.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import { useState } from "react";
66
import { TENSOR_DIAGRAMS } from "./diagrams";
7+
import { T } from "@/theme";
78

89
export interface HelpTopic {
910
title: string;
@@ -874,6 +875,22 @@ export const HELP_TOPICS: Record<string, HelpTopic> = {
874875
},
875876
};
876877

878+
// V7-Q11: adapter kinds also appear as nodes on the canvas; the
879+
// BrickContextPanel looks them up under brick_<kind>. Mirror the
880+
// adapter_* entries to brick_* so the panel doesn't show "No
881+
// explanation" for rmsnorm / residual / merge_heads / split_heads /
882+
// transpose_bnsd / linear_bridge nodes.
883+
for (const adapterKind of [
884+
"merge_heads", "split_heads", "transpose_bnsd",
885+
"linear_bridge", "rmsnorm", "residual",
886+
]) {
887+
const aKey = `adapter_${adapterKind}`;
888+
const bKey = `brick_${adapterKind}`;
889+
if (HELP_TOPICS[aKey] && !HELP_TOPICS[bKey]) {
890+
HELP_TOPICS[bKey] = HELP_TOPICS[aKey]!;
891+
}
892+
}
893+
877894
export interface HelpIconProps {
878895
topic: keyof typeof HELP_TOPICS | string;
879896
size?: number;
@@ -892,10 +909,11 @@ export function HelpIcon({ topic, size = 14 }: HelpIconProps): JSX.Element {
892909
onClick={() => setOpen(true)}
893910
style={{
894911
width: size, height: size, padding: 0,
895-
borderRadius: "50%", background: "#e5e7eb",
896-
color: "#374151", border: "none",
897-
fontSize: Math.max(9, size - 4), lineHeight: 1,
912+
borderRadius: "50%", background: "rgba(255, 255, 255, 0.08)",
913+
color: T.accent, border: `1px solid ${T.border}`,
914+
fontSize: Math.max(9, size - 4), lineHeight: `${size - 2}px`,
898915
cursor: "pointer", fontWeight: 700, marginLeft: 4,
916+
display: "inline-flex", alignItems: "center", justifyContent: "center",
899917
}}
900918
>
901919
?
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { describe, it, expect } from "vitest";
2+
import { render, screen, fireEvent } from "@testing-library/react";
3+
import { HelpIcon, HELP_TOPICS } from "@/components/HelpIcon";
4+
5+
describe("V7-Q11 brick_<kind> alias for adapter kinds", () => {
6+
it("brick_rmsnorm resolves to the same entry as adapter_rmsnorm", () => {
7+
expect(HELP_TOPICS["brick_rmsnorm"]).toBeDefined();
8+
expect(HELP_TOPICS["brick_rmsnorm"]).toBe(HELP_TOPICS["adapter_rmsnorm"]);
9+
});
10+
11+
it("brick_residual / brick_merge_heads / etc. all alias adapters", () => {
12+
for (const k of ["residual", "merge_heads", "split_heads",
13+
"transpose_bnsd", "linear_bridge"]) {
14+
expect(HELP_TOPICS[`brick_${k}`], `missing brick_${k}`).toBeDefined();
15+
expect(HELP_TOPICS[`brick_${k}`])
16+
.toBe(HELP_TOPICS[`adapter_${k}`]);
17+
}
18+
});
19+
20+
it("clicking brick_rmsnorm ? opens a modal with the RMSNorm content", () => {
21+
render(<HelpIcon topic="brick_rmsnorm" />);
22+
fireEvent.click(screen.getByTestId("help-icon-brick_rmsnorm"));
23+
expect(screen.getByTestId("help-modal-brick_rmsnorm")).toBeDefined();
24+
// The modal title should reference RMSNorm (the adapter entry's
25+
// title) rather than the missing-topic fallback.
26+
expect(screen.getByTestId("help-modal-title").textContent)
27+
.toMatch(/RMSNorm/i);
28+
expect(screen.queryByTestId("help-modal-missing")).toBeNull();
29+
});
30+
});

0 commit comments

Comments
 (0)