Answers · Putting a model into service
What do I actually lose by storing a model's weights in 8 bits instead of 16?
In shortHalf the memory, and twice the speed for anything that was waiting on memory rather than arithmetic, at the cost of coarser stored numbers and a few values that no longer fit the format at all.
Half the bytes, and twice the speed for any model that was waiting on memory rather than on arithmetic. What you actually give up is fineness in the stored numbers and, less obviously, reach: a handful of values that cease to be representable at all.
A 70 billion weight model at 16 bits is 140 GB of weights. The same model at 8 bits is 70 GB. That is the whole of the space saving and it is exactly a factor of two, because 16 bits is 2 bytes per weight and 8 bits is 1 byte per weight, and nothing else about the file changes.
The speed follows from the same number, provided you know which cost dominates. Producing one token means reading every weight once: each is used a single time and then not again until the next token. So bytes per weight is bytes per token. On hardware with 3350 GB/s of memory bandwidth:
140 GB at 3350 GB/s takes 0.0418 s, about 42 ms per token, which is 24 tokens a second. 70 GB at 3350 GB/s takes 0.0209 s, about 21 ms per token, which is 48 tokens a second.
The multiplications are identical in both cases. What changes is how long the units sit idle waiting for an operand to arrive. Nothing about the computation is approximated differently, only the rate at which data is fed into it. That is why the gain comes out so clean, and why it disappears entirely if the job is limited by arithmetic instead of movement.
| bits per weight | bytes per weight | gigabytes of weights | milliseconds per token | tokens per second |
|---|---|---|---|---|
| 32 | 4 | 280 | 83.6 | 12 |
| 16 | 2 | 140 | 41.8 | 24 |
| 8 | 1 | 70 | 20.9 | 48 |
| 4 | 0.5 | 35 | 10.4 | 96 |
What the bits are buying
A stored number is not a number in the mathematical sense. It is a sign, a fraction and an exponent, and the two fields answer two quite different questions.
The fraction decides how finely two nearby values can be told apart, because it fixes how many representable values sit between one power of two and the next. Widen it by one bit and every gap halves. The exponent decides how large and how small a value can be held at all. A format of a fixed total width divides its bits between those two jobs, and moving a bit from one to the other trades fineness against reach at a fixed price.
The natural guess is that eight bits makes every weight slightly wrong, in the way that rounding a measurement to fewer decimal places makes it slightly wrong, and that the damage is spread thinly and evenly across the model. Neither half of that holds. The error is not spread evenly, and it is not always slight.
A scale is not stored per weight, because storing one per weight would defeat the purpose. A group of weights shares a single scale, and that scale must accommodate the group's largest member. So a value near 500 is held to about eight parts in a thousand, which is perfectly adequate. A value of 1 in the same group has no code anywhere near it: dividing by the step gives 1/3.937 = 0.25, and it rounds to zero, or to 3.937, with nothing representable in between. Its relative error is not 0.79 per cent, it is 100 per cent, and the contribution that weight was making vanishes.
That is the fineness failure, and it is survivable. Thousands of these small contributions average out, the outputs drift a little, and it takes hundreds or thousands of cases to detect the drift at all. Which is precisely what makes it dangerous: the usual check, trying a few prompts and seeing that the answers look sensible, cannot see it.
The reach failure is not survivable. A value above the largest exponent the format can express becomes infinite, and an infinity poisons every number it subsequently touches. A value below the smallest becomes zero, which quietly removes a contribution rather than announcing itself. Either way the output is not slightly worse, it is wrong, and often visibly wrong. A half-size format with an exponent range from about 2 raised to -14 up to 2 raised to 15 has ample reach for weights, which cluster near 1, but tight reach for running totals, which can sit twenty or thirty powers of two higher. That asymmetry is why the half-size format that bought reach and gave up fineness beat the one that did the opposite, despite being worse on paper at representing any single number.
Where it stops being true
Large models have a small number of positions, a few out of thousands, whose values run enormously above everything else, and they recur in the same places rather than at random. That is what makes eight bits tractable rather than hopeless: those few can be given their own scale, or the weights can be split into groups of a few hundred with one scale each, so a single large value spoils only its own neighbourhood. Two bytes a weight costs nothing measurable and remains the default; one byte for the weights, with per-group scales and care over the outliers, is routine; half a byte is where quality visibly moves.
Three assumptions are doing work underneath all of this. Describing a system as running in eight bits is almost always shorthand for the weights only: the running totals are accumulated in a wider format whatever the inputs were, because thousands of small errors summed together stop being small, and the stored conversation and the activations are separate decisions with their own formats. The doubling of speed requires that movement is the limit; on a job limited by arithmetic, halving the bytes buys nothing. And the comparison is against 16 bits, not against an ideal: the weights themselves emerged from an averaging process and never carried three decimal digits of real information to begin with, so the first halving is nearly free and the second is where you start paying.
Worth keeping
- Every weight is read once per token, so bytes per weight is bytes per token, and halving it halves the time per token when movement is the limit.
- Losing fineness degrades a model gradually across hundreds of cases; losing reach makes it wrong immediately, because infinities and silent zeros propagate.
- A group of weights shares one scale sized for its largest member, so a few large values can destroy the small ones, which is why per-group scales and outlier handling matter more than the bit count itself.
Where this goes next
This question is one step of What It Costs to Run a Model, a course that works through its subject from the beginning. The page that teaches this part of it is How Many Bits a Number Needs, and it reads the whole way through whether or not you have an account.
What an account changes is the practice: problems standing under every idea, marked with a reason rather than a tick, in a copy of the course that is yours.
Read that pageThe whole courseOther questions people ask about this
- At what point does the conversation history cost more than the model itself?With sixty-four conversations grouped together, reading their history overtakes reading the model at roughly six and a half thousand words per conversation.
- Why does a long conversation with a model use so much more memory than a short one?The model keeps a growing summary for every word inside every layer, so a long chat uses memory in proportion to its length, not to the size of its text.
- Why is writing a long answer slower than reading a prompt of the same length?Reading a prompt happens in one parallel pass, while writing a reply happens one word at a time, so the reply rereads the model for every word.
- Why does the model return JSON wrapped in extra text when I asked for JSON?A request for JSON does not force the model to return only JSON, so you need to validate the reply and repair it once rather than assume it will parse.
- Why do the numbers look good while users keep complaining?Because the score measures a fixed set of cases with a fixed rule, and the users are living through behaviour that the set and the rule never covered.
- How do I make the model answer from the documents I give it instead of its own memory?Fence the documents as labelled reference material, put them before the question, tell the model to prefer them over memory, and require a passage number for each claim.