在开始TensorRT优化之旅前,我们需要一个可靠的起点——ONNX模型。这个阶段就像把精心制作的菜谱翻译成通用语言,确保不同厨房都能理解。以PyTorch模型为例,导出时最常遇到的坑是动态维度处理。我曾在一个图像分类项目中发现,直接导出带有动态batch维度的模型会导致TensorRT解析失败。
正确的导出姿势应该是这样的:
python复制import torch
model = YourTrainedModel().eval() # 确保模型处于eval模式
dummy_input = torch.randn(1, 3, 224, 224) # 固定输入维度
# 关键导出参数
torch.onnx.export(
model,
dummy_input,
"model.onnx",
input_names=["input"],
output_names=["output"],
dynamic_axes={
'input': {0: 'batch'}, # 仅允许batch维度动态
'output': {0: 'batch'}
},
opset_version=12 # 推荐使用11以上版本
)
导出后务必用ONNX Runtime验证模型有效性:
python复制import onnxruntime as ort
sess = ort.InferenceSession("model.onnx")
outputs = sess.run(None, {"input": dummy_input.numpy()})
print(outputs[0].shape) # 应与原模型输出一致
常见问题排查清单:
onnx.helper自定义缺失算子拿到ONNX模型后,真正的优化魔术开始于TensorRT构建器(IBuilder)的配置。这里的选择就像赛车调校,每个参数都影响着最终性能。经过多个项目实践,我总结出这些黄金配置组合:
对于NVIDIA T4显卡:
python复制builder_config = builder.create_builder_config()
builder_config.max_workspace_size = 1 << 30 # 1GB工作内存
builder_config.set_flag(trt.BuilderFlag.FP16) # 启用FP16
if use_int8:
builder_config.set_flag(trt.BuilderFlag.INT8)
builder_config.int8_calibrator = MyCalibrator() # 自定义校准器
层融合策略是性能提升的关键。通过builder_config可以控制:
set_tactic_sources:选择优化策略来源set_memory_pool_limit:配置各内存池大小set_preview_feature:启用实验性优化特性实测案例:在ResNet50上,合理配置这些参数能使吞吐量提升3倍。但要注意,过度激进的内存限制会导致构建失败,建议从保守值开始逐步调整。
混合精度推理是TensorRT的杀手锏,但需要精细校准。INT8模式下的校准过程尤其关键,这里分享我在人脸识别系统中的实战经验:
自定义校准器需要继承trt.IInt8EntropyCalibrator2:
python复制class ImageCalibrator(trt.IInt8EntropyCalibrator2):
def __init__(self, batch_size=32):
self.cache_file = "calib.cache"
self.batch_size = batch_size
self.current_index = 0
self.images = load_calibration_images() # 500张代表性图片
def get_batch_size(self):
return self.batch_size
def get_batch(self, names):
if self.current_index + self.batch_size > len(self.images):
return None
batch = self.images[self.current_index:self.current_index+self.batch_size]
self.current_index += self.batch_size
return [batch] # 返回GPU指针
校准注意事项:
在目标检测任务中,使用INT8+FP16混合精度可实现相比FP32近8倍的加速,而mAP仅下降0.5%。
优化后的引擎需要正确序列化才能用于生产环境。这里有个血泪教训:我曾因忽略版本兼容性导致线上服务崩溃。安全序列化应该这样操作:
python复制# 构建并序列化引擎
with builder.build_engine(network, builder_config) as engine:
with open("model.engine", "wb") as f:
f.write(engine.serialize())
# 跨平台部署时务必检查版本
print(f"TensorRT版本: {trt.__version__}")
print(f"CUDA版本: {torch.version.cuda}")
引擎验证的完整流程:
python复制def test_engine(engine_path):
with open(engine_path, "rb") as f, trt.Runtime(trt.Logger(trt.Logger.WARNING)) as runtime:
engine = runtime.deserialize_cuda_engine(f.read())
with engine.create_execution_context() as context:
# 分配输入输出缓冲区
buffers = allocate_buffers(engine)
# 模拟真实推理
for test_input in test_dataset:
feed_input(context, buffers, test_input)
context.execute_v2(buffers)
output = get_output(buffers)
# 验证精度损失
assert np.allclose(output, expected_output, rtol=1e-3)
部署时的性能调优技巧:
context.set_optimization_profile_async处理动态形状enqueue_emulated当标准优化无法满足需求时,这些进阶技巧可能会带来惊喜:
自定义插件开发:
当遇到不支持的算子时,可以通过实现IPluginV2DynamicExt扩展TensorRT:
cpp复制class MyPlugin : public nvinfer1::IPluginV2DynamicExt {
// 实现所有虚函数...
const char* getPluginType() const override { return "MY_PLUGIN"; }
int enqueue(const PluginTensorDesc* inputDesc, const PluginTensorDesc* outputDesc,
const void* const* inputs, void* const* outputs, void* workspace,
cudaStream_t stream) override {
// CUDA核函数实现
}
};
子图分割策略:
对于超大模型,可以结合ONNX-TensorRT分割模型:
python复制from polygraphy.backend.trt import CreateConfig, EngineFromNetwork
partial_engine = EngineFromNetwork(
OnnxFromPath("partial_model.onnx"),
config=CreateConfig(fp16=True)
)
时间序列模型优化:
处理LSTM/GRU时特别有效:
python复制builder_config.set_flag(trt.BuilderFlag.STRICT_TYPES)
builder_config.set_flag(trt.BuilderFlag.OBEY_PRECISION_CONSTRAINTS)
这些技巧在自然语言处理任务中,能使长序列推理延迟降低40%。但要注意,自定义插件会增加部署复杂度,需权衡开发成本和性能收益。
优化到最后阶段,精准定位瓶颈至关重要。TensorRT提供的内置分析工具往往不够直观,我推荐使用Nsight Systems进行可视化分析:
bash复制nsys profile -o trace --capture-range=cudaProfilerApi \
--sample=cpu --cuda-memory-usage=true \
python infer.py
典型性能问题解决方案:
在电商推荐系统中,通过分析发现80%时间花费在数据预处理上。将预处理移至GPU后,端到端延迟从15ms降至4ms。这提醒我们:优化不能只关注模型本身,整个流水线都需要考虑。