| 1 | --- |
| 2 | name: kernel-microbenchmark |
| 3 | description: Build, debug, and interpret vLLM GPU kernel microbenchmarks for CUDA, ROCm/HIP, Triton, and CuteDSL, including CUPTI timing, correctness checks, generated-code inspection, multi-GPU measurements, and SOL sanity checks. |
| 4 | --- |
| 5 | |
| 6 | # Kernel Microbenchmark |
| 7 | |
| 8 | ## Workflow |
| 9 | |
| 10 | 1. Create an isolated repro or benchmark when the existing harness is noisy. |
| 11 | 2. Check correctness before timing. Keep tolerances explicit. |
| 12 | 3. Time only the operation under study. Exclude allocation, compilation, random |
| 13 | input generation, logging, and host-device transfers unless those are the |
| 14 | target. |
| 15 | 4. Compare against a baseline and report enough metadata to reproduce the |
| 16 | result: GPU, dtype, shape, command, branch/commit, and relevant env vars. |
| 17 | 5. Treat explanations as hypotheses until backed by an artifact: ablation, |
| 18 | generated PTX/SASS or AMD ISA, profiler output, or controlled benchmark. |
| 19 | 6. If the result changes the conclusion, preserve the compact lesson in a note, |
| 20 | comment, benchmark table, or final summary. For experiments, a short |
| 21 | `Question / Change / Correctness / Result / Observation / Next` note is |
| 22 | usually enough. |
| 23 | |
| 24 | ## Benchmark Defaults |
| 25 | |
| 26 | - Use FlashInfer CUPTI timing by default, with CUDA graph and cold L2 cache: |
| 27 | `from flashinfer.testing import bench_gpu_time_with_cupti`. |
| 28 | - ROCm: CUPTI and FlashInfer do not exist. Use HIP graph replay over rotating |
| 29 | buffers whose total footprint exceeds the last-level cache (e.g. 256 MB on |
| 30 | MI355X), so every call reads cold from HBM, timed with HIP events — see |
| 31 | [benchmarks/graph_replay_benchmark.py](benchmarks/graph_replay_benchmark.py) |
| 32 | (platform-agnostic). Prefer this over `do_bench`, whose per-rep |
| 33 | launch/event overhead inflates small/decode-size kernels. |
| 34 | - For compute-heavy kernels, report TFLOPS with the FLOP formula in the |
| 35 | benchmark. For memory-heavy kernels, report estimated bytes moved and GB/s. |
| 36 | For mixed kernels, report the most honest metric available and call out the |
| 37 | caveats. TFLOPS and memory bandwidth should be computed from the theoretical |
| 38 | best for the operation, not from a particular kernel implementation. For |
| 39 | example, memory bandwidth should assume all data is read exactly once from |
| 40 | global memory. |
| 41 | - When comparing across shapes, prefer throughput metrics such as TFLOPS or |
| 42 | GB/s as the primary table columns; keep latency for absolute cost. |
| 43 | - If a result exceeds expected peak/SOL, first inspect units, FLOP/byte |
| 44 | formulas, skipped work, sparsity, caching, and whether the baseline is doing |
| 45 | the same operation. |
| 46 | - Force compilation/autotuning before measuring compiled kernels. |
| 47 | - Tune every arm of a comparison before trusting it. A variant's default or |
| 48 | inherited config can be far from its optimum, so conclusions drawn from |
| 49 | untuned variants — especially negative ones — are unreliable. A coarse |
| 50 | sweep of the cheap knobs at a couple of representative shapes is enough; |
| 51 | report the winning config with the numbers. |
| 52 | - Seed inputs when correctness comparisons matter. |
| 53 | - Keep metadata setup, plan construction, allocation, random input generation, |
| 54 | and logging outside the timed region unless that overhead is the experiment. |
| 55 | |
| 56 | ## Sanity-Check Reference Numbers |
| 57 | |
| 58 | Use these as rough reference points for large, well-shaped workloads, not as |
| 59 | gold standards, guaranteed peaks, or hard limits. Hardware SKU, clocks, shape, |
| 60 | precision conventions, and the FLOP/byte accounting can move the result. A |
| 61 | large gap is a prompt to investigate, not proof that a kernel is poor. |
| 62 | |
| 63 | | Kernel regime | Hardware | Rough reference | |
| 64 | | --- | --- | ---: | |
| 65 | | Memory-bound, large batch | Blackwell | 6 TB/s | |
| 66 | | BF16 GEMM | Blackwell | 2 PFLOP/s | |
| 67 | | BF16 attention | B200 | 1.6 PFLOP/s | |
| 68 | | FP8 GEMM | Blackwell | 4 PFLOP/s | |
| 69 | | FP8 attention | B300 | 2.8 PFLOP/s | |
| 70 | |
| 71 | The BF16 attention reference is approximately the 1613 TFLOP/s result reported |
| 72 | by the FlashAttention-4 paper. Compare kernels only with matching workload and |
| 73 | throughput conventions. |
| 74 | |
| 75 | ## Multi-GPU Benchmarks |
| 76 | |
| 77 | - State whether the run is local or multi-node and report the GPU topology, |
| 78 | world size, GPUs per node, collective backend, and relevant library versions. |
| 79 | - Compare like-for-like TP configurations. Report both per-rank and global |
| 80 | dimensions, and do not compare results from different TP sizes without an |
| 81 | explicit normalization or scaling question. |
| 82 | - Define the timed operation boundary before benchmarking. If the production |
| 83 | wrapper performs input staging, flag resets, generation barriers, padding, |
| 84 | or output copies, keep them in the timed region for an end-to-end comparison. |
| 85 | Use a separate, clearly labeled ablation for kernel-only timing. |
| 86 | - Check distributed correctness before timing. Seed each rank deliberately, |
| 87 | form the reference with the same collective semantics, and synchronize before |
| 88 | reading or comparing outputs. |
| 89 | - Use a device-side barrier immediately before each measured replay. Keep this |
| 90 | common synchronization outside the timed interval, but keep barriers required |
| 91 | by the candidate implementation inside it. |
| 92 | - With CUDA graphs, warm up before capture, coordinate capture across ranks, |
| 93 | rotate pointer-distinct graphs when cache reuse matters, and ensure every |
| 94 | collective is issued in the same order on every rank. |
| 95 | - Measure every rank and reduce each sample with `MAX`; report the median of |
| 96 | those per-sample maxima. A rank-local event time is not a distributed latency. |
| 97 | - Reset reusable symmetric-memory flags before each invocation and establish a |
| 98 | device-side generation barrier before peers may signal them. Otherwise a fast |
| 99 | rank can signal before a slow rank resets its flags, causing a lost arrival |
| 100 | and intermittent deadlock. |
| 101 | - Preserve symmetric-memory handles, CUDA graphs, streams, and graph outputs for |
| 102 | the full measurement lifetime. Allocate and rendezvous symmetric buffers in |
| 103 | identical order and with identical shapes on all ranks. |
| 104 | - Treat hangs and isolated millisecond outliers as synchronization bugs or rank |
| 105 | skew until disproven. Add stage markers, bounded synchronization checks, and |
| 106 | per-rank diagnostics before blaming compilation or kernel performance. |
| 107 | - For multi-node runs, record the scheduler allocation and verify the fabric |
| 108 | supports the required multicast or NVLink-domain assumptions. Do not describe |
| 109 | a two-node TP run as equivalent to a local NVLink-domain run without checking. |
| 110 | - Stabilize GPU clocks or run enough untimed work to reach a steady state. |
| 111 | Alternate candidate order so clock, thermal, and rank-skew effects are shared. |
| 112 | - ROCm: capturing ProcessGroupNCCL collectives in HIP graphs trips the NCCL |
| 113 | watchdog (HIP forbids querying an event recorded in a capturing stream; |
| 114 | the watchdog aborts the process). `TORCH_NCCL_BLOCKING_WAIT=1` before |
| 115 | `import torch` disables the watchdog — benchmark-only knob. Ask for backend |
| 116 | `"nccl"`; torch routes it to RCCL automatically (there is no `"rccl"` |
| 117 | backend string). |
| 118 | |
| 119 | ## Included Examples |
| 120 | |
| 121 | - Use [benchmarks/cupti_microbenchmark.py](benchmarks/cupti_microbenchmark.py) |
| 122 | as a minimal single-GPU FlashInfer CUPTI timing pattern. |
| 123 | - Use |
| 124 | [benchmarks/graph_replay_benchmark.py](benchmarks/graph_replay_benchmark.py) |
| 125 | as a graph-replay timing pattern over rotating buffers sized past the |
| 126 | last-level cache — the ROCm/HIP default, also usable on CUDA. |
| 127 | - Use |
| 128 | [benchmarks/multi_gpu_gemm_rs.py](benchmarks/multi_gpu_gemm_rs.py) as a |
| 129 | minimal distributed CUDA-graph timing pattern. |
| 130 | |
| 131 | Adapt the operation, cases, work formula, and correctness tolerances rather |
| 132 | than copying either example unchanged. |