Creating Token Embeddings
Input-target pairs for next-word prediction are made by taking the input tokens and shifting them one position to form the targets.
Try it: Indexing and Slicing
How Python indexes a sequence from 0 (or from the end with negative numbers) and how a slice start:stop:step picks positions — with the exact CPython rules for omitted and out-of-range bounds.
How it works
- Positions run 0 … len−1; negative positions count back from the end (−1 is the last).
- An index outside the string raises IndexError.
- A slice resolves start and stop (omitted → the ends; out of range → clamped), then walks by step, stopping before stop.
- A negative step walks backwards; a step of 0 is a ValueError.
Default run (7 steps): 'PYTHONIC'[1:6:2]: a slice copies characters from start up to (not including) stop, moving by step. … 'PYTHONIC'[1:6:2] → 'YHN' (positions 1, 3, 5).
Simplified: Works on a short string (up to 16 printable ASCII characters); lists slice by exactly the same rules.
Loading the simulation…
Try it: Text Vectorization: Tokens to Vectors
The text-to-tensor pipeline: standardize, split into tokens, build a frequency-ranked vocabulary with padding and [UNK], map tokens to integer ids, pad or truncate, then one-hot or multi-hot encode and look the ids up in an embedding table.
How it works
- Standardize: lowercase and delete punctuation.
- Split on whitespace into tokens.
- Build the vocabulary: 0 = padding, 1 = [UNK], then words by frequency (ties: first appearance), capped at max tokens.
- Map each token to its id (1 if it did not make the vocabulary), then pad with 0 or truncate to the sequence length.
- Encode: one-hot rows per position or one multi-hot row per text; embed: id k selects row k of the embedding table.
Default run (18 steps): Raw text (59 characters): "The movie was great. The plot was great, the acting wasn't!". A model cannot read strings, so it is turned into numbers step by step. … Embedding lookup: id k selects row k of a fixed 8 x 3 table, giving a (8, 3) dense matrix instead of (8, 8) sparse one-hots. Row 0 (padding) is looked up too; a trained Embedding layer would learn these rows.
Simplified: Bounded ASCII text (up to 200 characters, 40 tokens), whitespace splitting only, and a small FIXED embedding table (a real Embedding layer learns its rows). The multi-hot vector keeps the padding slot (always 0) so its positions line up with the ids.
Loading the simulation…
Predicting the Next Word
Training a large language model on raw text starts with a simple question: given some words, which word comes next? Before any model can learn to answer that question, the text has to be turned into training examples. Each example pairs a stretch of tokens the model can see with the token that follows it. This article builds those pairs from a single token sequence, and then shows how the pairs are turned into vectors that a model can work with.
What do you think happens?
Suppose the token sequence is 10, 20, 30, 40, 50. If the context is the first four tokens, 10, 20, 30, 40, what should the target be?
Reveal answer
50
The target is the token that appears immediately after the context in the original sequence. After 10, 20, 30, 40 comes 50, so 50 is the target.
Keep that answer in mind. The rest of this article explains how the code produces exactly that pair, and why the same mechanism produces every other pair hidden in the sequence.
Shifting a Sequence to Make Targets
The input-target pairs for next-word prediction are built from two variables. The variable x holds the input tokens, which is the text the model sees. The variable y holds the targets, which are the same tokens shifted by one position. Each target token is the word that appears immediately after its matching input token in the original sequence.
Input-target pairs for next-word prediction are made by taking the input tokens and shifting them one position to form the targets. The input is the stretch of known tokens, and the target is the same stretch moved one slot later.
The diagram uses a short sequence of five token IDs. The input row holds the first four, and the target row holds the same four positions moved one slot later. Read down any column and you find an input token together with the token that follows it. The last input position is paired with the fifth token of the sequence, which is why the target row ends with 50 even though 50 does not appear in the input row.
Reading the Two Slices
The two lines that matter are two slices of the token sequence. The first slice takes the opening block of the sequence. The second slice starts one position later and ends one position past the end of the first slice. The variable context_size controls how long each block is.
enc_sample[:context_size] takes positions 0 up to but not includingcontext_sizeenc_sample[1:context_size+1] starts at position 1, one slot later, and ends one past the end of the first slice- Both slices have length
context_size, so each row of x lines up with a row of y
Slicing a sequence with context size 4
The token sequence is 10, 20, 30, 40, 50, and context_size is 4. What do x and y contain?
- 1Identify the first slice
enc_sample[:4] takes positions 0, 1, 2, 3, which hold 10, 20, 30, 40. So x is 10, 20, 30, 40. - 2Identify the second slice
enc_sample[1:5] starts at position 1 and ends before position 5. It takes positions 1, 2, 3, 4, which hold 20, 30, 40, 50. So y is 20, 30, 40, 50. - 3Compare position by position
Input 10 is followed by target 20, input 20 by target 30, input 30 by target 40, and input 40 by target 50. Each target is the token one slot after its input.
x = 10, 20, 30, 40 and y = 20, 30, 40, 50. Three of the four input positions also appear as targets, and the first target is the second token of the sequence.
Expanding One Sequence into Many Pairs
A single pair of slices is only one training example. To see every next-word prediction task hidden in one sequence, the sequence is read one step at a time. At each step the input grows by one token, and the target is the token that comes right after the end of that growing input. Looping over the growing prefixes of a sequence reveals every such task, one context and one target at a time.
[10] ----> 20
[10, 20] ----> 30
[10, 20, 30] ----> 40
[10, 20, 30, 40] ----> 50range(1, context_size + 1)produces i equal to 1, 2, 3, 4, so the context grows from one token to fourenc_sample[:i] is the growing context, everything to the left of the arrowenc_sample[i] is the token that comes right after that context, the target to the right of the arrow
The four printed pairs are exactly the pairs that the single slice expression produces when it takes the whole window at once. The loop shows them one at a time, while the slices show the whole window together. Once the pairs are visible as token IDs, they can be decoded back into words with the tokenizer to check that they make sense. Each context and each target can be passed through decode in the same loop.
Read the diagram as a count. Four steps produce four context-to-target pairs from one five-token sequence. In general, a sequence of n tokens contains n minus 1 such pairs when every prefix is used, because the final token can only ever be a target and never has a token after it.
The Data Loader and Its Two Tensors
Generating pairs one by one works for a demonstration, but training needs a data loader. Its job is to iterate over the input dataset and return inputs and targets as PyTorch tensors. A tensor can be thought of as a multidimensional array. The loader returns two tensors. The input tensor holds the text the LLM sees, and the target tensor holds the targets the LLM must predict.
Each row of the input tensor is one context, and the matching row of the target tensor is the same window shifted by one position. Row k of the target tensor is the answer to row k of the input tensor.
The code operates on token IDs, not on words. The sample text is shown as words only to make it readable. The encode step of the BPE tokenizer performs tokenization and conversion to IDs in a single step. So the rows of the input tensor are rows of token IDs, and the rows of the target tensor are the same windows moved one position forward.
Why Token IDs Become Vectors
A tokenizer turns text into integers. Each integer is a token ID that names one entry in a fixed vocabulary. An integer by itself carries no information about how one token relates to another, so a model cannot do much arithmetic with it directly. The usual remedy is to replace each ID with a vector, a list of numbers that the model can learn to adjust during training. That replacement is called a token embedding.
A token embedding is a vector representation of an input token, created in PyTorch with an Embedding layer. The layer maps each token ID to one row of a learnable table of numbers.
In this lesson the token IDs come from the BPE tokenizer built earlier in the course, and the vocabulary has 50,257 entries. The embedding size is set to 256 dimensions. The source describes 256 as much smaller than the 12,288 dimensions used by the original GPT-3 model, yet still a reasonable size for experimentation on ordinary hardware. It is a practical choice for learning rather than a claim about what production models use.
Sizing the Embedding Table
The embedding layer is created with a constructor of the form torch.nn.Embedding(vocab_size, output_dim). In this lesson the first argument is 50,257 and the second is 256. These two numbers fully determine the layer's shape. The vocabulary size sets how many rows the layer holds, one for each possible token ID, and the output dimension sets how long each row is. A row is the vector that a given token ID maps to.
torch.Size([50257, 256])- The first argument, 50257, is the vocabulary size and sets the number of rows
- The second argument, 256, is the output dimension and sets the length of each row
- The weight table is therefore 50257 rows by 256 columns
- 1W is the embedding weight table, one learnable vector per token ID
- 2The first dimension, 50257, equals the vocabulary size and counts the rows, one for each possible token ID from 0 to 50256
- 3The second dimension, 256, equals the output dimension and is the length of each row, which is the vector for one token
Because the table is indexed by token ID, looking up an ID means retrieving one row. A token with ID 7 is represented by row 7 of the table, and a token with ID 50256 is represented by the last row. The values in those rows are the numbers the model adjusts during training.
Tracing the Shape Through the Embedding Layer
The batch passed to the layer is a two-dimensional grid of token IDs, with one row per text sample and one column per token position. The lesson uses a data loader to fetch a batch of 8 samples with 4 tokens each, so the input grid has shape 8 × 4. Every entry in that grid is one integer ID between 0 and 50,256.
torch.Size([8, 4])
torch.Size([8, 4, 256])- randint with a range of 0 to 50256 creates random IDs that are all valid rows of the table
- The input grid has shape 8 by 4, one row per sample and one column per position
- The output keeps the 8 by 4 grid and appends a final axis of length 256
Calling the embedding layer on that grid replaces every integer with its 256-value vector. The grid positions do not move. The batch and sequence dimensions are preserved, and a third dimension of length 256 is appended. The result has shape 8 × 4 × 256, which means every one of the 32 token IDs is now a 256-dimensional vector.
Predicting and verifying the output shape
A batch of token IDs has shape 8 × 4. The embedding layer is torch.nn.Embedding(50257, 256). Predict the output shape, then say where a mismatch would first appear if one occurred.
- 1Check the input shape first
The input is 8 samples by 4 positions. Confirm the grid really has those two dimensions before doing anything else.
- 2Keep the batch and sequence dimensions
The embedding layer keeps the 8 and the 4 in place, so the first two axes of the output are 8 and 4.
- 3Append the embedding dimension
The layer adds one final axis whose length is the output dimension, 256.
- 4Verify the final axis
The last axis of the output must equal 256. If it does not, the mismatch is in the embedding layer's output dimension, not in the batch.
The output shape is 8 × 4 × 256. A mismatch would first show up at the input-shape check if the grid were not 2-dimensional, and otherwise at the final-axis check if the last axis were not 256.
When checking embedding output, verify the input shape first, then confirm that the final axis equals the output dimension. Checking in that order tells you which stage introduced the problem.
The token embedding layer treats each token by its identity alone. It does not record where in the sequence a token sits. A GPT-style absolute embedding approach needs a second embedding layer with the same embedding dimension, 256 in this lesson, so that the two outputs can be combined. That second layer is the subject of the next step, and both layers must share the 256-dimensional size.
Mistakes Beginners Make with Pairs and Shapes
Common mistake Setting the target equal to the input
What it looks likey = xWhy it failsThe model would be asked to repeat the token it already sees rather than predict the token that follows it.
Do this insteadShift the target one position: y = enc_sample[1:context_size+1].
Common mistake Forgetting the one-position shift in the slice
What it looks likey = enc_sample[:context_size]Why it failsThe target starts at the same position as the input, so each target is the input token itself rather than the one after it.
Do this insteadStart the target slice at position 1 and end it one past the end of the input slice.
Common mistake Confusing vocabulary size with embedding dimension
What it looks liketorch.nn.Embedding(256, 50257)Why it failsThe first argument sets the number of rows, one per token ID, and the second sets the length of each row. Swapping them produces a table that cannot hold every token ID.
Do this insteadUse the vocabulary size first and the output dimension second: torch.nn.Embedding(50257, 256).
Common mistake Checking only the batch dimensions of the output
Why it failsThe first two axes can look correct even when the final axis is wrong, so the error goes unnoticed until later stages fail.
Do this insteadConfirm that the final axis of the embedding output equals the output dimension.
Common mistake Expecting the embedding to encode word order, when each token is embedded by its identity alone
Practice: Slicing a New Sequence
The token sequence is 10, 20, 30, 40, 50, 60, and context_size is 3. Without running any code, write out the values of x = enc_sample[:context_size] and y = enc_sample[1:context_size+1]. Then list the three context-to-target pairs that the growing-prefix loop would produce.
Hints
- x takes the first three positions, which are 10, 20, 30.
- y starts at position 1 and ends one past the end of x, so it covers positions 1, 2, 3.
- The loop uses prefixes of length 1, 2, and 3, and each target is the token immediately after that prefix.
Key Takeaways
- Input-target pairs for next-word prediction are made by taking the input tokens and shifting them one position to form the targets, so each target is the token that follows its input.
- The slice x =
enc_sample[:context_size] paired with y =enc_sample[1:context_size+1] produces one training example, and a loop over growing prefixes produces every context-to-target pair hidden in a sequence. - The data loader returns an input tensor and a target tensor, where each row of the input is a context and the matching row of the target is the same window shifted by one position.
- A token embedding is a vector representation of a token ID. The embedding layer is defined by the vocabulary size, which sets the number of rows, and the output dimension, which sets the length of each row.
- An embedding layer keeps the batch and sequence dimensions and appends the embedding dimension, so an 8 × 4 grid of IDs becomes an 8 × 4 × 256 tensor. Verify the input shape first, then check that the final axis equals the output dimension.