已合并
fix: ensure deterministic codegen and reliable PGO fallback #1662
fix: ensure deterministic codegen and reliable PGO fallback #1662
已合并
zhang_shengjie创建于 8月5日
15 个文件变更+318-54
@@ -28,7 +28,7 @@ void TilingLib::GenSharedPgoRuntimeProfiling(const ascir::FusedScheduledResult &
28std::string TilingLib::GenerateForPgo(const ascir::FusedScheduledResult &fused_schedule_result,28std::string TilingLib::GenerateForPgo(const ascir::FusedScheduledResult &fused_schedule_result,
29 const std::string &pgo_dir) const {29 const std::string &pgo_dir) const {
30 if (ShouldFallbackPgo(fused_schedule_result)) {30 if (ShouldFallbackPgo(fused_schedule_result)) {
31- return "int main() { return 0; }\n";31+ return "int main() { return 1; }\n";
32 }32 }
33 std::stringstream ss;33 std::stringstream ss;
34 GenPgoHeaders(ss, false);34 GenPgoHeaders(ss, false);
@@ -607,8 +607,11 @@ def extract_time(line):
607 607 
608 608 
609def pgo_get_top_result(search_path, top_n=5):609def pgo_get_top_result(search_path, top_n=5):
610- with open(search_path, "r") as file:610+ try:
611- lines = [line.strip() for line in file if line.strip()]611+ with open(search_path, "r") as file:
612+ lines = [line.strip() for line in file if line.strip()]
613+ except OSError:
614+ return None, None, None
612 615 
613 if not lines:616 if not lines:
614 return None, None, None617 return None, None, None
@@ -571,6 +571,7 @@ Status BufQueAllocator::InitTensorInfo(af::AscGraph &graph, TensorInfoMap &tenso
571}571}
572 572 
573Status BufQueAllocator::InitNodeTmpBuffInfo(af::AscGraph &graph, TmpBuffInfoMap &node_attr_to_tensor_info) {573Status BufQueAllocator::InitNodeTmpBuffInfo(af::AscGraph &graph, TmpBuffInfoMap &node_attr_to_tensor_info) {
574+ int64_t allocation_order = 0;
574 for (const auto &node : graph.GetAllNodes()) {575 for (const auto &node : graph.GetAllNodes()) {
575 GE_ASSERT_NOTNULL(node);576 GE_ASSERT_NOTNULL(node);
576 if (ScheduleUtils::IsBuffer(node)) {577 if (ScheduleUtils::IsBuffer(node)) {
@@ -578,6 +579,7 @@ Status BufQueAllocator::InitNodeTmpBuffInfo(af::AscGraph &graph, TmpBuffInfoMap
578 }579 }
579 for (auto &tmp_buff : node->attr.tmp_buffers) {580 for (auto &tmp_buff : node->attr.tmp_buffers) {
580 auto &tmp_buff_info = node_attr_to_tensor_info[&tmp_buff];581 auto &tmp_buff_info = node_attr_to_tensor_info[&tmp_buff];
582+ tmp_buff_info.allocation_order = allocation_order++;
581 tmp_buff_info.mem_position = af::Position::kPositionVecCalc;583 tmp_buff_info.mem_position = af::Position::kPositionVecCalc;
582 tmp_buff_info.life_start = 0L;584 tmp_buff_info.life_start = 0L;
583 tmp_buff_info.life_end = std::numeric_limits<int64_t>::max();585 tmp_buff_info.life_end = std::numeric_limits<int64_t>::max();
@@ -13,6 +13,13 @@
13namespace optimize {13namespace optimize {
14size_t kDbReuseThreshold = 2UL;14size_t kDbReuseThreshold = 2UL;
15 15 
16+static bool TensorGroupLifeLess(const TensorGroup &lhs, const TensorGroup &rhs) {
17+ if (lhs.merged_life_start != rhs.merged_life_start) {
18+ return lhs.merged_life_start < rhs.merged_life_start;
19+ }
20+ return std::make_pair(lhs.allocation_order, lhs.group_id) < std::make_pair(rhs.allocation_order, rhs.group_id);
21+}
22+ 
16bool MemReuseManager::IsLifetimeOverlap(int64_t start1, int64_t end1, int64_t start2, int64_t end2) {23bool MemReuseManager::IsLifetimeOverlap(int64_t start1, int64_t end1, int64_t start2, int64_t end2) {
17 if (end1 == std::numeric_limits<int64_t>::max() || end2 == std::numeric_limits<int64_t>::max()) {24 if (end1 == std::numeric_limits<int64_t>::max() || end2 == std::numeric_limits<int64_t>::max()) {
18 return true;25 return true;
@@ -23,16 +30,16 @@ bool MemReuseManager::IsLifetimeOverlap(int64_t start1, int64_t end1, int64_t st
23void MemReuseManager::MergeTensorByGroupId(std::vector<TensorGroup> &copy_in_groups,30void MemReuseManager::MergeTensorByGroupId(std::vector<TensorGroup> &copy_in_groups,
24 std::vector<TensorGroup> &copy_out_groups,31 std::vector<TensorGroup> &copy_out_groups,
25 std::vector<TensorGroup> &calc_groups) const {32 std::vector<TensorGroup> &calc_groups) const {
26- using GroupKey = int64_t;33+ std::unordered_map<int64_t, TensorGroup> temp_groups;
27- std::unordered_map<GroupKey, TensorGroup> temp_groups;
28 for (auto &info : tensor_attr_to_tensor_info_) {34 for (auto &info : tensor_attr_to_tensor_info_) {
29 auto cur_tensor = &info.second;35 auto cur_tensor = &info.second;
30 36 
31- GroupKey key = cur_tensor->group_id;37+ const auto key = cur_tensor->group_id;
32 auto [it, is_new] = temp_groups.try_emplace(key);38 auto [it, is_new] = temp_groups.try_emplace(key);
33 TensorGroup &group = it->second;39 TensorGroup &group = it->second;
34 if (is_new) {40 if (is_new) {
35 group.group_id = cur_tensor->group_id;41 group.group_id = cur_tensor->group_id;
42+ group.allocation_order = cur_tensor->allocation_order;
36 group.grouped_tensors = {cur_tensor};43 group.grouped_tensors = {cur_tensor};
37 group.merged_life_start = cur_tensor->life_start;44 group.merged_life_start = cur_tensor->life_start;
38 group.merged_life_end = cur_tensor->life_end;45 group.merged_life_end = cur_tensor->life_end;
@@ -211,8 +218,7 @@ MemoryBlock *MemReuseManager::SelectBestMemoryBlock(const TensorGroup &tensor_gr
211}218}
212 219 
213void MemReuseManager::AllocForTQue(MemoryType mem_type, std::vector<TensorGroup> &que_groups) {220void MemReuseManager::AllocForTQue(MemoryType mem_type, std::vector<TensorGroup> &que_groups) {
214- std::sort(que_groups.begin(), que_groups.end(),221+ std::sort(que_groups.begin(), que_groups.end(), TensorGroupLifeLess);
215- [](const TensorGroup &a, const TensorGroup &b) { return a.merged_life_start < b.merged_life_start; });
216 int64_t last_block_id = -1;222 int64_t last_block_id = -1;
217 for (const auto &tensor : que_groups) {223 for (const auto &tensor : que_groups) {
218 // 被标记为不能复用,需要直接创建224 // 被标记为不能复用,需要直接创建
@@ -237,8 +243,7 @@ void MemReuseManager::AllocForTQue(MemoryType mem_type, std::vector<TensorGroup>
237}243}
238 244 
239void MemReuseManager::AllocForCalc(std::vector<TensorGroup> &calc_groups) {245void MemReuseManager::AllocForCalc(std::vector<TensorGroup> &calc_groups) {
240- std::sort(calc_groups.begin(), calc_groups.end(),246+ std::sort(calc_groups.begin(), calc_groups.end(), TensorGroupLifeLess);
241- [](const TensorGroup &a, const TensorGroup &b) { return a.merged_life_start < b.merged_life_start; });
242 for (const auto &tensor_group : calc_groups) {247 for (const auto &tensor_group : calc_groups) {
243 // 不能复用别人的tensor,直接创建新块248 // 不能复用别人的tensor,直接创建新块
244 if (!tensor_group.group_is_can_reuse_others) {249 if (!tensor_group.group_is_can_reuse_others) {
@@ -311,6 +316,7 @@ void MemReuseManager::AllocTmpBuff(std::map<af::TmpBuffer *, std::vector<TensorG
311 auto cur_tensor = &info.second;316 auto cur_tensor = &info.second;
312 TensorGroup group;317 TensorGroup group;
313 group.group_id = cur_tensor->group_id;318 group.group_id = cur_tensor->group_id;
319+ group.allocation_order = cur_tensor->allocation_order;
314 group.grouped_tensors = {cur_tensor};320 group.grouped_tensors = {cur_tensor};
315 group.merged_life_start = cur_tensor->life_start;321 group.merged_life_start = cur_tensor->life_start;
316 group.merged_life_end = cur_tensor->life_end;322 group.merged_life_end = cur_tensor->life_end;
@@ -320,36 +326,36 @@ void MemReuseManager::AllocTmpBuff(std::map<af::TmpBuffer *, std::vector<TensorG
320 group.group_is_reusable = cur_tensor->is_reusable;326 group.group_is_reusable = cur_tensor->is_reusable;
321 tmp_buff_to_groups[info.first].push_back(std::move(group));327 tmp_buff_to_groups[info.first].push_back(std::move(group));
322 }328 }
329+ std::vector<std::pair<af::TmpBuffer *, const TensorGroup *>> ordered_groups;
323 for (const auto &info : tmp_buff_to_groups) {330 for (const auto &info : tmp_buff_to_groups) {
324- // 不能复用别人的tensor,直接创建新块
325- auto tmp_buff = info.first;
326- tmp_buff->mem.alloc_type = af::AllocType::kAllocTypeBuffer;
327 for (const auto &group : info.second) {331 for (const auto &group : info.second) {
328- if (group.group_id != -1) {332+ ordered_groups.emplace_back(info.first, &group);
329- tmp_buff->id = buf_id_;
330- CreateMemBlockByType(&group, MemoryType::kLoopTmpBuff);
331- continue;
332- }
333- 
334- if (type_blocks_[MemoryType::kTmpBuff].empty()) {
335- tmp_buff->id = buf_id_;
336- CreateMemBlockByType(&group, MemoryType::kTmpBuff);
337- continue;
338- }
339- // 更新内存块信息
340- std::vector<MemoryBlock *> candidate_blocks;
341- FindCandidateTmpBuffBlockWithSizeCheck(group, candidate_blocks);
342- if (candidate_blocks.empty()) {
343- tmp_buff->id = buf_id_;
344- CreateMemBlockByType(&group, MemoryType::kTmpBuff);
345- continue;
346- }
347- 
348- tmp_buff->id = candidate_blocks[0]->id;
349- candidate_blocks[0]->tensor_groups.push_back(&group);
350- GELOGD("[MemReuse] reuse mem block with type[%d] id[%d] for group[%ld].",
351- static_cast<int>(candidate_blocks[0]->mem_type), candidate_blocks[0]->id, group.group_id);
352 }333 }
353 }334 }
335+ std::sort(ordered_groups.begin(), ordered_groups.end(),
336+ [](const auto &lhs, const auto &rhs) { return TensorGroupLifeLess(*lhs.second, *rhs.second); });
337+ for (const auto &[tmp_buff, group] : ordered_groups) {
338+ AllocTmpBuffGroup(tmp_buff, *group);
339+ }
340+}
341+ 
342+void MemReuseManager::AllocTmpBuffGroup(af::TmpBuffer *tmp_buff, const TensorGroup &group) {
343+ tmp_buff->mem.alloc_type = af::AllocType::kAllocTypeBuffer;
344+ if (group.group_id != -1) {
345+ tmp_buff->id = buf_id_;
346+ CreateMemBlockByType(&group, MemoryType::kLoopTmpBuff);
347+ return;
348+ }
349+ std::vector<MemoryBlock *> candidate_blocks;
350+ FindCandidateTmpBuffBlockWithSizeCheck(group, candidate_blocks);
351+ if (candidate_blocks.empty()) {
352+ tmp_buff->id = buf_id_;
353+ CreateMemBlockByType(&group, MemoryType::kTmpBuff);
354+ return;
355+ }
356+ tmp_buff->id = candidate_blocks[0]->id;
357+ candidate_blocks[0]->tensor_groups.push_back(&group);
358+ GELOGD("[MemReuse] reuse mem block with type[%d] id[%d] for group[%ld].",
359+ static_cast<int>(candidate_blocks[0]->mem_type), candidate_blocks[0]->id, group.group_id);
354}360}
355} // namespace optimize361} // namespace optimize
@@ -36,6 +36,7 @@ class MemReuseManager {
36 std::vector<MemoryBlock *> &candidate_blocks);36 std::vector<MemoryBlock *> &candidate_blocks);
37 static void SetQueBufIdToTensorAttr(const MemoryBlock &block);37 static void SetQueBufIdToTensorAttr(const MemoryBlock &block);
38 void AllocTmpBuff(std::map<af::TmpBuffer *, std::vector<TensorGroup>> &tmp_buff_to_groups);38 void AllocTmpBuff(std::map<af::TmpBuffer *, std::vector<TensorGroup>> &tmp_buff_to_groups);
39+ void AllocTmpBuffGroup(af::TmpBuffer *tmp_buff, const TensorGroup &group);
39 void FindCandidateTmpBuffBlockWithSizeCheck(const TensorGroup &tensor_group,40 void FindCandidateTmpBuffBlockWithSizeCheck(const TensorGroup &tensor_group,
40 std::vector<MemoryBlock *> &candidate_blocks);41 std::vector<MemoryBlock *> &candidate_blocks);
41 // 按类型分组的内存块42 // 按类型分组的内存块
@@ -24,6 +24,7 @@ enum class MemorySizeLevel : int32_t { kScalar = 0, kMedium, kLargest };
24// tensor块大小信息24// tensor块大小信息
25struct TensorInfo {25struct TensorInfo {
26 int64_t group_id{-1};26 int64_t group_id{-1};
27+ int64_t allocation_order{-1};
27 af::AscTensorAttr *output_tensor_attr{nullptr};28 af::AscTensorAttr *output_tensor_attr{nullptr};
28 int64_t life_start{-1};29 int64_t life_start{-1};
29 int64_t life_end{-1};30 int64_t life_end{-1};
@@ -61,6 +62,7 @@ struct TensorInfo {
61// 需要绑定进行内存分配的tensor链62// 需要绑定进行内存分配的tensor链
62struct TensorGroup {63struct TensorGroup {
63 int64_t group_id{-1};64 int64_t group_id{-1};
65+ int64_t allocation_order{-1};
64 std::vector<TensorInfo *> grouped_tensors; // group中的tensor,会绑定生命周期66 std::vector<TensorInfo *> grouped_tensors; // group中的tensor,会绑定生命周期
65 std::set<int64_t> merged_loop_axes;67 std::set<int64_t> merged_loop_axes;
66 int64_t merged_life_start;68 int64_t merged_life_start;
@@ -256,8 +256,27 @@ const std::unordered_map<std::string, std::string> kTypeToGroup = {
256 {af::ascir_op::Scalar::Type, af::ascir_op::Scalar::Type},256 {af::ascir_op::Scalar::Type, af::ascir_op::Scalar::Type},
257 {af::ascir_op::Store::Type, af::ascir_op::Store::Type}};257 {af::ascir_op::Store::Type, af::ascir_op::Store::Type}};
258 258 
259-bool ShouldDeleteCastNode(DataType peer_output_dtype, DataType output_dtype) {259+bool IsSharedRoundingBoundary(const std::vector<NodePtr> &peer_in_nodes, DataType peer_output_dtype,
260- return IsFloatDataType(output_dtype) && IsFloatDataType(peer_output_dtype);260+ DataType output_dtype) {
261+ if (!IsHighPrecisionDataType(peer_output_dtype) || !IsLowPrecisionDataType(output_dtype)) {
262+ return false;
263+ }
264+ bool has_store = false;
265+ bool has_compute = false;
266+ for (const auto &peer_in_node : peer_in_nodes) {
267+ if (peer_in_node->GetType() == af::ascir_op::Store::Type) {
268+ has_store = true;
269+ } else {
270+ has_compute = true;
271+ }
272+ }
273+ return has_store && has_compute;
274+}
275+ 
276+bool ShouldDeleteCastNode(const std::vector<NodePtr> &peer_in_nodes, DataType peer_output_dtype,
277+ DataType output_dtype) {
278+ return IsFloatDataType(output_dtype) && IsFloatDataType(peer_output_dtype) &&
279+ !IsSharedRoundingBoundary(peer_in_nodes, peer_output_dtype, output_dtype);
261}280}
262 281 
263bool ShouldChangeDataType(const NodePtr &node, const std::vector<NodePtr> &peer_in_nodes, DataType peer_output_dtype,282bool ShouldChangeDataType(const NodePtr &node, const std::vector<NodePtr> &peer_in_nodes, DataType peer_output_dtype,
@@ -376,7 +395,7 @@ Status CastNodeProc(AscGraph &asc_graph, const NodePtr &node) {
376 GE_ASSERT_SUCCESS(GetOutputTensorDesc(node, output_tensor_desc));395 GE_ASSERT_SUCCESS(GetOutputTensorDesc(node, output_tensor_desc));
377 const auto peer_output_dtype = peer_output_tensor_desc->GetDataType();396 const auto peer_output_dtype = peer_output_tensor_desc->GetDataType();
378 const auto output_dtype = output_tensor_desc->GetDataType();397 const auto output_dtype = output_tensor_desc->GetDataType();
379- if (ShouldDeleteCastNode(peer_output_dtype, output_dtype)) {398+ if (ShouldDeleteCastNode(peer_in_nodes, peer_output_dtype, output_dtype)) {
380 GE_ASSERT_SUCCESS(DelNode(asc_graph, node));399 GE_ASSERT_SUCCESS(DelNode(asc_graph, node));
381 return af::SUCCESS;400 return af::SUCCESS;
382 }401 }
@@ -113,7 +113,7 @@ TEST_F(TestBackendPgoAddAbsInductorE2e, PgoTilingKeyOverflowFallsBackWithoutRunn
113 EXPECT_EQ(result.tiling.find("AUTOFUSE_SPLIT_FILE_BEGIN: PgoRunner"), std::string::npos);113 EXPECT_EQ(result.tiling.find("AUTOFUSE_SPLIT_FILE_BEGIN: PgoRunner"), std::string::npos);
114 const auto tiling_files = codegen.GenerateTiling(fused_schedule_result, {}, "/tmp", "10");114 const auto tiling_files = codegen.GenerateTiling(fused_schedule_result, {}, "/tmp", "10");
115 EXPECT_EQ(tiling_files.at(codegen::kTilingDefAndConstIdentify).find("autofuse_tiling_func_pgo.h"), std::string::npos);115 EXPECT_EQ(tiling_files.at(codegen::kTilingDefAndConstIdentify).find("autofuse_tiling_func_pgo.h"), std::string::npos);
116- EXPECT_EQ(codegen.GeneratorPgo(fused_schedule_result, "/tmp"), "int main() { return 0; }\n");116+ EXPECT_EQ(codegen.GeneratorPgo(fused_schedule_result, "/tmp"), "int main() { return 1; }\n");
117 117 
118 schedule_groups.back().impl_graphs.pop_back();118 schedule_groups.back().impl_graphs.pop_back();
119 fused_schedule_result.node_idx_to_scheduled_results[0].emplace_back();119 fused_schedule_result.node_idx_to_scheduled_results[0].emplace_back();
@@ -174,6 +174,27 @@ TEST_F(TestImprovePrecisionST, Fp32ToFp16CastBeforeStore_RemovedAndAbsPromoted)
174 EXPECT_TRUE(CheckNodeOutputDtype(graph, "abs0", ge::DT_FLOAT));174 EXPECT_TRUE(CheckNodeOutputDtype(graph, "abs0", ge::DT_FLOAT));
175}175}
176 176 
177+TEST_F(TestImprovePrecisionST, Fp32ToBf16CastSharedByStoreAndCompute_Preserved) {
178+ auto graph = AscGraphBuilder("st_fp32_to_bf16_shared_output")
179+ .Loops({Sym("s0")})
180+ .Data("data0", 0, ge::DT_FLOAT)
181+ .Load("load0", "data0")
182+ .Add("add0", "load0", "load0")
183+ .Cast("cast_to_bf16", "add0", ge::DT_BF16)
184+ .Store("store_bf16", "cast_to_bf16")
185+ .Output("output_bf16", "store_bf16", 0, ge::DT_BF16)
186+ .Cast("cast_to_fp32", "cast_to_bf16", ge::DT_FLOAT)
187+ .Mul("mul0", "cast_to_fp32", "cast_to_fp32")
188+ .Store("store_fp32", "mul0")
189+ .Output("output_fp32", "store_fp32", 1, ge::DT_FLOAT)
190+ .Build();
191+ 
192+ ASSERT_EQ(ImprovePrecisionForAscGraph(graph), af::SUCCESS);
193+ 
194+ EXPECT_TRUE(CheckNodeOutputDtype(graph, "cast_to_bf16", ge::DT_BF16));
195+ EXPECT_TRUE(CheckNodeOutputDtype(graph, "mul0", ge::DT_FLOAT));
196+}
197+ 
177TEST_F(TestImprovePrecisionST, ScalarFp16Promoted_DownstreamAllFp32) {198TEST_F(TestImprovePrecisionST, ScalarFp16Promoted_DownstreamAllFp32) {
178 auto graph = AscGraphBuilder("st_scalar_fp16_downstream")199 auto graph = AscGraphBuilder("st_scalar_fp16_downstream")
179 .Loops({Sym("s0")})200 .Loops({Sym("s0")})
@@ -1935,7 +1935,7 @@ TEST_F(TestCodegenTiling, PgoTilingKeyCountOverflowShouldFallbackTfAndPgoRunner)
1935 EXPECT_EQ(entry.find("#include \"autofuse_tiling_func_pgo.h\""), std::string::npos);1935 EXPECT_EQ(entry.find("#include \"autofuse_tiling_func_pgo.h\""), std::string::npos);
1936 EXPECT_NE(entry.find("extern \"C\" int64_t FindBestTilingKey"), std::string::npos);1936 EXPECT_NE(entry.find("extern \"C\" int64_t FindBestTilingKey"), std::string::npos);
1937 const auto pgo_source = GenerateForPgo(fused_schedule_result, "/tmp");1937 const auto pgo_source = GenerateForPgo(fused_schedule_result, "/tmp");
1938- EXPECT_NE(pgo_source.find("int main()"), std::string::npos);1938+ EXPECT_EQ(pgo_source, "int main() { return 1; }\n");
1939 EXPECT_EQ(pgo_source.find("PGOGetProfiling"), std::string::npos);1939 EXPECT_EQ(pgo_source.find("PGOGetProfiling"), std::string::npos);
1940 EXPECT_TRUE(CompileCode(pgo_source, false));1940 EXPECT_TRUE(CompileCode(pgo_source, false));
1941}1941}
@@ -11,6 +11,7 @@
11#include <ascendc_ir.h>11#include <ascendc_ir.h>
12#include <ascir_ops.h>12#include <ascir_ops.h>
13#include <ascir_utils.h>13#include <ascir_utils.h>
14+#include <array>
14#include <iostream>15#include <iostream>
15 16 
16#include "gtest/gtest.h"17#include "gtest/gtest.h"
@@ -23,6 +24,7 @@
23 24 
24#define private public25#define private public
25#include "buffer_allocate/buf_que_allocator.h"26#include "buffer_allocate/buf_que_allocator.h"
27+#include "buffer_allocate/mem_reuse_manager.h"
26#include "asc_graph_builder.h"28#include "asc_graph_builder.h"
27#include "ascgraph_info_complete.h"29#include "ascgraph_info_complete.h"
28#undef private30#undef private
@@ -1930,6 +1932,22 @@ TEST_F(BufQueAllocatorUT, TestTensorInfoToStr) {
1930 ASSERT_FALSE(res.empty());1932 ASSERT_FALSE(res.empty());
1931}1933}
1932 1934 
1935+TEST_F(BufQueAllocatorUT, tmp_buffer_allocation_follows_lifetime_instead_of_pointer_order) {
1936+ std::array<af::TmpBuffer, 2> tmp_buffers;
1937+ TmpBuffInfoMap tmp_buffer_infos;
1938+ tmp_buffer_infos[&tmp_buffers[0]].life_start = 2;
1939+ tmp_buffer_infos[&tmp_buffers[0]].life_end = std::numeric_limits<int64_t>::max();
1940+ tmp_buffer_infos[&tmp_buffers[1]].life_start = 1;
1941+ tmp_buffer_infos[&tmp_buffers[1]].life_end = std::numeric_limits<int64_t>::max();
1942+ TensorInfoMap tensor_infos;
1943+ 
1944+ MemReuseManager manager(tensor_infos, tmp_buffer_infos);
1945+ manager.AllocMemBlocks();
1946+ 
1947+ EXPECT_EQ(tmp_buffers[1].id, 0);
1948+ EXPECT_EQ(tmp_buffers[0].id, 1);
1949+}
1950+ 
1933TEST_F(BufQueAllocatorUT, test_tmp_buff_reuse) {1951TEST_F(BufQueAllocatorUT, test_tmp_buff_reuse) {
1934 af::AscGraph graph("LoadAbsStore");1952 af::AscGraph graph("LoadAbsStore");
1935 auto ONE = af::Symbol(1);1953 auto ONE = af::Symbol(1);
@@ -123,6 +123,18 @@ def test_device_compile_does_not_add_host_default_abi(compile_adapter_module):
123 temp_dir_ctx.cleanup()123 temp_dir_ctx.cleanup()
124 124 
125 125 
126+def test_pgo_get_top_result_missing_file_returns_failure(
127+ compile_adapter_module, tmpdir
128+):
129+ missing_search = str(tmpdir.join("missing_search.txt"))
130+ 
131+ assert compile_adapter_module.pgo_get_top_result(missing_search) == (
132+ None,
133+ None,
134+ None,
135+ )
136+ 
137+ 
126def test_jit_compile_records_atrace_and_reports(compile_adapter_module, tmpdir, capsys):138def test_jit_compile_records_atrace_and_reports(compile_adapter_module, tmpdir, capsys):
127 output_file = tmpdir.join("jit.so")139 output_file = tmpdir.join("jit.so")
128 argv = [f"--output_file={output_file}", f"--output_path={tmpdir}"]140 argv = [f"--output_file={output_file}", f"--output_path={tmpdir}"]
@@ -9,6 +9,7 @@
9 */9 */
10 10 
11#include "ascendc_ir.h"11#include "ascendc_ir.h"
12+#include "asc_graph_builder.h"
12#include "ascir_ops.h"13#include "ascir_ops.h"
13#include "ascir_utils.h"14#include "ascir_utils.h"
14 15 
@@ -80,6 +81,87 @@ void SetupGraphAxes(af::AscGraph &graph, const std::vector<af::Symbol> &loops) {
80 }81 }
81 }82 }
82}83}
84+ 
85+af::AscGraph BuildParallelVfGraph(bool reverse_branch_order) {
86+ af::testing::AscGraphBuilder builder("parallel_vf");
87+ builder.Loops({16}).Data("data", 0).Load("load", "data");
88+ if (reverse_branch_order) {
89+ builder.Abs("branch_z", "load").Abs("branch_a", "load");
90+ } else {
91+ builder.Abs("branch_a", "load").Abs("branch_z", "load");
92+ }
93+ builder.Add("sum", "branch_a", "branch_z")
94+ .Store("store_branch_a", "branch_a")
95+ .Output("output_branch_a", "store_branch_a", 0)
96+ .Store("store_branch_z", "branch_z")
97+ .Output("output_branch_z", "store_branch_z", 1)
98+ .Store("store_sum", "sum")
99+ .Output("output_sum", "store_sum", 2);
100+ auto graph = builder.Build();
101+ optimize::AscGraphInfoComplete::CompleteApiInfo(graph);
102+ for (const auto &node : graph.GetAllNodes()) {
103+ if (ScheduleUtils::IsBuffer(node)) {
104+ continue;
105+ }
106+ for (auto &output : node->outputs()) {
107+ output->attr.vectorized_axis = output->attr.axis;
108+ output->attr.vectorized_strides = output->attr.strides;
109+ }
110+ }
111+ return graph;
112+}
113+ 
114+std::vector<std::string> GetVfOutputConsumers(af::AscGraph &graph) {
115+ EXPECT_EQ(AlignmentHandler::AlignVectorizedStrides(graph), af::SUCCESS);
116+ VectorFuncPartitioner partitioner(graph);
117+ EXPECT_EQ(partitioner.Partition(), af::SUCCESS);
118+ for (const auto &node : graph.GetAllNodes()) {
119+ if (!af::ops::IsOps<af::ascir_op::VectorFunc>(node)) {
120+ continue;
121+ }
122+ std::vector<std::string> consumers;
123+ for (const auto &output : node->GetAllOutDataAnchors()) {
124+ const auto peers = output->GetPeerInDataAnchors();
125+ EXPECT_EQ(peers.size(), 1UL);
126+ consumers.push_back(peers.empty() ? "" : (*peers.begin())->GetOwnerNodeBarePtr()->GetName());
127+ }
128+ return consumers;
129+ }
130+ return {};
131+}
132+ 
133+std::vector<std::string> GetVfSubgraphTensorOrder(const af::AscGraph &graph) {
134+ std::vector<af::AscGraph> subgraphs;
135+ EXPECT_EQ(graph.GetAllSubGraphs(subgraphs), af::SUCCESS);
136+ EXPECT_EQ(subgraphs.size(), 1UL);
137+ std::vector<std::string> tensor_order;
138+ if (subgraphs.empty()) {
139+ return tensor_order;
140+ }
141+ for (const auto &node : subgraphs[0].GetAllNodes()) {
142+ for (const auto &output : node->outputs()) {
143+ tensor_order.push_back(node->GetName() + ":" + std::to_string(node->GetOpDescBarePtr()->GetId()) + ":" +
144+ std::to_string(output->attr.mem.tensor_id));
145+ }
146+ }
147+ return tensor_order;
148+}
149+ 
150+std::vector<std::string> GetGraphNodeOrder(const af::AscGraph &graph) {
151+ std::vector<std::string> node_order;
152+ for (const auto &node : graph.GetAllNodes()) {
153+ node_order.push_back(node->GetName() + ":" + std::to_string(node->GetOpDescBarePtr()->GetId()));
154+ }
155+ return node_order;
156+}
157+ 
158+std::vector<std::string> GetGraphNodeNames(const af::AscGraph &graph) {
159+ std::vector<std::string> node_names;
160+ for (const auto &node : graph.GetAllNodes()) {
161+ node_names.push_back(node->GetName());
162+ }
163+ return node_names;
164+}
83} // namespace165} // namespace
84 166 
85class VfPartition : public testing::Test {167class VfPartition : public testing::Test {
@@ -142,7 +224,7 @@ TEST_F(VfPartition, brc_abs) {
142 *abs01.y.strides = {s2, Zero, One};224 *abs01.y.strides = {s2, Zero, One};
143 *abs01.y.vectorized_axis = {z0.id, z1.id, z2.id};225 *abs01.y.vectorized_axis = {z0.id, z1.id, z2.id};
144 226 
145- af::ascir_op::Abs abs02("abs01");227+ af::ascir_op::Abs abs02("abs02");
146 abs02.x = abs01.y;228 abs02.x = abs01.y;
147 abs02.attr.api.compute_type = af::ComputeType::kComputeElewise;229 abs02.attr.api.compute_type = af::ComputeType::kComputeElewise;
148 abs02.attr.sched.axis = {z0.id, z1.id, z2.id};230 abs02.attr.sched.axis = {z0.id, z1.id, z2.id};
@@ -1821,4 +1903,60 @@ TEST_F(VfPartition, topological_sort_for_vf_graph_keeps_load_before_consumer) {
1821 ASSERT_NE(truediv_iter, node_names.end());1903 ASSERT_NE(truediv_iter, node_names.end());
1822 EXPECT_LT(std::distance(node_names.begin(), load1_iter), std::distance(node_names.begin(), truediv_iter));1904 EXPECT_LT(std::distance(node_names.begin(), load1_iter), std::distance(node_names.begin(), truediv_iter));
1823}1905}
1906+ 
1907+TEST_F(VfPartition, topological_sort_for_vf_graph_preserves_control_dependency) {
1908+ af::AscGraph graph("vf_sort_control_dependency");
1909+ af::ascir_op::Data control_src("z_src", graph);
1910+ af::ascir_op::Data control_dst("a_dst", graph);
1911+ auto src_node = graph.FindNode("z_src");
1912+ auto dst_node = graph.FindNode("a_dst");
1913+ ASSERT_NE(src_node, nullptr);
1914+ ASSERT_NE(dst_node, nullptr);
1915+ ASSERT_EQ(af::GraphUtils::AddEdge(src_node->GetOutControlAnchor(), dst_node->GetInControlAnchor()), af::SUCCESS);
1916+ 
1917+ VectorFuncPartitioner partitioner(graph);
1918+ ASSERT_EQ(partitioner.TopologicalSortingForVfGraph(graph), af::SUCCESS);
1919+ 
1920+ const auto node_names = GetGraphNodeNames(graph);
1921+ ASSERT_EQ(node_names.size(), 2UL);
1922+ EXPECT_EQ(node_names[0], "z_src");
1923+ EXPECT_EQ(node_names[1], "a_dst");
1924+}
1925+ 
1926+TEST_F(VfPartition, partition_rejects_duplicate_node_names_before_graph_mutation) {
1927+ af::AscGraph graph("vf_duplicate_node_names");
1928+ af::ascir_op::Data first("duplicate", graph);
1929+ af::ascir_op::Data second("duplicate", graph);
1930+ ASSERT_EQ(GetGraphNodeNames(graph).size(), 2UL);
1931+ 
1932+ VectorFuncPartitioner partitioner(graph);
1933+ EXPECT_NE(partitioner.Partition(), af::SUCCESS);
1934+ EXPECT_EQ(GetGraphNodeNames(graph).size(), 2UL);
1935+ std::vector<af::AscGraph> subgraphs;
1936+ EXPECT_EQ(graph.GetAllSubGraphs(subgraphs), af::SUCCESS);
1937+ EXPECT_TRUE(subgraphs.empty());
1938+}
1939+ 
1940+TEST_F(VfPartition, vf_output_order_is_independent_of_parallel_node_insertion_order) {
1941+ auto graph = BuildParallelVfGraph(false);
1942+ auto reversed_graph = BuildParallelVfGraph(true);
1943+ const std::vector<std::string> expected = {"store_branch_a", "store_branch_z", "store_sum"};
1944+ const auto consumers = GetVfOutputConsumers(graph);
1945+ const auto reversed_consumers = GetVfOutputConsumers(reversed_graph);
1946+ 
1947+ ASSERT_EQ(consumers.size(), expected.size());
1948+ ASSERT_EQ(reversed_consumers.size(), expected.size());
1949+ EXPECT_EQ(consumers, expected);
1950+ EXPECT_EQ(reversed_consumers, expected);
1951+}
1952+ 
1953+TEST_F(VfPartition, vf_tensor_order_is_independent_of_parallel_node_insertion_order) {
1954+ auto graph = BuildParallelVfGraph(false);
1955+ auto reversed_graph = BuildParallelVfGraph(true);
1956+ GetVfOutputConsumers(graph);
1957+ GetVfOutputConsumers(reversed_graph);
1958+ 
1959+ EXPECT_EQ(GetGraphNodeOrder(graph), GetGraphNodeOrder(reversed_graph));
1960+ EXPECT_EQ(GetVfSubgraphTensorOrder(graph), GetVfSubgraphTensorOrder(reversed_graph));
1961+}
1824} // namespace optimize1962} // namespace optimize
@@ -30,25 +30,41 @@ constexpr int32_t kMaxBitWidthGap = 2;
30constexpr int64_t kOutLoopAxisId = -1L;30constexpr int64_t kOutLoopAxisId = -1L;
31constexpr size_t kMinVfNodesNum = 2UL;31constexpr size_t kMinVfNodesNum = 2UL;
32 32 
33-std::unordered_map<af::Node *, size_t> BuildDependencyAwareRanks(33+af::Status ValidateUniqueNodeNames(const af::AscGraph &graph) {
34- const af::AscGraph &graph, const std::unordered_set<af::Node *> &outer_loop_sequences) {34+ std::unordered_set<std::string> node_names;
35- std::vector<af::NodePtr> nodes;
36- std::unordered_map<af::Node *, size_t> indegrees;
37- std::unordered_map<af::Node *, std::vector<af::NodePtr>> out_nodes;
38 for (const auto &node : graph.GetAllNodes()) {35 for (const auto &node : graph.GetAllNodes()) {
39- nodes.push_back(node);36+ GE_ASSERT_TRUE(node_names.emplace(node->GetName()).second, "VF input graph [%s] contains duplicate node name [%s].",
40- indegrees[node.get()] = 0UL;37+ graph.GetName().c_str(), node->GetName().c_str());
41 }38 }
39+ return af::SUCCESS;
40+}
42 41 
42+using NodeIndegrees = std::unordered_map<af::Node *, size_t>;
43+using NodeSuccessors = std::unordered_map<af::Node *, std::vector<af::NodePtr>>;
44+ 
45+void AddGraphDependencies(const std::vector<af::NodePtr> &nodes, NodeIndegrees &indegrees, NodeSuccessors &out_nodes) {
43 for (const auto &node : nodes) {46 for (const auto &node : nodes) {
44- for (const auto &out_node : node->GetOutDataNodes()) {47+ std::unordered_set<af::Node *> unique_out_nodes;
45- if (indegrees.find(out_node.get()) == indegrees.end()) {48+ for (const auto &out_node : node->GetOutAllNodes()) {
49+ if ((indegrees.find(out_node.get()) == indegrees.end()) || !unique_out_nodes.emplace(out_node.get()).second) {
46 continue;50 continue;
47 }51 }
48 out_nodes[node.get()].push_back(out_node);52 out_nodes[node.get()].push_back(out_node);
49 ++indegrees[out_node.get()];53 ++indegrees[out_node.get()];
50 }54 }
51 }55 }
56+}
57+ 
58+std::unordered_map<af::Node *, size_t> BuildDependencyAwareRanks(
59+ const af::AscGraph &graph, const std::unordered_set<af::Node *> &outer_loop_sequences) {
60+ std::vector<af::NodePtr> nodes;
61+ NodeIndegrees indegrees;
62+ NodeSuccessors out_nodes;
63+ for (const auto &node : graph.GetAllNodes()) {
64+ nodes.push_back(node);
65+ indegrees[node.get()] = 0UL;
66+ }
67+ AddGraphDependencies(nodes, indegrees, out_nodes);
52 68 
53 const auto has_higher_priority = [&outer_loop_sequences](const af::NodePtr &node1, const af::NodePtr &node2) -> bool {69 const auto has_higher_priority = [&outer_loop_sequences](const af::NodePtr &node1, const af::NodePtr &node2) -> bool {
54 bool is_node1_in_outer_seq = outer_loop_sequences.find(node1.get()) != outer_loop_sequences.end();70 bool is_node1_in_outer_seq = outer_loop_sequences.find(node1.get()) != outer_loop_sequences.end();
@@ -56,7 +72,8 @@ std::unordered_map<af::Node *, size_t> BuildDependencyAwareRanks(
56 if (is_node1_in_outer_seq != is_node2_in_outer_seq) {72 if (is_node1_in_outer_seq != is_node2_in_outer_seq) {
57 return is_node1_in_outer_seq;73 return is_node1_in_outer_seq;
58 }74 }
59- return node1->GetOpDescBarePtr()->GetId() < node2->GetOpDescBarePtr()->GetId();75+ return std::make_pair(node1->GetName(), node1->GetOpDescBarePtr()->GetId()) <
76+ std::make_pair(node2->GetName(), node2->GetOpDescBarePtr()->GetId());
60 };77 };
61 78 
62 std::vector<af::NodePtr> ready_nodes;79 std::vector<af::NodePtr> ready_nodes;
@@ -353,6 +370,26 @@ void AddAnchorToOrderMap(
353 }370 }
354}371}
355 372 
373+using BoundaryAnchorEntry = std::pair<af::OutDataAnchorPtr, std::vector<af::InDataAnchorPtr>>;
374+ 
375+void SortBoundaryAnchors(std::vector<BoundaryAnchorEntry> &anchors) {
376+ const auto out_anchor_less = [](const BoundaryAnchorEntry &lhs, const BoundaryAnchorEntry &rhs) {
377+ const auto *lhs_node = lhs.first->GetOwnerNodeBarePtr();
378+ const auto *rhs_node = rhs.first->GetOwnerNodeBarePtr();
379+ return std::make_pair(lhs_node->GetName(), lhs.first->GetIdx()) <
380+ std::make_pair(rhs_node->GetName(), rhs.first->GetIdx());
381+ };
382+ const auto in_anchor_less = [](const af::InDataAnchorPtr &lhs, const af::InDataAnchorPtr &rhs) {
383+ const auto *lhs_node = lhs->GetOwnerNodeBarePtr();
384+ const auto *rhs_node = rhs->GetOwnerNodeBarePtr();
385+ return std::make_pair(lhs_node->GetName(), lhs->GetIdx()) < std::make_pair(rhs_node->GetName(), rhs->GetIdx());
386+ };
387+ std::sort(anchors.begin(), anchors.end(), out_anchor_less);
388+ for (auto &entry : anchors) {
389+ std::sort(entry.second.begin(), entry.second.end(), in_anchor_less);
390+ }
391+}
392+ 
356bool NeedRemovePad(const af::AscNodePtr &node) {393bool NeedRemovePad(const af::AscNodePtr &node) {
357 // 如果是非scalar的Broadcast节点,直接插RemovePad,结束循环394 // 如果是非scalar的Broadcast节点,直接插RemovePad,结束循环
358 if (optimize::ScheduleUtils::IsBroadcast(node) && !optimize::ScheduleUtils::IsScalarBroadcastNode(node)) {395 if (optimize::ScheduleUtils::IsBroadcast(node) && !optimize::ScheduleUtils::IsScalarBroadcastNode(node)) {
@@ -423,6 +460,7 @@ const std::string kNamePrefixScalar = "Scalar_";
423const std::string kNamePrefixOutput = "Output_";460const std::string kNamePrefixOutput = "Output_";
424 461 
425af::Status VectorFuncPartitioner::Partition() {462af::Status VectorFuncPartitioner::Partition() {
463+ GE_ASSERT_SUCCESS(ValidateUniqueNodeNames(impl_graph_));
426 ascir::utils::DumpGraph(impl_graph_, "BeforePartition");464 ascir::utils::DumpGraph(impl_graph_, "BeforePartition");
427 GE_ASSERT_SUCCESS(ScheduleUtils::TopologicalSorting(impl_graph_), "Failed to do topological sorting for graph[%s].",465 GE_ASSERT_SUCCESS(ScheduleUtils::TopologicalSorting(impl_graph_), "Failed to do topological sorting for graph[%s].",
428 impl_graph_.GetName().c_str());466 impl_graph_.GetName().c_str());
@@ -960,6 +998,8 @@ af::Status VectorFuncPartitioner::BuildSubgraph(const ClusterPtr &cluster, af::A
960 GE_ASSERT_SUCCESS(AddInputDataAnchors(node, load_to_peer_in_anchors));998 GE_ASSERT_SUCCESS(AddInputDataAnchors(node, load_to_peer_in_anchors));
961 GE_ASSERT_SUCCESS(AddOutputDataAnchors(node, store_to_peed_in_anchors));999 GE_ASSERT_SUCCESS(AddOutputDataAnchors(node, store_to_peed_in_anchors));
962 }1000 }
1001+ SortBoundaryAnchors(load_to_peer_in_anchors);
1002+ SortBoundaryAnchors(store_to_peed_in_anchors);
963 1003 
964 vf_op.InstanceOutputy(store_to_peed_in_anchors.size());1004 vf_op.InstanceOutputy(store_to_peed_in_anchors.size());
965 std::vector<af::AscOpOutput> outputs;1005 std::vector<af::AscOpOutput> outputs;
@@ -1249,7 +1289,8 @@ af::Status VectorFuncPartitioner::TopologicalSortingForVfGraph(af::AscGraph &gra
1249 if (rank1 != ranks.end() && rank2 != ranks.end()) {1289 if (rank1 != ranks.end() && rank2 != ranks.end()) {
1250 return rank1->second < rank2->second;1290 return rank1->second < rank2->second;
1251 }1291 }
1252- return node1->GetOpDescBarePtr()->GetId() < node2->GetOpDescBarePtr()->GetId();1292+ return std::make_pair(node1->GetName(), node1->GetOpDescBarePtr()->GetId()) <
1293+ std::make_pair(node2->GetName(), node2->GetOpDescBarePtr()->GetId());
1253 };1294 };
1254 1295 
1255 auto compute_graph = af::AscGraphUtils::GetComputeGraph(graph);1296 auto compute_graph = af::AscGraphUtils::GetComputeGraph(graph);
@@ -22,6 +22,7 @@ namespace optimize {
22class VectorFuncPartitioner {22class VectorFuncPartitioner {
23 public:23 public:
24 explicit VectorFuncPartitioner(af::AscGraph &impl_graph) : impl_graph_(impl_graph) {};24 explicit VectorFuncPartitioner(af::AscGraph &impl_graph) : impl_graph_(impl_graph) {};
25+ // VF input graph node names must be unique to keep node and boundary-anchor ordering deterministic.
25 af::Status Partition();26 af::Status Partition();
26 27 
27 private:28 private: