Large Language Models & Generative AI 11 Aug 2026

Breaking the Memory Wall: A Deep Dive into FlashAttention

#Transformers #Self-Attention #GPU Memory Optimization #IO-Awareness #Tiling #Deep Learning Efficiency #SRAM #High Bandwidth Memory

Breaking the Memory Wall: A Deep Dive into FlashAttention

In the era of Large Language Models (LLMs), the "Attention" mechanism is the engine driving the revolution. However, this engine has a notorious flaw: quadratic complexity. As sequence lengths grow, the memory requirements for the attention matrix explode, leading to the dreaded OutOfMemory (OOM) error.

Enter FlashAttention. Unlike previous attempts to approximate attention (like sparse or linear attention), FlashAttention is an exact attention algorithm. It doesn't change the math; it changes how the hardware handles the data.

In this post, we will dissect the IO-aware design of FlashAttention, explore the "Incremental Softmax" trick, and implement a simulation in PyTorch to see the logic in action.


The Problem: The Memory Wall

To understand FlashAttention, we must first understand why standard attention is slow. In a GPU, there are two primary types of memory:

  1. HBM (High Bandwidth Memory): Large capacity (e.g., 80GB on an A100) but relatively slow.
  2. SRAM: Tiny capacity (a few MBs) but incredibly fast.

Standard attention computes the $N \times N$ attention matrix $P = \text{softmax}(\frac{QK^T}{\sqrt{d}})$. For a sequence length of 10k, this matrix contains 100 million elements. The GPU is forced to write this massive matrix to HBM and read it back multiple times during the softmax and weighted sum steps.

The bottleneck isn't the computation (FLOPs); it's the IO (reading/writing to HBM).

Complexity Comparison

Metric Standard Attention FlashAttention
IO Complexity $\Omega(Nd + N^2)$ $O(N^2 d^2 M^{-1})$
Memory Footprint $O(N^2)$ $O(Nd)$

Where $N$ is sequence length, $d$ is head dimension, and $M$ is SRAM size.


The Solution: IO-Awareness & Tiling

FlashAttention solves the memory bottleneck using two primary techniques: Tiling and Recomputation.

1. Tiling

Instead of computing the entire $N \times N$ matrix at once, FlashAttention breaks the $Q, K,$ and $V$ matrices into smaller blocks. These blocks are small enough to fit entirely within the fast SRAM. The algorithm loads a block, performs all necessary calculations, and writes only the final result back to HBM.

2. The Incremental Softmax Trick

The challenge with tiling is the Softmax. Softmax requires a global denominator (the sum of all exponentials): $$\text{softmax}(x_i) = \frac{e^{x_i}}{\sum_{j=1}^N e^{x_j}}$$ If we only have a "tile" of the data, we don't know the global sum. FlashAttention solves this by tracking a running maximum ($m$) and a running sum ($l$). As new blocks are processed, it rescales the previous partial results to match the new maximum, ensuring the final output is mathematically identical to the standard softmax.

3. Backward Pass Recomputation

To avoid storing the $N \times N$ matrix for the backward pass (which would defeat the purpose of saving memory), FlashAttention stores only the normalization factors. During backpropagation, it recomputes the necessary attention blocks on-the-fly. This trades a bit more computation for a massive reduction in memory IO.


Architectural Workflow

The following diagram illustrates how data moves between the slow HBM and the fast SRAM during the FlashAttention process.

flowchart TD subgraph HBM ["High Bandwidth Memory (HBM) - Slow Access"] InputQ["Input Q (B, N, D)"] InputK["Input K (B, N, D)"] InputV["Input V (B, N, D)"] GlobalO["Output O (B, N, D)"] GlobalM["Running Max m (B, N)"] GlobalL["Running Sum l (B, N)"] end subgraph SRAM ["On-Chip SRAM - Fast Access (Tiling)"] direction TB subgraph TilingLoop ["Tiling Process (Nested Loops)"] BlockKV["Load Blocks: K_block, V_block"] BlockQ["Load Block: Q_block"] subgraph ComputeCore ["Incremental Attention Core"] MatMul1["Compute Scores: S = (Q_block @ K_block.T) * scale"] MaxUpdate["Update Local Max: block_max = max(S)"] subgraph SoftmaxLogic ["Incremental Softmax Logic"] AlphaCalc["Compute Scaling Factor: alpha = exp(m_old - m_new)"] ExpCalc["Compute Exponentials: exp_block = exp(S - block_max)"] SumUpdate["Update Running Sum: l_new = alpha * l_old + sum(exp_block)"] end MatMul2["Weighted Sum: weighted_v = exp_block @ V_block"] OUpdate["Update Output: O_new = alpha * O_old + weighted_v"] end end end %% Data Flow InputQ --> BlockQ InputK --> BlockKV InputV --> BlockKV GlobalM --> AlphaCalc GlobalL --> SumUpdate GlobalO --> OUpdate BlockQ --> MatMul1 BlockKV --> MatMul1 BlockKV --> MatMul2 MatMul1 --> MaxUpdate MaxUpdate --> AlphaCalc MaxUpdate --> ExpCalc AlphaCalc --> SumUpdate AlphaCalc --> OUpdate ExpCalc --> SumUpdate ExpCalc --> MatMul2 MatMul2 --> OUpdate %% Write back to HBM OUpdate --> GlobalO MaxUpdate --> GlobalM SumUpdate --> GlobalL %% Final Step GlobalO --> FinalNorm["Final Normalization: O = O / l"] GlobalL --> FinalNorm FinalNorm --> FinalOutput["Final Attention Output"] style HBM fill:#f9f,stroke:#333,stroke-width:2px style SRAM fill:#bbf,stroke:#333,stroke-width:2px style ComputeCore fill:#dfd,stroke:#333,stroke-dasharray: 5 5

Implementation: Simulating FlashAttention in PyTorch

While a production-grade FlashAttention requires CUDA or Triton to manage SRAM explicitly, we can simulate the tiling and incremental softmax logic in PyTorch to verify the algorithm's correctness.

PYTHON
import torch
import torch.nn as nn
import torch.nn.functional as F
import time

class FlashAttentionSim(nn.Module):
    """
    Simulation of FlashAttention logic.
    Replicates Tiling and Incremental Softmax to demonstrate mathematical correctness.
    """
    def __init__(self, block_size: int = 128):
        super().__init__()
        self.block_size = block_size

    def forward(self, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor) -> torch.Tensor:
        B, N, D = q.shape
        scale = 1.0 / (D ** 0.5)
        
        # O: Output, m: running max, l: running sum
        O = torch.zeros_like(q)
        m = torch.full((B, N), float('-inf'), device=q.device)
        l = torch.zeros((B, N), device=q.device)

        # Outer Loop: Tiling over K, V
        for j in range(0, N, self.block_size):
            k_block = k[:, j : j + self.block_size, :] 
            v_block = v[:, j : j + self.block_size, :] 

            # Inner Loop: Tiling over Q
            for i in range(0, N, self.block_size):
                q_block = q[:, i : i + self.block_size, :] 

                # 1. Compute local attention scores
                attn_block = torch.matmul(q_block, k_block.transpose(-2, -1)) * scale

                # 2. Incremental Softmax Logic
                block_max = torch.max(attn_block, dim=-1).values 
                m_old = m[:, i : i + self.block_size]
                m_new = torch.max(m_old, block_max)
                
                exp_block = torch.exp(attn_block - block_max.unsqueeze(-1))
                alpha = torch.exp(m_old - m_new) # Rescaling factor
                
                l_old = l[:, i : i + self.block_size]
                l_new = alpha * l_old + torch.sum(exp_block, dim=-1)
                
                # 3. Update Output O
                weighted_v = torch.matmul(exp_block, v_block) 
                O_block_old = O[:, i : i + self.block_size, :]
                O[:, i : i + self.block_size, :] = alpha.unsqueeze(-1) * O_block_old + weighted_v
                
                # Update state for next tile
                m[:, i : i + self.block_size] = m_new
                l[:, i : i + self.block_size] = l_new

        return O / l.unsqueeze(-1) # Final normalization

# --- Verification ---
if __name__ == '__main__':
    B, N, D = 2, 512, 64
    q, k, v = torch.randn(B, N, D), torch.randn(B, N, D), torch.randn(B, N, D)
    
    # Standard Attention
    std_out = F.softmax(torch.matmul(q, k.transpose(-2, -1)) / (D**0.5), dim=-1) @ v
    
    # Flash Attention Sim
    flash_attn = FlashAttentionSim(block_size=128)
    flash_out = flash_attn(q, k, v)

    diff = torch.abs(std_out - flash_out).max().item()
    print(f"Max Difference: {diff:.2e}")
    print("✅ Verification Successful!" if diff < 1e-5 else "❌ Verification Failed!")

Key Takeaways

FlashAttention is a masterclass in hardware-aware algorithm design. By recognizing that the GPU's memory hierarchy—not the raw number of calculations—was the primary bottleneck, the authors achieved:

  1. Faster Training/Inference: Significant speedups by reducing HBM access.
  2. Longer Context Windows: Memory usage scales linearly $O(N)$ with sequence length instead of quadratically $O(N^2)$.
  3. Exact Results: Unlike "Approximate Attention," FlashAttention provides the exact same output as standard attention.

For ML engineers, the lesson is clear: To optimize for the next generation of models, we must look beyond Big-O complexity and start looking at how data actually moves through the silicon.