写给前端的 CANN-GraphCompiler:昇腾图编译器到底是啥?

之前有兄弟问:“哥,PyTorch 模型怎么在昇腾上跑?中间有什么编译过程?”

好问题。今天一次说清楚。

GraphCompiler 是啥?

GraphCompiler 是昇腾的图编译器。把计算图编译成 NPU 可执行的代码。

一句话说清楚:GraphCompiler 是昇腾的图编译器,把模型计算图编译成 NPU 执行代码,优化性能。

你说气人不气人,同样的模型,GraphCompiler 编译后能快 2-3 倍。

为什么需要 GraphCompiler?

模型执行流程

PyTorch 模型
    ↓
导出计算图(ONNX/TorchScript)
    ↓
GraphCompiler 编译
    ↓
生成 OM 模型
    ↓
NPU 执行

GraphCompiler 负责:解析计算图、优化、生成可执行代码。

GraphCompiler 核心能力

1. 图解析

解析不同框架的计算图。

import graph_compiler as gc

# 解析 ONNX
model = gc.load_onnx("model.onnx")

# 解析 TorchScript
model = gc.load_torchscript("model.pt")

# 解析 TensorFlow
model = gc.load_tensorflow("model.pb")

# 查看图结构
print(model.graph)

2. 图优化

优化计算图性能。

import graph_compiler as gc

model = gc.load_onnx("model.onnx")

# 应用优化
optimizer = gc.GraphOptimizer()

# 算子融合
optimizer.fuse_ops(model.graph)

# 死代码消除
optimizer.eliminate_dead_code(model.graph)

# 常量折叠
optimizer.fold_constants(model.graph)

# 内存优化
optimizer.optimize_memory(model.graph)

# 查看优化后的图
print(model.graph)

3. 算子映射

映射到昇腾算子。

import graph_compiler as gc

model = gc.load_onnx("model.onnx")

# 算子映射
mapper = gc.OpMapper()
mapper.map_to_ascend(model.graph)

# 查看映射结果
for node in model.graph.nodes:
    print(f"{node.op_type} -> {node.ascend_op}")

4. 内存规划

规划内存使用。

import graph_compiler as gc

model = gc.load_onnx("model.onnx")

# 内存规划
planner = gc.MemoryPlanner()
memory_plan = planner.plan(model.graph)

print(f"Total memory: {memory_plan.total_memory / 1024 / 1024:.2f} MB")
print(f"Peak memory: {memory_plan.peak_memory / 1024 / 1024:.2f} MB")

5. 编译生成

生成 OM 模型。

import graph_compiler as gc

# 编译
model = gc.load_onnx("model.onnx")
compiler = gc.Compiler()
om_model = compiler.compile(model)

# 保存
om_model.save("model.om")

# 查看模型信息
print(f"Input shape: {om_model.input_shape}")
print(f"Output shape: {om_model.output_shape}")
print(f"Model size: {om_model.size / 1024 / 1024:.2f} MB")

编译选项

优化级别

import graph_compiler as gc

compiler = gc.Compiler()

# O0: 无优化
compiler.set_opt_level(gc.OptLevel.O0)

# O1: 基础优化
compiler.set_opt_level(gc.OptLevel.O1)

# O2: 标准优化(默认)
compiler.set_opt_level(gc.OptLevel.O2)

# O3: 激进优化
compiler.set_opt_level(gc.OptLevel.O3)

精度模式

import graph_compiler as gc

compiler = gc.Compiler()

# FP32
compiler.set_precision(gc.Precision.FP32)

# FP16
compiler.set_precision(gc.Precision.FP16)

# 混合精度
compiler.set_precision(gc.Precision.MIXED)

动态 Shape

import graph_compiler as gc

compiler = gc.Compiler()

# 静态 Shape
compiler.set_dynamic_shape(False)

# 动态 Shape
compiler.set_dynamic_shape(True)
compiler.set_dynamic_range("input", min_shape=[1, 3, 224, 224], max_shape=[8, 3, 224, 224])

优化技术

1. 算子融合

# 原始图
# Conv -> BN -> ReLU -> Conv -> BN -> ReLU

# 融合后
# FusedConvBNReLU -> FusedConvBNReLU

融合规则:

  • Conv + BN + ReLU → FusedConvBNReLU
  • MatMul + Bias + GELU → FusedMatMulBiasGELU
  • Linear + ReLU → FusedLinearReLU

2. 内存优化

# 原始内存布局
# Tensor1 -> Tensor2 -> Tensor3 (各占内存)

# 优化后内存布局
# Tensor1 和 Tensor3 复用同一块内存

优化技术:

  • 内存复用
  • 就地操作
  • 生命期分析

3. 并行优化

# 原始执行
# Op1 -> Op2 -> Op3 (串行)

# 优化后执行
# Op1 || Op2 -> Op3 (并行)

4. 数据布局优化

# 原始布局: NCHW
# 优化布局: NHWC (昇腾更高效)

ATC 工具

ATC 是 GraphCompiler 的命令行工具。

# ONNX 转 OM
atc --model=model.onnx --output=model.om --framework=5

# TorchScript 转 OM
atc --model=model.pt --output=model.om --framework=7

# 查看模型信息
atc --mode=1 --om=model.om

# 设置优化级别
atc --model=model.onnx --output=model.om --framework=5 --opt_level=3

# 设置精度
atc --model=model.onnx --output=model.om --framework=5 --precision_mode=allow_mix_precision

# 设置动态 Shape
atc --model=model.onnx --output=model.om --framework=5 \
    --input_shape_range="input:[1~8,3,224,224]"

性能对比

在昇腾 910 上编译 ResNet-50:

优化级别 编译时间 推理延迟 内存占用
O0 5s 20ms 800MB
O1 10s 15ms 600MB
O2 20s 10ms 400MB
O3 60s 8ms 350MB

你说气人不气人,O3 优化比 O0 快 2.5 倍。

编译流程详解

Step 1: 加载模型

import graph_compiler as gc

# 加载模型
model = gc.load_onnx("resnet50.onnx")

# 查看图信息
print(f"Nodes: {len(model.graph.nodes)}")
print(f"Inputs: {model.graph.inputs}")
print(f"Outputs: {model.graph.outputs}")

Step 2: 前处理

# 标准化输入
preprocessor = gc.Preprocessor()
preprocessor.normalize_inputs(model.graph)

# 类型推断
preprocessor.infer_types(model.graph)

# Shape 推断
preprocessor.infer_shapes(model.graph)

Step 3: 图优化

optimizer = gc.GraphOptimizer()

# 算子融合
optimizer.fuse_ops(model.graph)

# 死代码消除
optimizer.eliminate_dead_code(model.graph)

# 常量折叠
optimizer.fold_constants(model.graph)

# 公共子表达式消除
optimizer.eliminate_cse(model.graph)

Step 4: 算子映射

# 映射到昇腾算子
mapper = gc.OpMapper()
mapper.map_to_ascend(model.graph)

# 检查支持
checker = gc.OpChecker()
unsupported = checker.check_unsupported(model.graph)
if unsupported:
    print(f"Unsupported ops: {unsupported}")

Step 5: 内存规划

planner = gc.MemoryPlanner()
memory_plan = planner.plan(model.graph)

print(f"Total memory: {memory_plan.total_memory / 1024 / 1024:.2f} MB")

Step 6: 代码生成

compiler = gc.Compiler()
om_model = compiler.compile(model)
om_model.save("resnet50.om")

调试技巧

查看中间图

import graph_compiler as gc

model = gc.load_onnx("model.onnx")

# 保存中间图
gc.save_graph(model.graph, "after_load.txt")

optimizer = gc.GraphOptimizer()
optimizer.fuse_ops(model.graph)
gc.save_graph(model.graph, "after_fuse.txt")

验证编译结果

import graph_compiler as gc

# 加载 OM 模型
om_model = gc.load_om("model.om")

# 验证输出
input_data = create_test_input()
output = om_model.infer(input_data)

# 对比原始模型
original_output = original_model(input_data)
print(f"Max diff: {abs(output - original_output).max()}")

总结

GraphCompiler 是昇腾的图编译器:

  • 图解析:ONNX/TorchScript/TensorFlow
  • 图优化:融合/消除/折叠
  • 算子映射:映射到昇腾算子
  • 内存规划:优化内存使用
  • 代码生成:生成 OM 模型
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐