共计 2483 个字符,预计需要花费 7 分钟才能阅读完成。
为什么需要 TensorRT?
在实时视频分析、自动驾驶等延迟敏感场景中,原生 PyTorch/TensorFlow 的 C ++ 推理接口常面临三大问题:

- 计算冗余:框架层间内存拷贝和动态调度开销占比超 30%
- 资源占用高:ResNet50 模型在 PyTorch 中需要 1.2GB 显存,远超理论计算需求
- 部署复杂:依赖项庞大(如 libtorch.so 达 1.8GB),难以嵌入边缘设备
技术选型对比
| 方案 | 吞吐量(FPS) | 显存占用 | 部署复杂度 | 动态 Shape 支持 |
|---|---|---|---|---|
| TensorRT 8.4 | 4500 | 680MB | ★★★ | 部分 |
| ONNX Runtime | 2100 | 920MB | ★★ | 完整 |
| TVM | 3800 | 750MB | ★★★★★ | 完整 |
数据基于 T4 GPU 测 ResNet50 模型,batch_size=32
模型转换全流程
1. PyTorch 转 ONNX
python -c "
import torch
model = torch.hub.load('pytorch/vision', 'resnet50', pretrained=True)
dummy_input = torch.randn(1,3,224,224)
torch.onnx.export(model, dummy_input, 'resnet50.onnx',
opset_version=11,
input_names=['input'],
output_names=['output'])
"
2. ONNX 转 TensorRT 引擎
trtexec --onnx=resnet50.onnx \
--saveEngine=resnet50.trt \
--fp16 \
--workspace=2048
核心 API 解析
// 构建阶段
nvinfer1::IBuilder* builder = nvinfer1::createInferBuilder(logger);
nvinfer1::INetworkDefinition* network = builder->createNetworkV2(1U << static_cast<uint32_t>(nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH));
// 配置优化参数
nvinfer1::IBuilderConfig* config = builder->createBuilderConfig();
config->setMemoryPoolLimit(nvinfer1::MemoryPoolType::kWORKSPACE, 1 << 30); // 1GB
if(builder->platformHasFastFp16()) {config->setFlag(nvinfer1::BuilderFlag::kFP16);
}
// 解析 ONNX
auto parser = nvonnxparser::createParser(*network, logger);
parser->parseFromFile(onnxModelPath.c_str(), 2);
// 生成引擎
nvinfer1::ICudaEngine* engine = builder->buildEngineWithConfig(*network, *config);
INT8 量化实战
- 准备校准数据集(500-1000 张典型图片)
- 实现校准器接口:
class Int8EntropyCalibrator : public nvinfer1::IInt8EntropyCalibrator2 { public: int getBatchSize() const override { return 32;} bool getBatch(void* bindings[], const char* names[], int nbBindings) override { // 填充当前 batch 数据到 bindings return !data.empty();} }; - 启用 INT8 模式:
config->setFlag(nvinfer1::BuilderFlag::kINT8); config->setInt8Calibrator(new Int8EntropyCalibrator());
性能优化技巧
层融合策略
通过 builder->getTacticSources() 控制融合策略:
[可选策略]
│
├── CUBLAS (基础矩阵运算)
├── CUDNN (卷积优化)
└── EDGE_MASK_CONVOLUTIONS (边缘计算优化)
显存预分配
void* buffers[2];
cudaMalloc(&buffers[inputIndex], inputSize * sizeof(float));
cudaMalloc(&buffers[outputIndex], outputSize * sizeof(float));
// 推理时复用
context->enqueueV2(buffers, stream, nullptr);
生产环境避坑
- 算子兼容性:
- 遇到不支持算子时,使用
IPluginV2接口实现自定义层 -
或回退到 ONNX Runtime 处理特定子图
-
版本控制矩阵:
| TensorRT | CUDA | cuDNN | 备注 |
|---|---|---|---|
| 8.4.x | 11.6 | 8.4 | 推荐生产环境 |
| 8.2.x | 11.4 | 8.2 | 兼容较旧驱动 |
- 异常处理模板:
try {auto outputs = inferencer->execute(inputs); } catch (const std::exception& e) {logger->log(ILogger::Severity::kERROR, e.what()); // 降级处理或重启引擎 }
思考题
- 当模型存在动态维度(如可变长度序列)时,除了
setOptimizationProfile,还有哪些方法可以保证吞吐量? - INT8 量化导致关键层精度损失超过 3% 时,应该采用哪些混合精度策略?
- 在多卡推理场景下,如何设计负载均衡方案避免 PCIe 带宽成为瓶颈?
实验环境:T4 GPU, TensorRT 8.4.1, CUDA 11.6, Ubuntu 20.04
完整代码见:https://github.com/example/tensorrt-cpp-demo
正文完
