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切换的延迟问题。嗯,先消化这些基础概念吧。