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 196 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/9
Gradients and BackpropVectors, Matrices & TensorsLinear Algebra for MLAdam, Momentum, SchedulersProbability for Machine LearningBayesian Inference from EvidenceStatistics 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/15
Linear Regression from ScratchLogistic Regression and MetricsDecision Trees, Forests, and BoostingReinforcement Learning BasicsValidation and LeakageStatistical Learning and GeneralizationConformal Prediction and UncertaintyClustering and PCAProbabilistic Generative ModelsCore Retrieval AlgorithmsDecoding AlgorithmsExperiment Design and A/B TestingCausal Inference and CounterfactualsPyTorch 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/25
Dimensionality Reduction for EmbeddingsCoT, ToT & Self-Consistency PromptingFunction Calling & Tool UseMCP & Tool Protocol StandardsContext EngineeringPrompt Injection DefenseResponsible AI GovernancePrivacy-Preserving Machine LearningData 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/17
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 TuningTraining Run OperationsReward 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/31
Inference: TTFT, TPS & KV CacheMulti-Query & Grouped-Query AttentionKV Cache & PagedAttentionPrefix Caching and Prompt CachingFlashAttention & Memory EfficiencyContinuous Batching & SchedulingScaling LLM InferenceModel Parallelism for LLM InferenceAccelerator Architecture Field GuideCorrect GPU Profiling and BenchmarkingGPU Kernel Performance Engineering LabGPU Parallel Primitives LabGPU Compilers and Kernel LanguagesGEMM From Naive CUDA to Tensor CoresLow-Precision GPU ProgrammingGPU Runtime Execution LabAI-Generated Kernel EngineeringModel 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 & AutoscalingGPU Failure OperationsDistributed Inference Data PlaneServing Benchmark EngineeringA/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 Agent System Design
🎤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.

25 min read
Learning path
Step 26 of 196 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.

The previous lesson traced a Transformer from token IDs all the way to unnormalized vocabulary scores. Now an editor prompt ends with The function returns the. How does text turn into a probability distribution over what comes next, and what mechanics govern the loop after the model picks a continuation?

A token is the atomic unit of text in the model's vocabulary. Tokenizers frequently split long words or prefix tokens with spaces, but our running example treats each visible word and punctuation mark as a distinct token. If the model chooses result, the conditioning context grows to The function returns the result. The updated distribution might next favor a period. This append-and-predict cycle is 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.
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.

Next-token scores, not a whole answer

An autoregressive language model doesn't emit completed answers or prewritten paragraphs in a single computational leap. Instead, it assigns a categorical probability distribution over the vocabulary for the single token that follows the prefix seen so far. A GPT-style large language model (LLM) executes this objective at immense scale.[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 Its output at each step is a vector of vocabulary scores, not a fixed sentence.

Suppose a model reads this four-token prompt:

"The function returns the"

The model's actual probabilities depend on its tokenizer, parameter weights, and earlier conversation context. In a simplified vocabulary slice, it might rank candidate continuations like this:

Next-token distributions after a prompt

Candidate tokenProbabilitySampling behavior
result60%Greedy argmax picks this directly; sampling makes it the most frequent choice.
value25%Plausible alternative continuation; frequently chosen under stochastic sampling.
index10%Lower-ranked candidate; selected occasionally when temperature is elevated.
Other tokens combined5%The remaining vocabulary mass distributed across thousands of long-tail tokens.

These values are didactic numbers rather than outputs from a specific checkpoint. The final row combines all omitted vocabulary entries so the total probability mass sums to exactly 100%.

Reading the distribution before sampling

The table above doesn't represent static facts stored in a database row. It represents the model's conditional belief given the prompt so far. Add even a single token to the input, and the model's conditional distribution shifts immediately.

Prompt contextTop continuationWhy the prediction shifts
The function returns theresultStandard completion in code documentation corpora.
The function returns the cachedvalueThe adjective cached shifts semantic expectation toward stored objects.
The function returns the firstindexThe ordinal modifier changes the likely noun to an offset or index.
The function returns the (REST API doc context)responseSystem instructions and surrounding context reshape the token ranking.

The network doesn't memorize one rigid answer for a phrase. Context vectors modulate activations at every layer, yielding a distinct output distribution for every prefix.

Probability theory explains why this local next-token choice suffices to score entire documents. Under the chain rule of probability, the joint probability of any sequence W=(w1,w2,…,wn)W = (w_1, w_2, \dots, w_n)W=(w1​,w2​,…,wn​) factorizes exactly into a product of conditional probabilities:

P(w1,w2,…,wn)=∏t=1nP(wt∣w1,…,wt−1)=∏t=1nP(wt∣w<t)P(w_1, w_2, \dots, w_n) = \prod_{t=1}^{n} P(w_t \mid w_1, \dots, w_{t-1}) = \prod_{t=1}^{n} P(w_t \mid w_{<t})P(w1​,w2​,…,wn​)=t=1∏n​P(wt​∣w1​,…,wt−1​)=t=1∏n​P(wt​∣w<t​)

Here wtw_twt​ represents the token at position ttt, nnn is total sequence length, and w<tw_{<t}w<t​ denotes the history of all preceding tokens. The first term is simply P(w1)P(w_1)P(w1​), conditioned on no prior text. When scoring continuations after a user prompt, each factor conditions on that prompt as well.

This factorization is an exact mathematical equality, not an approximation. It requires no independence assumptions. The language modeling objective tasks the network with approximating each individual conditional term P(wt∣w<t)P(w_t \mid w_{<t})P(wt​∣w<t​).

Consider guessing continuations for this test runner log:

completion-prompt.txt
1The test runner says the assertion

Multiple candidates compete for probability mass:

possible-completions.txt
1passed 2failed 3holds

All three candidates are grammatically and contextually sound. A language model doesn't require absolute certainty to generate helpful text. It needs calibrated relative probabilities across its vocabulary, leaving the final choice to a decoding algorithm.

Softmax probabilities from raw logits

Real tokenizers span tens of thousands of tokens, typically between 32,000 and 128,000 subwords. To trace the mathematics without clutter, shrink the vocabulary to five candidate tokens.

Take this prompt:

The compiler reported

The network projects its final hidden state through an unembedding matrix to produce logits, which are unconstrained real-valued scores:

TokenLogit (ziz_izi​)Semantic role
error3.0Dominant continuation in compilation logs.
warning1.5Plausible diagnostic outcome.
success-1.0Grammatically viable, but uncommon after reported.
the-2.0Grammatically unnatural immediately after reported.
</s>-3.0Premature sequence termination.

The softmax operator converts these real-valued logits into normalized probabilities:

P(wi)=exp⁡(zi−max⁡jzj)∑k=1∣V∣exp⁡(zk−max⁡jzj)P(w_i) = \frac{\exp(z_i - \max_j z_j)}{\sum_{k=1}^{|V|} \exp(z_k - \max_j z_j)}P(wi​)=∑k=1∣V∣​exp(zk​−maxj​zj​)exp(zi​−maxj​zj​)​

Subtracting max⁡jzj\max_j z_jmaxj​zj​ is a numerical stabilization safeguard. As established in the softmax optimization lesson, subtracting the maximum logit keeps the largest exponent at exp⁡(0)=1\exp(0) = 1exp(0)=1, preventing floating-point overflow while preserving exact probability ratios.

tiny-vocabulary-example.py
1import math 2 3tokens = ["error", "warning", "success", "the", "</s>"] 4logits = [3.0, 1.5, -1.0, -2.0, -3.0] 5 6peak = max(logits) 7exps = [math.exp(logit - peak) for logit in logits] 8total = sum(exps) 9probs = [exp / total for exp in exps] 10 11for token, prob in zip(tokens, probs): 12 print(f"{token:10s} {prob:.3f}") 13print(f"sum {sum(probs):.3f}") 14print("top token ", tokens[max(range(len(probs)), key=lambda i: probs[i])])
Output
1error 0.800 2warning 0.178 3success 0.015 4the 0.005 5</s> 0.002 6sum 1.000 7top token error

error commands 80% of the probability mass, followed by warning at roughly 18%. The probabilities sum cleanly to 1.0. Softmax guarantees valid probability math, but it can't tell whether a compiler actually failed. It only normalizes the model's internal scores. The next question is how a model acquires those scores from raw text.


Estimating probabilities from observed n-gram counts

Before neural networks dominated natural language processing, language models estimated sequence probabilities by counting token co-occurrences in text corpora.

A bigram model approximates the full conditional history w<tw_{<t}w<t​ using only the single immediately preceding token wt−1w_{t-1}wt−1​. This simplification is the first-order Markov assumption:

P(wt∣w1,…,wt−1)≈P(wt∣wt−1)P(w_t \mid w_1, \dots, w_{t-1}) \approx P(w_t \mid w_{t-1})P(wt​∣w1​,…,wt−1​)≈P(wt​∣wt−1​)

Under Maximum Likelihood Estimation (MLE), the bigram transition probability is the ratio of pair frequency to previous-token frequency:

P(wt∣wt−1)=C(wt−1,wt)C(wt−1)P(w_t \mid w_{t-1}) = \frac{C(w_{t-1}, w_t)}{C(w_{t-1})}P(wt​∣wt−1​)=C(wt−1​)C(wt−1​,wt​)​

Here C(wt−1,wt)C(w_{t-1}, w_t)C(wt−1​,wt​) counts how many times the adjacent pair appears, while C(wt−1)C(w_{t-1})C(wt−1​) counts total occurrences of the conditioning token.

Previous token (wt−1w_{t-1}wt−1​)Next token candidate (wtw_twt​)Observed countBigram probability
returnsvalue33/6=0.5003 / 6 = 0.5003/6=0.500
returnsresult22/6=0.3332 / 6 = 0.3332/6=0.333
returnserror11/6=0.1671 / 6 = 0.1671/6=0.167

Counting bigram events in code comments

We can verify these calculations directly on a toy corpus of six code comments. <bos> and <eos> act as explicit sequence delimiters, enabling the model to learn when continuations start and stop.

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

The sparsity collapse and Laplace smoothing

The bigram table reveals two severe architectural flaws:

First, Markov amnesia. The bigram model conditions solely on returns. It can't distinguish function returns from test returns, even though our corpus shows that tests frequently return error while functions return value.

Second, sparsity collapse. The pair returns exception never appears in our six training sentences. Its raw count is zero, so P(exception∣returns)=0.0P(\text{exception} \mid \text{returns}) = 0.0P(exception∣returns)=0.0. If an evaluation document contains that valid phrase, taking the negative log-likelihood (−log⁡(0)-\log(0)−log(0)) yields infinite loss. The entire test corpus probability crashes to zero because of one missing pair!

Laplace (add-one) smoothing mitigates zero-count collapse by adding pseudo-counts across the entire vocabulary VVV:

PLaplace(wt∣wt−1)=C(wt−1,wt)+1C(wt−1)+∣V∣P_{\text{Laplace}}(w_t \mid w_{t-1}) = \frac{C(w_{t-1}, w_t) + 1}{C(w_{t-1}) + |V|}PLaplace​(wt​∣wt−1​)=C(wt−1​)+∣V∣C(wt−1​,wt​)+1​

If our vocabulary has four potential continuations (value, result, error, exception), the counts become [4,3,2,1][4, 3, 2, 1][4,3,2,1], and the denominator becomes 6+4=106 + 4 = 106+4=10. exception receives probability 0.1000.1000.100 instead of zero.

While smoothing prevents infinities, it doesn't solve the core issue. Widening the context to an n-gram model (P(wt∣wt−n+1,…,wt−1)P(w_t \mid w_{t-n+1}, \dots, w_{t-1})P(wt​∣wt−n+1​,…,wt−1​)) creates an exponential parameter explosion. For a modest vocabulary of ∣V∣=50,000|V| = 50,000∣V∣=50,000, a 4-gram table demands ∣V∣4=6.25×1018|V|^4 = 6.25 \times 10^{18}∣V∣4=6.25×1018 parameters. Most combinations never occur in any training set, and discrete counts can't transfer knowledge between similar words like function and method.


Neural language models and distributed representations

Yoshua Bengio and colleagues resolved this combinatorial bottleneck by introducing continuous distributed representations for words.[3]Reference 3A Neural Probabilistic Language Modelhttps://www.jmlr.org/papers/v3/bengio03a.html

Instead of treating tokens as orthogonal atomic symbols where one-hot vectors share zero overlap (ei⊤ej=0e_i^\top e_j = 0ei⊤​ej​=0), a neural language model maps each token ID to a continuous embedding vector in Rd\mathbb{R}^dRd:

  1. Look up continuous embeddings for the previous n−1n - 1n−1 context tokens in an embedding matrix E∈R∣V∣×dE \in \mathbb{R}^{|V| \times d}E∈R∣V∣×d.
  2. Concatenate these vectors into a single feature vector x∈R(n−1)dx \in \mathbb{R}^{(n-1)d}x∈R(n−1)d.
  3. Project xxx through shared linear weights WWW and add a bias vector bbb to yield vocabulary logits.
  4. Pass logits through softmax to form next-token probabilities.
neural-ngram-path.txt
1context token IDs -> embedding lookup E -> concatenated vector x -> linear map Wx + b -> logits -> softmax

Because words with similar grammatical and semantic roles develop similar embedding orientations, the model generalizes probability mass smoothly. If the training data contains method returns result, the model assigns sensible probability to function returns result without requiring explicit frequency counts for that exact sequence.

A sparse bigram heatmap gives the returns row 50% value, 33% result, and 17% error, while a neural n-gram embeds function and returns into a two-by-six matrix, joins twelve features, and scores value highest after a shared linear map.
The bigram looks up only `returns`. The neural model reads both context tokens. Its displayed embeddings and three selected logits are rounded values from the lab after 200 updates; the strongest score is value=7.904. A trigram count table could also distinguish these two-token contexts.

Training a tiny neural n-gram with gradient descent

Let's implement a complete neural n-gram model from scratch in Python. It reads two context tokens, maps each to a 6-dimensional embedding, concatenates them into 12 features, and projects them to 8 vocabulary logits.

The backward pass applies the exact cross-entropy gradient derived in the softmax lesson: ∂L∂z=p−y\frac{\partial \mathcal{L}}{\partial z} = p - y∂z∂L​=p−y. Gradients flow through the projection weights back into the embedding rows, updating representations through gradient descent.

tiny-neural-ngram.py
1import math 2import random 3 4random.seed(7) 5sentences = [ 6 "function returns value", 7 "function returns value", 8 "method returns result", 9 "handler returns value", 10 "test returns result", 11 "test returns error", 12] 13vocab = sorted({word for sentence in sentences for word in sentence.split()}) 14to_id = {word: index for index, word in enumerate(vocab)} 15n_vocab = len(vocab) 16emb_dim = 6 17 18contexts = [] 19targets = [] 20for sentence in sentences: 21 words = sentence.split() 22 for index in range(2, len(words)): 23 contexts.append([to_id[words[index - 2]], to_id[words[index - 1]]]) 24 targets.append(to_id[words[index]]) 25 26embeddings = [[random.uniform(-0.3, 0.3) for _ in range(emb_dim)] for _ in range(n_vocab)] 27weights = [[random.uniform(-0.2, 0.2) for _ in range(n_vocab)] for _ in range(emb_dim * 2)] 28bias = [0.0] * n_vocab 29 30def concat_embeddings(token_ids): 31 return embeddings[token_ids[0]] + embeddings[token_ids[1]] 32 33def logits_from(features): 34 logits = bias[:] 35 for i, value in enumerate(features): 36 for j, weight in enumerate(weights[i]): 37 logits[j] += weight * value 38 return logits 39 40def softmax(logits): 41 peak = max(logits) 42 exps = [math.exp(logit - peak) for logit in logits] 43 total = sum(exps) 44 return [exp / total for exp in exps] 45 46def mean_loss(): 47 total = 0.0 48 for token_ids, target in zip(contexts, targets): 49 logits = logits_from(concat_embeddings(token_ids)) 50 peak = max(logits) 51 total += (peak - logits[target]) + math.log(sum(math.exp(z - peak) for z in logits)) 52 return total / len(contexts) 53 54def predict(text): 55 token_ids = [to_id[word] for word in text.split()] 56 logits = logits_from(concat_embeddings(token_ids)) 57 return vocab[max(range(n_vocab), key=lambda index: logits[index])] 58 59initial_loss = mean_loss() 60learning_rate = 0.4 61for _ in range(200): 62 grad_embeddings = [[0.0] * emb_dim for _ in range(n_vocab)] 63 grad_weights = [[0.0] * n_vocab for _ in range(emb_dim * 2)] 64 grad_bias = [0.0] * n_vocab 65 scale = 1.0 / len(contexts) 66 for token_ids, target in zip(contexts, targets): 67 features = concat_embeddings(token_ids) 68 dlogits = [prob * scale for prob in softmax(logits_from(features))] 69 dlogits[target] -= scale 70 for j, grad in enumerate(dlogits): 71 grad_bias[j] += grad 72 for i, value in enumerate(features): 73 for j, grad in enumerate(dlogits): 74 grad_weights[i][j] += value * grad 75 dfeatures = [ 76 sum(weights[i][j] * dlogits[j] for j in range(n_vocab)) 77 for i in range(emb_dim * 2) 78 ] 79 for offset, token_id in enumerate(token_ids): 80 start = offset * emb_dim 81 for dim in range(emb_dim): 82 grad_embeddings[token_id][dim] += dfeatures[start + dim] 83 for i in range(n_vocab): 84 bias[i] -= learning_rate * grad_bias[i] 85 for dim in range(emb_dim): 86 embeddings[i][dim] -= learning_rate * grad_embeddings[i][dim] 87 for i in range(emb_dim * 2): 88 for j in range(n_vocab): 89 weights[i][j] -= learning_rate * grad_weights[i][j] 90 91print(f"loss before training: {initial_loss:.3f}") 92print(f"loss after training: {mean_loss():.3f}") 93print("after 'function returns':", predict("function returns")) 94print("after 'test returns': ", predict("test returns")) 95assert predict("function returns") == "value" 96assert mean_loss() < initial_loss
Output
1loss before training: 2.145 2loss after training: 0.235 3after 'function returns': value 4after 'test returns': result

Notice what happened: the model distinguishes function returns from test returns! It outputs value after function returns and result after test returns.

The loss dropped from 2.145 to 0.235 nats. In our dataset, test returns appears twice with conflicting targets: once with result and once with error. An optimal model splits its probability between those two targets, giving a theoretical minimum cross-entropy floor of 2ln⁡(2)6≈0.231\frac{2 \ln(2)}{6} \approx 0.23162ln(2)​≈0.231. The trained network reaches 0.235, virtually matching the dataset's entropy floor.

Does distinguishing function returns from test returns prove that neural embeddings are strictly necessary?

Answer

No. A discrete trigram table also sees two tokens of context. What neural embeddings provide is parameter sharing: semantically similar words share embedding space. That shared geometry lets neural models generalize to unseen prefixes, which discrete count tables can't do without smoothing.

Temperature scaling on output logits

During inference, we don't always take the argmax token. We often sample from the distribution. A temperature parameter T>0T > 0T>0 rescales logits before applying softmax:

P(wi)=exp⁡(zi/T)∑jexp⁡(zj/T)P(w_i) = \frac{\exp(z_i / T)}{\sum_j \exp(z_j / T)}P(wi​)=∑j​exp(zj​/T)exp(zi​/T)​
  • T<1.0T < 1.0T<1.0 (Cooling): Amplifies logit differences. The top token monopolizes probability mass, making generation focused and deterministic.
  • T=1.0T = 1.0T=1.0 (Unscaled): Standard softmax probabilities derived directly from learned parameters.
  • T>1.0T > 1.0T>1.0 (Heating): Flattens logit differences. The distribution approaches uniform randomness, giving low-probability tokens a higher chance of being sampled.
compare-temperatures.py
1import math 2 3logits = [3.0, 1.5, -1.0, -2.0, -3.0] 4tokens = ["error", "warning", "success", "the", "</s>"] 5for temperature in [0.5, 1.0, 2.0]: 6 scaled = [logit / temperature for logit in logits] 7 peak = max(scaled) 8 exps = [math.exp(logit - peak) for logit in scaled] 9 total = sum(exps) 10 probabilities = [exp / total for exp in exps] 11 ranked = sorted(probabilities, reverse=True) 12 if temperature == 1.0: 13 print(f"{tokens[0]:<11}{probabilities[0]:.3f}") 14 print(f"{tokens[1]:<11}{probabilities[1]:.3f}") 15 print(f"T={temperature:.1f} top={ranked[0]:.3f} second={ranked[1]:.3f}")
Temperature comparison
1T=0.5 top=0.952 second=0.047 2error 0.800 3warning 0.178 4T=1.0 top=0.800 second=0.178 5T=2.0 top=0.575 second=0.272

At T=0.5T = 0.5T=0.5, error surges to 95.2% probability. At T=2.0T = 2.0T=2.0, it drops to 57.5%, leaving significant mass for alternatives. Temperature is a sampling control, not a parameter update: it alters decoding entropy without modifying underlying weights.

Causal versus masked language modeling

The architecture that powers modern generative models is causal language modeling (autoregressive generation). It conditions strictly on earlier tokens (w<tw_{<t}w<t​).

In contrast, masked language modeling (BERT) hides tokens and reconstructs them using both left and right context.[4]Reference 4BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding.https://arxiv.org/abs/1810.04805

ObjectiveVisible contextCore training taskPrimary architectural use
Causal language modelingOnly prior tokens (w<tw_{<t}w<t​)Predict the single next tokenGenerative decoders (GPT, LLaMA, Claude)
Masked language modelingFull bidirectional context (w<tw_{<t}w<t​ and w>tw_{>t}w>t​)Reconstruct masked tokens in placeEncoders for embeddings, reranking, classification (BERT)

Because masked language modeling allows tokens to attend to the right, it can't naturally roll out text token by token. Causal modeling strictly enforces the autoregressive chain rule factorization.


Teacher forcing and parallel causal training

How does a causal model learn from large corpora? Every document provides supervised labels for free. At each token position ttt, the ground-truth target label is simply the observed token wt+1w_{t+1}wt+1​.

The causal attention mask and parallel GEMM

During pre-training, the model receives ground-truth tokens at every position rather than its own predictions. This mechanism is teacher forcing.

In recurrent neural networks (RNNs), hidden state dependencies forced training to run sequentially step by step. In a decoder Transformer, teacher forcing unlocks massive parallelism:

  1. The entire token sequence x1,…,xTx_1, \dots, x_Tx1​,…,xT​ enters the network at once.
  2. The causal attention mask sets attention logits Mij=−∞M_{ij} = -\inftyMij​=−∞ for all positions j>ij > ij>i.
  3. Because exp⁡(−∞)=0\exp(-\infty) = 0exp(−∞)=0, position ttt can't attend to future tokens t+1,…,Tt+1, \dots, Tt+1,…,T.
  4. The GPU executes dense General Matrix Multiplies (GEMM) across all TTT positions simultaneously in an O(1)\mathcal{O}(1)O(1) parallel forward pass!
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.

Exposure bias and the train-inference gap

Teacher forcing provides a stable, clean training signal, but it creates a fundamental train-inference mismatch known as exposure bias.[5]Reference 5Scheduled Sampling for Sequence Prediction with Recurrent Neural Networkshttps://arxiv.org/abs/1506.03099

During training, position ttt always conditions on pristine human text from the dataset. During inference, position ttt conditions on the model's own previously sampled tokens. If the model makes a slight error or samples an unusual token at step 2, it enters an out-of-distribution prefix at step 3. The model was never trained on its own mistaken prefixes, so errors can compound rapidly across long generations.

Because of exposure bias, a low teacher-forced loss doesn't guarantee fluent rollouts. Evaluating a model requires checking both teacher-forced cross-entropy and free-running generations for repetition, drift, and stopping criteria.

Shifting tokens into inputs and target labels

Training a causal decoder requires aligning inputs and targets. Given the sequence [<bos>, function, returns, value, <eos>], input tokens drop the final token, while target labels drop the initial <bos>:

Input prefix to model (xxx)Supervised target label (yyy)
<bos>function
<bos> functionreturns
<bos> function returnsvalue
<bos> function returns value<eos>

In standard libraries like Hugging Face Transformers, DataCollatorForLanguageModeling with mlm=False takes raw token IDs and masks padding tokens with -100.[6]Reference 6Transformers Data Collatorshttps://huggingface.co/docs/transformers/main/en/main_classes/data_collator#transformers.DataCollatorForLanguageModeling The causal model shifts logits and labels internally: logits[..., :-1, :] pairs with labels[..., 1:].[7]Reference 7Transformers Causal Language-Model Losshttps://github.com/huggingface/transformers/blob/main/src/transformers/loss/loss_utils.py

shift-next-token-labels.py
1vocab = {"<bos>": 0, "function": 1, "returns": 2, "value": 3, "<eos>": 4} 2tokens = ["<bos>", "function", "returns", "value", "<eos>"] 3ids = [vocab[token] for token in tokens] 4inputs = ids[:-1] 5targets = ids[1:] 6 7print("input token ids: ", inputs) 8print("target token ids:", targets) 9for position in range(len(inputs)): 10 context = " ".join(tokens[: position + 1]) 11 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 loss across sequence positions

At every position ttt, the model emits a probability distribution over the vocabulary. We want the probability assigned to the ground-truth target xtx_txt​ to be as close to 1.0 as possible. Cross-entropy loss computes the negative log-probability averaged across all nnn positions:

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

If the network assigns probability 1.0 to the correct token, the term contributes −log⁡(1.0)=0-\log(1.0) = 0−log(1.0)=0 loss. If it assigns a tiny probability like 0.01, the penalty is large: −log⁡(0.01)≈4.605-\log(0.01) \approx 4.605−log(0.01)≈4.605 nats. Causal pre-training minimizes this average negative log-likelihood across trillions of tokens.

cross-entropy-over-positions.py
1import math 2 3targets = [1, 2, 3, 4] # function, returns, value, <eos> 4logits = [ 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] 10 11true_probs = [] 12losses = [] 13for row, target in zip(logits, targets): 14 peak = max(row) 15 exps = [math.exp(logit - peak) for logit in row] 16 total = sum(exps) 17 prob = exps[target] / total 18 true_probs.append(prob) 19 losses.append((peak - row[target]) + math.log(total)) 20 21print("true-token probabilities:", [round(prob, 3) for prob in true_probs]) 22print("per-token losses: ", [round(loss, 3) for loss in losses]) 23print(f"mean cross-entropy: {sum(losses) / len(losses):.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 as an effective branching factor

Cross-entropy loss in natural logarithms produces units of nats (or bits if using base 2). Exponentiating cross-entropy yields perplexity:[8]Reference 8A Mathematical Theory of Communicationhttps://people.math.harvard.edu/~ctm/home/text/others/shannon/entropy/entropy.pdf[9]Reference 9Perplexity — a Measure of the Difficulty of Speech Recognition Tasks.https://doi.org/10.1121/1.2016299

Perplexity=exp⁡(L)=exp⁡(−1n∑t=1nlog⁡P(xt∣x<t))=1∏t=1nP(xt∣x<t)n\text{Perplexity} = \exp(\mathcal{L}) = \exp\left(-\frac{1}{n} \sum_{t=1}^n \log P(x_t \mid x_{<t})\right) = \frac{1}{\sqrt[n]{\prod_{t=1}^n P(x_t \mid x_{<t})}}Perplexity=exp(L)=exp(−n1​t=1∑n​logP(xt​∣x<t​))=n∏t=1n​P(xt​∣x<t​)​1​

Perplexity is the reciprocal of the geometric mean of the target token probabilities.

Intuitively, perplexity represents the effective branching factor: the number of equally likely options the model is choosing between at each step.

  • A perplexity of 1.0 means the model predicted every target token with 100% confidence (L=0\mathcal{L} = 0L=0).
  • If a model guesses uniformly across a vocabulary of 50,000 tokens, its loss is ln⁡(50,000)≈10.82\ln(50,000) \approx 10.82ln(50,000)≈10.82, and its perplexity is exactly exp⁡(10.82)=50,000\exp(10.82) = 50,000exp(10.82)=50,000.
  • In our 4-position example above, a mean loss of 0.209 nats yields a perplexity of exp⁡(0.209)≈1.233\exp(0.209) \approx 1.233exp(0.209)≈1.233. The model predicts with the certainty of someone picking among roughly 1.2 equally weighted choices per token.
perplexity-by-hand.py
1import math 2 3# Probability the model gave to the token that was actually correct, per position 4true_token_probs = [0.5, 0.5, 0.5, 0.125] 5 6per_position_loss = [-math.log(prob) for prob in true_token_probs] 7avg_loss = sum(per_position_loss) / len(per_position_loss) 8perplexity = math.exp(avg_loss) 9 10print(f"per-position loss: {[round(loss, 4) for loss in per_position_loss]}") 11print(f"average loss: {avg_loss:.4f}") 12print(f"perplexity: {perplexity:.4f}") 13 14product = 1.0 15for prob in true_token_probs: 16 product *= prob 17geo_mean = product ** (1 / len(true_token_probs)) 18print(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

Notice how one low-probability target (0.125 at position 4) pulls the average loss from 0.693 to 1.039, elevating perplexity to 2.828. Perplexity penalizes surprise heavily.

Perplexity is only comparable across models when they share the exact same tokenizer, vocabulary size, and text normalization. A byte-level tokenizer naturally achieves lower perplexity per token than a word-level tokenizer simply because its vocabulary is vastly smaller, not because its predictions are superior.[10]Reference 10Perplexity of fixed-length modelshttps://huggingface.co/docs/transformers/perplexity

A model assigns the true next token a probability of 0.01 at one position. Roughly how much does that position contribute to cross-entropy loss?

Answer

The contribution is -log(0.01) ≈ 4.605 nats. That is a heavy penalty. Confident correct predictions contribute near 0 loss, whereas low-probability surprises drive loss and perplexity up quickly.


The autoregressive generation loop

During inference, ground-truth continuations don't exist. The model must generate text one token at a time in a sequential loop:

  1. Input: Feed the prompt tokens into the network.
  2. Predict: Compute vocabulary logits and softmax probabilities for the next token.
  3. Select: Pick a token via greedy argmax or temperature-scaled sampling.
  4. Append: Concatenate the newly selected token to the input sequence.
  5. Repeat: Feed the expanded context back into the model to predict the subsequent token.
Diagram showing Prompt: 'The function returns the', Model predicts next token, Sample token: result, and Append to context.
Prompt: 'The function returns the', Model predicts next token, Sample token: result, and Append to context.

A deterministic generation loop in code

This small script demonstrates the sequential mechanics: read context, pick the top token, append it, and check for a stop token.

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

In a full language model, the transition table isn't a hardcoded dictionary: billions of neural weights compute the distribution dynamically for any arbitrary token prefix.

The loop terminates when one of four conditions is satisfied:

Termination conditionTrigger mechanismProduction handling
End-of-sequence tokenModel samples </s>, <|endoftext|>, or <|im_end|>Server cleanly closes the response stream.
Output token budgetGenerated tokens reach user's max_tokens limitTruncate output gracefully or flag incomplete generation.
Stop sequence matchOutput contains predefined string like \nUser: or ```Halt generation and strip the trigger delimiter.
Safety filter cutoffModeration layer flags harmful generationSubstitute safe completion or raise guardrail event.

Greedy selection versus temperature sampling

In greedy decoding, the engine always takes the argmax: w^=arg⁡max⁡izi\hat{w} = \arg\max_i z_iw^=argmaxi​zi​. This choice is deterministic, but it often causes repetitive loops in open-ended text.

In stochastic sampling, the engine draws a random token weighted by its softmax probability. Sampling injects diversity, but pure multinomial sampling risks picking ungrammatical tail tokens. Modern serving combines temperature with top-kkk (filtering to the kkk most probable tokens) or top-ppp nucleus sampling (filtering to the smallest set of tokens whose cumulative probability exceeds ppp).

greedy-versus-sampling.py
1import math 2import random 3 4tokens = ["result", "value", "index"] 5logits = [2.4, 1.7, 0.2] 6 7def probabilities(temperature): 8 scaled = [logit / temperature for logit in logits] 9 peak = max(scaled) 10 exps = [math.exp(logit - peak) for logit in scaled] 11 total = sum(exps) 12 return [exp / total for exp in exps] 13 14rng = random.Random(4) 15greedy = tokens[max(range(len(logits)), key=lambda i: logits[i])] 16probs = probabilities(1.4) 17samples = [rng.choices(tokens, weights=probs, k=1)[0] for _ in range(8)] 18 19print("greedy:", greedy) 20print("sampled at temperature 1.4:", samples) 21print("probabilities:", [round(prob, 3) for prob in probs])
Output
1greedy: result 2sampled at temperature 1.4: ['result', 'result', 'result', 'result', 'result', 'result', 'index', 'value'] 3probabilities: [0.551, 0.334, 0.115]

Context window limits and memory growth

Because the model appends every emitted token back into its input, the context length grows at every step. At token 1,000, the model re-evaluates the prompt plus all 999 generated tokens.

Every architecture enforces a maximum context window (such as 8,192 or 128,000 tokens). Exceeding this limit forces the application to truncate historical messages, summarize earlier turns, or reject the request.


Production serving: prefill, decode, and the KV cache

Serving an autoregressive Transformer in production introduces a sharp computational divide between two distinct phases:

  1. The Prefill Phase: Processing the user's prompt tokens all at once.
  2. The Decode Phase: Generating new tokens sequentially one by one.
KV-cache diagram showing the four-token prompt The function returns the written to key and value rows during prefill, then result and period appended in two decode steps before an end token stops generation without another cache column; a bar chart compares 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 `result` 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.

Why naive autoregressive generation is quadratic

In standard multi-head self-attention, token ttt computes queries Qt=XtWQQ_t = X_t W_QQt​=Xt​WQ​, keys Kt=XtWKK_t = X_t W_KKt​=Xt​WK​, and values Vt=XtWVV_t = X_t W_VVt​=Xt​WV​.

Without caching, generating step ttt requires re-running linear projections for all t−1t - 1t−1 previous tokens. To generate a 2,000-token response from a 1,000-token prompt, the GPU would recompute keys and values for prompt tokens 2,000 times! Over an entire sequence of length TTT, the total operations scale quadratically as O(T2)\mathcal{O}(T^2)O(T2).

The KV cache and memory bandwidth bottlenecks

Because causal attention masks prevent future tokens from affecting past states, the Key (KKK) and Value (VVV) vectors of historical tokens never change during generation.

The Key-Value (KV) cache stores the evaluated KKK and VVV tensors from all previous positions across all Transformer layers in GPU High Bandwidth Memory (HBM).[11]Reference 11Efficiently Scaling Transformer Inference.https://arxiv.org/abs/2211.05102

At decode step ttt:

  1. The GPU only computes qt,kt,vtq_t, k_t, v_tqt​,kt​,vt​ for the single newly generated token.
  2. It appends ktk_tkt​ and vtv_tvt​ to the cached tensors in HBM.
  3. It computes attention by multiplying query vector qtq_tqt​ against all accumulated cached keys and values.

This reduces per-step computational complexity from O(t)\mathcal{O}(t)O(t) to O(1)\mathcal{O}(1)O(1) projection work and linear O(t)\mathcal{O}(t)O(t) attention operations.

However, caching introduces a major hardware bottleneck:

  • Prefill is compute-bound: Prompt tokens form a large matrix X∈RS×dX \in \mathbb{R}^{S \times d}X∈RS×d. The GPU runs General Matrix Multiplications (GEMM), achieving high arithmetic intensity (FLOPs per byte loaded) and saturating Tensor Cores.
  • Decode is memory-bandwidth bound: At each step, the input is a single token vector xt∈R1×dx_t \in \mathbb{R}^{1 \times d}xt​∈R1×d. Computing attention requires executing General Matrix-Vector products (GEMV). The GPU must load gigabytes of weights and cached KV tensors from HBM into SRAM just to perform a handful of operations (≈1–2\approx 1\text{--}2≈1–2 FLOPs per byte loaded).

In the decode phase, GPU execution units spend most of their time waiting for data to travel across the memory bus.

Sizing the KV cache in high bandwidth memory

The memory footprint of a standard KV cache per request is governed by this exact formula:

MemoryKV=2×L×HKV×dh×S×B×Pbytes\text{Memory}_{\text{KV}} = 2 \times L \times H_{\text{KV}} \times d_h \times S \times B \times P_{\text{bytes}}MemoryKV​=2×L×HKV​×dh​×S×B×Pbytes​

Where:

  • 222: stores both Key and Value matrices
  • LLL: number of Transformer layers
  • HKVH_{\text{KV}}HKV​: number of Key/Value attention heads
  • dhd_hdh​: head dimension (dmodel/HQd_{\text{model}} / H_Qdmodel​/HQ​)
  • SSS: total sequence length (prompt plus generated tokens)
  • BBB: concurrent batch size
  • PbytesP_{\text{bytes}}Pbytes​: bytes per value (2 for FP16/BF16, 1 for FP8/INT8)
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

At 32,768 tokens, our 4-request batch consumes 16 GiB of GPU memory solely for the KV cache. With a concurrent batch size of 32 requests, the cache demands 128 GiB, easily exceeding the memory needed for the model weights themselves!

Two architectural breakthroughs keep modern KV serving manageable:

  1. Grouped-Query Attention (GQA): Instead of allocating one KV head per query head (HKV=HQH_{\text{KV}} = H_QHKV​=HQ​), GQA groups query heads to share a smaller number of KV heads (for instance, 32 query heads sharing 8 KV heads). This slashes KV cache memory by 4× with virtually no degradation in model quality.[12]Reference 12GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints.https://arxiv.org/abs/2305.13245
  2. PagedAttention (vLLM): Traditional serving engines allocated contiguous memory blocks for the maximum possible context length, wasting 60% to 80% of VRAM on memory fragmentation. PagedAttention partitions the KV cache into fixed-size virtual memory blocks (pages of 16 or 32 tokens), eliminating internal fragmentation and enabling near-optimal memory utilization.[13]Reference 13Efficient Memory Management for Large Language Model Serving with PagedAttention.https://arxiv.org/abs/2309.06180

Why next-token objectives learn structured computation

It's common to wonder: if an LLM is only trained to predict the next token, how does it acquire reasoning, code synthesis, and multi-step logic?

Consider this code snippet:

tracking-a-value.txt
1retries = 3 2retries -= 1 3retries ==

What token comes next?

An n-gram model with a 2-token context only sees retries ==. It might predict 0 because == 0 is common in its training corpus.

A large Transformer reads the full prefix. To drive cross-entropy loss down across millions of similar code files, the network's internal attention heads learn to track variable assignments, decrement operations, and condition checks. Predicting the single token 2 correctly requires internally tracking execution state!

Next-token prediction acts as an information-dense compression objective. To predict the next word across web-scale text, the network must discover circuits that represent syntax, world facts, programmatic states, and conversational structure.[14]Reference 14Language Models are Few-Shot Learners.https://arxiv.org/abs/2005.14165

However, next-token prediction doesn't equal conscious understanding. The model remains a probabilistic engine that mirrors its training distribution.


Failure modes and debugging

Next-token prediction supplies a powerful training signal, but it introduces distinct operational failure modes.

Treating probabilities as ground-truth facts

High probability reflects stylistic frequency in the training corpus, not verified truth.

Suppose an internal documentation assistant reads:

The rate-limit page says free-tier requests are

The base model might assign 75% probability to unlimited because marketing copy frequently pairs those words. But if your production API enforces 60 requests per minute, the statistically likely continuation is factually wrong!

Failure symptomRoot mechanismProduction remediation
Plausible hallucinationStylistic frequency overrides missing domain factsInject retrieved documentation into context (RAG)
Outdated API limitsModel weights reflect static pre-training snapshotProvide tool calling for real-time API queries
Overconfident false citationsModel predicts plausible-looking paper URLsRequire explicit quote verification against retrieved documents
Repetitive runaway loopsModel gets trapped in self-reinforcing prefixApply frequency penalties, repetition penalties, or min-p sampling

The relational database myth

A common misconception treats language models like relational databases that look up rows during generation.

An LLM contains no internal SQL engine or indexed tables. It possesses only numerical weights that parameterize categorical distributions over tokens. When asked for a specific phone number or timestamp, it doesn't query a database; it generates a plausible continuation. If accurate factual recall is required, system designs must provide external sources of truth via retrieval or function calling.


Stress-testing prefix distributions and cache budgets

To solidify these concepts, test how prefix evidence and serving dimensions reshape model behavior.

In count-bigram-events.py, add "function returns exception" to the training list. The new row for returns has 7 total observations: value: 3, result: 2, error: 1, exception: 1. The probability of value drops from 0.5000.5000.500 to 3/7≈0.4293/7 \approx 0.4293/7≈0.429, and exception takes 1/7≈0.1431/7 \approx 0.1431/7≈0.143.

Now consider what happens in tiny-neural-ngram.py. The prefix function returns now appears twice with target value and once with target exception. A deterministic function can't output two different probabilities of 1.0 for the same input. The cross-entropy objective will drive the network toward assigning roughly 67% probability to value and 33% to exception.

Why can't any neural language model achieve zero loss on a dataset that contains both "function returns value" and "function returns exception"?

Answer

Because identical inputs can't map to conflicting one-hot vectors simultaneously. Softmax outputs must sum to 1.0. When the identical prefix maps to multiple targets, the optimal prediction matches their empirical frequencies, leaving an irreducible entropy floor known as the Bayes error rate.

Next, test the serving economics in kv-cache-memory-budget.py. Halving kv_heads from 8 to 4 (switching to a more aggressive Grouped-Query Attention ratio) reduces cache memory from 16.00 GiB to 8.00 GiB at 32,768 tokens. That 8 GiB savings directly translates to doubling the server's concurrent user capacity.

Language modeling unifies this entire pipeline: from chain-rule factorization and continuous embeddings to teacher-forced training and memory-bandwidth-bound KV cache serving.

Complete the lesson

Mastery Check

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

1.Under the chain rule of probability, what is the joint probability of a 3-token sequence with conditional probabilities P(w_1) = 0.6, P(w_2 | w_1) = 0.5, and P(w_3 | w_1, w_2) = 0.2?

Correct answer: 0.06, because sequence probability equals the exact product of conditional terms: 0.6 * 0.5 * 0.2.

The chain rule factorizes the joint probability into the product of each token's probability conditioned on its prefix. Here 0.6 * 0.5 * 0.2 = 0.06.

2.Why does an unsmoothed bigram count model fail when evaluating a valid phrase that never appeared in its training text?

Correct answer: Its observed count is zero, yielding probability zero and causing negative log-likelihood to become infinite.

Unobserved n-grams receive zero counts. Under MLE, P = 0, which makes cross-entropy loss -log(0) infinite and collapses evaluation scores.

3.How did Bengio's neural language model overcome the curse of dimensionality inherent in discrete n-gram tables?

Correct answer: By mapping discrete tokens into continuous embedding vectors where semantically related words share geometric space.

Continuous embeddings represent tokens as dense vectors. Related words share vector orientations, allowing the model to generalize to unseen contexts without discrete table explosion.

4.What enables a causal Transformer to process all sequence tokens in parallel during training while recurrent networks trained sequentially?

Correct answer: Teacher forcing supplies true tokens, and causal attention masks block future leakage in one GEMM pass.

With teacher forcing, the full sequence is known. The causal mask sets future attention logits to -inf, enabling dense parallel matrix multiplication across all timesteps simultaneously.

5.At four positions, a model assigns target probabilities 0.5, 0.5, 0.5, and 0.125. What are the mean cross-entropy loss and perplexity?

Correct answer: Mean loss ≈ 1.04 nats and perplexity ≈ 2.83, because perplexity is the exponential of mean cross-entropy.

The losses are -ln(0.5) ≈ 0.693, 0.693, 0.693, and -ln(0.125) ≈ 2.079. The mean is (3 * 0.693 + 2.079) / 4 ≈ 1.040. Perplexity is exp(1.040) ≈ 2.828.

6.Why is the decode phase of LLM serving memory-bandwidth bound rather than compute-bound?

Correct answer: Generating one token at a time streams weights and KV cache from HBM for low-intensity GEMV.

In decode, batch size per token is small, yielding arithmetic intensity near 1-2 FLOPs per byte. The GPU waits on memory transfers from HBM to SRAM rather than arithmetic compute.

7.A model has a simplified KV cache footprint of 1.00 GiB at context length 2,048. What is its footprint at context length 32,768 under identical serving parameters?

Correct answer: 16.00 GiB, because KV cache memory scales linearly with sequence length: 32,768 / 2,048 = 16.

KV cache stores key and value vectors for each position. When layers, heads, dimension, and precision are held constant, storage scales directly linearly with sequence length.

8.What is exposure bias in autoregressive language models?

Correct answer: The train-test gap where models trained on clean prefixes drift when feeding on their own errors.

Under teacher forcing, the model only sees true prefixes. At inference, it conditions on its own outputs, so early mistakes can push it into unfamiliar prefixes where errors cascade.

9.How does Grouped-Query Attention (GQA) reduce KV cache memory consumption during decoding?

Correct answer: Multiple query heads share a single Key and Value head, reducing the number of cached tensors.

GQA allocates fewer KV heads than query heads (e.g. 8 KV heads for 32 query heads). Because cache storage depends on H_kv, this slashes cache memory proportionally.

10.A team notices their LLM produces factually false API documentation with high confidence. What is the fundamental root cause?

Correct answer: The model samples token statistics; it doesn't verify facts against an internal relational database.

An LLM is a probabilistic token generator, not a factual database. High fluency and confidence reflect statistical phrase correlations, not factual verification.

10 questions remaining.

Next Step
Continue to From GPT to Modern LLMs

You can now factorize a sequence, train next-token loss under teacher forcing, and explain what a KV cache reuses during generation. Next you'll see how GPT-style decoders scaled that same loop into assistants, instruction tuning, and modern serving choices.

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

Improving Language Understanding by Generative Pre-Training.

Radford, A., et al. · 2018

https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf

Language Models are Unsupervised Multitask Learners.

Radford, A., et al. · 2019

https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf

A Neural Probabilistic Language Model

Bengio, Y., Ducharme, R., Vincent, P., and Jauvin, C. · 2003 · Journal of Machine Learning Research

https://www.jmlr.org/papers/v3/bengio03a.html

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

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

https://arxiv.org/abs/1810.04805

Scheduled Sampling for Sequence Prediction with Recurrent Neural Networks

Samy Bengio, Oriol Vinyals, Navdeep Jaitly, Noam Shazeer · 2015

https://arxiv.org/abs/1506.03099

Transformers Data Collators

Hugging Face · 2026

https://huggingface.co/docs/transformers/main/en/main_classes/data_collator#transformers.DataCollatorForLanguageModeling

Transformers Causal Language-Model Loss

Hugging Face · 2026

https://github.com/huggingface/transformers/blob/main/src/transformers/loss/loss_utils.py

A Mathematical Theory of Communication

Claude E. Shannon · 1948

https://people.math.harvard.edu/~ctm/home/text/others/shannon/entropy/entropy.pdf

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

Jelinek, F., et al. · 1977

https://doi.org/10.1121/1.2016299

Perplexity of fixed-length models

Hugging Face · 2026

https://huggingface.co/docs/transformers/perplexity

Efficiently Scaling Transformer Inference.

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

https://arxiv.org/abs/2211.05102

GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints.

Ainslie, J., et al. · 2023 · EMNLP 2023

https://arxiv.org/abs/2305.13245

Efficient Memory Management for Large Language Model Serving with PagedAttention.

Kwon, W., et al. · 2023 · SOSP 2023

https://arxiv.org/abs/2309.06180

Language Models are Few-Shot Learners.

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

https://arxiv.org/abs/2005.14165

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.