计算图与IR:计算图基础、中间表示(IR)概念、主流IR格式介绍

好,咱们今天聊聊计算图和IR。这两个概念,说白了就是模型编译器的“骨架”和“语言”。

我刚开始接触编译器时,也觉得这些概念挺虚的。直到有一次,我手写了一个简单的算子融合,结果跑出来结果不对。debug了整整两天,最后发现是计算图里一个节点的依赖关系搞错了。嗯,从那以后,我再也不敢小看计算图了。

计算图基础

计算图是什么?你想想看,一个深度学习模型,无非就是一堆算子的组合。卷积、ReLU、BatchNorm、全连接……这些算子按照一定的顺序连接起来,就形成了一张图。

这张图里,节点是算子,边是数据流向。就这么简单。

核心要点:计算图是一种有向无环图(DAG),它完整描述了模型的计算逻辑。

我个人习惯把计算图分成两种:

  • 静态图:先定义好图结构,再执行。TensorFlow 1.x 就是典型代表。好处是编译优化空间大,坏处是调试麻烦。
  • 动态图:边定义边执行,PyTorch 就是典型。灵活,好调试,但优化空间相对小一些。

我在项目中遇到过一个问题:一个用PyTorch写的模型,直接转成静态图后,性能反而下降了。为什么?因为动态图里的一些动态控制流,在静态图里被“拍平”了,导致计算图变得非常臃肿。后来我们手动做了一些图优化,才把性能提上来。

中间表示(IR)概念

IR,全称是Intermediate Representation,中间表示。说白了,就是编译器内部用来表示程序的一种“语言”。

为什么需要IR?你想想看,前端有各种框架(PyTorch、TensorFlow、ONNX),后端有各种硬件(GPU、NPU、CPU)。如果每个前端都要直接对接每个后端,那工作量就爆炸了。

IR就是中间的“翻译官”。前端把模型转成IR,后端从IR生成目标代码。这样,前端只需要对接IR,后端也只需要对接IR,解耦了。

我的经验:IR的设计决定了编译器的上限。一个好的IR,既要能表达丰富的语义,又要方便做各种优化。我曾经参与过一个项目,IR设计得太“高层”,导致很多底层优化做不了;后来改成多层IR,问题就解决了。

IR通常分为几个层次:

  • 高层IR:接近原始模型,保留张量、算子等语义。比如ONNX。
  • 中层IR:做一些初步优化,比如常量折叠、死代码消除。
  • 低层IR:接近硬件,考虑内存布局、指令选择等。比如LLVM IR。

主流IR格式介绍

现在业界主流的IR格式,我挑几个重点说说。

IR名称 所属项目 特点 适用场景
ONNX ONNX 开放、跨框架、算子标准 模型交换、部署
MLIR LLVM 多层IR、可扩展、模块化 编译器基础设施
TorchScript PyTorch 动态图转静态图、Python子集 PyTorch模型部署
XLA HLO TensorFlow 高层优化、自动融合 TensorFlow性能优化

ONNX:我最常用的IR。它定义了一套标准的算子集,不同框架的模型都可以转成ONNX。但要注意,ONNX的算子集更新比较慢,有些新算子可能不支持。我曾经踩过一个坑:一个用了F.grid_sample的模型,转ONNX时死活过不去,最后只能手写一个自定义算子。

MLIR:这是LLVM社区搞的“下一代编译器基础设施”。它的核心思想是“多层IR”,你可以定义自己的IR方言,然后在不同层次之间做转换。我个人觉得MLIR是未来的方向,但学习曲线比较陡。

TorchScript:PyTorch自家的IR。它把动态图“录制”成静态图,然后做优化。好处是和PyTorch无缝集成,坏处是只支持PyTorch。

XLA HLO:TensorFlow的IR,专门做高性能计算。它的自动融合做得很好,能把很多小算子融合成一个大的kernel,减少内存访问。

避坑指南:我曾经在一个项目里,把ONNX模型直接喂给一个只支持MLIR的编译器,结果各种报错。后来才发现,ONNX和MLIR的算子语义不完全一致,需要做一层“方言转换”。所以,选IR时一定要考虑清楚你的上下游工具链。

知识体系总览

下面这张图,是我自己总结的计算图与IR的知识体系。你可以把它当作一个“地图”,方便后续学习时定位。

计算图与IR知识体系 计算图基础 • 节点 = 算子 • 边 = 数据流向 • 有向无环图(DAG) • 静态图 vs 动态图 • 控制流与数据流 IR概念 • 中间表示定义 • 前端/后端解耦 • 高层IR • 中层IR • 低层IR 主流IR格式 • ONNX (开放标准) • MLIR (多层可扩展) • TorchScript (PyTorch) • XLA HLO (TensorFlow) • TVM Relay 应用:算子融合、内存优化、图调度 基于IR的图优化是模型编译器的核心 三者关系:计算图 → IR → 优化 → 目标代码 # 伪代码:计算图转IR graph = build_compute_graph(model) ir = graph_to_ir(graph, format="ONNX")

嗯,这张图把今天讲的内容串起来了。计算图是基础,IR是桥梁,主流IR格式是工具。三者缺一不可。

最后说一句:别怕这些概念。你只要动手写一个简单的图优化pass,就全明白了。我当年也是这么过来的。

可可精品资料库