SerializeToString and ParseFromString#

Startup#

This section creates an ONNX graph if there is not one.

import numpy
import onnx
from pyquickhelper.pycode.profiling import profile, profile2graph
from cpyquickhelper.numbers.speed_measure import measure_time
import matplotlib.pyplot as plt
import pandas
from tqdm import tqdm
from onnx.checker import check_model
from mlprodict.testing.experimental_c_impl.experimental_c import code_optimisation
from mlprodict.plotting.text_plot import onnx_simple_text_plot
from mlprodict.npy.xop import loadop
try:
    from mlprodict.onnx_tools._onnx_check_model import check_model as check_model_py
except ImportError:
    check_model_py = None

Available optimisation on this machine.

print(code_optimisation())
AVX-omp=8

Build an ONNX graph of different size#

def build_model(n_nodes, size, opv=15):
    OnnxAdd, OnnxIdentity = loadop('Add', 'Identity')
    x = 'X'
    for n in range(n_nodes):
        y = OnnxAdd(x, numpy.random.randn(size).astype(numpy.float32),
                    op_version=opv)
        x = y
    final = OnnxIdentity(x, op_version=opv, output_names=['Y'])
    x = numpy.zeros((10, 10), dtype=numpy.float32)
    return final.to_onnx({'X': x}, {'Y': x}, target_opset=opv)


model = build_model(2, 5)
print(onnx_simple_text_plot(model))
opset: domain='' version=15
input: name='X' type=dtype('float32') shape=[10, 10]
init: name='init' type=dtype('float32') shape=(0,) -- array([ 0.4048775,  0.3292866, -1.432597 , -2.725416 , -0.7655212],
      dtype=float32)
init: name='init_1' type=dtype('float32') shape=(0,) -- array([-0.26076522,  1.2521883 ,  1.2011648 , -0.5322365 , -0.20671108],
      dtype=float32)
Add(X, init) -> out_add_0
  Add(out_add_0, init_1) -> Y
output: name='Y' type=dtype('float32') shape=[10, 10]

Measure the time of serialization functions#

def parse(buffer):
    proto = onnx.ModelProto()
    proto.ParseFromString(buffer)
    return proto


data = []
nodes = [5, 10, 20]
for size in tqdm([10, 100, 1000, 10000, 100000, 200000, 300000]):
    for n_nodes in nodes:
        repeat = 20 if size < 100000 else 5
        onx = build_model(n_nodes, size)
        serialized = onx.SerializeToString()
        onnx_size = len(serialized)
        obs = measure_time(lambda: onx.SerializeToString(),
                           div_by_number=True, repeat=repeat)
        obs['size'] = size
        obs['n_nodes'] = n_nodes
        obs['onnx_size'] = onnx_size
        obs['task'] = "SerializeToString"
        data.append(obs)

        parsed = parse(serialized)
        obs = measure_time(lambda: parse(serialized),
                           div_by_number=True, repeat=repeat)
        obs['size'] = size
        obs['n_nodes'] = n_nodes
        obs['onnx_size'] = onnx_size
        obs['task'] = "ParseFromString"
        data.append(obs)

        obs = measure_time(lambda: check_model(onx, full_check=False),
                           div_by_number=True, repeat=repeat)
        obs['size'] = size
        obs['n_nodes'] = n_nodes
        obs['onnx_size'] = onnx_size
        obs['task'] = "check_model"
        data.append(obs)

        if check_model_py is None:
            continue

        obs = measure_time(lambda: check_model_py(onx),
                           div_by_number=True, repeat=repeat)
        obs['size'] = size
        obs['n_nodes'] = n_nodes
        obs['onnx_size'] = onnx_size
        obs['task'] = "check_model_py"
        data.append(obs)
  0%|          | 0/7 [00:00<?, ?it/s]
 14%|#4        | 1/7 [00:13<01:18, 13.03s/it]
 29%|##8       | 2/7 [00:26<01:05, 13.08s/it]
 43%|####2     | 3/7 [00:39<00:53, 13.26s/it]
 57%|#####7    | 4/7 [00:56<00:43, 14.61s/it]
 71%|#######1  | 5/7 [01:09<00:28, 14.11s/it]
 86%|########5 | 6/7 [01:32<00:17, 17.14s/it]
100%|##########| 7/7 [02:04<00:00, 22.10s/it]
100%|##########| 7/7 [02:04<00:00, 17.84s/it]

time

df = pandas.DataFrame(data).sort_values(
    ['task', 'onnx_size', 'size', 'n_nodes'])
df[['task', 'onnx_size', 'size', 'n_nodes', 'average']]
task onnx_size size n_nodes average
1 ParseFromString 573 10 5 0.000069
5 ParseFromString 1108 10 10 0.000102
9 ParseFromString 2274 10 20 0.000161
13 ParseFromString 2383 100 5 0.000073
17 ParseFromString 4728 100 10 0.000104
... ... ... ... ... ...
67 check_model_py 8000770 200000 10 0.016126
59 check_model_py 8001596 100000 20 0.015069
79 check_model_py 12000770 300000 10 0.022268
71 check_model_py 16001596 200000 20 0.031856
83 check_model_py 24001596 300000 20 0.043999

84 rows × 5 columns



Summary#

df.to_excel("time.xlsx", index=False)
piv = df.pivot(index='onnx_size', columns='task', values='average')
piv
task ParseFromString SerializeToString check_model check_model_py
onnx_size
573 0.000069 0.000087 0.000140 0.001812
1108 0.000102 0.000132 0.000232 0.003198
2274 0.000161 0.000218 0.000383 0.005960
2383 0.000073 0.000089 0.000154 0.001806
4728 0.000104 0.000134 0.000236 0.003209
9514 0.000167 0.000220 0.000389 0.005996
20389 0.000080 0.000093 0.000163 0.001841
40739 0.000119 0.000139 0.000247 0.003272
81535 0.000197 0.000231 0.000418 0.006111
200399 0.000113 0.000119 0.000252 0.001996
400759 0.000265 0.000232 0.000610 0.003562
801575 0.000608 0.000559 0.001186 0.006581
2000404 0.000947 0.000870 0.001790 0.004266
4000405 0.001215 0.001173 0.002384 0.008343
4000770 0.001765 0.001622 0.003341 0.007825
6000405 0.001737 0.001693 0.003383 0.011407
8000770 0.002312 0.002251 0.004532 0.016126
8001596 0.003392 0.003115 0.006365 0.015069
12000770 0.003329 0.003253 0.006510 0.022268
16001596 0.004460 0.004385 0.008697 0.031856
24001596 0.006515 0.006405 0.012705 0.043999


Graph#

fig, ax = plt.subplots(1, 1)
piv.plot(title="Time processing of serialization functions\n"
               "lower better", ax=ax)
ax.set_xlabel("onnx size")
ax.set_ylabel("s")
Time processing of serialization functions lower better
Text(33.972222222222214, 0.5, 's')

Conclusion#

This graph shows that implementing check_model in python is much slower than the C++ version. However, protobuf prevents from sharing ModelProto from Python to C++ (see Python Updates) unless the python package is compiled with a specific setting (problably slower). A profiling shows that the code spends quite some time in function getattr().

ps = profile(lambda: check_model_py(onx))[0]
root, nodes = profile2graph(ps, clean_text=lambda x: x.split('/')[-1])
text = root.to_text()
print(text)

# plt.show()
__getattr__                                                  --  160  160 -- 0.00103 0.00283 -- _onnx_check_model.py:68:__getattr__ (__getattr__)
    <method 'endswith' of 'str' objects>                     --  160  160 -- 0.00020 0.00020 -- ~:0:<method 'endswith' of 'str' objects> (<method 'endswith' of 'str' objects>) +++
    <built-in method builtins.getattr>                       --  160  160 -- 0.00067 0.00067 -- ~:0:<built-in method builtins.getattr> (<built-in method builtins.getattr>) +++
    <built-in method builtins.hasattr>                       --  160  160 -- 0.00094 0.00094 -- ~:0:<built-in method builtins.hasattr> (<built-in method builtins.hasattr>) +++
__init__                                                     --    2    2 -- 0.00002 0.00002 -- _onnx_check_model.py:312:__init__ (__init__)
get_ir_version                                               --   21   21 -- 0.00003 0.00003 -- _onnx_check_model.py:326:get_ir_version (get_ir_version)
get_opset_imports                                            --   21   21 -- 0.00001 0.00001 -- _onnx_check_model.py:334:get_opset_imports (get_opset_imports)
set_opset_imports                                            --    2    2 -- 0.00000 0.00000 -- _onnx_check_model.py:338:set_opset_imports (set_opset_imports)
__init__                                                     --    2    3 -- 0.00001 0.00001 -- _onnx_check_model.py:377:__init__ (__init__)
    copy                                                     --    1    1 -- 0.00000 0.00001 -- _onnx_check_model.py:398:copy (copy)
        __init__                                             --    1    1 -- 0.00000 0.00000 -- _onnx_check_model.py:377:__init__ (__init__) +++
this_graph_has                                               --   81   81 -- 0.00008 0.00008 -- _onnx_check_model.py:388:this_graph_has (this_graph_has)
this_or_ancestor_graph_has                                   --   60   80 -- 0.00013 0.00021 -- _onnx_check_model.py:392:this_or_ancestor_graph_has (this_or_ancestor_graph_has)
    this_graph_has                                           --   80   80 -- 0.00008 0.00008 -- _onnx_check_model.py:388:this_graph_has (this_graph_has) +++
    this_or_ancestor_graph_has                               --   20   20 -- 0.00002 0.00003 -- _onnx_check_model.py:392:this_or_ancestor_graph_has (this_or_ancestor_graph_has) +++
_enforce_has_field                                           --   46   46 -- 0.00007 0.00025 -- _onnx_check_model.py:405:_enforce_has_field (_enforce_has_field)
    <built-in method builtins.hasattr>                       --   46   46 -- 0.00018 0.00018 -- ~:0:<built-in method builtins.hasattr> (<built-in method builtins.hasattr>) +++
_enforce_non_empty_field                                     --   23   23 -- 0.00003 0.00009 -- _onnx_check_model.py:417:_enforce_non_empty_field (_enforce_non_empty_field)
    <built-in method builtins.getattr>                       --   23   23 -- 0.00006 0.00006 -- ~:0:<built-in method builtins.getattr> (<built-in method builtins.getattr>) +++
<lambda>                                                     --    1    1 -- 0.00000 0.04721 -- plot_benchmark_onnx_serialize.py:150:<lambda> (<lambda>)
    check_model                                              --    1    1 -- 0.00002 0.04721 -- _onnx_check_model.py:1265:check_model (check_model)
        __init__                                             --    1    1 -- 0.00001 0.00001 -- _onnx_check_model.py:312:__init__ (__init__) +++
        _check_model                                         --    1    1 -- 0.00008 0.04718 -- _onnx_check_model.py:1222:_check_model (_check_model)
            get_ir_version                                   --    1    1 -- 0.00000 0.00000 -- _onnx_check_model.py:326:get_ir_version (get_ir_version) +++
            set_ir_version                                   --    1    1 -- 0.00000 0.00000 -- _onnx_check_model.py:330:set_ir_version (set_ir_version)
            set_opset_imports                                --    1    1 -- 0.00000 0.00000 -- _onnx_check_model.py:338:set_opset_imports (set_opset_imports) +++
            __init__                                         --    1    1 -- 0.00000 0.00000 -- _onnx_check_model.py:377:__init__ (__init__) +++
            _check_graph                                     --    1    1 -- 0.00143 0.04705 -- _onnx_check_model.py:961:_check_graph (_check_graph)
                get_ir_version                               --   20   20 -- 0.00003 0.00003 -- _onnx_check_model.py:326:get_ir_version (get_ir_version) +++
                __init__                                     --    1    1 -- 0.00000 0.00001 -- _onnx_check_model.py:377:__init__ (__init__) +++
                add                                          --   41   41 -- 0.00014 0.00018 -- _onnx_check_model.py:384:add (add)
                    <method 'add' of 'set' objects>          --   41   41 -- 0.00004 0.00004 -- ~:0:<method 'add' of 'set' objects> (<method 'add' of 'set' objects>) +++
                this_graph_has                               --    1    1 -- 0.00000 0.00000 -- _onnx_check_model.py:388:this_graph_has (this_graph_has) +++
                this_or_ancestor_graph_has                   --   60   60 -- 0.00011 0.00021 -- _onnx_check_model.py:392:this_or_ancestor_graph_has (this_or_ancestor_graph_has) +++
                _enforce_has_field                           --   20   20 -- 0.00004 0.00017 -- _onnx_check_model.py:405:_enforce_has_field (_enforce_has_field) +++
                _enforce_non_empty_field                     --    1    1 -- 0.00001 0.00001 -- _onnx_check_model.py:417:_enforce_non_empty_field (_enforce_non_empty_field) +++
                _check_value_info                            --    2    2 -- 0.00016 0.00050 -- _onnx_check_model.py:423:_check_value_info (_check_value_info)
                    is_main_graph                            --    2    2 -- 0.00000 0.00000 -- _onnx_check_model.py:342:is_main_graph (is_main_graph)
                    _enforce_has_field                       --    6    6 -- 0.00002 0.00004 -- _onnx_check_model.py:405:_enforce_has_field (_enforce_has_field) +++
                    _enforce_non_empty_field                 --    2    2 -- 0.00000 0.00001 -- _onnx_check_model.py:417:_enforce_non_empty_field (_enforce_non_empty_field) +++
                    <method 'ByteSize' ...CMessage' objects> --   12   12 -- 0.00003 0.00003 -- ~:0:<method 'ByteSize' of 'google.protobuf.pyext._message.CMessage' objects> (<method 'ByteSize' of 'google.protobuf.pyext._message.CMessage' objects>)
                    <method 'endswith' of 'str' objects>     --  138  138 -- 0.00007 0.00007 -- ~:0:<method 'endswith' of 'str' objects> (<method 'endswith' of 'str' objects>) +++
                    <built-in method builtins.dir>           --    2    2 -- 0.00014 0.00014 -- ~:0:<built-in method builtins.dir> (<built-in method builtins.dir>)
                    <built-in method builtins.getattr>       --   12   12 -- 0.00005 0.00005 -- ~:0:<built-in method builtins.getattr> (<built-in method builtins.getattr>) +++
                _check_tensor                                --   20   20 -- 0.01052 0.03868 -- _onnx_check_model.py:483:_check_tensor (_check_tensor)
                    _enforce_has_field                       --   20   20 -- 0.00001 0.00004 -- _onnx_check_model.py:405:_enforce_has_field (_enforce_has_field) +++
                    _check_data_field                        --  100  100 -- 0.00046 0.01890 -- _onnx_check_model.py:464:_check_data_field (_check_data_field)
                        <built-in method builtins.getattr>   --  120  120 -- 0.01829 0.01829 -- ~:0:<built-in method builtins.getattr> (<built-in method builtins.getattr>) +++
                        <built-in method builtins.len>       --  100  100 -- 0.00015 0.00015 -- ~:0:<built-in method builtins.len> (<built-in method builtins.len>) +++
                    <built-in method builtins.hasattr>       --   40   40 -- 0.00913 0.00913 -- ~:0:<built-in method builtins.hasattr> (<built-in method builtins.hasattr>) +++
                    <built-in method builtins.len>           --   20   20 -- 0.00008 0.00008 -- ~:0:<built-in method builtins.len> (<built-in method builtins.len>) +++
                _check_node                                  --   20   20 -- 0.00059 0.00580 -- _onnx_check_model.py:910:_check_node (_check_node)
                    __getattr__                              --   20   20 -- 0.00013 0.00032 -- _onnx_check_model.py:68:__getattr__ (__getattr__) +++
                    verify                                   --   20   20 -- 0.00142 0.00418 -- _onnx_check_model.py:83:verify (verify)
                        __init__                             --   20   20 -- 0.00006 0.00006 -- _onnx_check_model.py:31:__init__ (__init__)
                        __getattr__                          --  140  140 -- 0.00090 0.00251 -- _onnx_check_model.py:68:__getattr__ (__getattr__) +++
                        num_inputs_allowed                   --   20   20 -- 0.00001 0.00001 -- _onnx_check_model.py:73:num_inputs_allowed (num_inputs_allowed)
                        num_outputs_allowed                  --   20   20 -- 0.00001 0.00001 -- _onnx_check_model.py:78:num_outputs_allowed (num_outputs_allowed)
                        <built-in method builtins.len>       --  200  200 -- 0.00017 0.00017 -- ~:0:<built-in method builtins.len> (<built-in method builtins.len>) +++
                    GetSchema                                --   20   20 -- 0.00003 0.00054 -- _onnx_check_model.py:302:GetSchema (GetSchema)
                        get_schema                           --   20   20 -- 0.00008 0.00052 -- _onnx_check_model.py:295:get_schema (get_schema)
                            __init__                         --   20   20 -- 0.00003 0.00003 -- _onnx_check_model.py:65:__init__ (__init__)
                            <built-in metho...fs.get_schema> --   20   20 -- 0.00041 0.00041 -- ~:0:<built-in method onnx.onnx_cpp2py_export.defs.get_schema> (<built-in method onnx.onnx_cpp2py_export.defs.get_schema>)
                    get_opset_imports                        --   20   20 -- 0.00001 0.00001 -- _onnx_check_model.py:334:get_opset_imports (get_opset_imports) +++
                    get_schema_registry                      --   20   20 -- 0.00001 0.00001 -- _onnx_check_model.py:354:get_schema_registry (get_schema_registry)
                    _enforce_non_empty_field                 --   20   20 -- 0.00002 0.00007 -- _onnx_check_model.py:417:_enforce_non_empty_field (_enforce_non_empty_field) +++
                    check_is_experimental_op                 --   20   20 -- 0.00007 0.00007 -- _onnx_check_model.py:1295:check_is_experimental_op (check_is_experimental_op)
                <method 'append' of 'list' objects>          --   20   20 -- 0.00001 0.00001 -- ~:0:<method 'append' of 'list' objects> (<method 'append' of 'list' objects>)
                <method 'add' of 'set' objects>              --   20   20 -- 0.00002 0.00002 -- ~:0:<method 'add' of 'set' objects> (<method 'add' of 'set' objects>) +++
            _check_model_local_functions                     --    1    1 -- 0.00002 0.00004 -- _onnx_check_model.py:1121:_check_model_local_functions (_check_model_local_functions)
                __init__                                     --    1    1 -- 0.00001 0.00001 -- _onnx_check_model.py:312:__init__ (__init__) +++
                get_opset_imports                            --    1    1 -- 0.00000 0.00000 -- _onnx_check_model.py:334:get_opset_imports (get_opset_imports) +++
                set_opset_imports                            --    1    1 -- 0.00000 0.00000 -- _onnx_check_model.py:338:set_opset_imports (set_opset_imports) +++
<built-in method builtins.len>                               --  321  321 -- 0.00041 0.00041 -- ~:0:<built-in method builtins.len> (<built-in method builtins.len>)
<method 'add' of 'set' objects>                              --   61   61 -- 0.00005 0.00005 -- ~:0:<method 'add' of 'set' objects> (<method 'add' of 'set' objects>)
<built-in method builtins.hasattr>                           --  246  246 -- 0.01024 0.01024 -- ~:0:<built-in method builtins.hasattr> (<built-in method builtins.hasattr>)
<built-in method builtins.getattr>                           --  315  315 -- 0.01906 0.01906 -- ~:0:<built-in method builtins.getattr> (<built-in method builtins.getattr>)
<method 'endswith' of 'str' objects>                         --  298  298 -- 0.00026 0.00026 -- ~:0:<method 'endswith' of 'str' objects> (<method 'endswith' of 'str' objects>)

Total running time of the script: ( 2 minutes 6.481 seconds)

Gallery generated by Sphinx-Gallery