PopcornBench

multi-model kernel-benchmark harness, last published 2026-05-28 03:33:28 UTC

PopcornBench is an extension of KernelBench with a multi-model agentic sweep harness for evaluating how well LLMs can write fast, numerically equivalent CUDA / Triton / Tilelang kernels against a PyTorch reference. Each run pairs a model with a level and problem set, drives a tool-using agent loop with compile / correctness / submit tools, and records per-problem speedups, fusion ratios, energy, and roofline metrics.

Experiments
41 sweep runs, per-model and per-problem reports
→
Docs
getting started, adding models, AlphaEvolve
→
Prompts
system, user, and tool prompts the agent receives
→

Plan

We score each correct kernel on three signals and compare submissions on the Pareto frontier across all three:

A kernel is considered to dominate another only if it is at least as good on all three signals and strictly better on one. Models are then ranked by the size of their Pareto-optimal subset within each (level, variant).

Eval architecture

The harness decouples agent oversubscription from GPU contention via a per-GPU FIFO eval queue. Each GPU runs one dedicated eval-server process that drains a Manager-backed queue in arrival order; agents push submit_kernel requests and block on the response while their next-turn LLM call is free to run in parallel for other problems. With agents_per_gpu at 4-6 (vs. the previous 1-3) the GPU stays busy without locks starving any agent past worker_timeout_s.

Multi-GPU communication kernels (NCCL collectives, real tensor parallelism, pipeline parallelism — six problems in L2 popcorn) need all GPUs visible to one worker, which is incompatible with per-GPU pinning, so they are skipped from the broad sweeps and run separately via sweep.comm.toml.

Sweep matrix

Two variants (original, popcorn), four levels (L1-L4), three tool tiers (single = no tools, default, all). Each sweep covers both variants. We split L1+L2 from L3+L4 because the heavy levels need fewer perf trials and lower agent oversubscription to stay inside worker timeouts. Plus a separate comm sweep for the multi-GPU L2 popcorn problems and two AlphaEvolve focus runs.

RunCoverageToolsModels
l12_singleL1+L2, original + popcorn (excl. multi-GPU)submit_kernel only, max_turns=1gpt-5.5-priority, Kimi-K2.6
l12_defaultsame coveragedefault (compile, correctness, specs, static_check, submit)same two
l12_allsame coverageall (default + profile, disassemble, ert)same two
l34_singleL3+L4, original + popcornsubmit_kernel only, max_turns=1same two
l34_defaultsame coveragedefaultsame two
l34_allsame coverageallsame two
comm_l2_popcornL2 popcorn multi-GPU subset (problems 2, 11, 18, 27, 34, 38)defaultsame two
ae_focus_popcornL1-L4 popcorn, problems 1, 2, 10AE evolutionary searchAlphaEvolve (Gemini 3.0 mixture)
ae_focus_originalL1-L2 original, problems 1, 2, 10AE evolutionary searchAlphaEvolve (Gemini 3.0 mixture)

Run order

Each sweep is GPU-bound and contests the full 8xH100 box, so the primary sweeps run sequentially. The aeproxy server and the gh-pages publisher run as background daemons throughout. Each step runs in a detached tmux session so SSH disconnects don't kill it.

Steptmux sessionCommandWall-clockParallel with
0a (background)aeproxytmux new -d -s aeproxy 'cd /scratch/tejas/sp26-ae-llm && export LD_LIBRARY_PATH=/opt/conda/lib:$LD_LIBRARY_PATH; uv run python -m aeproxy.server'persistenteverything (idle until step 4)
0b (background)publishtmux new -d -s publish 'cd /scratch/tejas/PopcornBench && export LD_LIBRARY_PATH=/opt/conda/lib:$LD_LIBRARY_PATH; uv run python scripts/publish_to_gh_pages.py --watch 300'persistenteverything
1smoketmux new -d -s smoke 'cd /scratch/tejas/PopcornBench && export LD_LIBRARY_PATH=/opt/conda/lib:$LD_LIBRARY_PATH; uv run python scripts/run_sweep.py configs/sweep.smoke_all.toml'~15 minnothing (alone)
2l12_singletmux new -d -s l12_single 'cd /scratch/tejas/PopcornBench && export LD_LIBRARY_PATH=/opt/conda/lib:$LD_LIBRARY_PATH; uv run python scripts/run_sweep.py configs/sweep.l12_single.toml'~30 minnothing (alone)
3l12_defaulttmux new -d -s l12_default 'cd /scratch/tejas/PopcornBench && export LD_LIBRARY_PATH=/opt/conda/lib:$LD_LIBRARY_PATH; uv run python scripts/run_sweep.py configs/sweep.l12_default.toml'~3-4 hoursnothing (alone)
4l12_alltmux new -d -s l12_all 'cd /scratch/tejas/PopcornBench && export LD_LIBRARY_PATH=/opt/conda/lib:$LD_LIBRARY_PATH; uv run python scripts/run_sweep.py configs/sweep.l12_all.toml'~6-8 hoursnothing (alone)
5l34_singletmux new -d -s l34_single 'cd /scratch/tejas/PopcornBench && export LD_LIBRARY_PATH=/opt/conda/lib:$LD_LIBRARY_PATH; uv run python scripts/run_sweep.py configs/sweep.l34_single.toml'~2-3 hoursnothing (alone)
6l34_defaulttmux new -d -s l34_default 'cd /scratch/tejas/PopcornBench && export LD_LIBRARY_PATH=/opt/conda/lib:$LD_LIBRARY_PATH; uv run python scripts/run_sweep.py configs/sweep.l34_default.toml'~12-15 hoursnothing (alone)
7l34_alltmux new -d -s l34_all 'cd /scratch/tejas/PopcornBench && export LD_LIBRARY_PATH=/opt/conda/lib:$LD_LIBRARY_PATH; uv run python scripts/run_sweep.py configs/sweep.l34_all.toml'~18-24 hoursnothing (alone)
8commtmux new -d -s comm 'cd /scratch/tejas/PopcornBench && export LD_LIBRARY_PATH=/opt/conda/lib:$LD_LIBRARY_PATH; uv run python scripts/run_sweep.py configs/sweep.comm.toml'~3-6 hoursnothing (alone, needs all GPUs)
9aepoptmux new -d -s aepop 'cd /scratch/tejas/PopcornBench && export LD_LIBRARY_PATH=/opt/conda/lib:$LD_LIBRARY_PATH; uv run python scripts/run_sweep.py configs/sweep.ae_focus.toml'~3-4 hoursnothing (alone)
10aeorigtmux new -d -s aeorig 'cd /scratch/tejas/PopcornBench && export LD_LIBRARY_PATH=/opt/conda/lib:$LD_LIBRARY_PATH; uv run python scripts/run_sweep.py configs/sweep.ae_focus_orig.toml'~1.5-2 hoursnothing (alone)

Total wall-clock for all 10 steps is roughly 50-65 hours. If time is tight, the priority order to drop is: aeorig (step 10), l34_all (step 7, the heaviest cell), l12_all (step 4), comm (step 8). Single-turn baselines and default-tier sweeps are most important since they ground the comparison.

Planned figures

  1. Pareto frontier in (speedup, SOL). One panel per level. Source: l12_default + l34_default.
  2. Pareto frontier in (speedup, 1 / energy_ratio). L3 and L4 only. Source: l34_default.
  3. Speedup vs SOL scatter, all problems. One dot per (model, problem), colored by level, faceted by variant. Source: l12_default + l34_default.
  4. Tier comparison: single vs default vs all. Per (model, level), correctness rate and speedup distribution across the three tool tiers. Source: l12_* + l34_*.
  5. Correctness rate stacked by tier on L2-popcorn. Source: existing cuda_l2pop_* sweeps.
  6. SOL distribution by tier on L2-popcorn. Source: existing L2-pop sweeps after the SOL clamp fix.
  7. AlphaEvolve speedup vs best non-AE speedup, per problem. Source: ae_focus_popcorn + ae_focus_original joined to l12_default / l34_default.
  8. AlphaEvolve original-vs-popcorn paired bars, L1 and L2 only. Source: ae_focus_popcorn + ae_focus_original, restricted to problems 1, 2, 10.
  9. Failure-mode breakdown per (model, level). Stacked correct / wrong / compile_fail / timeout / error. Source: l12_default + l34_default.
  10. Communication kernels (separate panel). Per-model speedup on the L2 popcorn comm subset, reported separately because they run in serial multi-GPU mode. Source: comm_l2_popcorn.