Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
Xiaomi
Mace
提交
936ef618
Mace
项目概览
Xiaomi
/
Mace
通知
107
Star
40
Fork
27
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
Mace
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
提交
936ef618
编写于
11月 29, 2017
作者:
L
Liangliang He
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Fix conv1x1 opencl tests
上级
e4370b73
变更
5
显示空白变更内容
内联
并排
Showing
5 changed file
with
122 addition
and
83 deletion
+122
-83
mace/kernels/opencl/cl/conv_2d_1x1.cl
mace/kernels/opencl/cl/conv_2d_1x1.cl
+70
-57
mace/kernels/opencl/conv_2d_opencl_1x1.cc
mace/kernels/opencl/conv_2d_opencl_1x1.cc
+5
-3
mace/ops/conv_2d_test.cc
mace/ops/conv_2d_test.cc
+38
-16
mace/ops/ops_test_util.h
mace/ops/ops_test_util.h
+1
-7
mace/utils/utils.h
mace/utils/utils.h
+8
-0
未找到文件。
mace/kernels/opencl/cl/conv_2d_1x1.cl
浏览文件 @
936ef618
...
@@ -19,49 +19,64 @@ __kernel void conv_2d_1x1(__read_only image2d_t input, /* [c%4 * w * c/4, h * b]
...
@@ -19,49 +19,64 @@ __kernel void conv_2d_1x1(__read_only image2d_t input, /* [c%4 * w * c/4, h * b]
const
sampler_t
sampler
=
CLK_NORMALIZED_COORDS_FALSE
| CLK_ADDRESS_CLAMP |
CLK_FILTER_NEAREST
;
const
sampler_t
sampler
=
CLK_NORMALIZED_COORDS_FALSE
| CLK_ADDRESS_CLAMP |
CLK_FILTER_NEAREST
;
DATA_TYPE4
out[4]
=
{0}
;
#
ifdef
BIAS
#
ifdef
BIAS
out[0]
=
DATA_TYPE4
out0
=
READ_IMAGET
(
bias,
sampler,
(
int2
)(
out_ch_blk,
0
))
;
READ_IMAGET
(
bias,
sampler,
(
int2
)(
out_ch_blk,
0
))
;
DATA_TYPE4
out1
=
out0
;
out[1]
=
out[0]
;
DATA_TYPE4
out2
=
out0
;
out[2]
=
out[0]
;
DATA_TYPE4
out3
=
out0
;
out[3]
=
out[0]
;
#
else
DATA_TYPE4
out0
=
0
;
DATA_TYPE4
out1
=
0
;
DATA_TYPE4
out2
=
0
;
DATA_TYPE4
out3
=
0
;
#
endif
#
endif
int
w[4]
;
int
4
w
;
w
[0]
=
out_w_blk
;
w
.x
=
out_w_blk
;
w
[1]
=
w[0]
+
out_w_blks
;
w
.y
=
w.x
+
out_w_blks
;
w
[2]
=
w[1]
+
out_w_blks
;
w
.z
=
w.y
+
out_w_blks
;
w
[3]
=
w[2]
+
out_w_blks
;
w
.w
=
w.z
+
out_w_blks
;
//
Unrolling
this
loop
hurt
perfmance
//
Unrolling
this
loop
hurt
perfmance
int
in_x_base
=
0
;
int
in_x_base
=
0
;
for
(
int
in_ch_blk
=
0
; in_ch_blk < in_ch_blks; ++in_ch_blk) {
for
(
int
in_ch_blk
=
0
; in_ch_blk < in_ch_blks; ++in_ch_blk) {
DATA_TYPE4
in[4]
;
DATA_TYPE4
in0
=
READ_IMAGET
(
input,
sampler,
(
int2
)(
in_x_base
+
w.x,
out_hb
))
;
in[0]
=
READ_IMAGET
(
input,
sampler,
(
int2
)(
in_x_base
+
w[0],
out_hb
))
;
DATA_TYPE4
in1
=
0
;
if
(
w[1]
<
width
)
{
DATA_TYPE4
in2
=
0
;
DATA_TYPE4
in3
=
0
;
if
(
w.y
<
width
)
{
//
conditional
load
hurt
perf,
this
branching
helps
sometimes
//
conditional
load
hurt
perf,
this
branching
helps
sometimes
in
[1]
=
READ_IMAGET
(
input,
sampler,
(
int2
)(
in_x_base
+
w[1]
,
out_hb
))
;
in
1
=
READ_IMAGET
(
input,
sampler,
(
int2
)(
in_x_base
+
w.y
,
out_hb
))
;
in
[2]
=
READ_IMAGET
(
input,
sampler,
(
int2
)(
in_x_base
+
w[2]
,
out_hb
))
;
in
2
=
READ_IMAGET
(
input,
sampler,
(
int2
)(
in_x_base
+
w.z
,
out_hb
))
;
in
[3]
=
READ_IMAGET
(
input,
sampler,
(
int2
)(
in_x_base
+
w[3]
,
out_hb
))
;
in
3
=
READ_IMAGET
(
input,
sampler,
(
int2
)(
in_x_base
+
w.w
,
out_hb
))
;
}
}
const
int
filter_x0
=
in_ch_blk
<<
2
;
const
int
filter_x0
=
in_ch_blk
<<
2
;
DATA_TYPE4
weights[4]
;
DATA_TYPE4
weights0
=
READ_IMAGET
(
filter,
sampler,
(
int2
)(
filter_x0,
out_ch_blk
))
;
#
pragma
unroll
DATA_TYPE4
weights1
=
READ_IMAGET
(
filter,
sampler,
(
int2
)(
filter_x0
+
1
,
out_ch_blk
))
;
for
(
int
c
=
0
; c < 4; ++c) {
DATA_TYPE4
weights2
=
READ_IMAGET
(
filter,
sampler,
(
int2
)(
filter_x0
+
2
,
out_ch_blk
))
;
weights[c]
=
READ_IMAGET
(
filter,
sampler,
(
int2
)(
filter_x0
+
c,
out_ch_blk
))
;
DATA_TYPE4
weights3
=
READ_IMAGET
(
filter,
sampler,
(
int2
)(
filter_x0
+
3
,
out_ch_blk
))
;
}
//
Will
prefetch
L2
improve
performance?
How
to
pretch
image
data?
//
Will
prefetch
L2
improve
performance?
How
to
pretch
image
data?
//
Interleaving
load
and
mul
does
not
improve
performance
as
expected
out0
+=
in0.x
*
weights0
;
#
pragma
unroll
out0
+=
in0.y
*
weights1
;
for
(
int
wi
=
0
; wi < 4; ++wi) {
out0
+=
in0.z
*
weights2
;
out[wi]
+=
in[wi].x
*
weights[0]
;
out0
+=
in0.w
*
weights3
;
out[wi]
+=
in[wi].y
*
weights[1]
;
out[wi]
+=
in[wi].z
*
weights[2]
;
out1
+=
in1.x
*
weights0
;
out[wi]
+=
in[wi].w
*
weights[3]
;
out1
+=
in1.y
*
weights1
;
}
out1
+=
in1.z
*
weights2
;
out1
+=
in1.w
*
weights3
;
out2
+=
in2.x
*
weights0
;
out2
+=
in2.y
*
weights1
;
out2
+=
in2.z
*
weights2
;
out2
+=
in2.w
*
weights3
;
out3
+=
in3.x
*
weights0
;
out3
+=
in3.y
*
weights1
;
out3
+=
in3.z
*
weights2
;
out3
+=
in3.w
*
weights3
;
in_x_base
+=
width
;
in_x_base
+=
width
;
}
}
...
@@ -70,42 +85,40 @@ __kernel void conv_2d_1x1(__read_only image2d_t input, /* [c%4 * w * c/4, h * b]
...
@@ -70,42 +85,40 @@ __kernel void conv_2d_1x1(__read_only image2d_t input, /* [c%4 * w * c/4, h * b]
//
batch
norm
//
batch
norm
DATA_TYPE4
bn_scale_value
=
DATA_TYPE4
bn_scale_value
=
READ_IMAGET
(
bn_scale,
sampler,
(
int2
)(
out_ch_blk,
0
))
;
READ_IMAGET
(
bn_scale,
sampler,
(
int2
)(
out_ch_blk,
0
))
;
DATA_TYPE4
scale[4]
;
DATA_TYPE4
scale0
=
(
DATA_TYPE4
)(
bn_scale_value.x
)
;
scale[0]
=
(
DATA_TYPE4
)(
bn_scale_value.x
)
;
DATA_TYPE4
scale1
=
(
DATA_TYPE4
)(
bn_scale_value.y
)
;
scale[1]
=
(
DATA_TYPE4
)(
bn_scale_value.y
)
;
DATA_TYPE4
scale2
=
(
DATA_TYPE4
)(
bn_scale_value.z
)
;
scale[2]
=
(
DATA_TYPE4
)(
bn_scale_value.z
)
;
DATA_TYPE4
scale3
=
(
DATA_TYPE4
)(
bn_scale_value.w
)
;
scale[3]
=
(
DATA_TYPE4
)(
bn_scale_value.w
)
;
DATA_TYPE4
bn_offset_value
=
DATA_TYPE4
bn_offset_value
=
READ_IMAGET
(
bn_offset,
sampler,
(
int2
)(
out_ch_blk,
0
))
;
READ_IMAGET
(
bn_offset,
sampler,
(
int2
)(
out_ch_blk,
0
))
;
DATA_TYPE4
offset[4]
;
DATA_TYPE4
offset0
=
(
DATA_TYPE4
)(
bn_offset_value.x
)
;
offset[0]
=
(
DATA_TYPE4
)(
bn_offset_value.x
)
;
DATA_TYPE4
offset1
=
(
DATA_TYPE4
)(
bn_offset_value.y
)
;
offset[1]
=
(
DATA_TYPE4
)(
bn_offset_value.y
)
;
DATA_TYPE4
offset2
=
(
DATA_TYPE4
)(
bn_offset_value.z
)
;
offset[2]
=
(
DATA_TYPE4
)(
bn_offset_value.z
)
;
DATA_TYPE4
offset3
=
(
DATA_TYPE4
)(
bn_offset_value.w
)
;
offset[3]
=
(
DATA_TYPE4
)(
bn_offset_value.w
)
;
out0
=
out0
*
scale0
+
offset0
;
#
pragma
unroll
out1
=
out1
*
scale1
+
offset1
;
for
(
int
wi
=
0
; wi < 4; ++wi) {
out2
=
out2
*
scale2
+
offset2
;
out[wi]
=
out[wi]
*
scale[wi]
+
offset[wi]
;
out3
=
out3
*
scale3
+
offset3
;
}
#
endif
#
endif
#
ifdef
FUSED_RELU
#
ifdef
FUSED_RELU
#
pragma
unroll
for
(
int
wi
=
0
; wi < 4; ++wi) {
//
TODO
relux
//
TODO
relux
out[wi]
=
fmax
(
out[wi],
0
)
;
out0
=
fmax
(
out0,
0
)
;
}
out1
=
fmax
(
out1,
0
)
;
out2
=
fmax
(
out2,
0
)
;
out3
=
fmax
(
out3,
0
)
;
#
endif
#
endif
const
int
out_x_base
=
out_ch_blk
*
width
;
const
int
out_x_base
=
out_ch_blk
*
width
;
WRITE_IMAGET
(
output,
(
int2
)(
out_x_base
+
w
[3],
out_hb
)
,
out[0]
)
;
WRITE_IMAGET
(
output,
(
int2
)(
out_x_base
+
w
.x,
out_hb
)
,
out0
)
;
if
(
w
[1]
>=
width
)
return
;
if
(
w
.y
>=
width
)
return
;
WRITE_IMAGET
(
output,
(
int2
)(
out_x_base
+
w
[1],
out_hb
)
,
out[1]
)
;
WRITE_IMAGET
(
output,
(
int2
)(
out_x_base
+
w
.y,
out_hb
)
,
out1
)
;
if
(
w
[2]
>=
width
)
return
;
if
(
w
.z
>=
width
)
return
;
WRITE_IMAGET
(
output,
(
int2
)(
out_x_base
+
w
[3],
out_hb
)
,
out[2]
)
;
WRITE_IMAGET
(
output,
(
int2
)(
out_x_base
+
w
.z,
out_hb
)
,
out2
)
;
if
(
w
[3]
>=
width
)
return
;
if
(
w
.w
>=
width
)
return
;
WRITE_IMAGET
(
output,
(
int2
)(
out_x_base
+
w
[3],
out_hb
)
,
out[3]
)
;
WRITE_IMAGET
(
output,
(
int2
)(
out_x_base
+
w
.w,
out_hb
)
,
out3
)
;
}
}
mace/kernels/opencl/conv_2d_opencl_1x1.cc
浏览文件 @
936ef618
...
@@ -5,8 +5,8 @@
...
@@ -5,8 +5,8 @@
#include "mace/kernels/conv_2d.h"
#include "mace/kernels/conv_2d.h"
#include "mace/core/runtime/opencl/cl2_header.h"
#include "mace/core/runtime/opencl/cl2_header.h"
#include "mace/core/runtime/opencl/opencl_runtime.h"
#include "mace/core/runtime/opencl/opencl_runtime.h"
#include "mace/utils/utils.h"
#include "mace/kernels/opencl/helper.h"
#include "mace/kernels/opencl/helper.h"
#include "mace/utils/utils.h"
namespace
mace
{
namespace
mace
{
namespace
kernels
{
namespace
kernels
{
...
@@ -36,8 +36,10 @@ void Conv1x1(const Tensor *input,
...
@@ -36,8 +36,10 @@ void Conv1x1(const Tensor *input,
std
::
set
<
std
::
string
>
built_options
;
std
::
set
<
std
::
string
>
built_options
;
built_options
.
emplace
(
"-DDATA_TYPE="
+
DataTypeToCLType
(
input
->
dtype
()));
built_options
.
emplace
(
"-DDATA_TYPE="
+
DataTypeToCLType
(
input
->
dtype
()));
built_options
.
emplace
(
"-DCMD_DATA_TYPE="
+
DataTypeToOPENCLCMDDataType
(
input
->
dtype
()));
built_options
.
emplace
(
"-DCMD_DATA_TYPE="
+
DataTypeToOPENCLCMDDataType
(
input
->
dtype
()));
built_options
.
emplace
(
"-DSTRIDE_1"
);
built_options
.
emplace
(
"-DSTRIDE="
+
ToString
(
stride
));
built_options
.
emplace
(
bias
!=
nullptr
?
"-DBIAS"
:
""
);
if
(
bias
!=
nullptr
)
{
built_options
.
emplace
(
"-DBIAS"
);
}
auto
runtime
=
OpenCLRuntime
::
Get
();
auto
runtime
=
OpenCLRuntime
::
Get
();
auto
program
=
runtime
->
program
();
auto
program
=
runtime
->
program
();
...
...
mace/ops/conv_2d_test.cc
浏览文件 @
936ef618
...
@@ -398,17 +398,7 @@ TEST_F(Conv2dOpTest, CPUCombined) {
...
@@ -398,17 +398,7 @@ TEST_F(Conv2dOpTest, CPUCombined) {
template
<
DeviceType
D
>
template
<
DeviceType
D
>
void
TestConv1x1
()
{
void
TestConv1x1
()
{
// Construct graph
OpsTestNet
net
;
OpsTestNet
net
;
OpDefBuilder
(
"Conv2D"
,
"Conv2DTest"
)
.
Input
(
"Input"
)
.
Input
(
"Filter"
)
.
Input
(
"Bias"
)
.
Output
(
"Output"
)
.
AddIntsArg
(
"strides"
,
{
1
,
1
})
.
AddIntArg
(
"padding"
,
Padding
::
VALID
)
.
AddIntsArg
(
"dilations"
,
{
1
,
1
})
.
Finalize
(
net
.
NewOperatorDef
());
// Add input data
// Add input data
net
.
AddInputFromArray
<
D
,
float
>
(
net
.
AddInputFromArray
<
D
,
float
>
(
...
@@ -425,8 +415,39 @@ void TestConv1x1() {
...
@@ -425,8 +415,39 @@ void TestConv1x1() {
{
1.0
f
,
2.0
f
,
1.0
f
,
2.0
f
,
1.0
f
,
2.0
f
,
1.0
f
,
2.0
f
,
1.0
f
,
2.0
f
});
{
1.0
f
,
2.0
f
,
1.0
f
,
2.0
f
,
1.0
f
,
2.0
f
,
1.0
f
,
2.0
f
,
1.0
f
,
2.0
f
});
net
.
AddInputFromArray
<
D
,
float
>
(
"Bias"
,
{
2
},
{
0.1
f
,
0.2
f
});
net
.
AddInputFromArray
<
D
,
float
>
(
"Bias"
,
{
2
},
{
0.1
f
,
0.2
f
});
// Run
// Construct graph
if
(
D
==
DeviceType
::
OPENCL
)
{
BufferToImage
<
D
>
(
net
,
"Input"
,
"InputImage"
,
kernels
::
BufferType
::
IN_OUT
);
BufferToImage
<
D
>
(
net
,
"Filter"
,
"FilterImage"
,
kernels
::
BufferType
::
FILTER
);
BufferToImage
<
D
>
(
net
,
"Bias"
,
"BiasImage"
,
kernels
::
BufferType
::
ARGUMENT
);
OpDefBuilder
(
"Conv2D"
,
"Conv2dTest"
)
.
Input
(
"InputImage"
)
.
Input
(
"FilterImage"
)
.
Input
(
"BiasImage"
)
.
Output
(
"OutputImage"
)
.
AddIntsArg
(
"strides"
,
{
1
,
1
})
.
AddIntArg
(
"padding"
,
Padding
::
VALID
)
.
AddIntsArg
(
"dilations"
,
{
1
,
1
})
.
Finalize
(
net
.
NewOperatorDef
());
net
.
RunOp
(
D
);
// Transfer output
ImageToBuffer
<
D
>
(
net
,
"OutputImage"
,
"Output"
,
kernels
::
BufferType
::
IN_OUT
);
}
else
{
OpDefBuilder
(
"Conv2D"
,
"Conv2DTest"
)
.
Input
(
"Input"
)
.
Input
(
"Filter"
)
.
Input
(
"Bias"
)
.
Output
(
"Output"
)
.
AddIntsArg
(
"strides"
,
{
1
,
1
})
.
AddIntArg
(
"padding"
,
Padding
::
VALID
)
.
AddIntsArg
(
"dilations"
,
{
1
,
1
})
.
Finalize
(
net
.
NewOperatorDef
());
net
.
RunOp
(
D
);
net
.
RunOp
(
D
);
}
// Check
// Check
auto
expected
=
CreateTensor
<
float
>
(
auto
expected
=
CreateTensor
<
float
>
(
...
@@ -445,9 +466,9 @@ TEST_F(Conv2dOpTest, CPUConv1x1) {
...
@@ -445,9 +466,9 @@ TEST_F(Conv2dOpTest, CPUConv1x1) {
TestConv1x1
<
DeviceType
::
CPU
>
();
TestConv1x1
<
DeviceType
::
CPU
>
();
}
}
//
TEST_F(Conv2dOpTest, OPENCLConv1x1) {
TEST_F
(
Conv2dOpTest
,
OPENCLConv1x1
)
{
//
TestConv1x1<DeviceType::OPENCL>();
TestConv1x1
<
DeviceType
::
OPENCL
>
();
//
}
}
template
<
DeviceType
D
>
template
<
DeviceType
D
>
static
void
TestComplexConvNxNS12
(
const
std
::
vector
<
index_t
>
&
shape
)
{
static
void
TestComplexConvNxNS12
(
const
std
::
vector
<
index_t
>
&
shape
)
{
...
@@ -457,6 +478,7 @@ static void TestComplexConvNxNS12(const std::vector<index_t> &shape) {
...
@@ -457,6 +478,7 @@ static void TestComplexConvNxNS12(const std::vector<index_t> &shape) {
srand
(
time
(
NULL
));
srand
(
time
(
NULL
));
// generate random input
// generate random input
// TODO test all sizes
index_t
batch
=
3
+
(
rand
()
%
10
);
index_t
batch
=
3
+
(
rand
()
%
10
);
index_t
height
=
shape
[
0
];
index_t
height
=
shape
[
0
];
index_t
width
=
shape
[
1
];
index_t
width
=
shape
[
1
];
...
@@ -507,7 +529,7 @@ static void TestComplexConvNxNS12(const std::vector<index_t> &shape) {
...
@@ -507,7 +529,7 @@ static void TestComplexConvNxNS12(const std::vector<index_t> &shape) {
ExpectTensorNear
<
float
>
(
expected
,
*
net
.
GetOutput
(
"OPENCLOutput"
),
0.001
);
ExpectTensorNear
<
float
>
(
expected
,
*
net
.
GetOutput
(
"OPENCLOutput"
),
0.001
);
};
};
for
(
int
kernel_size
:
{
3
})
{
for
(
int
kernel_size
:
{
1
,
3
})
{
for
(
int
stride
:
{
1
})
{
for
(
int
stride
:
{
1
})
{
func
(
kernel_size
,
kernel_size
,
stride
,
stride
,
VALID
);
func
(
kernel_size
,
kernel_size
,
stride
,
stride
,
VALID
);
func
(
kernel_size
,
kernel_size
,
stride
,
stride
,
SAME
);
func
(
kernel_size
,
kernel_size
,
stride
,
stride
,
SAME
);
...
...
mace/ops/ops_test_util.h
浏览文件 @
936ef618
...
@@ -13,6 +13,7 @@
...
@@ -13,6 +13,7 @@
#include "mace/core/tensor.h"
#include "mace/core/tensor.h"
#include "mace/core/runtime/opencl/opencl_runtime.h"
#include "mace/core/runtime/opencl/opencl_runtime.h"
#include "mace/kernels/opencl/helper.h"
#include "mace/kernels/opencl/helper.h"
#include "mace/utils/utils.h"
namespace
mace
{
namespace
mace
{
...
@@ -337,13 +338,6 @@ void ExpectTensorNear(const Tensor &x, const Tensor &y, const double abs_err) {
...
@@ -337,13 +338,6 @@ void ExpectTensorNear(const Tensor &x, const Tensor &y, const double abs_err) {
Expector
<
T
>::
Near
(
x
,
y
,
abs_err
);
Expector
<
T
>::
Near
(
x
,
y
,
abs_err
);
}
}
template
<
typename
T
>
std
::
string
ToString
(
const
T
&
input
)
{
std
::
stringstream
ss
;
ss
<<
input
;
return
ss
.
str
();
}
template
<
DeviceType
D
>
template
<
DeviceType
D
>
void
BufferToImage
(
OpsTestNet
&
net
,
void
BufferToImage
(
OpsTestNet
&
net
,
const
std
::
string
&
input_name
,
const
std
::
string
&
input_name
,
...
...
mace/utils/utils.h
浏览文件 @
936ef618
...
@@ -6,6 +6,7 @@
...
@@ -6,6 +6,7 @@
#define MACE_UTILS_UTILS_H_
#define MACE_UTILS_UTILS_H_
#include <sys/time.h>
#include <sys/time.h>
#include <sstream>
namespace
mace
{
namespace
mace
{
template
<
typename
Integer
>
template
<
typename
Integer
>
...
@@ -40,5 +41,12 @@ inline int64_t NowInMicroSec() {
...
@@ -40,5 +41,12 @@ inline int64_t NowInMicroSec() {
return
static_cast
<
int64_t
>
(
tv
.
tv_sec
*
1000000
+
tv
.
tv_usec
);
return
static_cast
<
int64_t
>
(
tv
.
tv_sec
*
1000000
+
tv
.
tv_usec
);
}
}
template
<
typename
T
>
inline
std
::
string
ToString
(
T
v
)
{
std
::
ostringstream
ss
;
ss
<<
v
;
return
ss
.
str
();
}
}
// namespace mace
}
// namespace mace
#endif // MACE_UTILS_UTILS_H_
#endif // MACE_UTILS_UTILS_H_
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录