Skip to content

Commit 20be846

Browse files
committed
Merge branch 'develop' into update_doc
2 parents b3ff125 + b3afe30 commit 20be846

File tree

20 files changed

+432
-116
lines changed

20 files changed

+432
-116
lines changed

doc/howto/dev/new_op_cn.md

Lines changed: 44 additions & 17 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、GPU共享Kernel实现在`.h`文件中,否则,CPU 实现在`.cc`文件中,GPU 实现在`.cu`文件中
34+
注册Op | Op注册实现在`.cc`文件;Kernel注册CPU实现在`.cc`文件中,GPU实现在`.cu`文件中
3535

3636

3737
实现新的op都添加至目录[paddle/operators](https://github.com/PaddlePaddle/Paddle/tree/develop/paddle/operators)下,文件命名以`*_op.h`(如有) 、 `*_op.cc``*_op.cu`(如有)结尾。
@@ -171,7 +171,9 @@ class MulKernel : public framework::OpKernel {
171171

172172
`MulKernel`需要重写`Compute`接口,该接口参数为`const framework::ExecutionContext& context`, `ExecutionContext`相比`InferShapeContext`增加了设备类型,同样可获取到输入输出和属性参数,`Compute`函数里写具体实现时。
173173

174-
注意,不同设备(CPU、GPU)共享一个Op定义,是否则共享同一个`OpKernel`,取决于`Compute`调用的函数是否支持不同设备。`MulOp`的CPU、GPU实现共享同一个`Kernel``OpKernel`不共享的例子可以参考[`OnehotCrossEntropyOpKernel`](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/operators/cross_entropy_op.h#L43)
174+
注意,不同设备(CPU、GPU)共享一个Op定义,是否则共享同一个`OpKernel`,取决于`Compute`调用的函数是否支持不同设备。`MulOp`的CPU、GPU实现共享同一个`Kernel``OpKernel`不共享的例子可以参考[`OnehotCrossEntropyOpKernel`](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/operators/cross_entropy_op.h#L43)
175+
176+
为了使得`OpKernel`的计算过程书写较为简单,CPU、GPU的代码可以复用,我们通常借助Eigen unsupported Tensor模块来实现。关于在paddle中如何使用Eigen库,请参考对应的使用[文档](https://github.com/PaddlePaddle/Paddle/blob/develop/doc/howto/dev/use_eigen_cn.md)
175177

176178
到此前向Op实现完成,需要在`.cc`文件中注册该op和kernel。反向Op类的定义和Kernel定义与前向Op类似,这里不再重复。但注意,反向Op没有`ProtoMaker`
177179

@@ -191,9 +193,12 @@ REGISTER_OP_CPU_KERNEL(mul_grad,
191193
- `REGISTER_OP_WITHOUT_GRADIENT` : 用于注册没有反向的Op。
192194
- `REGISTER_OP_CPU_KERNEL` :注册`ops::MulKernel`类,并特化模板参数为`paddle::platform::CPUPlace`和`float`类型,同理,注册`ops::MulKernel`类。
193195
194-
在 `.cu`文件中注册GPU Kernel。
196+
在 `.cu`文件中注册GPU Kernel。请注意,如果GPU Kernel的实现是基于Eigen unsupported模块,那么在 `.cu`的最前面请加上宏定义 `#define EIGEN_USE_GPU`
195197
196198
```cpp
199+
// if use Eigen unsupported module before include head files
200+
#define EIGEN_USE_GPU
201+
197202
namespace ops = paddle::operators;
198203
REGISTER_OP_GPU_KERNEL(mul, ops::MulKernel<paddle::platform::GPUPlace, float>);
199204
REGISTER_OP_GPU_KERNEL(mul_grad,
@@ -280,28 +285,50 @@ class TestMulOp(unittest.TestCase):
280285

281286
反向Op单测继承自`GradientChecker`,而`GradientChecker`集成自`unittest.TestCase`,所以反向单测函数需要`test_`开头。
282287

283-
```python
284-
class MulGradOpTest(GradientChecker):
285-
def test_mul(self):
286-
op = create_op("mul")
287-
inputs = {
288+
```cpp
289+
class TestMulGradOp(GradientChecker):
290+
def setUp(self):
291+
self.op = create_op("mul")
292+
self.inputs = {
288293
'X': np.random.random((32, 84)).astype("float32"),
289294
'Y': np.random.random((84, 100)).astype("float32")
290295
}
291-
self.compare_grad(op, inputs)
296+
297+
def test_cpu_gpu_compare(self):
298+
self.compare_grad(self.op, self.inputs)
299+
300+
def test_normal(self):
292301
# mul op will enlarge the relative error
293302
self.check_grad(
294-
op, inputs, set(["X", "Y"]), "Out", max_relative_error=0.5)
295-
```
303+
self.op, self.inputs, ["X", "Y"], "Out", max_relative_error=0.5)
304+
305+
def test_ignore_x(self):
306+
self.check_grad(
307+
self.op,
308+
self.inputs, ["Y"],
309+
"Out",
310+
max_relative_error=0.5,
311+
no_grad_set={"X"})
312+
313+
def test_ignore_y(self):
314+
self.check_grad(
315+
self.op,
316+
self.inputs, ["X"],
317+
"Out",
318+
max_relative_error=0.5,
319+
no_grad_set={"Y"})
320+
```
321+
322+
下面解释一些关键的地方:
296323
297324
- 调用`create_op("mul")`创建反向Op对应的前向Op。
298-
- 定义输入`inputs`
299325
- 调用`compare_grad`函数对比CPU、GPU计算结果。
300-
- 调用`check_grad`检查梯度稳定性,这里采用数值法检测梯度正确性。
301-
- 第一个参数`op` : 前向op
302-
- 第二个参数`inputs` : 输入词典,词典的Key和`ProtoMaker`定义保持一致。
303-
- 第三个参数`set(["X", "Y"])` : 指定对输入变量`X``Y`做梯度检测。
326+
- `test_normal`中调用`check_grad`检查梯度稳定性,这里采用数值法检测梯度正确性。
327+
- 第一个参数`self.op` : 前向Op
328+
- 第二个参数`self.inputs` : 输入词典,词典的Key和`ProtoMaker`定义保持一致。
329+
- 第三个参数`["X", "Y"]` : 指定对输入变量`X`、`Y`做梯度检测。
304330
- 第四个参数`"Out"` : 指定前向网络最终的输出目标变量`Out`
331+
- `test_ignore_x`和`test_ignore_y`分支测试只需要计算一个输入梯度的情况。
305332
306333
307334
### 编译和执行单元测试

doc/howto/dev/use_eigen_cn.md

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
## 在Paddle中如何使用Eigen
2+
3+
神经网络本质上是一个计算图,计算需要的数据存放在`Tensor`中,而计算过程是由`Operartor`来描述的。在执行时,`Operator`调用对应`OpKernel`中的`Compute`接口,实现对`Tensor`的操作。
4+
5+
6+
### Eigen Tensor模块
7+
8+
Eigen Tensor模块对element-wise计算提供了强大的支持,并且书写一份代码,可以同时在CPU、GPU执行。但Eigen Tensor是一个正在开发中的模块,因此可能测试不够完备,文档较少。
9+
10+
关于Eigen Tensor模块的详细介绍请参考[文档1](https://github.com/RLovelett/eigen/blob/master/unsupported/Eigen/CXX11/src/Tensor/README.md)[文档2](https://bitbucket.org/eigen/eigen/src/default/unsupported/Eigen/CXX11/src/Tensor/README.md)
11+
12+
13+
### paddle::framework::Tensor
14+
15+
Paddle Tensor定义在framework目录下,其主要接口如下:
16+
17+
```cpp
18+
class Tensor {
19+
public:
20+
/*! Return a pointer to mutable memory block. */
21+
template <typename T>
22+
inline T* data();
23+
24+
/**
25+
* @brief Return a pointer to mutable memory block.
26+
* @note If not exist, then allocation.
27+
*/
28+
template <typename T>
29+
inline T* mutable_data(platform::Place place);
30+
31+
/**
32+
* @brief Return a pointer to mutable memory block.
33+
*
34+
* @param[in] dims The dimensions of the memory block.
35+
* @param[in] place The place of the memory block.
36+
*
37+
* @note If not exist, then allocation.
38+
*/
39+
template <typename T>
40+
inline T* mutable_data(DDim dims, platform::Place place);
41+
42+
/*! Resize the dimensions of the memory block. */
43+
inline Tensor& Resize(const DDim& dims);
44+
45+
/*! Return the dimensions of the memory block. */
46+
inline const DDim& dims() const;
47+
48+
private:
49+
/*! holds the memory block if allocated. */
50+
std::shared_ptr<Placeholder> holder_;
51+
52+
/*! points to dimensions of memory block. */
53+
DDim dim_;
54+
};
55+
```
56+
57+
`Placeholder`的作用是延迟分配内存,即我们可以先定义一个Tensor,然后使用Resize接口设置Tensor的大小,最后再调用mutable_data接口分配实际的内存。
58+
59+
```cpp
60+
paddle::framework::Tensor t;
61+
paddle::platform::CPUPlace place;
62+
// set size first
63+
t.Resize({2, 3});
64+
// allocate memory on CPU later
65+
t.mutable_data(place);
66+
```
67+
68+
### paddle::framework::Tensor使用样例
69+
下面以AddOp为例说明Tensor的使用过程:
70+
71+
- InferShape
72+
73+
在运行神经网络计算图时,我们先调用每个`Operator``InferShape`接口,根据输入Tensor的大小来设置输出Tensor的大小,`Resize`接口会被调用。
74+
75+
```cpp
76+
void InferShape(const framework::InferShapeContext &ctx) const override {
77+
PADDLE_ENFORCE_EQ(ctx.Input<Tensor>("X")->dims(),
78+
ctx.Input<Tensor>("Y")->dims(),
79+
"Two input of Add Op's dimension must be same.");
80+
ctx.Output<Tensor>("Out")->Resize(ctx.Input<Tensor>("X")->dims());
81+
}
82+
```
83+
84+
85+
- Run
86+
87+
`Operator`的`Run`接口最终会调用对应`OpKernel`的`Compute`接口,在这时真正的分配内存,`mutable_data`接口会被调用。
88+
89+
```cpp
90+
void Compute(const framework::ExecutionContext& context) const override {
91+
auto* input0 = context.Input<Tensor>("X");
92+
auto* input1 = context.Input<Tensor>("Y");
93+
auto* output = context.Output<Tensor>("Out");
94+
95+
output->mutable_data<T>(context.GetPlace());
96+
97+
auto x = EigenVector<T>::Flatten(*input0);
98+
auto y = EigenVector<T>::Flatten(*input1);
99+
auto z = EigenVector<T>::Flatten(*output);
100+
101+
auto place = context.GetEigenDevice<Place>();
102+
103+
z.device(place) = x + y;
104+
}
105+
```
106+
107+
108+
### paddle::framework::Tensor到EigenTensor的转换
109+
110+
如上一小节所示,在具体的计算中,我们需要先把输入Tensor和输出Tensor转换为Eigen支持的格式。我们在[eigen.h](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/framework/eigen.h)中提供了一些全局函数用来实现paddle::framework::Tensor到EigenTensor/EigenMatrix/EigenVector/EigenScalar的转换。
111+
112+
以EigenTensor为例,做一个介绍
113+
114+
```cpp
115+
Tensor t;
116+
float* p = t.mutable_data<float>(make_ddim({1, 2, 3}), platform::CPUPlace());
117+
for (int i = 0; i < 1 * 2 * 3; i++) {
118+
p[i] = static_cast<float>(i);
119+
}
120+
121+
EigenTensor<float, 3>::Type et = EigenTensor<float, 3>::From(t);
122+
```
123+
124+
From是EigenTensor模板提供的一个接口,可以实现从paddle::framework::Tensor到对EigenTensor的转换。由于Tensor的rank是模板参数,因此在转换时需要显示的指定。
125+
126+
在Eigen中,不同rank的Tensor是不同类型,Vector是rank为1的Tensor。需要额外注意的是,EigenVector<T>::From方法是把paddle中的一维Tensor转为Eigen的一维Tensor,在这里用EigenVector来表示;而EigenVector<T>::Flatten方法是把paddle中的一个Tensor进行reshape操作,压扁成为Eigen的一维Tensor,类型仍然为EigenVector。
127+
128+
更多的转换方法请参考eigen_test.cc中的[单元测试](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/framework/eigen_test.cc)
129+
130+
131+
132+
### 实现计算
133+
134+
当需要完成计算时,我们需要等式左边的EigenTensor调用device接口。在这里需要注意的是,这里的EigenTensor之间的运算只是改变了原有Tensor中的数据,而不会改变原有Tensor的shape信息。
135+
136+
```cpp
137+
auto x = EigenVector<T>::Flatten(*input0);
138+
auto y = EigenVector<T>::Flatten(*input1);
139+
auto z = EigenVector<T>::Flatten(*output);
140+
auto place = context.GetEigenDevice<Place>();
141+
z.device(place) = x + y;
142+
```
143+
144+
在这段代码中,input0/input1/output可以是任意维度的Tensor。我们调用了EigenVector的Flatten接口,把任意维度的Tensor转为了一维的EigenVector。而在计算结束之后,input0/input1/output的原有shape信息不变。如果想改变原有Tensor的shape信息,可以调用Resize接口进行改变。
145+
146+
由于Eigen Tensor模块的文档较少,我们可以参考TensorFlow的[kernels](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/core/kernels)模块下的相关`OpKernel`的计算代码。

paddle/framework/attribute.cc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ template <>
4343
AttrType AttrTypeID<std::vector<std::string>>() {
4444
return STRINGS;
4545
}
46+
template <>
47+
AttrType AttrTypeID<std::vector<std::pair<int, int>>>() {
48+
return INT_PAIRS;
49+
}
4650

4751
Attribute GetAttrValue(const OpDesc::Attr& attr_desc) {
4852
switch (attr_desc.type()) {
@@ -76,6 +80,14 @@ Attribute GetAttrValue(const OpDesc::Attr& attr_desc) {
7680
}
7781
return val;
7882
}
83+
case paddle::framework::AttrType::INT_PAIRS: {
84+
std::vector<std::pair<int, int>> val(attr_desc.int_pairs_size());
85+
for (int i = 0; i < attr_desc.int_pairs_size(); ++i) {
86+
val[i].first = attr_desc.int_pairs(i).first();
87+
val[i].second = attr_desc.int_pairs(i).second();
88+
}
89+
return val;
90+
}
7991
}
8092
PADDLE_ENFORCE(false, "Unknown OpDesc::AttrDesc::type !");
8193
return boost::blank();

paddle/framework/attribute.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ namespace paddle {
2828
namespace framework {
2929

3030
typedef boost::variant<boost::blank, int, float, std::string, std::vector<int>,
31-
std::vector<float>, std::vector<std::string>>
31+
std::vector<float>, std::vector<std::string>,
32+
std::vector<std::pair<int, int>>>
3233
Attribute;
3334

3435
typedef std::unordered_map<std::string, Attribute> AttributeMap;

paddle/framework/framework.proto

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,14 @@ enum AttrType {
2222
INTS = 3;
2323
FLOATS = 4;
2424
STRINGS = 5;
25+
INT_PAIRS = 6;
2526
}
2627

28+
message IntPair {
29+
required int32 first = 1;
30+
required int32 second = 2;
31+
};
32+
2733
// OpDesc describes an instance of a C++ framework::OperatorBase
2834
// derived class type.
2935
message OpDesc {
@@ -37,6 +43,7 @@ message OpDesc {
3743
repeated int32 ints = 6;
3844
repeated float floats = 7;
3945
repeated string strings = 8;
46+
repeated IntPair int_pairs = 9;
4047
};
4148

4249
message Var {

paddle/framework/op_registry_test.cc

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -174,36 +174,4 @@ TEST(OpRegistry, CustomChecker) {
174174
op->Run(scope, dev_ctx);
175175
int test_attr = op->GetAttr<int>("test_attr");
176176
ASSERT_EQ(test_attr, 4);
177-
}
178-
179-
class TestAttrProtoMaker : public pd::OpProtoAndCheckerMaker {
180-
public:
181-
TestAttrProtoMaker(pd::OpProto* proto, pd::OpAttrChecker* op_checker)
182-
: OpProtoAndCheckerMaker(proto, op_checker) {
183-
AddAttr<float>("scale", "scale of test op");
184-
AddAttr<float>("scale", "scale of test op");
185-
}
186-
};
187-
188-
TEST(ProtoMaker, DuplicatedAttr) {
189-
pd::OpProto op_proto;
190-
pd::OpAttrChecker op_checker;
191-
auto proto_maker = TestAttrProtoMaker(&op_proto, &op_checker);
192-
ASSERT_THROW(proto_maker.Validate(), paddle::platform::EnforceNotMet);
193-
}
194-
195-
class TestInOutProtoMaker : public pd::OpProtoAndCheckerMaker {
196-
public:
197-
TestInOutProtoMaker(pd::OpProto* proto, pd::OpAttrChecker* op_checker)
198-
: OpProtoAndCheckerMaker(proto, op_checker) {
199-
AddInput("input", "input of test op");
200-
AddInput("input", "input of test op");
201-
}
202-
};
203-
204-
TEST(ProtoMaker, DuplicatedInOut) {
205-
pd::OpProto op_proto;
206-
pd::OpAttrChecker op_checker;
207-
auto proto_maker = TestInOutProtoMaker(&op_proto, &op_checker);
208-
ASSERT_THROW(proto_maker.Validate(), paddle::platform::EnforceNotMet);
209-
}
177+
}

paddle/framework/operator_test.cc

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,4 +263,38 @@ TEST(Operator, Clone) {
263263
OperatorClone a("ABC", {}, {}, {});
264264
auto b = a.Clone();
265265
ASSERT_EQ(a.Type(), b->Type());
266+
}
267+
268+
class TestAttrProtoMaker : public paddle::framework::OpProtoAndCheckerMaker {
269+
public:
270+
TestAttrProtoMaker(paddle::framework::OpProto* proto,
271+
paddle::framework::OpAttrChecker* op_checker)
272+
: OpProtoAndCheckerMaker(proto, op_checker) {
273+
AddAttr<float>("scale", "scale of test op");
274+
AddAttr<float>("scale", "scale of test op");
275+
}
276+
};
277+
278+
TEST(ProtoMaker, DuplicatedAttr) {
279+
paddle::framework::OpProto op_proto;
280+
paddle::framework::OpAttrChecker op_checker;
281+
auto proto_maker = TestAttrProtoMaker(&op_proto, &op_checker);
282+
ASSERT_THROW(proto_maker.Validate(), paddle::platform::EnforceNotMet);
283+
}
284+
285+
class TestInOutProtoMaker : public paddle::framework::OpProtoAndCheckerMaker {
286+
public:
287+
TestInOutProtoMaker(paddle::framework::OpProto* proto,
288+
paddle::framework::OpAttrChecker* op_checker)
289+
: OpProtoAndCheckerMaker(proto, op_checker) {
290+
AddInput("input", "input of test op");
291+
AddInput("input", "input of test op");
292+
}
293+
};
294+
295+
TEST(ProtoMaker, DuplicatedInOut) {
296+
paddle::framework::OpProto op_proto;
297+
paddle::framework::OpAttrChecker op_checker;
298+
auto proto_maker = TestInOutProtoMaker(&op_proto, &op_checker);
299+
ASSERT_THROW(proto_maker.Validate(), paddle::platform::EnforceNotMet);
266300
}

0 commit comments

Comments
 (0)