BechusRantus's picture
Upload folder using huggingface_hub
7134ce7 verified
Raw
History Blame Contribute Delete
9.05 kB
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
import json
from pathlib import Path
import torch
from ultralytics.utils import IS_JETSON, LOGGER
def export_onnx(
torch_model,
im,
onnx_file,
opset=14,
input_names=["images"],
output_names=["output0"],
dynamic=False,
):
"""
Exports a PyTorch model to ONNX format.
Args:
torch_model (torch.nn.Module): The PyTorch model to export.
im (torch.Tensor): Example input tensor for the model.
onnx_file (str): Path to save the exported ONNX file.
opset (int): ONNX opset version to use for export.
input_names (list): List of input tensor names.
output_names (list): List of output tensor names.
dynamic (bool | dict, optional): Whether to enable dynamic axes. Defaults to False.
Notes:
- Setting `do_constant_folding=True` may cause issues with DNN inference for torch>=1.12.
"""
torch.onnx.export(
torch_model,
im,
onnx_file,
verbose=False,
opset_version=opset,
do_constant_folding=True, # WARNING: DNN inference with torch>=1.12 may require do_constant_folding=False
input_names=input_names,
output_names=output_names,
dynamic_axes=dynamic or None,
)
def export_engine(
onnx_file,
engine_file=None,
workspace=None,
half=False,
int8=False,
dynamic=False,
shape=(1, 3, 640, 640),
dla=None,
dataset=None,
metadata=None,
verbose=False,
prefix="",
):
"""
Exports a YOLO model to TensorRT engine format.
Args:
onnx_file (str): Path to the ONNX file to be converted.
engine_file (str, optional): Path to save the generated TensorRT engine file.
workspace (int, optional): Workspace size in GB for TensorRT. Defaults to None.
half (bool, optional): Enable FP16 precision. Defaults to False.
int8 (bool, optional): Enable INT8 precision. Defaults to False.
dynamic (bool, optional): Enable dynamic input shapes. Defaults to False.
shape (tuple, optional): Input shape (batch, channels, height, width). Defaults to (1, 3, 640, 640).
dla (int, optional): DLA core to use (Jetson devices only). Defaults to None.
dataset (ultralytics.data.build.InfiniteDataLoader, optional): Dataset for INT8 calibration. Defaults to None.
metadata (dict, optional): Metadata to include in the engine file. Defaults to None.
verbose (bool, optional): Enable verbose logging. Defaults to False.
prefix (str, optional): Prefix for log messages. Defaults to "".
Raises:
ValueError: If DLA is enabled on non-Jetson devices or required precision is not set.
RuntimeError: If the ONNX file cannot be parsed.
Notes:
- TensorRT version compatibility is handled for workspace size and engine building.
- INT8 calibration requires a dataset and generates a calibration cache.
- Metadata is serialized and written to the engine file if provided.
"""
import tensorrt as trt # noqa
engine_file = engine_file or Path(onnx_file).with_suffix(".engine")
logger = trt.Logger(trt.Logger.INFO)
if verbose:
logger.min_severity = trt.Logger.Severity.VERBOSE
# Engine builder
builder = trt.Builder(logger)
config = builder.create_builder_config()
workspace = int((workspace or 0) * (1 << 30))
is_trt10 = int(trt.__version__.split(".")[0]) >= 10 # is TensorRT >= 10
if is_trt10 and workspace > 0:
config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, workspace)
elif workspace > 0: # TensorRT versions 7, 8
config.max_workspace_size = workspace
flag = 1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
network = builder.create_network(flag)
half = builder.platform_has_fast_fp16 and half
int8 = builder.platform_has_fast_int8 and int8
# Optionally switch to DLA if enabled
if dla is not None:
if not IS_JETSON:
raise ValueError("DLA is only available on NVIDIA Jetson devices")
LOGGER.info(f"{prefix} enabling DLA on core {dla}...")
if not half and not int8:
raise ValueError(
"DLA requires either 'half=True' (FP16) or 'int8=True' (INT8) to be enabled. Please enable one of them and try again."
)
config.default_device_type = trt.DeviceType.DLA
config.DLA_core = int(dla)
config.set_flag(trt.BuilderFlag.GPU_FALLBACK)
# Read ONNX file
parser = trt.OnnxParser(network, logger)
if not parser.parse_from_file(onnx_file):
raise RuntimeError(f"failed to load ONNX file: {onnx_file}")
# Network inputs
inputs = [network.get_input(i) for i in range(network.num_inputs)]
outputs = [network.get_output(i) for i in range(network.num_outputs)]
for inp in inputs:
LOGGER.info(f'{prefix} input "{inp.name}" with shape{inp.shape} {inp.dtype}')
for out in outputs:
LOGGER.info(f'{prefix} output "{out.name}" with shape{out.shape} {out.dtype}')
if dynamic:
if shape[0] <= 1:
LOGGER.warning(f"{prefix} WARNING ⚠️ 'dynamic=True' model requires max batch size, i.e. 'batch=16'")
profile = builder.create_optimization_profile()
min_shape = (1, shape[1], 32, 32) # minimum input shape
max_shape = (*shape[:2], *(int(max(1, workspace or 1) * d) for d in shape[2:])) # max input shape
for inp in inputs:
profile.set_shape(inp.name, min=min_shape, opt=shape, max=max_shape)
config.add_optimization_profile(profile)
LOGGER.info(f"{prefix} building {'INT8' if int8 else 'FP' + ('16' if half else '32')} engine as {engine_file}")
if int8:
config.set_flag(trt.BuilderFlag.INT8)
config.set_calibration_profile(profile)
config.profiling_verbosity = trt.ProfilingVerbosity.DETAILED
class EngineCalibrator(trt.IInt8Calibrator):
"""
Custom INT8 calibrator for TensorRT.
Args:
dataset (object): Dataset for calibration.
batch (int): Batch size for calibration.
cache (str, optional): Path to save the calibration cache. Defaults to "".
"""
def __init__(
self,
dataset, # ultralytics.data.build.InfiniteDataLoader
cache: str = "",
) -> None:
trt.IInt8Calibrator.__init__(self)
self.dataset = dataset
self.data_iter = iter(dataset)
self.algo = trt.CalibrationAlgoType.ENTROPY_CALIBRATION_2
self.batch = dataset.batch_size
self.cache = Path(cache)
def get_algorithm(self) -> trt.CalibrationAlgoType:
"""Get the calibration algorithm to use."""
return self.algo
def get_batch_size(self) -> int:
"""Get the batch size to use for calibration."""
return self.batch or 1
def get_batch(self, names) -> list:
"""Get the next batch to use for calibration, as a list of device memory pointers."""
try:
im0s = next(self.data_iter)["img"] / 255.0
im0s = im0s.to("cuda") if im0s.device.type == "cpu" else im0s
return [int(im0s.data_ptr())]
except StopIteration:
# Return [] or None, signal to TensorRT there is no calibration data remaining
return None
def read_calibration_cache(self) -> bytes:
"""Use existing cache instead of calibrating again, otherwise, implicitly return None."""
if self.cache.exists() and self.cache.suffix == ".cache":
return self.cache.read_bytes()
def write_calibration_cache(self, cache) -> None:
"""Write calibration cache to disk."""
_ = self.cache.write_bytes(cache)
# Load dataset w/ builder (for batching) and calibrate
config.int8_calibrator = EngineCalibrator(
dataset=dataset,
cache=str(Path(onnx_file).with_suffix(".cache")),
)
elif half:
config.set_flag(trt.BuilderFlag.FP16)
# Write file
build = builder.build_serialized_network if is_trt10 else builder.build_engine
with build(network, config) as engine, open(engine_file, "wb") as t:
# Metadata
if metadata is not None:
meta = json.dumps(metadata)
t.write(len(meta).to_bytes(4, byteorder="little", signed=True))
t.write(meta.encode())
# Model
t.write(engine if is_trt10 else engine.serialize())