AI 编译器与编译优化 复习资料
一、编译器基础概念
1. 编译器定义
编译器是一种计算机程序,用于将高级程序语言(如 C++、Java、Python 等)编写的源代码转换为计算机能够理解和执行的机器语言代码。
2. 编译器基本构成
| 组成部分 | 功能 |
|---|---|
| 前端(Front-End) | 词法和语法分析,检查语法和语义,生成抽象语法树(AST) |
| 优化器(Optimizer) | 对中间代码进行优化,使代码更高效 |
| 后端(Back-End) | 将优化后的中间代码转换为目标平台的机器代码 |
3. 从编译到装载的程序创建过程
HLL(高级语言)→ 编译器 → 汇编代码 → 汇编器 → 目标代码 → 链接器 → 可执行二进制代码 → 加载器 → 内存中的二进制指令
二、传统编译器
1. 代表:GCC 与 LLVM
核心概念:前端、后端、中间表达(IR)、优化过程
2. IR(Intermediate Representation,中间表示)
- 用于表示中间代码的数据结构
- 设计目的:保证编译器的跨平台性能
- 对不同硬件平台使用相同的 IR 表示
- 便于支持新语言和新硬件
3. LLVM 架构特点
- 多语言前端(C→Clang, Go→Gollvm, Rust→rustc, Toy→toyc)统一生成 LLVM IR
- 中间层(Middle-end)包含 LLVM 优化器,由多个 Pass 组成
- 后端生成不同硬件架构的机器码(X86、ARM、MIPS、RISC-V 等)
Pass:对源程序的一次完整扫描与优化处理
4. GCC vs LLVM 对比
| 维度 | GCC(传统) | LLVM(现代) |
|---|---|---|
| 架构 | 前端直连后端 | 前端 → IR → 后端 |
| 多语言支持 | 每种语言需适配所有后端 | 每种语言只需生成 IR |
| 多硬件支持 | 每个硬件需适配所有前端 | 每个硬件只需处理 IR |
| 复用性 | 低 | 高 |
三、AI 编译器
1. 传统编译器 vs AI 编译器对比
| 对比维度 | 传统编译器 | AI 编译器 |
|---|---|---|
| 输入 | 高级语言代码 | 计算图 / 算子 |
| 输出 | 低级语言 | 低级语言 |
| 主要目的 | 降低编程难度 | 优化程序性能 |
| 次要目的 | 优化程序性能 | 降低编程难度 |
3. 核心区别
IR 表达层级差异
- AI 编译器:High-level IR,抽象描述深度学习模型中的运算(Convolution、Matmul、Transformer 等),带有图结构
- 传统编译器:Low-level IR,描述基本指令运算(load、store 等)
优化策略差异
- AI 编译器:引入领域特定知识,进行 High-level 优化
- 算子融合(Operator Fusion)
- 降低计算精度(int8、fp16、bf16),因为深度学习对精度不敏感
- 传统编译器:一般不执行改变变量类型和精度的优化
四、前端优化(硬件无关)
4.1 中间表示——计算图
- 基本数据结构:Tensor(N 维数组)
- Tensor 形状:
[2, 3, 4] - 元素类型:int、float、string 等
- 基本运算单元:Operator
- 每个 Operator 接收 N 个输入 Tensor,输出 M 个输出 Tensor
- TensorFlow 中有 >400 个基本 operator
- 数据流图表示:
- 节点 = Operator
- 边 = Tensor
4.2 算术表达式化简
通过代数等价变换化简计算图:
| 化简规则 | 示例 |
|---|---|
| 乘零 | a × 0 → 0 |
| 乘一 | a × 1 → a |
| 加零 | a + 0 → a |
| 对数展开 | log(exp(x)/y) → x - log(y) |
还可利用交换律、结合律调整算子执行顺序。
4.3 公共子表达式消除(CSE)
- 找到程序中等价的计算表达式,复用结果消除冗余计算
- AI 编译器中基于计算图搜索相同结构的子图
4.4 死代码消除(DCE)
- 移除对程序执行结果无影响的代码
- 优点:提高效率、节省资源、减少代码长度
- 常在其他图优化 Pass 后应用(如删除推理时仅训练相关的子图)
4.5 常量折叠(Constant Folding)
- 对编译时常量或常量表达式进行计算来简化代码
- 在编译期计算并化简
BN 折叠(Batch Normalization Folding)
- 训练时:μ、σ、γ、β 持续更新
- 推理时:四个值固定,BN 变为线性变换
- 卷积也是线性变换 → 可合并为单一线性变换
4.6 算子融合(Operator Fusion)
- 将多个向量化算子的操作合并成一个
- 好处:减少内核启动开销、减少内存读取、提高计算密度
4.7 布局转换(Layout Transform)
多维数组在内存中的数据布局方式。
常见布局
| 布局 | 含义 | 特点 |
|---|---|---|
| NCHW | Batch, Channel, Height, Width | 每个通道数据连续,适合 GPU,PyTorch/MindSpore 默认 |
| NHWC | Batch, Height, Width, Channel | 每个像素点数据连续,适合 CPU,TensorFlow 默认 |
相同数据不同布局导致访问特性不同,影响计算性能。
4.8 内存分配优化
- Inplace Operation(原地操作):内存不再需要且下一操作是 element-wise 时,直接覆盖内存,无需分配新内存
- 通过对计算图分析,找到 Inplace Operation 和 Memory Sharing 的机会
前端优化总结
前端优化主要通过图的等价变换化简计算图,降低计算复杂度或内存开销,与硬件无关。
五、后端优化(硬件相关)
5.1 后端优化关注点
- 算子节点的内部具体实现
- 输入、输出、内存循环方式和计算逻辑
5.2 循环优化
循环展开(Loop Unrolling)
- 减少循环开销,使能后续优化(如指令并行)
循环分块(Loop Tiling / Blocking)
- 将数据分块存储在 Cache 中,提高访存效率和数据局部性
- 避免频繁从主存读取数据带来的访问延迟
循环融合(Loop Fusion)
- 将相邻或紧密间隔的循环融合在一起
- 减少循环开销,增加计算密度,改善缓存局部性
循环拆分(Loop Split / Distribution)
- 将一个循环分解成多个循环
- 便于后续优化或满足特定硬件约束