Skip to content

Commit b64aac5

Browse files
authored
Merge pull request #3857 from qingqing01/grad_test_for_multi_inputs
Enhance the unit test framework to explicitly test whether the operator correctly handles gradients for multiple inputs.
2 parents f2f839a + ab55d79 commit b64aac5

File tree

8 files changed

+117
-54
lines changed

8 files changed

+117
-54
lines changed

doc/howto/dev/new_op_cn.md

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -286,28 +286,50 @@ class TestMulOp(unittest.TestCase):
286286

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

289-
```
290-
class MulGradOpTest(GradientChecker):
291-
def test_mul(self):
292-
op = create_op("mul")
293-
inputs = {
289+
```
290+
class TestMulGradOp(GradientChecker):
291+
def setUp(self):
292+
self.op = create_op("mul")
293+
self.inputs = {
294294
'X': np.random.random((32, 84)).astype("float32"),
295295
'Y': np.random.random((84, 100)).astype("float32")
296296
}
297-
self.compare_grad(op, inputs)
297+
298+
def test_cpu_gpu_compare(self):
299+
self.compare_grad(self.op, self.inputs)
300+
301+
def test_normal(self):
298302
# mul op will enlarge the relative error
299303
self.check_grad(
300-
op, inputs, set(["X", "Y"]), "Out", max_relative_error=0.5)
301-
```
304+
self.op, self.inputs, ["X", "Y"], "Out", max_relative_error=0.5)
305+
306+
def test_ignore_x(self):
307+
self.check_grad(
308+
self.op,
309+
self.inputs, ["Y"],
310+
"Out",
311+
max_relative_error=0.5,
312+
no_grad_set={"X"})
313+
314+
def test_ignore_y(self):
315+
self.check_grad(
316+
self.op,
317+
self.inputs, ["X"],
318+
"Out",
319+
max_relative_error=0.5,
320+
no_grad_set={"Y"})
321+
```
322+
323+
下面解释一些关键的地方:
302324

303325
- 调用`create_op("mul")`创建反向Op对应的前向Op。
304-
- 定义输入`inputs`
305326
- 调用`compare_grad`函数对比CPU、GPU计算结果。
306-
- 调用`check_grad`检查梯度稳定性,这里采用数值法检测梯度正确性。
307-
- 第一个参数`op` : 前向op
308-
- 第二个参数`inputs` : 输入词典,词典的Key和`ProtoMaker`定义保持一致。
309-
- 第三个参数`set(["X", "Y"])` : 指定对输入变量`X``Y`做梯度检测。
327+
- `test_normal`中调用`check_grad`检查梯度稳定性,这里采用数值法检测梯度正确性。
328+
- 第一个参数`self.op` : 前向Op
329+
- 第二个参数`self.inputs` : 输入词典,词典的Key和`ProtoMaker`定义保持一致。
330+
- 第三个参数`["X", "Y"]` : 指定对输入变量`X``Y`做梯度检测。
310331
- 第四个参数`"Out"` : 指定前向网络最终的输出目标变量`Out`
332+
- `test_ignore_x``test_ignore_y`分支测试只需要计算一个输入梯度的情况。
311333

312334

313335
### 编译和执行

paddle/operators/mul_op.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ class MulOpGrad : public framework::OperatorWithKernel {
7575
PADDLE_ENFORCE(y_dims[1] == out_dims[1],
7676
"Out@GRAD M X N must equal to Y dims 1, N ");
7777

78-
x_grad->Resize(x_dims);
79-
y_grad->Resize(y_dims);
78+
if (x_grad) x_grad->Resize(x_dims);
79+
if (y_grad) y_grad->Resize(y_dims);
8080
}
8181
};
8282

paddle/operators/mul_op.h

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,34 +31,38 @@ template <typename Place, typename T>
3131
class MulKernel : public framework::OpKernel {
3232
public:
3333
void Compute(const framework::ExecutionContext& context) const override {
34-
auto* X = context.Input<Tensor>("X");
35-
auto* Y = context.Input<Tensor>("Y");
36-
auto* Z = context.Output<Tensor>("Out");
37-
Z->mutable_data<T>(context.GetPlace());
34+
auto* x = context.Input<Tensor>("X");
35+
auto* y = context.Input<Tensor>("Y");
36+
auto* z = context.Output<Tensor>("Out");
37+
z->mutable_data<T>(context.GetPlace());
3838
auto* device_context =
3939
const_cast<platform::DeviceContext*>(context.device_context_);
40-
math::matmul<Place, T>(*X, false, *Y, false, 1, Z, 0, device_context);
40+
math::matmul<Place, T>(*x, false, *y, false, 1, z, 0, device_context);
4141
}
4242
};
4343

4444
template <typename Place, typename T>
4545
class MulGradKernel : public framework::OpKernel {
4646
public:
4747
void Compute(const framework::ExecutionContext& ctx) const override {
48-
auto* X = ctx.Input<Tensor>("X");
49-
auto* Y = ctx.Input<Tensor>("Y");
50-
auto* dOut = ctx.Input<Tensor>(framework::GradVarName("Out"));
48+
auto* x = ctx.Input<Tensor>("X");
49+
auto* y = ctx.Input<Tensor>("Y");
50+
auto* dout = ctx.Input<Tensor>(framework::GradVarName("Out"));
5151

52-
auto* dX = ctx.Output<Tensor>(framework::GradVarName("X"));
53-
auto* dY = ctx.Output<Tensor>(framework::GradVarName("Y"));
54-
dX->mutable_data<T>(ctx.GetPlace());
55-
dY->mutable_data<T>(ctx.GetPlace());
52+
auto* dx = ctx.Output<Tensor>(framework::GradVarName("X"));
53+
auto* dy = ctx.Output<Tensor>(framework::GradVarName("Y"));
5654
auto* device_context =
5755
const_cast<platform::DeviceContext*>(ctx.device_context_);
58-
// dX = dOut * Y'. dX: M x K, dOut : M x N, Y : K x N
59-
math::matmul<Place, T>(*dOut, false, *Y, true, 1, dX, 0, device_context);
60-
// dY = X' * dOut. dY: K x N, dOut : M x N, X : M x K
61-
math::matmul<Place, T>(*X, true, *dOut, false, 1, dY, 0, device_context);
56+
if (dx) {
57+
dx->mutable_data<T>(ctx.GetPlace());
58+
// dx = dout * y'. dx: M x K, dout : M x N, y : K x N
59+
math::matmul<Place, T>(*dout, false, *y, true, 1, dx, 0, device_context);
60+
}
61+
if (dy) {
62+
dy->mutable_data<T>(ctx.GetPlace());
63+
// dy = x' * dout. dy K x N, dout : M x N, x : M x K
64+
math::matmul<Place, T>(*x, true, *dout, false, 1, dy, 0, device_context);
65+
}
6266
}
6367
};
6468

paddle/operators/rowwise_add_op.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,10 @@ class RowwiseAddGradOp : public framework::OperatorWithKernel {
6464
auto dims0 = ctx.Input<Tensor>("X")->dims();
6565
auto dims1 = ctx.Input<Tensor>("b")->dims();
6666
PADDLE_ENFORCE_EQ(1, dims1.size(), "b dims should be 1")
67-
ctx.Output<Tensor>(framework::GradVarName("X"))->Resize(dims0);
68-
ctx.Output<Tensor>(framework::GradVarName("b"))->Resize(dims1);
67+
auto *dx = ctx.Output<Tensor>(framework::GradVarName("X"));
68+
auto *db = ctx.Output<Tensor>(framework::GradVarName("b"));
69+
if (dx) dx->Resize(dims0);
70+
if (db) db->Resize(dims1);
6971
}
7072
};
7173

paddle/operators/rowwise_add_op.h

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,20 +51,24 @@ template <typename Place, typename T>
5151
class RowwiseAddGradKernel : public framework::OpKernel {
5252
public:
5353
void Compute(const framework::ExecutionContext& context) const override {
54-
auto* dOut = context.Input<Tensor>(framework::GradVarName("Out"));
55-
auto* dX = context.Output<Tensor>(framework::GradVarName("X"));
54+
auto* dout = context.Input<Tensor>(framework::GradVarName("Out"));
55+
auto* dx = context.Output<Tensor>(framework::GradVarName("X"));
5656
auto* db = context.Output<Tensor>(framework::GradVarName("b"));
57-
dX->mutable_data<T>(context.GetPlace());
58-
db->mutable_data<T>(context.GetPlace());
5957

60-
auto OutGrad = EigenMatrix<T>::From(*dOut);
58+
auto out_grad = EigenMatrix<T>::From(*dout);
6159
auto place = context.GetEigenDevice<Place>();
62-
EigenMatrix<T>::From(*dX).device(place) = OutGrad;
60+
if (dx) {
61+
dx->mutable_data<T>(context.GetPlace());
62+
EigenMatrix<T>::From(*dx).device(place) = out_grad;
63+
}
6364

64-
// https://eigen.tuxfamily.org/dox/unsupported/TensorBase_8h_source.html
65-
// colwise add
66-
Eigen::array<int, 1> dims{{0}}; /* dimension to reduce */
67-
EigenVector<T>::Flatten(*db).device(place) = OutGrad.sum(dims);
65+
if (db) {
66+
db->mutable_data<T>(context.GetPlace());
67+
// https://eigen.tuxfamily.org/dox/unsupported/TensorBase_8h_source.html
68+
// colwise add
69+
Eigen::array<int, 1> dims{{0}}; /* dimension to reduce */
70+
EigenVector<T>::Flatten(*db).device(place) = out_grad.sum(dims);
71+
}
6872
}
6973
};
7074
} // namespace operators

python/paddle/v2/framework/tests/gradient_checker.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,9 @@ def check_grad(self,
286286
for no_grad in no_grad_set:
287287
if no_grad not in in_names:
288288
raise ValueError("no_grad should be in in_names")
289+
if no_grad in inputs_to_check:
290+
raise ValueError("no_grad should not be in inputs_to_check")
291+
289292
backward_op = core.Operator.backward(forward_op, no_grad_set)
290293

291294
places = [core.CPUPlace()]
@@ -301,7 +304,6 @@ def check_grad(self,
301304

302305
check_names = [grad_var_name(name) for name in inputs_to_check]
303306
for place in places:
304-
# get analytical gradients according to different device
305307
analytic_grads = self.__get_gradient(forward_op, backward_op,
306308
input_vars, check_names, place)
307309
self.__assert_is_close(numeric_grads, analytic_grads, check_names,

python/paddle/v2/framework/tests/test_mul_op.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,37 @@ def setUp(self):
1616
self.outputs = {'Out': np.dot(self.inputs['X'], self.inputs['Y'])}
1717

1818

19-
class MulGradOpTest(GradientChecker):
20-
def test_mul(self):
21-
op = create_op("mul")
22-
inputs = {
19+
class TestMulGradOp(GradientChecker):
20+
def setUp(self):
21+
self.op = create_op("mul")
22+
self.inputs = {
2323
'X': np.random.random((32, 84)).astype("float32"),
2424
'Y': np.random.random((84, 100)).astype("float32")
2525
}
26+
27+
def test_cpu_gpu_compare(self):
28+
self.compare_grad(self.op, self.inputs)
29+
30+
def test_normal(self):
2631
# mul op will enlarge the relative error
2732
self.check_grad(
28-
op, inputs, set(["X", "Y"]), "Out", max_relative_error=0.5)
33+
self.op, self.inputs, ["X", "Y"], "Out", max_relative_error=0.5)
34+
35+
def test_ignore_x(self):
36+
self.check_grad(
37+
self.op,
38+
self.inputs, ["Y"],
39+
"Out",
40+
max_relative_error=0.5,
41+
no_grad_set={"X"})
42+
43+
def test_ignore_y(self):
44+
self.check_grad(
45+
self.op,
46+
self.inputs, ["X"],
47+
"Out",
48+
max_relative_error=0.5,
49+
no_grad_set={"Y"})
2950

3051

3152
# TODO(dzh,qijun) : mulgrad test case need transpose feature of blas library

python/paddle/v2/framework/tests/test_rowwise_add_op.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,22 @@ def setUp(self):
1616
self.outputs = {'Out': np.add(self.inputs['X'], self.inputs['b'])}
1717

1818

19-
class RowwiseAddGradOpTest(GradientChecker):
20-
def test_rowwise_add(self):
21-
op = create_op("rowwise_add")
22-
inputs = {
19+
class TestRowwiseAddGradOp(GradientChecker):
20+
def setUp(self):
21+
self.op = create_op("rowwise_add")
22+
self.inputs = {
2323
"X": np.random.uniform(0.1, 1, [5, 10]).astype("float32"),
2424
"b": np.random.uniform(0.1, 1, [10]).astype("float32")
2525
}
26-
self.check_grad(op, inputs, set(["X", "b"]), "Out")
26+
27+
def test_normal(self):
28+
self.check_grad(self.op, self.inputs, ["X", "b"], "Out")
29+
30+
def test_ignore_b(self):
31+
self.check_grad(self.op, self.inputs, ["X"], "Out", no_grad_set={"b"})
32+
33+
def test_ignore_x(self):
34+
self.check_grad(self.op, self.inputs, ["b"], "Out", no_grad_set={"X"})
2735

2836

2937
if __name__ == '__main__':

0 commit comments

Comments
 (0)