Skip to main content
Flash Attention is the attention algorithm used by miniVLLM during the prefill phase. It produces numerically identical results to standard scaled dot-product attention but accesses GPU high-bandwidth memory (HBM) in O(N) passes instead of O(N²), making it fast enough to handle long sequences that would otherwise be bottlenecked by memory bandwidth.

The memory problem with standard attention

Standard attention materializes the full N×N score matrix in HBM:
For a 4096-token sequence with 32 heads and float16, the score matrix alone occupies roughly 1 GB. HBM bandwidth, not arithmetic throughput, becomes the bottleneck.

Flash Attention’s solution: tiled computation

Flash Attention never materializes the full N×N matrix. Instead it processes Q in horizontal tiles of BLOCK_M rows and streams K, V in vertical tiles of BLOCK_N columns. The softmax denominator is maintained incrementally using an online softmax accumulator — only O(BLOCK_M) values are live in shared memory at once.
HBM reads scale with sequence length N (one full pass over K and V per Q tile) rather than N².

The flash_attention_prefill function

flash_attention_prefill in layers/attention.py is the Python entry point:
All sequences in the batch are concatenated into a single flat tensor. cu_seqlens is the array of cumulative lengths that tells the kernel where each sequence starts and ends — for example, [0, 512, 1024, 1536] represents three sequences of 512 tokens each.

Block size selection

Shared memory usage grows with BLOCK_M × head_dim (for Q) and BLOCK_N × head_dim (for K, V). The kernel picks conservative tile sizes to stay within the ~48 KB shared memory limit:

Grid layout

Each Triton program processes one BLOCK_M-row tile of Q, for one attention head, in one sequence. Programs for different sequences and heads are dispatched simultaneously.

The kernel: online softmax

The key to Flash Attention is updating the running max m_i and normalizer l_i as new K tiles arrive, rescaling the accumulated output acc accordingly.
alpha = exp(m_i - m_i_new) is always ≤ 1. It corrects the previously accumulated output and normalizer for the updated maximum, maintaining numerical stability throughout.

Variable-length sequence support

A batched prefill processes sequences of different lengths in one kernel launch. Rather than padding all sequences to the same length, miniVLLM passes cu_seqlens — a cumulative-length tensor — so each program can find its own sequence boundary:
This avoids wasted compute on padding tokens.

Grouped Query Attention (GQA)

Models like Qwen3 use fewer KV heads than query heads. Each query head maps to a KV head by integer division:
For example, with num_heads=32 and num_kv_heads=8, query heads 0–3 all read from KV head 0, query heads 4–7 from KV head 1, and so on. This halves or quarters the KV cache memory footprint without any additional code paths.

When flash attention is used

Flash attention is used only during prefill. The Attention.forward method selects the algorithm based on context.is_prefill:
During decode only one new token is generated per sequence per step, so the entire KV context lives in the paged cache. Flash Attention’s tiling advantage only applies when attending over a sequence being processed for the first time.
Flash Attention starts outperforming naive Triton at roughly sequence length 64–128 for head_dim=128. Below that threshold, the extra kernel launches from finer tiling add more overhead than the HBM savings recover. The benchmark_prefilling.py script measures this crossover point empirically.