add dequantize and quantize back to contrib ops (#1712)

This commit is contained in:
Ashwini Khade 2019-09-06 08:55:42 -07:00 committed by GitHub
parent e1a12b1760
commit b2a2326a45
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 452 additions and 32 deletions

View file

@ -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;

View 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

View file

@ -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

View file

@ -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;

View file

@ -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].

View file

@ -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();

View file

@ -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

View 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