Go underneath · Intermediate
Running Models Yourself
Draft, open for academic review
Local and self-hosted LLMs, with the arithmetic instead of the marketing.
Your progress here is recorded once you pass the Open Model Ecosystem exam.

9 modules. 82 lessons.
Deciding to run it yourself
9 lessons · 1 h 17
You will be able toDecide whether a stated task belongs on your own hardware: by auditioning the exact open weights through a hosted copy, naming the constraint that forces local rather than assuming one, reading a licence well enough to say what you may ship, and stating the capability ceiling the weights impose regardless of the machine.
- 1.1Why run a model yourself, and when not to
- 1.2What “open” means, and what it does not
- 1.3Reading a model card without being sold to
- 1.4What each size class can actually do
- 1.5Mixture of experts, and why it changes the sum
- 1.6Base, instruct and reasoning: three different products
- 1.7The audition: settling it for about a dollar
- 1.8The ceiling the weights impose
- 1.9Local is not automatically private
An illustrative case, built from documented patternsThirty discharge summaries and a privacy problem
The registrar had read that an 8B model at 4-bit runs on a card costing about half a month’s salary, and that it summarises well. Both claims are true. He wrote a note asking for the money.
The hospital’s medical superintendent asked one question before signing: how do we know it is good enough at this. Nobody could answer, because nobody had tried it. The obvious way to try it is the audition: take thirty real items, run them through a hosted copy of the exact open weights, and grade the outputs. It costs under a dollar and it answers the model question without answering the hardware question.
Except that the reason for the project was that ward notes cannot leave the building. Running thirty real discharge notes through a hosted endpoint, even for an hour, even for a test, is exactly the thing the whole deployment exists to prevent. The registrar had proposed to establish privacy by breaching it.
The two ways out, and what each costs
Write synthetic notes. Invent thirty plausible ward notes and audition on those. Free, immediate, and nobody’s data moves. The problem is the one the course states plainly: invented examples are always easier than real ones in a specific way. They are shorter, cleaner, better punctuated, and free of the internal shorthand that actually breaks models. Ward notes at this hospital contain abbreviations used only in that ward, drug names written three ways, Marathi words in Devanagari inside English sentences, and handwriting transcribed by whoever was on duty. A synthetic set would have contained none of that, and the audition would have returned a clear pass on a task the model had not been asked to do.
De-identify thirty real notes by hand. Two people, a morning each, removing names, registration numbers, addresses and dates, and checking each other’s work. Real structure, real abbreviations, real mess, and nothing identifiable leaves. The cost is a day of two clinicians’ time before a single line of the project exists, spent on something that produces no deliverable, which is precisely the kind of work that gets cut when a project is trying to prove itself quickly.
There was a third option nobody liked: buy the card first and audition locally. It removes the privacy question entirely. It also spends the money before learning whether the weights can do the job, which is the sequence this course exists to prevent.
What the set was for
The registrar argued for synthetic notes on the grounds that the model would be corrected by a doctor anyway. The superintendent’s objection was not about the risk of a bad summary. It was that an audition on easy items cannot distinguish between two models, which means it cannot tell you which weights to shop for, which means the purchase is still a guess with a document attached.
They also had to decide what the thirty items were for afterwards. The course is insistent that the set is the most valuable artefact the project owns: it is what tells you later whether a quantised copy still works, whether a new release is better, and whether a prompt change helped. A de-identified set can be kept, versioned and re-run for years. A set of real notes cannot be kept at all.
That argument turned out to matter more than the audition itself.
How it played out
They de-identified thirty notes by hand over one morning, with a second clinician checking. Two candidate models were run through a hosted endpoint at temperature 0, the outputs were shuffled and graded blind on a three-point scale, and the whole exercise cost about seventy rupees of tokens. Nineteen of thirty were usable as drafts; eight needed a small edit; three were wrong in ways that mattered, all of them on notes containing ward-specific abbreviations. That is the awkward middle, and it moved to a clear pass after they added a two-line glossary of those abbreviations to the prompt, a change that cost twenty minutes and would never have been discovered on synthetic notes, because synthetic notes do not contain abbreviations nobody outside the ward uses. The card was bought six weeks later, for a model they had already tested. The de-identified set is now run against every engine upgrade, and it caught a regression the following March.
Worth arguing about
The registrar’s plan would have sent real ward notes to a hosted provider for one hour. Why is that not a small, temporary exception?
One answer
Because the exception is the whole project. The deployment exists because the notes may not leave the building, and a test that breaks that rule has not tested the system, it has performed the thing the system was built to avoid. There is also no such thing as a temporary transfer: once the notes have been sent, they have been processed by a third party, and the hospital cannot demonstrate otherwise. The de-identification cost a morning and left the hospital able to say truthfully that no identifiable note has ever left.
Synthetic notes were free and immediate. What specifically would the audition have failed to measure?
One answer
The failures. Invented examples are shorter, cleaner and better punctuated than real ones, and they omit exactly the internal shorthand that breaks models. The three items that failed all contained ward-specific abbreviations, which is the class of problem a synthetic set cannot contain, because the person writing it does not think to include what they already understand. A set with no failures in it cannot distinguish between two candidate models, which is what the audition is for.
The audition came back at nineteen usable, eight near-usable and three wrong. What should happen next, and what should not?
One answer
That is the awkward middle, and the course gives an order: fix the prompt, then try the next size up in the same family, then a task-specialised model, then a newer model in the same class. Each takes about twenty minutes. What should not happen is a decision about hardware. A bigger card runs the same weights faster and does not make them better, so buying one at this point spends money against a result that has not settled. Here the prompt fix alone moved it to a clear pass.
Test yourself6 questions on this module
An audition of thirty real items against a hosted copy of the exact open weights returns eleven items unusable. What does buying a faster graphics card change about that result?
You download SomeLab/CoolModel-8B. The uploader’s README says MIT. The model card’s first line says it was fine-tuned from a model released under Meta’s community licence. Which terms govern what you may ship?
You are on a slow connection and want to know in three minutes whether a model will fit and behave. Which two files tell you most?
A machine has 64 GB of system RAM at about 30 GB/s and no useful GPU. Why does a 30B-A3B model generate several times faster on it than a dense 30B at the same quantisation?
You ask a freshly downloaded model for the capital of France and it replies with three more exam questions. What is the most likely explanation?
A team runs weights locally and wants evidence that nothing leaves the machine. Which check is the strongest?
The memory arithmetic
9 lessons · 1 h 20
You will be able toCompute, for any model and any context length, whether it fits on a stated machine and roughly how fast it will run: naming which of weights, KV cache, bandwidth or interconnect is the binding constraint, and choosing the remedy that costs the least quality when it does not fit.
- 2.1Does it fit: the arithmetic
- 2.2What actually runs on 8GB
- 2.3The KV cache, in depth
- 2.4Prefill and decode are two different machines
- 2.5Where the model physically lives
- 2.6CPU inference, honestly
- 2.7More than one GPU
- 2.8Measuring what you actually have
- 2.9When it does not fit: the ordered remedies
An illustrative case, built from documented patternsA 14B that nearly fitted
The centre’s systems person had done the arithmetic correctly, which is why the problem was interesting. A 14B at Q4_K_M is about 8.4 GB of weights. The model’s config file gives 48 layers, 8 KV heads and a head dimension of 128, so the cache costs two bytes for K and V, times 48, times 8, times 128, times two more for fp16: about 192 KiB per token. At the 16,384-token window he wanted, that is 3.0 GB. Add a gigabyte of overhead and the total is 12.4 GB on a 12 GB card.
It did not fit, by four hundred megabytes.
What happened instead is the part worth studying. llama.cpp did not refuse. It placed forty-four layers on the card and four on the CPU, and the assistant worked. In testing, with one person asking questions, it produced about eleven tokens per second where the same model fully resident produced twenty-six. Four layers out of forty-eight (eight per cent of the model) had cost well over half the speed, because every single token has to pass through all forty-eight of them and waits on the slow four.
Three options, priced
Drop to an 8B. Free, immediate, and about twice as fast because half as many bytes are read per token. The cost is real and it is exactly where this centre could least afford it: the questions students ask at half past ten at night are multi-step mechanics problems, and multi-step reasoning is the most size-sensitive thing a model does. The 8B answered them fluently and got the third step wrong often enough that a teacher would have to check every answer, which removes the point.
Keep the 14B and cut the window to 8k. The cache halves to 1.5 GB, the total comes to 10.9 GB, everything fits, and the speed returns to twenty-six tokens per second. The cost is that the 16k window existed for a reason: the assistant was pasting whole chapters of the textbook into the prompt, because nobody had built a retriever. Cutting the window means building one, which the systems person estimated at a fortnight he did not have before the session started.
Keep the 14B and the window, and quantise the KV cache to 8 bits. This halves the cache term to 1.5 GB for a quality difference most tasks cannot detect, and it requires flash attention to be switched on or the flags are silently ignored. Total 10.9 GB, fully resident, twenty-six tokens per second, window intact. Roughly ten minutes of work.
The third option looks obviously correct written down like that, and it was not obvious in the room, because the argument in the room was about which model to use. Both of the first two options are answers to the question which model. The arithmetic says the oversized term was not the weights at all: the cache was a quarter of the budget and the weights two thirds, so halving the cache saved an eighth of the total and that was enough.
The part that came back later
There was a fourth consideration nobody raised on the night. The centre’s plan was eventually to let several students query at once, and each concurrent sequence needs its own cache. A configuration that fits exactly one conversation at 16k fits precisely one student, and the second arrival would have pushed it back into offload: the failure that arrives not at startup but when the service gets popular.
How it played out
They quantised the KV cache to 8 bits, confirmed flash attention was actually on by reading the startup log rather than assuming, and measured again: 10.9 GB resident, twenty-six tokens per second, the 16k window intact. The retriever was built four months later for a different reason: pasting whole chapters was diluting the model’s attention and the answers on long questions were visibly worse than on short ones, which is the long-context failure the course describes and not a memory problem at all. When they did add concurrency, they capped the window at 8k with a retriever in front and ran four slots, which is the configuration still in use. The systems person now keeps a five-line file recording machine, model, quant, context, prefill rate, decode rate and memory used, and says the most useful thing in it is the line from the night everything was slow.
Worth arguing about
Eight per cent of the layers were on the CPU and the speed fell by more than half. Why is the loss so much larger than the fraction offloaded?
One answer
Because the parts add rather than average. Every token must pass through every layer, so the time per token is the GPU’s share plus the CPU’s share. With forty-four layers at a fast rate and four at a rate roughly twelve times slower, the four contribute more time than the forty-four do. The result behaves like a harmonic mean, which is why nearly fitting is such an unhappy place to be, and why getting the last few layers onto the card is worth more than it looks.
Why was quantising the cache the right first move rather than dropping a quantisation tier on the weights?
One answer
Because you fix the term that is oversized, and here the shortfall was four hundred megabytes against a 3.0 GB cache. Halving the cache frees 1.5 GB, which is more than enough, at a quality cost most tasks cannot detect at 8 bits. Dropping the weights from Q4_K_M to Q3_K_M would have saved roughly a gigabyte and cost real quality on exactly the precision-critical multi-step work the assistant exists for. Compute all three terms before changing anything: the proportions decide the order of the remedies.
The configuration fitted one conversation at 16k. What breaks when a second student connects, and what are the two fixes?
One answer
Each concurrent sequence needs its own KV cache, so a second student needs another 1.5 GB that is not there. The engine either refuses, queues, or spills layers back to the CPU, and the symptom is a service that was fine in testing and fails the day it becomes useful. The two honest fixes are to cap concurrency and size the window for the number of slots you intend to serve, or to use an engine with paged cache blocks so a request occupies only the blocks it has actually filled rather than a worst-case reservation.
Test yourself6 questions on this module
A model has 32 layers, 8 KV heads and a head dimension of 128, with an fp16 cache. How many bytes does each token of context cost?
A 4.9 GB model generates 38 tokens a second on a card rated at 360 GB/s of memory bandwidth. What does that tell you?
Using a laptop’s integrated GPU roughly triples prompt processing and leaves generation speed unchanged. Why?
A 32-layer model runs at 60 tokens a second entirely on a card and at 5 on the processor. Four layers are left on the processor. Roughly what speed results?
A model loads instantly and then produces a token every few seconds, with no error anywhere. What is the most likely cause?
A configuration is 1.2 GB over budget. Weights are 8.4 GB, the KV cache is 3.0 GB and overhead is 1.0 GB. Which remedy costs the least quality?
Quantisation and model formats
9 lessons · 1 h 18
You will be able toChoose a quantisation format and bit rate for a stated model, machine and task; produce a quantised model yourself from the original weights; and measure the resulting damage with a method that can actually detect it, rather than relying on a perplexity delta that cannot.
- 3.1Quantisation, properly
- 3.2Inside a GGUF file
- 3.3K-quants and importance matrices
- 3.4GPTQ, AWQ and the GPU-side formats
- 3.5FP8 and models born quantised
- 3.6Quantising a model yourself
- 3.7Measuring the damage, properly
- 3.8Bigger and crushed, or smaller and clean
- 3.9The other ways to make a model smaller
An illustrative case, built from documented patternsThe quant that was fine in English
The desk used the model for two jobs. It cleaned up English wire copy before translation, and it proof-read Marathi copy filed by district correspondents. The first job was reported as excellent. The second was reported as fine, then as slightly odd, then as something two sub-editors had quietly stopped using because it was faster to do it themselves.
Nobody had measured anything. The model had been chosen on an English benchmark table, the quant had been downloaded because it was the one the community had published, and the evaluation had been a week of people trying it.
The chief sub-editor did the one thing that settles this class of argument: she built a set. Thirty pieces of copy, twenty in Marathi and ten in English, each with the errors marked by hand in advance. She ran them at temperature 0 against the 4-bit file they were using and against a hosted copy of the same model at full precision.
The English results were identical to within one item. The Marathi results were not close. The 4-bit file missed nine errors the full-precision model caught, and four of the nine were in Devanagari conjunct spellings: precisely the rare, precision-critical tokens the course warns about.
Why, and what it costs to fix
The mechanism is the importance matrix. A low-bit quant is produced with a matrix computed by running the full-precision model over a body of calibration text and recording which weights are multiplied by large activations. Those weights get protected; the rest are rounded harder. Whatever the calibration text did not exercise was fair game, and the quantiser did exactly what it was told.
The community quant they were using had been calibrated on a general English corpus with some code in it. The weights that matter for Marathi were never exercised, so they were not protected, and the damage landed there first. In English nothing had happened, which is why three months passed without a complaint from anyone reading English.
Two remedies, and both cost something.
Buy a second-hand 16 GB card and run Q6_K. At six bits the damage largely disappears and no calibration question arises. The desk had no budget line for hardware, and the purchase would have to be argued through a finance process that treats capital differently from a monthly subscription. That argument was estimated at two months and an uncertain outcome.
Produce their own importance matrix, calibrated on Marathi. Free. The tooling is llama-imatrix followed by llama-quantize, and the desk had three years of its own published copy to calibrate on. The cost is that a quant calibrated on Marathi is better at Marathi and worse at everything else, and the same desk also handles English wire copy. They would be trading one of their two jobs for the other.
There was a third option that nobody had considered because it does not involve quantisation at all: find a base model whose training covered Marathi properly, and check whether a smaller one of those beats a larger general model at four bits. Language coverage is a property of the weights, and no quantisation choice repairs a model that was thin in a language to begin with.
How it played out
They did the third thing first, because it was an afternoon. Two models in the same size class with explicit Indic-language training were auditioned against the same thirty items, and one of them at Q4_K_M caught more Marathi errors than the original model did at full precision. That ended the quantisation argument by moving it. They kept the original model for the English wire copy, since it was already resident and already good at that, and ran the Indic model for Marathi: two files on the same card, added together first to check they fitted, which they did with room to spare. The custom importance matrix was built anyway, six weeks later, out of curiosity: calibrated on the desk’s own Marathi archive it improved the original model’s Marathi score by four items and cost three items of English, which is exactly the trade the course predicts and exactly the reason they did not deploy it. The thirty-item set is now run against every new model anybody proposes, and the whole review takes twenty minutes.
Worth arguing about
The quant was indistinguishable from full precision in English and clearly worse in Marathi. What mechanism produces that pattern?
One answer
Quantisation damage lands first on the rarest parts of the weight distribution, and a low-resource language occupies exactly those. The importance matrix makes it sharper: it is computed over calibration text, and weights that text never exercised are not protected. A quant calibrated on English protects English and leaves everything else to be rounded hard. Nothing about this is visible in an English test or in a single perplexity number, which is why three months passed without a complaint.
Why did finding a different base model settle the argument rather than choosing a better quantisation of the one they had?
One answer
Because language coverage lives in the weights and quantisation cannot add it back. A model trained with Marathi properly represented starts from a higher ceiling, and the course’s own rule applies: a newer or task-specialised model in the same size class usually beats going up a class, and it certainly beats spending money to run the wrong model at a higher bit rate. The check was an afternoon with a set they already had, which made it the cheapest thing to try.
The custom Marathi importance matrix gained four items and cost three. Is that a useful result, and what does it tell you about calibration text generally?
One answer
It is a useful result and a genuine finding: calibration text silently decides which languages and tasks a low-bit quant preserves, and calibrating on one narrow domain buys that domain at the expense of the rest. For a desk running one language it would be a real and underused optimisation. For a desk running two, it is a trade rather than an improvement. The other rule that follows is never to calibrate on the evaluation set, or the measurement becomes a model tuned to the text you are testing on.
Test yourself6 questions on this module
Why does a Q4_K_M file work out at about 4.8 bits per weight rather than 4.0?
Two IQ3 quants of the same model differ sharply on Bengali and not at all on English. What most likely explains it?
AWQ observes that about one per cent of weight channels see consistently large activations. What does it do about them?
Why does an 8-bit float survive quantisation better than an 8-bit integer for weights and activations?
You have a published Q4_K_M file and want a Q5_K_M of the same model. What should you convert from?
A quant moves perplexity from 6.21 to 6.24. What can you conclude about it?
Getting it running
10 lessons · 1 h 26
You will be able toTake a model from a repository to a working local endpoint on your own hardware (correct build, correct template, correct context configuration) call it from code through the OpenAI-compatible API with structured output that validates, and add embedding, transcription or vision models alongside it.
- 4.1Ollama and llama.cpp
- 4.2Interfaces: what each one is for
- 4.3Installing the right build for your hardware
- 4.4Downloading models without wasting a day
- 4.5Chat templates: the most common silent failure
- 4.6Context settings that truncate in silence
- 4.7The API every engine speaks
- 4.8Output that parses every time
- 4.9Embeddings and rerankers on your own machine
- 4.10Speech, vision and the rest of the local stack
An illustrative case, built from documented patternsTwelve laptops and two tokens a second
The teacher had installed Ollama on one laptop over a weekend and pulled a 4B model. It worked. It answered questions about a comprehension passage, it was private, it needed no internet during the lesson, and it was slow in a way she assumed was simply what a laptop does.
Two tokens a second is not what that laptop does. It is what that laptop does when the model is on the CPU and nothing else.
The laptops had Intel integrated graphics. The installer had picked a build with no Vulkan backend compiled in, so every layer ran on the processor. Nothing reported an error, because nothing had gone wrong from the software’s point of view: a CPU fallback is a supported path, and it is the one everything silently lands on.
There was a second problem underneath it. The comprehension passages were about nine hundred words, and the follow-up discussion added the class’s questions on top. Ollama allocates a modest context window by default regardless of what the model supports, and the older build on those machines allocated 2,048 tokens. By the fourth question the beginning of the passage had been dropped, with no error and no warning, and the model was answering about a text it could no longer see. The teacher had recorded this in her notes as the model getting confused, which is how it looks.
The decision
A volunteer from a nearby engineering college diagnosed both in about forty minutes, and then the decision was not a technical one.
Fix it properly. Install a Vulkan build on all twelve machines and raise the context window. The Vulkan backend reaches Intel and AMD integrated graphics, and on this class of machine it triples prompt processing while doing nothing at all for generation, because an integrated GPU shares the same system memory and therefore the same bandwidth ceiling. Prompt processing was the problem: a nine-hundred-word passage was forty seconds of silence before the first word appeared. The cost was that the laptops were locked down. Installing anything required an administrator account held by the district IT office, and the process for that ran in weeks.
Use a smaller model. A 1.7B at Q4 reads about 1.1 GB per token instead of 2.5, so it roughly doubles the generation rate on a bandwidth-limited machine, and it needs no new build and no administrator. The cost is what a 1.7B is: reliable at short, well-specified jobs and unable to hold more than about three instructions. The comprehension exercises the teacher wanted involved asking the model to compare two paragraphs and justify an answer, which is two steps past what that size class does.
The volunteer pointed out a third thing, which was free and which neither option covered. Whatever they did about the build, a nine-hundred-word passage sent on every single question was being re-processed every single time. Sending the passage once and keeping the conversation on the same slot would remove most of the wait without changing a single component.
How it played out
The district IT office took five weeks to grant the installation, which the volunteer had predicted, so they did the free things first. Raising the context window to 8,192 took one line in an environment variable and ended the amnesia immediately, and the teacher’s note about the model getting confused turned out to describe a setting rather than a model. Keeping each class’s passage in one conversation rather than re-pasting it cut the wait for the second and subsequent questions from forty seconds to about four. When the Vulkan builds finally landed, the first question of a lesson fell from forty seconds to about thirteen, and generation did not change at all, which is exactly what the arithmetic predicts: an integrated GPU has far more parallel compute than the cores and the same memory bandwidth. The 4B model stayed. The school’s written record now begins with one line: before changing anything, check that the accelerator is actually being used, because a model silently on the CPU is a thirtyfold problem and no other setting matters until it is fixed.
Worth arguing about
The Vulkan build tripled prompt processing and left generation unchanged. Why does one improve and not the other?
One answer
They are two different workloads. Prefill processes all the prompt tokens at once, with a great deal of arithmetic per byte of weights read, so it is compute-bound and more parallel hardware helps. Decode produces one token at a time and reads every weight to do it, so it is limited by memory bandwidth. An integrated GPU shares the same system memory as the processor, so it cannot raise the bandwidth ceiling at all, but it has far more parallel arithmetic than the cores. Same bandwidth, more compute: prefill improves, decode does not.
Two settings were changed before any software was installed, and both mattered. What were they and why were they free?
One answer
The context window was raised from the default 2,048 tokens, which ended the silent truncation that was dropping the beginning of every passage, and the class’s passage was kept in one conversation instead of being re-sent with every question, so its prefill was computed once rather than each time. Both are configuration rather than installation, so neither needed the administrator account the school did not have. The first cost a little memory; the second cost nothing at all.
The 1.7B model would have doubled the generation rate immediately. Why was that not the right answer here?
One answer
Because the task was two steps past that size class. A 1.7B is reliable at short, well-specified jobs and loses the thread after about three instructions, and the exercises required comparing two paragraphs and justifying an answer. Speed was not the complaint that mattered: the forty-second wait was prefill, which a smaller model barely helps, while the quality loss would have landed exactly on the thing the lesson was for. Matching the size class to the job is the decision, and the speed problem had a different cause entirely.
Test yourself6 questions on this module
Which of these is a genuine reason to move from Ollama to llama.cpp directly?
A model produces sensible text and runs about thirty times slower than expected. What should you check before any configuration?
You are on a metered connection and considering a 4.9 GB quant. What is worth doing first?
A model answers a question well, then writes a new user question and answers that one too. What is the fix?
llama-server is started with --parallel 4 and -c 32768. How much context does each of four concurrent users get?
A schema forces a model to return one of four category labels. What has that guaranteed?
Serving engines and performance
9 lessons · 1 h 21
You will be able toConfigure and tune a serving engine for a stated load (batching, cache blocks, prefix reuse, speculative drafting) measure it with an open-loop load test reporting percentiles rather than averages, and say which single change would raise the number that is actually limiting you.
- 5.1Serving engines, and what “faster” means
- 5.2Continuous batching, and the queue underneath it
- 5.3Prefix caching, and how to design prompts for it
- 5.4Speculative decoding: several tokens per weight read
- 5.5The kernels under everything: flash and paged attention
- 5.6A load test whose numbers mean something
- 5.7The tuning checklist, in order
- 5.8One machine, several models
- 5.9Queues, timeouts and refusing work gracefully
An illustrative case, built from documented patternsThree hundred students at seven o’clock
The assistant had been running for a term and had been fine, because for a term it had been used by four teaching assistants during the day. Then it was announced to the students, and the first evening lab began at seven o’clock with about three hundred people opening the page within ninety seconds of each other.
What the students experienced was not an error. The page accepted their question, showed a spinner, and eventually either answered or timed out in the browser. Several of them pressed the button again. By quarter past seven the queue held more than two thousand requests and the card was generating answers for people who had closed the tab twenty minutes earlier.
This is the standard failure shape and it arrives quickly. An unbounded queue converts a throughput problem into a total outage: requests time out at the client, the client retries, the retries join the same queue, and the server spends its whole capacity producing output nobody is waiting for.
What the measurement said
The following week they ran an open-loop load test: a fixed arrival rate rather than a fixed number of workers, because a closed-loop test slows down automatically when the server does and therefore can never show a queue building. Prompt and output lengths were drawn from the previous week’s logs rather than invented.
The curve was the usual one. The 95th-percentile time to first token stayed flat, then bent, then went vertical. The rate just before the bend was about eleven requests a second, and the lab produced roughly forty in the first minute and then a long tail.
Two tuning changes were available and both helped. Chunked prefill stopped one student pasting a four-hundred-line traceback from freezing everyone else’s stream mid-sentence, which had been reported as the server being unreliable rather than slow. Prefix caching mattered more: every request carried the same eight-hundred-token preamble of lab rules and style guidance, and with the cache hitting, that work was done once instead of forty times a minute. The preamble had to be moved above the student’s name, which had been at the top and had been invalidating the cache on every single request.
Neither change raised the ceiling by an order of magnitude. Tuning buys a factor, not a factor of ten.
The decision
So the real question was what happens past capacity, and it was not a technical question.
Queue everything. Nobody is refused. Everybody waits, some of them for minutes, retries pile on, and the service degrades for all three hundred at once. It has the appearance of fairness and it is the configuration that produced the first evening.
Bound the queue and refuse fast. Past a set depth, return a refusal immediately with a wait time. Some students are told to come back in a minute, which is a visible, attributable failure that a first-year student will describe as the college’s assistant not working. The rest get answers at a predictable speed.
The head of department did not like the second option, for an honest reason: a refusal is something a student notices and complains about, and a slow answer is something they blame on the wifi.
How it played out
They bounded the queue, set a server-side timeout shorter than the browser’s, enabled cancellation on client disconnect, and capped the maximum output length in the gateway rather than trusting it from the client: that last one alone removed most of the long tail, because a handful of requests with no limit had been holding slots for minutes. Then they did the thing that changed the complaints: they published the number. A line at the top of the page said the assistant serves about eleven questions a second and that at the start of a lab there may be a short wait, with a live queue depth beside it. Refusals became a queue position people could see rather than a failure. The following term the department added a second card, not split across one model but running a second independent copy behind the same gateway, which doubled throughput with no interconnect traffic and no synchronisation, and was the arrangement nobody had tried first.
Worth arguing about
Why would a closed-loop load test have failed to predict the seven o’clock failure?
One answer
Because a closed-loop test keeps a fixed number of workers busy, each sending a new request only when its previous one returns, so the offered load automatically slows when the server does. Concurrency is fixed by construction and a queue can never build. It measures capacity at a chosen concurrency, which is useful, but it cannot show the point where arrival rate exceeds service rate. An open-loop test sends at a fixed rate regardless, which is how three hundred students actually behave, and it is the only way to see the bend where p95 time to first token goes vertical.
The student’s name sat at the top of the preamble. Why did that one detail cost most of the prefix cache?
One answer
Prefix caching stores the KV cache in blocks, each hashed together with every block before it, so a reuse requires an exact match from the very first token. A name, a timestamp or a session id at the top of the preamble makes every request diverge at block one, and nothing after it can be reused. Moving the fixed content first and the variable content last is the whole technique, and the failure is invisible unless the hit rate is measured, because a cache that never hits produces correct answers slowly.
Refusing a request fast looks worse to a student than a long wait. Why is it the better design?
One answer
Because an unbounded queue turns a capacity problem into an outage for everyone. A fast refusal lets the client back off or fall back and lets the server spend its capacity on requests somebody is still waiting for; a long wait ending in a browser timeout gives the student nothing and gives the server a retry that makes the queue worse. The appearance problem was solved separately, by publishing the capacity and showing the queue position, which turned an unexplained failure into a number people could plan around.
Test yourself6 questions on this module
Seven requests in a batch want 40 tokens and one wants 900. What does continuous batching change?
A gateway inserts the caller’s name at the very top of an otherwise fixed 1,500-token preamble. What happens to the prefix cache?
A draft model proposes five tokens and the target accepts about half of them. Is speculation worth enabling?
A 4-bit model is no faster than the same model at sixteen bits. What is the usual explanation?
Why can a closed-loop load test not find the arrival rate at which a service breaks?
Arrival rate has exceeded service rate and the queue is unbounded. What happens next?
Getting good output
9 lessons · 1 h 18
You will be able toDiagnose a disappointing local output to its actual cause (sampler, template, prompt, context length, quantisation or the weights) in a defined order, and build a small evaluation harness that tells you whether any change you make is an improvement or noise.
- 6.1Sampling: why output quality is often not the model
- 6.2The samplers most interfaces do not show you
- 6.3Prompting a small model is a different craft
- 6.4Examples, anchoring and getting the shape right
- 6.5Tool calling that survives contact with a small model
- 6.6Long context in practice
- 6.7Why the same seed gives different answers
- 6.8The diagnostic ladder for a bad output
- 6.9A small harness that tells you whether a change helped
An illustrative case, built from documented patternsTwo days of measurement while the service was visibly worse
The cell processed several thousand questions a month, tagging each with a syllabus unit and a difficulty band. A local 8B model did the first pass and a clerk checked it, and for five months the clerk had been changing roughly one tag in twenty. In the week after an engine upgrade she was changing about one in six.
She reported it as the model getting worse. Three people had three explanations within an hour: the quant was bad, the new engine had a sampling regression, and somebody had edited the prompt. None of them had any evidence, because nothing had ever been measured. The clerk’s one-in-twenty was itself a memory rather than a record.
The cell’s technical officer proposed to stop and build the thirty-item harness the course describes before changing anything. Thirty frozen items with the correct tag written down, run at temperature 0, scored automatically against the expected label, with latency and token counts recorded beside each one. Two days, including collecting items that covered the awkward cases: questions with diagrams described in text, questions spanning two units, questions in the physics paper that mention chemistry.
The cost of stopping to measure
Those two days were not free. The tagging stayed wrong, the clerk re-tagged by hand, and the backlog grew during an admissions period. The head of the cell’s position was reasonable: we know when it broke, so roll back the upgrade this afternoon and the problem goes away.
Rolling back would have worked. It would also have left the cell in exactly the position it was in that morning: unable to say what had changed, unable to accept the next upgrade without the same fortnight of doubt, and unable to distinguish a real regression from a bad week, because a one-in-six figure remembered by one person is not a measurement.
The technical officer made the argument in the terms the course uses. Every question in the local-model subject becomes an argument without a harness and a ten-minute run with one. Is Q4 worse than Q5, did the prompt rewrite help, is the new release better, did the upgrade break something. They were about to answer one of those by guessing, and then answer the next one by guessing too.
What the diagnostic ladder found
With the set built, the ladder took under an hour and the causes were not any of the three theories.
The first rung came back clean: the card was being used. The second did not. The upgrade had changed a default context length, and the cell’s prompt carried a long list of unit definitions before the question. Under the new default the prompt was being truncated: the usage.prompt_tokens field in the response, compared against the configured window, showed it immediately. The definitions for the last four syllabus units were being dropped, and every question belonging to those units was tagged from whatever the model could still see.
That also explained a detail nobody had connected: the errors were not spread evenly across units. They clustered.
How it played out
Raising the context window fixed it in one line, and the harness scored twenty-eight of thirty against twenty-seven before the upgrade, so the upgrade itself was fine. The definitions list was then moved out of the prompt entirely and replaced with retrieval of the three most likely units, which cut the prompt from about two thousand tokens to four hundred and made the tagging slightly better rather than slightly worse, because irrelevant definitions had been diluting attention. The harness has been run after every upgrade since and has caught two further regressions, one of them a sampler default that changed in a point release. The clerk’s corrections are now logged, and every item she corrects becomes item thirty-one, which means the set grows exactly where the system is weak. The head of the cell, who had wanted the rollback, now asks for the number before approving any change, which the technical officer describes as the actual outcome of the two days.
Worth arguing about
The errors clustered in particular syllabus units rather than being spread evenly. Why is that pattern a clue, and what did it point at?
One answer
A model that has genuinely got worse degrades broadly, because the weights have not changed in a way that favours one unit over another. A pattern that clusters points at something structural about the input. Here the unit definitions were at the end of a long prompt and the context window had shrunk, so the last definitions were silently dropped and only questions belonging to those units suffered. Comparing the reported prompt token count against the configured window confirms it in seconds, and that check is free on every response.
Rolling back would have restored the service in an afternoon. What did the cell get for the two days it spent instead?
One answer
A measurement it can repeat. The rollback would have restored the behaviour and left every future question unanswerable: whether the next upgrade is safe, whether a prompt change helped, whether a bad week is a regression. With thirty frozen items and a scorer, each of those became a ten-minute run. It also found a cause the rollback would have hidden, since the upgrade was not at fault and the same truncation would have returned the next time anyone raised the prompt length.
Thirty items is a small sample. What differences can a set that size actually detect, and what should you conclude when two configurations score within one item of each other?
One answer
A change of one or two items is noise and a change of eight is real. When two configurations land within a couple of items, that is a genuine finding rather than an inconclusive one: it says the difference is below what you can measure on your own work, so choose on another basis such as speed, memory or simplicity, and take the cheaper one. The alternative, adding items, is legitimate but only worth it when the decision actually turns on the difference.
Test yourself6 questions on this module
Why does min-p behave better than top-p when a model is genuinely uncertain?
Raising repetition_penalty to 1.3 stops the looping and breaks the indentation in generated code. Why?
A prompt carries fifteen numbered constraints and a small model honours about eight. What is the most reliable fix?
Ending a prompt with an open brace, immediately before the value, removes a whole class of failure. Which one?
A model advertised at 128k finds a single planted fact at 100,000 tokens. What has that demonstrated?
Two identical requests at temperature 0 to a busy shared server return different text. What is the cause?
Serving it to other people
9 lessons · 1 h 19
You will be able toExpose a local model as a service other people can use safely (bound to loopback and reached over SSH or a WireGuard mesh, fronted by a gateway that holds keys and quotas, packaged and supervised so it restarts on its own, logging what is needed without retaining prompts you cannot defend) and state what an unauthenticated inference port costs you within hours of being reachable.
- 7.1Serving it to other people
- 7.2Reaching the machine from somewhere else
- 7.3A gateway in front: keys, quotas and a bill
- 7.4Containers, drivers and the version matrix
- 7.5Running it as a service that survives a reboot
- 7.6Logging without building a liability
- 7.7You are now the moderation team
- 7.8One card, several people
- 7.9Local first, API behind it
An illustrative case, built from documented patternsFound in eleven hours
The change was one environment variable. The reasoning was ordinary: the developer needed the model from home, the office had a static address from its provider, and setting up a tunnel looked like an afternoon nobody had. The port was left open over the weekend.
The firm found out on Monday because the model list had changed. Two models nobody recognised had been pulled onto the disk, and about ninety gigabytes of the drive was gone. The server log showed requests arriving from Sunday morning onwards, in volume.
Nothing dramatic had happened, which is the usual shape. Internet-wide scanners sweep the whole address space on common ports in hours, and there are public search engines whose product is the resulting index. Eleven hours is unremarkable. Somebody pulled the model list, found a permissive model, and used the firm’s electricity to generate whatever they wanted. The management endpoints did the rest: an exposed instance of that engine allows a caller to pull new models onto the disk and to delete the ones you have.
Two things made it worse than a wasted weekend of power. The first is that the firm’s own logs now contained other people’s prompts. The second is the one that actually frightened the partners: every token produced came from their machine and their address, and abuse originating there is attributable to them before it is attributable to anyone else.
What they had not understood
The developer’s mental model was that the engine had an API key setting, so it had authentication. It does for some engines and not that one, and where it exists it is a single shared bearer token with no per-person accounting and no rate limiting. Revoking it locks out everybody. It is better than nothing and it is not an access control system.
The second misconception took longer to dislodge. Somebody suggested that nobody could have found the machine because they had never forwarded a port. The office router was handing out globally routable IPv6 addresses with a default-allow inbound rule, so the machine had been directly addressable without anybody deciding it should be. The asterisk in the listening-socket output had been there all along and meant exactly that.
The decision
The engine went back to loopback the same morning. The argument was about how the developer reaches it from home.
SSH tunnels. Free, already installed, and per-person by construction: access is exactly the set of people with a key on that machine, and you revoke it by deleting a line. No third party is involved at any point, which mattered because the firm’s largest client contract named the parties permitted to process its data. The cost was the eight people it would eventually have to cover, most of them on Windows laptops, and a support burden that would land on the one developer who understood it.
A managed WireGuard mesh. Each device gets a stable private address, nothing is published, and a lost laptop is removed centrally from a console. Considerably easier for eight non-specialists. The honest caveat is that a managed mesh means a company coordinates the key exchange, and although it never sees the traffic, the partners had to decide whether a contract naming permitted processors covered a control plane that handles keys and not data.
A self-hosted control server was the third possibility, which removes the third party and adds a service the firm has to run and keep patched.
How it played out
They used SSH tunnels for two weeks while the partners read the client contract properly, and then deployed a self-hosted WireGuard control server on a small rented instance, which kept the key coordination inside the firm and cost about the price of a coffee a month. The engine has stayed on loopback ever since, with a gateway in front holding a key per person, a token-per-minute limit, a concurrency cap of two, a maximum output length the caller cannot raise, and an allow-list that forwards the inference paths and refuses model pull, delete and create. The test they now run before any change is the one they did not run before: from a phone on mobile data, outside the office network and outside the mesh, request the model list over both IPv4 and IPv6. A timeout is the only acceptable result. The Sunday logs were deleted after the partners agreed there was no reason to keep other people’s prompts, which was the first retention decision the firm had ever made.
Worth arguing about
The firm had never forwarded a port and was still reachable from the internet. How, and how would you check?
One answer
Many connections hand every device a globally routable IPv6 address with no network address translation in front of it, and some routers pass inbound IPv6 with a default-allow rule. A machine that has never had a port forwarded can therefore be directly addressable. The check is to look at what the server is actually listening on (an asterisk rather than a loopback address means every interface, IPv4 and IPv6), and then to probe from outside, over IPv6 explicitly, from a network that is not yours, such as a phone on mobile data.
Why is a single shared API key on the engine not an answer to this problem?
One answer
Because it is one bearer token with no identity attached. You cannot tell who spent the GPU hour, you cannot set a different budget for a batch job than for a person, and revoking it locks out everybody at once. It also does nothing about the management endpoints, which is how the disk filled up. Identity, quotas, audit and path filtering belong in a gateway in front, with the engine on loopback, so that a caller who somehow reaches the engine directly is impossible rather than merely discouraged.
The partners hesitated over a managed mesh because of a client contract. Was that a reasonable objection, given that the provider never sees the traffic?
One answer
It was reasonable to ask, and the answer is a judgement about the contract rather than about the technology. A managed mesh coordinates keys and does not carry or decrypt the traffic, so no client data passes through the provider. Whether a contract naming permitted processors reaches a control plane of that kind is a question for whoever wrote the contract. The firm resolved it by removing the question: a self-hosted control server keeps key coordination inside the firm at the cost of one more service to patch.
Test yourself6 questions on this module
An inference port is opened to the internet with no authentication. What is the first consequence to expect?
A colleague cannot reach your server. Which pair of checks distinguishes the two possible causes?
One caller runs a batch job with two hundred parallel workers and interactive users start waiting. Which limit addresses that specifically?
A container built for CUDA 12.6 fails on a host whose driver reports CUDA 12.4. What is the rule?
A supervised engine restarts every few hours with nothing in its own logs. Where should you look?
You want to know what prompt content your stack is actually storing. What is the reliable method?
What it actually costs
9 lessons · 1 h 19
You will be able toProduce a defensible cost per million tokens for a stated local deployment (capital amortised over a holding period you can justify, electricity at your own tariff, duty cycle measured rather than assumed, and your own time priced) then express the comparison with a hosted API as the monthly token volume at which local begins to win, and say which input the answer is most sensitive to.
- 8.1The honest cost comparison
- 8.2Duty cycle, and why an idle card is expensive
- 8.3Electricity, heat and the power limit knob
- 8.4Buying hardware, in the right order
- 8.5Renting a GPU, and the meter that runs while you think
- 8.6The card as an asset, and the sunk-cost trap
- 8.7The hours nobody invoices
- 8.8Building the break-even model
- 8.9When the API simply wins, and when it cannot
An illustrative case, built from documented patternsThe card that was busy three per cent of the time
The company had bought the card eighteen months earlier to replace a hosted API bill, and by most accounts it had worked. The bill had gone. What had also happened, without anyone writing it down, was that the content team had grown, the chat assistant had become the thing people complained about at eleven in the morning, and somebody had written a proposal for a second card.
The finance director asked for the cost per million tokens before approving it. Nobody had one.
The number that mattered turned out to be the denominator. The gateway had been counting tokens for months for its own rate limiting, so the total served in a month was available immediately: about ninety-four million. A saturating load test, run at a concurrency high enough that the queue never emptied, put the machine’s sustained peak at roughly 1,150 tokens a second. That is about 2,980 million tokens a month at full duty. Ninety-four against 2,980 is a duty cycle of just over three per cent.
The team’s own estimate, collected before the measurement out of curiosity, had averaged around thirty per cent. That gap is normal. Thinking, reading and typing take far longer than generation does, and a developer who feels they use the model constantly is typically under five per cent.
What three per cent does to the arithmetic
The fixed costs run whether or not anything is being generated. Amortisation runs on the calendar. The machine drew about ninety watts idle for the other ninety-seven per cent of the month, which at the local tariff was more than the electricity it used while working. Add the capital cost over a thirty-six-month window and two hours a month of somebody’s attention, and the all-in figure came to a little over a hundred and sixty a month for ninety-four million tokens: about 1.70 per million.
The hosted price for the open model they were running was well under half that. The card was not saving money at the volume it was actually doing. It had saved money in the first three months, when a one-off backfill of two years of archived question banks had run it flat out for a fortnight.
The decision
So the proposal for a second card was the wrong question, and the right one had a cost on both sides.
Buy the second card. It solves the eleven-o’clock complaint directly and keeps everything inside the building. It also doubles the fixed cost of a machine already idle ninety-seven per cent of the time, and it requires capital approval, which in this company took about three months against a monthly subscription that took an afternoon.
Move the chat assistant to a hosted API and keep the card saturated on the nightly batch. Cheaper on these numbers, immediate, and it raises the card’s duty cycle by removing the interactive work it was bad at and leaving the batch work it was good at. The cost is the reason the card was bought: the chat assistant handles unpublished question banks, which are the company’s actual product, and sending them to a third party is a decision about the business rather than about tokens.
There was a third option with no capital in it at all, and it was the one the course points at. The duty cycle is a denominator, and there was work sitting on the other side of the building that could raise it: re-tagging the archive, generating distractor options, backfilling embeddings over every paper the company owned. None of it was urgent and all of it was real.
How it played out
They split the traffic by task rather than by preference. Classification, tagging, extraction and the short rewrites stayed local, which was most of the volume and all of the sensitive material. The long-form work the content team complained about at eleven (drafting explanations, rewriting a whole chapter) went to a hosted model behind the same gateway, as a routing rule rather than as application code, with a written line in the team handbook saying which requests may leave and which may not. Then they moved three batch jobs onto the card that had been waiting for someone to have time, which took the measured duty cycle to about nineteen per cent over the following quarter and the all-in cost to roughly 0.32 per million. The second card was not bought. The finance director’s standing request now is that any hardware proposal arrives with a measured duty cycle and a break-even volume translated into a number of exchanges a day, because a single figure presented as an answer gets believed and then blamed.
Worth arguing about
The team estimated thirty per cent and the measurement said three. Why is that error so common, and where do the two numbers come from?
One answer
People estimate duty cycle from how often they are using the tool rather than from how much of the time the card is generating, and the difference is the time spent thinking, reading and typing. The numerator comes from the gateway, which is already counting tokens for rate limiting or billing, and the denominator from a saturating load test that finds the sustained peak. Duty cycle is tokens per day divided by peak tokens per second times the seconds in a day, and estimates of it are consistently an order of magnitude too high.
Which single input moves a break-even calculation most, and what follows for how you argue about one?
One answer
Duty cycle, nearly proportionally, followed by whether people’s hours are counted at all, which often changes the sign of the conclusion by itself. The amortisation window is next and is entirely a choice. Card price and electricity tariff are meaningful but bounded. So an argument about the electricity price is not worth the meeting, and an argument about duty cycle is the only one that matters, which is why the right response to a disagreement is to ask for the measurement rather than to trade assumptions.
Raising the duty cycle by moving batch work onto the card cut the cost per million by a factor of five. When is that not a saving?
One answer
When the work is invented. A team that starts summarising every document because the card is free has converted an idle asset into a busy one without producing anything anybody reads, and the cost per token falls while the cost per useful output does not. The metric to keep beside duty cycle is how many of the tokens produced were used by a person or a system that needed them. Here the three jobs were real work already waiting, which is the version that genuinely counts.
Test yourself6 questions on this module
The same card serves one user at 60 tokens a second, or thirty-two concurrent users at 1,600 aggregate. What happens to electricity cost per million tokens?
Which input, varied by a factor of two, moves a local cost-per-token figure most?
Capping a large consumer card’s board power at about seventy per cent of its default typically costs how much throughput?
You already own a suitable card. Which costs belong in the decision about whether to run a model on it?
What is the largest avoidable cost of renting a GPU by the hour?
A break-even calculation gives 280 million tokens a month. What should you do with that number before presenting it?
Making it yours: adapters and training
9 lessons · 1 h 21
You will be able toDecide whether a stated failure calls for a better prompt, retrieval or weight training; produce a LoRA adapter on one consumer card from a dataset you built with the correct chat template and loss masking; distinguish generalisation from memorisation and from catastrophic forgetting using a held-out set and a general-capability check; serve or merge the adapter; and state which licences the resulting weights inherit.
- 9.1Prompt, retrieval or training: choosing the right tool
- 9.2What a LoRA actually is
- 9.3QLoRA: training a 7B on the card you have
- 9.4The dataset is the project
- 9.5Running the training run
- 9.6Generalisation, memorisation and forgetting
- 9.7Serving what you made, merged or not
- 9.8When an adapter is not enough
- 9.9What you may ship, and what nobody has settled
An illustrative case, built from documented patternsBetter at triage, worse in Hindi
The desk received about four hundred messages a day across a web form, email and a messaging app. A triage step assigned each one an intent and an urgency, and a general model with a long prompt had been doing it adequately: the prompt ran to about two thousand tokens of category definitions, and it was sent on every single request.
The case for training was one the course recognises as sound. The behaviour existed and was demonstrable, the categories had subtle boundaries that several pages of definitions had failed to express, and folding the behaviour into the weights would remove two thousand tokens of prefill from four hundred requests a day. They wrote the sentence first, as instructed: after this, the model will assign intent and urgency, measured by exact match on a held-out set, on messages of the shape we actually receive.
The dataset took most of three weeks. Eighteen hundred real messages with the correct labels, rendered with the model’s own chat template and read before training, loss computed on the assistant tokens only, ten per cent held out and untouched, a further two hundred collected a month later from a different source so that the test set did not share the collection’s quirks. About a tenth of the examples were messages the desk should decline to triage automatically, because a dataset of successes teaches a model to always succeed.
The run itself was under an hour. Rank 16, alpha 32, two epochs, an effective batch of sixteen, the MLP projections included as well as attention. Evaluation loss fell with training loss and turned upward at step 150, so they took that checkpoint rather than the final one. Exact match on the held-out set was 0.91 against 0.78 for the prompted base.
The check somebody nearly skipped
The twenty-item general-capability set had been built before the run, because the course is insistent about it: neither loss curve detects catastrophic forgetting, and an evaluation set drawn from the same narrow distribution as the training set cannot either. It contained three general knowledge questions, three unrelated instruction-following items, two items in each language the desk’s customers actually use, two that should be refused, and two short reasoning items.
Run at temperature 0 against the base and against the adapter, side by side, it took twenty minutes. The task items were better. The Hindi items were markedly worse: the adapter answered them in English, and where it did answer in Hindi it did so badly. About a third of the desk’s incoming messages are in Hindi or in transliterated Hinglish, and the training set, assembled from the ticketing system’s English export because that was the export that existed, contained almost none.
The decision, under a deadline
The quarter ended in nine days and the triage improvement had been promised.
Ship it with a language router. Detect the language, send Hindi and Hinglish messages to the base model with the old two-thousand-token prompt, and send everything else to the adapter. It works, it ships on time, and it keeps most of the gain. The cost is two behaviours to maintain, two prompts to keep in step, inconsistent labelling between the two paths, and a quiet inequality in which a third of customers get the worse system.
Rebuild the dataset and retrain. Ten days of collecting and labelling Hindi and Hinglish examples, then another run and another evaluation. It misses the quarter and it produces one model that serves everybody the same way.
How it played out
They shipped the router, and then did the rebuild anyway. The router ran for six weeks and was worse than either party expected: the two paths disagreed on urgency often enough that the desk’s own reporting became unreliable, because a ticket’s priority depended on the language it arrived in rather than on what it said. The rebuild added six hundred Hindi and Hinglish examples, including code-switched messages with English product names inside Devanagari sentences, which had been the hardest category all along. The second adapter scored 0.89 on the original held-out set, slightly below the first, and 0.87 on a new Hindi held-out set, against 0.62 for the router’s base-model path. The team’s written note says the mistake was not the router, which was a defensible response to a deadline. It was that the dataset was assembled from the export that existed rather than from the traffic that exists, and that the general-capability check, which was nearly cut for time, was the only thing that caught it.
Worth arguing about
Both loss curves behaved well and the held-out score improved. Why did neither reveal the Hindi problem?
One answer
Because the held-out set was split from the same collection as the training set, so it shared its quirks, including having almost no Hindi in it. Evaluation loss measures how well the model predicts reference answers drawn from that same narrow distribution, so it improves while unrelated capabilities quietly degrade. Catastrophic forgetting does not appear in either curve by construction. The only thing that detects it is a small general-capability set built before the run and read side by side afterwards.
The router shipped on time and was later abandoned. What was the cost that neither side of the decision had priced?
One answer
That two paths produce two behaviours, and the labels they produce are not comparable. A ticket’s assigned urgency came to depend on the language it arrived in rather than on its content, which made the desk’s own reporting unreliable: a cost that lands on the organisation rather than on the model, and that nobody measures because it is not an accuracy number. The inequality was the visible objection; the incoherent data was the expensive one.
Was fine-tuning the right tool here at all, and how would you have known before starting?
One answer
Yes, and for the reasons the course names. The behaviour demonstrably existed, the category boundaries were a judgement that several pages of definitions had failed to express, and training removed two thousand tokens of prompt from four hundred requests a day. The test is whether you can write the measurement first: after this the model will do X, measured by Y, on inputs of shape Z. They could. Had the requirement instead been that the model should know the product documentation, that is a knowledge gap and belongs to retrieval, because fine-tuning adjusts behaviour and does not install facts.
Test yourself6 questions on this module
A support model should answer from company documentation that changes weekly. Which tool fits?
You raise the LoRA rank from 16 to 64 and leave alpha at 16. What happens to the adapter?
Why does LoRA remove most of the memory that full fine-tuning needs?
Before launching a run you decode the tokens that contribute to the loss and see the user’s message among them. What does that mean?
Training loss and evaluation loss both fall, and the adapter scores better on the held-out set. What has not been tested?
An adapter trained with QLoRA against a 4-bit base is merged into the 16-bit weights and then quantised for serving. What follows?
What this course is
A practical course on running large language models on your own hardware. It teaches the arithmetic that decides whether a model fits, what quantisation actually costs you, which serving engine wins under which conditions, and when local is genuinely cheaper than an API, with real commands, real numbers, and no engine advertising. Written for people on ordinary laptops as much as for people with a 4090.
Who wrote it
Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.