Simulate Sinusoidal Positional Encoding
Positional Encoding injects positional information into Transformer input sequences, as self-attention is inherently permutation invariant.
The sinusoidal positional encoding for token at position i with embedding dimension D is calculated as:
pi,k={sin(i/10000k/D)cos(i/10000(k−1)/D)if k is evenif k is odd
Task: Implement a function that generates the positional encoding vector p of length D for a token at sequence index i.
Example:
i=0, D=4
[0.0000, 1.0000, 0.0000, 1.0000]
At position 0: sin(0)=0, cos(0)=1. For all dimensions, the pattern alternates between sin(0)=0 and cos(0)=1.
Constraints:
- 0≤i<1000
- D is an even integer: 2≤D≤512
1. Background Knowledge
Positional Encoding addresses Transformer's permutation invariance: self-attention treats all tokens equally regardless of order, lacking inherent sequential awareness. The original Transformer uses sinusoidal positional encoding to inject position i information into D-dimensional embeddings via periodic sine/cosine functions with increasing frequencies 10000k/D, creating unique, wavelength-diverse encodings. This deterministic approach ensures fixed-length extrapolation and relative position learning via phase differences.
Prerequisites: Basic trigonometry (sin, cos), array indexing (0-based), floating-point math. D even ensures paired sin/cos dimensions.
2. Algorithm Approach
Direct formula implementation: for each dimension k∈[0,D−1],
pi,k={sin(i/100002k/D)cos(i/100002(k−1)/D)k evenk oddKey insight: Even indices use sin with frequency scaling 2k/D; odd use cos shifted by one pair (k−1). Precompute powers via log(10000) * k/D for numerical stability.
Common technique: Vectorized NumPy/PyTorch loops or broadcasting over k.
3. Step-by-Step Strategy
- Initialize zero vector p of shape (D,).
- Loop over dimensions k=0 to D−1:
- Compute angular frequency: ωk=10000−k/D (use exp(-log(10000) * k/D)).
- If k%2==0: pk=sin(i⋅\omegak).
- Else: pk=cos(i⋅\omegak−1) where ωk−1=10000−(k−1)/D.
- Return p.
Vectorized Python example:
import numpy as np
def positional_encoding(i: int, D: int) -> np.ndarray:
p = np.zeros(D)
div_term = np.exp(-np.log(10000.0) * np.arange(0, D, 2) / D) # Even freqs
p[0::2] = np.sin(i * div_term) # Even: sin(i / 10000^{k/D})
p[1::2] = np.cos(i * np.roll(div_term, -1)) # Odd: cos(i / 10000^{(k-1)/D})
return p[:D] # Truncate if needed
Note: np.roll(div_term, -1) shifts for odd indices; slice [:D] handles even D.
4. Common Pitfalls
- Off-by-one indexing: Odd k uses (k−1)/D, not k/D—easy mix-up.
- Numerical instability: Direct 10000 ** (k/D) overflows; use exp(log(10000) * k/D).
- Even D assumption: Code fails if D odd (last dim unpaired); constraints guarantee even.
- Integer division: Use float exponents (k/D, not k//D).
- Range errors: i<1000, D≤512 safe, but verify no overflow in sin/cos args.
- 1-based vs 0-based: Formula uses 0-based i≥0.
5. Time & Space Complexity
- Time: O(D) per call—single pass over dimensions with constant-time trig ops.
- Space: O(D) for output vector + O(D/2) temp array; optimal as output required.
- Batch optimization: For sequence length N, precompute matrix O(ND); here single i.
This yields exact original Transformer encoding, enabling position-aware attention. Test: positional_encoding(0, 4) → [0,1,0,1].