ML Grind

Sinusoidal Positional Encoding - Transformer Architecture

Implement Sinusoidal Positional Encoding in Python with NumPy. medium exercise with examples, hints, and instant feedback. No setup or signup required.

Sinusoidal Positional Encoding

What you'll build

The fixed position table from "Attention Is All You Need": one d_model-dimensional vector per position, made of sines and cosines at geometrically spaced frequencies. It is added to the token embeddings.

Background

Attention is permutation-invariant: shuffle the tokens and each output is shuffled the same way. Without position information, "dog bites man" and "man bites dog" look identical. The sinusoidal table gives every position a unique pattern, and because sin/cos at a fixed frequency rotate as the position advances, PE[pos + k] is a linear function of PE[pos] for every offset k. Modern LLMs mostly use RoPE (later in this section) instead, which applies the same frequencies as rotations inside attention.

Formula

For position pos and pair index i = 0, 1, ..., d_model/2 - 1:

omega_i         = 1 / 10000^(2i / d_model)
PE[pos, 2i]     = sin(pos * omega_i)
PE[pos, 2i + 1] = cos(pos * omega_i)

Column pair (2i, 2i+1) shares the frequency omega_i; frequencies go from 1 (fast) down to about 1/10000 (slow).

Inputs

  • max_len: int, number of positions (rows), positions are 0 .. max_len - 1
  • d_model: even int, embedding width

Expected Return Format

PE: float array (max_len, d_model), values in [-1, 1].

Edge cases and conventions

  • Even columns are sines, odd columns are cosines (interleaved, not first half / second half).
  • Row 0 is [0, 1, 0, 1, ...] because sin(0) = 0 and cos(0) = 1.
  • Computing omega_i as np.exp(-2i * ln(10000) / d_model) is equivalent and avoids huge powers.

Constraints

NumPy only, vectorised (an outer product of positions and frequencies, no Python loops).

Follow-up

  • Why does PE[pos + k] = R_k @ PE[pos] for a block-diagonal rotation R_k?
  • Learned position embeddings (GPT-2, used in Mini GPT) vs sinusoidal vs RoPE: which ones extrapolate past the training length?

Examples

Input: positional_encoding(3, 4)
Output: [[0.0, 1.0, 0.0, 1.0], [0.8415, 0.5403, 0.01, 1.0], [0.9093, -0.4161, 0.02, 0.9998]]

Frequencies are 1 and 1/100: columns 0-1 are sin/cos(pos), columns 2-3 are sin/cos(pos / 100).

Input: positional_encoding(100, 512).shape
Output: (100, 512)

One row per position.

Practice in Python

import numpy as np

def positional_encoding(max_len, d_model):
    """
    Generate sinusoidal positional encodings.

    Args:
        max_len: Number of positions (rows)
        d_model: Model dimension (even)

    Returns:
        PE: (max_len, d_model); PE[pos, 2i] = sin(pos * w_i),
            PE[pos, 2i+1] = cos(pos * w_i), w_i = 1 / 10000^(2i / d_model)
    """
    # Your code here
    pass

The interactive editor loads with Python and NumPy. Run your implementation against 6 tests, or use the progressive hints.

More exercises in this section →