A Reference
for the Rest of Us!

The #1 guide for people who have never done it

Building Your OwnAI

FORDUMMIES

What the parts are, how they wire together, what data goes in, and what it costs. Written from a working build, including the parts that went wrong.

AI4DUMMIES · Book One

The wiringThe data mixThe training runWhat breaks

Most explanations of language models are either three equations or three hundred pages. This is the middle: enough to actually plan a build, decide what data you need, and know what you are looking at when the numbers on screen start moving.

No code here on purpose. Code goes stale and every framework spells things differently. The shape of the thing does not change, and the shape is what nobody explains.

What's in here
  1. What a language model actually is
  2. The parts, and what each one does
  3. Parameters: what people mean by "a billion"
  4. Size: picking your numbers
  5. Data: the part that decides everything
  6. The mix, in percentages
  7. Tokenizer: turning text into numbers
  8. The actual kit: what to install, where to get data
  9. The training run, start to finish
  10. Reading the numbers while it runs
  11. When it stops learning and starts reciting
  12. Personality: LoRA, fine-tuning, and not paying twice
  13. How much room each number takes
  14. Checking your corpus before you spend the hours
  15. What goes wrong

Chapter 1What a language model actually is

It predicts the next word. That is the whole job.

Everything else, answering questions, writing code, having a personality, falls out of doing that one thing extremely well over a very large amount of text. Nobody programs in "be helpful." You train next-word prediction, and helpfulness is a behaviour that emerges when the training text contains helpful exchanges.

This matters practically: you never write rules. If the model does something wrong, the fix is almost always in the data, not in a rule you add afterward. A hand-written rule is a patch over a hole in the training set.

Why guessing the next word turns into everything else It sounds far too simple to produce anything interesting, so here is why it is not.

Take the sentence "the answer to 14 plus 9 is ___". To guess that word well you have to be able to add. Take "the murderer turned out to be the ___" at the end of a detective story: to guess well you have to have followed the plot. Take "Q: how do I fix a flat tyre? A: ___", to guess well you have to be helpful.

Nobody taught it arithmetic, plot, or helpfulness. Those are just things you need in order to win at guessing, and the training pushed relentlessly toward winning. The ability is a side effect of the goal.

This also tells you where the limits are. It is superb at things that make text predictable and unreliable at things that do not. A confident wrong fact and a confident right fact look identical from the inside, because both were simply the most likely next words.
The one sentence version A language model is a very large statistical guess about what comes next, refined billions of times against real text until the guesses get good.

Chapter 2The parts, and what each one does

A model is a stack of identical layers. Text goes in the bottom, comes out the top as a prediction. Each layer does the same two jobs.

Job one: attention (looking around)

Attention lets every word look at the other words and decide which ones matter. In "the dog that chased the cat was tired," attention is what connects "tired" back to "dog" and not to "cat." Without it a model only sees the previous word or two, which produces text that is topically right and grammatically scrambled.

How attention actually decides, without the maths Every word writes itself three notes.

A question: "I am the word tired. Who here is a thing that can be tired?" A label: "I am dog. I am a creature, I am the subject of this sentence." And a payload: what it actually passes along if chosen.

Then every question is compared against every label, all at once. Strong match, big share of attention; weak match, near zero. "Tired" reads mostly from "dog" and barely from "the". Nobody wrote that rule. The model learned what makes a good question and a good label by being wrong a few billion times.

This is also why context costs so much memory: every word compares against every other word, so doubling the amount of text it can see quadruples the comparisons.

Job two: the feed-forward block (thinking about it)

After looking around, each word gets processed on its own through a small expansion-and-contraction. This is where most of the model's actual stored knowledge lives, roughly two thirds of the parameters.

Why two jobs and not one Attention gathers, the feed-forward block decides. Attention can pull "dog" and "chased" and "tired" together into one place, but on its own it is only ever mixing and matching words that are already there. The feed-forward block is where the gathered pile gets turned into something new, where "creature that has been running" becomes the kind of thing that gets tired.

One layer of gather-then-decide handles simple relationships. Stack twenty and the twentieth is working on conclusions the first one could not have reached. That is what depth buys you, and it is why a deep narrow model often beats a shallow wide one.

The supporting cast

PartWhat it doesWhy you care
Embedding tableTurns each token into a list of numbersThe model's vocabulary lives here
Position informationTells the model word orderWithout it, sentences are just word bags. Modern builds use rotary positions (RoPE)
NormalisationKeeps numbers in a sane range between layersSkip it and training explodes. RMSNorm is the current default
Output headTurns the final numbers back into "which word next"Where the actual prediction pops out
Worth knowing Grouped-query attention (GQA) lets several attention heads share the same lookup data. It cuts memory noticeably at almost no quality cost, and it is why a modern model of a given size runs on smaller hardware than one from a few years ago.

Chapter 3Parameters: what people mean by "a billion"

You hear that a model "has 7 billion parameters" and it sounds like a filing cabinet with 7 billion facts in it. It is not. It is closer to 7 billion dials.

Picture an old mixing desk, the kind with rows of knobs. Sound goes in one end, comes out the other, and every knob nudges what happens in between. Nobody labelled the knobs. Nobody decided what any single one does. You just turn them until the output sounds right.

A parameter is one of those knobs. It is a single number, usually something small and unremarkable like 0.0384 or -1.27. On its own it means nothing at all, and you cannot look at one and say what it is for.

So who turns them?

Nobody. That is the whole trick, and it is the part people find hardest to believe.

Training is a loop that runs millions of times:

  1. Show the model some real text with the next word hidden.
  2. Let it guess.
  3. Compare its guess to the actual word.
  4. Nudge every dial a tiny amount in whichever direction would have made that guess better.
  5. Do it again with different text.

Step 4 is the whole of machine learning. The nudge is tiny on purpose, far too small to fix the current mistake. It is not meant to. Each nudge is a fraction of a correction, and the model only becomes good because it gets billions of them, from billions of different sentences, and the pressures that keep pointing the same way accumulate while the random ones cancel out.

Nobody chooses what any dial means. Meaning arrives on its own: some dials end up tracking whether the subject is plural, some end up caring about the tone of a sentence, some do jobs nobody has a word for. You do not assign these roles and you cannot read them off afterward. They emerge because they were useful for guessing the next word.

Two different kinds of number This is worth keeping straight, because both get called "settings":

Parameters are the dials the model turns itself, during training. Billions of them. You never touch these.

Hyperparameters are the handful you set before you start: learning rate, batch size, how many layers. Maybe a dozen. These are the ones you can get wrong, and Chapter 9 is about them.

Why more dials is not automatically better

More dials means more capacity to represent things. It also means more dials that need enough examples to settle sensibly. Give a model 600 million dials and only a few million sentences to learn from, and most of those dials never receive enough pressure to point anywhere useful, they end up memorising the training text rather than learning language, which has its own chapter, because it is the failure that hides best (Chapter 11).

That is the ratio in the next chapter, and it is the reason a small well-fed model beats a large starved one.

Chapter 4Size: picking your numbers

Four dials decide how big your model is:

Real configurations from a working build, so these are not invented:

NameWidthLayersContextRuns on
tiny2566256A phone, no graphics card
small5128768Any gaming PC
large1536241024One good graphics card (~600M parameters)
Learned the hard way Bigger is not automatically better. A big model trained on too little data is worse than a small model trained on plenty. There is a rough ratio, call it 20 words of training text per parameter. A 600M model wants somewhere north of 10 billion words to earn its size. If you cannot feed it, build smaller.
Learned the hard way The 20:1 ratio is necessary and not sufficient. Words are what the model sees. Updates are what actually change it, every gradient step nudges the weights once, no matter how much text went into that step. Get the words right and the updates wrong and you get the same dead flat loss line as feeding it nothing.

This bit us on a 28.5M model. The word count was correct at 590M. It flatlined at loss 6.9 and would not move. A model a third the size, same text, same trainer, was at 4.56 by the same point. The step count had been copied from the smaller model's recipe without noticing that its context window was a third as long, so each step carried three times the text, and the bigger model got the same number of updates to absorb three times the data.

The fix is not more text. Cut the batch size so each step is smaller, and raise the step count to match, keeping the total words the same. Four times the steps at a quarter the batch is the identical training set and four times the weight movement.
Why updates are a separate thing from words Imagine teaching someone to cook by having them read recipes. Words seen is how many recipes they read. Updates is how many times they actually cooked.

Read a thousand recipes and cook twice and you have not learned to cook. That is what a flatlined run is: enormous amounts of text poured past a model that was only allowed to change itself a few hundred times. Every step is one attempt in the kitchen, and it does not matter whether you read one recipe before it or fifty, you still only cooked once.

So two numbers have to be right, not one. Enough text to learn from, and enough attempts to learn in. Miss either and the loss curve looks the same: flat. This is the single easiest way to waste a night, because the natural instinct when a run stalls is to add more data, and more data with the same step count makes it worse.

Chapter 5Data: the part that decides everything

This chapter matters more than the rest combined. The architecture is nearly a solved problem you can copy. The data is where your model becomes good or stays useless, and it is where nearly all the real work goes.

The trap nobody warns you about

The easiest text to get legally is old: public-domain books, out-of-copyright encyclopedias. It is free, it is clean, and it will give your model the voice of a Victorian gentleman. It will not know what a phone is. Everything it says will sound like 1890 because it is 1890.

You need modern text, and you need conversation, and those are different problems from getting text at all.

The kinds of data, and what each buys

TypeWhat it teachesWatch out for
Filtered web textModern language, current concepts, rangeUse a quality-filtered set, not raw scrape. Raw web is mostly garbage
BooksLong-range structure, narrative, real paragraphsAge. Cap the old stuff or it dominates the voice
Conversation / dialogueTurn-taking, answering rather than continuingWithout this it monologues instead of replying
Reference (dictionary, encyclopedia)Facts, definitions, vocabulary breadthDense and repetitive. A little goes a long way
CodeStructure, logic, precision. Helps reasoning even if you never want code outOptional, but cheap gains
Instructions (task then response)Doing what it is toldUsually a later stage, not the main run
Why the mix matters more than any single source A model does not learn "facts from the encyclopedia" and "conversation from the chat logs" in separate compartments. It learns one set of habits from all of it at once, weighted by how much of each you fed it. Feed it 90% encyclopedia and it will answer you in encyclopedia, flat, third person, no idea it is in a conversation. The mix is not a shopping list. It is the personality dial, and you are turning it whether you meant to or not.

Quantity, honestly

Text is measured in tokens, roughly three-quarters of a word each. A useful yardstick: a full-length novel is about 120,000 tokens. So one billion tokens is around 8,000 books.

Chapter 6The mix, in percentages

Ratios matter as much as volume. A defensible starting mix for a general conversational model:

ShareSourceWhy this much
60–70%Quality-filtered modern webThe backbone. Sets the register as contemporary
15–20%BooksTeaches structure longer than a paragraph. Cap it. This is the Victorian-voice risk
8–12%ConversationThe difference between a model that answers and one that rambles
3–5%ReferenceVocabulary and facts, without letting dictionary cadence take over
0–10%CodeOptional. Improves structured reasoning
A real mistake, worth stealing Filtering a corpus to remove a specific name, we deleted every document containing it. That silently destroyed the entire conversation set: 158,000 exchanges gone, because that name appeared in nearly every one. Out of a 14-million-token budget, 2,045 tokens survived.

The fix was to substitute the word rather than drop the document, which kept the turn-taking intact. Always count what survives your filter. A filter that quietly eats 99% of a category looks exactly like a filter that worked.

Chapter 7Tokenizer: turning text into numbers

Models cannot read letters. A tokenizer chops text into pieces and assigns each a number. Pieces are usually chunks of words: "unhappiness" might become "un" + "happi" + "ness."

Why chunks, and not just letters or whole words

Both obvious options are bad, and understanding why tells you what a tokenizer is actually for.

One number per letter gives you a tiny alphabet, 26 letters and some punctuation. Lovely and simple. But now the word "unhappiness" is twelve separate steps, and the model has to work out from scratch that those twelve things go together before it can even start on what they mean. Every sentence becomes enormous, and since a model can only hold so many steps at once, you have just cut how much text it can see in one go by a factor of four.

One number per word gives you short sentences, which is what you want. But English has a few hundred thousand words, plus names, plus typos, plus "unhappinesses" which nobody has ever written before. Every word you did not plan for becomes a shrug, the model has literally no number for it. And every word you did plan for needs its own row of storage, which gets expensive fast.

Chunks split the difference. Common words get one piece each because they earn it. Rare words get built from parts. Nothing is ever unspellable, because worst case it falls back to letters. That is the whole trick.

The one sentence version A tokenizer is a compression scheme with one goal: say as much as possible in as few numbers as possible, without ever hitting a word it cannot spell.

Decisions that matter:

Why there is no migration This one catches people out, so it is worth knowing why rather than just obeying it. The model never learns the word "cat". It learns that number 4,182 tends to be followed by certain other numbers. The meaning does not live in the model, it lives in the agreement between the model and the tokenizer about which number means what.

Retrain the tokenizer and 4,182 becomes "however". Every weight in your model is now an extremely confident set of beliefs about the wrong words. Nothing errors, nothing crashes, and the output is fluent gibberish. The tokenizer is part of the model. Ship them together, back them up together, and never regenerate one without the other.

Chapter 8The actual kit: what to install, where to get data

Everything so far is the shape of the thing. This is the shopping list: names, what each one is for, and where it lives.

The one non-negotiable piece

PyTorch is what nearly everyone trains in. It runs the maths on the graphics card and, critically, works out the nudge for every single dial automatically, so you never write that part yourself. If you install one thing, this is it. Match the version to your card's CUDA release; the site gives you the exact command.

The alternative is JAX, which is genuinely good and common in research. But the tutorials, the example code and the answers to your questions are overwhelmingly PyTorch. Start where the help is.

Things you do not have to write yourself

WhatDoesWhere
TransformersModel architectures, ready-built. Worth reading, the code is unusually clearhuggingface/transformers
tokenizersTrains and runs the text-to-numbers stephuggingface/tokenizers
SentencePieceThe other standard tokenizer, used by many well-known modelsgoogle/sentencepiece
datasetsDownloads and streams big corpora without filling your diskhuggingface/datasets
AccelerateMulti-card and mixed-precision plumbinghuggingface/accelerate
PEFTLoRA and friends. See Chapter 12huggingface/peft
bitsandbytesShrinks a finished model for runningbitsandbytes
TensorBoard or Weights & BiasesDraws the loss curve, so you see the trend instead of one jumpy linetensorboard / wandb.ai

Where the data actually comes from

This is the part that is hardest to find written down anywhere, and it is the part that decides whether your model is any good.

SourceWhat it isRough size
FineWeb-EduWeb text filtered for educational quality. Modern register. The best single starting point1.3T tokens, take a slice
FineWebThe larger sibling, not education-filtered15T tokens
DolmaOpen corpus with a documented mix and permissive terms3T tokens
RedPajamaOpen reconstruction of a well-known training mix1T tokens
The Pile (uncopyrighted)Long-standing mixed corpus, academic and technical heavy~400B tokens
Project GutenbergPublic-domain books, free and clean. This is the Victorian-voice trap from Chapter 5. Cap it. Bulk mirror at the offline catalogues~75,000 books
Wikipedia dumpsFacts and clear expository prose. Simple English is a good small start; extract with wikiextractor~4B tokens (English)
The Stack v2Permissively-licensed source code, if you want the reasoning boost~900B tokens
UltraChat, Alpaca, OpenAssistantConversation and instruction data: the turn-taking without which it monologuesHundreds of thousands of exchanges
Internet Archive texts, WikisourceReference works, old encyclopedias, dictionaries. Same age caveat as GutenbergMillions of items
Downloading, the hard-won note Big datasets arrive as many compressed shards. Download each one whole, then verify it before accepting it. Some content servers ignore a request to resume a partial download and hand back the entire file again. If you are appending to what you already have, that silently produces a corrupt double-length shard that looks fine until training chokes on it days later. This cost us six files and a night.

Code worth reading before writing your own

Hardware, honestly

You haveRealistic target
No graphics cardRead nanoGPT and run a tiny model on the processor to watch the loop work. Do not try to train anything real
8 GB cardA small model (Chapter 4). Genuinely coherent output is reachable
16–24 GB cardSeveral hundred million dials. This is where it gets interesting, and it is an ordinary gaming card
Rented cloud hoursSensible for one long run. Rent before buying hardware to find out whether you enjoy this

Memory matters more than speed. A slower card with more of it beats a faster card that cannot hold your model.

When it breaks

Chapter 9The training run, start to finish

  1. Gather and clean: deduplicate, strip junk, fix encoding. Slow, unglamorous, decides your outcome.
  2. Train the tokenizer on a sample of the final mix.
  3. Pack: convert all text to token numbers in one big file. Do this once; doing it per-run wastes hours.
  4. Smoke test: 50 steps at full size. Catches memory blowups before you have burned a night.
  5. Train: the long part. Hours to weeks.
  6. Fine-tune: shorter runs on specific data to add behaviour or personality.
  7. Shrink and ship: compress for the target device.
Why that order, and why it hurts to break it Every step depends on the one above it in a way that cannot be undone later.

Clean before you pack, because packing turns your text into numbers and you can no longer see what is in it. Pack before you train, because doing it per-run costs you the same hour every single time. Lock the tokenizer before you pack, because the packed file is written in its alphabet. And smoke-test before the long run, because the failure you are checking for, out of memory, happens in the first minute or not at all, and finding it in the first minute costs one minute.

Skipping a step never fails immediately. It fails four hours later, and by then you cannot tell which step it was.

The knobs during training

KnobPlain meaningPractical note
Learning rateHow big a correction each step makesThe one most likely to ruin a run. Too high and it diverges; too low and it crawls
Batch sizeHow much text per stepBigger is steadier. Limited by memory
Gradient accumulationFaking a big batch on small hardwareHow consumer cards train serious models
WarmupStarting gently before full speedSkipping it is a classic early-blowup cause
StepsHow many rounds totalWith batch size, decides how many times it sees your data
Learning rate, in one picture You are trying to find the bottom of a valley in thick fog. All you can feel is which way the ground slopes under your feet. The learning rate is how big a step you take before feeling again.

Big steps get you down fast, until you stride straight over the bottom and up the far side, then overcorrect back, and now you are bouncing off the walls getting worse, that is a run that diverges, and you will see the loss number climb and then go to nonsense. Tiny steps never overshoot but you die of old age in the fog.

This is also why the rate shrinks as the run goes on. Big strides while you are high up and the direction is obvious; small careful ones near the bottom where you are trying to settle rather than travel. And it is why you warm up: the very first steps are taken from random noise, where the slope is meaningless, and taking a huge confident stride in a random direction is how runs blow up in the first ten seconds.
Batch size and accumulation, in the same picture Batch size is how many places you feel the ground before deciding which way is downhill. One reading is noisy, that patch might be a molehill. Average thirty and you get the real slope of the valley. So bigger batches make every step more trustworthy, and the price is memory: all that text has to sit on the card at once.

Gradient accumulation is the trick for cards that cannot hold it. Feel eight spots, remember the answer, feel eight more, remember, four times over, then step. You get the quality of a 32-wide batch on hardware that only holds 8. It costs time, not memory, and it is how consumer cards train real models.

Chapter 10Reading the numbers while it runs

A training run prints a line every few seconds. It looks like this, and this one is real:

step 2457/3000  lr 5.27e-05  loss 2.550  140,221 tok/s

What loss values mean

LossReality
10+Random. Just started, or something is broken
4–6Learning letter and word patterns
2.5–4Real grammar appearing
1.5–2.5Coherent for a small model
Under 1.5Strong, or you are reciting your training data. Held-out text will not tell you which. See Chapter 11
Normal, do not panic Loss bounces between steps. One line reading 1.9 and the next 2.5 is fine. Different batches are different difficulties. Watch the trend across hundreds of steps. A flat line for thousands of steps is a real problem; a jumpy line is just training.
Learned the hard way When the line goes flat, check these three before touching anything else. All three have cost us a full day, and none of them announce themselves. The run keeps going, the log keeps scrolling, and the number simply stops moving. And the instrument matters more than any of it. A loss number cannot tell you the difference between a model that is learning slowly and one that has stopped. Give it a plain sentence and read what comes back. Do that at ten percent through, not at the end. Both of the runs above would have been caught in minutes instead of hours.

Chapter 11When it stops learning and starts reciting

Every number on your screen will tell you this run is going well. That is the problem, and it is worth its own chapter because the previous chapter's advice cannot catch it.

What is actually happening in there

Think about how a child learns to read. Show them a picture book fifty times and they will "read" it to you perfectly with the book closed. They have not learned to read. They have learned that book. Hand them a new one and you find out.

A model has exactly two ways to get better at predicting your text, and it does not get to choose which:

Training is a hill-climb: each step nudges the dials whichever way makes the next prediction less wrong. On the first pass through your data, rules win easily, because a rule that helps on this sentence also helps on the next thousand it has not seen yet. There is nothing to memorise, every sentence is new.

The second time it sees the same sentence, that changes. Now storing the literal answer pays off, because that exact sentence really will come around again. Given enough repeats and enough spare dials, storing beats generalising, and the hill-climb takes the cheaper win every time. It is not a bug and it is not the model being lazy. You told it to be less wrong on this text, and the fastest way to be less wrong on text you keep showing it is to remember it.

That is also why the trade depends on your two numbers. A small model on a big corpus cannot memorise it, not enough dials to store that much, so rules are the only affordable option. A big model on a small corpus has room to spare and will memorise almost immediately. Same code, same settings, opposite outcome. This is why you must measure it on your own data rather than copying a number out of a guide, including this one.

Why the loss curve cannot tell you

Loss measures one thing: how surprised the model was by the next word. It does not care why the model was unsurprised. A model that has memorised your corpus is not just good at predicting it, it is close to perfect on it, better than any amount of genuine understanding could get. So memorising does not make the curve look bad. It makes the curve look fantastic. Ours reached 0.2 and I was pleased with it.

The standard answer is to hold text back, split off a slice the model never trains on, measure loss on that, and trust it. It is good advice, it is better than nothing, and it is not enough. Here is a real measurement, one small model fine-tuned on one 4.85M-token corpus at five different repeat counts:

Passes over the dataHeld-out lossWords copied verbatimVerdict
0.53.4032Generating
1.03.1175Generating
1.252.9845Generating
1.52.9305Generating
2.02.75025Reciting

Read the last two rows against each other. Between 1.5 passes and 2.0 passes the held-out loss got better, 2.930 down to 2.750, on text the model had never seen, while the longest verbatim quote went from five words to twenty-five, and the share of copied text went from 7% to 32%. One quarter of one extra pass.

The held-out number did not merely fail to warn. It pointed the wrong way. If you had been watching it, you would have concluded that the reciting model was the better one and shipped it.

Why held-out loss missed it

Because both things were happening at once, and held-out loss only sees one of them.

Your held-out slice comes from the same corpus, so it is the same voice, the same subject, the same format. A model getting better at the general shape of your data will score better on it. That was real, and it is why the number improved.

Meanwhile the model was also stuffing literal passages into its spare capacity. That work is invisible to a held-out sentence, a memorised paragraph does not help predict a sentence the model has never seen, but it does not hurt either. It just sits there. The held-out number does not go up. It has no opinion.

So the two effects do not cancel and they do not fight. One shows on the meter and one does not. You watch a needle that is genuinely moving in the right direction while something the needle cannot see gets worse underneath it.

The one sentence version Held-out loss asks "is this model good at my kind of text?" It never asks "is this model copying my text?" Those are different questions, and only the second one is the one you are worried about.

The classic textbook picture, held-out loss bottoms out and then climbs while training loss keeps falling, is real, but it is what happens well past the point where you should have stopped. By the time the meter turns around you have been reciting for a while. Waiting for the turn means catching it late.

The test that does work

Stop asking a number whether the model is copying. Check whether it is copying.

Take a passage from your training data, the stuff it did see, which is the opposite of what you were told to do, and that is the point. Feed the model the first half. Compare what it produces against the real second half.

A model that learned the rules writes something that fits: right voice, right subject, different words. It cannot reproduce the original because it never stored the original. A model that memorised hands the original straight back, because that is the highest-scoring thing it can say and it has it on file.

This works where loss fails because it is not a measure of quality at all. It is a measure of overlap. You are not asking "is this good", you are asking "is this the same", and a model cannot be accidentally word-for-word identical to a paragraph it never memorised. Thirty lines of code:

prefix, truth = passage[:60], passage[60:120]
guess = model.generate(prefix, max_new_tokens=60)

overlap = shared_words(guess, truth) / len(truth)
longest = longest_common_run(guess, truth)

# Over ~8 samples: overlap above 0.5, or any run of
# 12+ words, means it is reciting.

Two thresholds, and here is where they come from. Overlap is what share of the real continuation's words show up in the model's version, it will never be zero, because "the" and "and" appear in both, and normal English on the same subject shares a lot of vocabulary. Ours sits around 2-7% when the model is behaving. Half the words matching is not coincidence.

Longest run is the killer, and it is the one to trust. Two people writing about the same thing might land on the same four or five words in a row. Twelve in a row does not happen by chance, the odds compound with every word, and past about ten there is no innocent explanation left. Our clean runs top out at 5. The reciting one hit 25, which is most of a paragraph.

Run it on samples from several parts of the corpus, not one, because a model can memorise the distinctive passages and generalise on the boring ones. Run it on a checkpoint partway through, not at the end, the whole point is finding out before you have paid for the full run, and if you are only going to run it once, run it early.

Copy a file, do not open the live one Your trainer is writing that checkpoint file while you are trying to read it. A half-written model loads as garbage and looks exactly like a broken run. Copy it first, test the copy.

How many passes, then

The honest answer is measure it on your own data. You now know why: the trade between learning rules and storing answers depends on how much spare room your model has, which is your parameter count against your corpus size. Nobody else's number is about your two numbers.

Measuring is cheaper than it sounds. Train short runs at 0.5, 1, and 2 passes and test each one. Ours were four minutes apiece, the whole sweep cost less than the single bad run it replaced. If you are about to spend hours on a fine-tune, spend twenty minutes finding the cliff first.

What we can tell you is the shape of the thing:

What memorising looks like from the outside It is not just repeated paragraphs. Stored phrases act like attractors: you ask about something unrelated, the model wanders into a sentence it has memorised, and then it is on rails, reproducing that passage to the end. The giveaway is that several different prompts collapse into the same output. If two unrelated questions get you the same paragraph, stop and run the test above.

Chapter 12Personality: LoRA, fine-tuning, and not paying twice

You have a base model. It writes decent English and has no self. Now you want a character: a support agent, a narrator, someone with a voice. There are three ways to do that and they cost wildly different amounts.

1. Just ask it (prompting)

Put "you are a helpful pirate" at the front of every conversation. Free, instant, changes nothing permanent. It also eats context space, and the character drifts as the conversation gets long, because nothing about the model actually changed.

Fine for trying an idea. Not how you ship a character.

2. Full fine-tuning

Keep training the whole base model on text in your character's voice. Every dial is still free to move, so the voice sinks in properly.

The catch is cost: you need memory for every dial plus the optimiser's bookkeeping for every dial, which is typically several times the model's own size. And you get a whole new model per character. Ten characters, ten full copies.

It is the right call when you have one character and enough memory. Our own runs do exactly this: the base is trained once over hours, then each character is a short run of a few hundred steps starting from a copy of it, about twelve minutes each, because the grammar is already paid for and only the voice has to move.

3. LoRA, the one most people should use

LoRA stands for Low-Rank Adaptation, and the idea is better than the name.

Rather than adjusting the model's millions of dials, you freeze all of them and bolt a small extra set of dials alongside. Only the small set trains. At runtime the model's original output and the little add-on's output are added together, and the result behaves like a model that learned your character.

Why it works: the change from "general English" to "this specific voice" is a much simpler change than the model itself. It does not need millions of free dials to express. A few million will carry it.

Full fine-tuneLoRA
Dials being trainedAll of themTypically well under 1%
Memory neededLarge, often several times the modelA fraction of that
What you end up withA whole new modelA small file, often a few MB
Ten charactersTen full modelsOne model, ten small files
Swapping between themLoad a different modelSwap the add-on, keep the model loaded
Depth of changeAnything, including new knowledgeVoice, style, format: excellent. New facts: weaker

The practical dial in LoRA is its rank: how many extra dials you allow. Small (8–16) is plenty for a voice or an output format. Larger (32–64) if the behaviour is more involved. Bigger is not better here either; too large and it starts memorising your examples instead of learning the pattern.

PEFT implements this and it is a handful of lines to wire in. The original paper is readable. QLoRA combines it with a shrunk base model so you can adapt a large model on modest hardware.

What to feed it

Far less than you would guess. A few hundred to a few thousand examples in the voice you want, and quality beats quantity every time. A hundred sharp examples beat ten thousand mediocre ones.

Write them in the shape the model will actually be used in. If it will answer questions, write questions and answers, not prose about the character. The model learns the form as much as the content, which is why a character trained only on monologue will monologue at you forever, in a perfect voice, about nothing you asked.

The trap that got us A character fine-tune inherits everything in the base, including identity. Our base corpus contained roughly 56,000 mentions of an earlier model's name plus its biography. Every character built on top claimed to be that model when asked sideways. Not to "who are you", which answered correctly, but to "what was your name before".

Keep the base free of identity, and test by trying to break it rather than by asking the easy question. And when you filter the name out of your corpus, substitute it rather than deleting whole documents. See the disaster in Chapter 6, and the checks in Chapter 14 for what a substitution can do to a corpus that is formatted as dialogue.

Which to pick

Chapter 13How much room each number takes

Every one of those billions of dials has to be stored somewhere, and each takes up a certain amount of memory. How much room you give each one decides how big a model fits on the hardware you already own.

Think of it as how many decimal places you keep. A number stored generously might be 0.038471629. Stored leanly it becomes 0.038. Same number, less room, slightly less exact. Cut the storage in half and your model is half the size. Cut it to a quarter and a model that needed four graphics cards now needs one.

The two places precision matters, and they are not the same

People conflate these constantly, and the difference is the whole story.

Shrinking a finished model is easy and everyone does it. Train it generously, then round all the dials down afterward. This is called quantization. It works well, it is well understood, and it is why you can run a capable model on a laptop.

Storage per dialA 600M modelQuality after rounding
Generous (16-bit)~1.2 GBThe reference
Half (8-bit)~600 MBEssentially identical
Quarter (4-bit)~300 MBSlightly worse, usually fine

Training in low precision is a different problem entirely, and it is genuinely hard.

Remember that each training nudge is tiny, deliberately far smaller than the error it is correcting. Now store the dial with very few decimal places. The nudge is smaller than the smallest change that storage can represent. So the dial receives the nudge and does not move. Then it happens again. And again.

The model stops learning entirely. Not slowly, not badly. It sits there while the training loop runs, and the numbers never change. Everyone hits this, which is why nearly all training happens at generous precision and the shrinking is saved for the end.

What that looks like when it happens to you Bank balance. Yours is £8,431.22, and the bank has decided to stop storing pennies. Someone pays you 40p. The bank adds it, rounds to the nearest pound, and writes back £8,431. Nothing errored. The money is just gone. Pay you 40p a thousand more times and you have exactly the same balance.

It bit us on 2026-08-15 and it is the reason this chapter exists. Twenty-four of a model's twenty-five layers were receiving nudges too small to register, and taking exactly zero of them from the first step onward. Only the vocabulary table at the bottom was learning anything.

Here is the cruel part: it did not look broken. The loss fell from 10 to 6.9 and then sat there, which reads as "a small model that has learned as much as it can", because a vocabulary table on its own really does learn which words are common, and then genuinely has nothing left to learn. Four different explanations were chased over two days, every one of them reasoned from the loss curve, before anyone printed the actual nudge sizes per layer. Twenty lines of code, one run, and the answer was a column of zeroes.

If a run flatlines, print the numbers inside it before theorising about it. The loss line is a summary of a summary, and a summary cannot tell you that most of your model is not participating.
The thing worth understanding If a model can be trained at quarter size rather than merely stored that way, the memory the run needs drops by the same factor. It is not about the file being smaller. It is about how much model fits on the card while it is learning, and that number sets the ceiling on quality you can reach.

It has been done, and the point is quality

This is not hypothetical. The remaining hard part, getting the whole model into the lean format without training falling apart, has been solved and run to completion on an ordinary consumer graphics card. The published techniques did not transfer; what was actually killing the training turned out to be something not previously written down, and fixing that was the work.

The result that matters is not the smaller file, and it is not about avoiding expensive hardware. It is this: on the same card, you can now train a bigger model, and the bigger model trained in the lean format beat the best smaller model trained the generous way.

So it never was a tradeoff where you accept a worse model to save money. Same hardware, better model. From here the constraint is not what you can afford to run on. It is your corpus, your mix, and your patience, which is why the data chapters in this manual are the long ones and this one is short.

What this chapter does not contain How. The specific method is proprietary and is not described here, and no amount of reading between these lines will reconstruct it. What is public and worth knowing is the shape of the problem: small nudges vanish into coarse storage, and that is what stops low-precision training from working. Anyone attempting it will meet that wall.

Chapter 14Checking your corpus before you spend the hours

A training run turns text into a model and cannot tell you whether the text was right. It will faithfully learn whatever is in there, and you find out hours later by talking to the result. This chapter is the set of checks worth running first, because every one of them costs minutes and every one of them has caught something real.

Check the packed file, not the script that made it

Your corpus goes through a pipeline: read files, clean them, filter them, tokenize, write a binary. Every stage is a place something can go quietly wrong, and the log line at the end reports only that the stage ran.

A packer printing 39,596 names replaced tells you the substitution executed. It does not tell you the result reads correctly. Decode the finished binary back into text and search that. It is a few lines of code and it is the only check with nothing between it and the model.

Worth building as a standing script rather than a one-off, because you will run it before every pack. It should assert the things you believe are true: no foreign names, no markup, the character's own name present at a healthy rate, and exit non-zero when one fails.

A substitution is not a rename

Filters replace one string with another, and the replacement lands in a context the filter cannot see.

Replacing a name with a generic word inside a dialogue corpus formatted as Name: what they said does not remove the speaker tag. It renames it. Thousands of instances of Assistant: at the start of a line teach the model that this string is what precedes speech, and it will emit it. The name is gone and the damage is new.

Before running a filter across a corpus, run it across a sample and print each changed line before and after. Read them. It takes a minute and it is the only way to see the context the pattern is landing in.

Watch out for filters that fight your own data

If you write a file teaching a character to reject a name (are you [other name] / No. I'm [character].) and you also run a filter that scrubs that other name from the corpus, the two cancel. The rejection line becomes are you the assistant and you have taught the exact opposite lesson.

This is invisible in both files. Each is correct alone. Only the diff shows it.

Count what your character says about itself

Identity failures come in two flavours and they look identical from outside.

Contamination is a foreign name present in the corpus. A filter fixes it.

Dilution is your character's own name being too rare to learn. Nothing filters that. In one of our persona corpora the character's name appeared 64 times across 21.7 million characters, 0.0003%, because the general-conversation padding vastly outweighed the hand-written voice. The model learned a personality and never learned what it was called.

So count it. A persona corpus should mention its own character in the low thousands, not the dozens. If four of your nine voice files never use the name at all, that is the number to fix before you train.

Your padding is a voice too

Fine-tuning one character on a narrow set of examples will drift, so most people add a slice of general text to hold the grammar steady. Reasonable. But whatever that slice is written in becomes the model's fallback register, what it produces when a prompt gives it nothing specific to grab.

Public-domain dialogue is the tempting choice because it is free and it looks like conversation. It is also a century old. A modern character padded with it will answer ordinary questions in period idiom, complete with characters who are not in your corpus at all. Identity checks pass cleanly; the register is what is wrong, and no name filter can see it.

Pad with text from the era you want the model to sound like. Quality-filtered modern web prose has no characters and no speaker tags, so drifting into it costs you tone rather than replacing it.

Test the form, not just the content

A model trained on loose lines learns a voice. A model trained on question-then-answer pairs learns to answer. These are different skills and the first does not imply the second.

The symptom is distinctive: you ask something ordinary and get a reply in exactly the right voice that has nothing to do with what you asked. The character is intact. The comprehension was never trained. Fix it in the data, write pairs where the answer is specifically about the question, rather than reaching for sampling settings, which cannot manufacture a relationship that was never in the text.

Read the file you were handed

Corpora arrive from other people and from your own past self. Open them and look before they go in.

A persona file we received was 7.66 MB, and the character's real dialogue in the same folder was 35 KB. The big file was a configuration document written about the character in the third person. At 200:1 the model dutifully learned to recite its own spec sheet, grammatical, confident, and completely wrong. A quick ls would have shown the ratio; opening the file would have shown everything.

The five-minute pre-flight Before a run: list the corpus directory and look at the file sizes for anything wildly out of proportion. Decode the packed binary and search it for foreign names, markup and speaker tags. Count your character's own name. Read fifty lines of the padding and ask what decade it is from. Then start the run.

All of it together is minutes against hours of training, and it is checkable, which is the point. Confidence about a corpus is not a measurement of the corpus.

Chapter 15What goes wrong

SymptomUsual cause
Loss goes to NaNLearning rate too high, or no warmup
Loss flat from the startLearning rate near zero, or data not actually loading
Loss falls a little, then parks foreverToo few updates for the data (Chapter 4), or most layers are getting nudges too small to register (Chapter 13). Print the per-layer nudge sizes before theorising
Output is word saladUndertrained, or too little data for the size
Sounds antiqueCorpus is all old public-domain text
Right voice, wrong answerTrained on loose lines, not question-and-answer pairs
Slips into period idiom on open questionsThe general-text padding is old. It is the fallback register
Rambles, never answersNo conversational data in the mix
Repeats one phrase foreverNeeds a repetition penalty at generation time
Claims to be a different modelIdentity leaked in from the base corpus
Never says its own nameDilution, not leakage. Count the mentions in the packed file
Emits a speaker tag like "Assistant:"A name filter renamed the tags instead of removing them
Recites a description of itselfA config or character-card file outweighs the real dialogue
Boxes or "?" instead of apostrophesStreaming output split a multi-byte character across tokens
Out of memory immediatelyBatch or context too large. Cut batch, raise accumulation
Quotes your training data back at youMemorising. Fewer passes, and note it can score BETTER on new text while doing this, so the loss numbers will not warn you (Chapter 11)
Different questions get the same answerAlso memorising. Stored passages act as attractors that unrelated prompts fall into
The habit that saves the most time Measure before you change anything. The temptation is to guess a cause and patch it, and each guess is cheap for you and expensive in wall-clock. Prove the cause moves the symptom before committing to a fix, we lost a night to a "speedup" that turned out to touch code that ran barely a tenth of the time.

If you are starting today

  1. Build the small config, not the large one. Get all the way through the pipeline once.
  2. Gather 1 billion tokens in roughly the Chapter 5 mix.
  3. Train until loss is around 2.0, then look at what it says.
  4. Fix the data, not the architecture. That is where the improvement is.
  5. Only then go bigger.