Leon4gr45 commited on
Commit
9133d7b
·
verified ·
1 Parent(s): eeafe0a

Delete web/src/app/architecture/ArchitectureView.tsx with huggingface_hub

Browse files
web/src/app/architecture/ArchitectureView.tsx DELETED
@@ -1,391 +0,0 @@
1
- "use client";
2
-
3
- import { useEffect, useState } from "react";
4
- import { getJSON, type Experiments, type Stats, type View } from "@/lib/api";
5
- import { bytes, fmtDuration } from "@/lib/format";
6
- import { VIEW_INFO } from "@/lib/views";
7
- import { ViewSwatch } from "@/components/Controls";
8
-
9
- /* ---------- diagram primitives ---------- */
10
-
11
- function Node({
12
- title,
13
- sub,
14
- tag,
15
- children,
16
- accent,
17
- className = "",
18
- }: {
19
- title: string;
20
- sub?: string;
21
- tag?: string;
22
- children?: React.ReactNode;
23
- accent?: string;
24
- className?: string;
25
- }) {
26
- return (
27
- <div
28
- className={`relative rounded-xl border border-line bg-surface p-4 ${className}`}
29
- style={accent ? { boxShadow: `inset 3px 0 0 ${accent}` } : undefined}
30
- >
31
- <div className="flex items-start justify-between gap-2">
32
- <div className="font-medium">{title}</div>
33
- {tag && <span className="shrink-0 rounded bg-surface-2 px-1.5 py-0.5 font-mono text-[11px] text-ink-2">{tag}</span>}
34
- </div>
35
- {sub && <div className="mt-0.5 text-xs text-muted">{sub}</div>}
36
- {children && <div className="mt-2 space-y-1 text-sm text-ink-2">{children}</div>}
37
- </div>
38
- );
39
- }
40
-
41
- /** Connector between nodes: horizontal on wide screens, vertical when stacked. */
42
- function Arrow({ label }: { label?: string }) {
43
- return (
44
- <div className="flex shrink-0 items-center justify-center py-1 text-muted md:px-1 md:py-0">
45
- <div className="flex flex-col items-center gap-0.5 md:flex-row">
46
- {label && <span className="whitespace-nowrap text-[11px] md:order-2 md:hidden">{label}</span>}
47
- <svg width="28" height="16" viewBox="0 0 28 16" className="hidden md:block" aria-hidden>
48
- <path d="M1 8h24M19 2l6 6-6 6" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
49
- </svg>
50
- <svg width="16" height="22" viewBox="0 0 16 22" className="md:hidden" aria-hidden>
51
- <path d="M8 1v18M2 13l6 6 6-6" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
52
- </svg>
53
- </div>
54
- </div>
55
- );
56
- }
57
-
58
- function Section({ n, title, blurb, children }: { n: number; title: string; blurb: string; children: React.ReactNode }) {
59
- return (
60
- <section className="space-y-4">
61
- <div>
62
- <div className="flex items-baseline gap-3">
63
- <span className="font-mono text-sm text-muted">{String(n).padStart(2, "0")}</span>
64
- <h2 className="text-lg font-semibold">{title}</h2>
65
- </div>
66
- <p className="mt-1 max-w-3xl text-sm text-ink-2">{blurb}</p>
67
- </div>
68
- {children}
69
- </section>
70
- );
71
- }
72
-
73
- function Group({ label, children, className = "" }: { label: string; children: React.ReactNode; className?: string }) {
74
- return (
75
- <div className={`relative rounded-2xl border border-line p-4 pt-7 ${className}`}>
76
- <span className="absolute -top-2.5 left-4 bg-page px-2 text-xs text-muted">{label}</span>
77
- {children}
78
- </div>
79
- );
80
- }
81
-
82
- function Code({ children }: { children: React.ReactNode }) {
83
- return <code className="rounded bg-surface-2 px-1 py-0.5 font-mono text-[12px] text-ink">{children}</code>;
84
- }
85
-
86
- /* ---------- page ---------- */
87
-
88
- const EMBED_VIEWS: View[] = ["text", "frame", "video", "audio", "fused"];
89
- const VIEW_INPUT: Record<string, string> = {
90
- text: "title: {title} | text: {transcript}",
91
- frame: "{ image: NNN.jpg }",
92
- video: "{ video: NNN.mp4 } · ~30 frames × 140 tok",
93
- audio: "{ audio: NNN.wav } · 30s × 25 tok",
94
- fused: "title… | text… <|video|> <|audio|>",
95
- };
96
-
97
- export function ArchitectureView() {
98
- const [stats, setStats] = useState<Stats | null>(null);
99
- const [exp, setExp] = useState<Experiments | null>(null);
100
-
101
- useEffect(() => {
102
- getJSON<Stats>("/api/stats").then(setStats).catch(() => {});
103
- getJSON<Experiments>("/api/experiments").then(setExp).catch(() => {});
104
- }, []);
105
-
106
- const seg = stats?.segments ?? 427;
107
- const vids = stats?.videos ?? 15;
108
- const built = stats?.views_built ?? {};
109
- const perView768 = seg * 768 * 4;
110
-
111
- return (
112
- <div className="space-y-14">
113
- <section>
114
- <h1 className="text-2xl font-semibold">Architecture</h1>
115
- <p className="mt-1 max-w-3xl text-sm text-ink-2">
116
- How the pieces fit: an offline pipeline turns {vids} videos into {seg} segments embedded five ways by EmbeddingGemma 2;
117
- at query time the same model embeds the question, a cosine search (at any Matryoshka size) picks segments, and Gemma 4
118
- 12B watches those clips to answer. Everything runs on one box with an RTX PRO 6000 (96 GB).
119
- </p>
120
- </section>
121
-
122
- {/* 1. Runtime topology */}
123
- <Section
124
- n={1}
125
- title="Runtime topology"
126
- blurb="Three processes. Only the Next.js server is reachable over Tailscale; it proxies /api and /media to FastAPI, which talks to vLLM on loopback."
127
- >
128
- <div className="flex flex-col items-stretch md:flex-row md:items-center">
129
- <Node title="Browser" sub="any device on the tailnet" tag="tailscale" className="md:w-48">
130
- <div className="font-mono text-xs">&lt;tailnet-host&gt;:3000</div>
131
- </Node>
132
- <Arrow label="HTTP" />
133
- <Group label="this box · RTX PRO 6000 Blackwell · 96 GB" className="flex-1">
134
- <div className="flex flex-col items-stretch lg:flex-row lg:items-center">
135
- <Node title="Next.js 16" sub="web/ · pages + proxy" tag=":3000" className="lg:w-52">
136
- <div>Library · Search · Ask</div>
137
- <div>Experiments · Architecture</div>
138
- <div className="text-xs text-muted">rewrites /api, /media → :8000</div>
139
- </Node>
140
- <Arrow />
141
- <Node title="FastAPI" sub="vrag/api.py · uv venv" tag=":8000" className="lg:w-56">
142
- <div>search · compare · ask</div>
143
- <div>experiments · range-served mp4</div>
144
- </Node>
145
- <Arrow />
146
- <div className="grid flex-1 gap-2">
147
- <Node title="EmbeddingGemma 2" sub="in-process · bf16 · ~2.6 GB VRAM" accent="var(--series-1)" tag="740M">
148
- <div className="text-xs">query + corpus embeddings, 768d</div>
149
- </Node>
150
- <Node title="Vector index" sub="numpy · brute-force cosine" accent="var(--series-3)" tag={`${EMBED_VIEWS.length}×${seg}×768`}>
151
- <div className="text-xs">data/index/&#123;view&#125;.npy · {bytes(perView768)} per view</div>
152
- </Node>
153
- <Node title="vLLM · Gemma 4 12B" sub="QAT w4a16 + MTP drafter · serve/ venv" accent="var(--series-5)" tag=":8001">
154
- <div className="text-xs">answers · transcription · eval queries</div>
155
- </Node>
156
- </div>
157
- </div>
158
- </Group>
159
- </div>
160
- </Section>
161
-
162
- {/* 2. Offline pipeline */}
163
- <Section
164
- n={2}
165
- title="Build pipeline"
166
- blurb="uv run vrag build. Each stage is idempotent and writes plain files under data/, so any step can be re-run on its own."
167
- >
168
- <div className="flex flex-col items-stretch xl:flex-row xl:items-stretch">
169
- <Node title="Source files" sub="sams-videos/*.mp4" tag="input" className="xl:w-44">
170
- <div>{vids} creator exports</div>
171
- <div className="text-xs text-muted">1080p60, ~1 GB each</div>
172
- </Node>
173
- <Arrow />
174
- <Node title="ingest" sub="vrag/ingest.py" tag="ffmpeg" className="xl:flex-1">
175
- <div>480p NVENC proxy (faststart)</div>
176
- <div>match to upload by duration (YouTube rounds up)</div>
177
- <div className="text-xs text-muted">→ video.mp4 · thumb.jpg · meta.json</div>
178
- </Node>
179
- <Arrow />
180
- <Node title="transcribe" sub="vrag/transcribe.py" tag="gemma 4" accent="var(--series-5)" className="xl:flex-1">
181
- <div>Gemma 4 12B via vLLM (audio in)</div>
182
- <div>cut at pauses into ≤28s chunks, pad to 30s</div>
183
- <div className="text-xs text-muted">→ transcript.gemma.json · Whisper kept for comparison</div>
184
- </Node>
185
- <Arrow />
186
- <Node title="segment" sub="vrag/segment.py" tag="~30s" className="xl:flex-1">
187
- <div>pack cues into ~30s windows</div>
188
- <div>never cut mid-sentence</div>
189
- <div className="text-xs text-muted">→ NNN.mp4 · NNN.wav · NNN.jpg</div>
190
- </Node>
191
- <Arrow />
192
- <Node title="embed" sub="vrag/embed.py" tag="5 views" className="xl:flex-1">
193
- <div>{seg} segments × 5 views</div>
194
- <div>bf16, unit-norm, 768d</div>
195
- <div className="text-xs text-muted">→ &#123;view&#125;.npy · manifest.json</div>
196
- </Node>
197
- </div>
198
- </Section>
199
-
200
- {/* 3. Embedding views */}
201
- <Section
202
- n={3}
203
- title="One segment, five embeddings"
204
- blurb="Every segment's media goes through EmbeddingGemma 2 five different ways into the same 768d space, so one text query can be compared against any of them. Hybrid adds a sixth retrieval mode by fusing rankings instead of vectors."
205
- >
206
- <div className="grid gap-4 lg:grid-cols-[260px_auto_1fr] lg:gap-0">
207
- <Node title="Segment 2RA7jWJvQ1U_006" sub="Which is The Best Qwen3.8-27B? · 3:20–3:57">
208
- {/* eslint-disable-next-line @next/next/no-img-element */}
209
- <img src="/media/2RA7jWJvQ1U/segments/006.jpg" alt="" className="mt-1 aspect-video w-full rounded-md bg-black object-cover" />
210
- <div className="grid grid-cols-3 gap-1 pt-1 text-center text-[11px]">
211
- <span className="rounded bg-surface-2 py-0.5">transcript</span>
212
- <span className="rounded bg-surface-2 py-0.5">frames</span>
213
- <span className="rounded bg-surface-2 py-0.5">audio</span>
214
- </div>
215
- </Node>
216
- {/* Fan-out: stretches to the list height; curves end on the centres of the 5 embedding rows (of 6 equal rows). */}
217
- <div className="relative hidden w-14 lg:block">
218
- {/* Absolutely positioned so the SVG's intrinsic aspect ratio doesn't drive the row height. */}
219
- <svg viewBox="0 0 56 600" preserveAspectRatio="none" className="absolute inset-0 h-full w-full text-muted" aria-hidden>
220
- {EMBED_VIEWS.map((_, i) => {
221
- const y = (i + 0.5) * 100;
222
- return (
223
- <path
224
- key={i}
225
- d={`M2 250 C 30 250, 26 ${y}, 54 ${y}`}
226
- fill="none"
227
- stroke="currentColor"
228
- strokeWidth="1.5"
229
- vectorEffect="non-scaling-stroke"
230
- />
231
- );
232
- })}
233
- </svg>
234
- </div>
235
- <div className="grid auto-rows-fr gap-2">
236
- {EMBED_VIEWS.map((v) => (
237
- <div key={v} className="grid items-center gap-3 rounded-lg border border-line bg-surface px-3 py-2 md:grid-cols-[110px_1fr_auto]">
238
- <span className="flex items-center gap-2 font-medium">
239
- <ViewSwatch view={v} />
240
- {VIEW_INFO[v].label}
241
- </span>
242
- <span className="font-mono text-xs text-ink-2">{VIEW_INPUT[v]}</span>
243
- <span className="text-xs text-muted tabular">
244
- {built[v] ? `${built[v].ms_per_segment} ms/seg` : ""}
245
- </span>
246
- </div>
247
- ))}
248
- <div className="grid items-center gap-3 rounded-lg border border-dashed border-line px-3 py-2 md:grid-cols-[110px_1fr]">
249
- <span className="flex items-center gap-2 font-medium">
250
- <ViewSwatch view="hybrid" />
251
- {VIEW_INFO.hybrid.label}
252
- </span>
253
- <span className="text-xs text-ink-2">no extra vectors · reciprocal rank fusion of the text, video and audio rankings at query time</span>
254
- </div>
255
- </div>
256
- </div>
257
- <p className="text-xs text-muted">
258
- All inputs share one 8,192-token window: ~30 frames × 140 + ~750 audio tokens + transcript fits. Media gets no task
259
- prefix; text uses <Code>title: … | text: …</Code>.
260
- </p>
261
- </Section>
262
-
263
- {/* 4. Query path */}
264
- <Section
265
- n={4}
266
- title="Query → answer"
267
- blurb="Search stops after step 4. Ask continues: the retrieved clips themselves (not just their text) go to Gemma 4, which answers with citations the UI turns into playable clips."
268
- >
269
- <div className="flex flex-col items-stretch xl:flex-row xl:items-stretch">
270
- <Node title="1 · Question" sub="text, or an image for screenshot search" className="xl:flex-1">
271
- <div className="text-xs">“what did Sam think about 3-bit models for coding?”</div>
272
- </Node>
273
- <Arrow />
274
- <Node title="2 · Embed query" sub="EmbeddingGemma 2" accent="var(--series-1)" tag={exp ? `${exp.query_embed_ms.toFixed(0)} ms` : undefined} className="xl:flex-1">
275
- <div>
276
- prefix <Code>task: search result | query:</Code>
277
- </div>
278
- <div className="text-xs text-muted">→ 768d unit vector</div>
279
- </Node>
280
- <Arrow />
281
- <Node title="3 · Truncate" sub="Matryoshka" tag="768/512/256/128" className="xl:flex-1">
282
- <div>keep first d dims of query and corpus</div>
283
- <div>re-normalise both</div>
284
- </Node>
285
- <Arrow />
286
- <Node title="4 · Cosine top-k" sub="matmul over one view" accent="var(--series-3)" tag="<1 ms" className="xl:flex-1">
287
- <div>{seg} scores per query</div>
288
- <div className="text-xs text-muted">hybrid: RRF over 3 rankings</div>
289
- </Node>
290
- <Arrow />
291
- <Node title="5 · Gemma 4 watches" sub="vLLM, k clips" accent="var(--series-5)" tag="~4–5 s" className="xl:flex-1">
292
- <div>per clip: transcript + frames</div>
293
- <div className="text-xs text-muted">(+ audio optional) · ~10k prompt tokens</div>
294
- </Node>
295
- <Arrow />
296
- <Node title="6 · Cited answer" sub="markdown + [n]" className="xl:flex-1">
297
- <div>[n] → plays clip n at its timestamp</div>
298
- <div className="text-xs text-muted">saved to answers/history.jsonl</div>
299
- </Node>
300
- </div>
301
- </Section>
302
-
303
- {/* 5. Matryoshka */}
304
- <Section
305
- n={5}
306
- title="Matryoshka truncation"
307
- blurb="Only 768d vectors are stored. Smaller sizes are prefixes of the same vector, so every dim can be compared on identical data with no re-embedding."
308
- >
309
- <div className="space-y-2 rounded-xl border border-line bg-surface p-5">
310
- {[768, 512, 256, 128].map((d) => (
311
- <div key={d} className="grid grid-cols-[56px_1fr_120px] items-center gap-3">
312
- <span className="text-sm tabular">{d}d</span>
313
- <div className="h-5 rounded bg-surface-2">
314
- <div className="h-5 rounded" style={{ width: `${(d / 768) * 100}%`, background: "var(--series-1)", opacity: 0.35 + (d / 768) * 0.65 }} />
315
- </div>
316
- <span className="text-right text-xs text-muted tabular">
317
- {bytes(seg * d * 4)} / view {d !== 768 && `· ÷${(768 / d).toFixed(1)}`}
318
- </span>
319
- </div>
320
- ))}
321
- <p className="pt-2 text-xs text-muted">
322
- Re-normalising after slicing is required: a truncated unit vector is no longer unit length and silently skews cosine scores
323
- (<Code>vrag/index.py: truncate</Code>).
324
- </p>
325
- </div>
326
- </Section>
327
-
328
- {/* 6. Eval loop */}
329
- <Section
330
- n={6}
331
- title="Evaluation loop"
332
- blurb="Gemma 4 doubles as the benchmark author: it watches sampled segments and writes queries, so every query has a known source segment to score against."
333
- >
334
- <div className="flex flex-col items-stretch lg:flex-row lg:items-stretch">
335
- <Node title="Sample segments" sub="vrag/evalset.py" className="lg:flex-1">
336
- <div>8 per video, skip intro/outro</div>
337
- <div className="text-xs text-muted">{exp ? `${exp.num_queries / 2} segments` : ""}</div>
338
- </Node>
339
- <Arrow />
340
- <Node title="Gemma 4 writes queries" sub="watches clip + reads transcript" accent="var(--series-5)" className="lg:flex-1">
341
- <div>spoken: something Sam says</div>
342
- <div>visual: something only on screen</div>
343
- <div className="text-xs text-muted">+ data/eval/manual.jsonl for your own</div>
344
- </Node>
345
- <Arrow />
346
- <Node title="Grid" sub="vrag/experiments.py" className="lg:flex-1">
347
- <div>6 views × 4 dims × {exp?.num_queries ?? "…"} queries</div>
348
- <div className="text-xs text-muted">R@1/5/10 · MRR@10 · ±1-seg · video hit</div>
349
- </Node>
350
- <Arrow />
351
- <Node title="Experiments page" sub="results.json → charts" className="lg:flex-1">
352
- <div>line chart · heatmap · per-query ranks</div>
353
- </Node>
354
- </div>
355
- </Section>
356
-
357
- {/* 7. Stack + files */}
358
- <Section n={7} title="Stack & files" blurb="Two Python environments keep vLLM's pinned torch/transformers away from the embedding stack.">
359
- <div className="grid gap-4 lg:grid-cols-2">
360
- <div className="rounded-xl border border-line bg-surface p-5">
361
- <table className="w-full text-sm">
362
- <tbody className="[&_td]:py-1.5 [&_tr]:border-t [&_tr]:border-line [&_tr:first-child]:border-0">
363
- <tr><td className="text-muted">Embedding</td><td>google/embeddinggemma-2 · sentence-transformers 6.1 · torchcodec</td></tr>
364
- <tr><td className="text-muted">Generator</td><td>google/gemma-4-12B-it-qat-w4a16-ct · vLLM 0.23 · MTP</td></tr>
365
- <tr><td className="text-muted">ASR</td><td>Gemma 4 12B (default, ~100 s for 3.9 h) · Whisper large-v3-turbo (alt, ~35 min)</td></tr>
366
- <tr><td className="text-muted">Index</td><td>numpy float32 · brute-force cosine</td></tr>
367
- <tr><td className="text-muted">API</td><td>FastAPI · uvicorn · range-served media</td></tr>
368
- <tr><td className="text-muted">Web</td><td>Next.js 16 · React 19 · Tailwind 4 · Recharts</td></tr>
369
- <tr><td className="text-muted">Corpus</td><td>{vids} videos · {stats ? fmtDuration(stats.total_duration_s) : "…"} · {seg} segments</td></tr>
370
- </tbody>
371
- </table>
372
- </div>
373
- <pre className="overflow-x-auto rounded-xl border border-line bg-surface p-5 font-mono text-xs leading-relaxed text-ink-2">{`vrag/ pipeline + API
374
- ingest.py sams-videos → 480p proxy + meta
375
- transcribe.py Gemma 4 / Whisper → transcripts
376
- segment.py ~30s windows + clip/wav/jpg
377
- embedder.py EmbeddingGemma 2 + view inputs
378
- embed.py → data/index/{view}.npy
379
- index.py cosine, MRL truncate, RRF
380
- rag.py retrieve → Gemma 4 → answer
381
- evalset.py synthetic spoken/visual queries
382
- experiments.py view × dim metrics grid
383
- api.py FastAPI for the site
384
- serve/ vLLM env + serve.sh / stop.sh
385
- web/ this site
386
- scripts/dev.sh start everything`}</pre>
387
- </div>
388
- </Section>
389
- </div>
390
- );
391
- }