1use crate::gate::record::GateRecord;
13use std::collections::BTreeMap;
14
15#[derive(Clone, Copy, Debug)]
17pub enum Fmt {
18 Int,
20 One,
22 Two,
24 Ms,
26}
27
28impl Fmt {
29 pub fn apply(self, v: f64) -> String {
30 match self {
31 Fmt::Int => format!("{}", v.round() as i64),
32 Fmt::One => format!("{v:.1}"),
33 Fmt::Two => format!("{v:.2}"),
34 Fmt::Ms => format!("{} ms", v.round() as i64),
35 }
36 }
37}
38
39#[derive(Clone, Copy, Debug)]
41pub struct Slot {
42 pub label: &'static str,
43 pub key: &'static str,
44 pub fmt: Fmt,
45}
46
47const fn slot(label: &'static str, key: &'static str, fmt: Fmt) -> Option<Slot> {
48 Some(Slot { label, key, fmt })
49}
50
51#[derive(Clone, Debug)]
53pub struct CardSpec {
54 pub hero_label: &'static str,
55 pub hero_key: &'static str,
56 pub hero_note: &'static str,
57 pub hero_fmt: Fmt,
58 pub slots: [Option<Slot>; 4],
59}
60
61pub fn spec_for(benchmark_id: &str) -> CardSpec {
65 match benchmark_id {
66 "decode-floor" => CardSpec {
67 hero_label: "Tokens / sec",
68 hero_key: "server_decode_tok_s",
69 hero_note: "decode, steady state",
70 hero_fmt: Fmt::One,
71 slots: [
72 slot("Accept length", "accept_len_mean", Fmt::Two),
73 slot("Output tokens", "output_tokens", Fmt::Int),
74 slot("Runs", "runs", Fmt::Int),
75 None,
76 ],
77 },
78 "concurrency-sweep" => CardSpec {
79 hero_label: "Tokens / sec",
80 hero_key: "peak_aggregate_tok_s",
81 hero_note: "aggregate, best rung of C=1..128",
82 hero_fmt: Fmt::One,
83 slots: [
84 slot("C=1", "c1_aggregate_tok_s", Fmt::One),
85 slot("C=16", "c16_aggregate_tok_s", Fmt::One),
86 slot("C=128", "c128_aggregate_tok_s", Fmt::One),
87 slot("TTFT p50, C=1", "c1_ttft_p50_ms", Fmt::Ms),
88 ],
89 },
90 "concurrency-sweep-dflash2" => CardSpec {
93 hero_label: "Tokens / sec",
94 hero_key: "peak_aggregate_tok_s",
95 hero_note: "aggregate, best rung of C=1..16, DFlash2 armed",
96 hero_fmt: Fmt::One,
97 slots: [
98 slot("C=1", "c1_aggregate_tok_s", Fmt::One),
99 slot("C=4", "c4_aggregate_tok_s", Fmt::One),
100 slot("C=16", "c16_aggregate_tok_s", Fmt::One),
101 slot("TTFT p50, C=1", "c1_ttft_p50_ms", Fmt::Ms),
102 ],
103 },
104 "concurrency-sweep-moe" => CardSpec {
107 hero_label: "Tokens / sec",
108 hero_key: "peak_aggregate_tok_s",
109 hero_note: "aggregate, best rung of C=1..16, ISL 128 / OSL 1024",
110 hero_fmt: Fmt::One,
111 slots: [
112 slot("C=1", "c1_aggregate_tok_s", Fmt::One),
113 slot("C=4", "c4_aggregate_tok_s", Fmt::One),
114 slot("C=16", "c16_aggregate_tok_s", Fmt::One),
115 slot("TTFT p50, C=1", "c1_ttft_p50_ms", Fmt::Ms),
116 ],
117 },
118 "bfcl-subset" | "bfcl-subset-echolp" => CardSpec {
119 hero_label: "Overall accuracy",
120 hero_key: "overall_accuracy",
121 hero_note: "BFCL single-turn — see samples for the draw",
122 hero_fmt: Fmt::Two,
123 slots: [
124 slot("Normalized ST", "normalized_single_turn_score", Fmt::Two),
125 slot("Samples", "samples", Fmt::Int),
126 None,
127 None,
128 ],
129 },
130 "agentic-webserver" => CardSpec {
131 hero_label: "Webserver OK",
132 hero_key: "webserver_ok",
133 hero_note: "runs that built, served and tore down cleanly",
134 hero_fmt: Fmt::Int,
135 slots: [
136 slot("Followed directions", "followed_directions", Fmt::Int),
137 slot("Iterations", "iterations", Fmt::Int),
138 slot("Seconds / turn", "s_per_turn", Fmt::Two),
139 slot("Decode tok/s", "decode_tps", Fmt::One),
140 ],
141 },
142 "ttft-cold-gate" => CardSpec {
143 hero_label: "TTFT median",
144 hero_key: "median_ms",
145 hero_note: "cold, first token after load",
146 hero_fmt: Fmt::Ms,
147 slots: [
148 slot("p90", "p90_ms", Fmt::Ms),
149 slot("Samples", "samples", Fmt::Int),
150 None,
151 None,
152 ],
153 },
154 "ttft-warm-gate" => CardSpec {
155 hero_label: "TTFT median",
156 hero_key: "median_ms",
157 hero_note: "warm, prefix cache primed",
158 hero_fmt: Fmt::Ms,
159 slots: [
160 slot("p90", "p90_ms", Fmt::Ms),
161 slot("Samples", "samples", Fmt::Int),
162 None,
163 None,
164 ],
165 },
166 "high-isl-ttft-cold" | "high-isl-ttft-cold-moe" => CardSpec {
169 hero_label: "TTFT, 32k prompt",
170 hero_key: "median_ms",
171 hero_note: "cold, one fresh request",
172 hero_fmt: Fmt::Ms,
173 slots: [
174 slot("Prompt tokens", "prompt_tokens", Fmt::Int),
175 slot("Samples", "samples", Fmt::Int),
176 None,
177 None,
178 ],
179 },
180 "high-isl-ttft-warm" | "high-isl-ttft-warm-moe" => CardSpec {
181 hero_label: "TTFT, 32k prompt",
182 hero_key: "median_ms",
183 hero_note: "warm, the same request re-sent",
184 hero_fmt: Fmt::Ms,
185 slots: [
186 slot("Prompt tokens", "prompt_tokens", Fmt::Int),
187 slot("Samples", "samples", Fmt::Int),
188 None,
189 None,
190 ],
191 },
192 "vision-fidelity" => CardSpec {
193 hero_label: "Geometry cells matched",
194 hero_key: "geometry_matched",
195 hero_note: "vision fidelity, control held",
196 hero_fmt: Fmt::Int,
197 slots: [
198 slot("Cells asserted", "geometry_asserted", Fmt::Int),
199 slot("Probes passed", "probes_passed", Fmt::Int),
200 slot("Probes total", "probes_total", Fmt::Int),
201 None,
202 ],
203 },
204 "video-fidelity" => CardSpec {
205 hero_label: "Legs passed",
206 hero_key: "legs_passed",
207 hero_note: "video fidelity, control held",
208 hero_fmt: Fmt::Int,
209 slots: [
210 slot("Legs asserted", "legs_asserted", Fmt::Int),
211 slot("Skipped", "legs_skipped", Fmt::Int),
212 None,
213 None,
214 ],
215 },
216 "ssm-state-poisoning-gate" => CardSpec {
217 hero_label: "Replays byte-identical",
218 hero_key: "invariant",
219 hero_note: "SSM state isolation under interleaving",
220 hero_fmt: Fmt::Int,
221 slots: [
222 slot("Rounds", "rounds", Fmt::Int),
223 slot("Collapsed", "collapsed", Fmt::Int),
224 slot("Jittered", "jittered", Fmt::Int),
225 None,
226 ],
227 },
228 _ => CardSpec {
231 hero_label: "Result",
232 hero_key: "",
233 hero_note: "first metric, alphabetically — no card mapping for this benchmark yet",
234 hero_fmt: Fmt::Two,
235 slots: [None, None, None, None],
236 },
237 }
238}
239
240pub fn parse_args(raw: &str) -> Result<BTreeMap<String, String>, String> {
244 let mut out = BTreeMap::new();
245 for part in raw.split(',') {
246 let p = part.trim();
247 if p.is_empty() {
248 continue;
249 }
250 let (k, v) = p
251 .split_once('=')
252 .ok_or_else(|| format!("`{p}` is not key=value"))?;
253 let k = k.trim();
254 if k.is_empty() {
255 return Err(format!("`{p}` has an empty key"));
256 }
257 out.insert(k.to_lowercase(), v.trim().to_string());
258 }
259 Ok(out)
260}
261
262fn esc(s: &str) -> String {
265 s.replace('&', "&")
266 .replace('<', "<")
267 .replace('>', ">")
268}
269
270fn set_attr(svg: &mut String, id: &str, attr: &str, value: &str) {
273 let needle = format!("id=\"{id}\"");
274 let Some(at) = svg.find(&needle) else { return };
275 let Some(open) = svg[..at].rfind('<') else {
276 return;
277 };
278 let Some(close) = svg[at..].find('>').map(|i| at + i) else {
279 return;
280 };
281 let existing = format!(" {attr}=\"");
282 if let Some(k) = svg[open..close].find(&existing) {
283 let vstart = open + k + existing.len();
284 let Some(vend) = svg[vstart..close].find('"').map(|i| vstart + i) else {
285 return;
286 };
287 svg.replace_range(vstart..vend, value);
288 return;
289 }
290 svg.insert_str(close, &format!(" {attr}=\"{value}\""));
291}
292
293fn set(svg: &mut String, id: &str, value: &str) {
296 let needle = format!("id=\"{id}\"");
297 let Some(at) = svg.find(&needle) else { return };
298 let Some(gt) = svg[at..].find('>').map(|i| at + i + 1) else {
299 return;
300 };
301 let Some(lt) = svg[gt..].find('<').map(|i| gt + i) else {
302 return;
303 };
304 svg.replace_range(gt..lt, &esc(value));
305}
306
307pub fn render(template: &str, record: &GateRecord, args: &BTreeMap<String, String>) -> String {
309 let spec = spec_for(&record.benchmark_id);
310 let mut svg = template.to_string();
311 let m = &record.metrics;
312
313 let hero = if spec.hero_key.is_empty() {
314 m.iter().next().map(|(k, v)| (k.clone(), *v))
315 } else {
316 m.get(spec.hero_key)
317 .map(|v| (spec.hero_key.to_string(), *v))
318 };
319 match hero {
320 Some((key, v)) => {
321 set(&mut svg, "value-toks", &spec.hero_fmt.apply(v));
322 let label = if spec.hero_key.is_empty() {
323 key
324 } else {
325 spec.hero_label.to_string()
326 };
327 set(&mut svg, "label-toks", &label);
328 }
329 None => {
331 set(&mut svg, "value-toks", "—");
332 set(&mut svg, "label-toks", spec.hero_label);
333 }
334 }
335 set(&mut svg, "note-toks", spec.hero_note);
336
337 match record.verdict.as_deref() {
338 Some(_) if record.verdict_passes() => {
339 set(&mut svg, "value-verdict", "PASS");
340 set_attr(&mut svg, "verdict-chip", "fill", "#12B981");
341 }
342 Some(_) => {
343 set(&mut svg, "value-verdict", "FAIL");
344 set_attr(&mut svg, "verdict-chip", "fill", "#EFB338");
345 set_attr(&mut svg, "value-toks", "fill", "#82868F");
347 }
348 None => set_attr(&mut svg, "field-verdict", "display", "none"),
350 }
351
352 set(&mut svg, "value-model", &record.target_model);
353 set(
354 &mut svg,
355 "value-quant",
356 record
357 .params
358 .get("quant")
359 .or_else(|| record.params.get("quantization"))
360 .map_or_else(|| quant_from_model(&record.target_model), |q| q.clone())
361 .as_str(),
362 );
363 set(
364 &mut svg,
365 "value-recipe",
366 record.served_by.as_deref().unwrap_or("—"),
367 );
368 set(&mut svg, "value-test", &record.benchmark_id);
369 set(&mut svg, "value-hardware", &hardware_line(record));
370
371 for (i, s) in spec.slots.iter().enumerate() {
372 let (lid, vid) = (format!("label-m{}", i + 1), format!("value-m{}", i + 1));
373 match s.and_then(|s| m.get(s.key).map(|v| (s, *v))) {
374 Some((s, v)) => {
375 set(&mut svg, &lid, s.label);
376 set(&mut svg, &vid, &s.fmt.apply(v));
377 }
378 None => set_attr(&mut svg, &format!("field-m{}", i + 1), "display", "none"),
381 }
382 }
383
384 for (field, value_id, value) in [
385 ("field-author", "value-author", args.get("author")),
386 ("field-handle", "value-handle", args.get("handle")),
387 (
388 "field-site",
389 "value-site",
390 args.get("website").or_else(|| args.get("site")),
391 ),
392 ] {
393 match value {
394 Some(v) if !v.is_empty() => set(&mut svg, value_id, v),
395 _ => set_attr(&mut svg, field, "display", "none"),
396 }
397 }
398 svg
399}
400
401fn quant_from_model(model: &str) -> String {
404 let up = model.to_uppercase();
405 for tag in ["NVFP4", "FP8", "W4A4", "W4A16", "BF16", "INT8", "FP16"] {
406 if up.contains(tag) {
407 return tag.to_string();
408 }
409 }
410 "—".to_string()
411}
412
413fn hardware_line(record: &GateRecord) -> String {
414 let hw = &record.hardware;
415 let gpu = hw.gate_key();
416 match record
417 .hardware_state
418 .as_ref()
419 .and_then(|s| s.before.sm_clock_mhz)
420 {
421 Some(mhz) => format!("{gpu}, SM {mhz:.0} MHz"),
422 None => gpu,
423 }
424}