官方文档:动态图转静态图
1.动态图和静态图
在深度学习模型构建上,飞桨框架支持动态图编程和静态图编程两种方式,其代码编写和执行方式均存在差异。
-
动态图编程: 采用 Python 的编程风格,解析式地执行每一行网络代码,并同时返回计算结果。
-
静态图编程: 采用先编译后执行的方式。需先在代码中预定义完整的神经网络结构,飞桨框架会将神经网络描述为 Program 的数据结构,并对 Program 进行编译优化,再调用执行器获得计算结果。
1.1优劣
- 动态图编程体验更佳、更易调试,但是因为采用 Python 实时执行的方式,开销较大,在性能方面与 C++ 有一定差距;
- 静态图调试难度大,但是将前端 Python 编写的神经网络预定义为 Program描述,转到 C++ 端重新解析执行,脱离了 Python 依赖,往往执行性能更佳,并且预先拥有完整网络结构也更利于全局优化。
1.2动态图转静态图
-
在模型开发时,推荐采用动态图编程。 可获得更好的编程体验、更易用的接口、更友好的调试交互机制。
-
在模型训练或者推理部署时,只需添加一行装饰器 @to_static,即可将动态图代码转写为静态图代码,并在底层自动使用静态图执行器运行。 可获得更好的模型运行性能。
使用场景:使用动态图编程调试完成,转为静态图训练;使用动态图训练结束,转为静态图推理。
1.3使用静态图的方式
飞桨框架 2.0 及以上版本默认的编程模式是动态图模式,包括使用高层 API 编程和基础的 API 编程。
方式一:如果想切换到静态图模式编程,可以在程序的开始执行 enable_static() 函数。
方式二:如果程序已经使用动态图的模式编写了,想转成静态图模式训练或者保存模型用于部署,可以使用装饰器 @to_static。
2.动态图转静态图使用样例
2.1使用方式
2.1.1方式一 使用 @to_static 进行动静转换
使用 @to_static 装饰器装饰 SimpleNet
(继承了 nn.Layer
) 的 forward
函数。这种方式适合使用动态图编程调试完成,转为静态图训练。
import paddle
from paddle.jit import to_staticclass SimpleNet(paddle.nn.Layer):def __init__(self):super(SimpleNet, self).__init__()self.linear = paddle.nn.Linear(10, 3)@to_static # 动静转换def forward(self, x, y):out = self.linear(x)out = out + yreturn outnet = SimpleNet()
net.eval()
x = paddle.rand([2, 10])
y = paddle.rand([2, 3])
out = net(x, y)
paddle.jit.save(net, './net')
2.1.2方式二 调用 paddle.jit.to_static()
函数
调用 paddle.jit.to_static()
函数,仅做预测模型导出时推荐此种用法。
import paddle
from paddle.jit import to_staticclass SimpleNet(paddle.nn.Layer):def __init__(self):super(SimpleNet, self).__init__()self.linear = paddle.nn.Linear(10, 3)def forward(self, x, y):out = self.linear(x)out = out + yreturn outnet = SimpleNet()
net.eval()
net = paddle.jit.to_static(net) # 动静转换
x = paddle.rand([2, 10])
y = paddle.rand([2, 3])
out = net(x, y)
paddle.jit.save(net, './net')
方式一和方式二的主要区别是,使用 @to_static 除了支持预测模型导出外,在模型训练时,还会转为静态图子图训练,而方式二仅支持预测模型导出。即方式一支持训练,方式二仅支持推理。
2.2动转静模型导出
这里的导出与普通的保存不同,是指导出为静态图推理,将不再改动模型。
通过 forward
导出预测模型导出一般包括三个步骤:
-
切换
eval()
模式:类似Dropout
、LayerNorm
等接口在train()
和eval()
的行为存在较大的差异,在模型导出前,请务必确认模型已切换到正确的模式,否则导出的模型在预测阶段可能出现输出结果不符合预期的情况。 -
构造
InputSpec
信息:InputSpec 用于表示输入的shape、dtype、name信息,且支持用None
表示动态shape(如输入的 batch_size 维度),是辅助动静转换的必要描述信息。 -
调用
save
接口:调用paddle.jit.save
接口,若传入的参数是类实例,则默认对forward
函数进行@to_static
装饰,并导出其对应的模型文件和参数文件。
net = SimpleNet()
# train(net) 模型训练 (略)# step 1: 切换到 eval() 模式
net.eval()# step 2: 定义 InputSpec 信息
x_spec = InputSpec(shape=[None, 3], dtype='float32', name='x')
y_spec = InputSpec(shape=[3], dtype='float32', name='y')# step 3: 调用 jit.save 接口
net = paddle.jit.save(net, path='simple_net', input_spec=[x_spec, y_spec]) # 动静转换
3.动、静态图部署区别
3.1动态图
动态图下,模型指的是 Python 前端代码;参数指的是 model.state_dict()
中存放的权重数据。
上图展示了动态图下模型训练——>参数导出——>预测部署的流程。如图中所示,动态图预测部署时,除了已经序列化的参数文件,还须提供最初的模型组网代码。在动态图下,模型代码是 逐行被解释执行 的。
3.2静态图
静态图部署时,模型指的是 Program
;参数指的是所有的 Persistable=True
的 Variable
。二者都可以序列化导出为磁盘文件,与前端代码完全解耦。
上图展示了静态图下模型训练——>模型导出——>预测部署的流程。如图所示,静态图模型导出时将Program
和模型参数都导出为磁盘文件,Program
中包含了模型所有的计算描述( OpDesc
),不存在计算逻辑有遗漏的地方。