概述
图是"拓扑 + 类型 + 形状"的世界
这句话是理解 AI 编译器"图视角"的核心纲领。
它并不是在说图里没有"数值",而是在强调:
- 在图编译器(Graph Compiler)这个层面,它完全无视具体的数据值(比如像素是 0.5 还是 0.8)
- 只关心描述数据的"元数据"和描述计算的"骨架"。
通俗比喻
假设图视角是一家 中央厨房的调度 中心,它负责指挥整个烹饪流程。
-
拓扑 = 菜谱步骤的依赖关系(必须先洗菜,才能切菜;必须先切菜,才能炒菜)。调度中心不看菜切得漂不漂亮,只看"谁依赖谁"。
-
类型 = 食材的品种分类(这是肉、这是蔬菜、这是调料)。调度中心不关心这块肉是猪肉还是牛肉(具体值),但必须知道是肉(要用肉刀)还是菜(要用菜刀)。
-
形状 = 食材的分量和容器大小(这是 1 斤肉装在大盆里,那是 2 两葱花装在小碟里)。调度中心根据这个决定用什么锅炒、炒多久。
调度中心绝对不关心 "这块肉上的花纹长什么样"、"这棵白菜重多少克"(具体数值),它只关注数据(食材)的流向、类型和尺寸,以此来决定如何最高效地安排流水线。
例子:图编译器如何"看"代码
假设我们有一个简单的 PyTorch 模型片段:Conv2d -> BatchNorm -> ReLU。
1. 图编译器"看到"的抽象表示
图编译器不关心输入图片是猫还是狗,它只构建如下 DAG:
text
输入 (placeholder)
│ 形状: [1, 3, 224, 224] (类型: fp32)
▼
Conv2d 节点
│ 权重形状: [64, 3, 7, 7] (类型: fp32)
│ 输出形状: [1, 64, 112, 112] (类型: fp32) ← 中间张量 A
▼
BatchNorm 节点
│ 参数: γ, β (类型: fp32)
│ 输出形状: [1, 64, 112, 112] (类型: fp32) ← 中间张量 B
▼
ReLU 节点
│ 输出形状: [1, 64, 112, 112] (类型: fp32) ← 最终输出
▼
2. 拆解"拓扑 + 类型 + 形状"如何驱动优化
① 拓扑(依赖关系)告诉编译器"能不能融"
图编译器看拓扑:ReLU 只依赖 BatchNorm,BatchNorm 只依赖 Conv,且它们是一对一的线性关系(没有别的节点使用 中间张量 A)。
这三个节点可以融合(Fusion)!因为中间结果只有这一个"消费者",不需要存回内存给别人用。
② 形状(大小维度)告诉编译器"值不值得融"
图编译器看形状:中间张量 A 的大小是 1, 64, 112, 112,即 1 * 64 * 112 * 112 = 802,816 个元素(约 3.2 MB)。
如果不融合,这 3.2 MB 要写回 DRAM(主存),再读出来给 BN,成本极高(访存墙)。由于形状巨大,融合收益极高,必须融。
③ 类型(数据精度)告诉编译器"用什么指令融"
图编译器看类型:三个节点的数据类型都是 fp32(单精度浮点)。
既然类型一致,融合后的 Kernel 内部统一使用 SIMD 浮点向量指令(如 AVX2 的 vmulps)。如果类型是 int8,则会触发生成专用的低精度矩阵乘指令(如 VPMADDUBSW)。
对比:如果"类型"或"形状"变了,图编译器的决策会截然不同
| 场景 | 拓扑(依赖) | 类型 | 形状 | 图编译器(调度中心)的决策变化 |
|---|---|---|---|---|
| 原始场景 | Conv → BN → ReLU(链式) | fp32 | 1,64,112,112(大) | 融合:节省大量 DRAM 流量。 |
| 场景 A | Conv → BN → ReLU(链式) | fp32 | 1,64,7,7(小) | 不融合或不强求:中间张量只有 12 KB,访存成本低,融合带来的指令调度开销可能大于收益。 |
| 场景 B | Conv → BN → 输出给 ReLU 和 另一个分支) | fp32 | 1,64,112,112(大) | 不融合:拓扑显示中间张量有两个消费者,如果融合进 ReLU,另一个分支就拿不到数据了,必须存回内存。 |
| 场景 C | Conv → BN → ReLU(链式) | 中间转为 int8 | 1,64,112,112(大) | 强行融合但改变内核:为了省访存依然融合,但内部循环会插入量化(Quantization)指令,将 fp32 中间结果截断为 int8。 |
总结
一批具有固定类型(Type)和固定尺寸(Shape)的数据包(Tensor),按照固定的流向(Topology)流经一堆黑盒操作(Ops)
至于数据包里面的具体数值(比如像素 RGB 是 128 还是 255)------那是 Kernel 层(运行时循环)要处理的具体数值,图编译器完全不看,也不需要看。
在编译期完成 80% 的全局优化决策(内存分配、算子融合、布局转换)。
这,就是"图是拓扑 + 类型 + 形状的世界"的真正含义。