From aac711ab2f458cf3659c5698f0f44532c905d83a Mon Sep 17 00:00:00 2001 From: Dmitri Smirnov Date: Mon, 11 Feb 2019 10:25:54 -0800 Subject: [PATCH] Implement Sign operator. (#456) Implement Sign operator. --- .../providers/cpu/cpu_execution_provider.cc | 4 +- onnxruntime/core/providers/cpu/math/sign.cc | 112 +++++++++++ onnxruntime/test/onnx/main.cc | 1 - .../test/providers/cpu/math/sing_test.cc | 182 ++++++++++++++++++ .../test/python/onnx_backend_test_series.py | 1 - 5 files changed, 297 insertions(+), 3 deletions(-) create mode 100644 onnxruntime/core/providers/cpu/math/sign.cc create mode 100644 onnxruntime/test/providers/cpu/math/sing_test.cc diff --git a/onnxruntime/core/providers/cpu/cpu_execution_provider.cc b/onnxruntime/core/providers/cpu/cpu_execution_provider.cc index 955d545300..5d7a46313d 100644 --- a/onnxruntime/core/providers/cpu/cpu_execution_provider.cc +++ b/onnxruntime/core/providers/cpu/cpu_execution_provider.cc @@ -236,6 +236,7 @@ class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, class ONNX_OPERATOR_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 9, EyeLike); class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 9, float, IsNaN); class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 9, MLFloat16, IsNaN); +class ONNX_OPERATOR_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 9, Sign); class ONNX_OPERATOR_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 9, Erf); class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 9, int64_t_int64_t_int64_t, OneHot); class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 9, float_int64_t_int64_t, OneHot); @@ -459,7 +460,7 @@ void RegisterOnnxOperatorKernels(KernelRegistry& kernel_registry) { kernel_registry.Register(BuildKernelCreateInfo()); kernel_registry.Register(BuildKernelCreateInfo()); kernel_registry.Register(BuildKernelCreateInfo()); - kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); kernel_registry.Register(BuildKernelCreateInfo()); kernel_registry.Register(BuildKernelCreateInfo()); kernel_registry.Register(BuildKernelCreateInfo()); @@ -486,6 +487,7 @@ void RegisterOnnxOperatorKernels(KernelRegistry& kernel_registry) { kernel_registry.Register(BuildKernelCreateInfo()); kernel_registry.Register(BuildKernelCreateInfo()); kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); kernel_registry.Register(BuildKernelCreateInfo()); kernel_registry.Register(BuildKernelCreateInfo()); kernel_registry.Register(BuildKernelCreateInfo()); diff --git a/onnxruntime/core/providers/cpu/math/sign.cc b/onnxruntime/core/providers/cpu/math/sign.cc new file mode 100644 index 0000000000..fbfa4ce117 --- /dev/null +++ b/onnxruntime/core/providers/cpu/math/sign.cc @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "core/common/common.h" +#include "core/framework/data_types.h" +#include "core/framework/op_kernel.h" +#include "core/util/math.h" +#include "core/util/math_cpuonly.h" + +#include "gsl/span" +#include + +using namespace ::onnxruntime::common; +using namespace ONNX_NAMESPACE; +namespace onnxruntime { + +class Sign final : public OpKernel { + public: + explicit Sign(const OpKernelInfo& info) : OpKernel(info) {} + + Status Compute(OpKernelContext* ctx) const override; +}; + +ONNX_CPU_OPERATOR_KERNEL( + Sign, + 9, + KernelDefBuilder().TypeConstraint("T", {DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType()}), + Sign); + +namespace sign_internal { +// The spec does not specify how NaN is +// treated but we have to treat it somehow. We choose +// to return 0 for NaN as TF does. +template +inline T FloatingImpl(T val) { + if (std::isnan(val) || val == T(0)) { + return T(0); + } else if (val > T(0)) { + return T(1); + } else { + return T(-1); + } +} + +void SignMLFloat16(const Tensor* input, Tensor* output) { + auto span = gsl::make_span(input->Data(), input->Shape().Size()); + auto output_data = output->template MutableData(); + std::transform(span.cbegin(), span.cend(), output_data, [](const MLFloat16& val) { + float fl = math::halfToFloat(val.val); + return MLFloat16(math::floatToHalf(FloatingImpl(fl))); + }); +} + +void SignBFloat16(const Tensor* input, Tensor* output) { + auto span = gsl::make_span(input->Data(), input->Shape().Size()); + auto output_data = output->template MutableData(); + std::transform(span.cbegin(), span.cend(), output_data, [](const BFloat16& val) { + float fl = val.ToFloat(); + return BFloat16(FloatingImpl(fl)); + }); +} +} // namespace sign_internal + +Status Sign::Compute(OpKernelContext* ctx) const { + using namespace sign_internal; + + auto input = ctx->Input(0); + auto output = ctx->Output(0, input->Shape()); + + auto dtype = input->DataType(); + if (dtype == DataTypeImpl::GetType()) { + EigenMap(*output) = EigenMap(*input).array().cwiseSign(); + } else if (dtype == DataTypeImpl::GetType()) { + EigenMap(*output) = EigenMap(*input).array().cwiseSign(); + } else if (dtype == DataTypeImpl::GetType()) { + EigenMap(*output) = EigenMap(*input).array().cwiseSign(); + } else if (dtype == DataTypeImpl::GetType()) { + EigenMap(*output) = EigenMap(*input).array().cwiseSign(); + } else if (dtype == DataTypeImpl::GetType()) { + EigenMap(*output) = EigenMap(*input).array().cwiseSign(); + } else if (dtype == DataTypeImpl::GetType()) { + EigenMap(*output) = EigenMap(*input).array().cwiseSign(); + } else if (dtype == DataTypeImpl::GetType()) { + EigenMap(*output) = EigenMap(*input).array().cwiseSign(); + } else if (dtype == DataTypeImpl::GetType()) { + EigenMap(*output) = EigenMap(*input).array().cwiseSign(); + } else if (dtype == DataTypeImpl::GetType()) { + EigenMap(*output) = EigenMap(*input).array().cwiseSign(); + } else if (dtype == DataTypeImpl::GetType()) { + EigenMap(*output) = EigenMap(*input).array().cwiseSign(); + } else if (dtype == DataTypeImpl::GetType()) { + SignMLFloat16(input, output); + } else if (dtype == DataTypeImpl::GetType()) { + SignBFloat16(input, output); + } else { + return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, "Unsupported input datatype"); + } + return Status::OK(); +} + +} // namespace onnxruntime diff --git a/onnxruntime/test/onnx/main.cc b/onnxruntime/test/onnx/main.cc index faa2527e20..8a231615ae 100644 --- a/onnxruntime/test/onnx/main.cc +++ b/onnxruntime/test/onnx/main.cc @@ -309,7 +309,6 @@ int real_main(int argc, char* argv[]) { {"acosh_example", "opset 9 not supported yet"}, {"atanh_example", "opset 9 not supported yet"}, {"sign_model", "opset 9 not supported yet"}, - {"sign", "opset 9 not supported yet"}, {"scatter_with_axis", "opset 9 not supported yet"}, {"scatter_without_axis", "opset 9 not supported yet"}, {"scan_sum", "opset 9 not supported yet"}, diff --git a/onnxruntime/test/providers/cpu/math/sing_test.cc b/onnxruntime/test/providers/cpu/math/sing_test.cc new file mode 100644 index 0000000000..ea6cd1993a --- /dev/null +++ b/onnxruntime/test/providers/cpu/math/sing_test.cc @@ -0,0 +1,182 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "gtest/gtest.h" +#include "test/providers/provider_test_utils.h" +#include "core/util/math.h" + +namespace onnxruntime { +namespace test { + +namespace test_sign_internal { + +template +struct make_type { + static T make(A v) { + return T(v); + } +}; + +template +struct make_type { + static MLFloat16 make(A v) { + return MLFloat16(math::floatToHalf(float(v))); + } +}; + +template +struct make_type { + static BFloat16 make(A v) { + return BFloat16(float(v)); + } +}; + +template +typename std::enable_if::is_signed>::type +GenerateSequence(OutputIter out) { + for (int i = 0; i < 7; ++i) { + *out = make_type::make(i); + ++out; + } +} + +template +typename std::enable_if::is_signed>::type +GenerateSequence(OutputIter out) { + for (int i = -5; i < 2; ++i) { + *out = make_type::make(i); + ++out; + } +} + +template +inline auto to_testable_type(T v) { + return v; +} + +template <> +inline auto to_testable_type(MLFloat16 v) { + return math::halfToFloat(v.val); +} + +template <> +inline auto to_testable_type(BFloat16 v) { + return v.ToFloat(); +} + +template +typename std::enable_if::is_signed && + !std::is_same::value && + !std::is_same::value>::type +TestImpl(ForwardIter first, ForwardIter last, OutputIter out) { + std::transform(first, last, out, [](T v) { + auto t = to_testable_type(v); + if (t == 0) { + t = 0; + } else { + t = 1; + } + return make_type::make(t); + }); +} + +template +typename std::enable_if::is_signed || + std::is_same::value || + std::is_same::value>::type +TestImpl(ForwardIter first, ForwardIter last, OutputIter out) { + std::transform(first, last, out, [](T v) { + auto t = to_testable_type(v); + if (t == 0) { + t = 0; + } else if (t > 0) { + t = 1; + } else { + t = -1; + } + return make_type::make(t); + }); +} +} // namespace test_sign_internal + +TEST(MathOpTest, Sign_uint64) { + using namespace test_sign_internal; + OpTester test("Sign", 9); + + std::vector input_dims{7}; + std::vector input; + GenerateSequence(std::back_inserter(input)); + ASSERT_EQ(input.size(), 7U); + test.AddInput("input", input_dims, input); + + std::vector output; + TestImpl(input.cbegin(), input.cend(), std::back_inserter(output)); + test.AddOutput("output", input_dims, output); + test.Run(OpTester::ExpectResult::kExpectSuccess); +} + +TEST(MathOpTest, Sign_int64) { + using namespace test_sign_internal; + OpTester test("Sign", 9); + + std::vector input_dims{7}; + std::vector input; + GenerateSequence(std::back_inserter(input)); + ASSERT_EQ(input.size(), 7U); + test.AddInput("input", input_dims, input); + + std::vector output; + TestImpl(input.cbegin(), input.cend(), std::back_inserter(output)); + test.AddOutput("output", input_dims, output); + test.Run(OpTester::ExpectResult::kExpectSuccess); +} + +TEST(MathOpTest, Sign_float) { + using namespace test_sign_internal; + OpTester test("Sign", 9); + + std::vector input_dims{7}; + std::vector input; + GenerateSequence(std::back_inserter(input)); + ASSERT_EQ(input.size(), 7U); + test.AddInput("input", input_dims, input); + + std::vector output; + TestImpl(input.cbegin(), input.cend(), std::back_inserter(output)); + test.AddOutput("output", input_dims, output); + test.Run(OpTester::ExpectResult::kExpectSuccess); +} + +TEST(MathOpTest, Sign_double) { + using namespace test_sign_internal; + OpTester test("Sign", 9); + + std::vector input_dims{7}; + std::vector input; + GenerateSequence(std::back_inserter(input)); + ASSERT_EQ(input.size(), 7U); + test.AddInput("input", input_dims, input); + + std::vector output; + TestImpl(input.cbegin(), input.cend(), std::back_inserter(output)); + test.AddOutput("output", input_dims, output); + test.Run(OpTester::ExpectResult::kExpectSuccess); +} +TEST(MathOpTest, Sign_MLFloat16) { + using namespace test_sign_internal; + OpTester test("Sign", 9); + + std::vector input_dims{7}; + std::vector input; + GenerateSequence(std::back_inserter(input)); + ASSERT_EQ(input.size(), 7U); + test.AddInput("input", input_dims, input); + + std::vector output; + TestImpl(input.cbegin(), input.cend(), std::back_inserter(output)); + test.AddOutput("output", input_dims, output); + test.Run(OpTester::ExpectResult::kExpectSuccess); +} + +} // namespace test +} // namespace onnxruntime diff --git a/onnxruntime/test/python/onnx_backend_test_series.py b/onnxruntime/test/python/onnx_backend_test_series.py index 829975e641..cfc137e54c 100644 --- a/onnxruntime/test/python/onnx_backend_test_series.py +++ b/onnxruntime/test/python/onnx_backend_test_series.py @@ -31,7 +31,6 @@ backend_test.exclude(r'(' '|^test_scatter_without_axis_cpu.*' '|^test_shrink_hard_cpu.*' '|^test_shrink_soft_cpu.*' -'|^test_sign_cpu.*' '|^test_where_example_cpu.*' '|^test_AvgPool1d_cpu.*' '|^test_AvgPool1d_stride_cpu.*'