Als Markdown anzeigen

Quantisierung

Die Quantisierung in LibreYOLO läuft vollständig in PyTorch: model.quantize() tauscht die Conv2d- und Linear-Module eines Modells gegen quantisierte Äquivalente aus und kalibriert sie. Das Ergebnis behält den normalen Vertrag aus predict, val, train und save, ein quantisiertes Modell wird also von denselben Validatoren bewertet wie ein Float-Modell.

Aufruf
model.quantize(recipe="int8", calib="coco128.yaml")
Befehl
libreyolo quantize --model M.pt --recipe int8 --calib coco128.yaml
Extra
Keines. Die Quantisierung läuft in PyTorch.
Familien
yolo9, rfdetr, birefnet, feynobg
Rezepte
fp16, bf16, fp8, int8, w4a16, w4a8, nvfp4, mxfp4, int2
Deployment-Artefakte
export(format="pt") für einen gepackten Checkpoint, export(format="onnx") für einen QDQ-INT8-Graphen

Installation

Die Quantisierung braucht kein Extra. Der Modultausch, der Kalibrierungsdurchlauf und die simulierte Arithmetik laufen alle in PyTorch, pip install libreyolo ist also die gesamte Voraussetzung. Die Deployment-Artefakte brauchen das, was ihr eigenes Format braucht, für den ONNX-Weg also libreyolo[onnx].

Quantisierung

Python
from libreyolo import LibreYOLO model = LibreYOLO("LibreYOLO9s.pt") # Strukturtausch plus Kalibrierung. calib ist ein kleiner Bildsatz OHNE# LABELS, nur vorwärts gelesen, um Bereiche und Skalen abzuleiten.qmodel = model.quantize(recipe="int8", calib="coco128.yaml", samples=128) print(qmodel.quant_info())qmodel.val(data="coco8.yaml")          # dieselben Validatoren wie bei Floatqmodel.save("LibreYOLO9s-int8.pt")     # Checkpoint trägt ein Quant-Manifest
CLI
libreyolo quantize --model LibreYOLO9s.pt --recipe int8 --calib coco128.yaml
Argumente
model.quantize(    recipe="int8",    calib="coco128.yaml",      # data.yaml-Pfad oder eingebauter Name; None überspringt sie    samples=128,               # maximale Anzahl Kalibrierungsbilder    batch=8,                   # Batch-Größe der Kalibrierung    algorithm="auto",          # auto und minmax sind gleich; percentile ist die Alternative    keep_high_precision=None,  # None nutzt die Regel der Familie    verbose=True,)

quantize() verändert das geladene Modell an Ort und Stelle und gibt es zurück. Gradienten sind nicht beteiligt: Der Tausch installiert quantisierte Module und der Kalibrierungsdurchlauf läuft nur vorwärts.

Der entstehende Checkpoint ist ein normaler LibreYOLO-Checkpoint mit angehängtem quant-Manifest, er lädt also mit intakter Struktur und intakten Skalen zurück:

Ein quantisierter Checkpoint lädt als solcher zurück
from libreyolo import LibreYOLO # Das Quant-Manifest baut die quantisierte Struktur und die Skalen# wieder auf, bevor die Gewichte geladen werden.qmodel = LibreYOLO("LibreYOLO9s-int8.pt")print(qmodel.quant_info())

Die Trainer-Checkpoints, die während eines QAT-Laufs geschrieben werden, tragen das Manifest ebenfalls, best.pt aus so einem Lauf ist also selbst ein quantisierter Checkpoint.

Rezepte

Unterstützt werden vier Familien: yolo9, rfdetr, birefnet und feynobg.

RezeptWas es tutFamilienKalibrierung
fp16Cast auf halbe Präzision mit einem float32-Ein- und -Ausgabevertrag. Nur Inferenz.alle vierkeine
bf16Cast auf bfloat16, was den Exponentenbereich von float32 behält. Die Lösung, wenn fp16 auf einem Modell im DETR-Stil überläuft. Nur Inferenz.alle vierkeine
fp8E4M3-Gewichte und -Aktivierungen auf Conv2d und Linear: Gewichtsskalen pro Kanal, kalibrierte Aktivierungsskalen pro Tensor.alle viererforderlich
int8W8A8 auf Conv2d und Linear: symmetrische Gewichte pro Kanal, affine Aktivierungen pro Tensor.alle viererforderlich, oder calib=None nur für die Gewichte
w4a16Gruppierte symmetrische INT4-Gewichte, Gruppe 128 entlang in_features, Float-Aktivierungen, auf Linear.rfdetr, birefnet, feynobgnicht nötig
w4a8Gruppierte INT4-Gewichte plus kalibrierte INT8-Aktivierungen, auf Linear.rfdetr, birefnet, feynobgerforderlich
nvfp4W4A4-NVFP4 auf Linear: E2M1-Elemente, Blöcke zu 16 Elementen, FP8-E4M3-Blockskalen, FP32-Tensorskala. Dynamische Aktivierungsskalierung.rfdetr, birefnet, feynobgnicht nötig
mxfp4OCP-MXFP4 auf Linear: E2M1-Elemente, Blöcke zu 32 Elementen, E8M0-Blockskalen als Zweierpotenzen. Dynamische Aktivierungsskalierung.rfdetr, birefnet, feynobgnicht nötig
int2Nur für die Forschung: gruppierte 2-Bit-Gewichte, Gruppe 64, plus INT8-Aktivierungen, auf Linear. Rein nach dem Training ist es unbrauchbar, QAT oder QAD ist also erforderlich.rfdetrerforderlich

Die Rezepte unterhalb von 8 Bit zielen auf nn.Linear und werden für yolo9 mit Absicht abgelehnt: Diese Beschleunigung läuft auf aktueller Hardware nur über GEMM, die Faltungen blieben also in höherer Präzision. YOLO9 nutzt int8 oder fp8. int2 wird für birefnet und feynobg abgelehnt, weil diese Familien nur für die Inferenz gedacht sind, die heilende QAT, auf die das Rezept angewiesen ist, dort also nicht zur Verfügung steht.

Die Standardwerte pro Familie halten die erste Schicht und die Heads in Float, und die DFL-Faltung von YOLO9 wird nie quantisiert: Sie ist ein fester Integral-Erwartungswert-Operator. Überschreibe das mit keep_high_precision=("head.",), wenn du einen Grund dazu hast.

Kalibrierungsdaten sind keine Trainingsdaten

calib= nimmt ein paar hundert Bilder, liest keine Labels und läuft nur vorwärts, um Aktivierungsbereiche zu schätzen. data= in train() und val() ist der gelabelte Datensatz für Gradienten und Metriken. Das sind verschiedene Argumente mit verschiedenen Zwecken, und der Standard für calib ist coco128.yaml.

algorithm="minmax" behält die absoluten Extremwerte über alle Kalibrierungs-Batches hinweg und ist das, was "auto" wählt. "percentile" nutzt den Mittelwert der Perzentile 0.1 und 99.9 pro Batch; gemessen bricht damit die Accuracy der DETR-Familie ein, weil die Ausreißer in den Transformer-Aktivierungen tragend sind. Was die INT8-Empfindlichkeit kleiner Modelle wirklich behebt, ist die Kalibrierung auf genügend Batches: Mit dem Standard coco128 landet YOLO9-t etwa einen mAP-Punkt neben seinem Float-Wert. Der gewählte Algorithmus wird im Manifest des Checkpoints festgehalten.

Wiederherstellung der Accuracy

QAT ist einfach train() auf einem quantisierten Modell
from libreyolo import LibreYOLO qmodel = LibreYOLO("LibreYOLO9s-int8.pt") # Ein Finetune, kein Lauf von Grund auf neu: Finetune-Lernraten nutzen.qmodel.train(data="coco8.yaml", epochs=5, lr0=1e-4)
QAD ergänzt die vorhandenen Distillation-Argumente
qmodel.train(    data="coco8.yaml",    epochs=5,    lr0=1e-4,    distill_model="LibreYOLO9m.pt",)
CLI
libreyolo train --model LibreYOLO9s-int8.pt --data coco8.yaml --epochs 5 --lr0 1e-4

Quantisierte Module behalten fp32-Master-Gewichte und wenden Fake-Quantisierung mit einem Straight-Through-Estimator an, die Gradienten erreichen also die Master und die vorhandenen Trainer funktionieren unverändert: EMA, AMP, das Fortsetzen aus einem Checkpoint und die Distillation-Argumente lassen sich alle kombinieren.

QAT ist ein Finetune eines bereits trainierten Modells. Nutze Finetune-Lernraten statt der Standardwerte für einen Lauf von Grund auf neu, sonst zerstört schon ein kurzer Lauf die vortrainierten Gewichte, ganz unabhängig von der Quantisierung. Die Verfügbarkeit von QAD folgt der Distillation-Unterstützung der Familien, das heißt heute yolo9 und rfdetr.

Mit fp16 und bf16 quantisierte Modelle sind nur für die Inferenz, und der Trainer lehnt sie mit einem Hinweis auf amp=True ab.

Export

Gepackter PyTorch-Checkpoint
from libreyolo import LibreYOLO qmodel = LibreYOLO("LibreYOLO9s-int8.pt") # Schreibt LibreYOLO9s-int8-final.pt: gepackte Low-Bit-Gewichte und Skalen,# fp32-Master entfernt, der nicht quantisierte Rest auf fp16 gecastet.qmodel.export(format="pt") # remainder="fp32" hält die nicht quantisierten Tensoren exakt.qmodel.export(format="pt", remainder="fp32")
QDQ INT8 ONNX
from libreyolo import LibreYOLO qmodel = LibreYOLO("LibreYOLO9s-int8.pt") # QuantizeLinear/DequantizeLinear-Paare im Graphen, mit den eigenen# kalibrierten oder QAT-trainierten Skalen des Modells.qmodel.export(format="onnx")
CLI
libreyolo export --model LibreYOLO9s-int8.pt --format onnx

format="pt" lässt das Modell auskristallisieren. Gepackte Low-Bit-Gewichte und Skalen ersetzen die Master, und der nicht quantisierte Rest wird auf fp16 gecastet, sofern nicht remainder="fp32" übergeben wird. Die Invariante des Packens lautet, dass das Entpacken die Simulation Bit für Bit auf dem Gerät reproduziert, auf dem du finalisiert hast, die finalisierte Datei erreicht also genau den Wert, den du validiert hast. Gemessen: YOLO9-s int8 geht von 29.5 MB auf 9.6 MB, RF-DETR-n nvfp4 von 122 MB auf 26 MB. Lädst du so eine Datei, bekommst du ein einsatzbereites Modell für die Inferenz, und ein train() darauf rekonstruiert die Master automatisch aus den gepackten Gewichten.

format="onnx" gilt für int8-Modelle und gibt einen QDQ-Graphen aus, der die eigenen kalibrierten oder QAT-trainierten Skalen des Modells mitführt, den ONNX Runtime und TensorRT mit echten INT8-Kernels ausführen. Das ist ein anderer Weg als export(format="onnx", int8=True) auf einem Float-Modell, wo ONNX Runtime die Skalen selbst ableitet.

Die Cast-Rezepte brauchen überhaupt keinen quantisierten Exporter:

Zurück zu Float, mit erhaltenen QAT-trainierten Gewichten
from libreyolo import LibreYOLO qmodel = LibreYOLO("LibreYOLO9s-int8.pt")qmodel.dequantize() # Jeder Float-Exporter greift jetzt, in jeder Präzision, die er kann.qmodel.export(format="tensorrt", half=True)

Einschränkungen

Die quantisierte Arithmetik läuft in Simulation, also als Fake-Quantisierung, die selbst unter AMP in float32-Inseln gerechnet wird. Die Simulation ist numerisch treu, ein val()-Wert auf einem beliebigen Gerät ist also eine echte Aussage über die quantisierte Arithmetik. Eine Aussage über Geschwindigkeit ist er nicht.

Zwei Ausnahmen laufen nativ. fp16 und bf16 sind gewöhnliche Casts. Finalisierte fp8-Module rechnen ihr GEMM direkt auf gepackten E4M3-Gewichten über torch._scaled_mm auf Hardware der Klassen Ada, Hopper und Blackwell, mit denselben kalibrierten Aktivierungsskalen wie die Simulation; mit LIBREYOLO_KERNELS=off kommt überall wieder genau der simulierte Weg zum Einsatz.

Die Deployment-Abdeckung ist schmaler als die Rezeptliste. Nur int8 hat hier eine ONNX-Form, die sich ausrollen lässt; fp8 und die linearen Rezepte unterhalb von 8 Bit laufen in PyTorch und kristallisieren über format="pt" aus. Ein ONNX-Export aus ihnen wirft einen Fehler mit genau dieser Anweisung, und ebenso jedes Nicht-ONNX-Format aus einem int8-Modell: Baue nachgelagerte Engines stattdessen aus dem QDQ-Graphen.

Der Export eines int8-Modells, dessen Aktivierungen nie kalibriert wurden, protokolliert eine Warnung und erzeugt einen Graphen, der nur die Gewichtsquantisierung mitführt.

Gelesen aus libreyolo/quant/api.py, libreyolo/models/base/model.py, libreyolo/cli/commands/quantize.py und docs/quantization.md im dev-Branch. Die Angaben zur Checkpoint-Größe sind die in docs/quantization.md festgehaltenen Messwerte.