Kwantyzacja

Kwantyzacja w LibreYOLO działa w całości w PyTorch: model.quantize() zastępuje moduły Conv2d i Linear modelu ich skwantyzowanymi odpowiednikami i kalibruje je. Wynik zachowuje zwykły kontrakt predict, val, train i save, więc skwantyzowany model jest oceniany przez te same walidatory co model float.

Wywołanie
model.quantize(recipe="int8", calib="coco128.yaml")
Polecenie
libreyolo quantize --model M.pt --recipe int8 --calib coco128.yaml
Extra
Brak. Kwantyzacja działa w PyTorch.
Rodziny
yolo9, rfdetr, birefnet, feynobg
Przepisy
fp16, bf16, fp8, int8, w4a16, w4a8, nvfp4, mxfp4, int2
Artefakty do wdrożenia
export(format="pt") dla spakowanego checkpointu, export(format="onnx") dla grafu QDQ INT8

Instalacja

Kwantyzacja nie wymaga żadnego dodatku. Podmiana modułów, przebieg kalibracji i symulowana arytmetyka działają w całości w PyTorch, więc jedynym wymaganiem jest pip install libreyolo. Artefakty do wdrożenia potrzebują tego, czego potrzebuje ich własny format, czyli w ścieżce ONNX libreyolo[onnx].

Kwantyzacja

Python
from libreyolo import LibreYOLO model = LibreYOLO("LibreYOLO9s.pt") # Podmiana struktury plus kalibracja. calib to niewielki zbiór obrazów BEZ ETYKIET,# czytany tylko w przejściu w przód, aby wyznaczyć zakresy i skale aktywacji.qmodel = model.quantize(recipe="int8", calib="coco128.yaml", samples=128) print(qmodel.quant_info())qmodel.val(data="coco8.yaml")          # te same walidatory co dla modelu floatqmodel.save("LibreYOLO9s-int8.pt")     # checkpoint niesie manifest quant
CLI
libreyolo quantize --model LibreYOLO9s.pt --recipe int8 --calib coco128.yaml
Argumenty
model.quantize(    recipe="int8",    calib="coco128.yaml",      # ścieżka do data.yaml lub nazwa wbudowana; None pomija kalibrację    samples=128,               # maksymalna liczba obrazów kalibracyjnych    batch=8,                   # rozmiar batcha przy kalibracji    algorithm="auto",          # auto i minmax to to samo; percentile to alternatywa    keep_high_precision=None,  # None używa polityki rodziny    verbose=True,)

quantize() przekształca wczytany model w miejscu i zwraca go. Gradienty nie biorą w tym udziału: podmiana instaluje skwantyzowane moduły, a przebieg kalibracji działa tylko w przód.

Powstały checkpoint to zwykły checkpoint LibreYOLO z dołączonym manifestem quant, więc wczytuje się ponownie z nienaruszoną strukturą i skalami:

Skwantyzowany checkpoint wczytuje się jako skwantyzowany
from libreyolo import LibreYOLO # Manifest quant odtwarza skwantyzowaną strukturę i skale# przed wczytaniem wag.qmodel = LibreYOLO("LibreYOLO9s-int8.pt")print(qmodel.quant_info())

Checkpointy zapisywane przez moduł trenowania podczas przebiegu QAT również niosą ten manifest, co oznacza, że best.pt z takiego przebiegu sam jest skwantyzowanym checkpointem.

Przepisy

Obsługiwane są cztery rodziny: yolo9, rfdetr, birefnet i feynobg.

PrzepisCo robiRodzinyKalibracja
fp16Rzutowanie na połowiczną precyzję z kontraktem wejścia i wyjścia w float32. Tylko inferencja.wszystkie czterybrak
bf16Rzutowanie na bfloat16, które zachowuje zakres wykładnika z float32. Rozwiązanie na przepełnienie fp16 w modelu typu DETR. Tylko inferencja.wszystkie czterybrak
fp8Wagi i aktywacje E4M3 w Conv2d i Linear: skale wag na kanał, kalibrowane skale aktywacji na tensor.wszystkie czterywymagana
int8W8A8 w Conv2d i Linear: symetryczne wagi na kanał, afiniczne aktywacje na tensor.wszystkie czterywymagana, albo calib=None dla samych wag
w4a16Grupowane symetryczne wagi INT4, grupa 128 wzdłuż in_features, aktywacje float, w Linear.rfdetr, birefnet, feynobgniepotrzebna
w4a8Grupowane wagi INT4 plus kalibrowane aktywacje INT8, w Linear.rfdetr, birefnet, feynobgwymagana
nvfp4W4A4 NVFP4 w Linear: elementy E2M1, bloki 16-elementowe, skale bloków FP8 E4M3, skala tensora FP32. Dynamiczne skalowanie aktywacji.rfdetr, birefnet, feynobgniepotrzebna
mxfp4OCP MXFP4 w Linear: elementy E2M1, bloki 32-elementowe, skale bloków E8M0 będące potęgami dwójki. Dynamiczne skalowanie aktywacji.rfdetr, birefnet, feynobgniepotrzebna
int2Wyłącznie do badań: grupowane wagi 2-bitowe, grupa 64, plus aktywacje INT8, w Linear. Sama kwantyzacja po trenowaniu jest bezużyteczna, więc wymagane jest QAT lub QAD.rfdetrwymagana

Przepisy poniżej 8 bitów celują w nn.Linear i są celowo odrzucane dla yolo9: na obecnym sprzęcie to przyspieszenie dotyczy wyłącznie GEMM, więc sploty pozostają w wyższej precyzji. YOLO9 korzysta z int8 lub fp8. int2 jest odrzucany dla birefnet i feynobg, ponieważ te rodziny służą wyłącznie do inferencji, więc naprawcze trenowanie QAT, od którego ten przepis zależy, jest tam niedostępne.

Wartości domyślne każdej rodziny zostawiają pierwszą warstwę i głowice w float, a splot DFL w YOLO9 nigdy nie jest kwantyzowany: to stały operator całkowej wartości oczekiwanej. Gdy jest ku temu powód, można to nadpisać przez keep_high_precision=("head.",).

Dane kalibracyjne to nie dane treningowe

calib= przyjmuje kilkaset obrazów, nie czyta etykiet i wykonuje wyłącznie przejście w przód, aby oszacować zakresy aktywacji. data= w train() i val() to zbiór danych z etykietami, używany do gradientów i metryk. To różne argumenty o różnym przeznaczeniu, a wartością domyślną dla calib jest coco128.yaml.

algorithm="minmax" zachowuje bezwzględne ekstrema zaobserwowane we wszystkich batchach kalibracyjnych i to właśnie wybiera "auto". "percentile" używa średniej z percentyli 0.1 i 99.9 liczonych dla poszczególnych batchy; pomiary pokazały, że załamuje on dokładność w rodzinie DETR, ponieważ wartości odstające w aktywacjach transformerów pełnią funkcję nośną. Wrażliwość małych modeli na INT8 naprawia w rzeczywistości kalibracja na wystarczającej liczbie batchy: przy domyślnym coco128 YOLO9-t mieści się w granicach około jednego punktu mAP od swojego wyniku float. Wybrany algorytm jest zapisywany w manifeście checkpointu.

Odzyskiwanie dokładności

QAT to zwykłe train() na skwantyzowanym modelu
from libreyolo import LibreYOLO qmodel = LibreYOLO("LibreYOLO9s-int8.pt") # Dostrajanie, a nie przebieg od zera: użyj współczynników uczenia dla dostrajania.qmodel.train(data="coco8.yaml", epochs=5, lr0=1e-4)
QAD dodaje istniejące argumenty destylacji
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

Skwantyzowane moduły przechowują nadrzędne wagi fp32 i stosują sztuczną kwantyzację (fake quantization) z estymatorem straight-through, więc gradienty docierają do wag nadrzędnych, a istniejące moduły trenowania działają bez zmian: EMA, AMP, wznawianie z checkpointu i argumenty destylacji dają się łączyć.

QAT to dostrajanie już wytrenowanego modelu. Należy używać współczynników uczenia właściwych dla dostrajania, a nie wartości domyślnych dla trenowania od zera, w przeciwnym razie krótki przebieg zniszczy wstępnie wytrenowane wagi niezależnie od kwantyzacji. Dostępność QAD idzie za obsługą destylacji w danej rodzinie, co dziś oznacza yolo9 i rfdetr.

Modele skwantyzowane przepisami fp16 i bf16 służą wyłącznie do inferencji, a moduł trenowania odrzuca je, wskazując na amp=True.

Eksport

Spakowany checkpoint PyTorch
from libreyolo import LibreYOLO qmodel = LibreYOLO("LibreYOLO9s-int8.pt") # Zapisuje LibreYOLO9s-int8-final.pt: spakowane wagi niskobitowe i skale,# usunięte wagi nadrzędne fp32, nieskwantyzowana reszta rzutowana na fp16.qmodel.export(format="pt") # remainder="fp32" zachowuje nieskwantyzowane tensory bez zmian.qmodel.export(format="pt", remainder="fp32")
QDQ INT8 ONNX
from libreyolo import LibreYOLO qmodel = LibreYOLO("LibreYOLO9s-int8.pt") # Pary QuantizeLinear/DequantizeLinear w grafie, niosące własne# skale modelu, skalibrowane lub wytrenowane w QAT.qmodel.export(format="onnx")
CLI
libreyolo export --model LibreYOLO9s-int8.pt --format onnx

format="pt" krystalizuje model. Spakowane wagi niskobitowe i skale zastępują wagi nadrzędne, a nieskwantyzowana reszta jest rzutowana na fp16, chyba że przekazano remainder="fp32". Niezmiennikiem pakowania jest to, że rozpakowanie odtwarza symulację bit w bit na urządzeniu, na którym przeprowadzono finalizację, więc sfinalizowany plik osiąga dokładnie ten wynik, który zwalidowano. Wartości zmierzone: YOLO9-s int8 schodzi z 29.5 MB do 9.6 MB, RF-DETR-n nvfp4 ze 122 MB do 26 MB. Wczytanie takiego pliku daje model gotowy do inferencji, a wywołanie na nim train() automatycznie odtwarza wagi nadrzędne ze spakowanych wag.

format="onnx" dotyczy modeli int8 i generuje graf QDQ niosący własne skale modelu, skalibrowane lub wytrenowane w QAT, które ONNX Runtime i TensorRT wykonują na prawdziwych jądrach INT8. To inna ścieżka niż export(format="onnx", int8=True) na modelu float, gdzie ONNX Runtime sam wyznacza skale.

Przepisy rzutujące typ nie potrzebują żadnego eksportera dla modeli skwantyzowanych:

Powrót do float z zachowaniem wag wytrenowanych w QAT
from libreyolo import LibreYOLO qmodel = LibreYOLO("LibreYOLO9s-int8.pt")qmodel.dequantize() # Teraz działa dowolny eksporter float, w każdej obsługiwanej przez niego precyzji.qmodel.export(format="tensorrt", half=True)

Ograniczenia

Skwantyzowana arytmetyka wykonuje się w symulacji, czyli jako sztuczna kwantyzacja liczona w wyspach float32, nawet pod AMP. Symulacja jest wierna numerycznie, więc wynik val() na dowolnym urządzeniu jest prawdziwym stwierdzeniem o skwantyzowanej arytmetyce. Nie jest stwierdzeniem o szybkości.

Dwa wyjątki wykonują się natywnie. fp16 i bf16 to zwykłe rzutowania typów. Sfinalizowane moduły fp8 wykonują swój GEMM bezpośrednio na spakowanych wagach E4M3 przez torch._scaled_mm na sprzęcie klasy Ada, Hopper i Blackwell, używając tych samych skalibrowanych skal aktywacji co symulacja; ustawienie LIBREYOLO_KERNELS=off przywraca wszędzie dokładnie tę symulowaną ścieżkę.

Zakres wdrożeń jest węższy niż lista przepisów. Tylko int8 ma tu postać ONNX nadającą się do wdrożenia; fp8 i przepisy liniowe poniżej 8 bitów wykonują się w PyTorch i krystalizują przez format="pt". Żądanie eksportu do ONNX dla nich kończy się wyjątkiem z tą właśnie wskazówką, podobnie jak żądanie formatu innego niż ONNX dla modelu int8: silniki na dalszych etapach należy budować z grafu QDQ.

Eksport modelu int8, którego aktywacje nigdy nie zostały skalibrowane, zapisuje ostrzeżenie w logach i daje graf niosący samą kwantyzację wag.

Odczytane z libreyolo/quant/api.py, libreyolo/models/base/model.py, libreyolo/cli/commands/quantize.py oraz docs/quantization.md w gałęzi dev. Podane rozmiary checkpointów to wartości zmierzone i zapisane w docs/quantization.md.