Line data Source code
1 : /**
2 : * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3 : * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4 : * CANN Open Software License Agreement Version 2.0 (the "License").
5 : * Please refer to the License for details. You may not use this file except in compliance with the License.
6 : * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7 : * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8 : * See LICENSE in the root of the software repository for the full text of the License.
9 : */
10 :
11 : #include "broadcast_reduce_elimination_pass.h"
12 : #include <vector>
13 : #include <unordered_set>
14 : #include "graph/utils/graph_utils.h"
15 : #include "graph/utils/node_utils.h"
16 : #include "graph/utils/op_desc_utils.h"
17 : #include "graph/attribute_group/attr_group_symbolic_desc.h"
18 : #include "operator_reg.h"
19 : #include "common/checker.h"
20 : #include "debug/ge_util.h"
21 :
22 : namespace ge {
23 : namespace {
24 : // 广播操作类型
25 : constexpr auto kOpTypeBroadcastTo = "BroadcastTo";
26 : constexpr auto kOpTypeFill = "Fill";
27 : constexpr auto kOpTypeTile = "Tile";
28 : constexpr auto kOpTypeTileD = "TileD";
29 : constexpr auto kOpTypeReshape = "Reshape";
30 :
31 : // 归约操作类型(仅支持直接消除的操作,避免数值精度问题)
32 : // 注意:ReduceSum 和 ReduceProd 不支持,因为:
33 : // - Sum -> Mul: broadcast_size 很大时 x*size 可能溢出,而原始累加器更鲁棒
34 : // - Prod -> Pow: Pow 运算比连乘更昂贵且精度波动更大
35 : const std::unordered_set<std::string> kReduceOpTypes = {"ReduceMax", "ReduceMin", "ReduceMean",
36 : "ReduceMaxD", "ReduceMinD", "ReduceMeanD"};
37 :
38 : constexpr auto kAttrNameAxes = "axes";
39 : constexpr auto kAttrNameMultiples = "multiples";
40 : constexpr auto kAttrNameKeepDims = "keep_dims";
41 :
42 : bool IsReduceNode(const NodePtr &node) {
43 : GE_ASSERT_NOTNULL(node);
44 : return kReduceOpTypes.count(node->GetType()) > 0UL;
45 : }
46 :
47 : bool IsBroadcastNode(const NodePtr &node) {
48 : GE_ASSERT_NOTNULL(node);
49 : const auto &op_type = node->GetType();
50 : return op_type == kOpTypeBroadcastTo || op_type == kOpTypeFill || op_type == kOpTypeTile || op_type == kOpTypeTileD;
51 : }
52 :
53 : // 标准化轴索引(处理负数轴)
54 : int64_t NormalizeAxis(int64_t axis, int64_t rank) {
55 : if (axis < 0) {
56 : return axis + rank;
57 : }
58 : return axis;
59 : }
60 :
61 : // 获取 BroadcastTo 操作的广播轴(通过 shape 推导)
62 : // 使用标准广播语义:从右向左对齐
63 : bool GetBroadcastToAxes(const NodePtr &brc_node, std::vector<int64_t> &brc_axes) {
64 : brc_axes.clear();
65 : const auto &op_desc = brc_node->GetOpDesc();
66 : GE_ASSERT_NOTNULL(op_desc);
67 :
68 : if (op_desc->GetInputsSize() == 0) {
69 : return false;
70 : }
71 :
72 : const auto &input_shape = op_desc->GetInputDesc(0).GetShape().GetDims();
73 : const auto &output_shape = op_desc->GetOutputDesc(0).GetShape().GetDims();
74 : GE_ASSERT_TRUE(input_shape.size() <= output_shape.size(), "BroadcastTo %s: input rank > output rank",
75 : brc_node->GetNamePtr());
76 :
77 : // 检查输入 shape 是否有动态维度(输出可以有 -1)
78 : // 输入有 -1 时无法确定是否是广播轴,不能优化
79 : for (auto dim : input_shape) {
80 : if (dim == -1) {
81 : GELOGW("BroadcastTo %s has dynamic input shape", brc_node->GetNamePtr());
82 : return false;
83 : }
84 : }
85 :
86 : // 从右向左对齐(标准广播语义)
87 : // 例如:input [c, d] -> output [a, b, c, d]
88 : // 实际上是 [1, 1, c, d] -> [a, b, c, d]
89 : auto input_rank = static_cast<int64_t>(input_shape.size());
90 : auto output_rank = static_cast<int64_t>(output_shape.size());
91 : int64_t rank_diff = output_rank - input_rank;
92 :
93 : for (int64_t i = 0; i < output_rank; ++i) {
94 : int64_t in_dim = (i >= rank_diff) ? input_shape[i - rank_diff] : 1;
95 : // in_dim=1 且 output_dim!=1 时是广播轴(output_dim 可以是 -1,表示动态广播)
96 : if (in_dim == 1 && output_shape[i] != 1) {
97 : brc_axes.push_back(i);
98 : }
99 : }
100 :
101 : return !brc_axes.empty();
102 : }
103 :
104 : // 获取 Fill 的广播轴(所有输出维度都是广播轴)
105 : bool GetFillBroadcastAxes(const NodePtr &fill_node, std::vector<int64_t> &brc_axes) {
106 : brc_axes.clear();
107 : const auto &op_desc = fill_node->GetOpDesc();
108 : GE_ASSERT_NOTNULL(op_desc);
109 :
110 : if (op_desc->GetInputsSize() < 2UL) {
111 : return false;
112 : }
113 :
114 : // 检查 value 输入(index 1)是否有动态 shape
115 : // 如果 value shape 包含 -1,无法确定 reshape 目标,不能优化
116 : const auto &value_shape = op_desc->GetInputDesc(1).GetShape().GetDims();
117 : for (auto dim : value_shape) {
118 : if (dim == -1) {
119 : GELOGW("Fill %s has dynamic value shape, cannot optimize", fill_node->GetNamePtr());
120 : return false;
121 : }
122 : }
123 :
124 : const auto &output_shape = op_desc->GetOutputDesc(0).GetShape().GetDims();
125 : for (size_t i = 0; i < output_shape.size(); ++i) {
126 : brc_axes.push_back(static_cast<int64_t>(i));
127 : }
128 :
129 : return !brc_axes.empty();
130 : }
131 :
132 : bool ReadIntVecFromInput(const NodePtr &node, const std::string &input_name, std::vector<int64_t> &values) {
133 : const auto op = ge::OpDescUtils::CreateOperatorFromNode(node);
134 : ge::Tensor tensor;
135 : if (op.GetInputConstData(input_name.c_str(), tensor) != ge::SUCCESS) {
136 : GELOGD("ReadIntVecFromInput %s: GetInputConstData failed for input '%s'", node->GetNamePtr(), input_name.c_str());
137 : return false;
138 : }
139 : if (tensor.GetData() == nullptr) {
140 : GELOGD("ReadIntVecFromInput %s: tensor data is null for input '%s'", node->GetNamePtr(), input_name.c_str());
141 : return false;
142 : }
143 : const auto &dims = tensor.GetTensorDesc().GetShape().GetDims();
144 : if (dims.size() > 1U) {
145 : GELOGD("ReadIntVecFromInput %s: input '%s' dims size %zu is not scalar or 1D", node->GetNamePtr(),
146 : input_name.c_str(), dims.size());
147 : return false;
148 : }
149 : const int64_t num_elems = dims.empty() ? 1L : dims[0];
150 : const auto dtype = tensor.GetTensorDesc().GetDataType();
151 : for (int64_t i = 0L; i < num_elems; ++i) {
152 : if (dtype == ge::DT_INT32) {
153 : values.push_back(reinterpret_cast<const int32_t *>(tensor.GetData())[i]);
154 : } else if (dtype == ge::DT_INT64) {
155 : values.push_back(reinterpret_cast<const int64_t *>(tensor.GetData())[i]);
156 : } else {
157 : GELOGW("ReadIntVecFromInput %s: unsupported dtype=%d for input '%s'", node->GetNamePtr(),
158 : static_cast<int32_t>(dtype), input_name.c_str());
159 : return false;
160 : }
161 : }
162 : return true;
163 : }
164 :
165 : // 获取 Tile 的 multiples(优先属性,fallback 到输入 tensor)
166 : bool GetTileMultiples(const NodePtr &tile_node, std::vector<int64_t> &multiples) {
167 : const auto &op_desc = tile_node->GetOpDesc();
168 : if (AttrUtils::GetListInt(op_desc, kAttrNameMultiples, multiples)) {
169 : return true;
170 : }
171 : return ReadIntVecFromInput(tile_node, kAttrNameMultiples, multiples);
172 : }
173 :
174 : // 获取 Tile 的广播轴(输入维度=1 且 multiples>1 的轴)
175 : bool GetTileBroadcastAxes(const NodePtr &tile_node, std::vector<int64_t> &brc_axes) {
176 : brc_axes.clear();
177 : const auto &op_desc = tile_node->GetOpDesc();
178 : GE_ASSERT_NOTNULL(op_desc);
179 :
180 : std::vector<int64_t> multiples;
181 : if (!GetTileMultiples(tile_node, multiples)) {
182 : GELOGD("Tile %s: cannot get multiples from attr or input", tile_node->GetNamePtr());
183 : return false;
184 : }
185 :
186 : const auto &input_shape = op_desc->GetInputDesc(0).GetShape().GetDims();
187 : if (multiples.size() != input_shape.size()) {
188 : GELOGW("Tile %s: multiples.size() != input_shape.size()", tile_node->GetNamePtr());
189 : return false;
190 : }
191 :
192 : // 检查动态 shape
193 : for (auto dim : input_shape) {
194 : if (dim == -1) {
195 : GELOGW("Tile %s has dynamic input shape", tile_node->GetNamePtr());
196 : return false;
197 : }
198 : }
199 : for (auto mult : multiples) {
200 : if (mult == -1) {
201 : GELOGW("Tile %s has dynamic multiples", tile_node->GetNamePtr());
202 : return false;
203 : }
204 : }
205 :
206 : // 找出广播轴(输入维度=1 且 multiples>1 的轴)
207 : for (size_t i = 0; i < input_shape.size(); ++i) {
208 : if (input_shape[i] == 1 && multiples[i] > 1) {
209 : brc_axes.push_back(static_cast<int64_t>(i));
210 : }
211 : }
212 :
213 : return !brc_axes.empty();
214 : }
215 :
216 : bool GetReduceAttrs(const NodePtr &reduce_node, std::vector<int64_t> &reduce_axes, bool &keep_dims) {
217 : reduce_axes.clear();
218 : keep_dims = false;
219 : const auto &op_desc = reduce_node->GetOpDesc();
220 : GE_ASSERT_NOTNULL(op_desc);
221 : GE_ASSERT_TRUE(AttrUtils::GetBool(op_desc, kAttrNameKeepDims, keep_dims));
222 :
223 : if (AttrUtils::GetListInt(op_desc, kAttrNameAxes, reduce_axes)) {
224 : return true;
225 : }
226 : // 非 D 变体:axes 是第二个输入 tensor,动态获取输入名
227 : if (reduce_node->GetAllInDataAnchorsSize() >= 2U) {
228 : std::string input_name = op_desc->GetInputNameByIndex(1U);
229 : if (ReadIntVecFromInput(reduce_node, input_name, reduce_axes)) {
230 : return !reduce_axes.empty();
231 : }
232 : }
233 :
234 : GELOGW("GetReduceAttrs %s: failed to get reduce axes from attr or input", reduce_node->GetNamePtr());
235 : return false;
236 : }
237 :
238 : // 按 broadcast 类型获取广播轴
239 : bool GetBroadcastAxes(const NodePtr &brc_node, std::vector<int64_t> &brc_axes) {
240 : const auto &brc_type = brc_node->GetType();
241 :
242 : if (brc_type == kOpTypeBroadcastTo) {
243 : return GetBroadcastToAxes(brc_node, brc_axes);
244 : } else if (brc_type == kOpTypeFill) {
245 : return GetFillBroadcastAxes(brc_node, brc_axes);
246 : } else if (brc_type == kOpTypeTile || brc_type == kOpTypeTileD) {
247 : return GetTileBroadcastAxes(brc_node, brc_axes);
248 : }
249 :
250 : return false;
251 : }
252 :
253 : // 优化类型
254 : enum class EliminationType { kFullElimination, kNoElimination };
255 :
256 : EliminationType AnalyzeBroadcastReduce(const std::vector<int64_t> &brc_axes, const std::vector<int64_t> &reduce_axes) {
257 : // broadcast 轴和 reduce 轴必须完全一致:
258 : // - reduce 不能覆盖非广播轴(会丢失真实数据计算)
259 : // - 所有广播轴都必须被 reduce(否则输出仍含广播维度)
260 : const std::unordered_set<int64_t> brc_set(brc_axes.begin(), brc_axes.end());
261 : const std::unordered_set<int64_t> reduce_set(reduce_axes.begin(), reduce_axes.end());
262 : if (brc_set != reduce_set) {
263 : return EliminationType::kNoElimination;
264 : }
265 : return EliminationType::kFullElimination;
266 : }
267 :
268 : // 检查是否有其他消费者
269 : bool HasOtherConsumers(const NodePtr &brc_node, const NodePtr &reduce_node) {
270 : for (const auto &out_anchor : brc_node->GetAllOutDataAnchors()) {
271 : for (const auto &in_anchor : out_anchor->GetPeerInDataAnchors()) {
272 : if (in_anchor->GetOwnerNode() != reduce_node) {
273 : return true;
274 : }
275 : }
276 : }
277 : return false;
278 : }
279 :
280 : // 计算 Reduce 输出 shape
281 : std::vector<int64_t> ComputeReduceOutputShape(const std::vector<int64_t> &broadcast_output_dims,
282 : const std::vector<int64_t> &reduce_axes, bool keep_dims) {
283 : const std::unordered_set<int64_t> reduce_set(reduce_axes.begin(), reduce_axes.end());
284 : std::vector<int64_t> output_dims;
285 : for (size_t i = 0; i < broadcast_output_dims.size(); ++i) {
286 : if (reduce_set.count(static_cast<int64_t>(i)) > 0) {
287 : if (keep_dims) {
288 : output_dims.push_back(1);
289 : }
290 : } else {
291 : output_dims.push_back(broadcast_output_dims[i]);
292 : }
293 : }
294 : return output_dims;
295 : }
296 :
297 : // 替换节点并清理
298 : Status ReplaceAndCleanup(const ComputeGraphPtr &graph, const NodePtr &brc_node, const NodePtr &reduce_node,
299 : const NodePtr &replacement_node) {
300 : // 迁移数据边
301 : for (const auto &out_anchor : reduce_node->GetAllOutDataAnchors()) {
302 : for (const auto &in_anchor : out_anchor->GetPeerInDataAnchors()) {
303 : GE_ASSERT_GRAPH_SUCCESS(GraphUtils::RemoveEdge(out_anchor, in_anchor));
304 : GE_ASSERT_GRAPH_SUCCESS(GraphUtils::AddEdge(replacement_node->GetOutDataAnchor(0), in_anchor));
305 : }
306 : }
307 :
308 : // 迁移 Reduce 节点的入控制边
309 : for (const auto &ctrl_in : reduce_node->GetInControlNodes()) {
310 : GE_ASSERT_GRAPH_SUCCESS(GraphUtils::RemoveEdge(ctrl_in->GetOutControlAnchor(), reduce_node->GetInControlAnchor()));
311 : GE_ASSERT_GRAPH_SUCCESS(
312 : GraphUtils::AddEdge(ctrl_in->GetOutControlAnchor(), replacement_node->GetInControlAnchor()));
313 : }
314 :
315 : // 迁移 Broadcast 节点的入控制边(保持执行顺序依赖)
316 : for (const auto &ctrl_in : brc_node->GetInControlNodes()) {
317 : GE_ASSERT_GRAPH_SUCCESS(GraphUtils::RemoveEdge(ctrl_in->GetOutControlAnchor(), brc_node->GetInControlAnchor()));
318 : GE_ASSERT_GRAPH_SUCCESS(
319 : GraphUtils::AddEdge(ctrl_in->GetOutControlAnchor(), replacement_node->GetInControlAnchor()));
320 : }
321 :
322 : NodeUtils::UnlinkAll(*brc_node);
323 : NodeUtils::UnlinkAll(*reduce_node);
324 : GE_ASSERT_GRAPH_SUCCESS(graph->RemoveNode(brc_node));
325 : GE_ASSERT_GRAPH_SUCCESS(graph->RemoveNode(reduce_node));
326 : return ge::SUCCESS;
327 : }
328 :
329 : // 创建 Reshape 节点
330 : NodePtr CreateReshapeNode(const ComputeGraphPtr &graph, const NodePtr &input, const std::vector<int64_t> &target_shape,
331 : const std::string &name) {
332 : GeTensorDesc input_desc = input->GetOpDesc()->GetOutputDesc(0);
333 : GeTensorDesc output_desc(input_desc);
334 : output_desc.SetShape(GeShape(target_shape));
335 3 : output_desc.SetOriginShape(GeShape(target_shape));
336 :
337 : // 输入描述可能携带原始张量的符号化 shape。替换节点是真实的 Reshape,
338 : // 因此符号化 shape 的 rank 必须与目标 shape 以及静态 GeShape 保持一致。
339 3 : auto symbolic_attr = output_desc.GetOrCreateAttrsGroup<SymbolicDescAttr>();
340 3 : GE_ASSERT_NOTNULL(symbolic_attr);
341 3 : std::vector<Expression> symbolic_shape;
342 3 : symbolic_shape.reserve(target_shape.size());
343 7 : for (const auto dim : target_shape) {
344 4 : symbolic_shape.emplace_back(Symbol(dim));
345 : }
346 3 : symbolic_attr->symbolic_tensor.MutableOriginSymbolShape().MutableDims() = symbolic_shape;
347 :
348 : // Reshape 有两个输入:数据 x 和目标 shape。创建目标 shape 常量并连接到第二个输入。
349 3 : const auto shape_tensor = ComGraphMakeShared<GeTensor>();
350 3 : GE_ASSERT_NOTNULL(shape_tensor);
351 3 : auto &shape_desc = shape_tensor->MutableTensorDesc();
352 6 : const GeShape shape_tensor_shape({static_cast<int64_t>(target_shape.size())});
353 3 : shape_desc.Update(shape_tensor_shape, FORMAT_ND, DT_INT64);
354 3 : shape_desc.SetOriginShape(shape_tensor_shape);
355 3 : if (!target_shape.empty()) {
356 3 : GE_ASSERT_GRAPH_SUCCESS(shape_tensor->SetData(reinterpret_cast<const uint8_t *>(target_shape.data()),
357 : target_shape.size() * sizeof(int64_t)));
358 : }
359 3 : const auto shape_op_desc = OpDescUtils::CreateConstOpZeroCopy(shape_tensor);
360 3 : GE_ASSERT_NOTNULL(shape_op_desc);
361 3 : const auto shape_node = graph->AddNode(shape_op_desc);
362 3 : GE_ASSERT_NOTNULL(shape_node);
363 :
364 3 : const auto reshape_op_desc = ComGraphMakeShared<OpDesc>(name, kOpTypeReshape);
365 3 : GE_ASSERT_NOTNULL(reshape_op_desc);
366 9 : GE_ASSERT_GRAPH_SUCCESS(reshape_op_desc->AddInputDesc("x", input_desc));
367 9 : GE_ASSERT_GRAPH_SUCCESS(reshape_op_desc->AddInputDesc("shape", shape_desc));
368 9 : GE_ASSERT_GRAPH_SUCCESS(reshape_op_desc->AddOutputDesc("y", output_desc));
369 3 : auto reshape_node = graph->AddNode(reshape_op_desc);
370 : GE_ASSERT_NOTNULL(reshape_node);
371 3 : GE_ASSERT_GRAPH_SUCCESS(GraphUtils::AddEdge(shape_node->GetOutDataAnchor(0), reshape_node->GetInDataAnchor(1)),
372 : "Failed to add shape edge to Reshape node %s", reshape_node->GetNamePtr());
373 : GE_ASSERT_GRAPH_SUCCESS(GraphUtils::AddEdge(input->GetOutDataAnchor(0), reshape_node->GetInDataAnchor(0)),
374 : "Failed to add edge to Reshape node %s", reshape_node->GetNamePtr());
375 :
376 : return reshape_node;
377 : }
378 :
379 : // 创建替换节点
380 : NodePtr CreateReplacementNode(const ComputeGraphPtr &graph, const NodePtr &input,
381 : const std::vector<int64_t> &target_shape, const std::string &base_name) {
382 : auto input_dims = input->GetOpDesc()->GetOutputDesc(0).GetShape().GetDims();
383 : // shape 完全相同,直接用原始输入
384 : if (input_dims == target_shape) {
385 : return input;
386 : }
387 :
388 : // shape 不同,用 Reshape 节点
389 : return CreateReshapeNode(graph, input, target_shape, base_name + "_reshape");
390 : }
391 :
392 : // 获取 broadcast 的数据输入节点
393 : NodePtr GetBroadcastInput(const NodePtr &brc_node) {
394 : if (brc_node->GetType() == kOpTypeFill) {
395 : return NodeUtils::GetInDataNodeByIndex(*brc_node, 1);
396 : }
397 : return NodeUtils::GetInDataNodeByIndex(*brc_node, 0);
398 : }
399 :
400 : // 处理 Broadcast + Reduce 优化
401 : graphStatus ProcessBroadcastReduce(const NodePtr &brc_node, const NodePtr &reduce_node, const ComputeGraphPtr &graph,
402 : bool &changed) {
403 : // 1. 获取广播轴
404 : std::vector<int64_t> brc_axes;
405 : if (!GetBroadcastAxes(brc_node, brc_axes)) {
406 : return GRAPH_SUCCESS;
407 : }
408 :
409 : // 2. 获取归约属性并标准化
410 : std::vector<int64_t> reduce_axes;
411 : bool keep_dims = false;
412 : if (!GetReduceAttrs(reduce_node, reduce_axes, keep_dims)) {
413 : return GRAPH_SUCCESS;
414 : }
415 :
416 : int64_t output_rank = brc_node->GetOpDesc()->GetOutputDesc(0).GetShape().GetDims().size();
417 : for (auto &axis : reduce_axes) {
418 : axis = NormalizeAxis(axis, output_rank);
419 : }
420 :
421 : // 3. 检查是否可优化
422 : if (AnalyzeBroadcastReduce(brc_axes, reduce_axes) == EliminationType::kNoElimination) {
423 : return GRAPH_SUCCESS;
424 : }
425 :
426 : // 4. 获取广播输入
427 : auto brc_input = GetBroadcastInput(brc_node);
428 : GE_ASSERT_NOTNULL(brc_input);
429 :
430 : // 5. 创建替换节点并执行消除
431 : auto broadcast_output_dims = brc_node->GetOpDesc()->GetOutputDesc(0).GetShape().GetDims();
432 : auto reduce_output_dims = ComputeReduceOutputShape(broadcast_output_dims, reduce_axes, keep_dims);
433 : auto replacement_node = CreateReplacementNode(graph, brc_input, reduce_output_dims,
434 : std::string(brc_input->GetNamePtr()) + "_" + reduce_node->GetNamePtr());
435 : if (!replacement_node) {
436 : GELOGW("Failed to create replacement node for %s + %s", brc_node->GetNamePtr(), reduce_node->GetNamePtr());
437 : return GRAPH_SUCCESS;
438 : }
439 :
440 : GELOGD("BroadcastReduceElimination: eliminating %s + %s (keep_dims=%d)", brc_node->GetType().c_str(),
441 : reduce_node->GetType().c_str(), keep_dims);
442 : GE_ASSERT_SUCCESS(ReplaceAndCleanup(graph, brc_node, reduce_node, replacement_node));
443 : changed = true;
444 : return GRAPH_SUCCESS;
445 : }
446 : } // namespace
447 :
448 : graphStatus BroadcastReduceEliminationPass::Run(const ComputeGraphPtr &graph, bool &changed) const {
449 : GE_ASSERT_NOTNULL(graph);
450 : std::vector<std::pair<NodePtr, NodePtr> > pairs_to_process;
451 : for (const auto &node : graph->GetDirectNode()) {
452 : GE_ASSERT_NOTNULL(node);
453 :
454 : if (!IsReduceNode(node)) {
455 : continue;
456 : }
457 :
458 : auto input_node = NodeUtils::GetInDataNodeByIndex(*node, 0);
459 : if ((input_node == nullptr) || !IsBroadcastNode(input_node)) {
460 : continue;
461 : }
462 :
463 : if (HasOtherConsumers(input_node, node)) {
464 : continue;
465 : }
466 :
467 : pairs_to_process.emplace_back(input_node, node);
468 : }
469 :
470 : uint32_t optimized_count = 0U;
471 : for (const auto &pair : pairs_to_process) {
472 : bool cur_changed = false;
473 : auto ret = ProcessBroadcastReduce(pair.first, pair.second, graph, cur_changed);
474 : if (ret != GRAPH_SUCCESS) {
475 : GELOGW("Failed to process broadcast-reduce pattern for %s + %s", pair.first->GetNamePtr(),
476 : pair.second->GetNamePtr());
477 : continue;
478 : }
479 :
480 : if (cur_changed) {
481 : optimized_count++;
482 : }
483 : }
484 :
485 : if (optimized_count > 0U) {
486 : changed = true;
487 : GELOGI("BroadcastReduceEliminationPass: optimized %u patterns", optimized_count);
488 : }
489 :
490 : return GRAPH_SUCCESS;
491 : }
492 : } // namespace ge
|