Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
机器未来
Paddle
提交
56050a14
P
Paddle
项目概览
机器未来
/
Paddle
与 Fork 源项目一致
Fork自
PaddlePaddle / Paddle
通知
1
Star
1
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
1
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
P
Paddle
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
1
Issue
1
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
提交
56050a14
编写于
9月 22, 2020
作者:
W
Wojciech Uss
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Add support for (de/re)quantization with shift
上级
a0452475
变更
10
隐藏空白更改
内联
并排
Showing
10 changed file
with
686 addition
and
113 deletion
+686
-113
paddle/fluid/operators/dequantize_op.cc
paddle/fluid/operators/dequantize_op.cc
+4
-3
paddle/fluid/operators/mkldnn/dequantize_mkldnn_op.cc
paddle/fluid/operators/mkldnn/dequantize_mkldnn_op.cc
+28
-2
paddle/fluid/operators/mkldnn/quantize_mkldnn_op.cc
paddle/fluid/operators/mkldnn/quantize_mkldnn_op.cc
+35
-8
paddle/fluid/operators/mkldnn/requantize_mkldnn_op.cc
paddle/fluid/operators/mkldnn/requantize_mkldnn_op.cc
+58
-14
paddle/fluid/operators/quantize_op.cc
paddle/fluid/operators/quantize_op.cc
+7
-3
paddle/fluid/operators/requantize_op.cc
paddle/fluid/operators/requantize_op.cc
+6
-4
python/paddle/fluid/tests/unittests/mkldnn/test_dequantize_mkldnn_op.py
...fluid/tests/unittests/mkldnn/test_dequantize_mkldnn_op.py
+124
-11
python/paddle/fluid/tests/unittests/mkldnn/test_quantize_mkldnn_op.py
...e/fluid/tests/unittests/mkldnn/test_quantize_mkldnn_op.py
+172
-15
python/paddle/fluid/tests/unittests/mkldnn/test_requantize_mkldnn_op.py
...fluid/tests/unittests/mkldnn/test_requantize_mkldnn_op.py
+251
-52
tools/codestyle/clang_format.hook
tools/codestyle/clang_format.hook
+1
-1
未找到文件。
paddle/fluid/operators/dequantize_op.cc
浏览文件 @
56050a14
...
...
@@ -31,9 +31,10 @@ framework::OpKernelType DeQuantOp::GetExpectedKernelType(
}
void
DeQuantOpMaker
::
Make
()
{
AddInput
(
"Input"
,
"input data"
);
AddOutput
(
"Output"
,
"output data"
);
AddAttr
<
float
>
(
"Scale"
,
"scale data"
).
SetDefault
({
1.0
f
});
AddInput
(
"Input"
,
"Input data"
);
AddOutput
(
"Output"
,
"Output data"
);
AddAttr
<
float
>
(
"Scale"
,
"Scale data"
).
SetDefault
({
1.0
f
});
AddAttr
<
float
>
(
"Shift"
,
"Shift data"
).
SetDefault
({
0.0
f
});
AddComment
(
R"DOC(This op will dequantize data from INT8 to FP32)DOC"
);
}
...
...
paddle/fluid/operators/mkldnn/dequantize_mkldnn_op.cc
浏览文件 @
56050a14
...
...
@@ -16,6 +16,7 @@ limitations under the License. */
#include "paddle/fluid/framework/data_layout_transform.h"
#include "paddle/fluid/framework/tensor.h"
#include "paddle/fluid/operators/dequantize_op.h"
#include "paddle/fluid/platform/errors.h"
#include "paddle/fluid/platform/mkldnn_helper.h"
#include "paddle/fluid/platform/mkldnn_reuse.h"
...
...
@@ -37,14 +38,29 @@ class DeQuantOpKernel : public framework::OpKernel<T> {
void
Compute
(
const
framework
::
ExecutionContext
&
ctx
)
const
override
{
auto
*
input
=
ctx
.
Input
<
Tensor
>
(
"Input"
);
auto
scale_data
=
ctx
.
Attr
<
float
>
(
"Scale"
);
auto
scale_shift
=
ctx
.
Attr
<
float
>
(
"Shift"
);
bool
with_shift
=
scale_shift
!=
0.0
f
;
auto
*
output
=
ctx
.
Output
<
Tensor
>
(
"Output"
);
PADDLE_ENFORCE_NE
(
scale_data
,
0.0
f
,
platform
::
errors
::
InvalidArgument
(
"Dequantization scale cannot be 0.0"
));
PADDLE_ENFORCE_GE
(
scale_shift
,
0
,
platform
::
errors
::
Unimplemented
(
"Dequantization shift must be nonnegative."
));
PADDLE_ENFORCE_LE
(
scale_shift
,
255
,
platform
::
errors
::
Unimplemented
(
"Dequantization shift must be less than or equal to 255."
));
auto
&
dev_ctx
=
ctx
.
template
device_context
<
platform
::
MKLDNNDeviceContext
>();
const
auto
&
engine
=
dev_ctx
.
GetEngine
();
const
T
*
input_data
=
input
->
data
<
T
>
();
float
*
output_data
=
output
->
mutable_data
<
float
>
(
ctx
.
GetPlace
());
std
::
vector
<
float
>
reorder_scale
=
{
1.0
f
/
scale_data
};
float
reorder_shift
=
-
scale_shift
/
scale_data
;
auto
src_tz
=
paddle
::
framework
::
vectorize
<
int64_t
>
(
input
->
dims
());
auto
dst_tz
=
paddle
::
framework
::
vectorize
<
int64_t
>
(
output
->
dims
());
...
...
@@ -65,7 +81,15 @@ class DeQuantOpKernel : public framework::OpKernel<T> {
if
(
reorder_p
==
nullptr
)
{
mkldnn
::
primitive_attr
attri
;
int
mask
=
0
;
attri
.
set_output_scales
(
mask
,
reorder_scale
);
float
reorder_scale
=
1.
/
scale_data
;
attri
.
set_output_scales
(
mask
,
{
reorder_scale
});
if
(
with_shift
)
{
mkldnn
::
post_ops
post_operations
;
post_operations
.
append_sum
();
attri
.
set_post_ops
(
post_operations
);
std
::
fill
(
output_data
,
output_data
+
output
->
numel
(),
reorder_shift
);
}
auto
src_md
=
platform
::
MKLDNNMemDesc
({
src_tz
},
src_dt
,
src_fmt
);
src_memory
=
std
::
make_shared
<
mkldnn
::
memory
>
(
...
...
@@ -92,6 +116,8 @@ class DeQuantOpKernel : public framework::OpKernel<T> {
dst_memory
=
std
::
static_pointer_cast
<
mkldnn
::
memory
>
(
dev_ctx
.
GetBlob
(
key_dst_mem
));
if
(
with_shift
)
std
::
fill
(
output_data
,
output_data
+
output
->
numel
(),
reorder_shift
);
dst_memory
->
set_data_handle
(
output
->
mutable_data
<
float
>
(
ctx
.
GetPlace
()));
}
...
...
paddle/fluid/operators/mkldnn/quantize_mkldnn_op.cc
浏览文件 @
56050a14
...
...
@@ -36,7 +36,21 @@ class QuantOpKernel : public framework::OpKernel<T> {
void
Compute
(
const
framework
::
ExecutionContext
&
ctx
)
const
override
{
auto
*
input
=
ctx
.
Input
<
Tensor
>
(
"Input"
);
auto
scale_data
=
ctx
.
Attr
<
float
>
(
"Scale"
);
auto
scale_shift
=
ctx
.
Attr
<
float
>
(
"Shift"
);
bool
with_shift
=
scale_shift
!=
0.0
f
;
auto
*
output
=
ctx
.
Output
<
Tensor
>
(
"Output"
);
PADDLE_ENFORCE_NE
(
scale_data
,
0.0
f
,
platform
::
errors
::
InvalidArgument
(
"Quantization scale cannot be 0.0"
));
PADDLE_ENFORCE_GE
(
scale_shift
,
0
,
platform
::
errors
::
Unimplemented
(
"Quantization shift must be nonnegative."
));
PADDLE_ENFORCE_LE
(
scale_shift
,
255
,
platform
::
errors
::
Unimplemented
(
"Quantization shift must be less than or equal to 255."
));
auto
&
dev_ctx
=
ctx
.
template
device_context
<
platform
::
MKLDNNDeviceContext
>();
const
auto
&
engine
=
dev_ctx
.
GetEngine
();
...
...
@@ -47,11 +61,12 @@ class QuantOpKernel : public framework::OpKernel<T> {
const
T
*
input_data
=
input
->
data
<
T
>
();
bool
is_negative
=
ctx
.
Attr
<
bool
>
(
"is_negative_input"
);
bool
is_negative
_input
=
ctx
.
Attr
<
bool
>
(
"is_negative_input"
);
bool
bfloat16
=
ctx
.
Attr
<
bool
>
(
"bfloat16"
);
std
::
string
key
=
platform
::
CreateKey
(
platform
::
ThreadIDasStr
(),
src_tz
,
scale_data
,
is_negative
,
ctx
.
OutputName
(
"Output"
));
std
::
string
key
=
platform
::
CreateKey
(
platform
::
ThreadIDasStr
(),
src_tz
,
scale_data
,
scale_shift
,
is_negative_input
,
ctx
.
OutputName
(
"Output"
));
const
std
::
string
key_prim
=
key
+
"@r"
;
const
std
::
string
key_src_mem
=
key
+
"@s"
;
const
std
::
string
key_dst_mem
=
key
+
"@d"
;
...
...
@@ -69,6 +84,15 @@ class QuantOpKernel : public framework::OpKernel<T> {
int
mask
=
0
;
attri
.
set_output_scales
(
mask
,
{
scale_data
});
if
(
with_shift
)
{
mkldnn
::
post_ops
post_operations
;
post_operations
.
append_sum
();
attri
.
set_post_ops
(
post_operations
);
uint8_t
*
output_data
=
output
->
mutable_data
<
uint8_t
>
(
ctx
.
GetPlace
());
// memset casts scale_shift to unsigned char (uint8_t) internally
std
::
memset
(
output_data
,
scale_shift
,
output
->
numel
());
}
auto
src_md
=
platform
::
MKLDNNMemDesc
({
src_tz
},
memory
::
data_type
::
f32
,
input
->
format
());
src_memory
=
std
::
make_shared
<
mkldnn
::
memory
>
(
...
...
@@ -78,7 +102,7 @@ class QuantOpKernel : public framework::OpKernel<T> {
if
(
bfloat16
)
{
platform
::
SetDstMemoryQuantized
<
paddle
::
platform
::
bfloat16
>
(
ctx
,
output
,
dst_tz
,
engine
,
dst_md
,
dst_memory
,
out_format
);
}
else
if
(
is_negative
)
{
}
else
if
(
is_negative
_input
&&
!
with_shift
)
{
platform
::
SetDstMemoryQuantized
<
int8_t
>
(
ctx
,
output
,
dst_tz
,
engine
,
dst_md
,
dst_memory
,
out_format
);
}
else
{
...
...
@@ -104,10 +128,13 @@ class QuantOpKernel : public framework::OpKernel<T> {
if
(
bfloat16
)
{
dst_memory
->
set_data_handle
(
output
->
mutable_data
<
paddle
::
platform
::
bfloat16
>
(
place
));
}
else
if
(
is_negative
)
{
dst_memory
->
set_data_handle
(
output
->
mutable_data
<
int8_t
>
(
place
));
}
else
if
(
with_shift
||
!
is_negative_input
)
{
uint8_t
*
output_data
=
output
->
mutable_data
<
uint8_t
>
(
ctx
.
GetPlace
());
if
(
with_shift
)
std
::
memset
(
output_data
,
scale_shift
,
output
->
numel
());
dst_memory
->
set_data_handle
(
output_data
);
}
else
{
dst_memory
->
set_data_handle
(
output
->
mutable_data
<
uint8_t
>
(
place
));
dst_memory
->
set_data_handle
(
output
->
mutable_data
<
int8_t
>
(
ctx
.
GetPlace
()));
}
}
...
...
paddle/fluid/operators/mkldnn/requantize_mkldnn_op.cc
浏览文件 @
56050a14
...
...
@@ -26,20 +26,45 @@ using dnnl::reorder;
using
platform
::
to_void_cast
;
using
Tensor
=
framework
::
Tensor
;
namespace
{
inline
uint8_t
clip_to_uint8
(
float
x
)
{
return
std
::
max
(
0L
,
std
::
min
(
255L
,
std
::
lround
(
x
)));
}
}
// namespace
template
<
typename
T
>
class
ReQuantOpKernel
:
public
framework
::
OpKernel
<
T
>
{
public:
void
Compute
(
const
framework
::
ExecutionContext
&
ctx
)
const
override
{
auto
*
input
=
ctx
.
Input
<
Tensor
>
(
"Input"
);
auto
scale_in
=
ctx
.
Attr
<
float
>
(
"Scale_in"
);
auto
shift_in
=
ctx
.
Attr
<
float
>
(
"Shift_in"
);
auto
scale_out
=
ctx
.
Attr
<
float
>
(
"Scale_out"
);
auto
shift_out
=
ctx
.
Attr
<
float
>
(
"Shift_out"
);
bool
with_shift
=
shift_in
!=
0.0
f
||
shift_out
!=
0.0
f
;
auto
*
output
=
ctx
.
Output
<
Tensor
>
(
"Output"
);
PADDLE_ENFORCE_NE
(
scale_in
,
0.0
f
,
platform
::
errors
::
InvalidArgument
(
"Scale of input cannot be 0.0"
));
PADDLE_ENFORCE_NE
(
scale_out
,
0.0
f
,
platform
::
errors
::
InvalidArgument
(
"Scale of output cannot be 0.0"
));
if
(
shift_in
!=
0.0
f
)
{
PADDLE_ENFORCE_EQ
(
input
->
type
(),
framework
::
proto
::
VarType
::
UINT8
,
platform
::
errors
::
Unimplemented
(
"Requantize does not support nonzero "
"shift for signed input."
));
}
auto
&
dev_ctx
=
ctx
.
template
device_context
<
platform
::
MKLDNNDeviceContext
>();
const
auto
&
engine
=
dev_ctx
.
GetEngine
();
auto
src_tz
=
paddle
::
framework
::
vectorize
(
input
->
dims
());
float
reorder_scale
=
scale_out
/
scale_in
;
std
::
string
key
=
platform
::
CreateKey
(
platform
::
ThreadIDasStr
(),
src_tz
,
scale_in
,
scale_out
,
ctx
.
OutputName
(
"Output"
));
...
...
@@ -53,28 +78,37 @@ class ReQuantOpKernel : public framework::OpKernel<T> {
reorder_p
=
std
::
static_pointer_cast
<
reorder
>
(
dev_ctx
.
GetBlob
(
key_prim
));
const
T
*
input_data
=
input
->
data
<
T
>
();
T
*
output_data
=
output
->
mutable_data
<
T
>
(
ctx
.
GetPlace
());
if
(
reorder_p
==
nullptr
)
{
dnnl
::
primitive_attr
attri
;
int
mask
=
0
;
float
scale_shift
=
scale_out
/
scale_in
;
attri
.
set_output_scales
(
mask
,
{
scale_shift
});
auto
dst_tz
=
paddle
::
framework
::
vectorize
(
output
->
dims
());
dnnl
::
memory
::
data_type
src_dt
=
paddle
::
framework
::
ToMKLDNNDataType
(
input
->
type
());
dnnl
::
memory
::
data_type
dst_dt
=
src_dt
;
auto
dst_tz
=
framework
::
vectorize
(
output
->
dims
());
auto
src_dt
=
framework
::
ToMKLDNNDataType
(
input
->
type
());
auto
dst_dt
=
with_shift
?
framework
::
MKLDNNDataType
::
u8
:
src_dt
;
auto
src_md
=
platform
::
MKLDNNMemDesc
({
src_tz
},
src_dt
,
MKLDNNMemoryFormat
::
nhwc
);
src_memory
=
std
::
make_shared
<
dnnl
::
memory
>
(
src_md
,
engine
,
to_void_cast
<
T
>
(
input_data
));
auto
dst_md
=
platform
::
MKLDNNMemDesc
({
dst_tz
},
dst_dt
,
MKLDNNMemoryFormat
::
nhwc
);
dst_memory
=
std
::
make_shared
<
dnnl
::
memory
>
(
dst_md
,
engine
,
to_void_cast
<
T
>
(
output_data
));
dnnl
::
primitive_attr
attri
;
int
mask
=
0
;
attri
.
set_output_scales
(
mask
,
{
reorder_scale
});
if
(
with_shift
)
{
mkldnn
::
post_ops
post_operations
;
post_operations
.
append_sum
();
attri
.
set_post_ops
(
post_operations
);
uint8_t
*
output_data
=
output
->
mutable_data
<
uint8_t
>
(
ctx
.
GetPlace
());
uint8_t
reorder_shift
=
clip_to_uint8
(
shift_out
-
reorder_scale
*
shift_in
);
std
::
memset
(
output_data
,
reorder_shift
,
output
->
numel
());
dst_memory
=
std
::
make_shared
<
dnnl
::
memory
>
(
dst_md
,
engine
,
to_void_cast
<
uint8_t
>
(
output_data
));
}
else
{
T
*
output_data
=
output
->
mutable_data
<
T
>
(
ctx
.
GetPlace
());
dst_memory
=
std
::
make_shared
<
dnnl
::
memory
>
(
dst_md
,
engine
,
to_void_cast
<
T
>
(
output_data
));
}
auto
reorder_pd
=
reorder
::
primitive_desc
(
*
src_memory
,
*
dst_memory
,
attri
);
...
...
@@ -90,7 +124,17 @@ class ReQuantOpKernel : public framework::OpKernel<T> {
dst_memory
=
std
::
static_pointer_cast
<
dnnl
::
memory
>
(
dev_ctx
.
GetBlob
(
key_dst_mem
));
dst_memory
->
set_data_handle
(
output_data
);
if
(
with_shift
)
{
uint8_t
*
output_data
=
output
->
mutable_data
<
uint8_t
>
(
ctx
.
GetPlace
());
uint8_t
reorder_shift
=
clip_to_uint8
(
shift_out
-
reorder_scale
*
shift_in
);
std
::
memset
(
output_data
,
reorder_shift
,
output
->
numel
());
dst_memory
->
set_data_handle
(
output_data
);
}
else
{
T
*
output_data
=
output
->
mutable_data
<
T
>
(
ctx
.
GetPlace
());
dst_memory
->
set_data_handle
(
output_data
);
}
}
dnnl
::
stream
astream
(
engine
);
...
...
paddle/fluid/operators/quantize_op.cc
浏览文件 @
56050a14
...
...
@@ -31,12 +31,16 @@ framework::OpKernelType QuantOp::GetExpectedKernelType(
}
void
QuantOpMaker
::
Make
()
{
AddInput
(
"Input"
,
"
i
nput data"
);
AddOutput
(
"Output"
,
"
o
utput data"
);
AddInput
(
"Input"
,
"
I
nput data"
);
AddOutput
(
"Output"
,
"
O
utput data"
);
AddAttr
<
bool
>
(
"is_negative_input"
,
"(bool, default false) Only used in mkldnn INT8 kernel"
)
.
SetDefault
(
false
);
AddAttr
<
float
>
(
"Scale"
,
"scale data"
).
SetDefault
({
1.0
f
});
AddAttr
<
float
>
(
"Scale"
,
"Scale data"
).
SetDefault
({
1.0
f
});
AddAttr
<
float
>
(
"Shift"
,
"Shift data. When Shift is non-zero, data is quantized to unsigned int8."
)
.
SetDefault
({
0.0
f
});
AddAttr
<
std
::
string
>
(
"output_format"
,
"Convert format to NHWC or NCHW during quantization."
)
.
SetDefault
(
"NHWC"
);
...
...
paddle/fluid/operators/requantize_op.cc
浏览文件 @
56050a14
...
...
@@ -31,10 +31,12 @@ framework::OpKernelType ReQuantOp::GetExpectedKernelType(
}
void
ReQuantOpMaker
::
Make
()
{
AddInput
(
"Input"
,
"input data"
);
AddOutput
(
"Output"
,
"output data"
);
AddAttr
<
float
>
(
"Scale_in"
,
"scale in data"
).
SetDefault
({
1.0
f
});
AddAttr
<
float
>
(
"Scale_out"
,
"scale out data"
).
SetDefault
({
1.0
f
});
AddInput
(
"Input"
,
"Input data"
);
AddOutput
(
"Output"
,
"Output data"
);
AddAttr
<
float
>
(
"Scale_in"
,
"Scale in data"
).
SetDefault
({
1.0
f
});
AddAttr
<
float
>
(
"Scale_out"
,
"Scale out data"
).
SetDefault
({
1.0
f
});
AddAttr
<
float
>
(
"Shift_in"
,
"Shift in data"
).
SetDefault
({
1.0
f
});
AddAttr
<
float
>
(
"Shift_out"
,
"Shift out data"
).
SetDefault
({
1.0
f
});
AddComment
(
R"DOC(This op will re-quantize data from INT8 with scale_in to INT8 with scale_out)DOC"
);
}
...
...
python/paddle/fluid/tests/unittests/mkldnn/test_dequantize_mkldnn_op.py
浏览文件 @
56050a14
...
...
@@ -22,37 +22,60 @@ from paddle.fluid.tests.unittests.op_test import OpTest
class
TestDeQuantizeOp
(
OpTest
):
def
setUp
(
self
):
self
.
op_type
=
'dequantize'
self
.
scale
=
2.0
self
.
input_size
=
[
1
,
1
,
5
,
5
]
#Naive nChw16c
self
.
scale
=
127.0
self
.
shift
=
0.0
self
.
input_size
=
[
1
,
1
,
5
,
5
]
# Naive nChw16c
self
.
data_type
=
'int8'
self
.
set_scale
()
self
.
set_shift
()
self
.
set_data_type
()
self
.
set_input_size
()
self
.
prepare_input
()
self
.
prepare_output
()
def
prepare_input
(
self
):
if
self
.
data_type
==
'int8'
:
input
=
(
np
.
random
.
randint
(
0
,
100
,
self
.
input_size
)
-
50
).
astype
(
self
.
data_type
)
output
=
(
input
*
(
1
/
self
.
scale
)).
astype
(
'float'
)
# input data values are integers from interval [-128, 128)
self
.
input
=
(
np
.
random
.
randint
(
0
,
256
,
self
.
input_size
)
-
128
).
astype
(
self
.
data_type
)
else
:
input
=
(
np
.
random
.
randint
(
0
,
100
,
self
.
input_size
)).
astype
(
self
.
data_type
)
output
=
(
input
*
(
1
/
self
.
scale
)).
astype
(
'float'
)
# input data values are integers from interval [0, 256)
self
.
input
=
(
np
.
random
.
randint
(
0
,
256
,
self
.
input_size
)).
astype
(
self
.
data_type
)
self
.
inputs
=
{
'Input'
:
OpTest
.
np_dtype_to_fluid_dtype
(
input
)}
self
.
inputs
=
{
'Input'
:
OpTest
.
np_dtype_to_fluid_dtype
(
self
.
input
)}
self
.
attrs
=
{
'Scale'
:
self
.
scale
,
'Shift'
:
self
.
shift
}
def
prepare_output
(
self
):
output
=
(
self
.
input
/
self
.
scale
-
(
self
.
shift
/
self
.
scale
)).
astype
(
'float'
)
self
.
outputs
=
{
'Output'
:
output
}
self
.
attrs
=
{
'Scale'
:
self
.
scale
,
}
def
test_check_output
(
self
):
# TODO(wangzhongpu): support mkldnn op in dygraph mode
self
.
check_output
(
check_dygraph
=
False
)
def
check_raise_error
(
self
,
msg
):
try
:
self
.
check_output
()
except
Exception
as
e
:
if
msg
in
str
(
e
):
raise
AttributeError
else
:
print
(
e
)
def
set_scale
(
self
):
pass
def
set_shift
(
self
):
pass
def
set_data_type
(
OpTest
):
pass
def
set_input_size
(
self
):
pass
class
TestDeQuantizeOp1
(
TestDeQuantizeOp
):
def
set_scale
(
self
):
...
...
@@ -70,5 +93,95 @@ class TestDeQuantizeOp2(TestDeQuantizeOp):
self
.
data_type
=
'uint8'
class
TestDeQuantizeOp_ZeroScale
(
TestDeQuantizeOp
):
def
set_scale
(
self
):
self
.
scale
=
0.0
def
prepare_output
(
self
):
self
.
output
=
np
.
zeros
(
self
.
input_size
)
self
.
outputs
=
{
'Output'
:
self
.
output
}
def
test_check_output
(
self
):
self
.
assertRaises
(
AttributeError
,
self
.
check_raise_error
,
'Dequantization scale cannot be 0.0'
)
# 2-dim input
# P - positive input, with shift
class
TestDeQuantizeOpShift_2_P
(
TestDeQuantizeOp
):
def
set_data_type
(
self
):
self
.
data_type
=
'uint8'
def
set_scale
(
self
):
self
.
scale
=
255.0
def
set_shift
(
self
):
self
.
shift
=
128.0
def
set_input_size
(
self
):
self
.
input_size
=
[
2
,
3
]
# 2-dim input
# N - negative input, with shift
class
TestDeQuantizeOpShift_2_N
(
TestDeQuantizeOpShift_2_P
):
def
set_data_type
(
self
):
self
.
data_type
=
'int8'
def
set_scale
(
self
):
self
.
scale
=
127.0
def
set_shift
(
self
):
self
.
shift
=
10.0
def
set_input_size
(
self
):
self
.
input_size
=
[
2
,
3
]
# 3-dim input
class
TestDeQuantizeOpShift_3_P
(
TestDeQuantizeOpShift_2_P
):
def
set_input_size
(
self
):
self
.
input_size
=
[
2
,
3
,
4
]
class
TestDeQuantizeOpShift_3_N
(
TestDeQuantizeOpShift_2_N
):
def
set_input_size
(
self
):
self
.
input_size
=
[
2
,
3
,
4
]
# 4-dim input
class
TestDeQuantizeOpShift_4_P
(
TestDeQuantizeOpShift_2_P
):
def
set_input_size
(
self
):
self
.
input_size
=
[
2
,
3
,
4
,
5
]
class
TestDeQuantizeOpShift_4_N
(
TestDeQuantizeOpShift_2_N
):
def
set_input_size
(
self
):
self
.
input_size
=
[
2
,
3
,
4
,
5
]
class
TestDeQuantizeOp_NegativeShift
(
TestDeQuantizeOp
):
def
set_shift
(
self
):
self
.
shift
=
-
10.0
def
prepare_output
(
self
):
self
.
output
=
np
.
zeros
(
self
.
input_size
)
self
.
outputs
=
{
'Output'
:
self
.
output
}
def
test_check_output
(
self
):
self
.
assertRaises
(
AttributeError
,
self
.
check_raise_error
,
'Dequantization shift must be nonnegative.'
)
class
TestDeQuantizeOp_TooBigShift
(
TestDeQuantizeOp_NegativeShift
):
def
set_shift
(
self
):
self
.
shift
=
300.0
def
test_check_output
(
self
):
self
.
assertRaises
(
AttributeError
,
self
.
check_raise_error
,
'Dequantization shift must be less than or equal to 255.'
)
if
__name__
==
'__main__'
:
unittest
.
main
()
python/paddle/fluid/tests/unittests/mkldnn/test_quantize_mkldnn_op.py
浏览文件 @
56050a14
...
...
@@ -22,44 +22,75 @@ from paddle.fluid.tests.unittests.op_test import OpTest
class
TestQuantizeOp
(
OpTest
):
def
setUp
(
self
):
self
.
op_type
=
'quantize'
self
.
scale
=
2.0
self
.
input_size
=
[
1
,
1
,
5
,
5
]
#Naive nChw16c
self
.
scale
=
255.0
self
.
shift
=
0.0
self
.
input_size
=
[
1
,
1
,
5
,
5
]
# Naive nChw16c
self
.
is_negative
=
False
self
.
output_format
=
'NCHW'
self
.
set_scale
()
self
.
set_shift
()
self
.
set_is_negative
()
self
.
set_input_size
()
self
.
set_output_format
()
self
.
prepare_input
()
self
.
prepare_output
()
def
prepare_input
(
self
):
if
self
.
is_negative
:
input
=
(
100
*
np
.
random
.
random_sample
(
self
.
input_size
)
-
50
).
astype
(
'float32'
)
output
=
np
.
round
(
input
*
self
.
scale
).
astype
(
'int8
'
)
# input data values are from interval [-1.0, 1.0)
self
.
input
=
(
2
*
np
.
random
.
random_sample
(
self
.
input_size
)
-
1
).
astype
(
'float32
'
)
else
:
input
=
(
100
*
np
.
random
.
random_sample
(
self
.
input_size
)).
astype
(
'float32'
)
output
=
np
.
round
(
input
*
self
.
scale
).
astype
(
'uint8'
)
self
.
inputs
=
{
'Input'
:
OpTest
.
np_dtype_to_fluid_dtype
(
input
)}
self
.
outputs
=
{
'Output'
:
output
}
# input data values are from interval [0.0, 1.0)
self
.
input
=
(
np
.
random
.
random_sample
(
self
.
input_size
)).
astype
(
'float32'
)
self
.
inputs
=
{
'Input'
:
OpTest
.
np_dtype_to_fluid_dtype
(
self
.
input
)}
self
.
attrs
=
{
'Scale'
:
self
.
scale
,
'is_negative_input'
:
self
.
is_negative
'Shift'
:
self
.
shift
,
'is_negative_input'
:
self
.
is_negative
,
'output_format'
:
self
.
output_format
}
def
prepare_output
(
self
):
input_data_type
=
'int8'
if
self
.
is_negative
else
'uint8'
output
=
np
.
rint
(
self
.
input
*
self
.
scale
+
self
.
shift
).
astype
(
input_data_type
)
self
.
outputs
=
{
'Output'
:
output
}
def
test_check_output
(
self
):
# TODO(wangzhongpu): support mkldnn op in dygraph mode
self
.
check_output
(
check_dygraph
=
False
)
def
check_raise_error
(
self
,
msg
):
try
:
self
.
check_output
()
except
Exception
as
e
:
if
msg
in
str
(
e
):
raise
AttributeError
else
:
print
(
e
)
def
set_scale
(
self
):
pass
def
set_shift
(
self
):
pass
def
set_is_negative
(
self
):
pass
def
set_input_size
(
self
):
pass
def
set_output_format
(
self
):
pass
class
TestQuantizeOp1
(
TestQuantizeOp
):
def
set_scale
(
self
):
self
.
scale
=
1
.5
self
.
scale
=
1
27.0
def
set_is_negative
(
self
):
self
.
is_nagative
=
True
...
...
@@ -67,11 +98,137 @@ class TestQuantizeOp1(TestQuantizeOp):
class
TestQuantizeOp2
(
TestQuantizeOp
):
def
set_scale
(
self
):
self
.
scale
=
0.1
self
.
scale
=
255.0
def
set_is_negative
(
self
):
self
.
is_nagative
=
False
class
TestQuantizeOp_ZeroScale
(
TestQuantizeOp
):
def
set_scale
(
self
):
self
.
scale
=
0.0
def
prepare_output
(
self
):
self
.
output
=
np
.
zeros
(
self
.
input_size
)
self
.
outputs
=
{
'Output'
:
self
.
output
}
def
test_check_output
(
self
):
self
.
assertRaises
(
AttributeError
,
self
.
check_raise_error
,
'Quantization scale cannot be 0.0'
)
# 2-dim input
# P - positive input
class
TestQuantizeOpShift_NCHW_2_P
(
TestQuantizeOp
):
def
set_output_format
(
self
):
self
.
output_format
=
'NCHW'
def
set_is_negative
(
self
):
self
.
is_nagative
=
False
def
set_scale
(
self
):
self
.
scale
=
255.0
def
set_shift
(
self
):
self
.
shift
=
0.0
def
set_input_size
(
self
):
self
.
input_size
=
[
2
,
3
]
# 2-dim input
# N - negative input
class
TestQuantizeOpShift_NCHW_2_N
(
TestQuantizeOpShift_NCHW_2_P
):
def
set_is_negative
(
self
):
self
.
is_nagative
=
True
def
set_scale
(
self
):
self
.
scale
=
127.0
def
set_shift
(
self
):
self
.
shift
=
128.0
class
TestQuantizeOpShift_NHWC_2_P
(
TestQuantizeOpShift_NCHW_2_P
):
def
set_output_format
(
self
):
self
.
output_format
=
'NHWC'
class
TestQuantizeOpShift_NHWC_2_N
(
TestQuantizeOpShift_NCHW_2_N
):
def
set_output_format
(
self
):
self
.
output_format
=
'NHWC'
# 3-dim input
class
TestQuantizeOpShift_NCHW_3_P
(
TestQuantizeOpShift_NCHW_2_P
):
def
set_input_size
(
self
):
self
.
input_size
=
[
2
,
3
,
4
]
class
TestQuantizeOpShift_NCHW_3_N
(
TestQuantizeOpShift_NCHW_2_N
):
def
set_input_size
(
self
):
self
.
input_size
=
[
2
,
3
,
4
]
class
TestQuantizeOpShift_NHWC_3_P
(
TestQuantizeOpShift_NCHW_3_P
):
def
set_output_format
(
self
):
self
.
output_format
=
'NHWC'
class
TestQuantizeOpShift_NHWC_3_N
(
TestQuantizeOpShift_NCHW_3_N
):
def
set_output_format
(
self
):
self
.
output_format
=
'NHWC'
# 4-dim input
class
TestQuantizeOpShift_NCHW_4_P
(
TestQuantizeOpShift_NCHW_2_P
):
def
set_input_size
(
self
):
self
.
input_size
=
[
2
,
3
,
4
,
5
]
class
TestQuantizeOpShift_NCHW_4_N
(
TestQuantizeOpShift_NCHW_2_N
):
def
set_input_size
(
self
):
self
.
input_size
=
[
2
,
3
,
4
,
5
]
class
TestQuantizeOpShift_NHWC_4_P
(
TestQuantizeOpShift_NCHW_4_P
):
def
set_output_format
(
self
):
self
.
output_format
=
'NHWC'
class
TestQuantizeOpShift_NHWC_4_N
(
TestQuantizeOpShift_NCHW_4_N
):
def
set_output_format
(
self
):
self
.
output_format
=
'NHWC'
class
TestQuantizeOp_NegativeShift
(
TestQuantizeOp
):
def
set_is_negative
(
self
):
self
.
is_nagative
=
False
def
set_scale
(
self
):
self
.
scale
=
100.0
def
set_shift
(
self
):
self
.
shift
=
-
10.0
def
prepare_output
(
self
):
self
.
output
=
np
.
zeros
(
self
.
input_size
)
self
.
outputs
=
{
'Output'
:
self
.
output
}
def
test_check_output
(
self
):
self
.
assertRaises
(
AttributeError
,
self
.
check_raise_error
,
'Quantization shift must be nonnegative.'
)
class
TestQuantizeOp_TooBigShift
(
TestQuantizeOp_NegativeShift
):
def
set_shift
(
self
):
self
.
shift
=
300.0
def
test_check_output
(
self
):
self
.
assertRaises
(
AttributeError
,
self
.
check_raise_error
,
'Quantization shift must be less than or equal to 255.'
)
if
__name__
==
'__main__'
:
unittest
.
main
()
python/paddle/fluid/tests/unittests/mkldnn/test_requantize_mkldnn_op.py
浏览文件 @
56050a14
...
...
@@ -25,88 +25,271 @@ from mkldnn_op_test import format_reorder
class
TestReQuantizeOp
(
OpTest
):
def
setUp
(
self
):
self
.
op_type
=
'requantize'
self
.
scale_in
=
2.0
self
.
scale_out
=
1.5
self
.
scale_in
=
127.0
self
.
shift_in
=
0.0
self
.
scale_out
=
100.0
self
.
shift_out
=
0.0
self
.
input_size
=
[
1
,
1
,
10
,
10
]
self
.
data_type
=
'int8'
self
.
set_scale
()
self
.
set_data_type
()
self
.
prepare_inputs
()
def
prepare_inputs
(
self
):
scale_shift
=
self
.
scale_out
/
self
.
scale_in
if
self
.
data_type
==
'int8'
:
self
.
input
=
(
np
.
random
.
randint
(
0
,
100
,
self
.
input_size
)
-
50
).
astype
(
self
.
data_type
)
output_tmp
=
np
.
round
(
self
.
input
.
astype
(
'float32'
)
*
scale_shift
).
astype
(
'int8'
)
self
.
input_data_type
=
'int8'
self
.
set_scales
()
self
.
set_shifts
()
self
.
set_input_data_type
()
self
.
prepare_input
()
self
.
prepare_output
()
def
prepare_input
(
self
):
if
self
.
input_data_type
==
'int8'
:
# input data values are integers from interval [-128, 128)
self
.
input
=
(
np
.
random
.
randint
(
0
,
256
,
self
.
input_size
)
-
128
).
astype
(
self
.
input_data_type
)
else
:
# input data values are integers from interval [0, 256)
self
.
input
=
(
np
.
random
.
randint
(
0
,
100
,
self
.
input_size
)).
astype
(
self
.
data_type
)
output_tmp
=
np
.
round
(
self
.
input
.
astype
(
'float32'
)
*
scale_shift
).
astype
(
'uint8'
)
self
.
output
=
format_reorder
(
output_tmp
,
self
.
input_size
)
0
,
256
,
self
.
input_size
)).
astype
(
self
.
input_data_type
)
self
.
inputs
=
{
'Input'
:
OpTest
.
np_dtype_to_fluid_dtype
(
self
.
input
)}
self
.
attrs
=
{
'Scale_in'
:
self
.
scale_in
,
'Scale_out'
:
self
.
scale_out
,
'Shift_in'
:
self
.
shift_in
,
'Shift_out'
:
self
.
shift_out
}
self
.
outputs
=
{
'Output'
:
self
.
output
}
def
prepare_output
(
self
):
scale_ratio
=
self
.
scale_out
/
self
.
scale_in
with_shift
=
(
self
.
shift_in
!=
0.0
or
self
.
shift_out
!=
0.0
)
if
with_shift
or
self
.
input_data_type
==
'uint8'
:
dst_type
=
'uint8'
type_min
=
0
type_max
=
255
new_shift
=
np
.
clip
(
np
.
rint
(
self
.
shift_out
-
scale_ratio
*
self
.
shift_in
),
type_min
,
type_max
)
else
:
dst_type
=
'int8'
type_min
=
-
128
type_max
=
127
new_shift
=
0
self
.
attrs
=
{
'Scale_in'
:
self
.
scale_in
,
'Scale_out'
:
self
.
scale_out
}
output_tmp
=
np
.
clip
(
np
.
rint
(
self
.
input
.
astype
(
'float32'
)
*
scale_ratio
+
new_shift
),
type_min
,
type_max
).
astype
(
dst_type
)
self
.
output
=
format_reorder
(
output_tmp
,
self
.
input_size
)
self
.
outputs
=
{
'Output'
:
self
.
output
}
def
test_check_output
(
self
):
# TODO(wangzhongpu): support mkldnn op in dygraph mode
self
.
assertTrue
(
self
.
input_data_type
==
'uint8'
or
self
.
shift_in
==
0.0
,
'Input data must be unsigned if it has nonzero shift.'
)
self
.
check_output
(
check_dygraph
=
False
)
def
set_scale
(
self
):
def
check_raise_error
(
self
,
msg
):
try
:
self
.
check_output
()
except
Exception
as
e
:
if
msg
in
str
(
e
):
raise
AttributeError
else
:
print
(
e
)
def
set_scales
(
self
):
pass
def
set_
data_type
(
OpTest
):
def
set_
shifts
(
self
):
pass
def
set_input_data_type
(
OpTest
):
pass
# ---------------test requantize with s8 input, no shift--------------------
#--------------------test requantize with s8 input--------------------
class
TestReQuantizeOp_S8_SameScales
(
TestReQuantizeOp
):
def
set_scales
(
self
):
self
.
scale_in
=
127.0
self
.
scale_out
=
127.0
class
TestReQuantizeOp1
(
TestReQuantizeOp
):
def
set_scale
(
self
):
self
.
scale_in
=
1
.5
self
.
scale_out
=
1
.5
class
TestReQuantizeOp
_S8_DifferentScales_
1
(
TestReQuantizeOp
):
def
set_scale
s
(
self
):
self
.
scale_in
=
1
27.0
self
.
scale_out
=
1
00.0
class
TestReQuantizeOp2
(
TestReQuantizeOp
):
def
set_scale
(
self
):
self
.
scale_in
=
0.1
self
.
scale_out
=
0.2
class
TestReQuantizeOp
_S8_DifferentScales_
2
(
TestReQuantizeOp
):
def
set_scale
s
(
self
):
self
.
scale_in
=
100.0
self
.
scale_out
=
127.0
#--------------------test requantize with u8 input--------------------
class
TestReQuantizeOp_S8_ZeroInputScale
(
TestReQuantizeOp
):
def
set_scales
(
self
):
self
.
scale_in
=
0.0
self
.
scale_out
=
127.0
def
prepare_output
(
self
):
self
.
output
=
np
.
zeros
(
self
.
input_size
)
self
.
outputs
=
{
'Output'
:
self
.
output
}
def
test_check_output
(
self
):
self
.
assertRaises
(
AttributeError
,
self
.
check_raise_error
,
'Scale of input cannot be 0.0'
)
class
TestReQuantizeOp3
(
TestReQuantizeOp1
):
def
set_data_type
(
self
):
self
.
data_type
=
'uint8'
class
TestReQuantizeOp_S8_ZeroOutputScale
(
TestReQuantizeOp
):
def
set_scales
(
self
):
self
.
scale_in
=
127.0
self
.
scale_out
=
0.0
class
TestReQuantizeOp4
(
TestReQuantizeOp2
):
def
set_data_type
(
self
):
self
.
data_type
=
'uint8'
def
prepare_output
(
self
):
self
.
output
=
np
.
zeros
(
self
.
input_size
)
self
.
outputs
=
{
'Output'
:
self
.
output
}
def
test_check_output
(
self
):
self
.
assertRaises
(
AttributeError
,
self
.
check_raise_error
,
'Scale of output cannot be 0.0'
)
# ---------------test requantize with u8 input, no shift--------------------
class
TestReQuantizeOp_U8_SameScales
(
TestReQuantizeOp_S8_SameScales
):
def
set_input_data_type
(
self
):
self
.
input_data_type
=
'uint8'
class
TestReQuantizeOp_U8_DifferentScales_1
(
TestReQuantizeOp_S8_DifferentScales_1
):
def
set_input_data_type
(
self
):
self
.
input_data_type
=
'uint8'
class
TestReQuantizeOp_U8_DifferentScales_2
(
TestReQuantizeOp_S8_DifferentScales_2
):
def
set_input_data_type
(
self
):
self
.
input_data_type
=
'uint8'
# ---------------test requantize with s8 input, with shift------------------
class
TestReQuantizeOp_S8_WithShift
(
TestReQuantizeOp
):
def
set_scales
(
self
):
self
.
scale_in
=
60.0
self
.
scale_out
=
127.0
def
set_shifts
(
self
):
self
.
shift_in
=
128.0
self
.
shift_out
=
128.0
def
test_check_output
(
self
):
self
.
assertRaises
(
AttributeError
,
self
.
check_raise_error
,
'Requantize does not support nonzero shift for signed input.'
)
#-------------------test reused requantize op---------------------------
class
TestReQuantizeOp_S8_WithOutputShift
(
TestReQuantizeOp
):
def
set_scales
(
self
):
self
.
scale_in
=
127.0
self
.
scale_out
=
60.0
def
set_shifts
(
self
):
self
.
shift_in
=
0.0
self
.
shift_out
=
120.0
# ---------------test requantize with u8 input, with shift------------------
class
TestReQuantizeOp_U8_SameScales_SameShift
(
TestReQuantizeOp_U8_SameScales
):
def
set_shifts
(
self
):
self
.
shift_in
=
128.0
self
.
shift_out
=
128.0
class
TestReQuantizeOp_U8_SameScales_DifferentShift_1
(
TestReQuantizeOp_U8_SameScales
):
def
set_shifts
(
self
):
self
.
shift_in
=
60.0
self
.
shift_out
=
128.0
class
TestReQuantizeOp_U8_SameScales_DifferentShift_2
(
TestReQuantizeOp_U8_SameScales
):
def
set_shifts
(
self
):
self
.
shift_in
=
128.0
self
.
shift_out
=
60.0
class
TestReQuantizeOp_U8_DifferentScales_1_SameShift
(
TestReQuantizeOp_U8_DifferentScales_1
):
def
set_shifts
(
self
):
self
.
shift_in
=
128.0
self
.
shift_out
=
128.0
class
TestReQuantizeOp_U8_DifferentScales_2_SameShift
(
TestReQuantizeOp_U8_DifferentScales_2
):
def
set_shifts
(
self
):
self
.
shift_in
=
128.0
self
.
shift_out
=
128.0
class
TestReQuantizeOp_U8_DifferentScales_1_DifferentShift_1
(
TestReQuantizeOp_U8_DifferentScales_1
):
def
set_shifts
(
self
):
self
.
shift_in
=
128.0
self
.
shift_out
=
60.0
class
TestReQuantizeOp_U8_DifferentScales_2_DifferentShift_1
(
TestReQuantizeOp_U8_DifferentScales_2
):
def
set_shifts
(
self
):
self
.
shift_in
=
128.0
self
.
shift_out
=
60.0
class
TestReQuantizeOp_U8_DifferentScales_1_DifferentShift_2
(
TestReQuantizeOp_U8_DifferentScales_1
):
def
set_shifts
(
self
):
self
.
shift_in
=
60.0
self
.
shift_out
=
128.0
class
TestReQuantizeOp_U8_DifferentScales_2_DifferentShift_2
(
TestReQuantizeOp_U8_DifferentScales_2
):
def
set_shifts
(
self
):
self
.
shift_in
=
60.0
self
.
shift_out
=
128.0
# ---------------test reused requantize op, no shift------------------------
class
TestReQuantizeOpReused
(
TestReQuantizeOp
):
def
setUp
(
self
):
self
.
input_size
=
[
1
,
1
,
10
,
10
]
self
.
data_type
=
'int8'
self
.
set_scale
()
self
.
prepare_inputs
()
def
set_scale
(
self
):
self
.
scale_in
=
0.1
self
.
scale_out
=
0.2
# self.input_size = [1, 1, 10, 10]
self
.
input_size
=
[
1
,
1
,
2
,
2
]
self
.
input_data_type
=
'int8'
self
.
set_scales
()
self
.
set_shifts
()
self
.
set_input_data_type
()
self
.
prepare_input
()
self
.
prepare_output
()
def
set_scales
(
self
):
self
.
scale_in
=
100.0
self
.
scale_out
=
120.0
def
set_shifts
(
self
):
self
.
shift_in
=
0.0
self
.
shift_out
=
0.0
def
set_input_data_type
(
self
):
pass
def
test_check_output
(
self
):
variables
=
{
...
...
@@ -119,12 +302,16 @@ class TestReQuantizeOpReused(TestReQuantizeOp):
for
name
in
variables
:
block
.
create_var
(
name
=
name
,
dtype
=
"int8"
,
shape
=
variables
[
name
].
shape
)
requant_op
=
block
.
append_op
(
block
.
append_op
(
type
=
"requantize"
,
inputs
=
{
'Input'
:
block
.
var
(
'input'
),
},
outputs
=
{
"Output"
:
block
.
var
(
'output'
)},
attrs
=
{
'Scale_in'
:
self
.
scale_in
,
'Scale_out'
:
self
.
scale_out
})
attrs
=
{
'Scale_in'
:
self
.
scale_in
,
'Scale_out'
:
self
.
scale_out
,
'Shift_in'
:
self
.
shift_in
,
'Shift_out'
:
self
.
shift_out
})
place
=
core
.
CPUPlace
()
exe
=
fluid
.
Executor
(
place
)
for
i
in
range
(
2
):
...
...
@@ -137,5 +324,17 @@ class TestReQuantizeOpReused(TestReQuantizeOp):
variables
[
'output'
],
out
[
0
],
atol
=
1e-4
),
'output'
)
# ---------------test reused requantize op, no shift------------------------
class
TestReQuantizeOpReused_WithShift
(
TestReQuantizeOpReused
):
def
set_input_data_type
(
self
):
self
.
input_data_type
=
'uint8'
def
set_shifts
(
self
):
self
.
shift_in
=
128
self
.
shift_out
=
60
if
__name__
==
'__main__'
:
unittest
.
main
()
tools/codestyle/clang_format.hook
浏览文件 @
56050a14
#!/bin/bash
set
-e
readonly
VERSION
=
"3.
8
"
readonly
VERSION
=
"3.
9
"
version
=
$(
clang-format
-version
)
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录