已合并
fix: 保留后端不支持的浮点 Cast 路径(#247) #1762
zqmin创建于 20 天前
fix: 保留后端不支持的浮点 Cast 路径(#247) #1762
已合并
共 4 个文件变更+355-64
| @@ -11,6 +11,7 @@ | |||
| 11 | 11 | ||
| 12 | 12 | ||
| 13 | 13 | ||
| 14 | + | ||
| 14 | 15 | ||
| 15 | 16 | ||
| 16 | 17 | ||
| @@ -130,6 +131,13 @@ Status GetOutputTensorDesc(const NodePtr &node, GeTensorDescPtr &output_tensor_d | |||
| 130 | return af::SUCCESS; | 131 | return af::SUCCESS; |
| 131 | } | 132 | } |
| 132 | 133 | ||
| 134 | +Status GetNodeOutputDtype(const NodePtr &node, DataType &dtype) { | ||
| 135 | + GeTensorDescPtr output_tensor_desc; | ||
| 136 | + GE_ASSERT_SUCCESS(GetOutputTensorDesc(node, output_tensor_desc)); | ||
| 137 | + dtype = output_tensor_desc->GetDataType(); | ||
| 138 | + return af::SUCCESS; | ||
| 139 | +} | ||
| 140 | + | ||
| 133 | Status DelNode(AscGraph &asc_graph, const NodePtr &node) { | 141 | Status DelNode(AscGraph &asc_graph, const NodePtr &node) { |
| 134 | const auto in_data_anchor = node->GetInDataAnchor(0); | 142 | const auto in_data_anchor = node->GetInDataAnchor(0); |
| 135 | GE_ASSERT_NOTNULL(in_data_anchor); | 143 | GE_ASSERT_NOTNULL(in_data_anchor); |
| @@ -256,8 +264,145 @@ const std::unordered_map<std::string, std::string> kTypeToGroup = { | |||
| 256 | {af::ascir_op::Scalar::Type, af::ascir_op::Scalar::Type}, | 264 | {af::ascir_op::Scalar::Type, af::ascir_op::Scalar::Type}, |
| 257 | {af::ascir_op::Store::Type, af::ascir_op::Store::Type}}; | 265 | {af::ascir_op::Store::Type, af::ascir_op::Store::Type}}; |
| 258 | 266 | ||
| 259 | -bool ShouldDeleteCastNode(DataType peer_output_dtype, DataType output_dtype) { | 267 | +struct CastChain { |
| 260 | - return IsFloatDataType(output_dtype) && IsFloatDataType(peer_output_dtype); | 268 | + std::vector<NodePtr> nodes; |
| 269 | + std::vector<DataType> output_dtypes; | ||
| 270 | +}; | ||
| 271 | + | ||
| 272 | +bool CanCollapseCast(DataType input_dtype, DataType output_dtype) { | ||
| 273 | + return input_dtype == output_dtype || | ||
| 274 | + (IsFloatDataType(input_dtype) && IsFloatDataType(output_dtype) && CheckCastDtype(input_dtype, output_dtype)); | ||
| 275 | +} | ||
| 276 | + | ||
| 277 | +struct CastChainPlan { | ||
| 278 | + std::vector<size_t> costs; | ||
| 279 | + std::vector<size_t> previous; | ||
| 280 | + std::vector<bool> requires_cast; | ||
| 281 | +}; | ||
| 282 | + | ||
| 283 | +Status CollectLinearCastChain(const NodePtr &start, CastChain &chain) { | ||
| 284 | + chain.nodes.clear(); | ||
| 285 | + chain.output_dtypes.clear(); | ||
| 286 | + NodePtr current = start; | ||
| 287 | + while (current->GetType() == af::ascir_op::Cast::Type) { | ||
| 288 | + std::vector<NodePtr> consumers; | ||
| 289 | + GE_ASSERT_SUCCESS(GetPeerInNodes(current, consumers, 0)); | ||
| 290 | + if (consumers.size() != 1U) { | ||
| 291 | + chain.nodes.clear(); | ||
| 292 | + chain.output_dtypes.clear(); | ||
| 293 | + return af::SUCCESS; | ||
| 294 | + } | ||
| 295 | + const auto asc_node = std::dynamic_pointer_cast<af::AscNode>(current); | ||
| 296 | + GE_ASSERT_NOTNULL(asc_node); | ||
| 297 | + chain.nodes.push_back(current); | ||
| 298 | + chain.output_dtypes.push_back(asc_node->outputs[0].attr.dtype); | ||
| 299 | + current = consumers[0]; | ||
| 300 | + } | ||
| 301 | + return af::SUCCESS; | ||
| 302 | +} | ||
| 303 | + | ||
| 304 | +void UpdateMinimumCastPath(const std::vector<DataType> &output_dtypes, size_t target, CastChainPlan &plan) { | ||
| 305 | + const auto unreachable = output_dtypes.size() + 1U; | ||
| 306 | + for (size_t source = 0U; source < target; ++source) { | ||
| 307 | + if (plan.costs[source] == unreachable || !CanCollapseCast(output_dtypes[source], output_dtypes[target])) { | ||
| 308 | + continue; | ||
| 309 | + } | ||
| 310 | + const auto transition_cost = output_dtypes[source] == output_dtypes[target] ? 0U : 1U; | ||
| 311 | + if (plan.costs[source] + transition_cost < plan.costs[target]) { | ||
| 312 | + plan.costs[target] = plan.costs[source] + transition_cost; | ||
| 313 | + plan.previous[target] = source; | ||
| 314 | + plan.requires_cast[target] = transition_cost == 1U; | ||
| 315 | + } | ||
| 316 | + } | ||
| 317 | +} | ||
| 318 | + | ||
| 319 | +CastChainPlan BuildMinimumCastPlan(DataType source_dtype, const std::vector<DataType> &output_dtypes) { | ||
| 320 | + const auto chain_size = output_dtypes.size(); | ||
| 321 | + CastChainPlan plan{std::vector<size_t>(chain_size, chain_size + 1U), std::vector<size_t>(chain_size, chain_size), | ||
| 322 | + std::vector<bool>(chain_size, false)}; | ||
| 323 | + for (size_t target = 0U; target < chain_size; ++target) { | ||
| 324 | + if (CanCollapseCast(source_dtype, output_dtypes[target])) { | ||
| 325 | + plan.requires_cast[target] = source_dtype != output_dtypes[target]; | ||
| 326 | + plan.costs[target] = plan.requires_cast[target] ? 1U : 0U; | ||
| 327 | + } | ||
| 328 | + UpdateMinimumCastPath(output_dtypes, target, plan); | ||
| 329 | + } | ||
| 330 | + return plan; | ||
| 331 | +} | ||
| 332 | + | ||
| 333 | +std::vector<bool> MarkRetainedCastNodes(const CastChainPlan &plan) { | ||
| 334 | + const auto chain_size = plan.previous.size(); | ||
| 335 | + std::vector<bool> keep(chain_size, false); | ||
| 336 | + for (size_t index = chain_size - 1U; index < chain_size; index = plan.previous[index]) { | ||
| 337 | + keep[index] = plan.requires_cast[index]; | ||
| 338 | + if (plan.previous[index] >= chain_size) { | ||
| 339 | + break; | ||
| 340 | + } | ||
| 341 | + } | ||
| 342 | + return keep; | ||
| 343 | +} | ||
| 344 | + | ||
| 345 | +Status DeleteUnretainedCastNodes(AscGraph &asc_graph, const CastChain &chain, const std::vector<bool> &keep, | ||
| 346 | + std::vector<NodePtr> &retained_nodes) { | ||
| 347 | + retained_nodes.clear(); | ||
| 348 | + for (size_t i = 0U; i < chain.nodes.size(); ++i) { | ||
| 349 | + if (keep[i]) { | ||
| 350 | + retained_nodes.push_back(chain.nodes[i]); | ||
| 351 | + } | ||
| 352 | + } | ||
| 353 | + for (auto i = chain.nodes.size(); i > 0U; --i) { | ||
| 354 | + if (!keep[i - 1U]) { | ||
| 355 | + GE_ASSERT_SUCCESS(DelNode(asc_graph, chain.nodes[i - 1U])); | ||
| 356 | + } | ||
| 357 | + } | ||
| 358 | + return af::SUCCESS; | ||
| 359 | +} | ||
| 360 | + | ||
| 361 | +Status OptimizeLinearCastChain(AscGraph &asc_graph, const NodePtr &source, const CastChain &chain, | ||
| 362 | + std::vector<NodePtr> &retained_nodes) { | ||
| 363 | + retained_nodes.clear(); | ||
| 364 | + GE_ASSERT_TRUE(chain.nodes.size() > 1U); | ||
| 365 | + DataType source_dtype; | ||
| 366 | + GE_ASSERT_SUCCESS(GetNodeOutputDtype(source, source_dtype)); | ||
| 367 | + if (source_dtype == chain.output_dtypes.back()) { | ||
| 368 | + for (auto i = chain.nodes.size(); i > 0U; --i) { | ||
| 369 | + GE_ASSERT_SUCCESS(DelNode(asc_graph, chain.nodes[i - 1U])); | ||
| 370 | + } | ||
| 371 | + return af::SUCCESS; | ||
| 372 | + } | ||
| 373 | + const auto plan = BuildMinimumCastPlan(source_dtype, chain.output_dtypes); | ||
| 374 | + if (plan.costs.back() == chain.nodes.size() + 1U) { | ||
| 375 | + retained_nodes = chain.nodes; | ||
| 376 | + return af::SUCCESS; | ||
| 377 | + } | ||
| 378 | + return DeleteUnretainedCastNodes(asc_graph, chain, MarkRetainedCastNodes(plan), retained_nodes); | ||
| 379 | +} | ||
| 380 | + | ||
| 381 | +Status CanOptimizeLinearCastChain(const NodePtr &source, const CastChain &chain, bool &can_optimize) { | ||
| 382 | + can_optimize = false; | ||
| 383 | + if (chain.nodes.size() <= 1U) { | ||
| 384 | + return af::SUCCESS; | ||
| 385 | + } | ||
| 386 | + DataType source_dtype; | ||
| 387 | + GE_ASSERT_SUCCESS(GetNodeOutputDtype(source, source_dtype)); | ||
| 388 | + if (!IsFloatDataType(source_dtype)) { | ||
| 389 | + return af::SUCCESS; | ||
| 390 | + } | ||
| 391 | + can_optimize = std::all_of(chain.output_dtypes.begin(), chain.output_dtypes.end(), | ||
| 392 | + [](const auto dtype) { return IsFloatDataType(dtype); }); | ||
| 393 | + return af::SUCCESS; | ||
| 394 | +} | ||
| 395 | + | ||
| 396 | +Status DeleteIdentityCast(AscGraph &asc_graph, const NodePtr &source, const NodePtr &cast_node, bool &deleted) { | ||
| 397 | + DataType source_dtype; | ||
| 398 | + GE_ASSERT_SUCCESS(GetNodeOutputDtype(source, source_dtype)); | ||
| 399 | + DataType cast_dtype; | ||
| 400 | + GE_ASSERT_SUCCESS(GetNodeOutputDtype(cast_node, cast_dtype)); | ||
| 401 | + deleted = source_dtype == cast_dtype; | ||
| 402 | + if (deleted) { | ||
| 403 | + GE_ASSERT_SUCCESS(DelNode(asc_graph, cast_node)); | ||
| 404 | + } | ||
| 405 | + return af::SUCCESS; | ||
| 261 | } | 406 | } |
| 262 | 407 | ||
| 263 | bool ShouldChangeDataType(const NodePtr &node, const std::vector<NodePtr> &peer_in_nodes, DataType peer_output_dtype, | 408 | bool ShouldChangeDataType(const NodePtr &node, const std::vector<NodePtr> &peer_in_nodes, DataType peer_output_dtype, |
| @@ -344,7 +489,7 @@ Status ConfigureCastTensor(const NodePtr &src_node, const NodePtr &cast_node, co | |||
| 344 | GE_ASSERT_NOTNULL(c_o_attr); | 489 | GE_ASSERT_NOTNULL(c_o_attr); |
| 345 | // 当上游节点为 Scalar 时,其输出为标量形状,axis/repeats/strides 不包含目标张量的完整形状信息。 | 490 | // 当上游节点为 Scalar 时,其输出为标量形状,axis/repeats/strides 不包含目标张量的完整形状信息。 |
| 346 | // 前因:前端可能传入冗余连续 Cast(如 scalar->cast(FP32->FP32)->cast(FP32->FP16)->store), | 491 | // 前因:前端可能传入冗余连续 Cast(如 scalar->cast(FP32->FP32)->cast(FP32->FP16)->store), |
| 347 | - // ImprovePrecision 的 ShouldDeleteCastNode 会删除所有浮点间 Cast,再在 Store 前重新插入新 Cast。 | 492 | + // ImprovePrecision 会删除冗余浮点 Cast,再在 Store 前重新插入新 Cast。 |
| 348 | // 若从 Scalar 复制输出信息,新 Cast 的 axis/repeats/strides 为空, | 493 | // 若从 Scalar 复制输出信息,新 Cast 的 axis/repeats/strides 为空, |
| 349 | // 导致后续 InsertBroadcast 补充的 broadcast 节点输出信息也丢失。 | 494 | // 导致后续 InsertBroadcast 补充的 broadcast 节点输出信息也丢失。 |
| 350 | // 因此当上游为 Scalar 时,从下游节点取 tensor 信息。 | 495 | // 因此当上游为 Scalar 时,从下游节点取 tensor 信息。 |
| @@ -363,24 +508,17 @@ Status ConfigureCastTensor(const NodePtr &src_node, const NodePtr &cast_node, co | |||
| 363 | return af::SUCCESS; | 508 | return af::SUCCESS; |
| 364 | } | 509 | } |
| 365 | 510 | ||
| 366 | -// ====================== Per-type processing ====================== | 511 | +Status ProcessCastNodePrecision(AscGraph &asc_graph, const NodePtr &node) { |
| 367 | -Status CastNodeProc(AscGraph &asc_graph, const NodePtr &node) { | ||
| 368 | NodePtr peer_out_node; | 512 | NodePtr peer_out_node; |
| 369 | GE_ASSERT_SUCCESS(GetPeerOutNode(node, peer_out_node, 0)); | 513 | GE_ASSERT_SUCCESS(GetPeerOutNode(node, peer_out_node, 0)); |
| 370 | std::vector<NodePtr> peer_in_nodes; | 514 | std::vector<NodePtr> peer_in_nodes; |
| 371 | GE_ASSERT_SUCCESS(GetPeerInNodes(node, peer_in_nodes, 0)); | 515 | GE_ASSERT_SUCCESS(GetPeerInNodes(node, peer_in_nodes, 0)); |
| 372 | 516 | ||
| 373 | - GeTensorDescPtr peer_output_tensor_desc; | ||
| 374 | - GE_ASSERT_SUCCESS(GetOutputTensorDesc(peer_out_node, peer_output_tensor_desc)); | ||
| 375 | GeTensorDescPtr output_tensor_desc; | 517 | GeTensorDescPtr output_tensor_desc; |
| 376 | GE_ASSERT_SUCCESS(GetOutputTensorDesc(node, output_tensor_desc)); | 518 | GE_ASSERT_SUCCESS(GetOutputTensorDesc(node, output_tensor_desc)); |
| 377 | - const auto peer_output_dtype = peer_output_tensor_desc->GetDataType(); | 519 | + DataType peer_output_dtype; |
| 520 | + GE_ASSERT_SUCCESS(GetNodeOutputDtype(peer_out_node, peer_output_dtype)); | ||
| 378 | const auto output_dtype = output_tensor_desc->GetDataType(); | 521 | const auto output_dtype = output_tensor_desc->GetDataType(); |
| 379 | - if (ShouldDeleteCastNode(peer_output_dtype, output_dtype)) { | ||
| 380 | - GE_ASSERT_SUCCESS(DelNode(asc_graph, node)); | ||
| 381 | - return af::SUCCESS; | ||
| 382 | - } | ||
| 383 | - | ||
| 384 | if (IsFloatToUltraLowNeedInsertCast(peer_out_node, peer_output_dtype, output_dtype)) { | 522 | if (IsFloatToUltraLowNeedInsertCast(peer_out_node, peer_output_dtype, output_dtype)) { |
| 385 | GE_ASSERT_SUCCESS(UpdateTopoId(asc_graph, node, 1)); | 523 | GE_ASSERT_SUCCESS(UpdateTopoId(asc_graph, node, 1)); |
| 386 | NodePtr c_node = nullptr; | 524 | NodePtr c_node = nullptr; |
| @@ -400,6 +538,41 @@ Status CastNodeProc(AscGraph &asc_graph, const NodePtr &node) { | |||
| 400 | return af::SUCCESS; | 538 | return af::SUCCESS; |
| 401 | } | 539 | } |
| 402 | 540 | ||
| 541 | +Status ProcessCastChains(AscGraph &asc_graph, const std::vector<NodePtr> &nodes) { | ||
| 542 | + std::unordered_set<NodePtr> processed; | ||
| 543 | + std::vector<NodePtr> retained_nodes; | ||
| 544 | + for (const auto &node : nodes) { | ||
| 545 | + if (processed.find(node) != processed.end()) { | ||
| 546 | + continue; | ||
| 547 | + } | ||
| 548 | + NodePtr source; | ||
| 549 | + GE_ASSERT_SUCCESS(GetPeerOutNode(node, source, 0)); | ||
| 550 | + CastChain chain; | ||
| 551 | + GE_ASSERT_SUCCESS(CollectLinearCastChain(node, chain)); | ||
| 552 | + bool can_optimize = false; | ||
| 553 | + GE_ASSERT_SUCCESS(CanOptimizeLinearCastChain(source, chain, can_optimize)); | ||
| 554 | + if (!can_optimize) { | ||
| 555 | + processed.insert(node); | ||
| 556 | + bool deleted = false; | ||
| 557 | + GE_ASSERT_SUCCESS(DeleteIdentityCast(asc_graph, source, node, deleted)); | ||
| 558 | + if (!deleted) { | ||
| 559 | + retained_nodes.push_back(node); | ||
| 560 | + } | ||
| 561 | + continue; | ||
| 562 | + } | ||
| 563 | + std::vector<NodePtr> chain_retained_nodes; | ||
| 564 | + GE_ASSERT_SUCCESS(OptimizeLinearCastChain(asc_graph, source, chain, chain_retained_nodes)); | ||
| 565 | + retained_nodes.insert(retained_nodes.end(), chain_retained_nodes.begin(), chain_retained_nodes.end()); | ||
| 566 | + for (const auto &cast_node : chain.nodes) { | ||
| 567 | + processed.insert(cast_node); | ||
| 568 | + } | ||
| 569 | + } | ||
| 570 | + for (const auto &node : retained_nodes) { | ||
| 571 | + GE_ASSERT_SUCCESS(ProcessCastNodePrecision(asc_graph, node)); | ||
| 572 | + } | ||
| 573 | + return af::SUCCESS; | ||
| 574 | +} | ||
| 575 | + | ||
| 403 | Status IsNeedInsertCastAfterLoad(const NodePtr &node, bool &is_need_insert_cast) { | 576 | Status IsNeedInsertCastAfterLoad(const NodePtr &node, bool &is_need_insert_cast) { |
| 404 | const auto node_opdesc = node->GetOpDesc(); | 577 | const auto node_opdesc = node->GetOpDesc(); |
| 405 | GE_ASSERT_NOTNULL(node_opdesc); | 578 | GE_ASSERT_NOTNULL(node_opdesc); |
| @@ -440,13 +613,13 @@ Status InsertCastToIncreasePrecision(AscGraph &asc_graph, const NodePtr &load_no | |||
| 440 | Status IsNeedInsertCastBeforeOther(const NodePtr &other_node, bool &need_insert, std::vector<int32_t> &input_idxs) { | 613 | Status IsNeedInsertCastBeforeOther(const NodePtr &other_node, bool &need_insert, std::vector<int32_t> &input_idxs) { |
| 441 | std::vector<NodePtr> peer_out_nodes; | 614 | std::vector<NodePtr> peer_out_nodes; |
| 442 | GE_ASSERT_SUCCESS(GetPeerOutNodes(other_node, peer_out_nodes)); | 615 | GE_ASSERT_SUCCESS(GetPeerOutNodes(other_node, peer_out_nodes)); |
| 443 | - GeTensorDescPtr peer_output_tensor_desc; | ||
| 444 | for (auto idx = 0U; idx < peer_out_nodes.size(); idx++) { | 616 | for (auto idx = 0U; idx < peer_out_nodes.size(); idx++) { |
| 445 | const auto &peer_out_node = peer_out_nodes[idx]; | 617 | const auto &peer_out_node = peer_out_nodes[idx]; |
| 446 | - GE_ASSERT_SUCCESS(GetOutputTensorDesc(peer_out_node, peer_output_tensor_desc)); | 618 | + DataType peer_dtype; |
| 619 | + GE_ASSERT_SUCCESS(GetNodeOutputDtype(peer_out_node, peer_dtype)); | ||
| 447 | const auto &type = peer_out_node->GetType(); | 620 | const auto &type = peer_out_node->GetType(); |
| 448 | if (type == af::ascir_op::Cast::Type || type == af::ascir_op::Load::Type || type == af::ascir_op::Gather::Type) { | 621 | if (type == af::ascir_op::Cast::Type || type == af::ascir_op::Load::Type || type == af::ascir_op::Gather::Type) { |
| 449 | - if (IsLowPrecisionDataType(peer_output_tensor_desc->GetDataType())) { | 622 | + if (IsLowPrecisionDataType(peer_dtype)) { |
| 450 | need_insert = true; | 623 | need_insert = true; |
| 451 | input_idxs.push_back(static_cast<int32_t>(idx)); | 624 | input_idxs.push_back(static_cast<int32_t>(idx)); |
| 452 | } | 625 | } |
| @@ -475,12 +648,12 @@ Status InsertCastBeforeNode(AscGraph &asc_graph, const NodePtr &other_node, bool | |||
| 475 | Status IsNeedInsertCastBeforeStore(const NodePtr &store_node, bool &need_insert, bool &is_increase_precision) { | 648 | Status IsNeedInsertCastBeforeStore(const NodePtr &store_node, bool &need_insert, bool &is_increase_precision) { |
| 476 | NodePtr peer_out_node; | 649 | NodePtr peer_out_node; |
| 477 | GE_ASSERT_SUCCESS(GetPeerOutNode(store_node, peer_out_node, 0)); | 650 | GE_ASSERT_SUCCESS(GetPeerOutNode(store_node, peer_out_node, 0)); |
| 478 | - GeTensorDescPtr peer_output_tensor_desc; | 651 | + DataType peer_dtype; |
| 479 | - GE_ASSERT_SUCCESS(GetOutputTensorDesc(peer_out_node, peer_output_tensor_desc)); | 652 | + GE_ASSERT_SUCCESS(GetNodeOutputDtype(peer_out_node, peer_dtype)); |
| 480 | - GeTensorDescPtr store_output_tensor_desc; | 653 | + DataType store_dtype; |
| 481 | - GE_ASSERT_SUCCESS(GetOutputTensorDesc(store_node, store_output_tensor_desc)); | 654 | + GE_ASSERT_SUCCESS(GetNodeOutputDtype(store_node, store_dtype)); |
| 482 | - is_increase_precision = IsHighPrecisionDataType(store_output_tensor_desc->GetDataType()); | 655 | + is_increase_precision = IsHighPrecisionDataType(store_dtype); |
| 483 | - if (peer_output_tensor_desc->GetDataType() == store_output_tensor_desc->GetDataType()) { | 656 | + if (peer_dtype == store_dtype) { |
| 484 | need_insert = false; | 657 | need_insert = false; |
| 485 | return af::SUCCESS; | 658 | return af::SUCCESS; |
| 486 | } | 659 | } |
| @@ -569,9 +742,7 @@ Status ProcessStoreNodes(AscGraph &asc_graph, const std::vector<NodePtr> &nodes) | |||
| 569 | } | 742 | } |
| 570 | 743 | ||
| 571 | Status ProcessNodeGroups(AscGraph &asc_graph, TypeToNodesMap &type_to_nodes) { | 744 | Status ProcessNodeGroups(AscGraph &asc_graph, TypeToNodesMap &type_to_nodes) { |
| 572 | - for (const auto &node : type_to_nodes[af::ascir_op::Cast::Type]) { | 745 | + GE_ASSERT_SUCCESS(ProcessCastChains(asc_graph, type_to_nodes[af::ascir_op::Cast::Type])); |
| 573 | - GE_ASSERT_SUCCESS(CastNodeProc(asc_graph, node)); | ||
| 574 | - } | ||
| 575 | GE_ASSERT_SUCCESS(ProcessLoadGatherNodes(asc_graph, type_to_nodes[af::ascir_op::Load::Type])); | 746 | GE_ASSERT_SUCCESS(ProcessLoadGatherNodes(asc_graph, type_to_nodes[af::ascir_op::Load::Type])); |
| 576 | GE_ASSERT_SUCCESS(ProcessLoadGatherNodes(asc_graph, type_to_nodes[af::ascir_op::Gather::Type])); | 747 | GE_ASSERT_SUCCESS(ProcessLoadGatherNodes(asc_graph, type_to_nodes[af::ascir_op::Gather::Type])); |
| 577 | for (const auto &node : type_to_nodes[af::ascir_op::Scalar::Type]) { | 748 | for (const auto &node : type_to_nodes[af::ascir_op::Scalar::Type]) { |
| @@ -0,0 +1,56 @@ | |||
| 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 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | +namespace af::testing { | ||
| 21 | + | ||
| 22 | +inline size_t CountNodesByType(AscGraph &graph, const std::string &type) { | ||
| 23 | + size_t count = 0U; | ||
| 24 | + for (const auto &node : AscGraphUtils::GetComputeGraph(graph)->GetAllNodes()) { | ||
| 25 | + if (node->GetType() == type) { | ||
| 26 | + ++count; | ||
| 27 | + } | ||
| 28 | + } | ||
| 29 | + return count; | ||
| 30 | +} | ||
| 31 | + | ||
| 32 | +inline bool HasCastOutputDtype(AscGraph &graph, ge::DataType expected_dtype) { | ||
| 33 | + for (const auto &node : AscGraphUtils::GetComputeGraph(graph)->GetAllNodes()) { | ||
| 34 | + if (node->GetType() == ascir_op::Cast::Type && | ||
| 35 | + node->GetOpDesc()->GetOutputDesc(0).GetDataType() == expected_dtype) { | ||
| 36 | + return true; | ||
| 37 | + } | ||
| 38 | + } | ||
| 39 | + return false; | ||
| 40 | +} | ||
| 41 | + | ||
| 42 | +inline bool CheckNodeOutputDtype(AscGraph &graph, const std::string &node_name, ge::DataType expected_dtype) { | ||
| 43 | + for (const auto &node : AscGraphUtils::GetComputeGraph(graph)->GetAllNodes()) { | ||
| 44 | + if (node->GetName() == node_name) { | ||
| 45 | + auto desc = node->GetOpDesc(); | ||
| 46 | + if (desc != nullptr && desc->GetOutputDesc(0).GetDataType() == expected_dtype) { | ||
| 47 | + return true; | ||
| 48 | + } | ||
| 49 | + } | ||
| 50 | + } | ||
| 51 | + return false; | ||
| 52 | +} | ||
| 53 | + | ||
| 54 | +} // namespace af::testing | ||
| 55 | + | ||
| 56 | + | ||
| @@ -20,8 +20,8 @@ | |||
| 20 | 20 | ||
| 21 | 21 | ||
| 22 | 22 | ||
| 23 | - | ||
| 24 | 23 | ||
| 24 | + | ||
| 25 | 25 | ||
| 26 | 26 | ||
| 27 | using namespace af; | 27 | using namespace af; |
| @@ -29,34 +29,15 @@ using namespace af::ascir_op; | |||
| 29 | using af::ops::IsOps; | 29 | using af::ops::IsOps; |
| 30 | using af::ops::One; | 30 | using af::ops::One; |
| 31 | using af::testing::AscGraphBuilder; | 31 | using af::testing::AscGraphBuilder; |
| 32 | +using af::testing::CheckNodeOutputDtype; | ||
| 33 | +using af::testing::CountNodesByType; | ||
| 34 | +using af::testing::HasCastOutputDtype; | ||
| 32 | using af::testing::Sym; | 35 | using af::testing::Sym; |
| 33 | using namespace af::pre_process; | 36 | using namespace af::pre_process; |
| 34 | 37 | ||
| 35 | namespace { | 38 | namespace { |
| 36 | // ====================== Helpers ====================== | 39 | // ====================== Helpers ====================== |
| 37 | 40 | ||
| 38 | -size_t CountNodesByType(AscGraph &graph, const std::string &type) { | ||
| 39 | - size_t count = 0U; | ||
| 40 | - for (const auto &node : AscGraphUtils::GetComputeGraph(graph)->GetAllNodes()) { | ||
| 41 | - if (node->GetType() == type) { | ||
| 42 | - ++count; | ||
| 43 | - } | ||
| 44 | - } | ||
| 45 | - return count; | ||
| 46 | -} | ||
| 47 | - | ||
| 48 | -bool CheckNodeOutputDtype(AscGraph &graph, const std::string &node_name, ge::DataType expected_dtype) { | ||
| 49 | - for (const auto &node : AscGraphUtils::GetComputeGraph(graph)->GetAllNodes()) { | ||
| 50 | - if (node->GetName() == node_name) { | ||
| 51 | - auto desc = node->GetOpDesc(); | ||
| 52 | - if (desc != nullptr && desc->GetOutputDesc(0).GetDataType() == expected_dtype) { | ||
| 53 | - return true; | ||
| 54 | - } | ||
| 55 | - } | ||
| 56 | - } | ||
| 57 | - return false; | ||
| 58 | -} | ||
| 59 | - | ||
| 60 | class TestImprovePrecisionST : public ::testing::Test { | 41 | class TestImprovePrecisionST : public ::testing::Test { |
| 61 | protected: | 42 | protected: |
| 62 | void SetUp() override { | 43 | void SetUp() override { |
| @@ -321,6 +302,64 @@ TEST_F(TestImprovePrecisionST, LoadWithExistingCastPeer_NoDuplicateCast) { | |||
| 321 | EXPECT_TRUE(CheckNodeOutputDtype(graph, "abs0", ge::DT_FLOAT)); | 302 | EXPECT_TRUE(CheckNodeOutputDtype(graph, "abs0", ge::DT_FLOAT)); |
| 322 | } | 303 | } |
| 323 | 304 | ||
| 305 | +TEST_F(TestImprovePrecisionST, UnsupportedCastBypass_PreservesIntermediateFloatCast) { | ||
| 306 | + auto graph = AscGraphBuilder("st_unsupported_cast_bypass") | ||
| 307 | + .Loops({Sym("s0")}) | ||
| 308 | + .Data("data0", 0, ge::DT_BF16) | ||
| 309 | + .Load("load0", "data0") | ||
| 310 | + .Cast("cast_bf16_to_fp32", "load0", ge::DT_FLOAT) | ||
| 311 | + .Cast("cast_fp32_identity", "cast_bf16_to_fp32", ge::DT_FLOAT) | ||
| 312 | + .Cast("cast_fp32_identity2", "cast_fp32_identity", ge::DT_FLOAT) | ||
| 313 | + .Cast("cast_fp32_to_fp16", "cast_fp32_identity2", ge::DT_FLOAT16) | ||
| 314 | + .Store("store0", "cast_fp32_to_fp16") | ||
| 315 | + .Output("output0", "store0", 0, ge::DT_FLOAT16) | ||
| 316 | + .Build(); | ||
| 317 | + | ||
| 318 | + ASSERT_EQ(ImprovePrecisionForAscGraph(graph), af::SUCCESS); | ||
| 319 | + | ||
| 320 | + EXPECT_EQ(CountNodesByType(graph, Cast::Type), 2U); | ||
| 321 | + EXPECT_TRUE(HasCastOutputDtype(graph, ge::DT_FLOAT)); | ||
| 322 | + EXPECT_TRUE(HasCastOutputDtype(graph, ge::DT_FLOAT16)); | ||
| 323 | +} | ||
| 324 | + | ||
| 325 | +TEST_F(TestImprovePrecisionST, SupportedChainEndpoint_CollapsesAcrossUnsupportedIntermediateCast) { | ||
| 326 | + auto graph = AscGraphBuilder("st_supported_chain_endpoint") | ||
| 327 | + .Loops({Sym("s0")}) | ||
| 328 | + .Data("data0", 0, ge::DT_BF16) | ||
| 329 | + .Load("load0", "data0") | ||
| 330 | + .Cast("cast_bf16_to_fp32", "load0", ge::DT_FLOAT) | ||
| 331 | + .Cast("cast_fp32_to_fp16", "cast_bf16_to_fp32", ge::DT_FLOAT16) | ||
| 332 | + .Cast("cast_fp16_to_fp32", "cast_fp32_to_fp16", ge::DT_FLOAT) | ||
| 333 | + .Store("store0", "cast_fp16_to_fp32") | ||
| 334 | + .Output("output0", "store0", 0, ge::DT_FLOAT) | ||
| 335 | + .Build(); | ||
| 336 | + | ||
| 337 | + ASSERT_EQ(ImprovePrecisionForAscGraph(graph), af::SUCCESS); | ||
| 338 | + | ||
| 339 | + EXPECT_EQ(CountNodesByType(graph, Cast::Type), 1U); | ||
| 340 | + EXPECT_TRUE(HasCastOutputDtype(graph, ge::DT_FLOAT)); | ||
| 341 | +} | ||
| 342 | + | ||
| 343 | +TEST_F(TestImprovePrecisionST, UnsupportedChainEndpoint_RemovesIdentitySubchain) { | ||
| 344 | + auto graph = AscGraphBuilder("st_identity_subchain") | ||
| 345 | + .Loops({Sym("s0")}) | ||
| 346 | + .Data("data0", 0, ge::DT_BF16) | ||
| 347 | + .Load("load0", "data0") | ||
| 348 | + .Cast("cast_bf16_to_fp32", "load0", ge::DT_FLOAT) | ||
| 349 | + .Cast("cast_fp32_identity", "cast_bf16_to_fp32", ge::DT_FLOAT) | ||
| 350 | + .Cast("cast_fp32_identity2", "cast_fp32_identity", ge::DT_FLOAT) | ||
| 351 | + .Cast("cast_fp32_to_fp16", "cast_fp32_identity2", ge::DT_FLOAT16) | ||
| 352 | + .Store("store0", "cast_fp32_to_fp16") | ||
| 353 | + .Output("output0", "store0", 0, ge::DT_FLOAT16) | ||
| 354 | + .Build(); | ||
| 355 | + | ||
| 356 | + ASSERT_EQ(ImprovePrecisionForAscGraph(graph), af::SUCCESS); | ||
| 357 | + | ||
| 358 | + EXPECT_EQ(CountNodesByType(graph, Cast::Type), 2U); | ||
| 359 | + EXPECT_FALSE(CheckNodeOutputDtype(graph, "cast_fp32_identity", ge::DT_FLOAT)); | ||
| 360 | + EXPECT_FALSE(CheckNodeOutputDtype(graph, "cast_fp32_identity2", ge::DT_FLOAT)); | ||
| 361 | +} | ||
| 362 | + | ||
| 324 | TEST_F(TestImprovePrecisionST, PreProcessEntryPoint_Succeeds) { | 363 | TEST_F(TestImprovePrecisionST, PreProcessEntryPoint_Succeeds) { |
| 325 | auto graph = AscGraphBuilder("st_preprocess_entry") | 364 | auto graph = AscGraphBuilder("st_preprocess_entry") |
| 326 | .Loops({Sym("s0")}) | 365 | .Loops({Sym("s0")}) |
| @@ -3,7 +3,7 @@ | |||
| 3 | * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | 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"). | 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. | 5 | * Please refer to the License for details. You may not use this file except in compliance with the License. |
| 6 | - * THIS FILE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR ANY KIND, EITHER EXPRESS OR IMPLIED, | 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. | 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. | 8 | * See LICENSE in the root of the software repository for the full text of the License. |
| 9 | */ | 9 | */ |
| @@ -11,12 +11,11 @@ | |||
| 11 | 11 | ||
| 12 | 12 | ||
| 13 | 13 | ||
| 14 | - | ||
| 15 | 14 | ||
| 16 | 15 | ||
| 17 | 16 | ||
| 18 | 17 | ||
| 19 | -#include "ascgraph_info_complete.h" | 18 | +#include "tests/framework/improve_precision_test_utils.h" |
| 20 | 19 | ||
| 21 | 20 | ||
| 22 | 21 | ||
| @@ -27,16 +26,6 @@ using namespace af::ascir_op; | |||
| 27 | 26 | ||
| 28 | namespace { | 27 | namespace { |
| 29 | 28 | ||
| 30 | -size_t CountNodesByType(AscGraph &graph, const std::string &type) { | ||
| 31 | - size_t count = 0U; | ||
| 32 | - for (const auto &node : AscGraphUtils::GetComputeGraph(graph)->GetAllNodes()) { | ||
| 33 | - if (node->GetType() == type) { | ||
| 34 | - ++count; | ||
| 35 | - } | ||
| 36 | - } | ||
| 37 | - return count; | ||
| 38 | -} | ||
| 39 | - | ||
| 40 | bool HasNodeWithName(AscGraph &graph, const std::string &name) { | 29 | bool HasNodeWithName(AscGraph &graph, const std::string &name) { |
| 41 | for (const auto &node : AscGraphUtils::GetComputeGraph(graph)->GetAllNodes()) { | 30 | for (const auto &node : AscGraphUtils::GetComputeGraph(graph)->GetAllNodes()) { |
| 42 | if (node->GetName() == name) { | 31 | if (node->GetName() == name) { |
| @@ -81,10 +70,10 @@ TEST_F(TestImprovePrecisionUT, Fp16ToFp16CastBeforeStore_CastDeleted) { | |||
| 81 | EXPECT_FALSE(HasNodeWithName(graph, "cast0")); | 70 | EXPECT_FALSE(HasNodeWithName(graph, "cast0")); |
| 82 | } | 71 | } |
| 83 | 72 | ||
| 84 | -TEST_F(TestImprovePrecisionUT, Fp32ToFp16CastBeforeStore_CastDeletedAndAbsPromoted) { | 73 | +TEST_F(TestImprovePrecisionUT, Fp32ToFp16CastBeforeStore_CastPreserved) { |
| 85 | auto graph = AscGraphBuilder("ut_fp32_to_fp16_before_store") | 74 | auto graph = AscGraphBuilder("ut_fp32_to_fp16_before_store") |
| 86 | .Loops({Sym("s0")}) | 75 | .Loops({Sym("s0")}) |
| 87 | - .Data("data0", 0, ge::DT_FLOAT16) | 76 | + .Data("data0", 0, ge::DT_FLOAT) |
| 88 | .Load("load0", "data0") | 77 | .Load("load0", "data0") |
| 89 | .Abs("abs0", "load0") | 78 | .Abs("abs0", "load0") |
| 90 | .Cast("cast0", "abs0", ge::DT_FLOAT16) | 79 | .Cast("cast0", "abs0", ge::DT_FLOAT16) |
| @@ -94,5 +83,41 @@ TEST_F(TestImprovePrecisionUT, Fp32ToFp16CastBeforeStore_CastDeletedAndAbsPromot | |||
| 94 | 83 | ||
| 95 | ASSERT_EQ(ImprovePrecisionForAscGraph(graph), af::SUCCESS); | 84 | ASSERT_EQ(ImprovePrecisionForAscGraph(graph), af::SUCCESS); |
| 96 | 85 | ||
| 97 | - EXPECT_FALSE(HasNodeWithName(graph, "cast0")); | 86 | + EXPECT_TRUE(HasNodeWithName(graph, "cast0")); |
| 87 | + EXPECT_TRUE(HasCastOutputDtype(graph, ge::DT_FLOAT16)); | ||
| 88 | +} | ||
| 89 | + | ||
| 90 | +TEST_F(TestImprovePrecisionUT, UnsupportedCastChain_PreservesRequiredCast) { | ||
| 91 | + auto graph = AscGraphBuilder("ut_unsupported_cast_chain") | ||
| 92 | + .Loops({Sym("s0")}) | ||
| 93 | + .Data("data0", 0, ge::DT_BF16) | ||
| 94 | + .Load("load0", "data0") | ||
| 95 | + .Cast("cast_bf16_to_fp32", "load0", ge::DT_FLOAT) | ||
| 96 | + .Cast("cast_fp32_identity", "cast_bf16_to_fp32", ge::DT_FLOAT) | ||
| 97 | + .Cast("cast_fp32_to_fp16", "cast_fp32_identity", ge::DT_FLOAT16) | ||
| 98 | + .Store("store0", "cast_fp32_to_fp16") | ||
| 99 | + .Output("output0", "store0", 0, ge::DT_FLOAT16) | ||
| 100 | + .Build(); | ||
| 101 | + | ||
| 102 | + ASSERT_EQ(ImprovePrecisionForAscGraph(graph), af::SUCCESS); | ||
| 103 | + | ||
| 104 | + EXPECT_EQ(CountNodesByType(graph, Cast::Type), 2U); | ||
| 105 | + EXPECT_TRUE(HasCastOutputDtype(graph, ge::DT_FLOAT)); | ||
| 106 | + EXPECT_TRUE(HasCastOutputDtype(graph, ge::DT_FLOAT16)); | ||
| 107 | +} | ||
| 108 | + | ||
| 109 | +TEST_F(TestImprovePrecisionUT, NonFloatSourceCastChain_FallbackDeletesIdentity) { | ||
| 110 | + auto graph = AscGraphBuilder("ut_non_float_source") | ||
| 111 | + .Loops({Sym("s0")}) | ||
| 112 | + .Data("data0", 0, ge::DT_INT32) | ||
| 113 | + .Load("load0", "data0") | ||
| 114 | + .Cast("cast_int32_to_fp32", "load0", ge::DT_FLOAT) | ||
| 115 | + .Cast("cast_fp32_identity", "cast_int32_to_fp32", ge::DT_FLOAT) | ||
| 116 | + .Store("store0", "cast_fp32_identity") | ||
| 117 | + .Output("output0", "store0", 0, ge::DT_FLOAT) | ||
| 118 | + .Build(); | ||
| 119 | + | ||
| 120 | + ASSERT_EQ(ImprovePrecisionForAscGraph(graph), af::SUCCESS); | ||
| 121 | + | ||
| 122 | + EXPECT_FALSE(HasNodeWithName(graph, "cast_fp32_identity")); | ||
| 98 | } | 123 | } |