Execute ONNX graphs#

This package implements a python runtime for ONNX in class OnnxInference. It does not depend on scikit-learn, only numpy and this module. However, this module was not really developped to get the fastest python runtime but mostly to easily develop converters.

Python Runtime for ONNX#

Class OnnxInference implements a python runtime for a subset of ONNX operators needed to convert many scikit-learn models.

<<<

import numpy
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.cluster import KMeans
from skl2onnx import to_onnx
from mlprodict.onnxrt import OnnxInference

iris = load_iris()
X = iris.data.astype(numpy.float32)
X_train, X_test = train_test_split(X)
clr = KMeans(n_clusters=3)
clr.fit(X_train)

model_def = to_onnx(clr, X_train.astype(numpy.float32),
                    target_opset=12)

oinf = OnnxInference(model_def, runtime='python')
print(oinf.run({'X': X_test[:5]}))

>>>

    somewhere/.local/lib/python3.9/site-packages/sklearn/cluster/_kmeans.py:870: FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning
      warnings.warn(
    {'label': array([1, 0, 1, 1, 1]), 'scores': array([[4.968, 0.353, 1.842],
           [0.3  , 5.03 , 3.269],
           [4.493, 1.163, 1.281],
           [4.997, 0.397, 1.791],
           [4.814, 0.572, 1.571]], dtype=float32)}

It is usually useful to get information on intermediate results in the graph itself to understand where the discrepencies begin.

<<<

import numpy
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.cluster import KMeans
from skl2onnx import to_onnx
from mlprodict.onnxrt import OnnxInference

iris = load_iris()
X = iris.data.astype(numpy.float32)
X_train, X_test = train_test_split(X)
clr = KMeans(n_clusters=3)
clr.fit(X_train)

model_def = to_onnx(clr, X_train.astype(numpy.float32),
                    target_opset=12)

oinf = OnnxInference(model_def, runtime='python')
print(oinf.run({'X': X_test[:5]}, verbose=1, fLOG=print))

>>>

    somewhere/.local/lib/python3.9/site-packages/sklearn/cluster/_kmeans.py:870: FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning
      warnings.warn(
    +ki='Ad_Addcst': (3,) (dtype=float32 min=38.988189697265625 max=93.33505249023438)
    +ki='Ge_Gemmcst': (3, 4) (dtype=float32 min=0.2562500238418579 max=6.828571319580078)
    +ki='Mu_Mulcst': (1,) (dtype=float32 min=0.0 max=0.0)
    -- OnnxInference: run 7 nodes with 1 inputs
    Onnx-ReduceSumSquare(X) -> Re_reduced0    (name='Re_ReduceSumSquare')
    +kr='Re_reduced0': (5, 1) (dtype=float32 min=36.720001220703125 max=71.86000061035156)
    Onnx-Mul(Re_reduced0, Mu_Mulcst) -> Mu_C0    (name='Mu_Mul')
    +kr='Mu_C0': (5, 1) (dtype=float32 min=0.0 max=0.0)
    Onnx-Gemm(X, Ge_Gemmcst, Mu_C0) -> Ge_Y0    (name='Ge_Gemm')
    +kr='Ge_Y0': (5, 3) (dtype=float32 min=-163.74400329589844 max=-75.56999969482422)
    Onnx-Add(Re_reduced0, Ge_Y0) -> Ad_C01    (name='Ad_Add')
    +kr='Ad_C01': (5, 3) (dtype=float32 min=-91.88400268554688 max=-22.436256408691406)
    Onnx-Add(Ad_Addcst, Ad_C01) -> Ad_C0    (name='Ad_Add1')
    +kr='Ad_C0': (5, 3) (dtype=float32 min=0.023807525634765625 max=27.476768493652344)
    Onnx-ArgMin(Ad_C0) -> label    (name='Ar_ArgMin')
    +kr='label': (5,) (dtype=int64 min=1 max=2)
    Onnx-Sqrt(Ad_C0) -> scores    (name='Sq_Sqrt')
    +kr='scores': (5, 3) (dtype=float32 min=0.154296875 max=5.241828918457031)
    {'label': array([2, 1, 2, 2, 1]), 'scores': array([[1.205, 4.068, 0.692],
           [5.242, 0.372, 3.612],
           [3.432, 1.989, 1.698],
           [1.977, 3.33 , 0.583],
           [5.05 , 0.154, 3.463]], dtype=float32)}

The verbosity can be increased.

<<<

import numpy
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.cluster import KMeans
from skl2onnx import to_onnx
from mlprodict.onnxrt import OnnxInference

iris = load_iris()
X = iris.data.astype(numpy.float32)
X_train, X_test = train_test_split(X)
clr = KMeans(n_clusters=3)
clr.fit(X_train)

model_def = to_onnx(clr, X_train.astype(numpy.float32),
                    target_opset=12)

oinf = OnnxInference(model_def, runtime='python')
print(oinf.run({'X': X_test[:5]}, verbose=3, fLOG=print))

>>>

    somewhere/.local/lib/python3.9/site-packages/sklearn/cluster/_kmeans.py:870: FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning
      warnings.warn(
    +ki='Ad_Addcst': (3,) (dtype=float32 min=39.13035583496094 max=95.00074005126953
    [95.001 39.13  63.112]
    +ki='Ge_Gemmcst': (3, 4) (dtype=float32 min=0.25277769565582275 max=6.8689656257629395
    [[6.869 3.093 5.803 2.138]
     [5.017 3.442 1.433 0.253]
     [5.883 2.73  4.364 1.417]]
    +ki='Mu_Mulcst': (1,) (dtype=float32 min=0.0 max=0.0
    [0.]
    -kv='X' shape=(5, 4) dtype=float32 min=0.20000000298023224 max=6.300000190734863
    -- OnnxInference: run 7 nodes with 1 inputs
    Onnx-ReduceSumSquare(X) -> Re_reduced0    (name='Re_ReduceSumSquare')
    +kr='Re_reduced0': (5, 1) (dtype=float32 min=35.90999984741211 max=75.79000091552734)
    [[68.09]
     [35.91]
     [75.79]
     [38.25]
     [45.14]]
    Onnx-Mul(Re_reduced0, Mu_Mulcst) -> Mu_C0    (name='Mu_Mul')
    +kr='Mu_C0': (5, 1) (dtype=float32 min=0.0 max=0.0)
    [[0.]
     [0.]
     [0.]
     [0.]
     [0.]]
    Onnx-Gemm(X, Ge_Gemmcst, Mu_C0) -> Ge_Y0    (name='Ge_Gemm')
    +kr='Ge_Y0': (5, 3) (dtype=float32 min=-169.47930908203125 max=-74.90277862548828)
    [[-160.486  -90.435 -130.496]
     [-104.759  -74.903  -88.236]
     [-169.479  -97.862 -138.174]
     [-109.883  -77.111  -92.189]
     [-115.339  -84.049  -97.395]]
    Onnx-Add(Re_reduced0, Ge_Y0) -> Ad_C01    (name='Ad_Add')
    +kr='Ad_C01': (5, 3) (dtype=float32 min=-93.6893081665039 max=-22.07166290283203)
    [[-92.396 -22.345 -62.406]
     [-68.849 -38.993 -52.326]
     [-93.689 -22.072 -62.384]
     [-71.633 -38.861 -53.939]
     [-70.199 -38.909 -52.255]]
    Onnx-Add(Ad_Addcst, Ad_C01) -> Ad_C0    (name='Ad_Add1')
    +kr='Ad_C0': (5, 3) (dtype=float32 min=0.13757705688476562 max=26.152122497558594)
    [[ 2.605 16.785  0.706]
     [26.152  0.138 10.786]
     [ 1.311 17.059  0.728]
     [23.367  0.269  9.174]
     [24.801  0.221 10.857]]
    Onnx-ArgMin(Ad_C0) -> label    (name='Ar_ArgMin')
    +kr='label': (5,) (dtype=int64 min=1 max=2)
    [2 1 2 1 1]
    Onnx-Sqrt(Ad_C0) -> scores    (name='Sq_Sqrt')
    +kr='scores': (5, 3) (dtype=float32 min=0.3709138035774231 max=5.113914489746094)
    [[1.614 4.097 0.84 ]
     [5.114 0.371 3.284]
     [1.145 4.13  0.853]
     [4.834 0.519 3.029]
     [4.98  0.47  3.295]]
    [VALIDATE] type <class 'onnx.onnx_ml_pb2.ModelProto'>
    [VALIDATE] mis={}
    {'label': array([2, 1, 2, 1, 1]), 'scores': array([[1.614, 4.097, 0.84 ],
           [5.114, 0.371, 3.284],
           [1.145, 4.13 , 0.853],
           [4.834, 0.519, 3.029],
           [4.98 , 0.47 , 3.295]], dtype=float32)}

Other runtimes with OnnxInference#

OnnxInference can also call onnxruntime to compute the predictions by using runtime='onnxruntime1'.

<<<

import numpy
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.cluster import KMeans
from skl2onnx import to_onnx
from mlprodict.onnxrt import OnnxInference

iris = load_iris()
X = iris.data.astype(numpy.float32)
X_train, X_test = train_test_split(X)
clr = KMeans(n_clusters=3)
clr.fit(X_train)

model_def = to_onnx(clr, X_train.astype(numpy.float32),
                    target_opset=12)

oinf = OnnxInference(model_def, runtime='onnxruntime1')
print(oinf.run({'X': X_test[:5]}))

>>>

    somewhere/.local/lib/python3.9/site-packages/sklearn/cluster/_kmeans.py:870: FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning
      warnings.warn(
    {'label': array([2, 2, 2, 1, 1], dtype=int64), 'scores': array([[4.858, 1.743, 0.823],
           [4.61 , 1.556, 0.743],
           [4.218, 1.082, 0.918],
           [2.956, 0.504, 2.299],
           [3.607, 0.882, 1.575]], dtype=float32)}

Intermediate cannot be seen but the class may decompose the ONNX graph into smaller graphs, one per operator, to look into intermediate results.

<<<

import numpy
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.cluster import KMeans
from skl2onnx import to_onnx
from mlprodict.onnxrt import OnnxInference

iris = load_iris()
X = iris.data.astype(numpy.float32)
X_train, X_test = train_test_split(X)
clr = KMeans(n_clusters=3)
clr.fit(X_train)

model_def = to_onnx(clr, X_train.astype(numpy.float32),
                    target_opset=12)

oinf = OnnxInference(model_def, runtime='onnxruntime2')
print(oinf.run({'X': X_test[:5]}, verbose=1, fLOG=print))

>>>

    somewhere/.local/lib/python3.9/site-packages/sklearn/cluster/_kmeans.py:870: FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning
      warnings.warn(
    +ki='Ad_Addcst': (3,) (dtype=float32 min=39.464210510253906 max=93.69570922851562)
    +ki='Ge_Gemmcst': (3, 4) (dtype=float32 min=0.2558823227882385 max=6.889999866485596)
    +ki='Mu_Mulcst': (1,) (dtype=float32 min=0.0 max=0.0)
    -- OnnxInference: run 7 nodes with 1 inputs
    Onnx-ReduceSumSquare(X) -> Re_reduced0    (name='Re_ReduceSumSquare')
    +kr='Re_reduced0': (5, 1) (dtype=float32 min=27.32000160217285 max=67.20999908447266)
    Onnx-Mul(Re_reduced0, Mu_Mulcst) -> Mu_C0    (name='Mu_Mul')
    +kr='Mu_C0': (5, 1) (dtype=float32 min=0.0 max=0.0)
    Onnx-Gemm(X, Ge_Gemmcst, Mu_C0) -> Ge_Y0    (name='Ge_Gemm')
    +kr='Ge_Y0': (5, 3) (dtype=float32 min=-158.5153350830078 max=-65.11764526367188)
    Onnx-Add(Re_reduced0, Ge_Y0) -> Ad_C01    (name='Ad_Add')
    +kr='Ad_C01': (5, 3) (dtype=float32 min=-91.30533599853516 max=-23.957061767578125)
    Onnx-Add(Ad_Addcst, Ad_C01) -> Ad_C0    (name='Ad_Add1')
    +kr='Ad_C0': (5, 3) (dtype=float32 min=0.26983642578125 max=28.71038055419922)
    Onnx-Sqrt(Ad_C0) -> scores    (name='Sq_Sqrt')
    +kr='scores': (5, 3) (dtype=float32 min=0.5194578170776367 max=5.3582072257995605)
    Onnx-ArgMin(Ad_C0) -> label    (name='Ar_ArgMin')
    +kr='label': (5,) (dtype=int64 min=1 max=2)
    {'label': array([2, 2, 1, 2, 2], dtype=int64), 'scores': array([[2.422, 3.485, 1.083],
           [2.298, 2.817, 0.519],
           [5.358, 1.291, 3.574],
           [1.735, 3.696, 0.652],
           [1.546, 3.938, 0.856]], dtype=float32)}

Finally, a last runtime ‘python_compiled’ converts some part of the class OnnxInference into python code then dynamically compiled. As a consequence, interdiate results cannot be seen anymore.

<<<

import numpy
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.cluster import KMeans
from skl2onnx import to_onnx
from mlprodict.onnxrt import OnnxInference

iris = load_iris()
X = iris.data.astype(numpy.float32)
X_train, X_test = train_test_split(X)
clr = KMeans(n_clusters=3)
clr.fit(X_train)

model_def = to_onnx(clr, X_train.astype(numpy.float32),
                    target_opset=12)

oinf = OnnxInference(model_def, runtime='python_compiled')
print(oinf.run({'X': X_test[:5]}))

>>>

    somewhere/.local/lib/python3.9/site-packages/sklearn/cluster/_kmeans.py:870: FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning
      warnings.warn(
    {'label': array([1, 1, 2, 0, 0]), 'scores': array([[5.036, 0.433, 1.732],
           [5.021, 0.688, 1.674],
           [3.012, 2.427, 0.717],
           [0.74 , 4.964, 3.504],
           [0.572, 4.832, 3.332]], dtype=float32)}