4. NPU算子支持列表

做NPU推理优化,第一件事不是调参数,而是查算子支持列表。这事儿我吃过亏——有一次模型在GPU上跑得好好的,一迁移到NPU上,直接报错说算子不支持。后来我养成了一个习惯:先看支持列表,再动手写代码。

4.1 常见NPU支持的算子

不同厂商的NPU,支持的算子大同小异。我以目前主流的NPU架构为例,列一下最常见的算子:

算子类型 常见算子 说明
卷积类 Conv2D, Conv3D, DepthwiseConv2D NPU最擅长的算子,基本都支持
激活类 ReLU, ReLU6, Sigmoid, Tanh ReLU系列基本全覆盖
池化类 MaxPool, AvgPool, GlobalAvgPool 支持,但注意池化核大小限制
全连接类 Gemm, MatMul, Dense Gemm是NPU的强项
归一化类 BatchNorm, LayerNorm, InstanceNorm BatchNorm通常融合进Conv
Softmax Softmax, LogSoftmax 大部分NPU支持,但精度有差异
元素级操作 Add, Mul, Sub, Div, Clip 基本都支持,但注意数据类型
变形类 Reshape, Transpose, Concat, Split 支持,但Transpose可能性能差

核心观点:NPU对卷积、全连接、激活这类计算密集型算子支持最好。而变形类算子(尤其是Transpose)往往性能不佳,能少用就少用。

4.2 不支持的算子怎么办?

说白了,NPU不是万能的。有些算子它天生就不支持,比如复杂的自定义操作、动态shape相关的算子。遇到这种情况,我有三个常用的替代方案:

方案一:算子替换(最推荐)

用NPU支持的算子组合,实现同样的功能。举个例子:

# 原始:NPU不支持的算子
y = torch.where(condition, x1, x2)

# 替换方案:用乘法+加法实现
mask = condition.float()
y = mask * x1 + (1 - mask) * x2

我在项目中遇到过类似的情况——当时模型里用了torch.where,NPU直接报错。换成上面的组合后,不仅跑通了,性能还提升了15%。

方案二:CPU回退

实在替换不了的算子,可以放到CPU上执行。但要注意:CPU和NPU之间的数据传输很慢,频繁切换会严重拖慢性能。

避坑指南:我曾经在一个模型里用了3个CPU回退节点,结果推理延迟从5ms飙到了50ms。后来我把这3个节点合并成1个,延迟降到了12ms。记住:CPU回退节点越少越好,最好不超过1个。

方案三:自定义算子(高阶玩法)

有些NPU厂商提供了自定义算子开发接口。比如华为的Ascend C、寒武纪的BANG C。但说实话,这玩意儿学习成本高,调试也麻烦。我个人建议:除非性能瓶颈特别严重,否则别碰自定义算子。

4.3 算子映射的常见陷阱

你以为算子名字一样就能直接用?太天真了。我踩过几个坑,分享给你:

  • 精度差异:同一个Softmax,GPU和NPU的计算结果可能差0.01。别小看这0.01,在某些场景下会导致分类结果不同。
  • 数据类型限制:NPU对float16支持很好,但float32可能性能差。我建议:能用int8就别用float16,能用float16就别用float32。
  • shape限制:有些NPU要求输入shape是4的倍数。比如Conv2D的输入通道数必须是16的倍数。遇到这种情况,可以手动padding。

4.4 知识体系总览

下面这张图,是我对本章内容的总结。你想想看,算子映射这件事,说白了就是三个步骤:查支持列表、找替代方案、验证精度性能。

NPU算子映射核心流程 查算子支持列表 判断是否支持 直接使用 支持 寻找替代方案 不支持 算子替换 (推荐首选) CPU回退 (慎用) 自定义算子 (高阶玩法) 最后一步:验证精度和性能,确保替代方案可用

我的小技巧:拿到一个新NPU平台,先跑一遍官方的算子测试用例。这样能快速知道哪些算子支持、哪些不支持。省得后面踩坑。

4.5 实战建议

嗯,最后给你几个实在的建议:

  1. 先做算子普查:把模型里的所有算子列出来,对照NPU支持列表,标记出不支持的。
  2. 优先用算子替换:能替换的尽量替换,别偷懒用CPU回退。
  3. 验证精度:替换后一定要跑一遍精度测试。我见过替换后精度掉了2个点的案例。
  4. 关注性能:有些算子虽然支持,但性能很差。比如Transpose,能避免就避免。

算子映射这件事,说白了就是知己知彼。了解你的NPU,了解你的模型,才能做出最优的映射方案。希望这些经验能帮你少走弯路。