Current: Week 6

0%

Week 6

Week 6 — From tokens to a small language model: understanding prediction through one example

Key questionHow does a model turn you like AI into numbers, score every candidate using context, and learn to assign more probability to the correct next token?

Learning objectives

  • Distinguish tokens, tokenizers, vocabularies, token IDs, embeddings, context representations, logits, and probabilities.
  • Explain how an output head uses dot products and biases to produce raw logits; calculate Softmax and cross-entropy by hand.
  • Follow the same batch through [3,3] → [3,2] → [3,2,4] → [3,2,5] → [6,5] → scalar loss.
  • Run Bigram training and generation, then explain its missing access to earlier context and how attention can supply it.

100 min estimated reading time

Week progress: 0 of 20 sections (0%)Course progress: 0 of 321 sections (0%)

We will start with three sentences whose next tokens we already know, not with probability notation. You need Week 5's tensors, matrix multiplication, and one training step. The English teaching vocabulary is fixed as [you, like, AI, study, we]. Its IDs will stay the same when we introduce attention. These English sentences preserve the Chinese edition's numerical input and target patterns; they are an adaptation, not a word-for-word translation.

Scroll horizontally to view all columns.

Course data table
Study sessionThe problem this session solves
1. Turn text into prediction tasksStart with tokens, then inputs and targets, then a batch. Explain why B counts sample rows, T counts input positions in each row, and T input positions require T+1 original tokens.
2. Represent, score, and measure errorDistinguish C representation features from V vocabulary candidates. Follow embedding → output head applied to a given h → logits → Softmax → cross-entropy.
3. Connect the steps and learnConnect the calculations for all six prediction tasks. Then run one complete Bigram training step and repeat the training loop.
4. Generate text and identify the limitsFirst explain how to select and append a token. Then show why a model that sees only the last token cannot distinguish different prefixes. This limitation motivates attention.

Completion criteria: calculate p(like) ≈ 0.5923 and cross-entropy ≈ 0.5237 by hand, explain g = p − one-hot, and run python week06_bigram.py. You may return to the derivative of the natural logarithm and perplexity on a second pass. Do not skip the output head's actual multiplications and additions, probability normalization, or input/target alignment.

Alternate reading, hand calculation, and changing the code. You can split each study session into several shorter sittings. The original section numbers remain for existing links and reference; follow the page from top to bottom rather than jumping around to restore the old numbering.

Week 6 goal: turn text into six prediction tasks

Throughout this week, the tokenizer splits on spaces. The vocabulary, corpus, and dimensions stay fixed across sections. During training, the model learns from six current-token → actual-next-token tasks together. During generation, it appends just one token at a time.

Scroll horizontally to view all columns.

Fixed vocabulary (V = 5)
IDToken
0you
1like
2AI
3study
4we
text
Corpus:
you like AI
we like you
you study AI

raw IDs = [[0,1,2], [4,1,0], [0,3,2]]
inputs  = [[0,1],   [4,1],   [0,3]]
targets = [[1,2],   [1,0],   [3,2]]

Scroll horizontally to view all columns.

Course data table
SymbolFixed valueMeaning
B3The three sequences in the batch
N3Tokens in each original sequence, before shifting the targets
T2Next-token training positions per sequence after aligning inputs with the following tokens
C4Continuous-valued features in each token embedding
V5Vocabulary candidates / rows in the embedding table
Concept sequence
  1. raw IDs [3,3]
  2. shifted inputs [3,2]
  3. embeddings [3,2,4]
  4. logits [3,2,5]
  5. flattened logits [6,5]
  6. flattened targets [6]
  7. scalar mean cross-entropy loss

Knowledge check

What are the six fixed prediction tasks this week?

1. Token, tokenizer, vocabulary, and token ID

A token is a basic unit that a tokenizer passes to a model. It need not be a whole word: it might be a character, a subword, or a byte. For this week's hand calculations, our tokenizer splits on spaces. The vocabulary is the finite list of allowed tokens. A token ID is a token's integer address in that list.

Different tokenizers can turn the same text into different numbers of tokens. For example, learning might be one token or might split into learn and ing. Models read and generate tokens, so the splitting method affects sequence length, how much text fits in the context window, and the computation needed for training and inference. GPT-style systems commonly use subword or byte-based methods; Week 9 examines these in more detail.

Scroll horizontally to view all columns.

Complete encoding with one fixed tokenizer
Original textTokensIDs
you like AI[you, like, AI][0,1,2]
we like you[we, like, you][4,1,0]
you study AI[you, study, AI][0,3,2]

Knowledge check

Why does we like you encode as [4,1,0]? What does ID 4 express?

8. Inputs and targets: create prediction tasks from text by offsetting the answers by one position

The language-model training task is not to repeat the input unchanged. It is to predict the next token from the text available so far. The original text therefore supplies both the questions and their correct answers: each position predicts the token immediately after it. This is called self-supervised learning because the labels come from the text itself, rather than someone labeling every sentence by hand.

X=R[:,0:N1],Y=R[:,1:N],T=N1X=R[:,0:N-1],\qquad Y=R[:,1:N],\qquad T=N-1
text
raw IDs [B,N] = [[0,1,2], [4,1,0], [0,3,2]]  # [3,3]
inputs  [B,T] = [[0,1],   [4,1],   [0,3]]    # [3,2]
targets [B,T] = [[1,2],   [1,0],   [3,2]]    # [3,2]

T = N - 1 = 2

Now put the three independent sequences in a batch: row 0 is you like AI, row 1 is we like you, and row 2 is you study AI. B = 3 means that we process three samples together. They are not concatenated and do not supply context to each other. Each contributes T = 2 prediction positions, giving 3×2 = 6 tasks in total.

Scroll horizontally to view all columns.

The probability tensor p has shape [B,T,V] = [3,2,5]. Every task has five candidate probabilities. The target ID tells us which entry to select along the final axis.
Task position [b,t]Original sequenceContext available to a causal language model hereCorrect targetWhich probability does cross-entropy use?
[0,0]you like AIyoulike / ID 1p[0,0,1]
[0,1]you like AIyou likeAI / ID 2p[0,1,2]
[1,0]we like youwelike / ID 1p[1,0,1]
[1,1]we like youwe likeyou / ID 0p[1,1,0]
[2,0]you study AIyoustudy / ID 3p[2,0,3]
[2,1]you study AIyou studyAI / ID 2p[2,1,2]
Concept sequence
  1. Take original token windows R [B,N] from text
  2. Offset inputs X [B,T] and targets Y [B,T], where T = N−1
  3. Use a causal mask so each Transformer representation h [B,T,C] depends only on the current and earlier positions
  4. The output head produces five logits per task [B,T,V]; Softmax converts them to probabilities
  5. The target ID selects the correct probability p[b,t,target_id] for each task
  6. Cross-entropy computes and averages loss across all B×T tasks

Knowledge check

Why does the original sequence you like AI produce only two tasks? What are the target and visible context for task 0?

4. Start with a batch: what do B, T, C, and V do?

Do not treat [B,T,C] as a formula to memorize. Think of a table with three indexing steps: choose a training sample, choose a prediction position within that sample, then read the continuous-valued features at that position. Start with just one sample.

text
One sentence: you like AI

Input IDs  = [0, 1]    shape [T] = [2]
Target IDs = [1, 2]    shape [T] = [2]

Position 0: current token you; predict like
Position 1: current token like; predict AI

Here T = 2 means that this sample supplies two next-token prediction positions. The original sentence has N = 3 tokens; offsetting the targets gives T = N−1 = 2 tasks. T is not a universal constant. It is the number of input positions per sequence in the current batch.

Scroll horizontally to view all columns.

For a single sequence before adding a batch axis, read the shape as [T].
Before putting a sample in a batchThe question it answers
inputs[0] = youlike
inputs[1] = likeAI

A batch puts several independent samples in one tensor so the CPU or GPU can compute them together. In our training setup, we then average the losses from their prediction positions. This does not concatenate three sentences into one long sentence or pass information from sample 0 to sample 1. In this course's Transformer, attention never mixes different samples in the batch.

text
B = 3: process three independent samples together

inputs [B,T] = [[0,1],   [4,1],   [0,3]]    # shape [3,2]
targets[B,T] = [[1,2],   [1,0],   [3,2]]    # shape [3,2]

b=0: you -> like; like -> AI
b=1: we  -> like; like -> you
b=2: you -> study; study -> AI

Scroll horizontally to view all columns.

Read [B,T] = [3,2] as three samples × two prediction positions per sample, not as six tokens joined into a sentence.
SymbolValue in this exampleWhat it countsHow to read it here
B3Independent samples in the batchFirst choose a sentence
T2Prediction positions in each sampleThen choose a position within that sentence
B×T6Total next-token tasks in this training stepTwo tasks from each of three samples

Now add embedding. inputs[b,t] is still just an integer address. The embedding lookup retrieves a floating-point vector of length C for every address. B and T remain because we still need to identify the sample and position. A new final axis of length C holds the internal representation at that position.

text
Embedding table E has shape [V,C] = [5,4]

inputs[0,1] = 1                  # sample 0, position 1: like
x[0,1,:] = E[1]                 # retrieve all C=4 numbers
         = [0.60, 0.30,-0.20,0.10]

[B,T] --Embedding--> [B,T,C]
[3,2]  ------------> [3,2,4]

The colon in x[b,t,:] means all C features at this position. For example, x[1,0,:] contains the four numbers for we in sample 1. C is the representation width allocated to each position. We deliberately choose 4 for hand calculation; real models usually use much larger widths. Individual coordinates do not have predefined semantic names; training adjusts them.

text
x.shape       = [3,2,4]   # three axes: rank=3
x[1].shape    = [2,4]     # sample 1: two positions, four features each
x[1,0].shape  = [4]       # position 0: the whole representation of we
x[1,0,2]      = 0.30      # feature 2: a scalar

b: which sample?
t: which position in that sample?
c: which coordinate of that position's representation?

An axis tells you which question an index answers; its size tells you how many choices that index has. Shape [3,2,4] has rank 3 because it has three indexing axes: b, t, and c. It contains 3×2×4 = 24 scalar values. Rank 3 does not mean three values.

Scroll horizontally to view all columns.

Course data table
SymbolThis exampleIts roleWhere it appears
B3How many independent samples are processed together[B,T]、[B,T,C]、[B,T,V]
T2How many prediction positions each sample currently has[B,T]、[B,T,C]、[B,T,V]
C4How wide each position's continuous representation isEmbedding table [V,C] and representations [B,T,C]
V5How many candidate tokens the vocabulary contains; not an extra feature of input xRows of the embedding table [V,C]; later, candidate scores in logits [B,T,V]

Scroll horizontally to view all columns.

B counts samples in this batch. T, C, and V specify sequence length, representation width, and the vocabulary interface, respectively.
If we changeWhat changes directlyMain effect
B:3 → 1Each step contains only one sampleThe model architecture stays the same; parallel work, memory use, and samples contributing to one gradient change
T:2 → 4Each sample contains four ordered input positionsThe current window is longer; attention requires more computation between positions
C:4 → 8Each position's representation grows from four numbers to eightRepresentation width, associated weight matrices, parameter count, and computation change
V:5 → 10The vocabulary contains ten candidate tokensBoth the embedding row count and output logits per position become ten
text
Token IDs                 [B,T]   = [3,2]    one integer address per position
Embedding / context state [B,T,C] = [3,2,4]  four internal features per position
Output logits             [B,T,V] = [3,2,5]  five candidate scores per position

Knowledge check

What is x[1,0,:] in this example? Why are there six tasks rather than one sentence of six tokens?

2. Why turn a token ID into an embedding?

An embedding looks up a learnable continuous vector using a discrete ID. Our input table is nn.Embedding(V,C) = nn.Embedding(5,4): five vocabulary rows with four learnable features per row. The values below are specified teaching initializations, not human-written definitions of words.

Using IDs directly as numerical features would impose nonexistent relationships such as we = 4 being twice AI = 2. The ID only says where to look. The embedding provides floating-point features that later layers can combine, compare, and adjust through gradients.

Scroll horizontally to view all columns.

Specified teaching initialization: E has shape [V,C] = [5,4]
ID / Tokenc₀c₁c₂c₃
0 / you0.20-0.100.700.30
1 / like0.600.30-0.200.10
2 / AI-0.400.800.50-0.30
3 / study0.100.200.900.40
4 / we-0.700.400.300.60
E[token_id]R4E[\mathrm{token\_id}]\in\mathbb{R}^{4}

For example, you selects E[0] = [0.20,-0.10,0.70,0.30], while we selects E[4] = [-0.70,0.40,0.30,0.60]. Training adjusts these coordinates through loss calculation, backpropagation, and an optimizer update.

Knowledge check

Does input ID 4 retrieve a scalar or a vector?

3. One-hot equivalence and the [V,C] embedding matrix

ERV×C=R5×4E\in\mathbb{R}^{V\times C}=\mathbb{R}^{5\times4}

Scroll horizontally to view all columns.

The same teaching initialization as in Section 2
ID / Tokenc₀c₁c₂c₃
0 / you0.20-0.100.700.30
1 / like0.600.30-0.200.10
2 / AI-0.400.800.50-0.30
3 / study0.100.200.900.40
4 / we-0.700.400.300.60
text
one_hot(4) = [0,0,0,0,1]

one_hot(4) @ E
= E[4]
= [-0.70,0.40,0.30,0.60]

nn.Embedding(5,4).weight.shape == [5,4]

A one-hot vector acts as a selector: here it selects row 4. An embedding lookup does not need to allocate a large vector of length V; it reads E[id] directly. The two methods produce the same forward result.

Knowledge check

What are the one-hot vector and lookup result for AI?

9. Logits: calculate the raw scores of all five candidates

We now predict the token after the prefix you. Suppose the input embedding and context processor produce h = [0.20,-0.10,0.70,0.30] at this position. To keep the hand calculation simple, this step uses h equal to the input embedding of you. In a full Transformer, h has usually incorporated context and passed through several layers of computation.

Scroll horizontally to view all columns.

These features do not have predefined human-semantic labels
Context featureh valueHow to interpret it
h₀0.20Learned context feature 0
h₁-0.10Learned context feature 1
h₂0.70Learned context feature 2
h₃0.30Learned context feature 3

The output head stores a row of output weights and a bias for each vocabulary candidate. Each row is a learnable scoring rule: weight the same four features of h according to that candidate's rule, sum them, and add its bias.

zi=wih+bi=h0wi,0+h1wi,1+h2wi,2+h3wi,3+biz_i=w_i\cdot h+b_i=h_0w_{i,0}+h_1w_{i,1}+h_2w_{i,2}+h_3w_{i,3}+b_i

Scroll horizontally to view all columns.

Specified output-head parameters for this example; a real model learns its parameters through training
CandidateOutput Weight wᵢBias bᵢSubstitute and calculateLogit
you[1,0,0,0]-0.21(0.2)−0.20
like[0,1,2,2]0.1−0.1+2(0.7)+2(0.3)+0.12
AI[0,0,1,1]00.7+0.31
study[0,3,−1,0]03(−0.1)−0.7−1
we[1,2,0,0]00.2+2(−0.1)0

For like, the full calculation is 0×0.20 + 1×(−0.10) + 2×0.70 + 2×0.30 + 0.10 = 2. None of these operations looks up the correct answer. The current h and the scoring weights for like simply combine to produce a relatively high score.

z=[zyou,zlike,zAI,zstudy,zwe]=[0,2,1,1,0]z=[z_{\text{you}},z_{\text{like}},z_{\text{AI}},z_{\text{study}},z_{\text{we}}]=[0,2,1,-1,0]
python
import torch

# Four internal features of the current context
h = torch.tensor([
    0.20,
    -0.10,
    0.70,
    0.30,
])  # [C] = [4]

# One row of scoring weights per candidate token
W_out = torch.tensor([
    [1.0, 0.0,  0.0, 0.0],  # you
    [0.0, 1.0,  2.0, 2.0],  # like
    [0.0, 0.0,  1.0, 1.0],  # AI
    [0.0, 3.0, -1.0, 0.0],  # study
    [1.0, 2.0,  0.0, 0.0],  # we
])  # [V,C] = [5,4]

bias = torch.tensor([
    -0.2,
     0.1,
     0.0,
     0.0,
     0.0,
])  # [V] = [5]

logits = W_out @ h + bias
print(logits)
# tensor([0., 2., 1., -1., 0.])

By hand, we calculate five dot products separately. W_out stacks the five scoring rules into rows, so W_out @ h + bias produces all five scores at once. The bias vector [5] supplies a learnable baseline offset for each candidate, independent of the current h.

WoutRV×C,hRC,z=Wouth+bRVW_{\mathrm{out}}\in\mathbb{R}^{V\times C},\quad h\in\mathbb{R}^{C},\quad z=W_{\mathrm{out}}h+b\in\mathbb{R}^{V}
H:[B,T,C]HWoutT+b:[B,T,V]H:[B,T,C]\quad\to\quad H W_{\mathrm{out}}^{\mathsf T}+b:[B,T,V]
python
# H: [B,T,C] = [3,2,4]
# W_out.T: [C,V] = [4,5]
logits = H @ W_out.T + bias
# logits: [B,T,V] = [3,2,5]

Logits matter to Softmax because of the differences between candidates. If one logit exceeds another by 1, its Softmax probability is e¹ ≈ 2.718 times as large. Raising only the logit for like increases its probability relative to the others. Adding 100 to every logit leaves all differences unchanged, so it leaves the probability distribution unchanged too.

pipj=ezizj\frac{p_i}{p_j}=e^{z_i-z_j}

Knowledge check

How does the logit for like become 2 in this example? Does it mean 200%?

10. Softmax: turn logits into probabilities

Start with just two scores, [0,1]. Exponentiating gives approximately [1,2.718], whose sum is 3.718. Dividing by this sum gives approximately [0.269,0.731]. Both entries are nonnegative and they sum to 1. Our five-candidate table uses the same calculation with three more entries.

The preceding section calculated logits = [0,2,1,-1,0] from h, W_out, and bias. These scores can be compared, but they are not probabilities: 2 does not mean 200%, and −1 does not mean a negative probability. Sampling and the probability interpretation of cross-entropy require a valid distribution: nonnegative entries summing to 1.

pi=ezij=0V1ezjp_i=\frac{e^{z_i}}{\sum_{j=0}^{V-1}e^{z_j}}

Step 1 is exponentiation: e⁰ = 1, e² ≈ 7.389, e¹ ≈ 2.718, and e⁻¹ ≈ 0.368. Exponentiating a finite negative logit still gives a positive number. That candidate therefore retains a chance, although its relative weight is smaller.

Scroll horizontally to view all columns.

Fixed order [you, like, AI, study, we]; logits [0,2,1,-1,0]
candidate orderlogitexponentialprobability
you01.0000.080
like27.3890.592
AI12.7180.218
study-10.3680.029
we01.0000.080

Step 2 is normalization. The five exponential weights sum to approximately 12.475. Dividing each by that same sum gives approximately [0.080,0.592,0.218,0.029,0.080]. The exact probabilities sum to 1; these displayed rounded values sum to approximately 1.

text
exponentials = [1.000,7.389,2.718,0.368,1.000]
sum = 12.475
probabilities = [0.080,0.592,0.218,0.029,0.080]
0.080 + 0.592 + 0.218 + 0.029 + 0.080 ≈ 1

  1. Exponentiation: every finite logit becomes a positive weight, including negative logits.
  2. Order preservation: if zᵢ > zⱼ, then exp(zᵢ) > exp(zⱼ). The higher-scoring candidate stays higher.
  3. Relative weighting: pᵢ/pⱼ = exp(zᵢ−zⱼ). The difference between logits determines the probability ratio.
  4. Normalization: divide by the sum of all candidate weights so that the results sum to 1.
zizj=1pipj=e12.718z_i-z_j=1\quad\Rightarrow\quad\frac{p_i}{p_j}=e^1\approx2.718
python
import torch
import torch.nn.functional as F

logits = torch.tensor([0.0, 2.0, 1.0, -1.0, 0.0])
probabilities = F.softmax(logits, dim=-1)

print(probabilities)
# tensor([0.0802, 0.5923, 0.2179, 0.0295, 0.0802])

print(probabilities.sum())
# tensor(1.)

For logits shaped [B,T,V] = [3,2,5], dim=-1 applies Softmax separately to the five vocabulary candidates at each [b,t] position. It does not mix sentences or positions into the same denominator.

In the two-candidate example, adding 100 to both scores gives [100,101] without changing the probabilities: the difference is still 1. Implementations often subtract the maximum first, turning [0,1] into [-1,0], to avoid very large exponentials. Softmax depends on score differences, not an absolute zero point.

Knowledge check

Why does Softmax exponentiate first, then divide by the sum of the exponentials?

11. Cross-entropy: from the correct answer's probability to loss and gradients

Softmax has produced five candidate probabilities. Suppose the actual next token is like. We first select the model's probability for this correct answer, p_correct ≈ 0.592. We want loss to approach 0 as that probability approaches 1, and the penalty to grow as it approaches 0.

L=ln(py)L=-\ln(p_y)

Scroll horizontally to view all columns.

Correct-answer probability and cross-entropy at one position
p(correct)−ln(p)Training interpretation
0.990.010Almost certain of the correct answer; penalty close to 0
0.900.105Confident in the correct answer
0.600.511Favors the correct answer but remains noticeably uncertain
0.500.693Only half the probability goes to the correct answer
0.102.303Low probability for the correct answer
0.014.605Very little probability for the correct answer; a large penalty

Scroll horizontally to view all columns.

Course data table
Contexttargetp(correct)loss
youlike (ID 1)0.592-ln(0.592) ≈ 0.524
you (if it were the target instead)AI0.218Approximately 1.524
you (if it were the target instead)we0.080Approximately 2.524

Cross-entropy does more than check whether argmax is correct. If the answer is like, increasing its probability from 0.51 to 0.90 still reduces loss, even if it was already the top candidate. For several positions, mean loss averages the negative log-probability of the correct target at each position.

text
Vocabulary order        = [you,   like,  AI,    study, we]
predicted probabilities = [0.080, 0.592, 0.218, 0.029, 0.080]
one_hot(target=like)     = [0,     1,     0,     0,     0]

L = -Σ yᵢ ln(pᵢ)
  = -(0 ln 0.080 + 1 ln 0.592 + 0 ln 0.218
      + 0 ln 0.029 + 0 ln 0.080)
  = -ln(0.592)
  ≈ 0.524

L=i=0V1yiln(pi)=ln(pcorrect)L=-\sum_{i=0}^{V-1}y_i\ln(p_i)=-\ln(p_{\mathrm{correct}})

Scroll horizontally to view all columns.

Candidates are ordered [you, like, AI, study, we], and displayed values are rounded. The five logit gradients sum to approximately zero; exactly, Σ(pᵢ−yᵢ) = 1−1 = 0. This agrees with loss being unchanged when all logits receive the same additive shift.
CandidatepᵢTarget yᵢgᵢ=pᵢ−yᵢDirection of a direct gradient-descent step on logits
you0.08020+0.0802Lower z_you slightly
like (correct)0.59231−0.4077Raise z_like slightly
AI0.21790+0.2179Lower z_AI slightly
study0.02950+0.0295Lower z_study slightly
we0.08020+0.0802Lower z_we slightly
Lzi=pione_hot(y)i\frac{\partial L}{\partial z_i}=p_i-\mathrm{one\_hot}(y)_i
text
p                     = [0.080, 0.592, 0.218, 0.029, 0.080]
one_hot(target=like)   = [0,     1,     0,     0,     0]
p - one_hot(target)    = [0.080,-0.408, 0.218, 0.029, 0.080]

In a direct-logit illustration, gradient descent subtracts this gradient:
like has a negative gradient, so its logit increases;
the other candidates have positive gradients, so their logits decrease.

Notation reminder: y = 1 can denote the integer ID of the correct class. In the one-hot formula, yᵢ denotes entry i of the corresponding target vector: 1 only when i = 1, otherwise 0. It does not split the integer 1 into many ones.

Knowledge check

If the target is like and p_like = 0.5923, what are g_like and g_AI? What directions do they give gradient descent?

12. Why does PyTorch F.cross_entropy take logits?

F.cross_entropy combines log-Softmax with negative log-likelihood in a numerically stable way. Its score input is therefore raw logits. This course uses hard labels: torch.long target IDs from 0 to V−1, with one correct class per task.

python
import torch
import torch.nn.functional as F

# One task, five raw candidate logits
logits_one = torch.tensor(
    [[0.0, 2.0, 1.0, -1.0, 0.0]],
    requires_grad=True,
)  # [1,5]

# The correct answer is like, ID=1
target_one = torch.tensor([1], dtype=torch.long)  # [1]

loss_one = F.cross_entropy(logits_one, target_one)
loss_one.backward()

print(loss_one.item())
# approximately 0.524

print(logits_one.grad)
# approximately [[0.080, -0.408, 0.218, 0.029, 0.080]]

This code follows the previous hand calculation exactly. [1,5] means one task with five candidate scores; [1] supplies the one correct class ID it needs. Once the single-task calculation is clear, arrange the batch's six positions as six tasks.

Scroll horizontally to view all columns.

Reshape must preserve alignment: each row of flattened logits pairs with the target at the same flattened index
flattened rowbatch positioninput / contexttarget tokentarget ID
0[0,0]you / youlike1
1[0,1]like / you likeAI2
2[1,0]we / welike1
3[1,1]like / we likeyou0
4[2,0]you / youstudy3
5[2,1]study / you studyAI2
python
import torch
import torch.nn.functional as F

# logits: [B,T,V] = [3,2,5]; targets: [B,T] = [3,2]
B, T, V = logits.shape
logits_2d = logits.reshape(B * T, V)       # [6,5]
targets_1d = targets.reshape(B * T)        # [6], dtype=torch.long

loss = F.cross_entropy(
    logits_2d,
    targets_1d,
)                                           # scalar mean loss

# Incorrect: F.cross_entropy expects logits, not already-softmaxed probabilities.
# probabilities = F.softmax(logits_2d, dim=-1)
# loss = F.cross_entropy(probabilities, targets_1d)

[3,2,5][6,5]and[3,2][6][3,2,5]\to[6,5]\quad\text{and}\quad[3,2]\to[6]

API clarification: PyTorch cross-entropy also supports floating-point class-probability targets with the same shape as the logits, including soft labels. Saying it never accepts one-hot targets is therefore inaccurate. This course uses integer hard labels to keep the six tasks and six answers easy to match: [BT,V] logits and [BT] long targets.

Knowledge check

Why is logits_2d shaped [6,5], while targets_1d is [6]?

6. Language models: what questions do these five probabilities answer?

The preceding calculation followed task [0,0]: the visible text is you and the correct next token is like. A context representation h is passed to the output head to produce logits z = [0,2,1,−1,0]. Softmax then gives p ≈ [0.0802,0.5923,0.2179,0.0295,0.0802]. We will not repeat the dot products or exponentials here. Instead, ask what each of these five numbers means for language.

P(xt+1xt)P(x_{t+1}\mid x_{\le t})

Scroll horizontally to view all columns.

We assume a causal language model: a position may use current and earlier information, but cannot read its target to the right.
Part of the formulaMeaning hereExample: you like AI
xA sequence of tokens, usually represented in code by token IDsyou, like, and AI are x₀, x₁, and x₂ respectively.
tThe position whose prediction task we are consideringIf t = 1, the model has seen positions 0 and 1.
x≤tThe visible prefix from the start through tx≤1 is you like.
Given that …Not division; read it as given the prefix you like.
PThe model's probability ruleP(AI | you like) is the model's probability that AI is the next token.

Read P(xₜ₊₁ | x≤ₜ) as: after seeing prefix x≤ₜ, what is the probability of next token xₜ₊₁? In practice, the model does not return just this one number. It returns a distribution p over the whole vocabulary. For cross-entropy, we select the entry pᵢ corresponding to the correct target.

We can now read the main computation precisely: the context processor produces h; the output head calculates z = W_out h + b_out; and p = Softmax(z) gives the conditional probabilities of the candidates. h holds features computed from context, z contains unnormalized candidate scores, and p is the distribution that answers next-token probability questions.

Knowledge check

What does P(AI | you like) ask? How does it connect to p, z, and h?

5. Putting it together: how do six tasks become one loss?

This exercise combines the earlier sections. Original numbers and links are preserved, but the reading order has changed: first work through inputs/targets, batches, embeddings, logits, Softmax, and cross-entropy, then connect them here. Do not try to memorize a single diagram full of unfamiliar terms.

Scroll horizontally to view all columns.

Course data table
ObjectValue or shapeWhere it comes from and what it does
Original textyou like AI; we like you; you study AITraining material prepared by a person, not generated by the model.
raw IDs[[0,1,2],[4,1,0],[0,3,2]]Encode with the fixed vocabulary, keeping three consecutive tokens in each original sample.
inputs / targets[[0,1],[4,1],[0,3]] / [[1,2],[1,0],[3,2]]Offset the input and target positions within each row to obtain six labeled tasks.
embedding / h[3,2,4] / [3,2,4]The first is a lookup; the second is produced by the chosen model processing the visible context.
logits[3,2,5]Use the same output head at every task to score all five candidates.
mean lossOne numberSelect the corresponding target probability in each row, take its negative logarithm, and average the six losses.

First identify just one task: [0,0] has input you and target_id = 1, meaning like. To check the output head, the hand-calculation example supplies h = [0.20,−0.10,0.70,0.30] and fixed output weights that produce z = [0,2,1,−1,0]. Here h is a given input to a numerical demonstration, not a claim that a full Transformer always sets h equal to the embedding. Weeks 7–8 will open up the component that calculates h.

After vocabulary-axis Softmax, like has probability approximately 0.5923, giving this task loss ≈ 0.5237. If another task sees only you at the same position, the same deterministic model must produce the same distribution. If its target is study, select approximately 0.02949 from that distribution instead, giving loss ≈ 3.5237. The target does not enter the prediction computation.

text
Trace one [b,t] position:
inputs[b,t] → embedding[b,t,:] → h[b,t,:]
           → logits[b,t,:] → p[b,t,:]
targets[b,t] ───────────────→ select the correct entry to compute L[b,t]
Only then take L.mean(). The actual CE API receives logits directly.

Flattening only changes the arrangement: [0,0], [0,1], [1,0], [1,1], [2,0], and [2,1] become rows 0–5 in that order. It does not concatenate contexts from different sentences. Attention has already processed each batch row separately before flattening.

python
# Loss-calculation fragment: logits and targets come from the forward pass and data preparation above.
flat_logits = logits.reshape(6, 5)
flat_targets = targets.reshape(6)  # [1,2,1,0,3,2]
loss = F.cross_entropy(flat_logits, flat_targets)

EX06-Integration: without looking at the answer, explain three things. Which task does flat_logits[4] represent? Why can h and the embedding have the same shape but different values? After backward, which persistent parameters receive gradients and which integers do not?

Knowledge check

Check your answers after completing the integration exercise.

13. Bigram: the smallest language model that sees only the current token

Bigram means a pair of adjacent tokens. This model learns one relationship: given current token x_t, which token tends to follow it in the training data? For example, it can learn that like follows we. It does not examine text preceding the current token. It is not a replacement for a complete GPT, but a minimal baseline whose calculation we can inspect fully.

P(xt+1x1,,xt)=P(xt+1xt)P(x_{t+1}\mid x_1,\ldots,x_t)=P(x_{t+1}\mid x_t)

Scroll horizontally to view all columns.

Course data table
tableshaperow meaningcolumn / feature meaning
input embedding E[V,C]=[5,4]Current tokenFour learnable features
Bigram W_bigram[V,V]=[5,5]Current tokenFive candidate next-token logits
python
import torch
import torch.nn as nn
import torch.nn.functional as F


class BigramLanguageModel(nn.Module):
    def __init__(self, vocab_size: int):
        super().__init__()
        # Each ID retrieves next-token logits, not a semantic feature embedding.
        self.token_table = nn.Embedding(vocab_size, vocab_size)

    def forward(self, token_ids, targets=None):
        # token_ids: [B, T]
        logits = self.token_table(token_ids)
        # logits: [B, T, V_vocab]

        if targets is None:
            return logits, None

        B, T, V_vocab = logits.shape
        loss = F.cross_entropy(
            logits.reshape(B * T, V_vocab),
            targets.reshape(B * T),
        )
        return logits, loss

logits[b,t,:]=Wbigram[token_ids[b,t],:]\mathrm{logits}[b,t,:]=W_{\mathrm{bigram}}[\mathrm{token\_ids}[b,t],:]

Knowledge check

What does a Bigram do, and why can it not distinguish you like from we like?

14. Work through one Bigram training step: form tasks, measure error, and update the score table

The previous Bigram can predict by lookup, but a newly created table has not learned what follows you. Training adjusts that table by repeatedly using the actual next tokens as labels, encouraging suitable candidates to receive more probability. The loop is simply the program structure that repeats these operations.

The training-script body below runs after the BigramLanguageModel class in Section 13. Each step clears previous gradients and makes a fresh prediction using the same tasks and current parameters. Create the model and optimizer only once, outside the loop.

python
# Run after the imports and BigramLanguageModel definition in Section 13.
inputs = torch.tensor([
    [0, 1],  # you→like, like→AI
    [4, 1],  # we→like, like→you
    [0, 3],  # you→study, study→AI
], dtype=torch.long)

targets = torch.tensor([
    [1, 2],
    [1, 0],
    [3, 2],
], dtype=torch.long)

torch.manual_seed(7)
model = BigramLanguageModel(vocab_size=5)
with torch.no_grad():
    model.token_table.weight.zero_()  # Match the all-zero hand calculation.
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)

model.train()
for step in range(500):
    optimizer.zero_grad(set_to_none=True)    # Clear previous gradients.
    logits, loss = model(inputs, targets)   # [3,2,5], scalar
    loss.backward()                        # Gradients at current parameters.
    optimizer.step()                       # Update the score table.

    if step % 100 == 0:
        print("step", step, "loss_before_update", loss.item())

# Recompute with the final parameters to obtain loss after the update.
with torch.no_grad():
    _, final_loss = model(inputs, targets)
print("loss_after_500_updates", final_loss.item())

L=1B×Tbtlogsoftmax(logits[b,t,:])[targets[b,t]]L=-\frac{1}{B\times T}\sum_b\sum_t\log\operatorname{softmax}(\mathrm{logits}[b,t,:])[\mathrm{targets}[b,t]]

Scroll horizontally to view all columns.

Course data table
StepState affected
forwardUse the actual inputs to compute all [3,2,5] logits and the mean loss
zero_gradClear gradients accumulated in the previous iteration
backwardDifferentiate the loss for the six targets with respect to the parameters
stepUse the gradients to update the [5,5] parameter table

Knowledge check

Why does the like row receive training signals from different targets?

7. Autoregressive generation: choose a token, append it, and predict again

Think of generation as a small loop. The model reads the current prompt and produces a probability for every vocabulary candidate. A generator selects one token and adds it to the prompt. The next prompt has changed, so this simple implementation computes the next h, logits, and probabilities again. Feeding a selected output back as the next input is autoregressive generation.

Now compare training and generation. Training text already supplies the actual next tokens, so many input/target positions can be prepared and scored together. During generation, the actual next token is unavailable: we must choose and append the first next_id before we know the input for round 2.

Scroll horizontally to view all columns.

Course data table
AspectTraining: teacher forcingGeneration: autoregressive
Where the next token comes fromThe original training text supplies the correct targetArgmax or sampling chooses next_id from the current distribution
The next inputAll offset input positions can be constructed in advanceIt becomes available only after appending the chosen next_id
Positions processedScore all B×T known training positions togetherSelect one new token per sample using only the current final position
Do parameters change?Loss → backward → optimizer.step updates themNo; we select and append IDs using a fixed model

Knowledge check

After generating like from you, what is the next model input? Why not keep using only you?

15. Select the next token from the final position: argmax and sampling

text
prompt: you like -> [[0,1]], shape [1,2]
logits: [1,2,5]
logits[:, 0, :]: what follows you?
logits[:, 1, :]: what follows like at the final position?
last_logits = logits[:, -1, :]  # [1,5], used to choose this round's appended token

Scroll horizontally to view all columns.

Course data table
strategyruleresult characteristic
ArgmaxAlways choose a highest-probability candidateRepeatable for fixed inputs and model state, but potentially repetitive
SamplingSample according to Softmax probabilitiesHigher-probability tokens occur more often; lower-probability tokens remain possible
python
@torch.no_grad()
def generate(model, token_ids, max_new_tokens, temperature=1.0):
    if temperature <= 0:
        raise ValueError("temperature must be greater than 0")

    model.eval()
    for _ in range(max_new_tokens):
        logits, _ = model(token_ids)              # [B, T, V_vocab]
        last_logits = logits[:, -1, :]            # [B, V_vocab]
        probabilities = F.softmax(
            last_logits / temperature,
            dim=-1,
        )                                         # [B, V_vocab]
        next_id = torch.multinomial(
            probabilities,
            num_samples=1,
        )                                         # [B, 1]
        token_ids = torch.cat(
            (token_ids, next_id),
            dim=1,
        )                                         # [B, T + 1]

    return token_ids

# Greedy alternative:
# next_id = torch.argmax(last_logits, dim=-1, keepdim=True)  # [B,1]

For positive finite temperature τ, use softmax(last_logits / τ): τ = 1 preserves the distribution, τ < 1 sharpens it, and τ > 1 flattens it. Our teaching vocabulary has no end-of-sequence token, so max_new_tokens bounds the loop. This small generation fragment assumes a valid nonempty input and a nonnegative integer token count, and leaves the model in eval mode; later chapters introduce a stricter generation interface.

Knowledge check

Why should next_id have shape [B,1] rather than [B]?

16. The Bigram's key limitation: only the final token matters

python
prompt_a = torch.tensor([[0, 1]])  # you like
prompt_b = torch.tensor([[4, 1]])  # we like

logits_a, _ = model(prompt_a)
logits_b, _ = model(prompt_b)

last_a = logits_a[:, -1, :]
last_b = logits_b[:, -1, :]

assert torch.equal(last_a, last_b)

Both prompts end in ID 1, so the Bigram retrieves W_bigram[1,:] for both. This is not an accidental training result: with a current-token-only lookup into the [V_vocab,V_vocab] table, the earlier you or we has no computational path to affect the final-position output.

alast=blastWbigram[alast,:]=Wbigram[blast,:]P(nexta)=P(nextb)a_{\mathrm{last}}=b_{\mathrm{last}}\Rightarrow W_{\mathrm{bigram}}[a_{\mathrm{last}},:]=W_{\mathrm{bigram}}[b_{\mathrm{last}},:]\Rightarrow P(\mathrm{next}\mid a)=P(\mathrm{next}\mid b)

Our fixed-vocabulary table has only 5×5 = 25 logits. It can learn one-step transitions, making it a useful teaching and data-pipeline baseline, but cannot represent different continuations for the same current token under different longer prefixes.

Scroll horizontally to view all columns.

Course data table
A Bigram sees only the final tokenTargets among the six tasksIdeal limiting probability allocation
you, occurring twicelike / study1/2 each
like, occurring twiceAI / you1/2 each
we, occurring oncelikeCan approach 1
study, occurring onceAICan approach 1

The ideal Bigram's mean-loss infimum on these six tasks is therefore 4×ln(2)/6 ≈ 0.462, not zero. Finite logits can only approach probability 1. The causal Mini GPT in Week 11 can distinguish [you,like] from [we,like], leaving only the two conflicting targets for the identical prefix [you]. Its ideal lower limit is consequently 2×ln(2)/6 ≈ 0.231. The difference comes from available context, not inconsistent arithmetic, and does not promise that a particular small model will train to either limit.

Knowledge check

Why must [you,like] and [we,like] produce identical final logits in the Bigram?

17. Perplexity: how well does the model predict the observed next tokens?

mean_NLL=1NilogP(xix<i),PPL=exp(mean_NLL)\mathrm{mean\_NLL}=-\frac{1}{N}\sum_i\log P(x_i\mid x_{<i}),\qquad \mathrm{PPL}=\exp(\mathrm{mean\_NLL})

Scroll horizontally to view all columns.

Course data table
Probability assigned to each actual tokenmean_NLLPPLIntuition
1/5: uniform over five candidatesln(5) ≈ 1.6095Equivalent to the likelihood of a uniform five-candidate prediction
1/2ln(2) ≈ 0.6932Equivalent average likelihood to a uniform two-candidate prediction
python
import math


@torch.no_grad()
def evaluate_perplexity(model, data_loader):
    model.eval()
    total_nll = 0.0
    total_tokens = 0

    for inputs, targets in data_loader:
        logits, _ = model(inputs)                 # [B, T, V_vocab]
        V_vocab = logits.size(-1)
        nll_sum = F.cross_entropy(
            logits.reshape(-1, V_vocab),
            targets.reshape(-1),
            reduction="sum",
        )
        total_nll += float(nll_sum)
        total_tokens += targets.numel()  # This example assumes no ignored/padded targets.

    if total_tokens == 0:
        raise ValueError("No tokens are available for evaluation")

    return math.exp(total_nll / total_tokens)

PPL = exp(mean token loss), the reciprocal of the geometric mean of the correct-token probabilities. For probabilities 0.5 and 0.125, mean loss is [ln(2)+ln(8)]/2 = ln(4), so PPL = 4. It is not the arithmetic mean of their reciprocals, (2+8)/2 = 5, nor does it mean the model literally considered four equally likely tokens. Different tokenizers change the units being scored, so perplexity alone cannot directly compare their language capability.

Knowledge check

When is perplexity = torch.exp(loss) appropriate?

18. Week 6 recap: one complete minimal language-model process

Concept sequence
  1. Encode text with the tokenizer into raw IDs [3,3]
  2. Offset inputs and targets to obtain [3,2]
  3. General LM: input embeddings [3,2,4] → context processor / output head → logits [3,2,5]
  4. Bigram: IDs [3,2] → directly retrieve rows of a [5,5] score table → logits [3,2,5]
  5. Flatten into logits [6,5] and targets [6]
  6. F.cross_entropy returns a scalar mean loss
  7. Training: backward → optimizer update. Generation: select from the final position and append
text
Week 6 fixed batch:
raw IDs                              [3,3]
shifted inputs                       [3,2]

General language model path:
IDs                                  [3,2]
input embedding                      [3,2,4]
context model + output head          [3,2,5] vocabulary logits

Bigram shortcut path:
IDs                                  [3,2]
direct W_bigram [5,5] lookup         [3,2,5] vocabulary logits

flattened logits                     [6,5]
flattened targets                    [6]
mean cross-entropy loss              scalar

Generation from prompt [[0,1]]:
prompt IDs                           [1,2]
model logits                         [1,2,5]
last logits[:, -1, :]                [1,5]
next_id                              [1,1]
appended prompt                      [1,3]

Scroll horizontally to view all columns.

Course data table
TrainingGeneration
Text → IDs → offset inputs/targets → [B,T,V] logits → flatten → cross-entropy → backward → updateprompt IDs → [B,T,V] logits → logits[:, -1, :] [B,V] → Argmax/Sampling → next_id [B,1] → append → repeat

Knowledge check

Why do we keep logits, cross-entropy, and logits[:, -1, :] after adding attention in Week 7?

19. From Bigram to attention: let the current position read the prefix

Self-attention lets each position dynamically combine information from positions it may see. A query supplies matching features for the current position; a key supplies matching features for a candidate position; a value supplies the content that will actually be combined. These are typically different learned projections of the same hidden representations, not three extra tokens. The descriptions are useful intuitions, not predefined semantic labels on the coordinates.

Scroll horizontally to view all columns.

Course data table
quantitytypical shapemeaning
query, key[B,T,d_k]Used to match positions
value_states[B,T,d_v]The content combined using the weights
attention scores / weights[B,T,T]Scores / normalized weights for every query and key-position pair
context[B,T,d_v]Weighted values
final vocabulary logits[B,T,V_vocab]The same next-token interface as Week 6
python
import math

# query, key:    [B, T, d_k]
# value_states:  [B, T, d_v]
scores = query @ key.transpose(-2, -1)             # [B, T, T]
scores = scores / math.sqrt(d_k)

# causal_mask broadcasts to [B,T,T]; True means a visible position.
scores = scores.masked_fill(~causal_mask, float("-inf"))
weights = F.softmax(scores, dim=-1)                # [B, T, T]
context = weights @ value_states                   # [B, T, d_v]

scoret,j=qtkjdk,αt,j=softmaxj(scoret,j),contextt=jtαt,jvaluej\mathrm{score}_{t,j}=\frac{q_t\cdot k_j}{\sqrt{d_k}},\qquad \alpha_{t,j}=\operatorname{softmax}_j(\mathrm{score}_{t,j}),\qquad \mathrm{context}_t=\sum_{j\le t}\alpha_{t,j}\,\mathrm{value}_j

Prompts A = [you,like] and B = [we,like] have the same token at position 1, but attention can read their different position-0 inputs. Their final logits can therefore differ. This supplies a path for using longer context; it does not guarantee a particular answer before training or establish understanding on its own.

Knowledge check

How can attention give different final logits to two prompts that both end in like?