Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 49 additions & 35 deletions docs/dev_guides/api_contributing_guides/new_cpp_op_cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ b. 如果是实现自定义的 C++ API,需要在'paddle/phi/api/lib/api_custom
</tr>
</tbody>
</table>
`backward.yaml`中反向算子的配置规则如下:
`backward.yaml` 中反向算子的配置规则如下:

<table>
<thead>
Expand Down Expand Up @@ -309,7 +309,7 @@ InferMeta 的文件放置规则([paddle/phi/infermeta](https://github.com/Padd

**InferMeta 的编译时与运行时**

在静态图模型中,`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。
在静态图模型中,`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。

对于此类 InferMeta 函数,需要在 InferMeta 函数声明的参数列表末尾增加 `MetaConfig` 参数,例如:

Expand Down Expand Up @@ -479,28 +479,35 @@ paddle/phi/kernels

- 新增与设备无关的 kernel

该类 kernel 实现与所有硬件设备无关,只需要一份代码实现,可参考 [reshape kernel](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/phi/kernels/reshape_kernel.cc)。其新增文件及目录包括:
该类 kernel 实现与所有硬件设备无关,只需要一份代码实现,可参考 [reshape kernel](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/phi/kernels/reshape_kernel.cc)。其新增文件及目录包括:

- `paddle/phi/kernels/xxx_kernel.h`

- `paddle/phi/kernels/xxx_kernel.cc`

如果是反向 kernel,则使用 `grad_kernel` 后缀即可:
如果是反向 kernel,则使用 `grad_kernel` 后缀即可:

- `paddle/phi/kernels/xxx_grad_kernel.h`

- `paddle/phi/kernels/xxx_grad_kernel.cc`

- 新增与设备相关、且 CPU & GPU 分别实现的 kernel

还有部分 kernel 的实现,CPU 和 GPU 上逻辑不同,此时没有共同实现的代码,需要区分 CPU 和 GPU 硬件。
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) 等。其新增文件及目录包括:
还有部分 kernel 的实现,CPU 和 GPU 上逻辑不同,此时没有共同实现的代码,需要区分 CPU 和 GPU 硬件。
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) 等。其新增文件及目录包括:

- `paddle/phi/kernels/xxx_kernel.h`

- `paddle/phi/kernels/cpu/xxx_kernel.cc`

- `paddle/phi/kernels/gpu/xxx_kernel.cu`

相应地,反向 kernel 新增文件为:
相应地,反向 kernel 新增文件为:

- `paddle/phi/kernels/xxx_grad_kernel.h`

- `paddle/phi/kernels/cpu/xxx_grad_kernel.cc`

- `paddle/phi/kernels/gpu/xxx_grad_kernel.cu`

### 4.2 Kernel 写法
Expand Down Expand Up @@ -637,7 +644,7 @@ void TraceKernel(const Context& dev_ctx,
- [paddle/phi/kernels/gpu/trace_grad_kernel.cu](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/phi/kernels/gpu/trace_grad_kernel.cu)


**(4)公共函数管理**
**(4)公共函数管理**

如果有一些函数会被多个 kernel 调用,可以创建非 kernel 的文件管理代码,规则如下:

Expand Down Expand Up @@ -670,17 +677,17 @@ PD_REGISTER_KERNEL(trace,

字段说明:

1. `trace`: kernel 名称,和算子的名称一致
2. `CPU`: backend 名称, 一般主要就是 CPU 和 GPU
3. `ALL_LAYOUT`: kernel 支持的 Tensor 布局,一般为 ALL_LAYOUT,及支持所有布局类型
4. `phi::TraceKernel`: kernel 的函数名称,记得带上 namespace phi
5. 剩余的均为 kernel 支持的数据类型
- `trace`: kernel 名称,和算子的名称一致
- `CPU`: backend 名称, 一般主要就是 CPU 和 GPU
- `ALL_LAYOUT`: kernel 支持的 Tensor 布局,一般为 ALL_LAYOUT,及支持所有布局类型
- `phi::TraceKernel`: kernel 的函数名称,记得带上 namespace phi
- 剩余的均为 kernel 支持的数据类型

> 注意:
>
> 1. 如果忘记添加注册相关的头文件,会给出一个 error: expected constructor, destructor, or type conversion before ‘(’ token 的错误,如果遇到,请检查 include 的头文件;
> 2. phi 下的注册宏后边是带函数体{ },不是直接加分号,此处与旧的注册宏方式有小区别;
> 3. 注册 kernel 的宏声明需要在 global namespace。
> - 如果忘记添加注册相关的头文件,会给出一个 error: expected constructor, destructor, or type conversion before ‘(’ token 的错误,如果遇到,请检查 include 的头文件;
> - phi 下的注册宏后边是带函数体{ },不是直接加分号,此处与旧的注册宏方式有小区别;
> - 注册 kernel 的宏声明需要在 global namespace。

### 4.3 编译测试

Expand Down Expand Up @@ -857,26 +864,33 @@ class TestTraceOp(OpTest):
- `self.python_api = paddle.trace` : 定义 python api,与 python 调用接口一致。
- `self.inputs` : 定义输入,类型为`numpy.array`,并初始化。
- `self.outputs` : 定义输出,并在 Python 脚本中完成与算子同样的计算逻辑,返回 Python 端的计算结果。

- **前向算子单测**

- test_check_output 中会对算子的前向计算结果进行测试,对比参考的结果为 setUp 中 `self.outputs`提供的数据。`check_eager=True`表示开启新动态图(eager 模式)单测,`check_eager`默认为`False`

- **反向算子单测**

- `test_check_grad`中调用`check_grad`使用数值法检测梯度正确性和稳定性。
- 第一个参数`['Input']` : 指定对输入变量`Input`做梯度检测。
- 第二个参数`'Out'` : 指定前向网络最终的输出目标变量`Out`。
- 第三个参数`check_eager` : `check_eager=True`表示开启新动态图(eager 模式)单测,`check_eager`默认为`False`。
- 第三个参数`check_eager` : `check_eager=True` 表示开启新动态图(eager 模式)单测,`check_eager` 默认为`False`。
- 对于存在多个输入的反向算子测试,需要指定只计算部分输入梯度的 case
- 例如,[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`分支用来测试只需要计算一个输入梯度的情况
- 此处第三个参数 max_relative_error:指定检测梯度时能容忍的最大错误值。

```python
def test_check_grad_ingore_x(self):
self.check_grad(
['Y'], 'Out', max_relative_error=0.005, no_grad_set=set("X"))
```python
def test_check_grad_ingore_x(self):
self.check_grad(
['Y'], 'Out', max_relative_error=0.005, no_grad_set=set("X"))

def test_check_grad_ingore_y(self):
self.check_grad(
['X'], 'Out', max_relative_error=0.005, no_grad_set=set('Y'))
```



def test_check_grad_ingore_y(self):
self.check_grad(
['X'], 'Out', max_relative_error=0.005, no_grad_set=set('Y'))
```

### 6.2 Python API 单元测试

Expand Down Expand Up @@ -959,7 +973,7 @@ PADDLE_ENFORCE_EQ(比较对象 A, 比较对象 B, 错误提示信息)
所以在定义反向算子时需要注意以下几点:

- 如果反向不需要前向的某些输入或输出参数,则无需在 args 中设置。
- 如果有些反向算子需要依赖前向算子的输入或输出变量的的 Shape 或 LoD,但不依赖于变量中 Tensor 的内存 Buffer 数据,且不能根据其他变量推断出该 Shape 和 LoD,则可以通过`no_need_buffer`对该变量进行配置,详见[YAML 配置规则](new_cpp_op_cn.html#yaml)。示例:
- 如果有些反向算子需要依赖前向算子的输入或输出变量的的 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)
Expand All @@ -971,12 +985,12 @@ PADDLE_ENFORCE_EQ(比较对象 A, 比较对象 B, 错误提示信息)

### 7.4 性能优化
#### 7.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)中有描述。
在写算子过程中优先使用高性能(如 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)) 中有描述。

#### 7.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)中有介绍。
算子的计算速度与输入的数据量有关,对于某些算子可以根据输入数据的 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)中有介绍。
由于 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 多个参数会导致模型训练速度变慢。

Expand Down Expand Up @@ -1064,7 +1078,7 @@ The following device operations are asynchronous with respect to the host:

**(2)反向传导**

通常来讲,算子的某个输入 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)
通常来讲,算子的某个输入 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)

## 八、更多信息

Expand All @@ -1080,13 +1094,13 @@ Paddle 支持动态图和静态图两种模式,在 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`。
- 注:前向算子生成 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_op_function.cc`中。
- 注:生成的相关代码在 `paddle/fluid/eager/api/generated/eager_generated` 目录下。
- **Python-C 函数**:将支持自动微分功能的 C++ 的函数接口(Autograd API)暴露到 Python 层供 Python API 调用。
- 注:生成的 Python-C 接口代码在 `paddle/fluid/pybind/eager_op_function.cc` 中。
- 静态图的执行流程与动态图不同,所以生成的代码也与动态图有较大差异。

静态图由于是先组网后计算,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`。
静态图由于是先组网后计算,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`。

> **注意:由于代码自动生成在编译时进行,所以查看上述生成代码需要先完成** [**框架的编译**](../../install/compile/fromsource.html)**。**
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def trace(x, offset=0, axis1=0, axis2=1, name=None):

对于静态图,一般分为输入参数检查、创建输出 Tensor、添加 OP 几个步骤。

- **输入参数检查:**包括必要的类型检查、值检查,以及输入 Tensor 的 shape、dtype 等检查,确保组网能正常运行等。
- **输入参数检查:** 包括必要的类型检查、值检查,以及输入 Tensor 的 shape、dtype 等检查,确保组网能正常运行等。
- 输入参数的检查一般仅在静态图分支中使用。主要原因是静态图下该函数仅被执行一次,发生在组网时,而动态图下该函数会被多次执行,Python 端过多的输入检查会影响执行效率。并且由于动态图即时执行的优势,如果发生错误也可以通过分析 C++ 端的报错信息定位问题。
- 示例中输入参数检查的代码逻辑比较复杂但仅用于 `trace` 函数,因此在该函数内定义一个检查输入参数的函数 `__check_input`,代码见下文。
- **创建输出 Tensor ,添加 OP:**
Expand Down
Loading