2-Bit-Quantisierung: Qwen3-4B in nur 1,24 GB dank Trellis-Codes
Ein neuer 2-Bit-Quantisierer verkleinert Qwen3-4B von 8,05 GB auf 1,24 GB, ganz ohne Retraining. Das Verfahren wird anhand des Open-Source-Formats Pingala-8 auf Basis von llama.cpp erklärt und mit echten Benchmarks auf seine Qualität geprüft.

Qwen3-4B wird als 8,05 GB große Datei mit 16-Bit-Gewichten ausgeliefert. In diesem Beitrag zeigen wir, wie ein 2-Bit-Quantizer dasselbe Modell ohne Nachtraining auf 1,24 GB verkleinert (85 % kleiner) und wie man überprüft, ob das Ergebnis überhaupt brauchbar ist. Unser Praxisbeispiel ist Pingala-8, ein Open-Source-Format, das wir auf Basis von llama.cpp gebaut haben. Die meisten Ideen stammen nicht von uns: Die Rotation und das Runden kommen aus QuIP und QuIP#, das Trellis aus QTIP und Ungerboecks Modem-Codes, und ikawrakows IQ2_KT ist der nächste Verwandte. Wir zeigen außerdem, welcher Bestandteil weniger wichtig war, als wir erwartet hatten.
Bei 2 Bit pro Gewicht kann jedes Gewicht einen von 4 Werten annehmen. Jedes Gewicht wird auf den nächstgelegenen erlaubten Wert gerundet, und die Differenz ist der Fehler. Zwei Dinge sorgen dafür, dass dieser Fehler wehtut.
Er addiert sich auf. Jeder Ausgang einer Schicht ist eine Summe aus tausenden Produkten aus Gewicht mal Eingang. Die Rundungsfehler in diesen Produkten heben sich nicht von selbst auf und wandern durch Dutzende Schichten.
Ausreißer. Gewichte werden in kleinen Gruppen gespeichert, jede mit einem Skalierungsfaktor, sodass ihre 4 Stufen den Bereich der Gruppe abdecken. Nehmen wir die Gruppe [3.0, 0.2, -0.1, 0.1]. Um 3.0 zu erreichen, müssen die Stufen weit gespreizt sein, etwa {-3, -1, 1, 3}. Nun landen 0.2, -0.1 und 0.1 alle auf -1 oder +1, also mit einem Fehler von jeweils etwa 1, größer als die Gewichte selbst.
Ein guter 2-Bit-Quantizer muss Ausreißer entfernen, Fehler gegeneinander aufheben und mehr Präzision pro Bit herausholen. Das sind die drei Kniffe.
Wenn R eine orthogonale Matrix ist (R^T R = I), dann gilt W x = (W R^T)(R x). Wir können die rotierten Gewichte W R^T speichern und R auf der Eingangsseite anwenden, und die Schicht berechnet exakt dasselbe Ergebnis. Eine Rotation erhält Längen, verändert aber die Koordinaten, sodass sie einen großen Wert auf viele Koordinaten verteilen kann.
Die übliche Wahl ist eine Hadamard-Matrix mit zufälligen Vorzeichen, weil sie sich günstig anwenden lässt. Die 4x4-Version, skaliert mit 1/2:
[ 1 1 1 1 ] 1/2 * [ 1 -1 1 -1 ] [ 1 1 -1 -1 ] [ 1 -1 -1 1 ] Enter fullscreen mode Exit fullscreen mode Wendet man sie auf [3.0, 0.2, -0.1, 0.1] an, erhält man [1.6, 1.3, 1.6, 1.5]. Die Summe der Quadrate beträgt beide Male 9.06, aber der größte Wert liegt jetzt nahe bei den anderen, statt 15- bis 30-mal größer zu sein, und vier gleichmäßig verteilte Stufen passen gut dazu. Echte Schichten rotieren viele Koordinaten gleichzeitig, und die rotierten Gewichte sehen grob wie eine Glockenkurve ohne extreme Ausreißer aus.
Einfaches Runden behandelt jedes Gewicht für sich. Die Schicht interessiert sich aber für den Ausgang, die Summe der w_i * x_i.
Spielzeugbeispiel: Drei Gewichte [0.4, 0.4, 0.4] multiplizieren Eingänge, die fast immer gleich sind, sodass die Schicht effektiv ihre Summe verwendet, 1.2. Rundet man jedes auf die nächste ganze Zahl, ergibt sich [0, 0, 0], also eine Abweichung von 1.2. Mit Feedback wird das erste 0.4 auf 0 gerundet und sein Fehler (0.4) auf das nächste Gewicht übertragen, das zu 0.8 wird und auf 1 gerundet wird; dieser Fehler (-0.2) wandert zum dritten Gewicht, das zu 0.2 wird und auf 0 gerundet wird. Das Ergebnis [0, 1, 0] summiert sich zu 1, also nur 0.2 Abweichung.
LDLQ aus dem QuIP-Paper ist die prinzipielle Version davon. Es quantisiert die Spalten einer Gewichtsmatrix einzeln und schiebt den Fehler jeder Spalte auf die noch nicht quantisierten Spalten. Wie viel wohin weitergegeben wird, ergibt sich aus H, dem Mittelwert von x x^T über Eingänge, die durch den Lauf von Beispieltext durch das Modell gesammelt wurden. Wären die Eingänge unkorreliert, wäre H diagonal, und LDLQ würde zu einfachem Runden reduziert.
IQ2_XXS, llama.cpps kleinstes verbreitetes 2-Bit-Format, verwendet ein Codebuch: Jede Gruppe von Gewichten wird nacheinander auf den nächsten Eintrag einer festen Tabelle abgebildet. Trellis-codierte Quantisierung, wie QTIP und IQ2_KT sie nutzen, fügt stattdessen Gedächtnis hinzu.
Der Encoder trägt einen kleinen Zustand von einer Gruppe zur nächsten. Der Zustand entscheidet, welche Teilmenge (ein sogenanntes Coset) von Werten die nächste Gruppe verwenden darf. Die für diese Gruppe geschriebenen Bits wählen einen Wert innerhalb der Teilmenge und legen zugleich den nächsten Zustand fest. Der Decoder verfolgt den Zustand ohne Zusatzkosten, sodass das Format doppelt so viele Werte nutzen kann, wie die Bits allein adressieren könnten. Der Preis: Die heutige Entscheidung bestimmt das Menü von morgen, man kann also nicht jede Gruppe für sich runden.
Ein Spielzeugbeispiel mit 1 Bit pro Gewicht zeigt beide Seiten. Vier Stufen werden in zwei Cosets aufgeteilt: Zustand 0 darf {-1.5, 0.5} verwenden, Zustand 1 darf {-0.5, 1.5} verwenden. Bit 0 wählt den unteren Wert, Bit 1 den höheren, und das geschriebene Bit wird zum nächsten Zustand. Startet man in Zustand 0 und kodiert [-0.4, 0.1, 1.5]:
Greedy-Runden nimmt im ersten Schritt den nächsten Wert und bleibt in den Schritten 2 und 3 an den falschen Menüs hängen. Der beste Pfad akzeptiert einen schlechteren ersten Schritt, um später die richtigen Menüs zu bekommen. Einfaches 1-Bit-Runden auf {-1, 1} erreicht mit denselben 3 Bits einen Wert von 1.42: Greedy-Trellis-Runden verliert dagegen, der gesuchte Pfad gewinnt. Auf echten Daten ist der Vorteil ein Mittelwert über lange Sequenzen, keine Garantie bei jedem Schritt.
Die Suche ist der Viterbi-Algorithmus: dynamische Programmierung, die für jeden Zustand den billigsten dorthin führenden Pfad behält. Seine Kosten wachsen linear mit der Länge, und er läuft einmalig, offline, beim Erstellen der Datei.
Pingala-8 verwendet Ungerboecks 8-Zustands-Trellis mit Mengenpartitionierung über Symbole aus 4 Gewichten, bei etwa 2 Bit pro Gewicht. Das Dekodieren ist günstig (Bits lesen, Zustand aktualisieren, Werte nachschlagen), und ein fusionierter CUDA-Kernel erledigt das innerhalb des Matrix-Vektor-Produkts. Teure Suche einmal, billiges Dekodieren bei jedem Token: Diese Asymmetrie ist der Grund, warum Trellis-Codierung für Inferenz geeignet ist.
Quantisierungsergebnisse lassen sich leicht versehentlich schöner darstellen, als sie sind. Unsere Regeln, die für jeden Vergleich gelten:
Perplexität gegen Dateigröße. B4 ist die veröffentlichte Pingala-8-Datei. Links: Qwen3-4B, wobei "post hoc" das abgestimmte Ergebnis markiert. Rechts: Llama-3.1-8B, bewertet ohne Start-Token in jedem Fenster; die folgenden Zahlen verwenden eines.
Qwen3-4B, WikiText-2-Perplexität bei Kontext 4096 (niedriger ist besser):
Über die fünf Quizfragen hinweg erreicht Pingala-8 im Durchschnitt 50.8 gegenüber 41.9 für IQ2_XXS (+21 %) und behält 90 % des 8-Bit-Durchschnitts von Q8_0 (56.3).
Geschwindigkeit für einen einzelnen Nutzer (Batch 1), Qwen3-4B auf der RTX 5070 Ti Laptop:
Das Generieren von jeweils einem Token bedeutet vor allem, jedes Gewicht aus dem Speicher zu lesen, sodass weniger Bytes mehr Token pro Sekunde liefern, während das Dekodieren günstig bleibt. Das Lesen eines Prompts verarbeitet viele Token gleichzeitig und ist durch die Rechenleistung begrenzt, hier zeigt sich der zusätzliche Dekodieraufwand.
Auf Llama-3.1-8B (eine 2,39 GB große Datei) erreicht Pingala-8 8.44 gegenüber 9.36 für IQ2_KT bei gleicher Größe.
Eine Ablation entfernt einen Bestandteil und misst den Schaden. Wir haben das Trellis-Gedächtnis bei gleichen Bytes entfernt. Die Kosten betrugen 0.076 Nats pro Token an zusätzlichem Verlust, etwa 8 % höhere Perplexität (e^0.076 ist etwa 1.08).
Die Lehre für alle, die einen 2-Bit-Quantizer bauen: Rotation und Rundung mit Fehler-Feedback tragen den Großteil der Qualität. Das Trellis-Gedächtnis erklärt den größten Teil des kleinen Abstands zwischen Formaten, die ohnehin nah beieinanderliegen, aber nur wenig der großen Lücke zu einem einfachen Codebuch-Format. Wir hatten erwartet, dass das Trellis die Hauptgeschichte sei; die Ablation sagt etwas anderes, weshalb es sich lohnt, sie durchzuführen.
# perplexity: gleiche Textdatei und gleicher Kontext ergeben dieselben Fenster./llama-perplexity -m model.gguf -f wiki.test.raw -c 4096 -ngl 99 # batch-1 speed: prompt processing (pp) und text generation (tg)./llama-bench -m model.gguf -p 512 -n 128 Enter fullscreen mode Exit fullscreen mode Warum der Name Pingalas Chandahsutra, ein altes Sanskritwerk über poetische Metrik, listet die 8 Muster aus drei langen oder kurzen Silben auf, die Ganas. Unser Trellis hat 8 Zustände, deshalb haben wir das Format nach ihm benannt. Der Name ist eine Widmung; die Trellis-Labels stammen von Ungerboeck.
Hintergrundlektüre: QuIP, QuIP#, QTIP sowie die trellis-codierte Quantisierung von Marcellin und Fischer.
Dieser Artikel wurde mit Hilfe von KI verfasst; jede Zahl stammt aus unseren gemessenen Logs und dem Paper.
Automatisch übersetzt. Das Original auf Englisch ist unten aufklappbar.
Original (Englisch) anzeigen
Qwen3-4B ships as an 8.05 GB file of 16-bit weights. In this post we walk through how a 2-bit quantizer gets the same model down to 1.24 GB (85% smaller) without retraining, and how to check whether the result is any good. Our worked example is Pingala-8, an open-source format we built on llama.cpp. Most of the ideas are not ours: the rotation and rounding come from QuIP and QuIP#, the trellis from QTIP and Ungerboeck's modem codes, and ikawrakow's IQ2_KT is its closest relative. We will also show which piece mattered less than we expected.
At 2 bits per weight, each weight can take one of 4 values. Every weight is rounded to its nearest allowed value, and the difference is error. Two things make that error hurt.
It adds up. Each output of a layer is a sum of thousands of weight-times-input products. The rounding errors in those products do not cancel on their own, and they travel through dozens of layers.
Outliers. Weights are stored in small groups, each with a scale so its 4 levels cover the group's range. Take the group [3.0, 0.2, -0.1, 0.1]. To reach 3.0, the levels must be spread wide, say {-3, -1, 1, 3}. Now 0.2, -0.1 and 0.1 all land on -1 or +1, an error of about 1 each, larger than the weights themselves.
A good 2-bit quantizer must remove outliers, make errors cancel, and get more precision per bit. Those are the three tricks.
If R is an orthogonal matrix (R^T R = I), then W x = (W R^T)(R x). We can store the rotated weights W R^T, apply R on the input side, and the layer computes exactly the same result. A rotation keeps lengths but changes coordinates, so it can spread one large value across many coordinates.
The usual choice is a Hadamard matrix with random signs, because it is cheap to apply. The 4x4 version, scaled by 1/2:
[ 1 1 1 1 ] 1/2 * [ 1 -1 1 -1 ] [ 1 1 -1 -1 ] [ 1 -1 -1 1 ] Enter fullscreen mode Exit fullscreen mode Apply it to [3.0, 0.2, -0.1, 0.1] and you get [1.6, 1.3, 1.6, 1.5]. The sum of squares is 9.06 both times, but the largest value is now close to the others instead of 15 to 30 times bigger, and four evenly spaced levels fit it well. Real layers rotate many coordinates at once, and the rotated weights look roughly like a bell curve without extreme outliers.
Plain rounding treats each weight on its own. The layer cares about the output, the sum of w_i * x_i.
Toy example: three weights [0.4, 0.4, 0.4] multiply inputs that are almost always equal, so the layer effectively uses their sum, 1.2. Rounding each to the nearest integer gives [0, 0, 0], off by 1.2. With feedback, the first 0.4 rounds to 0 and its error (0.4) is carried to the next weight, which becomes 0.8 and rounds to 1; that error (-0.2) is carried to the third, which becomes 0.2 and rounds to 0. The result [0, 1, 0] sums to 1, off by only 0.2.
LDLQ, from the QuIP paper, is the principled version. It quantizes the columns of a weight matrix one at a time and pushes each column's error onto the columns not yet quantized. How much goes where comes from H, the average of x x^T over inputs collected by running sample text through the model. If the inputs were uncorrelated, H would be diagonal and LDLQ would reduce to plain rounding.
IQ2_XXS, llama.cpp's smallest common 2-bit format, uses a codebook: each group of weights becomes the nearest entry of a fixed table, one group at a time. Trellis-coded quantization, used by QTIP and IQ2_KT, adds memory instead.
The encoder carries a small state from one group to the next. The state decides which subset (a "coset") of values the next group may use. The bits written for that group pick a value inside the subset and also set the next state. The decoder tracks the state for free, so the format can use twice as many values as the bits alone could address. The price: today's choice decides tomorrow's menu, so you cannot round each group on its own.
A toy with 1 bit per weight shows both sides. Four levels are split into two cosets: state 0 may use {-1.5, 0.5} and state 1 may use {-0.5, 1.5}. Bit 0 picks the lower value, bit 1 the higher, and the bit you wrote becomes the next state. Start in state 0 and encode [-0.4, 0.1, 1.5]:
Greedy rounding takes the closest value at step 1 and is stuck with the wrong menus at steps 2 and 3. The best path accepts a worse first step to get the right menus later. Plain 1-bit rounding to {-1, 1}, with the same 3 bits, scores 1.42: greedy trellis rounding loses to it, the searched path beats it. On real data the advantage is an average over long sequences, not a guarantee at every step.
The search is the Viterbi algorithm: dynamic programming that keeps, for every state, the cheapest path ending there. Its cost grows linearly with length, and it runs once, offline, when the file is made.
Pingala-8 uses Ungerboeck's 8-state set-partitioned trellis over symbols of 4 weights, at about 2 bits per weight. Decoding is cheap (read bits, update state, look up values), and a fused CUDA kernel does it inside the matrix-vector product. Expensive search once, cheap decode on every token: that asymmetry is why trellis coding suits inference.
Quantization results are easy to flatter by accident. Our rules, which work for any comparison:
Perplexity against file size. B4 is the released Pingala-8 file. Left: Qwen3-4B, where "post hoc" marks the tuned result. Right: Llama-3.1-8B scored without a start token in each window; the figures below add one.
Qwen3-4B, WikiText-2 perplexity at context 4096 (lower is better):
On the five quizzes, Pingala-8 averages 50.8 against 41.9 for IQ2_XXS (+21%) and keeps 90% of the 8-bit Q8_0 average (56.3).
Speed for one user (batch 1), Qwen3-4B on the RTX 5070 Ti Laptop:
Generating one token at a time mostly means reading every weight from memory, so fewer bytes give more tokens per second while decoding stays cheap. Reading a prompt processes many tokens at once and is limited by compute, which is where the extra decode work shows.
On Llama-3.1-8B (a 2.39 GB file), Pingala-8 scores 8.44 against 9.36 for IQ2_KT at the same size.
An ablation removes one part and measures the damage. We removed the trellis memory at equal bytes. The cost was 0.076 nats per token of extra loss, about 8% higher perplexity (e^0.076 is about 1.08).
The lesson for anyone building a 2-bit quantizer: rotation and error-feedback rounding carry most of the quality. Trellis memory explains most of the small margin between formats that are already close, and little of the large gap to a plain codebook format. We expected the trellis to be the main story; the ablation says otherwise, which is why it is worth running.
# perplexity: same text file and context give the same windows./llama-perplexity -m model.gguf -f wiki.test.raw -c 4096 -ngl 99 # batch-1 speed: prompt processing (pp) and text generation (tg)./llama-bench -m model.gguf -p 512 -n 128 Enter fullscreen mode Exit fullscreen mode Why the name Pingala's Chandahsutra, an ancient Sanskrit work on poetic metre, lists the 8 patterns of three long or short syllables, the ganas. Our trellis has 8 states, so we named the format after him. The name is a credit; the trellis labels are Ungerboeck's.
Background reading: QuIP, QuIP#, QTIP, and Marcellin and Fischer's trellis-coded quantization.
This article was written with the help of AI; every number comes from our measured logs and the paper.