409 lines
18 KiB
Python
409 lines
18 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.
|
|
#
|
|
import threading
|
|
|
|
import numpy as np
|
|
import pytest
|
|
import torch
|
|
|
|
from polygraphy import config, cuda, mod
|
|
from polygraphy.backend.trt import (
|
|
EngineFromNetwork,
|
|
NetworkFromOnnxBytes,
|
|
Profile,
|
|
TrtRunner,
|
|
engine_from_network,
|
|
network_from_onnx_bytes,
|
|
)
|
|
from polygraphy.backend.trt.runner import _get_array_on_cpu
|
|
from polygraphy.exception import PolygraphyException
|
|
from polygraphy.logger import G_LOGGER
|
|
from tests.models.meta import ONNX_MODELS
|
|
|
|
# Import CreateConfigRTX conditionally for TensorRT-RTX builds
|
|
if config.USE_TENSORRT_RTX:
|
|
import tensorrt_rtx as trt
|
|
from polygraphy.backend.tensorrt_rtx import CreateConfigRTX as CreateConfig
|
|
else:
|
|
import tensorrt as trt
|
|
from polygraphy.backend.trt import CreateConfig
|
|
|
|
|
|
class TestLoggerCallbacks:
|
|
@pytest.mark.parametrize("sev", G_LOGGER.SEVERITY_LETTER_MAPPING.keys())
|
|
def test_set_severity(self, sev):
|
|
G_LOGGER.module_severity = sev
|
|
|
|
|
|
@pytest.fixture(scope="class")
|
|
def nonzero_engine():
|
|
model = ONNX_MODELS["nonzero"]
|
|
network_loader = NetworkFromOnnxBytes(model.loader)
|
|
return engine_from_network(network_loader)
|
|
|
|
|
|
@pytest.fixture()
|
|
def identity_engine():
|
|
model = ONNX_MODELS["identity"]
|
|
network_loader = NetworkFromOnnxBytes(model.loader)
|
|
return engine_from_network(network_loader)
|
|
|
|
|
|
@pytest.fixture()
|
|
def reducable_engine():
|
|
model = ONNX_MODELS["reducable"]
|
|
network_loader = NetworkFromOnnxBytes(model.loader)
|
|
return engine_from_network(network_loader)
|
|
|
|
|
|
class TestTrtRunner:
|
|
def test_can_name_runner(self):
|
|
NAME = "runner"
|
|
runner = TrtRunner(None, name=NAME)
|
|
assert runner.name == NAME
|
|
|
|
def test_basic(self, identity_engine):
|
|
with TrtRunner(identity_engine) as runner:
|
|
assert runner.optimization_profile is None
|
|
assert runner.is_active
|
|
ONNX_MODELS["identity"].check_runner(runner)
|
|
assert runner.last_inference_time() is not None
|
|
assert not runner.is_active
|
|
|
|
@pytest.mark.serial
|
|
@pytest.mark.skipif(config.USE_TENSORRT_RTX, reason="TensorRT-RTX has different warning output behavior")
|
|
def test_warn_if_impl_methods_called(self, check_warnings_on_runner_impl_methods, identity_engine):
|
|
runner = TrtRunner(identity_engine)
|
|
check_warnings_on_runner_impl_methods(runner)
|
|
|
|
@pytest.mark.parametrize(
|
|
"inp, expected",
|
|
[
|
|
([1, 0, 1, 1], [[0, 2, 3]]),
|
|
([1, 0, 0, 1], [[0, 3]]),
|
|
([0, 0, 0, 1], [[3]]),
|
|
],
|
|
)
|
|
@pytest.mark.skipif(config.USE_TENSORRT_RTX, reason="TensorRT-RTX does not support data dependent shapes")
|
|
def test_data_dependent_shapes(self, nonzero_engine, inp, expected):
|
|
with TrtRunner(nonzero_engine) as runner:
|
|
outputs = runner.infer(
|
|
{
|
|
"input": np.array(
|
|
inp,
|
|
dtype=(np.int32 if mod.version(trt.__version__) < mod.version("9.0") else np.int64),
|
|
)
|
|
}
|
|
)
|
|
assert np.array_equal(outputs["nonzero_out_0"], np.array(expected, dtype=np.int32))
|
|
|
|
@pytest.mark.parametrize("copy_outputs_to_host", [True, False])
|
|
@pytest.mark.parametrize("device", ["cpu", "cuda"])
|
|
def test_torch_tensors(self, copy_outputs_to_host, identity_engine, device):
|
|
with TrtRunner(identity_engine) as runner:
|
|
arr = torch.ones([1, 1, 2, 2], dtype=torch.float32, device=device)
|
|
outputs = runner.infer({"x": arr}, copy_outputs_to_host=copy_outputs_to_host)
|
|
assert all(isinstance(t, torch.Tensor) for t in outputs.values())
|
|
|
|
assert torch.equal(outputs["y"].to("cpu"), arr.to("cpu"))
|
|
|
|
assert outputs["y"].device.type == ("cpu" if copy_outputs_to_host else "cuda")
|
|
|
|
def test_context(self, identity_engine):
|
|
with TrtRunner(identity_engine.create_execution_context) as runner:
|
|
ONNX_MODELS["identity"].check_runner(runner)
|
|
|
|
def test_device_buffer_order_matches_bindings(self, reducable_engine):
|
|
with TrtRunner(reducable_engine) as runner:
|
|
dev_buf_order = list(runner.device_input_buffers.keys())
|
|
for binding, dev_buf_name in zip(reducable_engine, dev_buf_order):
|
|
assert binding == dev_buf_name
|
|
|
|
def test_shape_output(self):
|
|
model = ONNX_MODELS["reshape"]
|
|
engine = engine_from_network(NetworkFromOnnxBytes(model.loader))
|
|
with engine, TrtRunner(engine.create_execution_context) as runner:
|
|
model.check_runner(runner)
|
|
|
|
def test_multithreaded_runners_from_engine(self, identity_engine):
|
|
with TrtRunner(identity_engine) as runner0, TrtRunner(identity_engine) as runner1:
|
|
t1 = threading.Thread(target=ONNX_MODELS["identity"].check_runner, args=(runner0,))
|
|
t2 = threading.Thread(target=ONNX_MODELS["identity"].check_runner, args=(runner1,))
|
|
t1.start()
|
|
t2.start()
|
|
t1.join()
|
|
t2.join()
|
|
|
|
@pytest.mark.parametrize("use_optimization_profile", [True, False])
|
|
@pytest.mark.skipif(
|
|
not config.USE_TENSORRT_RTX
|
|
and mod.version(trt.__version__) >= mod.version("8.6")
|
|
and mod.version(trt.__version__) < mod.version("8.7"),
|
|
reason="Bug in TRT 8.6",
|
|
)
|
|
def test_multiple_profiles(self, use_optimization_profile):
|
|
model = ONNX_MODELS["dynamic_identity"]
|
|
profile0_shapes = [
|
|
(1, 2, 1, 1),
|
|
(1, 2, 1, 1),
|
|
(1, 2, 1, 1),
|
|
] # Use min==opt==max to fix shapes in the engine.
|
|
profile1_shapes = [(1, 2, 1, 1), (1, 2, 2, 2), (1, 2, 4, 4)]
|
|
profile2_shapes = [(1, 2, 4, 4), (1, 2, 8, 8), (1, 2, 16, 16)]
|
|
network_loader = NetworkFromOnnxBytes(model.loader)
|
|
profiles = [
|
|
Profile().add("X", *profile0_shapes),
|
|
Profile().add("X", *profile1_shapes),
|
|
Profile().add("X", *profile2_shapes),
|
|
]
|
|
config_loader = CreateConfig(profiles=profiles)
|
|
engine = engine_from_network(network_loader, config_loader)
|
|
context = engine.create_execution_context()
|
|
|
|
for index, shapes in enumerate([profile0_shapes, profile1_shapes, profile2_shapes]):
|
|
with TrtRunner(
|
|
context,
|
|
optimization_profile=index if use_optimization_profile else None,
|
|
) as runner:
|
|
if not use_optimization_profile:
|
|
runner.set_profile(index)
|
|
|
|
assert runner.context.active_optimization_profile == index
|
|
for shape in shapes:
|
|
model.check_runner(runner, {"X": shape})
|
|
|
|
@pytest.mark.skipif(
|
|
not config.USE_TENSORRT_RTX and mod.version(trt.__version__) < mod.version("10.0"),
|
|
reason="Feature not present before 10.0",
|
|
)
|
|
@pytest.mark.parametrize("allocation_strategy", [None, "static", "profile", "runtime"])
|
|
def test_allocation_strategies(self, allocation_strategy):
|
|
if config.USE_TENSORRT_RTX and allocation_strategy == "runtime":
|
|
pytest.skip("TensorRT-RTX issues with runtime allocation strategy")
|
|
|
|
model = ONNX_MODELS["residual_block"]
|
|
profile0_shapes = [(1, 3, 224, 224), (1, 3, 224, 224), (1, 3, 224, 224)]
|
|
profile1_shapes = [(1, 3, 224, 224), (1, 3, 224, 224), (2, 3, 224, 224)]
|
|
profile2_shapes = [(1, 3, 224, 224), (1, 3, 224, 224), (4, 3, 224, 224)]
|
|
network_loader = NetworkFromOnnxBytes(model.loader)
|
|
profiles = [
|
|
Profile().add("gpu_0/data_0", *profile0_shapes),
|
|
Profile().add("gpu_0/data_0", *profile1_shapes),
|
|
Profile().add("gpu_0/data_0", *profile2_shapes),
|
|
]
|
|
config_loader = CreateConfig(profiles=profiles)
|
|
engine = engine_from_network(network_loader, config_loader)
|
|
|
|
for index, shapes in enumerate([profile0_shapes, profile1_shapes, profile2_shapes]):
|
|
with TrtRunner(
|
|
engine,
|
|
optimization_profile=index,
|
|
allocation_strategy=allocation_strategy,
|
|
) as runner:
|
|
for shape in shapes:
|
|
model.check_runner(runner, {"gpu_0/data_0": shape})
|
|
|
|
def test_empty_tensor_with_dynamic_input_shape_tensor(self):
|
|
model = ONNX_MODELS["empty_tensor_expand"]
|
|
shapes = [(1, 2, 0, 3, 0), (2, 2, 0, 3, 0), (4, 2, 0, 3, 0)]
|
|
network_loader = NetworkFromOnnxBytes(model.loader)
|
|
profiles = [Profile().add("new_shape", *shapes)]
|
|
config_loader = CreateConfig(profiles=profiles)
|
|
|
|
with TrtRunner(EngineFromNetwork(network_loader, config_loader)) as runner:
|
|
for shape in shapes:
|
|
model.check_runner(runner, {"new_shape": shape})
|
|
|
|
@pytest.mark.parametrize(
|
|
"names, err",
|
|
[
|
|
(["fake-input", "x"], "Extra inputs in"),
|
|
(["fake-input"], "The following inputs were not found"),
|
|
([], "The following inputs were not found"),
|
|
],
|
|
)
|
|
@pytest.mark.parametrize("module", [torch, np])
|
|
def test_error_on_wrong_name_feed_dict(self, names, err, identity_engine, module):
|
|
with TrtRunner(identity_engine) as runner:
|
|
with pytest.raises(PolygraphyException, match=err):
|
|
runner.infer({name: module.ones((1, 1, 2, 2), dtype=module.float32) for name in names})
|
|
|
|
@pytest.mark.parametrize("module", [torch, np])
|
|
def test_error_on_wrong_dtype_feed_dict(self, identity_engine, module):
|
|
with TrtRunner(identity_engine) as runner:
|
|
with pytest.raises(PolygraphyException, match="unexpected dtype."):
|
|
runner.infer({"x": module.ones((1, 1, 2, 2), dtype=module.int32)})
|
|
|
|
@pytest.mark.parametrize("module", [torch, np])
|
|
def test_error_on_wrong_shape_feed_dict(self, identity_engine, module):
|
|
with TrtRunner(identity_engine) as runner:
|
|
with pytest.raises(PolygraphyException, match="incompatible shape."):
|
|
runner.infer({"x": module.ones((1, 1, 3, 2), dtype=module.float32)})
|
|
|
|
@pytest.mark.parametrize("use_view", [True, False]) # We should be able to use DeviceArray in place of DeviceView
|
|
def test_device_views(self, use_view, reducable_engine):
|
|
with TrtRunner(reducable_engine) as runner, cuda.DeviceArray((1,), dtype=np.float32) as x:
|
|
x.copy_from(np.ones((1,), dtype=np.float32))
|
|
outputs = runner.infer(
|
|
{
|
|
"X0": x.view() if use_view else x,
|
|
"Y0": np.ones((1,), dtype=np.float32),
|
|
}
|
|
)
|
|
assert outputs["identity_out_6"][0] == 2
|
|
assert outputs["identity_out_8"][0] == 2
|
|
|
|
def test_no_output_copy(self, identity_engine):
|
|
with TrtRunner(identity_engine) as runner:
|
|
inp = np.ones(shape=(1, 1, 2, 2), dtype=np.float32)
|
|
outputs = runner.infer({"x": inp}, copy_outputs_to_host=False)
|
|
assert isinstance(outputs["y"], cuda.DeviceView)
|
|
assert np.array_equal(outputs["y"].numpy(), inp)
|
|
|
|
def test_subsequent_infers_with_different_input_types(self, identity_engine):
|
|
with TrtRunner(identity_engine) as runner:
|
|
inp = np.ones(shape=(1, 1, 2, 2), dtype=np.float32)
|
|
|
|
def check(outputs):
|
|
assert np.all(outputs["y"] == inp)
|
|
|
|
check(runner.infer({"x": inp}))
|
|
check(runner.infer({"x": cuda.DeviceArray(shape=inp.shape, dtype=inp.dtype).copy_from(inp)}))
|
|
|
|
torch_outputs = runner.infer({"x": torch.from_numpy(inp)})
|
|
check({name: out.numpy() for name, out in torch_outputs.items()})
|
|
check(runner.infer({"x": inp}))
|
|
|
|
@pytest.mark.parametrize("use_view", [True, False]) # We should be able to use DeviceArray in place of DeviceView
|
|
def test_device_view_dynamic_shapes(self, use_view):
|
|
model = ONNX_MODELS["dynamic_identity"]
|
|
profiles = [
|
|
Profile().add("X", (1, 2, 1, 1), (1, 2, 2, 2), (1, 2, 4, 4)),
|
|
]
|
|
runner = TrtRunner(EngineFromNetwork(NetworkFromOnnxBytes(model.loader), CreateConfig(profiles=profiles)))
|
|
with runner, cuda.DeviceArray(shape=(1, 2, 3, 3), dtype=np.float32) as arr:
|
|
inp = np.random.random_sample(size=(1, 2, 3, 3)).astype(np.float32)
|
|
arr.copy_from(inp)
|
|
outputs = runner.infer({"X": (cuda.DeviceView(arr.ptr, arr.shape, arr.dtype) if use_view else arr)})
|
|
assert np.all(outputs["Y"] == inp)
|
|
assert outputs["Y"].shape == (1, 2, 3, 3)
|
|
|
|
def test_cannot_use_device_view_shape_tensor(self):
|
|
model = ONNX_MODELS["empty_tensor_expand"]
|
|
with TrtRunner(EngineFromNetwork(NetworkFromOnnxBytes(model.loader))) as runner, cuda.DeviceArray(
|
|
shape=(5,),
|
|
dtype=(
|
|
np.int32
|
|
if mod.version(trt.__version__) < mod.version("9.0") and not config.USE_TENSORRT_RTX
|
|
else np.int64
|
|
),
|
|
) as arr:
|
|
with pytest.raises(PolygraphyException, match="it must reside in host memory"):
|
|
runner.infer({"data": np.ones((2, 0, 3, 0), dtype=np.float32), "new_shape": arr})
|
|
|
|
@pytest.mark.parametrize("hwc_input", [True, False], ids=["hwc_input", "chw_input"])
|
|
@pytest.mark.parametrize("hwc_output", [True, False], ids=["hwc_output", "chw_output"])
|
|
@pytest.mark.skipif(config.USE_TENSORRT_RTX, reason="TensorRT-RTX does not support custom I/O format networks")
|
|
def test_infer_chw_format(self, hwc_input, hwc_output):
|
|
model = ONNX_MODELS["identity_multi_ch"]
|
|
inp_shape = model.input_metadata["x"].shape
|
|
builder, network, parser = network_from_onnx_bytes(model.loader)
|
|
|
|
formats = 1 << int(trt.TensorFormat.HWC)
|
|
if hwc_input:
|
|
network.get_input(0).allowed_formats = formats
|
|
if hwc_output:
|
|
network.get_output(0).allowed_formats = formats
|
|
|
|
engine = engine_from_network((builder, network))
|
|
|
|
with TrtRunner(engine) as runner:
|
|
inp = np.random.normal(size=(inp_shape)).astype(np.float32)
|
|
if hwc_input:
|
|
inp = inp.transpose(0, 2, 3, 1)
|
|
|
|
outputs = runner.infer({"x": inp})
|
|
if hwc_input == hwc_output: # output in CHW/HWC format and similarly shaped
|
|
assert np.allclose(outputs["y"], inp)
|
|
elif not hwc_input and hwc_output: # output in HWC format and shaped (N, H, W, C)
|
|
assert np.allclose(outputs["y"].transpose(0, 3, 1, 2), inp)
|
|
else: # hwc_input and not hwc_output: output in CHW format and shaped (N, C, H, W)
|
|
assert np.allclose(outputs["y"].transpose(0, 2, 3, 1), inp)
|
|
|
|
@pytest.mark.parametrize("use_torch", [True, False])
|
|
def test_get_array_on_cpu(self, use_torch):
|
|
shape = (4,)
|
|
with cuda.DeviceArray.raw(shape) as arr:
|
|
host_buffers = {}
|
|
stream = cuda.Stream()
|
|
host_arr = _get_array_on_cpu(arr, "test", host_buffers, stream, arr.nbytes, use_torch)
|
|
|
|
if use_torch:
|
|
assert isinstance(host_arr, torch.Tensor)
|
|
else:
|
|
assert isinstance(host_arr, np.ndarray)
|
|
|
|
@pytest.mark.skipif(
|
|
mod.version(trt.__version__) < mod.version("10.0") and not config.USE_TENSORRT_RTX,
|
|
reason="Feature not present before 10.0",
|
|
)
|
|
@pytest.mark.parametrize("budget", [None, -2, -1, 0, 0.5, 0.99, 1.0, 1000, np.inf])
|
|
def test_weight_streaming(self, budget):
|
|
model = ONNX_MODELS["matmul_2layer"]
|
|
network_loader = NetworkFromOnnxBytes(model.loader, strongly_typed=True)
|
|
config_loader = CreateConfig(weight_streaming=True)
|
|
engine = engine_from_network(network_loader, config_loader)
|
|
|
|
if budget == np.inf:
|
|
# set to max size - 1
|
|
budget = engine.streamable_weights_size - 1
|
|
|
|
kwargs = {"weight_streaming_budget": None, "weight_streaming_percent": None}
|
|
if budget is not None:
|
|
if 0 < budget <= 1:
|
|
kwargs["weight_streaming_percent"] = budget * 100
|
|
else:
|
|
kwargs["weight_streaming_budget"] = int(budget)
|
|
|
|
with TrtRunner(engine, optimization_profile=0, **kwargs) as runner:
|
|
model.check_runner(runner)
|
|
|
|
@pytest.mark.skipif(not config.USE_TENSORRT_RTX, reason="TensorRT-RTX not enabled")
|
|
def test_compute_capabilities_engine_building(self):
|
|
"""Test compute capabilities integration with engine building"""
|
|
model = ONNX_MODELS["identity"]
|
|
network_loader = NetworkFromOnnxBytes(model.loader)
|
|
|
|
# Test --use-gpu flag
|
|
config_loader = CreateConfig(use_gpu=True)
|
|
engine = engine_from_network(network_loader, config_loader)
|
|
with TrtRunner(engine) as runner:
|
|
model.check_runner(runner)
|
|
|
|
# Test --compute-capabilities flag
|
|
config_loader = CreateConfig(compute_capabilities=[(7, 5), (8, 0), (8, 6)])
|
|
engine = engine_from_network(network_loader, config_loader)
|
|
with TrtRunner(engine) as runner:
|
|
model.check_runner(runner)
|
|
|
|
@pytest.mark.skipif(not config.USE_TENSORRT_RTX, reason="TensorRT-RTX not enabled")
|
|
def test_compute_capabilities_mutual_exclusion(self):
|
|
"""Test that use_gpu and compute_capabilities are mutually exclusive"""
|
|
# Test mutual exclusion - should raise an exception
|
|
with pytest.raises(PolygraphyException, match="use_gpu and compute_capabilities are mutually exclusive"):
|
|
CreateConfig(use_gpu=True, compute_capabilities=[(7, 5)])
|