LeetLLM
My PlanLearnGlossaryTracksPracticeBlog
LeetLLM

Your go-to resource for mastering AI & LLM systems.

Product

  • Learn
  • Glossary
  • Tracks
  • Practice
  • Blog
  • RSS

Legal

  • Terms of Service
  • Privacy Policy

© 2026 LeetLLM. All rights reserved.

All Topics
Your Progress
0%

0 of 177 articles completed

🛠️Computing Foundations0/9
Git, Shell, Linux for AIDocker for Reproducible AIPython for AI EngineeringNumPy and Tensor ShapesCUDA for ML TrainingMPS & Metal for ML on MacData Structures for AISQL and Data ModelingAlgorithms for ML Engineers
📊Math & Statistics0/8
Gradients and BackpropVectors, Matrices & TensorsLinear Algebra for MLAdam, Momentum, SchedulersProbability for Machine LearningStatistics and UncertaintyDistributions and SamplingHypothesis Tests, Intervals, and pass@k
📚Preparation & Prerequisites0/13
Neural Networks from ScratchCNNs from ScratchTraining & BackpropagationSoftmax, Cross-Entropy & OptimizationRNNs, LSTMs, GRUs, and Sequence ModelingAutoencoders and VAEsThe Transformer Architecture End-to-EndLanguage Modeling & Next TokensFrom GPT to Modern LLMsPrompt Engineering FundamentalsCalling LLM APIs in ProductionFirst AI App End-to-EndThe LLM Lifecycle
🧮ML Algorithms & Evaluation0/11
Linear Regression from ScratchLogistic Regression and MetricsDecision Trees, Forests, and BoostingReinforcement Learning BasicsValidation and LeakageClustering and PCACore Retrieval AlgorithmsDecoding AlgorithmsExperiment Design and A/B TestingPyTorch Training LoopsDataset Pipelines and Data Quality
📦Production ML Systems0/6
Feature Engineering for Production MLBatch and Streaming Feature PipelinesGradient Boosted Trees in ProductionRanking and Recommendation SystemsForecasting and Anomaly DetectionMonitoring Predictive Models
🧪Core LLM Foundations0/8
The Bitter Lesson & ComputeBPE, WordPiece, and SentencePieceStatic to Contextual EmbeddingsPerplexity & Model EvaluationFile Ingestion for AIChunking StrategiesLLM Benchmarks & LimitationsInstruction Tuning & Chat Templates
🧰Applied LLM Engineering0/24
Dimensionality Reduction for EmbeddingsCoT, ToT & Self-Consistency PromptingFunction Calling & Tool UseMCP & Tool Protocol StandardsContext EngineeringPrompt Injection DefenseResponsible AI GovernanceData Labeling and Human FeedbackEvaluating AI AgentsProduction RAG PipelinesHybrid Search: Dense + SparseReranking and Cross-Encoders for RAGRAG Evaluation for Reliable AnswersLLM-as-a-Judge EvaluationBias & Fairness in LLMsHallucination Detection & MitigationLLM Observability & MonitoringExperiment Tracking with MLflow and W&BPrompt Optimization with DSPyModel Versioning & DeploymentSemantic Caching & Cost OptimizationLLM Cost Engineering & Token EconomicsModel Gateways, Routing, and FallbacksDesign an Automated Support Agent
🎓Portfolio Capstones0/9
Capstone: Delivery ETA PredictionCapstone: Product RankingCapstone: Demand ForecastingCapstone: Image Damage ClassifierCapstone: Production ML PipelineCapstone: Document QACapstone: Eval DashboardCapstone: Fine-Tuned ClassifierCapstone: Reproducible ML Study
🧠Transformer Deep Dives0/8
Sentence Embeddings & Contrastive LossEmbedding Similarity & QuantizationScaled Dot-Product AttentionVision Transformers and Image EncodersPositional Encoding: RoPE & ALiBiLayer Normalization: Pre-LN vs Post-LNMechanistic InterpretabilityDecoding Strategies: Greedy to Nucleus
🧬Advanced Training & Adaptation0/16
Scaling Laws & Compute-Optimal TrainingPre-training Data at ScaleBuild GPT from Scratch LabJAX for PyTorch ResearchersContinued Pretraining for Domain ShiftSynthetic Data PipelinesSupervised Fine-Tuning PipelineMixed Precision TrainingDistributed Training: FSDP & ZeROLoRA & Parameter-Efficient TuningReward Modeling from Preference DataRLHF & DPO AlignmentConstitutional AI & Red TeamingRLVR & Verifiable RewardsKnowledge Distillation for LLMsModel Merging and Weight Interpolation
🤖Advanced Agents & Retrieval0/16
Vector DB Internals: HNSW & IVFAdvanced RAG: HyDE & Self-RAGGraphRAG & Knowledge GraphsRAG Security & Access ControlStructured Output GenerationReAct & Plan-and-ExecuteGuardrails & Safety FiltersCode Generation & SandboxingComputer-Use / GUI / Browser AgentsHuman-in-the-Loop Agent ArchitectureAI Coding Workflow with AgentsAgent Memory & PersistenceAgent Failure & RecoveryRecursive Language Models (RLM)Multi-Agent OrchestrationCapstone: Production Agent
⚡Inference & Production Scale0/19
Inference: TTFT, TPS & KV CacheMulti-Query & Grouped-Query AttentionKV Cache & PagedAttentionPrefix Caching and Prompt CachingFlashAttention & Memory EfficiencyContinuous Batching & SchedulingScaling LLM InferenceModel Parallelism for LLM InferenceModel Quantization: GPTQ, AWQ & GGUFLocal LLM DeploymentSLM Specialization & Edge DeploymentSpeculative DecodingLong Context Window ManagementMixture of Experts ArchitectureMamba & State Space ModelsReasoning & Test-Time ComputeAdvanced MLOps & DevOps for AIGPU Serving & AutoscalingA/B Testing for LLMs
🏗️System Design Capstones0/9
Content Moderation SystemCode Completion SystemMulti-Tenant LLM PlatformLLM-Powered Search EngineVision-Language Models & CLIPMultimodal LLM ArchitectureDiffusion Models: Images & TextReal-Time Voice AI AgentReasoning Agent System Design
🎤AI Lab Interviewing0/4
AI Lab Coding Interview: Python SystemsAI Lab System Design InterviewAI Lab Behavioral InterviewAI Lab Technical Presentation
🔬Project Deep Dives0/17
Deep Dive - vLLMDeep Dive - SkyRLDeep Dive - FlashAttentionDeep Dive - FlashInferDeep Dive - DeepGEMMDeep Dive - NCCLDeep Dive - MegatronDeep Dive - DeepSpeedDeep Dive - RayDeep Dive - MLflowDeep Dive - PyTorchDeep Dive - TransformersDeep Dive - SGLangDeep Dive - slimeDeep Dive - DeepEPDeep Dive - TinkerDeep Dive - Light-PEFT
Back to Topics
LearnInference & Production ScaleFlashAttention & Memory Efficiency
🚀HardInference Optimization

FlashAttention & Memory Efficiency

Understand how FlashAttention cuts auxiliary attention memory from O(n²) to O(n) with tiling and online softmax, and analyze its IO complexity.

32 min read
Learning path
Step 133 of 177 in the full curriculum
Prefix Caching and Prompt CachingContinuous Batching & Scheduling

Personalize this lesson

Adapt explanations and teaching visuals to your background and preferred voice.

Prefix caching avoids repeating prefill work across requests. FlashAttention attacks a lower layer of the same bottleneck: it makes each dense attention computation move far less data through GPU memory.

FlashAttention is an attention implementation that reduces memory traffic without changing the mathematical attention operator. Start with why exact attention can be slow on GPUs, then look at how tiling and recomputation make long-context execution practical.

A long incident timeline lets every new token compare itself against every prior token: alert text, trace spans, deploy diffs, log excerpts, and runbook snippets. As the timeline gets longer, the number of connections you need to track explodes. This is exactly the problem modern AI models face with the attention mechanism. Dense attention still performs a quadratic number of score comparisons as input grows. In an implementation that materializes all score or probability values, its auxiliary storage is quadratic too: double the input length, and those matrices need four times the memory.

FlashAttention reorganizes how dense attention is computed at the hardware level. It computes the same operator, but doesn't materialize the full score or probability matrix in HBM (High Bandwidth Memory, the large GPU RAM pool).[1]Reference 1FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness.https://arxiv.org/abs/2205.14135 That lowers auxiliary attention memory and HBM traffic. When an eligible FlashAttention kernel is a bottleneck improvement for the workload, it can increase throughput or make a longer context fit.

What is the main promise of FlashAttention?

Answer

It computes exact dense attention while avoiding the full n×nn \times nn×n score and probability matrices in HBM. The win is lower memory traffic and lower auxiliary memory, not a different attention formula.

The memory wall

A quick recap of materialized attention

Before the fix, ground the problem with a tiny example. In the scaled dot-product attention article, you saw that each query token scores every key token, normalizes those scores with softmax, and blends the corresponding value vectors.

Suppose you have only three trace tokens and a head dimension of two:

  • Query QQQ is a 3×23 \times 23×2 matrix (one row per token).
  • Key KKK and Value VVV are also 3×23 \times 23×2.

The score matrix S=QKTS = QK^TS=QKT is 3×33 \times 33×3. That's nine numbers. For three tokens, this is trivial. But for real sequences, the story changes fast.

For batch=8batch=8batch=8, heads=32heads=32heads=32, seq_len=8192seq\_len=8192seq_len=8192 in 16-bit floating point (FP16):

Attention matrix=8×32×81922×2 bytes≈34 GB\text{Attention matrix} = 8 \times 32 \times 8192^2 \times 2 \text{ bytes} \approx 34 \text{ GB}Attention matrix=8×32×81922×2 bytes≈34 GB

That product is exactly 34,359,738,36834{,}359{,}738{,}36834,359,738,368 bytes, which is 32 GiB or about 34 GB depending on whether you count in binary or decimal units. If an attention implementation saves this intermediate, it consumes a large part of an 80 GB A100 before counting model weights, other activations, or gradients.

Run this calculation before blaming model weights for an out-of-memory error:

score-matrix-size.py
1batch, heads, sequence, bytes_per_value = 8, 32, 8192, 2 2score_bytes = batch * heads * sequence * sequence * bytes_per_value 3 4print(f"score values: {batch * heads * sequence * sequence:,}") 5print(f"binary size: {score_bytes / 1024**3:.2f} GiB") 6print(f"decimal size: {score_bytes / 1000**3:.2f} GB")
Output
1score values: 17,179,869,184 2binary size: 32.00 GiB 3decimal size: 34.36 GB

Why does an 8,192-token attention matrix become huge even before model weights are counted?

Answer

Each head compares every query token with every key token, so storage grows with seq_len2seq\_len^2seq_len2. With batch 8, 32 heads, 8,192 by 8,192 scores, and 2 bytes per FP16 score, the score matrix alone is about 34 GB (32 GiB).

GPU memory hierarchy

FlashAttention starts with where data lives. A GPU isn't a flat memory space. It has layers:

  • On-chip SRAM (Static Random Access Memory) is the tiny, very fast scratchpad right next to the compute cores.
  • HBM is the large pool of GPU memory (VRAM) that holds model weights and tensors.
  • CPU DRAM is host memory outside the GPU, used only when data must leave the card entirely.

Memory hierarchy intuition: On-chip SRAM is a tiny, fast scratchpad beside the compute cores. HBM is much larger, but every revisit costs bandwidth. A materializing attention baseline keeps returning to HBM because the full n×nn \times nn×n score matrix doesn't fit in the scratchpad. FlashAttention keeps only active tiles there at a time.

In the original FlashAttention paper, the motivating A100 numbers are roughly 20 MB of aggregate on-chip SRAM at about 19 TB/s bandwidth versus 40 GB of HBM at about 1.5 TB/s.[1]Reference 1FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness.https://arxiv.org/abs/2205.14135 A kernel doesn't get to use that full 20 MB as one giant scratchpad, though: practical tile sizes are bounded by much smaller per-SM shared-memory and register budgets. The exact chip layout varies by GPU generation, but the qualitative gap is the same: on-chip memory is tiny and fast, off-chip memory is large and much slower to revisit.

Memory tierTypical role in attentionCapacity / bandwidth intuition
On-chip SRAMHold the current Q/K/V tiles and running softmax statisticsTiny, but fast enough to reuse the same tile many times
HBMHold Q, K, V, O, model weights, and other activationsMuch larger, but expensive to touch for every intermediate
CPU DRAMHost memory outside the GPULarger still, but not suitable for the inner loop of an attention kernel
Diagram showing SRAM (On-chip) Tiny per-kernel working set ~20 MB aggregate on A100 ~19 TB/s bandwidth, HBM (GPU Memory) 40 GB on A100 ~1.5 TB/s bandwidth, and CPU DRAM Much larger Far lower bandwidth to the GPU.
SRAM (On-chip) Tiny per-kernel working set ~20 MB aggregate on A100 ~19 TB/s bandwidth, HBM (GPU Memory) 40 GB on A100 ~1.5 TB/s bandwidth, and CPU DRAM Much larger Far lower bandwidth to the GPU.

The flow above shows the three-tier memory pyramid. When a baseline writes score and probability matrices to HBM, those round-trips can dominate execution. FlashAttention pays for local bookkeeping to avoid them.[1]Reference 1FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness.https://arxiv.org/abs/2205.14135

Materialized attention can become IO-limited

In a materializing baseline, the n×nn \times nn×n score and probability matrices are written to HBM and read again. A fused backend may already avoid those intermediates, so the useful comparison is FlashAttention versus the backend the system would otherwise execute, not versus every call named "attention."

Why is HBM traffic the bottleneck even though GPUs have enormous FLOPs?

Answer

Matrix math can run very fast once data is on-chip, but repeatedly writing and rereading n2n^2n2 score and probability matrices through HBM stalls the compute units. FlashAttention improves arithmetic intensity by reusing tiles in SRAM before writing only final outputs and row statistics.

The FlashAttention algorithm

Core idea: tiling + online softmax

Instead of materializing the full n×nn \times nn×n attention matrix, FlashAttention splits the problem into three simultaneous ideas:

  1. Tiles the computation into blocks that fit in SRAM.
  2. Uses online softmax to compute exact softmax without seeing all values at once.
  3. Never materializes the full attention matrix in HBM.

This block-wise processing strategy avoids quadratic auxiliary score and probability storage. The input, output, and saved row-statistic tensors still scale with sequence length.

The illustration below contrasts the memory access patterns. A materializing baseline writes the full score matrix to HBM, reads it back for softmax, writes the probability matrix, and reads it again for the final multiply. FlashAttention streams small tiles through SRAM and saves only row-wise statistics.

A materializing attention baseline writing score and probability matrices to HBM versus FlashAttention streaming tiles through SRAM and writing only O and row stats.
A materializing baseline writes large intermediates to HBM. FlashAttention keeps tile work in SRAM and writes compact row statistics plus final output.

The two paths split on where attention state lives. On the left, the materializing path makes multiple round-trips to HBM for the n×nn \times nn×n intermediates. On the right, FlashAttention processes small blocks in fast SRAM and writes back the output and compact row statistics.

Why doesn't tiling make FlashAttention approximate?

Answer

Tiling changes the order of computation, not the attention definition. Online softmax rescales previous partial sums whenever a new block changes the row maximum, so the final normalized output matches dense attention up to normal floating-point ordering differences.

How tiling fits in SRAM

The illustration below visualizes the data flow: Q, K, and V blocks stream from large HBM into the small SRAM workspace, where only the current tile and running statistics are kept.

Q, K, and V tiles stream through SRAM; only O and row stats return to HBM.
Tiles fit in SRAM; full score and probability matrices are skipped.

During forward execution, QQQ, KKK, and VVV are read from HBM. The kernel writes final output OOO and, for training, compact row statistics needed by backward. It doesn't write the full score or probability matrices.

Tile size is bounded by on-chip capacity. This simplified payload check counts four FP16 tile-shaped arrays (Q, K, V, and a partial output), while production kernels also budget for statistics, registers, and implementation overhead:

tile-working-set.py
1block_rows, head_dimension, arrays, bytes_per_value = 128, 64, 4, 2 2payload_bytes = block_rows * head_dimension * arrays * bytes_per_value 3 4print(f"simplified tile payload: {payload_bytes / 1024:.0f} KiB") 5print("also budget: row statistics, registers, and kernel overhead")
Output
1simplified tile payload: 64 KiB 2also budget: row statistics, registers, and kernel overhead

Online softmax with a concrete example

To normalize 1,000 attention scores with a scratchpad that fits only 50 at a time, you don't need to hold all 1,000 scores to compute the final normalization. You keep running statistics and update them as each block arrives.

Online softmax works the same way: instead of needing the full n×nn \times nn×n attention matrix to compute softmax, it maintains running statistics (max and sum) and updates them block by block, without introducing any approximation.

A tiny numeric example makes the general formula easier to read.

Standard softmax on three scores

Suppose a query token sees key scores [1.0,  2.0,  0.5][1.0,\; 2.0,\; 0.5][1.0,2.0,0.5].

  1. Find the max: m=2.0m = 2.0m=2.0.
  2. Exponentiate relative to the max: [e−1.0,  e0,  e−1.5]≈[0.368,  1.0,  0.223][e^{-1.0},\; e^{0},\; e^{-1.5}] \approx [0.368,\; 1.0,\; 0.223][e−1.0,e0,e−1.5]≈[0.368,1.0,0.223].
  3. Sum: ℓ=0.368+1.0+0.223=1.591\ell = 0.368 + 1.0 + 0.223 = 1.591ℓ=0.368+1.0+0.223=1.591.
  4. Normalize: [0.231,  0.628,  0.141][0.231,\; 0.628,\; 0.141][0.231,0.628,0.141].

A materializing baseline would store the full 3×33 \times 33×3 score matrix in HBM just to perform that four-step process for every row.

Online softmax with two blocks

Now pretend the SRAM bench only fits two scores at a time. We split the scores into Block A [1.0,  2.0][1.0,\; 2.0][1.0,2.0] and Block B [0.5][0.5][0.5].

Processing Block A:

  • Local max: mA=2.0m_A = 2.0mA​=2.0.
  • Local denominator: ℓA=e−1.0+e0≈1.368\ell_A = e^{-1.0} + e^{0} \approx 1.368ℓA​=e−1.0+e0≈1.368.
  • Local unnormalized numerator: NA=e−1.0⋅V1+e0⋅V2N_A = e^{-1.0} \cdot V_1 + e^{0} \cdot V_2NA​=e−1.0⋅V1​+e0⋅V2​.

Processing Block B:

  • Local max: mB=0.5m_B = 0.5mB​=0.5.
  • New global max: mnew=max⁡(2.0,  0.5)=2.0m_{new} = \max(2.0,\; 0.5) = 2.0mnew​=max(2.0,0.5)=2.0.
  • Rescale the old denominator to the new max: ℓnew=e2.0−2.0⋅1.368+e0.5−2.0=1.368+0.223=1.591\ell_{new} = e^{2.0 - 2.0} \cdot 1.368 + e^{0.5 - 2.0} = 1.368 + 0.223 = 1.591ℓnew​=e2.0−2.0⋅1.368+e0.5−2.0=1.368+0.223=1.591.
  • Update the numerator by rescaling the old accumulator and adding the new block: Nnew=e0⋅NA+e−1.5⋅V3N_{new} = e^{0} \cdot N_A + e^{-1.5} \cdot V_3Nnew​=e0⋅NA​+e−1.5⋅V3​.

After both blocks, the final output for this query row is O=Nnew/1.591O = N_{new} / 1.591O=Nnew​/1.591, which is exactly the same result as standard softmax. We never held all three scores in the fast workspace at once.

This small program checks the rescaling rule with scalar values, independent of any GPU kernel:

online-softmax-two-blocks.py
1import math 2 3scores_a, values_a = [1.0, 2.0], [10.0, 20.0] 4scores_b, values_b = [0.5], [40.0] 5 6def local_state(scores, values): 7 max_score = max(scores) 8 weights = [math.exp(score - max_score) for score in scores] 9 return max_score, sum(weights), sum(weight * value for weight, value in zip(weights, values)) 10 11m_a, l_a, n_a = local_state(scores_a, values_a) 12m_b, l_b, n_b = local_state(scores_b, values_b) 13m = max(m_a, m_b) 14l = math.exp(m_a - m) * l_a + math.exp(m_b - m) * l_b 15n = math.exp(m_a - m) * n_a + math.exp(m_b - m) * n_b 16online = n / l 17 18all_scores = scores_a + scores_b 19all_values = values_a + values_b 20dense_weights = [math.exp(score - max(all_scores)) for score in all_scores] 21dense = sum(w * v for w, v in zip(dense_weights, all_values)) / sum(dense_weights) 22 23print(f"online output: {online:.6f}") 24print(f"dense output: {dense:.6f}") 25print(f"match: {abs(online - dense) < 1e-12}")
Output
1online output: 20.492649 2dense output: 20.492649 3match: True

If Block B had contained a score of 3.03.03.0 instead of 0.50.50.5, what must happen to Block A's previous contributions?

Answer

They must be rescaled by e2.0−3.0e^{2.0 - 3.0}e2.0−3.0 because the global max changed from 2.0 to 3.0. Without that down-weighting, the denominator and numerator would mix values normalized against different maxima.

The general update rule

For each new block of scores snews_{\text{new}}snew​ and value vectors VblockV_{\text{block}}Vblock​:

mnew=max⁡(mold,  max⁡(snew))m_{\text{new}} = \max(m_{\text{old}},\; \max(s_{\text{new}}))mnew​=max(mold​,max(snew​))

ℓnew=emold−mnew⋅ℓold+∑jesj−mnew\ell_{\text{new}} = e^{m_{\text{old}} - m_{\text{new}}} \cdot \ell_{\text{old}} + \sum_j e^{s_j - m_{\text{new}}}ℓnew​=emold​−mnew​⋅ℓold​+∑j​esj​−mnew​

Nnew=emold−mnew⋅Nold+∑jesj−mnewVjN_{\text{new}} = e^{m_{\text{old}} - m_{\text{new}}} \cdot N_{\text{old}} + \sum_j e^{s_j - m_{\text{new}}} V_jNnew​=emold​−mnew​⋅Nold​+∑j​esj​−mnew​Vj​

Onew=NnewℓnewO_{\text{new}} = \frac{N_{\text{new}}}{\ell_{\text{new}}}Onew​=ℓnew​Nnew​​

Where mmm is the running max score (for numerical stability), ℓ\ellℓ is the running softmax denominator, NNN is the running unnormalized numerator accumulator, and OOO is the normalized output.

The rescaling terms emold−mnewe^{m_{\text{old}} - m_{\text{new}}}emold​−mnew​ keep previous results correct even though the max changed. This is the mathematical trick that eliminates the need for a second pass over the full row.[2]Reference 2Online normalizer calculation for softmax.https://arxiv.org/abs/1805.02867

A materializing baseline makes multiple round-trips to HBM for the full n×nn \times nn×n matrix. FlashAttention performs tile-local score and softmax work on-chip, writing output and compact saved statistics to HBM.

Pseudocode

This function captures the core logic of FlashAttention. It takes the Query, Key, and Value matrices along with a specified block size to load into SRAM. It returns the same dense attention output as a materialized reference implementation, but iterates through tiles to avoid full score and probability matrices.

pseudocode.py
1import torch 2import math 3 4def flash_attention( 5 Q: torch.Tensor, 6 K: torch.Tensor, 7 V: torch.Tensor, 8 block_size: int = 256 9) -> torch.Tensor: 10 """ 11 Simplified forward-pass sketch of FlashAttention. 12 13 Args: 14 Q: Query tensor of shape (n, d) 15 K: Key tensor of shape (n, d) 16 V: Value tensor of shape (n, d) 17 block_size: Size of blocks to load into SRAM 18 19 Returns: 20 O: Output tensor of shape (n, d) 21 """ 22 n, d = Q.shape 23 O = torch.zeros_like(Q) 24 25 # Outer loop: iterate over Q blocks and keep the current output tile on-chip 26 for i in range(0, n, block_size): 27 Qi = Q[i:i+block_size] # Load Q block to SRAM 28 29 # Initialize running statistics for this Q block 30 Oi = torch.zeros_like(Qi) # Accumulator 31 li = torch.zeros(Qi.shape[0], 1, device=Q.device) # Denominator 32 mi = torch.full((Qi.shape[0], 1), -float('inf'), device=Q.device) # Max 33 34 # Inner loop: Iterate over K, V blocks 35 for j in range(0, n, block_size): 36 Kj = K[j:j+block_size] # Load K block to SRAM 37 Vj = V[j:j+block_size] # Load V block to SRAM 38 39 # Compute local attention scores (in SRAM!) 40 # Shape: (block_size_q, block_size_k) 41 Sij = Qi @ Kj.T / math.sqrt(d) 42 43 # Online softmax update logic 44 m_ij = Sij.max(dim=-1, keepdim=True).values 45 m_new = torch.max(mi, m_ij) 46 47 exp_old_scale = torch.exp(mi - m_new) 48 exp_new = torch.exp(Sij - m_new) 49 50 # Update output accumulator (unnormalized) 51 Oi = exp_old_scale * Oi + exp_new @ Vj 52 53 # Update running statistics 54 li = exp_old_scale * li + exp_new.sum(dim=-1, keepdim=True) 55 mi = m_new 56 57 # Normalize by the final denominator 58 O[i:i+block_size] = Oi / li 59 60 return O 61 62# Quick sanity check: FlashAttention should match reference attention on a small tensor 63if __name__ == "__main__": 64 torch.manual_seed(0) 65 n, d = 64, 32 66 Q = torch.randn(n, d) 67 K = torch.randn(n, d) 68 V = torch.randn(n, d) 69 70 # Reference attention (materializes full n x n matrix) 71 S = Q @ K.T / math.sqrt(d) 72 P = torch.softmax(S, dim=-1) 73 expected = P @ V 74 75 # FlashAttention sketch (tiling, no full score/probability materialization) 76 got = flash_attention(Q, K, V, block_size=16) 77 78 max_difference = (expected - got).abs().max().item() 79 print("Matches dense attention within 1e-5:", max_difference < 1e-5)
Output
1Matches dense attention within 1e-5: True

This sketch keeps Oi as an unnormalized numerator accumulator and divides by li once per Q tile at the end. The small test at the bottom proves the key claim: for a 64×3264 \times 3264×32 toy tensor, the tiled loop produces the same result as standard dense attention, with a maximum difference below 10−510^{-5}10−5.

Common mistake: Beginners sometimes think Oi is already normalized inside the inner loop. It isn't. The division by li happens only after every K/V block for that Q tile has been processed. If you normalize early, you lose the exact rescaling that makes online softmax correct.

Production kernels implement the same algebra much more aggressively, while also handling batching, multiple heads, masks, and dropout.

In the pseudocode, why does Oi stay unnormalized until all K/V blocks for a Q tile are processed?

Answer

The final denominator li is not known until every K/V block has contributed to the row. Normalizing early would lock in the wrong scale before later blocks can change the running max and denominator.

The backward pass: recomputation can win

A materializing training implementation can save the massive n×nn \times nn×n attention matrices SSS and PPP from the forward pass to compute gradients during the backward pass. Those saved intermediates can become a major source of out-of-memory (OOM) errors.

FlashAttention solves this by recomputing the needed score and probability tiles during the backward pass instead of storing them all from the forward pass.[1]Reference 1FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness.https://arxiv.org/abs/2205.14135 Because it saves compact row-wise softmax statistics instead, the saved attention state grows as O(n)O(n)O(n) rather than O(n2)O(n^2)O(n2).

In a naive attention kernel, you can write every score and probability to a giant intermediate matrix, then read it back later. That's the materializing baseline. Or you can keep compact running statistics, recompute local blocks when needed, and avoid storing the full matrix. That's FlashAttention.

Recomputation isn't free. It adds arithmetic in backward. The point of the FlashAttention paper is that, on the evaluated GPU workloads, avoiding much larger HBM reads and writes more than paid for that arithmetic cost.[1]Reference 1FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness.https://arxiv.org/abs/2205.14135 Measure the trade-off on the model shape and hardware you deploy.

This calculator makes the saved-state difference concrete. It compares one FP16 score matrix with the FP32 softmax_lse tensor saved by the current flash-attention interface, not every tensor in training:

saved-state-comparison.py
1batch, heads, sequence = 8, 32, 8192 2materialized_scores = batch * heads * sequence * sequence * 2 3softmax_lse = batch * heads * sequence * 4 4 5print(f"one saved score matrix: {materialized_scores / 1024**3:.2f} GiB") 6print(f"FP32 softmax LSE: {softmax_lse / 1024**2:.2f} MiB") 7print(f"size ratio: {materialized_scores / softmax_lse:,.0f}x")
Output
1one saved score matrix: 32.00 GiB 2FP32 softmax LSE: 8.00 MiB 3size ratio: 4,096x
PropertyMaterializing baselineFlashAttention
Saved attention state for backwardStore SSS and PPP explicitly: O(n2)O(n^2)O(n2)Store compact row-wise softmax statistics: O(n)O(n)O(n)
Backward strategyRead large intermediates from HBMRecompute local tiles from Q,K,VQ, K, VQ,K,V plus saved stats
Trade-offLess recomputation, much higher memoryMore recomputation, much lower memory

Why can recomputing attention tiles during backward be faster than storing them during forward?

Answer

On modern GPUs, rereading huge saved matrices from HBM can cost more wall-clock time than recalculating small tiles from QQQ, KKK, and VVV. FlashAttention saves row-wise softmax statistics, then spends cheap compute to avoid expensive memory movement.

Complexity analysis

FlashAttention changes the IO complexity by tiling the computation. Let MMM denote the amount of fast SRAM available to hold a tile's working set.

PropertyMaterializing baselineFlashAttention
Auxiliary attention memoryMaterialize SSS and PPP: O(n2)O(n^2)O(n2)Keep row stats and the current output tile: O(n)O(n)O(n)
FLOPsO(n2d)O(n^2 d)O(n2d)O(n2d)O(n^2 d)O(n2d) (same)
HBM reads/writesO(nd+n2)O(nd + n^2)O(nd+n2)O(n2d2/M)O(n^2 d^2 / M)O(n2d2/M)
ExactYesYes
Reported wall-clock speedBaselineUp to 2-4x faster in evaluated paper workloads[1]Reference 1FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness.https://arxiv.org/abs/2205.14135
FlashAttention memory comparison for batch 8 with 32 heads: materialized FP16 scores grow quadratically from 0.5 GiB at 1K to 128 GiB at 16K while FP32 LSE row state grows linearly from 1 MiB to 16 MiB.
For batch 8 with 32 heads, materialized FP16 scores grow quadratically: 0.5 GiB at 1K, 8 GiB at 4K, and 128 GiB at 16K. FP32 softmax LSE state grows linearly from 1 MiB to 16 MiB, so FlashAttention keeps compact row state and recomputes tiles instead of saving giant score matrices.

FLOPs stands for floating-point operations. The memory row here refers to the extra state created by the attention kernel itself, not the shared QQQ, KKK, VVV, and OOO tensors that both approaches still need to hold. FlashAttention doesn't reduce the asymptotic mathematical work required for dense attention: both paths perform O(n2d)O(n^2 d)O(n2d) operations. Its main algorithmic advantage is avoiding n2n^2n2 score and probability transfers to and from slow HBM. Tile size, scheduling, datatype, and hardware still affect observed speed.

Under the paper's SRAM model, tiling changes HBM reads and writes from O(nd+n2)O(nd + n^2)O(nd+n2) for the materializing baseline to O(n2d2/M)O(n^2 d^2 / M)O(n2d2/M).[1]Reference 1FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness.https://arxiv.org/abs/2205.14135 A larger usable SRAM budget MMM lets each tile reuse more data before returning to HBM. This doesn't make dense attention linear-time: both paths still perform O(n2d)O(n^2 d)O(n2d) arithmetic.

Because FlashAttention computes the same dense attention operator as the reference formula (thanks to the online softmax trick), it doesn't change the model's mathematical attention rule. Different floating-point operation order can still cause small numeric differences.

These numbers distinguish score-matrix scaling from row-statistic scaling:

auxiliary-state-scaling.py
1base_sequence = 1024 2for sequence in [1024, 2048, 4096, 8192, 16384]: 3 materialized_relative = (sequence / base_sequence) ** 2 4 row_stats_relative = sequence / base_sequence 5 print( 6 f"{sequence:>5} tokens: materialized={materialized_relative:>5.0f}x, " 7 f"row-stats={row_stats_relative:>2.0f}x" 8 )
Output
11024 tokens: materialized= 1x, row-stats= 1x 2 2048 tokens: materialized= 4x, row-stats= 2x 3 4096 tokens: materialized= 16x, row-stats= 4x 4 8192 tokens: materialized= 64x, row-stats= 8x 516384 tokens: materialized= 256x, row-stats=16x

Which complexity changes with FlashAttention: FLOPs, auxiliary attention memory, or model quality?

Answer

Auxiliary attention memory drops from O(n2)O(n^2)O(n2) saved score/probability matrices to O(n)O(n)O(n) row statistics, and HBM IO drops sharply. FLOPs remain O(n2d)O(n^2d)O(n2d), and model quality doesn't change because the dense attention result is still exact.

Causal masking in FlashAttention

For autoregressive transformers, attention is causal: token iii can only attend to tokens j≤ij \leq ij≤i. FlashAttention handles this efficiently without materializing a dense causal mask in HBM.

Block-level skipping

If a block of K/V tokens is entirely in the "future" relative to a Q block, the entire block multiplication is skipped. No compute wasted.

Within-block masking

For blocks that straddle the causal boundary, FlashAttention applies the mask after computing scores but before the softmax update. The masked positions are set to −∞-\infty−∞.

A four-by-four causal FlashAttention tile schedule colors past blocks green, diagonal boundary blocks purple, and future blocks red, with a zoom showing the triangular mask inside one diagonal tile.
The 4×4 schedule matches the runnable audit below: six past tiles run fully, four diagonal tiles apply an in-tile triangular mask, and six future tiles are skipped before matrix multiplication.

Causal masking is folded into the tile schedule itself: future tiles are skipped, and diagonal tiles apply an in-tile mask before the online softmax update. That reduces wasted work, but the exact speedup depends on sequence length, tile shape, and kernel implementation rather than being a guaranteed 2x.

The same tiled structure also adapts well to local windowed attention. Tiles that fall completely outside the attention window can be skipped before doing the matrix multiply.

You can audit causal tile decisions without any GPU code:

causal-tile-schedule.py
1tiles = 4 2counts = {"past": 0, "boundary": 0, "future": 0} 3 4for query_tile in range(tiles): 5 for key_tile in range(tiles): 6 if key_tile < query_tile: 7 decision = "past" 8 elif key_tile == query_tile: 9 decision = "boundary" 10 else: 11 decision = "future" 12 counts[decision] += 1 13 14print(counts) 15print(f"computed tiles: {counts['past'] + counts['boundary']} of {tiles * tiles}")
Output
1{'past': 6, 'boundary': 4, 'future': 6} 2computed tiles: 10 of 16

How does FlashAttention avoid materializing a full causal mask?

Answer

The tile scheduler skips K/V blocks that are entirely in the future and applies an in-tile mask only for blocks that cross the causal boundary. The mask becomes part of the tiled kernel schedule instead of a dense matrix stored in HBM.

FlashAttention-2, FlashAttention-3, and FlashAttention-4

Since the original algorithm, kernel design has evolved to use newer GPU features more effectively.

FlashAttention-2[3]Reference 3FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning.https://arxiv.org/abs/2307.08691 improves on the original by optimizing the hardware execution:

  • Better work partitioning across GPU thread blocks and warp-level parallelism (coordinating groups of GPU threads) to reduce synchronization overhead and increase occupancy.
  • Reduces non-matmul FLOPs (like causal masking and softmax operations).
  • Achieves about 2× faster speeds than FlashAttention-1, reaching 50-73% of theoretical max FLOPs/s.[3]Reference 3FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning.https://arxiv.org/abs/2307.08691

FlashAttention-3[4]Reference 4FlashAttention-3: Fast and Accurate Attention with Asynchrony and Low-precision.https://arxiv.org/abs/2407.08608 targets Hopper architecture capabilities such as H100 Tensor Memory Accelerator and WGMMA execution:

  • Exploits asynchronous execution using the TMA (Tensor Memory Accelerator) and WGMMA (Warpgroup Matrix-Matrix Multiply Accumulate) instructions, so data loading can overlap with matrix multiplication.
  • Adds an FP8 (8-bit floating point) forward path for further computational speedup.
  • Achieves about 75% of theoretical FLOPs/s on H100 GPUs.

FlashAttention-4[5]Reference 5FlashAttention-4: Algorithm and Kernel Pipelining Co-Design for Asymmetric Hardware Scalinghttps://arxiv.org/abs/2603.05451 extends the same exact-attention family with CuTe-DSL kernels co-designed for Blackwell B200 and GB200 GPUs. Its March 2026 paper reports up to 1,613 TFLOPs/s in BF16 on B200, or 71% of theoretical peak, with up to 1.3× speedup over cuDNN 9.13 and 2.7× over the paper's Triton baseline. Those are kernel benchmarks, not guaranteed application-level speedups.

The tiled online-softmax core stays the same across versions. Performance still depends on hardware-aware kernel design. Writing peak-tuned kernels often requires low-level CUDA control. Higher-level systems such as Triton and CuTeDSL trade some low-level control for more productive kernel development. Most application teams should start with a framework API, verify backend selection, and profile before writing a custom kernel.

What changed after FlashAttention-1?

Answer

The core exact tiled attention idea stayed the same. FlashAttention-2 improved work partitioning and reduced non-matmul overhead. FlashAttention-3 targeted Hopper features such as TMA, WGMMA, asynchronous overlap, and an FP8 forward path. FlashAttention-4 retuned the pipeline for Blackwell and implemented it in CuTe-DSL.

Measured performance

Training throughput

During model training, saving quadratic attention intermediates can sharply restrict the maximum sequence length a model can process. As sequence length increases, a materializing baseline may run out of memory even when a fused attention path can still fit.

Common mistake: Assuming FlashAttention is only a "long-sequence hack." The original paper reports a 15% end-to-end training speedup for BERT-large at sequence length 512, so even a moderate evaluated sequence can benefit when attention IO matters.[1]Reference 1FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness.https://arxiv.org/abs/2205.14135

Compared with a baseline that saves full attention intermediates, FlashAttention's auxiliary attention memory grows linearly rather than quadratically. That can enable longer sequences and improve throughput even while a materializing baseline still fits in memory.[1]Reference 1FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness.https://arxiv.org/abs/2205.14135[3]Reference 3FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning.https://arxiv.org/abs/2307.08691

SourceWorkloadReported result
FlashAttention (2022)BERT-large, sequence length 51215% end-to-end training speedup[1]Reference 1FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness.https://arxiv.org/abs/2205.14135
FlashAttention (2022)GPT-2, sequence length 1K3× speedup[1]Reference 1FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness.https://arxiv.org/abs/2205.14135
FlashAttention (2022)Long Range Arena, sequence length 1K-4K2.4× speedup[1]Reference 1FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness.https://arxiv.org/abs/2205.14135
FlashAttention-2 (2023)GPT-style training on A100Up to 225 TFLOPs/s per GPU, 72% model FLOPs utilization[3]Reference 3FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning.https://arxiv.org/abs/2307.08691

The benchmark number matters less than the memory path. Once the attention kernel stops writing giant intermediates to HBM, longer-sequence training becomes much more practical.

Inference impact

During inference, FlashAttention helps most when the workload still looks like dense attention over many prompt tokens:

  • Prefill phase benefits the most, because the model still performs full prompt self-attention. That prompt processing contributes directly to time to first token (TTFT). A long incident packet requires many token-pair comparisons before the first generated token, so reducing attention memory traffic matters immediately.
  • Decode phase benefits less from dense full-sequence FlashAttention alone, because each step introduces one new query token and reuses the KV cache (Key-Value cache), so weight streaming and other bottlenecks often dominate.
  • Long prompts benefit more than short prompts, because avoiding a materialized n×nn \times nn×n score matrix matters more as nnn grows.

FlashAttention has its biggest impact when attention itself is the bottleneck. That's usually training and prefill, not naive single-token decode over a contiguous cache.

Production decode uses a related but distinct kernel contract

Serving engines rarely keep one contiguous KV tensor per request. PagedAttention stores K/V in blocks and maps logical positions through a block table. Production decode therefore needs kernels that apply FlashAttention-style tiling and online softmax over non-contiguous KV blocks, beyond the training and prefill dense path.

Examples of that contract (names and APIs evolve):

  • flash_attn_with_kvcache and related FlashAttention decode entry points that accept a packed or paged cache layout
  • FlashInfer and engine-specific paged attention kernels used by stacks such as vLLM

Those kernels are complementary to the PagedAttention allocator: paging decides where blocks live and how they are shared; the decode kernel decides how Q attends to those blocks without materializing full scores. GQA/MQA further shrinks bytes per token that those kernels read. Measure prefill TTFT and decode ITL on your engine; don't assume "FlashAttention = prefill only" or that a training FA kernel is a drop-in for multi-tenant decode.

This shape check shows why prefill creates far more score work per request than one decode step:

prefill-versus-decode.py
1prompt_tokens = 8192 2prefill_scores = prompt_tokens * prompt_tokens 3decode_scores = 1 * prompt_tokens 4 5print(f"prefill scores: {prefill_scores:,}") 6print(f"one decode step scores: {decode_scores:,}") 7print(f"ratio: {prefill_scores // decode_scores:,}x")
Output
1prefill scores: 67,108,864 2one decode step scores: 8,192 3ratio: 8,192x

Hardware compatibility

The algorithmic idea is general, but the fastest kernels are hardware-specific. FlashAttention-2 describes better parallelism and work partitioning for modern GPUs. FlashAttention-3 is a Hopper-focused redesign that targets features such as TMA, WGMMA, and an FP8 forward path.[3]Reference 3FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning.https://arxiv.org/abs/2307.08691[4]Reference 4FlashAttention-3: Fast and Accurate Attention with Asynchrony and Low-precision.https://arxiv.org/abs/2407.08608 FlashAttention-4[5]Reference 5FlashAttention-4: Algorithm and Kernel Pipelining Co-Design for Asymmetric Hardware Scalinghttps://arxiv.org/abs/2603.05451 retunes the pipeline for Blackwell B200 and GB200 GPUs and implements it in CuTe-DSL.

Availability in an application depends on its framework build, device, datatype, tensor shapes, and attention features. Treat backend selection as something to verify, not something to infer from the model name.

When should you expect FlashAttention to help most during inference?

Answer

Expect the biggest inference gain during prefill, especially for long prompts, because the model computes dense attention over the full prompt. Decode often benefits less because each step has one new query token and the KV cache or memory bandwidth elsewhere may dominate.

Using FlashAttention in practice

In modern deep learning frameworks, you rarely implement FlashAttention from scratch. PyTorch exposes torch.nn.functional.scaled_dot_product_attention (SDPA), which may choose an optimized CUDA implementation when the inputs and build support it. Its sdpa_kernel context manager lets you select permitted implementations while testing or profiling. Eligibility and fallback behavior depend on the installed PyTorch build, device, datatype, layout, and attention features, so consult the documentation for that build and measure the actual path.[6]Reference 6torch.nn.functional.scaled_dot_product_attentionhttps://docs.pytorch.org/docs/stable/generated/torch.nn.functional.scaled_dot_product_attention.html

First validate operator behavior with a small CPU example. This checks causality and output shape, not FlashAttention dispatch:

sdpa-operator-check.py
1import torch 2import torch.nn.functional as F 3 4torch.manual_seed(0) 5query = torch.randn(1, 2, 4, 8) 6key = torch.randn(1, 2, 4, 8) 7value = torch.randn(1, 2, 4, 8) 8 9output = F.scaled_dot_product_attention( 10 query, key, value, 11 is_causal=True, 12 dropout_p=0.0, 13) 14print(f"output shape: {tuple(output.shape)}") 15print(f"finite values: {torch.isfinite(output).all().item()}")
Output
1output shape: (1, 2, 4, 8) 2finite values: True

On CUDA hardware, backend restriction is useful as a test probe. This snippet is intentionally not marked runnable because it requires a suitable installed CUDA build and GPU:

request-cuda-flash-backend.py
1import torch 2import torch.nn.functional as F 3from torch.nn.attention import SDPBackend, sdpa_kernel 4 5Q = torch.randn(2, 16, 1024, 64, device="cuda", dtype=torch.float16) 6K = torch.randn(2, 16, 1024, 64, device="cuda", dtype=torch.float16) 7V = torch.randn(2, 16, 1024, 64, device="cuda", dtype=torch.float16) 8 9with sdpa_kernel(backends=[SDPBackend.FLASH_ATTENTION]): 10 output = F.scaled_dot_product_attention(Q, K, V, is_causal=True, dropout_p=0.0)

If a model library offers a FlashAttention request flag, requesting it isn't evidence that the fast path ran. Record a before/after measurement and backend evidence:

backend-verification-record.py
1verification = { 2 "requested_backend": "flash_attention", 3 "operator_correctness_checked": True, 4 "profiler_shows_selected_kernel": False, 5 "latency_measured": False, 6} 7 8active = ( 9 verification["operator_correctness_checked"] 10 and verification["profiler_shows_selected_kernel"] 11 and verification["latency_measured"] 12) 13print(f"enough evidence to claim speedup: {active}") 14print("next check: capture backend/profiler output on target GPU")
Output
1enough evidence to claim speedup: False 2next check: capture backend/profiler output on target GPU

For example, Hugging Face Transformers exposes a model-load request in versions and models that support the corresponding integration. Choose a model that documents support for the requested backend:

using-flashattention-in-practice-2.py
1import torch 2from transformers import AutoModelForCausalLM 3 4model = AutoModelForCausalLM.from_pretrained( 5 "your-org/your-supported-causal-lm", 6 torch_dtype=torch.float16, 7 attn_implementation="flash_attention_2", 8 device_map="auto", 9)

If you set attn_implementation="flash_attention_2", what should you verify before assuming the speedup is active?

Answer

Check that your GPU, datatype, head dimension, mask pattern, framework version, and installed kernel package support the requested backend. Then profile or inspect backend diagnostics on that exact build before reporting a speedup.

Common mistakes

"FlashAttention is an approximation"

  • Symptom: You hear FlashAttention grouped with sparse or low-rank attention approximations and assume it drops some connections to save memory.

  • Cause: The word "efficient" often implies approximation in other contexts.

  • Fix: FlashAttention is exact. Thanks to the online softmax trick, it computes the same dense attention formula without using sparse or low-rank shortcuts. Numeric outputs can differ slightly from a reference implementation because floating-point operations are associated in a different order, but the mathematical operator is the same. If you need proof, run the small PyTorch test from the pseudocode section and check that the max difference is near zero.

"FlashAttention reduces the number of compute operations"

  • Symptom: You claim in an interview or code review that FlashAttention cuts FLOPs.

  • Cause: It's natural to equate "faster" with "fewer operations."

  • Fix: The forward attention computation still has O(n2d)O(n^2 d)O(n2d) floating-point operations (FLOPs). The speedup comes from reduced memory operations (IO), not from changing dense attention into a cheaper mathematical operator. In training, the backward pass can perform more operations because it recomputes tiles. The win is that compute is cheap and memory movement is expensive.

"GPU memory is one big pool"

  • Symptom: You only compare total VRAM capacity and miss why attention still runs slowly on large GPUs.

  • Cause: HBM, on-chip SRAM, shared memory, and registers have very different capacity and bandwidth profiles.

  • Fix: Ask where each tensor lives and how often it crosses the HBM/SRAM boundary. FlashAttention wins because it keeps Q/K/V tiles and softmax state on-chip long enough to reuse them, then writes only the final output and row statistics back to HBM.

"IO complexity is the same as time complexity"

  • Symptom: You explain FlashAttention as if it changes attention from quadratic time to linear time.

  • Cause: The memory table and the FLOP table get mixed together.

  • Fix: Keep the dimensions separate. Dense attention still does quadratic compute in sequence length. FlashAttention reduces HBM reads and writes, so wall-clock time improves when the workload is memory-bound.

"Online softmax is optional bookkeeping"

  • Symptom: You tile attention but normalize each block independently.

  • Cause: The running max and denominator updates look like an implementation detail.

  • Fix: Online softmax is the correctness mechanism. The running max rescales old contributions when a later tile contains a larger score, and the running denominator keeps all blocks normalized against the same global row.

"FlashAttention is only useful for long sequences"

  • Symptom: You skip enabling it on short-context models.

  • Cause: The OOM headlines make FlashAttention look like a long-sequence-only tool.

  • Fix: FlashAttention supports long sequences by avoiding memory limits, and it can still speed up shorter sequences because it reduces HBM access. Dao et al. reported a 15% BERT-large speedup at 512 tokens.[1]Reference 1FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness.https://arxiv.org/abs/2205.14135

"You have to write custom CUDA kernels to use it"

  • Symptom: You avoid FlashAttention because you assume it requires low-level GPU programming.

  • Cause: The original paper describes kernel-level details, which can give the impression that users must write CUDA.

  • Fix: Use a framework SDPA API or supported model integration, then check backend selection and measure on the target GPU. A request flag is configuration, not proof that an optimized kernel ran.

Complete the lesson

Mastery Check

Answer every question, then check your score. Score 75% or higher to mark this lesson complete.

1.A materializing attention implementation stores one FP16 score matrix for batch=8, heads=32, and seq_len=8192. Why can that intermediate alone use about 34 GB before model weights are counted?

Correct answer: Each head stores every query-key score, so storage grows with seq_len^2. With batch 8, 32 heads, 8192 by 8192 scores, and 2 bytes per FP16 score, the score matrix alone is about 34 GB, or 32 GiB.

The stored score tensor has shape batch x heads x seq_len x seq_len. Here that is 8 * 32 * 8192 * 8192 = 17,179,869,184 FP16 scores. At 2 bytes each, the matrix is 34,359,738,368 bytes, which is 32 GiB or about 34 GB. KV tensors scale with seq_len * d, and the attention score is one scalar per query-key pair, not one scalar per feature.

2.A teammate says, "FlashAttention is faster because it reduces dense attention from quadratic compute to linear compute." Which correction is technically accurate?

Correct answer: It keeps dense-attention FLOPs at O(n2d)O(n^2 d)O(n2d), but reduces HBM traffic and auxiliary attention memory by tiling into SRAM and avoiding materialized score and probability matrices.

FlashAttention still computes dense attention, so the all-pairs query-key dot products remain quadratic in sequence length. The win is not fewer mathematical comparisons. The win is that score and probability tiles are used while on-chip, then discarded, so the kernel avoids large HBM round-trips and reduces auxiliary attention state from quadratic matrices to row-wise statistics and current tiles.

3.Under the tiled SRAM model, M is the usable fast memory for a tile working set. Which statement correctly separates FlashAttention's IO complexity from its compute complexity?

Correct answer: It still performs O(n^2 d) FLOPs, while tiled HBM reads/writes are O(n^2 d^2 / M); larger M lets each tile reuse more data before returning to HBM.

M controls how much tile state can be kept and reused in fast memory. FlashAttention still evaluates dense all-pairs attention, so the arithmetic remains O(n^2 d). Under the paper's SRAM model, tiling reduces HBM reads and writes from the materializing O(n d + n^2) pattern to O(n^2 d^2 / M); larger usable SRAM improves reuse rather than removing the quadratic computation.

4.In online softmax, Block A has maximum score 2.0 and its saved denominator and numerator are expressed relative to that max. The next block has maximum score 3.0. What must happen to Block A's previous contributions before combining the blocks?

Correct answer: They must be rescaled by e2.0−3.0e^{2.0 - 3.0}e2.0−3.0 because the global max changed from 2.0 to 3.0. Without that down-weighting, the denominator and numerator would mix values normalized against different maxima.

Softmax terms are represented as exp(score - global_max). Block A's stored terms were relative to 2.0, but after seeing Block B the global max is 3.0. Converting old terms from exp(score - 2.0) to exp(score - 3.0) requires multiplying by exp(2.0 - 3.0). The same scale factor must apply to both the denominator and the numerator accumulator.

5.A simplified tile payload holds four FP16 arrays, each shaped 128 x 64. Ignoring statistics, registers, and overhead, how large is the payload and what sizing conclusion follows?

Correct answer: 64 KiB; a real kernel needs additional capacity for row statistics, registers, and overhead.

The simplified payload is 128 * 64 * 4 * 2 = 65,536 bytes, or 64 KiB. That is not the complete kernel working set. Production tile selection must also leave capacity for running softmax statistics, registers, synchronization needs, and implementation overhead.

6.During training, a materializing attention implementation can save the score matrix S and probability matrix P for backward. Why can FlashAttention recompute attention tiles during backward and still run faster or fit longer contexts?

Correct answer: On modern GPUs, rereading huge saved matrices from HBM can cost more wall-clock time than recalculating small tiles from QQQ, KKK, and VVV. FlashAttention saves row-wise softmax statistics, then spends cheap compute to avoid expensive memory movement.

The backward pass still needs local score and probability information to compute gradients, but FlashAttention does not have to store those full nnn by nnn matrices from forward. It saves compact row-wise softmax statistics, then reconstructs the needed tiles from Q, K, and V during backward. This can add arithmetic, but it avoids much larger HBM reads and writes and reduces saved attention state from O(n2)O(n^2)O(n2) to O(n)O(n)O(n).

7.Causal attention allows query token i to attend only to key token j <= i. In a causal FlashAttention kernel, a query tile covers tokens 128-255 and key/value tiles use the same 128-token width. How should the scheduler treat key/value tiles 0-127, 128-255, and 256-383?

Correct answer: Run 0-127 fully, apply an in-tile mask to 128-255, and skip 256-383 entirely.

For query tokens 128-255, all keys 0-127 are in the past, so that tile can run normally. Tile 128-255 crosses the causal diagonal, so some positions are valid and some must be set to -infinity before the softmax update. Tile 256-383 is entirely in the future, so FlashAttention can skip it instead of computing and masking it later.

8.For an 8192-token prompt, prefill uses every prompt token as a query, while one decode step uses one new query against 8192 cached keys. Which comparison is correct?

Correct answer: Prefill computes 67,108,864 scores; decode computes 8,192, so long-prompt prefill usually benefits more.

Prefill has 8192 query rows and 8192 key columns, giving 8192 * 8192 = 67,108,864 scores. A single decode step has one new query row against 8192 cached keys, giving 1 * 8192 scores. FlashAttention therefore tends to have greater inference impact on dense long-prompt prefill, although actual gains still depend on the active bottleneck.

9.An engineer sets attn_implementation="flash_attention_2" when loading a model and reports a speedup before profiling. What evidence is needed before claiming the optimized backend is active?

Correct answer: Verify support for the GPU, dtype, shapes, masks, and build, then inspect backend or profiler output and measure latency on the target system.

A request flag is configuration, not proof of dispatch. Fast-path eligibility depends on the actual framework build, GPU, datatype, layout, shapes, masks, and kernel package. A CPU correctness or shape check can validate the operator API, but claiming FlashAttention is active requires backend evidence and latency measurement on the target CUDA system.

10.Three kernel teams are choosing a FlashAttention generation. Team A wants better work partitioning and lower non-matmul overhead. Team B targets Hopper TMA/WGMMA plus an FP8 forward path. Team C wants CuTe-DSL kernels co-designed for Blackwell. Which mapping fits?

Correct answer: Team A: FlashAttention-2; Team B: FlashAttention-3; Team C: FlashAttention-4.

FlashAttention-2 improves GPU work partitioning, warp-level parallelism, and non-matmul overhead. FlashAttention-3 is the Hopper-focused redesign using features such as TMA, WGMMA, asynchronous overlap, and an FP8 forward path. FlashAttention-4 retunes the pipeline for Blackwell and implements it in CuTe-DSL. All retain the tiled online-softmax foundation.

10 questions remaining.

Next Step
Continue to Continuous Batching & Scheduling

FlashAttention makes one dense attention kernel more efficient. Next, move up to the serving scheduler: how request slots, chunked prefills, and decode work share each forward pass while protecting TTFT and <span data-glossary="itl">inter-token latency</span>.

PreviousPrefix Caching and Prompt Caching
Share this article
XFacebookLinkedInBlueskyRedditHacker NewsEmail
References

FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness.

Dao, T., Fu, D. Y., Ermon, S., Rudra, A., & Ré, C. · 2022 · NeurIPS 2022

https://arxiv.org/abs/2205.14135

Online normalizer calculation for softmax.

Milakov, M. & Gimelshein, N. · 2018

https://arxiv.org/abs/1805.02867

FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning.

Dao, T. · 2023 · ICLR 2024

https://arxiv.org/abs/2307.08691

FlashAttention-3: Fast and Accurate Attention with Asynchrony and Low-precision.

Shah, J., Bikshandi, G., Zhang, Y., Thakkar, V., Ramani, P., & Dao, T. · 2024

https://arxiv.org/abs/2407.08608

FlashAttention-4: Algorithm and Kernel Pipelining Co-Design for Asymmetric Hardware Scaling

Ted Zadouri, Markus Hoehnerbach, Jay Shah, Timmy Liu, Vijay Thakkar, Tri Dao · 2026 · arXiv

https://arxiv.org/abs/2603.05451

torch.nn.functional.scaled_dot_product_attention

PyTorch Contributors · 2026

https://docs.pytorch.org/docs/stable/generated/torch.nn.functional.scaled_dot_product_attention.html

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.