mcap/tools/gpu_bench/make_corpus.py
Musa Mahmood 32af1f6496 [0.1.36] 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 21:34:06 -04:00

193 lines
11 KiB
Python

# Makes the synthetic part of the GPU benchmark's corpus (tools/gpu_bench/corpus): screenshot-like
# images full of text, for OCR, and two phone-sized photos scaled up from COCO ones. The images are
# committed, and corpus.txt pins each one's SHA-256, so every machine benchmarks the same pixels; this
# script is kept to show how they were made, not to be rerun (another Pillow or FreeType could draw
# them a pixel differently, and then they would no longer match corpus.txt).
#
# python tools/gpu_bench/make_corpus.py
import os, random
from PIL import Image, ImageDraw, ImageFont
HERE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(HERE, "corpus")
FONTS = os.path.join(HERE, "..", "..", "assets", "fonts")
WORDS = ("the of and to in is that for it as with was on be by this are from or at have an not but "
"which they you were all one their there been has more when will would can if about what "
"into some time only other new could these two may first then its such like over also back "
"after use our work way well even because any most people where much through before right "
"capture library image model search folder window screen label segment vector memory driver "
"thread allocator buffer request response latency throughput benchmark kernel tensor batch "
"session provider runtime device budget scheduler compiler release version settings export "
"import config circuit board resistor capacitor footprint schematic voltage current signal "
"amplifier channel sample filter frequency spectrum electrode impedance firmware bootloader").split()
def font(name, size):
return ImageFont.truetype(os.path.join(FONTS, name), size)
def sentence(rng, n):
words = [rng.choice(WORDS) for _ in range(n)]
words[0] = words[0].capitalize()
return " ".join(words) + rng.choice([".", ".", ",", ":", "?"])
def code_line(rng):
kind = rng.randrange(6)
a, b, c = rng.choice(WORDS), rng.choice(WORDS), rng.choice(WORDS)
if kind == 0: return f"{a}_{b} := {c}_{rng.choice(WORDS)}({rng.randrange(1000)}, {a});"
if kind == 1: return f"if {a}.{b} > {rng.randrange(64)} {{ {c}({a}); return false; }}"
if kind == 2: return f"// {sentence(rng, rng.randrange(4, 12))}"
if kind == 3: return f"for {a}: {b}_{c} array_add(*{a}s, it.{c});"
if kind == 4: return f"{a}_{b} :: (x: s64, {c}: *{a.capitalize()}) -> bool {{"
return f"print(\"% {a} {b}\\n\", {c}.count);"
def editor(rng, w, h, dark, mono, size):
bg, fg, dim, side = ((30, 30, 30), (212, 212, 212), (110, 110, 110), (37, 37, 38)) if dark else \
((255, 255, 255), (30, 30, 30), (150, 150, 150), (243, 243, 243))
im = Image.new("RGB", (w, h), bg)
d = ImageDraw.Draw(im)
ui, code = font("OpenSans-Regular.ttf", 14), font(mono, size)
d.rectangle([0, 0, w, 32], fill=side)
for i, x in enumerate(range(10, w - 200, 180)):
d.text((x, 8), f"{rng.choice(WORDS)}_{rng.choice(WORDS)}.jai", font=ui, fill=fg if i == 0 else dim)
d.rectangle([0, 32, 260, h], fill=side)
y = 44
while y < h - 30:
d.text((16 + 12 * rng.randrange(3), y), f"{rng.choice(WORDS)}_{rng.choice(WORDS)}", font=ui, fill=fg)
y += 24
colours = [fg, (86, 156, 214), (206, 145, 120), (106, 153, 85), (197, 134, 192)] if dark else \
[fg, (0, 0, 255), (163, 21, 21), (0, 128, 0), (175, 0, 219)]
line, y = rng.randrange(1, 900), 44
while y < h - 30:
d.text((272, y), str(line).rjust(4), font=code, fill=dim)
if rng.random() > 0.12:
d.text((330 + 4 * size // 2 * rng.randrange(4), y), code_line(rng), font=code, fill=rng.choice(colours))
y += int(size * 1.5); line += 1
d.rectangle([0, h - 24, w, h], fill=(0, 122, 204))
d.text((10, h - 21), f"Ln {line}, Col {rng.randrange(80)} UTF-8 CRLF Jai {rng.choice(WORDS)}", font=ui, fill=(255, 255, 255))
return im
def document(rng, w, h, size):
im = Image.new("RGB", (w, h), (250, 250, 248))
d = ImageDraw.Draw(im)
title, body = font("OpenSans-Bold.ttf", size * 2), font("OpenSans-Regular.ttf", size)
margin = w // 8
d.text((margin, 40), sentence(rng, 5)[:-1], font=title, fill=(20, 20, 20))
y = 60 + size * 3
while y < h - size * 2:
if rng.random() < 0.15:
d.text((margin, y), sentence(rng, 4)[:-1], font=font("OpenSans-SemiBold.ttf", int(size * 1.3)), fill=(40, 40, 90))
y += int(size * 2.2); continue
for _ in range(rng.randrange(2, 7)):
text = sentence(rng, 40)
words, current = text.split(), ""
for word in words:
trial = (current + " " + word).strip()
if d.textlength(trial, font=body) > w - 2 * margin:
d.text((margin, y), current, font=body, fill=(30, 30, 30)); y += int(size * 1.6); current = word
else:
current = trial
d.text((margin, y), current, font=body, fill=(30, 30, 30)); y += int(size * 1.6)
if y > h - size * 2: break
y += size
return im
def spreadsheet(rng, w, h):
im = Image.new("RGB", (w, h), (255, 255, 255))
d = ImageDraw.Draw(im)
f, bold = font("OpenSans-Regular.ttf", 13), font("OpenSans-SemiBold.ttf", 13)
cols = [40] + [rng.choice([90, 110, 140, 180]) for _ in range(30)]
d.rectangle([0, 0, w, 60], fill=(33, 115, 70))
d.text((12, 18), f"{rng.choice(WORDS)}_{rng.choice(WORDS)}.xlsx", font=font("OpenSans-SemiBold.ttf", 18), fill=(255, 255, 255))
y, row = 60, 0
while y < h:
x = 0
for c, cw in enumerate(cols):
if x > w: break
d.rectangle([x, y, x + cw, y + 22], outline=(218, 220, 224), fill=(243, 243, 243) if row == 0 or c == 0 else None)
if row == 0 and c > 0: text, ft = chr(64 + c) if c <= 26 else "A" + chr(38 + c), bold
elif c == 0: text, ft = str(row) if row else "", bold
elif row == 1: text, ft = rng.choice(WORDS).capitalize(), bold
else:
r = rng.random()
text = f"{rng.uniform(0, 99999):,.2f}" if r < 0.5 else (str(rng.randrange(10000)) if r < 0.7 else rng.choice(WORDS))
ft = f
d.text((x + 4, y + 3), text, font=ft, fill=(20, 20, 20))
x += cw
y += 22; row += 1
return im
def terminal(rng, w, h, size):
im = Image.new("RGB", (w, h), (12, 12, 12))
d = ImageDraw.Draw(im)
f = font("FiraCode-Retina.ttf", size)
y = 8
while y < h - size:
r = rng.random()
if r < 0.2: text, colour = f"PS D:\\{rng.choice(WORDS)}\\{rng.choice(WORDS)}> jai first.jai - {rng.choice(WORDS)}", (240, 240, 240)
elif r < 0.3: text, colour = f"error: {sentence(rng, 8)}", (240, 80, 80)
elif r < 0.4: text, colour = f"[{rng.randrange(24):02}:{rng.randrange(60):02}:{rng.randrange(60):02}] {rng.choice(['INFO', 'WARN', 'DEBUG'])} {sentence(rng, 9)}", (180, 220, 120)
else: text, colour = " ".join(f"{rng.randrange(1 << 16):04x}" for _ in range(rng.randrange(4, 18))), (200, 200, 200)
d.text((8, y), text, font=f, fill=colour)
y += int(size * 1.35)
return im
def chat(rng, w, h):
im = Image.new("RGB", (w, h), (245, 245, 250))
d = ImageDraw.Draw(im)
f, name = font("OpenSans-Regular.ttf", 30), font("OpenSans-Bold.ttf", 26)
d.rectangle([0, 0, w, 140], fill=(60, 60, 140))
d.text((40, 60), rng.choice(WORDS).capitalize() + " " + rng.choice(WORDS).capitalize(), font=font("OpenSans-SemiBold.ttf", 40), fill=(255, 255, 255))
y = 170
while y < h - 200:
mine = rng.random() < 0.5
lines = [sentence(rng, rng.randrange(2, 6)) for _ in range(rng.randrange(1, 4))]
width = max(d.textlength(l, font=f) for l in lines) + 40
height = 44 * len(lines) + 56
x = w - width - 40 if mine else 40
d.rounded_rectangle([x, y, x + width, y + height], 24, fill=(90, 120, 230) if mine else (255, 255, 255))
d.text((x + 20, y + 10), f"{rng.randrange(1, 13)}:{rng.randrange(60):02} {'PM' if rng.random() < 0.5 else 'AM'}", font=name, fill=(230, 230, 255) if mine else (120, 120, 120))
for i, l in enumerate(lines):
d.text((x + 20, y + 48 + 44 * i), l, font=f, fill=(255, 255, 255) if mine else (20, 20, 20))
y += height + 24
return im
def dialog(rng, w, h):
im = editor(rng, w, h, True, "JetBrainsMono-Regular.ttf", 14).point(lambda v: v // 2)
d = ImageDraw.Draw(im)
x0, y0, x1, y1 = w // 2 - 320, h // 2 - 160, w // 2 + 320, h // 2 + 160
d.rectangle([x0, y0, x1, y1], fill=(250, 250, 250), outline=(120, 120, 120))
d.text((x0 + 24, y0 + 20), sentence(rng, 4)[:-1], font=font("OpenSans-SemiBold.ttf", 22), fill=(20, 20, 20))
for i in range(4):
d.text((x0 + 24, y0 + 70 + 30 * i), sentence(rng, 9), font=font("OpenSans-Regular.ttf", 17), fill=(40, 40, 40))
for i, label in enumerate(["Cancel", "Don't save", "Save"]):
bx = x1 - 130 * (3 - i) - 10
d.rectangle([bx, y1 - 60, bx + 115, y1 - 22], fill=(0, 103, 192) if label == "Save" else (225, 225, 225))
d.text((bx + 20, y1 - 52), label, font=font("OpenSans-Regular.ttf", 17), fill=(255, 255, 255) if label == "Save" else (20, 20, 20))
return im
def main():
rng = random.Random(20261017)
made = [
("screen-editor-dark-1080p.png", editor(rng, 1920, 1080, True, "JetBrainsMono-Regular.ttf", 15)),
("screen-editor-light-1080p.png", editor(rng, 1920, 1080, False, "RobotoMono-Regular.ttf", 14)),
("screen-editor-dark-1440p.png", editor(rng, 2560, 1440, True, "FiraCode-Retina.ttf", 16)),
("screen-document-1080p.png", document(rng, 1920, 1080, 18)),
("screen-document-4k.png", document(rng, 3840, 2160, 28)),
("screen-spreadsheet-1080p.png", spreadsheet(rng, 1920, 1080)),
("screen-spreadsheet-1440p.png", spreadsheet(rng, 2560, 1440)),
("screen-terminal-1080p.png", terminal(rng, 1920, 1080, 15)),
("screen-terminal-720p.png", terminal(rng, 1280, 720, 13)),
("screen-chat-phone.png", chat(rng, 1080, 2400)),
("screen-dialog-1080p.png", dialog(rng, 1920, 1080)),
("screen-document-strip.png", document(rng, 1600, 300, 16)),
]
for name, im in made:
im.save(os.path.join(OUT, name), optimize=False, compress_level=6)
# Phone-sized photos: the decoders and resizers see a 12-megapixel image, as from a phone.
for source, name in [("coco-000000000139.jpg", "photo-12mp-livingroom.jpg"), ("coco-000000001000.jpg", "photo-12mp-tennis.jpg")]:
im = Image.open(os.path.join(OUT, source)).convert("RGB").resize((4032, 3024), Image.LANCZOS)
im.save(os.path.join(OUT, name), quality=92)
main()