动态 Shape:昇腾 NPU 上的处理思路
摘要: 昇腾NPU依赖静态shape编译优化,但实际业务常面临动态shape问题。针对不同场景:文本长度变化可采用padding或分档位编译;图片尺寸不固定可将resize算子内置;batch_size变化可使用动态batch编译;完全随机shape建议CPU预处理或fallback;编译耗时问题可通过离线预编译解决。核心思路是将动态部分隔离,编译固定部分,运行时动态选择子图,避免NPU处理完全随

前言
昇腾 NPU 的编译优化依赖静态 shape——编译时确定了输入大小,GE 才能做内存规划、算子融合、内核选择。但实际业务里,输入 shape 经常变化:图片大小不固定、文本长度有长有短、batch_size 随负载调整。
怎么处理?
问题一:文本长度变化,每条请求都不一样
输入是文本,tokenized 之后长度在 32 到 512 之间。每条请求都要重新编译吗?
不用。有两种方案:
方案 A:Padding 到固定长度
把所有输入 padding 到最大长度(比如 512)。短的文本用 0 填充,attention_mask 标记哪些位置是填充。模型只编译一次,运行时不用重编译。
代价:padding 浪费计算。长度 64 的文本,padding 到 512,浪费 87% 的计算量。
方案 B:按档位编译
把长度分成几个档位:32、64、128、256、512。运行时根据输入长度,选择最近的档位,用对应的 .om 文件。ATC 编译时指定:
atc --model=model.onnx \
--dynamic_batch_size="1,2,4,8" \
--input_shape_range="input:[32,512]"
生成一个支持多档位的 .om 文件,内部是多个子图,每个子图对应一个档位。运行时自动选择。
代价:.om 文件变大(多个子图打包在一起),编译时间变长(要编译多份)。
推荐:短文本多的场景用方案 B,长文本为主的场景用方案 A。
问题二:图片尺寸不固定,有 224×224、384×384、512×512
目标检测模型,输入图片 resize 到不同尺寸,检测不同大小的目标。
方案:把 resize 也放进模型里
原始流程:CPU resize → 搬到 NPU → 模型推理。改成:原图直接搬到 NPU → NPU resize + 推理。
在模型前面加一个 ops-adv 的 resize 算子,输入是原始图片,输出是 resize 后的图片。这样模型输入固定是原始图片大小(比如 1080p),resize 在 NPU 内部完成,GE 能做优化。
ATC 编译时:
atc --model=model_with_resize.onnx \
--input_shape="image:1,3,1080,1920"
输入固定 1080p,resize 算子在模型内部,shape 变化不影响编译。
问题三:batch_size 随负载变化,有时 1,有时 32
在线服务,白天高峰 batch=32,凌晨低谷 batch=1。
方案:动态 batch 编译
ATC 支持指定 batch_size 的范围:
atc --model=model.onnx \
--dynamic_batch_size="1,2,4,8,16,32"
生成的 .om 文件支持这 6 个 batch_size,运行时根据输入自动选择对应的子图。不需要重启服务。
注意:batch_size 范围不要设太宽。1 到 32 可以,但 1 到 128 就不推荐了——子图太多,.om 文件膨胀,加载时间变长。
问题四:输入 shape 完全随机,没法预测
某些场景,输入 shape 完全随机(比如用户上传的图片,分辨率和宽高比都不确定),没法预先编译所有可能的 shape。
方案:fallback 到 CPU
NPU 不擅长处理完全动态的 shape。对于这种场景,可以:
- 在 CPU 上做 shape 相关的预处理(resize、padding)
- 把处理后的固定 shape 数据送到 NPU 推理
或者:用 Ascend CL 的动态 shape API(aclSetTensorShape),运行时动态设置 shape。但这种方式性能会下降(GE 的优化没法完全生效),只适合 shape 变化不那么频繁的场景。
问题五:编译太慢,shape 一变就要重新编译
大模型(比如 LLaMA),ATC 编译一次要 20 分钟。如果 shape 经常变化,编译时间不可接受。
方案:离线编译 + 热加载
把常用的 shape 组合提前编译好,存成多个 .om 文件。运行时根据输入 shape,选择对应的 .om 文件加载。加载时间 <1 秒,比重编译快得多。
如果遇到未编译的 shape,两种选择:
- 按最近的已编译 shape 运行(可能浪费计算)
- 异步触发编译,临时用 CPU 推理,编译完成后切换到 NPU
动态 shape 的处理没有银弹,核心思路是「把动态的部分隔离出去」——要么在模型外做 padding,要么在模型内加算子。编译时只处理固定部分,运行时动态选择对应的子图。不要试图让 NPU 处理完全随机的 shape,那是 CPU 的主场。
更多推荐




所有评论(0)