Positional Encoding

Why Order Needs Help

Self-attention looks at all tokens at once, so by itself it cannot tell "dog bites man" from "man bites dog". The model adds position information to each token vector. The original Transformer (2017) used fixed sine and cosine waves; most modern LLMs use RoPE (rotary position embeddings), which rotates the query and key vectors by an angle that depends on the position.

PE(pos, 2i)   = sin(pos / 10000^(2i / d))
PE(pos, 2i+1) = cos(pos / 10000^(2i / d))

Example

Example

import math
def positional_encoding(pos, d):
    return [round(math.sin(pos / 10000 ** (i / d)) if i % 2 == 0 else math.cos(pos / 10000 ** ((i - 1) / d)), 3)
            for i in range(d)]

for pos in range(4):
    print(f"position {pos}: {positional_encoding(pos, 4)}")

token_vec = [-0.95, -0.6, 0.3, 0.09]           # embedding of "refund" from the previous page
pos_vec = positional_encoding(0, 4)
print("refund at position 0:", [round(a + b, 3) for a, b in zip(token_vec, pos_vec)])

Output

position 0: [0.0, 1.0, 0.0, 1.0]
position 1: [0.841, 0.54, 0.01, 1.0]
position 2: [0.909, -0.416, 0.02, 1.0]
position 3: [0.141, -0.99, 0.03, 1.0]
refund at position 0: [-0.95, 0.4, 0.3, 1.09]

Context Length

Position handling is one reason context windows have limits. Models are trained up to some length; techniques like RoPE scaling let them work on longer inputs, but quality on very long contexts must still be tested.