From 54d290a81353c5daefa672860f09cf549239cc1f Mon Sep 17 00:00:00 2001 From: hezhenhao1 Date: Fri, 8 Apr 2022 10:57:56 +0800 Subject: [PATCH] Add cpu implementation of IsClose operator. --- .../device/cpu/kernel/is_close_cpu_kernel.cc | 152 ++++++++++++++++++ .../device/cpu/kernel/is_close_cpu_kernel.h | 61 +++++++ mindspore/core/ops/is_close.cc | 52 +++--- .../mindspore/ops/operations/math_ops.py | 8 +- tests/st/ops/cpu/test_is_close_op.py | 77 +++++++++ 5 files changed, 323 insertions(+), 27 deletions(-) create mode 100644 mindspore/ccsrc/plugin/device/cpu/kernel/is_close_cpu_kernel.cc create mode 100644 mindspore/ccsrc/plugin/device/cpu/kernel/is_close_cpu_kernel.h create mode 100644 tests/st/ops/cpu/test_is_close_op.py diff --git a/mindspore/ccsrc/plugin/device/cpu/kernel/is_close_cpu_kernel.cc b/mindspore/ccsrc/plugin/device/cpu/kernel/is_close_cpu_kernel.cc new file mode 100644 index 00000000000..6f6bad1bbeb --- /dev/null +++ b/mindspore/ccsrc/plugin/device/cpu/kernel/is_close_cpu_kernel.cc @@ -0,0 +1,152 @@ +/** + * Copyright 2022 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "plugin/device/cpu/kernel/is_close_cpu_kernel.h" +#include +#include +#include "abstract/utils.h" +#include "plugin/device/cpu/hal/device/cpu_device_address.h" + +namespace mindspore { +namespace kernel { +namespace { +constexpr size_t kIsCloseInputsNum = 2; +constexpr size_t kIsCloseOutputsNum = 1; +constexpr size_t kIsCloseInputIndex = 0; +constexpr size_t kIsCloseOtherIndex = 1; +constexpr size_t kIsCloseOutputIndex = 0; +constexpr char RTOL[] = "rtol"; +constexpr char ATOL[] = "atol"; +constexpr char EQUAL_NAN[] = "equal_nan"; + +template +inline bool compute(T a, T b, float rtol, float atol, bool equal_nan) { + if (a == b) { + return true; + } + if (equal_nan && std::isnan(a) && std::isnan(b)) { + return true; + } + if (atol == 0 && rtol == 0) { + return false; + } + auto left_side = std::abs(a - b); + auto right_side = atol + (rtol * std::abs(b)); + return std::isfinite(left_side) && left_side <= right_side; +} +} // namespace + +void IsCloseCpuKernelMod::InitKernel(const CNodePtr &kernel_node) { + MS_EXCEPTION_IF_NULL(kernel_node); + kernel_name_ = common::AnfAlgo::GetCNodeName(kernel_node); + auto kernel_attr = GetKernelAttrFromNode(kernel_node); + auto [is_match, index] = MatchKernelAttr(kernel_attr, GetOpSupport()); + if (!is_match) { + MS_LOG(EXCEPTION) << "IsClose does not support this kernel data type: " << kernel_attr; + } + kernel_func_ = func_list_[index].second; + rtol_ = common::AnfAlgo::GetNodeAttr(kernel_node, RTOL); + atol_ = common::AnfAlgo::GetNodeAttr(kernel_node, ATOL); + equal_nan_ = common::AnfAlgo::GetNodeAttr(kernel_node, EQUAL_NAN); + input_shape_ = common::AnfAlgo::GetPrevNodeOutputInferShape(kernel_node, kIsCloseInputIndex); + other_shape_ = common::AnfAlgo::GetPrevNodeOutputInferShape(kernel_node, kIsCloseOtherIndex); + output_shape_ = common::AnfAlgo::GetOutputInferShape(kernel_node, kIsCloseOutputIndex); +} + +template +bool IsCloseCpuKernelMod::LaunchKernel(const std::vector &inputs, + const std::vector &outputs) { + CHECK_KERNEL_INPUTS_NUM(inputs.size(), kIsCloseInputsNum, kernel_name_); + CHECK_KERNEL_OUTPUTS_NUM(outputs.size(), kIsCloseOutputsNum, kernel_name_); + auto input = reinterpret_cast(inputs[kIsCloseInputIndex]->addr); + auto other = reinterpret_cast(inputs[kIsCloseOtherIndex]->addr); + auto output = reinterpret_cast(outputs[kIsCloseOutputIndex]->addr); + + CTask task; + BroadcastIterator base_iter(input_shape_, other_shape_, output_shape_); + if (input_shape_ == other_shape_) { + task = [this, &input, &other, &output](size_t start, size_t end) { + for (size_t i = start; i < end; i++) { + if constexpr (!std::is_same_v && !std::is_same_v) { + auto a = static_cast(input[i]); + auto b = static_cast(other[i]); + output[i] = compute(a, b, rtol_, atol_, equal_nan_); + } else { + output[i] = compute(input[i], other[i], rtol_, atol_, equal_nan_); + } + } + return common::SUCCESS; + }; + } else { + task = [this, &base_iter, &input, &other, &output](size_t start, size_t end) { + auto iter = base_iter; + iter.SetPos(start); + for (size_t i = start; i < end; i++) { + auto idx1 = iter.GetInputPosA(); + auto idx2 = iter.GetInputPosB(); + if constexpr (!std::is_same_v && !std::is_same_v) { + auto a = static_cast(input[idx1]); + auto b = static_cast(other[idx2]); + output[i] = compute(a, b, rtol_, atol_, equal_nan_); + } else { + output[i] = compute(input[idx1], other[idx2], rtol_, atol_, equal_nan_); + } + iter.GenNextPos(); + } + return common::SUCCESS; + }; + } + size_t elem_num = outputs[kIsCloseOutputIndex]->size / sizeof(bool); + ParallelLaunchAutoSearch(task, elem_num, this, ¶llel_search_info_); + return true; +} + +std::vector> IsCloseCpuKernelMod::func_list_ = { + {KernelAttr().AddInputAttr(kNumberTypeBool).AddInputAttr(kNumberTypeBool).AddOutputAttr(kNumberTypeBool), + &IsCloseCpuKernelMod::LaunchKernel}, + {KernelAttr().AddInputAttr(kNumberTypeInt8).AddInputAttr(kNumberTypeInt8).AddOutputAttr(kNumberTypeBool), + &IsCloseCpuKernelMod::LaunchKernel}, + {KernelAttr().AddInputAttr(kNumberTypeInt16).AddInputAttr(kNumberTypeInt16).AddOutputAttr(kNumberTypeBool), + &IsCloseCpuKernelMod::LaunchKernel}, + {KernelAttr().AddInputAttr(kNumberTypeInt32).AddInputAttr(kNumberTypeInt32).AddOutputAttr(kNumberTypeBool), + &IsCloseCpuKernelMod::LaunchKernel}, + {KernelAttr().AddInputAttr(kNumberTypeInt64).AddInputAttr(kNumberTypeInt64).AddOutputAttr(kNumberTypeBool), + &IsCloseCpuKernelMod::LaunchKernel}, + {KernelAttr().AddInputAttr(kNumberTypeFloat16).AddInputAttr(kNumberTypeFloat16).AddOutputAttr(kNumberTypeBool), + &IsCloseCpuKernelMod::LaunchKernel}, + {KernelAttr().AddInputAttr(kNumberTypeFloat32).AddInputAttr(kNumberTypeFloat32).AddOutputAttr(kNumberTypeBool), + &IsCloseCpuKernelMod::LaunchKernel}, + {KernelAttr().AddInputAttr(kNumberTypeFloat64).AddInputAttr(kNumberTypeFloat64).AddOutputAttr(kNumberTypeBool), + &IsCloseCpuKernelMod::LaunchKernel}, + {KernelAttr().AddInputAttr(kNumberTypeUInt8).AddInputAttr(kNumberTypeUInt8).AddOutputAttr(kNumberTypeBool), + &IsCloseCpuKernelMod::LaunchKernel}, + {KernelAttr().AddInputAttr(kNumberTypeUInt16).AddInputAttr(kNumberTypeUInt16).AddOutputAttr(kNumberTypeBool), + &IsCloseCpuKernelMod::LaunchKernel}, + {KernelAttr().AddInputAttr(kNumberTypeUInt32).AddInputAttr(kNumberTypeUInt32).AddOutputAttr(kNumberTypeBool), + &IsCloseCpuKernelMod::LaunchKernel}, + {KernelAttr().AddInputAttr(kNumberTypeUInt64).AddInputAttr(kNumberTypeUInt64).AddOutputAttr(kNumberTypeBool), + &IsCloseCpuKernelMod::LaunchKernel}}; + +std::vector IsCloseCpuKernelMod::GetOpSupport() { + std::vector support_list; + (void)std::transform(func_list_.begin(), func_list_.end(), std::back_inserter(support_list), + [](const std::pair &pair) { return pair.first; }); + return support_list; +} + +MS_KERNEL_FACTORY_REG(NativeCpuKernelMod, IsClose, IsCloseCpuKernelMod); +} // namespace kernel +} // namespace mindspore diff --git a/mindspore/ccsrc/plugin/device/cpu/kernel/is_close_cpu_kernel.h b/mindspore/ccsrc/plugin/device/cpu/kernel/is_close_cpu_kernel.h new file mode 100644 index 00000000000..291b236d25e --- /dev/null +++ b/mindspore/ccsrc/plugin/device/cpu/kernel/is_close_cpu_kernel.h @@ -0,0 +1,61 @@ +/** + * Copyright 2022 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_CCSRC_PLUGIN_DEVICE_CPU_KERNEL_IS_CLOSE_CPU_KERNEL_H_ +#define MINDSPORE_CCSRC_PLUGIN_DEVICE_CPU_KERNEL_IS_CLOSE_CPU_KERNEL_H_ + +#include +#include +#include +#include "plugin/device/cpu/kernel/cpu_kernel.h" +#include "plugin/factory/ms_factory.h" + +namespace mindspore { +namespace kernel { +class IsCloseCpuKernelMod : public NativeCpuKernelMod { + public: + IsCloseCpuKernelMod() = default; + ~IsCloseCpuKernelMod() override = default; + + void InitKernel(const CNodePtr &kernel_node) override; + + bool Launch(const std::vector &inputs, const std::vector &, + const std::vector &outputs) override { + return kernel_func_(this, inputs, outputs); + } + + protected: + std::vector GetOpSupport() override; + + private: + template + bool LaunchKernel(const std::vector &inputs, const std::vector &outputs); + + using IsCloseFunc = std::function &, + const std::vector &)>; + static std::vector> func_list_; + IsCloseFunc kernel_func_; + float rtol_; + float atol_; + bool equal_nan_; + std::vector input_shape_; + std::vector other_shape_; + std::vector output_shape_; +}; +} // namespace kernel +} // namespace mindspore + +#endif // MINDSPORE_CCSRC_PLUGIN_DEVICE_CPU_KERNEL_IS_CLOSE_CPU_KERNEL_H_ diff --git a/mindspore/core/ops/is_close.cc b/mindspore/core/ops/is_close.cc index 982c8ae5cb5..838a6127d09 100644 --- a/mindspore/core/ops/is_close.cc +++ b/mindspore/core/ops/is_close.cc @@ -21,6 +21,7 @@ #include #include #include "ops/op_utils.h" +#include "utils/ms_context.h" #include "utils/check_convert_utils.h" #include "abstract/primitive_infer_map.h" #include "mindapi/src/helper.h" @@ -29,33 +30,38 @@ namespace mindspore { namespace ops { namespace { abstract::ShapePtr IsCloseInferShape(const PrimitivePtr &primitive, const std::vector &input_args) { - const int MAX = 0x3fffffff; MS_EXCEPTION_IF_NULL(primitive); auto op_name = primitive->name(); - auto input_shape = CheckAndConvertUtils::ConvertShapePtrToShapeMap(input_args[kInputIndex0]->BuildShape())[kShape]; - auto other_shape = CheckAndConvertUtils::ConvertShapePtrToShapeMap(input_args[kInputIndex1]->BuildShape())[kShape]; - auto input_rank = SizeToLong(input_shape.size()); - auto other_rank = SizeToLong(other_shape.size()); - CheckAndConvertUtils::Check("input rank", input_rank, kEqual, other_rank, op_name); - int64_t input_size = 1, other_size = 1; - for (size_t i = 0; i < input_shape.size(); i++) { - input_size *= input_shape[i]; - other_size *= other_shape[i]; - if (input_shape[i] != other_shape[i] && (input_shape[i] != 1 || other_shape[i] != 1)) { - MS_EXCEPTION(ValueError) << "For '" << op_name - << "', The size of tensor input must match the size of tensor other at the " << i - << " dimension, but got input size: " << input_shape[i] - << ", other size: " << other_shape[i] << "."; + auto context = MsContext::GetInstance(); + MS_EXCEPTION_IF_NULL(context); + bool is_ascend = (context->get_param(MS_CTX_DEVICE_TARGET) == kAscendDevice); + if (is_ascend) { + const int MAX = 0x3fffffff; + auto input_shape = CheckAndConvertUtils::ConvertShapePtrToShapeMap(input_args[kInputIndex0]->BuildShape())[kShape]; + auto other_shape = CheckAndConvertUtils::ConvertShapePtrToShapeMap(input_args[kInputIndex1]->BuildShape())[kShape]; + auto input_rank = SizeToLong(input_shape.size()); + auto other_rank = SizeToLong(other_shape.size()); + CheckAndConvertUtils::Check("input rank", input_rank, kEqual, other_rank, op_name); + int64_t input_size = 1, other_size = 1; + for (size_t i = 0; i < input_shape.size(); i++) { + input_size *= input_shape[i]; + other_size *= other_shape[i]; + if (input_shape[i] != other_shape[i] && (input_shape[i] != 1 || other_shape[i] != 1)) { + MS_EXCEPTION(ValueError) << "For '" << op_name + << "', The size of tensor input must match the size of tensor other at the " << i + << " dimension, but got input size: " << input_shape[i] + << ", other size: " << other_shape[i] << "."; + } } + if (input_size > MAX) + MS_EXCEPTION(ValueError) << "For '" << op_name + << "', The size of tensor input must should be less than [2147483648], actual is " + << input_size; + if (other_size > MAX) + MS_EXCEPTION(ValueError) << "For '" << op_name + << "', The size of tensor other must should be less than [2147483648], actual is " + << other_size; } - if (input_size > MAX) - MS_EXCEPTION(ValueError) << "For '" << op_name - << "', The size of tensor input must should be less than [2147483648], actual is " - << input_size; - if (other_size > MAX) - MS_EXCEPTION(ValueError) << "For '" << op_name - << "', The size of tensor other must should be less than [2147483648], actual is " - << other_size; return BroadCastInferShape(op_name, input_args); } diff --git a/mindspore/python/mindspore/ops/operations/math_ops.py b/mindspore/python/mindspore/ops/operations/math_ops.py index 0f5e0e4c606..419399248d0 100644 --- a/mindspore/python/mindspore/ops/operations/math_ops.py +++ b/mindspore/python/mindspore/ops/operations/math_ops.py @@ -5982,8 +5982,8 @@ class IsClose(Primitive): Args: rtol(float): Relative tolerance. Default: 1e-05. atol(float): Absolute tolerance. Default: 1e-08. - equal_nan(bool): If True, then two NaNs will be considered equal. At present, `equal_nan` must be True, - we will support False in future version. Default: True. + equal_nan(bool): If True, then two NaNs will be considered equal. At present, `equal_nan` must be True in Ascend + platform currently, we will support False in future version. Default: True. Inputs: -**input**(Tensor) – First tensor to compare, with data type belongs to float32, float16, int32. @@ -6004,7 +6004,7 @@ class IsClose(Primitive): ValueError: If `equal_nan` is False. Supported Platforms: - ``Ascend`` + ``Ascend`` ``CPU`` Examples: >>> input = Tensor(np.array([1.3, 2.1, 3.2, 4.1, 5.1]), mindspore.float16) @@ -6020,7 +6020,7 @@ class IsClose(Primitive): validator.check_value_type('rtol', rtol, [float], self.name) validator.check_value_type('atol', atol, [float], self.name) validator.check_value_type('equal_nan', equal_nan, [bool], self.name) - if not equal_nan: + if context.get_context("device_target") == "Ascend" and not equal_nan: raise ValueError("For IsClose, the `equal_nan` must be True, but got False.") validator.check_non_negative_float(rtol, 'rtol', self.name) validator.check_non_negative_float(atol, 'atol', self.name) diff --git a/tests/st/ops/cpu/test_is_close_op.py b/tests/st/ops/cpu/test_is_close_op.py new file mode 100644 index 00000000000..fb22af42903 --- /dev/null +++ b/tests/st/ops/cpu/test_is_close_op.py @@ -0,0 +1,77 @@ +# Copyright 2022 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +import numpy as np +import pytest + +import mindspore.context as context +import mindspore.nn as nn +from mindspore import Tensor +from mindspore.ops import operations as P + + +class Net(nn.Cell): + def __init__(self, rtol, atol, equal_nan): + super(Net, self).__init__() + self.ops = P.IsClose(rtol=rtol, atol=atol, equal_nan=equal_nan) + + def construct(self, a, b): + return self.ops(a, b) + + +def rand_int(*shape): + """return an random integer array with parameter shape""" + res = np.random.randint(low=1, high=5, size=shape) + if isinstance(res, np.ndarray): + return res.astype(np.float32) + return float(res) + + +def compare_with_numpy(a, b, rtol=1e-05, atol=1e-08, equal_nan=False): + # Graph Mode + context.set_context(mode=context.GRAPH_MODE, device_target='CPU') + ms_result_graph = Net(rtol, atol, equal_nan)(Tensor(a), Tensor(b)) + # PyNative Mode + context.set_context(mode=context.PYNATIVE_MODE, device_target='CPU') + ms_result_pynative = Net(rtol, atol, equal_nan)(Tensor(a), Tensor(b)) + + np_result = np.isclose(a, b, rtol, atol, equal_nan) + return np.array_equal(ms_result_graph, np_result) and np.array_equal(ms_result_pynative, np_result) + + +@pytest.mark.level0 +@pytest.mark.platform_x86_cpu +@pytest.mark.env_onecard +@pytest.mark.parametrize('equal_nan', [True, False]) +def test_net(equal_nan): + """ + Feature: ALL TO ALL + Description: test cases for IsClose operator. + Expectation: the result match numpy isclose. + """ + a = [0, 1, 2, float('inf'), float('inf'), float('nan')] + b = [0, 1, -2, float('-inf'), float('inf'), float('nan')] + assert compare_with_numpy(a, b, equal_nan=equal_nan) + + a = rand_int(2, 3, 4, 5) + diff = (np.random.random((2, 3, 4, 5)).astype("float32") - 0.5) / 1000 + b = a + diff + assert compare_with_numpy(a, b, atol=1e-3) + assert compare_with_numpy(a, b, atol=1e-3, rtol=1e-4) + assert compare_with_numpy(a, b, atol=1e-2, rtol=1e-6) + + a = rand_int(2, 3, 4, 5) + b = rand_int(4, 5) + assert compare_with_numpy(a, b, equal_nan=equal_nan)