4、TensorRT基础入门:推理流程与核心API
好,咱们进入TensorRT的世界。说实话,很多做模型部署的朋友,一开始都被TensorRT那套Builder、Engine、Runtime的概念搞晕过。我当年也一样,看了半天文档,感觉每个单词都认识,连起来就不知道它们在干嘛。
这一章,我就带你把这些概念彻底捋清楚。你跟着我走一遍,保证能明白TensorRT到底是怎么把模型“吃”进去,又怎么“吐”出推理结果的。
4.1 TensorRT推理流程概览
先看整体流程。TensorRT的推理,说白了就两大阶段:构建(Build)和推理(Inference)。
- 构建阶段:你把训练好的模型(比如ONNX格式)喂给TensorRT,它帮你做优化、做量化、生成一个高度优化的推理引擎。这个过程比较慢,但只做一次。
- 推理阶段:你加载那个优化好的引擎,用它来跑实际的推理任务。这个过程非常快,而且可以反复用。
我画了张图,你一看就明白。
你看,左边是构建,右边是推理。中间那个箭头,就是序列化和反序列化的过程。我个人习惯把构建好的引擎保存成一个文件,下次直接用,省得每次都重新优化一遍。
4.2 核心API:Builder / Config / Engine / Runtime
这四个API,是TensorRT的灵魂。我一个个给你讲清楚。
| API | 职责 | 一句话理解 |
|---|---|---|
| Builder | 创建引擎的“总指挥” | 负责整个构建流程的调度 |
| Config | 设置构建参数 | 告诉Builder你要怎么优化 |
| Engine | 优化后的推理“发动机” | 包含所有优化后的网络和权重 |
| Runtime | 加载和执行引擎 | 在推理阶段使用Engine |
举个例子你就懂了。Builder就像是一个建筑公司的项目经理,Config就是设计图纸,Engine是盖好的大楼,Runtime就是物业公司,负责让大楼正常运转。
4.3 ONNX解析器构建Engine
这是最常用的方式。你把模型导出成ONNX格式,然后用TensorRT的ONNX解析器(ONNX Parser)来构建Engine。
代码其实不复杂,核心就几步:
// 1. 创建Builder
nvinfer1::IBuilder* builder = nvinfer1::createInferBuilder(logger);
// 2. 创建Config
nvinfer1::IBuilderConfig* config = builder->createBuilderConfig();
// 3. 创建ONNX解析器
nvinfer1::INetworkDefinition* network = builder->createNetworkV2(flags);
nvonnxparser::IParser* parser = nvonnxparser::createParser(*network, logger);
// 4. 解析ONNX文件
parser->parseFromFile("model.onnx",
nvinfer1::ILogger::Severity::kINFO);
// 5. 构建Engine
nvinfer1::ICudaEngine* engine = builder->buildEngineWithConfig(*network, *config);
嗯,这里要注意一点:buildEngineWithConfig这个调用,在TensorRT 8.x之后是推荐方式。老版本用的是buildCudaEngine,但那个已经过时了。我刚开始迁移时还踩过这个坑。
4.4 Engine序列化与反序列化
构建Engine很耗时,尤其是做INT8量化时,可能需要跑几百张图片做校准。所以,我们通常会把Engine保存成文件,下次直接加载。
序列化(保存Engine):
// 获取序列化后的Engine数据
nvinfer1::IHostMemory* serializedModel = engine->serialize();
// 写入文件
std::ofstream file("model.engine", std::ios::binary);
file.write(reinterpret_cast<const char*>(serializedModel->data()),
serializedModel->size());
file.close();
反序列化(加载Engine):
// 读取文件
std::ifstream file("model.engine", std::ios::binary);
file.seekg(0, std::ios::end);
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<char> buffer(size);
file.read(buffer.data(), size);
file.close();
// 创建Runtime并反序列化
nvinfer1::IRuntime* runtime = nvinfer1::createInferRuntime(logger);
nvinfer1::ICudaEngine* engine = runtime->deserializeCudaEngine(buffer.data(), size);
你看,反序列化只需要Runtime,不需要Builder和Config。这就是为什么部署机器上可以只装TensorRT Runtime,不装完整的TensorRT库。
4.5 trtexec命令行工具使用
trtexec是TensorRT自带的命令行工具,非常实用。我个人觉得,它是调试和验证模型的首选工具,没有之一。
基本用法:
# 从ONNX构建Engine
trtexec --onnx=model.onnx --saveEngine=model.engine
# 测试推理性能
trtexec --loadEngine=model.engine --shapes=input:1x3x640x640
# 查看网络层信息
trtexec --onnx=model.onnx --verbose --dumpLayerInfo
几个常用参数:
| 参数 | 作用 | 我的使用场景 |
|---|---|---|
--onnx |
指定ONNX模型路径 | 构建Engine时必用 |
--saveEngine |
保存构建好的Engine | 构建完成后保存 |
--loadEngine |
加载已有的Engine | 测试性能时用 |
--shapes |
指定输入张量形状 | 动态batch时必用 |
--best |
启用所有优化 | 追求极致性能时用 |
--verbose |
打印详细信息 | 调试时必开 |
举个例子,我想看看YOLOv8在FP16下的性能:
trtexec --onnx=yolov8n.onnx --fp16 --saveEngine=yolov8n_fp16.engine
trtexec --loadEngine=yolov8n_fp16.engine --shapes=images:1x3x640x640
输出里会显示latency、throughput等关键指标。我一般重点关注GPU Compute Time和End-to-End Host Latency。
--verbose参数可以看到每个层的耗时,方便定位性能瓶颈。我曾经用这个参数发现某个卷积层用了float32而不是float16,一查才知道是算子不支持FP16。
4.6 本章小结
这一章,我们走通了TensorRT的完整推理流程。从Builder构建,到Config配置,再到Engine序列化,最后用Runtime加载推理。trtexec工具可以帮你快速验证和调试。