mcap/tools/gpu_bench/results.jai
Musa Mahmood da616d2802 [0.1.24] GPU benchmark (gpu-bench branch, not for master)
bin/gpu-bench times every GPU model (OCR mobile and server, the label
image encoder, segmentation; optionally the CPU and the label text
encoder) over a fixed 30-image corpus in tools/gpu_bench/corpus. Every
corpus file is checked against corpus.txt's SHA-256 before anything
runs; each model's files and models.config entry are fingerprinted;
the GPU, driver, power limit, CUDA runtime, cuDNN and onnxruntime are
recorded, and NVML samples memory, load, power and clocks during the
timed passes. Results go to bin/gpu-bench-results as text files.

The GPU Bench button opens a window that runs gpu-bench in its own
process (no console) and lays results files from several machines side
by side against a baseline, marking a different corpus or model.
2026-10-17 12:38:25 -04:00

92 lines
3.3 KiB
Plaintext

// gpu-bench's results file: what gpu-bench writes, and what mcap's GPU Benchmark window reads to put
// several machines side by side. Plain text, one fact a line:
//
// gpu-bench 1
// machine: NAME a header line: key, colon, value
// ...
// model: ocr/ppocr-v5-mobile 1a2b... the fingerprint of the files a task's model is made of
// result: ocr/ppocr-v5-mobile CUDA ms_per_image=12.3 images_per_s=81.2 ...
//
// Two results are comparable only when their corpus line (the images) and each task's model line
// match; the window marks them where they do not.
BENCH_RESULTS_FORMAT :: "gpu-bench 1";
Bench_Pair :: struct { key, value: string; }
Bench_Row :: struct {
task: string; // ocr/ppocr-v5-mobile, labels/siglip2-base-patch16-384, segment/...
provider: string; // CUDA or CPU
fields: [..] Bench_Pair;
}
Bench_Report :: struct {
path: string;
header: [..] Bench_Pair; // in the file's order; model lines too, as "model ocr/x" -> hash
rows: [..] Bench_Row;
}
bench_header :: (report: *Bench_Report, key: string) -> string {
for report.header if it.key == key return it.value;
return "";
}
bench_field :: (row: *Bench_Row, key: string) -> string {
for row.fields if it.key == key return it.value;
return "";
}
bench_row :: (report: *Bench_Report, task: string, provider: string) -> *Bench_Row {
for * report.rows if it.task == task && it.provider == provider return it;
return null;
}
// A results file, or ok false and why; from the context allocator.
bench_parse :: (text: string, path := "") -> Bench_Report, ok: bool, error: string {
report: Bench_Report;
report.path = copy_string(path);
lines := split(text, "\n",, temp);
if !lines.count || trim(lines[0]) != BENCH_RESULTS_FORMAT return report, false, tprint("% is not a gpu-bench results file (no \"%\" on its first line).", path, BENCH_RESULTS_FORMAT);
for raw: lines {
if it_index == 0 continue;
line := trim(raw);
if !line.count || line[0] == #char "#" continue;
colon := find_index_from_left(line, #char ":");
if colon < 0 continue;
key := trim(slice(line, 0, colon));
value := trim(advance(line, colon + 1));
if key == "result" {
parts := split(value, " ",, temp);
if parts.count < 2 continue;
row := array_add(*report.rows);
row.* = .{task = copy_string(parts[0]), provider = copy_string(parts[1])};
for 2..parts.count - 1 {
part := parts[it];
equals := find_index_from_left(part, #char "=");
if equals < 0 continue;
array_add(*row.fields, .{copy_string(slice(part, 0, equals)), copy_string(advance(part, equals + 1))});
}
} else if key == "model" {
space := find_index_from_left(value, #char " ");
if space < 0 continue;
array_add(*report.header, .{tprint("model %", slice(value, 0, space),, context.allocator), copy_string(trim(advance(value, space + 1)))});
} else {
array_add(*report.header, .{copy_string(key), copy_string(value)});
}
}
return report, true, "";
}
bench_report_free :: (report: *Bench_Report) {
free(report.path);
for report.header { free(it.key); free(it.value); }
array_free(report.header);
for * report.rows {
free(it.task); free(it.provider);
for it.fields { free(it.key); free(it.value); }
array_free(it.fields);
}
array_free(report.rows);
report.* = .{};
}