3、计算图与中间表示:计算图的概念、静态图与动态图、IR设计原则、MLIR基础
3.1 计算图:AI模型的骨架
做嵌入式AI编译器,第一个绕不开的概念就是计算图。说白了,计算图就是把神经网络的计算过程,用一张有向图给画出来。每个节点是一个算子(比如卷积、ReLU、矩阵乘),每条边是张量数据的流向。
我记得刚入行那会儿,看论文里画的计算图总觉得花里胡哨。后来自己动手写了一个简单的图优化pass,才真正理解——计算图就是模型的骨架,编译器所有的优化都在这张图上做文章。
举个例子,一个简单的两层网络:
// 伪代码表示计算图
input --> Conv2d --> ReLU --> MaxPool --> FC --> Softmax --> output
这张图里,每个节点代表一个操作,每条边代表数据的依赖关系。编译器拿到这张图,才能做算子融合、内存规划、死代码消除这些优化。
核心要点:计算图是AI编译器的“源代码”,所有后续工作都基于它展开。
3.2 静态图 vs 动态图:两种哲学
这里有个经典问题:静态图和动态图到底选哪个?我在项目里两种都踩过坑,说说我的体会。
静态图(Graph模式)
静态图的特点是:先定义,后执行。你先把整个网络结构搭好,然后编译器一次性编译优化,最后喂数据跑。TensorFlow 1.x就是典型代表。
- 优点:编译器能看到全局,可以做激进的优化(比如内存复用、算子融合)
- 缺点:调试困难,控制流不灵活,想加个if-else都费劲
动态图(Eager模式)
动态图则是边定义边执行。PyTorch默认就是这种模式。你写一行代码,它立刻执行,结果立等可见。
- 优点:调试方便,代码写起来像普通Python,控制流自然
- 缺点:编译器看不到全局,优化空间有限
我的建议:做嵌入式部署,最终一定要转成静态图。因为资源受限,我们需要编译器做全局优化。但开发阶段用动态图,效率高得多。PyTorch后来搞了torch.jit.trace和torchscript,就是为了兼顾两者。
我曾经在一个项目里,用动态图调试了三天模型,结果部署时发现静态图下算子顺序变了,精度对不上。后来学乖了——开发用动态图,部署前必须做静态图对齐验证。
3.3 IR(中间表示)设计原则
IR是编译器的核心。你想想看,从TensorFlow的GraphDef到ONNX,再到TVM的Relay,每个框架都有自己的IR。设计一个好的IR,我觉得要遵循几个原则:
- 层次化:高层IR保留语义信息(比如卷积+BN),低层IR贴近硬件(比如循环展开后的指令)
- SSA形式:静态单赋值,每个变量只赋值一次。这能简化数据流分析
- 类型系统:明确张量的形状、数据类型、内存布局
- 可扩展性:能方便地添加新算子、新属性
避坑指南:我曾经设计IR时忽略了内存布局信息,结果后面做内存规划时,不得不回头改IR结构,牵一发动全身。IR设计一定要预留好扩展点。
举个例子,一个简单的IR节点定义:
// 一个简化的IR节点结构
struct IRNode {
string op_type; // 算子类型,如 "conv2d"
vector<int> input_ids; // 输入张量ID列表
int output_id; // 输出张量ID
map<string, int> attrs; // 属性,如 stride, padding
DataType dtype; // 数据类型,如 float32
vector<int> shape; // 输出形状
};
3.4 MLIR基础:多级IR的实践
MLIR(Multi-Level Intermediate Representation)是Google提出的一个编译器基础设施。说白了,它让你可以定义多级IR,从高层到底层逐层lowering。
为什么需要MLIR?我举个例子。传统编译器(比如LLVM)只有一层IR,从源码直接降到LLVM IR。但AI模型太复杂了,从计算图到硬件指令,中间有太多层次需要表达。MLIR允许你定义自己的Dialect(方言),每一层解决一个特定问题。
MLIR的核心概念:
- Dialect:一组算子和类型的集合。比如TOSA Dialect定义张量运算,Linalg Dialect定义线性代数运算
- Operation:基本计算单元,类似计算图的节点
- Type:数据类型,支持自定义
- Pass:优化和转换的单元
下面我用SVG画一张MLIR的lowering流程图,帮你理解整个体系:
嗯,这里要注意。MLIR不是银弹,它只是一个框架。真正的工作量在于定义你自己的Dialect和Pass。我在一个项目里,为了适配某款NPU,自己写了三个Dialect和十几个Pass,从TOSA一路降到硬件指令。
实战技巧:刚开始接触MLIR,建议先跑通官方的Toy Tutorial。它用了一个简单的Toy语言,演示了从定义Dialect到Lowering的全过程。我当年花了两天跑完,对MLIR的理解直接上了一个台阶。
3.5 本章小结
计算图是AI编译器的起点,静态图和动态图各有适用场景。IR设计要兼顾层次化、SSA、类型系统和可扩展性。MLIR提供了多级IR的实践框架,让我们能灵活地定义和转换不同层次的表示。
说白了,这一章的内容是后面所有优化的基础。你只有把计算图和IR搞明白了,才能理解算子融合怎么做、内存怎么规划、代码怎么生成。下一章我们会深入具体的图优化技术,到时候这些概念都会用上。
一句话记住:计算图是骨架,IR是血肉,MLIR是骨架和血肉之间的连接器。