FlashInfer or Triton for local inference — article mark

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

FlashInferTriton
What it isKernel library: CUDA / CUTLASS templatesTile-level kernel language + compiler
PaperMLSys 2025MAPL 2019
HardwareNVIDIA, SM 7.5 → 12.1 (Turing to Blackwell, incl. DGX Spark)NVIDIA SM 8.0+, AMD ROCm 6.2+; CPU in development
CoversAttention (paged / ragged KV, MLA, cascade, sparse), FP8 / FP4 GEMM, fused MoE, sampling, AllReduce, RoPE, normsWhatever is written in it: fused MoE, linear attention, quant / dequant, attention
Inner attention backendsFA2 / FA3, cuDNN, CUTLASS, TensorRT-LLMOne kernel per op, compiled for the GPU it runs on
CompilationJIT on first use, or prebuilt cubins (flashinfer-cubin)JIT per kernel and shape on first use, then cached
New KV layout or dtypeNew CUDA template; arrives with a releaseA Python kernel change
Used bySGLang, vLLM, TensorRT-LLM, TGI, MLC-LLM, LightLLMSGLang, 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).

OpPrefillDecode / verifyWhy that library
Full attention (target)FlashInferFlashInferPaged-KV kernels with a CUDA-graph-safe scheduler
Drafter attentionFlashInferFlashInferSGLang default: inherits the prefill backend
Gated DeltaNet linear attentionFlashInferTritonThe decode kernels come from flash-linear-attention, written in Triton
NVFP4 weight GEMMsFlashInfer / CUTLASSFlashInfer / CUTLASSFP4 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 GPUWorkPrecisionGB10 peakIdeal time
Linear layers24.3 GFLOPNVFP4323.2 TFLOP/s75 µs
Attention at 46.5K depth9.1 GFLOPBF1696.5 TFLOP/s95 µs
Sum33.4 GFLOP~197 TFLOP/s mixed170 µs
Measured, FlashInfer469 µs (37%)
Prefill time per prompt token per GPU: ideal 170 microseconds (linear 75 in FP4, attention 95 in BF16) against 469 measured; 37 percent of the mixed ceiling Prefill time per prompt token, per GPU, µs (1 px per µs) Ideal at GB10 peaks linear 75 attention 95 170 Measured, FP8 KV + FlashInfer 299 µs not priced by the model 469 Gap causes are estimates, not measured: TP all-reduce over the network, small 2,048-token GEMMs, norms / rotary / drafter, no prefill–decode overlap.
Measured: 0.96 s per 2,048-token chunk on 2× DGX Spark. Peaks: torch / flashinfer microbench on one idle GB10, 2026-09-30 (BF16 96.5, FP8 188.3, NVFP4 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

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

KV cache bytes per element: BF16 2, FP8 1, FP4 0.5625. Tokens per GB of pool relative to BF16: 1.00, 2.00, 3.56 KV bytes per element (120 px per byte) BF16 2 FP8 (e4m3 / e5m2) 1 FP4 (nvfp4 / mx) 0.5625 4 bit + one FP8 scale per 16 Tokens per GB of pool, vs BF16 (100 px per 1×) BF16 FP8 FP4 1.00× 2.00× 3.56× = 1.78× FP8
Bytes per element: SGLang 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 itDecode reads itFlashInferTriton
bfloat16, fp8_e4m3, fp8_e5m2all backendsall backendsyesyes
nvfp4FlashInfer (via FP8 dequant workspace)trtllm_mha onlyprefill onlyno
fp4_mx_block16triton, torch_native, flex_attention, trtllm_mhasamenoyes

From fp4_kv_cache_quant_method.py in that build.

Support also depends on the GPU, not only the library:

EngineGPU (compute capability)NVFP4 KVPath
vLLM 0.29GB10 (12.1)refusedFlashInfer trtllm-gen, 10.x only
vLLM 0.31RTX 5090 (12.0)acceptedFlashInfer FA2
SGLang 708f51eGB10 (12.1), no drafterservesFlashInfer 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:

KVTarget attentionDrafter attentionResultCause in the log
fp8_e4m3FlashInferFlashInferserves—
nvfp4FlashInfer prefill, trtllm_mha decodeFlashInferdies at init”nvfp4 does not support decode with flashinfer”
nvfp4sametrtllm_mhadies at graph capture”native FP4 KV cache supports decode only”
fp4_mx_block16TritonTritondies at first batchDFLASH writer copies BF16 into the FP4 pool
nvfp4same as row 2own FP8 poolwrong outputnone; 17×23 answered “22 by 5”, recall failed at 8k / 60k / 110k
nvfp4same 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/s3,301 / 2,417 / 1,802 at 9k / 70k / 128knot measured
KV pool, tokens2,887,2281,979,584
Long-context recall (8k / 60k / 110k)passpass

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

SituationPickReason
NVIDIA GPU, BF16 or FP8 KVFlashInferTuned kernels; 29–69% lower ITL in the paper
AMD GPUTritonFlashInfer is NVIDIA-only
New or unusual KV format (MX-FP4)TritonFormat 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 708f51eneitherNo 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.

Questions? Let's Chat :)

ai llm engineering