Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
TMVA_SOFIE_ONNX.py
Go to the documentation of this file.
1## \file
2## \ingroup tutorial_ml
3## \notebook -nodraw
4## This macro provides a simple example for:
5## - creating a model with Pytorch and export to ONNX
6## - parsing the ONNX file with SOFIE and generate C++ code
7## - compiling the model using ROOT Cling
8## - run the code and optionally compare with ONNXRuntime
9##
10## \macro_code
11## \macro_output
12## \author Lorenzo Moneta
13
14import inspect
15
16import numpy as np
17import ROOT
18import torch
19import torch.nn as nn
20
21
22def CreateAndTrainModel(modelName):
23
24 model = nn.Sequential(nn.Linear(32, 16), nn.ReLU(), nn.Linear(16, 8), nn.ReLU(), nn.Linear(8, 2), nn.Softmax(dim=1))
25
26 criterion = nn.MSELoss()
27 optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
28
29 # train model with the random data
30 for i in range(500):
31 x = torch.randn(2, 32)
32 y = torch.randn(2, 2)
33 y_pred = model(x)
34 loss = criterion(y_pred, y)
38
39 # *******************************************************
40 ## EXPORT to ONNX
41 #
42 # need to evaluate the model before exporting to ONNX
43 # and to provide a dummy input tensor to set the input model shape
45
46 modelFile = modelName + ".onnx"
47 dummy_x = torch.randn(1, 32)
48 model(dummy_x)
49
50 # check for torch.onnx.export parameters
51 def filtered_kwargs(func, **candidate_kwargs):
52 sig = inspect.signature(func)
53 return {k: v for k, v in candidate_kwargs.items() if k in sig.parameters}
54
55 kwargs = filtered_kwargs(
57 input_names=["input"],
58 output_names=["output"],
59 external_data=False, # may not exist
60 dynamo=True, # may not exist
61 )
62 print("calling torch.onnx.export with parameters", kwargs)
63
64 torch.onnx.export(model, dummy_x, modelFile, **kwargs)
65
66 print("model exported to ONNX as", modelFile)
67 return modelFile
68
69
70def ParseModel(modelFile, verbose=False):
71
73 model = parser.Parse(modelFile, verbose)
74 #
75 # print model weights
76 if verbose:
78 data = model.GetTensorData["float"]("0weight")
79 print("0weight", data)
80 data = model.GetTensorData["float"]("2weight")
81 print("2weight", data)
82
83 # Generating inference code
85 # generate header file (and .dat file) with modelName+.hxx
87 if verbose:
89
90 modelCode = modelFile.replace(".onnx", ".hxx")
91 print("Generated model header file ", modelCode)
92 return modelCode
93
94
95###################################################################
96## Step 1 : Create and train the model, export it to ONNX
97###################################################################
98
99# use an arbitrary modelName
100modelName = "LinearModel"
101modelFile = CreateAndTrainModel(modelName)
102
103###################################################################
104## Step 2 : Parse model and generate inference code with SOFIE
105###################################################################
106
107modelCode = ParseModel(modelFile, False)
108
109###################################################################
110## Step 3 : Compile the generated C++ model code
111###################################################################
112
113ROOT.gInterpreter.Declare('#include "' + modelCode + '"')
114
115###################################################################
116## Step 4: Evaluate the model
117###################################################################
118
119# get first the SOFIE session namespace
120sofie = getattr(ROOT, "TMVA_SOFIE_" + modelName)
121session = sofie.Session()
122
123x = np.random.normal(0, 1, (1, 32)).astype(np.float32)
124print("\n************************************************************")
125print("Running inference with SOFIE ")
126print("\ninput to model is ", x)
127y = session.infer(x)
128# output shape is (1,2)
129y_sofie = np.asarray(y.data())
130print("-> output using SOFIE = ", y_sofie)
131
132# check inference with onnx
133try:
134 import onnxruntime as ort
135
136 # Load model
137 print("Running inference with ONNXRuntime ")
138 ort_session = ort.InferenceSession(modelFile)
139
140 # Run inference
141 outputs = ort_session.run(None, {"input": x})
142 y_ort = outputs[0]
143 print("-> output using ORT =", y_ort)
144
145 testFailed = abs(y_sofie - y_ort) > 0.01
146 if np.any(testFailed):
147 raise RuntimeError("Result is different between SOFIE and ONNXRT")
148 else:
149 print("OK")
150
151except ImportError:
152 print("Missing ONNXRuntime: skipping comparison test")
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.