一个简单的 Attention 前向在 PyTorch 里是七八行代码Reshape_Q、Reshape_K、MatMul_QK、Softmax、MatMul_SV、Reshape_O。你写起来是连续的 Python 调用但到了昇腾NPU上每个操作都有一个独立的 Kernel。六个 Kernel 各自启动、各自从 DDR 读输入、各自把结果写回 DDR。一个 Kernel Launch 走aclrtLaunchKernel的延迟在 5-15μs 之间——看起来不大。但这不是全部开销。每个 Kernel 启动伴随一次 DDR 读取输入 Tensor和一次 DDR 写回输出 Tensor。DDR 的读写延迟在 10-20μs。六个 Kernel 就是 6 次 Launch~90μs 5 次中间结果 DDR 写回~100μs。加起来约 190μs 的非计算时间——而六步里真正的矩阵乘法在 Cube Unit 上只花了约 200μs。Operator FusionKernel Fusion做的事情很朴素把这六个 Kernel 合成一个。融合后只 Launch 一次中间结果全留在 L1 Buffer 上流转不写回 DDR。190μs 的非计算开销直接省掉 80% 以上。融合的三个级别不是所有算子都能随便拼在一起。融合有三个级别从最简单到最复杂。Element-Wise 融合是最低风险的。Add 后面接 Mul两个算子的循环迭代空间完全一致。合并后循环体多一行指令循环开销从两次变一次。GE 的融合规则表里有几十对这种模式基本无条件融合。Pattern Fusion需要编译期精确匹配。Conv → BatchNorm → ReLU 这种固定组合ATC 内置了专用的融合模板。融合后的 Kernel 不是先算 Conv 再算 BN 再算 ReLU的简单拼接——它在一次遍历里把 BN 的乘加和 ReLU 的判断全部融进 Conv 的内层循环数据从 L1 只读一次。Graph-Level Fusion是收益最大也最复杂的。一个 FusedAttention 把 Reshape_Q、Transpose_Q、MatMul_QK、Softmax、MatMul_SV、Transpose_O 六个算子合并为一个 Kernel。Operator Fusion 的层级 Element-Wise FusionAdd Mul Div → 循环合并省循环开销 Pattern FusionConv BN ReLU → 模板替换省中间 DDR 读写 Graph-Level FusionReshape MatMul Softmax MatMul Reshape → 整链合并全 L1 流转融合前后的执行链路对比以一个 Attention Block 的六步为例融合前6 个独立 Kernel Kernel 1: Reshape_Q DDR Read: q [B, S, D] → 搬入 L1 DDR Write: q_reshaped [B, H, S, D] → 写回 DDR Kernel 2: Reshape_K DDR Read: k [B, S, D] → 搬入 L1 DDR Write: k_reshaped [B, H, S, D] → 写回 DDR Kernel 3: MatMul_QK DDR Read: q_reshaped k_reshaped → 搬入 L1 计算: Cube Unit 算 Q K^T DDR Write: scores [B, H, S, S] → 写回 DDR Kernel 4: Softmax DDR Read: scores → 搬入 L1 计算: Vector Unit 逐行 Softmax DDR Write: attn [B, H, S, S] → 写回 DDR Kernel 5: MatMul_SV DDR Read: attn v_reshaped → 搬入 L1 计算: Cube Unit 算 Attn V DDR Write: out [B, H, S, D] → 写回 DDR Kernel 6: Reshape_O DDR Read: out → 搬入 L1 DDR Write: final [B, S, D] → 写回 DDR 6 次 Launch、11 次 DDR 读写操作融合后1 个 FusedAttention Kernel FusedAttention: DDR Read: q, k, v → 全部搬入 L1一次批量搬运 L1 Buffer[0]: q_reshaped L1 Buffer[1]: k_reshaped Cube Unit: q_reshaped k_reshaped^T → L1 Buffer[2]: scores Vector Unit: Softmax(L1 Buffer[2]) → L1 Buffer[3]: attn Cube Unit: attn v_reshaped → L1 Buffer[4]: out DDR Write: out → 写回 DDR 1 次 Launch、1 次 DDR 读、1 次 DDR 写中间全在 L1 上流转# 融合前后的代码形态# 融合前——每个步骤都是一个独立的 aclopExecutedefattention_no_fusion(q,k,v):q_raclopExecute(reshape_op,q)# Kernel 1k_raclopExecute(reshape_op,k)# Kernel 2scoresaclopExecute(matmul_op,q_r,k_r)# Kernel 3attnaclopExecute(softmax_op,scores)# Kernel 4outaclopExecute(matmul_op,attn,v_r)# Kernel 5returnaclopExecute(reshape_op,out)# Kernel 6# 融合后——一次调用完成全部deffused_attention(q,k,v):returnaclopExecute(fused_attention_op,q,k,v)# 1 个 KernelGraph Compiler 的融合决策ATC 编译器内部维护了一张融合规则表。编译时遍历 ONNX 图逐对检查相邻算子两算子之间无分支。如果算子 A 的输出被多个算子消费比如同时给了 MatMul 和 Add不能融合——因为 L1 上的数据不能被两个 Kernel 同时读。融合后 Kernel 指令长度不超标。Ascend 910 的 L1 指令 Cache 约 64KB一个过度融合的 Kernel 指令长度可能超过这个值溢出到 DDR 取指——延迟从个位数 cycle 跳到上百 cycle。精度策略一致。如果一个算子被标记为必须 FP32比如 Softmax而前后算子都可以降 FP16则不能融合——除非整个融合 Kernel 统一保持 FP32。满足条件的相邻算子被打上可融合标记ATC 从输入节点开始贪婪合并——能融的都融直到碰到不满足条件的边界。你可以通过--fusion_switch_file手动控制哪些融合模式打开或关闭。实际收益LLaMA-7B 32 层LLaMA-7B 推理SeqLen4096Batch1FP16。单次 Attention 前向指标融合前融合后Kernel Launch6 次1 次DDR 读写次数11 次2 次Launch 搬运时间~190μs~25μs单 Attention 总延迟~400μs~230μs32 层 Transformer × 230μs 的节省 约 5.4ms。总推理延迟从约 25ms 降到约 19.5ms——提升约 22%。更大的收益在显存带宽上。SeqLen128K 时Score 矩阵 [B, H, S, S] 在 FP16 下是 32GB。融合前这块数据经历一次 DDR 写回Softmax 输出加一次 DDR 读入MatMul_SV 输入——64GB 的数据搬运。融合后从生成到消费全在 L1 上64GB 带宽直接省掉。融合的边界什么时候不该融融合不是越大越好。两个典型反模式一是 Element-Wise 算子过度堆积。两个 MatMul 之间夹了 5 个 Element-Wise 算子Add、Mul、GeLU、Dropout、Residual Add全部融合后 Kernel 指令长度膨胀到 L1 指令 Cache 边界以上Kernel 开始从 DDR 取指令——反而比不融合慢 10-15%。二是 L1 空间不够。Ascend 910 的 L1 Buffer 只有 192KB。SeqLen 超过 16K 时Score 矩阵就超出 L1 容量。这时候 FlashAttention 的分块策略每次算一块 Softmax不存完整 Score 矩阵比无条件融合更有效。参考仓库ATC 编译工具GE 图引擎ops-transformer 融合算子CANN 学习中心