Skip to content

Commit 93152e0

Browse files
committed
fix(ux): read vocab_size from active tokenizer, not hardcoded 65536
Previous patch wired vocab_size=65536 (cppmega default) into BrickContextPanel's dims block — but the architect can swap tokenizers via the Tokenizer Playground or the train-tokenizer picker, so embedding_table parameter counts went stale for GPT-2 / SmolLM / Llama-3-tiktoken / fim-only fixtures. Fix: new useTokenizerVocab(rpc, source) hook calls tokenizer.encode_visualize with a tiny probe text on tokenizer change, reads capabilities.vocab_size out of the response, caches by source path so toggling back-and-forth doesn't refetch. App.tsx wires the hook to trainTokenizerPath and passes the resolved size into BrickContextPanel — falling back to 65536 only when no tokenizer is loaded yet. Drop a Llama-3 tokenizer (vocab=128256), the embedding_table now reports 128256 × H params, not 65536 × H.
1 parent b962324 commit 93152e0

2 files changed

Lines changed: 58 additions & 1 deletion

File tree

vbgui/src/App.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { useRpc } from "@/hooks/useRpc";
3838
import { useCacheStats } from "@/hooks/useCacheStats";
3939
import { useVerifyAfter } from "@/hooks/useVerifyAfter";
4040
import { usePresets } from "@/hooks/usePresets";
41+
import { useTokenizerVocab } from "@/hooks/useTokenizerVocab";
4142

4243
import {
4344
INITIAL_SPEC, specReducer, type SpecState, type TopologyFactory,
@@ -47,6 +48,8 @@ import { migrate } from "@/state/migrations";
4748
import { adapterFor } from "@/lib/bricks";
4849
import { useHistory } from "@/hooks/useHistory";
4950
import type { ShardingProposalView } from "@/components/sidebar/ShardingTab";
51+
import { useNeuralDebugger } from "@/hooks/useNeuralDebugger";
52+
import { DebuggerDashboard } from "@/components/DebuggerDashboard";
5053

5154
// PRESETS list is now fetched dynamically from the backend via
5255
// architectures.list_presets — see usePresets() hook below. A fallback
@@ -264,6 +267,11 @@ export function App(): JSX.Element {
264267
onBackendBuildId: (bid) => setBackendBuildId(bid),
265268
});
266269

270+
// UX#3: read actual vocab_size from the active tokenizer instead of
271+
// hardcoding 65536 — so dropping a GPT-2 / SmolLM / cppmega tokenizer
272+
// gives the BrickContextPanel the right embedding_table param count.
273+
const tokenizerVocabSize = useTokenizerVocab(rpc, trainTokenizerPath);
274+
267275
// V7-I07: poll the JsonRPC LRU cache hit-rate so the BottomStrip
268276
// chip surfaces hit_rate / size / evictions live.
269277
const cacheStats = useCacheStats({
@@ -1542,6 +1550,8 @@ export function App(): JSX.Element {
15421550
onRedo={handleRedo}
15431551
canUndo={history.canUndo}
15441552
canRedo={history.canRedo}
1553+
debuggerMode={debugState.debuggerMode}
1554+
onToggleDebugger={() => debugState.setDebuggerMode(!debugState.debuggerMode)}
15451555
onMixedPrecisionChange={(enabled) => dispatch({ type: "optim.set",
15461556
optim: { ...spec.optim, mixed_precision: enabled } })}
15471557
onFp8EnabledChange={(enabled) => dispatch({ type: "sharding.set",
@@ -1908,7 +1918,7 @@ export function App(): JSX.Element {
19081918
brickKind={data.kind ?? "mlp"}
19091919
params={data.params ?? {}}
19101920
hidden_size={dimEnv.H ?? 128}
1911-
vocab_size={65536}
1921+
vocab_size={tokenizerVocabSize ?? 65536}
19121922
onApply={(newParams) => {
19131923
setNodes((prev) => prev.map((n) =>
19141924
n.id === selectedBrickId
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* useTokenizerVocab — given a tokenizer source path, return the
3+
* loaded tokenizer's vocab_size (or null while loading / on error).
4+
*
5+
* Backed by tokenizer.encode_visualize which already reports
6+
* capabilities.vocab_size in its response. We probe with a tiny
7+
* canonical text so the call is cheap, and cache by source path so
8+
* switching back/forth doesn't refetch.
9+
*/
10+
11+
import { useEffect, useState } from "react";
12+
import type { RpcClient } from "@/lib/rpc";
13+
14+
const CACHE = new Map<string, number>();
15+
const PROBE_TEXT = "hello world";
16+
17+
18+
export function useTokenizerVocab(
19+
rpc: RpcClient | null,
20+
source: string | null | undefined,
21+
): number | null {
22+
const [vocab, setVocab] = useState<number | null>(
23+
source && CACHE.has(source) ? CACHE.get(source) ?? null : null);
24+
25+
useEffect(() => {
26+
if (!rpc || !source) { setVocab(null); return; }
27+
const cached = CACHE.get(source);
28+
if (cached !== undefined) { setVocab(cached); return; }
29+
let cancelled = false;
30+
(async () => {
31+
try {
32+
const r = await rpc.call<{
33+
capabilities?: { vocab_size?: number };
34+
}>("tokenizer.encode_visualize",
35+
{ tokenizer_source: source, text: PROBE_TEXT });
36+
const v = r?.capabilities?.vocab_size;
37+
if (typeof v === "number" && v > 0) {
38+
CACHE.set(source, v);
39+
if (!cancelled) setVocab(v);
40+
}
41+
} catch { /* leave null — caller falls back to default */ }
42+
})();
43+
return () => { cancelled = true; };
44+
}, [rpc, source]);
45+
46+
return vocab;
47+
}

0 commit comments

Comments
 (0)