§2.7–2.9Token & Positional Embeddings

Part I pp. 16–20 · ~5 min read

  • token embedding
  • embedding dimension
  • nn.Embedding
  • positional embedding
  • absolute positional embedding
  • relative positional embedding
  • input embedding

We have token IDs and a way to batch them. The last step of Chapter 2 turns those integers into the continuous vectors the model actually multiplies — first the token embeddings, then the positional embeddings that tell the model where each token sits.

2.7 Creating token embeddings

A token embedding is produced by an nn.Embedding layer, which is nothing more than a learned lookup table: a weight matrix of shape (vocab_size, emb_dim) where row i is the embedding of token ID i. Looking up an embedding is just selecting a row — equivalent to multiplying a one-hot vector by the matrix, but far cheaper.

Take a toy layer with vocab_size = 6 and embedding dimension emb_dim = 3. Its weight matrix starts random (here, after torch.manual_seed(123)):

embedding_layer.weight (vocab_size=6 × emb_dim=3)
id 0
0.34
-0.18
-0.17
id 1
0.92
1.58
1.30
id 2
1.28
-0.20
-0.16
id 3
-0.40
0.97
-1.15
id 4
-1.16
0.33
-0.63
id 5
-2.84
-0.78
-1.41
The embedding matrix. To embed the token IDs [2, 3, 5, 1] we simply pull out rows 2, 3, 5, 1 (ringed) — in that order.

Applying it to input_ids = [2, 3, 5, 1] stacks those four rows into a (4 × 3) tensor:

embedding_layer(input_ids) (4 × 3)
id 2
1.28
-0.20
-0.16
id 3
-0.40
0.97
-1.15
id 5
-2.84
-0.78
-1.41
id 1
0.92
1.58
1.30
One embedding vector per input token, in input order. These weights are random now but are optimized during training.
</> A token-embedding layer
import torch

input_ids = torch.tensor([2, 3, 5, 1])
vocab_size = 6
output_dim = 3

torch.manual_seed(123)
embedding_layer = torch.nn.Embedding(vocab_size, output_dim)   #A

embedding_layer(torch.tensor([3]))     #B
# tensor([[-0.4015,  0.9666, -1.1481]], grad_fn=<EmbeddingBackward0>)

embedding_layer(input_ids)             #C
# tensor([[ 1.2753, -0.2010, -0.1606],
#         [-0.4015,  0.9666, -1.1481],
#         [-2.8400, -0.7849, -1.4096],
#         [ 0.9178,  1.5810,  1.3010]], grad_fn=<EmbeddingBackward0>)
  • #A Creates the (vocab_size × output_dim) weight matrix, initialized randomly. requires_grad=True, so it learns during training.
  • #B Embedding a single ID (3) returns exactly row 3 of the weight matrix.
  • #C Embedding the whole list returns one row per ID, stacked — the lookup, vectorized.

2.8 Encoding word positions

The lookup above has a subtle gap: it is position-independent. Token ID 5 gets the vector [-2.84, -0.78, -1.41] whether it is the first or the fiftieth token. That is fine for reproducibility, but self-attention (Chapter 3) is itself order-agnostic — it has no built-in sense of sequence — so we must inject position information explicitly. There are two families:

ApproachWhat it encodesUsed by
Absolute positional embeddingOne vector per position index (position 0, 1, 2, …).GPT (learned during training)+
Relative positional embeddingThe distance between tokens — "how far apart", not "which slot".Various later models+
Two ways to encode position. GPT uses learned absolute positional embeddings.

The recipe is delightfully simple: build a second embedding layer indexed by position instead of by token, then add the two. A positional-embedding layer of shape (context_length, emb_dim) produces one vector per slot; the input embedding is the element-wise sum.

token emb(depends on WHICH token)positional emb(depends on WHICH slot)pos 0pos 1pos 2pos 3input emb(fed to the GPT)+=
input_embeddings = token_embeddings + pos_embeddings, added row-by-row. The positional tensor (context_length × emb_dim) broadcasts across every sequence in the batch.
</> Token + positional embeddings (GPT-2 scale)
vocab_size = 50257
output_dim = 256
max_length = 4

token_embedding_layer = torch.nn.Embedding(vocab_size, output_dim)
pos_embedding_layer   = torch.nn.Embedding(max_length, output_dim)   #A

dataloader = create_dataloader_v1(
  raw_text, batch_size=8, max_length=max_length, stride=max_length)
inputs, targets = next(iter(dataloader))          # inputs: (8, 4)

token_embeddings = token_embedding_layer(inputs)                    #B
# shape: (8, 4, 256)   -> (batch, tokens, emb_dim)

pos_embeddings = pos_embedding_layer(torch.arange(max_length))      #C
# shape: (4, 256)

input_embeddings = token_embeddings + pos_embeddings               #D
# shape: (8, 4, 256)
  • #A A separate embedding layer indexed by position; context_length here equals max_length = 4.
  • #B Look up the token embeddings for a batch of 8 sequences of 4 tokens → a (8, 4, 256) tensor.
  • #C torch.arange(max_length) = [0,1,2,3] looks up one positional vector per slot → (4, 256).
  • #D Add them. The (4, 256) positional tensor broadcasts over the batch dimension, giving the final (8, 4, 256) input the model consumes.

2.9 The full input pipeline

That completes the journey from raw string to model input. Every arrow below is a step this chapter built:

Input text"This is an example."TokenizedThis·is·an·example·.Token IDs40 · 367 · 2885 · …Token emb+ positionalGPT input(batch, tokens, emb)
The complete Chapter 2 pipeline (book Figure, p. 16). The GPT input is a (batch, tokens, emb_dim) tensor — ready for attention.

Chapter 2 in one breath: raw text → tokens → token IDs (via BPE) → token embeddings (a learned lookup) → + positional embeddings = the input tensor. LLMs can’t read text, so we turned it into continuous vectors compatible with neural-network math.

Key idea

Two embedding tables — one indexed by token, one by position — are added to form the model’s input. Their weights are random now and become meaningful only through training. Next, Chapter 3 builds the self-attention mechanism that operates on exactly these input embeddings — and whose order-agnostic nature is the very reason we just added positions.

📝 Check yourself: embeddings & positions

0 / 5
  1. 1.An nn.Embedding layer is often described as 'just a lookup table'. What does it look up?

  2. 2.Where do the values in the embedding matrix come from?

  3. 3.Why do GPT models add positional embeddings on top of token embeddings?

  4. 4.GPT uses which kind of positional embedding?

  5. 5.How is the final input embedding formed from the token and positional embeddings?