TensorRT作为NVIDIA推出的高性能深度学习推理引擎,在工业部署领域有着不可替代的地位。最近在部署一个图像分类模型到边缘设备时,我深刻体会到掌握TensorRT C++ API的重要性——相比Python接口,C++版本不仅能带来20-30%的性能提升,更能实现更精细的内存控制和线程管理。本文将基于实际项目经验,详细解析如何从零开始构建一个完整的TensorRT C++推理流水线。
在Ubuntu 20.04 LTS系统上,需要安装以下核心组件:
安装完成后需验证环境变量配置:
bash复制export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/TensorRT-8.5.1.7/lib
注意:不同版本的TensorRT对CUDA和cuDNN有严格依赖,版本不匹配会导致运行时错误。建议使用NVIDIA官方提供的容器镜像作为开发环境。
推荐使用CMake构建项目,典型CMakeLists.txt配置如下:
cmake复制cmake_minimum_required(VERSION 3.12)
project(tensorrt_inference)
set(CMAKE_CXX_STANDARD 17)
find_package(TensorRT REQUIRED)
find_package(CUDA REQUIRED)
include_directories(
${TensorRT_INCLUDE_DIRS}
${CUDA_INCLUDE_DIRS}
)
add_executable(inference_demo main.cpp)
target_link_libraries(inference_demo
${TensorRT_LIBRARIES}
${CUDA_LIBRARIES}
nvinfer
nvonnxparser
)
以PyTorch模型为例,导出时需特别注意动态轴设置:
python复制dummy_input = torch.randn(1, 3, 224, 224, device='cuda')
torch.onnx.export(
model,
dummy_input,
"resnet50.onnx",
input_names=["input"],
output_names=["output"],
dynamic_axes={
"input": {0: "batch_size"},
"output": {0: "batch_size"}
}
)
创建优化配置文件时,关键参数包括:
cpp复制auto builder = nvinfer1::createInferBuilder(logger);
auto network = builder->createNetworkV2(1U << static_cast<uint32_t>(NetworkDefinitionCreationFlag::kEXPLICIT_BATCH));
auto parser = nvonnxparser::createParser(*network, logger);
builder->setMaxBatchSize(16);
auto config = builder->createBuilderConfig();
config->setMemoryPoolLimit(MemoryPoolType::kWORKSPACE, 1 << 30); // 1GB工作内存
config->setFlag(BuilderFlag::kFP16); // 启用FP16加速
实测表明:对于ResNet50模型,FP16模式在T4显卡上可实现1.8倍加速,而INT8量化(需校准)能带来3倍性能提升,但会损失约0.5%的准确率。
序列化引擎到文件:
cpp复制auto serializedEngine = engine->serialize();
std::ofstream engineFile("model.engine", std::ios::binary);
engineFile.write(static_cast<const char*>(serializedEngine->data()), serializedEngine->size());
运行时加载引擎:
cpp复制std::ifstream engineFile("model.engine", std::ios::binary);
engineFile.seekg(0, std::ios::end);
size_t engineSize = engineFile.tellg();
engineFile.seekg(0, std::ios::beg);
std::vector<char> engineData(engineSize);
engineFile.read(engineData.data(), engineSize);
auto runtime = nvinfer1::createInferRuntime(logger);
auto engine = runtime->deserializeCudaEngine(engineData.data(), engineSize);
典型CUDA流处理流程:
cpp复制auto context = engine->createExecutionContext();
cudaStream_t stream;
cudaStreamCreate(&stream);
void* bindings[2] = {inputBuffer, outputBuffer};
context->enqueueV2(bindings, stream, nullptr);
cudaMemcpyAsync(hostOutput, outputBuffer, outputSize, cudaMemcpyDeviceToHost, stream);
cudaStreamSynchronize(stream);
建议使用智能指针管理TensorRT对象:
cpp复制struct Destroy {
template <typename T>
void operator()(T* obj) const {
if (obj) obj->destroy();
}
};
using UniquePtr = std::unique_ptr<void, Destroy>;
UniquePtr<runtime> runtime(createInferRuntime(logger));
实现线程安全的推理上下文池:
cpp复制class ContextPool {
public:
ContextPool(nvinfer1::ICudaEngine* engine, size_t poolSize) {
for(size_t i=0; i<poolSize; ++i) {
contexts_.emplace_back(engine->createExecutionContext());
}
}
nvinfer1::IExecutionContext* get() {
std::lock_guard<std::mutex> lock(mutex_);
auto* ctx = contexts_.back();
contexts_.pop_back();
return ctx;
}
private:
std::vector<nvinfer1::IExecutionContext*> contexts_;
std::mutex mutex_;
};
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
| 段错误(segfault) | 未初始化logger | 实现ILogger接口并传入各创建函数 |
| 推理结果异常 | 输入数据未归一化 | 确保预处理与训练时一致 |
| 性能不达预期 | 未启用优化标志 | 检查FP16/INT8是否启用 |
启用详细日志输出:
cpp复制class Logger : public nvinfer1::ILogger {
void log(Severity severity, const char* msg) override {
if (severity <= Severity::kWARNING) {
std::cout << "[TRT] " << msg << std::endl;
}
}
} logger;
使用Nsight Systems进行性能分析:
bash复制nsys profile -o trace ./inference_demo
配置动态形状优化策略:
cpp复制auto profile = builder->createOptimizationProfile();
profile->setDimensions("input", OptProfileSelector::kMIN, Dims4(1,3,224,224));
profile->setDimensions("input", OptProfileSelector::kOPT, Dims4(8,3,224,224));
profile->setDimensions("input", OptProfileSelector::kMAX, Dims4(16,3,224,224));
config->addOptimizationProfile(profile);
实现IPluginV2接口示例:
cpp复制class MyPlugin : public nvinfer1::IPluginV2 {
// 必须实现的所有虚函数...
const char* getPluginType() const override { return "MY_PLUGIN"; }
int enqueue(int batchSize, const void* const* inputs,
void* const* outputs, void* workspace,
cudaStream_t stream) override {
// 自定义CUDA核函数调用
}
};
在实际部署中,我发现合理设置工作空间大小对性能影响显著——过大浪费内存,过小导致频繁重计算。经过多次测试,对于典型CNN模型,建议将工作空间设置为模型大小的2-3倍。另外,使用Triton Inference Server封装TensorRT引擎可以大幅简化生产部署流程,这是后续值得尝试的方向。