Quantization

Quantization

Um das ONNX-Modell auf einer CPU um bis zu 4x zu beschleunigen, ist eine statische oder dynamische Quantisierung in das INT8-Format erforderlich. Anstatt rechenintensive 32-Bit-Gleitkommazahlen (FP32) zu verwenden, führt der Prozessor Berechnungen mit effizienten 8-Bit-Ganzzahlen (INT8) durch. Moderne Prozessoren (sowohl Intel als auch AMD) nutzen integrierte Befehle (AVX-512, VNNI), um solche Operationen hardwareseitig mit enormer Geschwindigkeit auszuführen. Wir verwenden das offizielle ONNX Runtime Quantization Tool.

1. Quantisierungsskript. Wir verwenden das zuvor erhaltene Modell. (quantize_onnx.py)

Um eine hohe Präzision des fraktalen Routers zu gewährleisten, verwenden wir dynamische Quantisierung. Dabei werden die Layer-Gewichte im Voraus in INT8 konvertiert und die Aktivierungen dynamisch quantisiert, was ideal für CPUs ist.


import os
from onnxruntime.quantization import quantize_dynamic, QuantType

def quantize_model(model_fp32_path="onnx_models/fractal_net.onnx", model_int8_path="onnx_models/fractal_net_int8.onnx"):
    """
    Wendet die dynamische INT8-Quantisierung auf das FP32 ONNX-Modell an.
    Optimiert für mehrere CPU-Beschleunigungen.
    """
    if not os.path.exists(model_fp32_path):
        print(f"Fehler: Quellmodell '{model_fp32_path}' nicht gefunden. Führen Sie zuerst export_onnx.py aus.")
        return

    print("Starte den Prozess der Quantisierung des Modells auf INT8...")

    # Starten des ONNX Runtime Optimizers
    quantize_dynamic(
        model_input=model_fp32_path,        # Pfad zum ursprünglichen FP32-Modell
        model_output=model_int8_path,       # Pfad zum Speichern des INT8-Modells
        weight_type=QuantType.QUInt8        # Gewichte in 8-Bit-Ganzzahlen ohne Vorzeichen umwandeln
    )

    # Vergleichen Sie die Dateigrößen auf der Festplatte
    size_fp32 = os.path.getsize(model_fp32_path) / (1024 * 1024)
    size_int8 = os.path.getsize(model_int8_path) / (1024 * 1024)

    print("\n--- Quantisierung erfolgreich abgeschlossen! ---")
    print(f"Originale Modellgröße (FP32): {size_fp32:.2f} MB")
    print(f"Komprimierte Modellgröße (INT8): {size_int8:.2f} MB")
    print(f"Das Modell hat sich um etwa {size_fp32/size_int8:.1f} Mal verkleinert.")

if __name__ == "__main__":
    quantize_model()
              

2. Geschwindigkeitsmessung und Modellverifizierung (benchmark_onnx.py)

Um die Geschwindigkeitssteigerung um bis zu das Vierfache zu visualisieren, schreiben wir ein Skript, das 500 Inferenziterationen sowohl auf FP32- als auch auf INT8-Modellen durchführt, die Prozessorausführungszeit mit reinem NumPy misst und die endgültige Differenz anzeigt.


import time
import numpy as np
import onnxruntime as ort

def benchmark_models(fp32_path="onnx_models/fractal_net.onnx", int8_path="onnx_models/fractal_net_int8.onnx"):
    # 1. Vorbereitung einer Testcharge (Größe 1, 3 Kanäle, 128x128)
    fake_image = np.random.randn(1, 3, 128, 128).astype(np.float32)

    # Konfigurieren eines reinen CPU-Starts mit Thread-Optimierung
    opts = ort.SessionOptions()
    opts.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL
    opts.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL

    # Wir erstellen Sitzungen für beide Modelle
    session_fp32 = ort.InferenceSession(fp32_path, sess_options=opts, providers=['CPUExecutionProvider'])
    session_int8 = ort.InferenceSession(int8_path, sess_options=opts, providers=['CPUExecutionProvider'])

    input_name = session_fp32.get_inputs()[0].name
    inputs = {input_name: fake_image}

    # Anzahl der Testzyklen
    num_runs = 500

    print(f"Benchmark wird auf der CPU ausgeführt ({num_runs} Läufe)...")

    # Test 1: Basismodell FP32
    start_time = time.time()
    for _ in range(num_runs):
        _ = session_fp32.run(None, inputs)
    fp32_time = time.time() - start_time

    # Test 2: Beschleunigtes INT8-Modell
    start_time = time.time()
    for _ in range(num_runs):
        _ = session_int8.run(None, inputs)
    int8_time = time.time() - start_time

    # Berechnung der endgültigen Leistungskennzahlen
    fps_fp32 = num_runs / fp32_time
    fps_int8 = num_runs / int8_time
    speedup = fps_int8 / fps_fp32

    print("\n--- Ergebnisse des CPU-Leistungstests ---")
    print(f"Normales Modell (FP32): Gesamtzeit: {fp32_time: .3f} Sek. | Geschwindigkeit: {fps_fp32: .1f} FPS")
    print(f"Beschleunigtes Modell (INT8): Gesamtzeit: {int8_time: .3f} Sek. | Geschwindigkeit: {fps_int8: .1f} FPS")
    print(f"Finale Hardwarebeschleunigung auf der CPU: {speedup:.2f} mal!")

if __name__ == "__main__":
    benchmark_models()
              

3. Überprüfung auf Speicherlecks

Memray ist ein leistungsstarker, moderner Speicherprofiler. Er kann Speicherzuweisungen nicht nur im Python-Code nachverfolgen, sondern auch innerhalb nativer C/C++ Erweiterungsmodule (wie NumPy, PyTorch oder Treibern) – Bereiche, in die Standard-Python-Tools keinen Einblick gewähren.


pip install memray
                          

memray run benchmark_onnx.py
                          

Um Allokationen zu erfassen, die innerhalb der C++-Binärdateien von onnxruntime stattfinden, müssen Sie beim Ausführen von Memray das Flag --native verwenden.


memray run --native benchmark_onnx.py
                          

memray flamegraph memray_benchmark_onnx.bin.*
                          

Ergebnisse prüfen (memray-flamegraph-benchmark_onnx.html):

  • Beachten Sie die C++ Symbole: Da Sie die Option --native angegeben haben, sehen Sie neben Python-Funktionsframes wie `run_inference_loop` auch tief verschachtelte Aufrufstapel, die bis zu Funktionen in `onnxruntime.capsule` zurückreichen, die von `libonnxruntime.so` stammen.
  • Speicherlecks finden: Achten Sie auf breite Bereiche im oberen Bereich des Flammendiagramms, die mit der Zeit nicht verschwinden. Wenn ein onnxruntime-Speicherleck durch eine nicht beendete Sitzung verursacht wird, sehen Sie, wie Speicherbelegungen, die mit C++-Klassen wie onnxruntime::InferenceSession oder Speicherverwaltungspools (onnxruntime::AllocatorPtr) verbunden sind, einen großen Anteil des Gesamtspeichers belegen.

Verwendung in der Production

Einsatz in der Produktion. Sehen wir uns an, wie wir eine Produktionsumgebung einrichten können. Nächster Beitrag

Diese Website verwendet den Browser-Cache für den Offline-Modus und verarbeitet Ihre Daten aus dem Kontaktformular gemäß unserer Datenschutzerklärung.