How FlashInfer's Paged KV Cache Attention Outperforms SDPA Fallback in Streaming Inference
FlashInfer's paged KV cache attention reduces memory bandwidth and kernel launch overhead by reading only visible cache pages rather than dense tensors, delivering substantial speed-ups over PyTorch's native SDPA fallback for long streaming sequences.
LingBot-Map implements a two-stream paged KV-cache system using FlashInfer to accelerate transformer attention in streaming applications. According to the Robbyant/lingbot-map source code, the FlashInferAttention layer leverages the BatchPrefillWithPagedKVCacheWrapper kernel to process frames efficiently, falling back to native PyTorch Scaled Dot-Product Attention (SDPA) only when FlashInfer is unavailable or when use_sdpa=True is explicitly set. This architecture specifically optimizes for long sequences where frames share KV-cache entries, minimizing both memory traffic and computational overhead compared to dense attention approaches.
Architecture of the Paged KV Cache
Two-Stream Design
The cache splits storage across two distinct streams managed by FlashInferKVCacheManager in lingbot_map/layers/flashinfer_cache.py:
- Patch stream (recyclable): Holds KV entries for scale frames and sliding-window live frames. Each frame occupies exactly one patch page (
self.patches_per_frametokens) with zero padding. Pages are allocated fromself.free_patch_pagesand recycled after exiting the sliding window viaevict_frames. - Special stream (append-only): Stores KV for special tokens (camera, register, scale token) that appear in every frame. Tokens pack continuously across pages, which are never evicted. Allocation occurs in
_write_special_tokensusingself.all_special_pagesandself.free_special_pages.
Physical Memory Layout
The cache stores data as a 5-D tensor per block with shape [max_num_pages, 2, page_size, H, D], where dimension 1 separates keys and values. This layout is instantiated in FlashInferKVCacheManager.__init__ (lines 42-48 of flashinfer_cache.py).
Plan-Run Execution Workflow
The attention mechanism uses an efficient plan-run pattern implemented in compute_attention (lines 85-115 of lingbot_map/layers/attention.py):
- Plan (once per frame step): Only the first block (
block_idx == 0) builds the visible page table viabuild_visible_page_table(lines 38-44), computes the last-page length withcompute_last_page_len(lines 52-63), and callsself.prefill_wrapper.plan(...). - Run (once per block): Each block reuses the cached plan and executes the kernel via
self.prefill_wrapper.run(...).
This amortizes expensive planning work across all blocks, reducing per-block overhead to a lightweight kernel dispatch.
Performance Comparison: FlashInfer vs SDPA Fallback
Memory Bandwidth Efficiency
FlashInfer reads only the visible pages (scale + window + special) as determined by build_visible_page_table. This sparse access pattern avoids materializing dense KV tensors for the entire sliding window. In contrast, SDPA fallback operates on a dense tensor of shape [B, N, H, D], forcing the kernel to load the complete window (often thousands of tokens) at every step regardless of actual dependencies.
Kernel Launch Overhead
The FlashInfer path executes one plan kernel plus one run kernel per block, with planning cost shared across blocks. The SDPA fallback requires a separate torch.nn.functional.scaled_dot_product_attention call (line 59 of attention.py) per forward pass, processing the entire dense KV tensor each time without amortization.
Cache Reuse and Page Recycling
FlashInfer stores KV pages once and reuses them across frames; only new pages are written via append_frame, while evicted pages are recycled through free_patch_pages without memory copies. SDPA recomputes the dense KV representation each step, incurring extra allocation overhead and potential fragmentation.
Precision and Hardware Utilization
FlashInfer kernels operate natively in fp16/bf16 (with fp32 support via force_fp32), keeping data on-device without conversion and enabling tensor core usage. The SDPA fallback may up-cast to fp32 internally, and when forced to fp32 mode loses tensor core acceleration entirely.
Scalability for Streaming
The page-based design supports arbitrarily long streaming sequences bounded only by the sliding-window size. SDPA memory consumption grows linearly with total window length, quickly exhausting GPU memory for extended streams.
Implementation in the LingBot-Map Codebase
Cache Manager Integration
The AggregatorStream class in lingbot_map/aggregator/stream.py instantiates either FlashInferBlock or SDPABlock based on the use_flashinfer flag (lines 91-99). It lazily creates a FlashInferKVCacheManager on the first forward pass (lines 199-215), ensuring the cache is properly sized for the model configuration.
Attention Layer Logic
FlashInferBlock (defined in lingbot_map/layers/block.py, lines 151-180) preprocesses QKV projections, appends KV to the cache via the manager's methods, and invokes compute_attention. If the cache is not a FlashInferKVCacheManager instance (line 46 of attention.py), or if use_sdpa=True is passed to model constructors, the code bypasses the paged cache entirely and routes to the SDPA path.
Model Configuration
Model constructors in lingbot_map/models/gct_stream.py (line 125) and lingbot_map/models/gct_stream_window.py (line 166) expose the use_sdpa argument. When False (default), FlashInfer serves as the primary backend, as documented in gct_stream_window_v2.py (line 183).
Practical Usage Examples
Instantiating a Model with FlashInfer (Default)
from lingbot_map.models.gct_stream import GCTStream
model = GCTStream(
num_blocks=12,
dim=1024,
num_heads=16,
use_sdpa=False, # FlashInfer is default; set True to force SDPA
kv_cache_sliding_window=64,
kv_cache_scale_frames=8,
)
Running a Single Streaming Step
import torch
# Dummy input for one frame (B=1, N=262 tokens, C=dim)
x = torch.randn(1, 262, 1024, device="cuda")
# Forward pass – lazily creates KV-cache manager
out = model(x) # Uses FlashInferAttention internally
Forcing the SDPA Fallback
model_sdpa = GCTStream(
num_blocks=12,
dim=1024,
num_heads=16,
use_sdpa=True, # Disables FlashInfer completely
)
out = model_sdpa(x) # Routes through SDPA path in FlashInferAttention.forward
Inspecting Cache Statistics
# After processing frames
kv_manager = model.blocks[0].attn.kv_cache # FlashInferKVCacheManager instance
stats = kv_manager.get_cache_stats() # Defined in flashinfer_cache.py lines 303-322
print(stats)
# Output: {'frame_count': 42, 'scale_pages': 8, 'live_pages': 34,
# 'free_pages': 46, 'special_tokens': 252}
Summary
- FlashInfer's paged KV cache minimizes memory traffic by reading only visible pages rather than dense tensors, dramatically reducing bandwidth requirements for long sequences.
- The plan-run workflow amortizes planning overhead across transformer blocks, while SDPA requires full dense tensor processing on every forward pass.
- Two-stream architecture separates recyclable patch pages from append-only special tokens, enabling efficient page reuse without memory copies.
- Implementation spans
lingbot_map/layers/flashinfer_cache.py(cache management),lingbot_map/layers/attention.py(kernel execution), andlingbot_map/layers/block.py(integration), with fallback logic triggered whenuse_sdpa=Trueor when FlashInfer is unavailable. - The system automatically handles variable-length sequences via
compute_last_page_lenandbuild_visible_page_table, ensuring correctness while maintaining performance.
Frequently Asked Questions
When does LingBot-Map fall back to SDPA instead of using FlashInfer?
The code falls back to SDPA when FlashInfer is not installed, when the model runs in fp32 mode without tensor core support, or when the user explicitly sets use_sdpa=True in the model constructor. This check occurs in FlashInferAttention.forward (around line 46 of lingbot_map/layers/attention.py), which verifies whether the provided kv_cache is an instance of FlashInferKVCacheManager; if not, it routes to torch.nn.functional.scaled_dot_product_attention (line 59).
How does the paged cache handle variable sequence lengths across frames?
The cache handles variable lengths through the compute_last_page_len method (lines 52-63 of flashinfer_cache.py), which calculates how many tokens are valid on the final special page since special tokens append continuously and may not fill a page completely. The build_visible_page_table function (lines 38-44) constructs the ordered list of pages (scale → live → special) for each timestep, ensuring the attention kernel only processes valid entries regardless of how many frames are currently in the sliding window.
What is the physical memory format of the KV cache?
The KV cache stores data as a 5-D tensor with shape [max_num_pages, 2, page_size, H, D], where the second dimension (size 2) separates keys and values, page_size is the number of tokens per physical page, H is the number of heads, and D is the head dimension. This layout is allocated in FlashInferKVCacheManager.__init__ (lines 42-48) and enables FlashInfer to index pages directly without additional memory copies.
Can FlashInfer be used with fp32 precision?
Yes, FlashInfer supports fp32 through the force_fp32 path, though it is optimized for fp16 and bf16 to leverage tensor cores. When the model is forced to fp32, the kernels can still execute, but users may experience reduced performance compared to lower precision modes. The SDPA fallback is typically used only when FlashInfer is unavailable, not as a precision-specific optimization, since FlashInfer handles the necessary type conversions internally.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →