TensorRT 推理优化FP16、INT8、Layer Fusion 的实战对比一、个性化深度引言上线前一周压测数据出来ResNet-50在T4卡上跑batch1的图像分类P99延迟118ms。而SLA要求是50ms以内——差了不止一倍。常规PyTorch推理已经不够了。必须上TensorRT优化但面对FP16、INT8、Layer Fusion等多个优化方向每个方向的效果差异和引入的精度损失都不一样需要系统性地评测和选择。这不是调几个参数的事。TensorRT的优化需要逐层分析模型的算子和计算图然后组合使用多种优化技术。选错优化策略可能精度丢了一大截延迟却没降多少。二、个性化原理剖析TensorRT 的推理优化分为三个层次第一层——精度优化Precision Calibration将模型权重的精度从FP32降到FP16或INT8。FP16精度损失可忽略不计通常0.1%但理论计算速度翻倍。INT8通过量化校准Calibration来补偿精度损失推理速度可提升至原来的4倍。第二层——计算图优化Layer Fusion将连续的算子融合成一个内核。例如 ConvBNReLU 是CV模型中最常见的模式TensorRT可以将这三层融合为单一的CBR内核。融合后减少了kernel launch次数和显存带宽消耗。第三层——内存优化通过TensorRT的builder配置可以设定workspace最大空间优化显存分配策略。flowchart LR subgraph FP32 原始图 A1[Conv2D] -- B1[BatchNorm] B1 -- C1[ReLU] C1 -- D1[Conv2D] D1 -- E1[BatchNorm] E1 -- F1[Add] end subgraph TensorRT 优化后 A2[CBR Fused Kernel] -- B2[CBR Fused Kernel] B2 -- C2[Eltwise Fused] end A1 -.-|Layer Fusion| A2 D1 -.-|Layer Fusion| B2 F1 -.-|Layer Fusion| C2见证奇迹的时刻在ResNet-50上TensorRT FP16相比原生PyTorch FP32吞吐量提升了2.1倍从340 img/s到720 img/sP99延迟降低了52%从118ms到57ms。加上Layer Fusion后额外再提升18%最终P99延迟降到48ms刚好满足50ms SLA。而INT8优化在此基础上将延迟降到32ms但ImageNet准确率从76.13%降到了75.68%下降了0.45个百分点。精度损失的接受度取决于业务场景。如果是图片分类推荐0.45%的精度损失完全可接受如果是医疗影像分析任何精度下降都需要审慎评估。三、个性化代码实践import tensorrt as trt import pycuda.driver as cuda import numpy as np class TensorRTInferenceOptimizer: TensorRT 推理优化器 def __init__(self, onnx_path: str, precision: str fp16): self.logger trt.Logger(trt.Logger.WARNING) self.builder trt.Builder(self.logger) self.network None self.engine None self.precision precision def build_engine(self, calibration_dataNone): 构建优化后的推理引擎 # 设计原因explicit_batch 模式允许动态 batch size # 是 TensorRT 7 推荐的网络定义方式 network_flags 1 int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH) self.network self.builder.create_network(network_flags) parser trt.OnnxParser(self.network, self.logger) with open(self.onnx_path, rb) as f: parser.parse(f.read()) # 设计原因创建builder配置一次性设置所有优化选项 config self.builder.create_builder_config() # 设计原因workspace 大小决定了 TensorRT 能做多激进的层融合 # 4096MB 允许几乎所有融合模式但需要确保 GPU 显存量够用 config.max_workspace_size 4 30 # 4 GB if self.precision fp16: # 设计原因FP16 不需要校准数据直接开启即可 # 但部分层如Softmax仍会保留FP32以免数值溢出 config.set_flag(trt.BuilderFlag.FP16) elif self.precision int8: # 设计原因INT8 必须提供校准数据集 # TensorRT 用 KL 散度最小化来找到最佳的量化参数 config.set_flag(trt.BuilderFlag.INT8) config.int8_calibrator self._create_calibrator(calibration_data) # 设计原因Layer Fusion 是默认开启的但可以通过 LayerNorm fusion # 等更细粒度的 flag 来进一步优化特定模式 config.set_flag(trt.BuilderFlag.PREFER_PRECISION_CONSTRAINTS) self.engine self.builder.build_engine(self.network, config) return self.engine def benchmark(self, input_data: np.ndarray, num_warmup100, num_iter1000): 推理性能基准测试 context self.engine.create_execution_context() # 设计原因预先分配I/O缓冲区避免每次推理时的显存分配开销 bindings self._allocate_buffers(input_data) # 设计原因预热100次以确保GPU频率稳定和kernel cache命中 for _ in range(num_warmup): context.execute_v2(bindings) start cuda.Event() end cuda.Event() latencies [] for _ in range(num_iter): start.record() context.execute_v2(bindings) end.record() end.synchronize() latencies.append(start.time_till(end)) return { mean_latency_ms: np.mean(latencies), p50_latency_ms: np.percentile(latencies, 50), p99_latency_ms: np.percentile(latencies, 99), throughput_ips: 1000.0 / np.mean(latencies), } def _create_calibrator(self, calibration_data): INT8 校准器 class EntropyCalibrator(trt.IInt8EntropyCalibrator2): def __init__(self, data): super().__init__() self.data data self.current_idx 0 # 设计原因校准数据集500~1000张图即可收敛 # 更多不会显著提升精度但增加校准时间 self.cache_file calibration.cache def get_batch_size(self): return 64 def get_batch(self, names): if self.current_idx len(self.data): return None batch self.data[self.current_idx:self.current_idx64] self.current_idx 64 return [batch.ravel()] def read_calibration_cache(self): try: with open(self.cache_file, rb) as f: return f.read() except FileNotFoundError: return None def write_calibration_cache(self, cache): with open(self.cache_file, wb) as f: f.write(cache) return EntropyCalibrator(calibration_data)四、个性化边界权衡FP16的数值稳定性问题大部分CNN模型在FP16下数值稳定但含有大尺度求和操作的层如Attention中的softmax(QK^T/sqrt(d)))可能出现NaN。TensorRT会自动将风险层回退到FP32但这个决策不完全准确。如果推理结果出现NaN需要用trt.BuilderFlag.STRICT_TYPES禁用自动混合精度改为手动指定哪些层用FP16。INT8量化的校准数据依赖校准数据集的分布直接影响量化精度。如果校准数据与线上真实数据分布不一致如校准用了自然图片线上却多是文档截图精度损失可能远超预期。建议从线上流量中采样2000张作为校准集定期更新校准缓存。Layer Fusion的兼容性陷阱某些自定义算子无法被TensorRT识别和融合会导致融合链路被打断。例如引入了一个自定义的GELU实现而非标准算子TensorRT就无法将前面的Conv和后面的GELU融合。导出ONNX时尽量使用标准算子。模型更新频率与引擎构建时间TensorRT的引擎构建尤其是INT8校准可能需要10~30分钟。如果模型每天更新需要评估这个构建时间是否在发布窗口内。建议将引擎构建作为CI的一环在模型训练完成后自动触发。五、总结TensorRT通过精度降低FP16/INT8、计算图融合Layer Fusion和显存优化三个层次提升推理性能。FP16几乎无损但收益有限INT8收益最大但精度损失需要校准和验证。实际部署建议先用FP16Layer Fusion达到SLA要求不满足时再考虑INT8。校准数据的质量和分布是INT8精度损失控制的关键。