Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
TMVA_SOFIE_ONNX.py File Reference

Detailed Description

View in nbviewer Open in SWAN
This macro provides a simple example for:

  • creating a model with Pytorch and export to ONNX
  • parsing the ONNX file with SOFIE and generate C++ code
  • compiling the model using ROOT Cling
  • run the code and optionally compare with ONNXRuntime
import inspect
import numpy as np
import ROOT
import torch
import torch.nn as nn
def CreateAndTrainModel(modelName):
model = nn.Sequential(nn.Linear(32, 16), nn.ReLU(), nn.Linear(16, 8), nn.ReLU(), nn.Linear(8, 2), nn.Softmax(dim=1))
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
# train model with the random data
for i in range(500):
x = torch.randn(2, 32)
y = torch.randn(2, 2)
y_pred = model(x)
loss = criterion(y_pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
# *******************************************************
## EXPORT to ONNX
#
# need to evaluate the model before exporting to ONNX
# and to provide a dummy input tensor to set the input model shape
model.eval()
modelFile = modelName + ".onnx"
dummy_x = torch.randn(1, 32)
model(dummy_x)
# check for torch.onnx.export parameters
def filtered_kwargs(func, **candidate_kwargs):
sig = inspect.signature(func)
return {k: v for k, v in candidate_kwargs.items() if k in sig.parameters}
kwargs = filtered_kwargs(
torch.onnx.export,
input_names=["input"],
output_names=["output"],
external_data=False, # may not exist
dynamo=True, # may not exist
)
print("calling torch.onnx.export with parameters", kwargs)
torch.onnx.export(model, dummy_x, modelFile, **kwargs)
print("model exported to ONNX as", modelFile)
return modelFile
def ParseModel(modelFile, verbose=False):
parser = ROOT.TMVA.Experimental.SOFIE.RModelParser_ONNX()
model = parser.Parse(modelFile, verbose)
#
# print model weights
if verbose:
model.PrintInitializedTensors()
data = model.GetTensorData["float"]("0weight")
print("0weight", data)
data = model.GetTensorData["float"]("2weight")
print("2weight", data)
# Generating inference code
model.Generate()
# generate header file (and .dat file) with modelName+.hxx
model.OutputGenerated()
if verbose:
model.PrintGenerated()
modelCode = modelFile.replace(".onnx", ".hxx")
print("Generated model header file ", modelCode)
return modelCode
###################################################################
## Step 1 : Create and train the model, export it to ONNX
###################################################################
# use an arbitrary modelName
modelName = "LinearModel"
modelFile = CreateAndTrainModel(modelName)
###################################################################
## Step 2 : Parse model and generate inference code with SOFIE
###################################################################
modelCode = ParseModel(modelFile, False)
###################################################################
## Step 3 : Compile the generated C++ model code
###################################################################
ROOT.gInterpreter.Declare('#include "' + modelCode + '"')
###################################################################
## Step 4: Evaluate the model
###################################################################
# get first the SOFIE session namespace
sofie = getattr(ROOT, "TMVA_SOFIE_" + modelName)
session = sofie.Session()
x = np.random.normal(0, 1, (1, 32)).astype(np.float32)
print("\n************************************************************")
print("Running inference with SOFIE ")
print("\ninput to model is ", x)
y = session.infer(x)
# output shape is (1,2)
y_sofie = np.asarray(y.data())
print("-> output using SOFIE = ", y_sofie)
# check inference with onnx
try:
import onnxruntime as ort
# Load model
print("Running inference with ONNXRuntime ")
ort_session = ort.InferenceSession(modelFile)
# Run inference
outputs = ort_session.run(None, {"input": x})
y_ort = outputs[0]
print("-> output using ORT =", y_ort)
testFailed = abs(y_sofie - y_ort) > 0.01
if np.any(testFailed):
raise RuntimeError("Result is different between SOFIE and ONNXRT")
else:
print("OK")
except ImportError:
print("Missing ONNXRuntime: skipping comparison test")
calling torch.onnx.export with parameters {'input_names': ['input'], 'output_names': ['output'], 'external_data': False, 'dynamo': True}
[torch.onnx] Obtain model graph for `Sequential([...]` with `torch.export.export(..., strict=False)`...
[torch.onnx] Obtain model graph for `Sequential([...]` with `torch.export.export(..., strict=False)`... ✅
[torch.onnx] Run decompositions...
[torch.onnx] Run decompositions... ✅
[torch.onnx] Translate the graph into ONNX...
[torch.onnx] Translate the graph into ONNX... ✅
[torch.onnx] Optimize the ONNX graph...
[torch.onnx] Optimize the ONNX graph... ✅
model exported to ONNX as LinearModel.onnx
Generated model header file LinearModel.hxx
************************************************************
Running inference with SOFIE
input to model is [[ 1.4956051 -0.45741847 0.22977142 -2.242973 0.6244122 -0.20098144
-1.0824488 -1.5315623 -0.8679467 0.6189445 1.1098382 0.50021094
0.5726558 0.49593762 -0.46292037 0.10965236 -0.41202903 0.18202354
0.23431286 -0.7077288 0.68766975 -0.11586156 -1.4412189 0.52631104
1.0371273 -1.0557041 0.3855289 0.53025705 1.604358 0.40662277
1.1528642 1.2975549 ]]
-> output using SOFIE = [0.43782946 0.5621705 ]
Missing ONNXRuntime: skipping comparison test
Author
Lorenzo Moneta

Definition in file TMVA_SOFIE_ONNX.py.