LeetLLM
My PlanLearnGlossaryTracksPracticeBlog
LeetLLM

Your go-to resource for mastering AI & LLM systems.

Product

  • Learn
  • Glossary
  • Tracks
  • Practice
  • Blog
  • RSS

Legal

  • Terms of Service
  • Privacy Policy

© 2026 LeetLLM. All rights reserved.

All Topics
Your Progress
0%

0 of 177 articles completed

🛠️Computing Foundations0/9
Git, Shell, Linux for AIDocker for Reproducible AIPython for AI EngineeringNumPy and Tensor ShapesCUDA for ML TrainingMPS & Metal for ML on MacData Structures for AISQL and Data ModelingAlgorithms for ML Engineers
📊Math & Statistics0/8
Gradients and BackpropVectors, Matrices & TensorsLinear Algebra for MLAdam, Momentum, SchedulersProbability for Machine LearningStatistics and UncertaintyDistributions and SamplingHypothesis Tests, Intervals, and pass@k
📚Preparation & Prerequisites0/13
Neural Networks from ScratchCNNs from ScratchTraining & BackpropagationSoftmax, Cross-Entropy & OptimizationRNNs, LSTMs, GRUs, and Sequence ModelingAutoencoders and VAEsThe Transformer Architecture End-to-EndLanguage Modeling & Next TokensFrom GPT to Modern LLMsPrompt Engineering FundamentalsCalling LLM APIs in ProductionFirst AI App End-to-EndThe LLM Lifecycle
🧮ML Algorithms & Evaluation0/11
Linear Regression from ScratchLogistic Regression and MetricsDecision Trees, Forests, and BoostingReinforcement Learning BasicsValidation and LeakageClustering and PCACore Retrieval AlgorithmsDecoding AlgorithmsExperiment Design and A/B TestingPyTorch Training LoopsDataset Pipelines and Data Quality
📦Production ML Systems0/6
Feature Engineering for Production MLBatch and Streaming Feature PipelinesGradient Boosted Trees in ProductionRanking and Recommendation SystemsForecasting and Anomaly DetectionMonitoring Predictive Models
🧪Core LLM Foundations0/8
The Bitter Lesson & ComputeBPE, WordPiece, and SentencePieceStatic to Contextual EmbeddingsPerplexity & Model EvaluationFile Ingestion for AIChunking StrategiesLLM Benchmarks & LimitationsInstruction Tuning & Chat Templates
🧰Applied LLM Engineering0/24
Dimensionality Reduction for EmbeddingsCoT, ToT & Self-Consistency PromptingFunction Calling & Tool UseMCP & Tool Protocol StandardsContext EngineeringPrompt Injection DefenseResponsible AI GovernanceData Labeling and Human FeedbackEvaluating AI AgentsProduction RAG PipelinesHybrid Search: Dense + SparseReranking and Cross-Encoders for RAGRAG Evaluation for Reliable AnswersLLM-as-a-Judge EvaluationBias & Fairness in LLMsHallucination Detection & MitigationLLM Observability & MonitoringExperiment Tracking with MLflow and W&BPrompt Optimization with DSPyModel Versioning & DeploymentSemantic Caching & Cost OptimizationLLM Cost Engineering & Token EconomicsModel Gateways, Routing, and FallbacksDesign an Automated Support Agent
🎓Portfolio Capstones0/9
Capstone: Delivery ETA PredictionCapstone: Product RankingCapstone: Demand ForecastingCapstone: Image Damage ClassifierCapstone: Production ML PipelineCapstone: Document QACapstone: Eval DashboardCapstone: Fine-Tuned ClassifierCapstone: Reproducible ML Study
🧠Transformer Deep Dives0/8
Sentence Embeddings & Contrastive LossEmbedding Similarity & QuantizationScaled Dot-Product AttentionVision Transformers and Image EncodersPositional Encoding: RoPE & ALiBiLayer Normalization: Pre-LN vs Post-LNMechanistic InterpretabilityDecoding Strategies: Greedy to Nucleus
🧬Advanced Training & Adaptation0/16
Scaling Laws & Compute-Optimal TrainingPre-training Data at ScaleBuild GPT from Scratch LabJAX for PyTorch ResearchersContinued Pretraining for Domain ShiftSynthetic Data PipelinesSupervised Fine-Tuning PipelineMixed Precision TrainingDistributed Training: FSDP & ZeROLoRA & Parameter-Efficient TuningReward Modeling from Preference DataRLHF & DPO AlignmentConstitutional AI & Red TeamingRLVR & Verifiable RewardsKnowledge Distillation for LLMsModel Merging and Weight Interpolation
🤖Advanced Agents & Retrieval0/16
Vector DB Internals: HNSW & IVFAdvanced RAG: HyDE & Self-RAGGraphRAG & Knowledge GraphsRAG Security & Access ControlStructured Output GenerationReAct & Plan-and-ExecuteGuardrails & Safety FiltersCode Generation & SandboxingComputer-Use / GUI / Browser AgentsHuman-in-the-Loop Agent ArchitectureAI Coding Workflow with AgentsAgent Memory & PersistenceAgent Failure & RecoveryRecursive Language Models (RLM)Multi-Agent OrchestrationCapstone: Production Agent
⚡Inference & Production Scale0/19
Inference: TTFT, TPS & KV CacheMulti-Query & Grouped-Query AttentionKV Cache & PagedAttentionPrefix Caching and Prompt CachingFlashAttention & Memory EfficiencyContinuous Batching & SchedulingScaling LLM InferenceModel Parallelism for LLM InferenceModel Quantization: GPTQ, AWQ & GGUFLocal LLM DeploymentSLM Specialization & Edge DeploymentSpeculative DecodingLong Context Window ManagementMixture of Experts ArchitectureMamba & State Space ModelsReasoning & Test-Time ComputeAdvanced MLOps & DevOps for AIGPU Serving & AutoscalingA/B Testing for LLMs
🏗️System Design Capstones0/9
Content Moderation SystemCode Completion SystemMulti-Tenant LLM PlatformLLM-Powered Search EngineVision-Language Models & CLIPMultimodal LLM ArchitectureDiffusion Models: Images & TextReal-Time Voice AI AgentReasoning & Test-Time Compute
🎤AI Lab Interviewing0/4
AI Lab Coding Interview: Python SystemsAI Lab System Design InterviewAI Lab Behavioral InterviewAI Lab Technical Presentation
🔬Project Deep Dives0/17
Deep Dive - vLLMDeep Dive - SkyRLDeep Dive - FlashAttentionDeep Dive - FlashInferDeep Dive - DeepGEMMDeep Dive - NCCLDeep Dive - MegatronDeep Dive - DeepSpeedDeep Dive - RayDeep Dive - MLflowDeep Dive - PyTorchDeep Dive - TransformersDeep Dive - SGLangDeep Dive - slimeDeep Dive - DeepEPDeep Dive - TinkerDeep Dive - Light-PEFT
Back to Topics
LearnPreparation & PrerequisitesLanguage Modeling & Next Tokens
📝EasyNLP Fundamentals

Language Modeling & Next Tokens

Learn how next-token prediction becomes a trainable language model, from bigram counts and neural n-grams to causal Transformer generation and KV-cache serving.

21 min read
Learning path
Step 25 of 177 in the full curriculum
The Transformer Architecture End-to-EndFrom GPT to Modern LLMs

Personalize this lesson

Adapt explanations and teaching visuals to your background and preferred voice.

A Transformer forward pass produces logits. Softmax turns that vector of scores into a probability distribution over possible next tokens. Language modeling trains that distribution.

A large language model (LLM) does one repeated job: it predicts the next token. You'll build that job from counts into a trainable objective, then see how a causal Transformer uses it during generation.

One token at a time: An LLM doesn't generate a whole paragraph at once. It predicts a single token, appends it to its input, and runs the process again. This is called autoregressive generation.

Two next-token probability charts show result winning after the prompt The function returns the, then a period winning after result is appended to the context. Two next-token probability charts show result winning after the prompt The function returns the, then a period winning after result is appended to the context.
The prompt produces one probability distribution, where `result` leads. After that token is selected and appended, the longer context produces a different distribution, where a period now leads.

What is a language model?

An autoregressive language model is a probability engine for token sequences: given the tokens so far, it assigns a probability distribution over the vocabulary for what comes next.

Editor autocomplete is a useful first approximation. A language model doesn't treat every possible token as equally likely; it ranks them using the code, text, and instructions already in the context.

The probability of a whole sequence factorizes into a product of next-token predictions, one per position. This is just the chain rule of probability.

P(x1,x2,…,xn)=∏t=1nP(xt∣x1,…,xt−1)P(x_1, x_2, \dots, x_n) = \prod_{t=1}^{n} P(x_t \mid x_1, \dots, x_{t-1})P(x1​,x2​,…,xn​)=t=1∏n​P(xt​∣x1​,…,xt−1​)

Read it left to right: the chance of the first token, times the chance of the second given the first, times the chance of the third given the first two, and so on. Each factor is one next-token distribution. That single building block, "distribution over the next token given everything before it," is the entire job. Generation, training, and perplexity are all built on it.

Suppose a model receives this prompt: "The function returns the"

Its actual probabilities depend on its tokenizer, weights, and preceding context. In a simplified example, it might rank four possible continuations like this:

Probability table

Candidate tokenProbabilityWhat reader should notice
result60%Greedy decoding would choose this; sampling makes it the likeliest choice.
value25%Plausible alternatives remain available when sampling.
index10%Lower-ranked words can appear with higher temperature.
exception0.0001%A grammatical token can still be unlikely in this context.

This table is only an excerpt. Tokens omitted from the table receive the remaining probability mass.

Read the distribution before sampling

The table above isn't a list of words the model "knows." It's the model's next-step belief after reading the prompt.

That distinction matters because the same model can behave differently after one extra token.

Prompt so farLikely next tokenWhy
The function returns theresultCommon code explanation completion.
The function returns the cachedvalueNew context changes the expected object.
The function returns the firstindexThe adjective changes the likely noun.
The function returns the plus a style instruction for API docsvalue or responseTask instructions can change likely continuations.

The model doesn't store one fixed answer for "the function returns the." It computes a distribution from the whole context. Change the context, and you change the probabilities.

Try the mental move:

  1. Read this prompt.
  2. Predict three likely next tokens.
  3. Compare your predictions with the options below.
text
1The test runner says the assertion

Possible continuations:

text
1passed 2failed 3timed

All three are plausible. A model doesn't need certainty to generate text. It needs a probability ranking, then the decoding algorithm picks from that ranking.

Tiny vocabulary example

A production vocabulary contains many tokens, with its exact size set by the model's tokenizer. That's too many for a beginner example, so shrink the world to five tokens.

Use this prompt:

The compiler reported

The model produces logits, which are raw scores before softmax:

TokenLogitMeaning
error3.0Strong continuation.
warning1.5Plausible but less likely.
success-1.0Grammatically possible, semantically odd.
the-2.0Usually wrong after this prompt.
</s>-3.0Ending now is unlikely.

Softmax turns those scores into probabilities:

One line in the code deserves explanation before you read it: logits - logits.max().

Why subtract the maximum logit? Because softmax only cares about differences between logits, not their absolute level. If you subtract the same constant from every score, the ranking and final probabilities stay the same. But the numerics get much safer: the largest shifted logit becomes 0, so its exponential is e^0 = 1 instead of some huge number. On tiny toy logits like [3.0, 1.5, -1.0, ...] this barely matters. On real model logits it prevents overflow and keeps the computation stable.

tiny-vocabulary-example.py
1import numpy as np 2 3tokens = ["error", "warning", "success", "the", "</s>"] 4logits = np.array([3.0, 1.5, -1.0, -2.0, -3.0]) 5 6# Subtract max for numerical stability, then exponentiate 7exp = np.exp(logits - logits.max()) 8probs = exp / exp.sum() 9 10for token, prob in zip(tokens, probs): 11 print(f"{token:10s} {prob:.3f}") 12print(f"sum {probs.sum():.3f}") 13print("top token ", tokens[int(np.argmax(probs))])
Output
1error 0.800 2warning 0.178 3success 0.015 4the 0.005 5</s> 0.002 6sum 1.000 7top token error

The exact numbers are less important than the shape:

  1. One token is far ahead.
  2. Another token still has a real chance.
  3. Some tokens are valid but unlikely.
  4. The probabilities add to 1.

That's next-token prediction in miniature.

From bigram tables to neural n-grams

The smallest context-aware language model is a bigram model. It looks at one previous token and predicts the next token from a table of counts:

text
1P(next_token | previous_token)

If the code-comment corpus often contains returns -> value, the row for returns assigns high probability to value. If it never contains returns -> exception, an unsmoothed table assigns that pair zero probability.

Previous tokenCandidate next tokenCountProbability
returnsvalue850.85
returnsresult100.10
returnserror50.05

That table already teaches the core language-modeling contract: read context, produce a probability distribution over the next token. Its weakness is context length. A bigram model can't tell the difference between function returns the and test returns the, because it only sees the final token.

Build a count-based language model

Start with six code-comment fragments. Each adjacent token pair becomes one training event: after returns, which token appeared next?

count-bigram-events.py
1from collections import Counter, defaultdict 2 3messages = [ 4 "function returns value", 5 "function returns value", 6 "method returns result", 7 "handler returns value", 8 "test returns result", 9 "test returns error", 10] 11 12counts = defaultdict(Counter) 13for message in messages: 14 tokens = ["<bos>", *message.split(), "<eos>"] 15 for previous, current in zip(tokens, tokens[1:]): 16 counts[previous][current] += 1 17 18row = counts["returns"] 19total = sum(row.values()) 20for token, count in row.most_common(): 21 print(f"P({token:6s} | returns) = {count}/{total} = {count / total:.3f}") 22print("unseen token probability =", row["exception"] / total)
Output
1P(value | returns) = 3/6 = 0.500 2P(result | returns) = 2/6 = 0.333 3P(error | returns) = 1/6 = 0.167 4unseen token probability = 0.0

An unseen pair receives probability zero in this unsmoothed table. More data or smoothing helps, but a count table still can't share what it learned about related contexts.

An n-gram model predicts the next token from the previous n - 1 tokens:

text
1P(next_token | previous n - 1 tokens)

For tiny corpora, count tables still work. For real language, the table explodes because most exact contexts are rare. A neural n-gram model addresses that sparsity by learning embeddings and a small MLP (multi-layer perceptron):

  1. Look up embeddings for the previous n - 1 tokens.
  2. Concatenate them into one vector.
  3. Apply a matrix multiply, bias, and a nonlinearity such as the Gaussian Error Linear Unit (GELU).
  4. Project to vocabulary logits.
  5. Softmax into next-token probabilities.
text
1previous n - 1 token IDs -> embedding table -> joined embeddings -> matmul + GELU -> logits -> softmax

This is the bridge from count-based autocomplete to modern LLMs. The objective stays next-token prediction. The model family changes: bigram table, neural n-gram MLP, recurrent network, then Transformer. Each step gives the model a better way to use context.

A sparse bigram transition heatmap is compared with a neural n-gram pipeline that embeds returns and cached into a two-by-six matrix, joins twelve features, applies shared twelve-by-sixteen weights, and produces logits with value highest. A sparse bigram transition heatmap is compared with a neural n-gram pipeline that embeds returns and cached into a two-by-six matrix, joins twelve features, applies shared twelve-by-sixteen weights, and produces logits with value highest.
The bigram model selects one sparse probability row from the previous token. The neural n-gram instead embeds both context tokens, joins their features, and applies shared matrices before producing next-token logits.

Train a tiny neural n-gram

The next cell trains a small two-token-context, or trigram, model. It isn't a Transformer, but it exposes the same contract: embeddings and shared weights produce logits, and cross-entropy rewards the true next token.

tiny-neural-ngram.py
1import torch 2from torch import nn 3 4torch.manual_seed(7) 5sentences = [ 6 "function returns cached value", 7 "method returns cached value", 8 "handler returns cached value", 9 "function returns default result", 10 "method returns default result", 11 "test returns default error", 12] 13vocab = sorted({word for sentence in sentences for word in sentence.split()}) 14to_id = {word: index for index, word in enumerate(vocab)} 15 16contexts, targets = [], [] 17for sentence in sentences: 18 words = sentence.split() 19 for index in range(2, len(words)): 20 contexts.append([to_id[words[index - 2]], to_id[words[index - 1]]]) 21 targets.append(to_id[words[index]]) 22 23x = torch.tensor(contexts) 24y = torch.tensor(targets) 25model = nn.Sequential( 26 nn.Embedding(len(vocab), 6), 27 nn.Flatten(), 28 nn.Linear(12, 16), 29 nn.GELU(), 30 nn.Linear(16, len(vocab)), 31) 32optimizer = torch.optim.Adam(model.parameters(), lr=0.05) 33 34with torch.no_grad(): 35 initial_loss = nn.functional.cross_entropy(model(x), y).item() 36for _ in range(120): 37 loss = nn.functional.cross_entropy(model(x), y) 38 optimizer.zero_grad() 39 loss.backward() 40 optimizer.step() 41 42query = torch.tensor([[to_id["returns"], to_id["cached"]]]) 43with torch.no_grad(): 44 final_loss = nn.functional.cross_entropy(model(x), y).item() 45 predicted_id = int(model(query).argmax(dim=-1).item()) 46 47print(f"loss before training: {initial_loss:.3f}") 48print(f"loss after training: {final_loss:.3f}") 49print("after 'returns cached':", vocab[predicted_id])
Output
1loss before training: 2.304 2loss after training: 0.390 3after 'returns cached': value

The tiny corpus is deliberately simple, so this cell demonstrates optimization rather than a production-quality model. The important bridge is that a neural model can use one set of learned parameters across contexts instead of storing an isolated probability row for every exact phrase.

Temperature changes sampling

A temperature parameter rescales logits before softmax: softmax(logits / T), where T > 0. Values below 1 stretch logit gaps and sharpen the distribution; values above 1 compress gaps and give lower-ranked tokens more probability. Adding the same constant to every logit wouldn't change softmax at all. APIs that expose a zero-temperature mode should implement it as greedy decoding rather than divide by zero.

For logits [3.0, 1.5, -1.0, -2.0, -3.0], the two leading probabilities change directly:

compare-temperatures.py
1import numpy as np 2 3logits = np.array([3.0, 1.5, -1.0, -2.0, -3.0]) 4for temperature in [0.5, 1.0, 2.0]: 5 scaled = logits / temperature 6 probabilities = np.exp(scaled - scaled.max()) 7 probabilities /= probabilities.sum() 8 ranked = np.sort(probabilities)[::-1] 9 if temperature == 1.0: 10 print(f"correctly {probabilities[0]:.3f}") 11 print(f"slowly {probabilities[1]:.3f}") 12 print(f"T={temperature:.1f} top={ranked[0]:.3f} second={ranked[1]:.3f}")
Temperature comparison
1T=0.5 top=0.952 second=0.047 2correctly 0.800 3slowly 0.178 4T=1.0 top=0.800 second=0.178 5T=2.0 top=0.575 second=0.272

Production note: Low temperature makes source-grounded API-doc answers more repeatable, but it doesn't make unsupported facts true. Higher temperature produces more varied drafts for creative tools. Temperature is one lever among several for shaping generation; a later chapter covers the full decoding toolkit.

Causal language modeling

This specific flavor of language modeling, reading from left to right and predicting the future, is called causal language modeling (or autoregressive modeling). GPT-style models use this objective.[1]Reference 1Improving Language Understanding by Generative Pre-Training.https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf[2]Reference 2Language Models are Unsupervised Multitask Learners.https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf

It's called "causal" because the model is bound by causality: each position can use earlier context, but it can't peek at future tokens. In a Transformer block, a token position may attend to itself and earlier positions; the mask blocks later positions.

GPT-style decoder models are causal language models.

ObjectiveCan readGood forPoor fit
Causal language modelingEarlier context, no future tokensGenerating text left to rightFilling a middle blank with future context
Masked language modelingLeft and right contextClassification, extraction, sentence understandingOpen-ended generation

Masked language modeling, used by Bidirectional Encoder Representations from Transformers (BERT), hides a token and predicts it from both left and right context.[3]Reference 3BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding.https://arxiv.org/abs/1810.04805 That two-sided objective builds input representations; the left-to-right generation loop needs causal modeling.


How the model learns the distribution

We keep saying the model "predicts" the next token. Where does that skill come from? From training on text where the next token is already known. Every sentence in the training data is a free set of labeled examples: at each position, the correct answer is the token that came next.

Teacher forcing

During training the model is fed the real previous tokens at every position, not the tokens it would have guessed. This is called teacher forcing. The model never gets to wander off on its own mistakes while learning; each position is scored against ground truth using the true history.

This distinction matters whenever you interpret a training loss or measure generation latency:

PhaseWhat feeds position tWhy
Training (teacher forcing)The true tokens x_1 ... x_{t-1} from the datasetStable targets; all positions can be scored in one parallel forward pass.
Generation (autoregressive)The model's own previously sampled tokensNo ground truth exists; the model must build on what it produced.

Because the true history is known up front, training scores every position of a sequence in a single forward pass. The causal mask is what makes this honest: the logit produced from prefix x_1 ... x_{t-1} can't read its target token x_t.

This side-by-side view helps lock in why training is parallel while generation isn't:

Teacher forcing shows shifted input and target token rows with four true-token probabilities scored together, while generation shows a four-step staircase where each selected token expands the next context. Teacher forcing shows shifted input and target token rows with four true-token probabilities scored together, while generation shows a four-step staircase where each selected token expands the next context.
Teacher forcing shifts one known sequence into inputs and targets, so all four true-token probabilities can be scored in one masked pass. Generation has no target row; each selected token must be appended before the next row exists.

Shift tokens into inputs and labels

For a decoder, one training sequence supplies several labeled predictions. The input is the sequence shifted right; the target is the same sequence shifted left.

Context visible to modelCorrect next token
<bos>function
<bos> functionreturns
<bos> function returnsvalue
<bos> function returns value<eos>
shift-next-token-labels.py
1import numpy as np 2 3vocab = {"<bos>": 0, "function": 1, "returns": 2, "value": 3, "<eos>": 4} 4tokens = ["<bos>", "function", "returns", "value", "<eos>"] 5ids = np.array([vocab[token] for token in tokens]) 6inputs = ids[:-1] 7targets = ids[1:] 8 9print("input token ids: ", inputs.tolist()) 10print("target token ids:", targets.tolist()) 11for position in range(len(inputs)): 12 context = " ".join(tokens[: position + 1]) 13 print(f"{context:25s} -> {tokens[position + 1]}")
Output
1input token ids: [0, 1, 2, 3] 2target token ids: [1, 2, 3, 4] 3<bos> -> function 4<bos> function -> returns 5<bos> function returns -> value 6<bos> function returns value -> <eos>

Cross-entropy: the training loss

At each position the model outputs a distribution over the vocabulary. We want the probability it assigns to the true next token to be high. The loss for one position is the negative log of that probability, and the loss for a sequence is the average over positions. This is cross-entropy loss, the same objective introduced in the softmax chapter, now applied at every position.

L=−1n∑t=1nlog⁡P(xt∣x1,…,xt−1)\mathcal{L} = -\frac{1}{n} \sum_{t=1}^{n} \log P(x_t \mid x_1, \dots, x_{t-1})L=−n1​t=1∑n​logP(xt​∣x1​,…,xt−1​)

If the model gives the true token probability 1.0, that term contributes -log(1) = 0 loss: a perfect prediction. If it gives the true token a tiny probability, -log of a small number is large: a big penalty. Causal pretraining minimizes this average over a large corpus.

The logits at every shifted position can be scored together. This cell calculates stable cross-entropy for the four targets above.

cross-entropy-over-positions.py
1import numpy as np 2 3targets = np.array([1, 2, 3, 4]) # function, returns, value, <eos> 4logits = np.array([ 5 [0.0, 3.0, 0.5, -1.0, -2.0], 6 [-1.0, 0.1, 2.5, 0.0, -2.0], 7 [-1.0, 0.0, 0.2, 2.0, -1.5], 8 [-2.0, 0.0, -1.0, -0.5, 2.4], 9]) 10shifted = logits - logits.max(axis=1, keepdims=True) 11probs = np.exp(shifted) / np.exp(shifted).sum(axis=1, keepdims=True) 12true_probs = probs[np.arange(len(targets)), targets] 13losses = -np.log(true_probs) 14 15print("true-token probabilities:", np.round(true_probs, 3).tolist()) 16print("per-token losses: ", np.round(losses, 3).tolist()) 17print(f"mean cross-entropy: {losses.mean():.3f}")
Output
1true-token probabilities: [0.864, 0.824, 0.724, 0.839] 2per-token losses: [0.146, 0.194, 0.323, 0.175] 3mean cross-entropy: 0.209

Perplexity: loss in token units

Cross-entropy is measured in nats (because we used natural log), which is hard to feel. Perplexity is the friendlier form: just exponentiate the average loss.

Perplexity=exp⁡(L)\text{Perplexity} = \exp(\mathcal{L})Perplexity=exp(L)

Perplexity is often interpreted as an effective number of equally likely choices per step. A perplexity of 1 means the model assigned probability 1 to every evaluated token. A model that predicts uniformly over a 50,000-token vocabulary has perplexity 50,000, but the converse doesn't follow: a measured perplexity of 50,000 alone doesn't prove every prediction was uniform or that the model learned nothing. Lower is better when the tokenizer, evaluated data, and averaging convention match.[4]Reference 4A Mathematical Theory of Communicationhttps://people.math.harvard.edu/~ctm/home/text/others/shannon/entropy/entropy.pdf[5]Reference 5Perplexity — a Measure of the Difficulty of Speech Recognition Tasks.https://doi.org/10.1121/1.2016299

Work a tiny example by hand. Suppose the model predicts four tokens, assigning the true token these probabilities: 0.5, 0.5, 0.5, 0.125.

perplexity-by-hand.py
1import numpy as np 2 3# Probability the model gave to the token that was actually correct, per position 4true_token_probs = np.array([0.5, 0.5, 0.5, 0.125]) 5 6per_position_loss = -np.log(true_token_probs) # cross-entropy term per token 7avg_loss = per_position_loss.mean() # the training loss 8perplexity = np.exp(avg_loss) # loss in "effective choices" 9 10print(f"per-position loss: {np.round(per_position_loss, 4)}") 11print(f"average loss: {avg_loss:.4f}") 12print(f"perplexity: {perplexity:.4f}") 13 14# Perplexity equals the reciprocal geometric mean of the true-token probabilities 15geo_mean = np.prod(true_token_probs) ** (1 / len(true_token_probs)) 16print(f"1 / geometric mean:{1 / geo_mean:.4f}")
Output
1per-position loss: [0.6931 0.6931 0.6931 2.0794] 2average loss: 1.0397 3perplexity: 2.8284 41 / geometric mean:2.8284

A perplexity of about 2.83 means that, averaged across these four steps, the model behaved as if it were choosing between roughly 2.83 equally likely options. The one bad prediction (0.125) drags the average up. That's the whole point of the metric: it tells you, in token units, how surprised the model was by real text.

When you fine-tune a model and watch the loss curve, you're watching average cross-entropy fall. When an eval report uses the same tokenizer, evaluated tokens, and natural-log averaging, perplexity is exp of that same loss. Hold those choices constant before treating a mismatch as a regression.


The autoregressive loop

When you ask a chat model to write an API-doc sentence, it doesn't output the whole message in one step. It generates the message one token at a time in a loop.

This is the autoregressive generation loop:

  1. Input: The model receives the prompt.
  2. Predict: It calculates the probabilities for the very next token.
  3. Pick: We take the most likely token or sample from the distribution, optionally adjusting temperature.
  4. Append: That chosen token is glued to the end of the prompt.
  5. Repeat: The new, longer context determines the distribution for the next token.

This list describes the dependency between generated tokens, not an inefficient serving implementation. Production decode usually reuses earlier attention state through a key-value (KV) cache. It still performs one sequential decode step for each new token, but it doesn't rebuild earlier keys and values. The serving section below makes that tradeoff concrete.

Diagram showing Prompt: 'The API returns', Model predicts next token, Sample: 'JSON', and Append to context. Diagram showing Prompt: 'The API returns', Model predicts next token, Sample: 'JSON', and Append to context.
Prompt: 'The API returns', Model predicts next token, Sample: 'JSON', and Append to context.

This explains why generation speed is measured in tokens per second (t/s). The model has to run another decoding step for every generated token. A KV cache avoids recomputing earlier keys and values from scratch, but each new token still has to attend to previous context.

A tiny generation loop in code

This example isn't a neural network. It's a small hand-written language model that shows the same loop: look at current text, choose one next token, append it, and repeat.

a-tiny-generation-loop-in-code.py
1next_token_table = { 2 "The function returns the": [("value", 0.60), ("result", 0.30), ("index", 0.10)], 3 "The function returns the value": [(".", 0.80), ("when", 0.20)], 4 "The function returns the value.": [("</s>", 1.0)], 5} 6 7text = "The function returns the" 8 9for step in range(3): 10 choices = next_token_table[text] 11 token, _probability = max(choices, key=lambda item: item[1]) 12 13 if token == "</s>": 14 break 15 16 text = text + ("" if token == "." else " ") + token 17 print(step, repr(text)) 18print("final text:", repr(text))
Output
10 'The function returns the value' 21 'The function returns the value.' 3final text: 'The function returns the value.'

Real LLMs replace the hand-written table with a Transformer, but the control flow is the same.

Diagram showing Text so far, Decode next position, Logits + temperature, and Sample one token. Diagram showing Text so far, Decode next position, Logits + temperature, and Sample one token.
Text so far, Decode next position, Logits + temperature, and Sample one token.

The loop ends when one of these things happens:

Stop conditionExample
Model emits an end token</s> or another stop marker.
Application or API hits an output-token limitThe server stops after a token budget.
Application stop string appearsThe app stops at \n\nUser: or a JSON boundary.
Safety or policy system interruptsThe provider refuses or truncates output.

Picking a token from the distribution

The loop says "sample a token," but how? The model hands you a distribution, and a decoding strategy turns that distribution into one chosen token. Greedy decoding is the simplest strategy: always take the highest-probability token. It's deterministic, but it can fall into loops, repeating "We apologize for the inconvenience. We apologize for the inconvenience..." because "apologize" keeps scoring highest after "We."

The alternative is to sample: draw a token at random in proportion to its probability, so the second- or third-ranked token sometimes wins. Sampling can reduce repetition and add variety, but careless sampling can also hurt quality. The temperature knob from earlier controls how adventurous that draw is. Greedy versus sampling is the core fork; the full toolkit of decoding methods (and how to tune them) gets a dedicated chapter later, so here we only need the idea that one distribution can be turned into text in more than one way.

Common mistake: Setting a high temperature in factual docs generation and wondering why answers drift away from the source, or setting temperature to 0 in a creative app and wondering why every user gets the same paragraph. At each step, the current context produces a distribution; the decoding strategy decides how much you gamble on it.

Use a seeded random generator when testing a sampler, so samples repeat.

greedy-versus-sampling.py
1import numpy as np 2 3tokens = np.array(["value", "result", "index"]) 4logits = np.array([2.4, 1.7, 0.2]) 5 6def probabilities(temperature): 7 scaled = logits / temperature 8 exp = np.exp(scaled - scaled.max()) 9 return exp / exp.sum() 10 11rng = np.random.default_rng(4) 12greedy = tokens[int(logits.argmax())] 13samples = [str(rng.choice(tokens, p=probabilities(1.4))) for _ in range(8)] 14 15print("greedy:", greedy) 16print("sampled at temperature 1.4:", samples) 17print("probabilities:", np.round(probabilities(1.4), 3).tolist())
Output
1greedy: value 2sampled at temperature 1.4: ['index', 'value', 'index', 'value', 'result', 'value', 'result', 'value'] 3probabilities: [0.551, 0.334, 0.115]

The context window limit

Because the output is constantly being appended to the input, the text the model has to read keeps growing. Late in a 1000-token support answer, the model is reading the original prompt plus the 999 tokens it already generated.

This growing sequence must fit into the model's context window, its short-term memory limit. If the context window is 8,000 tokens and the prompt plus generated text reaches 8,001 tokens, the system has to truncate earlier context, reject the request, summarize, or use a longer-context strategy.

What the KV cache changes

Without a cache, the model would recompute attention information for the full prompt at every generation step. That would be wasteful.

The KV cache stores key and value tensors from earlier tokens at each attention layer. New decode queries can attend to these cached tensors instead of rebuilding earlier keys and values at every step.[6]Reference 6Efficiently Scaling Transformer Inference.https://arxiv.org/abs/2211.05102

Serving systems usually split this into two phases:

  • Prefill: process the full prompt in parallel and build the initial KV cache.
  • Decode: generate one new token at a time, appending each token and reusing the cache.
KV-cache diagram showing the four-token prompt The function returns the written to key and value rows during prefill, then value and period appended in two decode steps before an end token stops generation without another cache column; memory bars compare 1 GiB at 2,048 tokens with 16 GiB at 32,768 tokens. KV-cache diagram showing the four-token prompt The function returns the written to key and value rows during prefill, then value and period appended in two decode steps before an end token stops generation without another cache column; memory bars compare 1 GiB at 2,048 tokens with 16 GiB at 32,768 tokens.
Prefill writes key and value columns for `The function returns the` in parallel. Decode appends columns for `value` and `.`, then `</s>` stops generation without another append. With the example settings below, increasing sequence length 16× grows KV-cache memory from 1 GiB to 16 GiB.
StepNaive workWith KV cache
Select generated token 1 (prefill)Process the prompt; use its final-position logits.Process the prompt once, select token 1, and retain the prompt's keys and values.
Select generated token 2Recompute the prompt plus generated token 1.Process token 1's new position against the cached prompt, then append its keys and values.
Select generated token 3Recompute the prompt plus generated tokens 1 and 2.Process token 2's new position against the earlier cache, then append its keys and values.
Long answerWork keeps repeating old context.Memory grows, but old computation is reused.

Serving systems care so much about KV cache memory because the cache is both an accelerator and a memory bill. It speeds generation, but it also consumes GPU memory proportional to context length, number of layers, key/value-head count, head dimension, and batch size.

Production note: If a model seems cheap at 4K context but expensive at 128K context, check KV cache memory before blaming the model weights. Long context changes serving economics.

Estimate cache memory

A simplified cache estimate makes the tradeoff concrete. For each request, layer, token, and key/value head, the server stores one key vector and one value vector. The estimate uses bfloat16 or float16 values at two bytes each.

kv-cache-memory-budget.py
1batch = 4 2layers = 32 3kv_heads = 8 4head_dim = 128 5bytes_per_value = 2 6 7def cache_gib(sequence_length): 8 values = batch * layers * sequence_length * kv_heads * head_dim * 2 9 return values * bytes_per_value / (1024 ** 3) 10 11for sequence_length in [2_048, 32_768]: 12 print(f"{sequence_length:>6,} tokens: {cache_gib(sequence_length):.2f} GiB")
Output
12,048 tokens: 1.00 GiB 232,768 tokens: 16.00 GiB

This is only the KV cache, not model weights, activations, allocator overhead, or batching metadata. It also shows why fewer key/value heads can reduce cache cost.


Why next-token training can learn useful structure

Across large mixtures of code, math, dialogue, and documentation, local word association often isn't enough to minimize next-token loss. Reusable features for syntax, arithmetic, and task structure can improve predictions across many examples.

Worked prompt

Consider this prompt: "A job has a 5-second deadline. After 2 seconds elapsed, the remaining budget is"

Model familyLikely shortcutBetter internal feature
Markov-style autocompleteNearby token pattern like 2, 3None; it mostly follows local statistics.
Large language modelStill predicts one tokenA subtraction-like representation helps reduce loss across many examples.

A simple Markov chain might look at 2 and guess 3 because 2, 3 is common. A larger neural language model can benefit from features that track subtraction-like structure across many related examples, because those features can improve next-token predictions. This is a pressure from the objective, not a guarantee of correct arithmetic.

Next-token prediction explains the training signal, not human-like understanding or guaranteed reasoning. Memorization can occur, and each claimed capability still needs task-specific measurement.[1]Reference 1Improving Language Understanding by Generative Pre-Training.https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf[7]Reference 7Language Models are Few-Shot Learners.https://arxiv.org/abs/2005.14165

Common failure cases

Treating probabilities as facts

The model's top token can be wrong.

Suppose an internal docs assistant sees:

The rate-limit page says free-tier requests are

A model might assign high probability to unlimited because that phrase is common in marketing copy. But if your current API contract says free-tier requests are capped at 60 per minute, the statistically likely continuation is still wrong.

The fix isn't to hope the base model "knows" your API contract. Put the trusted spec into context, retrieve the right document, or use a tool that checks the source of truth.

SymptomCauseFix
Plausible but false answerTraining distribution dominates missing local facts.Add retrieved source text.
Old limit repeatedModel has stale or generic priors.Query current docs or database.
Confident unsupported claimProbability isn't evidence.Require citations or source-grounded output.
Long answer driftsContext fills with generated text.Re-anchor with instructions and source snippets.

The "database" myth

An LLM doesn't query a hidden fact table during generation. Learned parameters can reproduce stale, conflicting, or fabricated continuations. Ground current facts with retrieval, tools, or explicit source context.

Mastery check

Key concepts

  • Language-model objective: next-token prediction
  • Chain-rule factorization of sequence probability
  • Bigram and neural n-gram models share the next-token target
  • Teacher forcing: training on true history in one parallel pass
  • Cross-entropy loss as averaged negative log-likelihood
  • Perplexity equals exp(loss): effective choices per token
  • Autoregressive generation loop
  • Causal vs masked language modeling (GPT vs BERT)
  • Context window limits and KV-cache serving tradeoffs
  • The database myth: LLMs generate instead of retrieving facts

Evaluation rubric

  • Foundational: Defines an autoregressive language model, writes the chain-rule factorization, and traces the append-and-repeat generation loop.
  • Foundational: Explains bigram counts, softmax, temperature, and the difference between greedy decoding and sampling.
  • Intermediate: Connects neural n-gram and Transformer logits to teacher forcing, cross-entropy, and perplexity.
  • Intermediate: Contrasts causal and masked language modeling, then explains why training is parallel while generation is sequential.
  • Intermediate: Estimates KV-cache growth and distinguishes likely continuations from grounded facts.

Follow-up questions

Common pitfalls

  • Symptom: A creative demo keeps giving nearly identical completions. Cause: Decoding is too deterministic, often from greedy decoding or very low temperature. Fix: Sample from the distribution and raise temperature carefully instead of always taking the top token.
  • Symptom: Training loss looks good, but long generations drift after one bad token. Cause: Teacher forcing was confused with real generation, so evaluation never tested the model feeding on its own outputs. Fix: Run autoregressive generation during eval, not loss-only checks.
  • Symptom: A model answers a policy question confidently but cites the wrong company rule. Cause: High next-token probability was treated like evidence, even though the model is generating from patterns instead of querying a live source. Fix: Retrieve the current policy or call a tool before trusting the answer.
  • Symptom: Generation gets slower or more expensive as outputs get longer. Cause: The key-value cache saves recomputation but still grows with context length, layers, and batch size. Fix: Watch context budgets and cache memory, not model weight size alone.
  • Symptom: Someone claims perplexity and cross-entropy disagree. Cause: They forgot perplexity is exp(loss) under natural-log averaging, or compared runs with different token accounting. Fix: Hold tokenizer, evaluated tokens, and averaging convention constant before comparing them.
  • Symptom: Bigram tables, neural n-grams, and GPT-style models feel unrelated. Cause: Architecture changes were mistaken for objective changes. Fix: Trace all three back to the same job: predict the next token from context.
Complete the lesson

Mastery Check

Answer every question, then check your score. Score 75% or higher to mark this lesson complete.

1.A tiny causal language model assigns these next-token probabilities for the sequence <bos> function returns value <eos>: P(function | ) = 0.6, P(returns | function) = 0.8, P(value | function returns) = 0.25, and P( | function returns value) = 0.5. What probability does it assign to the whole sequence after <bos>?
2.An unsmoothed bigram table observes returns -> value 3 times, returns -> result 2 times, and returns -> error once, but never observes returns -> exception. Which result and model change are correct?
3.One system must generate a reply from left to right. Another must predict a hidden word using text on both sides of the blank. Which objectives fit these tasks?
4.For the training sequence <bos> function returns value <eos>, a decoder input is shifted right. Which input-target pairing and speed implication are correct?
5.At four positions, a model assigns the true next token probabilities 0.5, 0.5, 0.5, and 0.125. Using natural logs, what are its mean cross-entropy and perplexity?
6.An API-doc model has mean cross-entropy loss 0.69 on a held-out docs dataset, so its perplexity is about exp(0.69) = 2.0. It still answers a question about today's rate limit with an old rule. What conclusion and fix follow?
7.A creative-writing demo uses greedy decoding, which always takes the highest-probability next token, so every user gets nearly the same safe paragraph. The product goal is more variety while still respecting the model's next-token probability distribution. Which decoding change best fits this goal?
8.An application has an 8,000-token context window, starts with a 7,600-token prompt, and retains every generated token without truncation or summarization. What happens after it generates 400 tokens?
9.In the simplified KV-cache estimate, a request batch uses 1.00 GiB of cache at 2,048 tokens. All other settings stay fixed and sequence length grows to 32,768 tokens. What should the serving team expect?
10.A language model improves on unfamiliar subtraction prompts after training on diverse math, code, and text examples. Which conclusion is justified by the next-token objective?

10 questions remaining.

Next Step
Continue to From GPT to Modern LLMs

You can now explain the objective, loss, and generation loop. Next you'll see how GPT and later model families turn that same loop into modern assistants and tools.

PreviousThe Transformer Architecture End-to-End
Share this article
XFacebookLinkedInBlueskyRedditHacker NewsEmail
References

Improving Language Understanding by Generative Pre-Training.

Radford, A., et al. · 2018

Language Models are Unsupervised Multitask Learners.

Radford, A., et al. · 2019

BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding.

Devlin, J., et al. · 2019 · NAACL 2019

A Mathematical Theory of Communication

Claude E. Shannon · 1948

Perplexity — a Measure of the Difficulty of Speech Recognition Tasks.

Jelinek, F., et al. · 1977

Efficiently Scaling Transformer Inference.

Pope, R., et al. · 2023 · arXiv preprint

Language Models are Few-Shot Learners.

Brown, T., et al. · 2020 · NeurIPS 2020

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.