<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Awq on lo0 — Tech Blog</title><link>https://blog.lo0.es/en/tags/awq/</link><description>Recent content in Awq on lo0 — Tech Blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Tue, 09 Jun 2026 02:00:00 +0000</lastBuildDate><atom:link href="https://blog.lo0.es/en/tags/awq/index.xml" rel="self" type="application/rss+xml"/><item><title>Test-time quantization: quantising on the fly with no calibration dataset</title><link>https://blog.lo0.es/en/posts/test-time-quantization-without-calibration-dataset/</link><pubDate>Tue, 09 Jun 2026 02:00:00 +0000</pubDate><guid>https://blog.lo0.es/en/posts/test-time-quantization-without-calibration-dataset/</guid><description>&lt;blockquote>
&lt;p>This post is the natural continuation of &lt;a href="https://blog.lo0.es/en/posts/quantization-llm-inference-fp8-int4-gguf/">Quantization for LLM inference&lt;/a>, which is worth reading first: that is where GPTQ, AWQ, scale + zero-point and why activation outliers are the central problem all live. Here we are not discussing &lt;em>how many bits&lt;/em> to use, but &lt;strong>when and with what information the scales are computed&lt;/strong>: offline against a corpus (PTQ) or on the fly against real traffic (TTQ).&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>Activation-aware quantisation (AWQ, SmoothQuant) decides which channels to protect by measuring the magnitude of the activations over a &lt;strong>calibration dataset&lt;/strong> in an &lt;strong>offline pass&lt;/strong>, before deployment. The implicit assumption is that this corpus represents future traffic. But activation outliers, the channels with 10-100× the median magnitude that dominate the quantisation error, depend on the input: they change with the domain, the language and the client&amp;rsquo;s distribution. When real traffic drifts away from the calibration, the fixed scales stop being optimal and quality drops. &lt;strong>Test-time quantization (TTQ)&lt;/strong> removes the corpus and the offline pass: it derives the activation-aware scales &lt;strong>at inference time&lt;/strong>, from the activations that are actually observed, per token or per batch. The trade-off is honest and not minor: it introduces &lt;strong>runtime overhead&lt;/strong>, computing statistics, detecting outliers, recomputing scales at every step, which competes directly with the saving from quantising. On small models that overhead weighs proportionally more, because the forward is short and the fixed per-step costs dominate (the framing is in &lt;a href="https://blog.lo0.es/en/posts/roofline-flips-optimising-small-models/">the inverted roofline for SLMs&lt;/a>). TTQ is &lt;strong>orthogonal&lt;/strong> to the format: it is not a competitor to INT4 or FP8, it is a different way of deriving &lt;em>s&lt;/em>. It pays off when there is no calibration pipeline, when the traffic distribution is shifting or unknown, and in multitenant settings where no representative corpus exists.&lt;/p>
&lt;h2 id="you-are-here-deploy">You are here: DEPLOY&lt;/h2>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 90" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="you are here: Deploy">
&lt;defs>&lt;marker id="ttqm" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;text x="390" y="20" text-anchor="middle" fill="currentColor" font-size="12" font-weight="600">You are here: DEPLOY · deriving quantization scales on the fly&lt;/text>
&lt;rect x="30" y="35" width="110" height="35" rx="6" fill="#f4f4f4" stroke="#444" stroke-width="1.4"/>&lt;text x="85" y="58" text-anchor="middle" fill="currentColor" font-size="12" font-weight="600">1 · Data&lt;/text>
&lt;rect x="155" y="35" width="110" height="35" rx="6" fill="#f4f4f4" stroke="#444" stroke-width="1.4"/>&lt;text x="210" y="58" text-anchor="middle" fill="currentColor" font-size="12" font-weight="600">2 · Tune&lt;/text>
&lt;rect x="280" y="35" width="110" height="35" rx="6" fill="#f4f4f4" stroke="#444" stroke-width="1.4"/>&lt;text x="335" y="58" text-anchor="middle" fill="currentColor" font-size="12" font-weight="600">3 · Eval&lt;/text>
&lt;rect x="405" y="35" width="110" height="35" rx="6" fill="#7ad88f" stroke="#444" stroke-width="3"/>&lt;text x="460" y="58" text-anchor="middle" fill="#111" font-size="12" font-weight="600">4 · Deploy&lt;/text>
&lt;rect x="530" y="35" width="110" height="35" rx="6" fill="#f4f4f4" stroke="#444" stroke-width="1.4"/>&lt;text x="585" y="58" text-anchor="middle" fill="currentColor" font-size="12" font-weight="600">5 · Observe&lt;/text>
&lt;rect x="655" y="35" width="110" height="35" rx="6" fill="#f4f4f4" stroke="#444" stroke-width="1.4"/>&lt;text x="710" y="58" text-anchor="middle" fill="currentColor" font-size="12" font-weight="600">6 · Retrain&lt;/text>
&lt;path d="M140,52 L155,52" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttqm)"/>
&lt;path d="M265,52 L280,52" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttqm)"/>
&lt;path d="M390,52 L405,52" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttqm)"/>
&lt;path d="M515,52 L530,52" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttqm)"/>
&lt;path d="M640,52 L655,52" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttqm)"/>
&lt;path d="M710,72 L710,82 L85,82 L85,72" stroke="#888" stroke-width="1.2" fill="none" stroke-dasharray="4 2" marker-end="url(#ttqm)"/>
&lt;/svg>
&lt;/div>
&lt;h2 id="the-analogy-the-tailor-who-takes-measurements-versus-off-the-peg-sizes">The analogy: the tailor who takes measurements versus off-the-peg sizes&lt;/h2>
&lt;p>A clothes shop has two ways of dressing a customer.&lt;/p>
&lt;p>The first is to &lt;strong>sell off-the-peg sizes&lt;/strong>. At some point the factory measured an &amp;ldquo;average customer&amp;rdquo;, a mean mannequin built from a population sample, and cut the garments to those measurements. When a customer walks in, you hand them the size that comes closest. It is extremely fast: the garment is already sewn, it just gets handed over. The problem appears when the customer does not look like the average mannequin: if their shoulders are much broader than average, their particular outlier, the standard size pulls or leaves spare fabric, because it was cut to protect &lt;em>other&lt;/em> areas. This is &lt;strong>offline calibrated PTQ&lt;/strong>: AWQ measured the importance of each channel over a corpus and fixed the scales once and for all; fast at inference time, but blind to the specific customer.&lt;/p>
&lt;p>The second is &lt;strong>the tailor who takes measurements on the spot&lt;/strong>. When the customer walks in, the tailor gets out the tape, measures &lt;em>that customer&lt;/em>, spots where their particular bulk is and adjusts the cut to their real anatomy. The result fits better, especially for customers who fall outside the mould. But every customer costs time: measuring, marking, deciding. This is &lt;strong>TTQ&lt;/strong>: the scales are derived on the fly from the activations that the input actually generates.&lt;/p>
&lt;p>The analogy holds up in three details:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>The average mannequin = the calibration dataset.&lt;/strong> If the population walking into the shop resembles the mannequin, the sizes work; if not, they fail at the extremes.&lt;/li>
&lt;li>&lt;strong>Measuring every customer = computing activation statistics per token/batch.&lt;/strong> A better fit, but a fixed cost paid on &lt;em>every&lt;/em> garment.&lt;/li>
&lt;li>&lt;strong>The broad shoulders = the outlier activation channels.&lt;/strong> They are precisely the areas where the fit matters and where the generic size gets it most wrong.&lt;/li>
&lt;/ul>
&lt;p>The tailor wins when the customers are varied or unknown. The tailor loses when you have a homogeneous population and a mannequin that represents it well: there, paying for measurement on every customer is a waste of time.&lt;/p>
&lt;h2 id="the-problem-ttq-solves-fixed-calibration-ages-with-the-traffic">The problem TTQ solves: fixed calibration ages with the traffic&lt;/h2>
&lt;p>Let us recall from the &lt;a href="https://blog.lo0.es/en/posts/quantization-llm-inference-fp8-int4-gguf/">quantization post&lt;/a> what AWQ and SmoothQuant do exactly. They do not quantise all channels equally: they identify the ~1 % of channels whose activations have a large magnitude, the &lt;em>salient channels&lt;/em>, and protect them by scaling them before quantising. To measure that importance they need to see activations, and they see them over a &lt;strong>calibration dataset&lt;/strong> (128-512 samples, typically WikiText or a slice of the domain) in an &lt;strong>offline pass&lt;/strong> prior to deployment.&lt;/p>
&lt;p>The assumption is strong: that the activation distribution of the calibration corpus &lt;strong>represents that of production traffic&lt;/strong>. Two reasons why that assumption breaks:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Activation outliers depend on the input.&lt;/strong> They are not a fixed property of the model the way weights are. The channel that is an outlier when processing C++ code may not be one when processing conversational Arabic or log JSON. The magnitude and position of the peaks change with the domain, the language and the input format.&lt;/li>
&lt;li>&lt;strong>Real traffic is rarely the corpus.&lt;/strong> You calibrate with English WikiText and the client sends you support tickets in Spanish with tables pasted in. The calibration protected the channels that &lt;em>WikiText&lt;/em> activated, not the ones that real traffic activates. The scales are suboptimal exactly where the client lives.&lt;/li>
&lt;/ol>
&lt;p>The result is distribution-dependent degradation: the quantised model keeps its quality while the input resembles the calibration and loses it as the input drifts away. The most awkward case is &lt;strong>multitenant&lt;/strong>: if you serve clients with different domains from the same quantised model, no single representative corpus exists; any fixed calibration favours some tenants and penalises others.&lt;/p>
&lt;h2 id="the-ttq-mechanism-measure-the-real-activations-and-scale-on-the-fly">The TTQ mechanism: measure the real activations and scale on the fly&lt;/h2>
&lt;p>TTQ (arXiv:2603.19296, March 2026) proposes deriving &lt;strong>activation-aware quantisation at inference time&lt;/strong>, with no offline pass and no calibration dataset. The idea, in its bare conceptual form:&lt;/p>
&lt;p>&lt;strong>Step 1 — Observe.&lt;/strong> When the activation tensor &lt;code>X&lt;/code> arrives at a linear layer (per token or per batch), cheap statistics are computed over the channels: a measure of central tendency (median or mean magnitude) and one of dispersion per channel. This is the equivalent of AWQ looking at its corpus, but done over the activations that &lt;em>really&lt;/em> are coming in right now.&lt;/p>
&lt;p>&lt;strong>Step 2 — Detect outliers on the fly.&lt;/strong> With those statistics, the channels whose magnitude shoots up relative to the tensor median are identified; the typical criterion is a threshold of the form &amp;ldquo;magnitude &amp;gt; k × median&amp;rdquo;. These are the channels that, if quantised with the same scale as the rest, blow up the error.&lt;/p>
&lt;p>&lt;strong>Step 3 — Derive scales and segregate.&lt;/strong> For normal channels a scale is computed that exploits the range; for the outliers a different treatment is applied, a scale of their own, or keeping them at higher precision, in the style of &lt;em>on-the-fly mixed precision&lt;/em>. It is the same philosophy as LLM.int8() (segregating outliers to FP16) or AWQ (scaling salient channels), but with the threshold and the scales &lt;strong>recomputed over the current input&lt;/strong>, not frozen since calibration.&lt;/p>
&lt;p>&lt;strong>Step 4 — Quantise and multiply.&lt;/strong> With the fresh scales, the tensor is quantised and the GEMM runs. The activations entering the next layer compensate for the rescaling, just as in AWQ, so that the maths cancels out.&lt;/p>
&lt;p>The key difference with AWQ is not in &lt;em>what&lt;/em> is done (protecting activation outliers) but in &lt;em>when&lt;/em> and &lt;em>against what&lt;/em>: AWQ decides once, offline, against a corpus; TTQ decides at every step, on the fly, against real traffic. It is the transfer to inference of the &amp;ldquo;test-time&amp;rdquo; idea: adapting the computation to the specific sample in front of you instead of to a precomputed average.&lt;/p>
&lt;div class="diagram" style="max-width:780px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 780 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Offline calibrated PTQ versus on-the-fly TTQ">
&lt;defs>&lt;marker id="ttq2" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;text x="195" y="22" text-anchor="middle" fill="currentColor" font-size="13" font-weight="700">Offline-calibrated PTQ (AWQ / GPTQ)&lt;/text>
&lt;text x="585" y="22" text-anchor="middle" fill="currentColor" font-size="13" font-weight="700">On-the-fly TTQ&lt;/text>
&lt;line x1="390" y1="35" x2="390" y2="285" stroke="#bbb" stroke-width="1" stroke-dasharray="4 3"/>
&lt;rect x="30" y="45" width="150" height="38" rx="6" fill="#ffe6d6" stroke="#a05a2c" stroke-width="1.4"/>&lt;text x="105" y="68" text-anchor="middle" fill="#111" font-size="11" font-weight="600">calibration dataset&lt;/text>
&lt;rect x="210" y="45" width="150" height="38" rx="6" fill="#ffe6d6" stroke="#a05a2c" stroke-width="1.4"/>&lt;text x="285" y="64" text-anchor="middle" fill="#111" font-size="11" font-weight="600">OFFLINE pass&lt;/text>&lt;text x="285" y="78" text-anchor="middle" fill="#444" font-size="10">fixes scales s, outliers&lt;/text>
&lt;path d="M180,64 L210,64" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttq2)"/>
&lt;rect x="120" y="105" width="150" height="34" rx="6" fill="#f0f0f0" stroke="#444" stroke-width="1.4"/>&lt;text x="195" y="126" text-anchor="middle" fill="#111" font-size="11" font-weight="600">FROZEN scales&lt;/text>
&lt;path d="M285,83 L285,98 L195,98 L195,105" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttq2)"/>
&lt;rect x="30" y="165" width="150" height="34" rx="6" fill="#d6eaff" stroke="#1f5fa8" stroke-width="1.4"/>&lt;text x="105" y="186" text-anchor="middle" fill="#111" font-size="11" font-weight="600">similar input → OK&lt;/text>
&lt;rect x="210" y="165" width="150" height="34" rx="6" fill="#f6caca" stroke="#a52a2a" stroke-width="1.4"/>&lt;text x="285" y="182" text-anchor="middle" fill="#111" font-size="11" font-weight="600">distant input →&lt;/text>&lt;text x="285" y="195" text-anchor="middle" fill="#a52a2a" font-size="10" font-weight="600">degradation&lt;/text>
&lt;path d="M150,139 L105,165" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttq2)"/>
&lt;path d="M240,139 L285,165" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttq2)"/>
&lt;text x="195" y="225" text-anchor="middle" fill="#444" font-size="10">inference overhead ≈ 0 · quality depends on calibration&lt;/text>
&lt;rect x="510" y="45" width="150" height="38" rx="6" fill="#d9f5d6" stroke="#2a7a40" stroke-width="1.4"/>&lt;text x="585" y="62" text-anchor="middle" fill="#111" font-size="11" font-weight="600">REAL activations&lt;/text>&lt;text x="585" y="77" text-anchor="middle" fill="#444" font-size="10">from current traffic&lt;/text>
&lt;rect x="510" y="100" width="150" height="34" rx="6" fill="#d9f5d6" stroke="#2a7a40" stroke-width="1.4"/>&lt;text x="585" y="115" text-anchor="middle" fill="#111" font-size="11" font-weight="600">measure + detect&lt;/text>&lt;text x="585" y="128" text-anchor="middle" fill="#444" font-size="10">outliers ON THE FLY&lt;/text>
&lt;path d="M585,83 L585,100" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttq2)"/>
&lt;rect x="510" y="151" width="150" height="34" rx="6" fill="#d9f5d6" stroke="#2a7a40" stroke-width="1.4"/>&lt;text x="585" y="166" text-anchor="middle" fill="#111" font-size="11" font-weight="600">FRESH scales&lt;/text>&lt;text x="585" y="179" text-anchor="middle" fill="#444" font-size="10">per token / batch&lt;/text>
&lt;path d="M585,134 L585,151" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttq2)"/>
&lt;rect x="510" y="202" width="150" height="34" rx="6" fill="#fff5b0" stroke="#9a8400" stroke-width="1.4"/>&lt;text x="585" y="217" text-anchor="middle" fill="#111" font-size="11" font-weight="600">quantize + GEMM&lt;/text>&lt;text x="585" y="230" text-anchor="middle" fill="#9a6b00" font-size="10" font-weight="600">+ per-step overhead&lt;/text>
&lt;path d="M585,185 L585,202" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ttq2)"/>
&lt;text x="585" y="258" text-anchor="middle" fill="#444" font-size="10">no corpus · quality robust to distribution · overhead ≠ 0&lt;/text>
&lt;/svg>
&lt;/div>
&lt;h2 id="the-maths-that-matter">The maths that matter&lt;/h2>
&lt;h3 id="the-error-of-quantising-an-outlier-with-the-wrong-scale">The error of quantising an outlier with the wrong scale&lt;/h3>
&lt;p>Let us recall affine uniform quantisation from the &lt;a href="https://blog.lo0.es/en/posts/quantization-llm-inference-fp8-int4-gguf/">base post&lt;/a>: an integer code &lt;code>q = round(x/s) - z&lt;/code> with scale &lt;code>s&lt;/code> and zero-point &lt;code>z&lt;/code>, and reconstruction &lt;code>x̂ = s·(q + z)&lt;/code>. For a &lt;code>b&lt;/code>-bit quantiser with a symmetric range, the scale that covers a tensor of maximum magnitude &lt;code>M&lt;/code> is approximately &lt;code>s = M / (2^{b-1} - 1)&lt;/code>. The rounding error of each element is bounded by half a scale: &lt;code>|x - x̂| ≤ s/2&lt;/code>.&lt;/p>
&lt;p>Here is the outlier problem. The scale &lt;code>s&lt;/code> is chosen to cover the &lt;strong>largest&lt;/strong> value in the group. If one channel has 30× the median magnitude and you share a single scale with the rest of the tensor, that magnitude rules: &lt;code>M&lt;/code> is the outlier, so &lt;code>s&lt;/code> is inflated 30× relative to what the majority would need. The absolute rounding error of the normal values rises proportionally.&lt;/p>
&lt;p>A concrete calculation. Take a group where the median magnitude is 1.0 and an outlier channel is 30.0, quantised to INT4 (&lt;code>b = 4&lt;/code>, levels ±7):&lt;/p>
&lt;ul>
&lt;li>&lt;strong>With a shared scale&lt;/strong>, &lt;code>s = 30 / 7 ≈ 4.29&lt;/code>. The rounding error of a typical value (magnitude ~1) is up to &lt;code>s/2 ≈ 2.14&lt;/code>. In other words, &lt;strong>the error on the normal values is of the order of their own value&lt;/strong>: the outlier has destroyed the resolution of everything else. Relative error of a value of magnitude 1: up to ~214 %.&lt;/li>
&lt;li>&lt;strong>Segregating the outlier&lt;/strong> (pulling it out to FP16 or giving it its own scale) and quantising the rest with &lt;code>M = 1&lt;/code>, &lt;code>s = 1/7 ≈ 0.143&lt;/code>. The error of a typical value drops to &lt;code>s/2 ≈ 0.071&lt;/code>, ~7 % relative. &lt;strong>Thirty times less error&lt;/strong> on the majority of the group&amp;rsquo;s weights.&lt;/li>
&lt;/ul>
&lt;p>That is the entire reason for the existence of activation-aware quantisation: &lt;strong>to detect and treat separately the ~1 % of channels that, if not segregated, hijack the scale&lt;/strong>. AWQ does it against the corpus; TTQ does it against the real input. And if the channel that is an outlier &lt;em>in production&lt;/em> was not an outlier &lt;em>during calibration&lt;/em>, AWQ did not protect it: it quantised real traffic with the inflated scale from the case above. That is where TTQ gains precision.&lt;/p>
&lt;h3 id="the-overhead-the-cost-of-measuring-at-every-step">The overhead: the cost of measuring at every step&lt;/h3>
&lt;p>The price is symmetric. Computing the per-token statistics, per-channel magnitudes, median or percentile, outlier threshold, scales, means reductions over the activation tensor that did not exist in the forward pass with frozen scales. Let us call:&lt;/p>
&lt;ul>
&lt;li>&lt;code>T&lt;/code> = forward time per token with fixed scales (static PTQ), in µs.&lt;/li>
&lt;li>&lt;code>Δ&lt;/code> = extra per-token cost of deriving the statistics and scales on the fly, in µs.&lt;/li>
&lt;/ul>
&lt;p>The relative overhead is simply:&lt;/p>
$$\text{overhead} = \frac{\Delta}{T}$$
&lt;p>The key point is that &lt;code>Δ&lt;/code> is relatively fixed per step (it depends on the number of channels and layers, not on how much &amp;ldquo;useful&amp;rdquo; work the model does), while &lt;code>T&lt;/code> scales with the size of the model. That is why the ratio behaves very differently depending on the model:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Large model&lt;/strong> (say, 70B): &lt;code>T&lt;/code> is large, every forward moves tens of GB of weights from HBM. If &lt;code>Δ ≈ 8 µs&lt;/code> and &lt;code>T ≈ 800 µs&lt;/code>, the overhead is &lt;code>8/800 = 1 %&lt;/code>. Negligible against the saving from quantising.&lt;/li>
&lt;li>&lt;strong>SLM&lt;/strong> (say, 1B): &lt;code>T&lt;/code> is small, the forward per token is short. With the same &lt;code>Δ ≈ 8 µs&lt;/code> and &lt;code>T ≈ 60 µs&lt;/code>, the overhead is &lt;code>8/60 ≈ 13 %&lt;/code>. No longer negligible: it eats a good part of what you gained by quantising.&lt;/li>
&lt;/ul>
&lt;p>This connects directly to the &lt;a href="https://blog.lo0.es/en/posts/roofline-flips-optimising-small-models/">inverted roofline for small models&lt;/a>: on an SLM the &lt;strong>fixed per-step costs&lt;/strong> (kernel launches, synchronisations, overheads that do not scale with the model) weigh proportionally more, because there is less useful work to spread them over. TTQ&amp;rsquo;s &lt;code>Δ&lt;/code> is exactly one of those fixed costs. Going per-batch instead of per-token amortises &lt;code>Δ&lt;/code> across all the tokens of the batch and lowers the relative overhead, at the cost of coarser scales; it is the first parameter to touch.&lt;/p>
&lt;p>The uncomfortable conclusion: TTQ gives away robustness to distribution but spends part of the acceleration budget on measuring, and in the regime where acceleration is scarcest, the SLMs, the ones most often deployed at the edge, is where that spending hurts most. It is not free; it is a change of currency.&lt;/p>
&lt;blockquote>
&lt;p>A note of methodological scepticism: arXiv:2603.19296 is from &lt;strong>March 2026&lt;/strong>, very recent, and as of the date of this post there are no broad independent reproductions. Whatever speedup and quality figures circulate should be taken with the same caution as any number without published methodology: what hardware, what batch size, what real measured &lt;code>Δ&lt;/code>, against what baseline (well-calibrated or badly calibrated PTQ), in what domain? The &lt;em>conceptual&lt;/em> argument, robustness to distribution in exchange for per-step overhead, is solid; the concrete multipliers are pending validation.&lt;/p>
&lt;/blockquote>
&lt;h2 id="what-ttq-is-not-marking-it-off-from-the-rest-of-the-zoo">What TTQ is NOT: marking it off from the rest of the zoo&lt;/h2>
&lt;p>TTQ is easily confused with neighbouring techniques. The distinction that matters is that &lt;strong>TTQ is the &lt;em>how&lt;/em> of deriving the scales, not the format nor the moment in training&lt;/strong>.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Technique&lt;/th>
&lt;th>When the scales are fixed&lt;/th>
&lt;th>Needs a calibration corpus&lt;/th>
&lt;th>Touches training&lt;/th>
&lt;th>Is it a format&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Static PTQ&lt;/strong> (GPTQ, AWQ)&lt;/td>
&lt;td>Offline, before deployment&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>No&lt;/td>
&lt;td>No (it uses INT4/INT8)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>QAT&lt;/strong>&lt;/td>
&lt;td>During training&lt;/td>
&lt;td>No (training data)&lt;/td>
&lt;td>Yes (retrains)&lt;/td>
&lt;td>No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>FP8 end to end&lt;/strong>&lt;/td>
&lt;td>At runtime, but simple per-tensor scales&lt;/td>
&lt;td>Minimal / none&lt;/td>
&lt;td>No&lt;/td>
&lt;td>&lt;strong>Yes&lt;/strong> (E4M3/E5M2)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>TTQ&lt;/strong>&lt;/td>
&lt;td>At runtime, activation-aware per token/batch&lt;/td>
&lt;td>&lt;strong>No&lt;/strong>&lt;/td>
&lt;td>No&lt;/td>
&lt;td>No (orthogonal to the format)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The four distinctions, one by one:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Against static PTQ (GPTQ/AWQ).&lt;/strong> Same goal (protecting outliers), the same possible format (INT4), but PTQ freezes the decisions offline against a corpus and TTQ recomputes them on the fly. TTQ is, in a sense, &amp;ldquo;AWQ without the calibration phase, paid for at runtime&amp;rdquo;.&lt;/li>
&lt;li>&lt;strong>Against QAT.&lt;/strong> QAT puts quantisation inside the training loop so that the model learns to be robust to it; it costs a retrain. TTQ does not touch training: it operates on an already trained model, at inference. They are attacks at opposite moments of the pipeline.&lt;/li>
&lt;li>&lt;strong>Against FP8 end to end.&lt;/strong> FP8 is a &lt;strong>format&lt;/strong> with its own logarithmic range; its &amp;ldquo;dynamic scaling&amp;rdquo; computes a simple per-tensor scalar at runtime, but it does not do activation-aware per-channel outlier detection. TTQ could, conceptually, derive scales on the fly &lt;em>for&lt;/em> an FP8 or INT4 quantiser: it is orthogonal to the format.&lt;/li>
&lt;li>&lt;strong>TTQ is orthogonal to the format.&lt;/strong> It decides &lt;em>how&lt;/em> to obtain &lt;code>s&lt;/code>, not how many bits you store &lt;code>q&lt;/code> in. You can imagine &amp;ldquo;TTQ over INT4&amp;rdquo; or &amp;ldquo;TTQ over FP8&amp;rdquo;. What defines TTQ is the source of the scale, real activations on the fly, not the width of the code.&lt;/li>
&lt;/ul>
&lt;h2 id="when-it-pays-off-and-when-it-does-not">When it pays off (and when it does not)&lt;/h2>
&lt;p>TTQ is not a universal replacement for AWQ. It is a tool for a specific deployment profile. &lt;strong>It pays off when:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>You have no calibration pipeline.&lt;/strong> You want to deploy a quantised model &lt;em>now&lt;/em>, without assembling the calibration dataset, running the offline pass or validating that the corpus represents the traffic. TTQ cuts out that whole phase: you load the model and serve.&lt;/li>
&lt;li>&lt;strong>The traffic distribution is shifting or unknown.&lt;/strong> An assistant that one day receives code and the next legal contracts in another language. No fixed calibration covers both well; on-the-fly adaptation follows the distribution without recalibrating.&lt;/li>
&lt;li>&lt;strong>Multitenant with no representative corpus.&lt;/strong> You serve the same model to clients with disparate domains. No single corpus represents all of them; any fixed calibration creates winners and losers among tenants. TTQ adjusts to each input, whichever tenant it comes from.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>It does not pay off when:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>You have a stable domain and a good calibration corpus.&lt;/strong> If your traffic is homogeneous and representative, offline AWQ gives you the same quality with zero runtime overhead. Paying &lt;code>Δ&lt;/code> on every token to relearn what a corpus already captured is waste.&lt;/li>
&lt;li>&lt;strong>You serve SLMs with a tight latency SLA.&lt;/strong> This is exactly the case where &lt;code>Δ/T&lt;/code> is high. If the model is small and TPOT matters, the overhead of measuring can wipe out the gain from quantising. Measure your real &lt;code>Δ&lt;/code> before assuming it adds up.&lt;/li>
&lt;li>&lt;strong>The batch is large and compute-bound.&lt;/strong> With high concurrency the forward is no longer memory-bound and the cost of the extra reductions competes worse; at the very least, amortise &lt;code>Δ&lt;/code> per batch.&lt;/li>
&lt;/ul>
&lt;h2 id="implications-for-on-premise-hardware">Implications for on-premise hardware&lt;/h2>
&lt;h3 id="on-an-rtx-4090-24-gb-ada-lovelace">On an RTX 4090 (24 GB, Ada Lovelace)&lt;/h3>
&lt;p>The natural case for the 4090 is the SLM, Qwen 3 1.5B, Llama 3 8B AWQ-INT4, serving at low concurrency. That is precisely the regime where TTQ is riskiest: &lt;code>T&lt;/code> per token is small and the 4090 has no accelerated native FP8 (we discussed this in the &lt;a href="https://blog.lo0.es/en/posts/quantization-llm-inference-fp8-int4-gguf/">quantization post&lt;/a>), so TTQ&amp;rsquo;s extra reductions run on CUDA cores competing for the same time. Here the question is not &amp;ldquo;does it improve quality?&amp;rdquo; but &amp;ldquo;does the overhead leave me an acceptable TPOT?&amp;rdquo;. If the traffic is homogeneous, offline AWQ wins on simplicity and latency. TTQ only justifies its &lt;code>Δ&lt;/code> if the input distribution is genuinely unpredictable and the degradation from fixed calibration is measurable.&lt;/p>
&lt;h3 id="on-a-generic-4h100-sxm-cluster-320-gb-nvlink-native-fp8">On a generic 4×H100 SXM cluster (320 GB, NVLink, native FP8)&lt;/h3>
&lt;p>Here the calculation partly flips. With large models &lt;code>T&lt;/code> is high and &lt;code>Δ/T&lt;/code> drops to the low single-digit percentage range, so TTQ&amp;rsquo;s overhead is more digestible. The strong use case is &lt;strong>multitenant&lt;/strong>: a cluster serving a large model to clients with heterogeneous domains, where no calibration corpus satisfies everyone. There, TTQ&amp;rsquo;s robustness to distribution has real value and the overhead is diluted in a big forward. Even so, on an H100 with native FP8, the baseline to beat is demanding: static FP8 barely loses quality (see the table in the quantization post) and costs nothing at runtime. TTQ has to show that its robustness gain on the outlier tenants outweighs what it gives away in overhead. With a paper from March 2026 and no reproductions, that demonstration is pending.&lt;/p>
&lt;h2 id="what-we-have-not-covered">What we have not covered&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>The memory cost of the on-the-fly statistics&lt;/strong>: per-channel buffers, their impact on the footprint and on cache pressure.&lt;/li>
&lt;li>&lt;strong>Interaction with continuous batching&lt;/strong>: how scales are derived when a batch mixes requests from different domains in the same step.&lt;/li>
&lt;li>&lt;strong>TTQ + speculative decoding&lt;/strong>: whether the draft and the target derive scales on the fly separately, and how that affects the acceptance rate.&lt;/li>
&lt;li>&lt;strong>Numerical stability&lt;/strong>: what happens when a batch contains a one-off extreme outlier that inflates the scale for every token of that step.&lt;/li>
&lt;/ul>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/quantization-llm-inference-fp8-int4-gguf/">Quantization for LLM inference&lt;/a> — the indispensable base: scale + zero-point, GPTQ, AWQ and why activation outliers are the problem; TTQ is AWQ with the scales derived on the fly instead of offline.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/roofline-flips-optimising-small-models/">Inverted roofline for small models&lt;/a> — why fixed per-step costs weigh more on an SLM; it explains directly why TTQ&amp;rsquo;s &lt;code>Δ&lt;/code> overhead hurts more on small models.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/aggressive-quantisation-4-bit-to-ternary/">Aggressive sub-4-bit and ternary quantisation&lt;/a> — the static frontier below 4 bits; it complements TTQ, which attacks the &lt;em>how&lt;/em> of the scale instead of the &lt;em>how many bits&lt;/em>.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/qlora-multi-lora-limit-small-models/">QLoRA and aggressive multi-LoRA on SLMs&lt;/a> — adapters on a quantised base; the base could derive scales on the fly while the adapters stay in BF16.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/fp8-end-to-end-enable-measure-quality-decide/">FP8 end to end: weights, KV and quality&lt;/a> — the Hopper/Blackwell datacenter format; TTQ is orthogonal and could derive scales for an FP8 quantiser.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/kv-cache-working-memory-llm-inference/">KV cache: the working memory of LLM inference&lt;/a> — the KV cache is quantised too; its scales are another candidate to be derived on the fly by the same logic.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/knowledge-distillation-teaching-small-model-think-like-big-one/">Knowledge distillation&lt;/a> — the other route to serving robust small models; distilling shrinks the model, TTQ tunes its quantisation to the traffic.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/optimising-decode-vllm-squeezing-every-token-small-hardware/">Optimising decode in vLLM&lt;/a> — where the runtime quantisation levers for squeezing a 4090 turn into concrete parameters.&lt;/li>
&lt;/ul>
&lt;h2 id="references">References&lt;/h2>
&lt;ul>
&lt;li>&lt;em>TTQ: Activation-Aware Test-Time Quantization to Accelerate LLM Inference On The Fly&lt;/em> (March 2026). &lt;a href="https://arxiv.org/abs/2603.19296">https://arxiv.org/abs/2603.19296&lt;/a>&lt;/li>
&lt;li>Lin, J., Tang, J., Tang, H., Yang, S., Dang, X., Han, S. &lt;em>AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration&lt;/em> (MLSys 2024). &lt;a href="https://arxiv.org/abs/2306.00978">https://arxiv.org/abs/2306.00978&lt;/a>&lt;/li>
&lt;li>Frantar, E., Ashkboos, S., Hoefler, T., Alistarh, D. &lt;em>GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers&lt;/em> (ICLR 2023). &lt;a href="https://arxiv.org/abs/2210.17323">https://arxiv.org/abs/2210.17323&lt;/a>&lt;/li>
&lt;li>Xiao, G., Lin, J., Seznec, M., Wu, H., Demouth, J., Han, S. &lt;em>SmoothQuant: Accurate and Efficient Post-Training Quantization for Large Language Models&lt;/em> (ICML 2023). &lt;a href="https://arxiv.org/abs/2211.10438">https://arxiv.org/abs/2211.10438&lt;/a>&lt;/li>
&lt;li>Dettmers, T., Lewis, M., Belkada, Y., Zettlemoyer, L. &lt;em>LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale&lt;/em> (NeurIPS 2022). &lt;a href="https://arxiv.org/abs/2208.07339">https://arxiv.org/abs/2208.07339&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Quantization for LLM inference: FP8, INT4 (GPTQ, AWQ) and GGUF — the model's accounting zoom</title><link>https://blog.lo0.es/en/posts/quantization-llm-inference-fp8-int4-gguf/</link><pubDate>Wed, 27 May 2026 11:30:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/quantization-llm-inference-fp8-int4-gguf/</guid><description>&lt;blockquote>
&lt;p>This post complements &lt;a href="https://blog.lo0.es/en/posts/kv-cache-working-memory-llm-inference/">KV cache: the working memory of LLM inference&lt;/a>, where cache quantisation is mentioned as a savings lever; here we go into the whole method, model weights and cache, and into why each format does what it does.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>Quantising is a change of representation: instead of storing every model weight as a &lt;code>float16&lt;/code> or &lt;code>bfloat16&lt;/code> (2 bytes), it is stored as a short integer (1 byte INT8, half a byte INT4) with a scale factor that reconstructs a value close to the original. The price is loss of numerical precision; the reward is 2-4× less VRAM, 2-3× more throughput and, on Hopper and Blackwell, a radically lower compute cost because FP8/FP4 units execute in fewer cycles than BF16 ones. The four dominant formats in May 2026 are &lt;strong>FP8&lt;/strong> (E4M3/E5M2, datacenter), &lt;strong>INT4 GPTQ&lt;/strong> (Hessian-aware reconstruction), &lt;strong>INT4 AWQ&lt;/strong> (activation-aware) and &lt;strong>GGUF&lt;/strong> (the llama.cpp family). Each has a sweet spot: FP8 when the datacenter is Hopper/Blackwell and quality matters; GPTQ and AWQ when serving runs on Ampere/Ada and 4 bits are mandatory; GGUF when the target is edge or a consumer GPU. This post explains the minimum maths, the algorithms behind each format, what each one loses measured in perplexity and MMLU, and how it all applies on a 4090 versus an H100 cluster.&lt;/p>
&lt;h2 id="you-are-here-deploy">You are here: DEPLOY&lt;/h2>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 90" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="you are here: Deploy">
&lt;style>.box{stroke:#444;stroke-width:1.4;rx:6}.active{fill:#7ad88f;stroke-width:3}.idle{fill:#f4f4f4}.lbl{font:600 12px sans-serif;fill:#222}.arr{stroke:#666;stroke-width:1.4;fill:none;marker-end:url(#qm)}.cyc{stroke:#888;stroke-width:1.2;fill:none;stroke-dasharray:4 2;marker-end:url(#qm)}&lt;/style>
&lt;defs>&lt;marker id="qm" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;text x="390" y="20" text-anchor="middle" class="lbl">You are here: DEPLOY · weight and KV cache quantization&lt;/text>
&lt;rect x="30" y="35" width="110" height="35" class="box idle"/>&lt;text x="85" y="58" text-anchor="middle" class="lbl">1 · Data&lt;/text>
&lt;rect x="155" y="35" width="110" height="35" class="box idle"/>&lt;text x="210" y="58" text-anchor="middle" class="lbl">2 · Tune&lt;/text>
&lt;rect x="280" y="35" width="110" height="35" class="box idle"/>&lt;text x="335" y="58" text-anchor="middle" class="lbl">3 · Eval&lt;/text>
&lt;rect x="405" y="35" width="110" height="35" class="box active"/>&lt;text x="460" y="58" text-anchor="middle" class="lbl">4 · Deploy&lt;/text>
&lt;rect x="530" y="35" width="110" height="35" class="box idle"/>&lt;text x="585" y="58" text-anchor="middle" class="lbl">5 · Observe&lt;/text>
&lt;rect x="655" y="35" width="110" height="35" class="box idle"/>&lt;text x="710" y="58" text-anchor="middle" class="lbl">6 · Retrain&lt;/text>
&lt;path class="arr" d="M140,52 L155,52"/>&lt;path class="arr" d="M265,52 L280,52"/>&lt;path class="arr" d="M390,52 L405,52"/>&lt;path class="arr" d="M515,52 L530,52"/>&lt;path class="arr" d="M640,52 L655,52"/>
&lt;path class="cyc" d="M710,72 L710,82 L85,82 L85,72"/>
&lt;/svg>
&lt;/div>
&lt;h2 id="the-analogy-jpeg-with-an-edge-detector">The analogy: JPEG with an edge detector&lt;/h2>
&lt;p>A JPEG compresses an image by reducing the precision with which pixels are stored, but it does not reduce it uniformly. Where there is flat sky, thousands of very similar pixels, it throws away detail without anyone noticing. Where there is a sharp edge, the outline of a face, it keeps the fidelity. The trick is to detect which parts are sensitive before compressing.&lt;/p>
&lt;p>Quantization of an LLM works the same way. You do not take every model weight and say &amp;ldquo;all of them in 4 bits&amp;rdquo;. Some weights are very important, projection weights that move the output a lot when they change, and others are less so. Modern techniques (GPTQ, AWQ) are basically &lt;strong>edge detectors&lt;/strong>: they identify which weights can be quantised aggressively and which need more bits or special treatment, and they apply the quantisation with that information.&lt;/p>
&lt;p>The analogy holds up in three details:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Calibration with a small dataset&lt;/strong> = the phase in which the JPEG encoder analyses the image before choosing blocks.&lt;/li>
&lt;li>&lt;strong>A block of 128 weights with a shared scale&lt;/strong> = the JPEG 8×8 block with its DCT.&lt;/li>
&lt;li>&lt;strong>Outliers preserved with more precision&lt;/strong> = the high frequencies of an edge are preserved more than the flat areas.&lt;/li>
&lt;/ul>
&lt;p>From there on, what follows is the maths and the operational detail.&lt;/p>
&lt;h2 id="the-minimum-maths-scale-and-zero-point">The minimum maths: scale and zero-point&lt;/h2>
&lt;p>Quantising a weight vector &lt;code>w ∈ ℝ^n&lt;/code> (in BF16) to INT4 means finding two things:&lt;/p>
&lt;ul>
&lt;li>A &lt;strong>scale&lt;/strong> &lt;code>s ∈ ℝ&lt;/code> (in BF16 or FP16).&lt;/li>
&lt;li>For each weight, an integer &lt;strong>code&lt;/strong> &lt;code>q ∈ {0, 1, ..., 15}&lt;/code> that fits in 4 bits.&lt;/li>
&lt;/ul>
&lt;p>And an approximate reconstruction formula:&lt;/p>
$$\hat{w}_i \approx s \cdot (q_i - z)$$
&lt;p>where &lt;code>z&lt;/code> is the &lt;strong>zero-point&lt;/strong> (the integer that defines which code represents the original zero). The zero-point exists in asymmetric INT4/INT8 so that half the range is not wasted on negative values when the weight distribution is not symmetric.&lt;/p>
&lt;p>The choice of &lt;code>s&lt;/code> and &lt;code>z&lt;/code> for a block of weights &lt;code>w_block&lt;/code>:&lt;/p>
$$s = \frac{\max(w_\text{block}) - \min(w_\text{block})}{2^{b} - 1}, \quad z = -\frac{\min(w_\text{block})}{s},$$
&lt;p>with &lt;code>b&lt;/code> = number of bits (4 in INT4). Encoding:&lt;/p>
$$q_i = \text{clip}\!\left(\text{round}\!\left(\frac{w_i}{s} + z\right),\, 0,\, 2^b - 1\right).$$
&lt;p>And decoding at inference time:&lt;/p>
$$\hat{w}_i = s \cdot (q_i - z).$$
&lt;h3 id="numerical-example">Numerical example&lt;/h3>
&lt;p>Take 8 real weights from a linear layer: &lt;code>w = [0.31, -0.12, 0.78, -0.05, 1.42, -0.91, 0.23, 0.66]&lt;/code>. We want to quantise to INT4 (16 levels).&lt;/p>
&lt;p>&lt;code>max = 1.42&lt;/code>, &lt;code>min = -0.91&lt;/code>. Range = 2.33.&lt;/p>
$$s = \frac{2.33}{15} \approx 0.1553, \quad z = -\frac{-0.91}{0.1553} \approx 5.86 \to 6.$$
&lt;p>Encoding of each weight:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;code>w_i&lt;/code>&lt;/th>
&lt;th>&lt;code>w_i/s + z&lt;/code>&lt;/th>
&lt;th>&lt;code>round&lt;/code>&lt;/th>
&lt;th>&lt;code>q_i&lt;/code>&lt;/th>
&lt;th>&lt;code>ŵ_i = s·(q-z)&lt;/code>&lt;/th>
&lt;th>error&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>0.31&lt;/td>
&lt;td>8.00&lt;/td>
&lt;td>8&lt;/td>
&lt;td>8&lt;/td>
&lt;td>0.311&lt;/td>
&lt;td>+0.001&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>-0.12&lt;/td>
&lt;td>5.23&lt;/td>
&lt;td>5&lt;/td>
&lt;td>5&lt;/td>
&lt;td>-0.155&lt;/td>
&lt;td>-0.035&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0.78&lt;/td>
&lt;td>11.02&lt;/td>
&lt;td>11&lt;/td>
&lt;td>11&lt;/td>
&lt;td>0.776&lt;/td>
&lt;td>-0.004&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>-0.05&lt;/td>
&lt;td>5.68&lt;/td>
&lt;td>6&lt;/td>
&lt;td>6&lt;/td>
&lt;td>0.000&lt;/td>
&lt;td>+0.050&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1.42&lt;/td>
&lt;td>15.14&lt;/td>
&lt;td>15&lt;/td>
&lt;td>15&lt;/td>
&lt;td>1.398&lt;/td>
&lt;td>-0.022&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>-0.91&lt;/td>
&lt;td>-0.06&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>-0.932&lt;/td>
&lt;td>-0.022&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0.23&lt;/td>
&lt;td>7.48&lt;/td>
&lt;td>7&lt;/td>
&lt;td>7&lt;/td>
&lt;td>0.155&lt;/td>
&lt;td>-0.075&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0.66&lt;/td>
&lt;td>10.25&lt;/td>
&lt;td>10&lt;/td>
&lt;td>10&lt;/td>
&lt;td>0.621&lt;/td>
&lt;td>-0.039&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Mean squared error: &lt;code>MSE ≈ 0.0015&lt;/code>. For a single layer with millions of weights, the aggregate effect is what calibration tries to minimise.&lt;/p>
&lt;p>Storage: instead of 8 values × 2 bytes = 16 bytes (BF16), we have 8 × 4 bits = 4 bytes + 2 bytes (BF16 scale) + 0.5 byte (INT4 zero-point) ≈ 6.5 bytes. 2.5× less, yet the useful data is still recoverable with a small error.&lt;/p>
&lt;h2 id="ptq-vs-qat-when-quantisation-happens">PTQ vs QAT: when quantisation happens&lt;/h2>
&lt;p>Two different operational regimes.&lt;/p>
&lt;p>&lt;strong>Post-Training Quantization (PTQ)&lt;/strong> is applied after training, on a model already trained in BF16/FP16. It reads a small dataset (typically 128-512 examples) to calibrate the scales, runs the quantisation algorithm (GPTQ, AWQ, and so on) and produces the quantised weights. &lt;strong>Cost&lt;/strong>: minutes to a few hours. &lt;strong>Typical loss&lt;/strong>: 0.05-0.3 PPL in perplexity (~0.5-2 % in MMLU) for INT4 with modern methods.&lt;/p>
&lt;p>&lt;strong>Quantization-Aware Training (QAT)&lt;/strong> introduces the quantisation operations inside the training loop. During training the model &amp;ldquo;sees&amp;rdquo; that its weights get quantised and learns to be robust to it. &lt;strong>Cost&lt;/strong>: retraining the model (expensive), but little is needed, a short fine-tune on top of the already quantised PTQ model. &lt;strong>Typical loss&lt;/strong>: ~0 (quantisation becomes indistinguishable from the original model).&lt;/p>
&lt;p>&lt;strong>When to use which:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>PTQ&lt;/strong> = default. 90 % of production cases. The model arrives in BF16, you quantise it in 1-2 hours on one GPU, you deploy it.&lt;/li>
&lt;li>&lt;strong>QAT&lt;/strong> = when PTQ loses too much and the difference matters (typical case: INT2/INT3, or sensitive models such as specific reasoning ones).&lt;/li>
&lt;/ul>
&lt;h2 id="the-dominant-formats-in-2026">The dominant formats in 2026&lt;/h2>
&lt;div class="diagram" style="max-width:760px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 760 360" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Dominant quantization formats">
&lt;style>
.bx{fill:#f8f8f8;stroke:#444;stroke-width:1.4;rx:8}
.b1{fill:#ffe6d6}
.b2{fill:#d6eaff}
.b3{fill:#d9f5d6}
.b4{fill:#fff5b0}
.t{font:700 13px sans-serif;fill:#222}
.s{font:400 11px sans-serif;fill:#555}
.h{font:700 14px sans-serif;fill:#222}
&lt;/style>
&lt;text x="380" y="22" text-anchor="middle" class="h">Map of weight quantization formats (May 2026)&lt;/text>
&lt;rect x="20" y="40" width="170" height="300" class="bx b1"/>
&lt;text x="105" y="62" text-anchor="middle" class="t">FP8 (E4M3/E5M2)&lt;/text>
&lt;text x="30" y="84" class="s">Datacenter / Hopper-Blackwell&lt;/text>
&lt;text x="30" y="102" class="s">— H100, H200, B200&lt;/text>
&lt;text x="30" y="120" class="s">— native in vLLM&lt;/text>
&lt;text x="30" y="138" class="s">— FP8 tensor core hardware&lt;/text>
&lt;text x="30" y="160" class="t">Loss:&lt;/text>
&lt;text x="30" y="176" class="s">— PPL: +0.02-0.05&lt;/text>
&lt;text x="30" y="192" class="s">— MMLU: -0.3-0.8 pp&lt;/text>
&lt;text x="30" y="214" class="t">Sweet spot:&lt;/text>
&lt;text x="30" y="230" class="s">Model serving in a modern&lt;/text>
&lt;text x="30" y="246" class="s">datacenter. Quality almost identical&lt;/text>
&lt;text x="30" y="262" class="s">to BF16, ~2× less VRAM.&lt;/text>
&lt;text x="30" y="286" class="t">vLLM command:&lt;/text>
&lt;text x="30" y="304" class="s" font-family="monospace">--quantization=fp8&lt;/text>
&lt;text x="30" y="320" class="s" font-family="monospace">--kv-cache-dtype=fp8&lt;/text>
&lt;rect x="200" y="40" width="170" height="300" class="bx b2"/>
&lt;text x="285" y="62" text-anchor="middle" class="t">INT4 GPTQ&lt;/text>
&lt;text x="210" y="84" class="s">Hessian-aware reconstruction&lt;/text>
&lt;text x="210" y="102" class="s">— Ampere/Ada/Hopper&lt;/text>
&lt;text x="210" y="120" class="s">— vLLM, TensorRT-LLM, ExLlama&lt;/text>
&lt;text x="210" y="138" class="s">— calibration: 128 samples&lt;/text>
&lt;text x="210" y="160" class="t">Loss:&lt;/text>
&lt;text x="210" y="176" class="s">— PPL: +0.15-0.30&lt;/text>
&lt;text x="210" y="192" class="s">— MMLU: -1.5-3 pp&lt;/text>
&lt;text x="210" y="214" class="t">Sweet spot:&lt;/text>
&lt;text x="210" y="230" class="s">GPU serving without FP8 (Ampere/Ada),&lt;/text>
&lt;text x="210" y="246" class="s">mid-size models (8-70B).&lt;/text>
&lt;text x="210" y="262" class="s">~4× less VRAM.&lt;/text>
&lt;text x="210" y="286" class="t">vLLM command:&lt;/text>
&lt;text x="210" y="304" class="s" font-family="monospace">--quantization=gptq&lt;/text>
&lt;text x="210" y="320" class="s" font-family="monospace">(model *-GPTQ-Int4)&lt;/text>
&lt;rect x="380" y="40" width="170" height="300" class="bx b3"/>
&lt;text x="465" y="62" text-anchor="middle" class="t">INT4 AWQ&lt;/text>
&lt;text x="390" y="84" class="s">Activation-aware salient weights&lt;/text>
&lt;text x="390" y="102" class="s">— Ampere/Ada/Hopper&lt;/text>
&lt;text x="390" y="120" class="s">— vLLM, TensorRT-LLM&lt;/text>
&lt;text x="390" y="138" class="s">— keeps 1 % outlier channels&lt;/text>
&lt;text x="390" y="160" class="t">Loss:&lt;/text>
&lt;text x="390" y="176" class="s">— PPL: +0.10-0.25&lt;/text>
&lt;text x="390" y="192" class="s">— MMLU: -1-2 pp&lt;/text>
&lt;text x="390" y="214" class="t">Sweet spot:&lt;/text>
&lt;text x="390" y="230" class="s">Preferred alternative to GPTQ&lt;/text>
&lt;text x="390" y="246" class="s">in 2026. Better quality&lt;/text>
&lt;text x="390" y="262" class="s">preservation at similar cost.&lt;/text>
&lt;text x="390" y="286" class="t">vLLM command:&lt;/text>
&lt;text x="390" y="304" class="s" font-family="monospace">--quantization=awq_marlin&lt;/text>
&lt;text x="390" y="320" class="s" font-family="monospace">(model *-AWQ-INT4)&lt;/text>
&lt;rect x="560" y="40" width="180" height="300" class="bx b4"/>
&lt;text x="650" y="62" text-anchor="middle" class="t">GGUF (llama.cpp)&lt;/text>
&lt;text x="570" y="84" class="s">Edge / consumer / CPU-friendly&lt;/text>
&lt;text x="570" y="102" class="s">— CPU, Apple Silicon,&lt;/text>
&lt;text x="570" y="120" class="s"> consumer GPU (4090, AMD)&lt;/text>
&lt;text x="570" y="138" class="s">— sub-formats: Q4_K_M, Q5_K_M…&lt;/text>
&lt;text x="570" y="160" class="t">Loss (Q4_K_M):&lt;/text>
&lt;text x="570" y="176" class="s">— PPL: +0.20-0.40&lt;/text>
&lt;text x="570" y="192" class="s">— MMLU: -2-4 pp&lt;/text>
&lt;text x="570" y="214" class="t">Sweet spot:&lt;/text>
&lt;text x="570" y="230" class="s">Any non-CUDA deploy or one&lt;/text>
&lt;text x="570" y="246" class="s">with limited VRAM. Ollama, LMStudio.&lt;/text>
&lt;text x="570" y="262" class="s">~4× less VRAM/RAM.&lt;/text>
&lt;text x="570" y="286" class="t">Command:&lt;/text>
&lt;text x="570" y="304" class="s" font-family="monospace">ollama run llama3:8b-q4_K_M&lt;/text>
&lt;text x="570" y="320" class="s" font-family="monospace">llama.cpp --model *.gguf&lt;/text>
&lt;/svg>
&lt;/div>
&lt;h2 id="fp8-the-hopperblackwell-datacenter-format">FP8: the Hopper/Blackwell datacenter format&lt;/h2>
&lt;p>FP8 is not &amp;ldquo;INT8 + sign&amp;rdquo;: these are two 8-bit floating point formats.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>E4M3&lt;/strong> (4 exponent bits, 3 mantissa bits): range ±448, reasonable precision around ±1.0. Typically used for &lt;strong>weights&lt;/strong> and for &lt;strong>activations in most layers&lt;/strong>.&lt;/li>
&lt;li>&lt;strong>E5M2&lt;/strong> (5 exponent, 2 mantissa): range ±57,344, lower precision. Used for &lt;strong>gradients&lt;/strong> during training or for activations with large outliers at inference time.&lt;/li>
&lt;/ul>
&lt;p>Why FP8 left INT8 behind in the datacenter: the tensor cores on H100/H200/B200 execute FP8 operations natively with 2× the throughput of BF16 and 4× that of FP16. And because FP8 preserves logarithmic dynamics (just like FP16), matrices with values spread out in magnitude, typical of transformers, quantise with less error than with INT8.&lt;/p>
&lt;p>The loss measured in production is minimal: for a Llama 3.1 70B FP8 versus BF16, perplexity rises by ~0.03 and MMLU drops by ~0.5 points. It is the default option in any modern deployment on H100/B200.&lt;/p>
&lt;h3 id="microscaling-nvfp4-and-mxfp4">Microscaling: NVFP4 and MXFP4&lt;/h3>
&lt;p>Blackwell (B100/B200, 2025) introduces &lt;strong>NVFP4&lt;/strong> and &lt;strong>MXFP4&lt;/strong>, 4-bit formats with small-block scaling (typically 16 or 32 elements per scale, against 128 in INT4 GPTQ). The scale is FP8 instead of FP16/BF16, which reduces storage further.&lt;/p>
&lt;p>The result: 4 bits with quality close to FP8. In 2026 NVFP4 is becoming the default option for very large models (200B+) on Blackwell clusters. For 4×H100 SXM, Hopper rather than Blackwell, FP8 remains the sweet spot.&lt;/p>
&lt;h2 id="int4-gptq-vs-awq">INT4: GPTQ vs AWQ&lt;/h2>
&lt;p>The two algorithms that dominate 4-bit quantisation solve the same problem with different strategies.&lt;/p>
&lt;h3 id="gptq-frantar-et-al-2022">GPTQ (Frantar et al. 2022)&lt;/h3>
&lt;p>The idea: layer-by-layer quantisation, explicitly minimising the error at the output of each linear layer using information from the Hessian matrix (the second derivative of the loss). For each layer:&lt;/p>
&lt;ol>
&lt;li>Estimate the Hessian &lt;code>H = X^T X&lt;/code> where &lt;code>X&lt;/code> are the calibration activations.&lt;/li>
&lt;li>Quantise one weight at a time in order (typically the most sensitive one first).&lt;/li>
&lt;li>&lt;strong>Update the remaining weights&lt;/strong> to compensate for the error of the weight just quantised.&lt;/li>
&lt;/ol>
&lt;p>Step 3 is what makes GPTQ better than naive round-to-nearest: weights compensate for their neighbours&amp;rsquo; errors. The official implementation quantises a Llama 3 70B in ~3-4 hours on one H100 with 128 calibration samples.&lt;/p>
&lt;h3 id="awq-lin-et-al-2023">AWQ (Lin et al. 2023)&lt;/h3>
&lt;p>The AWQ observation: within a layer, not all channels (weight columns) are equally important. Roughly &lt;strong>1 % of the channels&lt;/strong> account for most of the impact on the activations. AWQ identifies them by measuring the mean magnitude of the activations that multiply them, and scales them before quantising so they are better preserved.&lt;/p>
&lt;p>Concretely: if a channel &lt;code>c&lt;/code> has large mean activations, AWQ multiplies that channel&amp;rsquo;s weights by a factor &lt;code>s_c&lt;/code> before quantising, and when quantising the input to the next layer it divides by &lt;code>s_c&lt;/code>. The maths cancels out, but the important weights end up with more resolution inside the INT4 range. No retraining, no Hessian, faster than GPTQ (~1-2 h for 70B on an H100).&lt;/p>
&lt;h3 id="which-one-to-choose">Which one to choose&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Criterion&lt;/th>
&lt;th>GPTQ&lt;/th>
&lt;th>AWQ&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Quantisation speed&lt;/td>
&lt;td>Slower&lt;/td>
&lt;td>Faster&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Quality preserved in INT4&lt;/td>
&lt;td>Good&lt;/td>
&lt;td>Slightly better (~0.05-0.1 PPL)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Supported hardware&lt;/td>
&lt;td>Broad (Ampere+)&lt;/td>
&lt;td>Broad (Ampere+)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Ecosystem&lt;/td>
&lt;td>Mature, widely integrated&lt;/td>
&lt;td>More recent, gaining ground&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Default in 2026&lt;/td>
&lt;td>When GPTQ artefacts already exist&lt;/td>
&lt;td>Default for new quantisations&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The practical rule in May 2026: &lt;strong>AWQ by default&lt;/strong> for new INT4. &lt;strong>GPTQ&lt;/strong> when a GPTQ-Int4 artefact published by the community already meets your requirements.&lt;/p>
&lt;h2 id="gguf-the-llamacpp-ecosystem">GGUF: the llama.cpp ecosystem&lt;/h2>
&lt;p>GGUF is not a quantisation algorithm, it is a file format, and a whole tooling ecosystem, around the llama.cpp runtime.&lt;/p>
&lt;p>Its value: universal compatibility. The same GGUF file runs on:&lt;/p>
&lt;ul>
&lt;li>Pure CPU (Intel/AMD x86, ARM).&lt;/li>
&lt;li>Apple Silicon (M1/M2/M3/M4) with Metal acceleration.&lt;/li>
&lt;li>Consumer GPUs (RTX, AMD Radeon) with layer offload to VRAM.&lt;/li>
&lt;li>Edge devices (Jetson, ARM phones).&lt;/li>
&lt;/ul>
&lt;p>That is what llama.cpp allows and vLLM/TensorRT-LLM do not. The trade-off: lower peak throughput on datacenter GPUs than vLLM.&lt;/p>
&lt;p>The most used GGUF sub-formats in 2026:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Sub-format&lt;/th>
&lt;th>Effective bits&lt;/th>
&lt;th>Relative quality&lt;/th>
&lt;th>Typical use&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>Q8_0&lt;/code>&lt;/td>
&lt;td>8.5&lt;/td>
&lt;td>Almost lossless&lt;/td>
&lt;td>Baseline validation&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Q6_K&lt;/code>&lt;/td>
&lt;td>6.6&lt;/td>
&lt;td>Very small loss&lt;/td>
&lt;td>High quality + savings&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Q5_K_M&lt;/code>&lt;/td>
&lt;td>5.7&lt;/td>
&lt;td>Small loss&lt;/td>
&lt;td>Quality/size sweet spot&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Q4_K_M&lt;/code>&lt;/td>
&lt;td>4.8&lt;/td>
&lt;td>Moderate loss&lt;/td>
&lt;td>&lt;strong>Consumer default&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Q4_K_S&lt;/code>&lt;/td>
&lt;td>4.5&lt;/td>
&lt;td>Moderate-to-high loss&lt;/td>
&lt;td>When Q4_K_M does not fit&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Q3_K_M&lt;/code>&lt;/td>
&lt;td>3.9&lt;/td>
&lt;td>Noticeable loss&lt;/td>
&lt;td>Very constrained hardware&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Q2_K&lt;/code>&lt;/td>
&lt;td>3.3&lt;/td>
&lt;td>Large loss&lt;/td>
&lt;td>Last resort&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The &lt;code>_K_M&lt;/code> suffix indicates the degree of mixing: inside the file, certain layers (typically &lt;code>attention.wv&lt;/code>, &lt;code>feed_forward.w2&lt;/code>) are stored with more bits than others. It is the equivalent of the JPEG &amp;ldquo;edge detector&amp;rdquo; applied layer by layer with a pre-established heuristic.&lt;/p>
&lt;h2 id="kv-cache-quantization">KV cache quantization&lt;/h2>
&lt;p>Quantising the model weights is half the problem. The &lt;strong>KV cache&lt;/strong>, covered in detail in &lt;a href="https://blog.lo0.es/en/posts/kv-cache-working-memory-llm-inference/">KV cache: the working memory&lt;/a>, typically consumes 20-50 % of VRAM in production with concurrency. Quantising the cache is a lever too:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>--kv-cache-dtype=auto&lt;/code>&lt;/strong> (BF16/FP16, the default). 2 bytes per dimension × num_heads × head_dim × 2 (K and V).&lt;/li>
&lt;li>&lt;strong>&lt;code>--kv-cache-dtype=fp8&lt;/code>&lt;/strong> (E4M3 or E5M2 depending on hardware). 1 byte. Halves the cache with a typical loss of &amp;lt; 0.5 % on quality benchmarks.&lt;/li>
&lt;li>&lt;strong>&lt;code>--kv-cache-dtype=int4&lt;/code>&lt;/strong> (with blocks of 128). 0.5 bytes plus scale overhead. Divides the cache by ~3.5. Measurable loss (1-2 %) but acceptable with long contexts.&lt;/li>
&lt;/ul>
&lt;p>KV cache quantisation is &lt;strong>orthogonal&lt;/strong> to weight quantisation: you can have BF16 weights and an FP8 cache, or INT4 weights and an FP8 cache, and so on. The dominant combination in 2026 on H100: &lt;strong>FP8 weights + FP8 cache&lt;/strong>, which is almost indistinguishable from BF16 in quality and doubles concurrency capacity.&lt;/p>
&lt;h2 id="measured-quality-loss-llama-31-70b-instruct-reference">Measured quality loss (Llama 3.1 70B Instruct, reference)&lt;/h2>
&lt;p>A representative table for Llama 3.1 70B Instruct with a WikiText-2 calibration dataset (128 samples). Figures aggregated from public sources; they may vary by ±0.05 PPL and ±0.5 MMLU depending on implementation and seed.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Format&lt;/th>
&lt;th>Model VRAM&lt;/th>
&lt;th>Perplexity (WikiText-2)&lt;/th>
&lt;th>MMLU (5-shot)&lt;/th>
&lt;th>Relative speed (H100)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>BF16 (baseline)&lt;/td>
&lt;td>140 GB&lt;/td>
&lt;td>4.85&lt;/td>
&lt;td>82.1&lt;/td>
&lt;td>1.00×&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>FP8 (E4M3)&lt;/td>
&lt;td>70 GB&lt;/td>
&lt;td>4.87 (+0.02)&lt;/td>
&lt;td>81.6 (-0.5)&lt;/td>
&lt;td>1.85×&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>INT8 SmoothQuant&lt;/td>
&lt;td>70 GB&lt;/td>
&lt;td>4.92 (+0.07)&lt;/td>
&lt;td>81.0 (-1.1)&lt;/td>
&lt;td>1.65×&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>INT4 AWQ&lt;/td>
&lt;td>35 GB&lt;/td>
&lt;td>4.99 (+0.14)&lt;/td>
&lt;td>80.4 (-1.7)&lt;/td>
&lt;td>2.50×&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>INT4 GPTQ&lt;/td>
&lt;td>35 GB&lt;/td>
&lt;td>5.05 (+0.20)&lt;/td>
&lt;td>80.0 (-2.1)&lt;/td>
&lt;td>2.40×&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>GGUF Q5_K_M&lt;/td>
&lt;td>49 GB&lt;/td>
&lt;td>4.94 (+0.09)&lt;/td>
&lt;td>81.1 (-1.0)&lt;/td>
&lt;td>n/a (llama.cpp)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>GGUF Q4_K_M&lt;/td>
&lt;td>42 GB&lt;/td>
&lt;td>5.08 (+0.23)&lt;/td>
&lt;td>79.8 (-2.3)&lt;/td>
&lt;td>n/a (llama.cpp)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>GGUF Q3_K_M&lt;/td>
&lt;td>33 GB&lt;/td>
&lt;td>5.45 (+0.60)&lt;/td>
&lt;td>77.5 (-4.6)&lt;/td>
&lt;td>n/a (llama.cpp)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Three lessons worth keeping:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>FP8 is almost free in quality terms&lt;/strong>. If your hardware supports it, there is no debate.&lt;/li>
&lt;li>&lt;strong>INT4 AWQ is noticeably better than INT4 GPTQ&lt;/strong> in preserved quality, at comparable speed.&lt;/li>
&lt;li>&lt;strong>Q3 is already in measurable-loss territory&lt;/strong>; Q2 should no longer be used except for experiments or extreme demos.&lt;/li>
&lt;/ol>
&lt;h2 id="implications-for-on-premise-hardware">Implications for on-premise hardware&lt;/h2>
&lt;h3 id="on-an-rtx-4090-24-gb-ada-lovelace-no-native-fp8">On an RTX 4090 (24 GB, Ada Lovelace, no native FP8)&lt;/h3>
&lt;p>Llama 3.1 8B Instruct fits comfortably in BF16 (16 GB), but that leaves little headroom for a KV cache under concurrency. The usual sweet spot:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Llama 3.1 8B AWQ-INT4&lt;/strong>: ~5 GB of weights, 19 GB free for the KV cache → 4-8 concurrent sessions with a moderate context.&lt;/li>
&lt;li>&lt;strong>Llama 3 70B GGUF Q4_K_M&lt;/strong>: ~42 GB. &lt;strong>It does not fit on a whole 4090&lt;/strong>; it requires CPU offload with llama.cpp (slow decode but workable for a single user).&lt;/li>
&lt;li>&lt;strong>Llama 3 70B AWQ-INT4 with TP=2 (two 4090s)&lt;/strong>: ~17 GB per GPU → it fits and leaves headroom.&lt;/li>
&lt;/ul>
&lt;p>The 4090 &lt;strong>does not support native FP8&lt;/strong> (Ada Lovelace has the instruction but not Hopper&amp;rsquo;s accelerated throughput). In practice, FP8 on a 4090 works but without the speed gain: the sensible choice is INT4 AWQ.&lt;/p>
&lt;h3 id="on-a-generic-4h100-sxm-cluster-320-gb-nvlink-native-fp8">On a generic 4×H100 SXM cluster (320 GB, NVLink, native FP8)&lt;/h3>
&lt;p>Here FP8 shines:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Llama 3.1 70B FP8&lt;/strong> with TP=2: ~35 GB/GPU. Comfortable, leaving a huge amount of room for the KV cache → dozens of concurrent sessions.&lt;/li>
&lt;li>&lt;strong>Llama 3.1 405B FP8&lt;/strong> with TP=4: ~200 GB/GPU. It just fits, with prefill+decode in the same pool.&lt;/li>
&lt;li>&lt;strong>Llama 3.1 405B INT4 AWQ&lt;/strong> with TP=2: ~100 GB/GPU. It allows serving the large model without saturating the cluster; there is headroom left for the cache and for serving another model at the same time.&lt;/li>
&lt;/ul>
&lt;p>The rule of thumb on an H100 cluster in 2026: &lt;strong>FP8 if quality matters and the model fits; INT4 AWQ if the model does not fit in FP8 or if you want more concurrency at the cost of 1-2 MMLU points&lt;/strong>.&lt;/p>
&lt;h2 id="what-we-have-not-covered-upcoming-articles">What we have not covered (upcoming articles)&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Speculative decoding&lt;/strong>: the other big acceleration lever in inference. Orthogonal to quantization, it multiplies the speedup.&lt;/li>
&lt;li>&lt;strong>MoE quantization&lt;/strong>: Mixture-of-Experts models (Mixtral, DeepSeek V3, Qwen3-235B-A22B) have different quantisation patterns, experts are not quantised uniformly and there is dynamic routing.&lt;/li>
&lt;li>&lt;strong>Calibration dataset matters&lt;/strong>: how to choose the 128-512 calibration samples. The common mistake of grabbing a random dataset off the internet, and how to avoid it.&lt;/li>
&lt;li>&lt;strong>Multimodal quantization&lt;/strong>: vision-language models have heterogeneous layers (a CNN vision encoder, a transformer language model) that need separate treatment.&lt;/li>
&lt;/ul>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/kv-cache-working-memory-llm-inference/">KV cache: the working memory that holds up LLM inference&lt;/a> — the cache is also quantised with the same formats (FP8, INT4); this post goes into the detail of how, and with what loss.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/disaggregated-serving-prefill-decode-specialised-pods/">Disaggregated serving: prefill and decode in specialised pods&lt;/a> — the two pools can be quantised &lt;strong>asymmetrically&lt;/strong>: prefill with less compression (quality), decode with more compression (throughput).&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/continuous-fine-tuning-production-real-traffic-deployed-adapter/">Continuous fine-tuning in production&lt;/a> — QLoRA uses NF4, a specific INT4 variant for the base model during training. It is the cousin of the inference formats described here.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/modern-alignment-dpo-kto-orpo-simpo/">Modern alignment: DPO, KTO, ORPO, SimPO&lt;/a> — the reference model (&lt;code>π_ref&lt;/code>) in DPO can be quantised to FP8 to free up VRAM. The quality of the reference matters less than that of the trained model.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/six-stage-llmops-pipeline-architecture-and-deep-dive/">The six-stage LLMOps pipeline&lt;/a> — the master map where Deploy is stage 4.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/speculative-decoding-fundamentals-maths-state-of-play/">Speculative decoding: the secretary who anticipates what the boss is going to say&lt;/a> — a lever orthogonal to and multiplicative with quantization. The draft is usually left in BF16 even when the target is in FP8/INT4, because quantising it degrades α and kills the speedup.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/flashattention-librarian-io-awareness-async-blackwell-asymmetry/">FlashAttention v1/v2/v3/v4: the librarian who never clears the desk&lt;/a> — FA3 does FP8 attention with block quantization and incoherent processing to preserve precision; FA4 combines that with Blackwell NVFP4 weights. The two quantisation layers (weights and attention) are applied at different moments in the kernel.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/moe-inference-fundamentals-expert-parallel-deepseek-economics/">MoE inference: the call centre with 256 specialists&lt;/a> — DeepSeek-V3 671B FP8 (~685 GB) only fits on modest clusters thanks to aggressive quantisation of the routed experts; NVFP4 on expert weights + FP8 attention is the peak setup on NVL72.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/multi-lora-serving-shared-base-concurrent-adapters-sgmv-kernel/">Multi-LoRA serving: one translator with a thousand glossaries&lt;/a> — the canonical stack is an FP8/INT4 base plus BF16 adapters. Quantising the base does not just free memory, it multiplies the economics: the same cluster goes from serving dozens to hundreds of concurrent adapters.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/optimising-decode-vllm-squeezing-every-token-small-hardware/">Optimising decode in vLLM&lt;/a> — the combination of AWQ (INT4 weights) + FP8 KV cache + speculative decoding is the reference config for squeezing an RTX 4090; the concrete numbers are laid out there.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/optimising-prefill-vllm-knobs-ttft-will-not-forgive/">Optimising prefill in vLLM&lt;/a> — &lt;code>--kv-cache-dtype fp8&lt;/code> and &lt;code>--calculate-kv-scales&lt;/code> are the parameters that enable KV cache quantisation in vLLM, with the precision trade-off measured on long contexts.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-pruning-cutting-without-amputating/">Pruning LLM models&lt;/a> — an orthogonal lever: quantising reduces the precision of the weights pruning has kept; combined, 50% Wanda sparsity + AWQ INT4 shrinks the model to ~1/8 of its original size.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/knowledge-distillation-teaching-small-model-think-like-big-one/">Knowledge distillation&lt;/a> — the previous step in the pipeline: you distil first (a new, smaller model), you quantise afterwards (the same model, more efficient); the two techniques attack different dimensions.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/capacity-planning-on-premise-llm-inference/">Capacity planning for on-premise LLM inference&lt;/a> — quantization is the first sizing lever when the BF16 arithmetic does not work out; the sensitivity table there quantifies the savings in VRAM and in TPOT.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/test-time-quantization-without-calibration-dataset/">Test-time quantization: quantising on the fly&lt;/a> — the alternative with no calibration dataset: it derives activation-aware scales at inference time, at the cost of runtime overhead that weighs more heavily on small models.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/aggressive-quantisation-4-bit-to-ternary/">Aggressive quantisation: from 4-bit to ternary&lt;/a> — what happens below 4-bit, where naive PTQ collapses and co-design is needed: codebooks (AQLM, QuIP#, QTIP), QAT and native ternary (BitNet b1.58).&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-serving-engines-pareto-frontier-comparison/">Serving engines compared (vLLM/SGLang/TRT-LLM/Dynamo)&lt;/a> — how each engine supports the quantization formats described here (FP8, AWQ, GPTQ, GGUF) and what difference in goodput it makes to pick the one that implements them best.&lt;/li>
&lt;/ul>
&lt;h2 id="references">References&lt;/h2>
&lt;ul>
&lt;li>Frantar, E., Ashkboos, S., Hoefler, T., Alistarh, D. &lt;em>GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers&lt;/em> (ICLR 2023).&lt;/li>
&lt;li>Lin, J., Tang, J., Tang, H., Yang, S., Dang, X., Han, S. &lt;em>AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration&lt;/em> (MLSys 2024).&lt;/li>
&lt;li>Dettmers, T., Pagnoni, A., Holtzman, A., Zettlemoyer, L. &lt;em>QLoRA: Efficient Finetuning of Quantized LLMs&lt;/em> (NeurIPS 2023). Introduces NF4 and double quantization.&lt;/li>
&lt;li>Xiao, G., Lin, J., Seznec, M., Wu, H., Demouth, J., Han, S. &lt;em>SmoothQuant: Accurate and Efficient Post-Training Quantization for Large Language Models&lt;/em> (ICML 2023).&lt;/li>
&lt;li>NVIDIA. &lt;em>FP8 Formats for Deep Learning&lt;/em> — E4M3/E5M2 white paper: &lt;a href="https://arxiv.org/abs/2209.05433">https://arxiv.org/abs/2209.05433&lt;/a>.&lt;/li>
&lt;li>Rouhani, B. et al. &lt;em>Microscaling Data Formats for Deep Learning&lt;/em> — MXFP4/MXFP8: &lt;a href="https://arxiv.org/abs/2310.10537">https://arxiv.org/abs/2310.10537&lt;/a>.&lt;/li>
&lt;li>llama.cpp GGUF spec: &lt;a href="https://github.com/ggerganov/llama.cpp/blob/master/docs/gguf.md">https://github.com/ggerganov/llama.cpp/blob/master/docs/gguf.md&lt;/a>.&lt;/li>
&lt;li>vLLM quantization docs: &lt;a href="https://docs.vllm.ai/en/latest/quantization/">https://docs.vllm.ai/en/latest/quantization/&lt;/a>.&lt;/li>
&lt;/ul></description></item></channel></rss>