Effizientere Inference durch Model Quantization
Schnellere Inferenz auf CPUs dank weniger präziser Modellparameter.
In Teil eins dieser zweiteiligen Serie über effizientere Inferenz auf CPUs habe ich verschiedene Methoden verglichen, die Modelle optimieren, indem sie diese in Graphen umwandeln, effizientere Operationen nutzen und mehrere Modell-Layer zu einem einzigen fusionieren. Keine dieser Methoden ändert jedoch die Parameter des Modells. Daher beeinflussen sie nicht deren Genauigkeit. Wenn man jedoch bereit ist, ein wenig Genauigkeit zu opfern, gibt es noch weitere Optimierungen für die Inferenz von Modellen. In diesem Artikel werde ich solche Methoden erläutern und einige experimentelle Resultate präsentieren.
Überblick über Optimierungsstrategien
Quantisierung ist der wohl bekannteste Ansatz, die Modellparameter zu verändern, um eine schnellere Inferenz zu ermöglichen, aber es ist nur einer von vielen. Ein weiterer beliebter Ansatz ist das Modell-Pruning. Wie der Name schon sagt, werden beim Modell-Pruning ganze Parameter entfernt, oft diejenigen mit der geringsten Magnitude. Dieser Ansatz funktioniert sowohl während des Trainings durch iteratives Entfernen von Parametern als auch nach dem Training, indem eine festgelegte Anzahl von Parametern entfernt wird, mit anschliessendem Fine-Tuning, um den durch das Pruning verursachten Qualitätsverlust auszugleichen. Komplexere Methoden, die zweite Ableitungen in ihrem Pruning-Kriterium verwenden, funktionieren sogar ohne ein solches Fine-Tuning, sind jedoch rechenintensiv. Alle diese Pruning-Methoden haben gemeinsam, dass sie zu wesentlich kleineren und schnelleren Modellen führen mit einer geringen Einbusse in der Genauigkeit.
Ein weiterer verbreiteter Ansatz ist die Modell-Destillation, bei der ein kleineres Modell auf den Softmax-Output eines grösseren Teacher-Modells trainiert wird. Die Prämisse ist, dass die Wahrscheinlichkeitsverteilung der Outputs des Teachers wertvolle Informationen enthält, die dem destillierten Modell helfen, besser zu generalisieren als beim Training auf gelabelten Daten alleine. Daher wählt man üblicherweise auch eine hohe (> 1) Temperatur für den Softmax Layer, um mehr Variabilität im Output des Teachers zu erhalten. Dieser Ansatz erfordert jedoch das Training eines komplett neuen, wenn auch kleineren Modells.
Es gibt noch weitere Methoden, wie die Low-Rank Approximation von linearen Layern und Kombinationen aus zwei oder mehr dieser Methoden. Ich habe mich jedoch entschieden, mich in meinen Experimenten ganz auf die Post-Training Quantisierung zu konzentrieren, da es sich bei ihr um eine Ad-hoc-Methode handelt, die für viele Modelltypen anwendbar ist.
Quantisierung
Bei einer Quantisierung der Modellparameter werden die Fliesskommazahlen in ganze Zahlen umgewandelt, die mit weniger Bits repräsentiert werden, was sowohl weniger Speicher braucht als auch weniger Rechenleistung beim Addieren und Multiplizieren. Dieser Prozess führt jedoch zu einem Präzisionsverlust und erfordert eine sorgfältige Kalibrierung.Eine Quantisierung Einführung
Ein einfacher, aber leistungsstarker
Quantisierungsalgorithmus ist die uniforme affine Quantisierung. Er wandelt eine Fliesskommazahl x in eine Ganzzahl p um:
In der obigen Gleichung ist S der Skalierfaktor und Z der Nullpunkt. Der Skalierfaktor bestimmt die Auflösung der Quantisierung. Wenn er auf beispielsweise auf 40 eingestellt ist, werden Fliesskommazahlen im Bereich [x - 20, x + 20] in denselben quantisierten Wert p umgewandelt. Daher ist eine sorgfältige Auswahl des Skalierfaktors entscheidend für den Erfolg einer Quantisierung. Sind beispielsweise alle Eingaben im Bereich [min, max], kann der Skalierfaktor für eine 8-Bit-Quantisierung nach dieser Formel festgelegt werden:
Der zweite Parameter, der Nullpunkt, dient dazu, die Null auf eine quantisierte Zahl abzubilden, was notwendig ist für Modelle, die ein Padding mit Null verwenden, für Inputs unterschiedlicher Länge. Für Zahlen im oben erwähnten Bereich, kann Z wie folgt berechnet werden:
Für einen Bereich von [-2, 3] wäre S beispielsweise 0,0196 und Z wäre 102. Mit der Formel für affine Quantisierung wird -1 zu 5 quantisiert. Das Konvertieren von Zahlen ausserhalb dieses Bereichs ist nicht möglich, da alle Werte unter -2 auf 0 und alle Werte über 3 auf 255 abgebildet werden.
Herausforderungen der Quantization
Die Wahl einer guten Quantization Method für Floating-Point Numbers ist nur die erste von vielen Herausforderungen. Eine weitere ist das Multiplizieren von zwei 8-Bit-Zahlen, was zu einer 16-Bit-Zahl führt, oder das noch komplexere Szenario der Matrix Multiplication, die eine Akkumulation in 32-Bit-Zahlen erfordert. Die Ergebnisse solcher Operationen müssen durch die Berechnung neuer Scales und Zero Points wieder auf eine neue 8-Bit-Repräsentation herunterskaliert werden. Um zudem die meisten Operationen der Matrix Multiplication zwischen 8-Bit-Zahlen zu halten, müssen zusätzliche Tricks angewendet werden, wie das Umschreiben der Gleichungen. Glücklicherweise kümmern sich Frameworks wie PyTorch um diese Implementierungsdetails.
Static vs Dynamic Quantization
PyTorch bietet zwei Wege zur Quantization eines Modells: Dynamic und Static Quantization. Bei der ersteren werden die Weights quantisiert und als solche gespeichert. Die Normalization Layers und Activations verbleiben jedoch im Floating Point und werden mit Floating-Point Operations berechnet. Mit anderen Worten: Nur die rechenintensiven Aufgaben wie der Linear Layer werden quantisiert. Dabei müssen die Inputs eines quantisierten Linear Layers on-the-fly quantisiert werden, indem ein passender Scale und Zero Point berechnet werden. Die Outputs dieses Linear Layers sind 32-Bit-Zahlen, das Ergebnis vieler akkumulierter 8-Bit-Multiplikationen. Daher müssen sie zurück in Floating-Point Numbers konvertiert werden, um an die nächsten Activation Functions weitergeleitet zu werden. Insgesamt funktioniert dieser Ansatz gut für Modelle mit großen linearen Blöcken, die den Rechen-Engpass (Bottleneck) darstellen, wie Transformers.
Bei der Static Quantization werden zudem die Layer miteinander verschmolzen (Fused), um die Anzahl solcher Downcasts zu reduzieren. Zum Beispiel wird die Sequenz Conv → BatchNorm → ReLU zu einem einzigen ConvReLU-Layer verschmolzen. Das Quantisieren dieser Activations erfordert jedoch deren Beobachtung, um gute Werte für den Scale und Zero Point zu finden. Daher ist eine zusätzliche Post-Training Calibration mit einem Calibration Set und eingefügten Observers an den Activation Layers erforderlich, welche die Statistiken zur Berechnung dieser Werte sammeln.Static Quantization kann nicht für Transformers verwendet werden, da Beobachtungen gezeigt haben, dass es gelegentlich Outlier Activations mit hohen Werten gibt, was die Auswahl eines Scales mit ausreichender Auflösung erschwert. Infolgedessen führt die Quantization der Activations zu einer Performanceverschlechterung. Jüngere Ansätze wie SmoothQuant konnten diese Probleme jedoch überwinden und ermöglichen die Quantization von Activation Functions ohne hohen Verlust an Accuracy.
Post Training Quantization vs. Quantization Aware Training
Die im vorherigen Abschnitt vorgestellten Methoden wurden alle nach dem Training angewendet und funktionieren gut für Quantizations bis hinunter zu 8 Bit. Um ein Modell auf 4 Bit oder weniger zu quantisieren, sind andere Ansätze erforderlich. Ein Ansatz ist Quantization Aware Training (QAT), das im Forward Pass des Trainings eine Fake Quantization anwendet. Fake Quantization führt die Affine Transformation durch, aber ihre Outputs werden nicht in Integers umgewandelt. Stattdessen bleiben sie Floating-Point Numbers (wenn die Quantization z. B. 17 ergeben würde, gibt die Fake Quantization 17.0 zurück). Solche Funktionen sind jedoch nicht differenzierbar. Daher müssen Tricks wie die Verwendung eines Straight-Through Estimators für die Gradients während des Backward Pass angewendet werden.
Neuere Ansätze wie AdaQuant erzielen eine bessere Post-Training Quantization Performance, indem sie die Quantization Layer für Layer durchführen, den optimalen Quantization Factor für jeden Layer auswählen und die Batch Normalization Layers nach der Quantization neu kalibrieren.
Experimente
Ich habe diese Quantization-Konzepte an den BERT- und ResNet-Modellen aus meinem vorherigen Artikel getestet. Für beide Modelle musste ich einen `QuantStub` und einen `DeQuantStub` am Anfang und Ende des Modells hinzufügen, um die Inputs zu quantisieren und zu dequantisieren. Da ich für beide Modelle die Static Quantization ausprobiert habe, musste ich zudem alle arithmetischen Operatoren in ihre quantisierten Äquivalente, die `FloatFunctional`-Operatoren, umwandeln. Dies war notwendig, da ich den Eager Quantization Mode verwendet habe. PyTorch ist derzeit dabei, die gesamte Arbeit zur Architekturoptimierung auf Torch AO zu verlagern, das eine graphbasierte Quantization bietet, die solche manuellen Änderungen überflüssig macht. Für das ResNet-Modell habe ich zudem versucht, alle Convolutional, Batch Normalization und ReLU Layers zu fusionieren. Dies führte jedoch zu einem erheblichen Verlust an Accuracy. Daher konnte ich nur die ersten beiden für alle außer der ersten Schicht fusionieren.
BERT
Für BERT zeige ich nur die Ergebnisse für die Dynamic Quantization, da die Static Quantization nicht funktionierte. In der Grafik unten können wir sehen, dass dies zu einer Beschleunigung von etwa 20 % über alle Batch Sizes hinweg führte.
Diese Beschleunigungen sind viel geringer als erwartet und führten auch dazu, dass das Modell Vorhersagen machte, die kaum genauer als zufälliges Raten waren. Die Ursache für beides ist wahrscheinlich ein Fehler, den ich bei der Eager Conversion von BERT in 8-Bit-Integers gemacht habe, da andere Arbeiten eine mehr als 3-fache Beschleunigung zeigten.
ResNet
Für ResNet habe ich Ergebnisse für die Static Quantization mit und ohne Layer Fusing. Die Beschleunigungen sind mit 39 % bzw. 34 % bei einer Batch Size von eins größer als bei BERT.
Wiederum sind die Beschleunigungen geringer als erwartet und stehen in keinem Verhältnis zu dem Aufwand, den die Einrichtung der Quantization dieser Modelle erforderte. Zumindest führte die Quantization beim ResNet-Modell nicht zu einer Performanceverschlechterung.
Fazit
PyTorch unterstützt die Quantization von Modellen. Die Verwendung der Eager-Implementierung erfordert jedoch die manuelle Anpassung des Modells für im schlimmsten Fall nur geringe Performancegewinne. Meine persönliche Empfehlung ist, zuerst die Modelllaufzeit zu optimieren, wie in Teil eins dieser Serie beschrieben. Nur wenn das Modell mit diesen Optimierungen noch nicht schnell genug läuft, würde ich eine Quantization in Betracht ziehen. Glücklicherweise kann die Quantization darauf aufbauend angewendet werden. Die Grafik unten zeigt beispielsweise, wie die Anwendung der Quantization auf die IPEX Optimization zu zusätzlichen Performancegewinnen führt.
Wenn Sie daran interessiert sind, diese Optimierungen selbst anzuwenden, werfen Sie einen Blick auf mein GitHub-Repository, das den Code für meine Experimente enthält: ML-Inference-Experiments.
More Efficient Inference Through Model Quantization © 2026 von Jeffrey Wigger lizenziert unter CC BY 4.0