diff --git a/requirements/optional.txt b/requirements/optional.txt index acd3024b8..3949c49cb 100644 --- a/requirements/optional.txt +++ b/requirements/optional.txt @@ -5,5 +5,4 @@ mmocr>=0.3.0 mmsegmentation ncnn onnxruntime>=1.8.0 -pyppl tensorrt diff --git a/tests/test_apis/test_torch2onnx.py b/tests/test_apis/test_torch2onnx.py index 0bc75d6d4..35af9cb44 100644 --- a/tests/test_apis/test_torch2onnx.py +++ b/tests/test_apis/test_torch2onnx.py @@ -23,7 +23,7 @@ def forward(self, x): test_model = TestModel().eval().cuda() -test_img = torch.rand([1, 3, 64, 64]) +test_img = torch.rand([1, 3, 8, 8]) def get_deploy_cfg(): diff --git a/tests/test_apis/test_wrapper.py b/tests/test_apis/test_wrapper.py index b63a9589d..127b4929a 100644 --- a/tests/test_apis/test_wrapper.py +++ b/tests/test_apis/test_wrapper.py @@ -1,3 +1,4 @@ +import subprocess import tempfile import pytest @@ -7,7 +8,7 @@ from mmdeploy.utils.constants import Backend onnx_file = tempfile.NamedTemporaryFile(suffix='.onnx').name -test_img = torch.rand([1, 3, 64, 64]) +test_img = torch.rand(1, 3, 8, 8) @pytest.mark.skip(reason='This a not test class but a utility class.') @@ -17,25 +18,15 @@ def __init__(self): super().__init__() def forward(self, x): - return x * 0.5 + return x + test_img -model = TestModel().eval().cuda() +model = TestModel().eval() @pytest.fixture(autouse=True, scope='module') def generate_onnx_file(): with torch.no_grad(): - dynamic_axes = { - 'input': { - 0: 'batch', - 2: 'width', - 3: 'height' - }, - 'output': { - 0: 'batch' - } - } torch.onnx.export( model, test_img, @@ -46,7 +37,7 @@ def generate_onnx_file(): do_constant_folding=True, verbose=False, opset_version=11, - dynamic_axes=dynamic_axes) + dynamic_axes=None) def check_backend_avaiable(backend): @@ -57,6 +48,22 @@ def check_backend_avaiable(backend): 'TensorRT is not installed or custom ops are not compiled.') if not torch.cuda.is_available(): pytest.skip('CUDA is not available.') + elif backend == Backend.ONNXRUNTIME: + from mmdeploy.apis.onnxruntime import is_available as ort_available + if not ort_available(): + pytest.skip( + 'ONNXRuntime is not installed or custom ops are not compiled.') + elif backend == Backend.PPL: + from mmdeploy.apis.ppl import is_available as ppl_avaiable + if not ppl_avaiable(): + pytest.skip('PPL is not available.') + elif backend == Backend.NCNN: + from mmdeploy.apis.ncnn import is_available as ncnn_available + if not ncnn_available(): + pytest.skip( + 'NCNN is not installed or custom ops are not compiled.') + else: + raise NotImplementedError(f'Unknown backend type: {backend.value}') def onnx2backend(backend, onnx_file): @@ -66,20 +73,46 @@ def onnx2backend(backend, onnx_file): engine = create_trt_engine( onnx_file, { 'input': { - 'min_shape': [1, 3, 64, 64], - 'opt_shape': [1, 3, 64, 64], - 'max_shape': [1, 3, 64, 64] + 'min_shape': [1, 3, 8, 8], + 'opt_shape': [1, 3, 8, 8], + 'max_shape': [1, 3, 8, 8] } }) save_trt_engine(engine, backend_file) return backend_file - - -def create_wrapper(backend, engine_file): + elif backend == Backend.ONNXRUNTIME: + return onnx_file + elif backend == Backend.PPL: + return onnx_file + elif backend == Backend.NCNN: + from mmdeploy.apis.ncnn import get_onnx2ncnn_path + onnx2ncnn_path = get_onnx2ncnn_path() + param_file = tempfile.NamedTemporaryFile(suffix='.param').name + bin_file = tempfile.NamedTemporaryFile(suffix='.bin').name + subprocess.call([onnx2ncnn_path, onnx_file, param_file, bin_file]) + return param_file, bin_file + + +def create_wrapper(backend, model_files): if backend == Backend.TENSORRT: from mmdeploy.apis.tensorrt import TRTWrapper - trt_model = TRTWrapper(engine_file) + trt_model = TRTWrapper(model_files) return trt_model + elif backend == Backend.ONNXRUNTIME: + from mmdeploy.apis.onnxruntime import ORTWrapper + ort_model = ORTWrapper(model_files, 0) + return ort_model + elif backend == Backend.PPL: + from mmdeploy.apis.ppl import PPLWrapper + ppl_model = PPLWrapper(model_files, 0) + return ppl_model + elif backend == Backend.NCNN: + from mmdeploy.apis.ncnn import NCNNWrapper + param_file, bin_file = model_files + ncnn_model = NCNNWrapper(param_file, bin_file, output_names=['output']) + return ncnn_model + else: + raise NotImplementedError(f'Unknown backend type: {backend.value}') def run_wrapper(backend, wrapper, input): @@ -88,9 +121,27 @@ def run_wrapper(backend, wrapper, input): results = wrapper({'input': input})['output'] results = results.detach().cpu() return results + elif backend == Backend.ONNXRUNTIME: + input = input.cuda() + results = wrapper({'input': input})[0] + return list(results) + elif backend == Backend.PPL: + input = input.cuda() + results = wrapper({'input': input})[0] + return list(results) + elif backend == Backend.NCNN: + input = input.float() + results = wrapper({'input': input})['output'] + results = results.detach().cpu().numpy() + results_list = list(results) + return results_list + else: + raise NotImplementedError(f'Unknown backend type: {backend.value}') -ALL_BACKEND = [Backend.TENSORRT] +ALL_BACKEND = [ + Backend.TENSORRT, Backend.ONNXRUNTIME, Backend.PPL, Backend.NCNN +] @pytest.mark.parametrize('backend', ALL_BACKEND) diff --git a/tests/test_utils/test_utils.py b/tests/test_utils/test_utils.py index 7abb238dc..1b3e6003d 100644 --- a/tests/test_utils/test_utils.py +++ b/tests/test_utils/test_utils.py @@ -4,8 +4,8 @@ import mmcv import pytest -from mmdeploy.utils import get_onnx_config, get_task_type, load_config -from mmdeploy.utils.constants import Task +import mmdeploy.utils as util +from mmdeploy.utils.constants import Backend, Codebase, Task from mmdeploy.utils.export_info import dump_info correct_model_path = 'tests/data/srgan.py' @@ -21,93 +21,276 @@ def create_empty_file(): os.mknod(empty_file_path) -def test_load_config_none(): - with pytest.raises(AssertionError): - load_config() - - -def test_load_config_type_error(): - with pytest.raises(TypeError): - load_config(1) - - -def test_load_config_file_error(): - with pytest.raises(FileNotFoundError): - load_config(empty_path) - - -@pytest.mark.parametrize('args', [ - [empty_file_path], - [correct_model_path], - [correct_model_cfg], - (correct_model_path, correct_deploy_path), - (correct_model_path, correct_deploy_cfg), - (correct_model_cfg, correct_deploy_cfg), -]) -def test_load_config(args): - configs = load_config(*args) - for v in zip(configs, args): - if isinstance(v[1], str): - cfg = mmcv.Config.fromfile(v[1]) - else: - cfg = v[1] - assert v[0]._cfg_dict == cfg._cfg_dict - - -@pytest.mark.parametrize('deploy_cfg, default', - [(empty_file_path, None), - (empty_file_path, Task.SUPER_RESOLUTION)]) -def test_get_task_type_default(deploy_cfg, default): - if default is None: - res = get_task_type(deploy_cfg) - else: - res = get_task_type(deploy_cfg, default) - assert res == default - - -@pytest.mark.parametrize('deploy_cfg, default', - [(correct_deploy_path, None), - (correct_deploy_path, Task.TEXT_DETECTION), - (correct_deploy_cfg, None)]) -def test_get_task_type(deploy_cfg, default): - if default is None: - res = get_task_type(deploy_cfg) - else: - res = get_task_type(deploy_cfg, default) - assert res == Task.SUPER_RESOLUTION - - -def test_get_onnx_config_error(): - with pytest.raises(Exception): - get_onnx_config(empty_file_path) - - -@pytest.mark.parametrize('deploy_cfg', - [correct_deploy_path, correct_deploy_cfg]) -def test_get_onnx_config(deploy_cfg): - onnx_config = dict( - dynamic_axes={ - 'input': { - 0: 'batch', - 2: 'height', - 3: 'width' +class TestLoadConfigError: + + def test_load_config_none(self): + with pytest.raises(AssertionError): + util.load_config() + + def test_load_config_type_error(self): + with pytest.raises(TypeError): + util.load_config(1) + + def test_load_config_file_error(self): + with pytest.raises(FileNotFoundError): + util.load_config(empty_path) + + +class TestLoadConfig: + + @pytest.mark.parametrize('args', [ + [empty_file_path], + [correct_model_path], + [correct_model_cfg], + (correct_model_path, correct_deploy_path), + (correct_model_path, correct_deploy_cfg), + (correct_model_cfg, correct_deploy_cfg), + ]) + def test_load_config(self, args): + configs = util.load_config(*args) + for v in zip(configs, args): + if isinstance(v[1], str): + cfg = mmcv.Config.fromfile(v[1]) + else: + cfg = v[1] + assert v[0]._cfg_dict == cfg._cfg_dict + + +class TestGetTaskType: + + def test_get_task_type_none(self): + assert util.get_task_type(mmcv.Config(dict())) is None + + def test_get_task_type_default(self): + assert util.get_task_type(mmcv.Config(dict()), + Task.SUPER_RESOLUTION) == \ + Task.SUPER_RESOLUTION + + def test_get_task_type(self): + assert util.get_task_type(correct_deploy_path) == Task.SUPER_RESOLUTION + + +class TestGetCodebase: + + def test_get_codebase_none(self): + assert util.get_codebase(mmcv.Config(dict())) is None + + def test_get_codebase_default(self): + assert util.get_codebase(mmcv.Config(dict()), + Codebase.MMEDIT) == Codebase.MMEDIT + + def test_get_codebase(self): + assert util.get_codebase(correct_deploy_path) == Codebase.MMEDIT + + +class TestGetBackend: + + def test_get_backend_none(self): + assert util.get_backend(mmcv.Config(dict())) is None + + def test_get_backend_default(self): + assert util.get_backend(empty_file_path, + Backend.ONNXRUNTIME) == Backend.ONNXRUNTIME + + def test_get_backend(self): + assert util.get_backend(correct_deploy_path) == Backend.ONNXRUNTIME + + +class TestGetOnnxConfig: + + def test_get_onnx_config_error(self): + with pytest.raises(Exception): + util.get_onnx_config(empty_file_path) + + def test_get_onnx_config(self): + onnx_config = dict( + dynamic_axes={ + 'input': { + 0: 'batch', + 2: 'height', + 3: 'width' + }, + 'output': { + 0: 'batch', + 2: 'height', + 3: 'width' + } }, - 'output': { - 0: 'batch', - 2: 'height', - 3: 'width' - } - }, - type='onnx', - export_params=True, - keep_initializers_as_inputs=False, - opset_version=11, - save_file='end2end.onnx', - input_names=['input'], - output_names=['output'], - input_shape=None) - res = get_onnx_config(deploy_cfg) - assert res == onnx_config + type='onnx', + export_params=True, + keep_initializers_as_inputs=False, + opset_version=11, + save_file='end2end.onnx', + input_names=['input'], + output_names=['output'], + input_shape=None) + assert util.get_onnx_config(correct_deploy_path) == onnx_config + + +class TestIsDynamic: + + config_with_onnx_config = mmcv.Config(dict(onnx_config=dict())) + + config_with_dynamic_axes = mmcv.Config( + dict( + onnx_config=dict( + dynamic_axes={'input': { + 0: 'batch', + 2: 'height', + 3: 'width' + }}))) + + def test_is_dynamic_batch_none(self): + assert util.is_dynamic_batch( + TestIsDynamic.config_with_onnx_config) is False + + def test_is_dynamic_batch_error_name(self): + assert util.is_dynamic_batch(TestIsDynamic.config_with_dynamic_axes, + 'output') is False + + def test_is_dynamic_batch(self): + assert util.is_dynamic_batch( + TestIsDynamic.config_with_dynamic_axes) is True + + def test_is_dynamic_shape_none(self): + assert util.is_dynamic_shape( + TestIsDynamic.config_with_onnx_config) is False + + def test_is_dynamic_shape_error_name(self): + assert util.is_dynamic_shape(TestIsDynamic.config_with_dynamic_axes, + 'output') is False + + def test_is_dynamic_shape(self): + assert util.is_dynamic_shape( + TestIsDynamic.config_with_dynamic_axes) is True + + +class TestGetInputShape: + config_without_input_shape = mmcv.Config( + dict(onnx_config=dict(input_shape=None))) + config_with_input_shape = mmcv.Config( + dict(onnx_config=dict(input_shape=[1, 1]))) + config_with_error_shape = mmcv.Config( + dict(onnx_config=dict(input_shape=[1, 1, 1]))) + + def test_get_input_shape_none(self): + assert util.get_input_shape( + TestGetInputShape.config_without_input_shape) is None + + def test_get_input_shape_error(self): + with pytest.raises(Exception): + util.get_input_shape(TestGetInputShape.config_with_error_shape) + + def test_get_input_shape(self): + assert util.get_input_shape( + TestGetInputShape.config_with_input_shape) == [1, 1] + + +class TestCfgApplyMark: + + config_with_mask = mmcv.Config( + dict(partition_config=dict(apply_marks=True))) + + def test_cfg_apply_marks_none(self): + assert util.cfg_apply_marks(mmcv.Config(dict())) is None + + def test_cfg_apply_marks(self): + assert util.cfg_apply_marks(TestCfgApplyMark.config_with_mask) is True + + +class TestGetPartitionConfig: + + config_with_mask = mmcv.Config( + dict(partition_config=dict(apply_marks=True))) + config_without_mask = mmcv.Config( + dict(partition_config=dict(apply_marks=False))) + + def test_get_partition_config_none(self): + assert util.get_partition_config(mmcv.Config(dict())) is None + + def test_get_partition_config_without_mask(self): + assert util.get_partition_config( + TestGetPartitionConfig.config_without_mask) is None + + def test_get_partition_config(self): + assert util.get_partition_config( + TestGetPartitionConfig.config_with_mask) == dict(apply_marks=True) + + +class TestGetCalib: + config_with_calib = mmcv.Config( + dict(calib_config=dict(create_calib=True, calib_file='calib_data.h5'))) + + config_without_calib = mmcv.Config( + dict( + calib_config=dict(create_calib=False, calib_file='calib_data.h5'))) + + def test_get_calib_config(self): + assert util.get_calib_config(TestGetCalib.config_with_calib) == dict( + create_calib=True, calib_file='calib_data.h5') + + def test_get_calib_filename_none(self): + assert util.get_calib_filename(mmcv.Config(dict())) is None + + def test_get_calib_filename_false(self): + assert util.get_calib_filename( + TestGetCalib.config_without_calib) is None + + def test_get_calib_filename(self): + assert util.get_calib_filename( + TestGetCalib.config_with_calib) == 'calib_data.h5' + + +class TestGetCommonConfig: + config_with_common_config = mmcv.Config( + dict( + backend_config=dict( + type='tensorrt', common_config=dict(fp16_mode=False)))) + + def test_get_common_config(self): + assert util.get_common_config( + TestGetCommonConfig.config_with_common_config) == dict( + fp16_mode=False) + + +class TestGetModelInputs: + + config_with_model_inputs = mmcv.Config( + dict(backend_config=dict(model_inputs=[dict(input_shapes=None)]))) + + def test_model_inputs(self): + assert util.get_model_inputs( + TestGetModelInputs.config_with_model_inputs) == [ + dict(input_shapes=None) + ] + + +class TestGetMmdetParams: + + config_with_mmdet_params = mmcv.Config( + dict( + codebase_config=dict( + type='mmdet', + task='ObjectDetection', + post_processing=dict( + score_threshold=0.05, + iou_threshold=0.5, + max_output_boxes_per_class=200, + pre_top_k=-1, + keep_top_k=100, + background_label_id=-1, + )))) + + def test_get_mmdet_params(self): + assert util.get_mmdet_params( + TestGetMmdetParams.config_with_mmdet_params) == dict( + score_threshold=0.05, + iou_threshold=0.5, + max_output_boxes_per_class=200, + pre_top_k=-1, + keep_top_k=100, + background_label_id=-1, + ) def test_AdvancedEnum():