mirror of
https://github.com/saymrwulf/onnxruntime.git
synced 2026-07-27 20:02:15 +00:00
add dequantize and quantize back to contrib ops (#1712)
This commit is contained in:
parent
e1a12b1760
commit
b2a2326a45
8 changed files with 452 additions and 32 deletions
|
|
@ -158,6 +158,11 @@ class OpKernelContext {
|
|||
*/
|
||||
Fence_t OutputFence(int index) const;
|
||||
|
||||
/**
|
||||
Returns the opset domain of the underlying kernel
|
||||
**/
|
||||
const std::string& GetOpDomain() const;
|
||||
|
||||
protected:
|
||||
onnxruntime::NodeIndex GetNodeIndex() const;
|
||||
|
||||
|
|
|
|||
48
onnxruntime/contrib_ops/cpu/quantize_ops.cc
Normal file
48
onnxruntime/contrib_ops/cpu/quantize_ops.cc
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "core/providers/cpu/tensor/quantize_linear.h"
|
||||
#include "core/providers/common.h"
|
||||
|
||||
|
||||
namespace onnxruntime {
|
||||
namespace contrib {
|
||||
|
||||
ONNX_CPU_OPERATOR_TYPED_MS_KERNEL(
|
||||
DequantizeLinear,
|
||||
1,
|
||||
uint8_t,
|
||||
KernelDefBuilder()
|
||||
.TypeConstraint("axis", DataTypeImpl::GetType<int64_t>())
|
||||
.TypeConstraint("x", DataTypeImpl::GetTensorType<uint8_t>())
|
||||
.TypeConstraint("x_scale", DataTypeImpl::GetTensorType<float>())
|
||||
.TypeConstraint("x_zero_point", DataTypeImpl::GetTensorType<uint8_t>())
|
||||
.TypeConstraint("y", DataTypeImpl::GetTensorType<float>()),
|
||||
DequantizeLinear<uint8_t>);
|
||||
|
||||
ONNX_CPU_OPERATOR_TYPED_MS_KERNEL(
|
||||
DequantizeLinear,
|
||||
1,
|
||||
int8_t,
|
||||
KernelDefBuilder()
|
||||
.TypeConstraint("axis", DataTypeImpl::GetType<int64_t>())
|
||||
.TypeConstraint("x", DataTypeImpl::GetTensorType<int8_t>())
|
||||
.TypeConstraint("x_scale", DataTypeImpl::GetTensorType<float>())
|
||||
.TypeConstraint("x_zero_point", DataTypeImpl::GetTensorType<int8_t>())
|
||||
.TypeConstraint("y", DataTypeImpl::GetTensorType<float>()),
|
||||
DequantizeLinear<int8_t>);
|
||||
|
||||
ONNX_CPU_OPERATOR_TYPED_MS_KERNEL(
|
||||
QuantizeLinear,
|
||||
1,
|
||||
uint8_t,
|
||||
KernelDefBuilder()
|
||||
.TypeConstraint("axis", DataTypeImpl::GetType<int64_t>())
|
||||
.TypeConstraint("x", DataTypeImpl::GetTensorType<float>())
|
||||
.TypeConstraint("y_scale", DataTypeImpl::GetTensorType<float>())
|
||||
.TypeConstraint("y_zero_point", DataTypeImpl::GetTensorType<uint8_t>())
|
||||
.TypeConstraint("y", DataTypeImpl::GetTensorType<uint8_t>()),
|
||||
QuantizeLinear<uint8_t>);
|
||||
|
||||
} // namespace contrib
|
||||
} // namespace onnxruntime
|
||||
|
|
@ -24,6 +24,9 @@ class ONNX_OPERATOR_KERNEL_CLASS_NAME(kCpuExecutionProvider, kMSDomain, 1, Pad);
|
|||
class ONNX_OPERATOR_KERNEL_CLASS_NAME(kCpuExecutionProvider, kMSDomain, 1, Unique);
|
||||
class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kMSDomain, 1, float, ConvTransposeWithDynamicPads);
|
||||
class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kMSDomain, 1, float, CropAndResize);
|
||||
class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kMSDomain, 1, uint8_t, DequantizeLinear);
|
||||
class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kMSDomain, 1, int8_t, DequantizeLinear);
|
||||
class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kMSDomain, 1, uint8_t, QuantizeLinear);
|
||||
|
||||
// This section includes all opkernel declarations for former experimental ops which have now been removed from onnx.
|
||||
// To maintain backward compatibility these are added as contrib ops.
|
||||
|
|
@ -94,6 +97,9 @@ void RegisterCpuContribKernels(KernelRegistry& kernel_registry) {
|
|||
BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kCpuExecutionProvider, kMSDomain, 1, Unique)>,
|
||||
BuildKernelCreateInfo<ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kMSDomain, 1, float, ConvTransposeWithDynamicPads)>,
|
||||
BuildKernelCreateInfo<ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kMSDomain, 1, float, CropAndResize)>,
|
||||
BuildKernelCreateInfo<ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kMSDomain, 1, uint8_t, DequantizeLinear)>,
|
||||
BuildKernelCreateInfo<ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kMSDomain, 1, int8_t, DequantizeLinear)>,
|
||||
BuildKernelCreateInfo<ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kMSDomain, 1, uint8_t, QuantizeLinear)>,
|
||||
|
||||
// These ops were experimental ops in onnx domain which have been removed now. We add them here as
|
||||
// contrib ops to main backward compatibility
|
||||
|
|
|
|||
|
|
@ -126,6 +126,10 @@ onnxruntime::NodeIndex OpKernelContext::GetNodeIndex() const {
|
|||
return kernel_->Node().Index();
|
||||
}
|
||||
|
||||
const std::string& OpKernelContext::GetOpDomain() const {
|
||||
return kernel_->KernelDef().Domain();
|
||||
}
|
||||
|
||||
const OrtValue* OpKernelContext::GetInputMLValue(int index) const {
|
||||
if (index < 0 || index >= InputCount())
|
||||
return nullptr;
|
||||
|
|
|
|||
|
|
@ -1053,6 +1053,119 @@ activation and leaky_relu_alpha.)DOC")
|
|||
ONNX_CONTRIB_OPERATOR_SCHEMA_ELSEWHERE(AttnLSTM, RegisterAttnLSTMContribOpSchema);
|
||||
ONNX_CONTRIB_OPERATOR_SCHEMA_ELSEWHERE(Range, RegisterRangeOpSchema);
|
||||
|
||||
static const char* QuantizeLinear_ver1_doc = R"DOC(
|
||||
The linear quantization operator. It consumes a full precision data, a scale, a zero point and computes the quantized data.
|
||||
The quantization formula is y = (x / y_scale) + y_zero_point. For (x / y_scale), it computes the nearest integer value to arg (in floating-point format),
|
||||
rounding halfway cases away from zero. Scale and zero point must have same shape. They must be either scalar (per tensor) or 1-D tensor (per 'axis').)DOC";
|
||||
|
||||
ONNX_CONTRIB_OPERATOR_SCHEMA(QuantizeLinear)
|
||||
.SetDomain(kMSDomain)
|
||||
.SinceVersion(1)
|
||||
.Attr(
|
||||
"axis",
|
||||
"The axis along which same quantization parameters are applied. It's optional."
|
||||
"If it's not specified, it means per-tensor quantization and input 'x_scale' and 'x_zero_point' must be scalars."
|
||||
"If it's specified, it means per 'axis' quantization and input 'x_scale' and 'x_zero_point' must be 1-D tensors.",
|
||||
AttributeProto::INT,
|
||||
false)
|
||||
.Input(
|
||||
0,
|
||||
"x",
|
||||
"N-D full precision Input tensor to be quantized.",
|
||||
"T1")
|
||||
.Input(
|
||||
1,
|
||||
"y_scale",
|
||||
"Scale for doing quantization to get 'y'. It could be a scalar or a 1-D tensor,"
|
||||
"which means a per-tensor or per-axis quantization. If it's a 1-D tensor, "
|
||||
"its number of elements should be equal to the dimension value of 'axis' dimension of input 'x'.",
|
||||
"T1")
|
||||
.Input(
|
||||
2,
|
||||
"y_zero_point",
|
||||
"Zero point for doing quantization to get 'y'. It could be a scalar or a 1-D tensor, which means a per-tensor"
|
||||
"or per-axis quantization. If it's a 1-D tensor, its number of elements should be equal to the dimension value of 'axis' dimension of input 'x'.",
|
||||
"T2")
|
||||
.Output(
|
||||
0,
|
||||
"y",
|
||||
"N-D quantized output tensor. It has same shape as input 'x'.",
|
||||
"T2")
|
||||
.TypeConstraint(
|
||||
"T1",
|
||||
{"tensor(float)"},
|
||||
"Constrain 'x', 'y_scale' to float tensors.")
|
||||
.TypeConstraint(
|
||||
"T2",
|
||||
{"tensor(int8)", "tensor(uint8)"},
|
||||
"Constrain 'y_zero_point' and 'y' to 8-bit integer tensors.")
|
||||
.SetDoc(QuantizeLinear_ver1_doc)
|
||||
.TypeAndShapeInferenceFunction([](ONNX_NAMESPACE::InferenceContext& ctx) {
|
||||
propagateElemTypeFromInputToOutput(ctx, 2, 0);
|
||||
|
||||
if (!hasInputShape(ctx, 0))
|
||||
return;
|
||||
|
||||
auto& input_shape = getInputShape(ctx, 0);
|
||||
updateOutputShape(ctx, 0, input_shape);
|
||||
});
|
||||
|
||||
static const char* DequantizeLinear_ver1_doc = R"DOC(
|
||||
The linear dequantization operator. It consumes a quantized data, a scale, a zero point and computes the full precision data.
|
||||
The dequantization formula is y = (x - x_zero_point) * x_scale.
|
||||
Scale and zero point must have same shape. They must be either scalar (per tensor) or 1-D tensor (per 'axis').)DOC";
|
||||
|
||||
ONNX_CONTRIB_OPERATOR_SCHEMA(DequantizeLinear)
|
||||
.SetDomain(kMSDomain)
|
||||
.SinceVersion(1)
|
||||
.Attr("axis",
|
||||
"The axis along which same quantization parameters are applied. It's optional."
|
||||
"If it's not specified, it means per-tensor quantization and input 'x_scale' and 'x_zero_point' must be scalars."
|
||||
"If it's specified, it means per 'axis' quantization and input 'x_scale' and 'x_zero_point' must be 1-D tensors.",
|
||||
AttributeProto::INT,
|
||||
false)
|
||||
.Input(0,
|
||||
"x",
|
||||
"N-D quantized Input tensor to be de-quantized.",
|
||||
"T2")
|
||||
.Input(
|
||||
1,
|
||||
"x_scale",
|
||||
"Scale for input 'x'. It could be a scalar or a 1-D tensor, which means a per-tensor or per-axis quantization."
|
||||
"If it's a 1-D tensor, its number of elements should be equal to the dimension value of 'axis' dimension of input 'x'.",
|
||||
"T1")
|
||||
.Input(
|
||||
2,
|
||||
"x_zero_point",
|
||||
"Zero point for input 'x'. It could be a scalar or a 1-D tensor, which means a per-tensor or per-axis quantization."
|
||||
"If it's a 1-D tensor, its number of elements should be equal to the dimension value of 'axis' dimension of input 'x'.",
|
||||
"T2")
|
||||
.Output(
|
||||
0,
|
||||
"y",
|
||||
"N-D full precision output tensor. It has same shape as input 'x'.",
|
||||
"T1")
|
||||
.TypeConstraint(
|
||||
"T1",
|
||||
{"tensor(float)"},
|
||||
"Constrain 'y', 'x_scale' to float tensors.")
|
||||
.TypeConstraint(
|
||||
"T2",
|
||||
{"tensor(int8)", "tensor(uint8)"},
|
||||
"Constrain 'x_zero_point' and 'x' to 8-bit integer tensors.")
|
||||
.SetDoc(DequantizeLinear_ver1_doc)
|
||||
.TypeAndShapeInferenceFunction([](ONNX_NAMESPACE::InferenceContext& ctx) {
|
||||
auto y_type = ctx.getOutputType(0);
|
||||
// only float is supported
|
||||
y_type->mutable_tensor_type()->set_elem_type(ONNX_NAMESPACE::TensorProto::FLOAT);
|
||||
|
||||
if (!hasInputShape(ctx, 0))
|
||||
return;
|
||||
|
||||
auto& input_shape = getInputShape(ctx, 0);
|
||||
updateOutputShape(ctx, 0, input_shape);
|
||||
});
|
||||
|
||||
static const char* Tokenizer_ver1_doc = R"DOC(
|
||||
Tokenizer divides each string in X into a vector of strings along the last axis. Allowed input shapes are [C] and [N, C].
|
||||
If the maximum number of tokens found per input string is D, the output shape would be [N, C, D] when input shape is [N, C].
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
#include "core/providers/cpu/tensor/quantize_linear.h"
|
||||
#include "core/providers/cpu/math/element_wise_ops.h"
|
||||
#include "core/providers/common.h"
|
||||
#include <cmath>
|
||||
#include <cfenv>
|
||||
|
|
@ -38,23 +37,49 @@ Status DequantizeLinear<T>::Compute(OpKernelContext* ctx) const {
|
|||
auto& x_scale = *ctx->Input<Tensor>(1);
|
||||
auto& x_zero_point = *ctx->Input<Tensor>(2);
|
||||
auto& y = *ctx->Output(0, x.Shape());
|
||||
|
||||
const auto& x_shape = x.Shape();
|
||||
const auto& scale_shape = x_scale.Shape();
|
||||
const auto& zero_point_shape = x_zero_point.Shape();
|
||||
|
||||
// enforce that scale and zero point are scalars or 1D tensors with size == 1
|
||||
ORT_ENFORCE(scale_shape.NumDimensions() == 0 || (scale_shape.NumDimensions() == 1 && scale_shape.GetDims().size() == 1), "x_scale must be a scalar.");
|
||||
ORT_ENFORCE(zero_point_shape.NumDimensions() == 0 || (zero_point_shape.NumDimensions() == 1 && zero_point_shape.GetDims().size() == 1), "x_zero_point must be a scalar.");
|
||||
int64_t axis = 0;
|
||||
int64_t broadcastDim = x_shape[axis];
|
||||
size_t stride = 0;
|
||||
|
||||
const T zero_point = *(x_zero_point.template Data<T>());
|
||||
const float scale = *(x_scale.template Data<float>());
|
||||
if (has_axis_) {
|
||||
axis = HandleNegativeAxis(axis_, x_shape.NumDimensions());
|
||||
broadcastDim = x_shape[axis];
|
||||
stride = 1;
|
||||
|
||||
// if an axis was specified, ensure the scale and zero point are compatible
|
||||
ORT_ENFORCE(x_scale.Shape().NumDimensions() == 1 && x_scale.Shape().Size() == broadcastDim, "x_scale must be 1D tensor with size ", broadcastDim);
|
||||
ORT_ENFORCE(x_zero_point.Shape().NumDimensions() == 1 && x_zero_point.Shape().Size() == broadcastDim, "x_zero_point must be 1D tensor with size ", broadcastDim);
|
||||
} else {
|
||||
// if no axis, enforce that scale and zero point are scalars
|
||||
ORT_ENFORCE(IsScalarOr1ElementVector(&x_scale), "x_scale must be a scalar or 1D tensor or size 1.");
|
||||
ORT_ENFORCE(IsScalarOr1ElementVector(&x_zero_point), "x_zero_point must be a scalar or 1D tensor or size 1.");
|
||||
}
|
||||
|
||||
size_t N = x_shape.SizeToDimension(axis);
|
||||
size_t block_size = x_shape.SizeFromDimension(axis + 1);
|
||||
|
||||
const T* zero_point = x_zero_point.template Data<T>();
|
||||
const float* scale = x_scale.template Data<float>();
|
||||
const T* input = x.template Data<T>();
|
||||
auto* output = y.template MutableData<float>();
|
||||
const auto num_of_elements = x_shape.Size();
|
||||
float* output = y.template MutableData<float>();
|
||||
|
||||
for (int i = 0; i < num_of_elements; ++i) {
|
||||
output[i] = static_cast<float>(static_cast<const int>(input[i]) - zero_point) * scale;
|
||||
for (size_t n = 0; n < N; n++) {
|
||||
const float* current_scale = scale;
|
||||
const T* current_zero_point = zero_point;
|
||||
|
||||
for (size_t bd = 0; bd < static_cast<size_t>(broadcastDim); bd++) {
|
||||
auto zp = static_cast<const int>(*current_zero_point);
|
||||
auto sc = *current_scale;
|
||||
|
||||
for (size_t bs = 0; bs < block_size; bs++) {
|
||||
*output++ = static_cast<float>(static_cast<const int>(*input++) - zp) * sc;
|
||||
}
|
||||
|
||||
current_scale += stride;
|
||||
current_zero_point += stride;
|
||||
}
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
|
|
@ -85,7 +110,6 @@ static float RoundHalfToEven(float input) {
|
|||
auto result = std::nearbyintf(input);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
// formula is Y = X / Scale + ZeroPoint
|
||||
Status QuantizeLinear<T>::Compute(OpKernelContext* ctx) const {
|
||||
|
|
@ -93,28 +117,67 @@ Status QuantizeLinear<T>::Compute(OpKernelContext* ctx) const {
|
|||
auto& y_scale = *ctx->Input<Tensor>(1);
|
||||
auto& y_zero_point = *ctx->Input<Tensor>(2);
|
||||
auto& y = *ctx->Output(0, x.Shape());
|
||||
|
||||
const auto& x_shape = x.Shape();
|
||||
const auto& scale_shape = y_scale.Shape();
|
||||
const auto& zero_point_shape = y_zero_point.Shape();
|
||||
|
||||
const float* input = x.template Data<float>();
|
||||
T* output = y.template MutableData<T>();
|
||||
|
||||
//enforce that scale and zero point are scalars or 1D tensors with size == 1
|
||||
ORT_ENFORCE(scale_shape.NumDimensions() == 0 || (scale_shape.NumDimensions() == 1 && scale_shape.GetDims().size() == 1), "x_scale must be a scalar.");
|
||||
ORT_ENFORCE(zero_point_shape.NumDimensions() == 0 || (zero_point_shape.NumDimensions() == 1 && zero_point_shape.GetDims().size() == 1), "x_zero_point must be a scalar.");
|
||||
|
||||
const T zero_point = *(y_zero_point.template Data<T>());
|
||||
const float scale = *(y_scale.template Data<float>());
|
||||
const auto* input = x.template Data<float>();
|
||||
auto* output = y.template MutableData<T>();
|
||||
const auto num_of_elements = x_shape.Size();
|
||||
const float qmax = std::numeric_limits<T>::max();
|
||||
const float qmin_default = std::numeric_limits<T>::min();
|
||||
// adjust qmin for int8 inputs. This is required to keep zero point as zero
|
||||
const float qmin = qmin_default == -128 ? -127 : qmin_default;
|
||||
|
||||
for (int i = 0; i < num_of_elements; ++i) {
|
||||
output[i] = static_cast<T>(clamp(RoundHalfToEven(static_cast<float>(input[i]/scale)) + zero_point, qmin, qmax));
|
||||
// Schema of QuantizeLinearOp changed when it was promoted to onnx domain. In order to maintain backward compatiblity
|
||||
// both the versions need to be supported.
|
||||
if (ctx->GetOpDomain() != kMSDomain) {
|
||||
ORT_ENFORCE(IsScalarOr1ElementVector(&y_scale), "x_scale must be a scalar or 1D tensor or size 1.");
|
||||
ORT_ENFORCE(IsScalarOr1ElementVector(&y_zero_point), "x_zero_point must be a scalar or 1D tensor or size 1.");
|
||||
|
||||
const T zero_point = *(y_zero_point.template Data<T>());
|
||||
const float scale = *(y_scale.template Data<float>());
|
||||
const auto num_of_elements = x_shape.Size();
|
||||
|
||||
for (int i = 0; i < num_of_elements; ++i) {
|
||||
output[i] = static_cast<T>(clamp(RoundHalfToEven(static_cast<float>(input[i] / scale)) + zero_point, qmin, qmax));
|
||||
}
|
||||
|
||||
} else {
|
||||
size_t stride = 0;
|
||||
const int64_t axis = HandleNegativeAxis(axis_, x_shape.NumDimensions());
|
||||
const auto& broadcastDim = x_shape[axis];
|
||||
|
||||
if (has_axis_) {
|
||||
// if an axis was specified, ensure the scale and zero point are compatible
|
||||
ORT_ENFORCE(y_scale.Shape().NumDimensions() == 1 && y_scale.Shape().Size() == broadcastDim, "x_scale must be 1D tensor with size ", broadcastDim);
|
||||
ORT_ENFORCE(y_zero_point.Shape().NumDimensions() == 1 && y_zero_point.Shape().Size() == broadcastDim, "x_zero_point must be 1D tensor with size ", broadcastDim);
|
||||
stride = 1;
|
||||
} else {
|
||||
// if no axis, enforce that scale and zero point are scalars
|
||||
ORT_ENFORCE(IsScalarOr1ElementVector(&y_scale), "x_scale must be a scalar or 1D tensor or size 1.");
|
||||
ORT_ENFORCE(IsScalarOr1ElementVector(&y_zero_point), "x_zero_point must be a scalar or 1D tensor or size 1.");
|
||||
}
|
||||
|
||||
size_t N = x_shape.SizeToDimension(axis);
|
||||
size_t block_size = x_shape.SizeFromDimension(axis + 1);
|
||||
const T* zero_point = y_zero_point.template Data<T>();
|
||||
const float* scale = y_scale.template Data<float>();
|
||||
|
||||
for (size_t n = 0; n < N; n++) {
|
||||
const float* current_scale = scale;
|
||||
const T* current_zero_point = zero_point;
|
||||
|
||||
for (size_t bd = 0; bd < static_cast<size_t>(broadcastDim); bd++) {
|
||||
float zp = *current_zero_point;
|
||||
auto sc = *current_scale;
|
||||
|
||||
for (size_t bs = 0; bs < block_size; bs++) {
|
||||
*output++ = static_cast<T>(clamp(std::round(static_cast<float>(*input++) / sc) + zp, qmin, qmax));
|
||||
}
|
||||
|
||||
current_scale += stride;
|
||||
current_zero_point += stride;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
|
|
|
|||
|
|
@ -12,16 +12,28 @@ namespace onnxruntime {
|
|||
template <typename T>
|
||||
class DequantizeLinear final : public OpKernel {
|
||||
public:
|
||||
DequantizeLinear(const OpKernelInfo& info) : OpKernel(info) { }
|
||||
DequantizeLinear(const OpKernelInfo& info) : OpKernel(info) {
|
||||
has_axis_ = info.GetAttr<int64_t>("axis", &axis_).IsOK();
|
||||
}
|
||||
|
||||
Status Compute(OpKernelContext* context) const override;
|
||||
Status Compute(OpKernelContext* context) const override;
|
||||
|
||||
private:
|
||||
int64_t axis_ = 0;
|
||||
bool has_axis_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class QuantizeLinear final : public OpKernel {
|
||||
public:
|
||||
QuantizeLinear(const OpKernelInfo& info) : OpKernel(info) { }
|
||||
|
||||
QuantizeLinear(const OpKernelInfo& info) : OpKernel(info) {
|
||||
has_axis_ = info.GetAttr<int64_t>("axis", &axis_).IsOK();
|
||||
}
|
||||
|
||||
Status Compute(OpKernelContext* context) const override;
|
||||
|
||||
private:
|
||||
int64_t axis_ = 0;
|
||||
bool has_axis_;
|
||||
};
|
||||
} // namespace onnxruntime
|
||||
|
|
|
|||
169
onnxruntime/test/contrib_ops/quantize_ops_test.cc
Normal file
169
onnxruntime/test/contrib_ops/quantize_ops_test.cc
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/providers/provider_test_utils.h"
|
||||
|
||||
namespace onnxruntime {
|
||||
namespace test {
|
||||
|
||||
// 1d zero & scale with uint8 broadcast axis 0
|
||||
TEST(DequantizeLinearContribOpTest, DequantizeLinear_0) {
|
||||
OpTester test("DequantizeLinear", 1, onnxruntime::kMSDomain);
|
||||
std::vector<int64_t> dims{3, 4};
|
||||
test.AddInput<uint8_t>("X", dims,
|
||||
{0, 1, 2, 3,
|
||||
0, 1, 2, 3,
|
||||
0, 10, 20, 30});
|
||||
test.AddAttribute<int64_t>("axis", 0);
|
||||
test.AddInput<float>("scale", {3},
|
||||
{1.0f,
|
||||
2.0f,
|
||||
4.0f});
|
||||
test.AddInput<uint8_t>("zero_point", {3},
|
||||
{0,
|
||||
0,
|
||||
0});
|
||||
test.AddOutput<float>("Y", dims,
|
||||
{0, 1, 2, 3,
|
||||
0, 2, 4, 6,
|
||||
0, 40, 80, 120});
|
||||
test.Run();
|
||||
}
|
||||
|
||||
// 1d zero & scale with int8 broadcast axis 1
|
||||
TEST(DequantizeLinearContribOpTest, DequantizeLinear_1) {
|
||||
OpTester test("DequantizeLinear", 1, onnxruntime::kMSDomain);
|
||||
std::vector<int64_t> dims{3, 4};
|
||||
test.AddInput<int8_t>("X", dims,
|
||||
{0, 1, 2, 3,
|
||||
0, 2, 4, 6,
|
||||
0, 10, 20, 30});
|
||||
test.AddAttribute<int64_t>("axis", 1);
|
||||
test.AddInput<float>("scale", {4}, {1, 2, 4, 8});
|
||||
test.AddInput<int8_t>("zero_point", {4}, {0, -10, -20, -30});
|
||||
test.AddOutput<float>("Y", dims,
|
||||
{0, 22, 88, 264,
|
||||
0, 24, 96, 288,
|
||||
0, 40, 160, 480});
|
||||
test.Run();
|
||||
}
|
||||
|
||||
// 1d zero & scale with int8 broadcast axis 0 with 4d tensor input/output
|
||||
TEST(DequantizeLinearContribOpTest, DequantizeLinear_2) {
|
||||
OpTester test("DequantizeLinear", 1, onnxruntime::kMSDomain);
|
||||
std::vector<int64_t> dims{2, 3, 2, 4};
|
||||
test.AddInput<int8_t>("X", dims,
|
||||
{7, 9, 10, 10,
|
||||
5, 8, 9, 1,
|
||||
|
||||
8, 6, 7, 9,
|
||||
10, 0, 7, 10,
|
||||
|
||||
8, 2, 6, 0,
|
||||
5, 9, 8, 1,
|
||||
|
||||
2, 7, 5, 3,
|
||||
2, 4, 1, 3,
|
||||
|
||||
8, 7, 4, 8,
|
||||
10, 1, 5, 5,
|
||||
|
||||
7, 7, 0, 2,
|
||||
4, 4, 0, 5});
|
||||
test.AddAttribute<int64_t>("axis", 1);
|
||||
test.AddInput<float>("scale", {3}, {1, 10, 7});
|
||||
test.AddInput<int8_t>("zero_point", {3}, {10, 2, 1});
|
||||
test.AddOutput<float>("Y", dims,
|
||||
{-3, -1, 0, 0,
|
||||
-5, -2, -1, -9,
|
||||
|
||||
60, 40, 50, 70,
|
||||
80, -20, 50, 80,
|
||||
|
||||
49, 7, 35, -7,
|
||||
28, 56, 49, 0,
|
||||
|
||||
-8, -3, -5, -7,
|
||||
-8, -6, -9, -7,
|
||||
|
||||
60, 50, 20, 60,
|
||||
80, -10, 30, 30,
|
||||
|
||||
42, 42, -7, 7,
|
||||
21, 21, -7, 28});
|
||||
test.Run();
|
||||
}
|
||||
|
||||
// 1d zero & scale with uint8 broadcast axis -2 (-2 resolves to axis 0)
|
||||
TEST(DequantizeLinearContribOpTest, DequantizeLinear_3) {
|
||||
OpTester test("DequantizeLinear", 1, onnxruntime::kMSDomain);
|
||||
std::vector<int64_t> dims{3, 4};
|
||||
test.AddInput<uint8_t>("X", dims,
|
||||
{0, 1, 2, 3,
|
||||
0, 1, 2, 3,
|
||||
0, 10, 20, 30});
|
||||
test.AddAttribute<int64_t>("axis", -2);
|
||||
test.AddInput<float>("scale", {3},
|
||||
{1.0f,
|
||||
2.0f,
|
||||
4.0f});
|
||||
test.AddInput<uint8_t>("zero_point", {3},
|
||||
{0,
|
||||
0,
|
||||
0});
|
||||
test.AddOutput<float>("Y", dims,
|
||||
{0, 1, 2, 3,
|
||||
0, 2, 4, 6,
|
||||
0, 40, 80, 120});
|
||||
test.Run();
|
||||
}
|
||||
|
||||
// quantize with scalar zero point and scale
|
||||
TEST(QuantizeLinearContribOpTest, QuantizeLinear_0) {
|
||||
OpTester test("QuantizeLinear", 1, onnxruntime::kMSDomain);
|
||||
std::vector<int64_t> dims{6};
|
||||
test.AddInput<float>("x", dims, {0, 2, 3, 1000, -254, -1000});
|
||||
test.AddInput<float>("y_scale", {}, {2.0f});
|
||||
test.AddInput<uint8_t>("y_zero_point", {}, {128});
|
||||
test.AddOutput<uint8_t>("y", dims, {128, 129, 130, 255, 1, 0});
|
||||
test.Run();
|
||||
}
|
||||
|
||||
// quantize with broadcasting
|
||||
TEST(QuantizeLinearContribOpTest, QuantizeLinear_1) {
|
||||
OpTester test("QuantizeLinear", 1, onnxruntime::kMSDomain);
|
||||
std::vector<int64_t> dims{3, 4};
|
||||
test.AddInput<float>("X", dims,
|
||||
{0, 2, 3, 1000,
|
||||
0, 2, 3, 1000,
|
||||
0, 2, 3, 1000});
|
||||
test.AddAttribute<int64_t>("axis", 0);
|
||||
test.AddInput<float>("scale", {3}, {1, 2, 4});
|
||||
test.AddInput<uint8_t>("zero_point", {3}, {0, 0, 0});
|
||||
test.AddOutput<uint8_t>("Y", dims,
|
||||
{0, 2, 3, 255,
|
||||
0, 1, 2, 255,
|
||||
0, 1, 1, 250});
|
||||
test.Run();
|
||||
}
|
||||
|
||||
// quantize with broadcasting and negative axis (-2 resolves to axis 0)
|
||||
TEST(QuantizeLinearContribOpTest, QuantizeLinear_2) {
|
||||
OpTester test("QuantizeLinear", 1, onnxruntime::kMSDomain);
|
||||
std::vector<int64_t> dims{3, 4};
|
||||
test.AddInput<float>("X", dims,
|
||||
{0, 2, 3, 1000,
|
||||
0, 2, 3, 1000,
|
||||
0, 2, 3, 1000});
|
||||
test.AddAttribute<int64_t>("axis", -2);
|
||||
test.AddInput<float>("scale", {3}, {1, 2, 4});
|
||||
test.AddInput<uint8_t>("zero_point", {3}, {0, 0, 0});
|
||||
test.AddOutput<uint8_t>("Y", dims,
|
||||
{0, 2, 3, 255,
|
||||
0, 1, 2, 255,
|
||||
0, 1, 1, 250});
|
||||
test.Run();
|
||||
}
|
||||
} // namespace test
|
||||
} // namespace onnxruntime
|
||||
Loading…
Reference in a new issue