2026-04-20
This report summarizes the key design decisions behind FlashKDA v1, our fused kernel implementation of Kimi Delta Attention. We walk through chunk-size selection, kernel fusion strategy, numerical-precision trade-offs, and the low-level optimizations that let the kernel reach its current performance level.
Unlike Flash Linear Attention, which uses CHUNK = 64, FlashKDA v1
uses CHUNK = 16. This decision is motivated by three considerations:
- Numerical range fits within bf16. With the gate
lower_boundset to-5,CHUNK = 16keeps the range ofexp(cumsum(g))within the representable precision of bf16. This eliminates the need for the elaborate intra-chunk rescaling tricks that larger chunk sizes require. - Cheap matrix inversion. Inverting a
16 × 16matrix is dramatically cheaper than inverting a64 × 64one, and the former can be computed directly from a Neumann-series expansion without further decomposition. - SM80-only MMA path. All
CHUNK = 16math maps cleanly onto SM80 MMA instructions. This keeps the kernel simple and makes it portable across a wide range of modern NVIDIA GPUs without relying on architecture-specific features.
We partition the full computation into two kernels along their natural parallelism axes:
- K1 (token-parallel, grid =
N × H × num_chunks):gactivation → L2 normalization → decay application →L/Mqkconstruction → matrix inversion. - K2 (head-parallel only, grid =
N × H): chunk-by-chunk delta-rule recurrence → output projection → running state accumulation.
Early prototypes of FlashKDA used a single fused kernel. In that design, the token-parallel work in the K1 stage was bottlenecked by the much lower parallelism of the recurrence in K2, leaving a large fraction of the SMs idle. Splitting the pipeline into two kernels yielded at least a 15% end-to-end speedup and made each stage independently tunable.
FlashKDA stores the on-chip recurrent state in bf16. This cuts the shared
memory footprint of the state roughly in half and removes the fp32 → bf16
cast that would otherwise sit on the critical path of every bf16 GEMM feeding
the state.
We validated this choice with extensive internal testing. As long as the
state update itself is performed with fp32 FMA instructions, storing the
accumulator in bf16 between updates introduces no measurable accuracy loss
across our inference benchmarks.
Several other precision-aware decisions appear inside the kernel:
- Sigmoid via
tanh.approx.f32. We implement sigmoid using the PTXtanh.approx.f32instruction, which is both faster and precise enough for the gating path. - FP16 matrix inversion. The
16 × 16inverse is computed in fp16 rather than bf16. As analyzed in this blog post, the elements of the inverse matrix are bounded within[-1, 1], so fp16's narrower dynamic range is sufficient. Using fp16 avoids thefp32 → bf16cast that bf16 MMA would otherwise require and gives the Neumann-series expansion extra headroom, improving the accuracy of the inverse.
- Base-2 exponent. In the
g_actstage we rebase the exponent to 2 and useex2.approx.ftz.f32. This removes the change-of-base FMA entirely and benefits from the higher throughput ofex2compared toexp. - K1 occupancy. Through aggressive shared-memory reuse (unions over
lifetimes that do not overlap) and
__launch_bounds__(256, 8), we trade a small amount of register spilling for a substantial increase in thread blocks per SM. The net effect is a significant improvement in K1 throughput. - K2 register-file transposes. By fusing the stages of K2 tightly and
using the
MOVM_Tinstruction to transpose operands directly inside the register file, we eliminate every intermediate shared-memory round trip between stages. This also shrinks the shared-memory buffer requirement of K2.
