Attention Complexity › The Quadratic Bottleneck
Language AI HandbookAll chapters 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 people, and everyone needs to have a brief conversation with everyone else. With 10 people, that's conversations. With 100 people, it's 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 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.



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:
-
Computing attention scores: Each token asks "how relevant is every other token to me?" This requires comparing all tokens against all tokens, producing an grid of relevance scores.
-
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.
-
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 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 multiplication allows the hardware to exploit its full parallelism. The result is still the same numbers, but computed much faster than separate dot products would be.
Mathematically, we arrange all queries into a matrix and all keys into a matrix , then compute:
where:
- : the attention score matrix, where measures how much token should attend to token
- : the query matrix with shape , containing one query vector per token
- : the transposed key matrix with shape , rearranging the key vectors for efficient matrix multiplication
- : the sequence length (number of tokens)
- : the dimension of each query/key vector (typically 64 in standard transformers)
Why does this produce values? Each row of (one token's query) gets multiplied against every column of (every token's key), producing one score per pair. With rows and columns, we get an grid of scores.
The computational cost of this matrix multiplication is substantial. Each entry in the output matrix requires multiplications and additions. With entries, the total operation count is .
A multiply-accumulate (MAC) operation computes 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 . The 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 scores express how much that token should attend to each of the positions. Softmax converts these arbitrary numbers into 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 , softmax converts its raw scores into attention weights that sum to 1. This involves:
- Computing for each of the scores (making them all positive)
- Summing these exponentials across the row
- Dividing each exponential by the sum
Each row requires approximately operations (exponentiate, sum, divide), and with rows, the total is . While still quadratic, this is dominated by the cost of score computation when is large. In practice, the softmax step is rarely the performance bottleneck, but it does introduce a numerical subtlety: computing 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 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:
where:
- : the attention weight matrix, with each row summing to 1
- : the value matrix with shape , containing one value vector per token
- : the dimension of each value vector (typically equal to )
This is another matrix multiplication: produces an output. Each of the output elements requires multiply-accumulate operations (summing across the positions), giving total operations.
This step is just as expensive as score computation. Notice that both of the dominant operations involve an intermediate matrix: in Stage 1 we create this matrix, and in Stage 3 we multiply by it. This 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 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 scaling explicit and shows that neither the softmax step nor the dimension can offset the quadratic term.
Summing the costs from all three stages:
where:
- : sequence length
- : query/key dimension
- : value dimension
- : model dimension (typically for attention heads)
The simplification to reflects that and are both proportional to the model dimension . In standard transformers, each head operates on dimensions, but since we have 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 () and the model dimension (). These two factors contribute very differently to the total. Doubling doubles the cost. Doubling quadruples it. For any sequence where , the sequence length term dominates. A model with and spends roughly times more computation on the factor than on the 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 (four tokens), query/key dimension , and value dimension .
Stage 1: Score computation. The query matrix has shape and the key matrix has shape . Computing produces a matrix. Each of the 16 entries requires 3 multiplications and 2 additions (a dot product of length 3), totaling multiplications.
Stage 2: Softmax. We apply softmax to each of the 4 rows of . Each row has 4 elements, requiring 4 exponentiations, 1 sum (over 4 values, so 3 additions), and 4 divisions. Approximately operations.
Stage 3: Output computation. The softmax weight matrix has shape and the value matrix has shape . The product has shape . Each of the 12 output entries requires 4 multiplications and 3 additions, totaling multiplications.
The total is dominated by Stages 1 and 3: approximately multiplications (plus roughly the same number of additions). Using our formula , we recover exactly this number. The formula works.
Now compare with scaling to (doubling the sequence length). Stage 1 becomes multiplications (4x more). Stage 3 becomes multiplications (4x more). The total scales by exactly 4x, as the quadratic formula predicts. Every doubling of multiplies the operation count by 4, regardless of what 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 quadruples the computation. This is the defining characteristic that limits context length.
- Linear in model dimension: Doubling only doubles the computation. Making models wider is relatively cheap.
The quadratic term dominates for long sequences. At tokens with (GPT-2 dimensions), the factor far exceeds the 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.
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.

Implementation: Counting FLOPs
The theoretical complexity 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 operations, softmax contributes operations, and output computation contributes another operations. Notice that the softmax term is negligible relative to the others when , which is always true in practice ( is typically in the hundreds). This confirms that the two matrix multiplications, not the softmax, are the computational bottleneck.
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 totalWith this function, we can explore how computational cost scales across different sequence lengths. We'll use GPT-2's model dimension () to ground our analysis in real-world numbers.
# 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}
)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.281The 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.

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.