ONNX: Train in Any Framework, Deploy on Any Hardware
Leer hoe je modellen converteert naar ONNX-indeling, ze optimaliseert met quantization en uitrolt op elk platform - van edge-apparaten tot cloudservers - zonder vendor lock-in.
Dus je hebt een model getraind in PyTorch, maar je productiesysteem draait op TensorFlow. Moet je dan alles vanaf nul herschrijven?
Nee, dat hoeft niet.
Modelinteroperabiliteit kan hoofdpijn veroorzaken voor machine learning engineers. Verschillende frameworks gebruiken verschillende formaten, en converteren ertussen kan je model breken of onverwacht gedrag introduceren. Dat wil je niet, maar je wilt ook geen meerdere versies van hetzelfde model onderhouden.
ONNX (Open Neural Network Exchange) lost dit op door een universeel formaat voor machinelearningmodellen te bieden. Het laat je in het ene framework trainen en in een ander deployen zonder code te herschrijven of met bugs te worstelen.
In dit artikel laat ik je zien hoe je modellen converteert naar ONNX-indeling, inference draait met ONNX Runtime, modellen optimaliseert voor productie en ze uitrolt op diverse platforms, van edge devices tot cloudservers.
Aan de slag met ONNX
Je kunt ONNX niet gebruiken zonder eerst je ontwikkelomgeving in te richten.
In deze sectie neem ik je mee door alles wat je nodig hebt - van softwarevereisten tot het creëren van een schone, reproduceerbare werkruimte. Ik laat je zien hoe je ONNX en ONNX Runtime installeert op Windows, Linux en macOS.
Vereisten
ONNX werkt op alle grote besturingssystemen.
Je hebt Python 3.8 of hoger nodig op je machine. Dat is het voor de basis. ONNX ondersteunt Python's ABI3 (Application Binary Interface), wat betekent dat je vooraf gebouwde binaire pakketten kunt gebruiken zonder vanaf de bron te compileren.
Tot slot kunnen macOS-gebruikers uv installeren met Homebrew, of via een curl-opdracht:
brew install uv
# orcurl-LsSf<https://astral.sh/uv/install.sh>|shMogelijk gemaakt door
De omgeving instellen
Zo richt je een project in met uv:
mkdir onnx-project
cd onnx-project
# Initialize a uv project with Python 3.10
uv init --python3.13Mogelijk gemaakt door
uv maakt een .venv-map en een pyproject.toml-bestand. De virtuele omgeving wordt automatisch geactiveerd wanneer je opdrachten via uv uitvoert.
Installeer nu ONNX en ONNX Runtime:
uv add onnx onnxruntime
# Verify the installation
uv run python -c"import onnx; print(onnx.__version__)"
uv run python -c"import onnxruntime; print(onnxruntime.__version__)"Mogelijk gemaakt door
De verificatie-opdrachten horen versienummers te printen:
Je afhankelijkheden staan al vastgepind. uv schrijft automatisch alle pakketversies naar uv.lock. Zo worden je builds reproduceerbaar - iedereen kan jouw exacte omgeving opnieuw creëren door uv sync uit te voeren.
Zo ziet je pyproject.toml eruit:
[project]
name = "onnx-project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"onnx>=1.19.1",
"onnxruntime>=1.23.2",
]Mogelijk gemaakt door
En dat was het, de omgeving is geconfigureerd! Laten we verdergaan met de basis.
De basis van ONNX begrijpen
Je moet begrijpen hoe ONNX werkt voordat je modellen gaat converteren.
In deze sectie splits ik het ONNX-formaat uit en leg ik de grafiekgebaseerde architectuur uit. Ik laat je zien wat er in een ONNX-bestand zit en hoe de gegevens erdoorheen stromen.
Het ONNX-modelformaat
Een ONNX-model is een bestand dat de structuur en gewichten van je neuraal netwerk bevat.
Zie het als een blauwdruk. Het bestand beschrijft welke operaties uitgevoerd moeten worden, in welke volgorde en met welke parameters. Je getrainde gewichten worden samen met deze blauwdruk opgeslagen, zodat het model klaar is om te draaien zonder extra bestanden.
ONNX werd in 2017 gelanceerd als een samenwerking tussen Microsoft en Facebook (nu Meta). Het doel was simpel: stop met het herschrijven van modellen telkens wanneer je van framework wisselt. PyTorch, TensorFlow en andere frameworks bleven verbeteren, maar modellen konden niet zonder handmatige conversie tussen hen bewegen.
ONNX veranderde dat. Versie 1.0 ondersteunde basisoperaties voor computervisie en eenvoudige neurale netwerken. De huidige ONNX ondersteunt transformers, grote taalmodellen en complexe architecturen die in 2017 niet bestonden.
Het gebruikt Protocol Buffers (protobuf) om deze gegevens te serialiseren. Protobuf is een binair formaat ontwikkeld door Google voor efficiënte gegevensopslag en -overdracht. Het is veel sneller te lezen en te schrijven dan JSON of XML, en de bestanden zijn kleiner. Win-win.
Dit zit er in een ONNX-model:
Graph: De netwerkarchitectuur
Weights: Getrainde modelparameters
Metadata: Versie-informatie, producentnaam en modeldocumentatie
Opset-versie: Welke operaties beschikbaar zijn
De opset-versie is belangrijk. ONNX evolueert in de tijd, en blijft nieuwe operaties toevoegen en bestaande verbeteren. Je modelbestand specificeert welke opset-versie het gebruikt, zodat de runtime weet hoe het uit te voeren.
Belangrijke concepten en architectuur
ONNX representeert je model als een computationele graaf.
Een graaf heeft knooppunten en randen. Knooppunten zijn operaties (zoals matrixvermenigvuldiging of activatiefuncties), en randen zijn tensors (je data) die tussen die operaties stromen. Zo beschrijft ONNX "vermenigvuldig deze matrices, pas dan ReLU toe, vermenigvuldig dan opnieuw".
Hier is een eenvoudig voorbeeld:
import onnx
from onnx import helper, TensorProto
# Create input and output tensors
input_tensor = helper.make_tensor_value_info("input", TensorProto.FLOAT,[1,3,224,224])
output_tensor = helper.make_tensor_value_info("output", TensorProto.FLOAT,[1,1000])# Create a node (operation)
node = helper.make_node("Relu",# Operation type["input"],# Input edges ["output"],# Output edges)# Create the graph
graph = helper.make_graph([node],# List of nodes"simple_model",# Graph name[input_tensor],# Inputs[output_tensor],# Outputs)# Create the model
model = helper.make_model(graph)
onnx.save(model,"simple_model.onnx")Mogelijk gemaakt door
Elk knooppunt voert één operatie uit. Het operatietype (zoals Relu, Conv of MatMul) komt uit de ONNX-operatorset. Je kunt niet zomaar operatienamen verzinnen - ze moeten bestaan in de opset-versie die je gebruikt.
Randen verbinden knooppunten en dragen tensors. Een tensor heeft een vorm en een datatype. Wanneer je [1, 3, 224, 224] definieert, zeg je "deze tensor heeft 4 dimensies met deze groottes". De runtime gebruikt deze informatie om geheugen toe te wijzen en de graaf te valideren.
De graaf stroomt in één richting. Data komt binnen via inputknooppunten, gaat door operaties en verlaat via outputknooppunten. Geen cycli toegestaan - ONNX ondersteunt geen recursieve verbindingen direct. Je moet lussen uitrollen of specifieke operaties voor sequenties gebruiken.
Deze graafstructuur maakt frameworkinteroperabiliteit mogelijk. PyTorch denkt in termen van eager execution, TensorFlow gebruikt statische grafen en scikit-learn heeft een heel andere abstractie. Maar ze kunnen allemaal naar hetzelfde graafformaat exporteren.
De graaf maakt ook gedeelde optimalisatie mogelijk. Een ONNX-optimizer kan operaties fuseren (meerdere knooppunten combineren tot één), dode code elimineren (ongebruikte knooppunten verwijderen) of trage operaties vervangen door snellere equivalenten. Deze optimalisaties werken ongeacht welk framework het model heeft gemaakt.
Straks laat ik je stap voor stap zien hoe je modellen converteert naar ONNX-indeling.
Modellen converteren naar ONNX-indeling
In deze sectie laat ik je zien hoe je modellen exporteert vanuit PyTorch, TensorFlow en scikit-learn. Ik loop door het conversieproces en laat je zien hoe je controleert of je model na conversie correct werkt.
Frameworkondersteuning en tools
ONNX ondersteunt de populairste machinelearningframeworks.
PyTorch heeft ingebouwde ONNX-export via torch.onnx.export(). Het is de meest volwassen converter omdat zowel PyTorch als ONNX door Meta worden onderhouden.
TensorFlow gebruikt tf2onnx voor conversie. Het is een aparte bibliotheek die TensorFlow-grafen converteert naar ONNX-indeling. Installeer het met uv add tf2onnx, en je bent klaar om te gaan.
scikit-learn converteert via skl2onnx. Deze bibliotheek behandelt traditionele ML-modellen zoals random forests, lineaire regressie en SVM's. Deep learning-frameworks krijgen meer aandacht, maar scikit-learn-modellen werken net zo goed.
Dit zijn de drie populairste, maar je kunt elk van deze andere ondersteunde frameworks gebruiken - installeer gewoon de afhankelijkheid via uv add <name>:
Keras: Gebruik tf2onnx (Keras is onderdeel van TensorFlow)
XGBoost: Gebruik onnxmltools
LightGBM: Gebruik onnxmltools
MATLAB: Ingebouwde ONNX-export
Paddle: Gebruik paddle2onnx
Elk framework heeft zijn eigen converter omdat ze modellen verschillend representeren. PyTorch gebruikt dynamische computationele grafen, TensorFlow gebruikt statische grafen en scikit-learn gebruikt helemaal geen grafen. De converters vertalen deze representaties naar het graafformaat van ONNX.
Je kunt ook modellen gebruiken die door anderen zijn getraind. De ONNX Model Zoo host vooraf getrainde modellen die klaar zijn voor gebruik. Je vindt er computervisiemodellen (ResNet, YOLO, EfficientNet), NLP-modellen (BERT, GPT-2) en meer. Download ze van de officiële GitHub-repository.
Hugging Face host ook ONNX-modellen. Zoek naar modellen met "onnx" in de naam of filter op ONNX-indeling. Veel populaire transformermodellen zijn beschikbaar in ONNX, al geoptimaliseerd voor inference.
Voer voordat je verdergaat deze opdracht uit om alle vereiste bibliotheken met uv op te halen:
uv add torch torchvision tensorflow tf2onnx scikit-learn skl2onnx onnxscriptMogelijk gemaakt door
Stapsgewijs conversieproces
Zo converteer je een PyTorch-model naar ONNX:
import torch
import torch.nn as nn
# Define a simple modelclassSimpleModel(nn.Module):def__init__(self):super(SimpleModel, self).__init__()
self.fc = nn.Linear(10,5)defforward(self, x):return self.fc(x)# Create and prepare the model
model = SimpleModel()
model.eval()# Set to evaluation mode# Create dummy input with the same shape as your real data
dummy_input = torch.randn(1,10)# Export to ONNX
torch.onnx.export(
model,# Model to export
dummy_input,# Example input"simple_model.onnx",# Output file
input_names=["input"],# Name for the input
output_names=["output"],# Name for the output
dynamic_shapes={"x":{0:"batch_size"}},# Allow variable batch size)Mogelijk gemaakt door
De dummy-input is belangrijk. ONNX moet de inputvorm weten om de graaf te bouwen. Als je model variabele-lengtesequenties of verschillende batchgroottes accepteert, gebruik dan dynamic_shapes om die dimensies als dynamisch te markeren.
Zet je model altijd in evaluatiemodus met model.eval() vóór export. Dit schakelt het trainingsgedrag van dropout en batchnormalisatie uit. Als je deze stap overslaat, komt je geconverteerde model niet overeen met het origineel.
Dynamische vormen zorgen soms voor problemen. Als je converter klaagt over onbekende dimensies, zorg dan dat je dynamic_shapes correct specificeert of geef concrete vormen op.
Overschakelend naar TensorFlow, hier is alternatieve code die je kunt draaien:
import numpy as np
# Temporary compatibility fixes for tf2onnxifnothasattr(np,"object"):
np.object=objectifnothasattr(np,"cast"):
np.cast =lambda dtype: np.asarray
import tensorflow as tf
import tf2onnx
# Define a simple Keras model (same spirit as your PyTorch one)
inputs = tf.keras.Input(shape=(10,), name="input")
outputs = tf.keras.layers.Dense(5, name="output")(inputs)
model = tf.keras.Model(inputs, outputs)# Save the model (optional, if you want a .h5 file)
model.save("simple_tf_model.h5")# Define a dynamic input spec (None = dynamic batch dimension)
spec =(tf.TensorSpec((None,10), tf.float32, name="input"),)# Convert to ONNX
model_proto, _ = tf2onnx.convert.from_keras(
model, input_signature=spec, output_path="simple_tf_model.onnx")Mogelijk gemaakt door
Helaas is er wat conflict met de nieuwste versie van Numpy, dus ik heb een tijdelijke fix toegevoegd. Hopelijk kun je tegen de tijd dat je dit leest het fragment draaien zonder de eerste zes regels.
En tot slot, zo werkt ONNX-conversie voor scikit-learn-modellen:
from sklearn.ensemble import RandomForestClassifier
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType
# Train a simple model
model = RandomForestClassifier(n_estimators=10)
model.fit(X_train, y_train)# Define input type and shape
initial_type =[('float_input', FloatTensorType([None,4]))]# Convert to ONNX
onnx_model = convert_sklearn(model, initial_types=initial_type)# Save the modelwithopen("sklearn_model.onnx","wb")as f:
f.write(onnx_model.SerializeToString())Mogelijk gemaakt door
Je kunt nu dit fragment draaien om je geconverteerde model te valideren:
import onnx
import onnxruntime as ort
import numpy as np
# Load and check the ONNX model
onnx_model = onnx.load("simple_model.onnx")
onnx.checker.check_model(onnx_model)# Run inference with ONNX Runtime
session = ort.InferenceSession("simple_model.onnx")
input_name = session.get_inputs()[0].name
# Create test input
test_input = np.random.randn(1,10).astype(np.float32)# Get prediction
onnx_output = session.run(None,{input_name: test_input})# Compare with original modelwith torch.no_grad():
original_output = model(torch.from_numpy(test_input))# Check if outputs match (within floating point tolerance)
np.testing.assert_allclose(
original_output.numpy(),
onnx_output[0],
rtol=1e-3,
atol=1e-5)print("Model conversion successful - outputs match!")Mogelijk gemaakt door
Voer deze validatie elke keer uit wanneer je een model converteert. Kleine numerieke verschillen zijn normaal (zwevendekommagetallen zijn niet exact), maar grote verschillen betekenen dat er iets misging tijdens de conversie.
Om af te ronden, hier zijn twee belangrijke kanttekeningen waar je van op de hoogte moet zijn:
Aangepaste operaties worden niet geconverteerd: Als je aangepaste PyTorch-operaties of TensorFlow-lagen hebt geschreven, weet de converter niet hoe hij die moet vertalen. Je moet ze ofwel herschrijven met standaardoperaties of aangepaste ONNX-operators registreren.
Versiemismatches breken dingen: Je frameworkversie, converterversie en ONNX-versie moeten compatibel zijn. Controleer de documentatie van de converter voor ondersteunde versiecombinaties.
Dan nu ONNX Runtime.
Inference draaien met ONNX Runtime
In deze sectie laat ik je zien hoe je ONNX Runtime gebruikt om modellen te laden en voorspellingen te doen. Ik behandel hardwareversnelling en laat zien waar ONNX Runtime in productie uitblinkt.
Introductie tot ONNX Runtime
ONNX Runtime is een cross-platform inference-engine. Microsoft ontwikkelde het om ONNX-modellen snel te draaien op elke hardware - CPU's, GPU's, mobiele chips en gespecialiseerde AI-accelerators.
Zo werkt het: je laadt een ONNX-model, en ONNX Runtime bouwt een uitvoeringsplan. Het analyseert de computationele graaf, past optimalisaties toe en bepaalt de beste manier om operaties op jouw hardware uit te voeren. Vervolgens draait het inference met het geoptimaliseerde plan.
De architectuur scheidt de uitvoeringslogica van hardware-specifieke code. Execution providers verzorgen de hardware-interface. Wil je op NVIDIA GPU's draaien? Gebruik de CUDA execution provider. Wil je Apple Silicon? Je onderliggende code blijft hetzelfde.
DmlExecutionProvider: DirectML voor Windows (werkt met elke GPU)
OpenVINOExecutionProvider: Intel CPU's en GPU's
ROCMExecutionProvider: AMD GPU's
Elke provider vertaalt ONNX-operaties naar hardware-specifieke instructies. De CPU-provider gebruikt standaard CPU-operaties. De CUDA-provider gebruikt cuDNN en cuBLAS. De TensorRT-provider compileert je model naar geoptimaliseerde GPU-kernels, enzovoort.
Een model instellen en uitvoeren
Je kunt een ONNX-model laden met drie regels code:
import onnxruntime as ort
# Create an inference session
session = ort.InferenceSession("simple_model.onnx")# Get input and output names
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].nameMogelijk gemaakt door
De InferenceSession doet het zware werk. Het laadt het model, valideert de graaf en bereidt voor op inference. Standaard gebruikt het de CPU execution provider.
Draai nu inference:
import numpy as np
import onnxruntime as ort
# Create an inference session
session = ort.InferenceSession("simple_model.onnx")# Get input and output names
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name
# Prepare input data
input_data = np.random.randn(1,10).astype(np.float32)# Run inference
outputs = session.run([output_name],{input_name: input_data},)# Get predictions
predictions = outputs[0]print(predictions.shape)Mogelijk gemaakt door
De run()-methode neemt twee argumenten. Eerst een lijst met outputnamen (of None voor alle outputs). Ten tweede een dictionary die inputnamen koppelt aan numpy-arrays.
Inputvormen moeten overeenkomen. Als je model [1, 10] verwacht en jij geeft [1, 10, 3] door, dan faalt de inference.
Wil je GPU-versnelling, specificeer dan execution providers bij het aanmaken van de sessie:
# For NVIDIA GPUs with CUDA
session = ort.InferenceSession("model.onnx",
providers=['CUDAExecutionProvider','CPUExecutionProvider'])# For Apple Silicon
session = ort.InferenceSession("model.onnx",
providers=['CoreMLExecutionProvider','CPUExecutionProvider'])# For Intel hardware
session = ort.InferenceSession("model.onnx",
providers=['OpenVINOExecutionProvider','CPUExecutionProvider'])Mogelijk gemaakt door
Zet providers in volgorde van prioriteit. ONNX Runtime probeert de eerste provider, en valt terug op de volgende als die niet beschikbaar is. Neem altijd CPUExecutionProvider als laatste fallback op.
Je kunt controleren welke provider daadwerkelijk wordt gebruikt:
import numpy as np
import onnxruntime as ort
# Create an inference session
session = ort.InferenceSession("simple_model.onnx", providers=["CoreMLExecutionProvider","CPUExecutionProvider"])# Get input and output names
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name
# Prepare input data
input_data = np.random.randn(1,10).astype(np.float32)# Run inference
outputs = session.run([output_name],{input_name: input_data},)# Get predictions
predictions = outputs[0]print(predictions.shape)print("Providers:")print(session.get_providers())Mogelijk gemaakt door
Zie je ["CPUExecutionProvider"] terwijl je een GPU verwacht, dan is de GPU-provider niet beschikbaar. Vaak komt dat door ontbrekende drivers, een verkeerd ONNX Runtime-pakket of een incompatibele GPU.
De configuratie stopt hier niet. Je kunt de execution provider verder optimaliseren voor betere prestaties:
providers =[("TensorRTExecutionProvider",{"device_id":0,"trt_max_workspace_size":2147483648,# 2GB"trt_fp16_enable":True,# Use FP16 precision},),"CUDAExecutionProvider","CPUExecutionProvider",]
session = ort.InferenceSession("simple_model.onnx", providers=providers)Mogelijk gemaakt door
Raadpleeg de officiële documentatie van ONNX Runtime voor een lijst met alle beschikbare opties voor de provider van jouw keuze.
Toepassingen van ONNX Runtime
Het indrukwekkende aan ONNX Runtime is dat het overal draait - servers, browsers en mobiele apparaten.
ONNX Runtime Web brengt inference naar browsers met WebAssembly en WebGL. Je model draait client-side zonder data naar servers te sturen. Dit werkt uitstekend voor privacygevoelige toepassingen zoals medische beeldanalyse of documentverwerking.
Hier is een eenvoudig voorbeeld van hoe je ONNX Runtime in een simpele HTML-file kunt gebruiken:
<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"/><metaname="viewport"content="width=device-width, initial-scale=1.0"/><title>ONNXRuntime Web Demo</title></head><body><h1>ONNXRuntime Web Demo</h1><!-- Load ONNXRuntime-Web from CDN --><scriptsrc="<https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js>"></script><script>asyncfunctionrunModel(){// Create inference sessionconst session =await ort.InferenceSession.create('./simple_model.onnx');// Prepare inputconst inputData =newFloat32Array(10).fill(0).map(()=> Math.random());const tensor =newort.Tensor('float32', inputData,[1,10]);// Run inferenceconst results =await session.run({input: tensor });const output = results.output;
console.log('Output shape:', output.dims);
console.log('Output values:', output.data);}runModel();</script></body></html>Mogelijk gemaakt door
Voor mobiele uitrol kun je ONNX Runtime Mobile gebruiken - een lichtgewicht versie, geoptimaliseerd voor iOS en Android. Het stript onnodige features en verkleint de binaire grootte.
Dit is een voorbeeldconfiguratie voor iOS:
import onnxruntime_objc
let modelPath =Bundle.main.path(forResource:"simple_model", ofType:"onnx")!let session =tryORTSession(modelPath: modelPath)let inputData =// Your input as Datalet inputTensor =tryORTValue(tensorData: inputData,
elementType:.float,
shape:[1,10])let outputs =try session.run(withInputs:["input": inputTensor])Mogelijk gemaakt door
Of, als je Python gebruikt om een REST API te bouwen, kun je ONNX Runtime aan een van je endpoints toevoegen met deze aanpak:
from fastapi import FastAPI
import onnxruntime as ort
import numpy as np
app = FastAPI()
session = ort.InferenceSession("simple_model.onnx")@app.post("/predict")asyncdefpredict(data:dict):
input_data = np.array(data["input"]).astype(np.float32)
outputs = session.run(None,{"input": input_data})return{"prediction": outputs[0].tolist()}Mogelijk gemaakt door
En dat was het! Andere omgevingen gebruiken een vergelijkbare aanpak, maar je snapt het punt - ONNX Runtime is eenvoudig te gebruiken. Laten we nu optimalisatie bespreken.
ONNX-modellen optimaliseren
In deze sectie laat ik zien hoe je ONNX-modellen sneller en kleiner maakt via quantization en graafoptimalisaties. Ik behandel de technieken die het meest tellen voor productie.
Quantization-technieken
Quantization verlaagt de precisie van je model om het sneller en kleiner te maken.
In plaats van gewichten als 32-bits floats op te slaan, sla je ze op als 8-bits integers. Dat vermindert het geheugen met 75% en versnelt inference omdat integerrekenwerk op de meeste hardware sneller is dan floating point.
Dynamische quantization converteert gewichten naar lagere precisie, maar houdt activaties (intermediaire waarden tijdens inference) in volle precisie. Het is de gemakkelijkste methode omdat je geen calibratiedata nodig hebt.
Zo pas je dynamische quantization toe:
from onnxruntime.quantization import quantize_dynamic, QuantType
# Quantize the model
quantize_dynamic(
model_input="model.onnx",
model_output="model_quantized.onnx",
weight_type=QuantType.QUInt8 # 8-bit unsigned integers)Mogelijk gemaakt door
Dat is alles. Je gequantizeerde model gebruikt nu 8-bits unsigned integers en is klaar voor gebruik.
Statische quantization converteert zowel gewichten als activaties naar lagere precisie. Het is agressiever en sneller dan dynamische quantization, maar je hebt representatieve calibratiedata nodig om activatiebereiken te meten.
Hier is een algemene strategie om statische quantization toe te passen:
from onnxruntime.quantization import quantize_static, CalibrationDataReader
import numpy as np
# Create a calibration data readerclassDataReader(CalibrationDataReader):def__init__(self, calibration_data):
self.data = calibration_data
self.index =0defget_next(self):if self.index >=len(self.data):returnNone
batch ={"input": self.data[self.index]}
self.index +=1return batch
# Load calibration data (100-1000 samples from your dataset)
calibration_data =[np.random.randn(1,10).astype(np.float32)for _ inrange(100)]
data_reader = DataReader(calibration_data)# Quantize
quantize_static(
model_input="model.onnx",
model_output="model_static_quantized.onnx",
calibration_data_reader=data_reader
)Mogelijk gemaakt door
De calibratiedata moet je daadwerkelijke inference-werkbelasting representeren, gebruik dus geen willekeurige data zoals hierboven getoond. Gebruik echte samples uit je dataset.
In het algemeen heeft quantization voordelen en trade-offs waar je als machine learning engineer van op de hoogte moet zijn. Dit zijn de voordelen:
4x kleinere modellen: INT8 gebruikt een kwart van het geheugen van FP32
2-4x snellere inference: Integeroperaties draaien sneller op CPU's
Lager energieverbruik: Minder berekening betekent minder energie
Betere cachebenutting: Kleinere modellen passen in de CPU-cache
En dit zijn de trade-offs:
Kleine nauwkeurigheidsverlies: Meestal 1-2% voor INT8, acceptabel voor de meeste toepassingen
Niet alle operaties quantizen goed: Batchnormalisatie en softmax zijn lastig
Hardwareafhankelijk: GPU-snelheidswinsten variëren, sommige GPU's versnellen INT8 niet
Quantization blinkt ook uit voor grote taalmodellen en generatieve AI. Een model met 7B parameters in FP32 neemt 28GB geheugen in. Gequantizeerd naar INT8 daalt dat naar 7GB. Naar INT4 is het 3,5GB - klein genoeg om op consumentenhardware te draaien.
Graafoptimalisaties
Graafoptimalisaties herschrijven de computationele graaf van je model om het sneller te maken.
Het concept van node-fusie combineert meerdere operaties in één operatie. Als je model een Conv-laag heeft gevolgd door BatchNorm en daarna ReLU, fuseert de optimizer ze tot één ConvBnRelu-knooppunt. Dit vermindert geheugentransport en versnelt de uitvoering.
Er zijn nog twee concepten die je moet kennen bij graafoptimalisatie:
Constant folding precomputeert operaties die niet van inputdata afhangen. Als je model een gewichtenmatrix met een constante vermenigvuldigt, doet de optimizer die vermenigvuldiging één keer tijdens optimalisatie in plaats van bij elke inference.
Dead code elimination verwijdert ongebruikte operaties. Als je model takken heeft die nooit worden uitgevoerd of outputs die je nooit gebruikt, snijdt de optimizer ze weg.
Zo kun je graafoptimalisaties inschakelen in ONNX Runtime:
import onnxruntime as ort
# Set optimization level
sess_options = ort.SessionOptions()
sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
# Create session with optimizations
session = ort.InferenceSession("simple_model.onnx",
sess_options,
providers=["CPUExecutionProvider"])Mogelijk gemaakt door
ONNX Runtime heeft de volgende optimalisatieniveaus:
ORT_DISABLE_ALL: Geen optimalisaties (handig voor debuggen)
ORT_ENABLE_BASIC: Veilige optimalisaties die numerieke resultaten niet veranderen
ORT_ENABLE_EXTENDED: Agressievere optimalisaties kunnen kleine numerieke verschillen geven
ORT_ENABLE_ALL: Alle optimalisaties, inclusief layouttransformaties
Gebruik ORT_ENABLE_ALL voor productie. De snelheidswinst is de kleine numerieke verschillen waard.
Je kunt dit fragment draaien om geoptimaliseerde modellen op te slaan voor hergebruik:
sess_options = ort.SessionOptions()
sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
sess_options.optimized_model_filepath ="model_optimized.onnx"# This creates and saves the optimized model
session = ort.InferenceSession("simple_model.onnx", sess_options)Mogelijk gemaakt door
Graafoptimalisaties werken ongeacht welk framework je model heeft gemaakt. Een PyTorch Conv-BN-ReLU-patroon en een TensorFlow Conv-BN-ReLU-patroon worden op dezelfde manier gefuseerd. Dit is het voordeel van gedeelde optimalisatie - je schrijft de optimalisatie één keer en past die toe op modellen uit elk framework.
Laten we nu deployment bespreken.
Deploymentscenario's
In deze sectie behandel ik drie deploymentscenario's - edge-apparaten, cloudinfrastructuur en webbrowsers. Ik laat de uitdagingen en oplossingen voor elk zien.
Deployment op edge-apparaten
De kernuitdaging van edge-apparaten is dat ze beperkte resources hebben.
Je smartphone heeft 4-16GB RAM. Een Raspberry Pi heeft nog minder. IoT-apparaten hebben misschien 512MB of minder. Je kunt niet zomaar een model met 7B parameters op deze apparaten zetten en verwachten dat het werkt.
Begin met quantization. INT8-modellen zouden je basis moeten zijn. Ga zelfs naar INT4 als je het nauwkeurigheidsverlies kunt tolereren. Zo krijg je je model klein genoeg om in het geheugen te passen.
Voor deployment op edge-apparaten is ONNX Runtime Mobile je vriend. Het stript serverfeatures die je niet nodig hebt en optimaliseert voor batterijduur. De binary is kleiner, de opstart sneller en het stroomverbruik lager.
Zo voeg je het toe aan je mobiele app-project:
# iOS
pod 'onnxruntime-mobile-objc'
# Android
implementation 'com.microsoft.onnxruntime:onnxruntime-android:latest.version'Mogelijk gemaakt door
Execution providers handelen hardwareverschillen af. Android-apparaten gebruiken ARM-CPU's, sommige hebben GPU's van verschillende leveranciers en nieuwere telefoons hebben NPU's (Neural Processing Units). Je wilt geen code voor elke chip schrijven.
Gebruik dit fragment om de juiste execution provider te kiezen:
# iOS - use CoreML for Apple Silicon optimization
session = ort.InferenceSession("simple_model.onnx",
providers=["CoreMLExecutionProvider","CPUExecutionProvider"])# Android - use NNAPI for hardware acceleration
session = ort.InferenceSession("simple_model.onnx",
providers=["NnapiExecutionProvider","CPUExecutionProvider"])Mogelijk gemaakt door
CoreML op iOS gebruikt de Neural Engine (de NPU van Apple). NNAPI op Android gebruikt welke accelerator je apparaat ook heeft - GPU, DSP of NPU. De provider abstraheert de hardware zodat je code hetzelfde blijft. Wanneer er een nieuwe, snellere chip uitkomt, hoef je alleen de execution provider te updaten en draait je app sneller zonder codewijzigingen.
Clouddeployment
Clouddeployment geeft je onbeperkte schaal en de nieuwste hardware.
Je kunt ONNX-modellen op elk cloudplatform deployen - AWS, Azure, GCP of je eigen servers. Het patroon is hetzelfde - containeriseer je inference-service en zet die achter een load balancer.
Hier is een kleine Python FastAPI-voorbeeld:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import onnxruntime as ort
import numpy as np
import logging
app = FastAPI()
logger = logging.getLogger(__name__)# Load model at startup
session =None@app.on_event("startup")asyncdefload_model():global session
session = ort.InferenceSession("simple_model.onnx",
providers=["CUDAExecutionProvider","CoreMLExecutionProvider","CPUExecutionProvider",],)
logger.info("Model loaded successfully")classPredictionRequest(BaseModel):input:listclassPredictionResponse(BaseModel):
prediction:list
inference_time_ms:float@app.post("/predict", response_model=PredictionResponse)asyncdefpredict(request: PredictionRequest):try:import time
start = time.time()# Input shape: (1, 10)
input_data = np.array(request.input, dtype=np.float32).reshape(1,10)
input_name = session.get_inputs()[0].name
outputs = session.run(None,{input_name: input_data})
inference_time =(time.time()- start)*1000return PredictionResponse(
prediction=outputs[0].tolist(), inference_time_ms=inference_time
)except Exception as e:
logger.error(f"Prediction failed: {str(e)}")raise HTTPException(status_code=500, detail=str(e))@app.get("/health")asyncdefhealth():return{"status":"healthy","model_loaded": session isnotNone}Mogelijk gemaakt door
Dit FastAPI-voorbeeld, hoewel klein, is niet het makkelijkst te doorgronden voor machine learning engineers. Je kunt de basis onder de knie krijgen met onze gratis Introductie tot FastAPI-cursus.
Je kunt het vervolgens containerizen met Docker:
FROM python:3.13-slimWORKDIR /app# Install dependenciesRUN pip install onnx onnxruntime onnxscript fastapi pydantic numpy uvicorn# Copy model and codeCOPY simple_model.onnx .COPY main.py .# Expose portEXPOSE 8000# Run the serviceCMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]Mogelijk gemaakt door
Elke machinelearningliefhebber moet de basis van Docker kennen. Volg onze Introductie tot Docker-cursus om eindelijk te leren hoe je een machinelearningmodel deployt.
Je krijgt een applicatie draaiend op poort 8000, waarvan je de documentatie direct kunt openen en het prediction-endpoint kunt uitproberen:
Als je Azure Machine Learning gebruikt, is het goed om te weten dat het direct integreert met ONNX. Je kunt je model in drie stappen deployen:
from azureml.core import Workspace, Model
from azureml.core.webservice import AciWebservice, Webservice
# Register model
ws = Workspace.from_config()
model = Model.register(
workspace=ws, model_path="simple_model.onnx", model_name="my-onnx-model")# Deploy
deployment_config = AciWebservice.deploy_configuration(cpu_cores=1, memory_gb=1)
service = Model.deploy(
workspace=ws,
name="onnx-service",
models=[model],
deployment_config=deployment_config,)Mogelijk gemaakt door
Azure verzorgt schalen, monitoring en updates. Je krijgt automatische logging, request tracing en health checks.
Ben je nieuw met Azure Machine Learning, lees dan onze Beginner's Guide om snel de basis te leren.
Als we het hebben over MLOps-workflows heb je deze componenten nodig:
Versiebeheer: Volg modelversies met DVC of MLflow
CI/CD: Automatiseer testen en deployment met GitHub Actions of Azure Pipelines
Monitoring: Volg inference-latency, throughput en foutpercentages
A/B-testen: Vergelijk modelversies in productie
Rollback: Ga terug naar eerdere versies wanneer er problemen optreden
Hier is een voorbeeldfragment dat je kunt gebruiken:
import prometheus_client as prom
import onnxruntime as ort
import numpy as np
# Define metrics
inference_duration = prom.Histogram("model_inference_duration_seconds","Time spent processing inference")
inference_count = prom.Counter("model_inference_total","Total number of inferences")# Load model
session = ort.InferenceSession("simple_model.onnx")@app.post("/predict")@inference_duration.time()asyncdefpredict(request: PredictionRequest):
inference_count.inc()# Input shape: (1, 10)
input_data = np.array(request.input, dtype=np.float32).reshape(1,10)
input_name = session.get_inputs()[0].name
outputs = session.run(None,{input_name: input_data})return{"prediction": outputs[0].tolist()}Mogelijk gemaakt door
Vanaf daar kun je horizontaal schalen door meerdere service-instanties achter een load balancer te draaien. ONNX Runtime is stateless, dus elke instantie kan elke request afhandelen.
Inference in de browser
ONNX Runtime Web draait modellen rechtstreeks in browsers met WebAssembly en WebGL.
In plaats van data naar een server te sturen, stuur je het model één keer naar de browser en draai je inference lokaal. Gebruikers krijgen directe respons zonder netwerklatentie.
Begin eenvoudig door een HTML-bestand te maken met een inline JS-script:
Je ziet de output in de console zodra je het bestand met live server opent:
En tot slot bespreken we een paar geavanceerde onderwerpen.
Geavanceerde onderwerpen
Standaard ONNX-operaties dekken de meeste use-cases, maar als je meer nodig hebt, is dit de sectie voor jou.
Ik behandel aangepaste operators en deployment van grote taalmodellen. Ik leg uit wanneer je deze geavanceerde features nodig hebt en welke trade-offs ze meebrengen.
Aangepaste operators
ONNX wordt geleverd met honderden operaties, maar jouw model kan iets unieks gebruiken.
Aangepaste operators laten je operaties definiëren die niet in de standaard ONNX-operatorset bestaan. Je schrijft de operatielogica zelf en vertelt ONNX Runtime hoe die uit te voeren. Dit breidt ONNX uit voorbij de ingebouwde mogelijkheden.
Wanneer heb je aangepaste operators nodig? Wanneer je vooroplopende research gebruikt die ONNX nog niet ondersteunt. Of wanneer je propriëtaire operaties hebt gebouwd die specifiek zijn voor jouw domein. Of wanneer je hardware-specifieke optimalisaties nodig hebt die standaard-ops niet kunnen bieden.
Maar hier zit 'm de crux - aangepaste operators breken portabiliteit. Je model draait niet op systemen die jouw implementatie van de aangepaste operator niet hebben. Als je een PyTorch-model met custom ops naar ONNX exporteert, moet je die ops apart verpakken en registreren bij ONNX Runtime.
Deployment wordt lastiger. Elke omgeving heeft jouw bibliotheek met aangepaste operators nodig. Edge-apparaten, cloudservers en browsers hebben allemaal dezelfde implementatie nodig. Je verliest het grootste voordeel van ONNX: write once, run anywhere.
Hier is een klein werkend voorbeeld van aangepaste operators, zodat je ziet hoe ze werken:
import numpy as np
import onnxruntime as ort
from onnxruntime import InferenceSession, SessionOptions
# Define the custom operationdefcustom_square(x):return x * x
# Register the custom op with ONNX Runtime# This tells ONNX Runtime how to execute "CustomSquare" operationsclassCustomSquareOp:@staticmethoddefforward(x):return np.square(x).astype(np.float32)# Create a simple ONNX model with a custom op using the helperfrom onnx import helper, TensorProto
import onnx
# Create a graph with custom operator
node = helper.make_node("CustomSquare",# Custom operation name["input"],["output"],
domain="custom.ops",# Custom domain)
graph = helper.make_graph([node],"custom_op_model",[helper.make_tensor_value_info("input", TensorProto.FLOAT,[1,10])],[helper.make_tensor_value_info("output", TensorProto.FLOAT,[1,10])],)
model = helper.make_model(graph)
onnx.save(model,"custom_op_model.onnx")# To use this model, you'd need to implement the custom op in C++# and register it with ONNX Runtime - Python-only custom ops aren't supportedprint("Custom operator model created - requires C++ implementation to run")Mogelijk gemaakt door
De meeste teams vermijden aangepaste operators waar mogelijk. Probeer eerst combinaties van standaard-ops te gebruiken. Ga alleen custom als standaard-ops je operatie niet kunnen uitdrukken of wanneer performance het vereist.
Omgaan met grote taalmodellen
Geheugen is een probleem bij LLM's. Een model met 7B parameters heeft 28GB nodig in FP32, 14GB in FP16 en 7GB in INT8. De meeste consumentenharde waar kan dat niet aan. Je hebt quantization nodig, vaak agressief naar INT4 of INT3.
KV-cachebeheer is belangrijk voor autoregressieve generatie. Elk token dat je genereert moet alle voorgaande tokens kunnen refereren. De cache groeit mee met de sequentielengte. Lange gesprekken eten snel geheugen. ONNX Runtime GenAI bevat geoptimaliseerd KV-cachebeheer dat geheugen hergebruikt en overhead vermindert.
Inferentiesnelheid hangt af van je hardware en modelgrootte. Kleinere modellen (onder 3B parameters) draaien goed op consumentengpu's. Grotere modellen hebben meerdere GPU's of gespecialiseerde inference-accelerators nodig. ONNX Runtime ondersteunt multi-GPU-inference via execution providers, maar je moet je model handmatig over apparaten splitsen.
Flash Attention versnelt het attentiemechanisme in transformers. Standaard attentie is O(n²) in sequentielengte - dat wordt snel traag. Flash Attention vermindert geheugentransfers en verbetert de snelheid zonder resultaten te veranderen. ONNX Runtime bevat Flash Attention-optimalisaties voor ondersteunde hardware.
LLM's trainen met ONNX Runtime is mogelijk maar zeldzaam. De meeste teams trainen met PyTorch of JAX en exporteren daarna naar ONNX voor alleen inference. ONNX Runtime Training bestaat voor fine-tuning en verdere pretraining, maar het ecosysteem rond PyTorch-training is volwassener.
Generatieve AI-workloads krijgen speciale behandeling in ONNX Runtime. De GenAI-extensies bieden high-level API's voor tekstgeneratie, het behouden van state tussen calls en het beheren van beam search of samplingstrategieën. Zo hoef je de generatielogica niet zelf te implementeren.
Het LLM-landschap verandert snel, en ONNX Runtime-updates doen hun best om bij te blijven. De operatorset en optimalisaties worden regelmatig bijgewerkt, maar er is altijd een vertraging tussen research en production-ready ondersteuning.
Conclusie
Je weet nu hoe je modellen uit elk framework converteert, ze optimaliseert voor productie en ze overal uitrolt.
ONNX haalt de frictie weg tussen training en deployment. Train in PyTorch omdat het geweldig is voor research. Deploy met ONNX Runtime omdat het snel is en overal draait. Quantize naar INT8 en halveer je inferencekosten. Schakel over van CPU naar GPU zonder code te wijzigen. Verplaats modellen tussen cloudproviders zonder vendor lock-in.
ONNX maakt het mogelijk.
Het ecosysteem blijft verbeteren. Ondersteuning voor generatieve AI wordt met elke release beter - snellere LLM-inference, beter geheugengebruik en nieuwe optimalisatietechnieken. Hardware-ondersteuning breidt uit naar meer accelerators en edge-apparaten. De community bouwt tools die ONNX makkelijker maken. Je kunt meedoen door nieuwe functies te testen, issues te melden of code bij te dragen via de GitHub-repositories.
Senior Data Scientist, gevestigd in Kroatië. Top Tech-schrijver met meer dan 700 gepubliceerde artikelen en meer dan 10 miljoen weergaven. Auteur van het boek Machine Learning Automation with TPOT.
FAQs
Wat is ONNX en waarom zou ik het gebruiken?⌄
ONNX (Open Neural Network Exchange) is een universeel formaat voor machinelearningmodellen dat over verschillende frameworks werkt. Het laat je een model trainen in PyTorch of TensorFlow en het overal deployen zonder code te herschrijven. Je krijgt frameworkonafhankelijkheid, betere prestaties via optimalisaties en de mogelijkheid om modellen op elke hardware te draaien, van edge-apparaten tot cloudservers.
Hoe verbetert ONNX de modelprestaties?⌄
ONNX Runtime past graafoptimalisaties toe zoals node-fusie en constant folding die inference versnellen. Quantization verkleint het model met tot 75% en maakt inference 2-4x sneller door gewichten van 32-bits floats naar 8-bits integers te converteren. Deze optimalisaties werken automatisch en vereisen niet dat je je model handmatig afstemt.
Kan ONNX op mobiele apparaten en in browsers draaien?⌄
Ja. ONNX Runtime Mobile draait op iOS- en Android-apparaten met optimalisaties voor batterijduur en geheugengebruik. ONNX Runtime Web draait modellen rechtstreeks in browsers met WebAssembly en WebGL, zodat je client-side inference kunt doen zonder data naar servers te sturen. Beide opties ondersteunen hardwareversnelling via execution providers zoals CoreML voor Apple-apparaten en NNAPI voor Android.
Wat is het verschil tussen dynamische en statische quantization?⌄
Dynamische quantization converteert gewichten naar lagere precisie maar houdt activaties in volle precisie, en vereist geen calibratiedata. Statische quantization converteert zowel gewichten als activaties naar lagere precisie, wat sneller is maar representatieve calibratiedata vereist om activatiebereiken te meten. Statische quantization geeft betere prestaties maar vergt meer setup.
Hoe ga ik om met modellen met aangepaste operaties bij het converteren naar ONNX?⌄
Aangepaste operaties vereisen C++-implementaties die bij ONNX Runtime zijn geregistreerd - alleen Python-custom ops worden niet ondersteund voor productiegebruik. Je moet de aangepaste operator in C++ implementeren, als gedeelde bibliotheek compileren en registreren bij ONNX Runtime in elke deploymentomgeving. De meeste teams vermijden custom ops en gebruiken combinaties van standaard ONNX-operaties, omdat custom ops portabiliteit tussen platforms breken.
Dus je hebt een model getraind in PyTorch, maar je productiesysteem draait op TensorFlow. Moet je dan alles vanaf nul herschrijven?
Nee, dat hoeft niet.
Modelinteroperabiliteit kan hoofdpijn veroorzaken voor machine learning engineers. Verschillende frameworks gebruiken verschillende formaten, en converteren ertussen kan je model breken of onverwacht gedrag introduceren. Dat wil je niet, maar je wilt ook geen meerdere versies van hetzelfde model onderhouden.
ONNX (Open Neural Network Exchange) lost dit op door een universeel formaat voor machinelearningmodellen te bieden. Het laat je in het ene framework trainen en in een ander deployen zonder code te herschrijven of met bugs te worstelen.
In dit artikel laat ik je zien hoe je modellen converteert naar ONNX-indeling, inference draait met ONNX Runtime, modellen optimaliseert voor productie en ze uitrolt op diverse platforms, van edge devices tot cloudservers.
Aan de slag met ONNX
Je kunt ONNX niet gebruiken zonder eerst je ontwikkelomgeving in te richten.
In deze sectie neem ik je mee door alles wat je nodig hebt - van softwarevereisten tot het creëren van een schone, reproduceerbare werkruimte. Ik laat je zien hoe je ONNX en ONNX Runtime installeert op Windows, Linux en macOS.
Vereisten
ONNX werkt op alle grote besturingssystemen.
Je hebt Python 3.8 of hoger nodig op je machine. Dat is het voor de basis. ONNX ondersteunt Python's
ABI3(Application Binary Interface), wat betekent dat je vooraf gebouwde binaire pakketten kunt gebruiken zonder vanaf de bron te compileren.Dit ga je installeren:
uv(haal het van astral.sh/uv)Python 3.8+ (
uvregelt dit voor je)pip(wordt meegeleverd metuv)Wil je meer leren over hoe uv werkt en waarom het je standaardkeuze zou moeten zijn, lees dan onze gids over de snelste Python-package manager.
Voor Windows-gebruikers kun je uv installeren met PowerShell:
Krijg je een fout over het uitvoeringsbeleid, voer PowerShell dan eerst uit als administrator.
Linux-gebruikers kunnen uv installeren met deze shell-opdracht:
Dit werkt op elke Linux-distributie.
Tot slot kunnen macOS-gebruikers
uvinstalleren met Homebrew, of via een curl-opdracht:De omgeving instellen
Zo richt je een project in met uv:
uv maakt een
.venv-map en eenpyproject.toml-bestand. De virtuele omgeving wordt automatisch geactiveerd wanneer je opdrachten via uv uitvoert.Installeer nu ONNX en ONNX Runtime:
De verificatie-opdrachten horen versienummers te printen:
Je afhankelijkheden staan al vastgepind.
uvschrijft automatisch alle pakketversies naaruv.lock. Zo worden je builds reproduceerbaar - iedereen kan jouw exacte omgeving opnieuw creëren dooruv syncuit te voeren.Zo ziet je
pyproject.tomleruit:En dat was het, de omgeving is geconfigureerd! Laten we verdergaan met de basis.
De basis van ONNX begrijpen
Je moet begrijpen hoe ONNX werkt voordat je modellen gaat converteren.
In deze sectie splits ik het ONNX-formaat uit en leg ik de grafiekgebaseerde architectuur uit. Ik laat je zien wat er in een ONNX-bestand zit en hoe de gegevens erdoorheen stromen.
Het ONNX-modelformaat
Een ONNX-model is een bestand dat de structuur en gewichten van je neuraal netwerk bevat.
Zie het als een blauwdruk. Het bestand beschrijft welke operaties uitgevoerd moeten worden, in welke volgorde en met welke parameters. Je getrainde gewichten worden samen met deze blauwdruk opgeslagen, zodat het model klaar is om te draaien zonder extra bestanden.
ONNX werd in 2017 gelanceerd als een samenwerking tussen Microsoft en Facebook (nu Meta). Het doel was simpel: stop met het herschrijven van modellen telkens wanneer je van framework wisselt. PyTorch, TensorFlow en andere frameworks bleven verbeteren, maar modellen konden niet zonder handmatige conversie tussen hen bewegen.
ONNX veranderde dat. Versie 1.0 ondersteunde basisoperaties voor computervisie en eenvoudige neurale netwerken. De huidige ONNX ondersteunt transformers, grote taalmodellen en complexe architecturen die in 2017 niet bestonden.
Het gebruikt Protocol Buffers (protobuf) om deze gegevens te serialiseren. Protobuf is een binair formaat ontwikkeld door Google voor efficiënte gegevensopslag en -overdracht. Het is veel sneller te lezen en te schrijven dan JSON of XML, en de bestanden zijn kleiner. Win-win.
Dit zit er in een ONNX-model:
De opset-versie is belangrijk. ONNX evolueert in de tijd, en blijft nieuwe operaties toevoegen en bestaande verbeteren. Je modelbestand specificeert welke opset-versie het gebruikt, zodat de runtime weet hoe het uit te voeren.
Belangrijke concepten en architectuur
ONNX representeert je model als een computationele graaf.
Een graaf heeft knooppunten en randen. Knooppunten zijn operaties (zoals matrixvermenigvuldiging of activatiefuncties), en randen zijn tensors (je data) die tussen die operaties stromen. Zo beschrijft ONNX "vermenigvuldig deze matrices, pas dan ReLU toe, vermenigvuldig dan opnieuw".
Hier is een eenvoudig voorbeeld:
Elk knooppunt voert één operatie uit. Het operatietype (zoals
Relu,ConvofMatMul) komt uit de ONNX-operatorset. Je kunt niet zomaar operatienamen verzinnen - ze moeten bestaan in de opset-versie die je gebruikt.Randen verbinden knooppunten en dragen tensors. Een tensor heeft een vorm en een datatype. Wanneer je
[1, 3, 224, 224]definieert, zeg je "deze tensor heeft 4 dimensies met deze groottes". De runtime gebruikt deze informatie om geheugen toe te wijzen en de graaf te valideren.De graaf stroomt in één richting. Data komt binnen via inputknooppunten, gaat door operaties en verlaat via outputknooppunten. Geen cycli toegestaan - ONNX ondersteunt geen recursieve verbindingen direct. Je moet lussen uitrollen of specifieke operaties voor sequenties gebruiken.
Deze graafstructuur maakt frameworkinteroperabiliteit mogelijk. PyTorch denkt in termen van eager execution, TensorFlow gebruikt statische grafen en scikit-learn heeft een heel andere abstractie. Maar ze kunnen allemaal naar hetzelfde graafformaat exporteren.
De graaf maakt ook gedeelde optimalisatie mogelijk. Een ONNX-optimizer kan operaties fuseren (meerdere knooppunten combineren tot één), dode code elimineren (ongebruikte knooppunten verwijderen) of trage operaties vervangen door snellere equivalenten. Deze optimalisaties werken ongeacht welk framework het model heeft gemaakt.
Straks laat ik je stap voor stap zien hoe je modellen converteert naar ONNX-indeling.
Modellen converteren naar ONNX-indeling
In deze sectie laat ik je zien hoe je modellen exporteert vanuit PyTorch, TensorFlow en scikit-learn. Ik loop door het conversieproces en laat je zien hoe je controleert of je model na conversie correct werkt.
Frameworkondersteuning en tools
ONNX ondersteunt de populairste machinelearningframeworks.
PyTorch heeft ingebouwde ONNX-export via
torch.onnx.export(). Het is de meest volwassen converter omdat zowel PyTorch als ONNX door Meta worden onderhouden.TensorFlow gebruikt
tf2onnxvoor conversie. Het is een aparte bibliotheek die TensorFlow-grafen converteert naar ONNX-indeling. Installeer het metuv add tf2onnx, en je bent klaar om te gaan.scikit-learn converteert via
skl2onnx. Deze bibliotheek behandelt traditionele ML-modellen zoals random forests, lineaire regressie en SVM's. Deep learning-frameworks krijgen meer aandacht, maar scikit-learn-modellen werken net zo goed.Dit zijn de drie populairste, maar je kunt elk van deze andere ondersteunde frameworks gebruiken - installeer gewoon de afhankelijkheid via
uv add <name>:Keras: Gebruik
tf2onnx(Keras is onderdeel van TensorFlow)XGBoost: Gebruik
onnxmltoolsLightGBM: Gebruik
onnxmltoolsMATLAB: Ingebouwde ONNX-export
Paddle: Gebruik
paddle2onnxElk framework heeft zijn eigen converter omdat ze modellen verschillend representeren. PyTorch gebruikt dynamische computationele grafen, TensorFlow gebruikt statische grafen en scikit-learn gebruikt helemaal geen grafen. De converters vertalen deze representaties naar het graafformaat van ONNX.
Je kunt ook modellen gebruiken die door anderen zijn getraind. De ONNX Model Zoo host vooraf getrainde modellen die klaar zijn voor gebruik. Je vindt er computervisiemodellen (ResNet, YOLO, EfficientNet), NLP-modellen (BERT, GPT-2) en meer. Download ze van de officiële GitHub-repository.
Hugging Face host ook ONNX-modellen. Zoek naar modellen met "onnx" in de naam of filter op ONNX-indeling. Veel populaire transformermodellen zijn beschikbaar in ONNX, al geoptimaliseerd voor inference.
Voer voordat je verdergaat deze opdracht uit om alle vereiste bibliotheken met uv op te halen:
Stapsgewijs conversieproces
Zo converteer je een PyTorch-model naar ONNX:
Nieuw in PyTorch? Laat dat je niet tegenhouden. Onze Deep Learning met PyTorch-cursus behandelt de basis in een paar uur.
De dummy-input is belangrijk. ONNX moet de inputvorm weten om de graaf te bouwen. Als je model variabele-lengtesequenties of verschillende batchgroottes accepteert, gebruik dan
dynamic_shapesom die dimensies als dynamisch te markeren.Zet je model altijd in evaluatiemodus met
model.eval()vóór export. Dit schakelt het trainingsgedrag van dropout en batchnormalisatie uit. Als je deze stap overslaat, komt je geconverteerde model niet overeen met het origineel.Dynamische vormen zorgen soms voor problemen. Als je converter klaagt over onbekende dimensies, zorg dan dat je
dynamic_shapescorrect specificeert of geef concrete vormen op.Overschakelend naar TensorFlow, hier is alternatieve code die je kunt draaien:
Nog nooit met TensorFlow gewerkt? We raden onze cursus Introductie tot TensorFlow in Python aan.
Helaas is er wat conflict met de nieuwste versie van Numpy, dus ik heb een tijdelijke fix toegevoegd. Hopelijk kun je tegen de tijd dat je dit leest het fragment draaien zonder de eerste zes regels.
En tot slot, zo werkt ONNX-conversie voor scikit-learn-modellen:
Net als voor TensorFlow heeft DataCamp ook een gratis Supervised Learning met scikit-learn-cursus - bekijk die als je het fragment hierboven verwarrend vindt.
En dat was het!
Je kunt nu dit fragment draaien om je geconverteerde model te valideren:
Voer deze validatie elke keer uit wanneer je een model converteert. Kleine numerieke verschillen zijn normaal (zwevendekommagetallen zijn niet exact), maar grote verschillen betekenen dat er iets misging tijdens de conversie.
Om af te ronden, hier zijn twee belangrijke kanttekeningen waar je van op de hoogte moet zijn:
Dan nu ONNX Runtime.
Inference draaien met ONNX Runtime
In deze sectie laat ik je zien hoe je ONNX Runtime gebruikt om modellen te laden en voorspellingen te doen. Ik behandel hardwareversnelling en laat zien waar ONNX Runtime in productie uitblinkt.
Introductie tot ONNX Runtime
ONNX Runtime is een cross-platform inference-engine. Microsoft ontwikkelde het om ONNX-modellen snel te draaien op elke hardware - CPU's, GPU's, mobiele chips en gespecialiseerde AI-accelerators.
Zo werkt het: je laadt een ONNX-model, en ONNX Runtime bouwt een uitvoeringsplan. Het analyseert de computationele graaf, past optimalisaties toe en bepaalt de beste manier om operaties op jouw hardware uit te voeren. Vervolgens draait het inference met het geoptimaliseerde plan.
De architectuur scheidt de uitvoeringslogica van hardware-specifieke code. Execution providers verzorgen de hardware-interface. Wil je op NVIDIA GPU's draaien? Gebruik de CUDA execution provider. Wil je Apple Silicon? Je onderliggende code blijft hetzelfde.
ONNX Runtime heeft deze execution providers:
CPUExecutionProvider: Standaardprovider, werkt overalCUDAExecutionProvider: NVIDIA GPU's met CUDATensorRTExecutionProvider: NVIDIA GPU's met TensorRT-optimalisatieCoreMLExecutionProvider: Apple-apparaten (macOS, iOS)DmlExecutionProvider: DirectML voor Windows (werkt met elke GPU)OpenVINOExecutionProvider: Intel CPU's en GPU'sROCMExecutionProvider: AMD GPU'sElke provider vertaalt ONNX-operaties naar hardware-specifieke instructies. De CPU-provider gebruikt standaard CPU-operaties. De CUDA-provider gebruikt cuDNN en cuBLAS. De TensorRT-provider compileert je model naar geoptimaliseerde GPU-kernels, enzovoort.
Een model instellen en uitvoeren
Je kunt een ONNX-model laden met drie regels code:
De
InferenceSessiondoet het zware werk. Het laadt het model, valideert de graaf en bereidt voor op inference. Standaard gebruikt het de CPU execution provider.Draai nu inference:
De
run()-methode neemt twee argumenten. Eerst een lijst met outputnamen (ofNonevoor alle outputs). Ten tweede een dictionary die inputnamen koppelt aan numpy-arrays.Inputvormen moeten overeenkomen. Als je model
[1, 10]verwacht en jij geeft[1, 10, 3]door, dan faalt de inference.Wil je GPU-versnelling, specificeer dan execution providers bij het aanmaken van de sessie:
Zet providers in volgorde van prioriteit. ONNX Runtime probeert de eerste provider, en valt terug op de volgende als die niet beschikbaar is. Neem altijd
CPUExecutionProviderals laatste fallback op.Je kunt controleren welke provider daadwerkelijk wordt gebruikt:
Zie je
["CPUExecutionProvider"]terwijl je een GPU verwacht, dan is de GPU-provider niet beschikbaar. Vaak komt dat door ontbrekende drivers, een verkeerd ONNX Runtime-pakket of een incompatibele GPU.De configuratie stopt hier niet. Je kunt de execution provider verder optimaliseren voor betere prestaties:
Raadpleeg de officiële documentatie van ONNX Runtime voor een lijst met alle beschikbare opties voor de provider van jouw keuze.
Toepassingen van ONNX Runtime
Het indrukwekkende aan ONNX Runtime is dat het overal draait - servers, browsers en mobiele apparaten.
ONNX Runtime Web brengt inference naar browsers met WebAssembly en WebGL. Je model draait client-side zonder data naar servers te sturen. Dit werkt uitstekend voor privacygevoelige toepassingen zoals medische beeldanalyse of documentverwerking.
Hier is een eenvoudig voorbeeld van hoe je ONNX Runtime in een simpele HTML-file kunt gebruiken:
Voor mobiele uitrol kun je ONNX Runtime Mobile gebruiken - een lichtgewicht versie, geoptimaliseerd voor iOS en Android. Het stript onnodige features en verkleint de binaire grootte.
Dit is een voorbeeldconfiguratie voor iOS:
Of, als je Python gebruikt om een REST API te bouwen, kun je ONNX Runtime aan een van je endpoints toevoegen met deze aanpak:
En dat was het! Andere omgevingen gebruiken een vergelijkbare aanpak, maar je snapt het punt - ONNX Runtime is eenvoudig te gebruiken. Laten we nu optimalisatie bespreken.
ONNX-modellen optimaliseren
In deze sectie laat ik zien hoe je ONNX-modellen sneller en kleiner maakt via quantization en graafoptimalisaties. Ik behandel de technieken die het meest tellen voor productie.
Quantization-technieken
Quantization verlaagt de precisie van je model om het sneller en kleiner te maken.
In plaats van gewichten als 32-bits floats op te slaan, sla je ze op als 8-bits integers. Dat vermindert het geheugen met 75% en versnelt inference omdat integerrekenwerk op de meeste hardware sneller is dan floating point.
Dynamische quantization converteert gewichten naar lagere precisie, maar houdt activaties (intermediaire waarden tijdens inference) in volle precisie. Het is de gemakkelijkste methode omdat je geen calibratiedata nodig hebt.
Zo pas je dynamische quantization toe:
Dat is alles. Je gequantizeerde model gebruikt nu 8-bits unsigned integers en is klaar voor gebruik.
Statische quantization converteert zowel gewichten als activaties naar lagere precisie. Het is agressiever en sneller dan dynamische quantization, maar je hebt representatieve calibratiedata nodig om activatiebereiken te meten.
Hier is een algemene strategie om statische quantization toe te passen:
De calibratiedata moet je daadwerkelijke inference-werkbelasting representeren, gebruik dus geen willekeurige data zoals hierboven getoond. Gebruik echte samples uit je dataset.
In het algemeen heeft quantization voordelen en trade-offs waar je als machine learning engineer van op de hoogte moet zijn. Dit zijn de voordelen:
En dit zijn de trade-offs:
Quantization blinkt ook uit voor grote taalmodellen en generatieve AI. Een model met 7B parameters in FP32 neemt 28GB geheugen in. Gequantizeerd naar INT8 daalt dat naar 7GB. Naar INT4 is het 3,5GB - klein genoeg om op consumentenhardware te draaien.
Graafoptimalisaties
Graafoptimalisaties herschrijven de computationele graaf van je model om het sneller te maken.
Het concept van node-fusie combineert meerdere operaties in één operatie. Als je model een Conv-laag heeft gevolgd door BatchNorm en daarna ReLU, fuseert de optimizer ze tot één
ConvBnRelu-knooppunt. Dit vermindert geheugentransport en versnelt de uitvoering.Zo ziet de graaf eruit vóór fusie:
Hij is vereenvoudigd na fusie:
Er zijn nog twee concepten die je moet kennen bij graafoptimalisatie:
Zo kun je graafoptimalisaties inschakelen in ONNX Runtime:
ONNX Runtime heeft de volgende optimalisatieniveaus:
ORT_DISABLE_ALL: Geen optimalisaties (handig voor debuggen)ORT_ENABLE_BASIC: Veilige optimalisaties die numerieke resultaten niet veranderenORT_ENABLE_EXTENDED: Agressievere optimalisaties kunnen kleine numerieke verschillen gevenORT_ENABLE_ALL: Alle optimalisaties, inclusief layouttransformatiesGebruik
ORT_ENABLE_ALLvoor productie. De snelheidswinst is de kleine numerieke verschillen waard.Je kunt dit fragment draaien om geoptimaliseerde modellen op te slaan voor hergebruik:
Graafoptimalisaties werken ongeacht welk framework je model heeft gemaakt. Een PyTorch Conv-BN-ReLU-patroon en een TensorFlow Conv-BN-ReLU-patroon worden op dezelfde manier gefuseerd. Dit is het voordeel van gedeelde optimalisatie - je schrijft de optimalisatie één keer en past die toe op modellen uit elk framework.
Laten we nu deployment bespreken.
Deploymentscenario's
In deze sectie behandel ik drie deploymentscenario's - edge-apparaten, cloudinfrastructuur en webbrowsers. Ik laat de uitdagingen en oplossingen voor elk zien.
Deployment op edge-apparaten
De kernuitdaging van edge-apparaten is dat ze beperkte resources hebben.
Je smartphone heeft 4-16GB RAM. Een Raspberry Pi heeft nog minder. IoT-apparaten hebben misschien 512MB of minder. Je kunt niet zomaar een model met 7B parameters op deze apparaten zetten en verwachten dat het werkt.
Begin met quantization. INT8-modellen zouden je basis moeten zijn. Ga zelfs naar INT4 als je het nauwkeurigheidsverlies kunt tolereren. Zo krijg je je model klein genoeg om in het geheugen te passen.
Voor deployment op edge-apparaten is ONNX Runtime Mobile je vriend. Het stript serverfeatures die je niet nodig hebt en optimaliseert voor batterijduur. De binary is kleiner, de opstart sneller en het stroomverbruik lager.
Zo voeg je het toe aan je mobiele app-project:
Execution providers handelen hardwareverschillen af. Android-apparaten gebruiken ARM-CPU's, sommige hebben GPU's van verschillende leveranciers en nieuwere telefoons hebben NPU's (Neural Processing Units). Je wilt geen code voor elke chip schrijven.
Gebruik dit fragment om de juiste execution provider te kiezen:
CoreML op iOS gebruikt de Neural Engine (de NPU van Apple). NNAPI op Android gebruikt welke accelerator je apparaat ook heeft - GPU, DSP of NPU. De provider abstraheert de hardware zodat je code hetzelfde blijft. Wanneer er een nieuwe, snellere chip uitkomt, hoef je alleen de execution provider te updaten en draait je app sneller zonder codewijzigingen.
Clouddeployment
Clouddeployment geeft je onbeperkte schaal en de nieuwste hardware.
Je kunt ONNX-modellen op elk cloudplatform deployen - AWS, Azure, GCP of je eigen servers. Het patroon is hetzelfde - containeriseer je inference-service en zet die achter een load balancer.
Hier is een kleine Python FastAPI-voorbeeld:
Dit FastAPI-voorbeeld, hoewel klein, is niet het makkelijkst te doorgronden voor machine learning engineers. Je kunt de basis onder de knie krijgen met onze gratis Introductie tot FastAPI-cursus.
Je kunt het vervolgens containerizen met Docker:
Elke machinelearningliefhebber moet de basis van Docker kennen. Volg onze Introductie tot Docker-cursus om eindelijk te leren hoe je een machinelearningmodel deployt.
Je krijgt een applicatie draaiend op poort 8000, waarvan je de documentatie direct kunt openen en het prediction-endpoint kunt uitproberen:
Als je Azure Machine Learning gebruikt, is het goed om te weten dat het direct integreert met ONNX. Je kunt je model in drie stappen deployen:
Azure verzorgt schalen, monitoring en updates. Je krijgt automatische logging, request tracing en health checks.
Ben je nieuw met Azure Machine Learning, lees dan onze Beginner's Guide om snel de basis te leren.
Als we het hebben over MLOps-workflows heb je deze componenten nodig:
Hier is een voorbeeldfragment dat je kunt gebruiken:
Vanaf daar kun je horizontaal schalen door meerdere service-instanties achter een load balancer te draaien. ONNX Runtime is stateless, dus elke instantie kan elke request afhandelen.
Inference in de browser
ONNX Runtime Web draait modellen rechtstreeks in browsers met WebAssembly en WebGL.
In plaats van data naar een server te sturen, stuur je het model één keer naar de browser en draai je inference lokaal. Gebruikers krijgen directe respons zonder netwerklatentie.
Begin eenvoudig door een HTML-bestand te maken met een inline JS-script:
Je ziet de output in de console zodra je het bestand met live server opent:
En tot slot bespreken we een paar geavanceerde onderwerpen.
Geavanceerde onderwerpen
Standaard ONNX-operaties dekken de meeste use-cases, maar als je meer nodig hebt, is dit de sectie voor jou.
Ik behandel aangepaste operators en deployment van grote taalmodellen. Ik leg uit wanneer je deze geavanceerde features nodig hebt en welke trade-offs ze meebrengen.
Aangepaste operators
ONNX wordt geleverd met honderden operaties, maar jouw model kan iets unieks gebruiken.
Aangepaste operators laten je operaties definiëren die niet in de standaard ONNX-operatorset bestaan. Je schrijft de operatielogica zelf en vertelt ONNX Runtime hoe die uit te voeren. Dit breidt ONNX uit voorbij de ingebouwde mogelijkheden.
Wanneer heb je aangepaste operators nodig? Wanneer je vooroplopende research gebruikt die ONNX nog niet ondersteunt. Of wanneer je propriëtaire operaties hebt gebouwd die specifiek zijn voor jouw domein. Of wanneer je hardware-specifieke optimalisaties nodig hebt die standaard-ops niet kunnen bieden.
Maar hier zit 'm de crux - aangepaste operators breken portabiliteit. Je model draait niet op systemen die jouw implementatie van de aangepaste operator niet hebben. Als je een PyTorch-model met custom ops naar ONNX exporteert, moet je die ops apart verpakken en registreren bij ONNX Runtime.
Deployment wordt lastiger. Elke omgeving heeft jouw bibliotheek met aangepaste operators nodig. Edge-apparaten, cloudservers en browsers hebben allemaal dezelfde implementatie nodig. Je verliest het grootste voordeel van ONNX: write once, run anywhere.
Hier is een klein werkend voorbeeld van aangepaste operators, zodat je ziet hoe ze werken:
De meeste teams vermijden aangepaste operators waar mogelijk. Probeer eerst combinaties van standaard-ops te gebruiken. Ga alleen custom als standaard-ops je operatie niet kunnen uitdrukken of wanneer performance het vereist.
Omgaan met grote taalmodellen
Geheugen is een probleem bij LLM's. Een model met 7B parameters heeft 28GB nodig in FP32, 14GB in FP16 en 7GB in INT8. De meeste consumentenharde waar kan dat niet aan. Je hebt quantization nodig, vaak agressief naar INT4 of INT3.
Ben je nieuw met LLM's en wil je meer leren, dan is onze Large Language Models (LLMs) Concepts-cursus een goed startpunt.
KV-cachebeheer is belangrijk voor autoregressieve generatie. Elk token dat je genereert moet alle voorgaande tokens kunnen refereren. De cache groeit mee met de sequentielengte. Lange gesprekken eten snel geheugen. ONNX Runtime GenAI bevat geoptimaliseerd KV-cachebeheer dat geheugen hergebruikt en overhead vermindert.
Inferentiesnelheid hangt af van je hardware en modelgrootte. Kleinere modellen (onder 3B parameters) draaien goed op consumentengpu's. Grotere modellen hebben meerdere GPU's of gespecialiseerde inference-accelerators nodig. ONNX Runtime ondersteunt multi-GPU-inference via execution providers, maar je moet je model handmatig over apparaten splitsen.
Flash Attention versnelt het attentiemechanisme in transformers. Standaard attentie is O(n²) in sequentielengte - dat wordt snel traag. Flash Attention vermindert geheugentransfers en verbetert de snelheid zonder resultaten te veranderen. ONNX Runtime bevat Flash Attention-optimalisaties voor ondersteunde hardware.
LLM's trainen met ONNX Runtime is mogelijk maar zeldzaam. De meeste teams trainen met PyTorch of JAX en exporteren daarna naar ONNX voor alleen inference. ONNX Runtime Training bestaat voor fine-tuning en verdere pretraining, maar het ecosysteem rond PyTorch-training is volwassener.
Generatieve AI-workloads krijgen speciale behandeling in ONNX Runtime. De GenAI-extensies bieden high-level API's voor tekstgeneratie, het behouden van state tussen calls en het beheren van beam search of samplingstrategieën. Zo hoef je de generatielogica niet zelf te implementeren.
Het LLM-landschap verandert snel, en ONNX Runtime-updates doen hun best om bij te blijven. De operatorset en optimalisaties worden regelmatig bijgewerkt, maar er is altijd een vertraging tussen research en production-ready ondersteuning.
Conclusie
Je weet nu hoe je modellen uit elk framework converteert, ze optimaliseert voor productie en ze overal uitrolt.
ONNX haalt de frictie weg tussen training en deployment. Train in PyTorch omdat het geweldig is voor research. Deploy met ONNX Runtime omdat het snel is en overal draait. Quantize naar INT8 en halveer je inferencekosten. Schakel over van CPU naar GPU zonder code te wijzigen. Verplaats modellen tussen cloudproviders zonder vendor lock-in.
ONNX maakt het mogelijk.
Het ecosysteem blijft verbeteren. Ondersteuning voor generatieve AI wordt met elke release beter - snellere LLM-inference, beter geheugengebruik en nieuwe optimalisatietechnieken. Hardware-ondersteuning breidt uit naar meer accelerators en edge-apparaten. De community bouwt tools die ONNX makkelijker maken. Je kunt meedoen door nieuwe functies te testen, issues te melden of code bij te dragen via de GitHub-repositories.
Klaar voor de volgende stap? Beheers Explainable Artificial Intelligence (XAI) Concepts en stop eindelijk met modellen als black boxes te zien.