diff --git a/docs/guides/04_dygraph_to_static/basic_usage_cn.md b/docs/guides/04_dygraph_to_static/basic_usage_cn.md new file mode 100644 index 00000000000..b3159c24a2a --- /dev/null +++ b/docs/guides/04_dygraph_to_static/basic_usage_cn.md @@ -0,0 +1,475 @@ +# 基础接口用法 + + +## 一、 @to_static 概览 + +动静转换(@to_static)通过解析 Python 代码(抽象语法树,下简称:AST) 实现一行代码即可转为静态图功能,即只需在待转化的函数前添加一个装饰器 ``@paddle.jit.to_static`` 。 + +如下是一个使用 @to_static 装饰器的 ``Model`` 示例: + +```python +import paddle +from paddle.jit import to_static + +class SimpleNet(paddle.nn.Layer): + def __init__(self): + super(SimpleNet, self).__init__() + self.linear = paddle.nn.Linear(10, 3) + + # 方式一:装饰 forward 函数(支持训练) + @to_static + def forward(self, x, y): + out = self.linear(x) + out = out + y + return out + +net = SimpleNet() +# 方式二:(推荐)仅做预测模型导出时,推荐此种用法 +net = paddle.jit.to_static(net) # 动静转换 +``` + +动转静 @to_static 除了支持预测模型导出,还兼容转为静态图子图训练,仅需要在 ``forward`` 函数上添加此装饰器即可,不需要修改任何其他的代码。 + +基本执行流程如下: + + + + + +## 二、 输入层 InputSpec + + +静态图下,模型起始的 Placeholder 信息是通过 ``paddle.static.data`` 来指定的,并以此作为编译期的 ``InferShape`` 推导起点。 + +```python +import paddle +# 开启静态图模式 +paddle.enable_static() + +# placeholder 信息 +x = paddle.static.data(shape=[None, 10], dtype='float32', name='x') +y = paddle.static.data(shape=[None, 3], dtype='float32', name='y') + +out = paddle.static.nn.fc(x, 3) +out = paddle.add(out, y) +``` + + +动转静代码示例,通过 ``InputSpec`` 设置 ``Placeholder`` 信息: + +```python +import paddle +from paddle.jit import to_static + +class 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 + y + return out + +net = SimpleNet() + +# 方式二:(推荐)仅做预测模型导出时,推荐此种用法 +x_spec = InputSpec(shape=[None, 10], name='x') +y_spec = InputSpec(shape=[3], name='y') + +net = paddle.jit.to_static(net, input_spec=[x_spec, y_spec]) # 动静转换 +``` + + +在导出模型时,需要显式地指定输入 ``Tensor`` 的**签名信息**,优势是: + + ++ 可以指定某些维度为 ``None`` , 如 ``batch_size`` ,``seq_len`` 维度 ++ 可以指定 Placeholder 的 ``name`` ,方面预测时根据 ``name`` 输入数据 + +> 注:InputSpec 接口的高阶用法,请参看 [【InputSpec 功能介绍】](./export_model_cn.html#inputspec) + + +## 三、函数转写 + +在 NLP、CV 领域中,一个模型常包含层层复杂的子函数调用,动转静中是如何实现**只需装饰最外层的 ``forward`` 函数**,就能递归处理所有的函数。 + +如下是一个模型样例: + +```python +import paddle +from paddle.jit import to_static + +class 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.my_fc(x) # <---- self.other_func + out = add_two(out, y) # <---- other plain func + return out + + def my_fc(self, x): + out = self.linear(x) + return out + +# 此函数可以在任意文件 +def add_two(x, y): + out = x + y + return out + +net = SimpleNet() +# 查看转写的代码内容 +paddle.jit.set_code_level(100) + +x = paddle.zeros([2,10], 'float32') +y = paddle.zeros([3], 'float32') + +out = net(x, y) +``` + +可以通过 ``paddle.jit.set_code_level(100)`` 在执行时打印代码转写的结果到终端,转写代码如下: + +```python +def forward(self, x, y): + out = paddle.jit.dy2static.convert_call(self.my_fc)(x) + out = paddle.jit.dy2static.convert_call(add_two)(out, y) + return out + +def my_fc(self, x): + out = paddle.jit.dy2static.convert_call(self.linear)(x) + return out + +def add_two(x, y): + out = x + y + return out +``` + + +如上所示,所有的函数调用都会被转写如下形式: + +```python + out = paddle.jit.dy2static.convert_call( self.my_fc )( x ) + ^ ^ ^ ^ + | | | | +返回列表 convert_call 原始函数 参数列表 +``` + +即使函数定义分布在不同的文件中, ``convert_call`` 函数也会递归地处理和转写所有嵌套的子函数。 + +## 四、控制流转写 + +控制流 ``if/for/while`` 的转写和处理是动转静中比较重要的模块,也是动态图模型和静态图模型实现上差别最大的一部分。 + +**转写上有两个基本原则:** + ++ **并非**所有动态图中的 ``if/for/while`` 都会转写为 ``cond_op/while_op`` ++ **只有**控制流的判断条件 **依赖了``Tensor``**(如 ``shape`` 或 ``value`` ),才会转写为对应 Op + + + + + + +### 4.1 IfElse + +无论是否会转写为 ``cond_op`` ,动转静都会首先对代码进行处理,**转写为 ``cond`` 接口可以接受的写法** + +**示例一:不依赖 Tensor 的控制流** + +```python +def not_depend_tensor_if(x, label=None): + out = x + 1 + if label is not None: # <----- python bool 类型 + out = paddle.nn.functional.cross_entropy(out, label) + return out + +print(to_static(not_depend_tensor_ifw).code) +# 转写后的代码: +""" +def not_depend_tensor_if(x, label=None): + out = x + 1 + + def true_fn_1(label, out): # true 分支 + out = paddle.nn.functional.cross_entropy(out, label) + return out + + def false_fn_1(out): # false 分支 + return out + + out = paddle.jit.dy2static.convert_ifelse(label is not None, true_fn_1, + false_fn_1, (label, out), (out,), (out,)) + + return out +""" +``` + + +**示例二:依赖 Tensor 的控制流** + +```python +def depend_tensor_if(x): + if paddle.mean(x) > 5.: # <---- Bool Tensor 类型 + out = x - 1 + else: + out = x + 1 + return out + +print(to_static(depend_tensor_if).code) +# 转写后的代码: +""" +def depend_tensor_if(x): + out = paddle.jit.dy2static.data_layer_not_check(name='out', shape=[-1], + dtype='float32') + + def true_fn_0(x): # true 分支 + out = x - 1 + return out + + def false_fn_0(x): # false 分支 + out = x + 1 + return out + + out = paddle.jit.dy2static.convert_ifelse(paddle.mean(x) > 5.0, + true_fn_0, false_fn_0, (x,), (x,), (out,)) + + return out +""" +``` + + +规范化代码之后,所有的 ``IfElse`` 均转为了如下形式: + +```python + out = convert_ifelse(paddle.mean(x) > 5.0, true_fn_0, false_fn_0, (x,), (x,), (out,)) + ^ ^ ^ ^ ^ ^ ^ ^ + | | | | | | | | + 输出 convert_ifelse 判断条件 true分支 false分支 分支输入 分支输入 输出 +``` + + +``convert_ifelse`` 是框架底层的函数,在逐行执行用户代码生成 ``Program`` 时,执行到此处时,会根据**判断条件**的类型( ``bool`` 还是 ``Bool Tensor`` ),自适应决定是否转为 ``cond_op`` 。 + +```python +def convert_ifelse(pred, true_fn, false_fn, true_args, false_args, return_vars): + + if isinstance(pred, Variable): # 触发 cond_op 的转换 + return _run_paddle_cond(pred, true_fn, false_fn, true_args, false_args, + return_vars) + else: # 正常的 python if + return _run_py_ifelse(pred, true_fn, false_fn, true_args, false_args) +``` + + +### 4.2 For/While + +``For/While`` 也会先进行代码层面的规范化,在逐行执行用户代码时,才会决定是否转为 ``while_op``。 + +**示例一:不依赖 Tensor 的控制流** + +```python +def not_depend_tensor_while(x): + a = 1 + + while a < 10: # <---- a is python scalar + x = x + 1 + a += 1 + + return x + +print(to_static(not_depend_tensor_while).code) +""" +def not_depend_tensor_while(x): + a = 1 + + def while_condition_0(a, x): + return a < 10 + + def while_body_0(a, x): + x = x + 1 + a += 1 + return a, x + + [a, x] = paddle.jit.dy2static.convert_while_loop(while_condition_0, + while_body_0, [a, x]) + + return x +""" +``` + + +**示例二:依赖 Tensor 的控制流** + +```python +def depend_tensor_while(x): + bs = paddle.shape(x)[0] + + for i in range(bs): # <---- bas is a Tensor + x = x + 1 + + return x + +print(to_static(depend_tensor_while).code) +""" +def depend_tensor_while(x): + bs = paddle.shape(x)[0] + i = 0 + + def for_loop_condition_0(x, i, bs): + return i < bs + + def for_loop_body_0(x, i, bs): + x = x + 1 + i += 1 + return x, i, bs + + [x, i, bs] = paddle.jit.dy2static.convert_while_loop(for_loop_condition_0, + for_loop_body_0, [x, i, bs]) + return x +""" +``` + + +``convert_while_loop`` 的底层的逻辑同样会根据 **判断条件是否为``Tensor``** 来决定是否转为 ``while_op`` + +## 五、 Parameters 与 Buffers + +### 5.1 动态图 layer 生成 Program + +文档开始的样例中 ``forward`` 函数包含两行组网代码: ``Linear`` 和 ``add`` 操作。以 ``Linear`` 为例,在 Paddle 的框架底层,每个 Paddle 的组网 API 的实现包括两个分支: + +```python + +class Linear(...): + def __init__(self, ...): + # ...(略) + + def forward(self, input): + + if in_dygraph_mode(): # 动态图分支 + core.ops.matmul(input, self.weight, pre_bias, ...) + return out + else: # 静态图分支 + self._helper.append_op(type="matmul", inputs=inputs, ...) # <----- 生成一个 Op + if self.bias is not None: + self._helper.append_op(type='elementwise_add', ...) # <----- 生成一个 Op + + return out +``` + +动态图 ``layer`` 生成 ``Program`` ,其实是开启 ``paddle.enable_static()`` 时,在静态图下逐行执行用户定义的组网代码,依次添加(对应 ``append_op`` 接口) 到默认的主 Program(即 ``main_program`` ) 中。 + +### 5.2 动态图 Tensor 转为静态图 Variable + +上面提到,所有的组网代码都会在静态图模式下执行,以生成完整的 ``Program`` 。**但静态图 ``append_op`` 有一个前置条件必须满足:** + +> **前置条件**:append_op() 时,所有的 inputs,outputs 必须都是静态图的 Variable 类型,不能是动态图的 Tensor 类型。 + + +**原因**:静态图下,操作的都是**描述类单元**:计算相关的 ``OpDesc`` ,数据相关的 ``VarDesc`` 。可以分别简单地理解为 ``Program`` 中的 ``Op`` 和 ``Variable`` 。 + +因此,在动转静时,我们在需要在**某个统一的入口处**,将动态图 ``Layers`` 中 ``Tensor`` 类型(包含具体数据)的 ``Weight`` 、``Bias`` 等变量转换为**同名的静态图 ``Variable``**。 + ++ ParamBase → Parameters ++ VarBase → Variable + +技术实现上,我们选取了框架层面两个地方作为类型**转换的入口**: + ++ ``Paddle.nn.Layer`` 基类的 ``__call__`` 函数 + ```python + def __call__(self, *inputs, **kwargs): + # param_guard 会对将 Tensor 类型的 Param 和 buffer 转为静态图 Variable + with param_guard(self._parameters), param_guard(self._buffers): + # ... forward_pre_hook 逻辑 + + outputs = self.forward(*inputs, **kwargs) # 此处为forward函数 + + # ... forward_post_hook 逻辑 + + return outputs + ``` + ++ ``Block.append_op`` 函数中,生成 ``Op`` 之前 + ```python + def append_op(self, *args, **kwargs): + if in_dygraph_mode(): + # ... (动态图分支) + else: + inputs=kwargs.get("inputs", None) + outputs=kwargs.get("outputs", None) + # param_guard 会确保将 Tensor 类型的 inputs 和 outputs 转为静态图 Variable + with param_guard(inputs), param_guard(outputs): + op = Operator( + block=self, + desc=op_desc, + type=kwargs.get("type", None), + inputs=inputs, + outputs=outputs, + attrs=kwargs.get("attrs", None)) + ``` + + +以上,是动态图转为静态图的两个核心逻辑,总结如下: + ++ 动态图 ``layer`` 调用在动转静时会走底层 ``append_op`` 的分支,以生成 ``Program`` ++ 动态图 ``Tensor`` 转为静态图 ``Variable`` ,并确保编译期的 ``InferShape`` 正确执行 + + +### 5.3 Buffer 变量 + +**什么是 ``Buffers`` 变量?** + ++ **Parameters**:``persistable`` 为 ``True`` ,且每个 batch 都被 Optimizer 更新的变量 ++ **Buffers**:``persistable`` 为 ``True`` ,``is_trainable = False`` ,不参与更新,但与预测相关;如 ``BatchNorm`` 层中的均值和方差 + +在动态图模型代码中,若一个 ``paddle.to_tensor`` 接口生成的 ``Tensor`` 参与了最终预测结果的的计算,则此 ``Tensor`` 需要在转换为静态图预测模型时,也需要作为一个 ``persistable`` 的变量保存到 ``.pdiparam`` 文件中。 + +**举一个例子(错误写法):** + +```python +import paddle +from paddle.jit import to_static + +class SimpleNet(paddle.nn.Layer): + def __init__(self, mask): + super(SimpleNet, self).__init__() + self.linear = paddle.nn.Linear(10, 3) + + # mask value,此处不会保存到预测模型文件中 + self.mask = mask # 假设为 [0, 1, 1] + + def forward(self, x, y): + out = self.linear(x) + out = out + y + mask = paddle.to_tensor(self.mask) # <----- 每次执行都转为一个 Tensor + out = out * mask + return out +``` + + +**推荐的写法是:** + +```python +class SimpleNet(paddle.nn.Layer): + def __init__(self, mask): + super(SimpleNet, self).__init__() + self.linear = paddle.nn.Linear(10, 3) + + # 此处的 mask 会当做一个 buffer Tensor,保存到 .pdiparam 文件 + self.mask = paddle.to_tensor(mask) # 假设为 [0, 1, 1] + + def forward(self, x, y): + out = self.linear(x) + out = out + y + out = out * self.mask # <---- 直接使用 self.mask + return out +``` + + +总结一下 ``buffers`` 的用法: + ++ 若某个非 ``Tensor`` 数据需要当做 ``Persistable`` 的变量序列化到磁盘,则最好在 ``__init__`` 中调用 ``self.XX= paddle.to_tensor(xx)`` 接口转为 ``buffer`` 变量 diff --git a/docs/guides/04_dygraph_to_static/basic_usage_cn.rst b/docs/guides/04_dygraph_to_static/basic_usage_cn.rst deleted file mode 100644 index 9c7b75e5b77..00000000000 --- a/docs/guides/04_dygraph_to_static/basic_usage_cn.rst +++ /dev/null @@ -1,140 +0,0 @@ -基本用法 -============== - -PaddlePaddle主要的动转静方式是基于源代码级别转换的ProgramTranslator。其基本原理是通过分析Python代码来将动态图代码转写为静态图代码,并在底层自动帮用户使用静态图执行器运行。这种转换方式使得用户可以灵活使用Python语法及其控制流来构建神经网络模型。除此之外,PaddlePaddle另外提供一种基于trace的动转静接口TracedLayer。若遇到ProgramTranslator不支持但是可以用TracedLayer运行的情况,可以作为备选方案。 - -基于源代码转写的ProgramTranslator ------------------------------------ - -源代码转写的ProgramTranslator进行动态图转静态图,其基本原理是通过分析Python代码来将动态图代码转写为静态图代码,并在底层自动帮用户使用执行器运行。其基本使用方法十分简便,只需要在要转化的函数(该函数也可以是用户自定义动态图Layer的forward函数)前添加一个装饰器 ``@paddle.jit.to_static`` ,一个转化例子如下,可以直接运行被装饰函数得到结果: - -.. code-block:: python - - import paddle - import numpy as np - - @paddle.jit.to_static - def func(input_var): - # if判断与输入input_var的shape有关 - if input_var.shape[0] > 1: - out = paddle.cast(input_var, "float64") - else: - out = paddle.cast(input_var, "int64") - return out - - in_np = np.array([-2]).astype('int') - input_var = paddle.to_tensor(in_np) - func(input_var) - - -若要存储转化后的静态图模型,可以调用 ``paddle.jit.save`` ,我们定义一个简单全连接网络SimpleFcLayer,需要在下面SimpleFcLayer的forward函数添加装饰器: - -.. code-block:: python - - import numpy as np - import paddle - - class SimpleFcLayer(paddle.nn.Layer): - def __init__(self, batch_size, feature_size, fc_size): - super(SimpleFcLayer, self).__init__() - self._linear = paddle.nn.Linear(feature_size, fc_size) - self._offset = paddle.to_tensor( - np.random.random((batch_size, fc_size)).astype('float32')) - - @paddle.jit.to_static - def forward(self, x): - fc = self._linear(x) - return fc + self._offset - - -存储该模型可以使用 ``paddle.jit.save`` 接口: - -.. code-block:: python - - import paddle - - fc_layer = SimpleFcLayer(3, 4, 2) - in_np = np.random.random([3, 4]).astype('float32') - input_var = paddle.to_tensor(in_np) - out = fc_layer(input_var) - - paddle.jit.save(fc_layer, "./fc_layer_dy2stat", input_spec=[input_var]) - - -基于trace的TracedLayer ------------------------- - -trace是指在模型运行时记录下其运行过哪些算子。TracedLayer就是基于这种技术,在一次执行动态图的过程中,记录所有运行的算子,并构建和保存静态图模型。一个使用例子如下: - -我们还是定义一个简单的全连接网络作为例子,注意这里不需要像ProgramTranslator在forward函数添加装饰器: - -.. code-block:: python - - import numpy as np - import paddle - - class SimpleFcLayer(paddle.nn.Layer): - def __init__(self, batch_size, feature_size, fc_size): - super(SimpleFcLayer, self).__init__() - self._linear = paddle.nn.Linear(feature_size, fc_size) - self._offset = paddle.to_tensor( - np.random.random((batch_size, fc_size)).astype('float32')) - - def forward(self, x): - fc = self._linear(x) - return fc + self._offset - - -接下来是TracedLayer如何存储模型: - -.. code-block:: python - - import paddle - from paddle.jit import TracedLayer - - fc_layer = SimpleFcLayer(3, 4, 2) - in_np = np.random.random([3, 4]).astype('float32') - # 将numpy的ndarray类型的数据转换为Tensor类型 - input_var = paddle.to_tensor(in_np) - # 通过 TracerLayer.trace 接口将命令式模型转换为声明式模型 - out_dygraph, static_layer = TracedLayer.trace(fc_layer, inputs=[input_var]) - save_dirname = './saved_infer_model' - # 将转换后的模型保存 - static_layer.save_inference_model(save_dirname, feed=[0], fetch=[0]) - - -载入的模型可以使用静态图方式运行 - -.. code-block:: python - - paddle.enable_static() - place = paddle.CPUPlace() - exe = paddle.static.Executor(place) - program, feed_vars, fetch_vars = paddle.static.load_inference_model(save_dirname, exe) - fetch, = exe.run(program, feed={feed_vars[0]: in_np}, fetch_list=fetch_vars) - - -但是也正如我们阐述的原理,trace只是记录了一次执行涉及的算子。若在用户的模型代码中,包含了依赖数据条件(包括输入的值或者shape)的控制流分支,即根据数据条件触发运行不同的算子,则TracedLayer无法正常工作。比如下面: - -.. code-block:: python - - import paddle - - def func(input_var): - # if判断与输入input_var的shape有关 - if input_var.shape[0] > 1: - return paddle.cast(input_var, "float64") - else: - return paddle.cast(input_var, "int64") - - in_np = np.array([-2]).astype('int') - input_var = paddle.to_tensor(in_np) - out = func(input_var) - - -如果对上述样例中的 ``func`` 使用 ``TracedLayer.trace(func, inputs=[input_var])`` ,由于trace只能记录if-else其中跑的一次算子,模型就无法按用户想要的根据input_var的形状进行if-else控制流保存。类似的控制流还有while/for循环的情况。 - -比较ProgramTranslator和TracedLayer ------------------------------------- -基于源代码转换的ProgramTranslator对比基于trace的TracedLayer,前者能够处理依赖数据条件的控制流分支。因此我们更推荐用户使用ProgramTranslator,如果遇到问题再以TracedLayer作为备选方案。 - diff --git a/docs/guides/04_dygraph_to_static/export_model/principle_cn.md b/docs/guides/04_dygraph_to_static/basic_usage_en.md similarity index 93% rename from docs/guides/04_dygraph_to_static/export_model/principle_cn.md rename to docs/guides/04_dygraph_to_static/basic_usage_en.md index 97fb409cff8..97adbb26586 100644 --- a/docs/guides/04_dygraph_to_static/export_model/principle_cn.md +++ b/docs/guides/04_dygraph_to_static/basic_usage_en.md @@ -1,8 +1,10 @@ -# 基本原理 +# 基本用法 ## 一、 @to_static概览 +动静转换(@to_static)通过解析 Python代码(抽象语法树,下简称:AST) 实现一行代码即可转为静态图功能,即只需在待转化的函数前添加一个装饰器 ``@paddle.jit.to_static`` 。 + 如下是一个使用 @to_static 装饰器的 ``Model`` 示例: ```python @@ -14,7 +16,7 @@ class SimpleNet(paddle.nn.Layer): super(SimpleNet, self).__init__() self.linear = paddle.nn.Linear(10, 3) - # 方式一:装饰 forward 函数 + # 方式一:装饰 forward 函数(支持训练) @to_static def forward(self, x, y): out = self.linear(x) @@ -26,6 +28,12 @@ net = SimpleNet() net = paddle.jit.to_static(net) # 动静转换 ``` +动转静 @to_static 除了支持预测模型导出,还兼容转为静态图子图训练。仅需要在 ``forward`` 函数上添加此装饰器即可,不需要修改任何其他的代码。 + +基本执行流程如下: + + + ### 1.1 动态图 layer 生成 Program @@ -243,7 +251,7 @@ def add_two(x, y): + **只有**控制流的判断条件**依赖了 ``Tensor`` **(如 ``shape`` 或 ``value`` ),才会转写为对应 Op -![image](./images/convert_cond.png) + @@ -324,7 +332,7 @@ def depend_tensor_if(x): ``` -``conver_ifelse`` 是框架底层的函数,在逐行执行用户代码生成 ``Program`` 时,执行到此处时,会根据**判断条件**的类型( ``bool`` 还是 ``Bool Tensor`` ),自适应决定是否转为 ``cond_op`` 。 +``convert_ifelse`` 是框架底层的函数,在逐行执行用户代码生成 ``Program`` 时,执行到此处时,会根据**判断条件**的类型( ``bool`` 还是 ``Bool Tensor`` ),自适应决定是否转为 ``cond_op`` 。 ```python def convert_ifelse(pred, true_fn, false_fn, true_args, false_args, return_vars): @@ -464,11 +472,3 @@ class SimpleNet(paddle.nn.Layer): + 若某个非 ``Tensor`` 数据需要当做 ``Persistable`` 的变量序列化到磁盘,则最好在 ``__init__`` 中调用 ``self.XX= paddle.to_tensor(xx)`` 接口转为 ``buffer`` 变量 -## 六、Program 执行与训练 - -动转静 @to_static 除了支持预测模型导出,还兼容转为静态图子图训练。仅需要在 ``forward`` 函数上添加此装饰器即可。 - -基本执行流程如下: - - -![image](./images/to_static_train.png) diff --git a/docs/guides/04_dygraph_to_static/basic_usage_en.rst b/docs/guides/04_dygraph_to_static/basic_usage_en.rst deleted file mode 100644 index c31912fa53b..00000000000 --- a/docs/guides/04_dygraph_to_static/basic_usage_en.rst +++ /dev/null @@ -1,134 +0,0 @@ -Basic Usage -============= - -The recommended way to transform dygraph to static graph is source-code-translate based ProgramTranslator. The basic idea is analyzing Python source code and turning into static graph code, then run the static graph code using Executor. Users could use Python syntax including control flow to build neural networks. Besides, PaddlePaddle has another tracing-based API for transforming dygraph to static graph which called TracedLayer. You can use it as a back-up API in case ProgramTranslator has problem. - -ProgramTranslator -------------------- - -The basic idea of source-code-translate based ProgramTranslator is analyzing Python source code and turning it into static graph code, then run the static graph code using Executor. The basic usage of ProgramTranslator is simple, put a decorator ``@paddle.jit.to_static`` before the definition of the function to transform (the function can also be a method of a class, e.g., the ``forward`` function of user-defined imperative Layer). An example is: - -.. code-block:: python - - import paddle - - @paddle.jit.to_static - def func(input_var): - # if condition depends on the shape of input_var - if input_var.shape[0] > 1: - out = paddle.cast(input_var, "float64") - else: - out = paddle.cast(input_var, "int64") - return out - - in_np = np.array([-2]).astype('int') - input_var = paddle.to_tensor(in_np) - func(input_var) - -To save the transformed model, we can call ``paddle.jit.save`` . Let's take a fully connected network called ``SimpleFcLayer`` as an example, we put decorator at the ``forward`` method of ``SimpleFcLayer`` : - -.. code-block:: python - - import numpy as np - import paddle - - class SimpleFcLayer(paddle.nn.Layer): - def __init__(self, batch_size, feature_size, fc_size): - super(SimpleFcLayer, self).__init__() - self._linear = paddle.nn.Linear(feature_size, fc_size) - self._offset = paddle.to_tensor( - np.random.random((batch_size, fc_size)).astype('float32')) - - @paddle.jit.to_static - def forward(self, x): - fc = self._linear(x) - return fc + self._offset - - -Call ``paddle.jit.save`` to save above model: - -.. code-block:: python - - import paddle - - fc_layer = SimpleFcLayer(3, 4, 2) - in_np = np.random.random([3, 4]).astype('float32') - input_var = paddle.to_tensor(in_np) - out = fc_layer(input_var) - - paddle.jit.save(fc_layer, "./fc_layer_dy2stat") - - -TracedLayer -------------- - -Tracing means recording the operators when running a model. TracedLayer is based on this technique. It runs dygraph program once and records all operators, then constructs static graph model and saves it. Now take a glance at an usage example: - -Define a simple fully connected network, note that we don't add a decorator before ``forward`` function as we did in ProgramTranslator example: - -.. code-block:: python - - import numpy as np - import paddle - - class SimpleFcLayer(paddle.nn.Layer): - def __init__(self, batch_size, feature_size, fc_size): - super(SimpleFcLayer, self).__init__() - self._linear = paddle.nn.Linear(feature_size, fc_size) - self._offset = paddle.to_tensor( - np.random.random((batch_size, fc_size)).astype('float32')) - - def forward(self, x): - fc = self._linear(x) - return fc + self._offset - -Save model by TracedLayer: - -.. code-block:: python - - import paddle - from paddle.jit import TracedLayer - - fc_layer = SimpleFcLayer(3, 4, 2) - in_np = np.random.random([3, 4]).astype('float32') - # Turn numpy ndarray into Tensor - input_var = paddle.to_tensor(in_np) - # Transforming imperative mode into declarative mode by TracerLayer.trace - out_dygraph, static_layer = TracedLayer.trace(fc_layer, inputs=[input_var]) - save_dirname = './saved_infer_model' - # Save the transformed model - static_layer.save_inference_model(save_dirname, feed=[0], fetch=[0]) - -Load model and run it in static graph mode: - -.. code-block:: python - - place = paddle.CPUPlace() - exe = paddle.static.Executor(place) - program, feed_vars, fetch_vars = paddle.static.load_inference_model(save_dirname, exe) - fetch, = exe.run(program, feed={feed_vars[0]: in_np}, fetch_list=fetch_vars) - -However, as tracing only records operators once, if user's code contains Tensor-dependent (including Tensor value or Tensor shape) control flow, that is the Tensor can cause different operators being executed, then TracedLayer cannot handle this case. For instance: - -.. code-block:: python - - import paddle - - def func(input_var): - # if condition depends on the shape of input_var - if input_var.shape[0] > 1: - return paddle.cast(input_var, "float64") - else: - return paddle.cast(input_var, "int64") - - in_np = np.array([-2]).astype('int') - input_var = paddle.to_tensor(in_np) - out = func(input_var) - -If we apply TracedLayer.trace(func, inputs=[input_var]) on above example, tracing can take record of operators in only one branch of if-else, then the model can not be saved as what user orignally means. The similar situations applies to while/for loop. - -Comparing ProgramTranslator and TracedLayer -------------------------------------------- - -Compared to tracing-based TracedLayer, source-code-translate based ProgramTranslator can handle the Tensor-dependent control flow. So we recommend users to use ProgramTranslator, use TracedLayer as a back-up plan when ProgramTranslator doesn't work. - diff --git a/docs/guides/04_dygraph_to_static/export_model/case_analysis_cn.md b/docs/guides/04_dygraph_to_static/case_analysis_cn.md similarity index 92% rename from docs/guides/04_dygraph_to_static/export_model/case_analysis_cn.md rename to docs/guides/04_dygraph_to_static/case_analysis_cn.md index e4ba7568489..4f1d09cf0fc 100644 --- a/docs/guides/04_dygraph_to_static/export_model/case_analysis_cn.md +++ b/docs/guides/04_dygraph_to_static/case_analysis_cn.md @@ -1,14 +1,14 @@ -# 案例解析 +# 常见案例解析 -上一节我们介绍了动转静的主要机制,下面会结合一些具体的模型代码,解答动转静中比较常见的问题。 +在[【基础接口用法】](./basic_usage_cn.html)章节我们介绍了动转静的用法和机制,下面会结合一些具体的模型代码,解答动转静中比较常见的问题。 ## 一、 @to_static 放在哪里? ``@to_static`` 装饰器开启动转静功能的唯一接口,支持两种使用方式: -+ 方式一(推荐用法):显式地通过 ``model = to_static(model)`` 调用 ++ **方式一(推荐用法)**:显式地通过 ``model = to_static(model)`` 调用 ```python from paddle.jit import to_static @@ -17,7 +17,7 @@ ``` -+ 方式二:在组网代码的 ``forward`` 函数处装饰 ++ **方式二**:在组网代码的 ``forward`` 函数处装饰 ```python class SimpleNet(paddle.nn.Layer): def __init__(self, ...): @@ -77,7 +77,7 @@ -> 注:InputSpec 接口的高阶用法,请参看 [【官方文档】InputSpec 功能介绍](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/guides/04_dygraph_to_static/input_spec_cn.html) +> 注:InputSpec 接口的高阶用法,请参看 [【InputSpec 功能介绍】](./input_spec_cn.html#inputspec) ## 三、内嵌 Numpy 操作? @@ -193,9 +193,9 @@ class SimpleNet(paddle.nn.Layer): 动态图模型常常包含很多嵌套的子网络,建议各个自定义的子网络 ``sublayer`` **无论是否包含了参数,都继承 ``nn.Layer`` .** -从 **Parmaters 和 Buffers** 章节可知,有些 ``paddle.to_tensor`` 接口转来的 ``Tensor`` 也可能参与预测逻辑分支的计算,即模型导出时,也需要作为参数序列化保存到 ``.pdparams`` 文件中。 +从 **Parameters 和 Buffers** 章节可知,有些 ``paddle.to_tensor`` 接口转来的 ``Tensor`` 也可能参与预测逻辑分支的计算,即模型导出时,也需要作为参数序列化保存到 ``.pdiparams`` 文件中。 -> **原因**: 若某个 sublayer 包含了 buffer Variables,但却没有继承 ``nn.Layer`` ,则可能导致保存的 ``.pdparams`` 文件缺失部分重要参数。 +> **原因**: 若某个 sublayer 包含了 buffer Variables,但却没有继承 ``nn.Layer`` ,则可能导致保存的 ``.pdiparams`` 文件缺失部分重要参数。 **举个例子:** @@ -204,7 +204,7 @@ class SimpleNet(object): # <---- 继承 Object def __init__(self, mask): super(SimpleNet, self).__init__() self.linear = paddle.nn.Linear(10, 3) # <---- Linear 参数永远都不会被更新 - self.mask = paddle.to_tensor(mask) # <---- mask 可能未保存到 .pdparams 文件中 + self.mask = paddle.to_tensor(mask) # <---- mask 可能未保存到 .pdiparams 文件中 def forward(self, x, y): out = self.linear(x) @@ -275,7 +275,7 @@ jit.save(mode, model_path) ## 七、再谈控制流 -前面提到,不论控制流 ``if/for/while`` 语句是否需要转为静态图中的 ``cond_op/while_op`` ,都会先进行代码规范化,如 ``IfElse`` 语句会规范为如下范式: +前面[【控制流转写】(./basic_usage_cn.html#sikongzhiliuzhuanxie)]提到,不论控制流 ``if/for/while`` 语句是否需要转为静态图中的 ``cond_op/while_op`` ,都会先进行代码规范化,如 ``IfElse`` 语句会规范为如下范式: ```python def true_fn_0(out): @@ -370,7 +370,7 @@ def forward(x): 如上面的例子: ```python -def foward(self, x): +def forward(self, x): bs = paddle.shape(x)[0] # <---- x.shape[0] 表示 batch_size,动态shape outs = [] for i in range(bs): @@ -411,5 +411,4 @@ path = "example.model/linear" paddle.jit.save(layer, path) # <---- Lazy mode, 此处才会触发 Program 的转换 ``` - > 更多用法可以参考:[【官网文档】jit.save](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/api/paddle/jit/save_cn.html#save) diff --git a/docs/guides/04_dygraph_to_static/export_model/basic_concept_cn.md b/docs/guides/04_dygraph_to_static/export_model/basic_concept_cn.md deleted file mode 100644 index 9e48fd1f1a4..00000000000 --- a/docs/guides/04_dygraph_to_static/export_model/basic_concept_cn.md +++ /dev/null @@ -1,184 +0,0 @@ -# 框架概念 - - -## 一、动转静预测部署 - -动态图由于其与 Python 语法契合的易用性,逐步成为各主流框架的默认模式。但这也带来了在非 Python 环境下的部署问题,需要将动态图的 Python 语句转为可以跨语言、跨平台部署的静态图来部署。 - -动转静模块**是架在动态图与静态图的一个桥梁**,旨在打破动态图与静态部署的鸿沟,消除部署时对模型代码的依赖,打通与预测端的交互逻辑。 - -![image](./images/to_static_export.png) - - - -在处理逻辑上,动转静主要包含两个主要模块: - -+ **代码层面**:将所有的 Paddle ``layers`` 接口在静态图模式下执行以转为 ``Op`` ,从而生成完整的静态 ``Program`` -+ **Tensor层面**:将所有的 ``Parameters`` 和 ``Buffers`` 转为**可导出的 ``Variable`` 参数**( ``persistable=True`` ) - -> 关于动转静模块的具体原理,可以参考 [基本原理](./principle_cn.html);搭配 `paddle.jit.save` 接口导出预测模型的用法案例,可以参考 [案例解析](./case_analysis_cn.html) 。 - -如下两小节,将介绍动态图和静态图的概念和差异性,以帮助理解动转静如何起到**桥梁作用**的。 - -## 二、动态图预测部署 - -2.0 版本后,Paddle 默认开启了动态图模式。动态图模式下编程组网更加灵活,也更 Pythonic 。在动态图下,模型代码是 **逐行被解释执行** 的。如: - -```python -import paddle - -zeros = paddle.zeros(shape=[1,2], dtype='float32') -print(zeros) - -#Tensor(shape=[1, 2], dtype=float32, place=CPUPlace, stop_gradient=True, -# [[0., 0.]]) -``` - - -**从框架层面上,上述的调用链是:** - -> 前端 zeros 接口 → core.ops.fill_constant (Pybind11) → 后端 Kernel → 前端 Tensor 输出 - -如下是一个简单的 Model 示例: - -```python - -import paddle - -class 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 + y - return out - -net = SimpleNet() -``` - -动态图下,当实例化一个 ``SimpleNet()`` 对象时,隐式地执行了如下几个步骤: - -+ 创建一个 ``Linear`` 对象,记录到 ``self._sub_layer`` 中(dict 类型) - - + 创建一个 ``ParamBase`` 类型的 ``weight`` ,记录到 ``self._parameters`` 中(dict类型) - + 创建一个 ``ParamBase`` 类型的 ``bias`` ,记录到 ``self._parameters`` 中 - -一个复杂模型可能包含很多子类,框架层就是通过 ``self._sub_layer`` 和 ``self._parameters`` 两个核心数据结构关联起来的,这也是后续动转静原理上操作的两个核心属性。 - -```python -sgd = paddle.optimizer.SGD(learning_rate=0.1, parameters=net.parameters()) - ^ - | - 所有待更新参数 -``` - -## 三、静态图预测部署 - -**静态图编程,总体上包含两个部分:** - -+ **编译期**:组合各个 ``Layer`` 接口,搭建网络结构,执行每个 Op 的 ``InferShape`` 逻辑,最终生成 ``Program`` -+ **执行期**:构建执行器,输入数据,依次执行每个 ``OpKernel`` ,进行训练和评估 - -在静态图编译期,变量 ``Variable`` 只是**一个符号化表示**,并不像动态图 ``Tensor`` 那样持有实际数据。 - -```python -import paddle -# 开启静态图模式 -paddle.enable_static() - -zeros = paddle.zeros(shape=[1,2], dtype='float32') -print(zeros) -# var fill_constant_1.tmp_0 : LOD_TENSOR.shape(1, 2).dtype(float32).stop_gradient(True) -``` - -**从框架层面上,静态图的调用链:** - -> layer 组网(前端) → InferShape 检查(编译期) → Executor(执行期) → 逐个执行 OP - - -如下是 ``SimpleNet`` 的静态图模式下的组网代码: - -```python -import paddle -# 开启静态图模式 -paddle.enable_static() - -# placeholder 信息 -x = paddle.static.data(shape=[None, 10], dtype='float32', name='x') -y = paddle.static.data(shape=[None, 3], dtype='float32', name='y') - -out = paddle.static.nn.fc(x, 3) -out = paddle.add(out, y) -# 打印查看 Program 信息 -print(paddle.static.default_main_program()) - -# { // block 0 -# var x : LOD_TENSOR.shape(-1, 10).dtype(float32).stop_gradient(True) -# var y : LOD_TENSOR.shape(-1, 3).dtype(float32).stop_gradient(True) -# persist trainable param fc_0.w_0 : LOD_TENSOR.shape(10, 3).dtype(float32).stop_gradient(False) -# var fc_0.tmp_0 : LOD_TENSOR.shape(-1, 3).dtype(float32).stop_gradient(False) -# persist trainable param fc_0.b_0 : LOD_TENSOR.shape(3,).dtype(float32).stop_gradient(False) -# var fc_0.tmp_1 : LOD_TENSOR.shape(-1, 3).dtype(float32).stop_gradient(False) -# var elementwise_add_0 : LOD_TENSOR.shape(-1, 3).dtype(float32).stop_gradient(False) - -# {Out=['fc_0.tmp_0']} = mul(inputs={X=['x'], Y=['fc_0.w_0']}, force_fp32_output = False, op_device = , op_namescope = /, op_role = 0, op_role_var = [], scale_out = 1.0, scale_x = 1.0, scale_y = [1.0], use_mkldnn = False, x_num_col_dims = 1, y_num_col_dims = 1) -# {Out=['fc_0.tmp_1']} = elementwise_add(inputs={X=['fc_0.tmp_0'], Y=['fc_0.b_0']}, Scale_out = 1.0, Scale_x = 1.0, Scale_y = 1.0, axis = 1, mkldnn_data_type = float32, op_device = , op_namescope = /, op_role = 0, op_role_var = [], use_mkldnn = False, use_quantizer = False, x_data_format = , y_data_format = ) -# {Out=['elementwise_add_0']} = elementwise_add(inputs={X=['fc_0.tmp_1'], Y=['y']}, Scale_out = 1.0, Scale_x = 1.0, Scale_y = 1.0, axis = -1, mkldnn_data_type = float32, op_device = , op_namescope = /, op_role = 0, op_role_var = [], use_mkldnn = False, use_quantizer = False, x_data_format = , y_data_format = ) -} -``` - - -静态图中的一些概念: - -+ **Program**:与 ``Model`` 对应,描述网络的整体结构,内含一个或多个 ``Block`` -+ **Block** - + **global_block**:全局 ``Block`` ,包含所有 ``Parameters`` 、全部 ``Ops`` 和 ``Variables`` - + **sub_block**:控制流,包含控制流分支内的所有 ``Ops`` 和必要的 ``Variables`` -+ **OpDesc**:对应每个前端 API 的计算逻辑描述 -+ **Variable**:对应所有的数据变量,如 ``Parameter`` ,临时中间变量等,全局唯一 ``name`` 。 - -## 三、模型和参数 - -当训练完一个模型后,下一阶段就是保存导出,实现**模型**和**参数**的分发,进行多端部署。 - -动态图下,**模型**指的是 Python 前端代码;**参数**指的是 ``model.state_dict()`` 中存放的权重数据。 - -```python -net = SimpleNet() - -# .... 训练过程(略) - -layer_state_dict = net.state_dict() -paddle.save(layer_state_dict, "net.pdparams") # 导出模型 -``` - - -即意味着,动态图预测部署时,除了已经序列化的参数文件,还须提供**最初的模型组网代码**。 - -![image](./images/dygraph_export.png) - - - -静态图下,**模型**指的是 ``Program`` ;参数指的是所有的 ``Persistable=True`` 的 ``Variable`` 。二者都可以序列化导出为磁盘文件,**与前端代码完全解耦**。 - -```python -main_program = paddle.static.default_main_program() - -# ...... 训练过程(略) - -prog_path='main_program.pdmodel' -paddle.save(main_program, prog_path) # 导出为 .pdmodel - -para_path='main_program.pdparams' -paddle.save(main_program.state_dict(), para_path) # 导出为 .pdparams -``` - -![image](./images/static_export.png) - - -即意味着, ``Program`` 中包含了模型所有的计算描述( ``OpDesc`` ),不存在计算逻辑有遗漏的地方。 - - -> 注:更多细节,请参考 [【官方文档】模型的存储与载入](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/guides/02_paddle2.0_develop/08_model_save_load_cn.html)。 diff --git a/docs/guides/04_dygraph_to_static/export_model/images/convert_cond.png b/docs/guides/04_dygraph_to_static/export_model/images/convert_cond.png deleted file mode 100644 index bcd0881afe7..00000000000 Binary files a/docs/guides/04_dygraph_to_static/export_model/images/convert_cond.png and /dev/null differ diff --git a/docs/guides/04_dygraph_to_static/export_model/images/dygraph_export.png b/docs/guides/04_dygraph_to_static/export_model/images/dygraph_export.png deleted file mode 100644 index fa5cef88d18..00000000000 Binary files a/docs/guides/04_dygraph_to_static/export_model/images/dygraph_export.png and /dev/null differ diff --git a/docs/guides/04_dygraph_to_static/export_model/images/static_export.png b/docs/guides/04_dygraph_to_static/export_model/images/static_export.png deleted file mode 100644 index 9a399675605..00000000000 Binary files a/docs/guides/04_dygraph_to_static/export_model/images/static_export.png and /dev/null differ diff --git a/docs/guides/04_dygraph_to_static/export_model/images/to_static_export.png b/docs/guides/04_dygraph_to_static/export_model/images/to_static_export.png deleted file mode 100644 index 4f93e85ab03..00000000000 Binary files a/docs/guides/04_dygraph_to_static/export_model/images/to_static_export.png and /dev/null differ diff --git a/docs/guides/04_dygraph_to_static/export_model/images/to_static_train.png b/docs/guides/04_dygraph_to_static/export_model/images/to_static_train.png deleted file mode 100644 index 77606f9fb0f..00000000000 Binary files a/docs/guides/04_dygraph_to_static/export_model/images/to_static_train.png and /dev/null differ diff --git a/docs/guides/04_dygraph_to_static/export_model/index_cn.rst b/docs/guides/04_dygraph_to_static/export_model/index_cn.rst deleted file mode 100644 index 20323e28809..00000000000 --- a/docs/guides/04_dygraph_to_static/export_model/index_cn.rst +++ /dev/null @@ -1,17 +0,0 @@ -################### -预测模型导出教程 -################### - -您将会在这里了解如何通过动转静模块导出预测模型: - -- `框架概念 <./basic_concept_cn.html>`_ : 介绍 Paddle 框架层关于静态图和动态图的一些基础概念。 -- `基本原理 <./principle_cn.html>`_ : 介绍 @to_static 在导出预测模型时的基本工作原理。 -- `案例解析 <./case_analysis_cn.html>`_ : 介绍 @to_static 导出预测模型时常见的问题和用法解析。 - -.. toctree:: - :hidden: - - basic_concept_cn.md - principle_cn.rst - case_analysis_cn.rst - case_debug_cn.rst diff --git a/docs/guides/04_dygraph_to_static/export_model_cn.md b/docs/guides/04_dygraph_to_static/export_model_cn.md new file mode 100644 index 00000000000..44e5dea3c73 --- /dev/null +++ b/docs/guides/04_dygraph_to_static/export_model_cn.md @@ -0,0 +1,472 @@ +# 预测模型导出 + + +## 一、动转静模型导出 + +动转静模块**是架在动态图与静态图的一个桥梁**,旨在打破动态图与静态部署的鸿沟,消除部署时对模型代码的依赖,打通与预测端的交互逻辑。 + + + + + +在处理逻辑上,动转静主要包含两个主要模块: + ++ **代码层面**:将所有的 Paddle ``layers`` 接口在静态图模式下执行以转为 ``Op`` ,从而生成完整的静态 ``Program`` ++ **Tensor层面**:将所有的 ``Parameters`` 和 ``Buffers`` 转为**可导出的 ``Variable`` 参数**( ``persistable=True`` ) + + +### 1.1 forward 函数导出 + +如下是一个简单的 ``Model`` 的代码: + +```python +import paddle +from paddle.jit import to_static +from paddle.static import InputSpec + +class 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 + y + return out + + def another_func(self, x): + out = self.linear(x) + out = out * 2 + return out + +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]) # 动静转换 +``` + +执行上述代码样例后,在当前目录下会生成三个文件: +``` +simple_net.pdiparams // 存放模型中所有的权重数据 +simple_net.pdimodel // 存放模型的网络结构 +simple_net.pdiparams.info // 存放额外的其他信息 +``` + + +预测模型导出一般包括三个步骤: + ++ **切换 `eval()` 模式**:类似 `Dropout` 、`LayerNorm` 等接口在 `train()` 和 `eval()` 的行为存在较大的差异,在模型导出前,**请务必确认模型已切换到正确的模式,否则导出的模型在预测阶段可能出现输出结果不符合预期的情况。** ++ **构造 `InputSpec` 信息**:InputSpec 用于表示输入的shape、dtype、name信息,且支持用 `None` 表示动态shape(如输入的 batch_size 维度),是辅助动静转换的必要描述信息。 ++ **调用 `save` 接口**:调用 `paddle.jit.save`接口,若传入的参数是类实例,则默认对 `forward` 函数进行 `@to_static` 装饰,并导出其对应的模型文件和参数文件。 + + +### 1.2 其他函数导出 + +`@to_static` 与 `jit.save` 接口搭配也支持导出非forward 的其他函数,具体使用方式如下: + +```python +# SimpleNet 类的定义见 1.1 + +net = SimpleNet() +# train(net) # 模型训练 + +# step 1: 切换到 eval() 模式 (同上) +net.eval() + +# step 2: 定义 InputSpec 信息 (同上) +x_spec = InputSpec(shape=[None, 3], dtype='float32', name='x') + +# step 3: @to_static 装饰 +static_func = to_static(net.another_func, input_spec=[x_spec]) + +# step 4: 调用 jit.save 接口 +net = paddle.jit.save(static_func, path='another_func') +``` + +使用上的区别主要在于: + ++ **`@to_static` 装饰**:导出其他函数时需要显式地用 `@to_static` 装饰,以告知动静转换模块将其识别、并转为静态图 Program; ++ **`save`接口参数**:调用`jit.save`接口时,需将上述被`@to_static` 装饰后的函数作为**参数**; + +执行上述代码样例后,在当前目录下会生成三个文件: +``` +another_func.pdiparams // 存放模型中所有的权重数据 +another_func.pdimodel // 存放模型的网络结构 +another_func.pdiparams.info // 存放额外的其他信息 +``` + + +> 关于动转静 @to_static 的用法,可以参考 [基本用法](./basic_usage_cn.html);搭配 `paddle.jit.save` 接口导出预测模型的用法案例,可以参考 [案例解析](./case_analysis_cn.html) 。 + + +### 1.3 InputSpec 功能介绍 + +动静转换在生成静态图 Program 时,依赖输入 Tensor 的 shape、dtype 和 name 信息。因此,Paddle 提供了 InputSpec 接口,用于指定输入 Tensor 的描述信息,并支持动态 shape 特性。 + + +#### 1.3.1 InputSpec 构造 + + +**方式一:直接构造** + + +InputSpec 接口在 ``paddle.static`` 目录下, 只有 ``shape`` 是必须参数, ``dtype`` 和 ``name`` 可以缺省,默认取值分别为 ``float32`` 和 ``None`` 。使用样例如下: + +```python +from paddle.static import InputSpec + +x = InputSpec([None, 784], 'float32', 'x') +label = InputSpec([None, 1], 'int64', 'label') + +print(x) # InputSpec(shape=(-1, 784), dtype=VarType.FP32, name=x) +print(label) # InputSpec(shape=(-1, 1), dtype=VarType.INT64, name=label) +``` + + +**方式二:由 Tensor 构造** + +可以借助 ``InputSpec.from_tensor`` 方法,从一个 Tensor 直接创建 InputSpec 对象,其拥有与源 Tensor 相同的 ``shape`` 和 ``dtype`` 。 使用样例如下: + +```python +import numpy as np +import paddle +from paddle.static import InputSpec + +x = paddle.to_tensor(np.ones([2, 2], np.float32)) +x_spec = InputSpec.from_tensor(x, name='x') +print(x_spec) # InputSpec(shape=(2, 2), dtype=VarType.FP32, name=x) +``` + +> 注:若未在 ``from_tensor`` 中指定新的name,则默认使用与源Tensor相同的name。 + + +**方式三:由 numpy.ndarray** + +也可以借助 ``InputSpec.from_numpy`` 方法,从一个 `Numpy.ndarray` 直接创建 InputSpec 对象,其拥有与源 ndarray 相同的 ``shape`` 和 ``dtype`` 。使用样例如下: + +```python +import numpy as np +from paddle.static import InputSpec + +x = np.ones([2, 2], np.float32) +x_spec = InputSpec.from_numpy(x, name='x') +print(x_spec) # InputSpec(shape=(2, 2), dtype=VarType.FP32, name=x) +``` + +> 注:若未在 ``from_numpy`` 中指定新的 name,则默认使用 None 。 + + +#### 1.3.2 基本用法 + +**方式一: @to_static 装饰器模式** + +如下是一个简单的使用样例: + +```python +import paddle +from paddle.jit import to_static +from paddle.static import InputSpec +from paddle.fluid.dygraph import Layer + +class SimpleNet(Layer): + def __init__(self): + super(SimpleNet, self).__init__() + self.linear = paddle.nn.Linear(10, 3) + + @to_static(input_spec=[InputSpec(shape=[None, 10], name='x'), InputSpec(shape=[3], name='y')]) + def forward(self, x, y): + out = self.linear(x) + out = out + y + return out + +net = SimpleNet() + +# save static model for inference directly +paddle.jit.save(net, './simple_net') +``` + +在上述的样例中, ``@to_static`` 装饰器中的 ``input_spec`` 为一个 InputSpec 对象组成的列表,用于依次指定参数 x 和 y 对应的 Tensor 签名信息。在实例化 SimpleNet 后,可以直接调用 ``paddle.jit.save`` 保存静态图模型,不需要执行任何其他的代码。 + +> 注: +> 1. input_spec 参数中不仅支持 InputSpec 对象,也支持 int 、 float 等常见 Python 原生类型。 +> 2. 若指定 input_spec 参数,则需为被装饰函数的所有必选参数都添加对应的 InputSpec 对象,如上述样例中,不支持仅指定 x 的签名信息。 +> 3. 若被装饰函数中包括非 Tensor 参数,推荐函数的非 Tensor 参数设置默认值,如 ``forward(self, x, use_bn=False)`` + + +**方式二:to_static函数调用** + +若期望在动态图下训练模型,在训练完成后保存预测模型,并指定预测时需要的签名信息,则可以选择在保存模型时,直接调用 ``to_static`` 函数。使用样例如下: + +```python +class SimpleNet(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 + y + return out + +net = SimpleNet() + +# train process (Pseudo code) +for epoch_id in range(10): + train_step(net, train_reader) + +net = to_static(net, input_spec=[InputSpec(shape=[None, 10], name='x'), InputSpec(shape=[3], name='y')]) + +# save static model for inference directly +paddle.jit.save(net, './simple_net') +``` + +如上述样例代码中,在完成训练后,可以借助 ``to_static(net, input_spec=...)`` 形式对模型实例进行处理。Paddle 会根据 input_spec 信息对 forward 函数进行递归的动转静,得到完整的静态图,且包括当前训练好的参数数据。 + + +**方式三:支持 list 和 dict 推导** + +上述两个样例中,被装饰的 forward 函数的参数均为 Tensor 。这种情况下,参数个数必须与 InputSpec 个数相同。但当被装饰的函数参数为 list 或 dict 类型时,``input_spec`` 需要与函数参数保持相同的嵌套结构。 + +当函数的参数为 list 类型时,input_spec 列表中对应元素的位置,也必须是包含相同元素的 InputSpec 列表。使用样例如下: + +```python +class SimpleNet(Layer): + def __init__(self): + super(SimpleNet, self).__init__() + self.linear = paddle.nn.Linear(10, 3) + + @to_static(input_spec=[[InputSpec(shape=[None, 10], name='x'), InputSpec(shape=[3], name='y')]]) + def forward(self, inputs): + x, y = inputs[0], inputs[1] + out = self.linear(x) + out = out + y + return out +``` + +其中 ``input_spec`` 参数是长度为 1 的 list ,对应 forward 函数的 inputs 参数。 ``input_spec[0]`` 包含了两个 InputSpec 对象,对应于参数 inputs 的两个 Tensor 签名信息。 + +当函数的参数为dict时, ``input_spec`` 列表中对应元素的位置,也必须是包含相同键(key)的 InputSpec 列表。使用样例如下: + +```python +class SimpleNet(Layer): + def __init__(self): + super(SimpleNet, self).__init__() + self.linear = paddle.nn.Linear(10, 3) + + @to_static(input_spec=[InputSpec(shape=[None, 10], name='x'), {'x': InputSpec(shape=[3], name='bias')}]) + def forward(self, x, bias_info): + x_bias = bias_info['x'] + out = self.linear(x) + out = out + x_bias + return out +``` + +其中 ``input_spec`` 参数是长度为 2 的 list ,对应 forward 函数的 x 和 bias_info 两个参数。 ``input_spec`` 的最后一个元素是包含键名为 x 的 InputSpec 对象的 dict ,对应参数 bias_info 的 Tensor 签名信息。 + + +**方式四:指定非Tensor参数类型** + +目前,``to_static`` 装饰器中的 ``input_spec`` 参数仅接收 ``InputSpec`` 类型对象。若被装饰函数的参数列表除了 Tensor 类型,还包含其他如 Int、 String 等非 Tensor 类型时,推荐在函数中使用 kwargs 形式定义非 Tensor 参数,如下述样例中的 use_act 参数。 + +```python + +class SimpleNet(Layer): + def __init__(self, ): + super(SimpleNet, self).__init__() + self.linear = paddle.nn.Linear(10, 3) + self.relu = paddle.nn.ReLU() + + def forward(self, x, use_act=False): + out = self.linear(x) + if use_act: + out = self.relu(out) + return out + +net = SimpleNet() +# 方式一:save inference model with use_act=False +net = to_static(input_spec=[InputSpec(shape=[None, 10], name='x')]) +paddle.jit.save(net, path='./simple_net') + + +# 方式二:save inference model with use_act=True +net = to_static(input_spec=[InputSpec(shape=[None, 10], name='x'), True]) +paddle.jit.save(net, path='./simple_net') +``` + + +在上述样例中,假设 step 为奇数时,use_act 取值为 False ; step 为偶数时, use_act 取值为 True 。动转静支持非 Tensor 参数在训练时取不同的值,且保证了取值不同的训练过程都可以更新模型的网络参数,行为与动态图一致。 + +在借助 ``paddle.jit.save`` 保存预测模型时,动转静会根据 input_spec 和 kwargs 的默认值保存推理模型和网络参数。**建议将 kwargs 参数默认值设置为预测时的取值。** + + +更多关于动转静 ``to_static`` 搭配 ``paddle.jit.save/load`` 的使用方式,可以参考 [【模型的存储与载入】](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/guides/02_paddle2.0_develop/08_model_save_load_cn.html)。 + + +## 二、动、静态图部署区别 + +当训练完一个模型后,下一阶段就是保存导出,实现**模型**和**参数**的分发,进行多端部署。如下两小节,将介绍动态图和静态图的概念和差异性,以帮助理解动转静如何起到**桥梁作用**的。 +### 2.1 动态图预测部署 + +动态图下,**模型**指的是 Python 前端代码;**参数**指的是 ``model.state_dict()`` 中存放的权重数据。 + +```python +net = SimpleNet() + +# .... 训练过程(略) + +layer_state_dict = net.state_dict() +paddle.save(layer_state_dict, "net.pdiparams") # 导出模型 +``` + + + +即意味着,动态图预测部署时,除了已经序列化的参数文件,还须提供**最初的模型组网代码**。 + +在动态图下,模型代码是 **逐行被解释执行** 的。如: + +```python +import paddle + +zeros = paddle.zeros(shape=[1,2], dtype='float32') +print(zeros) + +#Tensor(shape=[1, 2], dtype=float32, place=CPUPlace, stop_gradient=True, +# [[0., 0.]]) +``` + + +**从框架层面上,上述的调用链是:** + +> 前端 zeros 接口 → core.ops.fill_constant (Pybind11) → 后端 Kernel → 前端 Tensor 输出 + +如下是一个简单的 Model 示例: + +```python + +import paddle + +class 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 + y + return out + +net = SimpleNet() +``` + +动态图下,当实例化一个 ``SimpleNet()`` 对象时,隐式地执行了如下几个步骤: + ++ 创建一个 ``Linear`` 对象,记录到 ``self._sub_layer`` 中(dict 类型) + + + 创建一个 ``ParamBase`` 类型的 ``weight`` ,记录到 ``self._parameters`` 中(dict类型) + + 创建一个 ``ParamBase`` 类型的 ``bias`` ,记录到 ``self._parameters`` 中 + +一个复杂模型可能包含很多子类,框架层就是通过 ``self._sub_layer`` 和 ``self._parameters`` 两个核心数据结构关联起来的,这也是后续动转静原理上操作的两个核心属性。 + +```python +sgd = paddle.optimizer.SGD(learning_rate=0.1, parameters=net.parameters()) + ^ + | + 所有待更新参数 +``` + +### 2.2 静态图预测部署 + +静态图部署时,**模型**指的是 ``Program`` ;参数指的是所有的 ``Persistable=True`` 的 ``Variable`` 。二者都可以序列化导出为磁盘文件,**与前端代码完全解耦**。 + +```python +main_program = paddle.static.default_main_program() + +# ...... 训练过程(略) + +prog_path='main_program.pdimodel' +paddle.save(main_program, prog_path) # 导出为 .pdimodel + +para_path='main_program.pdiparams' +paddle.save(main_program.state_dict(), para_path) # 导出为 .pdiparams +``` + + + + +即意味着, ``Program`` 中包含了模型所有的计算描述( ``OpDesc`` ),不存在计算逻辑有遗漏的地方。 + + +**静态图编程,总体上包含两个部分:** + ++ **编译期**:组合各个 ``Layer`` 接口,搭建网络结构,执行每个 Op 的 ``InferShape`` 逻辑,最终生成 ``Program`` ++ **执行期**:构建执行器,输入数据,依次执行每个 ``OpKernel`` ,进行训练和评估 + +在静态图编译期,变量 ``Variable`` 只是**一个符号化表示**,并不像动态图 ``Tensor`` 那样持有实际数据。 + +```python +import paddle +# 开启静态图模式 +paddle.enable_static() + +zeros = paddle.zeros(shape=[1,2], dtype='float32') +print(zeros) +# var fill_constant_1.tmp_0 : LOD_TENSOR.shape(1, 2).dtype(float32).stop_gradient(True) +``` + +**从框架层面上,静态图的调用链:** + +> layer 组网(前端) → InferShape 检查(编译期) → Executor(执行期) → 逐个执行 OP + + +如下是 ``SimpleNet`` 的静态图模式下的组网代码: + +```python +import paddle +# 开启静态图模式 +paddle.enable_static() + +# placeholder 信息 +x = paddle.static.data(shape=[None, 10], dtype='float32', name='x') +y = paddle.static.data(shape=[None, 3], dtype='float32', name='y') + +out = paddle.static.nn.fc(x, 3) +out = paddle.add(out, y) +# 打印查看 Program 信息 +print(paddle.static.default_main_program()) + +# { // block 0 +# var x : LOD_TENSOR.shape(-1, 10).dtype(float32).stop_gradient(True) +# var y : LOD_TENSOR.shape(-1, 3).dtype(float32).stop_gradient(True) +# persist trainable param fc_0.w_0 : LOD_TENSOR.shape(10, 3).dtype(float32).stop_gradient(False) +# var fc_0.tmp_0 : LOD_TENSOR.shape(-1, 3).dtype(float32).stop_gradient(False) +# persist trainable param fc_0.b_0 : LOD_TENSOR.shape(3,).dtype(float32).stop_gradient(False) +# var fc_0.tmp_1 : LOD_TENSOR.shape(-1, 3).dtype(float32).stop_gradient(False) +# var elementwise_add_0 : LOD_TENSOR.shape(-1, 3).dtype(float32).stop_gradient(False) + +# {Out=['fc_0.tmp_0']} = mul(inputs={X=['x'], Y=['fc_0.w_0']}, force_fp32_output = False, op_device = , op_namescope = /, op_role = 0, op_role_var = [], scale_out = 1.0, scale_x = 1.0, scale_y = [1.0], use_mkldnn = False, x_num_col_dims = 1, y_num_col_dims = 1) +# {Out=['fc_0.tmp_1']} = elementwise_add(inputs={X=['fc_0.tmp_0'], Y=['fc_0.b_0']}, Scale_out = 1.0, Scale_x = 1.0, Scale_y = 1.0, axis = 1, mkldnn_data_type = float32, op_device = , op_namescope = /, op_role = 0, op_role_var = [], use_mkldnn = False, use_quantizer = False, x_data_format = , y_data_format = ) +# {Out=['elementwise_add_0']} = elementwise_add(inputs={X=['fc_0.tmp_1'], Y=['y']}, Scale_out = 1.0, Scale_x = 1.0, Scale_y = 1.0, axis = -1, mkldnn_data_type = float32, op_device = , op_namescope = /, op_role = 0, op_role_var = [], use_mkldnn = False, use_quantizer = False, x_data_format = , y_data_format = ) +} +``` + + +静态图中的一些概念: + ++ **Program**:与 ``Model`` 对应,描述网络的整体结构,内含一个或多个 ``Block`` ++ **Block** + + **global_block**:全局 ``Block`` ,包含所有 ``Parameters`` 、全部 ``Ops`` 和 ``Variables`` + + **sub_block**:控制流,包含控制流分支内的所有 ``Ops`` 和必要的 ``Variables`` ++ **OpDesc**:对应每个前端 API 的计算逻辑描述 ++ **Variable**:对应所有的数据变量,如 ``Parameter`` ,临时中间变量等,全局唯一 ``name`` 。 + + + +> 注:更多细节,请参考 [【官方文档】模型的存储与载入](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/guides/02_paddle2.0_develop/08_model_save_load_cn.html)。 diff --git a/docs/guides/04_dygraph_to_static/grammar_list_cn.md b/docs/guides/04_dygraph_to_static/grammar_list_cn.md index 65396e47cd7..a82c4a47163 100644 --- a/docs/guides/04_dygraph_to_static/grammar_list_cn.md +++ b/docs/guides/04_dygraph_to_static/grammar_list_cn.md @@ -1,6 +1,6 @@ # 语法支持列表 -## 主要针对场景 +## 一、主要针对场景 本文档概览性介绍了飞桨动转静功能的语法支持情况,旨在向用户提供一个便捷的语法速查表,**主要适用于如下场景**: @@ -11,13 +11,13 @@ 3. 当出现不支持的语法时,如何修改源码适配动转静语法 -若您初次接触动转静功能,或对此功能尚不熟悉,推荐您阅读:[动转静入门文档](https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/04_dygraph_to_static/basic_usage_cn.html); +若您初次接触动转静功能,或对此功能尚不熟悉,推荐您阅读:[基础接口用法](./basic_usage_cn.html); -若您对动转静原理感兴趣,或想深入了解此模块,推荐您阅读:[动转静基础原理](https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/04_dygraph_to_static/program_translator_cn.html); +若您想进行预测模型导出,或想深入了解此模块,推荐您阅读:[预测模型导出](./export_model_cn.html); -若您想进行模型预测导出,或想了解调试的技巧,推荐您阅读:[预测模型导出教程](https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/04_dygraph_to_static/export_model/index_cn.html)。 +若您动静转换遇到了问题,或想学习调试的技巧,推荐您阅读:[报错调试经验](./debugging_cn.html)。 -## 语法支持速查列表 +## 二、语法支持速查列表 |分类 |python语法 | 是否
支持 | 概要 | |:---:|:---:|:---:|:---:| @@ -46,10 +46,10 @@ -## 详细说明 +## 三、详细说明 -### if-else +### 3.1 if-else **主要逻辑:** @@ -73,7 +73,7 @@ -### while循环 +### 3.2 while循环 **主要逻辑:** @@ -88,7 +88,7 @@ -### for 循环 +### 3.3 for 循环 **主要逻辑:** @@ -118,7 +118,7 @@ def ForTensor(x): -### 流程控制语句说明 (return / break / continue) +### 3.4 流程控制语句说明 (return / break / continue) @@ -142,7 +142,7 @@ def break_usage(x): > 注:这里虽然idx是-1,但是返回值还是Tensor。因为`tensor_idx` 在 while loop中转化为了`Tensor`。 -### 与、或、非 +### 3.5 与、或、非 **主要逻辑:** @@ -165,7 +165,7 @@ def and(x, y): return z ``` -### 类型转换运算符 +### 3.6 类型转换运算符 **主要逻辑:** @@ -183,7 +183,7 @@ def float_convert(x): ``` -### 对一些python函数调用的转换 +### 3.7 对一些python函数调用的转换 **主要逻辑:** @@ -226,7 +226,7 @@ def recur_call(x): return recur_call(x * x) # < ------ 如果输入是 x = Tensor([2.0]) ,动态图输出为 Tensor([16]),静态图会出现调用栈溢出 ``` -### List和Dict容器 +### 3.8 List和Dict容器 **主要逻辑:** @@ -262,7 +262,7 @@ def sort_list(x, y): return a ``` -### paddle shape函数 +### 3.9 paddle shape函数 **主要逻辑:** diff --git a/docs/guides/04_dygraph_to_static/grammar_list_en.md b/docs/guides/04_dygraph_to_static/grammar_list_en.md index 512d7120ba6..88d3099f664 100644 --- a/docs/guides/04_dygraph_to_static/grammar_list_en.md +++ b/docs/guides/04_dygraph_to_static/grammar_list_en.md @@ -13,9 +13,9 @@ This article is mainly for the following scene : If you are new to the dynamic-to-static module, or are not familiar with this function, you are recommended to read [Introduction to dynamic and static documents](https://www.paddlepaddle.org.cn/documentation/docs/en/guides/04_dygraph_to_static/basic_usage_en.html) ; -If you are interested in the principle of dynamic-to-static, or want to learn more about this module, you are recommended to read: [Basic Principle of Dynamic and Static](https://www.paddlepaddle.org.cn/documentation/docs/en/guides/04_dygraph_to_static/program_translator_en.html) ; +If you want to export model for prediction, or want to learn more about this module, you are recommended to read: [Predictive Model Export Tutorial](https://www.paddlepaddle.org.cn/documentation/docs/en/guides/04_dygraph_to_static/export_model_en.html) ; -If you want to export model for prediction, or want to learn about debugging skills, you are recommended to read [Predictive Model Export Tutorial](https://www.paddlepaddle.org.cn/documentation/docs/en/guides/index_en.html). +If you encounter problems with @to_static, or want to learn about debugging skills, you are recommended to read [Error Debugging Experience](https://www.paddlepaddle.org.cn/documentation/docs/en/guides/debugging_en.html). ## Supported Grammars diff --git a/docs/guides/04_dygraph_to_static/index_cn.rst b/docs/guides/04_dygraph_to_static/index_cn.rst index 15e2db7e274..b54141fa883 100644 --- a/docs/guides/04_dygraph_to_static/index_cn.rst +++ b/docs/guides/04_dygraph_to_static/index_cn.rst @@ -2,38 +2,30 @@ 动态图转静态图 ############### -动态图有诸多优点,包括易用的接口,Python风格的编程体验,友好的debug交互机制等。在动态图模式下,代码是按照我们编写的顺序依次执行。这种机制更符合Python程序员的习 -惯,可以很方便地将大脑中的想法快速地转化为实际代码,也更容易调试。但在性能方面, -Python执行开销较大,与C++有一定差距。因此在工业界的许多部署场景中(如大型推荐系统、移动端)都倾向于直接使用C++来提速。 +动态图在接口易用性,交互式调试等方面具有诸多优势,但在工业界的许多部署场景中(如大型推荐系统、移动端)Python执行开销较大,与C++有一定的差距,静态图部署更具优势。 -相比动态图,静态图在部署方面更具有性能的优势。静态图程序在编译执行时,先搭建模型 -的神经网络结构,然后再对神经网络执行计算操作。预先搭建好的神经网络可以脱离Python依赖,在C++端被重新解析执行,而且拥有整体网络结构也能进行一些网络结构的优化。 +PaddlePaddle 在2.0版本之后,正式支持动态图转静态图(@to_static)的功能,对动态图代码进行智能化分析,自动转换为静态图网络结构,兼顾了动态图易用性和静态图部署性能两方面的优势。 -动态图代码更易编写和debug,但在部署性能上,静态图更具优势。因此我们新增了动态图转静态图的功能,支持用户依然使用动态图编写组网代码。PaddlePaddle会对用户代码进行 -分析,自动转换为静态图网络结构,兼顾了动态图易用性和静态图部署性能两方面优势。 +如下将详细地介绍动静转换的各个模块内容: -我们在以下链接介绍PaddlePaddle动态图转静态图的各个部分: +- `基础接口用法 `_ : 介绍了动静转换 @to_static 的基本用法 -- `基本用法 `_ : 介绍了动态图转静态图的基本使用方法 +- `语法支持列表 `_ :介绍了动静转换功能已支持的语法概况 -- `内部架构原理 `_ :介绍了动态图转静态图的架构原理 +- `预测模型导出 <./export_model/index_cn.html>`_ :介绍了导出动态图预测模型的详细教程 -- `支持语法列表 `_ :介绍了动态图转静态图支持的语法以及罗列不支持的语法写法 +- `常见案例解析 <./case_analysis_cn.html>`_ : 介绍使用 @to_static 时常见的问题和案例解析 -- `InputSpec功能介绍 `_ :介绍了动态图转静态图指定输入InputSpec的功能和用法 +- `报错调试经验 `_ :介绍了动静转换 @to_static 的调试方法和经验 -- `报错调试经验 `_ :介绍了动态图转静态图支持的调试方法 - -- `预测模型导出教程 <./export_model/index_cn.html>`_ :介绍了如何导出预测模型的详细教程 .. toctree:: :hidden: basic_usage_cn.rst - program_translator_cn.rst grammar_list_cn.md - input_spec_cn.rst + export_model_cn.md + case_analysis_cn.md debugging_cn.md - export_model/index_cn.rst diff --git a/docs/guides/04_dygraph_to_static/index_en.rst b/docs/guides/04_dygraph_to_static/index_en.rst index e0134c02b50..3dcdc63ceb8 100644 --- a/docs/guides/04_dygraph_to_static/index_en.rst +++ b/docs/guides/04_dygraph_to_static/index_en.rst @@ -10,23 +10,23 @@ While dygraph has usability and debug benefits and static graph yields performan We introduce the transformation of dygraph to static graph in the following links: -- `Basic Usage `_ : Introduce the basic usage for transforming dygraph code into static code. +- `Basic Usage `_ : Introduce the basic usage for @to_static. -- `Architecture `_ : Introduce the architecture of ProgramTranslator. +- `Supported Grammars `_ : Introduce supported grammars and unsupported grammars . -- `Supported Grammars `_ : Introduce the grammars supported by ProgramTranslator and list unsupported grammars. +- `Predictive Model Export Tutorial `_ : Introduce the tutorial for exporting predictive model. -- `Introduction of InputSpec `_ : Introduce the usage of InputSpec to specify the input signature from dygraph to static program. +- `Case analysis of InputSpec `_ : Introduce the common case studies of @to_static. -- `Error Debugging Experience `_ : Introduce the debugging methods when using ProgramTranslator. +- `Error Debugging Experience `_ : Introduce the debugging methods when using @to_static. .. toctree:: :hidden: basic_usage_en.rst - program_translator_en.rst grammar_list_en.md - input_spec_en.rst + export_model_en.md + case_analysis_en.md debugging_en.md diff --git a/docs/guides/04_dygraph_to_static/input_spec_cn.rst b/docs/guides/04_dygraph_to_static/input_spec_cn.rst deleted file mode 100644 index fd87a37a523..00000000000 --- a/docs/guides/04_dygraph_to_static/input_spec_cn.rst +++ /dev/null @@ -1,236 +0,0 @@ -.. _user_guide_dy2sta_input_spec_cn: - -InputSpec 功能介绍 -================= - - -在PaddlePaddle(下文简称:Paddle)框架中,可以通过 ``paddle.jit.to_static`` 装饰普通函数或 Layer 的最外层 forward 函数,将动态图模型转换为静态图执行。但在动转静时,需要给模型传入 Tensor 数据并执行一次前向,以保证正确地推导出网络中各 Tensor 的 shape 。此转换流程需要显式地执行一次动态图函数,增加了接口使用的成本;同时,传入实际 Tensor 数据则无法定制化模型输入的shape,如指定某些维度为 None 。 - -因此,Paddle 提供了 InputSpec 接口,可以更加便捷地执行动转静功能,以及定制化输入 Tensor 的 shape 、name 等信息。 - - -一、InputSpec 对象构造方法 -------------------------- - -1.1 直接构造 InputSpec 对象 -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -InputSpec 接口在 ``paddle.static`` 目录下,用于描述一个 Tensor 的签名信息:shape、dtype、name。使用样例如下: - -.. code-block:: python - - from paddle.static import InputSpec - - x = InputSpec([None, 784], 'float32', 'x') - label = InputSpec([None, 1], 'int64', 'label') - - print(x) # InputSpec(shape=(-1, 784), dtype=VarType.FP32, name=x) - print(label) # InputSpec(shape=(-1, 1), dtype=VarType.INT64, name=label) - - -InputSpec 初始化中的只有 ``shape`` 是必须参数, ``dtype`` 和 ``name`` 可以缺省,默认取值分别为 ``float32`` 和 ``None`` 。 - - - -1.2 根据 Tensor 构造 InputSpec 对象 -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -可以借助 ``InputSpec.from_tensor`` 方法,从一个 Tensor 直接创建 InputSpec 对象,其拥有与源 Tensor 相同的 ``shape`` 和 ``dtype`` 。使用样例如下: - -.. code-block:: python - - import numpy as np - import paddle - from paddle.static import InputSpec - - x = paddle.to_tensor(np.ones([2, 2], np.float32)) - x_spec = InputSpec.from_tensor(x, name='x') - print(x_spec) # InputSpec(shape=(2, 2), dtype=VarType.FP32, name=x) - - -.. note:: - 若未在 ``from_tensor`` 中指定新的name,则默认使用与源Tensor相同的name。 - - -1.3 根据 numpy.ndarray 构造 InputSpec 对象 -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -也可以借助 ``InputSpec.from_numpy`` 方法,从一个 Numpy.ndarray 直接创建 InputSpec 对象,其拥有与源 ndarray 相同的 ``shape`` 和 ``dtype`` 。使用样例如下: - -.. code-block:: python - - import numpy as np - from paddle.static import InputSpec - - x = np.ones([2, 2], np.float32) - x_spec = InputSpec.from_numpy(x, name='x') - print(x_spec) # InputSpec(shape=(2, 2), dtype=VarType.FP32, name=x) - - -.. note:: - 若未在 ``from_numpy`` 中指定新的 name,则默认使用 None 。 - - -二、基本使用方法 ------------------- - -动转静 ``paddle.jit.to_static`` 装饰器支持 ``input_spec`` 参数,用于指定被装饰函数每个 Tensor 类型输入参数的 ``shape`` 、 ``dtype`` 、 ``name`` 等签名信息。不必再显式地传入 Tensor 数据以触发网络层 shape 的推导。 Paddle 会解析 ``to_static`` 中指定的 ``input_spec`` 参数,构建网络的起始输入,进行后续的模型组网。 - -同时,借助 ``input_spec`` 参数,可以自定义输入 Tensor 的 shape ,比如指定 shape 为 ``[None, 784]`` ,其中 ``None`` 表示变长的维度。 - -2.1 to_static 装饰器模式 -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -如下是一个简单的使用样例: - -.. code-block:: python - - import paddle - from paddle.jit import to_static - from paddle.static import InputSpec - from paddle.fluid.dygraph import Layer - - class SimpleNet(Layer): - def __init__(self): - super(SimpleNet, self).__init__() - self.linear = paddle.nn.Linear(10, 3) - - @to_static(input_spec=[InputSpec(shape=[None, 10], name='x'), InputSpec(shape=[3], name='y')]) - def forward(self, x, y): - out = self.linear(x) - out = out + y - return out - - net = SimpleNet() - - # save static model for inference directly - paddle.jit.save(net, './simple_net') - - -在上述的样例中, ``to_static`` 装饰器中的 ``input_spec`` 为一个 InputSpec 对象组成的列表,用于依次指定参数 x 和 y 对应的 Tensor 签名信息。在实例化 SimpleNet 后,可以直接调用 ``paddle.jit.save`` 保存静态图模型,不需要执行任何其他的代码。 - -.. note:: - 1. input_spec 参数中只支持 InputSpec 对象,暂不支持如 int 、 float 等类型。 - 2. 若指定 input_spec 参数,则需为被装饰函数的所有必选参数都添加对应的 InputSpec 对象,如上述样例中,不支持仅指定 x 的签名信息。 - 3. 若被装饰函数中包括非 Tensor 参数,且指定了 input_spec ,请确保函数的非 Tensor 参数都有默认值,如 ``forward(self, x, use_bn=False)`` - - -2.2 to_static函数调用 -^^^^^^^^^^^^^^^^^^^^ - -若期望在动态图下训练模型,在训练完成后保存预测模型,并指定预测时需要的签名信息,则可以选择在保存模型时,直接调用 ``to_static`` 函数。使用样例如下: - -.. code-block:: python - - class SimpleNet(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 + y - return out - - net = SimpleNet() - - # train process (Pseudo code) - for epoch_id in range(10): - train_step(net, train_reader) - - net = to_static(net, input_spec=[InputSpec(shape=[None, 10], name='x'), InputSpec(shape=[3], name='y')]) - - # save static model for inference directly - paddle.jit.save(net, './simple_net') - - -如上述样例代码中,在完成训练后,可以借助 ``to_static(net, input_spec=...)`` 形式对模型实例进行处理。Paddle 会根据 input_spec 信息对 forward 函数进行递归的动转静,得到完整的静态图,且包括当前训练好的参数数据。 - - -2.3 支持 list 和 dict 推导 -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -上述两个样例中,被装饰的 forward 函数的参数均为 Tensor 。这种情况下,参数个数必须与 InputSpec 个数相同。但当被装饰的函数参数为list或dict类型时,``input_spec`` 需要与函数参数保持相同的嵌套结构。 - -当函数的参数为 list 类型时,input_spec 列表中对应元素的位置,也必须是包含相同元素的 InputSpec 列表。使用样例如下: - -.. code-block:: python - - class SimpleNet(Layer): - def __init__(self): - super(SimpleNet, self).__init__() - self.linear = paddle.nn.Linear(10, 3) - - @to_static(input_spec=[[InputSpec(shape=[None, 10], name='x'), InputSpec(shape=[3], name='y')]]) - def forward(self, inputs): - x, y = inputs[0], inputs[1] - out = self.linear(x) - out = out + y - return out - - -其中 ``input_spec`` 参数是长度为 1 的 list ,对应 forward 函数的 inputs 参数。 ``input_spec[0]`` 包含了两个 InputSpec 对象,对应于参数 inputs 的两个 Tensor 签名信息。 - -当函数的参数为dict时, ``input_spec`` 列表中对应元素的位置,也必须是包含相同键(key)的 InputSpec 列表。使用样例如下: - -.. code-block:: python - - class SimpleNet(Layer): - def __init__(self): - super(SimpleNet, self).__init__() - self.linear = paddle.nn.Linear(10, 3) - - @to_static(input_spec=[InputSpec(shape=[None, 10], name='x'), {'x': InputSpec(shape=[3], name='bias')}]) - def forward(self, x, bias_info): - x_bias = bias_info['x'] - out = self.linear(x) - out = out + x_bias - return out - - -其中 ``input_spec`` 参数是长度为 2 的 list ,对应 forward 函数的 x 和 bias_info 两个参数。 ``input_spec`` 的最后一个元素是包含键名为 x 的 InputSpec 对象的 dict ,对应参数 bias_info 的 Tensor 签名信息。 - - -2.4 指定非Tensor参数类型 -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -目前,``to_static`` 装饰器中的 ``input_spec`` 参数仅接收 ``InputSpec`` 类型对象。若被装饰函数的参数列表除了 Tensor 类型,还包含其他如 Int、 String 等非 Tensor 类型时,推荐在函数中使用 kwargs 形式定义非 Tensor 参数,如下述样例中的 use_act 参数。 - -.. code-block:: python - - class SimpleNet(Layer): - def __init__(self, ): - super(SimpleNet, self).__init__() - self.linear = paddle.nn.Linear(10, 3) - self.relu = paddle.nn.ReLU() - - @to_static(input_spec=[InputSpec(shape=[None, 10], name='x')]) - def forward(self, x, use_act=False): - out = self.linear(x) - if use_act: - out = self.relu(out) - return out - - net = SimpleNet() - adam = paddle.optimizer.Adam(parameters=net.parameters()) - - # train model - batch_num = 10 - for step in range(batch_num): - x = paddle.rand([4, 10], 'float32') - use_act = (step%2 == 0) - out = net(x, use_act) - loss = paddle.mean(out) - loss.backward() - adam.minimize(loss) - net.clear_gradients() - - # save inference model with use_act=False - paddle.jit.save(net, path='./simple_net') - - -在上述样例中,step 为奇数时,use_act 取值为 False ; step 为偶数时, use_act 取值为 True 。动转静支持非 Tensor 参数在训练时取不同的值,且保证了取值不同的训练过程都可以更新模型的网络参数,行为与动态图一致。 - -kwargs 参数的默认值主要用于保存推理模型。在借助 ``paddle.jit.save`` 保存预测模型时,动转静会根据 input_spec 和 kwargs 的默认值保存推理模型和网络参数。因此建议将 kwargs 参数默认值设置为预测时的取值。 - -更多关于动转静 ``to_static`` 搭配 ``paddle.jit.save/load`` 的使用方式,可以参考 :ref:`cn_doc_model_save_load` 。 diff --git a/docs/guides/04_dygraph_to_static/input_spec_en.rst b/docs/guides/04_dygraph_to_static/input_spec_en.rst deleted file mode 100644 index efddcc59194..00000000000 --- a/docs/guides/04_dygraph_to_static/input_spec_en.rst +++ /dev/null @@ -1,233 +0,0 @@ -.. _user_guide_dy2sta_input_spec_cn: - -Introduction of InputSpec -=========================== - - -In PaddlePaddle(Referred to as "Paddle"), The dygraph model can be converted to static program by decorating the outermost forward function of Layer with ``paddle.jit.to_static`` . But actual Tensor data should be feeded into the model to ensure that the shape of each Tensor in the network is correctly deduced in transformation. This transformation process needs to explicitly execute the forward function, which increases the cost of the interface. Meanwhile, the way that need feed Tensor data fails to customize the shape of inputs, such as assigning some dimensions to None. - -Therefore, Paddle provides the InputSpec interface to perform the transformation more easily, and supports to customize the signature of input Tensor, such as shape, name and so on. - - -1. InputSpec interface -------------------------- - -1.1 Construct InputSpec object -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The InputSpec interface is under the ``paddle.static`` directory. It's used to describe the Tensor's signature information: shape, dtype, name. See example as follows: - -.. code-block:: python - - from paddle.static import InputSpec - - x = InputSpec([None, 784], 'float32', 'x') - label = InputSpec([None, 1], 'int64', 'label') - - print(x) # InputSpec(shape=(-1, 784), dtype=VarType.FP32, name=x) - print(label) # InputSpec(shape=(-1, 1), dtype=VarType.INT64, name=label) - - -In InputSpec initialization, only ``shape`` is a required parameter. ``dtype`` and ``name`` can be default with values ``Float32`` and ``None`` respectively. - - - -1.2 Constructed from Tensor -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -An InputSpec object can be created directly from a Tensor by using ``inputSpec.from_tensor`` method. It has same ``shape`` and ``dtype`` as the source Tensor. See example as follows: - -.. code-block:: python - - import numpy as np - import paddle - from paddle.static import InputSpec - - x = paddle.to_tensor(np.ones([2, 2], np.float32)) - x_spec = InputSpec.from_tensor(x, name='x') - print(x_spec) # InputSpec(shape=(2, 2), dtype=VarType.FP32, name=x) - - -.. note:: - If a new name is not specified in ``from_tensor`` , the name from source Tensor is used by default. - - -1.3 Constructed from numpy.ndarray -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -An InputSpec object can also be created directly from an Numpy.ndarray by using the ``inputSpec.from_numpy`` method. It has same ``shape`` and ``dtype`` as the source ndarray. See example as follows: - -.. code-block:: python - - import numpy as np - from paddle.static import InputSpec - - x = np.ones([2, 2], np.float32) - x_spec = InputSpec.from_numpy(x, name='x') - print(x_spec) # InputSpec(shape=(2, 2), dtype=VarType.FP32, name=x) - - -.. note:: - If a new name is not specified in ``from_numpy`` , ``None`` is used by default. - - -2. Basic usage ------------------- - -Currently, the decorator ``paddle.jit.to_static`` support ``input_spec`` argument. It is used to specify signature information such as ``shape`` , ``dtype`` , ``name`` for each Tensor corresponding to argument from decorated function. Users do not have to feed actual data explicitly to trigger the deduction of the shape in the network. The ``input_spec`` argument specified in ``to_static`` will be analyzed to construct input placeholder of the network. - -At the same time, the ``input_spec`` allow us to easily define input Tensor shape. For example, specifying shape as ``[None, 784]`` , where ``None`` represents a variable length dimension. - -2.1 Decorator mode of to_static -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -A simple example as follows: - -.. code-block:: python - - import paddle - from paddle.jit import to_static - from paddle.static import InputSpec - from paddle.fluid.dygraph import Layer - - class SimpleNet(Layer): - def __init__(self): - super(SimpleNet, self).__init__() - self.linear = paddle.nn.Linear(10, 3) - - @to_static(input_spec=[InputSpec(shape=[None, 10], name='x'), InputSpec(shape=[3], name='y')]) - def forward(self, x, y): - out = self.linear(x) - out = out + y - return out - - net = SimpleNet() - - # save static model for inference directly - paddle.jit.save(net, './simple_net') - - -In the above example, ``input_spec`` in ``to_static`` decorator is a list of InputSpec objects. It is used to specify signature information corresponding x and y. After instantiating SimpleNet, ``paddle.jit.save`` can be directly called to save the static graph model without executing any other code. - -.. note:: - 1. Only InputSpec objects are supported in input_spec argument, and types such as int, float, etc. are not supported temporarily. - 2. If you specify the input_spec argument, you need to add the corresponding InputSpec object for all non-default parameters of the decorated function. As above sample, only specifying signature information x is not supported. - 3. If the decorated function includes non-tensor parameters and input_spec is specified, make sure that the non-tensor parameters of the function have default values, such as ``forward(self, x, use_bn=False)`` . - - -2.2 Call to_static directly -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -If we want to train model in dygraph mode and only expect to save the inference model after training with specified the signature information. We can call ``to_static`` function directly while saving the model. See example as follows: - -.. code-block:: python - - class SimpleNet(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 + y - return out - - net = SimpleNet() - - # train process (Pseudo code) - for epoch_id in range(10): - train_step(net, train_reader) - - net = to_static(net, input_spec=[InputSpec(shape=[None, 10], name='x'), InputSpec(shape=[3], name='y')]) - - # save static model for inference directly - paddle.jit.save(net, './simple_net') - -In the above example, ``to_static(net, input_spec=...)`` can be used to process the model after training. Paddle will recursively convert forward function to get the complete static program according to ``input_spec`` information. Meanwhile, it includes the trained parameters. - - -2.3 Support list and dict derivation -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -In the above two examples, the arguments of the decorated forward function correspond to the InputSpec one to one. But when the decorated function takes arguments with a list or dict type, ``input_spec`` needs to have the same nested structure as the arguments. - -If a function takes an argument of type list, the element in the ``input_spec`` must also be an InputSpec list containing the same elements. A simple example as follows: - -.. code-block:: python - - class SimpleNet(Layer): - def __init__(self): - super(SimpleNet, self).__init__() - self.linear = paddle.nn.Linear(10, 3) - - @to_static(input_spec=[[InputSpec(shape=[None, 10], name='x'), InputSpec(shape=[3], name='y')]]) - def forward(self, inputs): - x, y = inputs[0], inputs[1] - out = self.linear(x) - out = out + y - return out - - -The length of ``input_spec`` is 1 corresponding to argument inputs in forward function. ``input_spec[0]`` contains two InputSpec objects corresponding to two Tensor signature information of inputs. - -If a function takes an argument of type dict, the element in the ``input_spec`` must also be an InputSpec dict containing the same keys. A simple example as follows: - -.. code-block:: python - - class SimpleNet(Layer): - def __init__(self): - super(SimpleNet, self).__init__() - self.linear = paddle.nn.Linear(10, 3) - - @to_static(input_spec=[InputSpec(shape=[None, 10], name='x'), {'x': InputSpec(shape=[3], name='bias')}]) - def forward(self, x, bias_info): - x_bias = bias_info['x'] - out = self.linear(x) - out = out + x_bias - return out - - -The length of ``input_spec`` is 2 corresponding to arguments x and bias_info in forward function. The last element of ``input_spec`` is a InputSpec dict with same key corresponding to signature information of bias_info. - - -2.4 Specify non-Tensor arguments -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Currently, the ``input_spec`` from ``to_static`` decorator only receives objects with ``InputSpec`` type. When the decorated function contains some non-Tensor arguments, such as Int, String or other python types, we recommend to use kwargs with default values as argument, see use_act in followed example. - -.. code-block:: python - - class SimpleNet(Layer): - def __init__(self, ): - super(SimpleNet, self).__init__() - self.linear = paddle.nn.Linear(10, 3) - self.relu = paddle.nn.ReLU() - - @to_static(input_spec=[InputSpec(shape=[None, 10], name='x')]) - def forward(self, x, use_act=False): - out = self.linear(x) - if use_act: - out = self.relu(out) - return out - - net = SimpleNet() - adam = paddle.optimizer.Adam(parameters=net.parameters()) - - # train model - batch_num = 10 - for step in range(batch_num): - x = paddle.rand([4, 10], 'float32') - use_act = (step%2 == 0) - out = net(x, use_act) - loss = paddle.mean(out) - loss.backward() - adam.minimize(loss) - net.clear_gradients() - - # save inference model with use_act=False - paddle.jit.save(net, path='./simple_net') - - -In above example, use_act is equal to True if step is an odd number, and False if step is an even number. We support non-tensor argument applied to different values during training after conversion. Moreover, the shared parameters of the model can be updated during the training with different values. The behavior is consistent with the dynamic graph. - -The default value of the kwargs is primarily used for saving inference model. The inference model and network parameters will be exported based on input_spec and the default values of kwargs. Therefore, it is recommended to set the default value of the kwargs arguments for prediction. diff --git a/docs/guides/04_dygraph_to_static/program_translator_cn.rst b/docs/guides/04_dygraph_to_static/program_translator_cn.rst deleted file mode 100644 index 8a7f7ca27b5..00000000000 --- a/docs/guides/04_dygraph_to_static/program_translator_cn.rst +++ /dev/null @@ -1,42 +0,0 @@ -内部架构原理 -============== - -TracedLayer的原理就是trace,相对简单,因此我们在这里不展开描述。本节将主要阐述ProgramTranslator基于源代码将动态图代码转化为静态图代码。 - - -转化过程发生在用户开始调用被装饰的函数,转换过程在装饰器中实现。我们将内部涉及的过程分为以下几步: - -函数与缓存 ------------- - -动态图转静态图的主体是函数(Function)。对于函数内包含的PaddlePaddle接口,如果是仅计算相关算子代码语句,那么因为PaddlePaddle动态图和静态图接口一致,我们不需要额外转换这些代码为静态图代码。但是对于动态图,此类代码接口是直接运行计算和返回结果,而对于静态图此类代码接口其实是组网。那么如果被转化的函数被调用多次,动态图转静态图后会多次组网添加对应算子,这显然会导致问题。为了解决这个问题以及为了加速动转静转化过程,我们维护了被装饰器装饰的函数(Function)与其输入形状(shape),数据类型(dtype)映射到被转化后组网的Program的缓存(Cache)。当要被转化的函数命中缓存,我们直接用对应存储的Program运行静态图得到结果,否则我们才进行语句转化,并且转化成功后的Program存储进缓存。 - -动态图源码转AST(抽象语法树) ------------------------------- - -动态图转静态图的最核心部分类似一个编译器,解析动态图代码语句为AST,再对应AST进行改写,最后反转回成静态图代码。从函数转化为代码字符串可以使用Python的inspect.getsource。从字符串Python提供了自带的 `ast `_ 库来解析字符串为AST,但是由于Python2,Python3的语法略有不同,为了避免我们需要额外处理这些Python2,Python3的不同情况,我们使用了统一Python2,Python3的开源AST处理 `gast库 `_ 。这些接口使得函数转化为AST没有本质上的困难。 - -AST改写和静态图源码转换 -------------------------- - -这部分为动转静最核心的部分,我们对支持的各种语法进行ast转写。其中最重要的Python控制流,if-else,while,for循环被分别分析转化为PaddlePaddle静态图接口cond,while_loop等接口实现。我们对想转化的每一种主要语法创建一个Transformer(这里的Transformer是Python ast转写的概念,而不是自然语言处理NLP领域的Transformer),每个Transformer扫一遍AST并进行对应的改写。最后被转化完成的AST我们使用gast提供的接口转回成源码。 - -静态图源码作为动态图一部分运行的技术 --------------------------------------- - -为了动静转化更加易用和被转化的代码能在动态图中复用,我们在拥有源码后运行生成Program,并将这个Program作为一个大op,包装成动态图的一个op,这样既能把用户的代码转为静态图提速或者保存部署,另一方面如果用户想在Python层使用生成的静态图代码作为动态图的一部分继续训练或者别的动态图运算也是可以直接使用。 - -易用性与Debug功能在动转静过程的实现 -------------------------------------- - -正如AST转写类似编译器,而一般编译器都会提供debug断点,报错,输出一些中间代码等功能。我们在进行动转静时,万一用户的动态图代码出错,或者用户想断点调试,或者用户想看看被转化后的静态图代码是否符合其预期,我们也希望能够像编译器一样提供这些易用性功能,使得动转静兼顾性能和部署同时还具有易用性。我们这里将列出这些功能的实现方式 - -A. 报错对应到动态图代码行。由于被转化后的静态图代码和原动态图代码不同,Python运行出错时会报静态图的错误,因此我们在每一次AST转写时添加AST节点对应的原动态图代码行等信息,在Python报错栈中将静态图的报错转化成对应的动态图源码报错 - -B. 设置断点功能。我们保留了被转化后代码的中的pdb.set_trace(), 用户可以使用这种方式进行断点调试 - -C. 查看最后转化的静态图代码。我们输出为一个StaticLayer class,这个StaticLayer可以直接被调用,但是也存储转化后的代码,可以调用StaticLayer.code来获得转化后的代码。 - -D. 输出中间转化状态代码,甚至不同语法Transformer转化的代码,比如经过for循环转化后代码是什么样的。我们开放接口设定了log level来让用户可以打印中间状态转化的代码。 - - diff --git a/docs/guides/04_dygraph_to_static/program_translator_en.rst b/docs/guides/04_dygraph_to_static/program_translator_en.rst deleted file mode 100644 index e130bdac0c8..00000000000 --- a/docs/guides/04_dygraph_to_static/program_translator_en.rst +++ /dev/null @@ -1,41 +0,0 @@ -Architecture -============== - -The basic idea of TracedLayer is tracing, it is relatively simple so we won't expend here. This section will talk about the source code transformation of ProgramTranslator. - -The transformation is implemented in the decorator so transformation happens when user calls the decorated function, the procedure includes these steps: - -Function and cache --------------------- - -The entity for transforming dygraph to static graph is the decorated function. For the PaddlePaddle APIs in the function, since they are same code under dygraph mode and static mode, we don't have to transform those code. However, those APIs are computation in dygraph model while they are building network in static graph mode, if the transformed functions are called multiple times, those APIs will build network multiple times in static graph, which can cause problem. To solve it as well as speed up the transformation, we maintain a cache that maps from function, input shapes, input data types to the Program built by the transformed function. If the function hits cache, we run the stored Program in static graph mode to get result, else we do the code transformation on the function and store the transformed Program into the cache. - -From dygraph source code to AST (Abstract Syntax Tree) --------------------------------------------------------- - -The core of transforming dygraph to static graph is similar to a compiler, we parse the dygraph code into AST, change AST, then turn it back into static graph code. We use Python ``inspect.getsource`` to get the source code string of the function. Python provides `ast `_ library to parse string code into AST, but Python2, Python3 have slight grammar difference. To avoid the work to handle different grammars, we used an open source AST library `gast `_ that provides compatibility AST among various Python versions. There is no essential difficulty to turn function into AST with these library. - -Transform AST and turn it to static graph code ------------------------------------------------- - -This part is the key part in ProgramTranslator, we modify AST for supported grammars. Those important Python control flows, such as ``if-elif-else, while, for`` loop are converted to PaddlePaddle static graph API ``cond, while_loop`` and so on. We created a Transformer (AST-to-AST Transformer in Python, not the Transformer in Natural Language Process) to transform each grammar. Every Transformer scans AST and modify it. Lastly, we turn AST back to source code string by ``gast`` library. - -Running static graph code as part of dygraph ----------------------------------------------- - -In order to increase usability and re-use the transformed static graph code in dygraph, we wrap the generated Program as an dygraph op, the op can run the forward and backward computation of transformed Program. Then we can not only speed up dygraph code or save it for deployment, but also enable user to run part of their dygraph code in static graph mode so that they can continue training or other dygraph computation in their dygraph code. - -Error handling and Debug --------------------------- - -Compiler usually supports debug functionality like breakpoint, throwing exception, print some mid-level codes. ProgramTranslator is similar to a compiler, users may would like to set breakpoints for debugging, or see whether the transformed static graph code is expected. So we also implemented those error handling and debug functionality. Here we list those functions and their implementation. - -A. Report errors/exceptions on dygraph code line. Because the transformed static graph code is different to original dygraph code, when Python executes the static graph code, the exceptions will be reported at static graph code. To locate the corresponding dygraph code, we attach some informations such as line number on AST nodes when we transform AST, then we can re-write the static graph exception to the corresponding dygraph code exception. - -B. We support ``pdb.set_trace()`` when running ProgramTranslator, user can add this line to set breakpoints. - -C. Check the transformed static graph code. Our transformed output is a Python class named ``StaticLayer``, this class can be called, but it also stores the transformed code string. Users could call ``StaticLayer.code`` to get the converted code. - -D. Print mid-level transformed code, such as what's the code after transforming ``for`` loop. We provide APIs to set log level to let user check the mid-level code. - -