codekingpro/portable-devtools
114k
1import argparse
2import os
3
4import numpy
5import psutil
6from onnx import TensorProto
7
8"""
9This profiler tool could run a transformer model and print out the kernel time spent on each Node of the model.
10Example of profiling of longformer model:
11 python profiler.py --model longformer-base-4096_fp32.onnx --batch_size 1 --sequence_length 4096 --global_length 8 --samples 1000 --thread_num 8 --dummy_inputs longformer --use_gpu
12Example of importing profile result file from onnxruntime_perf_test:
13 python profiler.py --input profile_2021-10-25_12-02-41.json
14"""
15
16
17def parse_arguments(argv=None):
18 parser = argparse.ArgumentParser()
19
20 parser.add_argument(
21 "-i",
22 "--input",
23 required=False,
24 type=str,
25 help="Set the input file for reading the profile results",
26 )
27
28 parser.add_argument(
29 "-m",
30 "--model",
31 required=False,
32 type=str,
33 help="onnx model path to run profiling. Required when --input is not specified.",
34 )
35
36 parser.add_argument(
37 "-b",
38 "--batch_size",
39 required=False,
40 type=int,
41 default=1,
42 help="batch size of input",
43 )
44
45 parser.add_argument(
46 "-s",
47 "--sequence_length",
48 required=False,
49 type=int,
50 default=32,
51 help="sequence length of input",
52 )
53
54 parser.add_argument(
55 "--past_sequence_length",
56 required=False,
57 type=int,
58 default=1,
59 help="past sequence length for gpt2",
60 )
61
62 parser.add_argument(
63 "--global_length",
64 required=False,
65 type=int,
66 default=1,
67 help="number of global tokens for longformer",
68 )
69
70 parser.add_argument(
71 "--samples",
72 required=False,
73 type=int,
74 default=1000,
75 help="number of samples to test. Set it large enough to reduce the variance of performance result.",
76 )
77
78 parser.add_argument(
79 "--threshold",
80 required=False,
81 type=float,
82 default=0.01,
83 help="Threshold of run time ratio among all nodes. Nodes with larger ratio will show in top expensive nodes.",
84 )
85
86 parser.add_argument(
87 "--thread_num",
88 required=False,
89 type=int,
90 default=-1,
91 help="number of threads to use",
92 )
93
94 parser.add_argument(
95 "--input_ids_name",
96 required=False,
97 type=str,
98 default=None,
99 help="input name for input IDs, for bert",
100 )
101 parser.add_argument(
102 "--segment_ids_name",
103 required=False,
104 type=str,
105 default=None,
106 help="input name for segment IDs, for bert",
107 )
108 parser.add_argument(
109 "--input_mask_name",
110 required=False,
111 type=str,
112 default=None,
113 help="input name for attention mask, for bert",
114 )
115
116 parser.add_argument(
117 "--dummy_inputs",
118 required=False,
119 default="default",
120 choices=["bert", "gpt2", "longformer", "default"],
121 help="Type of model inputs. The default will create dummy inputs with ones.",
122 )
123
124 parser.add_argument("-g", "--use_gpu", required=False, action="store_true", help="use GPU")
125 parser.set_defaults(use_gpu=False)
126
127 parser.add_argument(
128 "--provider",
129 required=False,
130 type=str,
131 default="cuda",
132 help="Execution provider to use",
133 )
134
135 parser.add_argument(
136 "--basic_optimization",
137 required=False,
138 action="store_true",
139 help="Enable only basic graph optimizations. By default, all optimizations are enabled in OnnxRuntime",
140 )
141 parser.set_defaults(basic_optimization=False)
142
143 parser.add_argument(
144 "--kernel_time_only",
145 required=False,
146 action="store_true",
147 help="Only include the kernel time and no fence time",
148 )
149 parser.set_defaults(kernel_time_only=False)
150
151 parser.add_argument("-v", "--verbose", required=False, action="store_true")
152 parser.set_defaults(verbose=False)
153
154 return parser.parse_args(argv)
155
156
157def run_profile(onnx_model_path, use_gpu, provider, basic_optimization, thread_num, all_inputs):
158 from benchmark_helper import create_onnxruntime_session # noqa: PLC0415
159
160 session = create_onnxruntime_session(
161 onnx_model_path,
162 use_gpu,
163 provider,
164 enable_all_optimization=not basic_optimization,
165 num_threads=thread_num,
166 enable_profiling=True,
167 )
168
169 for inputs in all_inputs:
170 _ = session.run(None, inputs)
171
172 profile_file = session.end_profiling()
173 return profile_file
174
175
176def get_dim_from_type_proto(dim):
177 return getattr(dim, dim.WhichOneof("value")) if type(dim.WhichOneof("value")) == str else None # noqa: E721
178
179
180def get_shape_from_type_proto(type_proto):
181 return [get_dim_from_type_proto(d) for d in type_proto.tensor_type.shape.dim]
182
183
184def create_dummy_inputs(onnx_model, batch_size, sequence_length, samples):
185 """Create dummy inputs for ONNX model.
186
187 Args:
188 onnx_model (OnnxModel): ONNX model
189 batch_size (int): batch size
190 sequence_length (int): sequence length
191 samples (int): number of samples
192
193 Returns:
194 List[Dict]: list of inputs
195 """
196 dummy_inputs = {}
197 for graph_input in onnx_model.get_graph_inputs_excluding_initializers():
198 shape = get_shape_from_type_proto(graph_input.type)
199 symbol_dims = []
200 for i, dim in enumerate(shape):
201 if isinstance(dim, str):
202 symbol_dims.append(i)
203
204 # allowed symbolic dimensions: batch_size and sequence_length
205 if len(symbol_dims) > 2:
206 return None
207 if len(symbol_dims) > 0:
208 shape[symbol_dims[0]] = batch_size
209 if len(symbol_dims) > 1:
210 shape[symbol_dims[1]] = sequence_length
211
212 elem_type = graph_input.type.tensor_type.elem_type
213 assert elem_type in [TensorProto.FLOAT, TensorProto.INT32, TensorProto.INT64]
214 data_type = (
215 numpy.float32
216 if elem_type == TensorProto.FLOAT
217 else (numpy.int64 if elem_type == TensorProto.INT64 else numpy.int32)
218 )
219 data = numpy.ones(shape, dtype=data_type)
220 dummy_inputs[graph_input.name] = data
221
222 all_inputs = [dummy_inputs for _ in range(samples)]
223 return all_inputs
224
225
226def create_bert_inputs(
227 onnx_model,
228 batch_size,
229 sequence_length,
230 samples,
231 input_ids_name=None,
232 segment_ids_name=None,
233 input_mask_name=None,
234):
235 """Create dummy inputs for BERT model.
236
237 Args:
238 onnx_model (OnnxModel): ONNX model
239 batch_size (int): batch size
240 sequence_length (int): sequence length
241 samples (int): number of samples
242 input_ids_name (str, optional): Name of graph input for input IDs. Defaults to None.
243 segment_ids_name (str, optional): Name of graph input for segment IDs. Defaults to None.
244 input_mask_name (str, optional): Name of graph input for attention mask. Defaults to None.
245
246 Returns:
247 List[Dict]: list of inputs
248 """
249 from bert_test_data import find_bert_inputs, generate_test_data # noqa: PLC0415
250
251 input_ids, segment_ids, input_mask = find_bert_inputs(onnx_model, input_ids_name, segment_ids_name, input_mask_name)
252 all_inputs = generate_test_data(
253 batch_size,
254 sequence_length,
255 test_cases=samples,
256 seed=123,
257 verbose=False,
258 input_ids=input_ids,
259 segment_ids=segment_ids,
260 input_mask=input_mask,
261 random_mask_length=False,
262 )
263
264 return all_inputs
265
266
267def create_gpt2_inputs(onnx_model, batch_size, sequence_length, past_sequence_length, samples):
268 """Create dummy inputs for GPT-2 model.
269
270 Args:
271 onnx_model (OnnxModel): ONNX model
272 batch_size (int): batch size
273 sequence_length (int): sequence length
274 past_sequence_length (int): past sequence length
275 samples (int): number of samples
276
277 Raises:
278 RuntimeError: symbolic is not supported. Use the tool convert_to_onnx.py to export ONNX model instead.
279
280 Returns:
281 List[Dict]: list of inputs
282 """
283 # The symbolic names shall be same as those used in Gpt2Helper.export_onnx(...) function.
284 symbols = {
285 "batch_size": batch_size,
286 "seq_len": sequence_length,
287 "past_seq_len": past_sequence_length,
288 "total_seq_len": sequence_length + past_sequence_length,
289 }
290
291 dummy_inputs = {}
292 for graph_input in onnx_model.get_graph_inputs_excluding_initializers():
293 shape = get_shape_from_type_proto(graph_input.type)
294 for i, dim in enumerate(shape):
295 if isinstance(dim, str):
296 if dim not in symbols:
297 raise RuntimeError(f"symbol is not supported: {dim}")
298 else:
299 shape[i] = symbols[dim]
300
301 elem_type = graph_input.type.tensor_type.elem_type
302 assert elem_type in [TensorProto.FLOAT, TensorProto.INT32, TensorProto.INT64]
303 data_type = (
304 numpy.float32
305 if elem_type == TensorProto.FLOAT
306 else (numpy.int64 if elem_type == TensorProto.INT64 else numpy.int32)
307 )
308 data = numpy.ones(shape, dtype=data_type)
309 dummy_inputs[graph_input.name] = data
310
311 all_inputs = [dummy_inputs for _ in range(samples)]
312 return all_inputs
313
314
315def create_longformer_inputs(onnx_model, batch_size, sequence_length, global_length, samples):
316 """Create dummy inputs for Longformer model.
317
318 Args:
319 onnx_model (OnnxModel): ONNX model
320 batch_size (int): batch size
321 sequence_length (int): sequence length
322 global_length (int): number of global tokens
323 samples (int): number of samples
324
325 Raises:
326 RuntimeError: symbolic is not supported. Use the tool convert_longformer_to_onnx.py to export ONNX model instead.
327
328 Returns:
329 List[Dict]: list of inputs
330 """
331 symbols = {"batch_size": batch_size, "sequence_length": sequence_length}
332
333 dummy_inputs = {}
334 for graph_input in onnx_model.get_graph_inputs_excluding_initializers():
335 shape = get_shape_from_type_proto(graph_input.type)
336 for i, dim in enumerate(shape):
337 if isinstance(dim, str):
338 if dim not in symbols:
339 raise RuntimeError(f"symbol is not supported: {dim}")
340 else:
341 shape[i] = symbols[dim]
342
343 elem_type = graph_input.type.tensor_type.elem_type
344 assert elem_type in [TensorProto.FLOAT, TensorProto.INT32, TensorProto.INT64]
345 data_type = (
346 numpy.float32
347 if elem_type == TensorProto.FLOAT
348 else (numpy.int64 if elem_type == TensorProto.INT64 else numpy.int32)
349 )
350
351 if "global" in graph_input.name:
352 data = numpy.zeros(shape, dtype=data_type)
353 data[:, :global_length] = 1
354 else:
355 data = numpy.ones(shape, dtype=data_type)
356 dummy_inputs[graph_input.name] = data
357
358 all_inputs = [dummy_inputs for _ in range(samples)]
359 return all_inputs
360
361
362def run(args):
363 num_threads = args.thread_num if args.thread_num > 0 else psutil.cpu_count(logical=False)
364
365 # Set OMP environment variable before importing onnxruntime. Needed for cpu only, and no impact for onnxruntime-gpu package.
366 if "OMP_NUM_THREADS" not in os.environ:
367 os.environ["OMP_NUM_THREADS"] = str(num_threads)
368
369 from onnx import load # noqa: PLC0415
370 from onnx_model import OnnxModel # noqa: PLC0415
371
372 onnx_model = OnnxModel(load(args.model))
373
374 all_inputs = None
375 if args.dummy_inputs == "bert":
376 all_inputs = create_bert_inputs(
377 onnx_model,
378 args.batch_size,
379 args.sequence_length,
380 args.samples,
381 args.input_ids_name,
382 args.segment_ids_name,
383 args.input_mask_name,
384 )
385 elif args.dummy_inputs == "gpt2":
386 all_inputs = create_gpt2_inputs(
387 onnx_model,
388 args.batch_size,
389 args.sequence_length,
390 args.past_sequence_length,
391 args.samples,
392 )
393 elif args.dummy_inputs == "longformer":
394 all_inputs = create_longformer_inputs(
395 onnx_model,
396 args.batch_size,
397 args.sequence_length,
398 args.global_length,
399 args.samples,
400 )
401 else: # default
402 all_inputs = create_dummy_inputs(onnx_model, args.batch_size, args.sequence_length, args.samples)
403
404 profile_file = run_profile(
405 args.model,
406 args.use_gpu,
407 args.provider,
408 args.basic_optimization,
409 args.thread_num,
410 all_inputs,
411 )
412
413 return profile_file
414
415
416if __name__ == "__main__":
417 arguments = parse_arguments()
418 print("Arguments", arguments)
419
420 from benchmark_helper import setup_logger
421
422 setup_logger(arguments.verbose)
423
424 if not arguments.input:
425 assert arguments.model, "requires either --model to run profiling or --input to read profiling results"
426 profile_file = run(arguments)
427 else:
428 profile_file = arguments.input
429 from profile_result_processor import process_results
430
431 results = process_results(profile_file, arguments)
432
433 for line in results:
434 print(line)
435 