What a language model actually computes, and how we score it.
This is the first square on the map. Everything else in the book is built on top of one function, and this chapter is about that function: what goes in, what comes out, and how we decide whether the output was good. No architecture yet. No matrices. Just the game.
The question this chapter answers: a language model is a function from text to what, exactly, and what does "good" mean for it?
A friend opens a novel to a random page, reads a sentence aloud, and stops mid-way.
"The detective looked at the body and knew at once that the killer was still in the …"
You guess the next word. Room, probably. Maybe house. Maybe building. Not banana.
Now change the rules. Instead of one guess, you get 100 chips. You place them on any words you like, as many words as you like. Then your friend reveals the true next word and charges you a bill. The fewer chips you had on the true word, the bigger the bill. All 100 chips on room and the word was room: no charge. All 100 on room and the word was house: enormous bill. Chips spread 60 / 25 / 10 / 5 across room, house, building, area: a small bill whichever of the four it was.
Then the friend reads the true word aloud, adds it to the sentence, and you play again for the word after that. And again. Page after page. Book after book. Ten trillion words.
That is the entire job of a language model. Not writing. Not answering. Placing chips on the next word, over and over, and being billed for it. Everything it appears to do, from poetry to Python, is a side effect of getting very, very good at this one game.
| In the game | In the machine | The word we will use |
|---|---|---|
| The sentence so far | The sequence of tokens the model can see | context |
| Words, or word pieces, you may bet on | The fixed list of every token the model can output | vocabulary, size V |
| Where your 100 chips went | One number per vocabulary entry, all ≥ 0, summing to 1 | probability distribution over the next token |
| The player placing chips | A function from context to distribution, with adjustable internal numbers | the model, parameters θ |
| The true next word | The token that actually came next in the text | target |
| The bill | −ln (probability the model put on the target) | loss |
| Reading the true word aloud and playing again | Append a token, predict the next one | autoregression |
| Getting billed and adjusting how you bet | Changing θ to lower future loss | training |
| Playing for real, no bills | Using θ as it is to produce text | inference |
Nine rows. Every later chapter refines one of them. Chapters 2 to 4 open the "player" row. Chapter 5 opens the "training" row. Part 2 asks what happens when the player has a trillion adjustable numbers and reads the whole internet.
The game is played with words, but the machine does not see words. It sees tokens: chunks of text drawn from a fixed list. Common words are usually one token. Rarer words get split. Punctuation, spaces, and pieces of code are tokens too. The list is decided once, before any training, and it is called the vocabulary.
text "The detective looked at the body"
│
▼ chop into vocabulary pieces
tokens ["The", " detective", " looked", " at", " the", " body"]
│
▼ look each piece up in the vocabulary
ids [ 791, 31305, 7111, 520, 279, 2547 ]
Two things to notice. First, the leading space is part of the token: " the" and "the" are different entries. Second, the model only ever sees the integers on the last line. "The" is not a word to the model; it is entry number 791 in a list.
How large is that list? For current frontier models, on the order of one to two hundred thousand entries. Llama 3 uses 128,256 public. Closed labs do not always publish theirs, but tokenizers are usually shipped with the API and can be inspected, so the sizes are known to be in the same range inferred. Call it V ≈ 128k and hold that number; it matters in a moment.
How the chopping is decided, why it produces odd splits, and what that does to model behavior is Chapter 6. For now: text in, list of integers out.
Strip away everything, and a language model is this:
Read the picture as a Python signature, because that is what it is:
def model(context: list[int], theta) -> list[float]:
# returns V numbers, one per vocabulary entry, each ≥ 0, summing to 1
...
Three facts about this function carry the whole book.
It never answers. It always spreads. Ask it to continue "2 + 2 =" and it does not return " 4". It returns a distribution in which " 4" has, say, probability 0.97, " four" 0.02, and the other 128k tokens share the remaining 0.01. A good model is one whose spread is sharp where the text is predictable and honestly wide where it is not. "The killer was still in the …" should be wide. "2 + 2 =" should be sharp.
It is the same function at every step. To produce a paragraph, you call it, pick a token from the distribution, append that token to the context, and call it again. Nothing inside changes between calls. The parameters θ are fixed during generation. The only thing that changes is the context, which grows by one token each time.
Everything the model knows is in θ. The function has no database, no lookup, no memory across calls. Whatever it has learned about detectives, arithmetic, or Python is encoded in those billions of numbers and expressed only through the shape of the distribution it returns. Part 1 shows how numbers can encode that. Part 2 and 3 show how they are set.
The picture above cheats slightly. The inside of the model, once we open it, will not naturally produce numbers that are non-negative and sum to one. It will produce scores: one number per vocabulary entry, any real value, positive or negative, with no constraint at all. A score of 4.1 for " room", 2.3 for " house", −7.8 for " banana". Higher means "more likely", but they are not probabilities yet. These raw scores are called logits.
So the model needs a last step that converts V unconstrained scores into a valid distribution. The conversion has to do four things:
" room" scored higher than " house", it must end up more probable.The function that does all four is softmax. It is two operations: raise e to each score, then divide by the total.
exp(z) = ez, where e ≈ 2.718. Three properties matter for us and nothing else does.
ez > 0 for every real z, including very negative ones. e−7.8 ≈ 0.0004. Small, but never zero, never negative. Requirement 1, done.a > b then ea > eb. And the gap widens: a score lead of 1 becomes a ratio of about 2.7, a lead of 2 becomes 7.4, a lead of 3 becomes 20. Requirement 3, plus a useful property: the winner gets amplified.ea+b = ea·eb. Its inverse, the natural logarithm ln, does the reverse: ln(x·y) = ln x + ln y. This is why logs turn "probability of a whole sentence" (a product) into "loss of a whole sentence" (a sum), in §1.4.Dividing by the sum of all the exponentials makes the values add to 1. Requirement 2. And both operations are smooth. Requirement 4.
Shape check: softmax takes a vector of V scores and returns a vector of V probabilities. Same length. It never mixes positions; it only rescales them relative to each other.
A four-token vocabulary, because 128k rows would not fit on the page. Scores from the model: z = [2.0, 1.0, 0.0, −1.0] for [cat, dog, mat, rug].
| token | score z | exp(z) | ÷ 11.48 | probability p |
|---|---|---|---|---|
cat | 2.0 | 7.389 | 7.389 / 11.48 | 0.644 |
dog | 1.0 | 2.718 | 2.718 / 11.48 | 0.237 |
mat | 0.0 | 1.000 | 1.000 / 11.48 | 0.087 |
rug | −1.0 | 0.368 | 0.368 / 11.48 | 0.032 |
| sum 11.48 | sum 1.000 |
Notice what happened to the gaps. The scores were evenly spaced, one apart. The probabilities are not: cat got 64%, and each step down loses a factor of 2.7. That amplification is deliberate. It lets the model express strong preferences with modest score differences.
Check it yourself:
import math
z = [2.0, 1.0, 0.0, -1.0]
ex = [math.exp(v) for v in z]
p = [v / sum(ex) for v in ex]
print([round(v, 3) for v in p], round(sum(p), 6))
[0.644, 0.237, 0.087, 0.032] 1.0
Now play with it. The widget below is the table above with sliders.
You saw a temperature slider. It does one thing: divides every score by a number T before the exponential. T = 1 changes nothing. T = 0.5 doubles every score, which doubles every gap, which makes the winner win harder. T = 2 halves the gaps and flattens the distribution toward uniform. As T → 0 the distribution collapses onto the single top score; as T → ∞ it becomes flat.
Temperature is not part of the model and not learned. It is a knob applied at generation time to decide how adventurous the chip-placing should be. We return to it in §1.5 and properly in Chapter 7.
Back to the game. You placed your chips, the true word was revealed, and now you are charged. The rule for the charge is the single most important formula in this book, and it is short:
loss = −ln p(target)
Take the probability the model assigned to the token that actually came next. Take its natural log. Flip the sign. That is the bill.
Why this shape? Work through what it does at the edges.
| p on the true token | −ln p | Reading |
|---|---|---|
| 1.00 | 0.00 | Certain and right. No charge. |
| 0.50 | 0.69 | A coin flip between two options. Mild. |
| 0.10 | 2.30 | One of ten. Noticeable. |
| 0.01 | 4.61 | One in a hundred. Painful. |
| 0.0001 | 9.21 | You said "almost impossible" and it happened. |
| 0.00 | ∞ | You put zero chips on it. Unbounded charge. |
This quantity has a name: surprise. It measures how unexpected the outcome was given what you predicted. Low probability, high surprise. And the loss for a whole text is just the surprise summed over every token in it.
Three reasons it is exactly this formula and not some other decreasing function of p.
1. Products become sums. The probability the model assigns to a whole sentence is the product of the probabilities it gave to each token in turn: p(t₁) · p(t₂ | t₁) · p(t₃ | t₁, t₂) · …. Products of small numbers vanish into floating-point noise after a few dozen tokens. Logs fix it: the log of the product is the sum of the logs, so the loss of a sentence is the sum of per-token losses, a number computers can hold and compare. This is the third property from the Math box, doing real work.
2. Hedging is rewarded honestly. Suppose you genuinely believe the next word is room with 60% and house with 40%. With this scoring rule, the bet that minimises your expected bill is to put exactly 60 chips on room and 40 on house. Not 100 on room. Not 50/50. Your true belief. A scoring rule with that property is called a proper scoring rule, and −ln p is the simplest one. It means a model trained to minimise this loss is pushed toward reporting its actual uncertainty, not toward bravado. This is why a well-trained model's probabilities tend to be meaningful and not just rankings.
3. Zero is forbidden. The infinite charge for p = 0 looks harsh, but it is the point. It means the model can never afford to rule anything out completely. Every token keeps a sliver of probability, however small. That sliver is what lets the model recover when the text takes a turn it did not expect.
ln gives the loss in nats. Divide by ln 2 ≈ 0.693 and you have bits. A loss of 2.3 nats is 3.3 bits: "the model needed 3.3 bits of information to be told what came next." The book uses nats because the code does. When a paper reports "bits per byte" it is the same idea in different units.Take the mean loss per token over a text, then undo the log: perplexity = emean loss. If the mean loss is 2.3 nats, perplexity is 10. Read that as: "on average, the model was as uncertain as if it were choosing uniformly among 10 equally likely tokens." A perplexity of 1 is a model that is always certain and always right. A perplexity of 128k is a model that has learned nothing and spreads its chips evenly over the whole vocabulary. Frontier models on ordinary English land in the low single digits inferred: from published pretraining curves of open models, whose loss on held-out web text sits below 2 nats, and from the general shape of scaling results in Chapter 8.
Look at the last row of that widget. The model gave mat only 12% (it preferred rug, perhaps). That single token costs more than the other five combined. Loss is dominated by the tokens the model got most wrong, which is exactly where you would want a learner's attention to go. Chapter 5 shows that this is not a metaphor: the size of the loss on a token directly sets how hard the parameters get pushed because of it.
You have the function and the scoring rule. Now generate text. The loop is four lines long and it is the same loop for every language model ever deployed:
context = tokenize(prompt)
while not done:
p = model(context, theta) # V probabilities
t = choose(p) # one token id
context.append(t)
The only choice you have is inside choose. Two families:
Play it. The widget holds a toy model with a nine-token vocabulary. It has a real limitation that you should notice: it decides its distribution from the last token only. A real model conditions on the entire context. That gap, from "last token" to "everything so far", is exactly what Chapter 3 fills with attention.
Try three things. Press Take the most likely eight times and watch it settle into a loop. Reset, and press Sample eight times: same model, different sentence. Then push the temperature to 3 and sample: the bars flatten and the sentences turn to nonsense, because the model's preferences are being ignored. Push it to 0.2 and sampling becomes greedy in disguise.
Nothing about the model changed in any of those runs. Only choose did. Keep that separation clean in your head: the model produces the distribution; the sampler picks from it. Many things people attribute to "the model" (creativity, repetitiveness, randomness) live in the sampler.
There are two activities in the life of a model, and the same function sits in the middle of both.
Training is the game played for bills. Feed the model a slice of real text, ask it for the distribution at every position, bill it for every true next token, then adjust θ a little so the same bills would have been slightly lower. Repeat over trillions of tokens. The "adjust a little" step is Chapter 5; it is where calculus enters, and it is the only place anything is learned.
Inference is the game played for real. Feed the model a prompt, sample, append, repeat. θ is frozen. When you use a chat assistant, this is all that happens on the other end: the loop of §1.5, on a very large θ, very fast, for you and a few million other people at once. How that is made fast and cheap is Chapter 19.
A common confusion, cleared now: the model does not learn from your conversation. Nothing you type changes θ. It changes the context, and a different context gives a different distribution, which can look like learning. It is not. The red arrow is not there.
Time to introduce the model this book follows. It does not exist. Call it Beacon, built by an organisation we will call the Lab. Beacon stands in for a current frontier model, and every part of the book asks what the Lab does to it at that stage: how the corpus is built, how the run is planned, how the assistant behavior is shaped, how it is served. Where the real labs have published, Beacon follows their published numbers, marked public. Where they have not, the book says so.
Here is what Chapter 1 already tells you about Beacon.
| Property | Beacon | Evidence |
|---|---|---|
| Output type | A distribution over V tokens, per call | By construction; true of every model in this book |
Vocabulary size V | ≈ 128k | Llama 3: 128,256 public. Others in the same range inferred |
| Parameter count | Hundreds of billions | Llama 3.1 405B, DeepSeek-V3 671B total public. Closed frontier models: not disclosed unknown |
| Training objective | Minimise −ln p(next token) over a huge corpus | Every published frontier tech report public |
| Sampling at the API | Decided by the Lab; user knobs vary by provider | Some current frontier APIs no longer expose temperature public |
How many numbers does Beacon produce to answer you? Every generated token requires one full call to f, and every call returns V ≈ 128,000 probabilities.
reply length 1,000 tokens distributions 1,000 (one per generated token) numbers per dist. 128,000 numbers computed 128,000,000 ≈ 1.3 × 10⁸ numbers kept 1,000 (the sampled token ids)
A thousand-token reply means the model produced about 128 million probabilities and threw away all but a thousand of them. Every one of those 128 million was computed by pushing the context through the entire network. That waste is not a bug; it is the price of a function whose output is a distribution. Chapter 19 is largely about not paying more of that price than necessary.
The second thread of this book is a product you build on a frontier API, one layer per chapter. It is called Dispatch: an on-call assistant for the engineers who run Postbox, a fictional notification-delivery service. By Part 4 Dispatch will read runbooks, call tools, triage incidents, and be measured before every release. Today it says hello.
The first call is deliberately plain, so that you can see the game of this chapter underneath it. A system prompt sets the context. A user message extends it. The model plays the next-token game from there until it decides to stop.
# code/ch01/dispatch_hello.py
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from the environment
SYSTEM = """You are Dispatch, the on-call assistant for Postbox, a notification
delivery service. Answer briefly and concretely. If you do not know
something about Postbox, say so."""
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
system=SYSTEM,
messages=[{
"role": "user",
"content": "Pager went off: 'push-queue depth > 50k'. Where do I start?",
}],
)
for block in response.content:
if block.type == "text":
print(block.text)
print("stop_reason:", response.stop_reason)
print("output tokens:", response.usage.output_tokens)
(example output — the exact text differs per run, because each token is sampled) Start with three checks: (1) is the queue growing or draining — compare depth over the last 15 minutes; (2) are consumers healthy — look at push-worker error rate and restarts; (3) did volume spike upstream — check inbound event rate. I don't have Postbox's dashboards, so tell me which of these you can see and I'll narrow it down. stop_reason: end_turn output tokens: 94
Map that back to the chapter. The system prompt and your message became roughly 80 tokens of context. The model then played 94 rounds of the game: 94 distributions over 128k tokens, 94 samples, 94 appends. On the 94th round it sampled a special end-of-turn token, and the loop stopped. stop_reason: end_turn is the sampler reporting that the model itself chose to end. The other common value, max_tokens, means you cut it off.
Every number in response.usage is a count of rounds of the game. Every price on every provider's pricing page is a price per round.
Most frontier APIs return the sampled token, not the distribution it was drawn from. But you can see the distribution's shadow: ask for a single token, many times, and tally what comes back. If the model spreads its chips, the tally spreads.
# code/ch01/first_token_tally.py
import collections
import anthropic
client = anthropic.Anthropic()
prompt = "Complete this sentence with one word: The detective knew the killer was still in the"
tally = collections.Counter()
for _ in range(60):
r = client.messages.create(
model="claude-haiku-4-5", # a model that still accepts a temperature parameter
max_tokens=3,
temperature=1.0, # sample with the model's own odds
messages=[{"role": "user", "content": prompt}],
)
first = r.content[0].text.strip().split()[0].strip(".,").lower()
tally[first] += 1
for word, n in tally.most_common():
print(f"{word:<10} {n:>3} {'█' * n}")
(example output — a tally of 60 samples; yours will differ in the details) room 31 ███████████████████████████████ house 14 ██████████████ building 8 ████████ area 4 ████ mansion 2 ██ vicinity 1 █
That histogram is the chip layout, observed from outside. Run it again at temperature=0.2 and the tally collapses to almost all room. Run it at temperature=1.5 if the model allows it and the tail grows.
Two notes on the frontier reality here. The newest models from some providers, including the current top models on the Claude API, no longer accept a temperature parameter at all public: the lab has fixed the sampling policy. The knob has moved from your side of the API to theirs. Second, if a provider does expose token probabilities directly (some do, under names like logprobs), you can skip the tally and read the distribution straight off. Either way the object underneath is the same: V numbers summing to one.
max_tokens=3 and not 1: the model may spend its first token on a leading space or a quote mark before the word. Three tokens gets the word out; the script keeps only the first word. The point of the experiment survives.Each of the following removes one piece of the game. Predict what happens before reading the answer.
Output one token instead of a distribution. The model returns its single best guess and nothing else. Three things break. There is no way to express "60% room, 40% house", so a text where either continuation is common cannot be modelled honestly. There is no sampling, so every prompt has exactly one continuation, forever. And there is no smooth scoring: the guess is right or wrong, and "nearly right" earns nothing, so (Chapter 5) there is no gradient to learn from. The distribution is not a nicety. It is what makes the thing trainable.
Score with accuracy instead of −ln p. Charge 0 if the top token was right, 1 if not. Now the model is paid the same for 51% on the true token as for 99%. Hedging is never rewarded, so the model has no reason to keep its probabilities meaningful; only the argmax matters. Calibration, the property that "70%" means "right about 70% of the time", disappears. And the same problem as above: a step function has no slope to learn from.
Drop the sum-to-one constraint. Let the model output any positive numbers. The loss −ln p can now be driven to zero by making every number huge. The constraint is what forces a trade-off: chips on room are chips not on house. Without a budget there is no betting, only bragging.
Use −p instead of −ln p. Still decreasing in p, still rewards the true token. But the charge for p = 0 is a finite −0 and the charge for p = 0.01 barely differs from p = 0.02. The model can afford to zero out rare tokens, and confidently-wrong costs almost the same as slightly-wrong. The log is what makes ruling something out expensive and what makes sequence loss a sum.
Close the page and say it back. A language model is a function. In: a list of token ids, the context. Out: one non-negative number per vocabulary entry, about 128 thousand of them, summing to one. The function's internals produce unconstrained scores, logits; softmax exponentiates them and divides by the total to get a valid distribution, amplifying the leader as it does so. The model is scored on a true next token by the surprise −ln p, which is zero when certain and right, unbounded when certain and wrong, additive across a sequence, and honest in the sense that the cheapest strategy is to report your real beliefs. Text is generated by calling the function, sampling one token from the output, appending it, and calling again; the model returns distributions, the sampler chooses. Training is that same function billed on real text with its parameters nudged to lower the bill. Inference is the same function with the parameters frozen. Beacon is that function with hundreds of billions of parameters, and Dispatch is a program that sends it context and reads back what it sampled.
[3, 3, 1, 0, −2]. Compute the softmax to three decimals. Then compute the loss if the true token is the third one. Then recompute both at temperature 0.5. Check: at T = 1 the first two tokens should each get about 0.44.first_token_tally.py three times: at temperature 0.2, 1.0, and the highest the model allows. For each, compute the empirical perplexity of the tally: mean of −ln(count/60) weighted by count, then exponentiate. You should see it rise with temperature. Then change the prompt to "2 + 2 =" and confirm the tally collapses at every temperature. The model's spread depends on the context, not only on the knob.