从慢得离谱到性能翻倍:昇腾910B迁移小模型MobileNet避坑与调优实录
最近接到一个任务:把原本跑在英伟达GPU上的业务迁移到国产化昇腾(Ascend)平台。模型不大,是个魔改版的MobileNetV2,对时延非常敏感。原本在GPU上单次推理仅需25ms左右,迁移后直接飙到50ms+。经过深度的Profiling分析,我发现问题的核心不在算力,而在“调度”。通过针对性的参数调优,最终把推理时间压到18ms左右,比原来还快了7ms左右。这篇文章不讲虚的,直接复盘从环境搭建、踩坑、分析到最终优化的完整过程,代码全部基于CANN 8.0实测可用。
一、环境准备
1.1 为啥会变慢
在开始之前,有必要说下背景。我们的业务逻辑包含复杂的预处理和后处理,模型本身计算量其实不大(MobileNet级别)。
昇腾910系列是为大规模训练和高密度计算设计的(类似A100),它的强项是高并发、大吞吐。而跑小batch、小模型时,CPU下发任务的开销很容易超过GPU/NPU实际计算的时间。这就好比用一辆重型卡车去送一份外卖,启动和换挡的时间比路上跑的时间还长。
1.2 启动Notebook
点击上方的 “Notebook 工作区”,进入你的云端开发环境管理页面,首次使用会提示激活,直接确认开通即可:

1.3 选择配置
在启动页面选择以下配置:
- 硬件:昇腾910B AI加速芯片(32核VCPU + 64GB内存)
- 镜像:
euler2.9-py38-torch2.1.0-cann8.0-openmind0.6-notebook - 存储:50GB
这个镜像预装了Python 3.8、PyTorch 2.1.0(昇腾适配版)、CANN 8.0驱动,可以直接用。

1.4 安装NPU库
进入JupyterLab后,新建一个ipynb文件,执行:
暂时无法在飞书文档外展示此内容

1.5 验证环境
安装完后测试NPU是否可用(注意必须先import torch_npu):
暂时无法在飞书文档外展示此内容
正常输出应该显示NPU可用,输出结果如下:

二、问题复现
2.1 准备模型
用一个轻量级的MobileNetV2作为测试模型,这个模型参数量适中,能很好地暴露小模型在昇腾上的性能问题:
暂时无法在飞书文档外展示此内容
部署结果如图所示,这就显示我们的模型已经加载成功:

2.2 性能测试
分别测试推理和数据传输的耗时:
暂时无法在飞书文档外展示此内容
在我的测试中,输出结果如下:

我们可以看到结果是
推理耗时: 45.23ms,传输耗时: 8.67ms,总耗时: 53.90ms,看起来推理占大头,但这个数字其实有问题。
2.3 错误判断
一开始觉得推理慢,试了几种办法都没用:
- 调整batch size
- 换tensor格式
- 异步操作
后来查阅文档才醒悟:昇腾NPU的执行是异步的。 Python代码跑完了,指令只是下发到了任务队列,NPU可能还在干活。
三、Profiling分析
3.1 采集数据
既然不是带宽问题,那时间到底去哪了?我祭出了Ascend PyTorch Profiler。这是解决性能问题的杀手锏,绝对不要靠猜。
暂时无法在飞书文档外展示此内容
运行代码结果如下:

这段代码会生成一个json文件,记录了每个算子的详细执行情况:

这里要注意,必须加同步如果不加torch_npu.npu.synchronize(),导出的数据会不完整。因为CPU和NPU异步执行,prof.stop()的时候NPU可能还没跑完。
3.2 分析结果
把生成的json文件下载到本地,用MindStudio打开,其时间线(Timeline)界面支持缩小、放大和左右移动等功能,具体操作如下所示:
- 单击时间线(Timeline)界面树状图或者图形化窗格任意位置,可以使用键盘中的W(放大)和S(缩小)键进行操作,支持放大的最大精度为1ns。
- 单击时间线(Timeline)界面树状图或者图形化窗格任意位置,使用键盘中的A(左移)、D(右移)键,或者方向键左键(左移)、右键(右移)进行左右移动,也可使用方向键上键(上移)、下键(下移)进行上下移动。
- 将鼠标放置在时间线(Timeline)界面树状图或者图形化窗格任意位置,可以使用键盘中的Ctrl键加鼠标滚轮实现缩放操作。
- 在图形化窗格中,使用键盘中的Ctrl键加鼠标左键可以实现左右拖拽泳道图表。

在Timeline视图里能看到:
.to('cpu')算子实际只花了5-8ms- 真正的时间消耗在卷积、BN等推理算子上
- 算子之间有明显的调度间隙
之前以为的"传输慢"其实是假象,真正的问题是推理本身的效率不够。
四、性能优化
4.1 优化思路
根据Timeline分析,我们要做的就是减少CPU干预,于是我做了两个部分的优化:
开启任务队列加速:这是CANN 8.0的一个重要特性。开启后,可以减少CPU和NPU之间的交互开销,把一堆任务打包扔进队列。
- 操作:
os.environ['TASK_QUEUE_ENABLE'] = '2'
关闭JIT动态编译:默认情况下,PyTorch可能会尝试动态编译算子。对于这种标准模型,直接用昇腾内置的二进制算子库(Binary)速度最快,没有编译开销。
- 操作:
torch_npu.npu.set_compile_mode(jit_compile=False)
4.2 优化代码
完整的优化版本:
暂时无法在飞书文档外展示此内容
4.3 参数说明
TASK_QUEUE_ENABLE=2 这个环境变量让NPU的任务队列管理更激进,减少CPU-NPU之间的同步等待。在小batch场景下提升明显。
jit_compile=False 关闭JIT编译,强制使用预编译算子库。避免运行时编译带来的开销。
allow_internal_format=False 禁止算子内部格式转换(比如自动转NZ格式),减少不必要的layout变换。
版本要求
- 驱动固件:>=23.0.3
- CANN工具包:>=8.0.RC1
老版本设置这些参数无效,必须先升级。
4.4 结果展示
运行优化后的代码,输出结果:

优化后性能中平均推理时间: 18.32ms,吞吐量: 54.59 samples/sec,这对比优化前的53.90ms,性能提升了约65%。同时为了更好的对比,在同一台910B机器上,我运行了优化前后的对比脚本,结果非常惊人。
五、完整测试
5.1 对比脚本
写一个完整的对比测试,包含优化前后的性能数据:
暂时无法在飞书文档外展示此内容
5.2 结果分析
运行完整测试后,输出结果如下:

暂时无法在飞书文档外展示此内容
优化效果非常明显,推理时间从53ms降到18ms,提升了66%,这个提升主要归功于TASK_QUEUE和关闭JIT。Timeline显示,优化后算子之间的Gap大幅缩短,流水线变得紧凑。
六、经验总结
一开始觉得是.to('cpu')慢,结果完全不是。性能问题必须用工具定位,Python层面的计时在异步场景下不可靠。尤其是在涉及NPU/GPU异步计算时,不加synchronize()测出来的时间都是假的。对于ResNet18、MobileNet这种小计算量模型,调度开销是杀手,开启任务队列优化是必须的。
MindStudio的Timeline视图非常直观,能精确到每个算子的微秒级耗时。这个工具一定要学会用。同时昇腾NPU首次推理会慢很多。
这次优化让我对昇腾平台有了新认识。工具链确实没CUDA那么成熟,但整体在快速进步。CANN 8.0之后,很多之前需要手动hack的地方都有了官方方案。性能调优的核心还是找准瓶颈,用对方法,注明:昇腾PAE案例库对本文写作亦有帮助。
更多推荐


所有评论(0)