动态图 vs 静态图:深度学习框架到底该怎么选?别再被“概念战”忽悠了
作者:Echo_Wish
我发现一个挺有意思的现象:很多刚入门深度学习的朋友,第一次听到 动态计算图 和 静态计算图 的时候,往往会觉得这是一件特别高深的事情。
什么 Eager Execution、Graph Mode、Tracing、JIT 编译……
一堆术语听完之后,人基本已经懵了。
但如果咱们把这件事拆开来看,其实特别简单:
动态图像写 Python 程序,静态图像编译程序。
就像:
解释执行 vs 编译执行
今天咱们就用最接地气的方式,把这件事彻底聊明白:
动态图和静态图到底有什么区别?什么时候该选哪个?
一、先理解什么是“计算图”
深度学习其实就是一堆数学运算。
比如一个非常简单的神经网络:
y = ReLU(Wx + b)
如果把这个运算过程画出来,其实就是一个图:
x → matmul → add → relu → y
这就是所谓的 计算图(Computation Graph)。
每一个节点代表一个运算:
- 加法
- 乘法
- 激活函数
- 卷积
边代表 数据流。
简单理解:
神经网络 = 一张计算图
关键问题来了:
这张图是“运行时生成”还是“提前定义”?
于是就有了两种模式。
二、动态计算图:边跑边建
动态计算图的代表框架是:
- PyTorch
- DyNet
- PaddlePaddle(动态图模式)
特点只有一句话:
代码跑到哪,图建到哪。
来看一个 PyTorch 例子:
import torch
import torch.nn as nn
x = torch.tensor([1.0, 2.0, 3.0])
w = torch.tensor([0.2, 0.3, 0.5], requires_grad=True)
y = (x * w).sum()
print(y)
运行流程其实是:
执行一行代码
↓
创建一个计算节点
↓
连接到计算图
所以整个计算图是在 运行时动态生成的。
动态图的一个巨大优点是:
调试特别舒服。
比如你可以随时 print:
print(x * w)
甚至可以写复杂控制流:
if y > 0:
z = y * 2
else:
z = y * 3
这在动态计算图里完全没问题。
所以很多研究人员特别喜欢 PyTorch。
原因很简单:
写起来像普通 Python
三、静态计算图:先画图再执行
静态计算图的代表框架:
- TensorFlow 1.x
- MXNet
- Caffe
它的思想完全相反:
先定义图,再运行。
举个 TensorFlow 1.x 的例子:
import tensorflow as tf
x = tf.placeholder(tf.float32)
w = tf.Variable(2.0)
y = x * w
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
result = sess.run(y, feed_dict={
x:3})
print(result)
执行流程是:
第一步:构建计算图
第二步:提交给执行引擎
第三步:运行
所以在静态图里:
Python 只是建图工具
真正执行的是:
底层计算引擎
这就像:
Python → IR → 执行引擎
四、为什么曾经大家都用静态图?
很多人会问:
如果动态图这么方便,为什么早期深度学习框架都用静态图?
答案只有两个字:
性能
静态图有三个巨大优势。
1 图优化
因为图是提前定义的,所以系统可以做很多优化。
例如:
算子融合
内存复用
常量折叠
举个简单例子:
y = x * 2
z = y * 3
动态图执行:
mul
mul
静态图可能优化成:
mul(x,6)
这就是 Graph Optimization。
2 跨设备调度
静态图还能自动做设备分配:
GPU
TPU
CPU
例如:
conv → GPU
embedding → CPU
系统可以自动调度。
3 分布式执行
静态图更容易做:
pipeline
graph partition
distributed training
所以早期的大规模训练系统几乎都是:
静态图
五、动态图为什么后来崛起了?
原因也很现实。
研究效率。
研究人员写模型的时候,经常要改结构。
比如:
循环神经网络
递归神经网络
动态图结构
举个例子:
for node in tree:
hidden = f(node)
如果用静态图:
结构必须提前固定
这就很难受。
而动态图就很自然:
Python 控制流 = 模型结构
所以 PyTorch 在学术界迅速流行。
一句话总结:
研究人员喜欢动态图
工业界喜欢静态图
六、现代框架其实是“混合模式”
现在事情又发生了变化。
现代框架基本都在做一件事:
动态图 + 编译优化
例如:
PyTorch 的:
TorchScript
TorchDynamo
简单例子:
import torch
@torch.jit.script
def func(x):
return x * 2 + 1
print(func(torch.tensor(3)))
执行流程变成:
动态图
↓
Tracing
↓
静态图
↓
优化执行
所以今天的深度学习框架,其实已经不是:
动态图 vs 静态图
而是:
动态图开发
静态图执行
七、真实项目里到底怎么选?
我给大家一个非常实用的建议。
如果你是:
1 算法研究
选:
PyTorch 动态图
原因:
调试方便
开发效率高
社区生态强
2 大规模训练
可以考虑:
JAX
TensorFlow
PyTorch + torch.compile
原因:
编译优化
更高性能
3 工业推理部署
基本都会转成:
ONNX
TensorRT
TVM
例如:
torch.onnx.export(model, x, "model.onnx")
推理系统执行的其实是:
静态图
八、我对这件事的一点真实看法
很多技术争论其实挺有意思的。
曾经有一段时间,社区吵得特别凶:
动态图派
vs
静态图派
但今天再回头看,你会发现:
真正成熟的系统从来不是二选一。
而是:
开发体验 → 动态图
执行效率 → 静态图
换句话说:
动态图解决人类问题。
静态图解决机器问题。
而现代深度学习框架做的一件事就是:
让人写动态图
让机器跑静态图
这其实是一种非常优雅的工程设计。
结尾
如果用一句特别简单的话总结:
动态图:像写 Python
静态图:像编译程序