202 lines
7.5 KiB
Python
202 lines
7.5 KiB
Python
#
|
|
# SPDX-FileCopyrightText: Copyright (c) 1993-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
#
|
|
|
|
"""
|
|
Test ORT-TRT engine of DeBERTa model. Different precisions are supported.
|
|
|
|
Usage:
|
|
Test model inference time:
|
|
- python deberta_ort_inference.py --onnx=./test/deberta.onnx --test fp16
|
|
|
|
Correctness check by comparing original model and model with plugin:
|
|
- python deberta_ort_inference.py --onnx=./test/deberta --correctness-check fp16
|
|
|
|
Notes:
|
|
- supported precisions are fp32/fp16. For test, you can specify more than one precisions, and TensorRT engine of each precision will be built sequentially.
|
|
- engine files are saved at `./engine_cache/[Model name]_[GPU name]_[Precision]/`. Note that TensorRT engine is specific to both GPU architecture and TensorRT version.
|
|
- if in --correctness-check mode, the argument for --onnx is the stem name for the model without .onnx extension.
|
|
"""
|
|
|
|
import os, argparse
|
|
import onnxruntime as ort
|
|
import numpy as np
|
|
import torch
|
|
from time import time
|
|
|
|
ENGINE_PATH = './test'
|
|
if not os.path.exists(ENGINE_PATH):
|
|
os.makedirs(ENGINE_PATH)
|
|
|
|
def GPU_ABBREV(name):
|
|
'''
|
|
Map GPU device query name to abbreviation.
|
|
|
|
::param str name Device name from torch.cuda.get_device_name().
|
|
::return str GPU abbreviation.
|
|
'''
|
|
|
|
GPU_LIST = [
|
|
'V100',
|
|
'TITAN',
|
|
'T4',
|
|
'A100',
|
|
'A10G',
|
|
'A10'
|
|
]
|
|
# Partial list, can be extended. The order of A100, A10G, A10 matters. They're put in a way to not detect substring A10 as A100
|
|
|
|
for i in GPU_LIST:
|
|
if i in name:
|
|
return i
|
|
|
|
return 'GPU' # for names not in the partial list, use 'GPU' as default
|
|
|
|
gpu_name = GPU_ABBREV(torch.cuda.get_device_name())
|
|
|
|
VALID_PRECISION = [
|
|
'fp32',
|
|
'fp16',
|
|
]
|
|
|
|
parser = argparse.ArgumentParser(description="Build and test TensorRT engine.")
|
|
parser.add_argument('--onnx', required=True, help='ONNX model path (or filename stem if in correctness check mode).')
|
|
parser.add_argument('--test', nargs='+', help='Test ORT-TRT engine in precision fp32/fp16. You can list multiple precisions to test all of them.')
|
|
parser.add_argument('--correctness-check', nargs='+', help='Correctness check for original & plugin TRT engines in precision fp32/fp16. You can list multiple precisions to check all of them.')
|
|
|
|
args = parser.parse_args()
|
|
|
|
ONNX_MODEL = args.onnx
|
|
MODEL_STEM = os.path.splitext(args.onnx)[0].split('/')[-1]
|
|
TEST = args.test
|
|
CORRECTNESS = args.correctness_check
|
|
|
|
if TEST:
|
|
for i in TEST:
|
|
if i not in VALID_PRECISION:
|
|
parser.error(f'Unsupported precision {i}')
|
|
if CORRECTNESS:
|
|
for i in CORRECTNESS:
|
|
if i not in VALID_PRECISION:
|
|
parser.error(f'Unsupported precision {i}')
|
|
|
|
def test_engine():
|
|
|
|
for precision in TEST:
|
|
|
|
engine_cachepath = '/'.join([ENGINE_PATH, '_'.join([MODEL_STEM, gpu_name, precision, 'ort'])])
|
|
|
|
providers = [
|
|
('TensorrtExecutionProvider', {
|
|
'trt_max_workspace_size': 2147483648,
|
|
'trt_fp16_enable': precision == 'fp16',
|
|
'trt_engine_cache_enable': True,
|
|
'trt_engine_cache_path': engine_cachepath
|
|
}),
|
|
'CUDAExecutionProvider'] # EP order indicates priority
|
|
|
|
so = ort.SessionOptions()
|
|
|
|
sess = ort.InferenceSession(ONNX_MODEL, sess_options=so, providers=providers)
|
|
|
|
print(f'Running inference on engine {engine_cachepath}')
|
|
|
|
## psuedo-random input test
|
|
batch_size = 1
|
|
seq_len = sess.get_inputs()[0].shape[1]
|
|
vocab = 128203
|
|
input_ids = torch.randint(0, vocab, (batch_size, seq_len), dtype=torch.long)
|
|
attention_mask = torch.randint(0, 2, (batch_size, seq_len), dtype=torch.long)
|
|
inputs = {
|
|
'input_ids': input_ids.numpy(),
|
|
'attention_mask': attention_mask.numpy()
|
|
}
|
|
|
|
outputs = sess.run(None, inputs)
|
|
|
|
nreps = 100
|
|
start_time = time()
|
|
for _ in range(nreps):
|
|
sess.run(None, inputs)
|
|
end_time = time()
|
|
|
|
duration = end_time - start_time
|
|
print(f'Average Inference time (ms) of {nreps} runs: {duration/nreps*1000:.3f}. For more accurate test, please use the onnxruntime_perf_test commands.')
|
|
|
|
def correctness_check_engines():
|
|
|
|
for precision in CORRECTNESS:
|
|
|
|
engine_cachepath1 = '/'.join([ENGINE_PATH, '_'.join([MODEL_STEM, 'original', gpu_name, precision, 'ort'])])
|
|
engine_cachepath2 = '/'.join([ENGINE_PATH, '_'.join([MODEL_STEM, 'plugin', gpu_name, precision, 'ort'])])
|
|
|
|
if not os.path.exists(engine_cachepath1) or not os.path.exists(engine_cachepath2):
|
|
print('At least one of the original and/or plugin engines do not exist. Please build them first by --test')
|
|
return
|
|
|
|
print(f'Running inference on original engine {engine_cachepath1} and plugin engine {engine_cachepath2}')
|
|
|
|
so = ort.SessionOptions()
|
|
|
|
providers1 = [
|
|
('TensorrtExecutionProvider', {
|
|
'trt_max_workspace_size': 2147483648,
|
|
'trt_fp16_enable': precision == 'fp16',
|
|
'trt_engine_cache_enable': True,
|
|
'trt_engine_cache_path': engine_cachepath1
|
|
}),
|
|
'CUDAExecutionProvider']
|
|
|
|
providers2 = [
|
|
('TensorrtExecutionProvider', {
|
|
'trt_max_workspace_size': 2147483648,
|
|
'trt_fp16_enable': precision == 'fp16',
|
|
'trt_engine_cache_enable': True,
|
|
'trt_engine_cache_path': engine_cachepath2
|
|
}),
|
|
'CUDAExecutionProvider']
|
|
|
|
sess1 = ort.InferenceSession(ONNX_MODEL+'_original.onnx', sess_options=so, providers=providers1)
|
|
sess2 = ort.InferenceSession(ONNX_MODEL+'_plugin.onnx', sess_options=so, providers=providers2)
|
|
|
|
## psuedo-random input test
|
|
batch_size = 1
|
|
seq_len = sess1.get_inputs()[0].shape[1]
|
|
vocab = 128203
|
|
input_ids = torch.randint(0, vocab, (batch_size, seq_len), dtype=torch.long)
|
|
attention_mask = torch.randint(0, 2, (batch_size, seq_len), dtype=torch.long)
|
|
inputs = {
|
|
'input_ids': input_ids.numpy(),
|
|
'attention_mask': attention_mask.numpy()
|
|
}
|
|
|
|
outputs1 = sess1.run(None, inputs)
|
|
outputs2 = sess2.run(None, inputs)
|
|
|
|
for i in range(len(outputs1)):
|
|
avg_abs_error = np.sum(np.abs(outputs1[i] - outputs2[i])) / outputs1[i].size
|
|
max_abs_error = np.max(np.abs(outputs1[i] - outputs2[i]))
|
|
print(f"Output {i}:")
|
|
print("onnx model (original): ", outputs1[i])
|
|
print("onnx model (plugin): ", outputs2[i])
|
|
print(f"[Output {i} Element-wise Check] Avgerage absolute error: {avg_abs_error:e}, Maximum absolute error: {max_abs_error:e}. 1e-2~1e-3 expected for FP16 (10 significance bits) and 1e-6~1e-7 expected for FP32 (23 significance bits) " ) # machine epsilon for different precisions: https://en.wikipedia.org/wiki/Machine_epsilon
|
|
|
|
if TEST:
|
|
test_engine()
|
|
|
|
if CORRECTNESS:
|
|
correctness_check_engines()
|