DFloat11: Verlustfreie GPU-Inferenzkompression, die LLMs auf 70 % der Größe schrumpfen lässt
(arxiv.org)- Große LLMs sind wegen der Grenzen des GPU-Speichers schwer bereitzustellen; DFloat11 reduziert BFloat16-Gewichte auf etwa 70 % ihrer Größe und erhält dabei eine bitgenau identische Ausgabe zum Original
- Der Kernpunkt ist, dass der 8-Bit-Exponent von BFloat16 in der Praxis nur etwa 2,6 Bit Information enthält; Vorzeichen und Mantisse bleiben erhalten, und nur der Exponent wird per Huffman coding komprimiert
- Da Codierung mit variabler Länge auf GPUs leicht zum Flaschenhals wird, setzt DFloat11 auf hierarchische LUTs, einen zweistufigen Kernel und Kompression/Dekompression auf Transformer-Block-Ebene, zugeschnitten auf parallele Inferenz
- Bei Llama 3.3, Qwen 3, Mistral 3, DeepSeek R1 Distilled, FLUX.1, Stable Diffusion 3.5 usw. wurde eine Reduktion der Modellgröße um rund 30 % bei Erhalt der Originalausgabe bestätigt
- Gegenüber der Alternative CPU-Offload liegt der Durchsatz bei der Token-Generierung 2,3- bis 46,2-mal höher, und verlustfreie Inferenz für Llama 3.1 405B mit 810 GB wird auf einem einzelnen Node mit 8×80GB-GPUs möglich
Das von DFloat11 adressierte Speicherproblem
- Foundation Models wie LLMs und Diffusion Models wachsen schnell weiter und lassen sich auf Hardware mit begrenztem Speicher nur schwer effizient bereitstellen
- Llama 3.1 405B speichert 405 Milliarden Parameter im BFloat16-Format und benötigt für die vollständige Inferenz rund 810 GB Speicher
- Das übersteigt die Kapazität eines typischen High-End-GPU-Servers mit 8×80GB-GPUs
- Werden mehrere Nodes nötig, steigen die Bereitstellungskosten und die Zugangshürden deutlich
- DFloat11 zielt darauf ab, BFloat16-Modelle auf etwa 70 % ihrer ursprünglichen Größe zu komprimieren und dabei die Genauigkeit bei jeder Aufgabe zu 100 % zu erhalten
Ein anderer Ansatz als verlustbehaftete Quantisierung
- Quantisierung ist ein verlustbehaftetes Kompressionsverfahren, das den Speicherbedarf durch geringere Gewichtspräzision reduziert
- Sie kann den Speicherverbrauch stark senken und die Inferenz beschleunigen, erzeugt aber Approximationsfehler
- Der Genauigkeitsverlust hängt vom Basismodell, der Quantisierungsmethode, dem Evaluierungs-Benchmark und der Ziel-Bitbreite ab und ist daher schwer vorherzusagen
- Wird beispielsweise 8-bit SmoothQuant auf DeepSeek-R1-Distill-Qwen-1.5B angewendet, sinkt die durchschnittliche Genauigkeit bei Reasoning-Tasks um 9,09 %
- Selbst wenn die Gesamtmetriken ähnlich bleiben, kann ein quantisiertes Modell ein anderes Antwortverhalten als das Original zeigen
- Dutta et al. beobachteten das Phänomen der flips, bei dem richtige Antworten falsch werden oder umgekehrt
- Das mit W8A16 GPTQ quantisierte Qwen2-1.5B verliert bei GSM8K 8-shot zwar nur 0,3 % Genauigkeit, aber bei 6,37 % der Antworten wechselt der Status zwischen richtig und falsch
- In Bereichen wie Finanzen oder Medizin können solche Ausgabeänderungen quantisierter Modelle Anforderungen an Regulierung und Zuverlässigkeit schwer erfüllbar machen
- Bisherige verlustfreie Modellkompression zielte vor allem auf effizientere Checkpoint-Speicherung, kürzere Download-Zeiten in Model Hubs oder Spezialhardware wie FPGAs; für allgemeine GPU-Inferenz war der Nutzen gering
Kompressionspotenzial im BFloat16-Exponent
- BFloat16 teilt 16 Bit in 1 Bit Vorzeichen, 8 Bit Exponent und 7 Bit Mantisse auf
- Ausgangspunkt von DFloat11 ist eine Shannon-Entropieanalyse der BFloat16-Bestandteile von LLM-Gewichten
- Die Entropie von Vorzeichen und Mantisse liegt jeweils nahe an ihrer Bitbreite, daher ist das Kompressionspotenzial begrenzt
- Dem Exponenten sind zwar 8 Bit zugewiesen, seine Entropie beträgt jedoch nur etwa 2,6 Bit
- Die Verteilung der Exponentenwerte ist stark unausgewogen
- Von 256 möglichen 8-Bit-Werten werden nur etwa 40 verwendet
- Die übrigen Werte treten nicht auf
- Auch die Häufigkeitsrangfolge fällt schnell ab
- Dank der niedrigen Entropie eignet sich der Exponent für verlustfreie Kompression; dabei lassen sich etwa 5,4 Bit Exponenteninformation komprimieren
Das DFloat11-Format
- DFloat11 oder DF11 ist ein Gleitkommaformat mit variabler Länge, das nur den Exponenten von BFloat16-Gewichten per Entropiecodierung komprimiert
- Der Huffman-Baum wird auf Basis der Exponentenverteilung der Modellgewichte aufgebaut
- Häufig auftretende Exponentenwerte erhalten kurze Codes
- Seltene Werte erhalten lange Codes
- Vorzeichen und Mantisse bleiben unverändert
- Der Exponent wird bitgepackt im Byte-Array
EncodedExponentgespeichert - Vorzeichen und Mantisse werden separat im Byte-Array
PackedSignMantissagespeichert
- Der Exponent wird bitgepackt im Byte-Array
- Dadurch schrumpfen BFloat16-Gewichte im Mittel auf etwa 11 Bit, und der ursprüngliche BFloat16-Wert lässt sich ohne Präzisionsverlust rekonstruieren
Auf GPU-Inferenz zugeschnittene Dekompression
- Entropiecodierte Gewichte verwenden Codierung mit variabler Länge und können deshalb nicht direkt in die Matrixmultiplikation eingehen
- Die benötigte Weight Matrix muss unmittelbar zurück in das ursprüngliche BFloat16-Format dekomprimiert werden
- Nach der Matrixmultiplikation wird die BFloat16-Matrix zur Einsparung von GPU-Speicher sofort verworfen
- Gewöhnliches Huffman-Decoding durchsucht den Baum bitweise sequentiell und passt daher schlecht zur parallelen GPU-Architektur
- Wenn ein einzelner Thread die Dekompression übernimmt, sinken GPU-Auslastung und Latenz leidet
Decoding auf Basis hierarchischer LUTs
- DFloat11 verwendet statt der Traversierung des Huffman-Baums Lookup-Table-basiertes Decoding
- Ist die maximale Huffman-Code-Länge L, benötigt eine einzelne LUT die Größe
2^L- Bei LLMs liegt L typischerweise im Bereich 24–32
- Eine LUT mit
2^32Einträgen lässt sich kaum im GPU-SRAM unterbringen
- Um das zu vermeiden, wird der Huffman-Baum in überlappungsfreie Teilbäume der Höhe 8 zerlegt, und jeder Teilbaum wird zu einer kompakten LUT mit 256 Einträgen
- In der hierarchischen LUT müssen einige Einträge als Verweise auf untergeordnete LUTs dienen
- Dabei wird ausgenutzt, dass viele Exponentenwerte bei LLMs ungenutzt bleiben
- Ungenutzte Exponentenwerte im Bereich 240–255 werden als interne Pointer wiederverwendet
- Diese Werte stehen für sehr große Größenordnungen im Bereich von ±2^113 bis ±2^128 und kommen bei LLM-Gewichten nicht vor
- In Experimenten lag die Zahl kompakter LUTs k für den Huffman-Baum von BFloat16-Exponenten im Bereich 4–8
- Zusammen mit
CodeLengthswerden maximal(8 + 1) × 256 bytesSpeicher benötigt - Diese Größe passt in SRAM und ermöglicht schnelle wiederholte Lookups
- Zusammen mit
Zweistufiger GPU-Kernel und zusätzliche Metadaten
- Jeder GPU-Thread übernimmt einen zusammenhängenden Bereich von n Bytes des codierten Exponenten
- In den Experimenten wurde n = 8 verwendet
- Der Thread decodiert die Huffman-Codes, die innerhalb seines Bereichs beginnen
- Durch Codes variabler Länge entstehen zwei Probleme
- Die exakte Start-Bit-Position jedes Threads ist nicht eindeutig
- Außer beim ersten Thread ist der Ausgabeindex des decodierten Elements schwer zu bestimmen
- Das erste Problem wird mit dem Array Gaps gelöst
Gapsenthält einen Eintrag pro Thread- Jeder Eintrag beschreibt den Bit-Offset des ersten gültigen Huffman-Codes relativ zum Start-Byte des Threads
- Da die maximale Code-Länge 32 Bit beträgt, liegt der Offset im Bereich
[0, 31]und wird in 5 Bit gespeichert
- Das Problem der Ausgabeposition wird durch Speicherung nur auf Thread-Block-Ebene gelöst, um den Speicher-Overhead zu senken
- Würde man pro Thread eine 32-Bit-Ausgabeposition speichern, wäre der Overhead bei Zehntausenden Threads pro Weight Matrix groß
- DFloat11 speichert daher nur die Ausgabeposition des ersten Elements jedes Thread-Blocks
- Der Kernel arbeitet in zwei Phasen
- In Phase 1 decodiert jeder Thread seinen Bereich, zählt aber nur die Elementanzahl und schreibt nichts in HBM
- Die Threads innerhalb eines Blocks berechnen dann mit dem Blelloch-Algorithmus per Prefix Sum die Ausgabeposition für jeden Thread
- In Phase 2 wird derselbe Bereich erneut decodiert, und die decodierten Werte werden gemäß der berechneten Position in den SRAM-Schreibpuffer geschrieben
- Der codierte Exponent wird vor dem ersten Durchlauf in SRAM geladen, um doppelte Zugriffe auf den globalen Speicher zu vermeiden
- Nachdem alle decodierten Exponenten in SRAM geschrieben wurden, erfolgt ein einziger coalesced write nach HBM
Dekompression auf Transformer-Block-Ebene
- Die Dekompression einer einzelnen Weight Matrix kann zu klein sein, um die GPU-Ressourcen voll auszulasten
- Mit wachsender Matrixgröße verbessert sich der Dekompressionsdurchsatz von DFloat11
- Um den Durchsatz zu erhöhen und Latenz zu verbergen, werden mehrere Matrizen gemeinsam dekomprimiert
- Alle DFloat11-Weight-Matrizen innerhalb eines Transformer-Blocks werden als ein Batch dekomprimiert
- Diese Batch-Dekompression erfolgt unmittelbar vor dem Forward Pass des jeweiligen Transformer-Blocks
- Auch Token Embedding und Language Modeling Head des LLM werden komprimiert
- Diese Matrizen sind groß genug, um die GPU-Ressourcen auszulasten, und benötigen daher kein separates Batching
Evaluation und praktischer Effekt
- DFloat11 wurde an LLMs und Diffusion Transformers evaluiert, darunter Llama 3, Qwen 3, Mistral 3, DeepSeek R1 Distilled, FLUX.1 und Stable Diffusion 3.5
- Die Ergebnisse zeigen eine Reduktion der Modellgröße um etwa 30 % bei vollständigem Erhalt der Originalausgabe
- Die Ausgabe ist bit-for-bit identisch mit der des Originalmodells
- Da es sich nicht um verlustbehaftete Kompression handelt, bleibt die ursprüngliche Präzision der BFloat16-Gewichte erhalten
- Verglichen mit der Alternative, Teile eines unkomprimierten Modells zur Einhaltung des Speicherbudgets auf die CPU auszulagern, erreicht DFloat11 bei der Token-Generierung einen 2,3- bis 46,2-mal höheren Durchsatz
- Bei festem GPU-Speicherbudget ermöglicht es 5,7- bis 14,9-mal längere Generation Length als ein unkomprimiertes Modell
- Llama 3.1 405B ist ein 810-GB-Modell, doch DFloat11 ermöglicht verlustfreie Inferenz auf einem einzelnen Node mit 8×80GB-A100-GPUs
- Das zeigt, dass sich die für den Betrieb von Llama-3.1-405B nötigen Hardware-Anforderungen halbieren lassen, ohne Genauigkeitsverlust bei der Inferenz
1 Kommentare
Hacker-News-Kommentare
Das ist lediglich eine Folge der Tatsache, dass der dynamische Bereich von bfloat16 sehr groß ist, in der Praxis aber nicht vollständig ausgenutzt wird.
Menschen mögen es, wenn Hyperparameter eher wie 0,01 aussehen und nicht wie 10^10, aber für jeden Exponenten lässt sich dieselbe relative Präzision nutzen. Wenn man die Hyperparameter eines Netzes, initialisierte Gewichte, Trainingsdaten usw. sämtlich um den Faktor 10^6 skaliert, verhält es sich im Großen und Ganzen ähnlich, weil der obere Bereich kaum genutzt wird. Einige Spezialfunktionen können allerdings Ausnahmen sein.
Die typische Entropie der bfloat16-Werte, die man bei Gewichten und Aktivierungen sieht, liegt bei etwa 10–12 Bit, und tatsächlich werden nur etwa 65–75 % des Wertebereichs genutzt. Vorzeichen- und Mantissenbits sind eher rauschartig und schwer zu komprimieren.
Diese Eigenschaft wurde sowohl im klassischen High-Performance Computing als auch in der KI schon mehrfach genutzt. Beispiele sind Arbeiten zur verlustfreien Komprimierung aus dem Labor von Martin Burtscher (https://userweb.cs.txstate.edu/~burtscher/), fpzip von LLNL (https://computing.llnl.gov/projects/fpzip) und meine Bibliothek dietgpu von 2021 (https://github.com/facebookresearch/dietgpu). Wir haben in großen GPU-Clustern vor der Übertragung sämtliche Daten – etwa Gradienten oder aus Backups geladene Gewichte – verlustfrei komprimiert und beim Empfang dekomprimiert. Dadurch sank die gesamte reale Trainingszeit um etwa 10 %, und da es verlustfrei war, blieben die Rechenergebnisse unverändert.
Außerdem ist rANS auf SIMD-artigen Befehlssätzen effizienter als Huffman-Codierung und auch einfacher zu implementieren. Auch bei DFloat11 muss vor arithmetischen Operationen dekomprimiert werden, wodurch sich Latenz- und Durchsatzverluste reduzieren lassen.
Quantisierung ist ein zentrales Werkzeug für Leute, die lokale LLMs ausführen, und normalerweise ist RAM der Flaschenhals. Mich würde auch interessieren, ob es bessere verlustfreie Komprimierung für BF16-Gewichte gibt.
DFloat11 sieht so aus, als ließe es sich relativ leicht in bestehende Quantisierungs-Workflows einfügen, aber der Kommentar klingt gegenüber dem Paper ziemlich skeptisch; ich würde gern verstehen, was ich übersehe.
In neuronalen Netzschichten werden Eingaben mit Gewichten multipliziert und addiert, und die Ausgabe wird zur Eingabe der nächsten Schicht; das kann sich über hundert Mal wiederholen. Wenn man die finale Ausgabeschicht erreicht, wäre dieser Faktor 10^6 mehrfach angewendet worden und würde sich auf Größenordnungen von 10^600 aufschaukeln.
Fälle, in denen DFloat11 mehr Tokens pro Sekunde erreichte, gab es nur im Vergleich zu Inferenz, bei der einige Schichten auf die CPU ausgelagert wurden.
Das ist der klassische Space-Speed-Trade-off der Informatik; ein kostenloses Mittagessen gibt es nicht.
Immerhin sind die Kosten fürs Abschneiden und Auffüllen mit Nullen gering.
Am auffälligsten sind die praktischen Konsequenzen. Dass ein 405B-Parameter-Modell verlustfrei auf einem einzelnen Node mit 8×80-GB-GPUs inferiert werden kann, ist ziemlich erstaunlich.
Für Forschungslabore und Startups, die Frontier-Modelle ohne enorme Infrastrukturkosten betreiben wollen, könnte das ein großer Enabler sein.
Ich bin dankbar, in einer so interessanten Zeit zu leben. Jedes Mal, wenn ich HN öffne, sehe ich neue Nachrichten zu Machine Learning und Transformer-Modellen.
Ich muss noch genauer hineinlesen, frage mich aber, ob llama.cpp zusammen mit cuBLAS eine Art Custom Kernel nutzt oder ob es einfach die cuBLAS-Kernel gut ausreizt.
Zwei Wochen? Zwei Monate? Zwei Tage? Zwei Minuten?
Manchmal stimmt alles davon. Wirklich eine interessante Zeit.
Wenn sich dieser Krieg der Gewichtsformate gelegt hat, kann Hardware so gebaut werden, dass sie das unterstützt. Welches Gewichtsformat sich auch als vernünftig optimal herausstellt: Man wird dafür optimierte Matrixmultiplikations-Hardware brauchen.
Bei realen Agenten-Anwendungsfällen ist es oft schwierig, Qualität, Kosten und Performance auszubalancieren. Diese Technik kann helfen, die Trade-offs zu vermeiden, die Quantisierungsverfahren mit sich bringen – einschließlich schwer vorhersagbarer Ergebnisse, wenn man Agentenkosten optimieren will.
Wenn man ein Modell mit DFloat11 auf günstigere GPUs bekommt, können die Kosteneinsparungen je nach Fall erheblich sein. Ich arbeite bei xmad.ai.
Verglichen mit der Alternative, wegen Speicherbeschränkungen Teile eines unkomprimierten Modells auf die CPU auszulagern, soll DFloat11 einen 1,9- bis 38,8-mal höheren Durchsatz bei der Token-Generierung erreichen. Bei einem festen GPU-Speicherbudget ermöglicht es eine 5,3- bis 13,17-mal längere Kontextlänge als ein unkomprimiertes Modell.
Allein wegen der Kontextlänge wirkt das schon nützlich, selbst wenn das Modell in den Speicher passt. Nach meinem grundlegenden Verständnis sind LLMs aber oft durch die Speicherbandbreite begrenzt; daher frage ich mich, ob sich auch die Tokens pro Sekunde verbessern, wenn alles auf der GPU liegt.
Im Paper wird behauptet, dass es auf einer A100 unter 200 GB/s bleibt, und den Benchmarks zufolge scheint es je nach GPU und Modell bei Batch-Größe 1 1,5- bis 4-mal langsamer zu sein. Wenn die Batch-Größe groß genug wird, verschwindet dieser Overhead natürlich größtenteils.
Andere verlustfreie Codecs können auf derselben Hardware 600 GB/s erreichen, es gibt also offenbar Raum für Verbesserungen. Die rohe Speicherbandbreite der A100 liegt allerdings bei 1,6 TB/s.
Bedeutet das, dass sich der Speicherbedarf nicht quantisierter LLMs allgemein um 30 % reduzieren lässt? Falls ja, wäre das ziemlich erheblich.
Wenn man es zu bestehender Quantisierung hinzufügen kann, ist es sicher interessant. K-Quantisierung verwendet aber bereits je nach Auswirkung auf die Perplexity insgesamt unterschiedliche Präzisionen pro Schicht. Q6 zum Beispiel mischt 4 Bit und 8 Bit, was der hier verwendeten Entropie-Metrik ähnelt. Berücksichtigt man noch eine kalibrierte imatrix, komprimiert es konzeptionell stärker, ähnlich wie bei einer FFT.
Ist das etwas anderes als ZipNN? https://arxiv.org/pdf/2411.05239
Ich sehe eine Erwähnung, verstehe aber nicht, ob es darauf basiert oder anders beziehungsweise besser ist.
LZ-artige Kompressoren scheinen für neuronale Netzwerkgewichte aber nicht besonders sinnvoll zu sein. Die Redundanz ist nicht so hoch wie bei den meisten textlastigen Daten mit vielen Wiederholungen, und wenn die Daten nicht sehr sparse sind, gibt es möglicherweise nicht genug nutzbare Wiederholungen, um den Wörterbuch-Overhead zu rechtfertigen.
Wenn man einen LZ-artigen Kompressor hinzufügt und ihn in den kritischen Pfad der Inferenz legt, wird die Dekomprimierung deutlich langsamer. Am besten wäre es, die Dekomprimierung mit dem Rechenkernel zu fusionieren. Man könnte es zum Beispiel wie ein GEMM aufbauen, bei dem jede Tile vor den arithmetischen Operationen dekomprimiert wird; je einfacher die Dekomprimierungsroutine ist, desto leichter ist das.
Es ist ziemlich beeindruckend zu sehen, wie schnell sich das alles bewegt. Gefühlt gibt es jede Woche eine neue Effizienztechnik oder ein Hardware-Upgrade.
Von solchen Verbesserungen lässt man sich leicht ablenken.
Kann man das auch auf neue Modelle anwenden? Wenn ich es nicht falsch verstehe, scheint der Code nur für die Inferenz gedacht zu sein.