Skip to content

Commit 426504f

Browse files
committed
blog: DiffusionGemma finetune post with charts and live reveal embed
1 parent 9ffcbdc commit 426504f

5 files changed

Lines changed: 1022 additions & 0 deletions

File tree

Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
"use client";
2+
3+
/**
4+
* Charts for the DiffusionGemma finetune post. Every number comes from
5+
* lib/diffusion-blog-data.ts — nothing is hardcoded here.
6+
*/
7+
8+
import { useEffect, useRef, useState } from "react";
9+
import {
10+
B5_OWN_PROMPT,
11+
BANDS,
12+
BOARD_BRIEFS,
13+
DIAL,
14+
FRONTIER_BAND,
15+
OURS_OWN_PROMPT,
16+
ROUND_COST_USD,
17+
SCATTER,
18+
SPEED,
19+
STAGES,
20+
} from "@/lib/diffusion-blog-data";
21+
import { Chart, ChartDataDisclosure, DataTable, Row, styles as s, slotClass } from "./primitives";
22+
23+
function useWidth(initial = 712) {
24+
const holder = useRef<HTMLDivElement>(null);
25+
const [w, setW] = useState(initial);
26+
useEffect(() => {
27+
const el = holder.current;
28+
if (!el) return;
29+
const ro = new ResizeObserver(([entry]) => setW(Math.max(320, Math.round(entry.contentRect.width))));
30+
ro.observe(el);
31+
return () => ro.disconnect();
32+
}, []);
33+
return { holder, w };
34+
}
35+
36+
/* 1 ─ hero: score vs active parameters ------------------------------- */
37+
38+
export function DgScoreboard() {
39+
const { holder, w: W } = useWidth();
40+
const H = 430;
41+
const PAD = { left: 46, right: 24, top: 34, bottom: 44 };
42+
const PW = W - PAD.left - PAD.right;
43+
const PH = H - PAD.top - PAD.bottom;
44+
45+
const X_MIN = Math.log10(2);
46+
const X_MAX = Math.log10(45);
47+
const x = (b: number) => PAD.left + ((Math.log10(b) - X_MIN) / (X_MAX - X_MIN)) * PW;
48+
const y = (v: number) => PAD.top + (1 - v / 100) * PH;
49+
50+
const hero = SCATTER.find((p) => p.hero)!;
51+
const ghost = SCATTER.find((p) => p.ghost)!;
52+
53+
return (
54+
<Chart
55+
title="OpenUI score vs active parameters, open models"
56+
sub="Every model measured under the identical public protocol: 46 briefs, 4 runs each, temperature 0.7, strict parser scoring."
57+
note={
58+
<>
59+
Same 4B active parameters as its base and its autoregressive twin; {OURS_OWN_PROMPT}% with
60+
the prompt it was trained with. The shaded band is where closed frontier models score.
61+
</>
62+
}
63+
>
64+
<div ref={holder} className={s.svgHolder} style={{ position: "relative" }}>
65+
<svg width={W} height={H} viewBox={`0 0 ${W} ${H}`} role="img"
66+
aria-label="Scatter chart of OpenUI score against active parameters. The finetuned DiffusionGemma scores highest of all open models at 4 billion active parameters.">
67+
{/* frontier band */}
68+
<rect x={PAD.left} width={PW} y={y(FRONTIER_BAND.hi)} height={y(FRONTIER_BAND.lo) - y(FRONTIER_BAND.hi)}
69+
fill="var(--rule)" opacity=".45" />
70+
<text x={PAD.left + 8} y={y(FRONTIER_BAND.hi) + 15} fontSize="11.5" fill="var(--ink-muted)">
71+
{FRONTIER_BAND.label} · {FRONTIER_BAND.lo}{FRONTIER_BAND.hi}%
72+
</text>
73+
{/* gridlines */}
74+
{[0, 20, 40, 60, 80, 100].map((t) => (
75+
<g key={t}>
76+
<line x1={PAD.left} x2={PAD.left + PW} y1={y(t)} y2={y(t)} stroke="var(--rule)" />
77+
<text x={PAD.left - 9} y={y(t) + 4} textAnchor="end" fontSize="12" fill="var(--ink-muted)">{t}</text>
78+
</g>
79+
))}
80+
{[2, 4, 8, 14, 31].map((b) => (
81+
<text key={b} x={x(b)} y={H - 18} textAnchor="middle" fontSize="12" fill="var(--ink-muted)">{b}B</text>
82+
))}
83+
<text x={PAD.left + PW / 2} y={H - 2} textAnchor="middle" fontSize="11.5" fill="var(--ink-muted)">
84+
active parameters →
85+
</text>
86+
{/* the week arrow: base → finetuned, same params */}
87+
<line x1={x(ghost.params)} y1={y(ghost.score) - 9} x2={x(hero.params)} y2={y(hero.score) + 12}
88+
stroke="var(--ink-muted)" strokeWidth="1.4" strokeDasharray="4 4" markerEnd="url(#dg-arr)" />
89+
<defs>
90+
<marker id="dg-arr" markerWidth="7" markerHeight="7" refX="5.5" refY="3.5" orient="auto">
91+
<path d="M0,0 L7,3.5 L0,7 Z" fill="var(--ink-muted)" />
92+
</marker>
93+
</defs>
94+
{/* points — provider hues borrowed from the /benchmarks board scatter */}
95+
{SCATTER.map((p) => {
96+
const HUE: Record<string, string> = {
97+
ours: "var(--pO, #a78bfa)", dgbase: "#3978e6", g31: "#3978e6", twin: "#3978e6",
98+
phi4: "#2774c8", ministral: "#dc5a4f", granite: "#4d6fb8", lfm: "#c04f79",
99+
};
100+
const hue = HUE[p.id] ?? "var(--ink-muted)";
101+
const cx = x(p.params);
102+
const cy = y(p.score);
103+
if (p.hero) {
104+
return (
105+
<g key={p.id}>
106+
<circle cx={cx} cy={cy} r="13" fill={hue} opacity=".2" />
107+
<circle cx={cx} cy={cy} r="6.5" fill={hue} stroke="var(--surface)" strokeWidth="2" />
108+
<text x={cx + 17} y={cy - 8} fontSize="13.5" fontWeight={750} fill="var(--ink)"
109+
stroke="var(--surface)" strokeWidth={4} paintOrder="stroke">
110+
{p.label}
111+
</text>
112+
<text x={cx + 17} y={cy + 9} fontSize="11.5" fontWeight={650} fill={hue}
113+
stroke="var(--surface)" strokeWidth={4} paintOrder="stroke">
114+
{p.score}% · up from {SCATTER.find((q) => q.ghost)!.score}%
115+
</text>
116+
</g>
117+
);
118+
}
119+
const anchorRight = p.params > 12;
120+
return (
121+
<g key={p.id}>
122+
<circle cx={cx} cy={cy} r="4.5" fill={hue} stroke="var(--surface)" strokeWidth="2" />
123+
<text x={anchorRight ? cx - 10 : cx + 10} y={cy + 4} fontSize="12"
124+
textAnchor={anchorRight ? "end" : "start"} fill="var(--ink-muted)"
125+
stroke="var(--surface)" strokeWidth={4} paintOrder="stroke">
126+
{p.label} · {p.score}%
127+
</text>
128+
</g>
129+
);
130+
})}
131+
</svg>
132+
</div>
133+
<ChartDataDisclosure>
134+
<DataTable>
135+
<thead><tr><th>model</th><th>active params</th><th>OpenUI score</th></tr></thead>
136+
<tbody>
137+
{SCATTER.map((p) => (
138+
<tr key={p.id}><td>{p.label}</td><td>{p.params}B</td><td>{p.score}%</td></tr>
139+
))}
140+
</tbody>
141+
</DataTable>
142+
</ChartDataDisclosure>
143+
</Chart>
144+
);
145+
}
146+
147+
/* 2 ─ defect anatomy across the three stages -------------------------- */
148+
149+
export function DgAnatomy() {
150+
const CLASSES = [
151+
{ key: "schema" as const, label: "schema errors", slot: 1 as const },
152+
{ key: "orphans" as const, label: "orphaned sections", slot: 2 as const },
153+
{ key: "unresolved" as const, label: "undefined names", slot: 3 as const },
154+
];
155+
const total = (st: (typeof STAGES)[number]) => st.schema + st.orphans + st.unresolved;
156+
const maxT = Math.max(...STAGES.map(total));
157+
const COLH = 250;
158+
const reduction = (total(STAGES[0]) / total(STAGES[STAGES.length - 1])).toFixed(1);
159+
return (
160+
<Chart
161+
title="Where the errors went"
162+
sub="One bar per stage, split by error class. SFT crushed the grammar but wiring got worse. Self-distillation is what finally moved everything at once."
163+
legend={CLASSES.map((c) => ({ label: c.label, slot: c.slot }))}
164+
note={
165+
<>
166+
Defect sites across the same {BOARD_BRIEFS}-brief board, strict parser scoring. The model
167+
was writing MORE the whole time: {STAGES[0].statements.toLocaleString()}{" "}
168+
{STAGES[2].statements.toLocaleString()} statements.
169+
</>
170+
}
171+
>
172+
<div style={{ display: "flex", gap: 34, alignItems: "flex-end", justifyContent: "center", padding: "10px 8px 4px" }}>
173+
{STAGES.map((st, i) => {
174+
const t = total(st);
175+
const last = i === STAGES.length - 1;
176+
const rate = ((t / st.statements) * 100).toFixed(1);
177+
return (
178+
<div key={st.id} style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 7, flex: "0 1 200px", minWidth: 0 }}>
179+
<span style={{ fontSize: 15, fontWeight: 700 }}>
180+
{t}
181+
{last ? (
182+
<span className={slotClass(1)} style={{ fontSize: 12.5, fontWeight: 800, marginLeft: 8 }}>
183+
{reduction}× fewer
184+
</span>
185+
) : null}
186+
</span>
187+
<div style={{ display: "flex", flexDirection: "column", width: "100%", height: (t / maxT) * COLH, borderRadius: 7, overflow: "hidden" }}>
188+
{CLASSES.map((c) => (
189+
<span key={c.key} className={`${s.bar} ${slotClass(c.slot)}`}
190+
style={{ width: "100%", height: `${(st[c.key] / t) * 100}%`, minHeight: st[c.key] ? 2 : 0, borderRadius: 0 }} />
191+
))}
192+
</div>
193+
<span style={{ fontSize: 12.5, fontWeight: 650, textAlign: "center" }}>{st.label}</span>
194+
<span style={{ fontSize: 11.5, color: "var(--ink-muted)", textAlign: "center", lineHeight: 1.5 }}>
195+
{st.complete}/{BOARD_BRIEFS} screens complete
196+
<br />
197+
{rate} defects per 100 statements
198+
</span>
199+
</div>
200+
);
201+
})}
202+
</div>
203+
</Chart>
204+
);
205+
}
206+
207+
/* 3b ─ the self-distillation loop, as a block diagram ------------------ */
208+
209+
export function DgLoop() {
210+
const BOXES = [
211+
{ t: "generate", d: "the model writes a few hundred screens" },
212+
{ t: "verify", d: "the parser keeps only the perfect ones" },
213+
{ t: "repair", d: "near-misses fixed by a gated LLM, defects only" },
214+
{ t: "retrain", d: "the model learns from its own best work" },
215+
];
216+
return (
217+
<Chart
218+
title="Reinforcement learning where the compiler is the reward"
219+
sub="The simplest honest member of the RL family: generate, verify, keep only the wins. We call it self-distillation."
220+
note={<>Each pass costs about ${ROUND_COST_USD} and two hours on one A100. A repair touches ~2 statements out of 41 on average; the gate rejects anything that rewrites, deletes, or invents.</>}
221+
>
222+
<div style={{ position: "relative", padding: "14px 4px 44px" }}>
223+
<div style={{ display: "flex", alignItems: "stretch", gap: 0, flexWrap: "wrap", justifyContent: "center" }}>
224+
{BOXES.map((b, i) => (
225+
<div key={b.t} style={{ display: "flex", alignItems: "center" }}>
226+
<div style={{
227+
border: "1px solid var(--rule)", borderRadius: 12, padding: "12px 14px", width: 168,
228+
display: "flex", flexDirection: "column", gap: 4, background: "color-mix(in srgb, var(--rule) 26%, transparent)",
229+
}}>
230+
<span className={slotClass(1)} style={{ fontSize: 11, fontWeight: 800, letterSpacing: ".07em", textTransform: "uppercase" }}>
231+
{i + 1} · {b.t}
232+
</span>
233+
<span style={{ fontSize: 12, color: "var(--ink-muted)", lineHeight: 1.45 }}>{b.d}</span>
234+
</div>
235+
{i < BOXES.length - 1 ? (
236+
<span style={{ padding: "0 9px", color: "var(--ink-muted)", fontSize: 16 }} aria-hidden></span>
237+
) : null}
238+
</div>
239+
))}
240+
</div>
241+
{/* the return path: RETRAIN feeds GENERATE */}
242+
<svg aria-hidden style={{ position: "absolute", left: "6%", right: "6%", bottom: 2, width: "88%", height: 40, overflow: "visible" }}
243+
viewBox="0 0 100 30" preserveAspectRatio="none">
244+
<defs>
245+
<marker id="dg-loop-arr" markerWidth="7" markerHeight="7" refX="4.5" refY="3.5" orient="auto">
246+
<path d="M0,0 L7,3.5 L0,7 Z" fill="var(--ink-muted)" />
247+
</marker>
248+
</defs>
249+
<path d="M 92,0 L 92,16 Q 92,23 86,23 L 14,23 Q 8,23 8,16 L 8,4"
250+
fill="none" stroke="var(--ink-muted)" strokeWidth="1.4" strokeDasharray="4 4"
251+
vectorEffect="non-scaling-stroke" markerEnd="url(#dg-loop-arr)" />
252+
</svg>
253+
<span style={{
254+
position: "absolute", left: "50%", bottom: 4, transform: "translateX(-50%)",
255+
fontSize: 11.5, color: "var(--ink-muted)", background: "var(--surface)", padding: "0 10px", whiteSpace: "nowrap",
256+
}}>
257+
each pass trains the model that writes the next batch
258+
</span>
259+
</div>
260+
</Chart>
261+
);
262+
}
263+
264+
/* 4 ─ the step dial: fewer denoising steps, better screens ------------ */
265+
266+
export function DgDial() {
267+
const { holder, w: W } = useWidth();
268+
const H = 300;
269+
const PAD = { left: 44, right: 120, top: 20, bottom: 42 };
270+
const PW = W - PAD.left - PAD.right;
271+
const PH = H - PAD.top - PAD.bottom;
272+
const stepsAll = [16, 24, 32, 64];
273+
const x = (st: number) => PAD.left + (stepsAll.indexOf(st) / (stepsAll.length - 1)) * PW;
274+
const y = (v: number) => PAD.top + (1 - v / 36) * PH;
275+
const path = (pts: Array<{ steps: number; complete: number }>) =>
276+
pts.map((p, i) => `${i === 0 ? "M" : "L"} ${x(p.steps)},${y(p.complete)}`).join(" ");
277+
return (
278+
<Chart
279+
title="Half the denoising steps, better screens"
280+
sub="Complete screens out of 46 at a fixed step budget. Each round of self-teaching moves the whole curve up and the cliff to the left."
281+
legend={DIAL.map((d, i) => ({ label: d.label, slot: (i + 1) as 1 | 2 | 3 }))}
282+
note="At 32 steps the finished model beats its own 64-step score. Below 24, wiring runs out of passes before grammar does."
283+
>
284+
<div ref={holder} className={s.svgHolder}>
285+
<svg width={W} height={H} viewBox={`0 0 ${W} ${H}`} role="img"
286+
aria-label="Line chart: completed screens against denoising steps for round one and round three models.">
287+
{[0, 12, 24, 36].map((t) => (
288+
<g key={t}>
289+
<line x1={PAD.left} x2={PAD.left + PW} y1={y(t)} y2={y(t)} stroke="var(--rule)" />
290+
<text x={PAD.left - 9} y={y(t) + 4} textAnchor="end" fontSize="12" fill="var(--ink-muted)">{t}</text>
291+
</g>
292+
))}
293+
{stepsAll.map((st) => (
294+
<text key={st} x={x(st)} y={H - 18} textAnchor="middle" fontSize="12" fill="var(--ink-muted)">{st}</text>
295+
))}
296+
<text x={PAD.left + PW / 2} y={H - 2} textAnchor="middle" fontSize="11.5" fill="var(--ink-muted)">
297+
denoising steps per block →
298+
</text>
299+
{DIAL.map((d2, i) => (
300+
<g key={d2.id} className={slotClass((i + 1) as 1 | 2 | 3)}>
301+
<path d={path(d2.points)} fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" />
302+
{d2.points.map((p) => (
303+
<circle key={p.steps} cx={x(p.steps)} cy={y(p.complete)} r="4" fill="currentColor" />
304+
))}
305+
<text x={x(d2.points[d2.points.length - 1].steps) + 10}
306+
y={y(d2.points[d2.points.length - 1].complete) + 4}
307+
fontSize="12" fontWeight={650} fill="currentColor">{d2.short}</text>
308+
</g>
309+
))}
310+
</svg>
311+
</div>
312+
</Chart>
313+
);
314+
}
315+
316+
/* 5 ─ serving speed ---------------------------------------------------- */
317+
318+
export function DgSpeed() {
319+
const max = Math.max(...SPEED.map((r) => r.secPerScreen));
320+
return (
321+
<Chart
322+
title="Seconds per screen in production"
323+
sub="Same serving stack for all three: vLLM, FP8, one A100, one request at a time. The last two rows share an identical early-stop sampler."
324+
note="No serving config changed between the last two rows. The model simply became certain enough that the sampler stops early on its own."
325+
>
326+
<div className={s.rows}>
327+
{SPEED.map((r, i) => (
328+
<Row key={r.id} label={r.label} wide tip={r.note}>
329+
<span className={`${s.bar} ${slotClass(((i % 3) + 1) as 1 | 2 | 3)}`}
330+
style={{ width: `${(r.secPerScreen / max) * 86}%` }} />
331+
<span className={s.value}>{r.secPerScreen}s · {r.toksPerSec} tok/s</span>
332+
</Row>
333+
))}
334+
</div>
335+
</Chart>
336+
);
337+
}
338+
339+
/* 6 ─ difficulty bands: where the gain landed -------------------------- */
340+
341+
export function DgBands() {
342+
const COLH = 210;
343+
return (
344+
<Chart
345+
title="The hard screens are where it won"
346+
sub="Published runs completed per difficulty band, base vs finetuned, same standard prompt. Base collapses as briefs get denser; the finetune keeps going."
347+
legend={[
348+
{ label: "base model", slot: 3 },
349+
{ label: "after SFT + self-distillation", slot: 1 },
350+
]}
351+
note={
352+
<>
353+
46 briefs in five bands by complexity, 4 runs each. On the medium band base completed 1 run
354+
of 40. On the densest band the finetune reaches {B5_OWN_PROMPT}/32 with the prompt it was
355+
trained with.
356+
</>
357+
}
358+
>
359+
<div style={{ display: "flex", gap: 26, alignItems: "flex-end", justifyContent: "center", padding: "10px 8px 4px" }}>
360+
{BANDS.map((b) => (
361+
<div key={b.id} style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 7, flex: "0 1 130px", minWidth: 0 }}>
362+
<div style={{ display: "flex", gap: 6, alignItems: "flex-end", width: "100%", justifyContent: "center", height: COLH }}>
363+
{([["base", 3], ["ours", 1]] as const).map(([k, slot]) => (
364+
<div key={k} style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 4, width: 44 }}>
365+
<span style={{ fontSize: 12, fontWeight: 700, color: k === "base" ? "var(--ink-muted)" : undefined }}
366+
className={k === "ours" ? slotClass(1) : undefined}>
367+
{b[k]}
368+
</span>
369+
<span className={`${s.bar} ${slotClass(slot)}`}
370+
style={{ width: "100%", height: Math.max(3, (b[k] / b.runs) * (COLH - 26)), borderRadius: 5 }} />
371+
</div>
372+
))}
373+
</div>
374+
<span style={{ fontSize: 12, fontWeight: 650 }}>{b.label}</span>
375+
<span style={{ fontSize: 11, color: "var(--ink-muted)" }}>of {b.runs} runs</span>
376+
</div>
377+
))}
378+
</div>
379+
</Chart>
380+
);
381+
}

0 commit comments

Comments
 (0)