From 041bf1a95e28fc9d6b27bf3f1bb5d91c4d305d6a Mon Sep 17 00:00:00 2001 From: hw_hz Date: Fri, 7 Nov 2025 17:47:50 +0800 Subject: [PATCH] mint.broadcast_to --- .../frontend/expander/grad/grad_array_ops.cc | 87 ++++++++----------- .../customize/view/broadcast_to_view.cc | 9 +- .../yaml/doc/broadcast_to_view_doc.yaml | 28 +++--- .../ops/view/broadcast_to_strides_calc.cc | 22 +++-- mindspore/python/mindspore/mint/__init__.py | 2 +- tests/st/mint/test_broadcast_to.py | 59 +++++++++++++ 6 files changed, 120 insertions(+), 87 deletions(-) create mode 100644 tests/st/mint/test_broadcast_to.py diff --git a/mindspore/ccsrc/frontend/expander/grad/grad_array_ops.cc b/mindspore/ccsrc/frontend/expander/grad/grad_array_ops.cc index 711b75d8cd5..19dff496e02 100644 --- a/mindspore/ccsrc/frontend/expander/grad/grad_array_ops.cc +++ b/mindspore/ccsrc/frontend/expander/grad/grad_array_ops.cc @@ -958,6 +958,40 @@ NodePtrList BinopGradSelect(BpropBuilder *ib, const NodePtr &cond, const NodePtr return reduce; } +NodePtrList BroadcastToBackward(BpropBuilder *ib) { + auto x = ib->GetInput(i0); + auto dout = ib->GetInput(i3); + auto x_shape = ib->GetShape(x); + auto dout_shape = ib->GetShape(dout); + + bool input_dynamic = IsDynamic(x_shape) || IsDynamic(dout_shape); + if (!input_dynamic && x_shape == dout_shape) { + return {dout, ib->OutZeros(ib->GetInput(i1))}; + } + + auto x_shape_node = ib->Shape(x); + auto broadcast_axes = ib->BroadcastGradientArgs(dout, x); + MS_EXCEPTION_IF_CHECK_FAIL(!broadcast_axes.empty(), "BroadcastGradientArgs out should not be empty!"); + auto reduction_axes = broadcast_axes[i1]; + NodePtr reduced_grad = dout; + auto abs = reduction_axes->abstract(); + MS_EXCEPTION_IF_NULL(abs); + auto base_shape = abs->GetShape(); + MS_EXCEPTION_IF_NULL(base_shape); + auto is_dyn_seq = base_shape->isa(); + if (is_dyn_seq) { + reduced_grad = ib->ReduceSum(dout, reduction_axes, true, true); + } else { + auto sequence_shape = base_shape->cast(); + MS_EXCEPTION_IF_NULL(sequence_shape); + if (sequence_shape->size() != 0) { + reduced_grad = ib->SumExt(dout, reduction_axes, ib->Value(true)); + } + } + auto dx = ib->Reshape(reduced_grad, x_shape_node); + return {dx, ib->OutZeros(ib->GetInput(i1))}; +} + REG_BPROP_BUILDERS_BEGIN(GradArrayOps) REG_BPROP_BUILDER("GatherD").SetUnusedInputs({i3}).SetBody(BODYFUNC(ib) { const auto &x = ib->GetInput(i0); @@ -2081,60 +2115,11 @@ REG_BPROP_BUILDER("BatchToSpaceND").SetUnusedInputs({i0, i1}).SetBody(BODYFUNC(i }); REG_BPROP_BUILDER("BroadcastTo").SetUnusedInputs({i0, i1, i2}).SetBody(BODYFUNC(ib) { - const auto &x = ib->GetInput(i0); - const auto &dout = ib->GetInput(i3); - auto x_shape = ib->GetShape(x); - auto dout_shape = ib->GetShape(dout); - - bool input_dynamic = IsDynamic(x_shape) || IsDynamic(dout_shape); - if (!input_dynamic && x_shape == dout_shape) { - return {dout, ib->OutZeros(ib->GetInput(i1))}; - } - - auto x_shape_node = ib->Shape(x); - auto broadcast_axes = ib->BroadcastGradientArgs(dout, x); - MS_EXCEPTION_IF_CHECK_FAIL(!broadcast_axes.empty(), "BroadcastGradientArgs out should not be empty!"); - auto reduction_axes = broadcast_axes[i1]; - NodePtr reduced_grad = dout; - auto abs = reduction_axes->abstract(); - MS_EXCEPTION_IF_NULL(abs); - auto base_shape = abs->GetShape(); - MS_EXCEPTION_IF_NULL(base_shape); - auto is_dyn_seq = base_shape->isa(); - if (is_dyn_seq) { - reduced_grad = ib->ReduceSum(dout, reduction_axes, true, true); - } else { - auto sequence_shape = base_shape->cast(); - MS_EXCEPTION_IF_NULL(sequence_shape); - if (sequence_shape->size() != 0) { - reduced_grad = ib->SumExt(dout, reduction_axes, ib->Value(true)); - } - } - auto dx = ib->Reshape(reduced_grad, x_shape_node); - return {dx, ib->OutZeros(ib->GetInput(i1))}; + return BroadcastToBackward(ib); }); REG_BPROP_BUILDER("BroadcastToView").SetUnusedInputs({i0, i1, i2}).SetBody(BODYFUNC(ib) { - const auto &x = ib->GetInput(i0); - const auto &dout = ib->GetInput(i3); - auto x_shape = ib->GetShape(x); - auto dout_shape = ib->GetShape(dout); - - bool input_dynamic = IsDynamic(x_shape) || IsDynamic(dout_shape); - if (!input_dynamic && x_shape == dout_shape) { - return {dout, ib->OutZeros(ib->GetInput(i1))}; - } - - auto x_shape_node = ib->Shape(x); - auto broadcast_axes = ib->BroadcastGradientArgs(dout, x); - MS_EXCEPTION_IF_CHECK_FAIL(!broadcast_axes.empty(), "BroadcastGradientArgs out should not be empty!"); - auto reduction_axes = broadcast_axes[i1]; - NodePtr reduced_grad = nullptr; - - reduced_grad = ib->ReduceSum(dout, reduction_axes, true, true); - auto dx = ib->Reshape(reduced_grad, x_shape_node); - - return {dx, ib->OutZeros(ib->GetInput(i1))}; + return BroadcastToBackward(ib); }); REG_BPROP_BUILDER("ExpandAs").SetUnusedInputs({i0, i1, i2}).SetBody(BODYFUNC(ib) { diff --git a/mindspore/ops/kernel/ascend/aclnn/kernel_mod_impl/customize/view/broadcast_to_view.cc b/mindspore/ops/kernel/ascend/aclnn/kernel_mod_impl/customize/view/broadcast_to_view.cc index f464c7072af..7aeb383a2bb 100644 --- a/mindspore/ops/kernel/ascend/aclnn/kernel_mod_impl/customize/view/broadcast_to_view.cc +++ b/mindspore/ops/kernel/ascend/aclnn/kernel_mod_impl/customize/view/broadcast_to_view.cc @@ -14,7 +14,7 @@ * limitations under the License. */ #include "kernel/ascend/aclnn/kernel_mod_impl/customize/view/broadcast_to_view.h" - +#include #include "kernel/ascend/aclnn/kernel_mod_impl/customize/view/view_utils.h" #include "mindspore/ops/view/broadcast_to_strides_calc.h" @@ -23,11 +23,10 @@ namespace kernel { void BroadcastToView::UpdateOutputTensorInfo(const std::vector &inputs, const std::vector &outputs) { - ops::OldTensorInfoPtr old_info = GetOldTensorInfo(inputs[kIndex0]); auto shape = inputs[kIndex1]->GetValueWithCheck>(); - - info_ = ops::BroadCastToStrideCalc(old_info->old_shape, old_info->old_strides, inputs[kIndex1]->tensor_storage_info(), - shape); + const auto &input = inputs[kIndex0]; + info_ = + ops::BroadCastToStrideCalc(input->GetShapeVector(), GetTensorStride(input), input->tensor_storage_info(), shape); outputs[kIndex0]->set_tensor_storage_info(info_[0]); } diff --git a/mindspore/ops/op_def/yaml/doc/broadcast_to_view_doc.yaml b/mindspore/ops/op_def/yaml/doc/broadcast_to_view_doc.yaml index 209c43d0866..dae00dce293 100644 --- a/mindspore/ops/op_def/yaml/doc/broadcast_to_view_doc.yaml +++ b/mindspore/ops/op_def/yaml/doc/broadcast_to_view_doc.yaml @@ -1,7 +1,7 @@ broadcast_to_view: description: | - Broadcasts input tensor to a given shape. The dim of input shape must be smaller - than or equal to that of target shape. Suppose input shape is :math:`(x_1, x_2, ..., x_m)`, + Broadcasts input tensor to a given shape. The dim of input must be smaller + than or equal to that of target. Suppose input shape is :math:`(x_1, x_2, ..., x_m)`, target shape is :math:`(*, y_1, y_2, ..., y_m)`, where :math:`*` means any additional dimension. The broadcast rules are as follows: @@ -41,34 +41,26 @@ broadcast_to_view: input shape :math:`(1, 5, 9)`, instead of operating the dim-filling process first, it raises errors directly. Args: - input (Tensor): The input Tensor. - shape (tuple): The target shape to broadcast. Can be fully specified, or have -1 in one position - where it will be substituted by the input tensor's shape in that position, see example. + input (Tensor): The input tensor. + shape (tuple[int]): The target shape. Returns: - Tensor, with the given `shape` and the same data type as `input`. - - Raises: - TypeError: If `shape` is not a tuple. - ValueError: If the target and input shapes are incompatible, or if a - 1 in the target shape is in an invalid - location. + Tensor Supported Platforms: ``Ascend`` Examples: - >>> import numpy as np - >>> from mindspore import Tensor - >>> from mindspore.ops.auto_generate import BroadcastToView + >>> import mindspore >>> shape = (2, 3) - >>> x = Tensor(np.array([1, 2, 3]).astype(np.float32)) - >>> output = BroadcastToView()(x, shape) + >>> x = mindspore.tensor([1, 2, 3], mindspore.float32) + >>> output = mindspore.ops.auto_generate.broadcast_to_view(x, shape) >>> print(output) [[1. 2. 3.] [1. 2. 3.]] >>> shape = (-1, 2) - >>> x = Tensor(np.array([[1], [2]]).astype(np.float32)) - >>> output = BroadcastToView()(x, shape) + >>> x = mindspore.tensor([[1], [2]], mindspore.float32) + >>> output = mindspore.ops.auto_generate.broadcast_to_view(x, shape) >>> print(output) [[1. 1.] [2. 2.]] diff --git a/mindspore/ops/view/broadcast_to_strides_calc.cc b/mindspore/ops/view/broadcast_to_strides_calc.cc index 847cc150e06..526b2589508 100644 --- a/mindspore/ops/view/broadcast_to_strides_calc.cc +++ b/mindspore/ops/view/broadcast_to_strides_calc.cc @@ -18,13 +18,14 @@ #include #include #include +#include #include "utils/core_op_utils.h" #include "utils/check_convert_utils.h" #include "mindspore/ops/op_def/array_op_name.h" namespace mindspore::ops { namespace { -inline static bool BroadcastToCheck(const std::string &prim_name, const std::vector &proposed_shape, +inline static void BroadcastToCheck(const std::string &prim_name, const std::vector &proposed_shape, const std::vector &x_shape) { MS_CHECK_VALUE( x_shape.size() <= proposed_shape.size(), @@ -32,13 +33,11 @@ inline static bool BroadcastToCheck(const std::string &prim_name, const std::vec std::to_string(x_shape.size()) + " and " + std::to_string(proposed_shape.size())); auto outer_dim_offset = proposed_shape.size() - x_shape.size(); - bool flag = true; + bool has_infer_dim = true; if (proposed_shape.end() == find(proposed_shape.begin(), proposed_shape.end(), -1)) { - flag = false; - } else { - flag = true; + has_infer_dim = false; } - if (flag) { + if (has_infer_dim) { for (size_t i = 0; i < proposed_shape.size(); i++) { if (proposed_shape[i] == -1) { if (i < outer_dim_offset) { @@ -47,7 +46,6 @@ inline static bool BroadcastToCheck(const std::string &prim_name, const std::vec "location with given input tensor, -1 index in init shape: " << i << " but -1 can only be in index" << x_shape.size() << "onwards for this input."; - return false; } } } @@ -62,10 +60,8 @@ inline static bool BroadcastToCheck(const std::string &prim_name, const std::vec << "', in order to broadcast, each dimension pair must be equal or input dimension is 1 or target " "dimension is -1. But got x_shape: " << ShapeVectorToStr(x_shape) << ", target shape: " << ShapeVectorToStr(proposed_shape) << "."; - return false; } } - return true; } } // namespace @@ -73,10 +69,10 @@ TensorStorageInfoPtrList BroadCastToStrideCalc(const std::vector &old_s const std::vector &old_strides, const TensorStorageInfoPtr &storage_info, const std::vector &proposed_shape) { + MS_LOG(DEBUG) << "BroadCastTo: input shape " << old_shape << ", input stride " << old_strides << ", storage_info " + << (storage_info != nullptr ? storage_info->ToString() : "null") << ", shape " << proposed_shape; + BroadcastToCheck(kBroadcastToOpName, proposed_shape, old_shape); auto [ori_shape, ori_strides, old_storage_offset] = GetOriShapeStridesAndOffset(old_shape, old_strides, storage_info); - if (MS_UNLIKELY(!BroadcastToCheck(kBroadcastToOpName, proposed_shape, old_shape))) { - return {}; - } int64_t ndim = SizeToInt(proposed_shape.size()); int64_t tensor_ndim = SizeToInt(old_shape.size()); std::vector new_strides(ndim); @@ -85,6 +81,7 @@ TensorStorageInfoPtrList BroadCastToStrideCalc(const std::vector &old_s auto new_storage_info = std::make_shared(proposed_shape, std::move(new_strides), old_storage_offset, std::move(ori_shape), std::move(ori_strides), is_contiguous); + MS_LOG(DEBUG) << "BroadCastTo: output storage_info " << new_storage_info->ToString(); return {new_storage_info}; } @@ -109,6 +106,7 @@ TensorStorageInfoPtrList BroadCastToStrideCalc(const std::vector &old_s auto new_storage_info = std::make_shared(std::move(new_shape), std::move(new_strides), old_storage_offset, std::move(ori_shape), std::move(ori_strides), is_contiguous); + MS_LOG(DEBUG) << "BroadCastTo: output storage_info " << new_storage_info->ToString(); return {new_storage_info}; } diff --git a/mindspore/python/mindspore/mint/__init__.py b/mindspore/python/mindspore/mint/__init__.py index 09b07fa154b..97d072b9f0a 100644 --- a/mindspore/python/mindspore/mint/__init__.py +++ b/mindspore/python/mindspore/mint/__init__.py @@ -166,7 +166,7 @@ from mindspore.ops.function.random_func import normal_ext as normal # 56 from mindspore.ops.function.math_func import norm_ext as norm # 57 -from mindspore.ops.functional import broadcast_to +from mindspore.ops.auto_generate import broadcast_to_view as broadcast_to # 58 from mindspore.ops.functional_overload import greater_equal, ge diff --git a/tests/st/mint/test_broadcast_to.py b/tests/st/mint/test_broadcast_to.py new file mode 100644 index 00000000000..2416bdc1bc4 --- /dev/null +++ b/tests/st/mint/test_broadcast_to.py @@ -0,0 +1,59 @@ +# Copyright 2025 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. +# ============================================================================ +"""Tests for mint.broadcast_to: input, shape.""" +import numpy as np +import pytest + +import mindspore as ms +from tests.st.utils import test_utils +from tests.mark_utils import arg_mark + + +@test_utils.run_with_cell +def broadcast_to_forward(x, shape): + out = ms.mint.broadcast_to(x, shape) + return out + + +@test_utils.run_with_cell +def broadcast_to_backward(x, shape): + return ms.grad(broadcast_to_forward, (0,))(x, shape) + + +@arg_mark(plat_marks=['platform_ascend'], level_mark='level1', + card_mark='onecard', essential_mark='unessential') +@pytest.mark.parametrize('mode', [ms.GRAPH_MODE, ms.PYNATIVE_MODE]) +def test_mint_broadcast_to_normal(mode): + """ + Feature: mint.broadcast_to + Description: Verify the result of mint.broadcast_to + Expectation: success + """ + ms.set_context(mode=mode) + ms.context.set_context(jit_level='O0') + + x_np = np.random.rand(2, 1, 4).astype(np.float32) + x = ms.Tensor(x_np) + shape = (2, 5, 2, 5, 4) + expect_out = np.broadcast_to(x_np, shape) + + out = broadcast_to_forward(x, shape) + assert out.shape == expect_out.shape + assert np.allclose(out.asnumpy(), expect_out) + + expect_grad = np.broadcast_to(np.array([50]).astype(np.float32), (2, 1, 4)) + grad = broadcast_to_backward(x, shape) + assert grad.shape == expect_grad.shape + assert np.allclose(grad.asnumpy(), expect_grad) -- Gitee