From 3bcd979c21bc440d3dcd7c4056e757e7d0aef9a2 Mon Sep 17 00:00:00 2001 From: zyfncg Date: Fri, 17 Jun 2022 07:47:28 +0000 Subject: [PATCH 01/10] modify new_cpp_ op doc --- .../code_gen_by_yaml.png | Bin 0 -> 253293 bytes .../api_contributing_guides/new_cpp_op_cn.md | 331 ++++++++++-------- .../new_cpp_op_notes_cn.md | 282 +++------------ 3 files changed, 224 insertions(+), 389 deletions(-) create mode 100644 docs/dev_guides/api_contributing_guides/code_gen_by_yaml.png diff --git a/docs/dev_guides/api_contributing_guides/code_gen_by_yaml.png b/docs/dev_guides/api_contributing_guides/code_gen_by_yaml.png new file mode 100644 index 0000000000000000000000000000000000000000..32dd779608fae1568ee9dc5dde7f5a506b91c2d2 GIT binary patch literal 253293 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%0AUE#GWlN&zpc2vW|z7D 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

code_gen_by_yaml

+ +- 其中Yaml配置文件为前向:`python/paddle/utils/code_gen/api.yaml` 和反向:`python/paddle/utils/code_gen/backward.yaml`。 - 动态图中自动生成的代码包括从Python API到计算Kernel间的各层调用接口实现,从底层往上分别为: - C++ API:一套与Python API参数对齐的C++接口(只做逻辑计算,不支持自动微分),内部封装了底层kernel的选择和调用等逻辑,供上层灵活使用。 - 注:前向算子生成C++ API头文件和实现代码分别为`paddle/phi/api/include/api.h`和`paddle/phi/api/lib/api.cc`,反向算子生成的头文件和实现代码分别为`paddle/phi/api/backward/backward_api.h`,`paddle/phi/api/lib/backward_api.cc`。 - 动态图前向函数与反向节点(Autograd API):在C++ API的基础上进行了封装,组成一个提供自动微分功能的C++函数接口。 - - 注:生成的代码在`paddle/fluid/eager/api/generated/eager_generated`目录下 + - 注:生成的相关代码在`paddle/fluid/eager/api/generated/eager_generated`目录下 - Python-C 接口:将支持自动微分功能的C++的函数接口(Autograd API)暴露到Python层供Python API调用。 - - 注:生成的代码在`paddle/fluid/pybind/eager_final_state_op_function_impl.h`中 + - 注:生成的Python-C 接口代码在`paddle/fluid/pybind/eager_final_state_op_function_impl.h`中 - 静态图的执行流程与动态图不同,所以生成的代码也与动态图有较大差异。静态图由于是先组网后计算,Python API主要负责组网,算子的调度和kernel计算由静态图执行器来完成,因此自动生成的代码是将配置文件中的算子信息注册到框架内供执行器调度,主要包括[OpMaker](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/fluid/framework/op_proto_maker.h)(静态图中定义算子的输入、输出以及属性等信息)和`REGISTER_OPERATOR`(将算子名称以及OpMaker等信息进行注册)等静态图算子注册组件,具体的代码逻辑可参考`paddle/fluid/operators/generated_op.cc` -注意:由于代码生成在编译时执行,所以查看上述生成代码需要先完成框架的编译。 +**注意:由于代码自动生成在编译时进行,所以查看上述生成代码需要先完成[框架的编译](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/install/compile/fromsource.html)。** 更多内容请参考: [C++ OP开发](new_cpp_op_cn.html) ## 写Op注意事项 ### 1.Op兼容性问题 -对Op的修改需要考虑兼容性问题,要保证Op修改之后,之前的模型都能够正常加载及运行,即新版本的Paddle预测库能成功加载运行旧版本训练的模型。**所以,需要保证Op当前的所有输入输出参数不能被修改(文档除外)或删除,可以新增参数,但是新增的Tensor类型变量需要设置为optional,非Tensor变量需要设置默认值。更多详细内容请参考[OP修改规范:Input/Output/Attribute只能做兼容修改](https://github.com/PaddlePaddle/Paddle/wiki/OP-Input-Output-Attribute-Compatibility-Modification)** 。 +对Op的修改需要考虑兼容性问题,要保证Op修改之后,之前的模型都能够正常加载及运行,即新版本的Paddle预测库能成功加载运行旧版本训练的模型。**所以,需要保证Op当前的所有输入输出参数不能被修改(文档除外)或删除,可以新增参数,但是新增的Tensor类型变量需要设置为optional,非Tensor变量需要设置默认值。更多详细内容请参考[OP修改规范:Input/Output/Attribute只能做兼容修改](https://github.com/PaddlePaddle/Paddle/wiki/OP-Input-Output-Attribute-Compatibility-Modification)** 。 ### 2.显存优化 #### 2.1 为可原位计算的Op注册inplace 有些Op的计算逻辑中,输出可以复用输入的显存空间,也可称为原位计算。例如[reshape](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/phi/kernels/reshape_kernel.cc)中,输出`out`可以复用输入`x`的显存空间,因为该Op的计算逻辑不会改变`x`的实际数据,只是修改它的shape,输出和输入复用同一块显存空间不影响结果。对于这类OP,可以注册`inlace`,从而让框架在运行时自动地进行显存优化。 -注册方式为在算子的Yaml配置中添加inplace配置项,格式如:`(x -> out)`,详见[Yaml配置规则](new_cpp_op_cn.html)。示例: +注册方式为在算子的Yaml配置中添加`inplace`配置项,格式如:`(x -> out)`,详见[Yaml配置规则](new_cpp_op_cn.html#yaml)。示例: -``` +```yaml - api : reshape args : (Tensor x, IntArray shape) output : Tensor(out) @@ -43,8 +44,8 @@ Paddle支持动态图和静态图两种模式,在Yaml配置文件中完成算 所以在定义反向Op时需要注意以下几点: - 如果反向不需要前向的某些输入或输出参数,则无需在args中设置。 -- 如果有些反向Op需要依赖前向Op的输入或输出变量的的Shape或LoD,但不依赖于变量中Tensor的Buffer,且不能根据其他变量推断出该Shape和LoD,则可以通过`no_need_buffer`对该变量进行配置,可参考[Yaml配置规则](new_cpp_op_cn.html)。示例: -``` +- 如果有些反向Op需要依赖前向Op的输入或输出变量的的Shape或LoD,但不依赖于变量中Tensor的内存Buffer数据,且不能根据其他变量推断出该Shape和LoD,则可以通过`no_need_buffer`对该变量进行配置,详见[Yaml配置规则](new_cpp_op_cn.html#yaml)。示例: +```yaml - backward_api : trace_grad forward : trace (Tensor x, int offset, int axis1, int axis2) -> Tensor(out) args : (Tensor x, Tensor out_grad, int offset, int axis1, int axis2) From c97e33366acb05ced5e7ee2c59ba62af6c7e1e41 Mon Sep 17 00:00:00 2001 From: zyfncg Date: Fri, 17 Jun 2022 12:20:33 +0000 Subject: [PATCH 03/10] add invoke --- docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md | 6 ++++++ .../api_contributing_guides/new_cpp_op_notes_cn.md | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md b/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md index 565a6ea5e65..b0460d298ad 100644 --- a/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md +++ b/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md @@ -158,6 +158,12 @@ python/paddle/utils/code_gen/backward.yaml: intermediate 标记前向计算中输出的用于反向计算的中间变量,不会出现在Python API的返回结果中,相关设计正在完善中,新增算子时不建议使用 + +invoke +复用已有的算子接口或实现自定义的C++ API,配置时以函数调用的形式配置即可,使用invoke时则不需要配置infer_meta和kernel。
+a. 如果是复用已有算子,需要被复用的算子为前向算子且两者的返回值类型相同,可参考zeros_like算子
+b. 如果是实现自定义的C++ API,需要在'paddle/phi/api/lib/api_custom_impl.h'声明自定义实现函数并在'paddle/phi/api/lib/api_custom_impl.cc'中进行实现,具体可参考embedding算子 + diff --git a/docs/dev_guides/api_contributing_guides/new_cpp_op_notes_cn.md b/docs/dev_guides/api_contributing_guides/new_cpp_op_notes_cn.md index e53227fad36..942726e463e 100644 --- a/docs/dev_guides/api_contributing_guides/new_cpp_op_notes_cn.md +++ b/docs/dev_guides/api_contributing_guides/new_cpp_op_notes_cn.md @@ -2,8 +2,8 @@ ## Paddle基于Yaml配置的算子代码自动生成 Paddle支持动态图和静态图两种模式,在Yaml配置文件中完成算子基本属性的定义后,需要进行解析并分别生成动态图和静态图所对应的算子代码逻辑,从而将算子接入框架的执行体系。基于Yaml配置的算子代码自动生成示意图: - -

code_gen_by_yaml

+

code_gen_by_yaml

- 其中Yaml配置文件为前向:`python/paddle/utils/code_gen/api.yaml` 和反向:`python/paddle/utils/code_gen/backward.yaml`。 - 动态图中自动生成的代码包括从Python API到计算Kernel间的各层调用接口实现,从底层往上分别为: From 2e9b2531cf3e7d66a65305f101471a08712e5dde Mon Sep 17 00:00:00 2001 From: zyfncg Date: Fri, 17 Jun 2022 13:01:57 +0000 Subject: [PATCH 04/10] fix picture --- docs/dev_guides/api_contributing_guides/new_cpp_op_notes_cn.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/dev_guides/api_contributing_guides/new_cpp_op_notes_cn.md b/docs/dev_guides/api_contributing_guides/new_cpp_op_notes_cn.md index 942726e463e..75d4a144e10 100644 --- a/docs/dev_guides/api_contributing_guides/new_cpp_op_notes_cn.md +++ b/docs/dev_guides/api_contributing_guides/new_cpp_op_notes_cn.md @@ -2,8 +2,7 @@ ## Paddle基于Yaml配置的算子代码自动生成 Paddle支持动态图和静态图两种模式,在Yaml配置文件中完成算子基本属性的定义后,需要进行解析并分别生成动态图和静态图所对应的算子代码逻辑,从而将算子接入框架的执行体系。基于Yaml配置的算子代码自动生成示意图: -

code_gen_by_yaml

+![code_gen_by_yaml](./code_gen_by_yaml.png) - 其中Yaml配置文件为前向:`python/paddle/utils/code_gen/api.yaml` 和反向:`python/paddle/utils/code_gen/backward.yaml`。 - 动态图中自动生成的代码包括从Python API到计算Kernel间的各层调用接口实现,从底层往上分别为: From e1f60f9157b7aff73bc252290dc294082f4f9a11 Mon Sep 17 00:00:00 2001 From: zyfncg Date: Tue, 21 Jun 2022 05:40:52 +0000 Subject: [PATCH 05/10] update --- .../api_contributing_guides/new_cpp_op_cn.md | 261 ++++++++++++++---- .../new_cpp_op_notes_cn.md | 173 ------------ .../new_python_api_cn.md | 84 +++--- 3 files changed, 249 insertions(+), 269 deletions(-) delete mode 100644 docs/dev_guides/api_contributing_guides/new_cpp_op_notes_cn.md diff --git a/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md b/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md index b0460d298ad..f6aff7057dc 100644 --- a/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md +++ b/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md @@ -1,10 +1,10 @@ -# C++ OP 开发 +# C++ 算子开发指南 -> 注:飞桨原生算子的开发范式正处在重构升级后的上线初期,如果在开发过程中遇到问题欢迎通过[Issue](https://github.com/PaddlePaddle/Paddle/issues)向我们反馈。 +> 注:飞桨算子的开发范式正处在重构升级后的上线初期,如果在开发过程中遇到问题欢迎通过[Issue](https://github.com/PaddlePaddle/Paddle/issues)向我们反馈。 ## 1. 概念简介 -本教程对新增原生算子的方法进行介绍,首先新增一个算子大概需要以下几个步骤: +本教程对新增算子的方法进行介绍,首先新增一个算子大概需要以下几个步骤: 1. 新增算子描述及定义:描述前反向算子的输入、输出、属性,实现InferMeta函数 2. 新增算子Kernel:实现算子在各种设备上的计算逻辑 @@ -44,16 +44,14 @@ -关于Python API所处位置,可以参考 [飞桨官方 API 文档](https://www.paddlepaddle.org.cn/documentation/docs/zh/api/index_cn.html) ,了解各个目录存放API的性质,从而决定具体的放置目录。 - -接下来,我们以Trace操作,计算输入 Tensor 在指定平面上的对角线元素之和,并输出相应的计算结果,即 [trace](https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/trace_cn.html#trace) 为例来介绍如何新增算子。 +接下来,我们以Trace操作,计算输入 Tensor 在指定平面上的对角线元素之和,并输出相应的计算结果,即 [trace](../../api/paddle/trace_cn.html#trace) 为例来介绍如何新增算子。 ## 2. 新增算子描述及定义 算子描述及定义是定义运算的基本属性,主要包括算子的输入、输出以及各项非计算逻辑的配置,这些都是设备无关的。 ### 2.1 算子Yaml文件配置 -我们在`python/paddle/utils/code_gen/api.yaml`和`python/paddle/utils/code_gen/backward.yaml`文件中对算子进行描述及定义,在框架编译时会根据Yaml文件中的配置自动生成C++端的相关代码接口以及内部实现(详见[Paddle基于Yaml配置的算子代码自动生成](new_cpp_op_notes_cn.md#paddleyaml)),下面主要以Trace为例介绍算子的Yaml配置规则: +我们在`python/paddle/utils/code_gen/api.yaml`和`python/paddle/utils/code_gen/backward.yaml`文件中对算子进行描述及定义,在框架编译时会根据Yaml文件中的配置自动生成C++端的相关代码接口以及内部实现(详见[Paddle基于Yaml配置的算子代码自动生成](new_cpp_op_cn.md#paddleyaml)),下面主要以Trace为例介绍算子的Yaml配置规则: python/paddle/utils/code_gen/api.yaml: ```yaml @@ -96,11 +94,15 @@ python/paddle/utils/code_gen/backward.yaml: args -算子输入参数,与该算子Python API函数的输入参数对应(当前支持的输入数据类型包括:Tensor, Tensor[]/*Tensor数组*/, float, double, bool, int, int64_t, int[], int64_t[], str, Place, DataType, DataLayout, IntArray/*主要用于表示shape,index和axes等类型数据,可以直接使用Tensor或者普通整型数组构造,目前仍在测试阶段,如非必要暂不建议使用*/, Scalar/*标量,支持不同的普通数据类型*/)。我们一般称这里Tensor类型的参数为Input(输入),非Tensor类型的参数为Attribute(属性) +算子输入参数,与该算子Python API函数的输入参数对应(当前支持的输入数据类型包括:Tensor, Tensor[], float, double, bool, int, int64_t, int[], int64_t[], str, Place, DataType, DataLayout, IntArray, Scalar)。我们一般称这里Tensor类型的参数为Input(输入),非Tensor类型的参数为Attribute(属性)
+注:Tensor[]表示Tensor数组;IntArray为int类型数组,主要用于表示shape,index和axes等类型数据,可以直接使用Tensor或者普通整型数组构造,目前仍在测试阶段,如非必要暂不建议使用;Scalar表示标量,可以支持不同的普通数据类型 + output -算子输出类型(目前支持Tensor和Tensor[]类型),多个输出间用逗号“,”分隔开。可以使用”()”选择性标记输入的名字,如未标记默认为'out' +算子输出类型(目前支持Tensor和Tensor[]类型),多个输出间用逗号“,”分隔开。可以使用”()”选择性标记输入的名字,如未标记默认为'out'
+注:当返回类型为Tensor[]时,由于数组的size要在kernel执行前推导完成,所以需要在Tensor[]后的'{}'内通过表达式指定返回数组的size,如:Tensor[](out){input.size()} + infer_meta @@ -128,10 +130,10 @@ python/paddle/utils/code_gen/backward.yaml: kernel:data_type -根据指定参数推导调用kernel的data_type(对应kernel函数的模板参数'T'),默认不进行配置,会根据输入Tensor自动进行推导。如果kernel的data_type类型由某个输入Tensor决定,需要将该Tensor参数的变量名填入该项。示例中未配置则kernel的data_type由输入变量'x'决定 +根据指定参数推导调用kernel的data_type(对应kernel函数的模板参数'T'),默认不进行配置,会根据输入Tensor自动进行推导。如果kernel的data_type类型由某个输入参数(Tensor或者DataType参数),需要将该参数的变量名填入该项。示例中未配置则kernel的data_type由输入变量'x'决定 kernel:backend -根据指定参数来选择调用kernel的Backend(Kernel执行的具体设备,如CPU、GPU等),默认不进行配置,会根据输入Tensor自动进行推导。如果kernel执行的backend类型由某个输入Tensor决定,需要将该Tensor参数的变量名填入该项。示例中未配置则kernel执行的Backend与输入变量'x'的Backend相同 +根据指定参数来选择调用kernel的Backend(Kernel执行的具体设备,如CPU、GPU等),默认不进行配置,会根据输入Tensor自动进行推导。如果kernel执行的backend类型由某个输入参数(Tensor或者Backend参数)决定,需要将该参数的变量名填入该项。示例中未配置则kernel执行的Backend与输入变量'x'的Backend相同 backward @@ -536,11 +538,40 @@ void TraceKernel(const Context& dev_ctx, > **特殊情况说明:** > 1. **特殊模板参数**:对于某些Kernel (如reshape ,copy),这些kernel不关注数据类型T, 可以省去第一个模板参数,即为:`template ` -> 2. **特殊输入类型**:对于某些特殊Kernel (如concat 和split kernel)的部分输入或输出是数组类型的DenseTensor(OpMaker中有`AsDuplicable`标记), 此时输入类型为:`const std::vector&`; 输出类型为:`std::vector` +> 2. **特殊输入类型**:对于某些特殊Kernel (如concat 和split kernel)的部分输入或输出是数组类型的DenseTensor, 此时输入类型为:`const std::vector&`; 输出类型为:`std::vector` #### 3.2.2 实现 Kernel 函数 -此处trace op的kernel属于前述第2中情况,即CPU与GPU Kernel需要分别实现。 +**复用已有Kernel实现设备无关Kernel函数** +由于目前的Kernel复用机制为新推出的功能,暂未对已有算子进行升级改造,所以这里我们以一个不在框架中的linear算子(out = x * w + b)为例来介绍复用已有Kernel实现设备无关Kernel函数。(linear kernel 的实现源码需要放置在`paddle/phi/kernels/linear_kernel.cc`) + +`LinearKernel` 的实现代码如下: + +```cpp +#include ... +#include "paddle/phi/kernels/elementwise_add_kernel.h" +#include "paddle/phi/kernels/elementwise_multiply_kernel.h" + +template +void LinearKernel(const Context& dev_ctx, + const DenseTensor& x, + const DenseTensor& w, + const DenseTensor& b, + DenseTensor* out) { + dev_ctx.template Alloc(out); // 为 out 分配内存 + MultiplyKernel(dev_ctx, x, w, out); // 复用 MultiplyKernel + AddKernel(dev_ctx, out, b, out); // 复用 AddKernel +} +``` +复用Kernel的流程包括: +1. 在源文件中 include 要复用 Kernel 的头文件 +2. 直接调用相应的Kernel函数进行复用 + +注意:设备无关Kernel实现时计算逻辑部分只能复用现有Kernel或设备无关的Functor,不能使用设备相关的语法或者函数接口(如cuda、cudnn等)进行计算处理 + +**实现设备相关Kernel函数** + +此处 trace 算子的kernel属于前述第2中情况,即与设备相关,CPU和GPU Kernel需要分别实现。 - cpu kernel实现位于:[paddle/phi/kernels/cpu/trace_kernel.cc](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/phi/kernels/cpu/trace_kernel.cc) - gpu kernel实现位于:[paddle/phi/kernels/gpu/trace_kernel.cu](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/phi/kernels/gpu/trace_kernel.cu) @@ -571,9 +602,7 @@ void TraceKernel(const Context& dev_ctx, } ``` -**Kernel复用:** - -此处TraceKernel的实现并未复用其他Kernel,但如果有需要也是可以复用的,Kernel复用时,直接 include 相应Kernel头文件,在函数中调用即可,例如 [triangular_solve_kernel](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/phi/kernels/cpu/triangular_solve_kernel.cc) 复用 empty和expand kernel。 +此处TraceKernel的实现并未复用其他Kernel,但如果有需要也是可以复用的,Kernel复用时,同样是直接 include 相应Kernel头文件,在函数中调用即可,例如 [triangular_solve_kernel](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/phi/kernels/cpu/triangular_solve_kernel.cc) 复用 empty和expand kernel。 首先在triangular_solve_kernel.cc头部include相应头文件: @@ -592,6 +621,8 @@ void TraceKernel(const Context& dev_ctx, ExpandKernel(dev_ctx, x, x_bst_dims, &x_bst); ``` +补充:对于Kernel内部临时使用的`DenseTensor`目前推荐使用`Empty`、`EmptyLike`、`Full`和`FullLike`接口进行创建。 + 反向Kernel的实现与前向是类似的,此处不再赘述,可以直接参考前述对应链接中的代码实现。 **公共函数管理:** @@ -605,38 +636,6 @@ void TraceKernel(const Context& dev_ctx, 3. 有跨设备多个kernel使用的辅助函数,在`kernels/funcs`目录下创建`.h/cc/cu`管理代码 4. 如果当前依赖的辅助函数可以直接归类到`kernels/funcs`目录下已有的文件中,则直接放过去,不用创建新的文件 -**反向Kernel参数映射函数添加** - -现阶段,反向Kernel除了实现外,还需要添加一个参数映射函数。 - -仍然以trace op为例,首先在`paddle/phi/ops/compat`目录下新建`trace_sig.cc`文件,用于放置这里的映射函数。 - -- 由于函数式kernel的一个最重要的特别就是参数顺序和类型(顺序和类型是关键,变量名称不影响),我们需要定义一个函数来做一个从OpMaker中如何获取信息,并且按照顺序传递给新的kernel函数; 这个模块就是OpArgumentMapping, trace反向op的OpArgumentMapping定义如下, KernelSignature共包含4个内容 - 1. kernel名称,这个是我们给kernel注册的时候的名称 - 2. input list: 这个要和OpMaker(或者GradOpMaker)中定义的Key要完全一致 - 3. attribute list: 这个要和OpMaker(或者GradOpMaker)中定义的Key要完全一致 - 4. output list: 这个要和OpMaker(或者GradOpMaker)中定义的Key要完全一致 - - - ```cpp - #include "paddle/phi/core/compat/op_utils.h" - - namespace phi { - - KernelSignature TraceGradOpArgumentMapping(const ArgumentMappingContext& ctx) { - return KernelSignature("trace_grad", - {GradVarName("Out"), "Input"}, - {"offset", "axis1", "axis2"}, - {GradVarName("Input")}); - } - - } // namespace phi - - PD_REGISTER_ARG_MAPPING_FN(trace_grad, phi::TraceGradOpArgumentMapping); - ``` - ->注:没有input list或attribute list的,相应花括号内留空,不能省略花括号 - #### 3.2.3 注册 Kernel 函数 注册kernel的方式比较简单,直接使用注册宏注册即可,示例如下: @@ -794,17 +793,11 @@ def trace(x, offset=0, axis1=0, axis2=1, name=None): > 概念解释:LayerHelper是一个用于创建op输出变量、向program中添加op的辅助工具类 -- Python API 实现要点 +- Python API 实现要点(详见[飞桨API Python 端开发指南](./new_python_api_cn.html)) - 对输入参数进行合法性检查,即 `__check_input(input, offset, axis1, axis2)` - 添加动态图分支调用,即 `if in_dygraph_mode` 新动态图分支和 `if _in_legacy_dygraph` 旧动态图分支 - 添加静态图分支调用,即dygraph分支后剩余的代码 -- Python API 放置位置 - - 根据 API 自身属性,结合现有目录分类情况,放置导致对应子目录中的相应文件中 - - 可以参考 [飞桨官方 API 文档](https://www.paddlepaddle.org.cn/documentation/docs/zh/api/index_cn.html) 中对各个子目录 **功能和包含的API** 的介绍 - -- Python API 文档 - - 参考示例格式进行添加,内容尽可能准确、翔实,详细规范请参考 [PaddlePaddle 文档](https://github.com/PaddlePaddle/docs/wiki) ## 5. 添加单元测试 @@ -881,6 +874,9 @@ Op单元测试继承自`OpTest`。各项具体的单元测试在`TestTraceOp`里 ['X'], 'Out', max_relative_error=0.005, no_grad_set=set('Y')) ``` +### 5.3 Python API 单元测试 +Python API也需要编写相关的单测进行测试,详见[添加 Python API 单元测试](new_python_api_cn.html#id2) + 其他有关单元测试添加的注意事项请参考 [《Op开发手册》](https://github.com/PaddlePaddle/Paddle/wiki/Operator-Development-Manual-Index) 及 [《Paddle单元测试规范》](https://github.com/PaddlePaddle/Paddle/wiki/PaddlePaddle-Unit-test-specification)。 @@ -902,15 +898,11 @@ make test ARGS="-R test_trace_op -V" ctest -R test_trace_op -V ``` -**注意事项:** - -- 注册Op时的类型名,需要和该Op的名字一样。即不允许在`A_op.cc`里面,注册`REGISTER_OPERATOR(B, ...)`等,这将会导致单元测试出错。 - -## 6. 其他编码要点 +## 6. 开发算子注意事项 ### 6.1 报错检查 -实现Op时检查数据的合法性需要使用PADDLE_ENFORCE以及PADDLE_ENFORCE_EQ等宏定义,基本格式如下: +实现算子时检查数据的合法性需要使用PADDLE_ENFORCE以及PADDLE_ENFORCE_EQ等宏定义,基本格式如下: ``` PADDLE_ENFORCE(表达式, 错误提示信息) @@ -938,3 +930,150 @@ PADDLE_ENFORCE_EQ(比较对象A, 比较对象B, 错误提示信息) - 例如:`Suggested Fix:If your classifier expects one-hot encoding label,check your n_classes argument to the estimatorand/or the shape of your label.Otherwise, check the shape of your label.` 更详细的报错检查规范介绍请参考 [《Paddle报错信息文案书写规范》](https://github.com/PaddlePaddle/Paddle/wiki/Paddle-Error-Message-Writing-Specification)。 + +### 6.2 算子兼容性问题 +对算子的修改需要考虑兼容性问题,要保证算子修改之后,之前的模型都能够正常加载及运行,即新版本的Paddle预测库能成功加载运行旧版本训练的模型。**所以,需要保证算子当前的所有输入输出参数不能被修改(文档除外)或删除,可以新增参数,但是新增的Tensor类型变量需要设置为optional,非Tensor变量需要设置默认值。更多详细内容请参考[OP修改规范:Input/Output/Attribute只能做兼容修改](https://github.com/PaddlePaddle/Paddle/wiki/OP-Input-Output-Attribute-Compatibility-Modification)** 。 + +### 6.3 显存优化 + +#### 6.3.1 为可原位计算的算子注册inplace +有些算子的计算逻辑中,输出可以复用输入的显存空间,也可称为原位计算。例如[reshape](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/phi/kernels/reshape_kernel.cc)中,输出`out`可以复用输入`x`的显存空间,因为该算子的计算逻辑不会改变`x`的实际数据,只是修改它的shape,输出和输入复用同一块显存空间不影响结果。对于这类算子,可以注册`inlace`,从而让框架在运行时自动地进行显存优化。 + +注册方式为在算子的Yaml配置中添加`inplace`配置项,格式如:`(x -> out)`,详见[Yaml配置规则](new_cpp_op_cn.html#yaml)。示例: + +```yaml +- api : reshape + args : (Tensor x, IntArray shape) + output : Tensor(out) + ... + inplace : (x -> out) +``` + +#### 6.3.2 减少反向算子中的无关变量 +通常反向算子会依赖于前向算子的某些输入、输出Tensor,以供反向算子计算使用。但有些情况下,反向算子不需要前向Op的所有输入和输出;有些情况下,反向算子只需要前向算子的部分输入和输出;有些情况下,反向算子只需要使用前向算子中输入和输出变量的Shape和LoD信息。若开发者在注册反向算子时,将不必要的前向算子输入和输出作为反向算子的输入,会导致这部分显存无法被框架现有的显存优化策略优化,从而导致模型显存占用过高。 + +所以在定义反向算子时需要注意以下几点: + +- 如果反向不需要前向的某些输入或输出参数,则无需在args中设置。 +- 如果有些反向算子需要依赖前向算子的输入或输出变量的的Shape或LoD,但不依赖于变量中Tensor的内存Buffer数据,且不能根据其他变量推断出该Shape和LoD,则可以通过`no_need_buffer`对该变量进行配置,详见[Yaml配置规则](new_cpp_op_cn.html#yaml)。示例: +```yaml +- backward_api : trace_grad + forward : trace (Tensor x, int offset, int axis1, int axis2) -> Tensor(out) + args : (Tensor x, Tensor out_grad, int offset, int axis1, int axis2) + output : Tensor(x_grad) + ... + no_need_buffer : x +``` + +### 6.4 性能优化 +#### 6.4.1 第三方库的选择 +在写算子过程中优先使用高性能(如cudnn、mkldnn、mklml、eigen等)中提供的操作,但是一定要做benchmark,有些库中的操作在深度学习任务中可能会比较慢。因为高性能库(如eigen等)中提供的操作为了更为通用,在性能方面可能并不是很好,通常深度学习模型中数据量较小,所以有些情况下可能高性能库中提供的某些操作速度较慢。比如Elementwise系列的所有算子(前向和反向),Elementwise操作在模型中调用的次数比较多,尤其是Elementwise_add,在很多操作之后都需要添加偏置项。在之前的实现中Elementwise_op直接调用Eigen库,由于Elementwise操作在很多情况下需要对数据做Broadcast,而实验发现Eigen库做Broadcast的速度比较慢,慢的原因在这个PR[#6229](https://github.com/PaddlePaddle/Paddle/pull/6229)中有描述。 + +#### 6.4.2 算子性能优化 +算子的计算速度与输入的数据量有关,对于某些算子可以根据输入数据的Shape和算子的属性参数来选择不同的计算方式。比如concat_op,当axis>=1时,在对多个tensor做拼接过程中需要对每个tensor做很多次拷贝,如果是在GPU上,需要调用cudaMemCopy。相对CPU而言,GPU属于外部设备,所以每次调用GPU的操作都会有一定的额外开销,并且当需要拷贝的次数较多时,这种开销就更为凸现。目前concat_op的实现会根据输入数据的Shape以及axis值来选择不同的调用方式,如果输入的tensor较多,且axis不等于0,则将多次拷贝操作转换成一个CUDA Kernel来完成;如果输入tensor较少,且axis等于0,使用直接进行拷贝。相关实验过程在该PR([#8669](https://github.com/PaddlePaddle/Paddle/pull/8669))中有介绍。 + +由于CUDA Kernel的调用有一定的额外开销,所以如果算子中出现多次调用CUDA Kernel,可能会影响算子的执行速度。比如之前的sequence_expand_op中包含很多CUDA Kernel,通常这些CUDA Kernel处理的数据量较小,所以频繁调用这样的Kernel会影响算子的计算速度,这种情况下最好将这些小的CUDA Kernel合并成一个。在优化sequence_expand_op过程(相关PR[#9289](https://github.com/PaddlePaddle/Paddle/pull/9289))中就是采用这种思路,优化后的sequence_expand_op比之前的实现平均快出约1倍左右,相关实验细节在该PR([#9289](https://github.com/PaddlePaddle/Paddle/pull/9289))中有介绍。 + +减少CPU与GPU之间的拷贝和同步操作的次数。比如fetch操作,在每个迭代之后都会对模型参数进行更新并得到一个loss,并且数据从GPU端到没有页锁定的CPU端的拷贝是同步的,所以频繁的fetch多个参数会导致模型训练速度变慢。 + +### 6.5 稀疏梯度参数更新方法 +目前稀疏梯度在做更新的时候会先对梯度做merge,即对相同参数的梯度做累加,然后做参数以及附加参数(如velocity)的更新。 + +### 6.6 混合设备调用 +由于GPU是异步执行的,当CPU调用返回之后,GPU端可能还没有真正的执行,所以如果在算子中创建了GPU运行时需要用到的临时变量,当GPU开始运行的时候,该临时变量可能在CPU端已经被释放,这样可能会导致GPU计算出错。 + +关于GPU中的一些同步和异步操作: +``` +The following device operations are asynchronous with respect to the host: + Kernel launches; + Memory copies within a single device's memory; + Memory copies from host to device of a memory block of 64 KB or less; + Memory copies performed by functions that are suffixed with Async; + Memory set function calls. +``` + +关于cudaMemCpy和cudaMemCpyAsync注意事项: + +- 如果数据传输是从GPU端到非页锁定的CPU端,数据传输将是同步,即使调用的是异步拷贝操作。 +- 如果数据传输是从CPU端到CPU端,数据传输将是同步的,即使调用的是异步拷贝操作。 + +更多内容可参考:[Asynchronous Concurrent Execution](https://docs.nvidia.com/cuda/cuda-c-programming-guide/#asynchronous-concurrent-execution),[API synchronization behavior](https://docs.nvidia.com/cuda/cuda-runtime-api/api-sync-behavior.html#api-sync-behavior) + +### 6.7 算子数值稳定性问题 +有些算子存在数值稳定性问题,出现数值稳定性的主要原因程序在多次运行时,对浮点型数据施加操作的顺序可能不同,进而导致最终计算结果不同。而GPU是通过多线程并行计算的方式来加速计算的,所以很容易出现对浮点数施加操作的顺序不固定现象。 + +目前发现cudnn中的卷积操作、cudnn中的MaxPooling、CUDA中CudaAtomicXX、ParallelExecutor的Reduce模式下参数梯度的聚合等操作运行结果是非确定的。 + +为此Paddle中添加了一些FLAGS,比如使用FLAGS_cudnn_deterministic来强制cudnn使用确定性算法、FLAGS_cpu_deterministic强制CPU端的计算使用确定性方法。 + +### 6.8 算子的数学公式 +如果算子有数学公式,一定要在代码中将数学公式写明,并在Python API的Doc中显示,因为用户在对比不同框架的计算结果时可能需要了解Paddle对算子是怎么实现的。 + +### 6.9 LoD 在算子内部的传导规范 + +[LoD](https://github.com/PaddlePaddle/FluidDoc/blob/develop/doc/fluid/design/concepts/lod_tensor.md) 是 Paddle 框架用来表示变长序列数据的属性,除了仅支持输入是 padding data 的算子外,所有算子的实现都要考虑 LoD 的传导问题。 + +根据算子的计算过程中是否用到 LoD,我们可以将涉及到 LoD 传导问题的算子分为两类: LoD-Transparent 与 LoD-Based。 + + + + + + + + + + + + + + + + + + + + + +
类型特点示例
LoD-Transparent 计算过程不依赖 LoD,输入是否有 LoD 不会影响计算的结果,通常是 position-wise 的计算 conv2d_op、batch_norm_op、dropout_op 等
LoD-Based 计算以序列为单位, 计算过程依赖 LoD lstm_op、gru_op、sequence_ops 等
+ +这两类算子的 LoD 传导需要考虑前向和反向两个过程。 + +#### 前向传导 + +在前向传导过程,与输入的 LoD 相比较,算子输出的 LoD 可能出现不变、改变和消失这三种情况: + + - 不变:适用于所有的 LoD-Transparent 算子与部分的 LoD-Based算子。可以在`InferMeta` 中调用 `ShareLoD()` 直接将输入 Var 的 LoD 共享给输出 Var, 可参考 [lstm_op](https://github.com/PaddlePaddle/Paddle/blob/a88a1faa48a42a8c3737deb0f05da968d200a7d3/paddle/fluid/operators/lstm_op.cc#L92); 如果有多个输入且都可能存在 LoD 的情况,通常默认共享第一个输入, 例如 [elementwise_ops forward](https://github.com/PaddlePaddle/Paddle/blob/5d6a1fcf16bcb48d2e66306b27d9994d9b07433c/paddle/fluid/operators/elementwise/elementwise_op.h#L69); + + - 改变:适用于部分 LoD-Based 算子。在实现 OpKernel 时需考虑输出 LoD 的正确计算,真实的 LoD 在前向计算结束后才能确定,此时仍需要在`InferMeta` 中调用 `ShareLoD()`,以确保CompileTime 时对 LoD Level 做了正确的传导,可参考 [sequence_expand_op](https://github.com/PaddlePaddle/Paddle/blob/565d30950138b9f831caa33904d9016cf53c6c2e/paddle/fluid/operators/sequence_ops/sequence_expand_op.cc); + + - 消失:适用于输出不再是序列数据的 LoD-Based 算子。此时不用再考虑前向的 LoD 传导问题,可参考 [sequence_pool_op](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/fluid/operators/sequence_ops/sequence_pool_op.cc); + +其它重要的注意事项: + + - 实现 LoD-Based 算子时,需要处理好 LoD 传导的边界情况,例如对长度为零的输入的支持,并完善相应的单测,单测 case 覆盖空序列出现在 batch 开头、中间和末尾等位置的情况,可参考 [test_lstm_op.py](https://github.com/PaddlePaddle/Paddle/blob/4292bd8687ababc7737cffbddc0d38ead2138c00/python/paddle/fluid/tests/unittests/test_lstm_op.py#L203-L216) + + - 对 LoD Level 有明确要求的算子,推荐的做法是在 `InferMeta` 中即完成 LoD Level的检查,例如 [sequence_pad_op](https://github.com/PaddlePaddle/Paddle/blob/4292bd8687ababc7737cffbddc0d38ead2138c00/paddle/fluid/operators/sequence_ops/sequence_pad_op.cc#L79)。 + + +#### 反向传导 + +通常来讲,算子的某个输入 Var 所对应的梯度 GradVar 的 LoD 应该与 Var 自身相同,所以应直接将 Var 的 LoD 共享给 GradVar,可以参考 [elementwise ops 的 backward](https://github.com/PaddlePaddle/Paddle/blob/a88a1faa48a42a8c3737deb0f05da968d200a7d3/paddle/fluid/operators/elementwise/elementwise_op.h#L189-L196) + + +## 7. 补充 +### 7.1 Paddle基于Yaml配置的算子代码自动生成 +Paddle支持动态图和静态图两种模式,在Yaml配置文件中完成算子基本属性的定义后,需要进行解析并分别生成动态图和静态图所对应的算子代码逻辑,从而将算子接入框架的执行体系。基于Yaml配置的算子代码自动生成示意图: +![code_gen_by_yaml](./code_gen_by_yaml.png) + +- 其中Yaml配置文件为前向:`python/paddle/utils/code_gen/api.yaml` 和反向:`python/paddle/utils/code_gen/backward.yaml`。 +- 动态图中自动生成的代码包括从Python API到计算Kernel间的各层调用接口实现,从底层往上分别为: + - C++ API:一套与Python API参数对齐的C++接口(只做逻辑计算,不支持自动微分),内部封装了底层kernel的选择和调用等逻辑,供上层灵活使用。 + - 注:前向算子生成C++ API头文件和实现代码分别为`paddle/phi/api/include/api.h`和`paddle/phi/api/lib/api.cc`,反向算子生成的头文件和实现代码分别为`paddle/phi/api/backward/backward_api.h`,`paddle/phi/api/lib/backward_api.cc`。 + - 动态图前向函数与反向节点(Autograd API):在C++ API的基础上进行了封装,组成一个提供自动微分功能的C++函数接口。 + - 注:生成的相关代码在`paddle/fluid/eager/api/generated/eager_generated`目录下 + - Python-C 接口:将支持自动微分功能的C++的函数接口(Autograd API)暴露到Python层供Python API调用。 + - 注:生成的Python-C 接口代码在`paddle/fluid/pybind/eager_final_state_op_function_impl.h`中 +- 静态图的执行流程与动态图不同,所以生成的代码也与动态图有较大差异。静态图由于是先组网后计算,Python API主要负责组网,算子的调度和kernel计算由静态图执行器来完成,因此自动生成的代码是将配置文件中的算子信息注册到框架内供执行器调度,主要包括[OpMaker](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/fluid/framework/op_proto_maker.h)(静态图中定义算子的输入、输出以及属性等信息)和`REGISTER_OPERATOR`(将算子名称以及OpMaker等信息进行注册)等静态图算子注册组件,具体的代码逻辑可参考`paddle/fluid/operators/generated_op.cc` + +**注意:由于代码自动生成在编译时进行,所以查看上述生成代码需要先完成[框架的编译](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/install/compile/fromsource.html)。** diff --git a/docs/dev_guides/api_contributing_guides/new_cpp_op_notes_cn.md b/docs/dev_guides/api_contributing_guides/new_cpp_op_notes_cn.md deleted file mode 100644 index 75d4a144e10..00000000000 --- a/docs/dev_guides/api_contributing_guides/new_cpp_op_notes_cn.md +++ /dev/null @@ -1,173 +0,0 @@ -# C++ OP 开发注意事项 - -## Paddle基于Yaml配置的算子代码自动生成 -Paddle支持动态图和静态图两种模式,在Yaml配置文件中完成算子基本属性的定义后,需要进行解析并分别生成动态图和静态图所对应的算子代码逻辑,从而将算子接入框架的执行体系。基于Yaml配置的算子代码自动生成示意图: -![code_gen_by_yaml](./code_gen_by_yaml.png) - -- 其中Yaml配置文件为前向:`python/paddle/utils/code_gen/api.yaml` 和反向:`python/paddle/utils/code_gen/backward.yaml`。 -- 动态图中自动生成的代码包括从Python API到计算Kernel间的各层调用接口实现,从底层往上分别为: - - C++ API:一套与Python API参数对齐的C++接口(只做逻辑计算,不支持自动微分),内部封装了底层kernel的选择和调用等逻辑,供上层灵活使用。 - - 注:前向算子生成C++ API头文件和实现代码分别为`paddle/phi/api/include/api.h`和`paddle/phi/api/lib/api.cc`,反向算子生成的头文件和实现代码分别为`paddle/phi/api/backward/backward_api.h`,`paddle/phi/api/lib/backward_api.cc`。 - - 动态图前向函数与反向节点(Autograd API):在C++ API的基础上进行了封装,组成一个提供自动微分功能的C++函数接口。 - - 注:生成的相关代码在`paddle/fluid/eager/api/generated/eager_generated`目录下 - - Python-C 接口:将支持自动微分功能的C++的函数接口(Autograd API)暴露到Python层供Python API调用。 - - 注:生成的Python-C 接口代码在`paddle/fluid/pybind/eager_final_state_op_function_impl.h`中 -- 静态图的执行流程与动态图不同,所以生成的代码也与动态图有较大差异。静态图由于是先组网后计算,Python API主要负责组网,算子的调度和kernel计算由静态图执行器来完成,因此自动生成的代码是将配置文件中的算子信息注册到框架内供执行器调度,主要包括[OpMaker](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/fluid/framework/op_proto_maker.h)(静态图中定义算子的输入、输出以及属性等信息)和`REGISTER_OPERATOR`(将算子名称以及OpMaker等信息进行注册)等静态图算子注册组件,具体的代码逻辑可参考`paddle/fluid/operators/generated_op.cc` - -**注意:由于代码自动生成在编译时进行,所以查看上述生成代码需要先完成[框架的编译](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/install/compile/fromsource.html)。** - -更多内容请参考: [C++ OP开发](new_cpp_op_cn.html) - -## 写Op注意事项 -### 1.Op兼容性问题 -对Op的修改需要考虑兼容性问题,要保证Op修改之后,之前的模型都能够正常加载及运行,即新版本的Paddle预测库能成功加载运行旧版本训练的模型。**所以,需要保证Op当前的所有输入输出参数不能被修改(文档除外)或删除,可以新增参数,但是新增的Tensor类型变量需要设置为optional,非Tensor变量需要设置默认值。更多详细内容请参考[OP修改规范:Input/Output/Attribute只能做兼容修改](https://github.com/PaddlePaddle/Paddle/wiki/OP-Input-Output-Attribute-Compatibility-Modification)** 。 - -### 2.显存优化 - -#### 2.1 为可原位计算的Op注册inplace -有些Op的计算逻辑中,输出可以复用输入的显存空间,也可称为原位计算。例如[reshape](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/phi/kernels/reshape_kernel.cc)中,输出`out`可以复用输入`x`的显存空间,因为该Op的计算逻辑不会改变`x`的实际数据,只是修改它的shape,输出和输入复用同一块显存空间不影响结果。对于这类OP,可以注册`inlace`,从而让框架在运行时自动地进行显存优化。 - -注册方式为在算子的Yaml配置中添加`inplace`配置项,格式如:`(x -> out)`,详见[Yaml配置规则](new_cpp_op_cn.html#yaml)。示例: - -```yaml -- api : reshape - args : (Tensor x, IntArray shape) - output : Tensor(out) - ... - inplace : (x -> out) -``` - -#### 2.2 减少OP中的无关变量 -通常反向Op会依赖于前向Op的某些输入、输出Tensor,以供反向Op计算使用。但有些情况下,反向Op不需要前向Op的所有输入和输出;有些情况下,反向Op只需要前向Op的部分输入和输出;有些情况下,反向Op只需要使用前向Op中输入和输出变量的Shape和LoD信息。若Op开发者在注册反向Op时,将不必要的前向Op输入和输出作为反向Op的输入,会导致这部分显存无法被框架现有的显存优化策略优化,从而导致模型显存占用过高。 - -所以在定义反向Op时需要注意以下几点: - -- 如果反向不需要前向的某些输入或输出参数,则无需在args中设置。 -- 如果有些反向Op需要依赖前向Op的输入或输出变量的的Shape或LoD,但不依赖于变量中Tensor的内存Buffer数据,且不能根据其他变量推断出该Shape和LoD,则可以通过`no_need_buffer`对该变量进行配置,详见[Yaml配置规则](new_cpp_op_cn.html#yaml)。示例: -```yaml -- backward_api : trace_grad - forward : trace (Tensor x, int offset, int axis1, int axis2) -> Tensor(out) - args : (Tensor x, Tensor out_grad, int offset, int axis1, int axis2) - output : Tensor(x_grad) - ... - no_need_buffer : x -``` - -### 3.ShareDataWith的调用 -ShareDataWith的功能是使两个Tensor共享底层buffer,在调用这个操作的时候需要特别注意,在Op内部不能将ShareDataWith作用在Op的输出上,即Op输出的Tensor必须是Malloc出来的。 - -### 4.稀疏梯度参数更新方法 -目前稀疏梯度在做更新的时候会先对梯度做merge,即对相同参数的梯度做累加,然后做参数以及附加参数(如velocity)的更新。 - -### 5.混合设备调用 -由于GPU是异步执行的,当CPU调用返回之后,GPU端可能还没有真正的执行,所以如果在Op中创建了GPU运行时需要用到的临时变量,当GPU开始运行的时候,该临时变量可能在CPU端已经被释放,这样可能会导致GPU计算出错。 - -关于GPU中的一些同步和异步操作: -``` -The following device operations are asynchronous with respect to the host: - Kernel launches; - Memory copies within a single device's memory; - Memory copies from host to device of a memory block of 64 KB or less; - Memory copies performed by functions that are suffixed with Async; - Memory set function calls. -``` - -关于cudaMemCpy和cudaMemCpyAsync注意事项: - -- 如果数据传输是从GPU端到非页锁定的CPU端,数据传输将是同步,即使调用的是异步拷贝操作。 -- 如果数据传输是从CPU端到CPU端,数据传输将是同步的,即使调用的是异步拷贝操作。 - -更多内容可参考:[Asynchronous Concurrent Execution](https://docs.nvidia.com/cuda/cuda-c-programming-guide/#asynchronous-concurrent-execution),[API synchronization behavior](https://docs.nvidia.com/cuda/cuda-runtime-api/api-sync-behavior.html#api-sync-behavior) - -### 6. LoD 在 Op 内部的传导规范 - -[LoD](https://github.com/PaddlePaddle/FluidDoc/blob/develop/doc/fluid/design/concepts/lod_tensor.md) 是 Paddle 框架用来表示变长序列数据的属性,除了仅支持输入是 padding data 的 Op 外,所有 Op 的实现都要考虑 LoD 的传导问题。 - -根据 OP 的计算过程中是否用到 LoD,我们可以将涉及到 LoD 传导问题的 OP 分为两类: LoD-Transparent 与 LoD-Based。 - - - - - - - - - - - - - - - - - - - - - -
类型特点示例
LoD-Transparent 计算过程不依赖 LoD,输入是否有 LoD 不会影响计算的结果,通常是 position-wise 的计算 conv2d_op、batch_norm_op、dropout_op 等
LoD-Based 计算以序列为单位, 计算过程依赖 LoD lstm_op、gru_op、sequence_ops 等
- -这两类 OP 的 LoD 传导需要考虑前向和反向两个过程。 - -#### 前向传导 - -在前向传导过程,与输入的 LoD 相比较,Op 输出的 LoD 可能出现不变、改变和消失这三种情况: - - - 不变:适用于所有的 LoD-Transparent OP 与部分的 LoD-Based OP。可以在`InferMeta` 中调用 `ShareLoD()` 直接将输入 Var 的 LoD 共享给输出 Var, 可参考 [lstm_op](https://github.com/PaddlePaddle/Paddle/blob/a88a1faa48a42a8c3737deb0f05da968d200a7d3/paddle/fluid/operators/lstm_op.cc#L92); 如果有多个输入且都可能存在 LoD 的情况,通常默认共享第一个输入, 例如 [elementwise_ops forward](https://github.com/PaddlePaddle/Paddle/blob/5d6a1fcf16bcb48d2e66306b27d9994d9b07433c/paddle/fluid/operators/elementwise/elementwise_op.h#L69); - - - 改变:适用于部分 LoD-Based OP。在实现 OpKernel 时需考虑输出 LoD 的正确计算,真实的 LoD 在前向计算结束后才能确定,此时仍需要在`InferMeta` 中调用 `ShareLoD()`,以确保CompileTime 时对 LoD Level 做了正确的传导,可参考 [sequence_expand_op](https://github.com/PaddlePaddle/Paddle/blob/565d30950138b9f831caa33904d9016cf53c6c2e/paddle/fluid/operators/sequence_ops/sequence_expand_op.cc); - - - 消失:适用于输出不再是序列数据的 LoD-Based OP。此时不用再考虑前向的 LoD 传导问题,可参考 [sequence_pool_op](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/fluid/operators/sequence_ops/sequence_pool_op.cc); - -其它重要的注意事项: - - - 实现 LoD-Based OP 时,需要处理好 LoD 传导的边界情况,例如对长度为零的输入的支持,并完善相应的单测,单测 case 覆盖空序列出现在 batch 开头、中间和末尾等位置的情况,可参考 [test_lstm_op.py](https://github.com/PaddlePaddle/Paddle/blob/4292bd8687ababc7737cffbddc0d38ead2138c00/python/paddle/fluid/tests/unittests/test_lstm_op.py#L203-L216) - - - 对 LoD Level 有明确要求的 OP,推荐的做法是在 `InferMeta` 中即完成 LoD Level的检查,例如 [sequence_pad_op](https://github.com/PaddlePaddle/Paddle/blob/4292bd8687ababc7737cffbddc0d38ead2138c00/paddle/fluid/operators/sequence_ops/sequence_pad_op.cc#L79)。 - - -#### 反向传导 - -通常来讲,OP 的某个输入 Var 所对应的梯度 GradVar 的 LoD 应该与 Var 自身相同,所以应直接将 Var 的 LoD 共享给 GradVar,可以参考 [elementwise ops 的 backward](https://github.com/PaddlePaddle/Paddle/blob/a88a1faa48a42a8c3737deb0f05da968d200a7d3/paddle/fluid/operators/elementwise/elementwise_op.h#L189-L196) - - -## Op性能优化 -### 1.第三方库的选择 -在写Op过程中优先使用高性能(如cudnn、mkldnn、mklml、eigen等)中提供的操作,但是一定要做benchmark,有些库中的操作在深度学习任务中可能会比较慢。因为高性能库(如eigen等)中提供的操作为了更为通用,在性能方面可能并不是很好,通常深度学习模型中数据量较小,所以有些情况下可能高性能库中提供的某些操作速度较慢。比如Elementwise系列的所有Op(前向和反向),Elementwise操作在模型中调用的次数比较多,尤其是Elementwise_add,在很多操作之后都需要添加偏置项。在之前的实现中Elementwise_op直接调用Eigen库,由于Elementwise操作在很多情况下需要对数据做Broadcast,而实验发现Eigen库做Broadcast的速度比较慢,慢的原因在这个PR[#6229](https://github.com/PaddlePaddle/Paddle/pull/6229)中有描述。 - -### 2.Op性能优化 -Op的计算速度与输入的数据量有关,对于某些Op可以根据输入数据的Shape和Op的属性参数来选择不同的计算方式。比如concat_op,当axis>=1时,在对多个tensor做拼接过程中需要对每个tensor做很多次拷贝,如果是在GPU上,需要调用cudaMemCopy。相对CPU而言,GPU属于外部设备,所以每次调用GPU的操作都会有一定的额外开销,并且当需要拷贝的次数较多时,这种开销就更为凸现。目前concat_op的实现会根据输入数据的Shape以及axis值来选择不同的调用方式,如果输入的tensor较多,且axis不等于0,则将多次拷贝操作转换成一个CUDA Kernel来完成;如果输入tensor较少,且axis等于0,使用直接进行拷贝。相关实验过程在该PR([#8669](https://github.com/PaddlePaddle/Paddle/pull/8669))中有介绍。 - -由于CUDA Kernel的调用有一定的额外开销,所以如果Op中出现多次调用CUDA Kernel,可能会影响Op的执行速度。比如之前的sequence_expand_op中包含很多CUDA Kernel,通常这些CUDA Kernel处理的数据量较小,所以频繁调用这样的Kernel会影响Op的计算速度,这种情况下最好将这些小的CUDA Kernel合并成一个。在优化sequence_expand_op过程(相关PR[#9289](https://github.com/PaddlePaddle/Paddle/pull/9289))中就是采用这种思路,优化后的sequence_expand_op比之前的实现平均快出约1倍左右,相关实验细节在该PR([#9289](https://github.com/PaddlePaddle/Paddle/pull/9289))中有介绍。 - -减少CPU与GPU之间的拷贝和同步操作的次数。比如fetch操作,在每个迭代之后都会对模型参数进行更新并得到一个loss,并且数据从GPU端到没有页锁定的CPU端的拷贝是同步的,所以频繁的fetch多个参数会导致模型训练速度变慢。 - -## Op数值稳定性问题 -### 1.有些Op存在数值稳定性问题 -出现数值稳定性的主要原因程序在多次运行时,对浮点型数据施加操作的顺序可能不同,进而导致最终计算结果不同。而GPU是通过多线程并行计算的方式来加速计算的,所以很容易出现对浮点数施加操作的顺序不固定现象。 - -目前发现cudnn中的卷积操作、cudnn中的MaxPooling、CUDA中CudaAtomicXX、ParallelExecutor的Reduce模式下参数梯度的聚合等操作运行结果是非确定的。 - -为此Paddle中添加了一些FLAGS,比如使用FLAGS_cudnn_deterministic来强制cudnn使用确定性算法、FLAGS_cpu_deterministic强制CPU端的计算使用确定性方法。 - -## 其他 -### 1.报错信息 -Enforce提示信息不能为空,并且需要写明,因为报错信息可以更快更方便地分析出错误的原因。 - -### 2.Op的数学公式 -如果Op有数学公式,一定要在代码中将数学公式写明,并在Python API的Doc中显示,因为用户在对比不同框架的计算结果时可能需要了解Paddle对Op是怎么实现的。 - -**注意:**在merge到develop分支之前一定进行公式预览。 - -### 3.Python端Op接口中参数的顺序 -Python API中参数的顺序一般按照重要性来排,以fc为例: -``` -def fc(input, - size, - num_flatten_dims=1, - param_attr=None, - bias_attr=None, - act=None, - is_test=False, - name=None) -``` diff --git a/docs/dev_guides/api_contributing_guides/new_python_api_cn.md b/docs/dev_guides/api_contributing_guides/new_python_api_cn.md index 4c309275309..e5605245cfa 100644 --- a/docs/dev_guides/api_contributing_guides/new_python_api_cn.md +++ b/docs/dev_guides/api_contributing_guides/new_python_api_cn.md @@ -4,10 +4,10 @@ ## 开发 Python API代码 -这分为两种情况,Paddle 的 API 包含需要开发 c++ operator 的和不需要开发 operator 而仅使用现有 Python API 组合得到的两种,但两种情况下均有 Python 端的开发工作。 +这分为两种情况,Paddle 的 API 包含需要开发 c++ 算子的和不需要开发 c++ 算子而仅使用现有 Python API 组合得到的两种,但两种情况下均有 Python 端的开发工作。 -1. 包含 c++ operator 的开发的情况,需要在 Python 端添加相应 API 以调用对应的 operator; -2. 不需要开发 c++ operator 的情况,需要在 Python 端添加相应 API 以调用其他 API 组合实现功能; +1. 包含 c++ 算子的开发的情况,需要在 Python 端添加相应 API 以调用对应的算子; +2. 不需要开发 c++ 算子的情况,需要在 Python 端添加相应 API 以调用其他 API 组合实现功能; ### 文件位置与 API 名称 @@ -76,7 +76,7 @@ from a import f # it's ok, too # Python/paddle/tensor/math.py def logsumexp(...): ... - + # Python/paddle/tensor/__init__.py from .math import logsumexp @@ -155,17 +155,19 @@ Python API 一般包含如下的部分: 例子: ```Python -def mm(input, mat2, name=None): - # 为了突出重点,省略部分代码 - - # 动态图,直接调用 op 对应的 CPython 函数 - if paddle.in_dynamic_mode(): +def mm(input, mat2, name=None): + # 为了突出重点,省略部分代码 + # 新动态图模式,直接调用 op 对应的 CPython 函数 + if in_dygraph_mode(): + return _C_ops.final_state_matmul(input, mat2, False, False) + # 旧动态图模式 + elif _in_legacy_dygraph(): return _C_ops.matmul_v2(input, mat2) - # 静态分支 + # 静态分支 ## 检测输入 - __check_input(input, mat2) - + __check_input(input, mat2) + ## 构造输出,添加 op,返回输出 helper = LayerHelper('mm', **locals()) out = helper.create_variable_for_type_inference(dtype=input.dtype) @@ -188,44 +190,56 @@ def ones(shape, dtype=None, name=None): 因为 `fill_constant` 里已经处理了动态图和静态图的情况,所以直接调用即可。 -而如果 API 的实现中需要调用一个 op 时,则需要根据动态图和静态图使用不同的写法,用 `paddle.in_dynamic_mode()` 获取当前状态走不同的分支。 +而如果 API 的实现中需要调用一个C++算子时,则需要根据动态图和静态图使用不同的写法。 #### 动静态图分支 +**动态图分支** +由于目前动态图正处在重构升级阶段,所以需要为新旧动态图分别添加对应的代码分支。其中 `in_dygraph_mode()` 表示新动态图分支,`_in_legacy_dygraph()`表示旧动态图分支。 -参考前面 `paddle.nn.functional.kl_div` 的代码,动态图分支的写法一般是调用 API 对应的 CPython 函数。 +参考`paddle.trace` 的代码,动态图分支的写法一般是调用 API 对应的 CPython 函数。 ```Python -_C_ops.matmul_v2(input, mat2) +# 新动态图模式 +if in_dygraph_mode(): + return _C_ops.final_state_trace( x, offset, axis1, axis2 ) + +# 旧动态图模式 +if _in_legacy_dygraph(): + return _C_ops.trace(x, 'offset', offset, 'axis1', axis1, 'axis2', axis2) ``` -`_C_ops` 是 `Python/paddle/_C_ops.py`,其中从 paddle 编译得到的二进制文件中 import 了 c++ operator 对应的 Python C 函数,函数名和 operator 名一致。如希望调用名为 `matmul_v2` 的 operator,则使用 `_C_ops.matmul_v2`, 然后传入参数。 +`_C_ops` 是 `Python/paddle/_C_ops.py`,其中从 paddle 编译得到的二进制文件中 import 了 c++ 算子对应的 Python C 函数。 -其中参数分为两个部分,`Tensor` 对于 `Tensor` 类型的输入,直接按照定义 opmaker 时添加输入的次序,以按位置传参的方式传入。关于 opmaker 可以参考 [定义OpProtoMaker类](new_cpp_op_cn.html#opprotomaker)(本文中用 opmaker 简称 operator ). +- 在新动态图模式下,Python C 的调用函数名为`final_state_` + 算子名,然后将参数按照Yaml中定义的输入参数顺序传入即可。 +- 在旧动态图模式下,Python C 函数名和算子名一致。如希望调用名为 `trace` 的算子,则使用 `_C_ops.trace`, 然后传入参数。其中参数分为两个部分: + - 对于 `Tensor` 类型的输入,直接按照Yaml中的定义,按位置传参的方式传入 + - 对于非 `Tensor` 类型的输入,则以 `attribute 名,attribute 值` 交替的方式传入,这类似 Python 中的按关键字传参的方式。然后返回调用函数得到的结果。 -而对于非 `Tensor` 类型的输入(对应 opmaker 中的 Attribute),则以 `attribute 名,attribute 值` 交替的方式传入,这类似 Python 中的按关键字传参的方式。然后返回调用函数得到的结果。 -而对于静态图,则一般分为创建输出 Tensor,添加 operator 两步。 +**静态图分支** +对于静态图,一般分为创建输出 Tensor,添加 operator 两步。 ```Python -loss = _C_ops.kldiv_loss(input, label, 'reduction', 'none') - -# layerhelper 创建准备工作 -helper = LayerHelper('kl_div', **locals()) +# LayerHelper是一个用于创建op输出变量、向program中添加op的辅助工具类 +helper = LayerHelper('trace', **locals()) # 创建输出 Tensor -loss = helper.create_variable_for_type_inference(dtype=input.dtype) +out = helper.create_variable_for_type_inference(dtype=x.dtype) # 将输入 Tensor,输出 Tensor, 非 Tensor 的 attributes 以三个字典的形式 # 作为参数添加 operator helper.append_op( - type='kldiv_loss', - inputs={'X': input, - 'Target': label}, - outputs={'Loss': loss}, - attrs={'reduction': 'none'}) + type='trace', + inputs={'Input': [x]}, + attrs={'offset': offset, + 'axis1': axis1, + 'axis2': axis2}, + outputs={'Out': [out]}) +return out ``` +注意:在`append_op`添加的`inputs`和`outputs`项,其中的key值(静态图中变量名)一般为Yaml中定义的输入输出Tensor变量名的首字母大写格式,静态图中的变量名可以在`paddle/fluid/operators/generated_op.cc`(需要先开发C++算子的并完成编译)文件内对应算子的`OpMaker`中找到;`attrs`项的变量名与Yaml中相同。 +这里`trace`中的'Input'没有与Yaml配置的中'x'直接对应是由于为了兼容旧算子体系下`Trace`算子的`OpMaker`实现而做了额外的映射,新增算子时无需考虑这种情况。 -上述的代码中,动态图分支的 `input, label` 对应静态图分支中的 inputs 字典,其次序和 opmaker 中定义的有关。而静态图中的 attrs 字典在动态图分支中则以 `key, value` 交替的形式排列,如果有多个 attribute, 则依次排列。 ## 开发单元测试代码 @@ -235,7 +249,7 @@ helper.append_op( 单元测试相关的开发规范可以参考 - [C++ OP 开发(新增原生算子)](new_cpp_op_cn.html) ,[Op开发手册(Operator Development Manual)](https://github.com/PaddlePaddle/Paddle/wiki/Operator-Development-Manual-Index). + [C++ 算子开发指南-添加单元测试](new_cpp_op_cn.html#tianjiadanyuanceshi) ,[Op开发手册(Operator Development Manual)](https://github.com/PaddlePaddle/Paddle/wiki/Operator-Development-Manual-Index). 在此不作展开,主要讲述 Python API 的单元测试。 @@ -267,7 +281,7 @@ helper.append_op( self.x_np = np.random.uniform(-3, 3, [10, 12]).astype('float32') self.place=paddle.CUDAPlace(0) if paddle.is_compiled_with_cuda() \ else paddle.CPUPlace() - + def test_static_api(self): paddle.enable_static() with paddle.static.program_guard(paddle.static.Program()): @@ -280,7 +294,7 @@ helper.append_op( out_ref = ref_hardtanh(self.x_np) for r in res: self.assertEqual(np.allclose(out_ref, r), True) - + def test_dygraph_api(self): paddle.disable_static(self.place) x = paddle.to_tensor(self.x_np) @@ -290,7 +304,7 @@ helper.append_op( out_ref = ref_hardtanh(self.x_np) for r in [out1, out2]: self.assertEqual(np.allclose(out_ref, r.numpy()), True) - + out1 = F.hardtanh(x, -2.0, 2.0) m = paddle.nn.Hardtanh(-2.0, 2.0) out2 = m(x) @@ -306,7 +320,7 @@ helper.append_op( paddle.disable_static(place=paddle.fluid.CPUPlace()) self.run_imperative() paddle.enable_static() - + with fluid.program_guard(fluid.Program()): self.run_static() From 70fefc4d9171571145c2ea55039592aa08f69650 Mon Sep 17 00:00:00 2001 From: zyfncg Date: Tue, 21 Jun 2022 07:50:53 +0000 Subject: [PATCH 06/10] fix format --- .../api_contributing_guides_cn.rst | 1 - .../api_contributing_guides/new_cpp_op_cn.md | 19 +++++++++---------- .../new_python_api_cn.md | 12 +++++++----- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/dev_guides/api_contributing_guides/api_contributing_guides_cn.rst b/docs/dev_guides/api_contributing_guides/api_contributing_guides_cn.rst index 7bd2a84a8b2..c027d16e4d2 100644 --- a/docs/dev_guides/api_contributing_guides/api_contributing_guides_cn.rst +++ b/docs/dev_guides/api_contributing_guides/api_contributing_guides_cn.rst @@ -105,6 +105,5 @@ API设计文档的目的是为了社区开发者更容易的参与开源项目 api_design_guidelines_standard_cn.md new_python_api_cn.md new_cpp_op_cn.md - new_cpp_op_notes_cn.md api_docs_guidelines_cn.md api_accpetance_criteria_cn.md diff --git a/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md b/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md index f6aff7057dc..447aa549e9e 100644 --- a/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md +++ b/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md @@ -543,6 +543,7 @@ void TraceKernel(const Context& dev_ctx, #### 3.2.2 实现 Kernel 函数 **复用已有Kernel实现设备无关Kernel函数** + 由于目前的Kernel复用机制为新推出的功能,暂未对已有算子进行升级改造,所以这里我们以一个不在框架中的linear算子(out = x * w + b)为例来介绍复用已有Kernel实现设备无关Kernel函数。(linear kernel 的实现源码需要放置在`paddle/phi/kernels/linear_kernel.cc`) `LinearKernel` 的实现代码如下: @@ -791,8 +792,6 @@ def trace(x, offset=0, axis1=0, axis2=1, name=None): return out ``` -> 概念解释:LayerHelper是一个用于创建op输出变量、向program中添加op的辅助工具类 - - Python API 实现要点(详见[飞桨API Python 端开发指南](./new_python_api_cn.html)) - 对输入参数进行合法性检查,即 `__check_input(input, offset, axis1, axis2)` - 添加动态图分支调用,即 `if in_dygraph_mode` 新动态图分支和 `if _in_legacy_dygraph` 旧动态图分支 @@ -801,19 +800,19 @@ def trace(x, offset=0, axis1=0, axis2=1, name=None): ## 5. 添加单元测试 -单测包括对比前向Op不同设备(CPU、CUDA)的实现、对比反向OP不同设备(CPU、CUDA)的实现、反向Op的梯度测试。下面介绍介绍[`TraceOp`的单元测试](https://github.com/PaddlePaddle/Paddle/blob/develop/python/paddle/fluid/tests/unittests/test_trace_op.py)。 +单测包括对比前向算子不同设备(CPU、CUDA)的实现、对比反向算子不同设备(CPU、CUDA)的实现、反向算子的梯度测试。下面介绍介绍[`TraceOp`的单元测试](https://github.com/PaddlePaddle/Paddle/blob/develop/python/paddle/fluid/tests/unittests/test_trace_op.py)。 **注意:** 单测中的测试用例需要尽可能的覆盖Kernel中的所有分支。 -### 5.1 前向 Operator 单测 +### 5.1 前向算子单测 -Op单元测试继承自`OpTest`。各项具体的单元测试在`TestTraceOp`里完成。测试Operator,需要: +算子单元测试继承自`OpTest`。各项具体的单元测试在`TestTraceOp`里完成。测试算子,需要: 1. 在`setUp`函数定义输入、输出,以及相关的属性参数。 2. 生成随机的输入数据。 -3. 在Python脚本中实现与前向operator相同的计算逻辑,得到输出值,与operator前向计算的输出进行对比。 +3. 在Python脚本中实现与前向算子相同的计算逻辑,得到输出值,与算子前向计算的输出进行对比。 4. 反向计算已经自动集成进测试框架,直接调用相应接口即可。 @@ -846,12 +845,12 @@ Op单元测试继承自`OpTest`。各项具体的单元测试在`TestTraceOp`里 上面的代码首先导入依赖的包,下面是对`setUp`函数中操作的重要变量的详细解释: - - `self.op_type = "trace" ` : 定义类型,与operator注册时注册的类型一致。 + - `self.op_type = "trace" ` : 定义类型,与算子定义的名称相同。 - `self.python_api = paddle.trace` : 定义python api,与python调用接口一致。 - `self.inputs` : 定义输入,类型为`numpy.array`,并初始化。 - - `self.outputs` : 定义输出,并在Python脚本中完成与operator同样的计算逻辑,返回Python端的计算结果。 + - `self.outputs` : 定义输出,并在Python脚本中完成与算子同样的计算逻辑,返回Python端的计算结果。 -### 5.2 反向 operator 单测 +### 5.2 反向算子单测 而反向测试中: @@ -860,7 +859,7 @@ Op单元测试继承自`OpTest`。各项具体的单元测试在`TestTraceOp`里 - 第二个参数`'Out'` : 指定前向网络最终的输出目标变量`Out`。 - 第三个参数`check_eager` : `check_eager=True`表示开启新动态图(eager模式)单测,`check_eager`默认为`False`。 -- 对于存在多个输入的反向Op测试,需要指定只计算部分输入梯度的case +- 对于存在多个输入的反向算子测试,需要指定只计算部分输入梯度的case - 例如,`test_elementwise_sub_op.py`中的`test_check_grad_ingore_x`和`test_check_grad_ingore_y`分支用来测试只需要计算一个输入梯度的情况 - 此处第三个参数max_relative_error:指定检测梯度时能容忍的最大错误值。 diff --git a/docs/dev_guides/api_contributing_guides/new_python_api_cn.md b/docs/dev_guides/api_contributing_guides/new_python_api_cn.md index e5605245cfa..ecb2932105c 100644 --- a/docs/dev_guides/api_contributing_guides/new_python_api_cn.md +++ b/docs/dev_guides/api_contributing_guides/new_python_api_cn.md @@ -156,15 +156,15 @@ Python API 一般包含如下的部分: ```Python def mm(input, mat2, name=None): - # 为了突出重点,省略部分代码 - # 新动态图模式,直接调用 op 对应的 CPython 函数 + ## 为了突出重点,省略部分代码 + ## 新动态图模式,直接调用 op 对应的 CPython 函数 if in_dygraph_mode(): return _C_ops.final_state_matmul(input, mat2, False, False) - # 旧动态图模式 + ## 旧动态图模式 elif _in_legacy_dygraph(): return _C_ops.matmul_v2(input, mat2) - # 静态分支 + ## 静态分支 ## 检测输入 __check_input(input, mat2) @@ -194,6 +194,7 @@ def ones(shape, dtype=None, name=None): #### 动静态图分支 **动态图分支** + 由于目前动态图正处在重构升级阶段,所以需要为新旧动态图分别添加对应的代码分支。其中 `in_dygraph_mode()` 表示新动态图分支,`_in_legacy_dygraph()`表示旧动态图分支。 参考`paddle.trace` 的代码,动态图分支的写法一般是调用 API 对应的 CPython 函数。 @@ -217,6 +218,7 @@ if _in_legacy_dygraph(): **静态图分支** + 对于静态图,一般分为创建输出 Tensor,添加 operator 两步。 ```Python @@ -237,7 +239,7 @@ helper.append_op( outputs={'Out': [out]}) return out ``` -注意:在`append_op`添加的`inputs`和`outputs`项,其中的key值(静态图中变量名)一般为Yaml中定义的输入输出Tensor变量名的首字母大写格式,静态图中的变量名可以在`paddle/fluid/operators/generated_op.cc`(需要先开发C++算子的并完成编译)文件内对应算子的`OpMaker`中找到;`attrs`项的变量名与Yaml中相同。 +注意:在`append_op`添加的`inputs`和`outputs`项,其中的key值(静态图中变量名)一般为Yaml中定义的输入输出Tensor变量名的首字母大写格式,静态图中的变量名可以在`paddle/fluid/operators/generated_op.cc`(需要先开发C++算子并完成编译)文件内对应算子的`OpMaker`中找到;`attrs`项的变量名与Yaml中相同。 这里`trace`中的'Input'没有与Yaml配置的中'x'直接对应是由于为了兼容旧算子体系下`Trace`算子的`OpMaker`实现而做了额外的映射,新增算子时无需考虑这种情况。 From 3f02c53a2988a64014a81c54a9cd99a9955df2b9 Mon Sep 17 00:00:00 2001 From: zyfncg Date: Tue, 21 Jun 2022 09:34:03 +0000 Subject: [PATCH 07/10] fix code format --- .../api_contributing_guides/new_python_api_cn.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/dev_guides/api_contributing_guides/new_python_api_cn.md b/docs/dev_guides/api_contributing_guides/new_python_api_cn.md index ecb2932105c..5bcc910293b 100644 --- a/docs/dev_guides/api_contributing_guides/new_python_api_cn.md +++ b/docs/dev_guides/api_contributing_guides/new_python_api_cn.md @@ -156,15 +156,15 @@ Python API 一般包含如下的部分: ```Python def mm(input, mat2, name=None): - ## 为了突出重点,省略部分代码 - ## 新动态图模式,直接调用 op 对应的 CPython 函数 - if in_dygraph_mode(): + # 为了突出重点,省略部分代码 + # 新动态图模式,直接调用 op 对应的 CPython 函数 + if in_dygraph_mode(): return _C_ops.final_state_matmul(input, mat2, False, False) - ## 旧动态图模式 + # 旧动态图模式 elif _in_legacy_dygraph(): return _C_ops.matmul_v2(input, mat2) - ## 静态分支 + # 静态分支 ## 检测输入 __check_input(input, mat2) From 0e1c5e8b188da32ebe728666621163e89a607fbd Mon Sep 17 00:00:00 2001 From: Chen Long <1300851984@qq.com> Date: Thu, 23 Jun 2022 10:32:31 +0800 Subject: [PATCH 08/10] Update new_cpp_op_cn.md --- docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md b/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md index 447aa549e9e..f2357428237 100644 --- a/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md +++ b/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md @@ -974,6 +974,8 @@ PADDLE_ENFORCE_EQ(比较对象A, 比较对象B, 错误提示信息) 由于CUDA Kernel的调用有一定的额外开销,所以如果算子中出现多次调用CUDA Kernel,可能会影响算子的执行速度。比如之前的sequence_expand_op中包含很多CUDA Kernel,通常这些CUDA Kernel处理的数据量较小,所以频繁调用这样的Kernel会影响算子的计算速度,这种情况下最好将这些小的CUDA Kernel合并成一个。在优化sequence_expand_op过程(相关PR[#9289](https://github.com/PaddlePaddle/Paddle/pull/9289))中就是采用这种思路,优化后的sequence_expand_op比之前的实现平均快出约1倍左右,相关实验细节在该PR([#9289](https://github.com/PaddlePaddle/Paddle/pull/9289))中有介绍。 减少CPU与GPU之间的拷贝和同步操作的次数。比如fetch操作,在每个迭代之后都会对模型参数进行更新并得到一个loss,并且数据从GPU端到没有页锁定的CPU端的拷贝是同步的,所以频繁的fetch多个参数会导致模型训练速度变慢。 + +更多算子性能优化方法,请参考 [算子性能优化 方法介绍](../op_optimization/op_optimization_method_introduction_cn.html)。 ### 6.5 稀疏梯度参数更新方法 目前稀疏梯度在做更新的时候会先对梯度做merge,即对相同参数的梯度做累加,然后做参数以及附加参数(如velocity)的更新。 From 4fb2d2b82b762587b3006d7e96f3cd01bbae68b6 Mon Sep 17 00:00:00 2001 From: Chen Long <1300851984@qq.com> Date: Thu, 23 Jun 2022 11:07:54 +0800 Subject: [PATCH 09/10] Update new_cpp_op_cn.md --- docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md b/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md index f2357428237..22c7d566f82 100644 --- a/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md +++ b/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md @@ -975,7 +975,7 @@ PADDLE_ENFORCE_EQ(比较对象A, 比较对象B, 错误提示信息) 减少CPU与GPU之间的拷贝和同步操作的次数。比如fetch操作,在每个迭代之后都会对模型参数进行更新并得到一个loss,并且数据从GPU端到没有页锁定的CPU端的拷贝是同步的,所以频繁的fetch多个参数会导致模型训练速度变慢。 -更多算子性能优化方法,请参考 [算子性能优化 方法介绍](../op_optimization/op_optimization_method_introduction_cn.html)。 +更多算子性能优化方法,请参考 [算子性能优化 方法介绍](../../op_optimization/op_optimization_method_introduction_cn.html)。 ### 6.5 稀疏梯度参数更新方法 目前稀疏梯度在做更新的时候会先对梯度做merge,即对相同参数的梯度做累加,然后做参数以及附加参数(如velocity)的更新。 From 658ca6bee4cd8dd27c3c0c7ca51f78c6c664634e Mon Sep 17 00:00:00 2001 From: Chen Long <1300851984@qq.com> Date: Thu, 23 Jun 2022 11:45:35 +0800 Subject: [PATCH 10/10] Update new_cpp_op_cn.md --- docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md b/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md index 22c7d566f82..f2357428237 100644 --- a/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md +++ b/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md @@ -975,7 +975,7 @@ PADDLE_ENFORCE_EQ(比较对象A, 比较对象B, 错误提示信息) 减少CPU与GPU之间的拷贝和同步操作的次数。比如fetch操作,在每个迭代之后都会对模型参数进行更新并得到一个loss,并且数据从GPU端到没有页锁定的CPU端的拷贝是同步的,所以频繁的fetch多个参数会导致模型训练速度变慢。 -更多算子性能优化方法,请参考 [算子性能优化 方法介绍](../../op_optimization/op_optimization_method_introduction_cn.html)。 +更多算子性能优化方法,请参考 [算子性能优化 方法介绍](../op_optimization/op_optimization_method_introduction_cn.html)。 ### 6.5 稀疏梯度参数更新方法 目前稀疏梯度在做更新的时候会先对梯度做merge,即对相同参数的梯度做累加,然后做参数以及附加参数(如velocity)的更新。

6cp13a3g5*) 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 literal 0 HcmV?d00001 diff --git a/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md b/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md index 5215f6ed5ee..45c732f3b90 100644 --- a/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md +++ b/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md @@ -1,6 +1,6 @@ # C++ OP 开发 -> 注:飞桨原生算子的开发范式正在进行重构与升级,升级后算子开发方式会大幅简化,我们会及时更新本文档内容,升级后的算子开发范式预计会在2.3版本正式上线。 +> 注:飞桨原生算子的开发范式正处在重构升级后的上线初期,如果在开发过程中遇到问题欢迎通过[issue](https://github.com/PaddlePaddle/Paddle/issues)向我们反馈。 ## 1. 概念简介 @@ -23,7 +23,7 @@ 算子描述及定义 -paddle/fluid/operators/xxx_op.cc +python/paddle/utils/code_gen/api.yaml & python/paddle/utils/code_gen/backward.yaml 算子InferMeta @@ -46,147 +46,182 @@ 关于Python API所处位置,可以参考 [飞桨官方 API 文档](https://www.paddlepaddle.org.cn/documentation/docs/zh/api/index_cn.html) ,了解各个目录存放API的性质,从而决定具体的放置目录。 -接下来,我们以Trace操作,计算输入 Tensor 在指定平面上的对角线元素之和,并输出相应的计算结果,即 [TraceOp](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/fluid/operators/trace_op.cc) 为例来介绍如何新增算子。 +接下来,我们以Trace操作,计算输入 Tensor 在指定平面上的对角线元素之和,并输出相应的计算结果,即 [trace](https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/trace_cn.html#trace) 为例来介绍如何新增算子。 ## 2. 新增算子描述及定义 -算子描述及定义是定义运算的基本属性,本身是设备无关的。 +算子描述及定义是定义运算的基本属性,主要包括算子的输入、输出以及各项非计算逻辑的配置,这些都是设备无关的。 -首先简单介绍新增算子(以下简称Op)描述需要用到的基类。 +### 2.1 算子Yaml文件配置 +我们在`python/paddle/utils/code_gen/api.yaml`和`python/paddle/utils/code_gen/backward.yaml`文件中对算子进行描述及定义,在框架编译时会根据Yaml文件中的配置自动生成C++端的相关代码接口以及内部实现(可参考[Paddle基于Yaml配置的算子代码自动生成](new_cpp_op_notes_cn.md)),下面主要以Trace为例介绍算子的Yaml配置规则: -- `framework::OpProtoAndCheckerMaker`:描述该Op的输入、输出、属性、注释。 -- `framework::OperatorBase`: Operator(简写,Op)基类。 -- `framework::OperatorWithKernel`:继承自OperatorBase,Op有计算函数,称作有Kernel。 - -根据是否包含Kernel,可以将Op分为两种:包含Kernel的Op和不包含kernel的Op: - -- 包含 Kernel 的 Op 继承自 `OperatorWithKernel`:这类Op的功能实现与输入的数据类型、数据布局、数据所在的设备以及Op实现所调用第三方库等有关。比如ConvOp,如果使用CPU计算,一般通过调用mkl库中的矩阵乘操作实现,如果使用GPU计算,一般通过调用cublas库中的矩阵乘操作实现,或者直接调用cudnn库中的卷积操作。 -- 不包含 Kernel 的 Op 继承自 `OperatorBase`:因为这类Op的功能实现与设备以及输入的数据不相关。比如WhileOp、IfElseOp等。 - -> 注:本教程仅介绍如何实现带有计算Kernel的算子,不带Kernel的算子主要用于特殊场景,一般没有需求。 - -### 2.1 定义OpProtoMaker类 - -Trace运算由一个输入,三个属性与一个输出组成。 - -首先定义`ProtoMaker`来描述该Op的输入、输出、属性并添加注释: - -```cpp -class TraceOpMaker : public framework::OpProtoAndCheckerMaker { - public: - void Make() override { - AddInput("Input", - "(Tensor) The input tensor, from which the diagonals are taken."); - AddOutput("Out", "(Tensor) the sum along diagonals of the input tensor"); - AddAttr( - "offset", - R"DOC((int, default 0), offset of the diagonal from the main diagonal. Can be both positive and negative. Defaults to 0. - )DOC") - .SetDefault(0); - AddAttr( - "axis1", - R"DOC((int, default 0), the first axis of the 2-D planes from which the diagonals should be taken. - Can be either positive or negative. Default: 0. - )DOC") - .SetDefault(0); - AddAttr( - "axis2", - R"DOC((int, default 1), the second axis of the 2-D planes from which the diagonals should be taken. - Can be either positive or negative. Default: 1. - )DOC") - .SetDefault(1); - AddComment(R"DOC( -Trace Operator. -Return the sum along diagonals of the input tensor. -The behavior of this operator is similar to how `numpy.trace` works. - -If Input is 2-D, returns the sum of diagonal. -If Input has larger dimensions, then returns an tensor of diagonals sum, diagonals be taken from -the 2-D planes specified by dim1 and dim2. - -)DOC"); - } -}; +python/paddle/utils/code_gen/api.yaml: ``` - -[`TraceOpMaker`](https://github.com/PaddlePaddle/Paddle/blob/befa78ea3fa9d0dae096a7de91f626b0c31daee8/paddle/fluid/operators/trace_op.cc#L29)继承自`framework::OpProtoAndCheckerMaker`。 - -开发者通过覆盖`framework::OpProtoAndCheckerMaker`中的`Make`函数来定义Op所对应的Proto,通过`AddInput`添加输入参数,通过`AddOutput`添加输出参数,通过`AddAttr`添加属性参数,通过`AddComment`添加Op的注释。这些函数会将对应内容添加到`OpProto`中。 - -上面的代码在`TraceOp`中添加两个输入`X`和`Y`,添加了一个输出`Out`,并简要解释了各自含义,命名请遵守[命名规范](https://github.com/PaddlePaddle/FluidDoc/blob/release/1.2/doc/fluid/dev/name_convention.md)。 - -> 注意:OpProtoMaker中不允许定义未使用的输入、输出或属性。 - -### 2.2 定义GradOpMaker类 - -通常情况下,大部分Op只有一个对应的反向Op,每个Op都会有一个对应的`GradOpMaker`。为方便代码编写,paddle为只有一个反向的Op提供了一个模板类[`SingleGradOpMaker`](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/fluid/framework/grad_op_desc_maker.h#L188)。`TraceOp`的`GradOpMaker`需要继承这个模板类,并在`Apply()`方法中设置反向Op的输入、输出和属性。此外,paddle还提供了一个默认的`GradOpMaker`, -[`DefaultGradOpMaker`](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/fluid/framework/grad_op_desc_maker.h#L227),该模板类会使用前向Op的全部输入(`Input`)输出(`Output`)以及输出变量所对应的梯度(`Output@Grad`)作为反向Op的输入,将前向Op的输入变量所对应的的梯度(`Input@Grad`)作为输出。 - -**注意:** - -不要将反向Op不会用到的变量放到反向Op的输入列表中,这样会导致这些不会被反向Op用到的变量的空间不能够及时回收,进而有可能导致用到该Op的模型可以设置的batch_size较低。 -比如`relu`操作的前向操作为:`out.device(d) = x.cwiseMax(static_cast(0));`反向操作为:`dx.device(d) = dout * (out > static_cast(0)).template cast();`。显然,反向操作中只是用到了`out`、`dout`、`dx`,没有用到`x`。因此,通常不建议使用默认的`DefaultGradOpMaker`。 - -下面示例定义了`TraceOp`的`GradOpMaker`。 - -```cpp -template -class TraceGradOpMaker : public framework::SingleGradOpMaker { - public: - using framework::SingleGradOpMaker::SingleGradOpMaker; - - protected: - void Apply(GradOpPtr grad_op) const override { - grad_op->SetType("trace_grad"); - grad_op->SetInput("Input", this->Input("Input")); - grad_op->SetInput(framework::GradVarName("Out"), this->OutputGrad("Out")); - grad_op->SetOutput(framework::GradVarName("Input"), - this->InputGrad("Input")); - grad_op->SetAttrMap(this->Attrs()); - } -}; +- api : trace + args : (Tensor x, int offset = 0, int axis1 = 0, int axis2 = 1) + output : Tensor(out) + infer_meta : + func : TraceInferMeta + kernel : + func : trace + backward : trace_grad ``` - -**注意:** - -- 有些Op的前向逻辑和反向逻辑是一样的,比如[`ScaleOp`](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/fluid/operators/scale_op.cc).这种情况下,前向Op和反向Op的Kernel可以为同一个。 -- 有些前向Op所对应的反向Op可能有多个,比如[`SumOp`](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/fluid/operators/sum_op.cc),这种情况下,`GradMaker`需要继承`framework::GradOpDescMakerBase`。 -- 有些Op的反向对应另一个Op的前向,比如[`SplitOp`](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/fluid/operators/split_op.h),这种情况下,[`SplitGradMaker`](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/fluid/operators/split_op.h#L157)中定义的`SplitOp`反向Op的Type就是`concat`, -- 为高效地同时支持命令式编程模式(动态图)和声明式编程模式(静态图),`SingleGradOpMaker`是一个模板类,在注册Operator时需要同时注册`TraceOpGradMaker`(静态图使用)和`TraceOpGradMaker`(动态图使用)。 - -### 2.3 定义Op类 - -下面实现了TraceOp的定义: - -```cpp -class TraceOp : public framework::OperatorWithKernel { - public: - using framework::OperatorWithKernel::OperatorWithKernel; -}; +python/paddle/utils/code_gen/backward.yaml: ``` - -[`TraceOp`](https://github.com/PaddlePaddle/Paddle/blob/bd4dc3be34584f9b273ecec07297fb05e1cf4c52/paddle/fluid/operators/trace_op.cc#L24)继承自`OperatorWithKernel`。`public`成员: - -```cpp -using framework::OperatorWithKernel::OperatorWithKernel; +- backward_api : trace_grad + forward : trace (Tensor x, int offset, int axis1, int axis2) -> Tensor(out) + args : (Tensor x, Tensor out_grad, int offset, int axis1, int axis2) + output : Tensor(x_grad) + infer_meta : + func : UnchangedInferMeta + param : [x] + kernel : + func : trace_grad + data_type : x + no_need_buffer : x ``` -这句表示使用基类`OperatorWithKernel`的构造函数,也可写成: - -```cpp -TraceOp(const std::string &type, const framework::VariableNameMap &inputs, - const framework::VariableNameMap &outputs, - const framework::AttributeMap &attrs) - : OperatorWithKernel(type, inputs, outputs, attrs) {} -``` - -此外,Operator类需要在有必要时重写`GetExpectedKernelType`接口。 +`api.yaml`和`backward.yaml`分别对算子的前向和反向进行配置,首先`api.yaml`中前向算子的配置规则如下: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
配置项配置内容及规则
api算子名称,与该算子Python API函数名相同(命名方式为:全小写+下划线),示例中为trace
args算子输入参数,与该算子Python API函数的输入参数对应(当前支持的输入数据类型包括:Tensor, Tensor[]/*Tensor数组*/, float, double, bool, int, int64_t, int[], int64_t[], str, Place, DataType, DataLayout, IntArray/*主要用于表示shape,index和axes等类型数据,可以直接使用Tensor或者普通整型数组构造,目前仍在测试阶段,如非必要暂不建议使用*/, Scalar/*标量,支持不同的普通数据类型*/)
output算子输出类型,目前支持Tensor和Tensor[], 多个输出间用逗号“,”分隔开。可以使用”()”选择性标记输入的名字, 如未标记默认为'out'
infer_metaInferMeta函数负责根据输入推断返回Tensor的维度与类型,这里是对算子使用的InferMeta函数进行配置
infer_meta:func调用的InferMeta函数, 这里trace调用的是TraceInferMeta函数
infer_meta:paramInferMeta函数的输入参数,可以对args中的参数进行选择传入,未配置则默认传入args中的所有参数,示例中未配置本项,所以传入的参数为[x, offset, axis1, axis2]。output项中的参数作为输出无需配置会自动传入InferMeta函数中
kernel算子的计算Kernel配置
kernel:func算子对应kernel函数的注册名
kernel:paramkernel函数的输入参数,配置规则与InferMeta函数的param配置相同
kernel:data_type根据指定参数推导调用kernel的data_type类型(对应kernel函数的模板参数'T'),默认不进行配置,会根据输入Tensor自动进行推导。如果kernel的data_type类型由某个输入的Tensor决定,需要将该Tensor参数的变量名填入该项。示例中未配置则kernel的data_type由输入变量'x'决定
kernel:backend根据指定参数来选择调用kernel的Backend(Kernel执行的具体设备,如CPU、GPU等),默认不进行配置,会根据输入Tensor自动进行推导。如果kernel执行的backend类型由某个输入的Tensor决定,需要将该Tensor参数的变量名填入该项。示例中未配置则kernel执行的Backend与输入变量'x'的Backend相同
backward算子对应的反向算子名称,如果没有反向则不需要配置,示例中trace算子的反向为trace_grad
特殊配置项(目前特殊配置项还处于不稳定阶段,后续可能会有调整更新)
optional指定输入Tensor为可选输入,用法可参考dropout中seed_tensor(python/paddle/utils/code_gen/legacy_api.yaml中)
inplace算子对指定的输入做原位处理并作为输出结果返回,使用格式:(x -> out),具体用法可参考relu算子
+特殊规则:如果api中算子名称有'_'后缀则只生成支持inplace功能的接口,如果算子名称没有'_'后缀,则会同时生成支持inplace操作的接口(自动添加'_'后缀)和不支持inplace的普通接口共两套接口 +
view与inplace机制类似,区别在于view模式返回的结果只是与输入共享内存,并不是输入Tensor变量本身,使用格式:(x -> out),具体用法可参考reshape算子
intermediate标记前向计算中输出的用于反向计算的中间变量,不会出现在Python API的返回结果中,相关设计正在完善中,新增算子时不建议使用
-`GetExpectedKernelType`接口OperatorWithKernel类中用于获取指定设备(例如CPU,GPU)上指定数据类型(例如double,float)的OpKernel的方法。该方法的重写可见请参考 [原生算子开发注意事项](https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/07_new_op/op_notes_cn.html#getexpectedkerneltype) 第4点 GetExpectedKernelType方法重写。 -通常`OpProtoMaker`和`Op`类的定义写在`.cc`文件中,和下面将要介绍的注册函数一起放在`.cc`中 +`backward.yaml`中反向算子的配置规则如下: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
配置项配置内容及规则
backward_api反向算子名称,一般命名方式为:前向算子名称+'_grad',二阶算子则为前向算子名称+'_double_grad'
forward对应前向算子的名称、参数、返回值,需要与api.yaml中前向算子配置一致
args反向算子输入参数, 示例中'x'表示将前向的'x'变量输入到反向,'out_grad'表示前向输出'out'对应的反向梯度
+约束条件1:所有参数需要在forward配置项的参数中(输入、输出以及输出对应的反向梯度)找到对应(根据变量名匹配)
+约束条件2:反向输入参数需要以:a.前向输入Tensor b.前向输出Tensor c.前向输出Tensor的反向梯度 d.前向非Tensor类型属性变量 的顺序排列,只需添加反向计算需要用到的前向参数
+
output反向算子输出,顺序需要与前向输入Tensor一致,比如前向输入(Tensor x, Tensor y),则反向输出必须为Tensor(x_grad), Tensor(y_grad)
infer_meta与前向配置规则相同
kernel与前向配置规则相同
backward反向算子对应的更高阶反向算子名称,如一阶反向算子的反向为二阶反向算子
特殊配置项(目前特殊配置项还处于不稳定阶段,后续可能会有调整更新)
no_need_buffer可选配置,标记的Tensor变量在前向运行完成后,持有的内存或显存会被释放,以减少训练过程中的内存使用。trace_grad由于反向算子只需要前向变量'x'的维度信息,不需要内存数据,所以可以标记为no_need_buffer提前释放内存
+注意:由于Tensor内存被释放后会影响dtype接口的使用,所以需要在kernel的data_type配置项中指定其他的Tensor来推导kernel的data_type
optional与前向配置规则相同
inplace与前向配置规则相同
-### 2.4 实现InferMeta函数 +### 2.2 实现InferMeta函数 `InferMeta`函数是根据输入参数,推断算子输出Tensor基本信息的函数,推断的信息包括输出Tensor的`shape`、`data type`及`data layout`,同时它也承担了检查输入数据维度、类型等是否合法的功能。 @@ -409,25 +444,6 @@ void ConcatInferMeta(const std::vector& x, } ``` -### 2.5 注册Op - -在`xxx_op.cc`文件中声明InferShapeFunctor,并注册前向、反向Op。 - -```cpp -namespace ops = paddle::operators; -DECLARE_INFER_SHAPE_FUNCTOR(trace, TraceInferShapeFunctor, - PD_INFER_META(phi::TraceInferMeta)); -REGISTER_OPERATOR(trace, ops::TraceOp, ops::TraceOpMaker, - ops::TraceGradOpMaker, - ops::TraceGradOpMaker, - TraceInferShapeFunctor); -REGISTER_OPERATOR(trace_grad, ops::TraceOpGrad, - ops::TraceGradNoNeedBufferVarsInferer); -``` - -在上面的代码中,首先使用`DECLARE_INFER_SHAPE_FUNCTOR`声明InferShapeFunctor,然后使用`REGISTER_OPERATOR`注册了`ops::TraceOp`类,算子名为`trace`,该类的`ProtoMaker`为`ops::TraceOpMaker`,其`GradOpMaker`分别是`ops::TraceOpGradMaker`(静态图模式使用)和`ops::TraceOpGradMaker`(动态图模式使用),同时将前面声明的TraceInferShapeFunctor一并放入注册列表。 -前向算子注册完成后,再使用`REGISTER_OPERATOR`注册`ops::TraceGradOp`,类型名为`trace_grad`。 - ## 3. 新增算子Kernel 新增算子Kernel在 `paddle/phi/kernels` 目录中完成 @@ -748,7 +764,10 @@ def trace(x, offset=0, axis1=0, axis2=1, name=None): __check_input(input, offset, axis1, axis2) - if paddle.in_dynamic_mode(): + if in_dygraph_mode(): + return _C_ops.final_state_trace( x, offset, axis1, axis2 ) + + if _in_legacy_dygraph(): return _C_ops.trace(x, 'offset', offset, 'axis1', axis1, 'axis2', axis2) inputs = {'Input': [x]} @@ -771,8 +790,8 @@ def trace(x, offset=0, axis1=0, axis2=1, name=None): - Python API 实现要点 - 对输入参数进行合法性检查,即 `__check_input(input, offset, axis1, axis2)` - - 添加动态图分支调用,即 `if paddle.in_dynamic_mode()` 分支 - - 添加静态图分支调用,即dygraph mode分支后剩余的代码 + - 添加动态图分支调用,即 `if in_dygraph_mode` 新动态图分支和 `if _in_legacy_dygraph` 旧动态图分支 + - 添加静态图分支调用,即dygraph分支后剩余的代码 - Python API 放置位置 - 根据 API 自身属性,结合现有目录分类情况,放置导致对应子目录中的相应文件中 @@ -800,6 +819,7 @@ Op单元测试继承自`OpTest`。各项具体的单元测试在`TestTraceOp`里 ```python + import paddle import unittest import numpy as np from op_test import OpTest @@ -808,14 +828,15 @@ Op单元测试继承自`OpTest`。各项具体的单元测试在`TestTraceOp`里 class TestTraceOp(OpTest): def setUp(self): self.op_type = "trace" + self.python_api = paddle.trace self.init_config() self.outputs = {'Out': self.target} def test_check_output(self): - self.check_output() + self.check_output(check_eager=True) def test_check_grad(self): - self.check_grad(['Input'], 'Out') + self.check_grad(['Input'], 'Out', check_eager=True) def init_config(self): self.case = np.random.randn(20, 6).astype('float64') @@ -827,6 +848,7 @@ Op单元测试继承自`OpTest`。各项具体的单元测试在`TestTraceOp`里 上面的代码首先导入依赖的包,下面是对`setUp`函数中操作的重要变量的详细解释: - `self.op_type = "trace" ` : 定义类型,与operator注册时注册的类型一致。 + - `self.python_api = paddle.trace` : 定义python api,与python调用接口一致。 - `self.inputs` : 定义输入,类型为`numpy.array`,并初始化。 - `self.outputs` : 定义输出,并在Python脚本中完成与operator同样的计算逻辑,返回Python端的计算结果。 @@ -837,6 +859,7 @@ Op单元测试继承自`OpTest`。各项具体的单元测试在`TestTraceOp`里 - `test_check_grad`中调用`check_grad`使用数值法检测梯度正确性和稳定性。 - 第一个参数`['Input']` : 指定对输入变量`Input`做梯度检测。 - 第二个参数`'Out'` : 指定前向网络最终的输出目标变量`Out`。 + - 第三个参数`check_eager` : `check_eager=True`表示开启新动态图(eager模式)单测,`check_eager`默认为`False`。 - 对于存在多个输入的反向Op测试,需要指定只计算部分输入梯度的case - 例如,`test_elementwise_sub_op.py`中的`test_check_grad_ingore_x`和`test_check_grad_ingore_y`分支用来测试只需要计算一个输入梯度的情况 diff --git a/docs/dev_guides/api_contributing_guides/new_cpp_op_notes_cn.md b/docs/dev_guides/api_contributing_guides/new_cpp_op_notes_cn.md index 98295ba7ec9..46f58244f24 100644 --- a/docs/dev_guides/api_contributing_guides/new_cpp_op_notes_cn.md +++ b/docs/dev_guides/api_contributing_guides/new_cpp_op_notes_cn.md @@ -1,250 +1,65 @@ # C++ OP 开发注意事项 -## Paddle中Op的构建逻辑 -### 1.Paddle中Op的构建逻辑 -Paddle中所有的Op都继承自`OperatorBase`,且所有的Op都是无状态的,每个Op包含的成员变量只有四个:type、inputs、outputs、attribute。 +## Paddle基于Yaml配置的算子代码自动生成 +Paddle支持动态图和静态图两种模式,在Yaml配置文件中完成算子基本属性的定义后,需要进行解析并分别生成动态图和静态图所对应的算子代码逻辑,从而将算子接入框架的执行体系。 +基于Yaml配置的算子代码自动生成示意图:![code_gen_by_yaml](./code_gen_by_yaml.png) -Op的核心方法是Run,Run方法需要两方面的资源:数据资源和计算资源,这两个资源分别通过`Scope`和`Place`获取。框架内部有一个全局的`DeviceContextPool`,用来记录`Place`和`DeviceContext`之间的对应的关系,即每个`Place`有且仅有一个`DeviceContext`与之对应,`DeviceContext`中存放了当前设备的计算资源。比如对于GPU,这些资源包括`cudnn_handle`、`cublas_handle`、`stream`等,**Op内部所有的计算(数据拷贝和CUDA Kernel等)都必须在`DeviceContext`中进行**。 +- 其中Yaml配置文件为前向:`python/paddle/utils/code_gen/api.yaml`和反向:`python/paddle/utils/code_gen/backward.yaml`。 +- 动态图中自动生成的代码包括从Python API到计算Kernel间的各层调用接口实现,从底层往上分别为: + - C++ API:一套与Python API参数对齐的C++接口(只做逻辑计算,不支持自动微分),内部封装了底层kernel的选择和调用等逻辑,供上层灵活使用。 + - 注:前向算子生成C++ API头文件和实现代码分别为`paddle/phi/api/include/api.h`和`paddle/phi/api/lib/api.cc`,反向算子生成的头文件和实现代码分别为`paddle/phi/api/backward/backward_api.h`,`paddle/phi/api/lib/backward_api.cc`。 + - 动态图前向函数与反向节点(Autograd API):在C++ API的基础上进行了封装,组成一个提供自动微分功能的C++函数接口。 + - 注:生成的代码在`paddle/fluid/eager/api/generated/eager_generated`目录下 + - Python-C 接口:将支持自动微分功能的C++的函数接口(Autograd API)暴露到Python层供Python API调用。 + - 注:生成的代码在`paddle/fluid/pybind/eager_final_state_op_function_impl.h`中 +- 静态图的执行流程与动态图不同,所以生成的代码也与动态图有较大差异。静态图由于是先组网后计算,Python API主要负责组网,算子的调度和kernel计算由静态图执行器来完成,因此自动生成的代码是将配置文件中的算子信息注册到框架内供执行器调度,主要包括[OpMaker](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/fluid/framework/op_proto_maker.h)(静态图中定义算子的输入、输出以及属性等信息)和`REGISTER_OPERATOR`(将算子名称以及OpMaker等信息进行注册)等静态图算子注册组件,具体的代码逻辑可参考`paddle/fluid/operators/generated_op.cc` -Paddle框架的设计理念是可以在多种设备及第三方库上运行,有些Op的实现可能会因为设备或者第三方库的不同而不同。为此,Paddle引入了OpKernel的方式,即一个Op可以有多个OpKernel,这类Op继承自`OperatorWithKernel`,这类Op的代表是conv_op,conv_op的OpKernel有:`GemmConvKernel`、`CUDNNConvOpKernel`、`ConvMKLDNNOpKernel`,且每个OpKernel都有double和float两种数据类型。不需要OpKernel的代表有`WhileOp`等。 +注意:由于代码生成在编译时执行,所以查看上述生成代码需要先完成框架的编译。 -Operator继承关系图: -![op_inheritance_relation_diagram](./op_inheritance_relation_diagram.png) - -进一步了解可参考:[multi_devices](https://github.com/PaddlePaddle/FluidDoc/tree/develop/doc/fluid/design/multi_devices),[scope](https://github.com/PaddlePaddle/FluidDoc/blob/develop/doc/fluid/design/concepts/scope.md),[Developer's_Guide_to_Paddle_Fluid](https://github.com/PaddlePaddle/FluidDoc/blob/release/1.2/doc/fluid/getstarted/Developer's_Guide_to_Paddle_Fluid.md) - -### 2.Op的注册逻辑 -每个Operator的注册项包括: - ```C++ - OpCreator creator_; - GradOpMakerFN grad_op_maker_; - proto::OpProto* proto_{nullptr}; - OpAttrChecker* checker_{nullptr}; - InferVarTypeFN infer_var_type_; - InferShapeFN infer_shape_; - ``` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
注册项类型说明调用
proto::OpProto Class 存放Op的输入/输出/属性/Op类型 编译时调用
GradOpMakerFN Functor 返回当前Op对应的反向Op的一组OpDesc,因为正向Op的反向可能有多个Op构成 编译时调用
OpAttrChecker Class 对Op的attr进行check 编译时调用
InferVarTypeFN Functor 用于推断输出Var的Type,比如是LoDTensor还是SelectedRows,或者其他 编译时调用
InferShapeFN Functor 用于推断Output的Shape 分为编译时和运行时,编译时是在Python端调用;如果Op继承自OperatorWithKernel,运行时是在op.run中调用
OpCreator Functor 每次调用都会创建一个新的OperatorBase 运行时调用
- -通常Op注释时需要调用REGISTER_OPERATOR,即: - ``` - REGISTER_OPERATOR(op_type, - OperatorBase - op_maker_and_checker_maker, - op_grad_opmaker, - op_infer_var_shape, - op_infer_var_type) - ``` - -**注意:** - -1. 对于所有Op,前三个参数是必须的,op_type指明op的名字,OperatorBase是该Op的对象,op_maker_and_checker_maker是op的maker以及Op中attr的checker。 -2. 如果该Op有反向,则必须要有op_grad_opmaker,因为在backward会根据正向的Op中获取反向Op的Maker。 -3. 框架提供了一个默认的op_grad_opmaker:`DefaultGradOpDescMaker`,这个Maker会将前向Op的输入和输出都作为反向Op的输入,将前向Op的输入的梯度作为反向Op的输出,并将前向Op的属性拷贝过来。**注意:DefaultGradOpDescMaker会将前向Op的所有输入输出都做反向Op的输入,即使这个输入是没有必要的,这将会导致无法对没有用到的变量做内存优化**。 -4. 框架没有提供默认的op_infer_var_shape方法。如果该Op是无OpKernel的,通常需要用户添加对应的op_infer_var_shape方法;如果该Op是有OpKernel的,需要实现`OperatorWithKernel`中的`InferShape`方法,此时不需要提供op_infer_var_shape方法。具体实现可参考[while_op.cc](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/fluid/operators/controlflow/while_op.cc),[conv_op.cc](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/fluid/operators/conv_op.cc)。 -5. 框架没有提供默认的op_infer_var_type方法,用户需要根据实际情况添加op_infer_var_type。严格来说每个Op都应该注册一个InferVarType,op_infer_var_type根据输入的Var的type和dtype推断输出Var的type和dtype。**注意:在Python端的LayerHelper中create_variable_for_type_inference操作返回的Variable里面是LoDTensor,C++端的InferVarType可以修改`Variable`的type和dtype**。 - - - -更多内容请参考: [如何写新的Op](new_op.html) +更多内容请参考: [C++ OP开发](new_cpp_op_cn.html) ## 写Op注意事项 -### 1.Op可以支持输入输出类型 -Paddle的Op的输入输出都是`Variable`,从设计上讲,`Variable`中可以存放任意类型,Op的输入输出`Variable`可能是是任意类型,通常情况下`Variable`中存放的是`LoDTensor`、`SelectedRows`。 - -**注意:** - -- 代码中经常出现`context.Input("Input")`,并不表示"Input"的`Variable`是`Tensor`,而是从"Input"的`Variable`的`LoDTensor`中获取`Tensor`。如果"Input"的`Variable`是`SelectedRows`,则会报错。 -- 如果”Input”是`SelectedRows`,`context->GetInputDim("Input")`返回的是`var->Get().GetCompleteDims()`,而不是`SelectedRows`中`Tensor`的Dim。 - -### 2.在Op内部不能对输入的数据做任何的改写 -在Op内部绝不允许对输入数据做任何改写,因为可能存在其他Op需要读这个数据。 - -### 3.OpKernel需要注册的数据类型 -目前要求所有OpKernel都要注册double和float数据类型。 - -### 4.GetExpectedKernelType方法重写 -GetExpectedKernelType方法是OperatorWithKernel类中用于获取指定设备(例如CPU,GPU)上指定数据类型(例如double,float)的OpKernel的方法。该方法通过获取输入变量内部的Tensor数据类型得知需要的Kernel数据类型,但是由于Tensor在此处可能尚未被初始化,所以在该方法内使用输入变量时需要进行必要的初始化检查。在新增含Kernel的Op的时候,关于该方法的重写需要注意以下两点。 - -#### 4.1 仅在必要时重写此方法 - -基类OperatorWithKernel中的GetExpectedKernelType方法对于派生类Op的所有输入变量进行了完备的初始化检查,建议在新增的Op中直接使用基类的此方法,例如: - -- [MeanOp](https://github.com/PaddlePaddle/Paddle/blob/3556514e971bdbb98fdf0f556371c527f4dfa98c/paddle/fluid/operators/mean_op.cc#L39):该Op的所有输入变量在Run之前应该全部被初始化,初始化检查是必要且合理的 - -但是在一些情况下,直接使用基类的GetExpectedKernelType方法无法满足需求,则需要对该方法进行重写,具体情况及示例如下: - -1. OP的输入有多个,且数据类型不同,例如 [AccuracyOp](https://github.com/PaddlePaddle/Paddle/blob/370f0345b6d35a513c8e64d519a0edfc96b9276c/paddle/fluid/operators/metrics/accuracy_op.cc#L80),需要重写GetExpectedKernelType方法,指定用某一输入变量获取kernel类型 - -2. Op包含Dispensable的输入变量,该类输入变量是可选的,当用户未输入时,该类变量未被初始化属于合理情况,例如 [ConvOp](https://github.com/PaddlePaddle/Paddle/blob/250e72d254ccbe3521c29aa2801a1cb15b75ea73/paddle/fluid/operators/conv_op.cc#L206),存在Bias等可选的输入变量,需要重写GetExpectedKernelType方法,指定用必须提供的输入变量获取kernel类型 - -3. Op的部分输入变量即使未被初始化也属于合理情况,例如 [ConcatOp](https://github.com/PaddlePaddle/Paddle/blob/250e72d254ccbe3521c29aa2801a1cb15b75ea73/paddle/fluid/operators/concat_op.cc#L90),输入变量X中有个Tensor需要连接,其中可能包含未被初始化的Tensor,需要重写GetExpectedKernelType方法,使用输入变量X获取kernel的过程中,合理忽略掉部分Tensor为空的情况 - -4. OP的Kernel类型与输入变量无关(可能由其他参数指定),例如 [FillOp](https://github.com/PaddlePaddle/Paddle/blob/efbdad059634bef022d4a3f5b00aef6ef8e88ed6/paddle/fluid/operators/one_hot_op.cc#L72),该Op没有输入,Kernel类型通过Op的dtype参数指定,因此需要重写GetExpectedKernelType方法,用参数指定的数据类型获取kernel类型 +### 1.Op兼容性问题 +对Op的修改需要考虑兼容性问题,要保证Op修改之后,之前的模型都能够正常加载及运行,即新版本的Paddle预测库能成功加载运行旧版本训练的模型。**所以,需要保证Op当前的所有输入输出参数不能被修改(文档除外)或删除,可以新增参数,但是新增的Tensor类型变量需要设置为optional,非Tensor变量需要设置默认值。更多详细内容请参考[OP修改规范:Input/Output/Attribute只能做兼容修改](https://github.com/PaddlePaddle/Paddle/wiki/OP-Input-Output-Attribute-Compatibility-Modification)** 。 -5. Op Kernel的部分参数在使用某些库时,需要指定为相应的值,因此需要重写GetExpectedKernelType方法,覆盖默认参数 - - 使用CUDNN库:需要指定OpKernel的LibraryType为kCUDNN,例如 [AffineGridOp](https://github.com/PaddlePaddle/Paddle/blob/370f0345b6d35a513c8e64d519a0edfc96b9276c/paddle/fluid/operators/affine_grid_op.cc#L78) - - 使用MKLDNN库:需要指定OpKernel的LibraryType和DataLayout为kMKLDNN [MulOp](https://github.com/PaddlePaddle/Paddle/blob/250e72d254ccbe3521c29aa2801a1cb15b75ea73/paddle/fluid/operators/mul_op.cc#L89) +### 2.显存优化 -#### 4.2 重写此方法时需要对输入变量进行初始化检查 +#### 2.1 为可原位计算的Op注册inplace +有些Op的计算逻辑中,输出可以复用输入的显存空间,也可称为原位计算。例如[reshape](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/phi/kernels/reshape_kernel.cc)中,输出`out`可以复用输入`x`的显存空间,因为该Op的计算逻辑不会改变`x`的实际数据,只是修改它的shape,输出和输入复用同一块显存空间不影响结果。对于这类OP,可以注册`inlace`,从而让框架在运行时自动地进行显存优化。 -在需要重写GetExpectedKernelType方法时,一般会根据某一输入变量获取Kernel的数据类型,此时请使用`OperatorWithKernel::IndicateVarDataType`接口获取变量的dtype,该方法对指定的输入变量进行了必要的初始化检查,详见[Paddle PR #20044](https://github.com/PaddlePaddle/Paddle/pull/20044),实现示例如下,: +注册方式为在算子的Yaml配置中添加inplace配置项,格式如:`(x -> out)`,详见[Yaml配置规则](new_cpp_op_cn.html)。示例: ``` - framework::OpKernelType GetExpectedKernelType( - const framework::ExecutionContext& ctx) const override { - return framework::OpKernelType( - OperatorWithKernel::IndicateVarDataType(ctx, "X"), ctx.GetPlace()); - } +- api : reshape + args : (Tensor x, IntArray shape) + output : Tensor(out) + ... + inplace : (x -> out) ``` -如果未使用带有初始化检查的方法,直接使用了`Tensor->type()`,可能会导致报出`holder_ should not be null. Tensor not initialized yet when Tensor::type()`的错误,例如[Paddle issue #19522](https://github.com/PaddlePaddle/Paddle/issues/19522) ,用户仅凭该错误信息将无法得知具体出错的Op,不利于调试。 +#### 2.2 减少OP中的无关变量 +通常反向Op会依赖于前向Op的某些输入、输出Tensor,以供反向Op计算使用。但有些情况下,反向Op不需要前向Op的所有输入和输出;有些情况下,反向Op只需要前向Op的部分输入和输出;有些情况下,反向Op只需要使用前向Op中输入和输出变量的Shape和LoD信息。若Op开发者在注册反向Op时,将不必要的前向Op输入和输出作为反向Op的输入,会导致这部分显存无法被框架现有的显存优化策略优化,从而导致模型显存占用过高。 -### 5.Op兼容性问题 -对Op的修改需要考虑兼容性问题,要保证Op修改之后,之前的模型都能够正常加载及运行,即新版本的Paddle预测库能成功加载运行旧版本训练的模型。**所以,需要保证Op的Input、Output和Attribute不能被修改(文档除外)或删除,可以新增Input、Output和Attribute,但是新增的Input,Output必须设置AsDispensable,新增的Attribute必须设置默认值。更多详细内容请参考[OP修改规范:Input/Output/Attribute只能做兼容修改](https://github.com/PaddlePaddle/Paddle/wiki/OP-Input-Output-Attribute-Compatibility-Modification)** 。 - -### 6.ShareDataWith的调用 -ShareDataWith的功能是使两个Tensor共享底层buffer,在调用这个操作的时候需要特别注意,在Op内部不能将ShareDataWith作用在Op的输出上,即Op输出的Tensor必须是Malloc出来的。 - -### 7.稀疏梯度参数更新方法 -目前稀疏梯度在做更新的时候会先对梯度做merge,即对相同参数的梯度做累加,然后做参数以及附加参数(如velocity)的更新。 - -### 8.显存优化 - -#### 8.1 为可原位计算的Op注册Inplace -有些Op的计算逻辑中,输出可以复用输入的显存空间,也可称为原位计算。例如[reshape_op](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/fluid/operators/reshape_op.cc)中,输出`Out`可以复用输入`X`的显存空间,因为该Op的计算逻辑不会改变`X`的实际数据,只是修改它的shape,输出和输入复用同一块显存空间不影响结果。对于这类OP,可以注册`Inlace`,从而让框架在运行时自动地进行显存优化。 - -Paddle提供了`DECLARE_INPLACE_OP_INFERER`宏用于注册`Inplace`,该宏第一个参数是一个类名,如`ReshapeOpInplaceInToOut`;第二个参数是一对复用的输入输出,以`{"X", "Out"}`的形式给出。在`REGISTER_OPERATOR`时, -可以将类名传传入,从而为该Op注册`Inplace`。 +所以在定义反向Op时需要注意以下几点: +- 如果反向不需要前向的某些输入或输出参数,则无需在args中设置。 +- 如果有些反向Op需要依赖前向Op的输入或输出变量的的Shape或LoD,但不依赖于变量中Tensor的Buffer,且不能根据其他变量推断出该Shape和LoD,则可以通过`no_need_buffer`对该变量进行配置,可参考[Yaml配置规则](new_cpp_op_cn.html)。示例: ``` -DECLARE_INPLACE_OP_INFERER(ReshapeOpInplaceInToOut, {"X", "Out"}); - -REGISTER_OPERATOR( - reshape, ops::ReshapeOp, ops::ReshapeOpMaker, - paddle::framework::DefaultGradOpMaker, - paddle::framework::DefaultGradOpMaker, - ops::ReshapeOpInplaceInToOut); +- backward_api : trace_grad + forward : trace (Tensor x, int offset, int axis1, int axis2) -> Tensor(out) + args : (Tensor x, Tensor out_grad, int offset, int axis1, int axis2) + output : Tensor(x_grad) + ... + no_need_buffer : x ``` -#### 8.2 减少OP中的无关变量 -通常反向Op会依赖于前向Op的某些输入(Input)、输出(Output),以供反向Op计算使用。但有些情况下,反向Op不需要前向Op的所有输入和输出;有些情况下,反向Op只需要前向Op的部分输入和输出;有些情况下,反向Op只需要使用前向Op中输入和输出变量的Shape和LoD信息。若Op开发者在注册反向Op时,将不必要的前向Op输入和输出作为反向Op的输入,会导致这部分显存无法被框架现有的显存优化策略优化,从而导致模型显存占用过高。 - -所以在写注册反向Op时需要注意以下几点: +### 3.ShareDataWith的调用 +ShareDataWith的功能是使两个Tensor共享底层buffer,在调用这个操作的时候需要特别注意,在Op内部不能将ShareDataWith作用在Op的输出上,即Op输出的Tensor必须是Malloc出来的。 -- Paddle提供的`DefaultGradOpMaker`,默认会将前向op的所有输入(`Input`)、输出(`Output`)以及输出变量所对应的梯度(`Output@Grad`)作为反向Op的输入,将前向Op输入所对应的梯度(`Input@Grad`)作为反向Op的输出。所以在使用`DefaultGradOpMaker`时需要考虑是否有些变量在计算中不被用到。 -- 如果`DefaultGradOpMaker`不能够满足需求,需要用户自己手动构建`GradOpMaker`,具体实现请参考[相关文档](new_op.html#gradopmaker); -- 如果有些反向Op需要依赖前向Op的输入或输出变量的的Shape或LoD,但不依赖于变量中Tensor的Buffer,且不能根据其他变量推断出该Shape和LoD,则可以通过`DECLARE_NO_NEED_BUFFER_VARS_INFERER`接口对该变量(以下称该变量为`X`)在反向Op中进行注册`NoNeedBufferVars`。**一旦注册了`NoNeedBufferVars`,反向op中就不能读写该变量对应的Tensor中的buffer,只能调用Tensor的dims()和lod()方法,同时,反向Op中的`GetExpectedKernelType()`必须要重写,并且`GetExpectedKernelType()`中不能访问`X`变量中Tensor的type()方法**。比如在`SliceOpGrad`中只会用到`Input`中变量的Shape信息,所以需要为对`Input`在`SliceOpGrad`上进行注册: -``` -namespace paddle { -namespace operators { -// ... -class SliceOpGrad : public framework::OperatorWithKernel { - public: - using framework::OperatorWithKernel::OperatorWithKernel; - - void InferShape(framework::InferShapeContext* ctx) const override { - // ... - } - - framework::OpKernelType GetExpectedKernelType( - const framework::ExecutionContext& ctx) const override { - // Note: don't get data type from ctx.Input("Input"); - auto dtype = ctx.Input(framework::GradVarName("Out"))->type(); - return framework::OpKernelType( dtype, ctx.GetPlace()); - } -}; - - -template -class SliceOpGradMaker : public framework::SingleGradOpMaker { - public: - using framework::SingleGradOpMaker::SingleGradOpMaker; - - protected: - void Apply(GradOpPtr bind) const override { - bind->SetInput("Input", this->Input("Input")); - if (this->HasInput("StartsTensor")) { - bind->SetInput("StartsTensor", this->Input("StartsTensor")); - } - if (this->HasInput("EndsTensor")) { - bind->SetInput("EndsTensor", this->Input("EndsTensor")); - } - if (this->HasInput("StartsTensorList")) { - bind->SetInput("StartsTensorList", this->Input("StartsTensorList")); - } - if (this->HasInput("EndsTensorList")) { - bind->SetInput("EndsTensorList", this->Input("EndsTensorList")); - } - bind->SetInput(framework::GradVarName("Out"), this->OutputGrad("Out")); - bind->SetOutput(framework::GradVarName("Input"), this->InputGrad("Input")); - bind->SetAttrMap(this->Attrs()); - bind->SetType("slice_grad"); - } -}; - -DECLARE_NO_NEED_BUFFER_VARS_INFERER(SliceOpGradNoNeedBufferVarsInference, - "Input"); -} // namespace operators -} // namespace paddle -namespace ops = paddle::operators; -REGISTER_OPERATOR(slice, ops::SliceOp, ops::SliceOpMaker, - ops::SliceOpGradMaker, - ops::SliceOpGradMaker); -REGISTER_OPERATOR(slice_grad, ops::SliceOpGrad, - ops::SliceDoubleOpGradMaker, - ops::SliceDoubleOpGradMaker, - ops::SliceOpGradNoNeedBufferVarsInference); -``` +### 4.稀疏梯度参数更新方法 +目前稀疏梯度在做更新的时候会先对梯度做merge,即对相同参数的梯度做累加,然后做参数以及附加参数(如velocity)的更新。 -### 9.混合设备调用 +### 5.混合设备调用 由于GPU是异步执行的,当CPU调用返回之后,GPU端可能还没有真正的执行,所以如果在Op中创建了GPU运行时需要用到的临时变量,当GPU开始运行的时候,该临时变量可能在CPU端已经被释放,这样可能会导致GPU计算出错。 关于GPU中的一些同步和异步操作: @@ -264,7 +79,7 @@ The following device operations are asynchronous with respect to the host: 更多内容可参考:[Asynchronous Concurrent Execution](https://docs.nvidia.com/cuda/cuda-c-programming-guide/#asynchronous-concurrent-execution),[API synchronization behavior](https://docs.nvidia.com/cuda/cuda-runtime-api/api-sync-behavior.html#api-sync-behavior) -### 10. LoD 在 Op 内部的传导规范 +### 6. LoD 在 Op 内部的传导规范 [LoD](https://github.com/PaddlePaddle/FluidDoc/blob/develop/doc/fluid/design/concepts/lod_tensor.md) 是 Paddle 框架用来表示变长序列数据的属性,除了仅支持输入是 padding data 的 Op 外,所有 Op 的实现都要考虑 LoD 的传导问题。 @@ -298,9 +113,9 @@ The following device operations are asynchronous with respect to the host: 在前向传导过程,与输入的 LoD 相比较,Op 输出的 LoD 可能出现不变、改变和消失这三种情况: - - 不变:适用于所有的 LoD-Transparent OP 与部分的 LoD-Based OP。可以在`InferShape` 中调用 `ShareLoD()` 直接将输入 Var 的 LoD 共享给输出 Var, 可参考 [lstm_op](https://github.com/PaddlePaddle/Paddle/blob/a88a1faa48a42a8c3737deb0f05da968d200a7d3/paddle/fluid/operators/lstm_op.cc#L92); 如果有多个输入且都可能存在 LoD 的情况,通常默认共享第一个输入, 例如 [elementwise_ops forward](https://github.com/PaddlePaddle/Paddle/blob/5d6a1fcf16bcb48d2e66306b27d9994d9b07433c/paddle/fluid/operators/elementwise/elementwise_op.h#L69); + - 不变:适用于所有的 LoD-Transparent OP 与部分的 LoD-Based OP。可以在`InferMeta` 中调用 `ShareLoD()` 直接将输入 Var 的 LoD 共享给输出 Var, 可参考 [lstm_op](https://github.com/PaddlePaddle/Paddle/blob/a88a1faa48a42a8c3737deb0f05da968d200a7d3/paddle/fluid/operators/lstm_op.cc#L92); 如果有多个输入且都可能存在 LoD 的情况,通常默认共享第一个输入, 例如 [elementwise_ops forward](https://github.com/PaddlePaddle/Paddle/blob/5d6a1fcf16bcb48d2e66306b27d9994d9b07433c/paddle/fluid/operators/elementwise/elementwise_op.h#L69); - - 改变:适用于部分 LoD-Based OP。在实现 OpKernel 时需考虑输出 LoD 的正确计算,真实的 LoD 在前向计算结束后才能确定,此时仍需要在`InferShape` 中调用 `ShareLoD()`,以确保CompileTime 时对 LoD Level 做了正确的传导,可参考 [sequence_expand_op](https://github.com/PaddlePaddle/Paddle/blob/565d30950138b9f831caa33904d9016cf53c6c2e/paddle/fluid/operators/sequence_ops/sequence_expand_op.cc); + - 改变:适用于部分 LoD-Based OP。在实现 OpKernel 时需考虑输出 LoD 的正确计算,真实的 LoD 在前向计算结束后才能确定,此时仍需要在`InferMeta` 中调用 `ShareLoD()`,以确保CompileTime 时对 LoD Level 做了正确的传导,可参考 [sequence_expand_op](https://github.com/PaddlePaddle/Paddle/blob/565d30950138b9f831caa33904d9016cf53c6c2e/paddle/fluid/operators/sequence_ops/sequence_expand_op.cc); - 消失:适用于输出不再是序列数据的 LoD-Based OP。此时不用再考虑前向的 LoD 传导问题,可参考 [sequence_pool_op](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/fluid/operators/sequence_ops/sequence_pool_op.cc); @@ -308,7 +123,7 @@ The following device operations are asynchronous with respect to the host: - 实现 LoD-Based OP 时,需要处理好 LoD 传导的边界情况,例如对长度为零的输入的支持,并完善相应的单测,单测 case 覆盖空序列出现在 batch 开头、中间和末尾等位置的情况,可参考 [test_lstm_op.py](https://github.com/PaddlePaddle/Paddle/blob/4292bd8687ababc7737cffbddc0d38ead2138c00/python/paddle/fluid/tests/unittests/test_lstm_op.py#L203-L216) - - 对 LoD Level 有明确要求的 OP,推荐的做法是在 `InferShape` 中即完成 LoD Level的检查,例如 [sequence_pad_op](https://github.com/PaddlePaddle/Paddle/blob/4292bd8687ababc7737cffbddc0d38ead2138c00/paddle/fluid/operators/sequence_ops/sequence_pad_op.cc#L79)。 + - 对 LoD Level 有明确要求的 OP,推荐的做法是在 `InferMeta` 中即完成 LoD Level的检查,例如 [sequence_pad_op](https://github.com/PaddlePaddle/Paddle/blob/4292bd8687ababc7737cffbddc0d38ead2138c00/paddle/fluid/operators/sequence_ops/sequence_pad_op.cc#L79)。 #### 反向传导 @@ -342,12 +157,9 @@ Enforce提示信息不能为空,并且需要写明,因为报错信息可以 ### 2.Op的数学公式 如果Op有数学公式,一定要在代码中将数学公式写明,并在Python API的Doc中显示,因为用户在对比不同框架的计算结果时可能需要了解Paddle对Op是怎么实现的。 -**注意:**在merge到develop分支之前一定进行公式预览。可参考[dynamic_lstmp](../../../api_cn/layers_cn/nn_cn.html#dynamic-lstmp)。 - -### 3.Op变量名的命名要规范 -在定义Op时,Op的输入输出以及属性的命名需要符合规范,具体命名规则请参考:[name_convention](https://github.com/PaddlePaddle/FluidDoc/blob/release/1.2/doc/fluid/dev/name_convention.md)。 +**注意:**在merge到develop分支之前一定进行公式预览。 -### 4.Python端Op接口中参数的顺序 +### 3.Python端Op接口中参数的顺序 Python API中参数的顺序一般按照重要性来排,以fc为例: ``` def fc(input, From 1a151d046c54029885bf1027e7a804b342cf7f89 Mon Sep 17 00:00:00 2001 From: zyfncg Date: Fri, 17 Jun 2022 10:59:57 +0000 Subject: [PATCH 02/10] fix format --- .../api_contributing_guides/new_cpp_op_cn.md | 32 +++++++++---------- .../new_cpp_op_notes_cn.md | 23 ++++++------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md b/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md index 45c732f3b90..565a6ea5e65 100644 --- a/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md +++ b/docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md @@ -1,6 +1,6 @@ # C++ OP 开发 -> 注:飞桨原生算子的开发范式正处在重构升级后的上线初期,如果在开发过程中遇到问题欢迎通过[issue](https://github.com/PaddlePaddle/Paddle/issues)向我们反馈。 +> 注:飞桨原生算子的开发范式正处在重构升级后的上线初期,如果在开发过程中遇到问题欢迎通过[Issue](https://github.com/PaddlePaddle/Paddle/issues)向我们反馈。 ## 1. 概念简介 @@ -53,10 +53,10 @@ 算子描述及定义是定义运算的基本属性,主要包括算子的输入、输出以及各项非计算逻辑的配置,这些都是设备无关的。 ### 2.1 算子Yaml文件配置 -我们在`python/paddle/utils/code_gen/api.yaml`和`python/paddle/utils/code_gen/backward.yaml`文件中对算子进行描述及定义,在框架编译时会根据Yaml文件中的配置自动生成C++端的相关代码接口以及内部实现(可参考[Paddle基于Yaml配置的算子代码自动生成](new_cpp_op_notes_cn.md)),下面主要以Trace为例介绍算子的Yaml配置规则: +我们在`python/paddle/utils/code_gen/api.yaml`和`python/paddle/utils/code_gen/backward.yaml`文件中对算子进行描述及定义,在框架编译时会根据Yaml文件中的配置自动生成C++端的相关代码接口以及内部实现(详见[Paddle基于Yaml配置的算子代码自动生成](new_cpp_op_notes_cn.md#paddleyaml)),下面主要以Trace为例介绍算子的Yaml配置规则: python/paddle/utils/code_gen/api.yaml: -``` +```yaml - api : trace args : (Tensor x, int offset = 0, int axis1 = 0, int axis2 = 1) output : Tensor(out) @@ -67,7 +67,7 @@ python/paddle/utils/code_gen/api.yaml: backward : trace_grad ``` python/paddle/utils/code_gen/backward.yaml: -``` +```yaml - backward_api : trace_grad forward : trace (Tensor x, int offset, int axis1, int axis2) -> Tensor(out) args : (Tensor x, Tensor out_grad, int offset, int axis1, int axis2) @@ -96,23 +96,23 @@ python/paddle/utils/code_gen/backward.yaml: args -算子输入参数,与该算子Python API函数的输入参数对应(当前支持的输入数据类型包括:Tensor, Tensor[]/*Tensor数组*/, float, double, bool, int, int64_t, int[], int64_t[], str, Place, DataType, DataLayout, IntArray/*主要用于表示shape,index和axes等类型数据,可以直接使用Tensor或者普通整型数组构造,目前仍在测试阶段,如非必要暂不建议使用*/, Scalar/*标量,支持不同的普通数据类型*/) +算子输入参数,与该算子Python API函数的输入参数对应(当前支持的输入数据类型包括:Tensor, Tensor[]/*Tensor数组*/, float, double, bool, int, int64_t, int[], int64_t[], str, Place, DataType, DataLayout, IntArray/*主要用于表示shape,index和axes等类型数据,可以直接使用Tensor或者普通整型数组构造,目前仍在测试阶段,如非必要暂不建议使用*/, Scalar/*标量,支持不同的普通数据类型*/)。我们一般称这里Tensor类型的参数为Input(输入),非Tensor类型的参数为Attribute(属性) output -算子输出类型,目前支持Tensor和Tensor[], 多个输出间用逗号“,”分隔开。可以使用”()”选择性标记输入的名字, 如未标记默认为'out' +算子输出类型(目前支持Tensor和Tensor[]类型),多个输出间用逗号“,”分隔开。可以使用”()”选择性标记输入的名字,如未标记默认为'out' infer_meta -InferMeta函数负责根据输入推断返回Tensor的维度与类型,这里是对算子使用的InferMeta函数进行配置 +InferMeta函数负责根据输入变量推断返回Tensor的维度与类型,这里是对算子使用的InferMeta函数进行配置 infer_meta:func -调用的InferMeta函数, 这里trace调用的是TraceInferMeta函数 +调用的InferMeta函数,这里trace调用的是TraceInferMeta函数 infer_meta:param -InferMeta函数的输入参数,可以对args中的参数进行选择传入,未配置则默认传入args中的所有参数,示例中未配置本项,所以传入的参数为[x, offset, axis1, axis2]。output项中的参数作为输出无需配置会自动传入InferMeta函数中 +InferMeta函数的输入参数,可以对args中的参数进行选择传入,未配置则默认传入args中的所有参数。示例中未配置本项,所以传入的参数为[x, offset, axis1, axis2]。output项中的参数作为输出无需配置会自动传入InferMeta函数中 kernel @@ -124,21 +124,21 @@ python/paddle/utils/code_gen/backward.yaml: kernel:param -kernel函数的输入参数,配置规则与InferMeta函数的param配置相同 +kernel函数的输入参数,配置规则与InferMeta函数的param配置项相同 kernel:data_type -根据指定参数推导调用kernel的data_type类型(对应kernel函数的模板参数'T'),默认不进行配置,会根据输入Tensor自动进行推导。如果kernel的data_type类型由某个输入的Tensor决定,需要将该Tensor参数的变量名填入该项。示例中未配置则kernel的data_type由输入变量'x'决定 +根据指定参数推导调用kernel的data_type(对应kernel函数的模板参数'T'),默认不进行配置,会根据输入Tensor自动进行推导。如果kernel的data_type类型由某个输入Tensor决定,需要将该Tensor参数的变量名填入该项。示例中未配置则kernel的data_type由输入变量'x'决定 kernel:backend -根据指定参数来选择调用kernel的Backend(Kernel执行的具体设备,如CPU、GPU等),默认不进行配置,会根据输入Tensor自动进行推导。如果kernel执行的backend类型由某个输入的Tensor决定,需要将该Tensor参数的变量名填入该项。示例中未配置则kernel执行的Backend与输入变量'x'的Backend相同 +根据指定参数来选择调用kernel的Backend(Kernel执行的具体设备,如CPU、GPU等),默认不进行配置,会根据输入Tensor自动进行推导。如果kernel执行的backend类型由某个输入Tensor决定,需要将该Tensor参数的变量名填入该项。示例中未配置则kernel执行的Backend与输入变量'x'的Backend相同 backward 算子对应的反向算子名称,如果没有反向则不需要配置,示例中trace算子的反向为trace_grad -特殊配置项(目前特殊配置项还处于不稳定阶段,后续可能会有调整更新) +特殊配置项(目前特殊配置项还处于不稳定阶段,后续可能会有调整更新) optional @@ -183,7 +183,7 @@ python/paddle/utils/code_gen/backward.yaml: args 反向算子输入参数, 示例中'x'表示将前向的'x'变量输入到反向,'out_grad'表示前向输出'out'对应的反向梯度
约束条件1:所有参数需要在forward配置项的参数中(输入、输出以及输出对应的反向梯度)找到对应(根据变量名匹配)
-约束条件2:反向输入参数需要以:a.前向输入Tensor b.前向输出Tensor c.前向输出Tensor的反向梯度 d.前向非Tensor类型属性变量 的顺序排列,只需添加反向计算需要用到的前向参数
+约束条件2:反向输入参数需要以:a.前向输入Tensor b.前向输出Tensor c.前向输出Tensor的反向梯度 d.前向非Tensor类型属性变量(Attribute) 的顺序排列,反向计算中不需要使用的前向变量无须添加
@@ -203,7 +203,7 @@ python/paddle/utils/code_gen/backward.yaml: 反向算子对应的更高阶反向算子名称,如一阶反向算子的反向为二阶反向算子 -特殊配置项(目前特殊配置项还处于不稳定阶段,后续可能会有调整更新) +特殊配置项(目前特殊配置项还处于不稳定阶段,后续可能会有调整更新) no_need_buffer @@ -580,7 +580,7 @@ void TraceKernel(const Context& dev_ctx, ```cpp // Tensor broadcast to 'out' and temp 'x_bst' - ScalarArray x_bst_dims(x_bst_dims_vec); + IntArray x_bst_dims(x_bst_dims_vec); DenseTensor x_bst = phi::Empty(dev_ctx, x_bst_dims); const T* x_bst_data = x_bst.data(); ExpandKernel(dev_ctx, x, x_bst_dims, &x_bst); diff --git a/docs/dev_guides/api_contributing_guides/new_cpp_op_notes_cn.md b/docs/dev_guides/api_contributing_guides/new_cpp_op_notes_cn.md index 46f58244f24..e53227fad36 100644 --- a/docs/dev_guides/api_contributing_guides/new_cpp_op_notes_cn.md +++ b/docs/dev_guides/api_contributing_guides/new_cpp_op_notes_cn.md @@ -1,35 +1,36 @@ # C++ OP 开发注意事项 ## Paddle基于Yaml配置的算子代码自动生成 -Paddle支持动态图和静态图两种模式,在Yaml配置文件中完成算子基本属性的定义后,需要进行解析并分别生成动态图和静态图所对应的算子代码逻辑,从而将算子接入框架的执行体系。 -基于Yaml配置的算子代码自动生成示意图:![code_gen_by_yaml](./code_gen_by_yaml.png) +Paddle支持动态图和静态图两种模式,在Yaml配置文件中完成算子基本属性的定义后,需要进行解析并分别生成动态图和静态图所对应的算子代码逻辑,从而将算子接入框架的执行体系。基于Yaml配置的算子代码自动生成示意图: -- 其中Yaml配置文件为前向:`python/paddle/utils/code_gen/api.yaml`和反向:`python/paddle/utils/code_gen/backward.yaml`。 +