|
|
@@ -14,6 +14,9 @@ use serde::{Deserialize, Serialize};
|
|
|
use std::collections::HashSet;
|
|
|
use std::path::Path;
|
|
|
|
|
|
+pub(crate) mod parse;
|
|
|
+pub(crate) use parse::{PerfCell, parse_bench_json, perf_from};
|
|
|
+
|
|
|
const PAGE_TEMPLATE: &str = include_str!("page.html");
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
@@ -37,6 +40,8 @@ pub(crate) struct ReportEntry {
|
|
|
points: Option<String>,
|
|
|
/// points/max as a ratio, for sorting the Points column (null when absent).
|
|
|
ratio: Option<f64>,
|
|
|
+ /// llama-benchy decode speeds (empty when the report has no perf box).
|
|
|
+ perf: Vec<PerfCell>,
|
|
|
}
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
@@ -172,26 +177,6 @@ fn slash_pair(s: &str) -> Option<(u32, u32)> {
|
|
|
None
|
|
|
}
|
|
|
|
|
|
-/// Parse the tool-eval-bench `--json-file` output (authoritative for
|
|
|
-/// `sm121 bench` runs): `scores.final_score` (0–100), `scores.total_points`
|
|
|
-/// / `max_points` (hardmode). Returns `(pct, points, ratio)`; `points` is
|
|
|
-/// suppressed when max is 100 (already normalized, same rule as the text
|
|
|
-/// parsers).
|
|
|
-fn parse_bench_json(content: &str) -> Option<(f64, Option<String>, Option<f64>)> {
|
|
|
- let v: serde_json::Value = serde_json::from_str(content).ok()?;
|
|
|
- let s = v.get("scores")?;
|
|
|
- let pct = s.get("final_score").and_then(|x| x.as_f64())?;
|
|
|
- let tp = s.get("total_points").and_then(|x| x.as_u64());
|
|
|
- let mp = s.get("max_points").and_then(|x| x.as_u64());
|
|
|
- let (points, ratio) = match (tp, mp) {
|
|
|
- (Some(p), Some(m)) if m > 0 && m != 100 => {
|
|
|
- (Some(format!("{p}/{m}")), Some(p as f64 / m as f64))
|
|
|
- }
|
|
|
- _ => (None, None),
|
|
|
- };
|
|
|
- Some((pct, points, ratio))
|
|
|
-}
|
|
|
-
|
|
|
fn scan_reports(recipe_dir: &Path) -> Vec<ReportEntry> {
|
|
|
let reports = recipe_dir.join("reports");
|
|
|
let Ok(rd) = std::fs::read_dir(&reports) else {
|
|
|
@@ -251,6 +236,7 @@ fn scan_reports(recipe_dir: &Path) -> Vec<ReportEntry> {
|
|
|
.as_deref()
|
|
|
.and_then(extract_time)
|
|
|
.or_else(|| log.as_deref().and_then(extract_time));
|
|
|
+ let perf = perf_from(md.as_deref(), log.as_deref());
|
|
|
let file = files
|
|
|
.iter()
|
|
|
.find(|f| f.ends_with(".md"))
|
|
|
@@ -267,6 +253,7 @@ fn scan_reports(recipe_dir: &Path) -> Vec<ReportEntry> {
|
|
|
pct,
|
|
|
points,
|
|
|
ratio,
|
|
|
+ perf,
|
|
|
});
|
|
|
}
|
|
|
out.sort_by(|a, b| (&a.date, &a.label).cmp(&(&b.date, &b.label)));
|
|
|
@@ -328,6 +315,13 @@ fn latest_score(r: &ReportRow) -> Option<&ReportEntry> {
|
|
|
.find(|e| e.pct.is_some() || e.points.is_some())
|
|
|
}
|
|
|
|
|
|
+fn latest_perf(r: &ReportRow) -> Option<(&PerfCell, Option<&PerfCell>)> {
|
|
|
+ r.reports.iter().rev().find_map(|e| {
|
|
|
+ let c1 = e.perf.iter().find(|p| p.d == 0 && p.c == 1);
|
|
|
+ c1.map(|c1| (c1, e.perf.iter().find(|p| p.d == 0 && p.c == 2)))
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
/// Normalized score, whole numbers without decimals (81, not 81.0).
|
|
|
fn fmt_pct(pct: f64) -> String {
|
|
|
if (pct - pct.trunc()).abs() < 1e-9 {
|
|
|
@@ -337,13 +331,18 @@ fn fmt_pct(pct: f64) -> String {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+fn fmt_tps(c: Option<&PerfCell>) -> String {
|
|
|
+ c.map(|c| format!("{:.1}", c.tg))
|
|
|
+ .unwrap_or_else(|| "—".into())
|
|
|
+}
|
|
|
+
|
|
|
fn markdown(rows: &[ReportRow]) -> String {
|
|
|
let mut s = format!(
|
|
|
"# {}\n\n",
|
|
|
fluent_i18n::t!("report-title", { "count" => rows.len() })
|
|
|
);
|
|
|
s.push_str(&format!(
|
|
|
- "| {} | {} | {} | {} | {} | {} | {} | {} | {} |\n",
|
|
|
+ "| {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} |\n",
|
|
|
t("report-col-model"),
|
|
|
t("report-col-quant"),
|
|
|
t("report-col-recipe"),
|
|
|
@@ -353,13 +352,16 @@ fn markdown(rows: &[ReportRow]) -> String {
|
|
|
t("report-col-last"),
|
|
|
t("report-col-points"),
|
|
|
t("report-col-score"),
|
|
|
+ t("report-col-tps1"),
|
|
|
+ t("report-col-tps2"),
|
|
|
));
|
|
|
- s.push_str("|---|---|---|---|---|---|---|---|---|\n");
|
|
|
+ s.push_str("|---|---|---|---|---|---|---|---|---|---|---|\n");
|
|
|
for r in rows {
|
|
|
let last = r.reports.last();
|
|
|
let latest = latest_score(r);
|
|
|
+ let perf = latest_perf(r);
|
|
|
s.push_str(&format!(
|
|
|
- "| {} | {} | {} | {} | {} | {} | {} | {} | {} |\n",
|
|
|
+ "| {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} |\n",
|
|
|
r.model,
|
|
|
r.quant,
|
|
|
r.name,
|
|
|
@@ -380,6 +382,8 @@ fn markdown(rows: &[ReportRow]) -> String {
|
|
|
.and_then(|e| e.pct)
|
|
|
.map(fmt_pct)
|
|
|
.unwrap_or_else(|| "—".into()),
|
|
|
+ fmt_tps(perf.map(|(a, _)| a)),
|
|
|
+ fmt_tps(perf.and_then(|(_, b)| b)),
|
|
|
));
|
|
|
}
|
|
|
s
|