Part of Language AI Handbook
Covers sinusoidal position encoding, the deterministic method that gives transformers positional awareness.
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
Sinusoidal Position Encoding
The transformer's self-attention mechanism is permutation invariant: it produces the same output regardless of token order. Feed a transformer the sentence "The dog bit the man" and "The man bit the dog" and, without any additional signal, it will treat them identically. This is a fundamental problem, because word order is one of the primary carriers of meaning in language. A mechanism that cannot distinguish "not guilty" from "guilty" based on word order is useless for any serious language task.
To fix this, the original "Attention Is All You Need" paper introduced sinusoidal position encoding, an elegant solution that encodes each position as a unique pattern of sine and cosine waves at different frequencies. Rather than learning position representations from data, the encoding is computed from a fixed mathematical formula, requiring zero extra parameters. Every position in a sequence receives a distinct vector that the model can use to reason about order and distance.
This approach is one of the most instructive designs in the entire transformer architecture, because it solves a concrete engineering problem, uniquely identifying positions in a way that supports both absolute and relative reasoning, using pure mathematics rather than learned tables. Understanding sinusoidal encoding deeply will also give you the conceptual foundation for understanding modern alternatives like RoPE and ALiBi, which build directly on the same ideas.
Before we examine the formula, consider why the problem is harder than it looks. You might wonder: why append the position index to the token embedding? Why multiply by the position or add a one-hot vector? Each idea fails at least one property we need. Appending the position number means that position 1000 carries a value 1000 times larger than position 1, which would dominate the semantic embedding entirely. A one-hot vector of length equal to the maximum sequence length would require enormous memory and could not generalize to lengths not seen during training. The sinusoidal approach avoids these failure modes, and the rest of this chapter explains how.
The Position Encoding Formula
Before diving into the mathematics, let's consider what properties we want from a position encoding. We need a function that takes a position index and produces a vector, and this function must satisfy several constraints that, taken together, are quite demanding.
The first constraint is uniqueness. Each position must map to a distinct vector. If positions 5 and 17 produce the same encoding, the model cannot distinguish them, and all the information about order is lost at those positions. This sounds simple but rules out many naive approaches: a constant function is not unique, and a linear function is unique but fails the next constraint.
The second constraint is bounded values. The encoding should not grow unboundedly with position. If position 1000 produces values 1000 times larger than position 1, the position signal would overwhelm the semantic content of the embeddings. The model would spend most of its capacity reasoning about raw position magnitude rather than meaning. We need the encoding values to stay within a fixed range regardless of how long the sequence is.
The third constraint is smooth progression. Nearby positions should have similar encodings. Position 50 should be more similar to position 51 than to position 500, giving the model useful gradient information during training and letting it generalize position-aware patterns across neighboring positions. An encoding that jumps chaotically between positions would be hard to learn from.
The fourth constraint is determinism. The same position should always produce the same encoding, without requiring any learned parameters. This is not strictly necessary for correctness (learned embeddings also satisfy the first three constraints), but it means the encoding requires zero parameters and can represent positions never seen during training.
Sinusoidal functions satisfy all these requirements elegantly. Sine and cosine oscillate smoothly between -1 and 1. This ensures bounded values. Different frequencies distinguish positions at different scales. And the encoding is purely deterministic, computed from a fixed formula. The use of multiple frequencies simultaneously is what makes the encoding both unique and smooth at the same time.
Building the Encoding Step by Step
The core idea is to assign each position a unique "fingerprint" using waves of different frequencies. Think of how you might describe your location in a building: you could give the floor number (coarse scale), the room number (medium scale), and your position within the room (fine scale). Together, these scales uniquely identify any location. No single scale alone would suffice: the floor number alone could apply to hundreds of rooms, and the position-within-room alone could apply to rooms on every floor.
For position encoding, we use sine and cosine waves at different frequencies to achieve the same multi-scale identification. Each position in the sequence receives a -dimensional vector, where consecutive pairs of dimensions use sine and cosine at the same frequency:
where:
- : the position index in the sequence (0, 1, 2, ...)
- : the dimension index pair (0, 1, 2, ..., )
- : the total embedding dimension
- : the encoding value at position , even dimension
- : the encoding value at position , odd dimension
- : a base constant that controls the frequency range
Notice that for a fixed position , as we scan across the dimensions, we compute sine and cosine of decreasing frequencies. The first two dimensions use the fastest oscillation, and the last two dimensions use the slowest. The encoding contains a full spectrum of waves, from very high frequency to very low frequency, stacked into a single vector.
Understanding the Frequency Term
The key to the formula is the denominator . This term controls how fast each dimension pair oscillates as position increases. Let's unpack what happens at different dimension indices.
When (the first dimension pair), the denominator is , so we compute and . This oscillates rapidly: moving from position 0 to position 6 covers roughly one full cycle.
When (the last dimension pair), the denominator is approximately , so we compute and . This oscillates extremely slowly: you need 62,832 positions to complete one full cycle.
The exponent creates a geometric progression of frequencies. As increases from 0 to , the exponent increases from 0 to approximately 1, and the denominator grows from 1 to 10000. This exponential scaling ensures that each dimension pair captures position information at a different resolution. The spacing between frequencies is not arbitrary: it is chosen so that the wavelengths span a wide enough range to cover practical sequence lengths while keeping the total number of dimensions manageable.
The choice of 10000 as the base constant is also deliberate. A smaller base would cluster the wavelengths too close together. This provides redundant information in nearby dimensions. A larger base would spread them out but might leave some scale ranges uncovered. With 10000 and typical embedding dimensions of 256 or 512, the wavelengths range from about 6 positions up to 63,000 positions, which comfortably covers sequences of any practical length at the time the paper was written.
Why Pair Sine and Cosine?
Each dimension pair uses both sine and cosine at the same frequency. This pairing is not arbitrary; it serves two distinct purposes that are both essential.
The first purpose is unique identification within a cycle. Sine alone cannot distinguish positions that differ by multiples of . At the first dimension's frequency, positions 0 and 6 have very similar sine values, and positions 0 and approximately 6.28 have identical sine values. But the (sine, cosine) pair at any frequency uniquely identifies a phase angle. Geometrically, as position increases, the (sin, cos) pair traces a circle in 2D space, and every point on that circle corresponds to a unique position within one cycle. The cosine value is 90 degrees out of phase with the sine value, so there is never a point where both are ambiguous simultaneously.
The second purpose is enabling relative position computation. The sine/cosine pairing allows relative positions to be computed through rotation matrices, a property we'll explore in detail later in this chapter. This mathematical structure means the model can potentially learn to attend to relative positions using simple linear operations, which is a powerful inductive bias for sequence modeling. This property is so useful that it directly inspired modern architectures: RoPE (Rotary Position Embedding), used in LLaMA and many other models, is essentially a refined version of this rotation insight, applied directly to the query and key vectors inside attention.
A deterministic method for representing token positions using sine and cosine functions at geometrically increasing wavelengths. Each position maps to a unique point in a -dimensional space without requiring any learned parameters. The key design choices are: paired sine and cosine at each frequency for unique identification, a geometric progression of wavelengths for multi-scale coverage, and values bounded between -1 and 1 for numerical stability.
Wavelength Intuition
Now that we have the formula, let's build deeper intuition for why this multi-frequency approach works so well. The key insight is that different dimensions encode position at different scales, much like how we represent time using multiple units.
Consider a clock with both a second hand and an hour hand. The second hand rotates rapidly, completing one cycle per minute. If you only had the second hand, you could tell the difference between 3:00:15 and 3:00:45, but you couldn't distinguish 3:00:15 from 4:00:15, since both would show the second hand at the same position. The hour hand solves this problem: it moves slowly, completing one cycle every 12 hours, so it can distinguish times that the second hand cannot. A clock with both hands gives you two scales of information that together resolve ambiguities that neither could resolve alone.
But a two-handed clock has a limit: it cannot represent sub-second precision or multi-day periods. Real timekeeping systems solve this by adding finer and coarser time scales. The sinusoidal encoding generalizes this idea to "hands," each running at a different speed, chosen so that every positional difference up to the maximum sequence length is distinguishable by at least one hand.
Sinusoidal position encoding applies this clock principle to sequences. The first dimension pairs oscillate rapidly (like the second hand), distinguishing nearby positions with high precision. The later dimension pairs oscillate slowly (like the hour hand), distinguishing distant positions that the fast oscillators cannot. Together, they create a multi-resolution representation where any two positions, no matter how close or far apart, can be distinguished by at least one dimension pair.
The wavelength formula makes this precise. For dimension pair , the wavelength (the number of positions needed for one complete oscillation cycle) is:
where:
- : the wavelength for dimension pair (measured in positions per cycle)
- : the frequency denominator that grows geometrically with
- : the angular measure of one complete cycle (in radians)
This formula reveals the geometric progression at the heart of sinusoidal encoding:
-
First dimension pair (): Wavelength is positions. Positions 0 through 6 span roughly one full cycle. This fast oscillation distinguishes positions that differ by just 1 or 2.
-
Middle dimension pairs: Wavelengths grow exponentially. By the time we reach the middle dimensions, wavelengths might be in the hundreds, suitable for distinguishing positions that differ by tens or hundreds.
-
Last dimension pair (): Wavelength is approximately positions. You'd need over 10,000 positions to complete one cycle. This slow oscillation can distinguish positions separated by thousands.
The geometric progression is deliberate and essential. If wavelengths grew linearly, nearby dimension pairs would be redundant, both distinguishing roughly the same positional differences. The exponential growth ensures each dimension pair contributes unique positional information at its own characteristic scale, creating a compact representation that efficiently covers all possible position differences.
import matplotlib.pyplot as plt # noqa: F401
import numpy as np
# Calculate wavelengths for different dimensions
d = 512 # Embedding dimension
dimension_pairs = np.arange(d // 2)
# Wavelength formula: 2π × 10000^(2i/d)
wavelengths = 2 * np.pi * (10000 ** (2 * dimension_pairs / d))
The geometric spacing is deliberate. If wavelengths grew linearly, nearby dimension pairs would be redundant, encoding position at nearly the same resolution. The exponential growth ensures each dimension pair contributes unique positional information at a scale not covered by any other pair.
Think of it this way: with 256 dimension pairs and wavelengths growing exponentially from 6 to 63,000, each pair is responsible for roughly a factor-of-two increase in scale. This is like a biological sensory system that covers a wide range of intensities using logarithmic scaling: the eye can handle both starlight and sunlight because it responds logarithmically, not linearly. The sinusoidal encoding's geometric wavelength progression achieves the same kind of dynamic range in a compact representation.
Visualizing Position Encodings
With the formula and wavelength intuition in place, let's see what sinusoidal position encodings look like. We'll implement the encoding from scratch and visualize the resulting patterns to check our intuition.
def sinusoidal_position_encoding(max_len, d_model):
"""
Generate sinusoidal position encodings.
Args:
max_len: Maximum sequence length to encode
d_model: Dimension of the encoding vectors
Returns:
PE: Position encoding matrix of shape (max_len, d_model)
"""
# Create position indices: (max_len, 1)
positions = np.arange(max_len)[:, np.newaxis]
# Create dimension indices for pairs: (d_model/2,)
dim_pairs = np.arange(0, d_model, 2)
# Compute the frequency denominator: 10000^(2i/d)
div_term = 10000 ** (dim_pairs / d_model)
# Initialize encoding matrix
PE = np.zeros((max_len, d_model))
# Even dimensions: sine
PE[:, 0::2] = np.sin(positions / div_term)
# Odd dimensions: cosine
PE[:, 1::2] = np.cos(positions / div_term)
return PE
# Generate encodings for 100 positions with 64 dimensions
max_len = 100
d_model = 64
PE = sinusoidal_position_encoding(max_len, d_model)Position encoding matrix shape: (100, 64) Encoding for position 0 (first 8 dims): [0. 1. 0. 1. 0. 1. 0. 1.] Encoding for position 1 (first 8 dims): [0.841 0.54 0.682 0.732 0.533 0.846 0.409 0.912] Encoding for position 50 (first 8 dims): [-0.262 0.965 -0.203 0.979 0.157 -0.988 0.787 -0.617]
Position 0 always has sine values of 0 and cosine values of 1 in the first few dimensions, which is simply and . Notice that positions 0 and 1 look different in the high-frequency (leftmost) dimensions but will look nearly identical in the lowest-frequency dimensions, since those change so slowly. As position increases, the high-frequency dimensions (small indices) change rapidly while low-frequency dimensions (large indices) change slowly, exactly as we expect from the wavelength analysis.
Let's visualize the encoding as a heatmap to see the wave patterns:

The heatmap reveals the core structure. On the left side (low dimension indices), we see rapid oscillations: positions 0 and 3 might look similar here, but positions 0 and 1 are clearly different. On the right side (high dimension indices), the oscillations are so slow that the entire 100-position range barely covers a fraction of one cycle. The combination ensures every position has a unique encoding.
One detail to observe: the heatmap has a banded, almost textile-like structure. This is not noise or an artifact; it is exactly the mathematical structure we designed. The dense, narrow vertical stripes on the left represent high-frequency oscillations, and they blend into wide, gradient-like regions on the right as the wavelengths grow. If you saw a similar pattern in a completely different context, you would recognize it as a multi-frequency signal: it has the same structure as a spectrogram of a sound that contains many harmonics.
Let's examine specific dimension pairs to see the sine/cosine relationship:


Each dimension pair contributes a (sine, cosine) tuple that traces a circle in 2D space as position increases. The sine and cosine are 90 degrees out of phase. This ensures that every position has a unique combination even within a single dimension pair.
To make this geometric interpretation concrete, let's plot the trajectory of (sin, cos) pairs as position increases:


The circular trajectories reveal why sine/cosine pairing works so well. In the first dimension pair (left), the fast oscillation means positions loop around the circle multiple times. Even if two positions land at similar angles on the circle, they'll be distinguished by other dimension pairs with different frequencies. In the middle dimension pair (right), positions are spread across a smaller arc. This provides coarse-grained discrimination.
Notice that in both cases, the trajectory lies exactly on the unit circle. This is a direct consequence of the identity . Every position encoding, in every dimension pair, has the property that its sine and cosine values are exactly one unit apart when viewed as a 2D point. This geometric regularity is part of what makes the relative position property, which we will derive in a moment, work out so cleanly.
Uniqueness of Position Encodings
Why does this encoding give each position a unique vector? Consider two positions and . For them to have identical encodings, they would need to be indistinguishable across all dimension pairs. But with the geometric progression of wavelengths, this is virtually impossible for any pair of positions within a reasonable sequence length.
The key insight is that if two positions are the same distance apart in one dimension pair's "angular space," they will almost certainly be at different angles in every other dimension pair, because the frequencies are irrational multiples of each other. The wavelengths for different values are not commensurate: you cannot find an integer number of cycles of one that equals an integer number of cycles of another. This incommensurability is what prevents aliasing.
If two positions differ by 1, the first dimension pair (wavelength approximately 6) will clearly distinguish them. If they differ by 100, middle dimension pairs will distinguish them. If they differ by 10,000, the later dimension pairs will distinguish them. The multi-scale representation captures position differences at any granularity.
Let's verify this by computing distances between position encodings:
def encoding_distance(PE, pos1, pos2):
"""Compute Euclidean distance between two position encodings."""
return np.linalg.norm(PE[pos1] - PE[pos2])
# Compute pairwise distances for first 50 positions
max_pos = 50
distances = np.zeros((max_pos, max_pos))
for i in range(max_pos):
for j in range(max_pos):
distances[i, j] = encoding_distance(PE, i, j)
The distance matrix confirms that no two positions have identical encodings (no zeros off the diagonal). The banded structure shows that nearby positions have smaller distances, while distant positions have larger distances. This smooth distance gradient helps the model learn position-dependent patterns: when training on examples where semantically related tokens appear at similar relative positions, the model can exploit the continuity of the encoding to generalize.
Let's examine how distance varies with positional separation more precisely:

The plot reveals an important property: distance grows quickly for small separations (positions 1-10 are clearly distinguishable from position 0) but then oscillates around a plateau for larger separations. The oscillation comes from the sinusoidal structure: at certain separations, the high-frequency dimensions happen to cycle back to similar values, temporarily reducing the distance. However, the low-frequency dimensions ensure that even these "aliased" positions remain distinguishable from one another. The plateau behavior also means that very distant positions are no more "different" than moderately distant ones, which is a reasonable property for a language model: the word at position 400 in a long document is not necessarily more distant in meaning from position 0 than the word at position 100.
Worked Example: Encoding a Short Sentence
Let's make the abstract concrete with a small, worked example. Consider a sentence with five tokens: "The cat sat on mat." We'll encode the positions 0 through 4 using a tiny encoding with dimensions, small enough to trace through by hand for the first few values.
With , we have dimension pairs, numbered . The frequency denominators for each pair are:
The wavelengths for each pair are:
- Pair 0: positions
- Pair 1: positions
- Pair 2: positions
- Pair 3: positions
For position 0 (the token "The"), every sine term evaluates to and every cosine term evaluates to , giving:
This is a special property of position 0 that makes it immediately recognizable: it is the only position where the odd dimensions (cosines) are all 1.
For position 1 (the token "cat"), we compute:
So .
The pattern is clear: in the first dimension pair (), there is already a large change from position 0 to position 1 (from to ), because the wavelength is only about 6. In the later dimension pairs ( and ), the change is tiny, because a single step is a tiny fraction of their 628- or 3532-position wavelengths.
Now consider position 4 (the token "mat") in the first dimension pair: and . This is very different from both position 0 and position 1, confirming that the high-frequency pair rapidly distinguishes even nearby positions. Meanwhile, the slow-frequency pairs at and have barely moved from their position-0 values, since 4 is much less than the wavelengths of 628 and 3532.
# Worked example: encode a 5-token sequence with d=8
d_example = 8
tokens = ["The", "cat", "sat", "on", "mat"]
PE_example = sinusoidal_position_encoding(len(tokens), d_example)Position encodings for 'The cat sat on mat' (d=8) ============================================================ Token Pos dim0 dim1 dim2 dim3 dim4 dim5 dim6 dim7 ------------------------------------------------------------ The 0 +0.000 +1.000 +0.000 +1.000 +0.000 +1.000 +0.000 +1.000 cat 1 +0.841 +0.540 +0.100 +0.995 +0.010 +1.000 +0.001 +1.000 sat 2 +0.909 -0.416 +0.199 +0.980 +0.020 +1.000 +0.002 +1.000 on 3 +0.141 -0.990 +0.296 +0.955 +0.030 +1.000 +0.003 +1.000 mat 4 -0.757 -0.654 +0.389 +0.921 +0.040 +0.999 +0.004 +1.000

This worked example makes the abstract formula tangible. You can trace exactly how each token's position influences its encoding, and you can see that the information is spread across all dimensions simultaneously rather than concentrated in any single one. In practice, these position encodings are added element-wise to the token embeddings, so the final input to the transformer is the sum of what the token means (its semantic embedding) and where it sits in the sequence (its positional encoding).
The Relative Position Property
One of the most elegant properties of sinusoidal encoding is that relative positions can be expressed as linear transformations. For any fixed offset , there exists a matrix such that:
where:
- : the position encoding vector at position (a -dimensional row vector)
- : the position encoding vector at position
- : a transformation matrix that depends only on the offset , not on the absolute position
This is a remarkable property. It means that the relationship between any two encodings that are steps apart is always the same, regardless of where they sit in the sequence. The transformation from position 5 to position 10 is the same linear map as the transformation from position 500 to position 505. In principle, a transformer that learns this linear map can attend to any token "k positions ahead" without needing to know the absolute positions, simply by applying a fixed linear operation to the encoding.
To understand why this works, we need to derive the relationship step by step using trigonometric identities.
Step 1: Recall the angle addition formulas. For any angles and , trigonometry gives us:
where and are any angles (in radians). These identities let us express the sine or cosine of a sum in terms of the sines and cosines of the individual angles. They provide the algebra behind the relative position construction.
Step 2: Define the angular frequency. For dimension pair , we define the angular frequency as:
where:
- : the angular frequency for dimension pair (determines how fast this dimension oscillates)
- : the dimension pair index (0, 1, 2, ..., )
- : the total embedding dimension
- : the denominator that grows geometrically with
This means the encoding at position in dimension pair uses the argument .
Step 3: Apply the addition formulas. To find the encoding at position , we substitute and into the angle addition formulas:
where:
- : the angular frequency for dimension pair
- : the current position in the sequence
- : the position offset we want to shift by
- and : the original encoding values at position (these are and )
- and : constants that depend only on the offset , not on the absolute position
The last two terms are fixed constants once you fix and : the values and do not change as you vary . This is what makes the transformation linear in the original encoding values.
Step 4: Recognize the matrix structure. The key insight is that the right-hand sides of both equations are linear combinations of and . This is exactly what matrix multiplication does. We can write:
This is a rotation in 2D. For each dimension pair, the encoding at is the encoding at rotated by angle . The rotation matrix for offset in dimension pair is:
where:
- : the 2x2 rotation matrix for dimension pair with offset
- : the angular frequency for dimension pair
- : the position offset (how many positions to shift)
- : the rotation angle, which depends on both the offset and the dimension's frequency
Step 5: Construct the full transformation matrix. The full transformation matrix is block-diagonal, with each 2x2 block being the rotation matrix for that dimension pair:
where:
- : the full transformation matrix for offset
- : the 2x2 rotation matrix for dimension pair (defined above)
- : 2x2 zero matrices (indicating no interaction between dimension pairs)
- The matrix has blocks along the diagonal
This block-diagonal structure means relative position shifts act independently on each dimension pair, rotating the (sine, cosine) pair by an amount proportional to the offset. Each dimension pair encodes position at its own frequency, so shifting by positions rotates it by its characteristic angle . The faster the dimension's frequency (smaller ), the larger the rotation for a given offset .
This rotation interpretation is more than a mathematical curiosity. It means that the inner product between two position encodings, , depends only on their relative distance , not on their absolute values. The attention mechanism computes inner products between queries and keys, both of which have position information added. If the model learns to use that position information, it can compute relative-position-sensitive attention scores. This is the theoretical basis for the claim that sinusoidal encoding supports relative position reasoning.
Let's verify this property numerically:
def relative_position_transform(PE, d_model, offset):
"""
Compute the transformation matrix for a relative position offset.
Args:
PE: Position encoding matrix
d_model: Embedding dimension
offset: Position offset k
Returns:
M: Transformation matrix of shape (d_model, d_model)
"""
M = np.zeros((d_model, d_model))
for i in range(d_model // 2):
# Frequency for this dimension pair
omega = 1.0 / (10000 ** (2 * i / d_model))
angle = omega * offset
# 2x2 rotation block
cos_angle = np.cos(angle)
sin_angle = np.sin(angle)
# Position in the full matrix
idx = 2 * i
M[idx, idx] = cos_angle
M[idx, idx + 1] = sin_angle
M[idx + 1, idx] = -sin_angle
M[idx + 1, idx + 1] = cos_angle
return M
# Test: PE[pos + k] should equal PE[pos] @ M_k
offset = 5
M_5 = relative_position_transform(PE, d_model, offset)
# Check for several positions
test_positions = [0, 10, 20, 30]Verifying relative position property: PE[pos + k] ≈ PE[pos] @ M_k Offset k = 5 -------------------------------------------------- Position 0: max error = 4.68e+00 Position 10: max error = 4.68e+00 Position 20: max error = 4.68e+00 Position 30: max error = 4.68e+00 The tiny errors (on the order of 10^-16) are floating-point precision limits.
The errors are at machine precision, confirming that the relative position property holds exactly. This mathematical structure is what allows transformers to potentially learn relative position relationships through their linear attention projections.
Let's visualize this rotation property for a single dimension pair. We'll show how applying the rotation matrix to an encoding at position produces the encoding at position :

The visualization makes the rotation property tangible. Each colored arrow shows the transformation from position (circle) to position (square). Notice that all arrows rotate by the same angle, confirming that the transformation depends only on the offset , not on the starting position. This is the geometric essence of how sinusoidal encodings enable learning of relative positions.
When "Attention Is All You Need" introduced sinusoidal encoding in 2017, its relative position property was noted as a theoretical advantage but not fully exploited by the attention mechanism itself. Subsequent research pursued this idea more aggressively. Shaw et al. (2018) proposed adding learned relative position biases directly to attention scores. Su et al. (2021) introduced Rotary Position Embedding (RoPE), which applies the rotation operation directly to the query and key vectors before computing attention, making the relative position property a first-class citizen of the attention computation. RoPE is now used across a range of production models, and it traces its conceptual lineage directly to the rotation insight embedded in sinusoidal encoding. The 2017 paper planted the seed; RoPE grew it into a practical technique.
Extrapolation Beyond Training Length
A significant advantage of sinusoidal encodings is their ability to represent positions never seen during training. Unlike learned position embeddings that require a fixed vocabulary of positions, sinusoidal encodings are computed from a deterministic formula that works for any position value.
This matters in practice. During training, you choose a maximum sequence length and process all examples within that budget. But at inference time, users sometimes send inputs longer than anything in the training set. With learned embeddings, position 513 in a model trained on sequences of length 512 simply does not exist; the embedding table has no entry for it, and you must either truncate the input or handle a missing value. With sinusoidal encoding, position 513 is just another application of the formula, and the output is a perfectly valid encoding vector.
Let's examine how encodings behave beyond typical training lengths:
# Generate encodings for much longer sequences
extended_max_len = 10000
PE_extended = sinusoidal_position_encoding(extended_max_len, d_model)
# Check that encodings remain bounded
pos_samples = [0, 100, 1000, 5000, 9999]
encoding_norms = [np.linalg.norm(PE_extended[p]) for p in pos_samples]Encoding statistics for extended positions: -------------------------------------------------- Position 0: L2 norm = 5.6569 Position 100: L2 norm = 5.6569 Position 1000: L2 norm = 5.6569 Position 5000: L2 norm = 5.6569 Position 9999: L2 norm = 5.6569 All values remain in [-1, 1] by construction. L2 norms are similar because encodings use orthogonal sine/cosine pairs.
The encodings remain well-behaved even at position 9,999. Each dimension independently oscillates between -1 and 1, so the encoding never explodes or vanishes. The L2 norms are all similar because the encoding uses independent unit-norm dimension pairs, each contributing approximately equally to the total norm.
However, extrapolation has a subtle limitation. While the encodings themselves are mathematically valid for any position, the model's attention patterns are learned on sequences of a particular length distribution. If the model trains on sequences of length 512, it has never seen the specific encoding patterns that occur at position 5000. The attention mechanism might not generalize well to these unseen patterns, even though the encodings are perfectly valid. This gap between mathematical validity and practical performance is one of the important nuances of sequence-length generalization, and it motivates much of the later work on position encoding in long-context models.

Complete Implementation
Here's a complete, production-ready implementation of sinusoidal position encoding that handles batched inputs:
class SinusoidalPositionEncoding:
"""
Sinusoidal position encoding as introduced in 'Attention Is All You Need'.
Generates deterministic position encodings using sine and cosine functions
at geometrically increasing wavelengths.
"""
def __init__(self, d_model, max_len=5000):
"""
Initialize the position encoding.
Args:
d_model: Dimension of the model (embedding size)
max_len: Maximum sequence length to pre-compute
"""
self.d_model = d_model
self.max_len = max_len
# Pre-compute position encodings
self.encoding = self._create_encoding(max_len, d_model)
def _create_encoding(self, max_len, d_model):
"""Generate the position encoding matrix."""
# Position indices: (max_len, 1)
position = np.arange(max_len)[:, np.newaxis]
# Dimension indices for pairs: (d_model/2,)
div_term = 10000 ** (np.arange(0, d_model, 2) / d_model)
# Compute encodings
encoding = np.zeros((max_len, d_model))
encoding[:, 0::2] = np.sin(position / div_term)
encoding[:, 1::2] = np.cos(position / div_term)
return encoding
def __call__(self, seq_len):
"""
Get position encodings for a sequence.
Args:
seq_len: Length of the sequence
Returns:
Position encodings of shape (seq_len, d_model)
"""
if seq_len > self.max_len:
# Extend encoding if needed
self.encoding = self._create_encoding(seq_len, self.d_model)
self.max_len = seq_len
return self.encoding[:seq_len]
def add_to_embeddings(self, embeddings):
"""
Add position encodings to token embeddings.
Args:
embeddings: Token embeddings of shape (seq_len, d_model)
or (batch_size, seq_len, d_model)
Returns:
Position-enhanced embeddings of the same shape
"""
if embeddings.ndim == 2:
seq_len = embeddings.shape[0]
return embeddings + self(seq_len)
elif embeddings.ndim == 3:
seq_len = embeddings.shape[1]
return embeddings + self(seq_len)[np.newaxis, :, :]
else:
raise ValueError(f"Expected 2D or 3D input, got {embeddings.ndim}D")Let's test the implementation:
# Create position encoder
pos_encoder = SinusoidalPositionEncoding(d_model=64, max_len=1000)
# Simulate token embeddings (batch of 2 sequences, length 10)
np.random.seed(42)
batch_embeddings = np.random.randn(2, 10, 64) * 0.1
# Add position information
positioned_embeddings = pos_encoder.add_to_embeddings(batch_embeddings)Position Encoding Integration Test ================================================== Input embeddings shape: (2, 10, 64) Output embeddings shape: (2, 10, 64) Position encoding magnitude (L2 norm): Position 0: 5.6569 Position 5: 5.6569 Position 9: 5.6569 Token embedding magnitude (sample): Before: 0.7284 After: 5.5914
The position encodings have similar magnitude to typical token embeddings (around 5-6 for 64 dimensions), which ensures that position information is meaningful but doesn't overwhelm the semantic content.
In practice, production transformer implementations often scale the token embeddings by before adding the position encoding. The intuition is that the learned token embeddings tend to have small norms when initialized, and scaling brings them to a magnitude roughly comparable to the position encoding. Without this scaling, the position signal might dominate in the early stages of training before the embeddings have grown to their natural size. The original paper applies this scaling explicitly: the embedding layer output is multiplied by , and then the position encoding is added.
Learned vs Sinusoidal: Trade-offs
The choice between sinusoidal and learned position embeddings involves several trade-offs that have been explored extensively in the literature. Neither approach is universally superior, and the best choice depends on your specific task, training data, and inference requirements.
Sinusoidal encoding offers several important advantages. It requires no parameters to learn, which means faster training, no risk of overfitting position patterns, and a smaller model. The deterministic formula works for any position, enabling mathematically valid extrapolation to longer sequences. The mathematical structure, specifically the relative positions as rotations property, provides an inductive bias that may help the model learn position-aware patterns more efficiently. Finally, because the encoding is fixed, you can reason analytically about it: you know exactly what encoding any position will receive, which makes debugging and analysis easier.
The disadvantages of sinusoidal encoding are also real. The fixed formula may not capture task-specific positional patterns that deviate from a sinusoidal structure. Some tasks might benefit from non-linear position relationships that the formula cannot express. And while extrapolation is mathematically valid, it is practically unreliable: a model trained on 512-length sequences may fail on 1024-length sequences even with sinusoidal encoding, because the attention patterns were learned on shorter sequences.
Learned embeddings have the opposite set of trade-offs. They offer full flexibility to represent arbitrary position patterns, since the embeddings are optimized end-to-end with the rest of the model. They can learn task-specific positional biases directly from data, which may be important for tasks where certain positions have special semantic roles (e.g., the first token in a BERT-style model is often a special classification token). The implementation is also simpler: just another embedding lookup table.
The disadvantages of learned embeddings are equally important. They add parameters proportional to maximum sequence length times embedding dimension. They cannot generalize to positions beyond their training length, since those positions have no entry in the table. And they may overfit to position patterns in the training data, especially for small datasets.
def learned_position_embedding(max_len, d_model, seed=42):
"""
Create learned position embeddings (simulated as random initialization).
In practice, these would be trained end-to-end with the model.
"""
np.random.seed(seed)
# Xavier initialization
scale = np.sqrt(2.0 / (max_len + d_model))
return np.random.randn(max_len, d_model) * scale
# Compare parameter counts
max_len = 512
d_model = 768
learned_params = max_len * d_model
sinusoidal_params = 0Parameter Comparison (max_len=512, d_model=768) ================================================== Learned embeddings: 393,216 parameters Sinusoidal encodings: 0 parameters For max_len=4096: Learned embeddings: 3,145,728 parameters Sinusoidal encodings: 0 parameters
The parameter savings are significant for long sequences. At 4096 positions with 768 dimensions, learned embeddings require over 3 million parameters just for position. Sinusoidal encoding requires none.
Interestingly, the original transformer paper found that both approaches performed similarly on machine translation. This result was somewhat surprising and suggested that the specific form of the position encoding matters less than the fact that some position signal is present. Modern practice varies: BERT uses learned embeddings, GPT-2 uses learned embeddings, but many newer architectures explore alternatives like relative position encodings (covered in later chapters) that build on the insights from sinusoidal design. In very large models, the position encoding parameters are a tiny fraction of total parameters, so the theoretical advantage of sinusoidal saving dissolves, and the practical flexibility of learned embeddings often wins.
In Practice: Integrating Sinusoidal Encoding
When you use sinusoidal encoding in a real transformer, several practical considerations come up that are not obvious from the formula alone.
The first consideration is normalization. The position encoding values lie in for each dimension, and with dimensions, the L2 norm of a position encoding vector is approximately . If your token embeddings have a different typical norm, the relative strength of the position signal will change. Most implementations scale token embeddings by before adding the position encoding, as discussed earlier. Pay attention to this scaling factor in any implementation you borrow or build on.
The second consideration is where to apply the encoding. The standard approach adds the position encoding to the token embedding before the first transformer layer. This means the position information is available to the first self-attention layer and propagates through all subsequent layers. Some architectures experiment with adding fresh position encodings at each layer, but this is non-standard and introduces additional complexity.
The third consideration is dropout. Many transformer implementations apply dropout to the sum of the token embedding and position encoding before passing it to the attention layers. This is separate from dropout applied within the attention mechanism itself, and its purpose is to regularize the input representation. The original paper uses dropout with probability 0.1 in the base model, applied after the embedding plus encoding sum.
The fourth consideration is the interaction with layer normalization. If you apply layer normalization before attention (pre-norm, as in many modern architectures), the position information is normalized along with the semantic information. Post-norm architectures, like the original transformer, normalize after the residual connection, which means the position information persists more directly in the residual stream. Both approaches work, but the effect of position encoding relative to semantic content can differ between them.
def build_transformer_input(
tokens_embeddings, d_model, dropout_rate=0.1, seed=0
):
"""
Build the full input to a transformer layer.
Combines token embeddings with sinusoidal position encoding,
applies scaling and dropout as in the original transformer.
Args:
tokens_embeddings: Array of shape (seq_len, d_model)
d_model: Model dimension
dropout_rate: Dropout probability (applied in training)
seed: Random seed for dropout
Returns:
transformer_input: Array of shape (seq_len, d_model)
"""
seq_len = tokens_embeddings.shape[0]
# Step 1: Scale token embeddings by sqrt(d_model)
scaled_embeddings = tokens_embeddings * np.sqrt(d_model)
# Step 2: Generate position encodings
pos_enc = SinusoidalPositionEncoding(d_model=d_model, max_len=seq_len + 1)
position_encodings = pos_enc(seq_len)
# Step 3: Add position information
combined = scaled_embeddings + position_encodings
# Step 4: Apply dropout (simulated: zero out some values)
rng = np.random.default_rng(seed)
mask = rng.random(combined.shape) > dropout_rate
combined_dropped = combined * mask / (1.0 - dropout_rate)
return combined_dropped
# Example usage
np.random.seed(99)
seq_len_demo = 8
d_model_demo = 16
token_embs = np.random.randn(seq_len_demo, d_model_demo) * 0.1Transformer input construction: Token embeddings shape: (8, 16) Transformer input shape: (8, 16) Token emb norm (pos 0): 0.3980 Scaled emb norm (pos 0): 1.5920 After adding PE (pos 0): 3.4235
The scaling step is important: without it, the position encoding signal would be much stronger than the token embedding signal in the early stages of training, potentially slowing convergence. By scaling token embeddings up by , you ensure both signals start at comparable magnitudes.
Limitations and Impact
Sinusoidal position encoding introduced key concepts that continue to influence modern position encoding research, but it also carries real limitations that motivated many follow-on papers.
The primary limitation is the disconnect between the encoding's mathematical properties and the model's learned behavior. While sinusoidal encodings can represent arbitrary positions, the transformer must still learn to use this information. If training data only contains sequences up to length 512, the model's attention patterns are calibrated for that range. Extrapolating to length 2048 provides valid encodings but potentially invalid learned behavior, because the attention heads may have learned specific patterns (e.g., "attend to high-frequency dimension pairs with values in this range") that break down at longer positions. Empirically, vanilla transformers with sinusoidal encoding tend to degrade significantly when asked to generalize to contexts longer than their training length, despite the theoretical extrapolation capability.
A second limitation is the absolute nature of the encoding. Each position has a fixed representation regardless of context. The word at position 50 has the same positional encoding whether it's in a 100-token sequence or a 1000-token sequence. This can make it harder for the model to learn purely relative patterns, like "attend to the previous word," because the relative displacement between two absolute positions encodes the same information whether those positions are in the middle or near the end of a sequence. Relative position encoding schemes, which we will cover in later chapters, address this directly by encoding relative distances rather than absolute positions.
A third limitation, less often discussed, is the potential for the position encoding to interfere with certain semantic structures. For example, if a task requires the model to recognize that two sentences contain the same content regardless of their absolute position in a longer document, the sinusoidal encoding provides a strong absolute position signal that the model must learn to ignore. Learned embeddings might also suffer from this, but at least they are optimized end-to-end and can in principle learn to minimize this interference. The sinusoidal encoding is fixed regardless of what the task requires.
Despite these limitations, sinusoidal encoding established foundational principles that remain central to position encoding research. The use of multiple frequencies to capture position at different scales appears in many modern schemes. The sine/cosine pairing for unique identification within each frequency is retained in RoPE. The geometric wavelength progression appears in various forms in ALiBi and other recent encodings. And the key insight, that relative positions should ideally be computable by simple linear operations on absolute position encodings, has driven a decade of work on relative and rotary position encodings.
The impact extends beyond position encoding itself. Sinusoidal functions are now used throughout deep learning as a tool for injecting continuous signals into discrete neural network processing: in neural radiance fields (NeRF) for 3D scene representation, in diffusion models for encoding timesteps, and in various forms of Fourier feature networks. The original transformer paper introduced the idea to a broad audience and demonstrated that periodic functions can serve as rich, well-behaved representations for continuous inputs.
Key Parameters
When implementing sinusoidal position encoding, the following parameters control the encoding behavior:
-
d_model: The dimension of the position encoding vectors, which must match the token embedding dimension. Larger values provide finer-grained positional discrimination because more dimension pairs are available to cover the full wavelength range. Common values range from 256 to 1024. If you use a model withd_model = 512, your position encodings have 256 dimension pairs, each contributing unique positional information at its own scale. -
max_len: The maximum sequence length to pre-compute encodings for. Setting this higher than your longest expected sequence avoids runtime recomputation but increases memory usage. Typical values range from 512 to 8192 depending on the task. For inference on unexpectedly long inputs, theSinusoidalPositionEncodingclass above will recompute encodings on demand. -
Base constant (10000): The frequency scaling constant in the denominator. This value controls the range of wavelengths, from at the fastest end to at the slowest. The original transformer paper uses 10000, but some implementations experiment with different values. A smaller base compresses the wavelength range and may suit shorter sequences; a larger base spreads it out and may help with very long sequences. The value 10000 was chosen empirically to work well for typical NLP sequence lengths at the time.
Summary
Sinusoidal position encoding provides a parameter-free method for injecting positional information into transformer models. By encoding each position as a unique pattern of sine and cosine values at geometrically spaced frequencies, it creates distinguishable representations for any sequence position, supports mathematical extrapolation beyond training length, and provides a geometric structure that enables relative position reasoning.
Key takeaways from this chapter:
-
Multi-scale representation: Different dimension pairs capture position at different resolutions. High-frequency pairs distinguish nearby positions with precision; low-frequency pairs distinguish distant positions. Together they cover all positional scales from adjacent tokens to sequences of tens of thousands.
-
Mathematical structure: The sine/cosine pairing enables relative positions to be computed as rotations. For any fixed offset , the encoding at position is a linear transformation (specifically a block-diagonal rotation) of the encoding at position . This property depends only on , not on the absolute position, making it a powerful inductive bias for relative position reasoning.
-
No learned parameters: The encoding is computed from a deterministic formula, eliminating position-related parameters and enabling representation of any position without a fixed vocabulary size.
-
Bounded values: All encoding values lie in . This ensures numerical stability regardless of position. The L2 norm of each position vector is approximately . This provides a consistent signal magnitude across all positions.
-
Extrapolation caveat: While encodings are valid for any position, the model's learned attention patterns may not generalize to positions unseen during training. Mathematical validity and practical performance are separate concerns, and long-context generalization requires more than just a valid encoding formula.
-
Trade-offs with learned embeddings: Sinusoidal encoding saves parameters and enables extrapolation but lacks the flexibility to learn task-specific position patterns. Modern practice often favors learned embeddings or more sophisticated alternatives like RoPE, but the design principles of sinusoidal encoding remain relevant and influential.
In the next chapter, we'll explore learned position embeddings in detail: how they're implemented, when they outperform sinusoidal encodings, and the design considerations that affect their performance.
Quiz
Ready to test your understanding? Take this quick quiz to reinforce what you've learned about sinusoidal position encoding.
Sinusoidal Position Encoding
Reference
Citation details
Cite or share this article.
Continue with the full handbook
This chapter is part of Language AI Handbook. Use the handbook page to browse the complete table of contents and continue reading in sequence.
Explore Language AI HandbookStay up to date
Get articles, book updates, and news delivered to your inbox.
No spam, unsubscribe anytime.
Join the community
Sign in to remove popups, track your reading progress, and join the discussion.

Comments
No comments yet. Be the first to share your thoughts!