Skip to content

Commit f7c96f0

Browse files
authored
Big data op_test benchmark, for checking output consistent in different runs. (#10646)
* "init benchmark ops" * "untrack outputs" * "delete some usused code" * "benchmark" * "fix ci" * "fix op test" * "fix uint16 missing" * "fix ci" * "follow comments" * "fix ci" * "follow comments" * "conficts. merge develop branch" * repick * "merge develop branch"
1 parent 3ff9ba0 commit f7c96f0

File tree

8 files changed

+621
-352
lines changed

8 files changed

+621
-352
lines changed

paddle/fluid/framework/operator.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -661,8 +661,10 @@ proto::VarType::Type OperatorWithKernel::IndicateDataType(
661661
}
662662
if (t != nullptr) {
663663
int tmp = static_cast<int>(ToDataType(t->type()));
664-
PADDLE_ENFORCE(tmp == data_type || data_type == -1,
665-
"DataType of Paddle Op %s must be the same.", Type());
664+
PADDLE_ENFORCE(
665+
tmp == data_type || data_type == -1,
666+
"DataType of Paddle Op %s must be the same. Get %d != %d", Type(),
667+
data_type, tmp);
666668
data_type = tmp;
667669
}
668670
}

python/paddle/fluid/executor.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ def to_name_str(var):
170170
return var.desc.name()
171171
elif isinstance(var, str):
172172
return var
173+
elif isinstance(var, basestring):
174+
return str(var)
173175
else:
174176
raise TypeError(str(var) + " should be Variable or str")
175177

python/paddle/fluid/framework.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ def convert_np_dtype_to_dtype_(np_dtype):
7272
return core.VarDesc.VarType.INT64
7373
elif dtype == np.bool:
7474
return core.VarDesc.VarType.BOOL
75+
elif dtype == np.uint16:
76+
return core.VarDesc.VarType.INT16
7577
elif dtype == np.uint8:
7678
return core.VarDesc.VarType.UINT8
7779
else:
@@ -368,6 +370,13 @@ class Operator(object):
368370
Block. Users can use the build in instructions to describe their neural
369371
network.
370372
"""
373+
OP_WITHOUT_KERNEL_SET = {
374+
'feed', 'fetch', 'save', 'load', 'recurrent', 'go',
375+
'rnn_memory_helper_grad', 'conditional_block', 'while', 'send', 'recv',
376+
'listen_and_serv', 'parallel_do', 'save_combine', 'load_combine',
377+
'ncclInit', 'channel_create', 'channel_close', 'channel_send',
378+
'channel_recv', 'select'
379+
}
371380

372381
def __init__(self,
373382
block,
@@ -504,17 +513,13 @@ def find_name(var_list, name):
504513
else:
505514
self.desc.set_attr(attr_name, self.attrs[attr_name])
506515
self.desc.check_attrs()
507-
no_kernel_op_set = {
508-
'feed', 'fetch', 'save', 'load', 'recurrent', 'go',
509-
'rnn_memory_helper_grad', 'conditional_block', 'while', 'send',
510-
'recv', 'listen_and_serv', 'parallel_do', 'save_combine',
511-
'load_combine', 'ncclInit', 'channel_create', 'channel_close',
512-
'channel_send', 'channel_recv', 'select', 'gen_nccl_id'
513-
}
514-
if type not in no_kernel_op_set:
516+
if self.has_kernel(type):
515517
self.desc.infer_var_type(self.block.desc)
516518
self.desc.infer_shape(self.block.desc)
517519

520+
def has_kernel(self, op_type):
521+
return op_type not in self.OP_WITHOUT_KERNEL_SET
522+
518523
def to_string(self, throw_on_error):
519524
"""
520525
To debug string.
@@ -742,7 +747,9 @@ def idx(self):
742747

743748
def var(self, name):
744749
if not isinstance(name, basestring):
745-
raise TypeError()
750+
raise TypeError(
751+
"var require string as parameter, but get %s instead." %
752+
(type(name)))
746753
v = self.vars.get(name, None)
747754
if v is None:
748755
raise ValueError("var %s not in this block" % name)
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import numpy as np
16+
import unittest
17+
import time
18+
import itertools
19+
20+
import paddle.fluid as fluid
21+
import paddle.fluid.core as core
22+
from paddle.fluid.op import Operator
23+
from op_test import OpTest
24+
25+
26+
class BenchmarkSuite(OpTest):
27+
def timeit_function(self, callback, iters, *args, **kwargs):
28+
assert iters != 0, "Iters should >= 1"
29+
start = time.time()
30+
for i in range(iters):
31+
callback(*args, **kwargs)
32+
elapse = time.time() - start
33+
return elapse / iters
34+
35+
def _assert_cpu_gpu_same(self, cpu_outs, gpu_outs, fetch_list, atol):
36+
for item_cpu_out, item_gpu_out, variable in zip(cpu_outs, gpu_outs,
37+
fetch_list):
38+
# the cpu version is baseline, expect gpu version keep same with cpu version.
39+
expect = item_cpu_out
40+
expect_t = np.array(item_cpu_out)
41+
actual = item_gpu_out
42+
actual_t = np.array(item_gpu_out)
43+
var_name = variable if isinstance(variable,
44+
basestring) else variable.name
45+
self.assertTrue(
46+
np.allclose(
47+
actual_t, expect_t, atol=atol),
48+
"Output (" + var_name + ") has diff" + str(actual_t) + "\n" +
49+
str(expect_t))
50+
self.assertListEqual(actual.lod(),
51+
expect.lod(),
52+
"Output (" + var_name + ") has different lod")
53+
54+
def _get_input_names(self):
55+
inputs = []
56+
for name, value in self.inputs.iteritems():
57+
if isinstance(value, list):
58+
inputs.extend([sub_name for sub_name, _ in value])
59+
inputs.append(name)
60+
return inputs
61+
62+
def _get_output_names(self):
63+
outputs = []
64+
for var_name, var in self.outputs.iteritems():
65+
if isinstance(var, list):
66+
for sub_var_name, sub_var in var:
67+
outputs.append(sub_var_name)
68+
else:
69+
outputs.append(var_name)
70+
if len(outputs) == 0:
71+
for out_name, out_dup in Operator.get_op_outputs(self.op_type):
72+
outputs.append(str(out_name))
73+
return outputs
74+
75+
def check_output_stability(self, atol=1e-8):
76+
places = self._get_places()
77+
if len(places) < 2:
78+
return
79+
cpu_outs, fetch_list = self._calc_output(places[0])
80+
gpu_outs, _ = self._calc_output(places[1])
81+
self._assert_cpu_gpu_same(cpu_outs, gpu_outs, fetch_list, atol)
82+
83+
def timeit_output_with_place(self, place, iters):
84+
return self.timeit_function(self.calc_output, iters, place)
85+
86+
def timeit_output(self, iters=100):
87+
places = self._get_places()
88+
elapses = []
89+
for place in places:
90+
elapses.append(self.timeit_output_with_place(place, iters))
91+
for place, elapse in zip(places, elapses):
92+
print("One pass of ({2}_op) at {0} cost {1}".format(
93+
str(place), elapse, self.op_type))
94+
95+
def timeit_grad_with_place(self, place, iters=100):
96+
inputs_to_check = self._get_input_names()
97+
output_names = self._get_output_names()
98+
return self.timeit_function(
99+
self._get_gradient,
100+
iters,
101+
inputs_to_check,
102+
place,
103+
output_names,
104+
no_grad_set=None)
105+
106+
def timeit_grad(self, iters=100):
107+
places = self._get_places()
108+
elapses = []
109+
for place in places:
110+
elapses.append(self.timeit_grad_with_place(place, iters))
111+
for place, elapse in zip(places, elapses):
112+
print("One pass of ({2}_grad_op) at {0} cost {1}".format(
113+
str(place), elapse, self.op_type))
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import unittest
16+
import numpy as np
17+
18+
import paddle.fluid as fluid
19+
from benchmark import BenchmarkSuite
20+
from op_test import OpTest
21+
22+
# This is a demo op test case for operator benchmarking and high resolution number stability alignment.
23+
24+
25+
class TestSumOp(BenchmarkSuite):
26+
def setUp(self):
27+
self.op_type = "sum"
28+
self.customize_testcase()
29+
self.customize_fetch_list()
30+
31+
def customize_fetch_list(self):
32+
"""
33+
customize fetch list, configure the wanted variables.
34+
>>> self.fetch_list = ["Out"]
35+
"""
36+
self.fetch_list = ["Out"]
37+
# pass
38+
39+
def customize_testcase(self):
40+
# a test case
41+
x0 = np.random.random((300, 400)).astype('float32')
42+
x1 = np.random.random((300, 400)).astype('float32')
43+
x2 = np.random.random((300, 400)).astype('float32')
44+
45+
# NOTE: if the output is empty, then it will autofilled by benchmarkSuite.
46+
# only the output dtype is used, the shape, lod and data is computed from input.
47+
self.inputs = {"X": [("x0", x0), ("x1", x1), ("x2", x2)]}
48+
self.outputs = {"Out": x0 + x1 + x2}
49+
50+
def test_check_output(self):
51+
"""
52+
compare the output with customized output. In this case,
53+
you should set the correct output by hands.
54+
>>> self.outputs = {"Out": x0 + x1 + x2}
55+
"""
56+
self.check_output(atol=1e-8)
57+
58+
def test_output_stability(self):
59+
# compare the cpu gpu output in high resolution.
60+
self.check_output_stability()
61+
62+
def test_timeit_output(self):
63+
"""
64+
perf the op, time cost will be averged in iters.
65+
output example
66+
>>> One pass of (sum_op) at CPUPlace cost 0.000461330413818
67+
>>> One pass of (sum_op) at CUDAPlace(0) cost 0.000556070804596
68+
"""
69+
self.timeit_output(iters=100)
70+
71+
def test_timeit_grad(self):
72+
"""
73+
perf the op gradient, time cost will be averged in iters.
74+
output example
75+
>>> One pass of (sum_grad_op) at CPUPlace cost 0.00279935121536
76+
>>> One pass of (sum_grad_op) at CUDAPlace(0) cost 0.00500632047653
77+
"""
78+
self.timeit_grad(iters=100)
79+
80+
81+
if __name__ == "__main__":
82+
unittest.main()

0 commit comments

Comments
 (0)