静态形状与动态形状:从固定到灵活的演进

做TensorRT部署这些年,我遇到过不少让人头疼的问题。其中最典型的,就是模型跑起来好好的,换个输入尺寸就崩了。嗯,这就要从静态形状和动态形状说起了。

静态形状的定义与限制

静态形状,说白了就是模型在构建时就把输入输出的尺寸定死了。比如你定义一个输入是[1, 3, 224, 224],那它这辈子就只能处理224x224的图片。多一分不行,少一分也不行。

在TensorRT中,静态形状的构建流程是这样的:

// 静态形状示例
nvinfer1::IBuilder* builder = nvinfer1::createInferBuilder(logger);
nvinfer1::INetworkDefinition* network = builder->createNetwork();

// 输入张量,形状固定
nvinfer1::ITensor* input = network->addInput(
    "input", nvinfer1::DataType::kFLOAT, 
    nvinfer1::Dims4{1, 3, 224, 224}  // 这里写死了
);

// 构建引擎
nvinfer1::IBuilderConfig* config = builder->createBuilderConfig();
nvinfer1::IHostMemory* serializedModel = builder->buildSerializedNetwork(*network, *config);

静态形状的限制很明显:

  • 灵活性为零:换一个尺寸就得重新构建引擎
  • 部署成本高:生产环境往往需要处理多种尺寸
  • 资源浪费:为了覆盖所有场景,你可能得维护多个引擎文件

我曾经踩过的坑:有个客户要求模型同时处理480p和1080p的视频流。我一开始图省事,只做了静态形状的1080p版本。结果480p的输入进来,显存占用和计算量完全没降下来,白白浪费了资源。后来我才意识到,静态形状在这种场景下就是给自己挖坑。

动态形状的引入背景

为什么会需要动态形状?你想想看,现实世界的输入哪有那么规整?

  • 用户上传的图片,尺寸五花八门
  • 视频流的分辨率可能动态切换
  • NLP任务中,句子长度千差万别

静态形状在这些场景下就显得力不从心了。TensorRT从5.0版本开始引入了动态形状支持,说白了就是允许你在运行时指定输入的实际尺寸。

动态形状的核心概念有三个:

概念 说明 示例
最小尺寸 模型能处理的最小输入 [1, 3, 128, 128]
最优尺寸 性能最优的输入尺寸 [1, 3, 224, 224]
最大尺寸 模型能处理的最大输入 [1, 3, 512, 512]

关键点:动态形状不是无限制的。你需要在构建引擎时指定一个范围,TensorRT会在这个范围内做优化。超出范围?对不起,跑不了。

动态形状的适用场景

我个人习惯把动态形状的应用场景分成三类:

1. 图像处理类任务

目标检测、图像分割这些任务,输入图片的原始尺寸往往不一致。我记得有个项目是做工业质检的,产品尺寸经常变化,用动态形状就特别合适。

// 动态形状配置示例
nvinfer1::IOptimizationProfile* profile = builder->createOptimizationProfile();
profile->setDimensions("input", nvinfer1::OptProfileSelector::kMIN, 
    nvinfer1::Dims4{1, 3, 128, 128});
profile->setDimensions("input", nvinfer1::OptProfileSelector::kOPT, 
    nvinfer1::Dims4{1, 3, 224, 224});
profile->setDimensions("input", nvinfer1::OptProfileSelector::kMAX, 
    nvinfer1::Dims4{1, 3, 512, 512});

config->addOptimizationProfile(profile);

2. 视频分析场景

视频流的分辨率可能动态变化。比如监控摄像头,白天用高清模式,晚上为了省带宽切到低分辨率。用动态形状,一个引擎就能搞定。

3. NLP和语音任务

文本长度、语音时长都是不确定的。用动态形状处理变长输入,比静态形状优雅得多。

我的建议:如果你确定生产环境的输入尺寸是固定的,比如人脸识别门禁,那就用静态形状。性能更好,也更简单。但如果你不确定,或者场景多变,直接上动态形状。别犹豫,犹豫就会败北。

不过要注意,动态形状不是银弹。它带来的代价是:

  • 构建时间更长:TensorRT需要为范围内的多个尺寸做优化
  • 引擎文件更大:包含了多个尺寸的优化策略
  • 性能可能略低:相比专门为某个尺寸优化的静态引擎

嗯,这就是静态形状和动态形状的基本情况。下一节我们会深入动态形状的具体实现,包括如何配置优化策略、如何处理多profile等实战内容。到时候我会分享一些我在实际项目中踩过的坑,保证让你少走弯路。