FlashInfer or Triton for local inference
FlashInfer is a library of finished, hand-tuned CUDA kernels for LLM serving. Triton is a Python-embedded language and compiler that you write GPU kernels in. An inference engine uses both: FlashInfer where a tuned kernel exists, Triton where a format or an operation is new. (OpenAI’s Triton language is unrelated to NVIDIA’s Triton Inference Server.)
The two, side by side
| FlashInfer | Triton | |
|---|---|---|
| What it is | Kernel library: CUDA / CUTLASS templates | Tile-level kernel language + compiler |
| Paper | MLSys 2025 | MAPL 2019 |
| Hardware | NVIDIA, SM 7.5 → 12.1 (Turing to Blackwell, incl. DGX Spark) | NVIDIA SM 8.0+, AMD ROCm 6.2+; CPU in development |
| Covers | Attention (paged / ragged KV, MLA, cascade, sparse), FP8 / FP4 GEMM, fused MoE, sampling, AllReduce, RoPE, norms | Whatever is written in it: fused MoE, linear attention, quant / dequant, attention |
| Inner attention backends | FA2 / FA3, cuDNN, CUTLASS, TensorRT-LLM | One kernel per op, compiled for the GPU it runs on |
| Compilation | JIT on first use, or prebuilt cubins (flashinfer-cubin) | JIT per kernel and shape on first use, then cached |
| New KV layout or dtype | New CUDA template; arrives with a release | A Python kernel change |
| Used by | SGLang, vLLM, TensorRT-LLM, TGI, MLC-LLM, LightLLM | SGLang, vLLM, PyTorch Inductor, flash-linear-attention |
From the FlashInfer and Triton repositories, 2026-10-22.
Where each one runs inside an engine
One served configuration as the example: SGLang 708f51e, Qwen3.8-27B NVFP4 with a DFlash2 speculative drafter, FP8 KV cache, TP=2 across two DGX Sparks (GB10, sm_121).
| Op | Prefill | Decode / verify | Why that library |
|---|---|---|---|
| Full attention (target) | FlashInfer | FlashInfer | Paged-KV kernels with a CUDA-graph-safe scheduler |
| Drafter attention | FlashInfer | FlashInfer | SGLang default: inherits the prefill backend |
| Gated DeltaNet linear attention | FlashInfer | Triton | The decode kernels come from flash-linear-attention, written in Triton |
| NVFP4 weight GEMMs | FlashInfer / CUTLASS | FlashInfer / CUTLASS | FP4 tensor-core GEMM (mm_fp4) |
So “FlashInfer or Triton” is a per-op choice. The flag that matters (--attention-backend in SGLang, VLLM_ATTENTION_BACKEND in vLLM) picks only the attention kernel.
Speed
Published: FlashInfer reports 29–69% lower inter-token latency than compiler-generated backends in LLM serving benchmarks, 28–30% lower latency on long-context inference and 13–17% speedup with parallel generation (paper, datacenter GPUs). No published FlashInfer-vs-Triton attention numbers exist for GB10, and Triton attention speed on GB10 is not measured here.
What the attention backend can win depends on how far the step is from its ceiling. Measured prefill on the example configuration: 60 chunks × 2,048 tokens in 57.6 s (0.96 s per chunk, mean attended depth 46,530 tokens).
| Per prompt token, per GPU | Work | Precision | GB10 peak | Ideal time |
|---|---|---|---|---|
| Linear layers | 24.3 GFLOP | NVFP4 | 323.2 TFLOP/s | 75 µs |
| Attention at 46.5K depth | 9.1 GFLOP | BF16 | 96.5 TFLOP/s | 95 µs |
| Sum | 33.4 GFLOP | ~197 TFLOP/s mixed | 170 µs | |
| Measured, FlashInfer | 469 µs (37%) |
flashinfer.mm_fp4 CUTLASS 4096³ 323.2 TFLOP/s).Attention is 95 of the 170 µs ideal. Even a perfect attention kernel leaves the measured 469 µs above 374 µs, so swapping the attention backend moves at most ~20% of prefill time here; the rest sits outside the kernel.
Why attention stays in BF16
- Weights are static and quantized offline; K and V are produced per request, at runtime.
- Softmax exponentiates the QKᵀ scores, so a small error in K becomes a large error in the attention weights.
- On sm_121, FlashInfer and SGLang dequantize an FP8 KV cache and compute attention in BF16. The FP8-attention fast path (
trtllm_mha) is SM100-only.
So a smaller KV format is a capacity change, not a compute change: more tokens fit in the same memory, and they are dequantized before the math.
Formats: where Triton wins
708f51e KV layouts (source-read). Ratios are arithmetic (2 ÷ bytes); a real pool also pays any dequant workspace.A new KV format needs a kernel that reads it. In Triton that is a dequant step inside a Python kernel; in FlashInfer it is a new CUDA template per attention variant and architecture. In the same SGLang build, the MX-FP4 KV format is read by Triton and not by FlashInfer:
| KV format (SGLang 708f51e) | Prefill reads it | Decode reads it | FlashInfer | Triton |
|---|---|---|---|---|
bfloat16, fp8_e4m3, fp8_e5m2 | all backends | all backends | yes | yes |
nvfp4 | FlashInfer (via FP8 dequant workspace) | trtllm_mha only | prefill only | no |
fp4_mx_block16 | triton, torch_native, flex_attention, trtllm_mha | same | no | yes |
From fp4_kv_cache_quant_method.py in that build.
Support also depends on the GPU, not only the library:
| Engine | GPU (compute capability) | NVFP4 KV | Path |
|---|---|---|---|
| vLLM 0.29 | GB10 (12.1) | refused | FlashInfer trtllm-gen, 10.x only |
| vLLM 0.31 | RTX 5090 (12.0) | accepted | FlashInfer FA2 |
| SGLang 708f51e | GB10 (12.1), no drafter | serves | FlashInfer prefill + trtllm_mha decode |
vLLM rows: read from the images' source, not run. SGLang row: run on 2× DGX Spark.
Speculative decoding: the catch for both
A speculative drafter (DFLASH here) writes and reads a block of draft tokens per step. That is neither plain prefill nor plain decode, so a backend that reads a format “for decode only” or “for prefill only” does not cover it. Every FP4-KV + drafter combination tried on 2× DGX Spark, SGLang 708f51e, 2026-10-22:
| KV | Target attention | Drafter attention | Result | Cause in the log |
|---|---|---|---|---|
| fp8_e4m3 | FlashInfer | FlashInfer | serves | — |
| nvfp4 | FlashInfer prefill, trtllm_mha decode | FlashInfer | dies at init | ”nvfp4 does not support decode with flashinfer” |
| nvfp4 | same | trtllm_mha | dies at graph capture | ”native FP4 KV cache supports decode only” |
| fp4_mx_block16 | Triton | Triton | dies at first batch | DFLASH writer copies BF16 into the FP4 pool |
| nvfp4 | same as row 2 | own FP8 pool | wrong output | none; 17×23 answered “22 by 5”, recall failed at 8k / 60k / 110k |
| nvfp4 | same as row 2 | — (drafter off) | serves | — |
With Triton everywhere and a pool sized to free memory, a separate start lost ~28 GB of free memory in ~20 s after “Overriding draft attention backend to triton” and was killed by an 8 GB memory guard. A BF16 dequant workspace sized by the pool’s token count would explain it; the log does not name it.
What dropping the drafter costs, same pair:
| FP8 KV + drafter (FlashInfer) | NVFP4 KV, no drafter | |
|---|---|---|
| Decode, tok/s per stream | ~32 (6 streams, one 5-s window) | ~14 (3–4 streams), ~11 (2) |
| Prefill, tok/s | 3,301 / 2,417 / 1,802 at 9k / 70k / 128k | not measured |
| KV pool, tokens | 2,887,228 | 1,979,584 |
| Long-context recall (8k / 60k / 110k) | pass | pass |
The FP8 pool is larger here because the NVFP4 run pinned KV to 28 GB. Pool sizing, checks and the pending same-load speed comparison: KV cache: FP8 vs NVFP4.
Which one to pick
| Situation | Pick | Reason |
|---|---|---|
| NVIDIA GPU, BF16 or FP8 KV | FlashInfer | Tuned kernels; 29–69% lower ITL in the paper |
| AMD GPU | Triton | FlashInfer is NVIDIA-only |
| New or unusual KV format (MX-FP4) | Triton | Format support is a Python kernel, not a release |
| Linear-attention / hybrid layers (Gated DeltaNet) | Triton (already the default) | flash-linear-attention kernels are Triton |
| Speculative drafter + FP4 KV, SGLang 708f51e | neither | No backend served it correctly above |
Measured on 2× DGX Spark: FlashInfer with FP8 KV and the drafter is the fastest configuration that serves correctly. Triton reads MX-FP4 KV that FlashInfer cannot, but with a DFLASH drafter it did not serve, so its format advantage does not turn into capacity until the drafter’s write path supports FP4.