Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
P
pytensor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
testgroup
pytensor
Commits
6c0f23db
提交
6c0f23db
authored
7月 11, 2017
作者:
notoraptor
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Update tests script.
Add a script to quickly run specific tests.
上级
ef9095f9
隐藏空白字符变更
内嵌
并排
正在显示
3 个修改的文件
包含
567 行增加
和
346 行删除
+567
-346
cudnn_defs.py
theano/gpuarray/cudnn_defs.py
+20
-13
check_dnn_conv.py
theano/gpuarray/tests/check_dnn_conv.py
+400
-333
run_dnn_conv.py
theano/gpuarray/tests/run_dnn_conv.py
+147
-0
没有找到文件。
theano/gpuarray/cudnn_defs.py
浏览文件 @
6c0f23db
...
...
@@ -122,27 +122,34 @@ class CuDNNV51(object):
# empty list of enum to don't crash with cudnn 5
cudnnReduceTensorOp_t
=
CEnumType
()
def
get_supported_dtype_configs
(
self
):
def
get_supported_dtype_configs
(
self
,
check_runtime
=
None
):
"""
Return the tuple of data type configurations supported by this version of cuDNN.
This is currently convenient for both cuDNN V5.1 and V6, as Theano does not
yet support new data types (like INT8, INT8x4, etc.).
``check_runtime`` may be a function that tests if a data type configuration is supported.::
is_supported = check_runtime(dtype, precision)
.. warning::
From documentation for cudnnConvolutionForward (for both v5.1 and v6):
.. code-block::
TRUE_HALF_CONFIG is only supported on architectures with true fp16 support
(compute capability 5.3 and 6.0)
This seems to be a general remark about f16 support (not only for FWD).
It can be checked at runtime only.
"""
return
(
TRUE_HALF_CONFIG
,
PSEUDO_HALF_CONFIG
,
FLOAT_CONFIG
,
DOUBLE_CONFIG
)
def
get_fwd_dtype_configs
(
self
,
check_runtime
=
None
):
# NB: "TRUE_HALF_CONFIG is only supported on architectures with true fp16 support
# (compute capability 5.3 and 6.0)". Can be checked at runtime only.
if
check_runtime
is
None
or
check_runtime
(
*
TRUE_HALF_CONFIG
):
return
self
.
get_supported_dtype_configs
(
)
return
(
TRUE_HALF_CONFIG
,
PSEUDO_HALF_CONFIG
,
FLOAT_CONFIG
,
DOUBLE_CONFIG
)
return
(
PSEUDO_HALF_CONFIG
,
FLOAT_CONFIG
,
DOUBLE_CONFIG
)
def
get_bwd_filter_dtype_configs
(
self
,
check_runtime
=
None
):
return
self
.
get_supported_dtype_configs
()
def
get_bwd_data_dtype_configs
(
self
,
check_runtime
=
None
):
return
self
.
get_supported_dtype_configs
()
def
fwd_algo_supports_dtype_config
(
self
,
algo
,
dtype
,
precision
,
ndim
):
algorithms
=
self
.
cudnnConvolutionFwdAlgo_t
algo
=
algorithms
.
fromalias
(
algo
)
...
...
@@ -209,7 +216,7 @@ class CuDNNV51(object):
if
ndim
==
3
:
return
not
is_true_half_config
(
dtype
,
precision
)
if
algo
==
algorithms
.
CUDNN_CONVOLUTION_BWD_DATA_ALGO_WINOGRAD
:
return
ndim
==
2
and
is_pseudo_half_config
(
dtype
,
precision
)
or
is_float_config
(
dtype
,
precision
)
return
ndim
==
2
and
(
is_pseudo_half_config
(
dtype
,
precision
)
or
is_float_config
(
dtype
,
precision
)
)
if
algo
==
algorithms
.
CUDNN_CONVOLUTION_BWD_DATA_ALGO_WINOGRAD_NONFUSED
:
# NB: "If wDesc 's filter (height, width) is (5,5), data type config TRUE_HALF_CONFIG is not supported".
# We could not check it before being in C code.
...
...
theano/gpuarray/tests/check_dnn.py
→
theano/gpuarray/tests/check_dnn
_conv
.py
浏览文件 @
6c0f23db
#!/usr/bin/env python
# Without args, this script executes all its tests like `nosetests -vs`
# python check_dnn
.py # nosetests mode.
# python check_dnn
_conv.py
# You can pass args for nosetests as long as your first arg is not in `help, infos, fwd, bwd-filter, bwd-data`.
# python check_dnn.py -xvs # nosetests: verbose mode, capture output, exit at first error.
# If there is only one arg `infos`, this script prints some infos about
# supported algorithms and data type configurations for current GPU and cuDNN version.
# python check_dnn_conv.py infos
# Else, this script uses its own args and can be used to run a specific test case.
# python check_dnn.py help # Print help for script mode.
# python check_dnn.py infos # Print infos about algorithms and number of test cases.
# python check_dnn.py {fwd|bwd-filter|bwd-data} {2d|3d} -a <algo> -i <inputShape> -f <filterShape> ...
# If there is only one arg `list`, this script prints all tests without running them.
# python check_dnn_conv.py list
# Else, any arg will be directly passed to nosetests.
# python check_dnn_conv.py -xvs # nosetests: verbose mode, capture output, exit at first error.
from
__future__
import
absolute_import
,
print_function
,
division
import
argparse
import
sys
from
itertools
import
product
,
chain
from
itertools
import
product
import
nose
import
numpy
as
np
...
...
@@ -23,6 +24,7 @@ from nose.plugins.skip import SkipTest
import
theano
import
theano.tests.unittest_tools
as
utt
from
theano.compat
import
ifilter
from
theano.compile.ops
import
shape_i_op
from
theano.configdefaults
import
SUPPORTED_DNN_CONV_ALGO_RUNTIME
from
theano.gpuarray
import
cudnn_defs
...
...
@@ -32,28 +34,114 @@ from theano.gpuarray.tests.config import mode_with_gpu, ref_cast
from
theano.tensor.nnet.abstract_conv
import
get_conv_output_shape
,
assert_conv_shape
from
theano.tensor.opt
import
Assert
cudnn
=
cudnn_defs
.
get_definitions
(
version
(
raises
=
False
))
# We provide a special implementation of dnn_conv, dnn_gradweight and dnn_gradinput
# that support alpha, beta and out as parameters.
def
ifilter
(
function
,
sequence
):
# For compatibility with Python 3.
return
(
element
for
element
in
sequence
if
function
(
element
)
)
def
dnn_conv
(
img
,
kerns
,
alpha
=
1
,
beta
=
0
,
out
=
None
,
border_mode
=
'valid'
,
subsample
=
(
1
,
1
),
dilation
=
(
1
,
1
),
conv_mode
=
'conv'
,
algo
=
None
,
precision
=
None
):
ctx_name
=
infer_context_name
(
img
,
kerns
)
img
=
gpu_contiguous
(
as_gpuarray_variable
(
img
,
ctx_name
))
kerns
=
gpu_contiguous
(
as_gpuarray_variable
(
kerns
,
ctx_name
))
class
DnnCase
:
"""
Help class to generate special test cases quickly.
precision
=
get_precision
(
precision
,
[
img
,
kerns
])
desc
=
GpuDnnConvDesc
(
border_mode
=
border_mode
,
subsample
=
subsample
,
dilation
=
dilation
,
conv_mode
=
conv_mode
,
precision
=
precision
)(
kerns
.
shape
)
desc_op
=
desc
.
owner
.
op
# We can use Shape_i and bypass the infer_shape here as this is on
# the input of node and it will always be present.
ishape
=
[
shape_i_op
(
i
)(
img
)
for
i
in
range
(
img
.
ndim
)]
kshape
=
[
shape_i_op
(
i
)(
kerns
)
for
i
in
range
(
kerns
.
ndim
)]
out_shp
=
get_conv_output_shape
(
ishape
,
kshape
,
desc_op
.
border_mode
,
desc_op
.
subsample
,
filter_dilation
=
dilation
)
out_shp
=
assert_conv_shape
(
out_shp
)
if
beta
==
0
:
real_out
=
GpuAllocEmpty
(
dtype
=
img
.
dtype
,
context_name
=
ctx_name
)(
*
out_shp
)
else
:
assert
out
is
not
None
out
=
gpu_contiguous
(
as_gpuarray_variable
(
out
,
ctx_name
))
check
=
Assert
(
'GpuDnnConv: qiven output (for beta not null) does not have expected shape'
)
real_out
=
check
(
out
,
theano
.
tensor
.
all
(
theano
.
tensor
.
eq
(
out
.
shape
,
out_shp
)))
return
GpuDnnConv
(
algo
=
algo
)(
img
,
kerns
,
real_out
,
desc
,
alpha
,
beta
)
def
dnn_gradweight
(
img
,
topgrad
,
kerns_shp
,
alpha
=
1
,
beta
=
0
,
out
=
None
,
border_mode
=
'valid'
,
subsample
=
(
1
,
1
),
dilation
=
(
1
,
1
),
conv_mode
=
'conv'
,
algo
=
None
,
precision
=
None
):
ctx_name
=
infer_context_name
(
img
,
topgrad
)
img
=
gpu_contiguous
(
as_gpuarray_variable
(
img
,
ctx_name
))
topgrad
=
gpu_contiguous
(
as_gpuarray_variable
(
topgrad
,
ctx_name
))
kerns_shp
=
theano
.
tensor
.
as_tensor_variable
(
kerns_shp
)
precision
=
get_precision
(
precision
,
[
img
,
topgrad
])
desc
=
GpuDnnConvDesc
(
border_mode
=
border_mode
,
subsample
=
subsample
,
dilation
=
dilation
,
conv_mode
=
conv_mode
,
precision
=
precision
)(
kerns_shp
)
if
beta
==
0
:
real_out
=
GpuAllocEmpty
(
dtype
=
img
.
dtype
,
context_name
=
ctx_name
)(
*
kerns_shp
)
else
:
assert
out
is
not
None
out
=
gpu_contiguous
(
as_gpuarray_variable
(
out
,
ctx_name
))
check
=
Assert
(
'GpuDnnConvGradW: qiven output (for beta not null) does not have expected shape'
)
real_out
=
check
(
out
,
theano
.
tensor
.
all
(
theano
.
tensor
.
eq
(
out
.
shape
,
kerns_shp
)))
return
GpuDnnConvGradW
(
algo
=
algo
)(
img
,
topgrad
,
real_out
,
desc
,
alpha
,
beta
)
def
dnn_gradinput
(
kerns
,
topgrad
,
img_shp
,
alpha
=
1
,
beta
=
0
,
out
=
None
,
border_mode
=
'valid'
,
subsample
=
(
1
,
1
),
dilation
=
(
1
,
1
),
conv_mode
=
'conv'
,
algo
=
None
,
precision
=
None
):
ctx_name
=
infer_context_name
(
kerns
,
topgrad
)
kerns
=
gpu_contiguous
(
as_gpuarray_variable
(
kerns
,
ctx_name
))
topgrad
=
gpu_contiguous
(
as_gpuarray_variable
(
topgrad
,
ctx_name
))
img_shp
=
theano
.
tensor
.
as_tensor_variable
(
img_shp
)
precision
=
get_precision
(
precision
,
[
kerns
,
topgrad
])
desc
=
GpuDnnConvDesc
(
border_mode
=
border_mode
,
subsample
=
subsample
,
dilation
=
dilation
,
conv_mode
=
conv_mode
,
precision
=
precision
)(
kerns
.
shape
)
if
beta
==
0
:
real_out
=
GpuAllocEmpty
(
dtype
=
kerns
.
dtype
,
context_name
=
ctx_name
)(
*
img_shp
)
else
:
assert
out
is
not
None
out
=
gpu_contiguous
(
as_gpuarray_variable
(
out
,
ctx_name
))
check
=
Assert
(
'GpuDnnConvGradI: qiven output (for beta not null) does not have expected shape'
)
real_out
=
check
(
out
,
theano
.
tensor
.
all
(
theano
.
tensor
.
eq
(
out
.
shape
,
img_shp
)))
return
GpuDnnConvGradI
(
algo
=
algo
)(
kerns
,
topgrad
,
real_out
,
desc
,
alpha
,
beta
)
def
check_dtype_config_support
(
dtype
,
precision
):
# We use FWD 2D to check it.
# Based on documentation, algo small (CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_PRECOMP_GEMM)
# should support all configurations, for both v5.1 and v6.
inputs
=
theano
.
shared
(
np
.
zeros
((
1
,
1
,
2
,
2
),
dtype
=
dtype
))
filters
=
theano
.
shared
(
np
.
zeros
((
1
,
1
,
2
,
2
),
dtype
=
dtype
))
conv
=
dnn_conv
(
inputs
,
filters
,
precision
=
precision
,
algo
=
'small'
)
f
=
theano
.
function
([],
conv
,
mode
=
mode_with_gpu
)
try
:
f
()
except
RuntimeError
as
e
:
assert
'CUDNN_STATUS_ARCH_MISMATCH'
in
e
.
message
return
False
return
True
cudnn
=
cudnn_defs
.
get_definitions
(
version
(
raises
=
False
))
class
ConvCase
:
"""
Help class to describe a special test case quickly.
This handles only 2D and 3D cases.
"""
def
__init__
(
self
,
type
,
inputs_shape
,
filters_shape
,
FWD
,
GRADINPUT
,
GRADWEIGHT
=
0
,
1
,
2
def
__init__
(
self
,
type
,
inputs_shape
,
filters_shape
,
algo
=
None
,
dtype
=
None
,
precision
=
None
,
subsample
=
None
,
dilation
=
None
,
border_mode
=
'valid'
,
conv_mode
=
'conv'
,
alpha
=
1
,
beta
=
0
,
should_fail
=
False
):
assert
type
in
(
'fwd'
,
'bwd-filter'
,
'bwd-data'
)
assert
len
(
inputs_shape
)
==
len
(
filters_shape
)
>
2
assert
type
in
(
ConvCase
.
FWD
,
ConvCase
.
GRADINPUT
,
ConvCase
.
GRADWEIGHT
)
assert
len
(
inputs_shape
)
==
len
(
filters_shape
)
in
(
4
,
5
)
ndim
=
len
(
inputs_shape
)
-
2
if
dtype
is
None
:
dtype
=
theano
.
config
.
floatX
...
...
@@ -66,7 +154,8 @@ class DnnCase:
assert
dtype
in
(
'float16'
,
'float32'
,
'float64'
)
assert
precision
in
(
'float16'
,
'float32'
,
'float64'
)
assert
len
(
subsample
)
==
len
(
dilation
)
==
ndim
assert
border_mode
in
(
'valid'
,
'full'
,
'half'
)
or
len
(
border_mode
)
==
ndim
assert
(
border_mode
in
(
'valid'
,
'full'
,
'half'
)
or
(
isinstance
(
border_mode
,
(
list
,
tuple
))
and
len
(
border_mode
)
==
ndim
))
assert
conv_mode
in
(
'conv'
,
'cross'
)
assert
alpha
!=
0
...
...
@@ -86,13 +175,13 @@ class DnnCase:
self
.
should_fail
=
bool
(
should_fail
)
def
is_fwd
(
self
):
return
self
.
type
==
'fwd'
return
self
.
type
==
ConvCase
.
FWD
def
is_bwd_filter
(
self
):
return
self
.
type
==
'bwd-filter'
return
self
.
type
==
ConvCase
.
GRADWEIGHT
def
is_bwd_data
(
self
):
return
self
.
type
==
'bwd-data'
return
self
.
type
==
ConvCase
.
GRADINPUT
def
get_case
(
self
):
return
(
self
.
algo
,
self
.
dtype
,
self
.
precision
,
...
...
@@ -102,29 +191,30 @@ class DnnCase:
@staticmethod
def
fwd
(
*
args
,
**
kwargs
):
return
DnnCase
(
'fwd'
,
*
args
,
**
kwargs
)
return
ConvCase
(
ConvCase
.
FWD
,
*
args
,
**
kwargs
)
@staticmethod
def
bwd_filter
(
*
args
,
**
kwargs
):
return
DnnCase
(
'bwd-filter'
,
*
args
,
**
kwargs
)
return
ConvCase
(
ConvCase
.
GRADWEIGHT
,
*
args
,
**
kwargs
)
@staticmethod
def
bwd_data
(
*
args
,
**
kwargs
):
return
DnnCase
(
'bwd-data'
,
*
args
,
**
kwargs
)
return
ConvCase
(
ConvCase
.
GRADINPUT
,
*
args
,
**
kwargs
)
class
Dnn
CaseGenerator
:
class
Conv
CaseGenerator
:
"""
Main class used to generate test cases.
This handles only 2D and 3D cases.
"""
def
as_tuple_of_tuples
(
self
,
iterable
):
def
_
as_tuple_of_tuples
(
self
,
iterable
):
return
tuple
(
tuple
(
sequence
)
for
sequence
in
iterable
)
def
__init__
(
self
,
ndim
=
2
,
alpha
=
2
,
beta
=-
3
,
batch_size
=
2
,
input_channels
=
3
,
inputs_sizes
=
None
,
output_channels
=
2
,
filters_sizes
=
None
,
borders
=
None
,
subsamples
=
None
,
dilations
=
None
):
def
__init__
(
self
,
ndim
,
alpha
=
2
,
beta
=-
3
,
batch_size
=
2
,
input_channels
=
3
,
inputs_sizes
=
None
,
output_channels
=
2
,
filters_sizes
=
None
,
subsamples
=
None
,
dilations
=
None
,
borders
=
None
,
with_border_valid
=
True
,
with_border_half
=
True
,
with_border_full
=
True
):
self
.
ndim
=
int
(
ndim
)
self
.
alpha
=
float
(
alpha
)
self
.
beta
=
float
(
beta
)
...
...
@@ -132,12 +222,19 @@ class DnnCaseGenerator:
self
.
input_channels
=
int
(
input_channels
)
self
.
output_channels
=
int
(
output_channels
)
assert
self
.
ndim
>=
2
assert
self
.
ndim
in
(
2
,
3
)
assert
self
.
alpha
!=
0
assert
self
.
batch_size
>
0
assert
self
.
input_channels
>
0
assert
self
.
output_channels
>
0
# NB: it is a bit arbitrary to choose default values for inputs sizes and filters sizes.
# Here, we just put some values that may generate errors in some cases, but that should be OK for other cases.
# For instance, input size 300 is > 256, that is a limit for certain algorithms (cf. documentation).
# Filter size 40 is > 32 and > 16, that are limits for certain algorithms (cf. documentation).
# We should either manually specify sizes, or give an appropriate filter to this generator
# (see `self.get_cases()`) before testing values.
if
inputs_sizes
is
None
:
inputs_sizes
=
((
5
,)
*
self
.
ndim
,
(
300
,
5
)
+
(
2
,)
*
(
self
.
ndim
-
2
))
...
...
@@ -158,30 +255,41 @@ class DnnCaseGenerator:
for
sequence_list
in
(
inputs_sizes
,
filters_sizes
,
borders
,
subsamples
,
dilations
):
assert
(
isinstance
(
sequence_list
,
(
tuple
,
list
))
and
all
(
isinstance
(
sequence
,
(
tuple
,
list
))
and
len
(
sequence
)
==
self
.
ndim
for
sequence
in
sequence_list
)),
sequence_list
self
.
inputs_sizes
=
self
.
as_tuple_of_tuples
(
inputs_sizes
)
self
.
filters_sizes
=
self
.
as_tuple_of_tuples
(
filters_sizes
)
self
.
borders
=
self
.
as_tuple_of_tuples
(
borders
)
self
.
subsamples
=
self
.
as_tuple_of_tuples
(
subsamples
)
self
.
dilations
=
self
.
as_tuple_of_tuples
(
dilations
)
for
sequence
in
sequence_list
)),
(
self
.
ndim
,
sequence_list
)
self
.
auto_borders
=
tuple
()
if
with_border_valid
:
self
.
auto_borders
+=
(
'valid'
,)
if
with_border_half
:
self
.
auto_borders
+=
(
'half'
,)
if
with_border_full
:
self
.
auto_borders
+=
(
'full'
,)
self
.
inputs_sizes
=
self
.
_as_tuple_of_tuples
(
inputs_sizes
)
self
.
filters_sizes
=
self
.
_as_tuple_of_tuples
(
filters_sizes
)
self
.
borders
=
self
.
_as_tuple_of_tuples
(
borders
)
self
.
subsamples
=
self
.
_as_tuple_of_tuples
(
subsamples
)
self
.
dilations
=
self
.
_as_tuple_of_tuples
(
dilations
)
@staticmethod
def
get_if_valid_conv_output_shape
(
case_tuple
):
# Filter function to keep only cases that produce valid convolution output shapes.
out_shp
=
get_conv_output_shape
(
case_tuple
[
0
],
# input shape
case_tuple
[
1
],
# filter shape
case_tuple
[
4
],
# border mode
case_tuple
[
2
],
# subsample
case_tuple
[
3
]
# dilation
)
case_tuple
[
3
])
# dilation
try
:
return
assert_conv_shape
(
out_shp
)
except
ValueError
:
return
False
def
get_cases
(
self
):
def
get_cases
(
self
,
filter
=
None
):
# Generate an iterator of tuples with format:
# (input shape, filter shape, subsample, dilation, border mode, convolution mode, alpha, beta)
# filter may be a callable that gets one tuple (with format specified above) and returns
# a boolean, so that tuple is kept only if filter(tuple) is True.
all_batch_sizes
=
(
self
.
batch_size
,)
all_input_channels
=
(
self
.
input_channels
,)
all_input_sizes
=
self
.
inputs_sizes
...
...
@@ -189,7 +297,7 @@ class DnnCaseGenerator:
all_filter_sizes
=
self
.
filters_sizes
all_subsamples
=
self
.
subsamples
all_dilations
=
self
.
dilations
all_border_modes
=
(
'valid'
,
'full'
,
'half'
)
+
self
.
borders
all_border_modes
=
self
.
auto_borders
+
self
.
borders
all_conv_modes
=
(
'conv'
,
'cross'
)
all_alphas
=
(
self
.
alpha
,)
all_betas
=
(
0
,)
if
self
.
beta
==
0
else
(
0
,
self
.
beta
)
...
...
@@ -198,122 +306,176 @@ class DnnCaseGenerator:
for
bs
in
all_batch_sizes
for
ic
in
all_input_channels
for
ins
in
all_input_sizes
)
all_filter_shapes
=
((
oc
,
ic
)
+
fis
for
oc
in
all_output_channels
for
ic
in
all_input_channels
for
fis
in
all_filter_sizes
)
return
ifilter
(
DnnCaseGenerator
.
get_if_valid_conv_output_shape
,
if
callable
(
filter
):
def
local_filter
(
case_tuple
):
return
ConvCaseGenerator
.
get_if_valid_conv_output_shape
(
case_tuple
)
and
filter
(
case_tuple
)
else
:
local_filter
=
ConvCaseGenerator
.
get_if_valid_conv_output_shape
return
ifilter
(
local_filter
,
product
(
all_input_shapes
,
all_filter_shapes
,
all_subsamples
,
all_dilations
,
all_border_modes
,
all_conv_modes
,
all_alphas
,
all_betas
))
# We provide a special implementation of dnn_conv, dnn_gradweight and dnn_gradinput
# that take algo, alpha, beta and out as parameters.
def
dnn_conv
(
img
,
kerns
,
alpha
=
1
,
beta
=
0
,
out
=
None
,
border_mode
=
'valid'
,
subsample
=
(
1
,
1
),
dilation
=
(
1
,
1
),
conv_mode
=
'conv'
,
algo
=
None
,
precision
=
None
):
# Establish dtype in which to perform the computation of the convolution
precision
=
get_precision
(
precision
,
[
img
,
kerns
])
ctx_name
=
infer_context_name
(
img
,
kerns
)
img
=
gpu_contiguous
(
img
)
kerns
=
gpu_contiguous
(
kerns
)
desc
=
GpuDnnConvDesc
(
border_mode
=
border_mode
,
subsample
=
subsample
,
dilation
=
dilation
,
conv_mode
=
conv_mode
,
precision
=
precision
)(
kerns
.
shape
)
desc_op
=
desc
.
owner
.
op
# We can use Shape_i and bypass the infer_shape here as this is on
# the input of node and it will always be present.
ishape
=
[
shape_i_op
(
i
)(
img
)
for
i
in
range
(
img
.
ndim
)]
kshape
=
[
shape_i_op
(
i
)(
kerns
)
for
i
in
range
(
kerns
.
ndim
)]
out_shp
=
get_conv_output_shape
(
ishape
,
kshape
,
desc_op
.
border_mode
,
desc_op
.
subsample
,
filter_dilation
=
dilation
)
out_shp
=
assert_conv_shape
(
out_shp
)
if
beta
==
0
:
real_out
=
GpuAllocEmpty
(
dtype
=
img
.
dtype
,
context_name
=
ctx_name
)(
*
out_shp
)
else
:
assert
out
is
not
None
out
=
as_gpuarray_variable
(
out
,
ctx_name
)
out
=
gpu_contiguous
(
out
)
check
=
Assert
(
'GpuDnnConv: qiven output (for beta not null) does not have expected shape'
)
real_out
=
check
(
out
,
theano
.
tensor
.
all
(
theano
.
tensor
.
eq
(
out
.
shape
,
out_shp
)))
return
GpuDnnConv
(
algo
=
algo
)(
img
,
kerns
,
real_out
,
desc
,
alpha
,
beta
)
def
dnn_gradweight
(
img
,
topgrad
,
kerns_shp
,
alpha
=
1
,
beta
=
0
,
out
=
None
,
border_mode
=
'valid'
,
subsample
=
(
1
,
1
),
dilation
=
(
1
,
1
),
conv_mode
=
'conv'
,
algo
=
None
,
precision
=
None
):
ctx_name
=
infer_context_name
(
img
,
topgrad
)
img
=
as_gpuarray_variable
(
img
,
ctx_name
)
topgrad
=
as_gpuarray_variable
(
topgrad
,
ctx_name
)
img
=
gpu_contiguous
(
img
)
topgrad
=
gpu_contiguous
(
topgrad
)
kerns_shp
=
theano
.
tensor
.
as_tensor_variable
(
kerns_shp
)
precision
=
get_precision
(
precision
,
[
img
,
topgrad
])
desc
=
GpuDnnConvDesc
(
border_mode
=
border_mode
,
subsample
=
subsample
,
dilation
=
dilation
,
conv_mode
=
conv_mode
,
precision
=
precision
)(
kerns_shp
)
if
beta
==
0
:
real_out
=
GpuAllocEmpty
(
dtype
=
img
.
dtype
,
context_name
=
ctx_name
)(
*
kerns_shp
)
else
:
assert
out
is
not
None
out
=
as_gpuarray_variable
(
out
,
ctx_name
)
out
=
gpu_contiguous
(
out
)
check
=
Assert
(
'GpuDnnConvGradW: qiven output (for beta not null) does not have expected shape'
)
real_out
=
check
(
out
,
theano
.
tensor
.
all
(
theano
.
tensor
.
eq
(
out
.
shape
,
kerns_shp
)))
return
GpuDnnConvGradW
(
algo
=
algo
)(
img
,
topgrad
,
real_out
,
desc
,
alpha
,
beta
)
def
dnn_gradinput
(
kerns
,
topgrad
,
img_shp
,
alpha
=
1
,
beta
=
0
,
out
=
None
,
border_mode
=
'valid'
,
subsample
=
(
1
,
1
),
dilation
=
(
1
,
1
),
conv_mode
=
'conv'
,
algo
=
None
,
precision
=
None
):
ctx_name
=
infer_context_name
(
kerns
,
topgrad
)
kerns
=
as_gpuarray_variable
(
kerns
,
ctx_name
)
topgrad
=
as_gpuarray_variable
(
topgrad
,
ctx_name
)
class
CuDNNV51ConvCaseGenerator
(
object
):
"""
Helper class to generate specific test cases for every algorithm supported by cuDNN V5.1.
Same class exists for cuDNN V6.0 (see below).
This should help avoid test cases that are intended to fail according to cuDNN documentations.
"""
NONE
=
'none'
FFT
=
'fft'
FFT_TILING
=
'fft_tiling'
WINOGRAD
=
'winograd'
WINOGRAD_NON_FUSED
=
'winograd_non_fused'
# Protected interface.
def
_dilations
(
self
,
ndim
):
return
[(
1
,)
*
ndim
]
def
_fwd_fft
(
self
,
ndim
):
inputs_sizes
=
[(
10
,)
*
ndim
,
(
248
,
5
)
+
(
2
,)
*
(
ndim
-
2
)]
filters_sizes
=
[
tuple
(
range
(
9
,
9
-
ndim
,
-
1
))]
subsamples
=
[(
1
,)
*
ndim
]
return
ConvCaseGenerator
(
ndim
=
ndim
,
inputs_sizes
=
inputs_sizes
,
filters_sizes
=
filters_sizes
,
subsamples
=
subsamples
,
dilations
=
self
.
_dilations
(
ndim
))
def
_fwd_fft_tiling
(
self
,
ndim
):
if
ndim
==
2
:
filters_sizes
=
[(
32
,
5
)]
if
ndim
==
3
:
filters_sizes
=
[(
16
,
5
,
5
)]
subsamples
=
[(
1
,)
*
ndim
]
return
ConvCaseGenerator
(
ndim
=
ndim
,
filters_sizes
=
filters_sizes
,
subsamples
=
subsamples
,
dilations
=
self
.
_dilations
(
ndim
))
def
_fwd_winograd
(
self
,
ndim
):
filters_sizes
=
[(
3
,)
*
ndim
]
subsamples
=
[(
1
,)
*
ndim
]
return
ConvCaseGenerator
(
ndim
=
ndim
,
filters_sizes
=
filters_sizes
,
subsamples
=
subsamples
,
dilations
=
self
.
_dilations
(
ndim
))
def
_fwd_winograd_non_fused
(
self
,
ndim
):
filters_sizes
=
[(
3
,)
*
ndim
]
if
not
check_dtype_config_support
(
'float16'
,
'float16'
):
filters_sizes
+=
[(
5
,)
*
ndim
]
subsamples
=
[(
1
,)
*
ndim
]
return
ConvCaseGenerator
(
ndim
=
ndim
,
filters_sizes
=
filters_sizes
,
subsamples
=
subsamples
,
dilations
=
self
.
_dilations
(
ndim
))
def
_gw_fft
(
self
,
ndim
):
return
self
.
_fwd_fft
(
ndim
)
def
_gw_winograd_non_fused
(
self
,
ndim
):
return
self
.
_fwd_winograd_non_fused
(
ndim
)
def
_gi_fft
(
self
,
ndim
):
return
self
.
_fwd_fft
(
ndim
)
def
_gi_fft_tiling
(
self
,
ndim
):
return
self
.
_fwd_fft_tiling
(
ndim
)
def
_gi_winograd
(
self
,
ndim
):
return
self
.
_fwd_winograd
(
ndim
)
def
_gi_winograd_non_fused
(
self
,
ndim
):
return
self
.
_fwd_winograd_non_fused
(
ndim
)
# Public interface.
def
fwd
(
self
,
algo
,
ndim
):
if
algo
==
self
.
FFT
:
return
self
.
_fwd_fft
(
ndim
)
if
algo
==
self
.
FFT_TILING
:
return
self
.
_fwd_fft_tiling
(
ndim
)
if
algo
==
self
.
WINOGRAD
:
return
self
.
_fwd_winograd
(
ndim
)
if
algo
==
self
.
WINOGRAD_NON_FUSED
:
return
self
.
_fwd_winograd_non_fused
(
ndim
)
return
ConvCaseGenerator
(
ndim
=
ndim
,
dilations
=
self
.
_dilations
(
ndim
))
def
gw
(
self
,
algo
,
ndim
):
if
algo
==
self
.
FFT
:
return
self
.
_gw_fft
(
ndim
)
if
algo
==
self
.
WINOGRAD_NON_FUSED
:
return
self
.
_gw_winograd_non_fused
(
ndim
)
return
ConvCaseGenerator
(
ndim
=
ndim
,
dilations
=
self
.
_dilations
(
ndim
))
def
gi
(
self
,
algo
,
ndim
):
if
algo
==
self
.
FFT
:
return
self
.
_gi_fft
(
ndim
)
if
algo
==
self
.
FFT_TILING
:
return
self
.
_gi_fft_tiling
(
ndim
)
if
algo
==
self
.
WINOGRAD
:
return
self
.
_gi_winograd
(
ndim
)
if
algo
==
self
.
WINOGRAD_NON_FUSED
:
return
self
.
_gi_winograd_non_fused
(
ndim
)
return
ConvCaseGenerator
(
ndim
=
ndim
,
dilations
=
self
.
_dilations
(
ndim
))
class
CuDNNV6ConvCaseGenerator
(
CuDNNV51ConvCaseGenerator
):
def
_fwd_none
(
self
,
ndim
):
# All dilations allowed.
return
ConvCaseGenerator
(
ndim
=
ndim
)
def
_fwd_fft_tiling
(
self
,
ndim
):
if
ndim
==
2
:
filters_sizes
=
[(
32
,
5
),
(
256
,
1
),
(
10
,
10
),
(
5
,
1
)]
subsamples
=
[(
1
,
1
)]
borders
=
[(
1
,
1
),
(
2
,
1
)]
return
ConvCaseGenerator
(
ndim
=
ndim
,
filters_sizes
=
filters_sizes
,
subsamples
=
subsamples
,
borders
=
borders
,
dilations
=
self
.
_dilations
(
ndim
))
if
ndim
==
3
:
return
super
(
CuDNNV6ConvCaseGenerator
,
self
)
.
_fwd_fft_tiling
(
ndim
)
kerns
=
gpu_contiguous
(
kerns
)
topgrad
=
gpu_contiguous
(
topgrad
)
def
_gw_none
(
self
,
ndim
):
return
self
.
_fwd_none
(
ndim
)
img_shp
=
theano
.
tensor
.
as_tensor_variable
(
img_shp
)
precision
=
get_precision
(
precision
,
[
kerns
,
topgrad
])
def
_gw_fft_tiling
(
self
,
ndim
):
inputs_sizes
=
[(
256
,
1
),
(
20
,
1
)]
filters_sizes
=
[(
3
,
1
),
(
10
,
1
)]
subsamples
=
[(
1
,)
*
ndim
]
borders
=
[(
1
,
1
),
(
2
,
1
)]
return
ConvCaseGenerator
(
ndim
=
ndim
,
inputs_sizes
=
inputs_sizes
,
filters_sizes
=
filters_sizes
,
subsamples
=
subsamples
,
borders
=
borders
,
dilations
=
self
.
_dilations
(
ndim
))
desc
=
GpuDnnConvDesc
(
border_mode
=
border_mode
,
subsample
=
subsample
,
dilation
=
dilation
,
conv_mode
=
conv_mode
,
precision
=
precision
)(
kerns
.
shape
)
if
beta
==
0
:
real_out
=
GpuAllocEmpty
(
dtype
=
kerns
.
dtype
,
context_name
=
ctx_name
)(
*
img_shp
)
else
:
assert
out
is
not
None
out
=
as_gpuarray_variable
(
out
,
ctx_name
)
out
=
gpu_contiguous
(
out
)
check
=
Assert
(
'GpuDnnConvGradI: qiven output (for beta not null) does not have expected shape'
)
real_out
=
check
(
out
,
theano
.
tensor
.
all
(
theano
.
tensor
.
eq
(
out
.
shape
,
img_shp
)))
def
_gi_none
(
self
,
ndim
):
return
self
.
_fwd_none
(
ndim
)
return
GpuDnnConvGradI
(
algo
=
algo
)(
kerns
,
topgrad
,
real_out
,
desc
,
alpha
,
beta
)
def
fwd
(
self
,
algo
,
ndim
):
if
algo
==
self
.
NONE
:
return
self
.
_fwd_none
(
ndim
)
return
super
(
CuDNNV6ConvCaseGenerator
,
self
)
.
fwd
(
algo
,
ndim
)
def
gw
(
self
,
algo
,
ndim
):
if
algo
==
self
.
NONE
:
return
self
.
_gw_none
(
ndim
)
return
super
(
CuDNNV6ConvCaseGenerator
,
self
)
.
gw
(
algo
,
ndim
)
def
check_fwd_dtype_config_support
(
dtype
,
precision
):
inputs_shape
=
(
1
,
1
,
3
,
3
)
filters_shape
=
(
1
,
1
,
2
,
2
)
inputs
=
np
.
zeros
(
inputs_shape
,
dtype
=
dtype
)
filters
=
np
.
zeros
(
filters_shape
,
dtype
=
dtype
)
inputs
=
theano
.
shared
(
inputs
)
filters
=
theano
.
shared
(
filters
)
conv
=
dnn_conv
(
inputs
,
filters
,
precision
=
precision
)
f
=
theano
.
function
([],
conv
,
mode
=
mode_with_gpu
)
try
:
f
()
except
RuntimeError
as
e
:
assert
'CUDNN_STATUS_ARCH_MISMATCH'
in
e
.
message
return
False
return
True
def
gi
(
self
,
algo
,
ndim
):
if
algo
==
self
.
NONE
:
return
self
.
_gi_none
(
ndim
)
return
super
(
CuDNNV6ConvCaseGenerator
,
self
)
.
gi
(
algo
,
ndim
)
def
test_fwd_true_half_config_support
():
# For cuDNN V5.1 and V6.0:
# "TRUE_HALF_CONFIG is only supported on architectures with true fp16 support (compute capability 5.3 and 6.0)"
if
not
check_fwd_dtype_config_support
(
'float16'
,
'float16'
):
raise
SkipTest
(
'FWD: TRUE_HALF_CONFIG not supported on this GPU.'
)
cudnn_conv_case_generator
=
CuDNNV51ConvCaseGenerator
()
if
cudnn
.
version
<
6
else
CuDNNV6ConvCaseGenerator
()
class
BaseTestDnnConv
(
object
):
...
...
@@ -334,18 +496,21 @@ class BaseTestDnnConv(object):
cpu_gradinput_class
=
None
cpu_gradweight_class
=
None
special_cases
=
[]
# List of
Dnn
Cases.
special_cases
=
[]
# List of
special Conv
Cases.
# Utility methods.
def
get_cases
(
self
):
def
__init__
(
self
):
utt
.
seed_rng
(
1234
)
self
.
dtype_configs
=
cudnn
.
get_supported_dtype_configs
(
check_dtype_config_support
)
def
get_cases
(
self
,
filter
=
None
):
# Return an iterable of test cases. Each test case is a tuple (or list) with following syntax:
# (input shape, filter shape, subsample, dilation, border mode, convolution mode, alpha, beta)
generator
=
DnnCaseGenerator
(
ndim
=
self
.
ndim
)
return
generator
.
get_cases
()
return
ConvCaseGenerator
(
ndim
=
self
.
ndim
)
.
get_cases
(
filter
)
def
array_like_conv_output
(
self
,
inputs_shape
,
filters_shape
,
border_mode
,
subsample
,
dilation
,
dtype
):
# Return a
n
random array with inferred convolution output shape.
# Return a random array with inferred convolution output shape.
out_shp
=
get_conv_output_shape
(
inputs_shape
,
filters_shape
,
border_mode
,
subsample
,
dilation
)
out_shp
=
assert_conv_shape
(
out_shp
)
return
np
.
random
.
random
(
out_shp
)
.
astype
(
dtype
)
...
...
@@ -375,7 +540,7 @@ class BaseTestDnnConv(object):
f
=
theano
.
function
([],
conv
,
mode
=
mode_with_gpu
)
# If conv_mode is 'conv' the reference implementation should use
# filters f
il
pped according to the width, height and time axis
# filters f
li
pped according to the width, height and time axis
if
conv_mode
==
'conv'
:
if
inputs
.
ndim
==
5
:
flipped_filters
=
filters
[:,
:,
::
-
1
,
::
-
1
,
::
-
1
]
...
...
@@ -392,10 +557,9 @@ class BaseTestDnnConv(object):
# Compare the results of the two implementations
res_ref
=
f_ref
()
res
=
f
(
)
res
=
np
.
asarray
(
f
()
)
if
algo
in
cudnn
.
deterministic_fwd_algorithms
:
res2
=
f
()
utt
.
assert_allclose
(
res
,
res2
)
utt
.
assert_allclose
(
res
,
np
.
asarray
(
f
()))
# Raise tolerance for float16
rtol
=
6e-2
if
dtype
==
'float16'
else
None
...
...
@@ -430,7 +594,7 @@ class BaseTestDnnConv(object):
f
=
theano
.
function
([],
grad_i
,
mode
=
mode_with_gpu
)
# If conv_mode is 'conv' the reference implementation should use
# filters f
il
pped according to the width, height and time axis
# filters f
li
pped according to the width, height and time axis
if
conv_mode
==
'conv'
:
if
filters
.
ndim
==
5
:
flipped_filters
=
filters
[:,
:,
::
-
1
,
::
-
1
,
::
-
1
]
...
...
@@ -448,10 +612,9 @@ class BaseTestDnnConv(object):
# Compare the results of the two implementations
res_ref
=
f_ref
()
res
=
f
(
)
res
=
np
.
asarray
(
f
()
)
if
algo
in
cudnn
.
deterministic_bwd_data_algorithms
:
res2
=
f
()
utt
.
assert_allclose
(
res
,
res2
)
utt
.
assert_allclose
(
res
,
np
.
asarray
(
f
()))
# Raise tolerance for float16
rtol
=
5e-2
if
dtype
==
'float16'
else
None
...
...
@@ -499,10 +662,9 @@ class BaseTestDnnConv(object):
# Compare the results of the two implementations
res_ref
=
f_ref
()
res
=
f
(
)
res
=
np
.
asarray
(
f
()
)
if
algo
in
cudnn
.
deterministic_bwd_filter_algorithms
:
res2
=
f
()
utt
.
assert_allclose
(
res
,
res2
)
utt
.
assert_allclose
(
res
,
np
.
asarray
(
f
()))
# Raise tolerance for float16
rtol
=
5e-2
if
dtype
==
'float16'
else
None
...
...
@@ -511,27 +673,6 @@ class BaseTestDnnConv(object):
else
:
utt
.
assert_allclose
(
alpha
*
res_ref
+
beta
*
filters_val
,
res
,
rtol
=
rtol
)
def
get_expected_tcount
(
self
):
"""
Utility function to get expected test count
without actually run nosetests.
"""
len_cases
=
sum
(
1
for
case
in
self
.
get_cases
())
count_contexts
=
0
for
dtype
,
precision
in
cudnn
.
get_fwd_dtype_configs
(
check_runtime
=
check_fwd_dtype_config_support
):
algos
=
(
algo
for
algo
in
self
.
fwd_algorithms
if
cudnn
.
fwd_algo_supports_dtype_config
(
algo
,
dtype
,
precision
,
self
.
ndim
))
count_contexts
+=
sum
(
1
for
algo
in
algos
)
+
len
(
SUPPORTED_DNN_CONV_ALGO_RUNTIME
)
for
dtype
,
precision
in
cudnn
.
get_bwd_data_dtype_configs
():
algos
=
(
algo
for
algo
in
self
.
bwd_data_algorithms
if
cudnn
.
bwd_data_algo_supports_dtype_config
(
algo
,
dtype
,
precision
,
self
.
ndim
))
count_contexts
+=
sum
(
1
for
algo
in
algos
)
+
len
(
SUPPORTED_DNN_CONV_ALGO_RUNTIME
)
for
dtype
,
precision
in
cudnn
.
get_bwd_filter_dtype_configs
():
algos
=
(
algo
for
algo
in
self
.
bwd_filter_algorithms
if
cudnn
.
bwd_filter_algo_supports_dtype_config
(
algo
,
dtype
,
precision
,
self
.
ndim
))
count_contexts
+=
sum
(
1
for
algo
in
algos
)
+
len
(
SUPPORTED_DNN_CONV_ALGO_RUNTIME
)
return
len
(
self
.
special_cases
)
+
len_cases
*
count_contexts
def
should_fail
(
self
,
callable
,
*
args
):
try
:
print
(
'(should fail)'
,
file
=
sys
.
stderr
,
end
=
' '
)
...
...
@@ -541,13 +682,25 @@ class BaseTestDnnConv(object):
else
:
raise
AssertionError
(
'Should fail'
,
callable
.
__name__
,
*
args
)
def
get_expected_tcount
(
self
):
"""
Utility function to get expected test count
without actually run nosetests.
"""
return
(
sum
(
1
for
t
in
self
.
test_fwd
())
+
sum
(
1
for
t
in
self
.
test_gradweight
())
+
sum
(
1
for
t
in
self
.
test_gradinput
()))
# Iterable test methods.
def
test_fwd
(
self
):
for
dtype
,
precision
in
cudnn
.
get_fwd_dtype_configs
(
check_runtime
=
check_fwd_dtype_config_support
)
:
for
dtype
,
precision
in
self
.
dtype_configs
:
algos
=
(
algo
for
algo
in
self
.
fwd_algorithms
if
cudnn
.
fwd_algo_supports_dtype_config
(
algo
,
dtype
,
precision
,
self
.
ndim
))
for
algo
in
chain
(
algos
,
SUPPORTED_DNN_CONV_ALGO_RUNTIME
):
for
algo
in
algos
:
for
parameters
in
cudnn_conv_case_generator
.
fwd
(
algo
,
self
.
ndim
)
.
get_cases
():
yield
(
self
.
run_conv_fwd
,
algo
,
dtype
,
precision
,
parameters
)
for
algo
in
SUPPORTED_DNN_CONV_ALGO_RUNTIME
:
for
parameters
in
self
.
get_cases
():
yield
(
self
.
run_conv_fwd
,
algo
,
dtype
,
precision
,
parameters
)
for
dnn_case
in
self
.
special_cases
:
...
...
@@ -558,10 +711,13 @@ class BaseTestDnnConv(object):
yield
(
self
.
run_conv_fwd
,)
+
dnn_case
.
get_case
()
def
test_gradinput
(
self
):
for
dtype
,
precision
in
cudnn
.
get_bwd_data_dtype_configs
()
:
for
dtype
,
precision
in
self
.
dtype_configs
:
algos
=
(
algo
for
algo
in
self
.
bwd_data_algorithms
if
cudnn
.
bwd_data_algo_supports_dtype_config
(
algo
,
dtype
,
precision
,
self
.
ndim
))
for
algo
in
chain
(
algos
,
SUPPORTED_DNN_CONV_ALGO_RUNTIME
):
for
algo
in
algos
:
for
parameters
in
cudnn_conv_case_generator
.
gi
(
algo
,
self
.
ndim
)
.
get_cases
():
yield
(
self
.
run_conv_gradinput
,
algo
,
dtype
,
precision
,
parameters
)
for
algo
in
SUPPORTED_DNN_CONV_ALGO_RUNTIME
:
for
parameters
in
self
.
get_cases
():
yield
(
self
.
run_conv_gradinput
,
algo
,
dtype
,
precision
,
parameters
)
for
dnn_case
in
self
.
special_cases
:
...
...
@@ -572,10 +728,13 @@ class BaseTestDnnConv(object):
yield
(
self
.
run_conv_gradinput
,)
+
dnn_case
.
get_case
()
def
test_gradweight
(
self
):
for
dtype
,
precision
in
cudnn
.
get_bwd_filter_dtype_configs
()
:
for
dtype
,
precision
in
self
.
dtype_configs
:
algos
=
(
algo
for
algo
in
self
.
bwd_filter_algorithms
if
cudnn
.
bwd_filter_algo_supports_dtype_config
(
algo
,
dtype
,
precision
,
self
.
ndim
))
for
algo
in
chain
(
algos
,
SUPPORTED_DNN_CONV_ALGO_RUNTIME
):
for
algo
in
algos
:
for
parameters
in
cudnn_conv_case_generator
.
gw
(
algo
,
self
.
ndim
)
.
get_cases
():
yield
(
self
.
run_conv_gradweight
,
algo
,
dtype
,
precision
,
parameters
)
for
algo
in
SUPPORTED_DNN_CONV_ALGO_RUNTIME
:
for
parameters
in
self
.
get_cases
():
yield
(
self
.
run_conv_gradweight
,
algo
,
dtype
,
precision
,
parameters
)
for
dnn_case
in
self
.
special_cases
:
...
...
@@ -597,14 +756,14 @@ class TestDnnConv2D(BaseTestDnnConv):
cpu_gradinput_class
=
theano
.
tensor
.
nnet
.
corr
.
CorrMM_gradInputs
cpu_gradweight_class
=
theano
.
tensor
.
nnet
.
corr
.
CorrMM_gradWeights
special_cases
=
[
Dnn
Case
.
bwd_filter
(
algo
=
'deterministic'
,
dtype
=
'float32'
,
precision
=
'float32'
,
inputs_shape
=
(
1
,
1
,
541211
,
10
),
filters_shape
=
(
50
,
1
,
3
,
10
),
border_mode
=
(
1
,
0
),
should_fail
=
True
),
Dnn
Case
.
fwd
(
algo
=
'small'
,
dtype
=
'float32'
,
precision
=
'float32'
,
inputs_shape
=
(
65536
,
2
,
2
,
2
),
filters_shape
=
(
1
,
2
,
2
,
2
)),
Dnn
Case
.
fwd
(
algo
=
'small'
,
dtype
=
'float32'
,
precision
=
'float32'
,
inputs_shape
=
(
65537
,
2
,
2
,
2
),
filters_shape
=
(
1
,
2
,
2
,
2
),
should_fail
=
(
version
(
raises
=
False
)
<=
6020
))]
special_cases
=
[
Conv
Case
.
bwd_filter
(
algo
=
'deterministic'
,
dtype
=
'float32'
,
precision
=
'float32'
,
inputs_shape
=
(
1
,
1
,
541211
,
10
),
filters_shape
=
(
50
,
1
,
3
,
10
),
border_mode
=
(
1
,
0
),
should_fail
=
True
),
Conv
Case
.
fwd
(
algo
=
'small'
,
dtype
=
'float32'
,
precision
=
'float32'
,
inputs_shape
=
(
65536
,
2
,
2
,
2
),
filters_shape
=
(
1
,
2
,
2
,
2
)),
Conv
Case
.
fwd
(
algo
=
'small'
,
dtype
=
'float32'
,
precision
=
'float32'
,
inputs_shape
=
(
65537
,
2
,
2
,
2
),
filters_shape
=
(
1
,
2
,
2
,
2
),
should_fail
=
(
version
(
raises
=
False
)
<=
6020
))]
class
TestDnnConv3D
(
BaseTestDnnConv
):
...
...
@@ -618,14 +777,21 @@ class TestDnnConv3D(BaseTestDnnConv):
cpu_gradinput_class
=
theano
.
tensor
.
nnet
.
corr3d
.
Corr3dMM_gradInputs
cpu_gradweight_class
=
theano
.
tensor
.
nnet
.
corr3d
.
Corr3dMM_gradWeights
special_cases
=
[
Dnn
Case
.
fwd
(
algo
=
'small'
,
dtype
=
'float32'
,
precision
=
'float32'
,
inputs_shape
=
(
65536
,
2
,
2
,
2
,
2
),
filters_shape
=
(
1
,
2
,
2
,
2
,
2
)),
Dnn
Case
.
fwd
(
algo
=
'small'
,
dtype
=
'float32'
,
precision
=
'float32'
,
inputs_shape
=
(
65537
,
2
,
2
,
2
,
2
),
filters_shape
=
(
1
,
2
,
2
,
2
,
2
),
should_fail
=
(
version
(
raises
=
False
)
<=
6020
))]
special_cases
=
[
Conv
Case
.
fwd
(
algo
=
'small'
,
dtype
=
'float32'
,
precision
=
'float32'
,
inputs_shape
=
(
65536
,
2
,
2
,
2
,
2
),
filters_shape
=
(
1
,
2
,
2
,
2
,
2
)),
Conv
Case
.
fwd
(
algo
=
'small'
,
dtype
=
'float32'
,
precision
=
'float32'
,
inputs_shape
=
(
65537
,
2
,
2
,
2
,
2
),
filters_shape
=
(
1
,
2
,
2
,
2
,
2
),
should_fail
=
(
version
(
raises
=
False
)
<=
6020
))]
class
CheckDnn
():
def
test_true_half_config_support
():
# For cuDNN V5.1 and V6.0:
# "TRUE_HALF_CONFIG is only supported on architectures with true fp16 support (compute capability 5.3 and 6.0)"
if
not
check_dtype_config_support
(
'float16'
,
'float16'
):
raise
SkipTest
(
'FWD: TRUE_HALF_CONFIG not supported on this GPU.'
)
class
CheckDnn
:
"""
Utility functions for scripting and infos printing.
"""
...
...
@@ -641,16 +807,17 @@ class CheckDnn():
return
'FLOAT_CONFIG'
if
dtype
==
precision
==
'float64'
:
return
'DOUBLE_CONFIG'
raise
ValueError
raise
ValueError
(
'unknown data type configuration'
,
dtype_config
)
@staticmethod
def
print_infos
():
def
print_infos
(
count_tests
=
True
):
# Print infos about tests and cuDNN supported algorithms and configurations.
test_2d
=
TestDnnConv2D
()
test_3d
=
TestDnnConv3D
()
print
()
print
(
'Available data type configurations:'
,
', '
.
join
(
CheckDnn
.
dtype_config_to_str
(
d
)
for
d
in
cudnn
.
get_supported_dtype_configs
()))
', '
.
join
(
CheckDnn
.
dtype_config_to_str
(
d
)
for
d
in
cudnn
.
get_supported_dtype_configs
(
check_dtype_config_support
)))
print
()
print
(
'2D algorithms:'
)
print
(
'FWD :'
,
', '
.
join
(
test_2d
.
fwd_algorithms
))
...
...
@@ -662,34 +829,36 @@ class CheckDnn():
print
(
'BWD FILTER :'
,
', '
.
join
(
test_3d
.
bwd_filter_algorithms
))
print
(
'BWD DATA :'
,
', '
.
join
(
test_3d
.
bwd_data_algorithms
))
print
()
count_tests_2d
=
test_2d
.
get_expected_tcount
()
count_tests_3d
=
test_3d
.
get_expected_tcount
()
print
(
count_tests_2d
,
'conv2D test cases.'
)
print
(
count_tests_3d
,
'conv3D test cases.'
)
print
(
count_tests_2d
+
count_tests_3d
,
'total conv test cases.'
)
print
()
if
count_tests
:
count_tests_2d
=
test_2d
.
get_expected_tcount
()
count_tests_3d
=
test_3d
.
get_expected_tcount
()
print
(
count_tests_2d
,
'conv2D test cases.'
)
print
(
count_tests_3d
,
'conv3D test cases.'
)
print
(
'1 supplementary test.'
)
print
(
count_tests_2d
+
count_tests_3d
+
1
,
'total conv tests.'
)
print
()
class
TupleAction
(
argparse
.
Action
):
def
__call__
(
self
,
parser
,
namespace
,
values
,
option_string
=
None
):
values
=
tuple
(
int
(
v
)
for
v
in
values
.
split
(
','
))
setattr
(
namespace
,
self
.
dest
,
values
)
class
BorderAction
(
TupleAction
):
def
__call__
(
self
,
parser
,
namespace
,
values
,
option_string
=
None
):
if
values
not
in
(
'valid'
,
'full'
,
'half'
):
super
(
CheckDnn
.
BorderAction
,
self
)
.
__call__
(
parser
,
namespace
,
values
,
option_string
)
else
:
setattr
(
namespace
,
self
.
dest
,
values
)
@staticmethod
def
print_tests
():
# Print test cases without running them.
for
test
in
(
TestDnnConv2D
(),
TestDnnConv3D
()):
for
tcase
in
test
.
test_fwd
():
print
(
*
(
x
.
__name__
if
callable
(
x
)
else
x
for
x
in
tcase
))
for
tcase
in
test
.
test_gradinput
():
print
(
*
(
x
.
__name__
if
callable
(
x
)
else
x
for
x
in
tcase
))
for
tcase
in
test
.
test_gradweight
():
print
(
*
(
x
.
__name__
if
callable
(
x
)
else
x
for
x
in
tcase
))
if
__name__
==
'__main__'
:
computations
=
FWD
,
BWD_FILTER
,
BWD_DATA
=
(
'fwd'
,
'bwd-filter'
,
'bwd-data'
)
# We remove programe name from args.
args
=
sys
.
argv
[
1
:]
if
len
(
args
)
==
0
or
args
[
0
]
not
in
computations
+
(
'help'
,
'infos'
):
if
len
(
args
)
==
1
and
args
[
0
]
in
(
'infos'
,
'list'
):
if
args
[
0
]
==
'infos'
:
CheckDnn
.
print_infos
()
if
args
[
0
]
==
'list'
:
CheckDnn
.
print_tests
()
else
:
# We run all tests with nosetests.
module_name
=
sys
.
modules
[
__name__
]
.
__file__
if
len
(
args
)
==
0
:
...
...
@@ -700,105 +869,3 @@ if __name__ == '__main__':
CheckDnn
.
print_infos
()
nose
.
main
(
argv
=
argv
)
elif
len
(
args
)
==
1
and
args
[
0
]
==
'infos'
:
CheckDnn
.
print_infos
()
else
:
# User wants to run a specific test.
dimensions
=
(
'2D'
,
'2d'
,
'3D'
,
'3d'
)
algorithms
=
(
tuple
(
sorted
(
list
(
set
(
cudnn
.
cudnnConvolutionFwdAlgo_t
.
get_aliases
()
+
cudnn
.
cudnnConvolutionBwdFilterAlgo_t
.
get_aliases
()
+
cudnn
.
cudnnConvolutionBwdDataAlgo_t
.
get_aliases
()))))
+
SUPPORTED_DNN_CONV_ALGO_RUNTIME
)
types
=
(
'float16'
,
'float32'
,
'float64'
)
parser
=
argparse
.
ArgumentParser
()
parser
.
add_argument
(
'computation'
,
choices
=
computations
,
help
=
'Computation to run.'
)
parser
.
add_argument
(
'ndim'
,
choices
=
dimensions
,
help
=
'Number od dimensions ("2D" or "3D", case ignored).'
)
parser
.
add_argument
(
'-a'
,
'--algo'
,
choices
=
algorithms
,
required
=
True
,
help
=
'Algorithm to use for computation.'
)
parser
.
add_argument
(
'-i'
,
'--input-shape'
,
action
=
CheckDnn
.
TupleAction
,
required
=
True
,
help
=
'Input shape. Comma-separated list of integers (no spaces).'
)
parser
.
add_argument
(
'-f'
,
'--filter-shape'
,
action
=
CheckDnn
.
TupleAction
,
required
=
True
,
help
=
'Filter shape. Comma-separated list of integers (no spaces).'
)
parser
.
add_argument
(
'-t'
,
'--dtype'
,
choices
=
types
,
default
=
theano
.
config
.
floatX
,
help
=
'Data type (default theano floatX).'
)
parser
.
add_argument
(
'-p'
,
'--precision'
,
choices
=
types
,
default
=
theano
.
config
.
floatX
,
help
=
'Precision (default theano floatX).'
)
parser
.
add_argument
(
'-s'
,
'--subsample'
,
action
=
CheckDnn
.
TupleAction
,
help
=
'Subsample. Comma-separated list of integers (no spaces). '
'Default: 1 per dimension.'
)
parser
.
add_argument
(
'-d'
,
'--dilation'
,
action
=
CheckDnn
.
TupleAction
,
help
=
'Dilation. Comma-separated list of integers (no spaces). '
'Default: 1 per dimension.'
)
parser
.
add_argument
(
'-b'
,
'--border-mode'
,
default
=
'valid'
,
action
=
CheckDnn
.
BorderAction
,
help
=
'Border mode. "valid" (default), "full", "half" '
'or a comma-separated list of integers (no spaces).'
)
parser
.
add_argument
(
'-c'
,
'--conv-mode'
,
choices
=
(
'conv'
,
'cross'
),
default
=
'conv'
,
help
=
'Conv mode (default: conv).'
)
parser
.
add_argument
(
'-A'
,
'--alpha'
,
type
=
float
,
default
=
1
,
help
=
"alpha (floating), must not be zero. Default 1."
)
parser
.
add_argument
(
'-B'
,
'--beta'
,
type
=
float
,
default
=
0
,
help
=
'beta (floating). Default 0.'
)
parser
.
add_argument
(
'--print-infos'
,
action
=
'store_true'
,
default
=
False
,
help
=
'Print some infos before testing.'
)
if
len
(
args
)
==
1
and
args
[
0
]
==
'help'
:
parser
.
parse_args
([
'-h'
])
exit
(
0
)
args
=
parser
.
parse_args
(
args
)
test
=
args
.
computation
ndim
=
int
(
args
.
ndim
[
0
])
if
ndim
==
2
:
tests
=
TestDnnConv2D
()
if
ndim
==
3
:
tests
=
TestDnnConv3D
()
if
args
.
subsample
is
None
:
args
.
subsample
=
(
1
,)
*
ndim
if
args
.
dilation
is
None
:
args
.
dilation
=
(
1
,)
*
ndim
if
not
(
ndim
==
len
(
args
.
input_shape
[
2
:])
==
len
(
args
.
filter_shape
[
2
:])
==
len
(
args
.
subsample
)
==
len
(
args
.
dilation
)):
raise
ValueError
(
'Expected parameters sized for
%
d dimensions.'
%
ndim
)
if
isinstance
(
args
.
border_mode
,
tuple
)
and
ndim
!=
len
(
args
.
border_mode
):
raise
ValueError
(
'Expected borders sized for
%
d dimensions.'
%
ndim
)
if
args
.
alpha
==
0
:
raise
ValueError
(
'Nothing could be computed if alpha is 0.'
)
if
(
args
.
dtype
,
args
.
precision
)
not
in
cudnn
.
get_supported_dtype_configs
():
raise
ValueError
(
'Unsupported data type configuration
%
s
%
s.'
%
(
args
.
dtype
,
args
.
precision
))
if
args
.
algo
not
in
SUPPORTED_DNN_CONV_ALGO_RUNTIME
:
check_config
=
False
if
test
==
FWD
:
check_config
=
cudnn
.
fwd_algo_supports_dtype_config
(
args
.
algo
,
args
.
dtype
,
args
.
precision
,
ndim
)
if
test
==
BWD_FILTER
:
check_config
=
cudnn
.
bwd_filter_algo_supports_dtype_config
(
args
.
algo
,
args
.
dtype
,
args
.
precision
,
ndim
)
if
test
==
BWD_DATA
:
check_config
=
cudnn
.
bwd_data_algo_supports_dtype_config
(
args
.
algo
,
args
.
dtype
,
args
.
precision
,
ndim
)
if
not
check_config
:
raise
ValueError
(
'
%
s computation does not support configuration (
%
s,
%
s) for algo
%
s.'
%
(
test
,
args
.
dtype
,
args
.
precision
,
args
.
algo
))
algo
=
args
.
algo
dtype
=
args
.
dtype
precision
=
args
.
precision
parameters
=
(
args
.
input_shape
,
args
.
filter_shape
,
args
.
subsample
,
args
.
dilation
,
args
.
border_mode
,
args
.
conv_mode
,
args
.
alpha
,
args
.
beta
)
if
args
.
print_infos
:
CheckDnn
.
print_infos
()
print
(
'======================'
)
print
(
'Running
%
s
%
s
%
s
%
s
%
s'
%
(
test
,
algo
,
dtype
,
precision
,
str
(
parameters
)))
if
test
==
FWD
:
tests
.
run_conv_fwd
(
algo
,
dtype
,
precision
,
parameters
)
if
test
==
BWD_FILTER
:
tests
.
run_conv_gradweight
(
algo
,
dtype
,
precision
,
parameters
)
if
test
==
BWD_DATA
:
tests
.
run_conv_gradinput
(
algo
,
dtype
,
precision
,
parameters
)
print
(
'... OK'
)
theano/gpuarray/tests/run_dnn_conv.py
0 → 100644
浏览文件 @
6c0f23db
# This script allows to run one specific cuDNN convolution test case.
# python run_dnn_conv.py --help # Print help.
# python run_dnn_conv.py {fwd|bwd-filter|bwd-data} {2d|3d} -a <algo> -i <inputShape> -f <filterShape> ...
from
__future__
import
absolute_import
,
print_function
,
division
import
argparse
import
sys
import
theano
from
theano.configdefaults
import
SUPPORTED_DNN_CONV_ALGO_RUNTIME
from
theano.gpuarray.cudnn_defs
import
(
HALF
,
FLOAT
,
DOUBLE
,
TRUE_HALF_CONFIG
,
PSEUDO_HALF_CONFIG
,
FLOAT_CONFIG
,
DOUBLE_CONFIG
)
from
theano.gpuarray.tests.check_dnn_conv
import
(
cudnn
,
TestDnnConv2D
,
TestDnnConv3D
,
CheckDnn
)
class
TupleAction
(
argparse
.
Action
):
# Tuple extractor for command line args parser.
def
__call__
(
self
,
parser
,
namespace
,
values
,
option_string
=
None
):
values
=
tuple
(
int
(
v
)
for
v
in
values
.
split
(
','
))
setattr
(
namespace
,
self
.
dest
,
values
)
class
BorderAction
(
TupleAction
):
# Border extractor for command line args parser.
def
__call__
(
self
,
parser
,
namespace
,
values
,
option_string
=
None
):
if
values
not
in
(
'valid'
,
'full'
,
'half'
):
super
(
BorderAction
,
self
)
.
__call__
(
parser
,
namespace
,
values
,
option_string
)
else
:
setattr
(
namespace
,
self
.
dest
,
values
)
if
__name__
!=
'__main__'
:
raise
ImportError
(
'This script cannot be imported.'
)
args
=
sys
.
argv
[
1
:]
computations
=
FWD
,
BWD_FILTER
,
BWD_DATA
=
(
'fwd'
,
'gradweight'
,
'gradinput'
)
dimensions
=
(
'2D'
,
'2d'
,
'3D'
,
'3d'
)
algorithms
=
(
tuple
(
sorted
(
list
(
set
(
cudnn
.
cudnnConvolutionFwdAlgo_t
.
get_aliases
()
+
cudnn
.
cudnnConvolutionBwdFilterAlgo_t
.
get_aliases
()
+
cudnn
.
cudnnConvolutionBwdDataAlgo_t
.
get_aliases
()))))
+
SUPPORTED_DNN_CONV_ALGO_RUNTIME
)
types
=
(
HALF
,
FLOAT
,
DOUBLE
)
data_type_configurations
=
dict
(
TRUE_HALF_CONFIG
=
TRUE_HALF_CONFIG
,
PSEUDO_HALF_CONFIG
=
PSEUDO_HALF_CONFIG
,
FLOAT_CONFIG
=
FLOAT_CONFIG
,
DOUBLE_CONFIG
=
DOUBLE_CONFIG
)
parser
=
argparse
.
ArgumentParser
()
parser
.
add_argument
(
'computation'
,
choices
=
computations
,
help
=
'Computation to run.'
)
parser
.
add_argument
(
'ndim'
,
choices
=
dimensions
,
help
=
'Number of dimensions ("2D" or "3D", case insensitive).'
)
parser
.
add_argument
(
'-a'
,
'--algo'
,
choices
=
algorithms
,
required
=
True
,
help
=
'Algorithm to use for computation.'
)
parser
.
add_argument
(
'-i'
,
'--input-shape'
,
action
=
TupleAction
,
required
=
True
,
help
=
'Input shape. Comma-separated list of integers (no spaces).'
)
parser
.
add_argument
(
'-f'
,
'--filter-shape'
,
action
=
TupleAction
,
required
=
True
,
help
=
'Filter shape. Comma-separated list of integers (no spaces).'
)
parser
.
add_argument
(
'-D'
,
'--dtype-config'
,
choices
=
list
(
sorted
(
data_type_configurations
.
keys
())),
default
=
None
,
help
=
'Data type configuration for (data type; precision). Default (theano floatX; theano floatX). '
'To specify data type configuration, you can either use this option or set data type and '
'precision separately with "-t" and "-p" options.'
)
parser
.
add_argument
(
'-t'
,
'--dtype'
,
choices
=
types
,
default
=
None
,
help
=
'Data type (default theano floatX).'
)
parser
.
add_argument
(
'-p'
,
'--precision'
,
choices
=
types
,
default
=
None
,
help
=
'Precision (default theano floatX).'
)
parser
.
add_argument
(
'-s'
,
'--subsample'
,
action
=
TupleAction
,
help
=
'Subsample. Comma-separated list of integers (no spaces). '
'Default: 1 per dimension.'
)
parser
.
add_argument
(
'-d'
,
'--dilation'
,
action
=
TupleAction
,
help
=
'Dilation. Comma-separated list of integers (no spaces). '
'Default: 1 per dimension.'
)
parser
.
add_argument
(
'-b'
,
'--border-mode'
,
default
=
'valid'
,
action
=
BorderAction
,
help
=
'Border mode. "valid" (default), "full", "half" '
'or a comma-separated list of integers (no spaces).'
)
parser
.
add_argument
(
'-c'
,
'--conv-mode'
,
choices
=
(
'conv'
,
'cross'
),
default
=
'conv'
,
help
=
'Conv mode (default: conv).'
)
parser
.
add_argument
(
'-A'
,
'--alpha'
,
type
=
float
,
default
=
1
,
help
=
"alpha (floating), must not be zero. Default 1."
)
parser
.
add_argument
(
'-B'
,
'--beta'
,
type
=
float
,
default
=
0
,
help
=
'beta (floating). Default 0.'
)
parser
.
add_argument
(
'-I'
,
'--print-infos'
,
action
=
'store_true'
,
default
=
False
,
help
=
'Print some infos before testing.'
)
args
=
parser
.
parse_args
(
args
)
test
=
args
.
computation
ndim
=
int
(
args
.
ndim
[
0
])
if
ndim
==
2
:
tests
=
TestDnnConv2D
()
if
ndim
==
3
:
tests
=
TestDnnConv3D
()
if
args
.
subsample
is
None
:
args
.
subsample
=
(
1
,)
*
ndim
if
args
.
dilation
is
None
:
args
.
dilation
=
(
1
,)
*
ndim
if
not
(
ndim
==
len
(
args
.
input_shape
[
2
:])
==
len
(
args
.
filter_shape
[
2
:])
==
len
(
args
.
subsample
)
==
len
(
args
.
dilation
)):
raise
ValueError
(
'Expected parameters sized for
%
d dimensions.'
%
ndim
)
if
isinstance
(
args
.
border_mode
,
tuple
)
and
ndim
!=
len
(
args
.
border_mode
):
raise
ValueError
(
'Expected borders sized for
%
d dimensions.'
%
ndim
)
if
args
.
alpha
==
0
:
raise
ValueError
(
'Nothing could be computed if alpha is 0.'
)
if
args
.
dtype_config
is
None
:
if
args
.
dtype
is
None
:
args
.
dtype
=
theano
.
config
.
floatX
if
args
.
precision
is
None
:
args
.
precision
=
theano
.
config
.
floatX
else
:
if
args
.
dtype
is
not
None
or
args
.
precision
is
not
None
:
raise
ValueError
(
'You must specify either -D <data-type-configuration> '
'or (-t <data-type> -p <precision>), not both.'
)
args
.
dtype
,
args
.
precision
=
data_type_configurations
[
args
.
dtype_config
]
if
(
args
.
dtype
,
args
.
precision
)
not
in
cudnn
.
get_supported_dtype_configs
():
raise
ValueError
(
'Unsupported data type configuration
%
s
%
s.'
%
(
args
.
dtype
,
args
.
precision
))
if
args
.
algo
not
in
SUPPORTED_DNN_CONV_ALGO_RUNTIME
:
check_config
=
False
if
test
==
FWD
:
check_config
=
cudnn
.
fwd_algo_supports_dtype_config
(
args
.
algo
,
args
.
dtype
,
args
.
precision
,
ndim
)
if
test
==
BWD_FILTER
:
check_config
=
cudnn
.
bwd_filter_algo_supports_dtype_config
(
args
.
algo
,
args
.
dtype
,
args
.
precision
,
ndim
)
if
test
==
BWD_DATA
:
check_config
=
cudnn
.
bwd_data_algo_supports_dtype_config
(
args
.
algo
,
args
.
dtype
,
args
.
precision
,
ndim
)
if
not
check_config
:
raise
ValueError
(
'
%
s computation does not support configuration (
%
s,
%
s) for algo
%
s.'
%
(
test
,
args
.
dtype
,
args
.
precision
,
args
.
algo
))
algo
=
args
.
algo
dtype
=
args
.
dtype
precision
=
args
.
precision
parameters
=
(
args
.
input_shape
,
args
.
filter_shape
,
args
.
subsample
,
args
.
dilation
,
args
.
border_mode
,
args
.
conv_mode
,
args
.
alpha
,
args
.
beta
)
if
args
.
print_infos
:
CheckDnn
.
print_infos
(
count_tests
=
False
)
print
(
'======================'
)
print
(
'Running'
,
test
,
algo
,
dtype
,
precision
,
*
parameters
)
if
test
==
FWD
:
tests
.
run_conv_fwd
(
algo
,
dtype
,
precision
,
parameters
)
if
test
==
BWD_FILTER
:
tests
.
run_conv_gradweight
(
algo
,
dtype
,
precision
,
parameters
)
if
test
==
BWD_DATA
:
tests
.
run_conv_gradinput
(
algo
,
dtype
,
precision
,
parameters
)
print
(
'... OK'
)
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论