2、动态Shape核心概念:Optimization Profile、Binding、Dynamic Dimensions、Shape Tensor
好,咱们进入正题。动态Shape这块,说白了就是让模型能处理不同大小的输入。你想想看,要是每次换张图片都得重新编译模型,那还怎么上线?我刚开始接触TensorRT时,就被这几个概念绕晕过——Optimization Profile、Binding、Dynamic Dimensions、Shape Tensor。今天咱们一个一个掰开揉碎了讲。
2.1 Dynamic Dimensions:动态维度的本质
动态维度,就是允许输入张量的某个维度在指定范围内变化。比如一个图像分类模型,输入是[N, 3, 224, 224],其中N就是batch size。静态Shape下N固定为1,动态Shape下N可以是1到32之间的任意整数。
在TensorRT中,定义动态维度需要指定三个值:最小值(min)、最优值(opt)、最大值(max)。这三个值构成了一个范围,TensorRT会在opt值附近做最优优化,但保证在[min, max]区间内都能正确推理。
核心要点:动态维度不是无限自由的,它必须在一个预定义的范围内。这个范围就是Optimization Profile的核心内容。
我在项目中遇到过一个问题:有个同事把min设成1,max设成64,opt设成8。结果实际推理时batch size经常跳到32,性能反而比opt=32时差了不少。嗯,这里要注意——opt值决定了TensorRT的优化方向,尽量选实际部署中最常见的值。
2.2 Optimization Profile:优化配置文件的秘密
Optimization Profile,我习惯叫它「优化配置文件」。它本质上是一个结构体,包含了所有动态维度的[min, opt, max]三元组。一个网络可以有多个Profile,推理时可以在它们之间切换。
为什么需要多个Profile?举个例子:你既要处理小图(224x224),又要处理大图(1024x1024)。如果只用一个Profile,范围太宽会导致优化效果打折扣。这时候可以建两个Profile:
- Profile 1:min=[1,3,224,224], opt=[4,3,224,224], max=[8,3,224,224]
- Profile 2:min=[1,3,1024,1024], opt=[2,3,1024,1024], max=[4,3,1024,1024]
每个Profile都会生成一套专属的优化内核。推理时根据实际输入尺寸,选择最匹配的Profile。我曾经在视频分析项目里用过3个Profile,分别对应360p、720p、1080p,效果比单个宽范围Profile好很多。
个人建议:Profile数量不要太多,2-3个就够了。每个Profile都会增加显存占用和构建时间。我一般按实际业务场景的典型尺寸来划分。
2.3 Binding:输入输出的绑定机制
Binding是TensorRT中一个容易混淆的概念。它指的是输入输出张量在引擎中的索引位置。静态Shape时,Binding索引是固定的;动态Shape时,Binding索引虽然固定,但张量的形状可以变化。
看个代码示例就明白了:
// 创建Builder和Network
IBuilder* builder = createInferBuilder(gLogger);
INetworkDefinition* network = builder->createNetwork();
// 定义动态输入
ITensor* input = network->addInput("input", DataType::kFLOAT, Dims4{-1, 3, -1, -1});
// -1表示动态维度
// 构建引擎时指定Profile
IOptimizationProfile* profile = builder->createOptimizationProfile();
profile->setDimensions(input->getName(), OptProfileSelector::kMIN, Dims4{1, 3, 224, 224});
profile->setDimensions(input->getName(), OptProfileSelector::kOPT, Dims4{4, 3, 224, 224});
profile->setDimensions(input->getName(), OptProfileSelector::kMAX, Dims4{8, 3, 224, 224});
// 推理时设置实际形状
context->setBindingDimensions(0, Dims4{2, 3, 224, 224});
// 0是input的binding索引
这里要注意:setBindingDimensions必须在执行推理前调用,而且传入的形状必须在Profile定义的范围内。我曾经踩过一个坑——忘记调用这个函数,结果TensorRT用了上次推理的形状,导致数据错位。
避坑指南:每次推理前都要重新设置所有动态Binding的维度。不要假设TensorRT会记住上次的形状。我吃过这个亏,排查了整整一天才发现是形状没更新。
2.4 Shape Tensor:形状张量的妙用
Shape Tensor是TensorRT 7.0之后引入的高级特性。它允许你在网络内部传递形状信息,实现动态控制流。说白了,就是让网络自己知道输入有多大,然后根据尺寸做不同的处理。
举个例子:一个目标检测网络,需要根据输入图片的宽高比决定anchor的生成方式。用Shape Tensor可以这样实现:
// 获取输入的形状信息
ITensor* input = ...; // 假设shape是[N, 3, H, W]
IShapeLayer* shapeLayer = network->addShape(input);
// shapeLayer的输出是[N, 3, H, W]这个形状张量
// 提取H和W
ISliceLayer* hSlice = network->addSlice(shapeLayer->getOutput(0),
Dims3{0, 2, 0}, Dims3{1, 1, 1}, Dims3{1, 1, 1});
// 提取第2个维度(H)
// 根据H和W动态生成anchor
// ... 这里可以用Gather、Concatenation等操作组合形状信息
Shape Tensor的典型应用场景包括:
- 动态Reshape:根据输入尺寸调整中间张量的形状
- 条件计算:根据形状信息选择不同的计算分支
- 循环控制:实现动态长度的循环操作
我记得在做人脸检测模型时,需要根据输入图片的人脸数量动态调整后处理逻辑。用Shape Tensor把检测框数量作为形状信息传递下去,整个流程就顺畅了。不过要注意,Shape Tensor操作会增加一些计算开销,别滥用。
关键区别:普通张量存的是数据,Shape Tensor存的是形状信息。前者参与计算,后者控制计算流程。两者在TensorRT中的处理方式完全不同。
2.5 四个概念的关系总结
咱们理一下这四个概念的关系:
| 概念 | 作用 | 使用时机 |
|---|---|---|
| Dynamic Dimensions | 定义哪些维度可变 | 网络定义阶段 |
| Optimization Profile | 指定可变范围[min, opt, max] | 引擎构建阶段 |
| Binding | 管理输入输出的索引和形状 | 推理执行阶段 |
| Shape Tensor | 在网络内部传递形状信息 | 网络定义阶段(可选) |
简单来说:先用Dynamic Dimensions标记可变维度,再用Optimization Profile限定范围,推理时通过Binding设置实际形状,如果需要动态控制流就引入Shape Tensor。这四个概念配合好了,动态Shape部署就不是难事。
下一章咱们会深入讲Optimization Profile的配置细节和性能调优技巧。到时候我会分享一些实际项目中的Profile调优经验,包括怎么选opt值、怎么处理多Profile切换的延迟问题。嗯,先消化这些基础概念吧。