4、计算图与芯片映射:静态图 vs 动态图,如何映射到硬件执行单元。

好,咱们今天聊个硬核话题——计算图怎么跟芯片“对上话”。

我做了这么多年AI系统,发现很多同学把框架和芯片当成两个独立的东西。其实不然。你写的每一行Python代码,最终都要变成芯片上的一条条指令。这中间的桥梁,就是计算图。

说白了,计算图就是AI模型的“施工图纸”。芯片是“施工队”。图纸画得清不清楚,直接影响施工效率。

4.1 静态图 vs 动态图:两种“画图纸”的方式

先看个最简单的例子。假设我们要算 y = (x + 1) * 2

动态图(比如PyTorch的默认模式)

import torch
x = torch.tensor([3.0])
a = x + 1      # 立刻算,立刻出结果
y = a * 2      # 再算下一步
print(y)       # 输出 tensor([8.])

动态图的特点是:写一行,算一行。你执行到 a = x + 1,芯片马上就开始算加法。等算完了,再执行下一行乘法。

这就像你画图纸,画一笔,工人就砌一堵墙。好处是灵活,想改哪里改哪里。坏处是——工人得来回跑,效率不高。

静态图(比如TensorFlow 1.x或PyTorch的torch.compile)

import torch

@torch.jit.script
def compute(x):
    a = x + 1
    y = a * 2
    return y

x = torch.tensor([3.0])
y = compute(x)   # 先编译,再执行
print(y)

静态图的做法是:先把整张图画好,再一次性交给芯片执行。你调用 compute(x) 时,框架先扫描整个函数,把 x+1a*2 这两步拼成一张完整的图。然后才让芯片去算。

这就像你先画完整栋楼的图纸,再让工人按图施工。工人不用来回跑,效率自然高。

核心区别一句话总结:动态图是“边想边干”,静态图是“想好了再干”。

4.2 芯片到底在“看”什么?——硬件执行单元

聊完图纸,咱们看看芯片这边。AI芯片里最核心的硬件单元,我列几个:

  • ALU(算术逻辑单元):做加减乘除这些基本运算
  • MAC(乘累加单元):专门做 a * b + c 这种操作,是矩阵计算的主力
  • SIMD单元:一条指令同时处理多个数据,比如一次算8个加法
  • Tensor Core(张量核心):NVIDIA的专用单元,一次能做4x4矩阵乘法
  • DMA(直接内存访问):负责搬数据,不占计算资源

这些单元各有各的脾气。有的擅长并行,有的擅长串行。计算图要做的,就是把图中的每个操作,映射到最合适的单元上。

4.3 静态图如何映射到硬件?——一个完整的例子

咱们拿一个简单的神经网络层来举例:y = ReLU(W * x + b)

在静态图里,框架会先构建出这张图:

输入 x → 矩阵乘 (W * x) → 加法 (+ b) → ReLU → 输出 y

然后,框架的编译器(比如XLA或TVM)会做三件事:

  1. 算子融合(Operator Fusion):把 矩阵乘 + 加法 + ReLU 合并成一个“大算子”。因为这三个操作是连续的,中间不需要存临时结果。合并后,数据不用来回搬,省时间。
  2. 内存规划:提前算好每个张量该放哪里。比如 W 放显存,x 放缓存,b 放寄存器。这样执行时不用临时找地方。
  3. 指令生成:把合并后的大算子,翻译成芯片能懂的指令。比如矩阵乘用Tensor Core,ReLU用SIMD单元。

我个人的习惯:在部署模型到边缘设备时,我几乎只用静态图。因为边缘芯片资源有限,静态图能提前把内存安排得明明白白,不会出现“跑着跑着内存不够”的尴尬。

4.4 动态图如何映射?——灵活但代价高

动态图的映射方式完全不同。还是 y = ReLU(W * x + b),动态图的做法是:

  1. 遇到 W * x,立刻调用矩阵乘的kernel(内核函数)
  2. 结果出来后,遇到 + b,再调用加法的kernel
  3. 最后遇到 ReLU,调用ReLU的kernel

每个kernel都是独立的。这意味着:

  • 每次调用kernel,都要从显存读数据、写数据
  • 无法做算子融合,因为不知道下一步是什么
  • 内存分配是动态的,可能产生碎片

但好处也很明显——调试方便。我在研究新模型结构时,一定用动态图。因为可以随时 print 中间结果,看形状对不对。

我曾经踩过的坑:有一次用动态图训练一个超大模型,训练到一半显存爆了。查了半天,发现是动态图每步都分配了新的临时张量,旧的没及时释放。换成静态图后,内存占用直接降了40%。

4.5 核心知识体系:计算图到芯片的映射流程

下面这张图,是我自己总结的映射流程。你看一遍就能明白全貌:

计算图 → 芯片映射流程 动态图(Eager模式) 静态图(Graph模式) 解释执行(逐行翻译) 编译优化(整体分析) 算子融合 内存规划 指令生成 ALU MAC SIMD Tensor Core DMA 逐算子调度 整体调度 动态图 静态图

你看,动态图走的是左边这条线——逐算子解释执行,每个算子独立调度到硬件。静态图走右边——先编译优化,再整体调度。两条路都能到硬件,但效率天差地别。

4.6 实际项目中的选择策略

说了这么多,到底该用哪种?我根据经验给个参考:

场景 推荐方式 原因
模型研究/快速实验 动态图 灵活调试,随时改结构
生产部署 静态图 性能高,内存可控
边缘设备/嵌入式 静态图 资源有限,必须提前规划
超大模型训练 静态图 内存优化空间大,支持流水线并行
动态网络结构(如树/图结构数据) 动态图 静态图难以处理不确定的计算路径

我建议:如果你刚开始接触AI框架,先用动态图把模型跑通。等要上线了,再转静态图做优化。别一上来就搞静态图,调试起来能把你逼疯。

4.7 避坑指南

最后分享几个我踩过的坑:

  • 别以为静态图一定快:如果模型里有很多if-else分支,静态图会把所有分支都编译进去,反而变慢。这时候动态图更合适。
  • 注意控制流:静态图里不能用Python的 if x > 0: 这种写法。得用框架提供的 torch.wheretf.cond。我第一次用静态图时,光改这个就花了两天。
  • 内存碎片问题:动态图频繁分配释放内存,容易产生碎片。长时间运行的推理服务,建议用静态图。
  • 算子融合不是万能的:融合后的算子如果太大,可能超出硬件单元的处理能力。我遇到过融合后性能反而下降的情况,后来拆成两个算子才解决。

嗯,关于计算图与芯片映射,今天就聊这么多。记住一句话:动态图让你跑得快(开发效率),静态图让芯片跑得快(执行效率)。两者没有绝对的好坏,关键看你在哪个阶段。


公众号:蓝海资料掘金营,微信deep3321