AI 推理优化实战:ONNX Runtime 与 TensorRT 的性能对比与部署选型

📅 2026/6/19 14:37:56
AI 推理优化实战:ONNX Runtime 与 TensorRT 的性能对比与部署选型
AI 推理优化实战ONNX Runtime 与 TensorRT 的性能对比与部署选型一、模型推理的延迟瓶颈为什么训练快推理慢深度学习模型在训练和推理阶段面临截然不同的性能瓶颈。训练阶段关注吞吐量每秒处理多少样本可以利用大 Batch Size 和梯度累积来充分利用 GPU推理阶段关注延迟单个请求的响应时间通常 Batch Size 为 1GPU 利用率极低。一个在训练时吞吐量 1000 samples/s 的模型推理时单样本延迟可能高达 50ms远不能满足实时服务的需求。推理延迟的三大来源计算量FLOPs、内存访问Memory Bandwidth和框架开销Python 解释器、动态图调度。PyTorch 的动态图机制在训练时提供了灵活性但推理时每次执行都要重新构建计算图引入了不必要的开销。此外PyTorch 的算子实现未针对推理场景做极致优化如 Kernel 融合、精度混合导致 GPU 计算单元利用率不足。二、两种推理优化框架的架构差异ONNX Runtime 和 TensorRT 是两种主流的推理优化框架它们的优化策略和适用场景有显著差异。flowchart TB A[PyTorch 模型] -- B[导出 ONNX] B -- C{推理框架选择} C -- D[ONNX Runtime] C -- E[TensorRT] D -- D1[图优化: 算子融合 常量折叠] D -- D2[量化: INT8/FP16] D -- D3[执行提供器: CPU/GPU/TensorRT] D1 D2 D3 -- D4[通用性高, 跨平台] E -- E1[图优化: 层融合 Kernel 自动调优] E -- E2[量化: INT8/FP8 校准] E -- E3[内存优化: 张量复用 流水线] E1 E2 E3 -- E4[NVIDIA GPU 专属, 极致性能] D4 E4 -- F[性能基准测试]ONNX Runtime 是通用推理框架支持 CPU、GPU 和多种加速器优化以图级别变换为主TensorRT 是 NVIDIA 专属推理框架针对 NVIDIA GPU 做了极致优化包括 Kernel 自动调优、内存复用和 FP8 支持。三、两种框架的部署实现与性能对比3.1 ONNX Runtime 推理部署 ONNX Runtime 推理部署 支持 CPU 和 GPU 执行提供器 import onnxruntime as ort import numpy as np import torch from transformers import BertModel import time class OnnxRuntimeInferencer: ONNX Runtime 推理器 def __init__(self, onnx_path: str, provider: str CUDAExecutionProvider): 初始化推理会话 provider: CPUExecutionProvider 或 CUDAExecutionProvider # 配置会话选项 sess_options ort.SessionOptions() sess_options.graph_optimization_level ( ort.GraphOptimizationLevel.ORT_ENABLE_ALL) sess_options.intra_op_num_threads 4 # 创建推理会话 providers [provider, CPUExecutionProvider] self.session ort.InferenceSession( onnx_path, sess_optionssess_options, providersproviders) # 获取输入输出信息 self.input_names [inp.name for inp in self.session.get_inputs()] self.output_names [out.name for out in self.session.get_outputs()] def predict(self, input_ids: np.ndarray, attention_mask: np.ndarray) - np.ndarray: 执行推理 inputs { input_ids: input_ids.astype(np.int64), attention_mask: attention_mask.astype(np.int64), } outputs self.session.run(self.output_names, inputs) return outputs[0] def benchmark(self, input_ids: np.ndarray, attention_mask: np.ndarray, num_iterations: int 100) - dict: 性能基准测试 # 预热 for _ in range(10): self.predict(input_ids, attention_mask) # 正式测试 latencies [] for _ in range(num_iterations): start time.perf_counter() self.predict(input_ids, attention_mask) latencies.append( (time.perf_counter() - start) * 1000) return { mean_ms: np.mean(latencies), p50_ms: np.percentile(latencies, 50), p99_ms: np.percentile(latencies, 99), framework: ONNX Runtime, } def export_to_onnx(model: BertModel, onnx_path: str, seq_len: int 128): 将 PyTorch 模型导出为 ONNX 格式 model.eval() dummy_input_ids torch.randint( 0, 30000, (1, seq_len)) dummy_attention_mask torch.ones(1, seq_len, dtypetorch.long) torch.onnx.export( model, (dummy_input_ids, dummy_attention_mask), onnx_path, input_names[input_ids, attention_mask], output_names[logits], dynamic_axes{ input_ids: {0: batch, 1: seq_len}, attention_mask: {0: batch, 1: seq_len}, logits: {0: batch}, }, opset_version17, do_constant_foldingTrue, )3.2 TensorRT 推理部署 TensorRT 推理部署 利用 NVIDIA GPU 的极致优化能力 import tensorrt as trt import numpy as np import time class TensorRTInferencer: TensorRT 推理器 def __init__(self, engine_path: str): 加载 TensorRT 引擎 self.logger trt.Logger(trt.Logger.WARNING) self.runtime trt.Runtime(self.logger) with open(engine_path, rb) as f: engine_data f.read() self.engine self.runtime.deserialize_cuda_engine( engine_data) self.context self.engine.create_execution_context() # 分配 GPU 内存 self.inputs [] self.outputs [] self.bindings [] for i in range(self.engine.num_io_tensors): name self.engine.get_tensor_name(i) shape self.engine.get_tensor_shape(name) dtype trt.nptype(self.engine.get_tensor_dtype(name)) size trt.volume(shape) # 分配 GPU 内存 import pycuda.driver as cuda import pycuda.autoinit mem cuda.mem_alloc(size * np.dtype(dtype).itemsize) self.bindings.append(int(mem)) if self.engine.get_tensor_mode(name) trt.TensorIOMode.INPUT: self.inputs.append( {name: name, mem: mem, shape: shape, dtype: dtype}) else: self.outputs.append( {name: name, mem: mem, shape: shape, dtype: dtype}) def predict(self, input_ids: np.ndarray, attention_mask: np.ndarray) - np.ndarray: 执行推理 import pycuda.driver as cuda # 将输入数据拷贝到 GPU np.copyto( cuda.mem_alloc_like( self.inputs[0][mem]), input_ids.astype(self.inputs[0][dtype])) cuda.memcpy_htod( self.inputs[0][mem], input_ids.astype(self.inputs[0][dtype])) cuda.memcpy_htod( self.inputs[1][mem], attention_mask.astype(self.inputs[1][dtype])) # 设置输入输出地址 for inp in self.inputs: self.context.set_tensor_address( inp[name], int(inp[mem])) for out in self.outputs: self.context.set_tensor_address( out[name], int(out[mem])) # 执行推理 self.context.execute_async_v3( streamcuda.Stream()) # 将输出数据拷贝回 CPU output np.empty( self.outputs[0][shape], dtypeself.outputs[0][dtype]) cuda.memcpy_dtoh(output, self.outputs[0][mem]) return output def build_tensorrt_engine(onnx_path: str, engine_path: str, fp16: bool True, int8: bool False, calibration_dataNone): 从 ONNX 模型构建 TensorRT 引擎 包含 FP16/INT8 量化优化 logger trt.Logger(trt.Logger.WARNING) builder trt.Builder(logger) network builder.create_network( 1 int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)) parser trt.OnnxParser(network, logger) # 解析 ONNX 模型 with open(onnx_path, rb) as f: if not parser.parse(f.read()): for error in range(parser.num_errors): print(fONNX 解析错误: {parser.get_error(error)}) return # 配置构建器 config builder.create_builder_config() config.set_memory_pool_limit( trt.MemoryPoolType.WORKSPACE, 4 30) # 4GB 工作空间 if fp16: config.set_flag(trt.BuilderFlag.FP16) print(已启用 FP16 量化) if int8 and calibration_data is not None: config.set_flag(trt.BuilderFlag.INT8) config.int8_calibrator calibration_data print(已启用 INT8 量化) # 构建引擎 print(正在构建 TensorRT 引擎可能需要数分钟...) engine_bytes builder.build_serialized_network(network, config) with open(engine_path, wb) as f: f.write(engine_bytes) print(fTensorRT 引擎已保存: {engine_path})四、两种框架的适用边界与部署陷阱ONNX Runtime 的算子兼容性并非所有 PyTorch 算子都能导出为 ONNX 格式。自定义算子、动态控制流如if/else依赖输入值和部分高级操作如torch.nn.functional.grid_sample的某些模式可能导致导出失败或精度偏差。建议在导出后用onnxruntime的验证模式对比 PyTorch 和 ONNX 的输出差异确保精度损失在 1e-5 以内。TensorRT 的构建耗时TensorRT 引擎构建时会对每层做 Kernel 自动调优在当前 GPU 上测试不同 Kernel 实现的性能选择最优方案。这个过程可能需要 10-30 分钟且构建结果与 GPU 型号绑定——在 A100 上构建的引擎无法在 V100 上使用。生产环境中建议在部署目标 GPU 上预构建引擎将构建产物.engine 文件纳入版本管理。动态形状的支持差异ONNX Runtime 对动态形状如可变序列长度的支持较好通过dynamic_axes配置即可。TensorRT 对动态形状的支持通过 Optimization Profile 实现需要指定每个维度的最小值、最优值和最大值且运行时形状变化会触发重新调优增加延迟。对于序列长度变化频繁的场景建议使用固定长度 Padding 策略。INT8 量化的校准数据TensorRT 的 INT8 量化需要校准数据集来确定量化参数。校准数据应代表生产环境的真实数据分布如果校准数据与实际数据分布差异大量化后精度损失可能超过 5%。建议使用 500-1000 条生产数据作为校准集并在量化后用完整验证集评估精度。五、总结推理优化框架的选型核心在于通用性 vs. 极致性能的权衡。ONNX Runtime 通用性强、跨平台、部署简单适合快速上线和多平台场景TensorRT 在 NVIDIA GPU 上性能极致适合延迟敏感的在线服务。落地时建议先用 ONNX Runtime 快速验证确认精度和延迟基本满足需求后再考虑迁移到 TensorRT 做极致优化。FP16 量化是成本最低的优化手段精度损失通常 0.5%应优先启用INT8 量化需要校准数据建议在 FP16 不满足需求时再考虑。