Attention Mechanisms & The Transformer
Derive the mathematical variance proof of the 1/sqrt(d_k) scaling factor in self-attention, trigonometric linear shift properties of sinusoidal positional encodings, and zero-dependency implementations in Python and Rust.
Before 2017, sequence-to-sequence modeling relied on recurrent architectures that compressed variable-length source sentences into a single fixed-size bottleneck vector .
Vaswani et al. (2017) eliminated recurrence entirely in "Attention Is All You Need", introducing the Transformer. By replacing sequential unrolling with pairwise Scaled Dot-Product Self-Attention, Transformers enable massive GPU parallelization and direct path length between any two tokens across time.
Dividing dot products by sqrt(d_k) prevents softmax saturation and vanishing gradients.
1. Scaled Dot-Product Attention Formulation#
Let sequence representations be packed into matrix of tokens.
We project using three learnable parameter matrices:
- Queries:
- Keys:
- Values:
The canonical Scaled Dot-Product Attention equation is:
Where .
2. Mathematical Proof: The Scaling Factor#
Why is the dot-product matrix scaled specifically by the inverse square root of key dimension ?
Consider a query vector and key vector . Assume their scalar components and are independent random variables with zero mean and unit variance:
The dot product is the sum of scalar products:
Step 1: Compute the Expected Value
Because and are independent:
Step 2: Compute the Variance of a Single Product $q_i k_i$
Using the variance identity for independent variables:
Substituting zero means and unit variances:
Step 3: Compute the Variance of the Sum
Because the terms are mutually independent:
The standard deviation of the raw dot product is:
Step 4: Scale to Restore Unit Variance
For large head dimensions (e.g. or ), the standard deviation is to . Raw dot products grow large in magnitude, driving the softmax function into regions with near-zero gradients.
Dividing by :
Scaling by preserves unit variance into the softmax regardless of feature dimensionality .
3. Sinusoidal Positional Encoding & Linear Relative Shifts#
Because self-attention computes dot products symmetrically across all pairs, the permutation of input tokens yields identical attention scores (permutation equivariance).
To supply positional order, Vaswani et al. add fixed sinusoidal encodings :
Where is token position and is feature channel.
Mathematical Proof of Relative Linear Shift#
For any fixed offset , can the model attend to relative position using a linear transformation?
Recall the trigonometric addition formulas:
Let . We can represent and as a rotation matrix acting on :
Because the rotation matrix depends solely on offset and not on absolute position , the self-attention mechanism can easily learn to attend to relative distances.
4. From-Scratch Implementation: Zero Dependencies#
The following implementations construct the complete Scaled Dot-Product Attention mechanism (including numerically stable softmax and value aggregation) from scratch in pure Python and pure Rust.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768import math from typing import List class AttentionFromScratch: @staticmethod def _dot(a: List[float], b: List[float]) -> float: return sum(x * y for x, y in zip(a, b)) @staticmethod def _softmax(row: List[float]) -> List[float]: # Numerical stability: subtract max(row) max_val = max(row) exp_vals = [math.exp(x - max_val) for x in row] sum_exp = sum(exp_vals) return [v / sum_exp for v in exp_vals] @classmethod def scaled_dot_product_attention( cls, Q: List[List[float]], K: List[List[float]], V: List[List[float]] ) -> List[List[float]]: # Q: [T, d_k], K: [T, d_k], V: [T, d_v] T = len(Q) d_k = len(Q[0]) d_v = len(V[0]) scale = 1.0 / math.sqrt(d_k) # 1. Compute Q * K^T / sqrt(d_k) scores = [[0.0] * T for _ in range(T)] for i in range(T): for j in range(T): scores[i][j] = cls._dot(Q[i], K[j]) * scale # 2. Row-wise Softmax to compute attention weights attention_weights = [cls._softmax(row) for row in scores] # 3. Multiply weights by V: [T, T] * [T, d_v] -> [T, d_v] output = [[0.0] * d_v for _ in range(T)] for i in range(T): for col in range(d_v): output[i][col] = sum(attention_weights[i][j] * V[j][col] for j in range(T)) return output if __name__ == "__main__": # Sequence of 3 tokens with d_k = 4, d_v = 2 Q = [ [1.0, 0.0, 1.0, 0.0], [0.0, 2.0, 0.0, 1.0], [1.0, 1.0, 1.0, 1.0] ] K = [ [1.0, 0.0, 1.0, 0.0], [0.0, 2.0, 0.0, 1.0], [1.0, 1.0, 1.0, 1.0] ] V = [ [10.0, 1.0], [20.0, 2.0], [30.0, 3.0] ] out = AttentionFromScratch.scaled_dot_product_attention(Q, K, V) print("Scaled Dot-Product Attention Output (3x2):") for row in out: print(" ", [round(v, 4) for v in row])
5. Active Knowledge Checks#
Why does multiplying two zero-mean, unit-variance vectors q, k in R^(d_k) produce a dot product with variance d_k rather than 1.0?
How do sinusoidal positional encodings enable the attention mechanism to attend to relative token offsets k?