Skip to content

Commit de8c462

Browse files
authored
Update new op docs (#6505)
* update docs about how to add a new operator
1 parent 61ec0b9 commit de8c462

File tree

2 files changed

+80
-111
lines changed

2 files changed

+80
-111
lines changed

doc/howto/dev/new_op_cn.md

Lines changed: 40 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030
-------------- | :----------------------
3131
OpProtoMake定义 | `.cc`文件,Backward Op不需要定义OpProtoMake
3232
Op定义 | `.cc`文件
33-
Kernel实现 | CPU、GPU共享Kernel实现在`.h`文件中,否则,CPU 实现在`.cc`文件中,GPU 实现在`.cu`文件中。
34-
注册Op | Op注册实现在`.cc`文件;Kernel注册CPU实现在`.cc`文件中,GPU实现在`.cu`文件中
33+
Kernel实现 | CPU、CUDA共享Kernel实现在`.h`文件中,否则,CPU 实现在`.cc`文件中,CUDA 实现在`.cu`文件中。
34+
注册Op | Op注册实现在`.cc`文件;Kernel注册CPU实现在`.cc`文件中,CUDA实现在`.cu`文件中
3535

3636

3737
实现新的op都添加至目录[paddle/operators](https://github.com/PaddlePaddle/Paddle/tree/develop/paddle/operators)下,文件命名以`*_op.h`(如有) 、 `*_op.cc``*_op.cu`(如有)结尾。**系统会根据文件名自动构建op和其对应的Python扩展。**
@@ -153,7 +153,7 @@ MulOp(const std::string &type, const framework::VariableNameMap &inputs,
153153
154154
`MulKernel`继承自`framework::OpKernel`,带有下面两个模板参数:
155155
156-
- `typename Place`: 表示设备类型,不同设备(CPU、GPU)共享同一个Kernel时,需加该模板参数,不共享则不加,一个不共享的例子是[`OnehotCrossEntropyOpKernel`](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/operators/cross_entropy_op.h#L43)。
156+
- `typename DeviceContext`: 表示设备类型,不同设备(CPU、CUDA)共享同一个Kernel时,需加该模板参数,不共享则不加,一个不共享的例子是[`OnehotCrossEntropyOpKernel`](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/operators/cross_entropy_op.h#L43)。
157157
158158
- `typename T` : 表示数据类型,如`float`, `double`等。
159159
@@ -165,26 +165,24 @@ MulOp(const std::string &type, const framework::VariableNameMap &inputs,
165165
下面是 `MulKernel` `Compute`的实现:
166166
167167
```cpp
168-
template <typename Place, typename T>
168+
template <typename DeviceContext, typename T>
169169
class MulKernel : public framework::OpKernel {
170170
public:
171171
void Compute(const framework::ExecutionContext& context) const override {
172172
auto* X = context.Input<Tensor>("X");
173173
auto* Y = context.Input<Tensor>("Y");
174174
auto* Z = context.Output<Tensor>("Out");
175175
Z->mutable_data<T>(context.GetPlace());
176-
auto* device_context =
177-
const_cast<platform::DeviceContext*>(context.device_context_);
178-
math::matmul<Place, T>(*X, false, *Y, false, 1, Z, 0, device_context);
176+
auto& device_context = context.template device_context<DeviceContext>();
177+
math::matmul<DeviceContext, T>(*X, false, *Y, false, 1, Z, 0, device_context);
179178
}
180179
};
181-
```
182180
183-
需要注意:**不同设备(CPU、GPU)共享一个Op定义,是否则共享同一个`OpKernel`,取决于`Compute`调用的函数是否支持不同设备。**
181+
需要注意:**不同设备(CPU、CUDA)共享一个Op定义,是否则共享同一个`OpKernel`,取决于`Compute`调用的函数是否支持不同设备。**
184182
185-
`MulOp`的CPU、GPU实现共享同一个`Kernel``OpKernel`不共享的例子可以参考:[`OnehotCrossEntropyOpKernel`](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/operators/cross_entropy_op.h#L43)
183+
`MulOp`的CPU、CUDA实现共享同一个`Kernel`。`OpKernel`不共享的例子可以参考:[`OnehotCrossEntropyOpKernel`](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/operators/cross_entropy_op.h#L43)。
186184
187-
为了使`OpKernel`的计算过程书写更加简单,并且CPU、GPU的代码可以复用,我们通常借助 Eigen unsupported Tensor模块来实现`Compute`接口。关于在PaddlePaddle中如何使用Eigen库,请参考[使用文档](https://github.com/PaddlePaddle/Paddle/blob/develop/doc/howto/dev/use_eigen_cn.md)
185+
为了使`OpKernel`的计算过程书写更加简单,并且CPU、CUDA的代码可以复用,我们通常借助 Eigen unsupported Tensor模块来实现`Compute`接口。关于在PaddlePaddle中如何使用Eigen库,请参考[使用文档](https://github.com/PaddlePaddle/Paddle/blob/develop/doc/howto/dev/use_eigen_cn.md)。
188186
189187
190188
到此,前向Op实现完成。接下来,需要在`.cc`文件中注册该op和kernel。
@@ -197,9 +195,9 @@ MulOp(const std::string &type, const framework::VariableNameMap &inputs,
197195
```cpp
198196
namespace ops = paddle::operators;
199197
REGISTER_OP(mul, ops::MulOp, ops::MulOpMaker, mul_grad, ops::MulOpGrad);
200-
REGISTER_OP_CPU_KERNEL(mul, ops::MulKernel<paddle::platform::CPUPlace, float>);
198+
REGISTER_OP_CPU_KERNEL(mul, ops::MulKernel<paddle::platform::CPUDeviceContext, float>);
201199
REGISTER_OP_CPU_KERNEL(mul_grad,
202-
ops::MulGradKernel<paddle::platform::CPUPlace, float>);
200+
ops::MulGradKernel<paddle::platform::CPUDeviceContext, float>);
203201
```
204202
205203
在上面的代码中:
@@ -209,17 +207,17 @@ MulOp(const std::string &type, const framework::VariableNameMap &inputs,
209207
- `REGISTER_OP_CPU_KERNEL` :注册`ops::MulKernel`类,并特化模板参数为`paddle::platform::CPUPlace`和`float`类型,同理,注册`ops::MulGradKernel`类。
210208
211209
212-
-`.cu`文件中注册GPU Kernel。
213-
- 请注意,如果GPU Kernel的实现基于Eigen unsupported模块,那么在 `.cu`的开始请加上宏定义 `#define EIGEN_USE_GPU`,代码示例如下:
210+
- 在 `.cu`文件中注册CUDA Kernel。
211+
- 请注意,如果CUDA Kernel的实现基于Eigen unsupported模块,那么在 `.cu`的开始请加上宏定义 `#define EIGEN_USE_GPU`,代码示例如下:
214212
215213
```cpp
216214
// if use Eigen unsupported module before include head files
217-
// #define EIGEN_USE_GPU
215+
#define EIGEN_USE_GPU
218216
219217
namespace ops = paddle::operators;
220-
REGISTER_OP_GPU_KERNEL(mul, ops::MulKernel<paddle::platform::GPUPlace, float>);
221-
REGISTER_OP_GPU_KERNEL(mul_grad,
222-
ops::MulGradKernel<paddle::platform::GPUPlace, float>);
218+
REGISTER_OP_CUDA_KERNEL(mul, ops::MulKernel<paddle::platform::CUDADeviceContext, float>);
219+
REGISTER_OP_CUDA_KERNEL(mul_grad,
220+
ops::MulGradKernel<paddle::platform::CUDADeviceContext, float>);
223221
```
224222
225223
### 5. 编译
@@ -236,71 +234,55 @@ make mul_op
236234
237235
## 实现单元测试
238236
239-
单测包括对比前向Op不同设备(CPU、GPU)的实现、对比反向OP不同设备(CPU、GPU)的实现、反向Op的梯度测试。下面介绍介绍[`MulOp`的单元测试](https://github.com/PaddlePaddle/Paddle/blob/develop/python/paddle/v2/framework/tests/test_mul_op.py)
237+
单测包括对比前向Op不同设备(CPU、CUDA)的实现、对比反向OP不同设备(CPU、CUDA)的实现、反向Op的梯度测试。下面介绍介绍[`MulOp`的单元测试](https://github.com/PaddlePaddle/Paddle/blob/develop/python/paddle/v2/framework/tests/test_mul_op.py)。
240238
241-
### 前向Operator单元测试
242239
243-
前向Op单元测试继承自`unittest.TestCase`,并定义元类`__metaclass__ = OpTestMeta`。各项更加具体的单元测试在`OpTestMeta`里完成。测试前向Operator,需要:
240+
Op单元测试继承自`OpTest`。各项更加具体的单元测试在`TestMulOp`里完成。测试Operator,需要:
244241
245242
1. 在`setUp`函数定义输入、输出,以及相关的属性参数。
246243
2. 生成随机的输入数据。
247244
3. 在Python脚本中实现与前向operator相同的计算逻辑,得到输出值,与operator前向计算的输出进行对比。
245+
4. 反向计算已经自动集成进测试框架,直接调用相应接口即可。
248246
249247
250248
```python
251249
import unittest
252250
import numpy as np
253-
from gradient_checker import GradientChecker, create_op
254-
from op_test_util import OpTestMeta
251+
from op_test import OpTest
255252
256-
class TestMulOp(unittest.TestCase):
257-
__metaclass__ = OpTestMeta
258253
254+
class TestMulOp(OpTest):
259255
def setUp(self):
260-
self.type = "mul"
256+
self.op_type = "mul"
261257
self.inputs = {
262258
'X': np.random.random((32, 84)).astype("float32"),
263259
'Y': np.random.random((84, 100)).astype("float32")
264260
}
265261
self.outputs = {'Out': np.dot(self.inputs['X'], self.inputs['Y'])}
266-
```
267262
268-
上面的代码首先导入依赖的包,下面是对`setUp`函数中操作的重要变量的详细解释:
269-
270-
- `self.type = "mul" ` : 定义类型,与operator注册时注册的类型一致。
271-
- `self.inputs` : 定义输入,类型为`numpy.array`,并初始化。
272-
- `self.outputs` : 定义输出,并在Python脚本中完成与operator同样的计算逻辑,返回Python端的计算结果。
273-
274-
275-
### 反向Operator单元测试
263+
def test_check_output(self):
264+
self.check_output()
276265
277-
反向Op单元测试继承自`GradientChecker`,而`GradientChecker`继承自`unittest.TestCase`,因此,**反向单元测试函数需要以`test_`开头**
266+
def test_check_grad_normal(self):
267+
self.check_grad(['X', 'Y'], 'Out', max_relative_error=0.5)
278268
279-
```python
280-
class TestMulGradOp(GradientChecker):
281-
def setUp(self):
282-
self.op = create_op("mul")
283-
self.inputs = {
284-
'X': np.random.random((32, 84)).astype("float32"),
285-
'Y': np.random.random((84, 100)).astype("float32")
286-
}
269+
def test_check_grad_ingore_x(self):
270+
self.check_grad(
271+
['Y'], 'Out', max_relative_error=0.5, no_grad_set=set("X"))
287272
288-
def test_check_grad_normal(self):
289-
# mul op will enlarge the relative error
290-
self.check_grad(['X', 'Y'], 'Out', max_relative_error=0.5)
273+
def test_check_grad_ingore_y(self):
274+
self.check_grad(
275+
['X'], 'Out', max_relative_error=0.5, no_grad_set=set('Y'))
291276
292-
def test_check_grad_ingore_x(self):
293-
self.check_grad(
294-
['Y'], 'Out', max_relative_error=0.5, no_grad_set=set("X"))
277+
```
295278
296-
def test_check_grad_ingore_y(self):
297-
self.check_grad(
298-
['X'], 'Out', max_relative_error=0.5, no_grad_set=set('Y'))
299-
```
279+
上面的代码首先导入依赖的包,下面是对`setUp`函数中操作的重要变量的详细解释:
300280
301-
下面解释代码中一些关键的地方:
281+
- `self.op_type = "mul" ` : 定义类型,与operator注册时注册的类型一致。
282+
- `self.inputs` : 定义输入,类型为`numpy.array`,并初始化。
283+
- `self.outputs` : 定义输出,并在Python脚本中完成与operator同样的计算逻辑,返回Python端的计算结果。
302284
303-
- 调用`create_op("mul")`创建反向Op对应的前向Op。
285+
而反向测试中:
304286
- `test_check_grad_normal`中调用`check_grad`使用数值法检测梯度正确性和稳定性。
305287
- 第一个参数`["X", "Y"]` : 指定对输入变量`X`、`Y`做梯度检测。
306288
- 第二个参数`"Out"` : 指定前向网络最终的输出目标变量`Out`。
@@ -328,5 +310,5 @@ ctest -R test_mul_op
328310

329311
- 为每个Op创建单独的`*_op.h`(如有)、`*_op.cc``*_op.cu`(如有)。不允许一个文件中包含多个Op,这将会导致编译出错。
330312
- 注册Op时的类型名,需要和该Op的名字一样。即不允许在`A_op.cc`里面,注册`REGISTER_OP(B, ...)`等,这将会导致单元测试出错。
331-
- 如果Op没有实现GPU Kernel,请不要创建空的`*_op.cu`,这将会导致单元测试出错。
313+
- 如果Op没有实现CUDA Kernel,请不要创建空的`*_op.cu`,这将会导致单元测试出错。
332314
- 如果多个Op依赖一些共用的函数,可以创建非`*_op.*`格式的文件来存放,如`gather.h`文件。

0 commit comments

Comments
 (0)