cudatraining · lab notes

ESSAY 10 · 2026.04.19 · L4

Can 50 lines of Python replace 5000 lines of CUDA?

Where does the cost of abstraction show up? Four kernels on L4, four regimes with the answer.

question · the ROI of abstraction subjects · Triton / CUDA / cuBLAS·cuDNN length · essay

The question

Triton's pitch is "CUDA-level performance without CUDA." Sounds like marketing — but Tri Dao actually wrote Flash Attention-2 in Triton. OpenAI's Kernel Gym is Triton. Is the claim true?

I decided to answer only with numbers. The four kernels from Lessons 1–6 — reduction, softmax, matmul, flash attention — rewritten in Triton. Run CUDA / torch built-in / Triton side by side on the same L4 (sm_89). One question: in which regime is abstraction expensive, and in which is it free?

Four regimes

regimeTriton vs CUDAcause
Small N (< 4 MB)3–12× slowerPython → autotune → JIT → cuLaunch; launch floor ~50–100 µs
HBM-bound medium95%nearly none — if HBM is the bottleneck, a compiler can't overshoot a human by much
HBM-bound largetienone
Compute-bound large matmul / FAwinsautotune narrowly edges out human tuning

Evidence 1 · HBM-bound is a tie

taskCUDAtorchTriton
Reduction 67M258 GB/s · 86%254 · 85%245 · 82%
Softmax 4096²237 GB/s240221

82–86% of HBM floor (300 GB/s). The spread among the three approaches is noise. In this regime, everything is "who can drink HBM fastest," and there's no reason a JIT would fall behind hand-written CUDA.

Evidence 2 · Triton edges out cuBLAS in compute-bound

matmul 4096³CUDAcuBLASTriton
FP32 (TF32 TC)3.9 TF (our v3)25.828.9 (+12%)
FP1618.5 TF (our WMMA)51.854.0 (+4%)

2.9× over our WMMA. More importantly, narrowly over cuBLAS. The reason: autotune configs explore more points than a human. And TF32/FP16 selection happens automatically through tl.dot's dtype.

Evidence 3 · Flash Attention (N=8192)

Triton fp16 vs our CUDA FA fp32
6.14× faster
Triton fp32 vs SDPA fp32
2.35× faster (torch fp32 doesn't take the FA path on L4)
Triton fp16 vs SDPA fp16 (cuDNN FA-2)
0.79× (we're 25% slower)

100 lines of Triton at 79% of cuDNN FA-2. This is why Tri Dao wrote FA-2 in Triton. Look at the number and Triton stops being an experiment.

One line = dozens of lines, repeated four times

Where the cost is real

At n=2²⁰ (4 MB) in the reduction of Lesson 3, Triton was 3–12× slower than CUDA. One cause: launch floor 50–100 µs. Python interpreter → autotune cache lookup → JIT (once) → cuLaunchKernel. The floor's relative weight grows as the kernel shrinks.

If a Transformer layer is ≥1 ms, 100 µs is <10% — fine. But launch 30 element-wise ops individually in Triton and you're done. For small ops, PyTorch eager or torch.compile is better.

Two footguns

(1) TF32 benchmark lie

torch.matmul(fp32) doesn't use TF32 by default. tl.dot does. Compare them head-to-head and you see "Triton beats torch 2×." A fair comparison:

torch.backends.cuda.matmul.allow_tf32 = True
torch.backends.cudnn.allow_tf32 = True

With those two lines, both sides take the TF32 TC path. Before I set them, Phase 3's 2× lead was a lie.

(2) Autotune stale writes

@triton.autotune can leave a partial buffer polluted by other configs mid-trial. Reduction partials got mixed, rel_err blew up to 1.75. Fix:

@triton.autotune(configs=..., reset_to_zero=["partial_ptr"])
@triton.jit
def kernel(...): ...

best = kernel.best_config
block = best.kwargs["BLOCK_SIZE"]
return partial[:cdiv(n, block)].sum()

So why still CUDA

  1. Triton hits walls — Blackwell's BF8/FP4 mma, persistent kernel, async-copy fine-grained control.
  2. Reading PTX — chasing perf bugs means reading the PTX Triton emits. *.ptx, *.cubin in TRITON_CACHE_DIR.
  3. Reference code — vLLM, FA-3, Mamba kernels are still CUDA. Reading them requires CUDA as your first language.
  4. Perf diagnosis vocabulary — "why is it slow?" answers are bank conflict, register spill, occupancy. Those concepts carry into Triton unchanged.
Analogy

CUDA = assembly, Triton = C. Most code in C, hot paths in assembly. And when the C compiler has a bug, you have to read the assembly.