Sinusoidal Position Encoding

Michael BrenndoerferUpdated June 6, 202552 min read

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 pospos in the sequence receives a dd-dimensional vector, where consecutive pairs of dimensions use sine and cosine at the same frequency:

PE(pos,2i)=sin⁡(pos100002i/d)PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d}}\right) PE(pos,2i+1)=cos⁡(pos100002i/d)PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d}}\right)

where:

  • pospos: the position index in the sequence (0, 1, 2, ...)
  • ii: the dimension index pair (0, 1, 2, ..., d/2−1d/2 - 1)
  • dd: the total embedding dimension
  • PE(pos,2i)PE_{(pos, 2i)}: the encoding value at position pospos, even dimension 2i2i
  • PE(pos,2i+1)PE_{(pos, 2i+1)}: the encoding value at position pospos, odd dimension 2i+12i+1
  • 1000010000: a base constant that controls the frequency range

Notice that for a fixed position pospos, as we scan across the dd 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 100002i/d10000^{2i/d}. This term controls how fast each dimension pair oscillates as position increases. Let's unpack what happens at different dimension indices.

When i=0i = 0 (the first dimension pair), the denominator is 100000=110000^0 = 1, so we compute sin⁡(pos)\sin(pos) and cos⁡(pos)\cos(pos). This oscillates rapidly: moving from position 0 to position 6 covers roughly one full cycle.

When i=d/2−1i = d/2 - 1 (the last dimension pair), the denominator is approximately 100001=1000010000^1 = 10000, so we compute sin⁡(pos/10000)\sin(pos/10000) and cos⁡(pos/10000)\cos(pos/10000). This oscillates extremely slowly: you need 62,832 positions to complete one full cycle.

The exponent 2i/d2i/d creates a geometric progression of frequencies. As ii increases from 0 to d/2−1d/2 - 1, 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 2π2\pi. 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.

Sinusoidal Position Encoding

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 dd-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 d/2d/2 "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 ii, the wavelength (the number of positions needed for one complete oscillation cycle) is:

λi=2π⋅100002i/d\lambda_i = 2\pi \cdot 10000^{2i/d}

where:

  • λi\lambda_i: the wavelength for dimension pair ii (measured in positions per cycle)
  • 100002i/d10000^{2i/d}: the frequency denominator that grows geometrically with ii
  • 2π2\pi: the angular measure of one complete cycle (in radians)

This formula reveals the geometric progression at the heart of sinusoidal encoding:

  • First dimension pair (i=0i = 0): Wavelength is 2π≈6.282\pi \approx 6.28 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 (i=d/2−1i = d/2 - 1): Wavelength is approximately 2π⋅10000≈62,8322\pi \cdot 10000 \approx 62,832 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.

In[3]:
Code
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))
Out[4]:
Visualization
Log-scale line plot showing wavelength increasing exponentially from about 6 to 63000 across 256 dimension pairs.
Wavelengths grow geometrically across dimension pairs in a 512-dimensional encoding. The first dimension pair has a wavelength of about 6.28 positions (one full cycle every six positions), while the final pair has a wavelength near 62,832 positions. This exponential spread ensures every scale of positional separation is covered by at least one dimension pair.

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.

In[5]:
Code
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)
Out[6]:
Console
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 sin⁡(0)=0\sin(0) = 0 and cos⁡(0)=1\cos(0) = 1. 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:

Out[7]:
Visualization
Heatmap showing alternating light and dark bands that oscillate rapidly on the left side and slowly on the right side.
Heatmap of sinusoidal position encodings for 100 positions and 64 dimensions. Each row is a position (0-99) and each column is a dimension (0-63). The rapidly alternating bands on the left correspond to high-frequency dimension pairs that change quickly with position, while the slowly varying gradients on the right correspond to low-frequency pairs. Every row (position) is distinct. This shows that the encoding uniquely identifies each position.

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:

Out[8]:
Visualization
Line plot showing sine and cosine waves completing about 15 cycles over 100 positions.
High-frequency encoding: dimension pair i=0 completes many cycles within 100 positions. The sine (blue) and cosine (red) waves are identical in shape but shifted by a quarter cycle. This keeps each position maps to a unique point on the unit circle.
Line plot showing sine and cosine waves completing about 2 cycles over 100 positions.
Lower-frequency encoding: dimension pair i=16 oscillates much more slowly. The same 100-position window covers far fewer cycles. This gives coarser positional discrimination that complements the high-frequency pairs.

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:

Out[9]:
Visualization
Scatter plot showing points tracing multiple circular loops, with color gradient indicating position.
Unit circle trajectory for the first dimension pair (i=0) across the first 50 positions. The high-frequency oscillation drives the encoding rapidly around the circle, completing multiple loops. Even though different positions may occupy similar angles, the other dimension pairs resolve any ambiguity.
Scatter plot showing points tracing a partial arc of a circle, with color gradient indicating position.
Unit circle trajectory for dimension pair i=16 across the same 50 positions. The much slower frequency means the encoding only traces a small arc. This gives coarse positional information that distinguishes positions separated by large gaps rather than adjacent steps.

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 sin⁡2(θ)+cos⁡2(θ)=1\sin^2(\theta) + \cos^2(\theta) = 1. 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 pos1pos_1 and pos2pos_2. 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 2π⋅100002i/d2\pi \cdot 10000^{2i/d} for different ii 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:

In[10]:
Code
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)
Out[11]:
Visualization
Heatmap showing pairwise distances between position encodings, with zero on the diagonal and increasing values off-diagonal.
Pairwise Euclidean distances between the first 50 position encodings. The diagonal is zero (each position is identical to itself). Nearby positions have smaller distances (warm colors near the diagonal), while distant positions have larger distances (cooler colors far from the diagonal). The symmetric banded structure confirms that distance grows smoothly with positional separation.

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:

Out[12]:
Visualization
Line plot showing encoding distance on y-axis versus positional separation on x-axis, with smooth growth at small separations and oscillatory plateau at larger separations.
Encoding distance from position 0 to all other positions in a 100-position sequence. Distance rises steeply for small separations, then oscillates around a plateau for larger separations. The oscillation arises because the high-frequency dimensions occasionally return to similar values while the low-frequency dimensions still distinguish the positions. The dashed red line shows the mean distance for separations greater than 20.

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 d=8d = 8 dimensions, small enough to trace through by hand for the first few values.

With d=8d = 8, we have d/2=4d/2 = 4 dimension pairs, numbered i=0,1,2,3i = 0, 1, 2, 3. The frequency denominators for each pair are:

i=0:100000/8=100000=1i=1:100002/8=100000.25≈17.78i=2:100004/8=100000.5=100i=3:100006/8=100000.75≈562.3\begin{aligned} i = 0: \quad 10000^{0/8} &= 10000^{0} = 1 \\ i = 1: \quad 10000^{2/8} &= 10000^{0.25} \approx 17.78 \\ i = 2: \quad 10000^{4/8} &= 10000^{0.5} = 100 \\ i = 3: \quad 10000^{6/8} &= 10000^{0.75} \approx 562.3 \end{aligned}

The wavelengths for each pair are:

  • Pair 0: λ=2π⋅1≈6.28\lambda = 2\pi \cdot 1 \approx 6.28 positions
  • Pair 1: λ=2π⋅17.78≈111.7\lambda = 2\pi \cdot 17.78 \approx 111.7 positions
  • Pair 2: λ=2π⋅100≈628\lambda = 2\pi \cdot 100 \approx 628 positions
  • Pair 3: λ=2π⋅562.3≈3532\lambda = 2\pi \cdot 562.3 \approx 3532 positions

For position 0 (the token "The"), every sine term evaluates to sin⁡(0)=0\sin(0) = 0 and every cosine term evaluates to cos⁡(0)=1\cos(0) = 1, giving:

PE0=[0,1,0,1,0,1,0,1]PE_0 = [0, 1, 0, 1, 0, 1, 0, 1]

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:

PE1,0=sin⁡(1/1)=sin⁡(1)≈0.841PE1,1=cos⁡(1/1)=cos⁡(1)≈0.540PE1,2=sin⁡(1/17.78)≈sin⁡(0.0562)≈0.056PE1,3=cos⁡(1/17.78)≈cos⁡(0.0562)≈0.998PE1,4=sin⁡(1/100)=sin⁡(0.01)≈0.010PE1,5=cos⁡(1/100)=cos⁡(0.01)≈1.000PE1,6=sin⁡(1/562.3)≈sin⁡(0.00178)≈0.002PE1,7=cos⁡(1/562.3)≈cos⁡(0.00178)≈1.000\begin{aligned} PE_{1,0} &= \sin(1/1) = \sin(1) \approx 0.841 \\ PE_{1,1} &= \cos(1/1) = \cos(1) \approx 0.540 \\ PE_{1,2} &= \sin(1/17.78) \approx \sin(0.0562) \approx 0.056 \\ PE_{1,3} &= \cos(1/17.78) \approx \cos(0.0562) \approx 0.998 \\ PE_{1,4} &= \sin(1/100) = \sin(0.01) \approx 0.010 \\ PE_{1,5} &= \cos(1/100) = \cos(0.01) \approx 1.000 \\ PE_{1,6} &= \sin(1/562.3) \approx \sin(0.00178) \approx 0.002 \\ PE_{1,7} &= \cos(1/562.3) \approx \cos(0.00178) \approx 1.000 \end{aligned}

So PE1≈[0.841,0.540,0.056,0.998,0.010,1.000,0.002,1.000]PE_1 \approx [0.841, 0.540, 0.056, 0.998, 0.010, 1.000, 0.002, 1.000].

The pattern is clear: in the first dimension pair (i=0i = 0), there is already a large change from position 0 to position 1 (from [0,1][0, 1] to [0.841,0.540][0.841, 0.540]), because the wavelength is only about 6. In the later dimension pairs (i=2i = 2 and i=3i = 3), 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: sin⁡(4)≈−0.757\sin(4) \approx -0.757 and cos⁡(4)≈−0.654\cos(4) \approx -0.654. 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 i=2i = 2 and i=3i = 3 have barely moved from their position-0 values, since 4 is much less than the wavelengths of 628 and 3532.

In[13]:
Code
# 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)
Out[14]:
Console
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
Out[15]:
Visualization
Small heatmap showing 5 rows and 8 columns of position encoding values for a short example sequence.
Position encodings for a 5-token sequence with d=8 dimensions. Each row is one token position and each column is one dimension. Notice that position 0 ('The') has all-zero sine values and all-one cosine values. High-frequency dimensions (left) change substantially between adjacent positions, while low-frequency dimensions (right) barely change across the entire 5-token sequence.

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 kk, there exists a matrix MkM_k such that:

PEpos+k=PEpos⋅MkPE_{pos+k} = PE_{pos} \cdot M_k

where:

  • PEposPE_{pos}: the position encoding vector at position pospos (a dd-dimensional row vector)
  • PEpos+kPE_{pos+k}: the position encoding vector at position pos+kpos + k
  • MkM_k: a d×dd \times d transformation matrix that depends only on the offset kk, not on the absolute position

This is a remarkable property. It means that the relationship between any two encodings that are kk 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 aa and bb, trigonometry gives us:

sin⁡(a+b)=sin⁡(a)cos⁡(b)+cos⁡(a)sin⁡(b)\sin(a + b) = \sin(a)\cos(b) + \cos(a)\sin(b) cos⁡(a+b)=cos⁡(a)cos⁡(b)−sin⁡(a)sin⁡(b)\cos(a + b) = \cos(a)\cos(b) - \sin(a)\sin(b)

where aa and bb 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 ii, we define the angular frequency as:

ωi=1100002i/d\omega_i = \frac{1}{10000^{2i/d}}

where:

  • ωi\omega_i: the angular frequency for dimension pair ii (determines how fast this dimension oscillates)
  • ii: the dimension pair index (0, 1, 2, ..., d/2−1d/2 - 1)
  • dd: the total embedding dimension
  • 100002i/d10000^{2i/d}: the denominator that grows geometrically with ii

This means the encoding at position pospos in dimension pair ii uses the argument ωi⋅pos\omega_i \cdot pos.

Step 3: Apply the addition formulas. To find the encoding at position pos+kpos + k, we substitute a=ωi⋅posa = \omega_i \cdot pos and b=ωi⋅kb = \omega_i \cdot k into the angle addition formulas:

sin⁡(ωi(pos+k))=sin⁡(ωi⋅pos)cos⁡(ωik)+cos⁡(ωi⋅pos)sin⁡(ωik)\sin(\omega_i(pos + k)) = \sin(\omega_i \cdot pos)\cos(\omega_i k) + \cos(\omega_i \cdot pos)\sin(\omega_i k) cos⁡(ωi(pos+k))=cos⁡(ωi⋅pos)cos⁡(ωik)−sin⁡(ωi⋅pos)sin⁡(ωik)\cos(\omega_i(pos + k)) = \cos(\omega_i \cdot pos)\cos(\omega_i k) - \sin(\omega_i \cdot pos)\sin(\omega_i k)

where:

  • ωi=1/100002i/d\omega_i = 1/10000^{2i/d}: the angular frequency for dimension pair ii
  • pospos: the current position in the sequence
  • kk: the position offset we want to shift by
  • sin⁡(ωi⋅pos)\sin(\omega_i \cdot pos) and cos⁡(ωi⋅pos)\cos(\omega_i \cdot pos): the original encoding values at position pospos (these are PE(pos,2i)PE_{(pos, 2i)} and PE(pos,2i+1)PE_{(pos, 2i+1)})
  • sin⁡(ωik)\sin(\omega_i k) and cos⁡(ωik)\cos(\omega_i k): constants that depend only on the offset kk, not on the absolute position

The last two terms are fixed constants once you fix kk and ii: the values sin⁡(ωik)\sin(\omega_i k) and cos⁡(ωik)\cos(\omega_i k) do not change as you vary pospos. 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 sin⁡(ωi⋅pos)\sin(\omega_i \cdot pos) and cos⁡(ωi⋅pos)\cos(\omega_i \cdot pos). This is exactly what matrix multiplication does. We can write:

[sin⁡(ωi(pos+k))cos⁡(ωi(pos+k))]=[cos⁡(ωik)sin⁡(ωik)−sin⁡(ωik)cos⁡(ωik)][sin⁡(ωi⋅pos)cos⁡(ωi⋅pos)]\begin{bmatrix} \sin(\omega_i(pos + k)) \\ \cos(\omega_i(pos + k)) \end{bmatrix} = \begin{bmatrix} \cos(\omega_i k) & \sin(\omega_i k) \\ -\sin(\omega_i k) & \cos(\omega_i k) \end{bmatrix} \begin{bmatrix} \sin(\omega_i \cdot pos) \\ \cos(\omega_i \cdot pos) \end{bmatrix}

This is a rotation in 2D. For each dimension pair, the encoding at pos+kpos + k is the encoding at pospos rotated by angle ωik\omega_i k. The rotation matrix for offset kk in dimension pair ii is:

Rk(i)=[cos⁡(ωik)sin⁡(ωik)−sin⁡(ωik)cos⁡(ωik)]R_k^{(i)} = \begin{bmatrix} \cos(\omega_i k) & \sin(\omega_i k) \\ -\sin(\omega_i k) & \cos(\omega_i k) \end{bmatrix}

where:

  • Rk(i)R_k^{(i)}: the 2x2 rotation matrix for dimension pair ii with offset kk
  • ωi=1/100002i/d\omega_i = 1/10000^{2i/d}: the angular frequency for dimension pair ii
  • kk: the position offset (how many positions to shift)
  • ωik\omega_i k: 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 MkM_k is block-diagonal, with each 2x2 block being the rotation matrix for that dimension pair:

Mk=[Rk(0)0⋯00Rk(1)⋯0⋮⋮⋱⋮00⋯Rk(d/2−1)]M_k = \begin{bmatrix} R_k^{(0)} & 0 & \cdots & 0 \\ 0 & R_k^{(1)} & \cdots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & R_k^{(d/2-1)} \end{bmatrix}

where:

  • MkM_k: the full d×dd \times d transformation matrix for offset kk
  • Rk(i)R_k^{(i)}: the 2x2 rotation matrix for dimension pair ii (defined above)
  • 00: 2x2 zero matrices (indicating no interaction between dimension pairs)
  • The matrix has d/2d/2 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 kk positions rotates it by its characteristic angle ωik\omega_i k. The faster the dimension's frequency (smaller ii), the larger the rotation for a given offset kk.

This rotation interpretation is more than a mathematical curiosity. It means that the inner product between two position encodings, PEpos1⋅PEpos2PE_{pos_1} \cdot PE_{pos_2}, depends only on their relative distance ∣pos1−pos2∣|pos_1 - pos_2|, 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:

In[16]:
Code
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]
Out[17]:
Console
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 pospos produces the encoding at position pos+kpos + k:

Out[18]:
Visualization
Plot showing position encodings as points on a circle with arrows indicating rotation from each position to its offset position.
Rotation property of sinusoidal encodings for dimension pair i=0. Each colored dot (circle) shows an original position encoding, and the matching square shows the encoding after a fixed offset of k=5. All arrows rotate by the same angle, confirming that the shift transformation depends only on the offset k, not on which absolute position you start from. This is the geometric essence of the relative position property.

The visualization makes the rotation property tangible. Each colored arrow shows the transformation from position pospos (circle) to position pos+5pos + 5 (square). Notice that all arrows rotate by the same angle, confirming that the transformation depends only on the offset kk, not on the starting position. This is the geometric essence of how sinusoidal encodings enable learning of relative positions.

Historical Context: From Sinusoidal Encoding to RoPE

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:

In[19]:
Code
# 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]
Out[20]:
Console
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 d/2d/2 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.

Out[21]:
Visualization
Line plot showing sinusoidal encoding values for a single dimension across positions 0 to 10000, with smooth continuous oscillation.
A single encoding dimension (dimension 20) plotted across 10,000 positions. The sinusoidal pattern continues smoothly and remains bounded throughout, confirming that the encoding is mathematically valid far beyond any practical training length. The shaded region marks a typical training range of up to 512 positions; positions beyond this boundary receive valid encodings but may not be interpreted correctly by a model that has only seen the shaded region during training.

Complete Implementation

Here's a complete, production-ready implementation of sinusoidal position encoding that handles batched inputs:

In[22]:
Code
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:

In[23]:
Code
# 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)
Out[24]:
Console
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 d_model\sqrt{d\_model} before adding the position encoding. The intuition is that the learned token embeddings tend to have small norms when initialized, and d_model\sqrt{d\_model} 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 d_model\sqrt{d\_model}, 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.

In[25]:
Code
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 = 0
Out[26]:
Console
Parameter 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 [−1,1][-1, 1] for each dimension, and with dd dimensions, the L2 norm of a position encoding vector is approximately d/2\sqrt{d/2}. If your token embeddings have a different typical norm, the relative strength of the position signal will change. Most implementations scale token embeddings by d_model\sqrt{d\_model} 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.

In[27]:
Code
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.1
Out[28]:
Console
Transformer 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 d_model\sqrt{d\_model}, 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 with d_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, the SinusoidalPositionEncoding class above will recompute encodings on demand.

  • Base constant (10000): The frequency scaling constant in the denominator. This value controls the range of wavelengths, from 2π2\pi at the fastest end to 2π×100002\pi \times 10000 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 kk, the encoding at position pos+kpos + k is a linear transformation (specifically a block-diagonal rotation) of the encoding at position pospos. This property depends only on kk, 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 [−1,1][-1, 1]. This ensures numerical stability regardless of position. The L2 norm of each position vector is approximately d/2\sqrt{d/2}. 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

Question 1 of 80 of 8 completed
Why do transformers need position encoding?

Comments

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

Reference

Citation details

Cite or share this article.

BIBTEXAcademic
@misc{brenndoerfer2025sinusoidalposition, author = {Michael Brenndoerfer}, title = {Sinusoidal Position Encoding}, year = {2025}, url = {https://mbrenndoerfer.com/writing/sinusoidal-position-encoding-transformers-word-order}, organization = {mbrenndoerfer.com}, note = {Accessed: 2026-09-23} }
APAAcademic
Michael Brenndoerfer (2025). Sinusoidal Position Encoding. Retrieved from https://mbrenndoerfer.com/writing/sinusoidal-position-encoding-transformers-word-order
MLAAcademic
Michael Brenndoerfer. "Sinusoidal Position Encoding." 2026. Web. September 23, 2026. <https://mbrenndoerfer.com/writing/sinusoidal-position-encoding-transformers-word-order>.
CHICAGOAcademic
Michael Brenndoerfer. "Sinusoidal Position Encoding." Accessed September 23, 2026. https://mbrenndoerfer.com/writing/sinusoidal-position-encoding-transformers-word-order.
HARVARDAcademic
Michael Brenndoerfer (2025) 'Sinusoidal Position Encoding'. Available at: https://mbrenndoerfer.com/writing/sinusoidal-position-encoding-transformers-word-order (Accessed: September 23, 2026).
SimpleBasic
Michael Brenndoerfer (2025). Sinusoidal Position Encoding. https://mbrenndoerfer.com/writing/sinusoidal-position-encoding-transformers-word-order

About the author

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 Handbook
Newsletter

Stay up to date

Get articles, book updates, and news delivered to your inbox.

No spam, unsubscribe anytime.

or

Join the community

Sign in to remove popups, track your reading progress, and join the discussion.