@@ -170,7 +170,7 @@ b. 如果是实现自定义的 C++ API,需要在'paddle/phi/api/lib/api_custom
170
170
</tr>
171
171
</tbody>
172
172
</table>
173
- ` backward.yaml` 中反向算子的配置规则如下:
173
+ ` backward.yaml` 中反向算子的配置规则如下:
174
174
175
175
<table>
176
176
<thead>
@@ -309,7 +309,7 @@ InferMeta 的文件放置规则([paddle/phi/infermeta](https://github.com/Padd
309
309
310
310
**InferMeta 的编译时与运行时**
311
311
312
- 在静态图模型中,`InferMeta`操作在 [编译时(compile time)和运行时(run time)](https://github.com/PaddlePaddle/FluidDoc /blob/release/1.2/doc/fluid/getstarted/Developer's_Guide_to_Paddle_Fluid.md#让我们在 fluid 程序实例中区分编译时和运行时 ) 都会被调用,在 compile time 时,由于真实的维度未知,框架内部用 -1 来表示,在 run time 时,用实际的维度表示,因此维度的值在 compile time 和 run time 时可能不一致,如果存在维度的判断和运算操作,InferMeta 就需要区分 compile time 和 run time。
312
+ 在静态图模型中,`InferMeta`操作在 [编译时(compile time)和运行时(run time)](https://github.com/PaddlePaddle/docs /blob/release/1.2/doc/fluid/getstarted/Developer's_Guide_to_Paddle_Fluid.md) 都会被调用,在 compile time 时,由于真实的维度未知,框架内部用 -1 来表示,在 run time 时,用实际的维度表示,因此维度的值在 compile time 和 run time 时可能不一致,如果存在维度的判断和运算操作,InferMeta 就需要区分 compile time 和 run time。
313
313
314
314
对于此类 InferMeta 函数,需要在 InferMeta 函数声明的参数列表末尾增加 `MetaConfig` 参数,例如:
315
315
@@ -479,28 +479,35 @@ paddle/phi/kernels
479
479
480
480
- 新增与设备无关的 kernel
481
481
482
- 该类 kernel 实现与所有硬件设备无关,只需要一份代码实现,可参考 [reshape kernel](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/phi/kernels/reshape_kernel.cc)。其新增文件及目录包括:
482
+ 该类 kernel 实现与所有硬件设备无关,只需要一份代码实现,可参考 [reshape kernel](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/phi/kernels/reshape_kernel.cc)。其新增文件及目录包括:
483
483
484
484
- ` paddle/phi/kernels/xxx_kernel.h`
485
+
485
486
- ` paddle/phi/kernels/xxx_kernel.cc`
486
487
487
- 如果是反向 kernel,则使用 `grad_kernel` 后缀即可:
488
+ 如果是反向 kernel,则使用 `grad_kernel` 后缀即可:
488
489
489
490
- ` paddle/phi/kernels/xxx_grad_kernel.h`
491
+
490
492
- ` paddle/phi/kernels/xxx_grad_kernel.cc`
493
+
491
494
- 新增与设备相关、且 CPU & GPU 分别实现的 kernel
492
495
493
- 还有部分 kernel 的实现,CPU 和 GPU 上逻辑不同,此时没有共同实现的代码,需要区分 CPU 和 GPU 硬件。
494
- CPU 的实现位于`paddle/phi/kernels/cpu` 目录下; GPU 的实现位于`paddle/phi/kernels/gpu` 下,可参考 [dot kernel](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/phi/kernels/gpu/dot_kernel.cu),[cast kernel](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/phi/kernels/gpu/cast_kernel.cu) 等。其新增文件及目录包括:
496
+ 还有部分 kernel 的实现,CPU 和 GPU 上逻辑不同,此时没有共同实现的代码,需要区分 CPU 和 GPU 硬件。
497
+ CPU 的实现位于`paddle/phi/kernels/cpu` 目录下; GPU 的实现位于`paddle/phi/kernels/gpu` 下,可参考 [dot kernel](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/phi/kernels/gpu/dot_kernel.cu),[cast kernel](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/phi/kernels/gpu/cast_kernel.cu) 等。其新增文件及目录包括:
495
498
496
499
- ` paddle/phi/kernels/xxx_kernel.h`
500
+
497
501
- ` paddle/phi/kernels/cpu/xxx_kernel.cc`
502
+
498
503
- ` paddle/phi/kernels/gpu/xxx_kernel.cu`
499
504
500
- 相应地,反向 kernel 新增文件为:
505
+ 相应地,反向 kernel 新增文件为:
501
506
502
507
- ` paddle/phi/kernels/xxx_grad_kernel.h`
508
+
503
509
- ` paddle/phi/kernels/cpu/xxx_grad_kernel.cc`
510
+
504
511
- ` paddle/phi/kernels/gpu/xxx_grad_kernel.cu`
505
512
506
513
# ## 4.2 Kernel 写法
@@ -637,7 +644,7 @@ void TraceKernel(const Context& dev_ctx,
637
644
- [paddle/phi/kernels/gpu/trace_grad_kernel.cu](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/phi/kernels/gpu/trace_grad_kernel.cu)
638
645
639
646
640
- **(4)公共函数管理: **
647
+ **(4)公共函数管理**
641
648
642
649
如果有一些函数会被多个 kernel 调用,可以创建非 kernel 的文件管理代码,规则如下:
643
650
@@ -670,17 +677,17 @@ PD_REGISTER_KERNEL(trace,
670
677
671
678
字段说明:
672
679
673
- 1. `trace` : kernel 名称,和算子的名称一致
674
- 2. `CPU` : backend 名称, 一般主要就是 CPU 和 GPU
675
- 3. `ALL_LAYOUT` : kernel 支持的 Tensor 布局,一般为 ALL_LAYOUT,及支持所有布局类型
676
- 4. `phi::TraceKernel` : kernel 的函数名称,记得带上 namespace phi
677
- 5. 剩余的均为 kernel 支持的数据类型
680
+ - `trace` : kernel 名称,和算子的名称一致
681
+ - `CPU` : backend 名称, 一般主要就是 CPU 和 GPU
682
+ - `ALL_LAYOUT` : kernel 支持的 Tensor 布局,一般为 ALL_LAYOUT,及支持所有布局类型
683
+ - `phi::TraceKernel` : kernel 的函数名称,记得带上 namespace phi
684
+ - 剩余的均为 kernel 支持的数据类型
678
685
679
686
> 注意:
680
687
>
681
- > 1. 如果忘记添加注册相关的头文件,会给出一个 error: expected constructor, destructor, or type conversion before ‘(’ token 的错误,如果遇到,请检查 include 的头文件;
682
- > 2. phi 下的注册宏后边是带函数体{ },不是直接加分号,此处与旧的注册宏方式有小区别;
683
- > 3. 注册 kernel 的宏声明需要在 global namespace。
688
+ > - 如果忘记添加注册相关的头文件,会给出一个 error: expected constructor, destructor, or type conversion before ‘(’ token 的错误,如果遇到,请检查 include 的头文件;
689
+ > - phi 下的注册宏后边是带函数体{ },不是直接加分号,此处与旧的注册宏方式有小区别;
690
+ > - 注册 kernel 的宏声明需要在 global namespace。
684
691
685
692
# ## 4.3 编译测试
686
693
@@ -857,26 +864,33 @@ class TestTraceOp(OpTest):
857
864
- ` self.python_api = paddle.trace ` : 定义 python api,与 python 调用接口一致。
858
865
- ` self.inputs ` : 定义输入,类型为` numpy.array ` ,并初始化。
859
866
- ` self.outputs ` : 定义输出,并在 Python 脚本中完成与算子同样的计算逻辑,返回 Python 端的计算结果。
867
+
860
868
- ** 前向算子单测**
869
+
861
870
- test_check_output 中会对算子的前向计算结果进行测试,对比参考的结果为 setUp 中 ` self.outputs ` 提供的数据。` check_eager=True ` 表示开启新动态图(eager 模式)单测,` check_eager ` 默认为` False `
871
+
862
872
- ** 反向算子单测**
873
+
863
874
- ` test_check_grad ` 中调用` check_grad ` 使用数值法检测梯度正确性和稳定性。
864
875
- 第一个参数` ['Input'] ` : 指定对输入变量` Input ` 做梯度检测。
865
876
- 第二个参数` 'Out' ` : 指定前向网络最终的输出目标变量` Out ` 。
866
- - 第三个参数` check_eager ` : ` check_eager=True ` 表示开启新动态图(eager 模式)单测,` check_eager ` 默认为` False ` 。
877
+ - 第三个参数` check_eager ` : ` check_eager=True ` 表示开启新动态图(eager 模式)单测,` check_eager ` 默认为` False ` 。
867
878
- 对于存在多个输入的反向算子测试,需要指定只计算部分输入梯度的 case
868
879
- 例如,[ test_elementwise_sub_op.py] ( https://github.com/PaddlePaddle/Paddle/blob/develop/python/paddle/fluid/tests/unittests/test_elementwise_sub_op.py ) 中的` test_check_grad_ingore_x ` 和` test_check_grad_ingore_y ` 分支用来测试只需要计算一个输入梯度的情况
869
880
- 此处第三个参数 max_relative_error:指定检测梯度时能容忍的最大错误值。
870
881
871
- ``` python
872
- def test_check_grad_ingore_x (self ):
873
- self .check_grad(
874
- [' Y' ], ' Out' , max_relative_error = 0.005 , no_grad_set = set (" X" ))
882
+ ``` python
883
+ def test_check_grad_ingore_x (self ):
884
+ self .check_grad(
885
+ [' Y' ], ' Out' , max_relative_error = 0.005 , no_grad_set = set (" X" ))
886
+
887
+ def test_check_grad_ingore_y (self ):
888
+ self .check_grad(
889
+ [' X' ], ' Out' , max_relative_error = 0.005 , no_grad_set = set (' Y' ))
890
+ ```
891
+
892
+
875
893
876
- def test_check_grad_ingore_y (self ):
877
- self .check_grad(
878
- [' X' ], ' Out' , max_relative_error = 0.005 , no_grad_set = set (' Y' ))
879
- ```
880
894
881
895
### 6.2 Python API 单元测试
882
896
@@ -959,7 +973,7 @@ PADDLE_ENFORCE_EQ(比较对象 A, 比较对象 B, 错误提示信息)
959
973
所以在定义反向算子时需要注意以下几点:
960
974
961
975
- 如果反向不需要前向的某些输入或输出参数,则无需在 args 中设置。
962
- - 如果有些反向算子需要依赖前向算子的输入或输出变量的的 Shape 或 LoD,但不依赖于变量中 Tensor 的内存 Buffer 数据,且不能根据其他变量推断出该 Shape 和 LoD,则可以通过` no_need_buffer`对该变量进行配置,详见[YAML 配置规则](new_cpp_op_cn.html#yaml)。示例:
976
+ - 如果有些反向算子需要依赖前向算子的输入或输出变量的的 Shape 或 LoD,但不依赖于变量中 Tensor 的内存 Buffer 数据,且不能根据其他变量推断出该 Shape 和 LoD,则可以通过 ` no_need_buffer` 对该变量进行配置,详见[YAML 配置规则](new_cpp_op_cn.html#yaml)。示例:
963
977
` ` ` yaml
964
978
- backward_api : trace_grad
965
979
forward : trace (Tensor x, int offset, int axis1, int axis2) -> Tensor(out)
@@ -971,12 +985,12 @@ PADDLE_ENFORCE_EQ(比较对象 A, 比较对象 B, 错误提示信息)
971
985
972
986
# ## 7.4 性能优化
973
987
# ### 7.4.1 第三方库的选择
974
- 在写算子过程中优先使用高性能(如 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)中有描述。
988
+ 在写算子过程中优先使用高性能(如 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)) 中有描述。
975
989
976
990
# ### 7.4.2 算子性能优化
977
- 算子的计算速度与输入的数据量有关,对于某些算子可以根据输入数据的 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)) 中有介绍。
991
+ 算子的计算速度与输入的数据量有关,对于某些算子可以根据输入数据的 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)) 中有介绍。
978
992
979
- 由于 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)) 中有介绍。
993
+ 由于 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)) 中有介绍。
980
994
981
995
减少 CPU 与 GPU 之间的拷贝和同步操作的次数。比如 fetch 操作,在每个迭代之后都会对模型参数进行更新并得到一个 loss,并且数据从 GPU 端到没有页锁定的 CPU 端的拷贝是同步的,所以频繁的 fetch 多个参数会导致模型训练速度变慢。
982
996
@@ -1064,7 +1078,7 @@ The following device operations are asynchronous with respect to the host:
1064
1078
1065
1079
**(2)反向传导**
1066
1080
1067
- 通常来讲,算子的某个输入 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)
1081
+ 通常来讲,算子的某个输入 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)。
1068
1082
1069
1083
## 八、更多信息
1070
1084
@@ -1080,13 +1094,13 @@ Paddle 支持动态图和静态图两种模式,在 YAML 配置文件中完成
1080
1094
1081
1095
- 动态图中自动生成的代码包括从 Python API 到计算 Kernel 间的各层调用接口实现,从底层往上分别为:
1082
1096
- **C++ API**:一套与 Python API 参数对齐的 C++ 接口(只做逻辑计算,不支持自动微分),内部封装了底层 kernel 的选择和调用等逻辑,供上层灵活使用。
1083
- - 注:前向算子生成 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`。
1097
+ - 注:前向算子生成 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`。
1084
1098
- **动态图前向函数与反向节点(Autograd API)**:在 C++ API 的基础上进行了封装,组成一个提供自动微分功能的 C++函数接口。
1085
- - 注:生成的相关代码在`paddle/fluid/eager/api/generated/eager_generated`目录下。
1086
- - **Python-C 函数**:将支持自动微分功能的 C++的函数接口(Autograd API)暴露到 Python 层供 Python API 调用。
1087
- - 注:生成的 Python-C 接口代码在`paddle/fluid/pybind/eager_op_function.cc`中。
1099
+ - 注:生成的相关代码在 `paddle/fluid/eager/api/generated/eager_generated` 目录下。
1100
+ - **Python-C 函数**:将支持自动微分功能的 C++ 的函数接口(Autograd API)暴露到 Python 层供 Python API 调用。
1101
+ - 注:生成的 Python-C 接口代码在 `paddle/fluid/pybind/eager_op_function.cc` 中。
1088
1102
- 静态图的执行流程与动态图不同,所以生成的代码也与动态图有较大差异。
1089
1103
1090
- 静态图由于是先组网后计算,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`。
1104
+ 静态图由于是先组网后计算,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`。
1091
1105
1092
1106
> **注意:由于代码自动生成在编译时进行,所以查看上述生成代码需要先完成** [**框架的编译**](../../install/compile/fromsource.html)**。**
0 commit comments