CANN的图优化这两年一直是性能调优圈子里绕不开的话题,尤其是图引擎里的融合策略。我上个月帮同事排查一个ResNet推理性能问题时,发现一个很典型的状况:AI Core利用率看着不低,但整卡吞吐就是上不去。逐个算子拉profiling数据,时间全耗在中间张量落DDR、再读回来的路上,加上大量几十微秒级小kernel的启动开销,算力都被“碎片化”吃掉了。这篇内容就基于这次实战,把CANN图引擎里融合相关的东西拆开聊一聊:融合发生在哪一步、高级融合策略有哪些具体形态、收益模型怎么算、规则怎么落地,以及上线前容易踩的坑。这里的“图优化”是深度学习计算图优化,和概率图模型里的“因子图优化”不是一个概念,但两者都追求“结构重写”带来的全局收益,只是对象完全不同。
1. 一个性能问题的现场:从一次ResNet排查说起
1.1 现象还原:算得很快,但跑得不快
模型是ResNet-50,batch=128的推理任务,单卡跑。第一版结果出来,端到端时延比理论估算值高了将近一倍。我一开始怀疑是某个算子实现有性能缺陷,但逐算子看下来,绝大多数算子的单次执行时间都很短,AI Core的峰值算力利用率也不算差。这种“单个算子都正常,整体却不正常”的现象,通常意味着瓶颈不在算子内部,而在算子之间的衔接上。
msprof拉出来的数据印证了这一点:整张图被拆成了8000多个kernel,单算子平均执行时间只有几十微秒,而kernel launch、调度、同步这些非计算开销占了总时间的三成以上。更夸张的是中间张量读写,很多小算子之间传递的特征图都要经过DDR写入再读回,数据流量巨大,AI Core真正“闲着等数据”的时间远比我想象得多。
1.2 拆解瓶颈:kernel碎片化与中间张量驻留
在NPU上跑模型,和CPU/GPU的道理一样:你不可能让每个算子都单独拿一份完整的数据来算。数据从DDR搬到片上缓存要时间,算完写回DDR也要时间。如果每个算子都是“搬数据-计算-写回”三步走,那么特征图越大,读写的代价就越夸张。我算了一笔账:一个batch=128、通道数256、空间尺寸56x56的中间特征图,单张float32就是约400MB的数据量。这样一个张量从DDR读一遍再写一遍,按约1TB/s的带宽估算,消耗的时间就要0.8ms上下,而很多单算子的计算时间不过几十微秒——到底谁在拖后腿,一目了然。
这就是图优化存在的根本原因:不要每次都把中间结果交给DDR,尽量让数据在片上缓存里直接“接力”。而承接这件事的,正是CANN的图引擎(Graph Engine,GE)。从框架侧拿到计算图之后,GE会在图层面做一系列改写,把合适的算子组合起来,减少启动开销、消除中间张量的DDR往返。这个“组合”的动作,就是融合。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 图引擎的优化流水线:融合在哪个环节发生
2.1 从框架侧到硬件执行:GE到底干什么
要理解融合,得先把CANN的图编译链路看明白。推理场景下,PyTorch/TensorFlow模型一般先通过相应前端转换成成IR表达,再进入CANN侧。GE拿到这张计算图之后,并不会直接把它映射成硬件任务,而是先做几条流水线操作:
- 图结构的基础化简:常量折叠、死节点消除、冗余算子合并;
- 数据格式推导:结合NPU对数据排布的要求,给每个张量推导layout;
- 算子融合:把能融合的计算子图合并成新的、更粗粒度的融合算子;
- 内存分配与任务切分:为中间张量分配buffer,并将融合算子切分成AI Core可执行的ta
