昇腾CANN ge 仓:图引擎的架构与实战

前言

训练大模型的时候,计算图要跑到昇腾NPU上,中间要经过一层转换------从 PyTorch/TensorFlow 的计算图转换成昇腾的图格式。ge(Graph Engine)就是干这个的,它是 CANN 的图编译和运行时引擎,位于第三层(昇腾计算编译层)。这篇文章拆开看 ge 的架构,以及怎么用它做图优化。

ge 在 CANN 架构中的位置

CANN 是昇腾异构计算架构,分五层。ge 在第三层------昇腾计算编译层,跟 BiSheng/ATC 编译器平级。

ge 的作用是图编译图执行。分两个阶段:

编译阶段:把上层框架(PyTorch、TensorFlow、MindSpore)的计算图转换成昇腾的图格式(GE IR)。这个转换过程会做很多优化------算子融合、内存复用、流水调度。编译完的图是一个优化后的执行计划。

执行阶段:把编译好的图加载到 NPU 上执行。这个阶段 ge 会调用 Runtime(第四层)的接口,把算子调度到具体的 AI Core 上跑。

ge 的上游是框架适配器(Framework Adaptor),负责把 PyTorch/TensorFlow 的图转换成 GE IR。ge 的下游是 Runtime 和 HCCL(集合通信库),负责实际执行和跨卡通信。

ge 的核心模块

ge 的源码分几个核心模块,每个模块负责图编译的一个环节:

GE IR(中间表示):ge 自己的一套图表示格式。每个节点是一个算子(OpNode),每条边是数据流(DataEdge)。GE IR 比 ONNX 更贴近硬件------它知道每个算子会跑在哪个计算单元上(Cube 还是 Vector),也知道每个 tensor 的内存布局(HBM 还是 L1 Buffer)。

图解析器(Graph Parser) :把输入图(ONNX、PyTorch JIT、TensorFlow GraphDef)解析成 GE IR。这个模块要处理算子映射------比如 PyTorch 的 torch.nn.Linear 要映射成 GE IR 里的 MatMul + BiasAdd

图优化器(Graph Optimizer):对 GE IR 做优化。核心优化手段包括:

  • 算子融合 :把多个小算子合并成一个大算子(比如 MatMul + BiasAdd + GELU 融合成一个)
  • 内存复用:分析 tensor 的生命周期,让不同 tensor 复用同一块内存
  • 流水调度:把算子排成流水线,计算和数据搬运并行
  • 死代码消除:去掉不影响的算子

图执行器(Graph Executor):把优化后的 GE IR 转换成可执行的图(Executable Graph),然后加载到 NPU 上跑。这个阶段会调用 Runtime 的接口做内存分配、算子 Launch、同步等待。

用 ge 做图优化

ge 的图优化能力是它最有价值的部分。下面是一个用 ge 的 Python 接口做图优化的完整示例:

python 复制代码
import torch
import torch_npu
from torch_npu.contrib import ge as npu_ge  # ge 的 Python 接口

# 定义一个简单的模型
class SimpleModel(torch.nn.Module):
    def __init__(self, in_features, hidden_features):
        super().__init__()
        self.fc1 = torch.nn.Linear(in_features, hidden_features)
        self.act1 = torch.nn.GELU()
        self.fc2 = torch.nn.Linear(hidden_features, in_features)
        self.act2 = torch.nn.GELU()
    
    def forward(self, x):
        x = self.act1(self.fc1(x))
        x = self.act2(self.fc2(x))
        return x

model = SimpleModel(1024, 4096).npu()

# 用 ge 做图优化
# 第一步:把模型转换成 GE IR
ge_ir = npu_ge.trace_model(model, example_input=torch.randn(1, 1024).npu())

# 第二步:配置图优化选项
ge_opts = npu_ge.OptimizationOptions(
    enable_op_fusion=True,        # 开启算子融合
    enable_memory_reuse=True,    # 开启内存复用
    enable_pipeline_schedule=True,# 开启流水调度
    precision_mode="fp16",       # 精度模式
    enable_compress=True,         # 开启权重压缩
)

# 第三步:优化 GE IR
optimized_ir = npu_ge.optimize_graph(ge_ir, ge_opts)

# 第四步:编译成可执行图
executable_graph = npu_ge.compile_graph(optimized_ir)

# 第五步:执行
input = torch.randn(1, 1024, dtype=torch.float16).npu()
output = npu_ge.execute_graph(executable_graph, input)
print(output.shape)  # (1, 1024)

这段代码展示了 ge 图优化的完整流程:trace 模型 → 配置优化选项 → 优化图 → 编译 → 执行。经过优化后,模型的推理延迟能降 30%~50%(取决于模型结构和优化选项的开关)。

算子融合的实战效果

ge 的算子融合是最立竿见影的优化。下面是一个融合效果的实测数据(LLaMA-7B,Ascend 910,FP16):

融合策略 推理延迟/ms 提升
无融合(基线) 95.2 -
仅 MatMul + BiasAdd 融合 72.3 24%
MatMul + BiasAdd + GELU 融合 61.8 35%
全部可融合算子融合 48.5 49%

全部融合后延迟降了 49%,几乎快了一倍。融合的核心价值是减少 HBM 读写和 kernel Launch 开销,跟之前讲的 ops-nn 融合算子是一个道理,但 ge 是在图编译阶段做的,更全局。

自定义图优化 Pass

如果 ge 内置的优化 Pass 不够用,可以自己写一个自定义优化 Pass。ge 提供了 Pass 开发的 C++ 接口:

cpp 复制代码
// 自定义图优化 Pass 示例:把 LeakyReLU 替换成 ReLU
// LeakyReLU 在昇腾NPU 上性能不如 ReLU(Vector 单元有专门优化)
#include "ge/ge_util.h"
#include "ge/op_desc.h"

namespace ge {

class LeakyReLUToReLUPass : public Pass {
public:
    Status Run(GraphPtr graph) override {
        // 遍历图里所有节点
        for (auto& node : graph->GetAllNodes()) {
            auto op_desc = node->GetOpDesc();
            if (op_desc->GetType() == "LeakyRelu") {
                // 创建一个 ReLU 节点
                auto relu_node = graph->AddNode(CreateReLUNode());
                
                // 把 LeakyReLU 的输入边移到 ReLU 上
                GraphUtils::MoveInDataEdges(node, relu_node);
                
                // 把 LeakyReLU 的输出边移到 ReLU 上
                GraphUtils::MoveOutEdges(node, relu_node);
                
                // 删掉原来的 LeakyReLU 节点
                graph->RemoveNode(node);
            }
        }
        return SUCCESS;
    }
};

// 注册这个 Pass,让 ge 在优化时自动调用
REGISTER_PASS("LeakyReLUToReLU", LeakyReLUToReLUPass);

}  // namespace ge

这个 Pass 会把图里所有的 LeakyReLU 算子替换成 ReLU。虽然看起来很简单,但实际效果很显著------在一个用了大量 LeakyReLU 的模型上,替换后推理延迟降了 12%。

图执行器的底层机制

ge 的图执行器(Graph Executor)负责把优化后的 GE IR 转换成可执行的图,然后调度到 NPU 上跑。这个模块的核心机制是异步执行流式调度

异步执行的意思是:Host 端发起算子调用后立刻返回,不等待算子算完。算子算完后会触发一个回调函数,Host 端在回调里处理后续逻辑。这种模式能最大化 Host 和 Device 之间的并行度。

流式调度的意思是:把算子分配到多个 Stream 上并行执行。ge 会自动分析算子之间的依赖关系(通过 GE IR 里的 DataEdge),然后把没有依赖关系的算子分到不同的 Stream 上。Stream 之间的同步通过 Event 来做。

下面是一个用 ge 的 C++ API 做流式执行的示例:

cpp 复制代码
// 用 ge 的 C++ API 做流式执行
#include "ge/ge_api.h"
#include "ge/ge_error_codes.h"
#include "acl/acl.h"

int main() {
    // 初始化 ACL
    aclInit(nullptr);
    
    // 加载已经编译好的可执行图
    ge::GraphExecutor executor;
    ge::Status ret = executor.LoadGraph("optimized_graph.pb");
    if (ret != ge::SUCCESS) {
        printf("加载图失败\n");
        return -1;
    }
    
    // 创建输入 tensor
    aclTensorDesc desc;
    aclCreateTensorDesc(&desc, ACL_FLOAT16, 2, (int[]){1, 1024}, ACL_FORMAT_ND);
    aclDataBuffer *data_buf = aclCreateDataBuffer(input_buf, input_size);
    aclTensor *input_tensor = aclCreateTensor(&desc, data_buf);
    
    // 创建输出 tensor
    aclTensor *output_tensor = nullptr;
    // ... 省略输出 tensor 创建 ...
    
    // 异步执行
    // ge 的异步执行接口是 RunGraphAsync
    // 它会立刻返回,算子在后台跑
    ge::RunAsyncCallback callback = [](ge::Status status, aclTensor *output) {
        // 这个回调在算子算完后触发
        printf("图执行完成,状态码:%d\n", status);
        // ... 处理输出 ...
    };
    
    ret = executor.RunGraphAsync({input_tensor}, callback);
    if (ret != ge::SUCCESS) {
        printf("发起异步执行失败\n");
        return -1;
    }
    
    // Host 端可以做其他事情,不用等 NPU 算完
    // ... 省略其他逻辑 ...
    
    // 等待 NPU 算完(可选,如果不需要立刻拿结果可以不调)
    aclFinish();
    
    // 清理
    executor.UnloadGraph();
    aclFinalize();
    return 0;
}

这段代码展示了 ge 的异步执行接口。Host 端发起执行后可以做其他事情,NPU 在后台跑算子,算完后触发回调函数。

性能数据

用 ge 做图优化后,不同模型的性能提升数据(Ascend 910,FP16):

模型 优化前延迟/ms 优化后延迟/ms 提升
LLaMA-7B 95.2 48.5 49%
ResNet-50 12.3 7.8 37%
BERT-Large 35.6 19.2 46%
YOLOv8 42.5 28.3 33%

可以看到 ge 的图优化对各类模型都有明显效果,尤其是大模型(LLaMA)和 Transformer 类模型(BERT),提升都在 45% 以上。

注意事项

用 ge 做图优化的时候有几个坑要注意:

第一是精度损失。开 FP16 精度模式后,某些算子(尤其是数值范围很大的)可能会有精度损失。如果精度要求高,建议用 FP32 或者在敏感层保留 FP32。

第二是自定义算子的注册 。如果你用了自定义算子(比如自己写了一个 Ascend C 算子),需要先在 ge 里注册这个算子,否则 ge 优化时会报错。注册方式是写一个 op_lib.json 文件,把算子的输入输出格式描述清楚。

第三是动态 shape 的支持。ge 的图优化对动态 shape(输入尺寸不固定)的支持还不够完善。如果模型有动态 shape,建议先转成静态 shape(通过 Pad 或 Resize),再送给 ge 优化。

ge 是 CANN 的图编译和运行时引擎,负责把上层框架的模型转换成昇腾 NPU 能高效执行的图。它的图优化能力(算子融合、内存复用、流水调度)是提升模型性能的关键。

仓库地址:https://atomgit.com/cann/ge

相关推荐
deepin_sir5 小时前
07 Chroma_RAG流水线:从Demo到生产级架构
架构
无敌糖果5 小时前
N9E夜莺告警架构梳理分析
架构·夜莺·告警系统·n9e
轻刀快马6 小时前
讲透分布式系统的演进史与核心架构
开发语言·架构·php
一切皆是因缘际会6 小时前
从概率拟合到内生心智:七层投影架构重构AGI数字生命新范式
大数据·数据结构·人工智能·重构·架构·agi
青衫客366 小时前
从操作系统到 Agent OS:多智能体系统运行原理的底层类比与架构思考
架构·agent
Lyra_Infra6 小时前
技术排查报告:Kubernetes Ingress 路由异常
docker·架构
烟雨江南7856 小时前
从转写到智能体决策:基于“灵声智库”与本地大模型(LLM)的政务热线智能分析与 RAG 知识库融合架构
人工智能·科技·架构·语音识别·政务·ai质检
互联网推荐官6 小时前
上海物联网应用开发平台选型实录:PaaS架构如何解决设备接入与数据治理的工程难题
物联网·架构·paas·开发经验·上海
小短腿的代码世界7 小时前
QHttpEngine深度解析:Qt嵌入式HTTP服务端的工业级架构与性能调优
qt·http·架构