1. TensorRT部署的核心价值与定位
在工业级AI推理场景中,我们常常面临这样的困境:训练好的PyTorch模型直接部署时,吞吐量达不到生产要求,GPU利用率低得可怜。这时候就需要TensorRT这样的推理优化神器出场了——它能将模型推理速度提升3-10倍,这对需要实时处理的业务(如自动驾驶、工业质检)简直是救命稻草。
我最近刚完成一个OCR项目的TensorRT部署,把PaddleOCR模型从ONNX转换到TensorRT后,单卡QPS从50直接飙到280。这种性能飞跃让我决定系统梳理TensorRT的核心工作流程,分享给正在踩坑的同行们。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. TensorRT优化全流程拆解
2.1 模型格式转换:从训练框架到中间态
TensorRT不能直接处理PyTorch或TensorFlow的模型文件,需要先转为ONNX这个"通用语言"。以PyTorch为例,转换时要注意:
python复制torch.onnx.export(
model,
dummy_input,
"model.onnx",
input_names=["input"],
output_names=["output"],
dynamic_axes={
"input": {0: "batch"},
"output": {0: "batch"}
}, # 关键!支持动态batch
opset_version=11 # 版本过低会导致某些算子不支持
)
踩坑提醒:如果模型包含自定义算子,需要先实现对应的ONNX符号(Symbolic)。最近处理一个包含DCNv2的检测模型时,就花了半天时间折腾这个。
2.2 精度校准:FP16/INT8的魔法
TensorRT最厉害的能力之一是支持低精度推理。但INT8需要校准数据集来量化:
python复制# 创建校准器
calibrator = EntropyCalibrator2(
data_dir="calib_data/",
input_shape=(3, 224, 224)
)
# 构建配置时启用INT8
config.set_flag(trt.BuilderFlag.INT8)
config.int8_calibrator = calibrator
实测ResNet50在T4显卡上:
- FP32: 120ms
- FP16: 45ms
- INT8: 28ms
但要注意:不是所有算子都支持INT8,遇到不支持的层会自动回退到FP16。
2.3 核心构建流程代码详解
完整的引擎构建流程应该是这样的:
python复制logger = trt.Logger(trt.Logger.VERBOSE)
builder = trt.Builder(logger)
network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
# 解析ONNX
parser = trt.OnnxParser(network, logger)
with open("model.onnx", "rb") as f:
parser.parse(f.read())
# 配置优化参数
config = builder.create_builder_config()
config.max_workspace_size = 1 << 30 # 1GB
config.set_flag(trt.BuilderFlag.FP16)
# 动态shape配置
profile = builder.create_optimization_profile()
profile.set_shape(
"input",
min=(1, 3, 224, 224), # 最小batch
opt=(8, 3, 224, 224), # 最优batch
max=(32, 3, 224, 224) # 最大batch
)
config.add_optimization_profile(profile)
# 构建引擎
engine = builder.build_engine(network, config)
with open("model.engine", "wb") as f:
f.write(engine.serialize())
3. 部署时的性能调优技巧
3.1 内存管理最佳实践
TensorRT的显存管理是个技术活,推荐使用以下模式:
cpp复制// 创建执行上下文
auto context = std::unique_ptr<nvinfer1::IExecutionContext>(
engine->createExecutionContext());
// 分配输入输出buffer
void* buffers[2];
cudaMalloc(&buffers[0], inputSize);
cudaMalloc(&buffers[1], outputSize);
// 异步推理
cudaMemcpyAsync(buffers[0], inputData, inputSize, cudaMemcpyHostToDevice, stream);
context->enqueueV2(buffers, stream, nullptr);
cudaMemcpyAsync(outputData, buffers[1], outputSize, cudaMemcpyDeviceToHost, stream);
cudaStreamSynchronize(stream);
血泪教训:一定要用异步流(stream)处理!同步方式会让GPU计算和内存拷贝串行,性能直接腰斩。
3.2 多模型流水线优化
当需要串联多个模型时(比如先检测后分类),可以构建pipeline:
python复制# 创建多个执行上下文
detect_context = detect_engine.create_execution_context()
cls_context = cls_engine.create_execution_context()
# 使用不同的CUDA流
detect_stream = cuda.Stream()
cls_stream = cuda.Stream()
# 流水线执行
with detect_stream:
detect_context.execute_async_v2(detect_buffers, detect_stream)
with cls_stream:
cls_context.execute_async_v2(cls_buffers, cls_stream)
这样检测和分类可以并行执行,整体延迟降低40%以上。
4. 常见问题诊断手册
4.1 典型错误与解决方案
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
| ONNX解析失败 | 算子不支持 | 使用polygraphy工具检查不受支持的算子 |
| INT8精度暴跌 | 校准数据不具代表性 | 使用验证集子集作为校准数据 |
| 动态shape报错 | profile配置错误 | 检查min/opt/max的数值关系 |
4.2 性能分析工具链
- Nsight Systems: 查看GPU利用率瓶颈
bash复制
nsys profile -o trace ./inference_app - trt-profiler: 分析层级别耗时
python复制
context.profiler = trt.Profiler() context.execute_async_v2(...) - CUDA Events: 测量关键段耗时
cpp复制cudaEvent_t start, stop; cudaEventCreate(&start); cudaEventRecord(start, stream); // ...执行代码 cudaEventRecord(stop, stream); cudaEventElapsedTime(&ms, start, stop);
5. 进阶技巧:自定义插件开发
当遇到不支持的算子时,需要开发TensorRT插件。以实现一个简单的ReLU6为例:
cpp复制class ReLU6Plugin : public IPluginV2DynamicExt {
public:
ReLU6Plugin() = default;
// 必须实现的核心方法
int32_t enqueue(const PluginTensorDesc* inputDesc,
const PluginTensorDesc* outputDesc,
const void* const* inputs,
void* const* outputs,
void* workspace,
cudaStream_t stream) noexcept override {
const float* input = static_cast<const float*>(inputs[0]);
float* output = static_cast<float*>(outputs[0]);
relu6KernelLauncher(input, output, stream, inputDesc[0].dims.d[0]);
return 0;
}
// 序列化/反序列化
size_t getSerializationSize() const noexcept override { return 0; }
void serialize(void* buffer) const noexcept override {}
};
开发插件时要注意版本兼容性,最好同时实现IPluginV2和IPluginV2DynamicExt接口。
最后分享一个实用技巧:使用TensorRT的trtexec工具快速测试模型:
bash复制trtexec --onnx=model.onnx \
--saveEngine=model.engine \
--fp16 \
--workspace=2048 \
--verbose
