Trace FlashInfer from irregular KV-cache layouts through load-balanced attention kernels, composable state, and production serving boundaries.
Personalize this lesson
Adapt explanations and teaching visuals to your background and preferred voice.
Most inference requests look small to a Python caller and irregular to a GPU. One request may have 17 cached tokens, another 2,401, and a third may share a prefix with five neighbors. Their keys and values live in different physical pages, yet one kernel launch must produce the next-token states.
FlashInfer is a library and kernel generator for that boundary. It gives serving systems common APIs for attention, matrix multiplication, mixture-of-experts operations, sampling, and cache updates while choosing specialized implementations for a workload and GPU.[1] This deep dive follows one decode step from logical token rows to GPU work, then asks what the design buys and what it leaves to the serving engine.
FlashInfer began in 2023 with researchers from the University of Washington, Carnegie Mellon University, and OctoAI. Current source, review paths, and extended CI show active NVIDIA participation alongside the FlashInfer community.[2][3] The paper lists Zihao Ye, Lequn Chen, Ruihang Lai, Wuwei Lin, Yineng Zhang, Stephanie Wang, Tianqi Chen, Baris Kasikci, Vinod Grover, Arvind Krishnamurthy, and Luis Ceze as authors.[4] That mix matters: the project sits between academic kernel research, compiler and GPU engineering, and the serving frameworks that need a stable operator boundary.
| Field | Current project fact |
|---|---|
| Origin | University of Washington, Carnegie Mellon, and OctoAI researchers started the project; the launch post names the initial engineering team.[2] |
| Stewardship | The flashinfer-ai community repository uses core-maintainer review, public CI, and an additional NVIDIA GPU test path.[1][3] |
| Founding contributors | Zihao Ye, Lequn Chen, Ruihang Lai, and the paper's compiler, systems, and GPU collaborators form the documented research lineage.[4] |
| Source license | Apache-2.0 for FlashInfer's core source.[5] Bundled CUDA components and dependencies can retain BSD, MIT, or other notices. |
| Commercial boundary | NVIDIA participates in maintenance and CI, but FlashInfer remains a public kernel library rather than a model or hosted inference product.[3] |
| Asset boundary | Tutorials may download gated or separately licensed checkpoints. FlashInfer's Apache license doesn't grant rights to those models or datasets. |
The repository names SGLang, vLLM, TensorRT-LLM, Text Generation Inference, MLC-LLM, LightLLM, lorax, and ScaleLLM as adopters or integrations.[1] Adoption doesn't mean every framework enables every operator. Check the framework's attention backend, FlashInfer release, CUDA version, and model feature matrix before treating a project-level capability as a service guarantee.
The transformer equation is regular: each query compares itself with keys, turns scores into probabilities, and combines values. A serving engine isn't regular. Requests arrive and finish independently, sequence lengths change every step, and key-value (KV) cache pages are allocated from a shared pool.
For a batch with requests r = 0, 1, 2, the logical lengths could be [3, 1, 4]. A ragged query buffer stores eight rows back-to-back. Its row boundaries are carried by an index pointer array:
| request | query rows | qo_indptr interval | logical KV length |
|---|---|---|---|
| 0 | 3 | [0, 3) | 6 |
| 1 | 1 | [3, 4) | 2 |
| 2 | 4 | [4, 8) | 9 |
The query tensor is compact, but the KV cache is usually paged. A page table maps each request's logical page to a physical page. Page size P makes allocation independent of the exact sequence length, much like virtual memory maps a process address to a physical frame.[6]
FlashInfer documents several logical layouts because no single physical arrangement wins every phase. Ragged storage is contiguous over tokens. Paged storage breaks a sequence into fixed-size pages. Block-sparse row (BSR) and compressed sparse row (CSR) metadata describe which page blocks are active. Multi-head latent attention (MLA) can store compressed latent states instead of ordinary K/V heads.[7]
The kernel needs three kinds of information:
Payload buffers can remain stable while metadata changes every scheduling step. A serving engine can recycle pages without asking a kernel to understand allocator policy. A kernel can specialize memory access around a known page size without owning the allocator.
Prefill consumes many new tokens per request. A ragged layout keeps those tokens contiguous, so a batch prefill wrapper can traverse row ranges with one set of offsets. Decode usually consumes one token per live request and reads a long history. A paged layout avoids copying each history into a newly padded tensor.
The two phases can meet in one mixed batch. FlashInfer exposes wrappers for single-request operations, batch decode with paged KV, batch prefill with paged KV, and batch prefill with ragged KV. Its unified BatchAttention wrapper can dispatch between paged prefill and paged decode based on per-request lengths, although a serving engine still owns the policy deciding which requests enter the batch.[1]
BSR metadata doesn't mean the data are mathematically sparse in every model. It says the kernel may load fixed-size blocks through an index table. If a request references pages [7, 2, 14], the GPU can gather those pages without a defragmentation copy. If a prefix is shared, several request rows can point at the same physical pages while their suffix pages diverge.
That indirection adds pointer arithmetic and less predictable memory access. It pays off when avoiding copies and padding saves more work than the extra gathers cost. Tiny sequences can lose to a simpler contiguous kernel. This is a workload decision, not a universal speedup.
An attention kernel normally looks like a matrix multiplication followed by a softmax and a value multiplication. FlashInfer's important move is to treat the state needed to finish attention as the composable unit.
For one query row, keep the output vector o and log-sum-exp statistic l instead of every score. If two workers process disjoint key ranges, each returns (o_1, l_1) and (o_2, l_2). The states can be merged exactly:
FlashInfer's public state tensors store base-2 log-sum-exp. The equations and worked values below use natural log-sum-exp for readability. For API state , natural LSE is ; to pass natural back to merge_state, use .[1]
m = max(l_1, l_2)
o = (exp(l_1 - m) o_1 + exp(l_2 - m) o_2) / (exp(l_1 - m) + exp(l_2 - m))
l = m + log(exp(l_1 - m) + exp(l_2 - m))
The max subtraction keeps the exponentials in a safe range. A tree can merge many chunks without replaying all key-value tiles. FlashInfer calls this family of operations merge-state, cascade, or recursive attention depending on the wrapper.[8]
Take one query row and two key chunks. Chunk A has l_A = 2 and output o_A = [1, 0]. Chunk B has l_B = 1 and output o_B = [0, 2].
m = max(2, 1) = 2.w_A = exp(0) = 1 and w_B = exp(-1) ≈ 0.3679.z = 1.3679.o ≈ ([1, 0] + 0.3679 · [0, 2]) / 1.3679 ≈ [0.7311, 0.5379].l = 2 + log(1.3679) ≈ 2.3133 for a parent merge.The merge doesn't need the original scores. Split-KV workers can run on separate pages or sequence ranges, then publish a small state record. Matching state precision and masking rules makes the merged value mathematically equivalent to one larger softmax.
FlashInfer wrappers separate a metadata pass (plan) from the hot data pass (run). The plan computes offsets, request-to-tile assignments, temporary-buffer sizes, and choices such as whether to split KV work. The run consumes that plan with query and cache tensors.
This is an inspector-executor pattern:
The split matters because request metadata changes more often than tensor shapes. A scheduler can call plan after admission or batch reshaping, then call run repeatedly for a CUDA graph or a decode step. Reusing the plan avoids repeating integer arithmetic and allows the run kernel to use precomputed offsets.
It also creates an explicit lifetime contract:
| object | created by | consumed by | invalidated when |
|---|---|---|---|
| page indices | allocator/scheduler | plan and run | pages move or requests finish |
| plan workspace | plan | run | batch shape, head shape, or policy changes |
| partial states | run kernel | merge kernel | current query step ends |
| output tensor | serving engine | sampler/model block | next layer overwrites it |
1# Shape-only sketch. Exact argument names vary by wrapper and release.
2wrapper.plan(
3 qo_indptr=qo_indptr,
4 kv_indptr=kv_indptr,
5 kv_indices=kv_indices,
6 kv_last_page_len=kv_last_page_len,
7 num_qo_heads=32,
8 num_kv_heads=8,
9 head_dim=128,
10 page_size=16,
11)
12output = wrapper.run(q, paged_kv_cache)The sketch is intentionally not a copy-paste training recipe. Production callers must match dtypes, device, page layout, head grouping, and wrapper lifetime. The repository's tests and generated API docs are the contract for a specific release.[1]
If one request has 128K cached tokens and seven requests have 128 tokens, assigning one CTA per request leaves most warps idle behind the long request. FlashInfer can partition long KV ranges into chunks and distribute them across CTAs. The plan records the mapping and the run phase computes partial states.
The scheduler must choose a chunking policy. A tiny chunk raises merge overhead and metadata traffic. A large chunk raises tail latency because one CTA owns too much work. Maximum occupancy alone is the wrong target; the current mix of decode lengths, page locality, head dimension, and concurrent streams needs balanced work.
| choice | likely benefit | cost or risk |
|---|---|---|
| no split for short histories | low metadata overhead | long request can become a tail |
| split long histories | better CTA balance | partial output and merge workspace |
| split plus CUDA graph | stable launch path | graph shape must stay compatible |
| dynamic plan each step | follows request churn | plan CPU/GPU work and synchronization |
The paper describes a load-balanced scheduler designed to cope with dynamic user requests while remaining compatible with CUDA Graphs, which prefer stable execution shapes.[4] That claim is paper-era and workload-specific. Measure inter-token latency (ITL), tail latency, and memory traffic on the exact model and GPU rather than copying a chunk threshold from a benchmark.
FlashInfer isn't one monolithic CUDA kernel. Its Python and C++ APIs select among implementations such as FlashAttention-2/3, cuDNN, CUTLASS, TensorRT-LLM, and generated kernels. The choice depends on architecture, operation, dtype, layout, and optional features.[1]
The JIT path matters when the combination is unusual: a custom attention score, a new head dimension, a novel cache layout, or a deployment GPU that isn't covered by a precompiled binary. A template specializes code, compiles it, and caches the resulting module. Precompiled flashinfer-cubin and JIT-cache packages reduce first-use latency when their CUDA and architecture match.
The boundary is practical:
| deployment mode | startup behavior | best fit |
|---|---|---|
| precompiled cubin | load matching binary | known GPU fleet, predictable startup |
| JIT module | compile on first use, then cache | custom shape or attention variant |
| backend dispatch | select cuDNN/CUTLASS/TRT-LLM/FA | reuse mature vendor path |
| Python fallback or reference | correctness and diagnosis | development, unsupported shape |
JIT can make a feature possible, but it moves compiler compatibility into your release. Pin CUDA, compiler, driver, and cache location. Warm kernels before exposing production traffic. Log which backend was selected, so a silent fallback doesn't look like a model regression.
The repository exposes module status, cache management, and API logging commands. API logging can capture calls and system information for diagnosis, but tensor-dumping modes may write sensitive prompts and outputs to disk. Treat diagnostics as production data handling, not as a free debug switch.
FlashInfer owns kernel-level execution. It doesn't decide admission, tenant quotas, request cancellation, model weights, or page allocation. A stack such as vLLM or SGLang owns those policies, then passes shape and indirection metadata into FlashInfer. TensorRT-LLM can supply one backend implementation, but backend availability isn't the same as serving-engine integration.
| layer | responsibility | FlashInfer boundary |
|---|---|---|
| API gateway | auth, rate limit, cancellation | outside |
| serving scheduler | continuous batching, priority, deadlines | supplies batch metadata |
| KV allocator | physical page ownership and reuse | supplies page table |
| FlashInfer wrapper | plan offsets, choose kernel, run attention | core |
| model block | projections, residuals, logits | calls attention output |
| sampler | top-k/top-p, stop rules | separate FlashInfer operators can help |
The allocator boundary is a source of bugs. Reusing pages while a CUDA stream still reads them can corrupt another request. A disagreement between the page table and kv_last_page_len can expose uninitialized values in the last tile. Reusing a plan after page indices change may produce numerically plausible output for the wrong sequence.
The same project covers prefill, decode, append, mixed prefill/decode, MLA, sparse patterns, sampling, quantization, and communication. A serving engine can keep a stable integration surface while the selected kernel changes.
Ragged and paged layouts, plan/run wrappers, split-KV state, and cascade attention expose the metadata that a scheduler already has. The pieces compose instead of requiring one opaque end-to-end graph.
JIT templates let an unusual attention variant stay close to the established wrapper contract. Mature backend implementations can handle common shapes while generated code covers new combinations.
The repository lists support from Turing (SM75) through Ampere, Ada, Hopper, and Blackwell families, while noting that features differ by compute capability. That range is useful for mixed fleets, but an operation advertised at project level can still be unavailable on a particular GPU.
The engine must now maintain indptr, page indices, last-page lengths, workspaces, events, and plan validity. PagedAttention makes memory efficient, but correctness depends on allocator and kernel agreeing on the mapping.[6]
First-use compilation can create startup spikes or fail because a driver, CUDA toolkit, compiler, or architecture is missing. A warm cache can go stale after a version change. Precompiled wheels reduce risk but narrow supported combinations.
Every backend, dtype, head grouping, page layout, causal mode, and architecture multiplies the test matrix. A green unit test on one GPU doesn't prove a mixed-fleet deployment.
FlashInfer targets NVIDIA CUDA architectures rather than CPU, AMD, or Apple inference backends. Porting the API idea is possible, but the CUDA kernels, backend dispatch, and cache packages are hardware-specific.
FlashInfer optimizes inference kernels and serving operators. It can be useful around evaluation or generation during training, but it isn't a distributed training framework, optimizer, checkpoint format, or gradient engine. Use it for forward-time serving work, not as a replacement for a training stack.
The FlashInfer paper was submitted in January 2025 and revised in April 2025. Its abstract reports 29% to 69% inter-token-latency reduction against compiler backends on an LLM-serving benchmark, 28% to 30% latency reduction for long-context inference, and 13% to 17% speedup for parallel generation.[4] Those ranges are useful evidence that layout-aware kernels can matter, not universal service-level SLOs.
Read every number with its denominator:
| paper-era result | what it supports | what it doesn't support |
|---|---|---|
| 29% to 69% lower ITL vs compiler backends | kernel and serving comparison under reported conditions | same gain on another model, GPU, or scheduler |
| 28% to 30% lower long-context latency | benefit when long KV reads dominate | a fixed latency budget for all context lengths |
| 13% to 17% faster parallel generation | useful split or batching behavior in tested setup | guaranteed throughput at another concurrency |
For a production decision, replay your trace with the same prompt length distribution, output length, batch policy, quantization, CUDA graph mode, and error budget. Compare p50 and p99 time-to-first-token, ITL, tokens per second, GPU memory, compile time, and correctness against a reference implementation.
Use the local repository as a map rather than trying to read every kernel first:
flashinfer/decode.py and find BatchDecodeWithPagedKVCacheWrapper.plan and .run.csrc/batch_decode.cu, where offsets and optional split-KV buffers become kernel parameters.flashinfer/cascade.py to see how attention states compose across shared prefixes.flashinfer/page.py and csrc/page.cu for page append and slot mapping.The productive question at each layer is: what shape and lifetime does this function assume, and who owns the next buffer? That question finds more bugs than memorizing kernel names.
By this point, you can:
Answer every question, then check your score. Score 75% or higher to mark this lesson complete.
7 questions remaining.
FlashInfer
FlashInfer Community and NVIDIA · 2026
FlashInfer: Kernel Library for LLM Serving
FlashInfer Project · 2024
Contributing to FlashInfer
FlashInfer Community · 2026
FlashInfer: Efficient and Customizable Attention Engine for LLM Inference Serving.
Ye, Z., et al. · 2025
FlashInfer Apache License 2.0
FlashInfer Community · 2026
Efficient Memory Management for Large Language Model Serving with PagedAttention.
Kwon, W., et al. · 2023 · SOSP 2023
KV-Cache Layouts
FlashInfer Project · 2026
Recursive Attention
FlashInfer Project · 2026
Questions and insights from fellow learners.