14
14
15
15
import argparse
16
16
import datetime
17
+ import glob
17
18
import math
18
19
import os
19
20
import platform
20
21
import shutil
21
22
import subprocess
22
23
import sys
24
+ import tarfile
23
25
import tempfile
24
26
27
+ import numpy as np
25
28
import onnx
26
29
import tensorflow as tf
27
30
import onnx_tf
@@ -97,6 +100,40 @@ def summary(self):
97
100
' (dry_run)' if _CFG ['dry_run' ] else '' )
98
101
99
102
103
+ def _get_model_and_test_data ():
104
+ """Get the filename of the model and directory of the test data set"""
105
+ onnx_model = None
106
+ test_data_set = []
107
+ for root , dirs , files in os .walk (_CFG ['untar_directory' ]):
108
+ for dir_name in dirs :
109
+ if dir_name .startswith ('test_data_set_' ):
110
+ test_data_set .append (os .path .join (root , dir_name ))
111
+ for file_name in files :
112
+ if file_name .endswith ('.onnx' ) and not file_name .startswith ('.' ):
113
+ onnx_model = os .path .join (root , file_name )
114
+ elif (file_name .startswith ('input_' ) and file_name .endswith ('.pb' ) and
115
+ len (test_data_set ) == 0 ):
116
+ # data files are not in test_data_set_* but in the same
117
+ # directory of onnx file
118
+ test_data_set .append (root )
119
+ elif file_name .startswith ('test_data_' ) and file_name .endswith ('.npz' ):
120
+ test_data_file = os .path .join (root , file_name )
121
+ test_data_dir = os .path .join (root , file_name .split ('.' )[0 ])
122
+ new_test_data_file = os .path .join (test_data_dir , file_name )
123
+ os .mkdir (test_data_dir )
124
+ os .rename (test_data_file , new_test_data_file )
125
+ test_data_set .append (test_data_dir )
126
+ return onnx_model , test_data_set
127
+
128
+
129
+ def _extract_model_and_test_data (file_path ):
130
+ """Extract all files in the tar.gz to test_model_and_data_dir"""
131
+ tar = tarfile .open (file_path , "r:gz" )
132
+ tar .extractall (_CFG ['untar_directory' ])
133
+ tar .close ()
134
+ return _get_model_and_test_data ()
135
+
136
+
100
137
def _pull_model_file (file_path ):
101
138
"""Use Git LFS to pull down a large file.
102
139
@@ -123,15 +160,28 @@ def _pull_model_file(file_path):
123
160
shell = True ,
124
161
check = True ,
125
162
stdout = subprocess .DEVNULL )
126
- new_size = os .stat (model_path ).st_size
163
+ if file_path .endswith ('.tar.gz' ):
164
+ onnx_model , test_data_set = _extract_model_and_test_data (model_path )
165
+ else :
166
+ onnx_model = model_path
167
+ test_data_set = []
168
+ new_size = os .stat (onnx_model ).st_size
127
169
pulled = new_size != file_size
128
170
file_size = new_size
129
- return (file_size , pulled )
171
+ else :
172
+ # model file is pulled already
173
+ if file_path .endswith ('.tar.gz' ):
174
+ onnx_model , test_data_set = _extract_model_and_test_data (model_path )
175
+ else :
176
+ onnx_model = model_path
177
+ test_data_set = []
178
+ return (file_size , pulled ), onnx_model , test_data_set
130
179
131
180
132
181
def _revert_model_pointer (file_path ):
133
182
"""Remove downloaded model, revert to pointer, remove cached file."""
134
183
cmd_args = ('rm -f {0} && '
184
+ 'git reset HEAD {0} && '
135
185
'git checkout {0} && '
136
186
'rm -f $(find . | grep $(grep oid {0} | cut -d ":" -f 2))'
137
187
).format (file_path )
@@ -147,17 +197,24 @@ def _include_model(file_path):
147
197
return True
148
198
for item in _CFG ['include' ]:
149
199
if (file_path .startswith (item ) or file_path .endswith (item + '.onnx' ) or
150
- '/{}/model/' .format (item ) in file_path ):
200
+ '/{}/model/' .format (item ) in file_path or
201
+ '/{}/models/' .format (item ) in file_path ):
151
202
return True
152
203
return False
153
204
154
205
155
206
def _has_models (dir_path ):
156
- for item in os .listdir (os .path .join (_CFG ['models_dir' ], dir_path , 'model' )):
157
- if item .endswith ('.onnx' ):
158
- file_path = os .path .join (dir_path , 'model' , item )
159
- if _include_model (file_path ):
160
- return True
207
+ for m_dir in ['model' , 'models' ]:
208
+ # in age_gender there are 2 different models in there so the
209
+ # directory is "models" instead of "model" like the rest of
210
+ # the other models
211
+ model_dir = os .path .join (_CFG ['models_dir' ], dir_path , m_dir )
212
+ if os .path .exists (model_dir ):
213
+ for item in os .listdir (model_dir ):
214
+ if item .endswith ('.onnx' ):
215
+ file_path = os .path .join (dir_path , model_dir , item )
216
+ if _include_model (file_path ):
217
+ return True
161
218
return False
162
219
163
220
@@ -187,6 +244,7 @@ def _report_check_model(model):
187
244
onnx .checker .check_model (model )
188
245
return ''
189
246
except Exception as ex :
247
+ _del_location (_CFG ['untar_directory' ])
190
248
first_line = str (ex ).strip ().split ('\n ' )[0 ].strip ()
191
249
return '{}: {}' .format (type (ex ).__name__ , first_line )
192
250
@@ -195,53 +253,157 @@ def _report_convert_model(model):
195
253
"""Test conversion and returns a report string."""
196
254
try :
197
255
tf_rep = onnx_tf .backend .prepare (model )
198
- tf_rep .export_graph (_CFG ['output_filename' ])
199
- _del_location (_CFG ['output_filename' ])
256
+ tf_rep .export_graph (_CFG ['output_directory' ])
200
257
return ''
201
258
except Exception as ex :
202
- _del_location (_CFG ['output_filename' ])
203
- strack_trace = str (ex ).strip ().split ('\n ' )
204
- if len (strack_trace ) > 1 :
205
- err_msg = strack_trace [- 1 ].strip ()
259
+ _del_location (_CFG ['untar_directory' ])
260
+ _del_location (_CFG ['output_directory' ])
261
+ stack_trace = str (ex ).strip ().split ('\n ' )
262
+ if len (stack_trace ) > 1 :
263
+ err_msg = stack_trace [- 1 ].strip ()
206
264
# OpUnsupportedException gets raised as a RuntimeError
207
265
if 'OP_UNSUPPORTED_EXCEPT' in str (ex ):
208
266
err_msg = err_msg .replace (type (ex ).__name__ , 'OpUnsupportedException' )
209
267
return err_msg
210
- return '{}: {}' .format (type (ex ).__name__ , strack_trace [0 ].strip ())
268
+ return '{}: {}' .format (type (ex ).__name__ , stack_trace [0 ].strip ())
269
+
270
+
271
+ def _get_inputs_outputs_pb (tf_rep , data_dir ):
272
+ """Get the input and reference output tensors"""
273
+ inputs = {}
274
+ inputs_num = len (glob .glob (os .path .join (data_dir , 'input_*.pb' )))
275
+ for i in range (inputs_num ):
276
+ input_file = os .path .join (data_dir , 'input_{}.pb' .format (i ))
277
+ tensor = onnx .TensorProto ()
278
+ with open (input_file , 'rb' ) as f :
279
+ tensor .ParseFromString (f .read ())
280
+ tensor .name = tensor .name if tensor .name else tf_rep .inputs [i ]
281
+ inputs [tensor .name ] = onnx .numpy_helper .to_array (tensor )
282
+ ref_outputs = {}
283
+ ref_outputs_num = len (glob .glob (os .path .join (data_dir , 'output_*.pb' )))
284
+ for i in range (ref_outputs_num ):
285
+ output_file = os .path .join (data_dir , 'output_{}.pb' .format (i ))
286
+ tensor = onnx .TensorProto ()
287
+ with open (output_file , 'rb' ) as f :
288
+ tensor .ParseFromString (f .read ())
289
+ tensor .name = tensor .name if tensor .name else tf_rep .outputs [i ]
290
+ ref_outputs [tensor .name ] = onnx .numpy_helper .to_array (tensor )
291
+ return inputs , ref_outputs
292
+
293
+
294
+ def _get_inputs_outputs_npz (tf_rep , data_dir ):
295
+ """Get the input and reference output tensors"""
296
+ npz_file = os .path .join (data_dir , '{}.npz' .format (data_dir .split ('/' )[- 1 ]))
297
+ data = np .load (npz_file , encoding = 'bytes' )
298
+ inputs = {}
299
+ ref_outputs = {}
300
+ for i in range (len (tf_rep .inputs )):
301
+ inputs [tf_rep .inputs [i ]] = data ['inputs' ][i ]
302
+ for i in range (len (tf_rep .outputs )):
303
+ ref_outputs [tf_rep .outputs [i ]] = data ['outputs' ][i ]
304
+ return inputs , ref_outputs
305
+
306
+
307
+ def _get_inputs_and_ref_outputs (tf_rep , data_dir ):
308
+ """Get the input and reference output tensors"""
309
+ if len (glob .glob (os .path .join (data_dir , 'input_*.pb' ))) > 0 :
310
+ inputs , ref_outputs = _get_inputs_outputs_pb (tf_rep , data_dir )
311
+ else :
312
+ inputs , ref_outputs = _get_inputs_outputs_npz (tf_rep , data_dir )
313
+ return inputs , ref_outputs
314
+
315
+
316
+ def _assert_outputs (outputs , ref_outputs , rtol , atol ):
317
+ np .testing .assert_equal (len (outputs ), len (ref_outputs ))
318
+ for key in outputs .keys ():
319
+ np .testing .assert_equal (outputs [key ].dtype , ref_outputs [key ].dtype )
320
+ if ref_outputs [key ].dtype == np .object :
321
+ np .testing .assert_array_equal (outputs [key ], ref_outputs [key ])
322
+ else :
323
+ np .testing .assert_allclose (outputs [key ],
324
+ ref_outputs [key ],
325
+ rtol = rtol ,
326
+ atol = atol )
327
+
328
+
329
+ def _report_run_model (model , data_set ):
330
+ """Run the model and returns a report string."""
331
+ try :
332
+ tf_rep = onnx_tf .backend .prepare (model )
333
+ for data in data_set :
334
+ inputs , ref_outputs = _get_inputs_and_ref_outputs (tf_rep , data )
335
+ outputs = tf_rep .run (inputs )
336
+ outputs_dict = {}
337
+ for i in range (len (tf_rep .outputs )):
338
+ outputs_dict [tf_rep .outputs [i ]] = outputs [i ]
339
+ _assert_outputs (outputs_dict , ref_outputs , rtol = 1e-3 , atol = 1e-3 )
340
+ except Exception as ex :
341
+ stack_trace = str (ex ).strip ().split ('\n ' )
342
+ if len (stack_trace ) > 1 :
343
+ if ex .__class__ == AssertionError :
344
+ return stack_trace [:5 ]
345
+ else :
346
+ return stack_trace [- 1 ].strip ()
347
+ return '{}: {}' .format (type (ex ).__name__ , stack_trace [0 ].strip ())
348
+ finally :
349
+ _del_location (_CFG ['untar_directory' ])
350
+ _del_location (_CFG ['output_directory' ])
211
351
212
352
213
353
def _report_model (file_path , results = Results (), onnx_model_count = 1 ):
214
354
"""Generate a report status for a single model, and append it to results."""
215
- size_pulled = _pull_model_file (file_path )
355
+ size_pulled , onnx_model , test_data_set = _pull_model_file (file_path )
216
356
if _CFG ['dry_run' ]:
217
357
ir_version = ''
218
358
opset_version = ''
219
359
check_err = ''
220
360
convert_err = ''
361
+ ran_err = ''
221
362
emoji_validated = ''
222
363
emoji_converted = ''
364
+ emoji_ran = ''
223
365
emoji_overall = ':heavy_minus_sign:'
224
366
results .skip_count += 1
225
367
else :
226
368
if _CFG ['verbose' ]:
227
369
print ('Testing' , file_path )
228
- model = onnx .load (os . path . join ( _CFG [ 'models_dir' ], file_path ) )
370
+ model = onnx .load (onnx_model )
229
371
ir_version = model .ir_version
230
372
opset_version = model .opset_import [0 ].version
231
373
check_err = _report_check_model (model )
232
374
convert_err = '' if check_err else _report_convert_model (model )
375
+ run_err = '' if convert_err or len (
376
+ test_data_set ) == 0 else _report_run_model (model , test_data_set )
233
377
234
- if not check_err and not convert_err :
378
+ if (not check_err and not convert_err and not run_err and
379
+ len (test_data_set ) > 0 ):
235
380
# https://github-emoji-list.herokuapp.com/
236
- # validation and conversion passed
381
+ # ran successfully
237
382
emoji_validated = ':ok:'
238
383
emoji_converted = ':ok:'
384
+ emoji_ran = ':ok:'
239
385
emoji_overall = ':heavy_check_mark:'
240
386
results .pass_count += 1
387
+ elif (not check_err and not convert_err and not run_err and
388
+ len (test_data_set ) == 0 ):
389
+ # validation & conversion passed but no test data available
390
+ emoji_validated = ':ok:'
391
+ emoji_converted = ':ok:'
392
+ emoji_ran = 'No test data provided in model zoo'
393
+ emoji_overall = ':warning:'
394
+ results .warn_count += 1
395
+ elif not check_err and not convert_err :
396
+ # validation & conversion passed but failed to run
397
+ emoji_validated = ':ok:'
398
+ emoji_converted = ':ok:'
399
+ emoji_ran = run_err
400
+ emoji_overall = ':x:'
401
+ results .fail_count += 1
241
402
elif not check_err :
242
403
# validation pass, but conversion did not
243
404
emoji_validated = ':ok:'
244
405
emoji_converted = convert_err
406
+ emoji_ran = ':heavy_minus_sign:'
245
407
if ('BackendIsNotSupposedToImplementIt' in convert_err or
246
408
'OpUnsupportedException' in convert_err ):
247
409
# known limitations
@@ -257,14 +419,18 @@ def _report_model(file_path, results=Results(), onnx_model_count=1):
257
419
# validation failed
258
420
emoji_validated = check_err
259
421
emoji_converted = ':heavy_minus_sign:'
422
+ emoji_ran = ':heavy_minus_sign:'
260
423
emoji_overall = ':x:'
261
424
results .fail_count += 1
262
425
263
- results .append_detail ('{} | {}. {} | {} | {} | {} | {} | {}' .format (
264
- emoji_overall , onnx_model_count , file_path [file_path .rindex ('/' ) + 1 :],
426
+ results .append_detail ('{} | {}. {} | {} | {} | {} | {} | {} | {}' .format (
427
+ emoji_overall , onnx_model_count ,
428
+ file_path [file_path .rindex ('/' ) + 1 :file_path .index ('.' )],
265
429
_size_with_units (size_pulled [0 ]), ir_version , opset_version ,
266
- emoji_validated , emoji_converted ))
430
+ emoji_validated , emoji_converted , emoji_ran ))
267
431
432
+ if len (test_data_set ) == 0 :
433
+ _del_location (_CFG ['output_directory' ])
268
434
if size_pulled [1 ]:
269
435
# only remove model if it was pulled above on-demand
270
436
_revert_model_pointer (file_path )
@@ -291,7 +457,8 @@ def _configure(models_dir='models',
291
457
_configure_env ()
292
458
293
459
norm_output_dir = os .path .normpath (output_dir )
294
- _CFG ['output_filename' ] = os .path .join (norm_output_dir , 'tmp_model.pb' )
460
+ _CFG ['untar_directory' ] = os .path .join (norm_output_dir , 'test_model_and_data' )
461
+ _CFG ['output_directory' ] = os .path .join (norm_output_dir , 'test_model_pb' )
295
462
_CFG ['report_filename' ] = os .path .join (norm_output_dir ,
296
463
_CFG ['report_filename' ])
297
464
@@ -354,13 +521,14 @@ def modelzoo_report(models_dir='models',
354
521
355
522
_configure (models_dir , output_dir , include , verbose , dry_run )
356
523
_del_location (_CFG ['report_filename' ])
357
- _del_location (_CFG ['output_filename' ])
524
+ _del_location (_CFG ['output_directory' ])
525
+ _del_location (_CFG ['untar_directory' ])
358
526
359
527
# run tests first, but append to report after summary
360
528
results = Results ()
361
529
for root , subdir , files in os .walk (_CFG ['models_dir' ]):
362
530
subdir .sort ()
363
- if 'model' in subdir :
531
+ if 'model' in subdir or 'models' in subdir :
364
532
dir_path = os .path .relpath (root , _CFG ['models_dir' ])
365
533
if _has_models (dir_path ):
366
534
results .model_count += 1
@@ -371,15 +539,21 @@ def modelzoo_report(models_dir='models',
371
539
results .append_detail ('' )
372
540
results .append_detail (
373
541
'Status | Model | Size | IR | Opset | ONNX Checker | '
374
- 'ONNX-TF Converted' )
542
+ 'ONNX-TF Converted | ONNX-TF Ran ' )
375
543
results .append_detail (
376
544
'------ | ----- | ---- | -- | ----- | ------------ | '
377
- '---------' )
545
+ '----------------- | ----------- ' )
378
546
onnx_model_count = 0
547
+ file_path = ''
379
548
for item in sorted (files ):
380
549
if item .endswith ('.onnx' ):
381
550
file_path = os .path .relpath (os .path .join (root , item ),
382
551
_CFG ['models_dir' ])
552
+ # look for gz file for this model
553
+ gzfile_path = file_path .replace ('.onnx' , '.tar.gz' )
554
+ gzfile_path = os .path .join (_CFG ['models_dir' ], gzfile_path )
555
+ if gzfile_path in glob .glob (gzfile_path ):
556
+ file_path = os .path .relpath (gzfile_path , _CFG ['models_dir' ])
383
557
if _include_model (file_path ):
384
558
onnx_model_count += 1
385
559
results .total_count += 1
0 commit comments