LLM Fundamentals · Chapter 01 bbycroft.net/llm

TOKEN &
POSITION
EMBEDDINGS

YOUR QUESTIONS, ANSWERED DIRECTLY
ONE PARAGRAPH SUMMARY

Transformers can't read words — they need float vectors. Token embeddings convert each word-ID into a vector of numbers that carries meaning. Position embeddings add a second vector that tells the model where in the sentence that word sits. Both are lookup tables trained by gradient descent. They get added together to form the actual input the transformer sees.

QUESTION 01 / 07
YOU ASKED
What is token embed and position embed?
ANSWER

TWO LOOKUP TABLES

Think of them as two separate dictionaries the model consults every time it reads a token.

TOKEN EMBED
A table with one row per unique word/token. Look up "cat" → get a vector of numbers that captures what "cat" means. Rows = vocabulary. Learned during training. Shape: [vocab_size × C]
POSITION EMBED
A table with one row per possible position (slot 0, slot 1, slot 2…). Look up position 3 → get a vector that says "this token is the 4th in the sentence". Shape: [max_seq_len × C]
INPUT EMBED
= Token embed result + Position embed result. One combined vector per token. This is what actually goes into the transformer layers.

Why two separate tables? Meaning and position are independent signals. "cat" means the same thing whether it's at position 0 or position 20. By keeping them separate, the model can learn each signal independently, then combine them.

VISUAL — how a 6-token input produces the Input Embed matrix
Token lookup result [6×C]
WHAT it is (meaning)
+
Position lookup result [6×C]
WHERE it is (order)
=
Input Embed [6×C] → into transformer
WHAT + WHERE combined
QUESTION 02 / 07
YOU ASKED
Is it part of the tokenizer? How does it fit in the pipeline?
ANSWER

NO — SEPARATE SYSTEM

The tokenizer and embeddings are two completely different things that happen at different stages.

📝
Raw Text
"Hello world"
→
✂️
Tokenizer
BPE rules
→
#
Token IDs
[15496, 995]
→
⚡
Embeddings
IDs → vectors
→
🧠
Transformer
attention layers
PropertyTokenizerEmbeddings
What it doestext → integer IDsinteger IDs → float vectors
Output typeint[]float[][]
Part of neural network?NO — preprocessingYES — first layer
Trained via backprop?NO — fixed rulesYES — updated every step
Has learnable weights?NOYES
Can change after training?NONO (frozen at inference)

Mental model: Tokenizer is a dictionary lookup (static rules). Embedding is a neural network weight matrix (learned numbers). The tokenizer's job ends the moment it produces integer IDs. Embeddings take over from there.

QUESTION 03 / 07
YOU ASKED
How are they being trained?
ANSWER

SAME AS ANY WEIGHT — BACKPROP

Both embedding tables are just matrices of floating-point numbers. They start random. Every training step, the model makes a prediction, measures how wrong it was, and nudges all weights — including the embedding rows — in the direction that reduces error.

1
Forward Pass
Look up token & position vectors, add them, run through all transformer layers, predict next token
2
Loss
Compare predicted token probabilities to the actual next token. Compute how wrong the model was (cross-entropy loss)
3
Backprop
Gradient flows backward through every layer — all the way back into the embedding tables. Only used rows get gradients
4
Update
Optimizer (Adam etc.) nudges each row's numbers slightly. Over billions of steps, rows develop meaningful structure
KEY DETAIL
Only rows that were actually used in a batch get updated that step. Rare tokens get updated rarely. Common tokens get refined every batch.
WHAT EMERGES
Similar words end up with similar vectors (the model figures this out by seeing them in similar contexts). The famous "king − man + woman ≈ queen" geometry appears entirely from predicting next tokens — never explicitly taught.
POSITION EMBED
Learned the same way in GPT-2 style. Modern models (Llama) use RoPE instead — position is encoded via math inside the attention layer, so no explicit position table and no position table weights to train.
QUESTION 04 / 07
YOU ASKED
Does the column size determine the embedding / vector size?
ANSWER

YES — COLUMNS = VECTOR WIDTH

Correct. In both embedding tables, each row is one vector and the number of columns is the length of that vector.

CALLED
C in Karpathy's nanoGPT · d_model in the original paper · hidden_size in HuggingFace · n_embd in GPT-2 config · dim in Llama — all the same thing.
SAME EVERYWHERE
C is the same number everywhere in the model. Every layer, every matrix, every intermediate tensor — all C columns wide. It's set once at design time.
MUST MATCH
Token embed and position embed MUST have the same C. They get added element-wise — if the widths were different, the addition would be impossible.
TOKEN = [vocab_size × C] — each row is a learned vector of length C
col 0
col 1
col 2
col 3
col 4
A (id=0)
B (id=1)
C (id=2)
.12
-.4
.87
.03
.61
← C columns wide (same as every other matrix in the model)
↕ vocab_size rows (one per unique token)
GPT-2 SMALL
C (columns)
768
Model size
117M
LLAMA-3 8B
C (columns)
4,096
Model size
8B
GPT-3
C (columns)
12,288
Model size
175B
QUESTION 05 / 07
YOU ASKED
Why does token embed have fewer columns compared to position embed?
ANSWER — IMPORTANT CORRECTION

IT DOESN'T — SAME COLUMNS

MISCONCEPTION TO FIX

Columns (= C = vector width) are identical on both tables. They MUST be — you can't add vectors of different lengths. What you saw in bbycroft's visualization was a difference in rows, not columns.

Here's what's actually different between the two tables:

TOKEN EMBED TABLE has fewer ROWS than POSITION EMBED TABLE (in the toy model)
Token Embed Table
A=0
B=1
C=2
3 rows (vocab_size=3: A,B,C)
6 columns (C=6)
Shape: [3 × 6]
Position Embed Table
pos 0
pos 1
pos 2
pos 3
pos 4
pos 5
6 rows (max_seq_len=6 positions)
6 columns (C=6) — SAME C
Shape: [6 × 6]

In bbycroft's toy model: only 3 unique tokens exist (A, B, C) → token table has 3 rows. The sequence has 6 positions → position table has 6 rows. Both tables are equally wide (same C). The visual difference is height (rows), not width (columns).

When you actually use the tables to look up values for a 6-token input, both produce a [6 × C] result — same shape — and those get added.

QUESTION 06 / 07
YOU ASKED
What determines the row counts?
ANSWER

DIFFERENT THINGS SET EACH

Each matrix has a different answer for what sets its row count.

Matrix Row count Example Set by
Token embed TABLE vocab_size 50,257 (GPT-2) Tokenizer vocabulary — how many unique tokens the tokenizer knows
Position embed TABLE max_seq_len 1,024 (GPT-2) Model design choice — max context window (how long an input it can handle)
Token lookup RESULT T (current seq len) 6 (for 6 input tokens) The actual input you're running — changes every request
Position lookup RESULT T (current seq len) 6 (same as above) Always matches token lookup result — they must be added together
vocab_size
Decided by how the tokenizer was built. GPT-2 tokenizer has 50,257 merge rules → 50,257 rows in token table. Llama-3 tokenizer has 128,000 → 128,000 rows. You can't change this without retraining the tokenizer and model.
max_seq_len
Decided by the model architects at design time. GPT-2: 1,024. Llama-3 8B: 8,192. If you send a prompt longer than this, the model can't handle it (for learned position embed — RoPE handles longer lengths better).
T (runtime)
The number of tokens in your specific input. Ask "how are you?" → T≈4. Ask a long question → T could be 500. Both tables produce T rows each request. Must be ≤ max_seq_len.
ALL SHAPES AT A GLANCE
// The two permanent tables (exist for the lifetime of the model)
token_embed_table   shape: [vocab_size,   C]   // e.g. [50257, 768]
pos_embed_table     shape: [max_seq_len, C]   // e.g. [1024,  768]

// The runtime lookup results (change every request)
tok_lookup  shape: [T, C]   // e.g. [6, 768] for a 6-token input
pos_lookup  shape: [T, C]   // same shape, ALWAYS
input_embed shape: [T, C]   // tok_lookup + pos_lookup → into transformer

// C is identical in ALL of the above
LOOKUP IN ACTION — input [C, B, A, B, B, C]
C(2)
pos=0, id=2 →
tok[2]+pos[0] = [−0.33+0.01, 0.71+0.44, ...] (C floats)
B(1)
pos=1, id=1 →
tok[1]+pos[1] = [0.12−0.22, −0.44+0.11, ...] (C floats)
A(0)
pos=2, id=0 →
tok[0]+pos[2] = [0.56+0.38, 0.09−0.07, ...] (C floats)
QUESTION 07 / 07
YOU ASKED
If what's shown in the image are lookup results — how big is the original full table? And how is it trained?
ANSWER

THE FULL TABLE IS MUCH BIGGER

bbycroft shows you the lookup result — a small slice pulled out of a much larger table. The full tables live permanently in model memory for the entire lifetime of the model.

WHAT YOU SEE
The [T × C] result — only the rows for your current input tokens. If T=6 you see 6 rows. The rest of the table is not shown.
FULL TOKEN TABLE
[vocab_size × C] — one row for every possible token. GPT-2: 50,257 rows × 768 cols = ~38.6M numbers. Always in memory. You only "activate" the rows matching your input.
FULL POSITION TABLE
[max_seq_len × C] — one row per possible position slot. GPT-2: 1,024 rows × 768 cols = ~786K numbers. Always in memory. Row 0 used for first token, row 1 for second, etc.
THE FULL TOKEN TABLE — only highlighted rows appear in bbycroft's visualization
Full Token Embed Table [vocab_size × C]
tok 0
tok 1
tok 2 ← used
tok 3
tok 4
· · ·
tok N
Amber row = the one row that gets pulled out and shown in bbycroft.
All other rows sit idle for this particular input. Full table = vocab_size rows deep.
REAL SIZES — full tables in memory
// GPT-2 Small
token_table    [50_257 × 768]  = 38_597_376 params  (~38.6M floats)
position_table [ 1_024 × 768]  =    786_432 params  (~786K floats)

// Llama-3 8B
token_table    [128_000 × 4_096] = 524_288_000 params (~524M floats — 6.5% of total model!)
position_table  → RoPE — no explicit table, zero extra params

// What bbycroft shows (for a 6-token input)
tok_lookup_result [6 × 768]  =      4_608 values   ← tiny slice
pos_lookup_result [6 × 768]  =      4_608 values   ← tiny slice
HOW THE FULL TABLE IS TRAINED

The full table is always present. But during any single training step, only the rows that were actually used get updated. This is the key insight:

STEP 1
Input tokens [2, 1, 0, 1, 1, 2] → look up rows 2, 1, 0 from the full token table. Those are the only rows that participate in this forward pass.
STEP 2
The model makes a prediction. Loss is computed. Gradient flows backward — but only back through the rows that were accessed. Rows 3, 4, 5 ... 50,256 have zero gradient this step.
STEP 3
Optimizer updates rows 0, 1, 2. Rows that weren't used stay exactly the same this step. They'll be updated in future batches where they appear.
AFTER TRILLIONS OF STEPS
Every row has been nudged countless times by countless contexts. Common tokens (the, a, is) get updated millions of times. Rare tokens (obscure words) get updated fewer times — so they tend to have noisier embeddings.

Summary: The full table is always in memory and always in the model. bbycroft only renders the T rows you asked for. Training updates only the rows used in each batch via backprop. Over billions of steps the whole table gets shaped into something meaningful.