2
2
from typing import Any , Dict , List , Iterator , Optional , Tuple , Union
3
3
from enum import IntEnum
4
4
import numpy as np
5
- from onnx import ModelProto , TensorProto , ValueInfoProto
5
+ from onnx import ModelProto , TensorProto , ValueInfoProto , load
6
+ from onnx .helper import tensor_dtype_to_np_dtype
7
+ from onnx .shape_inference import infer_shapes
6
8
from .evaluator import ExtendedReferenceEvaluator
7
9
8
10
@@ -20,6 +22,7 @@ class ResultType(IntEnum):
20
22
SPARSE_INITIALIZER = 4
21
23
INPUT = 8
22
24
OUTPUT = 16
25
+ NODE = 32
23
26
24
27
def __repr__ (self ):
25
28
return f"{ self .__class__ .__name__ } .{ self ._name_ } "
@@ -57,12 +60,13 @@ def __getitem__(self, i: int) -> Any:
57
60
raise IndexError (f"i={ i } out of boundary" )
58
61
59
62
def __str__ (self ):
63
+ dtype = self .dtype if self .dtype != 0 else ""
60
64
els = [
61
65
_align (self .kind ._name_ , 6 ),
62
- _align (str (self . dtype ).replace ("dtype(" , "" ).replace (")" , "" ), 8 ),
63
- _align ("x" .join (map (str , self .shape )), 15 ),
66
+ _align (str (dtype ).replace ("dtype(" , "" ).replace (")" , "" ), 8 ),
67
+ _align ("x" .join ("" if self . shape is None else map (str , self .shape )), 15 ),
64
68
self .summary ,
65
- _align (self .op_type or "" , 10 ),
69
+ _align (self .op_type or "" , 12 ),
66
70
self .name or "" ,
67
71
]
68
72
return " " .join (els )
@@ -270,6 +274,22 @@ def _cost_type(self, t1: "np.dtype", t2: "np.dtype") -> float:
270
274
return 1
271
275
272
276
def _cost_shape (self , s1 : Tuple [int , ...], s2 : Tuple [int , ...]) -> float :
277
+ if s1 is None or s2 is None :
278
+ return self .rank_cost
279
+ if any (map (lambda s : isinstance (s , str ), s1 )) or any (
280
+ map (lambda s : isinstance (s , str ), s2 )
281
+ ):
282
+ # dynamic shapes
283
+ if len (s1 ) != len (s2 ):
284
+ return self .rank_cost
285
+ d = 0
286
+ for i , j in zip (s1 , s2 ):
287
+ if isinstance (i , int ) and isinstance (j , int ):
288
+ d += abs (i - j )
289
+ elif i != j :
290
+ d += self .rank_cost / 2
291
+ return d
292
+
273
293
d = abs (np .prod (s1 ) - np .prod (s2 ))
274
294
if len (s1 ) != len (s2 ):
275
295
return self .rank_cost + d
@@ -424,12 +444,85 @@ def generate_inputs(model: ModelProto) -> List[np.ndarray]:
424
444
return inputs
425
445
426
446
447
+ def _update_shape_types_with_proto (
448
+ proto : ModelProto ,
449
+ ) -> Dict [str , Tuple [int , Tuple [Union [int , str ], ...]]]:
450
+ """
451
+ Retrieves the shapes and types for a model.
452
+ """
453
+ assert isinstance (proto , ModelProto ), f"Unexpected type { type (proto )} for proto"
454
+ res = {}
455
+
456
+ for val in proto .graph .input :
457
+ itype = val .type .tensor_type .elem_type
458
+ shape = tuple (
459
+ d .dim_param if d .dim_param else d .dim_value
460
+ for d in val .type .tensor_type .shape .dim
461
+ )
462
+ res [val .name ] = [itype , shape ]
463
+
464
+ for val in proto .graph .output :
465
+ itype = val .type .tensor_type .elem_type
466
+ shape = tuple (
467
+ d .dim_param if d .dim_param else d .dim_value
468
+ for d in val .type .tensor_type .shape .dim
469
+ )
470
+ res [val .name ] = [itype , shape ]
471
+
472
+ for val in proto .graph .initializer :
473
+ itype = val .data_type
474
+ shape = tuple (d for d in val .dims )
475
+ res [val .name ] = [itype , shape ]
476
+
477
+ new_proto = infer_shapes (proto )
478
+ for val in new_proto .graph .value_info :
479
+ itype = val .type .tensor_type .elem_type
480
+ shape = tuple (
481
+ d .dim_param if d .dim_param else d .dim_value
482
+ for d in val .type .tensor_type .shape .dim
483
+ )
484
+ res [val .name ] = [itype , shape ]
485
+
486
+ return res
487
+
488
+
489
+ def _enumerate_result_no_execution (model : ModelProto ) -> Iterator [ResultType ]:
490
+ """
491
+ Produces a list of results based on a model in order to
492
+ trigger the edit distance comparison.
493
+ """
494
+ type_shape = _update_shape_types_with_proto (model )
495
+ for i in model .graph .initializer :
496
+ itype , shape = type_shape .get (i .name , (0 , None ))
497
+ dtype = tensor_dtype_to_np_dtype (itype )
498
+ yield ResultExecution (
499
+ ResultType .INITIALIZER , dtype , shape , "????" , "INIT" , i .name
500
+ )
501
+ for i in model .graph .input :
502
+ itype , shape = type_shape .get (i .name , (0 , None ))
503
+ dtype = tensor_dtype_to_np_dtype (itype )
504
+ yield ResultExecution (ResultType .INPUT , dtype , shape , "????" , "INPUT" , i .name )
505
+ for node in model .graph .node :
506
+ yield ResultExecution (ResultType .NODE , 0 , None , "????" , node .op_type , node .name )
507
+ for o in node .output :
508
+ itype , shape = type_shape .get (o , (0 , None ))
509
+ dtype = tensor_dtype_to_np_dtype (itype )
510
+ yield ResultExecution (
511
+ ResultType .RESULT , dtype , shape , "????" , node .op_type , o
512
+ )
513
+ for i in model .graph .output :
514
+ itype , shape = type_shape .get (i .name , (0 , None ))
515
+ dtype = tensor_dtype_to_np_dtype (itype )
516
+ yield ResultExecution (ResultType .OUTPUT , dtype , shape , "????" , "OUTPUT" , i .name )
517
+
518
+
427
519
def compare_onnx_execution (
428
520
model1 : ModelProto ,
429
521
model2 : ModelProto ,
430
522
inputs : Optional [Union [List [Any ], Tuple [Dict [str , Any ]]]] = None ,
431
523
verbose : int = 0 ,
432
524
raise_exc : bool = True ,
525
+ mode : str = "execute" ,
433
526
) -> Tuple [List [ResultExecution ], List [ResultExecution ], List [Tuple [int , int ]]]:
434
527
"""
435
528
Compares the execution of two onnx models.
@@ -443,33 +536,55 @@ def compare_onnx_execution(
443
536
the same number of inputs or two dictionaries, one for each model
444
537
:param verbose: verbosity
445
538
:param raise_exc: raise exception if the execution fails or stop at the error
539
+ :param mode: the model should be executed but the function can be executed
540
+ but the comparison may append on nodes only
446
541
:return: four results, a sequence of results for the first model and the second model,
447
542
the alignment between the two, DistanceExecution
448
543
"""
449
- if verbose :
450
- print ("[compare_onnx_execution] generate inputs" )
451
- if inputs is None :
452
- inputs = generate_inputs (model1 )
453
- if isinstance (inputs , tuple ):
454
- assert len (inputs ) == 2 , f"Unexpected number { len (inputs )} of inputs."
455
- feeds1 , feeds2 = inputs
544
+ assert mode in {"execute" , "nodes" }, f"Unexpected value for mode={ mode !r} ."
545
+
546
+ if mode == "execute" :
547
+ if inputs is None :
548
+ if verbose :
549
+ print ("[compare_onnx_execution] generate inputs" )
550
+ inputs = generate_inputs (model1 )
551
+ if isinstance (inputs , tuple ):
552
+ assert len (inputs ) == 2 , f"Unexpected number { len (inputs )} of inputs."
553
+ feeds1 , feeds2 = inputs
554
+ else :
555
+ feeds1 = {i .name : v for i , v in zip (model1 .graph .input , inputs )}
556
+ feeds2 = {i .name : v for i , v in zip (model2 .graph .input , inputs )}
557
+ assert isinstance (feeds1 , dict ), f"Unexpected type { type (feeds1 )} for inputs"
558
+ assert isinstance (feeds2 , dict ), f"Unexpected type { type (feeds2 )} for inputs"
559
+ if verbose :
560
+ print (f"[compare_onnx_execution] execute with { len (inputs )} inputs" )
561
+ print ("[compare_onnx_execution] execute first model" )
562
+ res1 = list (
563
+ YieldEvaluator (model1 ).enumerate_summarized (
564
+ None , feeds1 , raise_exc = raise_exc
565
+ )
566
+ )
567
+ if verbose :
568
+ print (f"[compare_onnx_execution] got { len (res1 )} results" )
569
+ print ("[compare_onnx_execution] execute second model" )
570
+ res2 = list (
571
+ YieldEvaluator (model2 ).enumerate_summarized (
572
+ None , feeds2 , raise_exc = raise_exc
573
+ )
574
+ )
575
+ elif mode == "nodes" :
576
+ # No execution.
577
+ if verbose :
578
+ print ("[compare_onnx_execution] loading first model" )
579
+ proto1 = load (model1 ) if isinstance (model1 , str ) else model2
580
+ if verbose :
581
+ print ("[compare_onnx_execution] loading first model" )
582
+ proto2 = load (model2 ) if isinstance (model2 , str ) else model1
583
+ res1 = list (_enumerate_result_no_execution (proto1 ))
584
+ res2 = list (_enumerate_result_no_execution (proto2 ))
456
585
else :
457
- feeds1 = {i .name : v for i , v in zip (model1 .graph .input , inputs )}
458
- feeds2 = {i .name : v for i , v in zip (model2 .graph .input , inputs )}
459
- assert isinstance (feeds1 , dict ), f"Unexpected type { type (feeds1 )} for inputs"
460
- assert isinstance (feeds2 , dict ), f"Unexpected type { type (feeds2 )} for inputs"
461
- if verbose :
462
- print (f"[compare_onnx_execution] got { len (inputs )} inputs" )
463
- print ("[compare_onnx_execution] execute first model" )
464
- res1 = list (
465
- YieldEvaluator (model1 ).enumerate_summarized (None , feeds1 , raise_exc = raise_exc )
466
- )
467
- if verbose :
468
- print (f"[compare_onnx_execution] got { len (res1 )} results" )
469
- print ("[compare_onnx_execution] execute second model" )
470
- res2 = list (
471
- YieldEvaluator (model2 ).enumerate_summarized (None , feeds2 , raise_exc = raise_exc )
472
- )
586
+ return
587
+
473
588
if verbose :
474
589
print (f"[compare_onnx_execution] got { len (res2 )} results" )
475
590
print ("[compare_onnx_execution] compute edit distance" )
0 commit comments