Attention Complexity › The Quadratic Bottleneck

Michael BrenndoerferUpdated May 29, 202519 min read
Language AI HandbookAll chapters
Part 13 of 67Self-Attention
Chapter 95Attention Complexity › The Quadratic Bottleneck
Reading section 2 of 11 · The Quadratic Bottleneck · 19 minSection 2 of 11 · 19 min

Book contents

Section 2 of 11 · 19 min read

Attention Complexity › The Quadratic BottleneckOne section · Sections in this chapter2 / 11 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Self-attention's computational cost is best understood not as a formula but as a structural property of the mechanism itself. Before we count operations, it helps to ask: why is a quadratic cost inevitable here, and is there any way around it?

The answer is that full self-attention is defined as exhaustive pairwise comparison. The mechanism works by asking, for every token, "how relevant is every other token to me right now?" This question cannot be answered without computing a relevance score between every pair. If you skip any pair, you may miss a critical dependency. A pronoun might refer to a noun fifty tokens back. A closing bracket might need to match an opening bracket hundreds of tokens earlier. The mechanism is designed to handle exactly these cases, and handling them requires examining all pairs. The cost is the price of generality.

To understand why attention becomes expensive, we need to trace through exactly what happens when self-attention processes a sequence. The mechanism is elegant but computationally demanding, and the cost comes from a single, fundamental fact: every token must interact with every other token.

Imagine you're in a room with nn people, and everyone needs to have a brief conversation with everyone else. With 10 people, that's 10×10=10010 \times 10 = 100 conversations. With 100 people, it's 100×100=10,000100 \times 100 = 10,000 conversations. The number of interactions grows as the square of the group size. Self-attention faces exactly this scaling challenge.

The quadratic cost follows from the requirement to evaluate all pairwise relevances exactly. There is no clever algorithm that evaluates all pairwise relevances in fewer than O(n2)O(n^2) steps while remaining exact. Algorithms that claim linear attention complexity always involve an approximation: they find a way to avoid computing all pairs exactly, at some cost to expressiveness or accuracy. This distinction between exact and approximate attention is a recurring theme in the efficiency literature, and understanding the quadratic baseline is essential for evaluating those trade-offs.

Out[3]:
Visualization
8x8 attention matrix showing 64 pairwise interactions.
n=8: 64 interactions
16x16 attention matrix showing 256 pairwise interactions.
n=16: 256 interactions
32x32 attention matrix showing 1024 pairwise interactions.
n=32: 1024 interactions

The Three-Stage Computation Pipeline

Self-attention proceeds through three distinct operations, each contributing to the overall computational cost. These three stages correspond exactly to the formula you have seen before in this book, but here we examine them through the lens of cost rather than function. Each stage has a well-defined computational profile, and understanding them separately makes the total complexity easy to build up from first principles.

The three stages are:

  1. Computing attention scores: Each token asks "how relevant is every other token to me?" This requires comparing all nn tokens against all nn tokens, producing an n×nn \times n grid of relevance scores.

  2. Applying softmax: The raw scores are converted into proper attention weights that sum to 1, allowing us to interpret them as a probability distribution. This normalizes along each row of the score matrix.

  3. Computing outputs: Each token gathers information from all other tokens, weighted by the attention scores, to produce its contextual representation. This is a weighted sum over value vectors.

The first and third steps are where the quadratic cost emerges. Step two is also quadratic, but it is dominated by the other two when the model dimension is large. Let's examine each stage carefully, counting operations precisely.

Stage 1: Computing Attention Scores

The heart of self-attention is measuring pairwise relevance between tokens. Given a sequence of nn tokens, we need to compute how strongly each token should attend to every other token. This is done by comparing query vectors against key vectors using the dot product. You can think of the query as a question and the key as an index: the dot product measures how well a token's question matches another token's index entry.

The reason we use a matrix multiplication here, rather than computing each score individually in a loop, is efficiency. Modern hardware, especially GPUs and TPUs, is designed to execute large matrix multiplications at peak throughput. Expressing the operation as a single QKTQK^T multiplication allows the hardware to exploit its full parallelism. The result is still the same n2n^2 numbers, but computed much faster than n2n^2 separate dot products would be.

Mathematically, we arrange all queries into a matrix QQ and all keys into a matrix KK, then compute:

S=QKTS = QK^T

where:

  • SS: the n×nn \times n attention score matrix, where SijS_{ij} measures how much token ii should attend to token jj
  • QQ: the query matrix with shape n×dkn \times d_k, containing one query vector per token
  • KTK^T: the transposed key matrix with shape dk×nd_k \times n, rearranging the key vectors for efficient matrix multiplication
  • nn: the sequence length (number of tokens)
  • dkd_k: the dimension of each query/key vector (typically 64 in standard transformers)

Why does this produce n2n^2 values? Each row of QQ (one token's query) gets multiplied against every column of KTK^T (every token's key), producing one score per pair. With nn rows and nn columns, we get an n×nn \times n grid of scores.

The computational cost of this matrix multiplication is substantial. Each entry in the output matrix requires dkd_k multiplications and dk−1d_k - 1 additions. With n2n^2 entries, the total operation count is n2⋅dkn^2 \cdot d_k.

Multiply-Accumulate Operations

A multiply-accumulate (MAC) operation computes a+b×ca + b \times c and is the fundamental unit of computation in matrix multiplication. When counting FLOPs, we typically count multiplications and additions separately, giving 2 FLOPs per MAC. For simplicity, we often count just multiplications, knowing the total FLOPs is roughly double.

The critical observation is that this score computation already scales as O(n2dk)O(n^2 d_k). The n2n^2 term is unavoidable: we cannot know which token pairs are important without examining all of them. Even in cases where the final attention weights are sparse (most tokens barely attending to most others), we still must compute all the raw scores before we can discover which ones are large. The sparsity only emerges after the computation, not before it.

Stage 2: Normalizing with Softmax

Raw dot products can be any real number, positive or negative, with no upper bound. To use them as weights for a weighted average, we need to transform them into a valid probability distribution. The softmax function accomplishes this by exponentiating each score and normalizing within each row.

The key insight is that softmax operates independently on each row of the score matrix. Each row corresponds to one query token, and its nn scores express how much that token should attend to each of the nn positions. Softmax converts these nn arbitrary numbers into nn non-negative weights that sum to exactly 1. Think of it as converting raw relevance signals into a proper distribution over positions to attend to.

For each token ii, softmax converts its nn raw scores into nn attention weights that sum to 1. This involves:

  1. Computing exp⁡(Sij)\exp(S_{ij}) for each of the nn scores (making them all positive)
  2. Summing these exponentials across the row
  3. Dividing each exponential by the sum

Each row requires approximately 3n3n operations (exponentiate, sum, divide), and with nn rows, the total is O(n2)O(n^2). While still quadratic, this is dominated by the O(n2dk)O(n^2 d_k) cost of score computation when dkd_k is large. In practice, the softmax step is rarely the performance bottleneck, but it does introduce a numerical subtlety: computing exp⁡\exp of large values causes overflow. The standard remedy is to subtract the row maximum before exponentiating, which leaves the softmax output unchanged (the constant cancels in the numerator and denominator) but keeps all values in a numerically safe range.

Stage 3: Computing the Weighted Output

The final step uses the attention weights to compute a weighted combination of value vectors. Each token's output is a blend of all tokens' values, weighted by attention. You can think of this as a soft lookup: instead of retrieving a single value at a specific position, each token retrieves a smooth mixture of all values in the sequence, weighted by how relevant each position was determined to be in Stage 1.

The operation computes a new representation for each of the nn tokens. Each token's new representation aggregates information from every other token in the sequence, scaled by attention weights. Tokens that received high attention scores contribute more to the output; tokens with near-zero attention weight contribute almost nothing.

The formula for this aggregation is:

Output=softmax(S)⋅V\text{Output} = \text{softmax}(S) \cdot V

where:

  • softmax(S)\text{softmax}(S): the n×nn \times n attention weight matrix, with each row summing to 1
  • VV: the value matrix with shape n×dvn \times d_v, containing one value vector per token
  • dvd_v: the dimension of each value vector (typically equal to dkd_k)

This is another matrix multiplication: (n×n)⋅(n×dv)(n \times n) \cdot (n \times d_v) produces an n×dvn \times d_v output. Each of the n⋅dvn \cdot d_v output elements requires nn multiply-accumulate operations (summing across the nn positions), giving n2⋅dvn^2 \cdot d_v total operations.

This step is just as expensive as score computation. Notice that both of the dominant operations involve an n×nn \times n intermediate matrix: in Stage 1 we create this matrix, and in Stage 3 we multiply by it. This n×nn \times n structure is the crux of attention's quadratic scaling, and it is also the primary target of memory-efficient implementations like FlashAttention, which avoid materializing the full n×nn \times n matrix in memory at once.

Putting It Together: Total Complexity

Now that we have analyzed each stage individually, we can combine the costs to get the total complexity of a single self-attention operation. This summary is important because it makes the n2dn^2 d scaling explicit and shows that neither the softmax step nor the dd dimension can offset the quadratic term.

Summing the costs from all three stages:

Total FLOPs=O(n2dk)⏟scores+O(n2)⏟softmax+O(n2dv)⏟output=O(n2(dk+dv))=O(n2d)\text{Total FLOPs} = \underbrace{O(n^2 d_k)}_{\text{scores}} + \underbrace{O(n^2)}_{\text{softmax}} + \underbrace{O(n^2 d_v)}_{\text{output}} = O(n^2(d_k + d_v)) = O(n^2 d)

where:

  • nn: sequence length
  • dkd_k: query/key dimension
  • dvd_v: value dimension
  • dd: model dimension (typically dk=dv=d/hd_k = d_v = d/h for hh attention heads)

The simplification to O(n2d)O(n^2 d) reflects that dkd_k and dvd_v are both proportional to the model dimension dd. In standard transformers, each head operates on d/hd/h dimensions, but since we have hh heads processing in parallel and concatenating their outputs, the total complexity across all heads scales with the full model dimension.

It is worth pausing here to appreciate what this formula tells us. Two factors control the cost: the sequence length squared (n2n^2) and the model dimension (dd). These two factors contribute very differently to the total. Doubling dd doubles the cost. Doubling nn quadruples it. For any sequence where n>1n > 1, the sequence length term dominates. A model with n=2048n = 2048 and d=768d = 768 spends roughly 20482/768≈5,4612048^2 / 768 \approx 5,461 times more computation on the n2n^2 factor than on the dd factor. This asymmetry is exactly what makes sequence length the central bottleneck in transformer scaling.

Worked Example: Computing FLOPs for a Short Sequence

Let's trace through a concrete small example to make the formula visceral. Consider a single-head attention layer with sequence length n=4n = 4 (four tokens), query/key dimension dk=3d_k = 3, and value dimension dv=3d_v = 3.

Stage 1: Score computation. The query matrix QQ has shape 4×34 \times 3 and the key matrix KK has shape 4×34 \times 3. Computing S=QKTS = QK^T produces a 4×44 \times 4 matrix. Each of the 16 entries requires 3 multiplications and 2 additions (a dot product of length 3), totaling 16×3=4816 \times 3 = 48 multiplications.

Stage 2: Softmax. We apply softmax to each of the 4 rows of SS. Each row has 4 elements, requiring 4 exponentiations, 1 sum (over 4 values, so 3 additions), and 4 divisions. Approximately 4×(4+3+4)=444 \times (4 + 3 + 4) = 44 operations.

Stage 3: Output computation. The softmax weight matrix has shape 4×44 \times 4 and the value matrix VV has shape 4×34 \times 3. The product has shape 4×34 \times 3. Each of the 12 output entries requires 4 multiplications and 3 additions, totaling 12×4=4812 \times 4 = 48 multiplications.

The total is dominated by Stages 1 and 3: approximately 48+48=9648 + 48 = 96 multiplications (plus roughly the same number of additions). Using our formula 2n2dk=2×16×3=962n^2 d_k = 2 \times 16 \times 3 = 96, we recover exactly this number. The formula works.

Now compare with scaling to n=8n = 8 (doubling the sequence length). Stage 1 becomes 64×3=19264 \times 3 = 192 multiplications (4x more). Stage 3 becomes 64×3=19264 \times 3 = 192 multiplications (4x more). The total scales by exactly 4x, as the quadratic formula predicts. Every doubling of nn multiplies the operation count by 4, regardless of what dd is. This is the essential arithmetic that makes long sequences expensive.

The Scaling Insight

This analysis reveals the fundamental scaling behavior of self-attention. The two key findings are:

  • Quadratic in sequence length: Doubling nn quadruples the computation. This is the defining characteristic that limits context length.
  • Linear in model dimension: Doubling dd only doubles the computation. Making models wider is relatively cheap.

The quadratic term dominates for long sequences. At n=1000n = 1000 tokens with d=768d = 768 (GPT-2 dimensions), the n2=1,000,000n^2 = 1,000,000 factor far exceeds the d=768d = 768 factor. Understanding this asymmetry helps when reasoning about transformer scalability.

The key insight is that this asymmetry shapes all of modern LLM engineering. When practitioners want more capable models, they often increase model depth and dimension, which costs linearly. When they want models that understand longer documents, they face a quadratic penalty. This is why context length has historically lagged so far behind model parameter counts: a 10x increase in context length requires 100x more attention computation, while a 10x increase in parameters (achieved through wider or deeper models) costs only 10x more. The entire field of efficient attention is essentially an attempt to change this ratio.

Historical Context: The 512-Token Limit

The original BERT model (2018) was trained with a maximum sequence length of 512 tokens. This was not chosen because 512 tokens is semantically sufficient for language understanding. It was chosen because the quadratic cost of attention made longer sequences prohibitively expensive at the compute budgets available at the time. Many downstream tasks required creative workarounds: sliding windows over longer texts, truncation, or chunking into smaller segments. The gradual increase in practical context length from 512 tokens (BERT, 2018) to 2048 tokens (GPT-3, 2020) to 32K (many 2023 models) to 1M+ tokens (Gemini 1.5, 2024) tracks hardware improvements and algorithmic advances in attention efficiency, not changes in what is theoretically possible.

Out[4]:
Visualization
Contour plot with sequence length on x-axis and model dimension on y-axis, showing steeper gradients in the horizontal direction.
Attention complexity as a function of both sequence length (n) and model dimension (d). The contour lines show equal-computation curves. Moving horizontally (increasing n) raises complexity much faster than moving vertically (increasing d), visualizing the quadratic vs linear scaling asymmetry.

Implementation: Counting FLOPs

The theoretical complexity O(n2d)O(n^2 d) tells us about relative growth rates, but it hides constant factors and additive terms that matter enormously in practice. To see the actual numbers, let's build a function that calculates exact FLOPs for self-attention. This exercise converts the abstract complexity formula into concrete operation counts that can be compared against hardware throughput specifications.

The function mirrors our three-stage analysis: score computation contributes n2⋅dn^2 \cdot d operations, softmax contributes 3⋅n23 \cdot n^2 operations, and output computation contributes another n2⋅dn^2 \cdot d operations. Notice that the softmax term is negligible relative to the others when d≫3d \gg 3, which is always true in practice (dd is typically in the hundreds). This confirms that the two matrix multiplications, not the softmax, are the computational bottleneck.

In[5]:
Code
def attention_flops(n, d, include_projections=False):
    """
    Calculate FLOPs for self-attention.

    Args:
        n: Sequence length
        d: Model dimension
        include_projections: Whether to include Q, K, V projections

    Returns:
        Total FLOPs (counting multiplications only)
    """
    # Stage 1: Score computation (Q @ K^T)
    # Matrix multiplication: (n x d) @ (d x n) = n^2 * d multiplications
    score_flops = n * n * d

    # Stage 2: Softmax normalization
    # Per row: exp (n ops), sum (n ops), divide (n ops) = 3n per row
    # Total: 3n * n rows = 3n^2
    softmax_flops = 3 * n * n

    # Stage 3: Output computation (attention @ V)
    # Matrix multiplication: (n x n) @ (n x d) = n^2 * d multiplications
    output_flops = n * n * d

    total = score_flops + softmax_flops + output_flops

    # Optional: include the linear projections that create Q, K, V
    if include_projections:
        # Q, K, V projections: each is (n x d) @ (d x d) = n * d^2
        projection_flops = 3 * n * d * d
        # Output projection: (n x d) @ (d x d) = n * d^2
        output_proj_flops = n * d * d
        total += projection_flops + output_proj_flops

    return total

With this function, we can explore how computational cost scales across different sequence lengths. We'll use GPT-2's model dimension (d=768d = 768) to ground our analysis in real-world numbers.

In[6]:
Code
# Example: GPT-2 style model
d = 768  # Model dimension
seq_lengths = [128, 512, 1024, 2048, 4096, 8192]

flops_data = []
for n in seq_lengths:
    flops = attention_flops(n, d)
    flops_data.append(
        {"seq_len": n, "flops": flops, "flops_billions": flops / 1e9}
    )
Out[7]:
Console
Attention FLOPs for d=768 (model dimension):

Sequence Length           FLOPs     Billions
---------------------------------------------
            128      25,214,976        0.025
            512     403,439,616        0.403
          1,024   1,613,758,464        1.614
          2,048   6,455,033,856        6.455
          4,096  25,820,135,424       25.820
          8,192 103,280,541,696      103.281

The table shows the quadratic explosion in computational cost. At sequence length 128, attention requires roughly 25 million operations. By 1024 tokens (a typical context window), we're performing over 1.6 billion operations. At 8192 tokens, this jumps to nearly 100 billion operations. These numbers are per layer only, so a 12-layer transformer multiplies each value by 12.

To put these numbers in perspective, a modern A100 GPU performs approximately 77 teraFLOPS (trillion floating-point operations per second) in fp16 precision. Dividing the per-layer operation counts by this throughput gives approximate execution times of fractions of a millisecond for short contexts, growing to tens of milliseconds for very long ones. The important realization is that these operation counts are not small: even at 1024 tokens, each layer of attention performs more operations than the entire computation for many classical machine learning algorithms on typical datasets.

Out[8]:
Visualization
Line plot showing exponential growth of FLOPs as sequence length increases from 128 to 8192.
FLOPs scaling for self-attention with model dimension d=768. The quadratic growth means that doubling sequence length quadruples the computational cost. Note the log scale on the y-axis.

The plot confirms the quadratic relationship. Moving from 512 to 1024 tokens quadruples the computation, and moving from 1024 to 2048 quadruples it again. At the right edge of the plot, we're computing nearly 100 billion operations for a single attention layer.