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.
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.
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.
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:
| Candidate token | Probability | What reader should notice |
|---|---|---|
result | 60% | Greedy decoding would choose this; sampling makes it the likeliest choice. |
value | 25% | Plausible alternatives remain available when sampling. |
index | 10% | Lower-ranked words can appear with higher temperature. |
exception | 0.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.
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 far | Likely next token | Why |
|---|---|---|
The function returns the | result | Common code explanation completion. |
The function returns the cached | value | New context changes the expected object. |
The function returns the first | index | The adjective changes the likely noun. |
The function returns the plus a style instruction for API docs | value or response | Task 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:
1The test runner says the assertionPossible continuations:
1passed
2failed
3timedAll 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.
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:
| Token | Logit | Meaning |
|---|---|---|
error | 3.0 | Strong continuation. |
warning | 1.5 | Plausible but less likely. |
success | -1.0 | Grammatically possible, semantically odd. |
the | -2.0 | Usually wrong after this prompt. |
</s> | -3.0 | Ending 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.
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))])1error 0.800
2warning 0.178
3success 0.015
4the 0.005
5</s> 0.002
6sum 1.000
7top token errorThe exact numbers are less important than the shape:
That's next-token prediction in miniature.
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:
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 token | Candidate next token | Count | Probability |
|---|---|---|---|
returns | value | 85 | 0.85 |
returns | result | 10 | 0.10 |
returns | error | 5 | 0.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.
Start with six code-comment fragments. Each adjacent token pair becomes one training event: after returns, which token appeared next?
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)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.0An 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:
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):
n - 1 tokens.1previous n - 1 token IDs -> embedding table -> joined embeddings -> matmul + GELU -> logits -> softmaxThis 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.
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.
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])1loss before training: 2.304
2loss after training: 0.390
3after 'returns cached': valueThe 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.
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:
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}")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.272Production 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.
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][2]
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.
| Objective | Can read | Good for | Poor fit |
|---|---|---|---|
| Causal language modeling | Earlier context, no future tokens | Generating text left to right | Filling a middle blank with future context |
| Masked language modeling | Left and right context | Classification, extraction, sentence understanding | Open-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] That two-sided objective builds input representations; the left-to-right generation loop needs causal modeling.
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.
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:
| Phase | What feeds position t | Why |
|---|---|---|
| Training (teacher forcing) | The true tokens x_1 ... x_{t-1} from the dataset | Stable targets; all positions can be scored in one parallel forward pass. |
| Generation (autoregressive) | The model's own previously sampled tokens | No 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:
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 model | Correct next token |
|---|---|
<bos> | function |
<bos> function | returns |
<bos> function returns | value |
<bos> function returns value | <eos> |
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]}")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>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.
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.
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}")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.209Cross-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 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][5]
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.
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}")1per-position loss: [0.6931 0.6931 0.6931 2.0794]
2average loss: 1.0397
3perplexity: 2.8284
41 / geometric mean:2.8284A 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
expof that same loss. Hold those choices constant before treating a mismatch as a regression.
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:
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.
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.
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.
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))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.
The loop ends when one of these things happens:
| Stop condition | Example |
|---|---|
| Model emits an end token | </s> or another stop marker. |
| Application or API hits an output-token limit | The server stops after a token budget. |
| Application stop string appears | The app stops at \n\nUser: or a JSON boundary. |
| Safety or policy system interrupts | The provider refuses or truncates output. |
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.
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())1greedy: value
2sampled at temperature 1.4: ['index', 'value', 'index', 'value', 'result', 'value', 'result', 'value']
3probabilities: [0.551, 0.334, 0.115]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.
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]
Serving systems usually split this into two phases:
| Step | Naive work | With 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 2 | Recompute 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 3 | Recompute 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 answer | Work 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.
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.
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")12,048 tokens: 1.00 GiB
232,768 tokens: 16.00 GiBThis 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.
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.
Consider this prompt:
"A job has a 5-second deadline. After 2 seconds elapsed, the remaining budget is"
| Model family | Likely shortcut | Better internal feature |
|---|---|---|
| Markov-style autocomplete | Nearby token pattern like 2, 3 | None; it mostly follows local statistics. |
| Large language model | Still predicts one token | A 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][7]
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.
| Symptom | Cause | Fix |
|---|---|---|
| Plausible but false answer | Training distribution dominates missing local facts. | Add retrieved source text. |
| Old limit repeated | Model has stale or generic priors. | Query current docs or database. |
| Confident unsupported claim | Probability isn't evidence. | Require citations or source-grounded output. |
| Long answer drifts | Context fills with generated text. | Re-anchor with instructions and source snippets. |
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.
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.Answer every question, then check your score. Score 75% or higher to mark this lesson complete.
10 questions remaining.
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
Questions and insights from fellow learners.