Introduction
ONNX Runtime est le runtime d'inférence le plus performant pour les modèles ONNX en 2026, supportant CPU, GPU (CUDA, DirectML), et même WebAssembly. Contrairement aux frameworks comme TensorFlow ou PyTorch qui alourdissent le déploiement, ONNX Runtime optimise le graphe du modèle au niveau bas, réduisant la latence de 5 à 10x sur du hardware standard. Pour un ingénieur ML expert, c'est l'outil indispensable en production : il gère les inputs dynamiques, le batching asynchrone et les providers hardware-specific. Imaginez comme un moteur turbo pour vos modèles – il parse le graphe ONNX une seule fois, puis exécute en JIT avec des kernels vectorisés. Ce tutoriel vous guide pas à pas pour générer un modèle Iris (sklearn -> ONNX), inférer sur CPU/GPU, optimiser les sessions et mesurer les perfs réelles. À la fin, vous déployez des pipelines scalables, prêts pour Kubernetes ou edge devices.
Prérequis
- Python 3.10 ou supérieur
- pip à jour
- scikit-learn, numpy pour générer le modèle
- GPU NVIDIA avec CUDA 12+ pour la partie GPU (optionnel, sinon CPU uniquement)
- Connaissances avancées en ML : ONNX basics, graphes computationnels et profiling
Installation des dépendances
pip install onnxruntime scikit-learn skl2onnx numpy pandas matplotlib
# Pour GPU NVIDIA :
pip install onnxruntime-gpuCette commande installe ONNX Runtime pour CPU et prépare les outils pour convertir sklearn en ONNX via skl2onnx. Pour GPU, onnxruntime-gpu active CUDA automatiquement si disponible. Évitez les conflits en utilisant un virtualenv ; vérifiez avec python -c 'import onnxruntime as ort; print(ort.__version__)'.
Génération d'un modèle ONNX de référence
Nous utilisons le dataset Iris pour un classificateur DecisionTree simple, convertible en ONNX. Cela illustre parfaitement les shapes dynamiques ([None, 4] pour batch variable). Le modèle généré est lightweight (few KB), idéal pour tester les optimisations sans dépendances externes.
Créer le modèle Iris ONNX
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType
import numpy as np
# Charger et entraîner le modèle
iris = load_iris()
clf = DecisionTreeClassifier(max_depth=3, random_state=42)
clf.fit(iris.data, iris.target)
# Définir le type d'input dynamique
initial_type = [('float_input', FloatTensorType([None, 4]))]
# Convertir en ONNX
model_onnx = convert_sklearn(
clf,
initial_types=initial_type,
target_opset=15
)
# Sauvegarder
with open("iris.onnx", "wb") as f:
f.write(model_onnx.SerializeToString())
print("Modèle iris.onnx généré avec succès.")
print(f"Inputs: {model_onnx.graph.input}")
print(f"Outputs: {model_onnx.graph.output}")Ce script entraîne un arbre de décision sur Iris et l'exporte en ONNX opset 15 (stable en 2026). Le FloatTensorType([None,4]) permet des batches variables, clé pour l'expert. Piège : toujours spécifier target_opset pour compatibilité runtime ; testez avec onnx.checker.check_model(model_onnx).
Inférence basique sur CPU
import onnxruntime as ort
import numpy as np
# Charger la session CPU par défaut
session = ort.InferenceSession("iris.onnx")
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name
# Input exemple : setosa
input_data = np.array([[5.1, 3.5, 1.4, 0.2]], dtype=np.float32)
# Inférence
outputs = session.run([output_name], {input_name: input_data})
print(f"Prédiction: {np.argmax(outputs[0])} (classe {iris.target_names[np.argmax(outputs[0])]})")
print(f"Probabilités: {outputs[0][0]}")La session se crée avec providers CPU par défaut, parse le graphe ONNX et exécute l'inférence. Utilisez get_inputs() pour nommer dynamiquement les tensors. Piège courant : dtype np.float32 obligatoire, sinon crash ; pour prod, pré-allouez les outputs avec run(None, ...).
Accélération GPU avec providers
Analogie : Les providers sont comme des pilotes hardware – CUDAExecutionProvider priorise le GPU si disponible, fallback CPU. En 2026, cela booste les modèles CNN/Transformer x5-20. Vérifiez avec session.get_providers().
Inférence optimisée GPU
import onnxruntime as ort
import numpy as np
providers = [
'CUDAExecutionProvider',
'CPUExecutionProvider'
]
session = ort.InferenceSession("iris.onnx", providers=providers)
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name
# Même input
input_data = np.array([[5.1, 3.5, 1.4, 0.2]], dtype=np.float32)
outputs = session.run([output_name], {input_name: input_data})
print(f"Provider utilisé: {session.get_providers()[0]}")
print(f"Prédiction GPU: {np.argmax(outputs[0])}")Les providers tentent CUDA en premier ; utilisez ort.get_device() pour confirmer GPU. Gain immédiat sur modèles >1MB. Piège : synchronisez CUDA avec torch.cuda.synchronize() si mixé ; pour multi-GPU, spécifiez device_id.
Optimisation avancée de la session
import onnxruntime as ort
import numpy as np
# Options de session pour optimisations
session_options = ort.SessionOptions()
session_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_EXTENDED
session_options.intra_op_num_threads = 4 # Parallélisme CPU
session_options.enable_cpu_mem_arena = False # Pour petits modèles
providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']
session = ort.InferenceSession("iris.onnx", sess_options=session_options, providers=providers)
input_name = session.get_inputs()[0].name
input_data = np.random.rand(1, 4).astype(np.float32)
outputs = session.run(None, {input_name: input_data})
print("Session optimisée prête.")
print(f"Temps de création: rapide grâce aux opts.")ORT_ENABLE_EXTENDED fusionne les nœuds redondants et vectorise ; intra_op_num_threads exploite les cores CPU. Pour GPU, ajoutez session_options.enable_mem_pattern=True. Piège : trop d'opts sur petits modèles peut overhead ; profilez toujours.
Gestion des batches et dynamiques
Les shapes [batch_size, features] dynamiques scalent l'inférence. Pour expert, IO binding évite les copies CPU-GPU, boostant throughput x2-3 en prod.
Inférence en batch avec IO binding
import onnxruntime as ort
import numpy as np
session = ort.InferenceSession("iris.onnx")
# Batch de 32 échantillons
batch_data = np.random.rand(32, 4).astype(np.float32)
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name
# IO Binding pour zéro-copy (GPU)
io_binding = session.io_binding()
io_binding.bind_cpu_input(input_name, batch_data)
io_binding.bind_output(output_name)
session.run_with_iobinding(io_binding)
outputs = io_binding.copy_outputs_to_cpu()[output_name]
print(f"Batch shape: {outputs.shape}")
print(f"Moyenne prédictions: {np.mean(np.argmax(outputs, axis=1))}")IO binding mappe directement les tensors sans copie, critique pour gros batches. bind_cpu_input fallback GPU si provider match. Piège : pour GPU, bind_cuda_input nécessite cuda pointers ; scalez batch_size à votre VRAM.
Benchmark des performances
import onnxruntime as ort
import numpy as np
import time
session = ort.InferenceSession("iris.onnx")
input_name = session.get_inputs()[0].name
batch_size = 1024
warmup = 10
iters = 100
batch_data = np.random.rand(batch_size, 4).astype(np.float32)
# Warmup
for _ in range(warmup):
session.run(None, {input_name: batch_data})
start = time.perf_counter()
for _ in range(iters):
session.run(None, {input_name: batch_data})
end = time.perf_counter()
latency_ms = (end - start) * 1000 / iters
throughput = batch_size / ((end - start) / iters)
print(f"Latence moyenne: {latency_ms:.2f} ms")
print(f"Throughput: {throughput:.0f} échantillons/s")Ce benchmark mesure latence/throughput réalistes post-warmup (cache JIT). Utilisez pour comparer providers. Piège : incluez toujours warmup, car premier run x10 plus lent ; intégrez tensorrt_execution_provider pour +50% perf sur NVIDIA.
Bonnes pratiques
- Profilez toujours : Utilisez ort.profiler pour JSON traces, analysez bottlenecks avec Chrome DevTools.
- Préparez les inputs : Normalisez en float32, batcher à 80% VRAM max pour éviter OOM.
- Multi-threading : Set inter_op_num_threads pour pipelines parallèles en serving.
- Cachez les sessions : Réutilisez une session par modèle, clonez pour multi-instances.
- Versionnez ONNX : Opset 18+ pour features 2026 comme attention fusion.
Erreurs courantes à éviter
- Mauvais dtype/shape : Float64 ou shape statique crash ; toujours [None,...] et np.float32.
- Pas de fallback providers : GPU down ? Ajoutez 'CPUExecutionProvider' en dernier.
- Oubli warmup : Benchmarks faux ; premier run inclut parse/optim.
- IO sans binding : Copies CPU-GPU tuent perf ; forcez io_binding en prod.
Pour aller plus loin
Intégrez ONNX Runtime dans FastAPI pour serving scalable, ou explorez TensorRT provider pour RTX 50xx. Consultez la doc officielle ONNX Runtime et nos formations Learni sur le déploiement ML. Testez sur HuggingFace ONNX models pour LLMs.