已合并
topo parse + topo match重构 #2773
shenjiahui_创建于 10 天前
topo parse + topo match重构 #2773
已合并
shenjiahui_创建于 10 天前
30 个文件变更+2373-110
@@ -96,15 +96,16 @@ HcclResult haclrtGetCaptureInfo(aclrtStream stream, aclmdlRICaptureStatus& captu
96 return HCCL_SUCCESS;96 return HCCL_SUCCESS;
97}97}
98 98 
99-HcclResult hcalrtGetDeviceInfo(u32 deviceId, aclrtDevAttr devAttr, s64& val)99+HcclResult hcalrtGetDeviceInfo(u32 deviceId, aclrtDevAttr devAttr, s64& val, bool quiet)
100{100{
101#ifndef AICPU_COMPILE101#ifndef AICPU_COMPILE
102- static const std::set<aclrtDevAttr> supportType102+ static const std::set<aclrtDevAttr> supportType = {
103- = {{ACL_DEV_ATTR_PHY_CHIP_ID},103+ {ACL_DEV_ATTR_PHY_CHIP_ID}, {ACL_DEV_ATTR_SUPER_POD_DEVIDE_ID}, {ACL_DEV_ATTR_SUPER_POD_SERVER_ID},
104- {ACL_DEV_ATTR_SUPER_POD_DEVIDE_ID},104+ {ACL_DEV_ATTR_SUPER_POD_ID}, {ACL_DEV_ATTR_CUST_OP_PRIVILEGE},
105- {ACL_DEV_ATTR_SUPER_POD_SERVER_ID},105+#if HCCL_SUPPORT_DEV_FORM_FACTOR
106- {ACL_DEV_ATTR_SUPER_POD_ID},106+ {ACL_DEV_ATTR_DEVICE_FORM_FACTOR},
107- {ACL_DEV_ATTR_CUST_OP_PRIVILEGE}};107+#endif
108+ };
108 109 
109 auto it = supportType.find(devAttr);110 auto it = supportType.find(devAttr);
110 CHK_PRT_RET(111 CHK_PRT_RET(
@@ -112,10 +113,19 @@ HcclResult hcalrtGetDeviceInfo(u32 deviceId, aclrtDevAttr devAttr, s64& val)
112 HCCL_E_NOT_SUPPORT);113 HCCL_E_NOT_SUPPORT);
113 114 
114 aclError ret = aclrtGetDeviceInfo(deviceId, devAttr, reinterpret_cast<int64_t*>(&val));115 aclError ret = aclrtGetDeviceInfo(deviceId, devAttr, reinterpret_cast<int64_t*>(&val));
115- CHK_PRT_RET(116+ if (ret != ACL_SUCCESS) {
116- ret != ACL_SUCCESS,117+ // quiet用于"取不到就降级"的可选属性: 老驱动不支持某个infoType时会稳定失败,
117- HCCL_ERROR("[hcalrtGetDeviceInfo]rt get device info failed. ret[%d], attr[%d], val[%ld]", ret, devAttr, val),118+ // 按ERROR打会在完全正常的老环境上持续刷错误日志。传quiet的调用方必须自己处理返回值
118- HCCL_E_RUNTIME);119+ if (quiet) {
120+ HCCL_WARNING(
121+ "[hcalrtGetDeviceInfo]rt get device info failed. ret[%d], attr[%d]. caller will fall back.", ret,
122+ devAttr);
123+ } else {
124+ HCCL_ERROR(
125+ "[hcalrtGetDeviceInfo]rt get device info failed. ret[%d], attr[%d], val[%ld]", ret, devAttr, val);
126+ }
127+ return HCCL_E_RUNTIME;
128+ }
119 HCCL_DEBUG("Call aclrtGetDeviceInfo, ret[%d], attr[%d], val[%ld]", ret, devAttr, val);129 HCCL_DEBUG("Call aclrtGetDeviceInfo, ret[%d], attr[%d], val[%ld]", ret, devAttr, val);
120#endif130#endif
121 return HCCL_SUCCESS;131 return HCCL_SUCCESS;
@@ -17,6 +17,17 @@
17#include "acl_base.h"17#include "acl_base.h"
18#include "acl_rt.h"18#include "acl_rt.h"
19 19 
20+/* ACL_DEV_ATTR_DEVICE_FORM_FACTOR是新版acl_rt.h才有的枚举值, 老CANN上引用会编译不过。
21+ * 枚举对预处理器不可见, 因此探测与它同批引入、且只为它服务的宏ACL_DEVICE_FORM_FACTOR_POD。
22+ */
23+#ifndef HCCL_SUPPORT_DEV_FORM_FACTOR
24+#ifdef ACL_DEVICE_FORM_FACTOR_POD
25+#define HCCL_SUPPORT_DEV_FORM_FACTOR 1
26+#else
27+#define HCCL_SUPPORT_DEV_FORM_FACTOR 0
28+#endif
29+#endif
30+ 
20namespace ops_hccl {31namespace ops_hccl {
21 32 
22#define ACLCHECK(cmd) \33#define ACLCHECK(cmd) \
@@ -38,7 +49,11 @@ haclrtGetCaptureInfo(aclrtStream stream, aclmdlRICaptureStatus& captureStatus, u
38 49 
39HcclResult haclrtGetDeviceIndexByPhyId(u32 devicePhyId, u32& deviceLogicId);50HcclResult haclrtGetDeviceIndexByPhyId(u32 devicePhyId, u32& deviceLogicId);
40 51 
41-HcclResult hcalrtGetDeviceInfo(u32 deviceId, aclrtDevAttr devAttr, s64& val);52+/**
53+ * @param quiet 取不到时是否降噪。默认false保持原有行为(按ERROR打); 对"取不到就走降级值"的可选属性
54+ * 传true, 失败改按WARNING打。传true的调用方必须自己判返回值并给出降级值。
55+ */
56+HcclResult hcalrtGetDeviceInfo(u32 deviceId, aclrtDevAttr devAttr, s64& val, bool quiet = false);
42 57 
43HcclResult LoadBinaryFromFile(58HcclResult LoadBinaryFromFile(
44 const char* binPath, aclrtBinaryLoadOptionType optionType, uint32_t cpuKernelMode, aclrtBinHandle& binHandle);59 const char* binPath, aclrtBinaryLoadOptionType optionType, uint32_t cpuKernelMode, aclrtBinHandle& binHandle);
@@ -14,6 +14,7 @@
14#include <string>14#include <string>
15#include <vector>15#include <vector>
16#include <map>16#include <map>
17+#include <set>
17#include <cstdint>18#include <cstdint>
18#include "hccl_common.h"19#include "hccl_common.h"
19#include "op_common.h"20#include "op_common.h"
@@ -21,7 +22,6 @@
21#include "cost_model.h"22#include "cost_model.h"
22 23 
23namespace ops_hccl {24namespace ops_hccl {
24- 
25// ---------------------------------------------------------------------------25// ---------------------------------------------------------------------------
26// AlgoType 枚举:算法模板类型(与 ALGO_TYPES 映射表一一对应)26// AlgoType 枚举:算法模板类型(与 ALGO_TYPES 映射表一一对应)
27// ---------------------------------------------------------------------------27// ---------------------------------------------------------------------------
@@ -42,6 +42,17 @@ enum class AlgoType : uint8_t {
42 UNKNOWN,42 UNKNOWN,
43};43};
44 44 
45+// Mesh 类算法集合
46+const std::set<AlgoType> MESH_ALGO_TYPES
47+ = {AlgoType::MESH, AlgoType::MESH_2DIE, AlgoType::MESH_ONESHOT,
48+ AlgoType::MESH_TWOSHOT, AlgoType::MESH_CONCUR, AlgoType::MESH_MULTILINK,
49+ AlgoType::MESH_CHUNK, AlgoType::MESH_CHUNK_TWOSHOT, AlgoType::MESH_SINGLE_CHANNEL,
50+ AlgoType::MESH_CONCURRENT};
51+// NHR 类算法集合
52+const std::set<AlgoType> NHR_ALGO_TYPES = {AlgoType::NHR, AlgoType::NHR_MULTILINK, AlgoType::NHR_AICPU_REDUCE};
53+// MeshConcur 类算法(MESH_CONCUR 与 MESH_CONCURRENT 都触发 CLOS 双层规则)
54+const std::set<AlgoType> MESH_CONCUR_ALGO_TYPES = {AlgoType::MESH_CONCUR, AlgoType::MESH_CONCURRENT};
55+ 
45// ---------------------------------------------------------------------------56// ---------------------------------------------------------------------------
46// 算法(template)条目57// 算法(template)条目
47// algoType: 算法名称(驼峰命名),如 "mesh", "nhr", "ring", "meshMultiLink"58// algoType: 算法名称(驼峰命名),如 "mesh", "nhr", "ring", "meshMultiLink"
@@ -160,6 +160,40 @@ private:
160 std::stringstream stream;160 std::stringstream stream;
161};161};
162 162 
163+/*
164+ * 读写方向适配器。把方向从字段清单里剥离出来, 使Serialize与DeSerialize能共用同一份清单,
165+ * 两侧顺序不一致这类错误因此不可能再发生。用法: BinaryWriter ar(bs); ar & a & b & c;
166+ */
167+class BinaryWriter {
168+public:
169+ explicit BinaryWriter(BinaryStream& stream) : stream_(stream) {}
170+ 
171+ template <typename T>
172+ BinaryWriter& operator&(const T& t)
173+ {
174+ stream_ << t;
175+ return *this;
176+ }
177+ 
178+private:
179+ BinaryStream& stream_;
180+};
181+ 
182+class BinaryReader {
183+public:
184+ explicit BinaryReader(BinaryStream& stream) : stream_(stream) {}
185+ 
186+ template <typename T>
187+ BinaryReader& operator&(T& t)
188+ {
189+ stream_ >> t;
190+ return *this;
191+ }
192+ 
193+private:
194+ BinaryStream& stream_;
195+};
196+ 
163} // namespace ops_hccl197} // namespace ops_hccl
164 198 
165#endif // HCCL_SERIALIZATION199#endif // HCCL_SERIALIZATION
@@ -9,8 +9,9 @@
9 */9 */
10 10 
11#include "ins_v2_all_gather_v_sole_executor.h"11#include "ins_v2_all_gather_v_sole_executor.h"
12-#include "topo_match_1d.h"12+#include "topo_match_one_level.h"
13#include "ins_temp_all_gather_v_mesh_1D.h"13#include "ins_temp_all_gather_v_mesh_1D.h"
14+#include "alg_attrs_registry.h"
14 15 
15#ifndef AICPU_COMPILE16#ifndef AICPU_COMPILE
16#if CANN_VERSION_NUM >= CANN_VERSION(9, 0, 0)17#if CANN_VERSION_NUM >= CANN_VERSION(9, 0, 0)
@@ -28,9 +29,18 @@ template <typename AlgTopoMatch, typename InsAlgTemplate>
28HcclResult InsV2AllGatherVSoleExecutor<AlgTopoMatch, InsAlgTemplate>::CalcAlgHierarchyInfo(29HcclResult InsV2AllGatherVSoleExecutor<AlgTopoMatch, InsAlgTemplate>::CalcAlgHierarchyInfo(
29 HcclComm comm, TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo)30 HcclComm comm, TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo)
30{31{
31- // 使用topo match计算AlgHierarchyInfoForAllLevel32+ (void)comm;
32 AlgTopoMatch topoMatch;33 AlgTopoMatch topoMatch;
33- CHK_RET(topoMatch.MatchTopo(comm, topoInfo, algHierarchyInfo));34+ CHK_RET(topoMatch.MatchTopo(topoInfo, algHierarchyInfo, AlgAttrs{}));
35+ return HCCL_SUCCESS;
36+}
37+ 
38+template <typename AlgTopoMatch, typename InsAlgTemplate>
39+HcclResult InsV2AllGatherVSoleExecutor<AlgTopoMatch, InsAlgTemplate>::CalcAlgHierarchyInfoV2(
40+ TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo, const AlgAttrs& algAttrs)
41+{
42+ AlgTopoMatch topoMatch;
43+ CHK_RET(topoMatch.MatchTopo(topoInfo, algHierarchyInfo, algAttrs));
34 return HCCL_SUCCESS;44 return HCCL_SUCCESS;
35}45}
36 46 
@@ -194,13 +204,16 @@ HcclResult InsV2AllGatherVSoleExecutor<AlgTopoMatch, InsAlgTemplate>::Orchestrat
194}204}
195 205 
196REGISTER_EXEC_V2(206REGISTER_EXEC_V2(
197- HcclCMDType::HCCL_CMD_ALLGATHER_V, AicpuAllGatherVSoleMesh, InsV2AllGatherVSoleExecutor, TopoMatch1D,207+ HcclCMDType::HCCL_CMD_ALLGATHER_V, AicpuAllGatherVSoleMesh, InsV2AllGatherVSoleExecutor, TopoMatchOneLevel,
198 InsTempAllGatherVMesh1D);208 InsTempAllGatherVMesh1D);
209+REGISTER_ALG_ATTRS(AicpuAllGatherVSoleMesh, topo.maxTopoLevelNum = 3; topo.supportLevel0Topos = LEVEL0_TOPO_ANY;);
199#ifndef AICPU_COMPILE210#ifndef AICPU_COMPILE
200#if CANN_VERSION_NUM >= CANN_VERSION(9, 0, 0)211#if CANN_VERSION_NUM >= CANN_VERSION(9, 0, 0)
201REGISTER_EXEC_V2(212REGISTER_EXEC_V2(
202- HcclCMDType::HCCL_CMD_ALLGATHER_V, CcuSchedAllGatherVSoleMesh, InsV2AllGatherVSoleExecutor, TopoMatch1D,213+ HcclCMDType::HCCL_CMD_ALLGATHER_V, CcuSchedAllGatherVSoleMesh, InsV2AllGatherVSoleExecutor, TopoMatchOneLevel,
203 CcuTempAllGatherVMesh1DMem2Mem);214 CcuTempAllGatherVMesh1DMem2Mem);
215+REGISTER_ALG_ATTRS(CcuSchedAllGatherVSoleMesh, topo.maxTopoLevelNum = 1; topo.supportLevel0Topos = LEVEL0_TOPO_MESH_1D;
216+ op.isSupportInplace = false;);
204#endif // CANN_VERSION_NUM >= CANN_VERSION(9, 0, 0)217#endif // CANN_VERSION_NUM >= CANN_VERSION(9, 0, 0)
205#endif218#endif
206} // namespace ops_hccl219} // namespace ops_hccl
@@ -12,6 +12,7 @@
12#define HCCLV2_INS_V2_REDUCE_SCATTER_SOLE_EXECUTOR_H12#define HCCLV2_INS_V2_REDUCE_SCATTER_SOLE_EXECUTOR_H
13 13 
14#include "executor_common_ops.h"14#include "executor_common_ops.h"
15+#include "topo_match_one_level.h"
15 16 
16namespace ops_hccl {17namespace ops_hccl {
17template <typename AlgTopoMatch, typename InsAlgTemplate>18template <typename AlgTopoMatch, typename InsAlgTemplate>
@@ -31,6 +32,10 @@ public:
31 HcclResult CalcAlgHierarchyInfo(32 HcclResult CalcAlgHierarchyInfo(
32 HcclComm comm, TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo) override;33 HcclComm comm, TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo) override;
33 34 
35+ HcclResult CalcAlgHierarchyInfoV2(
36+ TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo,
37+ const AlgAttrs& algAttrs) override;
38+ 
34protected:39protected:
35 /* *************** 算法编排 *************** */40 /* *************** 算法编排 *************** */
36 HcclResult OrchestrateLoop(const OpParam& param, const AlgResourceCtxSerializable& resCtx);41 HcclResult OrchestrateLoop(const OpParam& param, const AlgResourceCtxSerializable& resCtx);
@@ -23,6 +23,38 @@ std::string InsCollAlgBase::Describe() const
23 return s;23 return s;
24}24}
25 25 
26+CommTopo InsCollAlgBase::GetPhysicalLevelTopoType(const TopoInfoWithNetLayerDetails* topoInfo, u32 levelIdx) const
27+{
28+ // 不用CHK_PTR_NULL: 它返回HcclResult, 与本函数的返回类型对不上
29+ if (topoInfo == nullptr) {
30+ HCCL_WARNING("[InsCollAlgBase][GetPhysicalLevelTopoType] topoInfo is null");
31+ return CommTopo::COMM_TOPO_RESERVED;
32+ }
33+ if (levelIdx >= topoInfo->physicalLevels.size()) {
34+ HCCL_WARNING(
35+ "[InsCollAlgBase][GetPhysicalLevelTopoType] levelIdx[%u] out of range, physicalLevelNum[%zu]", levelIdx,
36+ topoInfo->physicalLevels.size());
37+ return CommTopo::COMM_TOPO_RESERVED;
38+ }
39+ return topoInfo->physicalLevels[levelIdx].topoType;
40+}
41+ 
42+std::vector<u32>
43+InsCollAlgBase::GetPhysicalLevelPortNums(const TopoInfoWithNetLayerDetails* topoInfo, u32 levelIdx) const
44+{
45+ if (topoInfo == nullptr) {
46+ HCCL_WARNING("[InsCollAlgBase][GetPhysicalLevelPortNums] topoInfo is null");
47+ return std::vector<u32>();
48+ }
49+ if (levelIdx >= topoInfo->physicalLevels.size()) {
50+ HCCL_WARNING(
51+ "[InsCollAlgBase][GetPhysicalLevelPortNums] levelIdx[%u] out of range, physicalLevelNum[%zu]", levelIdx,
52+ topoInfo->physicalLevels.size());
53+ return std::vector<u32>();
54+ }
55+ return topoInfo->physicalLevels[levelIdx].portNums;
56+}
57+ 
26HcclResult InsCollAlgBase::RestoreChannelMap(58HcclResult InsCollAlgBase::RestoreChannelMap(
27 const AlgResourceCtxSerializable& resCtx,59 const AlgResourceCtxSerializable& resCtx,
28 std::vector<std::map<u32, std::vector<ChannelInfo>>>& rankIdToChannelInfo) const60 std::vector<std::map<u32, std::vector<ChannelInfo>>>& rankIdToChannelInfo) const
@@ -57,6 +57,15 @@ public:
57 HcclComm comm, TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo)57 HcclComm comm, TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo)
58 = 0;58 = 0;
59 59 
60+ virtual HcclResult CalcAlgHierarchyInfoV2(
61+ TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo, const AlgAttrs& algAttrs)
62+ {
63+ (void)topoInfo;
64+ (void)algHierarchyInfo;
65+ (void)algAttrs;
66+ return HcclResult::HCCL_SUCCESS;
67+ }
68+ 
60 virtual HcclResult CalcRes(69 virtual HcclResult CalcRes(
61 HcclComm comm, const OpParam& param, const TopoInfoWithNetLayerDetails* topoInfo,70 HcclComm comm, const OpParam& param, const TopoInfoWithNetLayerDetails* topoInfo,
62 const AlgHierarchyInfoForAllLevel& algHierarchyInfo, AlgResourceRequest& resourceRequest)71 const AlgHierarchyInfoForAllLevel& algHierarchyInfo, AlgResourceRequest& resourceRequest)
@@ -95,6 +104,21 @@ public:
95 u32 notifyNumOnMainThread) const;104 u32 notifyNumOnMainThread) const;
96#endif105#endif
97protected:106protected:
107+ /*
108+ * 读取physicalLevels[levelIdx]的互联形态。Level下标与netLayer编号没有固定对应关系,
109+ * 一个netLayer可能贡献一级或两级, 拓扑形态必须走这里回查, 不能由下标推断。
110+ * 下标越界或physicalLevels为空(标准化降级)时返回COMM_TOPO_RESERVED并告警;
111+ * 该级无TopoInstance时字段本身就停在COMM_TOPO_RESERVED, 与前者同值, 调用方均按"形态不可用"处理。
112+ */
113+ CommTopo GetPhysicalLevelTopoType(const TopoInfoWithNetLayerDetails* topoInfo, u32 levelIdx) const;
114+ 
115+ /*
116+ * 读取physicalLevels[levelIdx]上本卡各条物理链路的端口数, 降序, 按iface去重。
117+ * 局部量: 同一级上各rank可能不同, 不能用它做跨rank一致的决策。
118+ * 返回空统一表示"端口数不可用"(越界/无TopoInstance/采集降级), 不表示该级有0个端口。
119+ */
120+ std::vector<u32> GetPhysicalLevelPortNums(const TopoInfoWithNetLayerDetails* topoInfo, u32 levelIdx) const;
121+ 
98 inline void SetOrderPreservedBaseParams(const OrderPreservedBaseParams& params)122 inline void SetOrderPreservedBaseParams(const OrderPreservedBaseParams& params)
99 {123 {
100 myRank_ = params.myRank;124 myRank_ = params.myRank;
@@ -18,6 +18,7 @@
18#include <unordered_set>18#include <unordered_set>
19#include <memory>19#include <memory>
20#include <functional>20#include <functional>
21+#include <type_traits>
21#include <functional>22#include <functional>
22#include <memory>23#include <memory>
23#include <hccl/hccl_comm.h>24#include <hccl/hccl_comm.h>
@@ -40,6 +41,9 @@ constexpr u32 MAX_NUM_BLOCKS = 56; // 56-72
40 41 
41constexpr u32 HCCL_LOGIC_TOPO_LEVEL_NUM = 4; // HCCL逻辑拓扑层级最多4级42constexpr u32 HCCL_LOGIC_TOPO_LEVEL_NUM = 4; // HCCL逻辑拓扑层级最多4级
42 43 
44+// physicalLevels的条目数上界。
45+constexpr u32 PHYSICAL_LEVEL_NUM_LIMIT = 10;
46+ 
43constexpr uint32_t DATATYPE_SIZE_TABLE[HCCL_DATA_TYPE_RESERVED]47constexpr uint32_t DATATYPE_SIZE_TABLE[HCCL_DATA_TYPE_RESERVED]
44 = {sizeof(int8_t),48 = {sizeof(int8_t),
45 sizeof(int16_t),49 sizeof(int16_t),
@@ -175,6 +179,58 @@ struct TopoInstDetails {
175 std::map<CommTopo, std::vector<u32>> rankNumForTopoType;179 std::map<CommTopo, std::vector<u32>> rankNumForTopoType;
176};180};
177 181 
182+// 该Level是否知道整个通信域在这个粒度上的完整划分。这是RankGraph两组接口的能力差异, 无法互相推导:
183+// 只有GetInstSizeListByLayer看得到兄弟NetInstance, GetTopoInstsByLayer只看得到本rank所在的那一个
184+enum class PhysicalLevelView : u32 {
185+ LOCAL = 0, // 只知道当前rank所在的那一块; instSizeListByLayer恒为空
186+ GLOBAL = 1, // 知道该netLayer的完整分区; instSizeListByLayer非空
187+};
188+ 
189+// 该Level在RankGraph中的原始身份, 用于回查。
190+// netLayer恒有效(每个Level必然归属某一层); topoInstId在该Level有TopoInstance支撑时才有效
191+struct PhysicalSourceRef {
192+ u32 netLayer = INVALID_UINT;
193+ u32 topoInstId = INVALID_UINT;
194+};
195+ 
196+// 范围链上的一环。整条链按三键排序, 相邻两环的rank集合满足包含关系(可以相等)
197+struct PhysicalLevelInfo {
198+ // 当前rank在该范围内可见的全部rank, 升序去重, 必然含当前rank。
199+ // 局部量: 同一级上不同rank看到的集合不同(rank 0看到{0..7}, rank 9看到{8..15}),
200+ std::vector<u32> localRanks;
201+ PhysicalLevelView view = PhysicalLevelView::LOCAL;
202+ // 该netLayer上全部NetInstance的大小, 按最小rankId升序, 即一份分区布局; view为LOCAL时恒为空。
203+ // 原样透传HcclRankGraphGetInstSizeListByLayer的返回序, 不重排 —— 重排会毁掉布局语义。
204+ // 全局量, 跨rank逐字节相同, 是本结构唯一可用的跨rank一致性锚点
205+ std::vector<u32> instSizeListByLayer;
206+ PhysicalSourceRef ref;
207+ 
208+ // ---- 以下为链路属性: 由该Level的TopoInstance提供, 全部随hasTopoInst一起生效 ----
209+ 
210+ // 该Level有无TopoInstance支撑。false时下面全部链路属性无意义, 各自保持无效值
211+ bool hasTopoInst = false;
212+ // 互联形态。同时是排序第三键: netLayer 0上同范围的Mesh与CLOS靠它定序
213+ CommTopo topoType = CommTopo::COMM_TOPO_RESERVED;
214+ // 该Level的链路落在Device还是Host。消费侧据此判断"是否需要使用host网卡"(看最高一级)。
215+ EndpointLocType locType = EndpointLocType::ENDPOINT_LOC_TYPE_RESERVED;
216+ // 该Level上出现的协议集合, 去重升序。是集合而不是单值: 同一个iface可以同时跑多种协议
217+ // (如ub_ctp与ub_mem), HCOMM侧会为每种协议各生成一个EndpointDesc但它们指向同一个iface
218+ std::vector<CommProtocol> protocols;
219+ // 该Level上本卡各条物理链路的端口数, 降序, 按iface(commAddr)去重, 求和为本卡在该级的总端口数。
220+ // 取自ENDPOINT_ATTR_BW_COEFF, HCOMM侧实现即iface->GetPorts().size()。
221+ std::vector<u32> portNums;
222+ // 当前rank在该Level上的Endpoint快照, 供建链侧回查。已按(protocol, locType, addr)排序:
223+ // 原始返回是哈希序, 不排序会导致同一拓扑在不同进程下得到不同的字节流
224+ std::vector<EndpointDesc> endpoints;
225+};
226+ 
227+/*
228+ * endpoints走BinaryStream的整块裸拷贝, 只对POD正确。EndpointDesc将来若引入变长成员(如std::string),
229+ * 写进流的会是堆指针而不是内容, 且不报错、只在远处随机崩溃。这条断言让那种改动直接编译失败。
230+ */
231+static_assert(
232+ std::is_trivially_copyable<EndpointDesc>::value, "EndpointDesc must be trivially copyable for serialization");
233+ 
178#define HCCL_GROUP_NAME_MAX_LEN 127234#define HCCL_GROUP_NAME_MAX_LEN 127
179 235 
180typedef struct {236typedef struct {
@@ -224,56 +280,57 @@ struct TopoInfoWithNetLayerDetails : public TopoInfo { // 通信域拓扑ctx
224 bool level0Symmetric{false};280 bool level0Symmetric{false};
225 bool level1Symmetric{false};281 bool level1Symmetric{false};
226 u32 topoInstDetailsOfLayerSize = 0;282 u32 topoInstDetailsOfLayerSize = 0;
283+ // 本卡是否为POD机型, 由CalcDeviceFormFactor查ACL_DEV_ATTR_DEVICE_FORM_FACTOR得到, 取不到停在false
284+ bool isPod = false;
227 Level0MeshType level0MeshType;285 Level0MeshType level0MeshType;
228 NetLayerDetails netLayerDetails;286 NetLayerDetails netLayerDetails;
229 std::vector<TopoInstDetails> topoInstDetailsOfLayer;287 std::vector<TopoInstDetails> topoInstDetailsOfLayer;
288+ // physicalLevels的条目数, 由Serialize统一回填。
289+ u32 physicalLevelNum = 0;
290+ std::vector<PhysicalLevelInfo> physicalLevels;
291+ 
292+ // 全部定长字段与netLayerDetails, 按声明顺序列出一次。Serialize与DeSerialize共用本清单
293+ template <typename Ar>
294+ void VisitFields(Ar& ar)
295+ {
296+ ar & userRank & userRankSize & serverIdx & superPodIdx & deviceType & deviceNumPerModule;
297+ ar & serverNumPerSuperPod & serverNum & moduleNum & superPodNum & moduleIdx;
298+ ar & isDiffDeviceModule & multiModuleDiffDeviceNumMode & multiSuperPodDiffServerNumMode;
299+ ar & isHCCSSWNumEqualToTwiceSIONum & mainThread & notifyNumOnMainThread;
300+ ar & topoLevelNums & level0Topo & Level0Nhr & Level1Nhr & Level1Hd & is2DieFullMesh;
301+ ar & level0PcieMix & level0BigClosRange & topLevelUboe & level2UbRtp & hostDpuOnly;
302+ ar & level0Symmetric & level1Symmetric & topoInstDetailsOfLayerSize & isPod & level0MeshType;
303+ ar & netLayerDetails.netLayerNum & netLayerDetails.netLayers & netLayerDetails.netInstNumOfLayer;
304+ ar & netLayerDetails.instSizeListOfLayer & netLayerDetails.localNetInsSizeOfLayer;
305+ }
306+ 
307+ template <typename Ar>
308+ static void VisitTopoInstDetails(Ar& ar, TopoInstDetails& details)
309+ {
310+ ar & details.topoInstNum & details.sizeOfTopo & details.typeOfTopo & details.ranksInTopo;
311+ ar & details.rankNumForTopoType;
312+ }
313+ 
314+ template <typename Ar>
315+ static void VisitPhysicalLevel(Ar& ar, PhysicalLevelInfo& level)
316+ {
317+ ar & level.localRanks & level.view & level.instSizeListByLayer;
318+ ar & level.ref.netLayer & level.ref.topoInstId & level.hasTopoInst & level.topoType;
319+ ar & level.locType & level.protocols & level.portNums & level.endpoints;
320+ }
230 321 
231 std::vector<char> Serialize()322 std::vector<char> Serialize()
232 {323 {
233 BinaryStream binaryStream;324 BinaryStream binaryStream;
234- binaryStream << userRank;325+ BinaryWriter ar(binaryStream);
235- binaryStream << userRankSize;326+ VisitFields(ar);
236- binaryStream << serverIdx;
237- binaryStream << superPodIdx;
238- binaryStream << deviceType;
239- binaryStream << deviceNumPerModule;
240- binaryStream << serverNumPerSuperPod;
241- binaryStream << serverNum;
242- binaryStream << moduleNum;
243- binaryStream << superPodNum;
244- binaryStream << moduleIdx;
245- binaryStream << isDiffDeviceModule;
246- binaryStream << multiModuleDiffDeviceNumMode;
247- binaryStream << multiSuperPodDiffServerNumMode;
248- binaryStream << isHCCSSWNumEqualToTwiceSIONum;
249- binaryStream << mainThread;
250- binaryStream << notifyNumOnMainThread;
251- binaryStream << topoLevelNums;
252- binaryStream << level0Topo;
253- binaryStream << Level0Nhr;
254- binaryStream << Level1Nhr;
255- binaryStream << Level1Hd;
256- binaryStream << is2DieFullMesh;
257- binaryStream << level0PcieMix;
258- binaryStream << level0BigClosRange;
259- binaryStream << topLevelUboe;
260- binaryStream << level2UbRtp;
261- binaryStream << hostDpuOnly;
262- binaryStream << level0Symmetric;
263- binaryStream << level1Symmetric;
264- binaryStream << topoInstDetailsOfLayerSize;
265- binaryStream << level0MeshType;
266- binaryStream << netLayerDetails.netLayerNum;
267- binaryStream << netLayerDetails.netLayers;
268- binaryStream << netLayerDetails.netInstNumOfLayer;
269- binaryStream << netLayerDetails.instSizeListOfLayer;
270- binaryStream << netLayerDetails.localNetInsSizeOfLayer;
271 for (uint32_t idx = 0; idx < topoInstDetailsOfLayerSize; idx++) {327 for (uint32_t idx = 0; idx < topoInstDetailsOfLayerSize; idx++) {
272- binaryStream << topoInstDetailsOfLayer[idx].topoInstNum;328+ VisitTopoInstDetails(ar, topoInstDetailsOfLayer[idx]);
273- binaryStream << topoInstDetailsOfLayer[idx].sizeOfTopo;329+ }
274- binaryStream << topoInstDetailsOfLayer[idx].typeOfTopo;330+ physicalLevelNum = static_cast<u32>(physicalLevels.size());
275- binaryStream << topoInstDetailsOfLayer[idx].ranksInTopo;331+ binaryStream << physicalLevelNum;
276- binaryStream << topoInstDetailsOfLayer[idx].rankNumForTopoType;332+ for (auto& level : physicalLevels) {
333+ VisitPhysicalLevel(ar, level);
277 }334 }
278 std::vector<char> result;335 std::vector<char> result;
279 binaryStream.Dump(result);336 binaryStream.Dump(result);
@@ -283,53 +340,28 @@ struct TopoInfoWithNetLayerDetails : public TopoInfo { // 通信域拓扑ctx
283 void DeSerialize(std::vector<char>& data)340 void DeSerialize(std::vector<char>& data)
284 {341 {
285 BinaryStream binaryStream(data);342 BinaryStream binaryStream(data);
286- binaryStream >> userRank;343+ BinaryReader ar(binaryStream);
287- binaryStream >> userRankSize;344+ VisitFields(ar);
288- binaryStream >> serverIdx;
289- binaryStream >> superPodIdx;
290- binaryStream >> deviceType;
291- binaryStream >> deviceNumPerModule;
292- binaryStream >> serverNumPerSuperPod;
293- binaryStream >> serverNum;
294- binaryStream >> moduleNum;
295- binaryStream >> superPodNum;
296- binaryStream >> moduleIdx;
297- binaryStream >> isDiffDeviceModule;
298- binaryStream >> multiModuleDiffDeviceNumMode;
299- binaryStream >> multiSuperPodDiffServerNumMode;
300- binaryStream >> isHCCSSWNumEqualToTwiceSIONum;
301- binaryStream >> mainThread;
302- binaryStream >> notifyNumOnMainThread;
303- binaryStream >> topoLevelNums;
304- binaryStream >> level0Topo;
305- binaryStream >> Level0Nhr;
306- binaryStream >> Level1Nhr;
307- binaryStream >> Level1Hd;
308- binaryStream >> is2DieFullMesh;
309- binaryStream >> level0PcieMix;
310- binaryStream >> level0BigClosRange;
311- binaryStream >> topLevelUboe;
312- binaryStream >> level2UbRtp;
313- binaryStream >> hostDpuOnly;
314- binaryStream >> level0Symmetric;
315- binaryStream >> level1Symmetric;
316- binaryStream >> topoInstDetailsOfLayerSize;
317- binaryStream >> level0MeshType;
318- binaryStream >> netLayerDetails.netLayerNum;
319- binaryStream >> netLayerDetails.netLayers;
320- binaryStream >> netLayerDetails.netInstNumOfLayer;
321- binaryStream >> netLayerDetails.instSizeListOfLayer;
322- binaryStream >> netLayerDetails.localNetInsSizeOfLayer;
323 if (topoInstDetailsOfLayerSize > HCCL_LOGIC_TOPO_LEVEL_NUM) {345 if (topoInstDetailsOfLayerSize > HCCL_LOGIC_TOPO_LEVEL_NUM) {
324 topoInstDetailsOfLayerSize = HCCL_LOGIC_TOPO_LEVEL_NUM;346 topoInstDetailsOfLayerSize = HCCL_LOGIC_TOPO_LEVEL_NUM;
325 }347 }
326 topoInstDetailsOfLayer.resize(topoInstDetailsOfLayerSize);348 topoInstDetailsOfLayer.resize(topoInstDetailsOfLayerSize);
327 for (uint32_t idx = 0; idx < topoInstDetailsOfLayerSize; idx++) {349 for (uint32_t idx = 0; idx < topoInstDetailsOfLayerSize; idx++) {
328- binaryStream >> topoInstDetailsOfLayer[idx].topoInstNum;350+ VisitTopoInstDetails(ar, topoInstDetailsOfLayer[idx]);
329- binaryStream >> topoInstDetailsOfLayer[idx].sizeOfTopo;351+ }
330- binaryStream >> topoInstDetailsOfLayer[idx].typeOfTopo;352+ physicalLevelNum = 0;
331- binaryStream >> topoInstDetailsOfLayer[idx].ranksInTopo;353+ physicalLevels.clear();
332- binaryStream >> topoInstDetailsOfLayer[idx].rankNumForTopoType;354+ binaryStream >> physicalLevelNum;
355+ if (physicalLevelNum > PHYSICAL_LEVEL_NUM_LIMIT) {
356+ HCCL_WARNING(
357+ "[TopoInfo][DeSerialize] implausible physicalLevelNum[%u], drop the whole physical level section",
358+ physicalLevelNum);
359+ physicalLevelNum = 0;
360+ return;
361+ }
362+ physicalLevels.resize(physicalLevelNum);
363+ for (auto& level : physicalLevels) {
364+ VisitPhysicalLevel(ar, level);
333 }365 }
334 }366 }
335};367};
@@ -459,9 +491,24 @@ struct AlgResourceCtx {
459 // ChannelInfo* channels; // 通信链路,数量可根据algHierarchyInfo字段进行推算491 // ChannelInfo* channels; // 通信链路,数量可根据algHierarchyInfo字段进行推算
460};492};
461 493 
494+// 物理层索引,用于 physicalIdxForAlgoLevels
495+enum class PhysicalLevelIndex : uint32_t {
496+ PHYSICAL_LEVEL_IDX_0,
497+ PHYSICAL_LEVEL_IDX_1,
498+ PHYSICAL_LEVEL_IDX_2,
499+ PHYSICAL_LEVEL_IDX_3,
500+ PHYSICAL_LEVEL_IDX_4,
501+ PHYSICAL_LEVEL_IDX_5,
502+ PHYSICAL_LEVEL_IDX_6,
503+ PHYSICAL_LEVEL_IDX_7,
504+ PHYSICAL_LEVEL_IDX_8,
505+ PHYSICAL_LEVEL_IDX_9,
506+};
507+ 
462// 如果能够序列化那么就是下面的结构体508// 如果能够序列化那么就是下面的结构体
463struct AlgHierarchyInfoForAllLevel {509struct AlgHierarchyInfoForAllLevel {
464 std::vector<std::vector<std::vector<u32>>> infos; // 第一维表示有多少level,第二维是每个level的rankID510 std::vector<std::vector<std::vector<u32>>> infos; // 第一维表示有多少level,第二维是每个level的rankID
511+ std::vector<std::vector<PhysicalLevelIndex>> physicalIdxForAlgoLevels; // 每个算法层可对应多个物理层
465};512};
466// 如果能够序列化那么就是下面的结构体513// 如果能够序列化那么就是下面的结构体
467// 先序列化,把东西考到device,然后把指针存到OpParam,在device侧反序列该指针执行的内存514// 先序列化,把东西考到device,然后把指针存到OpParam,在device侧反序列该指针执行的内存
@@ -497,6 +544,7 @@ struct AlgResourceCtxSerializable {
497 544 
498 binaryStream << algType;545 binaryStream << algType;
499 binaryStream << algHierarchyInfo.infos;546 binaryStream << algHierarchyInfo.infos;
547+ binaryStream << algHierarchyInfo.physicalIdxForAlgoLevels;
500 binaryStream << cclMem;548 binaryStream << cclMem;
501 binaryStream << notifyNumOnMainThread;549 binaryStream << notifyNumOnMainThread;
502 binaryStream << slaveThreadNum;550 binaryStream << slaveThreadNum;
@@ -532,6 +580,7 @@ struct AlgResourceCtxSerializable {
532 580 
533 binaryStream >> algType;581 binaryStream >> algType;
534 binaryStream >> algHierarchyInfo.infos;582 binaryStream >> algHierarchyInfo.infos;
583+ binaryStream >> algHierarchyInfo.physicalIdxForAlgoLevels;
535 binaryStream >> cclMem;584 binaryStream >> cclMem;
536 binaryStream >> notifyNumOnMainThread;585 binaryStream >> notifyNumOnMainThread;
537 binaryStream >> slaveThreadNum;586 binaryStream >> slaveThreadNum;
@@ -793,7 +793,12 @@ HcclResult GeReuseResource(
793{793{
794 // 计算AlgHierarchyInfo794 // 计算AlgHierarchyInfo
795 AlgHierarchyInfoForAllLevel algHierarchyInfo; // 分级通信域信息{localRankId, localRankSize}795 AlgHierarchyInfoForAllLevel algHierarchyInfo; // 分级通信域信息{localRankId, localRankSize}
796- CHK_RET(executor->CalcAlgHierarchyInfo(comm, topoInfo, algHierarchyInfo));796+ if (param.opType == HcclCMDType::HCCL_CMD_ALLGATHER_V || param.opType == HcclCMDType::HCCL_CMD_REDUCE_SCATTER_V) {
797+ AlgAttrs algAttrs = executor->GetAlgoMeta(std::string(param.algName));
798+ CHK_RET(executor->CalcAlgHierarchyInfoV2(topoInfo, algHierarchyInfo, algAttrs));
799+ } else {
800+ CHK_RET(executor->CalcAlgHierarchyInfo(comm, topoInfo, algHierarchyInfo));
801+ }
797 // 资源计算802 // 资源计算
798 AlgResourceRequest resRequest;803 AlgResourceRequest resRequest;
799 CHK_RET(executor->CalcRes(comm, param, topoInfo, algHierarchyInfo, resRequest));804 CHK_RET(executor->CalcRes(comm, param, topoInfo, algHierarchyInfo, resRequest));
@@ -1228,7 +1233,11 @@ HcclResult HcclGetAlgRes(
1228 1233 
1229 // 计算AlgHierarchyInfo1234 // 计算AlgHierarchyInfo
1230 AlgHierarchyInfoForAllLevel algHierarchyInfo; // 分级通信域信息{localRankId, localRankSize}1235 AlgHierarchyInfoForAllLevel algHierarchyInfo; // 分级通信域信息{localRankId, localRankSize}
1231- CHK_RET(executor->CalcAlgHierarchyInfo(comm, topoInfo, algHierarchyInfo));1236+ if (param.opType == HcclCMDType::HCCL_CMD_ALLGATHER_V || param.opType == HcclCMDType::HCCL_CMD_REDUCE_SCATTER_V) {
1237+ CHK_RET(executor->CalcAlgHierarchyInfoV2(topoInfo, algHierarchyInfo, algoMeta));
1238+ } else {
1239+ CHK_RET(executor->CalcAlgHierarchyInfo(comm, topoInfo, algHierarchyInfo));
1240+ }
1232 // 资源计算1241 // 资源计算
1233 HCCL_INFO("[HcclGetAlgRes] executor->CalcRes.");1242 HCCL_INFO("[HcclGetAlgRes] executor->CalcRes.");
1234 AlgResourceRequest resRequest;1243 AlgResourceRequest resRequest;
@@ -13,7 +13,15 @@ set(src_list
13 ${CMAKE_CURRENT_SOURCE_DIR}/topo_host.cc13 ${CMAKE_CURRENT_SOURCE_DIR}/topo_host.cc
14 ${CMAKE_CURRENT_SOURCE_DIR}/topo_match_1d.cc14 ${CMAKE_CURRENT_SOURCE_DIR}/topo_match_1d.cc
15 ${CMAKE_CURRENT_SOURCE_DIR}/topo_match_base.cc15 ${CMAKE_CURRENT_SOURCE_DIR}/topo_match_base.cc
16+ ${CMAKE_CURRENT_SOURCE_DIR}/topo_match_base_v2.cc
16 ${CMAKE_CURRENT_SOURCE_DIR}/topo_match_concurrent.cc17 ${CMAKE_CURRENT_SOURCE_DIR}/topo_match_concurrent.cc
18+ # PhysicalLevel标准化: 不依赖版本宏, 因此放在无条件块
19+ ${CMAKE_CURRENT_SOURCE_DIR}/physical_level_build.cc
20+ ${CMAKE_CURRENT_SOURCE_DIR}/physical_level_normalize.cc
21+ ${CMAKE_CURRENT_SOURCE_DIR}/topo_match_one_level.cc
22+ ${CMAKE_CURRENT_SOURCE_DIR}/topo_match_two_level.cc
23+ ${CMAKE_CURRENT_SOURCE_DIR}/topo_match_three_level.cc
24+ ${CMAKE_CURRENT_SOURCE_DIR}/topo_match_concurrent_v2.cc
17)25)
18if(NOT HCCL_CANN_COMPAT_850)26if(NOT HCCL_CANN_COMPAT_850)
19 list(APPEND src_list27 list(APPEND src_list
@@ -0,0 +1,64 @@
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+#ifndef OPS_HCCL_SRC_OPS_TOPO_PHYSICAL_LEVEL
12+#define OPS_HCCL_SRC_OPS_TOPO_PHYSICAL_LEVEL
13+ 
14+#include <hccl/hccl_types.h>
15+#include <vector>
16+#include "alg_param.h"
17+ 
18+namespace ops_hccl {
19+ 
20+// Endpoint数量的合理性阈值, 不是防御性截断: 该值是纯本地量(接口数 x 协议数), 不随rankSize增长,
21+// 超过该量级只可能是HCOMM侧异常, 此时局部降级并告警
22+constexpr u32 ENDPOINT_NUM_SANITY_LIMIT = 64;
23+ 
24+// 单个端口组的端口数合理性阈值。HCOMM侧MAX_PORT_NUM是32, 驱动侧UB口上限36,
25+// 超过该量级说明ENDPOINT_ATTR_BW_COEFF返回的不是端口数, 整个Level的portNums不可信
26+constexpr u32 PORT_NUM_SANITY_LIMIT = 64;
27+ 
28+// ---- 纯函数: 不依赖HcclComm与RankGraph, 可离线UT (physical_level_normalize.cc) ----
29+ 
30+/**
31+ * EndpointDesc的稳定排序键。GetEndpointDesc的输出是哈希序, 必须归一化后再保存。
32+ * 按字段比较而不是memcmp整个结构体: 尾部raws在HCOMM侧从未赋值。
33+ */
34+bool EndpointDescLess(const EndpointDesc& lhs, const EndpointDesc& rhs);
35+ 
36+/**
37+ * 两个EndpointDesc是否指向同一个iface。判据是commAddr —— HCOMM的endpointToIfaceMap以
38+ * (commAddr, protocol)为键, 同addr不同protocol必然映射到同一个iface。用于按链路统计端口数。
39+ */
40+bool CommAddrEqual(const CommAddr& lhs, const CommAddr& rhs);
41+ 
42+/**
43+ * 候选范围的标准化: 归一 -> 三键排序 -> 范围链校验, 不做合并。candidates按值语义被移动消耗。
44+ * 返回HCCL_E_NOT_SUPPORT表示不构成范围链或排序键取不到值, 由调用方降级。
45+ */
46+HcclResult NormalizePhysicalLevels(
47+ std::vector<PhysicalLevelInfo>& candidates, u32 userRank, u32 userRankSize, std::vector<PhysicalLevelInfo>& levels);
48+ 
49+/**
50+ * 标准化结果的一致性校验, 逐条对应标准化后应当成立的不变量。
51+ */
52+HcclResult ValidatePhysicalLevels(const std::vector<PhysicalLevelInfo>& levels, u32 userRank, u32 userRankSize);
53+ 
54+// ---- 依赖HcclComm (physical_level_build.cc) ----
55+ 
56+/**
57+ * 构建topoInfo->physicalLevels。任何失败一律降级为空视图并返回HCCL_SUCCESS,
58+ * 绝不改变CalcTopoShape的返回值。
59+ */
60+HcclResult BuildPhysicalLevels(HcclComm comm, TopoInfoWithNetLayerDetails* topoInfo);
61+ 
62+} // namespace ops_hccl
63+ 
64+#endif
@@ -0,0 +1,495 @@
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 "physical_level.h"
12+ 
13+#include <algorithm>
14+#include <functional>
15+#include <numeric>
16+#include <string>
17+ 
18+#include "log.h"
19+#include "hccl_rank_graph_dl.h"
20+ 
21+namespace ops_hccl {
22+namespace {
23+ 
24+ // 日志里一个vector最多展开的元素数, 超出部分省略
25+ constexpr size_t LOG_VEC_MAX_ITEM = 16;
26+ 
27+ std::string VecToStr(const std::vector<u32>& vec)
28+ {
29+ std::string str;
30+ for (size_t i = 0; i < vec.size(); ++i) {
31+ if (i >= LOG_VEC_MAX_ITEM) {
32+ str += ",...";
33+ break;
34+ }
35+ if (i > 0) {
36+ str += ",";
37+ }
38+ str += std::to_string(vec[i]);
39+ }
40+ return str;
41+ }
42+ 
43+ // 把一个Level拼成一行日志。localRanks只打首尾与个数, 不整条展开: 顶层那一级等于整个通信域,
44+ // 万卡场景整条打出来没人看得完, 而升序与含myRank由校验侧保证
45+ std::string DescribeLevel(const PhysicalLevelInfo& level, size_t idx, size_t total)
46+ {
47+ return "level[" + std::to_string(idx) + "/" + std::to_string(total) + "] rankNum["
48+ + std::to_string(level.localRanks.size()) + "] ranks["
49+ + (level.localRanks.empty() ?
50+ std::string("-") :
51+ std::to_string(level.localRanks.front()) + ".." + std::to_string(level.localRanks.back()))
52+ + "] view[" + std::to_string(static_cast<u32>(level.view)) + "] instSizeListByLayer["
53+ + VecToStr(level.instSizeListByLayer) + "] ref[layer " + std::to_string(level.ref.netLayer) + " inst "
54+ + std::to_string(level.ref.topoInstId) + "] hasTopoInst[" + std::to_string(level.hasTopoInst ? 1 : 0)
55+ + "] topoType[" + std::to_string(static_cast<s32>(level.topoType)) + "] locType["
56+ + std::to_string(static_cast<s32>(level.locType)) + "] protocolNum["
57+ + std::to_string(level.protocols.size()) + "] portNums[" + VecToStr(level.portNums) + "]";
58+ }
59+ 
60+ /**
61+ * 提取当前rank在指定TopoInstance上的Endpoint快照。
62+ * 返回void: endpoints是payload叶子, 不参与排序键与范围链结构, 全部失败路径都局部降级为空。
63+ */
64+ void FetchEndpoints(HcclComm comm, u32 layer, u32 instId, std::vector<EndpointDesc>& out)
65+ {
66+ out.clear();
67+ u32 num = 0;
68+ // 与下面的num == 0分开判: 取数失败是真异常, 需要留日志
69+ if (HcclRankGraphGetEndpointNum(comm, layer, instId, &num) != HCCL_SUCCESS) {
70+ HCCL_WARNING(
71+ "[PhysicalLevel][Build] get endpoint num failed at layer[%u] inst[%u], skip endpoints", layer, instId);
72+ return;
73+ }
74+ // 0是合法结果, 不是错误: 当前rank在该topoInst上没有接口/协议时就是0
75+ if (num == 0) {
76+ HCCL_DEBUG("[PhysicalLevel][Build] no endpoint at layer[%u] inst[%u]", layer, instId);
77+ return;
78+ }
79+ // 合理性阈值, 不截断: 截断只会把异常掩盖成"正常但数据少", 直接局部降级并告警
80+ if (num > ENDPOINT_NUM_SANITY_LIMIT) {
81+ HCCL_WARNING(
82+ "[PhysicalLevel][Build] implausible endpoint num[%u] at layer[%u] inst[%u], skip endpoints", num, layer,
83+ instId);
84+ return;
85+ }
86+ 
87+ // num是实际写入条数的上界(GetEndpointNum求和时不去重), 必须以回写的descNum为准resize
88+ std::vector<EndpointDesc> buf(num);
89+ u32 actualNum = num;
90+ if (HcclRankGraphGetEndpointDesc(comm, layer, instId, &actualNum, buf.data()) != HCCL_SUCCESS) {
91+ HCCL_WARNING(
92+ "[PhysicalLevel][Build] get endpoint desc failed at layer[%u] inst[%u], skip endpoints", layer, instId);
93+ return;
94+ }
95+ if (actualNum > num) {
96+ HCCL_WARNING(
97+ "[PhysicalLevel][Build] endpoint descNum[%u] exceeds requested[%u] at layer[%u] inst[%u], skip "
98+ "endpoints",
99+ actualNum, num, layer, instId);
100+ return;
101+ }
102+ buf.resize(actualNum);
103+ // GetEndpointDesc的输出顺序是unordered_map哈希序, 必须归一化后再保存
104+ std::sort(buf.begin(), buf.end(), EndpointDescLess);
105+ out = std::move(buf);
106+ }
107+ 
108+ /**
109+ * 采集本rank在该Level上各条物理链路的端口数, 降序写入out。按iface(commAddr)去重, 一条链路一项:
110+ * 一个iface有N种协议就有N个EndpointDesc, 逐endpoint查会把同一条链路的端口数重复计入。
111+ * 全有或全无: 任一条取不到就整个清空 —— 残缺数组会让消费侧算出"看着合理但偏小"的总端口数。
112+ */
113+ void FetchPortNums(HcclComm comm, u32 myRank, const std::vector<EndpointDesc>& endpoints, std::vector<u32>& out)
114+ {
115+ out.clear();
116+ // 空有两种来源: 本就没有接口, 或FetchEndpoints已降级(含HCOMM低版本弱符号未命中),
117+ // 因此这里不需要再做一次能力探测
118+ if (endpoints.empty()) {
119+ return;
120+ }
121+ 
122+ std::vector<CommAddr> seenAddrs; // 已计入的iface。规模是个位数, 线性查找即可
123+ std::vector<u32> portNums;
124+ for (const auto& desc : endpoints) {
125+ bool seen = false;
126+ for (const auto& addr : seenAddrs) {
127+ if (CommAddrEqual(addr, desc.commAddr)) {
128+ seen = true;
129+ break;
130+ }
131+ }
132+ if (seen) {
133+ continue; // 同一个iface的另一种协议, 端口数已经计过
134+ }
135+ 
136+ EndpointAttrBwCoeff portNum{};
137+ // ENDPOINT_ATTR_BW_COEFF名为"带宽系数", HCOMM侧实现即iface->GetPorts().size()
138+ if (HcclRankGraphGetEndpointInfo(
139+ comm, myRank, &desc, ENDPOINT_ATTR_BW_COEFF, sizeof(EndpointAttrBwCoeff), &portNum)
140+ != HCCL_SUCCESS) {
141+ HCCL_WARNING(
142+ "[PhysicalLevel][Build] get port num failed for rank[%u] protocol[%d], drop port nums of this "
143+ "level",
144+ myRank, static_cast<s32>(desc.protocol));
145+ return;
146+ }
147+ // 0与超限都判为不可信, 口径对齐op_common.cc的BuildChannelInfo
148+ if (portNum == 0 || portNum > PORT_NUM_SANITY_LIMIT) {
149+ HCCL_WARNING(
150+ "[PhysicalLevel][Build] implausible port num[%u] for rank[%u] protocol[%d], drop port nums of "
151+ "this level",
152+ portNum, myRank, static_cast<s32>(desc.protocol));
153+ return;
154+ }
155+ seenAddrs.push_back(desc.commAddr);
156+ portNums.push_back(static_cast<u32>(portNum));
157+ }
158+ // 降序。与endpoints的(protocol, locType, addr)序无关, 两者是同一批iface的两种独立排列
159+ std::sort(portNums.begin(), portNums.end(), std::greater<u32>());
160+ out = std::move(portNums);
161+ }
162+ 
163+ /**
164+ * 从endpoints提炼该Level的位置与协议集合。locType各endpoint不一致时置RESERVED并告警:
165+ * 一个Level对应一种网络平面, 位置本应唯一, 给出任一个都会误导"是否需要host网卡"的判断。
166+ */
167+ void FetchLocAndProtocols(
168+ const std::vector<EndpointDesc>& endpoints, EndpointLocType& locType, std::vector<CommProtocol>& protocols)
169+ {
170+ locType = EndpointLocType::ENDPOINT_LOC_TYPE_RESERVED;
171+ protocols.clear();
172+ if (endpoints.empty()) {
173+ return;
174+ }
175+ 
176+ locType = endpoints.front().loc.locType;
177+ for (const auto& desc : endpoints) {
178+ if (desc.loc.locType != locType) {
179+ HCCL_WARNING(
180+ "[PhysicalLevel][Build] mixed endpoint locType[%d] vs [%d] on one level, mark location unknown",
181+ static_cast<s32>(desc.loc.locType), static_cast<s32>(locType));
182+ locType = EndpointLocType::ENDPOINT_LOC_TYPE_RESERVED;
183+ break;
184+ }
185+ }
186+ 
187+ protocols.reserve(endpoints.size());
188+ for (const auto& desc : endpoints) {
189+ protocols.push_back(desc.protocol);
190+ }
191+ // 去重升序: endpoints已按protocol为首键排过, 但同一协议可能出现在多个iface上
192+ std::sort(protocols.begin(), protocols.end());
193+ protocols.erase(std::unique(protocols.begin(), protocols.end()), protocols.end());
194+ }
195+ 
196+ // 取该layer本地NetInstance的rank集合, 并与netLayerDetails做跨调用一致性校验
197+ HcclResult
198+ FetchLocalNetRanks(HcclComm comm, const NetLayerDetails& details, u32 layer, u32 myRank, std::vector<u32>& ranks)
199+ {
200+ u32* rawRanks = nullptr;
201+ u32 rankNum = 0;
202+ if (HcclRankGraphGetRanksByLayer(comm, layer, &rawRanks, &rankNum) != HCCL_SUCCESS || rawRanks == nullptr) {
203+ HCCL_WARNING("[PhysicalLevel][Build] get ranks by layer[%u] failed", layer);
204+ return HCCL_E_INTERNAL;
205+ }
206+ // HCOMM为该接口只持有一个成员vector, 下一次调用会clear()并重填它, 必须立即复制
207+ ranks.assign(rawRanks, rawRanks + rankNum);
208+ 
209+ // 跨调用一致性校验: localNetInsSizeOfLayer来自ExtractNetLayerDetails中的另一次调用,
210+ // 与此处不同源, 不一致说明RankGraph在两次调用之间发生了变化
211+ if (ranks.size() != details.localNetInsSizeOfLayer[layer]) {
212+ HCCL_WARNING(
213+ "[PhysicalLevel][Build] netLayer[%u] rankNum[%zu] mismatches localNetInsSize[%u]", layer, ranks.size(),
214+ details.localNetInsSizeOfLayer[layer]);
215+ return HCCL_E_INTERNAL;
216+ }
217+ if (std::find(ranks.begin(), ranks.end(), myRank) == ranks.end()) {
218+ HCCL_WARNING(
219+ "[PhysicalLevel][Build] netLayer[%u] local instance does not contain myRank[%u]", layer, myRank);
220+ return HCCL_E_INTERNAL;
221+ }
222+ return HCCL_SUCCESS;
223+ }
224+ 
225+ // 校验分区布局: 非空、总和等于通信域规模, 且用myRank做前缀和能定位到大小正确的那一块
226+ HcclResult ValidateInstSizeLayout(
227+ const std::vector<u32>& instSizeList, u32 layer, u32 myRank, size_t localRankNum, u32 userRankSize)
228+ {
229+ if (instSizeList.empty()) {
230+ HCCL_WARNING("[PhysicalLevel][Build] netLayer[%u] inst size list is empty", layer);
231+ return HCCL_E_INTERNAL;
232+ }
233+ // 哨兵, 正常路径永不触发(ExtractNetLayerDetails已用同一等式先行校验过)。
234+ // 保留它只为在HCOMM改变分层语义时第一时间暴露
235+ const u32 totalRankNum = std::accumulate(instSizeList.begin(), instSizeList.end(), 0U);
236+ if (totalRankNum != userRankSize) {
237+ HCCL_WARNING(
238+ "[PhysicalLevel][Build] netLayer[%u] inst size sum[%u] mismatches userRankSize[%u]", layer,
239+ totalRankNum, userRankSize);
240+ return HCCL_E_INTERNAL;
241+ }
242+ // 布局自检: 定位到本rank所在的块, 其大小必须等于本地实例的rank数。两个量来源独立,
243+ // 对得上才说明"按最小rankId升序"这个布局假设在本层成立
244+ u32 cumulative = 0;
245+ for (u32 instSize : instSizeList) {
246+ cumulative += instSize;
247+ if (myRank >= cumulative) {
248+ continue;
249+ }
250+ if (instSize == static_cast<u32>(localRankNum)) {
251+ return HCCL_SUCCESS;
252+ }
253+ HCCL_WARNING(
254+ "[PhysicalLevel][Build] netLayer[%u] rank[%u] locates a block of size[%u] but local "
255+ "instance has [%zu] ranks, inst size list is not laid out by ascending min rankId",
256+ layer, myRank, instSize, localRankNum);
257+ return HCCL_E_INTERNAL;
258+ }
259+ return HCCL_E_INTERNAL;
260+ }
261+ 
262+ // 取该netLayer本地NetInstance的rank集合与全层分区。这是"合一"里ranktable那一半:
263+ // 只有NetInstance看得到兄弟实例, 因此只有它能给出全局分区
264+ HcclResult FetchNetInstance(
265+ HcclComm comm, const TopoInfoWithNetLayerDetails* topoInfo, u32 layer, std::vector<u32>& ranks,
266+ std::vector<u32>& instSizeListByLayer)
267+ {
268+ const NetLayerDetails& details = topoInfo->netLayerDetails;
269+ const u32 myRank = topoInfo->userRank;
270+ if (layer >= details.localNetInsSizeOfLayer.size() || layer >= details.instSizeListOfLayer.size()) {
271+ HCCL_WARNING("[PhysicalLevel][Build] netLayer[%u] out of range of netLayerDetails arrays", layer);
272+ return HCCL_E_INTERNAL;
273+ }
274+ HcclResult ret = FetchLocalNetRanks(comm, details, layer, myRank, ranks);
275+ if (ret != HCCL_SUCCESS) {
276+ return ret;
277+ }
278+ // 原样透传HCOMM的返回序, 不重排。该序是"按最小rankId升序的分区布局",
279+ // topo_host.cc的CalcGroupIdx/GetCurrentServerStartRank已在其上做前缀和定位, 必须与之一致
280+ instSizeListByLayer = details.instSizeListOfLayer[layer];
281+ return ValidateInstSizeLayout(instSizeListByLayer, layer, myRank, ranks.size(), topoInfo->userRankSize);
282+ }
283+ 
284+ // 取该layer上全部TopoInstance的id。空map是合法结果, 返回空列表且不算失败
285+ HcclResult FetchTopoInstIds(HcclComm comm, u32 layer, std::vector<u32>& instIds)
286+ {
287+ instIds.clear();
288+ u32* rawInstIds = nullptr;
289+ u32 instNum = 0;
290+ HcclResult ret = HcclRankGraphGetTopoInstsByLayer(comm, layer, &rawInstIds, &instNum);
291+ if (ret != HCCL_SUCCESS) {
292+ HCCL_WARNING("[PhysicalLevel][Build] get topo insts of layer[%u] failed, ret[%d]", layer, ret);
293+ return HCCL_E_INTERNAL;
294+ }
295+ if (instNum == 0) {
296+ // 空map即返回0且不报错。意味着该层没有endpoints与topoType可供建链,
297+ // 调用方据此把hasTopoInst置false
298+ HCCL_DEBUG("[PhysicalLevel][Build] layer[%u] has no topo instance", layer);
299+ return HCCL_SUCCESS;
300+ }
301+ if (rawInstIds == nullptr) {
302+ HCCL_WARNING("[PhysicalLevel][Build] topo insts of layer[%u] is null while num[%u]", layer, instNum);
303+ return HCCL_E_INTERNAL;
304+ }
305+ // 立即复制: 该接口的下一次调用会clear()并重填同一个成员vector
306+ instIds.assign(rawInstIds, rawInstIds + instNum);
307+ return HCCL_SUCCESS;
308+ }
309+ 
310+ // 采集单个TopoInstance并追加到out。当前rank不在其中时跳过且不写out, 不算失败
311+ HcclResult
312+ AppendTopoInstLevel(HcclComm comm, u32 myRank, u32 layer, u32 instId, std::vector<PhysicalLevelInfo>& out)
313+ {
314+ u32* rawRanks = nullptr;
315+ u32 rankNum = 0;
316+ if (HcclRankGraphGetRanksByTopoInst(comm, layer, instId, &rawRanks, &rankNum) != HCCL_SUCCESS
317+ || rawRanks == nullptr) {
318+ HCCL_WARNING("[PhysicalLevel][Build] get ranks by topo inst[%u] of layer[%u] failed", instId, layer);
319+ return HCCL_E_INTERNAL;
320+ }
321+ std::vector<u32> ranks(rawRanks, rawRanks + rankNum);
322+ 
323+ // GetTopoInstsByLayer返回的应当只含当前rank所在的topoInstance, 这里再过滤一次兜底
324+ if (std::find(ranks.begin(), ranks.end(), myRank) == ranks.end()) {
325+ HCCL_DEBUG(
326+ "[PhysicalLevel][Build] skip sibling topo inst[%u] of layer[%u], myRank[%u] not in it", instId, layer,
327+ myRank);
328+ return HCCL_SUCCESS;
329+ }
330+ 
331+ // 必须用按topoInst的GetTopoType。按netLayer的GetTopoTypeByLayer查的是NetType,
332+ // A5上Mesh层是TOPO_FILE_DESC描述的, 会返回COMM_TOPO_CUSTOM, TopoTypeOrder定不了序
333+ CommTopo topoType = CommTopo::COMM_TOPO_RESERVED;
334+ if (HcclRankGraphGetTopoType(comm, layer, instId, &topoType) != HCCL_SUCCESS) {
335+ HCCL_WARNING("[PhysicalLevel][Build] get topo type of inst[%u] layer[%u] failed", instId, layer);
336+ return HCCL_E_INTERNAL;
337+ }
338+ 
339+ PhysicalLevelInfo level;
340+ level.localRanks = std::move(ranks);
341+ level.ref.netLayer = layer;
342+ level.ref.topoInstId = instId;
343+ level.hasTopoInst = true;
344+ level.topoType = topoType;
345+ // 以下三项无返回值: 内部失败一律局部降级, 理由见各自声明处
346+ FetchEndpoints(comm, layer, instId, level.endpoints);
347+ FetchLocAndProtocols(level.endpoints, level.locType, level.protocols);
348+ FetchPortNums(comm, myRank, level.endpoints, level.portNums);
349+ out.push_back(std::move(level));
350+ return HCCL_SUCCESS;
351+ }
352+ 
353+ // 取该netLayer上、含当前rank的每个TopoInstance的rank集合, 并把链路属性填进level。
354+ // 这是"合一"里topo那一半: 只有TopoInstance带得出形态/位置/协议/端口数
355+ HcclResult FetchTopoInstances(HcclComm comm, u32 myRank, u32 layer, std::vector<PhysicalLevelInfo>& out)
356+ {
357+ out.clear();
358+ std::vector<u32> instIds;
359+ HcclResult ret = FetchTopoInstIds(comm, layer, instIds);
360+ if (ret != HCCL_SUCCESS) {
361+ return ret;
362+ }
363+ for (u32 instId : instIds) {
364+ ret = AppendTopoInstLevel(comm, myRank, layer, instId, out);
365+ if (ret != HCCL_SUCCESS) {
366+ return ret;
367+ }
368+ }
369+ return HCCL_SUCCESS;
370+ }
371+ 
372+ /**
373+ * 按netLayer把ranktable层级与topo层级合成候选Level。合并规则:
374+ * 同范围的TopoInstance -> 与NetInstance合并成一级(view=GLOBAL); 更小的 -> 独立成级(view=LOCAL);
375+ * 该层没有TopoInstance -> NetInstance独立成级(hasTopoInst=false)。
376+ */
377+ HcclResult BuildLayerCandidates(
378+ HcclComm comm, const TopoInfoWithNetLayerDetails* topoInfo, u32 layer,
379+ std::vector<PhysicalLevelInfo>& candidates)
380+ {
381+ const u32 myRank = topoInfo->userRank;
382+ std::vector<u32> netRanks;
383+ std::vector<u32> instSizeListByLayer;
384+ CHK_RET(FetchNetInstance(comm, topoInfo, layer, netRanks, instSizeListByLayer));
385+ 
386+ std::vector<PhysicalLevelInfo> topoLevels;
387+ CHK_RET(FetchTopoInstances(comm, myRank, layer, topoLevels));
388+ 
389+ // 两侧都来自HCOMM且均为升序无重复, 直接比vector即可
390+ std::vector<u32> sortedNetRanks = netRanks;
391+ std::sort(sortedNetRanks.begin(), sortedNetRanks.end());
392+ 
393+ bool merged = false;
394+ for (auto& level : topoLevels) {
395+ std::vector<u32> sortedTopoRanks = level.localRanks;
396+ std::sort(sortedTopoRanks.begin(), sortedTopoRanks.end());
397+ if (sortedTopoRanks == sortedNetRanks) {
398+ // 同范围: 把NetInstance的全局分区并进来。分区是该层的全局事实, 同层多个
399+ // 同范围TopoInstance(如netLayer 0的Mesh与CLOS)各自都持有它
400+ level.view = PhysicalLevelView::GLOBAL;
401+ level.instSizeListByLayer = instSizeListByLayer;
402+ merged = true;
403+ } else {
404+ // 比NetInstance更细: 看不到兄弟NetInstance, 没有全局分区可言
405+ level.view = PhysicalLevelView::LOCAL;
406+ level.instSizeListByLayer.clear();
407+ }
408+ HCCL_DEBUG(
409+ "[PhysicalLevel][Build] layer[%u] inst[%u] rankNum[%zu] view[%u] topoType[%d] locType[%d] "
410+ "protocolNum[%zu] portNumCnt[%zu]",
411+ layer, level.ref.topoInstId, level.localRanks.size(), static_cast<u32>(level.view),
412+ static_cast<s32>(level.topoType), static_cast<s32>(level.locType), level.protocols.size(),
413+ level.portNums.size());
414+ candidates.push_back(std::move(level));
415+ }
416+ 
417+ if (!merged) {
418+ // 没有同范围的TopoInstance: 分区信息仍有效但拿不到链路属性, 用hasTopoInst=false显式注明
419+ PhysicalLevelInfo level;
420+ level.localRanks = std::move(netRanks);
421+ level.view = PhysicalLevelView::GLOBAL;
422+ level.instSizeListByLayer = std::move(instSizeListByLayer);
423+ level.ref.netLayer = layer;
424+ level.ref.topoInstId = INVALID_UINT;
425+ level.hasTopoInst = false;
426+ HCCL_DEBUG(
427+ "[PhysicalLevel][Build] layer[%u] has no same-range topo instance, level carries partition only, "
428+ "rankNum[%zu]",
429+ layer, level.localRanks.size());
430+ candidates.push_back(std::move(level));
431+ }
432+ return HCCL_SUCCESS;
433+ }
434+ 
435+ HcclResult BuildPhysicalLevelCandidates(
436+ HcclComm comm, const TopoInfoWithNetLayerDetails* topoInfo, std::vector<PhysicalLevelInfo>& candidates)
437+ {
438+ candidates.clear();
439+ if (topoInfo->netLayerDetails.netLayers.empty()) {
440+ HCCL_WARNING("[PhysicalLevel][Build] netLayers is empty, rank[%u]", topoInfo->userRank);
441+ return HCCL_E_INTERNAL;
442+ }
443+ // 只遍历netLayers里的layer: 规避HCOMM对非法layer的抛异常分支, 并保证ref.netLayer都来自
444+ // GetLayers的实际结果。收集先后无所谓, LevelLess是全序, 不依赖输入顺序
445+ for (u32 layer : topoInfo->netLayerDetails.netLayers) {
446+ CHK_RET(BuildLayerCandidates(comm, topoInfo, layer, candidates));
447+ }
448+ return HCCL_SUCCESS;
449+ }
450+ 
451+} // namespace
452+ 
453+HcclResult BuildPhysicalLevels(HcclComm comm, TopoInfoWithNetLayerDetails* topoInfo)
454+{
455+ CHK_PTR_NULL(topoInfo);
456+ topoInfo->physicalLevels.clear();
457+ if (comm == nullptr) {
458+ HCCL_WARNING("[PhysicalLevel][Build] comm is null, physicalLevels stays empty");
459+ return HCCL_SUCCESS;
460+ }
461+ 
462+ // 在临时对象中构建, 全部校验通过后再赋值; 降级或失败时physicalLevels保持为空
463+ std::vector<PhysicalLevelInfo> candidates;
464+ std::vector<PhysicalLevelInfo> levels;
465+ 
466+ HcclResult ret = BuildPhysicalLevelCandidates(comm, topoInfo, candidates);
467+ if (ret == HCCL_SUCCESS) {
468+ ret = NormalizePhysicalLevels(candidates, topoInfo->userRank, topoInfo->userRankSize, levels);
469+ }
470+ if (ret == HCCL_SUCCESS) {
471+ ret = ValidatePhysicalLevels(levels, topoInfo->userRank, topoInfo->userRankSize);
472+ }
473+ if (ret != HCCL_SUCCESS) {
474+ // 任何失败一律降级, 不改变CalcTopoShape的返回值
475+ HCCL_WARNING(
476+ "[PhysicalLevel][Build] normalize degraded, ret[%d], rank[%u]. physicalLevels stays empty, legacy path "
477+ "unaffected.",
478+ ret, topoInfo->userRank);
479+ return HCCL_SUCCESS;
480+ }
481+ 
482+ topoInfo->physicalLevels = std::move(levels);
483+ const size_t levelNum = topoInfo->physicalLevels.size();
484+ HCCL_RUN_INFO("[PhysicalLevel][Build] rank[%u] built [%zu] physical levels", topoInfo->userRank, levelNum);
485+ // 最终产物逐级各打一行, 打的是真正落进topoInfo的内容(BuildLayerCandidates那条DEBUG打的是候选)。
486+ // RUN_INFO让默认日志级别下就搜得到, INFO让这几行与同批INFO落在同一条时间线上
487+ for (size_t idx = 0; idx < levelNum; ++idx) {
488+ const std::string desc = DescribeLevel(topoInfo->physicalLevels[idx], idx, levelNum);
489+ HCCL_RUN_INFO("[PhysicalLevel][Build] rank[%u] %s", topoInfo->userRank, desc.c_str());
490+ HCCL_INFO("[PhysicalLevel][Build] rank[%u] %s", topoInfo->userRank, desc.c_str());
491+ }
492+ return HCCL_SUCCESS;
493+}
494+ 
495+} // namespace ops_hccl
@@ -0,0 +1,434 @@
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 "physical_level.h"
12+ 
13+#include <algorithm>
14+#include <cstring>
15+#include <functional>
16+#include <numeric>
17+ 
18+#include "log.h"
19+ 
20+namespace ops_hccl {
21+namespace {
22+ 
23+ void SortUnique(std::vector<u32>& ranks)
24+ {
25+ std::sort(ranks.begin(), ranks.end());
26+ ranks.erase(std::unique(ranks.begin(), ranks.end()), ranks.end());
27+ }
28+ 
29+ bool ContainsRank(const std::vector<u32>& sortedRanks, u32 rank)
30+ {
31+ return std::binary_search(sortedRanks.begin(), sortedRanks.end(), rank);
32+ }
33+ 
34+ /**
35+ * topoType定序, 含义是互联紧密度递减(MESH直连, CLOS经交换)。不能用枚举值代替 —— 枚举里
36+ * COMM_TOPO_CLOS=0 < COMM_TOPO_1DMESH=1, 正好相反。返回false(预期外类型)时必须整体降级。
37+ */
38+ bool TopoTypeOrder(CommTopo type, u32& order)
39+ {
40+ switch (type) {
41+ case CommTopo::COMM_TOPO_1DMESH:
42+ order = 0;
43+ return true;
44+ case CommTopo::COMM_TOPO_CLOS:
45+ order = 1;
46+ return true;
47+ default:
48+ return false;
49+ }
50+ }
51+ 
52+ // 无TopoInstance的Level没有形态可言, 排在同键位的有形态Level之后。
53+ // 取值必须与TopoTypeOrder的输出空间不重叠, 否则两类Level会在第三键上打平
54+ constexpr u32 TOPO_TYPE_ORDER_NO_TOPO_INST = 2;
55+ 
56+ u32 LevelTopoOrder(const PhysicalLevelInfo& level)
57+ {
58+ u32 order = TOPO_TYPE_ORDER_NO_TOPO_INST;
59+ if (level.hasTopoInst) {
60+ // 排序前已逐个校验过topoType可定序, 此处必然成功
61+ (void)TopoTypeOrder(level.topoType, order);
62+ }
63+ return order;
64+ }
65+ 
66+ // 排序三键 + 两个确定性兜底键, 各键语义见下方逐段注释
67+ bool LevelLess(const PhysicalLevelInfo& lhs, const PhysicalLevelInfo& rhs)
68+ {
69+ // 键1: 当前rank在该级的块大小。GLOBAL与LOCAL级的localRanks同量纲, 可直接比较
70+ if (lhs.localRanks.size() != rhs.localRanks.size()) {
71+ return lhs.localRanks.size() < rhs.localRanks.size();
72+ }
73+ // 键2: LOCAL(0)在GLOBAL(1)之前
74+ if (lhs.view != rhs.view) {
75+ return lhs.view < rhs.view;
76+ }
77+ // 键3: netLayer 0同时挂MESH和CLOS且rank集合相同时, 前两键全部打平, 定序完全依赖这一键
78+ const u32 lhsOrder = LevelTopoOrder(lhs);
79+ const u32 rhsOrder = LevelTopoOrder(rhs);
80+ if (lhsOrder != rhsOrder) {
81+ return lhsOrder < rhsOrder;
82+ }
83+ // 兜底键1, 正常输入上永不决定顺序: 此时两级必然互相重叠且互不包含, 会在链校验中被拒。
84+ // 保留它只为让"被拒"这件事本身也是确定的, 不随RankGraph的哈希返回序抖动
85+ if (lhs.localRanks != rhs.localRanks) {
86+ return lhs.localRanks < rhs.localRanks;
87+ }
88+ // 兜底键2: 原始身份。前面所有键都相同仍可能是两个不同Level(如两个netLayer的本地
89+ // NetInstance恰好同范围)。少了这一键它们在比较器下等价, std::sort的相对顺序未指定,
90+ // 各rank排出的下标语义会分叉; netLayer与topoInstId跨rank一致, 补上后比较器成为全序
91+ if (lhs.ref.netLayer != rhs.ref.netLayer) {
92+ return lhs.ref.netLayer < rhs.ref.netLayer;
93+ }
94+ return lhs.ref.topoInstId < rhs.ref.topoInstId;
95+ }
96+ 
97+ bool IsStrictlyAscending(const std::vector<u32>& ranks)
98+ {
99+ for (size_t idx = 1; idx < ranks.size(); idx++) {
100+ if (ranks[idx] <= ranks[idx - 1]) {
101+ return false;
102+ }
103+ }
104+ return true;
105+ }
106+ 
107+ bool IsSuperSetOf(const std::vector<u32>& outer, const std::vector<u32>& inner)
108+ {
109+ return std::includes(outer.begin(), outer.end(), inner.begin(), inner.end());
110+ }
111+ 
112+} // namespace
113+ 
114+bool EndpointDescLess(const EndpointDesc& lhs, const EndpointDesc& rhs)
115+{
116+ if (lhs.protocol != rhs.protocol) {
117+ return lhs.protocol < rhs.protocol;
118+ }
119+ if (lhs.loc.locType != rhs.loc.locType) {
120+ return lhs.loc.locType < rhs.loc.locType;
121+ }
122+ if (lhs.commAddr.type != rhs.commAddr.type) {
123+ return lhs.commAddr.type < rhs.commAddr.type;
124+ }
125+ return memcmp(lhs.commAddr.raws, rhs.commAddr.raws, sizeof(lhs.commAddr.raws)) < 0;
126+}
127+ 
128+bool CommAddrEqual(const CommAddr& lhs, const CommAddr& rhs)
129+{
130+ return lhs.type == rhs.type && memcmp(lhs.raws, rhs.raws, sizeof(lhs.raws)) == 0;
131+}
132+ 
133+HcclResult NormalizePhysicalLevels(
134+ std::vector<PhysicalLevelInfo>& candidates, u32 userRank, u32 userRankSize, std::vector<PhysicalLevelInfo>& levels)
135+{
136+ levels.clear();
137+ if (userRankSize == 0 || userRank >= userRankSize) {
138+ HCCL_WARNING(
139+ "[PhysicalLevel][Normalize] invalid rank info, userRank[%u], userRankSize[%u]", userRank, userRankSize);
140+ return HCCL_E_NOT_SUPPORT;
141+ }
142+ 
143+ // 1. 归一: rank列表排序去重、portNums降序、protocols去重升序, 剔除不含当前rank的候选,
144+ // 并确认topoType可定序(排序第三键的前提)。instSizeListByLayer不参与归一 —— 重排会毁掉布局语义。
145+ // 构建侧已做过同样的规范化, 这一步在正常路径上幂等, 保留是为了让本函数可离线UT
146+ std::vector<PhysicalLevelInfo> validCands;
147+ validCands.reserve(candidates.size());
148+ for (auto& cand : candidates) {
149+ SortUnique(cand.localRanks);
150+ // 不能去重: 两条8口链路就是{8,8}, 求和才是总端口数。去重的是iface, 不是端口数值
151+ std::sort(cand.portNums.begin(), cand.portNums.end(), std::greater<u32>());
152+ std::sort(cand.protocols.begin(), cand.protocols.end());
153+ cand.protocols.erase(std::unique(cand.protocols.begin(), cand.protocols.end()), cand.protocols.end());
154+ if (!ContainsRank(cand.localRanks, userRank)) {
155+ HCCL_DEBUG(
156+ "[PhysicalLevel][Normalize] drop candidate without myRank[%u], rankNum[%zu]", userRank,
157+ cand.localRanks.size());
158+ continue;
159+ }
160+ u32 unusedOrder = 0;
161+ if (cand.hasTopoInst && !TopoTypeOrder(cand.topoType, unusedOrder)) {
162+ HCCL_WARNING(
163+ "[PhysicalLevel][Normalize] level at layer[%u] inst[%u] has unorderable topoType[%d], rank[%u]",
164+ cand.ref.netLayer, cand.ref.topoInstId, static_cast<s32>(cand.topoType), userRank);
165+ return HCCL_E_NOT_SUPPORT;
166+ }
167+ validCands.push_back(std::move(cand));
168+ }
169+ if (validCands.empty()) {
170+ HCCL_WARNING("[PhysicalLevel][Normalize] no valid candidate for rank[%u]", userRank);
171+ return HCCL_E_NOT_SUPPORT;
172+ }
173+ 
174+ // 2. 三键排序, 不做合并(Level与NetInstance/TopoInstance一一对应)。用sort而非stable_sort:
175+ // LevelLess是全序, 结果与输入顺序无关 —— 输入顺序来自RankGraph的哈希遍历, 本就不可依赖
176+ std::sort(validCands.begin(), validCands.end(), LevelLess);
177+ levels = std::move(validCands);
178+ 
179+ // 3. 链校验: 相邻范围必须满足包含关系, 允许相等。
180+ // 互相重叠但互不包含的范围(典型为2D Mesh的x/y环)在此被拒绝
181+ for (size_t idx = 1; idx < levels.size(); idx++) {
182+ if (!IsSuperSetOf(levels[idx].localRanks, levels[idx - 1].localRanks)) {
183+ HCCL_WARNING(
184+ "[PhysicalLevel][Normalize] level[%zu] with rankNum[%zu] does not contain level[%zu] with "
185+ "rankNum[%zu], ranges do not form a chain, rank[%u]",
186+ idx, levels[idx].localRanks.size(), idx - 1, levels[idx - 1].localRanks.size(), userRank);
187+ levels.clear();
188+ return HCCL_E_NOT_SUPPORT;
189+ }
190+ }
191+ 
192+ HCCL_INFO("[PhysicalLevel][Normalize] rank[%u] got [%zu] levels", userRank, levels.size());
193+ return HCCL_SUCCESS;
194+}
195+ 
196+namespace {
197+ 
198+ // 不变量2: 非空、升序严格递增(等价于无重复)、无越界、含当前rank
199+ HcclResult ValidateRankList(const PhysicalLevelInfo& level, size_t idx, u32 userRank, u32 userRankSize)
200+ {
201+ if (level.localRanks.empty() || !IsStrictlyAscending(level.localRanks)) {
202+ HCCL_WARNING("[PhysicalLevel][Validate] level[%zu] rank list is empty or not ascending", idx);
203+ return HCCL_E_NOT_SUPPORT;
204+ }
205+ if (level.localRanks.back() >= userRankSize) {
206+ HCCL_WARNING(
207+ "[PhysicalLevel][Validate] level[%zu] max rank[%u] exceeds userRankSize[%u]", idx,
208+ level.localRanks.back(), userRankSize);
209+ return HCCL_E_NOT_SUPPORT;
210+ }
211+ if (!ContainsRank(level.localRanks, userRank)) {
212+ HCCL_WARNING("[PhysicalLevel][Validate] level[%zu] does not contain myRank[%u]", idx, userRank);
213+ return HCCL_E_NOT_SUPPORT;
214+ }
215+ return HCCL_SUCCESS;
216+ }
217+ 
218+ // 不变量3a: view必须是有效枚举值。底层类型是u32, 不白名单则非法值会静默落进else被当成GLOBAL
219+ // 不变量3b: ref.netLayer恒有效。无效值说明构建侧漏填, 消费侧回查时会拿到错误的层
220+ HcclResult ValidateViewAndRef(const PhysicalLevelInfo& level, size_t idx)
221+ {
222+ if (level.view != PhysicalLevelView::LOCAL && level.view != PhysicalLevelView::GLOBAL) {
223+ HCCL_WARNING(
224+ "[PhysicalLevel][Validate] level[%zu] has invalid view[%u]", idx, static_cast<u32>(level.view));
225+ return HCCL_E_NOT_SUPPORT;
226+ }
227+ if (level.ref.netLayer == INVALID_UINT) {
228+ HCCL_WARNING("[PhysicalLevel][Validate] level[%zu] has no valid netLayer", idx);
229+ return HCCL_E_NOT_SUPPORT;
230+ }
231+ return HCCL_SUCCESS;
232+ }
233+ 
234+ // 不变量3c之有TopoInstance侧: 链路属性必须自洽, 否则消费侧会把无效值当成真实链路事实建模
235+ HcclResult ValidateTopoInstAttrs(const PhysicalLevelInfo& level, size_t idx)
236+ {
237+ if (level.ref.topoInstId == INVALID_UINT) {
238+ HCCL_WARNING("[PhysicalLevel][Validate] level[%zu] hasTopoInst but topoInstId is invalid", idx);
239+ return HCCL_E_NOT_SUPPORT;
240+ }
241+ // portNums按iface去重, 条数不会超过endpoint数; 超过说明去重逻辑坏了, 总端口数会被算大
242+ if (level.portNums.size() > level.endpoints.size()) {
243+ HCCL_WARNING(
244+ "[PhysicalLevel][Validate] level[%zu] portNum count[%zu] exceeds endpoint count[%zu]", idx,
245+ level.portNums.size(), level.endpoints.size());
246+ return HCCL_E_NOT_SUPPORT;
247+ }
248+ // 0能完整穿过下面的降序检查(排在末尾), 于是不存在的链路会被当成真实出口计入
249+ for (u32 portNum : level.portNums) {
250+ if (portNum == 0 || portNum > PORT_NUM_SANITY_LIMIT) {
251+ HCCL_WARNING("[PhysicalLevel][Validate] level[%zu] has implausible portNum[%u]", idx, portNum);
252+ return HCCL_E_NOT_SUPPORT;
253+ }
254+ }
255+ // 降序规范化: 采集顺序来自endpoints的哈希序, 不规范化则跨进程字节流不同
256+ if (!std::is_sorted(level.portNums.begin(), level.portNums.end(), std::greater<u32>())) {
257+ HCCL_WARNING("[PhysicalLevel][Validate] level[%zu] portNums is not sorted descending", idx);
258+ return HCCL_E_NOT_SUPPORT;
259+ }
260+ // protocols去重升序, 理由同上
261+ if (!std::is_sorted(level.protocols.begin(), level.protocols.end())
262+ || std::adjacent_find(level.protocols.begin(), level.protocols.end()) != level.protocols.end()) {
263+ HCCL_WARNING("[PhysicalLevel][Validate] level[%zu] protocols is not sorted and unique", idx);
264+ return HCCL_E_NOT_SUPPORT;
265+ }
266+ return HCCL_SUCCESS;
267+ }
268+ 
269+ // 不变量3c之无TopoInstance侧: 全部链路属性必须保持无效值。"半有"状态最危险 —— 消费侧判定为
270+ // 不可用, 却又能从字段里读出看似合理的值
271+ HcclResult ValidateNoTopoInstAttrs(const PhysicalLevelInfo& level, size_t idx)
272+ {
273+ const bool clean = level.ref.topoInstId == INVALID_UINT && level.topoType == CommTopo::COMM_TOPO_RESERVED
274+ && level.locType == EndpointLocType::ENDPOINT_LOC_TYPE_RESERVED && level.protocols.empty()
275+ && level.portNums.empty() && level.endpoints.empty();
276+ if (!clean) {
277+ HCCL_WARNING("[PhysicalLevel][Validate] level[%zu] has no topo instance but carries link attributes", idx);
278+ return HCCL_E_NOT_SUPPORT;
279+ }
280+ return HCCL_SUCCESS;
281+ }
282+ 
283+ // 不变量4/4b/5: GLOBAL级的instSizeListByLayer是该netLayer对整个通信域的一次完整划分
284+ HcclResult ValidatePartitionList(const PhysicalLevelInfo& level, size_t idx, u32 userRankSize)
285+ {
286+ const auto begin = level.instSizeListByLayer.begin();
287+ const auto end = level.instSizeListByLayer.end();
288+ const u32 total = std::accumulate(begin, end, 0U);
289+ if (level.instSizeListByLayer.empty() || total != userRankSize) {
290+ HCCL_WARNING(
291+ "[PhysicalLevel][Validate] level[%zu] instSizeListByLayer sum[%u] mismatches userRankSize[%u]", idx,
292+ total, userRankSize);
293+ return HCCL_E_NOT_SUPPORT;
294+ }
295+ // 不变量4b: 每个分区非空。0能完整穿过本块其余检查, 于是幽灵空分区会被当成真实Instance计入
296+ if (std::find(begin, end, 0U) != end) {
297+ HCCL_WARNING("[PhysicalLevel][Validate] level[%zu] instSizeListByLayer contains a zero entry", idx);
298+ return HCCL_E_NOT_SUPPORT;
299+ }
300+ // 不变量5: 当前rank的块大小必须是这一层的某个真实分区。逻辑上被不变量6蕴含, 单列是为了
301+ // 把两类失败分开: 本条不过是"大小根本不存在", 只有本条过、6不过才是布局假设出了问题
302+ if (std::find(begin, end, static_cast<u32>(level.localRanks.size())) == end) {
303+ HCCL_WARNING(
304+ "[PhysicalLevel][Validate] level[%zu] localRankNum[%zu] is not one of the inst sizes", idx,
305+ level.localRanks.size());
306+ return HCCL_E_NOT_SUPPORT;
307+ }
308+ return HCCL_SUCCESS;
309+ }
310+ 
311+ // 不变量6: 布局自检。用userRank做前缀和必然落进某一块, 其大小必须等于localRanks.size()。
312+ // 这是instSizeListByLayer唯一一处能在本地验证的跨rank性质
313+ HcclResult ValidateRankLocatable(const PhysicalLevelInfo& level, size_t idx, u32 userRank)
314+ {
315+ u32 cumulative = 0;
316+ bool located = false;
317+ for (u32 instSize : level.instSizeListByLayer) {
318+ cumulative += instSize;
319+ if (userRank < cumulative) {
320+ located = (instSize == static_cast<u32>(level.localRanks.size()));
321+ break;
322+ }
323+ }
324+ if (!located) {
325+ HCCL_WARNING(
326+ "[PhysicalLevel][Validate] level[%zu] rank[%u] cannot be located in instSizeListByLayer "
327+ "with a block of localRankNum[%zu]",
328+ idx, userRank, level.localRanks.size());
329+ return HCCL_E_NOT_SUPPORT;
330+ }
331+ return HCCL_SUCCESS;
332+ }
333+ 
334+ // 不变量3d: view与instSizeListByLayer是否为空严格等价; 错开之后消费侧会把只知道本块的级
335+ // 当成全局分区来切算法
336+ HcclResult ValidatePartition(const PhysicalLevelInfo& level, size_t idx, u32 userRank, u32 userRankSize)
337+ {
338+ if (level.view == PhysicalLevelView::LOCAL) {
339+ if (!level.instSizeListByLayer.empty()) {
340+ HCCL_WARNING("[PhysicalLevel][Validate] level[%zu] is LOCAL but carries partition sizes", idx);
341+ return HCCL_E_NOT_SUPPORT;
342+ }
343+ return HCCL_SUCCESS;
344+ }
345+ HcclResult ret = ValidatePartitionList(level, idx, userRankSize);
346+ if (ret == HCCL_SUCCESS) {
347+ ret = ValidateRankLocatable(level, idx, userRank);
348+ }
349+ return ret;
350+ }
351+ 
352+ HcclResult ValidateLevel(const PhysicalLevelInfo& level, size_t idx, u32 userRank, u32 userRankSize)
353+ {
354+ HcclResult ret = ValidateRankList(level, idx, userRank, userRankSize);
355+ if (ret == HCCL_SUCCESS) {
356+ ret = ValidateViewAndRef(level, idx);
357+ }
358+ if (ret == HCCL_SUCCESS) {
359+ ret = level.hasTopoInst ? ValidateTopoInstAttrs(level, idx) : ValidateNoTopoInstAttrs(level, idx);
360+ }
361+ if (ret == HCCL_SUCCESS) {
362+ ret = ValidatePartition(level, idx, userRank, userRankSize);
363+ }
364+ return ret;
365+ }
366+ 
367+ // 不变量7: 大小非递减 + 包含链。允许相等 —— netLayer 0上同范围的MESH与CLOS两级、
368+ // 两个netLayer的本地NetInstance恰好同范围, 都是合法的相等相邻对
369+ HcclResult ValidateChain(const std::vector<PhysicalLevelInfo>& levels)
370+ {
371+ for (size_t idx = 1; idx < levels.size(); idx++) {
372+ if (levels[idx].localRanks.size() < levels[idx - 1].localRanks.size()) {
373+ HCCL_WARNING(
374+ "[PhysicalLevel][Validate] level[%zu] rankNum[%zu] is less than level[%zu] rankNum[%zu]", idx,
375+ levels[idx].localRanks.size(), idx - 1, levels[idx - 1].localRanks.size());
376+ return HCCL_E_NOT_SUPPORT;
377+ }
378+ if (!IsSuperSetOf(levels[idx].localRanks, levels[idx - 1].localRanks)) {
379+ HCCL_WARNING("[PhysicalLevel][Validate] level[%zu] does not contain level[%zu]", idx, idx - 1);
380+ return HCCL_E_NOT_SUPPORT;
381+ }
382+ }
383+ return HCCL_SUCCESS;
384+ }
385+ 
386+ // 不变量8: 身份(netLayer, topoInstId)全域唯一。LevelLess的兜底键正是靠这两项才构成全序,
387+ // 重复则两个Level在比较器下等价, std::sort的相对顺序未指定, 各rank的下标语义会分叉。
388+ // levels规模是个位数(上限PHYSICAL_LEVEL_NUM_LIMIT), 两两比较不需要额外容器
389+ HcclResult ValidateUniqueSource(const std::vector<PhysicalLevelInfo>& levels)
390+ {
391+ for (size_t i = 0; i < levels.size(); i++) {
392+ for (size_t j = i + 1; j < levels.size(); j++) {
393+ if (levels[i].ref.netLayer == levels[j].ref.netLayer
394+ && levels[i].ref.topoInstId == levels[j].ref.topoInstId) {
395+ HCCL_WARNING(
396+ "[PhysicalLevel][Validate] level[%zu] and level[%zu] share the same source: layer[%u] inst[%u]",
397+ i, j, levels[i].ref.netLayer, levels[i].ref.topoInstId);
398+ return HCCL_E_NOT_SUPPORT;
399+ }
400+ }
401+ }
402+ return HCCL_SUCCESS;
403+ }
404+ 
405+} // namespace
406+ 
407+HcclResult ValidatePhysicalLevels(const std::vector<PhysicalLevelInfo>& levels, u32 userRank, u32 userRankSize)
408+{
409+ // 不变量1
410+ if (userRankSize == 0 || userRank >= userRankSize) {
411+ HCCL_WARNING(
412+ "[PhysicalLevel][Validate] invalid rank info, userRank[%u], userRankSize[%u]", userRank, userRankSize);
413+ return HCCL_E_NOT_SUPPORT;
414+ }
415+ if (levels.empty()) {
416+ HCCL_WARNING("[PhysicalLevel][Validate] levels is empty, rank[%u]", userRank);
417+ return HCCL_E_NOT_SUPPORT;
418+ }
419+ 
420+ for (size_t idx = 0; idx < levels.size(); idx++) {
421+ HcclResult ret = ValidateLevel(levels[idx], idx, userRank, userRankSize);
422+ if (ret != HCCL_SUCCESS) {
423+ return ret;
424+ }
425+ }
426+ 
427+ HcclResult ret = ValidateChain(levels);
428+ if (ret == HCCL_SUCCESS) {
429+ ret = ValidateUniqueSource(levels);
430+ }
431+ return ret;
432+}
433+ 
434+} // namespace ops_hccl
@@ -23,6 +23,7 @@
23#include "dev_type.h"23#include "dev_type.h"
24#include "dlsym_common.h"24#include "dlsym_common.h"
25#include "hccl_rank_graph_dl.h"25#include "hccl_rank_graph_dl.h"
26+#include "physical_level.h"
26 27 
27constexpr u32 FACTOR_NUM_TWO = 2;28constexpr u32 FACTOR_NUM_TWO = 2;
28constexpr s32 DEVICE_PER_MODULE = 8;29constexpr s32 DEVICE_PER_MODULE = 8;
@@ -784,6 +785,62 @@ static HcclResult CalcLevel2UbRtp(const HcclComm comm, TopoInfoWithNetLayerDetai
784 return HCCL_SUCCESS;785 return HCCL_SUCCESS;
785}786}
786 787 
788+/**
789+ * 查询本卡是否为POD机型。恒返回HCCL_SUCCESS: 该字段是纯附加信息, 取不到时停在false,
790+ * 现有字段与旧执行路径完全不受影响。
791+ */
792+HcclResult CalcDeviceFormFactor(TopoInfoWithNetLayerDetails* topoInfo)
793+{
794+ CHK_PTR_NULL(topoInfo);
795+ topoInfo->isPod = false;
796+#ifndef AICPU_COMPILE
797+#if !HCCL_SUPPORT_DEV_FORM_FACTOR
798+ // 老CANN的acl_rt.h没有ACL_DEV_ATTR_DEVICE_FORM_FACTOR, 无从查起, 一律按非POD建模。
799+ // 与成功路径同为INFO同前缀: 现场grep一次就能分清走的是哪一条
800+ HCCL_INFO("[Topo][CalcDeviceFormFactor] acl has no device form factor attr, isPod stays false");
801+#else
802+ // aclrtGetDevice返回的是userDevId, 不是logicDevId, 变量名必须如实反映
803+ s32 userDevId = 0;
804+ aclError aclRet = aclrtGetDevice(&userDevId);
805+ if (aclRet != ACL_SUCCESS) {
806+ HCCL_WARNING("[Topo][CalcDeviceFormFactor] get current device failed, ret[%d]. isPod stays false.", aclRet);
807+ return HCCL_SUCCESS;
808+ }
809+ 
810+ // userDevId -> logicDevId。aclrtGetDeviceInfo要的是logicDevId, 少这一步在配了
811+ // ASCEND_RT_VISIBLE_DEVICES的环境上会静默读到另一张卡的形态
812+ s32 logicDevId = 0;
813+ aclRet = aclrtGetLogicDevIdByUserDevId(userDevId, &logicDevId);
814+ if (aclRet != ACL_SUCCESS) {
815+ HCCL_WARNING(
816+ "[Topo][CalcDeviceFormFactor] get logic dev id by user dev id[%d] failed, ret[%d]. "
817+ "isPod stays false.",
818+ userDevId, aclRet);
819+ return HCCL_SUCCESS;
820+ }
821+ 
822+ s64 val = 0;
823+ // quiet: 老驱动不支持该infoType时会稳定失败, 按ERROR打会在正常的老环境上持续刷错误日志
824+ HcclResult ret = hcalrtGetDeviceInfo(static_cast<u32>(logicDevId), ACL_DEV_ATTR_DEVICE_FORM_FACTOR, val, true);
825+ if (ret != HCCL_SUCCESS) {
826+ HCCL_WARNING(
827+ "[Topo][CalcDeviceFormFactor] get device form factor failed, ret[%d], logicDevId[%d]. "
828+ "isPod stays false.",
829+ ret, logicDevId);
830+ return HCCL_SUCCESS;
831+ }
832+ 
833+ // 必须是严格相等的正向判断: ACL将来新增形态时, 未识别的取值必须落到false一侧
834+ topoInfo->isPod = (val == ACL_DEVICE_FORM_FACTOR_POD);
835+ // 原始取值与两个设备号都打出来: 现场据此分辨"确实不是POD"还是"取到了个没见过的形态"
836+ HCCL_INFO(
837+ "[Topo][CalcDeviceFormFactor] userDevId[%d] logicDevId[%d] formFactor[%ld] isPod[%d]", userDevId, logicDevId,
838+ val, static_cast<s32>(topoInfo->isPod));
839+#endif // HCCL_SUPPORT_DEV_FORM_FACTOR
840+#endif // AICPU_COMPILE
841+ return HCCL_SUCCESS;
842+}
843+ 
787HcclResult CalcTopoShape(HcclComm comm, TopoInfoWithNetLayerDetails* topoInfo)844HcclResult CalcTopoShape(HcclComm comm, TopoInfoWithNetLayerDetails* topoInfo)
788{845{
789 CHK_RET(ExtractNetLayerDetails(comm, topoInfo));846 CHK_RET(ExtractNetLayerDetails(comm, topoInfo));
@@ -796,6 +853,11 @@ HcclResult CalcTopoShape(HcclComm comm, TopoInfoWithNetLayerDetails* topoInfo)
796 CHK_RET(CalcTopLevelUboe(comm, topoInfo));853 CHK_RET(CalcTopLevelUboe(comm, topoInfo));
797 CHK_RET(CalcLevel2UbRtp(comm, topoInfo));854 CHK_RET(CalcLevel2UbRtp(comm, topoInfo));
798 CHK_RET(CalcHostDPUOnly(comm, topoInfo));855 CHK_RET(CalcHostDPUOnly(comm, topoInfo));
856+ // 与comm无关, 只查本卡; 恒返回HCCL_SUCCESS, 取不到时停在false
857+ CHK_RET(CalcDeviceFormFactor(topoInfo));
858+ // 放在最后: 现有字段的提取与派生逻辑完全不受影响, 且可复用已提取的netLayerDetails。
859+ // BuildPhysicalLevels恒返回HCCL_SUCCESS, 内部失败一律降级为空视图
860+ CHK_RET(BuildPhysicalLevels(comm, topoInfo));
799 return HCCL_SUCCESS;861 return HCCL_SUCCESS;
800}862}
801 863 
@@ -94,6 +94,12 @@ HcclResult CalcTopoShape(HcclComm comm, TopoInfoWithNetLayerDetails* topoInfo);
94 94 
95HcclResult CalcHostDPUOnly(HcclComm comm, TopoInfoWithNetLayerDetails* topoInfo);95HcclResult CalcHostDPUOnly(HcclComm comm, TopoInfoWithNetLayerDetails* topoInfo);
96 96 
97+/**
98+ * 查询本卡是否为POD机型并写入topoInfo->isPod。不依赖HcclComm, 只查本设备。
99+ * 恒返回HCCL_SUCCESS: 取不到时停在false, 不影响任何现有字段与执行路径。
100+ */
101+HcclResult CalcDeviceFormFactor(TopoInfoWithNetLayerDetails* topoInfo);
102+ 
97HcclResult ExtractNetLayerDetails(const HcclComm comm, TopoInfoWithNetLayerDetails* topoInfo);103HcclResult ExtractNetLayerDetails(const HcclComm comm, TopoInfoWithNetLayerDetails* topoInfo);
98 104 
99HcclResult ExtractTopoDetails(HcclComm comm, TopoInfoWithNetLayerDetails* topoInfo);105HcclResult ExtractTopoDetails(HcclComm comm, TopoInfoWithNetLayerDetails* topoInfo);
@@ -0,0 +1,343 @@
1+/**
2+ * Copyright (c) 2025 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 "topo_match_base_v2.h"
12+#include <algorithm>
13+ 
14+namespace ops_hccl {
15+ 
16+TopoMatchBaseV2::TopoMatchBaseV2() {}
17+TopoMatchBaseV2::~TopoMatchBaseV2() {}
18+ 
19+u32 CalcGcdByPair(u32 a, u32 b)
20+{
21+ if (a == 0 || b == 0) {
22+ return 1;
23+ }
24+ while (b != 0) {
25+ u32 r = a % b;
26+ a = b;
27+ b = r;
28+ }
29+ HCCL_DEBUG("[CalcGcdByPair] a[%u] b[%u], gcd[%u]", a, b, a);
30+ return a;
31+}
32+ 
33+u32 CalcGcd(const std::vector<u32>& nums)
34+{
35+ if (nums.empty()) {
36+ return 1;
37+ }
38+ u32 result = nums[0];
39+ for (size_t i = 1; i < nums.size(); i++) {
40+ result = CalcGcdByPair(result, nums[i]);
41+ if (result == 1) {
42+ return 1;
43+ }
44+ }
45+ HCCL_DEBUG("[CalcGcd] size[%u], gcd[%u]", static_cast<u32>(nums.size()), result);
46+ return result;
47+}
48+ 
49+int32_t FindHighestEffectiveLevel(const std::vector<PhysicalLevelInfo>& physicalLevels)
50+{
51+ for (int32_t i = static_cast<int32_t>(physicalLevels.size()) - 1; i >= 0; i--) {
52+ if (physicalLevels[i].hasTopoInst) {
53+ return i;
54+ }
55+ }
56+ return INVALID_PHYSICAL_LEVEL_IDX;
57+}
58+ 
59+bool IsInstListSymmetric(const std::vector<uint32_t>& instList)
60+{
61+ if (instList.empty()) {
62+ HCCL_WARNING("[TopoMatchBase] instList is empty!");
63+ return true;
64+ }
65+ for (size_t i = 0; i < instList.size(); i++) {
66+ if (instList[i] != instList[0]) {
67+ return false;
68+ }
69+ }
70+ return true;
71+}
72+ 
73+std::vector<u32> BuildRepresentativeGroup(u32 step, u32 count, u32 offset)
74+{
75+ std::vector<u32> group;
76+ group.reserve(count);
77+ for (u32 i = 0; i < count; i++) {
78+ group.push_back(offset + i * step);
79+ }
80+ return group;
81+}
82+ 
83+HcclResult ValidateGroup(const std::vector<u32>& group, u32 dim, u32 myRank, const std::string& levelName)
84+{
85+ if (group.size() != dim) {
86+ HCCL_ERROR(
87+ "[TopoMatchBase] Rank [%u], %s group size[%zu] != dim[%u].", myRank, levelName.c_str(), group.size(), dim);
88+ return HcclResult::HCCL_E_INTERNAL;
89+ }
90+ if (std::find(group.begin(), group.end(), myRank) == group.end()) {
91+ HCCL_ERROR("[TopoMatchBase] Rank [%u], %s group does not contain myRank.", myRank, levelName.c_str());
92+ return HcclResult::HCCL_E_INTERNAL;
93+ }
94+ return HcclResult::HCCL_SUCCESS;
95+}
96+ 
97+// 判断 protocols 是否含 UBG 链路(AIV 引擎需排除这种层)
98+static bool HasUbgLink(const std::vector<CommProtocol>& protocols)
99+{
100+#if CANN_VERSION_NUM >= CANN_VERSION(9, 2, 0)
101+ for (CommProtocol p : protocols) {
102+ if (p == COMM_PROTOCOL_UBG) {
103+ return true;
104+ }
105+ }
106+#endif
107+ return false;
108+}
109+ 
110+// 按引擎过滤收集 hasTopoInst 的物理层序号:非 hostdpu 排除 HOST 层,AIV 排除含 UBG 链路的层
111+std::vector<u32> CollectEffectiveIndices(const std::vector<PhysicalLevelInfo>& physicalLevels, OpExecuteConfig engine)
112+{
113+ bool isHostdpu = (engine == OpExecuteConfig::HOSTCPU);
114+ bool isAiv = (engine == OpExecuteConfig::AIV);
115+ std::vector<u32> effIdx;
116+ for (u32 i = 0; i < physicalLevels.size(); i++) {
117+ if (!physicalLevels[i].hasTopoInst) {
118+ HCCL_INFO("[CollectEffectiveIndices] skip level[%u]: no TopoInstance.", i);
119+ continue;
120+ }
121+ if (!isHostdpu && physicalLevels[i].locType == EndpointLocType::ENDPOINT_LOC_TYPE_HOST) {
122+ HCCL_INFO("[CollectEffectiveIndices] skip level[%u]: HOST locType (non-hostdpu excludes HOST).", i);
123+ continue;
124+ }
125+ if (isAiv && HasUbgLink(physicalLevels[i].protocols)) {
126+ HCCL_INFO("[CollectEffectiveIndices] skip level[%u]: UBG protocol (AIV excludes UBG).", i);
127+ continue;
128+ }
129+ effIdx.push_back(i);
130+ }
131+ return effIdx;
132+}
133+ 
134+// 判断算法是否属于 Mesh 类
135+bool IsMeshAlgo(AlgoType algo) { return MESH_ALGO_TYPES.count(algo) > 0; }
136+ 
137+// 判断算法是否属于 MeshConcur 类(触发 CLOS 双层规则)
138+bool IsMeshConcurAlgo(AlgoType algo) { return MESH_CONCUR_ALGO_TYPES.count(algo) > 0; }
139+ 
140+// 段内匹配:算法 [algoLow..algoHigh] ↔ 物理 [physLow..physHigh],低层一一 + 最高层压缩多余
141+static void MatchLayerIdxBySegment(u32 algoLow, u32 algoHigh, u32 physLow, u32 physHigh, std::vector<u32>& pIndices)
142+{
143+ if (algoLow > algoHigh) {
144+ return;
145+ }
146+ u32 algoCount = algoHigh - algoLow + 1;
147+ for (u32 k = 0; k + 1 < algoCount; k++) {
148+ pIndices[algoLow + k] = physLow + k;
149+ }
150+ pIndices[algoHigh] = physHigh;
151+}
152+ 
153+// hostdpu 强约束:最高算法层锚定 HOST 且 localRanks==userRankSize 的物理层(从高到低找首个),
154+// 并校验 HOST 锚点以下物理层数 >= 剩余待匹配算法层数;找不到或不满足则 not support
155+static HcclResult AnchorHostDpu(
156+ const std::vector<PhysicalLevelInfo>& physicalLevels, const std::vector<u32>& effIdx, u32 userRankSize, u32 topAlgo,
157+ u32& topPhysPos, std::set<u32>& anchoredPhys, std::map<u32, u32>& anchors)
158+{
159+ bool found = false;
160+ for (int32_t k = static_cast<int32_t>(effIdx.size()) - 1; k >= 0; k--) {
161+ const PhysicalLevelInfo& lvl = physicalLevels[effIdx[k]];
162+ if (lvl.locType == EndpointLocType::ENDPOINT_LOC_TYPE_HOST && lvl.localRanks.size() == userRankSize) {
163+ anchors[topAlgo] = static_cast<u32>(k);
164+ anchoredPhys.insert(static_cast<u32>(k));
165+ topPhysPos = static_cast<u32>(k);
166+ HCCL_INFO(
167+ "[FindAnchors] hostdpu: algo level[%u] anchored to phys[%u] (HOST, localRankSize==%u).", topAlgo,
168+ effIdx[k], userRankSize);
169+ found = true;
170+ break;
171+ }
172+ }
173+ if (!found) {
174+ HCCL_INFO(
175+ "[FindAnchors] hostdpu but no HOST layer with localRanks==userRankSize[%u], not support.", userRankSize);
176+ return HcclResult::HCCL_E_NOT_SUPPORT;
177+ }
178+ // HOST 锚点以下的物理层数(= topPhysPos)须 >= 剩余待匹配的算法层数(= topAlgo),否则低层无足够物理层
179+ if (topPhysPos < topAlgo) {
180+ HCCL_INFO(
181+ "[FindAnchors] hostdpu phys layers below host[%u] < remaining algo levels[%u], not support.", topPhysPos,
182+ topAlgo);
183+ return HcclResult::HCCL_E_NOT_SUPPORT;
184+ }
185+ return HcclResult::HCCL_SUCCESS;
186+}
187+ 
188+// Mesh 锚点:算法层从低到高遍历,优先匹配 COMM_TOPO_1DMESH 物理层,不可重复锚定;
189+// hostdpu 已锚定的最高层跳过;MeshConcur 未匹配到则 not support
190+static HcclResult AnchorMeshLevels(
191+ const std::vector<PhysicalLevelInfo>& physicalLevels, const std::vector<u32>& effIdx,
192+ const std::vector<AlgoType>& algoTypes, u32 topAlgo, u32 topPhysPos, std::set<u32>& anchoredPhys,
193+ std::map<u32, u32>& anchors)
194+{
195+ for (u32 i = 0; i < algoTypes.size(); i++) {
196+ if (anchors.count(i) > 0) {
197+ continue;
198+ }
199+ if (!IsMeshAlgo(algoTypes[i])) {
200+ continue;
201+ }
202+ // 为上层算法层(i+1..topAlgo)留足物理位:candidateHigh = topPhysPos - (topAlgo - i)
203+ u32 candidateHigh = topPhysPos - (topAlgo - i);
204+ bool found = false;
205+ for (u32 k = i; k <= candidateHigh; k++) {
206+ if (anchoredPhys.count(k) > 0) {
207+ continue;
208+ }
209+ if (physicalLevels[effIdx[k]].topoType == COMM_TOPO_1DMESH) {
210+ anchors[i] = k;
211+ anchoredPhys.insert(k);
212+ HCCL_INFO("[FindAnchors] mesh: algo level[%u] anchored to phys[%u] (1DMESH).", i, effIdx[k]);
213+ found = true;
214+ break;
215+ }
216+ }
217+ if (!found && IsMeshConcurAlgo(algoTypes[i])) {
218+ HCCL_INFO("[FindAnchors] algo[%u] MeshConcur but no Mesh layer, not support.", i);
219+ return HcclResult::HCCL_E_NOT_SUPPORT;
220+ }
221+ }
222+ return HcclResult::HCCL_SUCCESS;
223+}
224+ 
225+// 锚点匹配:hostdpu 强约束最高层选 HOST(优先级高于 Mesh)+ Mesh 层优先匹配 COMM_TOPO_1DMESH
226+HcclResult FindAnchors(
227+ const std::vector<PhysicalLevelInfo>& physicalLevels, const std::vector<u32>& effIdx,
228+ const std::vector<AlgoType>& algoTypes, OpExecuteConfig engine, u32 userRankSize, std::map<u32, u32>& anchors)
229+{
230+ std::set<u32> anchoredPhys;
231+ u32 topAlgo = static_cast<u32>(algoTypes.size()) - 1;
232+ // 最高算法层对应的物理 effIdx 位置:非 hostdpu 由尾段取 effIdx.back();hostdpu 取 HOST 锚点
233+ u32 topPhysPos = static_cast<u32>(effIdx.size()) - 1;
234+ if (engine == OpExecuteConfig::HOSTCPU) {
235+ CHK_RET(AnchorHostDpu(physicalLevels, effIdx, userRankSize, topAlgo, topPhysPos, anchoredPhys, anchors));
236+ }
237+ CHK_RET(AnchorMeshLevels(physicalLevels, effIdx, algoTypes, topAlgo, topPhysPos, anchoredPhys, anchors));
238+ return HcclResult::HCCL_SUCCESS;
239+}
240+ 
241+// 分段压缩:按锚点将算法层与物理层分段,每段低层一一 + 最高层压缩多余物理层
242+HcclResult ResolveSegmentMapping(
243+ const std::vector<u32>& effIdx, const std::vector<AlgoType>& algoTypes, const std::map<u32, u32>& anchors,
244+ std::vector<u32>& pIndices)
245+{
246+ pIndices.resize(algoTypes.size(), INVALID_UINT);
247+ u32 algoStart = 0;
248+ u32 physStart = 0;
249+ for (const auto& [anchorAlgo, anchorPhys] : anchors) {
250+ // 前段存在当且仅当锚点之前同时有算法层与物理层;anchorAlgo==algoStart 时 anchorAlgo-1 会 u32 下溢,须跳过
251+ if (anchorAlgo > algoStart && anchorPhys > physStart) {
252+ MatchLayerIdxBySegment(algoStart, anchorAlgo - 1, physStart, anchorPhys - 1, pIndices);
253+ }
254+ pIndices[anchorAlgo] = anchorPhys;
255+ algoStart = anchorAlgo + 1;
256+ physStart = anchorPhys + 1;
257+ }
258+ MatchLayerIdxBySegment(algoStart, algoTypes.size() - 1, physStart, effIdx.size() - 1, pIndices);
259+ return HcclResult::HCCL_SUCCESS;
260+}
261+ 
262+// 在 meshEffPos 之上(更高 index)找首个 localRanks 包含 mesh 层 localRanks 的物理层;找不到返回
263+// INVALID_PHYSICAL_LEVEL_IDX
264+int32_t FindUpperEncompassingLevel(
265+ const std::vector<PhysicalLevelInfo>& physicalLevels, const std::vector<u32>& effIdx, u32 meshEffPos)
266+{
267+ const auto& meshRanks = physicalLevels[effIdx[meshEffPos]].localRanks;
268+ for (u32 k = meshEffPos + 1; k < effIdx.size(); k++) {
269+ const auto& upperRanks = physicalLevels[effIdx[k]].localRanks;
270+ if (std::includes(upperRanks.begin(), upperRanks.end(), meshRanks.begin(), meshRanks.end())) {
271+ return static_cast<int32_t>(k);
272+ }
273+ }
274+ return INVALID_PHYSICAL_LEVEL_IDX;
275+}
276+ 
277+// 引擎过滤 + 锚点匹配 + 分段,得 effIdx 与 pIndices;校验最高层 localRanks==userRankSize
278+HcclResult ResolveMapping(
279+ const std::vector<PhysicalLevelInfo>& physicalLevels, const AlgAttrs& profile, u32 userRankSize,
280+ std::vector<u32>& effIdx, std::vector<u32>& pIndices)
281+{
282+ effIdx = CollectEffectiveIndices(physicalLevels, profile.engine);
283+ u32 algoLevelNum = profile.algoTypes.size();
284+ if (effIdx.size() < algoLevelNum) {
285+ HCCL_INFO("[ResolveMapping] valid level num[%zu] < algoLevelNum[%u].", effIdx.size(), algoLevelNum);
286+ return HcclResult::HCCL_E_NOT_SUPPORT;
287+ }
288+ std::map<u32, u32> anchors;
289+ // 锚点匹配:含 MeshConcur 的 1DMESH 校验与 hostdpu 强约束,须无条件执行(1:1 时也需校验底层 1DMESH)
290+ CHK_RET(FindAnchors(physicalLevels, effIdx, profile.algoTypes, profile.engine, userRankSize, anchors));
291+ CHK_RET(ResolveSegmentMapping(effIdx, profile.algoTypes, anchors, pIndices));
292+ // 最高算法层 localRanks 必须等于 userRankSize
293+ u32 topPhys = effIdx[pIndices[algoLevelNum - 1]];
294+ if (physicalLevels[topPhys].localRanks.size() != userRankSize) {
295+ HCCL_INFO(
296+ "[ResolveMapping] top layer localRanks[%zu] != userRankSize[%u].",
297+ physicalLevels[topPhys].localRanks.size(), userRankSize);
298+ return HcclResult::HCCL_E_NOT_SUPPORT;
299+ }
300+ return HcclResult::HCCL_SUCCESS;
301+}
302+ 
303+// 填充 physicalIdxForAlgoLevels(二级):MeshConcur 层记 {Mesh层, 上层超集层},普通层记 {该层}
304+HcclResult FillPhysicalIdxForAlgoLevels(
305+ const std::vector<PhysicalLevelInfo>& physicalLevels, const std::vector<u32>& effIdx,
306+ const std::vector<u32>& pIndices, const std::vector<AlgoType>& algoTypes,
307+ std::vector<std::vector<PhysicalLevelIndex>>& physicalIdxForAlgoLevels)
308+{
309+ physicalIdxForAlgoLevels.resize(algoTypes.size());
310+ for (u32 i = 0; i < algoTypes.size(); i++) {
311+ u32 physIdx = effIdx[pIndices[i]];
312+ if (IsMeshConcurAlgo(algoTypes[i])) {
313+ int32_t upperPos = FindUpperEncompassingLevel(physicalLevels, effIdx, pIndices[i]);
314+ if (upperPos == INVALID_PHYSICAL_LEVEL_IDX) {
315+ HCCL_INFO("[FillPhysicalIdx] level[%u] MeshConcur no upper encompassing layer, not support.", i);
316+ return HcclResult::HCCL_E_NOT_SUPPORT;
317+ }
318+ physicalIdxForAlgoLevels[i]
319+ = {static_cast<PhysicalLevelIndex>(physIdx), static_cast<PhysicalLevelIndex>(effIdx[upperPos])};
320+ } else {
321+ physicalIdxForAlgoLevels[i] = {static_cast<PhysicalLevelIndex>(physIdx)};
322+ }
323+ }
324+ return HcclResult::HCCL_SUCCESS;
325+}
326+ 
327+std::string FormatPhysicalIdxForAlgoLevels(const std::vector<std::vector<PhysicalLevelIndex>>& physicalIdxForAlgoLevels)
328+{
329+ std::string idxStr;
330+ for (size_t i = 0; i < physicalIdxForAlgoLevels.size(); i++) {
331+ idxStr += "{";
332+ for (size_t j = 0; j < physicalIdxForAlgoLevels[i].size(); j++) {
333+ idxStr += std::to_string(static_cast<u32>(physicalIdxForAlgoLevels[i][j]));
334+ if (j + 1 < physicalIdxForAlgoLevels[i].size()) {
335+ idxStr += ",";
336+ }
337+ }
338+ idxStr += "} ";
339+ }
340+ return idxStr;
341+}
342+ 
343+} // namespace ops_hccl
@@ -0,0 +1,103 @@
1+/**
2+ * Copyright (c) 2025 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+#ifndef TOPO_MATCH_BASE_V2
12+#define TOPO_MATCH_BASE_V2
13+ 
14+#include "topo_match_base.h"
15+#include "alg_parse.h"
16+#include "alg_attrs.h"
17+#include <set>
18+#include <map>
19+#include <vector>
20+#include <string>
21+#include <algorithm>
22+ 
23+namespace ops_hccl {
24+ 
25+// topo match 相关常量
26+constexpr int32_t INVALID_PHYSICAL_LEVEL_IDX = -1;
27+constexpr u32 ALGO_LEVEL_NUM_TWO = 2;
28+constexpr u32 ALGO_LEVEL_NUM_THREE = 3;
29+constexpr u32 CONCURRENT_SUBGROUP_NUM = 2;
30+ 
31+// 辗转相除求两数最大公约数;a 或 b 为 0 时返回 1 避免退化
32+u32 CalcGcdByPair(u32 a, u32 b);
33+ 
34+// 对一组数逐对归约求最大公约数,result==1 时早停
35+u32 CalcGcd(const std::vector<u32>& nums);
36+ 
37+// 从高到低找首个 hasTopoInst 的物理层序号;不存在返回 INVALID_PHYSICAL_LEVEL_IDX
38+int32_t FindHighestEffectiveLevel(const std::vector<PhysicalLevelInfo>& physicalLevels);
39+ 
40+// instList 各元素是否全等(对称判定)
41+bool IsInstListSymmetric(const std::vector<uint32_t>& instList);
42+ 
43+// 构造跨层代表 rank:count 个,从 offset 起、按 step 步长(offset 取 myRank 在本层的偏移,保证 myRank 命中)
44+std::vector<u32> BuildRepresentativeGroup(u32 step, u32 count, u32 offset);
45+ 
46+// 校验单个 group:规模等于 dim 且包含 myRank;失败打 ERROR 并返回 HCCL_E_INTERNAL
47+HcclResult ValidateGroup(const std::vector<u32>& group, u32 dim, u32 myRank, const std::string& levelName);
48+ 
49+// 引擎过滤:非 hostdpu 排除 HOST 层,AIV 排除含 UBG 链路的层
50+std::vector<u32> CollectEffectiveIndices(const std::vector<PhysicalLevelInfo>& physicalLevels, OpExecuteConfig engine);
51+ 
52+// 判断算法是否属于 Mesh 类
53+bool IsMeshAlgo(AlgoType algo);
54+ 
55+// 判断算法是否属于 MeshConcur 类(触发 CLOS 双层规则)
56+bool IsMeshConcurAlgo(AlgoType algo);
57+ 
58+// 锚点匹配:hostdpu 强约束最高算法层锚定 HOST 且 localRanks==userRankSize 的物理层;Mesh 算法优先匹配 COMM_TOPO_1DMESH
59+// 物理层(不可重复锚定)
60+HcclResult FindAnchors(
61+ const std::vector<PhysicalLevelInfo>& physicalLevels, const std::vector<u32>& effIdx,
62+ const std::vector<AlgoType>& algoTypes, OpExecuteConfig engine, u32 userRankSize, std::map<u32, u32>& anchors);
63+ 
64+// 分段压缩得各算法层对应的物理层 effIdx position
65+HcclResult ResolveSegmentMapping(
66+ const std::vector<u32>& effIdx, const std::vector<AlgoType>& algoTypes, const std::map<u32, u32>& anchors,
67+ std::vector<u32>& pIndices);
68+ 
69+// 引擎过滤 + 锚点匹配 + 分段,得 effIdx 与 pIndices;校验最高层 localRanks==userRankSize
70+HcclResult ResolveMapping(
71+ const std::vector<PhysicalLevelInfo>& physicalLevels, const AlgAttrs& profile, u32 userRankSize,
72+ std::vector<u32>& effIdx, std::vector<u32>& pIndices);
73+ 
74+// 在 meshEffPos 之上找首个 localRanks 包含 mesh 层 localRanks 的物理层
75+int32_t FindUpperEncompassingLevel(
76+ const std::vector<PhysicalLevelInfo>& physicalLevels, const std::vector<u32>& effIdx, u32 meshEffPos);
77+ 
78+// 填充 physicalIdxForAlgoLevels(二级):MeshConcur 层记 {Mesh层, 上层超集层},普通层记 {该层}
79+HcclResult FillPhysicalIdxForAlgoLevels(
80+ const std::vector<PhysicalLevelInfo>& physicalLevels, const std::vector<u32>& effIdx,
81+ const std::vector<u32>& pIndices, const std::vector<AlgoType>& algoTypes,
82+ std::vector<std::vector<PhysicalLevelIndex>>& physicalIdxForAlgoLevels);
83+ 
84+// 将 physicalIdxForAlgoLevels 拼成日志字符串,每层用 {} 包裹,层内多值逗号分隔,如 "{0,1} {2} {3}"
85+std::string
86+FormatPhysicalIdxForAlgoLevels(const std::vector<std::vector<PhysicalLevelIndex>>& physicalIdxForAlgoLevels);
87+ 
88+// V2 基类:MatchTopo 增加 AlgAttrs 参数
89+class TopoMatchBaseV2 {
90+public:
91+ explicit TopoMatchBaseV2();
92+ virtual ~TopoMatchBaseV2();
93+ 
94+ virtual std::string Describe() const = 0;
95+ 
96+ virtual HcclResult MatchTopo(
97+ TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo, const AlgAttrs& profile)
98+ = 0;
99+};
100+ 
101+} // namespace ops_hccl
102+ 
103+#endif // !TOPO_MATCH_BASE_V2
@@ -0,0 +1,62 @@
1+/**
2+ * Copyright (c) 2025 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 "topo_match_concurrent_v2.h"
12+#include "log.h"
13+#include "hccl_common.h"
14+ 
15+namespace ops_hccl {
16+ 
17+TopoMatchConcurrentV2::TopoMatchConcurrentV2() {}
18+ 
19+TopoMatchConcurrentV2::~TopoMatchConcurrentV2() {}
20+ 
21+HcclResult TopoMatchConcurrentV2::MatchTopo(
22+ TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo, const AlgAttrs& profile)
23+{
24+ u32 myRank = topoInfo->userRank;
25+ const auto& physicalLevels = topoInfo->physicalLevels;
26+ if (physicalLevels.empty()) {
27+ HCCL_ERROR("[TopoMatchConcurrentV2] Rank [%u], physicalLevels is empty.", myRank);
28+ return HcclResult::HCCL_E_INTERNAL;
29+ }
30+ 
31+ // 引擎过滤后收集有效层
32+ std::vector<u32> effIdx = CollectEffectiveIndices(physicalLevels, profile.engine);
33+ u32 effNum = effIdx.size();
34+ CHK_PRT_RET(
35+ effNum == 0 || effNum > ALGO_LEVEL_NUM_TWO,
36+ HCCL_INFO("[TopoMatchConcurrentV2] Rank [%u], level num[%u] not support.", myRank, effNum),
37+ HcclResult::HCCL_E_NOT_SUPPORT);
38+ CHK_PRT_RET(
39+ (topoInfo->userRankSize == 0), HCCL_ERROR("[TopoMatchConcurrentV2] Rank [%d], rankSize is 0.", myRank),
40+ HcclResult::HCCL_E_INTERNAL);
41+ 
42+ // infos 沿用原 Concurrent:两组同 rank(mesh 组 + clos 组并发),不依赖 physicalLevels 内容
43+ std::vector<u32> rankIds;
44+ rankIds.reserve(topoInfo->userRankSize);
45+ for (u32 rankId = 0; rankId < topoInfo->userRankSize; rankId++) {
46+ rankIds.push_back(rankId);
47+ }
48+ algHierarchyInfo.infos.resize(1);
49+ algHierarchyInfo.infos[0].resize(CONCURRENT_SUBGROUP_NUM);
50+ algHierarchyInfo.infos[0][0] = rankIds;
51+ algHierarchyInfo.infos[0][1] = rankIds;
52+ 
53+ // physicalIdx 指向最高有效层
54+ u32 highestIdx = effIdx.back();
55+ algHierarchyInfo.physicalIdxForAlgoLevels = {{static_cast<PhysicalLevelIndex>(highestIdx)}};
56+ HCCL_INFO(
57+ "[TopoMatchConcurrentV2] Rank [%u], rankSize[%u], physicalIdxForAlgoLevels: [%s].", myRank,
58+ topoInfo->userRankSize, FormatPhysicalIdxForAlgoLevels(algHierarchyInfo.physicalIdxForAlgoLevels).c_str());
59+ return HcclResult::HCCL_SUCCESS;
60+}
61+ 
62+} // namespace ops_hccl
@@ -0,0 +1,35 @@
1+/**
2+ * Copyright (c) 2025 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+#ifndef TOPO_MATCH_CONCURRENT_V2
12+#define TOPO_MATCH_CONCURRENT_V2
13+ 
14+#include "topo_match_base_v2.h"
15+ 
16+namespace ops_hccl {
17+ 
18+class TopoMatchConcurrentV2 : public TopoMatchBaseV2 {
19+public:
20+ explicit TopoMatchConcurrentV2();
21+ ~TopoMatchConcurrentV2() override;
22+ 
23+ std::string Describe() const override
24+ {
25+ return "Topo Match for Concurrent Algorithm V2 (supports 950/960 out-place devices).";
26+ }
27+ 
28+ HcclResult MatchTopo(
29+ TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo,
30+ const AlgAttrs& profile) override;
31+};
32+ 
33+} // namespace ops_hccl
34+ 
35+#endif // !TOPO_MATCH_CONCURRENT_V2
@@ -0,0 +1,74 @@
1+/**
2+ * Copyright (c) 2025 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 "topo_match_one_level.h"
12+#include <algorithm>
13+#include "log.h"
14+ 
15+namespace ops_hccl {
16+ 
17+TopoMatchOneLevel::TopoMatchOneLevel() {}
18+ 
19+TopoMatchOneLevel::~TopoMatchOneLevel() {}
20+ 
21+namespace {
22+ // 从 effIdx 中找 localRanks==userRankSize 的最低有效层;hostdpu 额外要求 locType==HOST
23+ u32 PickFullLocalRanksLayer(
24+ const std::vector<PhysicalLevelInfo>& physicalLevels, const std::vector<u32>& effIdx, u32 userRankSize,
25+ bool requireHost)
26+ {
27+ for (u32 idx : effIdx) {
28+ if (physicalLevels[idx].localRanks.size() != userRankSize) {
29+ continue;
30+ }
31+ if (requireHost && physicalLevels[idx].locType != EndpointLocType::ENDPOINT_LOC_TYPE_HOST) {
32+ continue;
33+ }
34+ return idx;
35+ }
36+ return INVALID_UINT;
37+ }
38+} // namespace
39+ 
40+HcclResult TopoMatchOneLevel::MatchTopo(
41+ TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo, const AlgAttrs& profile)
42+{
43+ const auto& physicalLevels = topoInfo->physicalLevels;
44+ if (physicalLevels.empty() || topoInfo->userRankSize == 0) {
45+ HCCL_ERROR("[TopoMatchOneLevel] Rank [%u], physicalLevels empty or userRankSize 0.", topoInfo->userRank);
46+ return HcclResult::HCCL_E_INTERNAL;
47+ }
48+ 
49+ std::vector<u32> effIdx = CollectEffectiveIndices(physicalLevels, profile.engine);
50+ if (effIdx.empty()) {
51+ HCCL_INFO("[TopoMatchOneLevel] Rank [%u], no valid layer after engine filter.", topoInfo->userRank);
52+ return HcclResult::HCCL_E_NOT_SUPPORT;
53+ }
54+ 
55+ bool requireHost = (profile.engine == OpExecuteConfig::HOSTCPU);
56+ u32 picked = PickFullLocalRanksLayer(physicalLevels, effIdx, topoInfo->userRankSize, requireHost);
57+ if (picked == INVALID_UINT) {
58+ HCCL_INFO(
59+ "[TopoMatchOneLevel] Rank [%u], no layer with localRanks == userRankSize[%u] (requireHost[%d]).",
60+ topoInfo->userRank, topoInfo->userRankSize, static_cast<int32_t>(requireHost));
61+ return HcclResult::HCCL_E_NOT_SUPPORT;
62+ }
63+ 
64+ algHierarchyInfo.infos.resize(1);
65+ algHierarchyInfo.infos[0].resize(1);
66+ algHierarchyInfo.infos[0][0] = physicalLevels[picked].localRanks;
67+ algHierarchyInfo.physicalIdxForAlgoLevels = {{static_cast<PhysicalLevelIndex>(picked)}};
68+ HCCL_INFO(
69+ "[TopoMatchOneLevel] Rank [%u], userRankSize [%u], physicalIdxForAlgoLevels: [%s].", topoInfo->userRank,
70+ topoInfo->userRankSize, FormatPhysicalIdxForAlgoLevels(algHierarchyInfo.physicalIdxForAlgoLevels).c_str());
71+ return HcclResult::HCCL_SUCCESS;
72+}
73+ 
74+} // namespace ops_hccl
@@ -0,0 +1,32 @@
1+/**
2+ * Copyright (c) 2025 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+#ifndef TOPO_MATCH_ONE_LEVEL
12+#define TOPO_MATCH_ONE_LEVEL
13+ 
14+#include "topo_match_base_v2.h"
15+ 
16+namespace ops_hccl {
17+ 
18+class TopoMatchOneLevel : public TopoMatchBaseV2 {
19+public:
20+ explicit TopoMatchOneLevel();
21+ ~TopoMatchOneLevel() override;
22+ 
23+ std::string Describe() const override { return "Topo Match for One Level Algorithm."; }
24+ 
25+ HcclResult MatchTopo(
26+ TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo,
27+ const AlgAttrs& profile) override;
28+};
29+ 
30+} // namespace ops_hccl
31+ 
32+#endif // !TOPO_MATCH_ONE_LEVEL
@@ -0,0 +1,124 @@
1+/**
2+ * Copyright (c) 2025 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 "topo_match_three_level.h"
12+#include <algorithm>
13+#include "log.h"
14+ 
15+namespace ops_hccl {
16+ 
17+namespace {
18+ // 校验 level 对称并取维度:GLOBAL 看 instList 是否全等;LOCAL 视为对称
19+ HcclResult ValidateLevelAndCalcDim(
20+ u32 levelIdx, const std::vector<PhysicalLevelInfo>& physicalLevels, bool& symmetricOut, u32& dim)
21+ {
22+ const PhysicalLevelInfo& level = physicalLevels[levelIdx];
23+ if (level.view == PhysicalLevelView::LOCAL) {
24+ // LOCAL 无全局 instList,对称性由其上级 netLayer 层判定
25+ dim = static_cast<u32>(level.localRanks.size());
26+ symmetricOut = true;
27+ return HcclResult::HCCL_SUCCESS;
28+ }
29+ if (!IsInstListSymmetric(level.instSizeListByLayer)) {
30+ symmetricOut = false;
31+ return HcclResult::HCCL_SUCCESS;
32+ }
33+ symmetricOut = true;
34+ dim = static_cast<u32>(level.localRanks.size());
35+ return HcclResult::HCCL_SUCCESS;
36+ }
37+ 
38+ // ThreeLevel 不支持非对称:p_0/p_1 任一非对称即 not support;维度 d0/d1/d2
39+ HcclResult CalcDimsAndCheckSymmetry(
40+ const std::vector<PhysicalLevelInfo>& physicalLevels, u32 phys0, u32 phys1, u32 userRankSize, u32 myRank,
41+ u32& d0, u32& d1, u32& d2)
42+ {
43+ u32 level1TotalSize = 0;
44+ bool sym0 = false;
45+ bool sym1 = false;
46+ CHK_RET(ValidateLevelAndCalcDim(phys0, physicalLevels, sym0, d0));
47+ CHK_RET(ValidateLevelAndCalcDim(phys1, physicalLevels, sym1, level1TotalSize));
48+ if (!sym0 || !sym1) {
49+ HCCL_INFO(
50+ "[TopoMatchThreeLevel] Rank [%u], asymmetric detected (sym0[%d] sym1[%d]), not support.", myRank,
51+ static_cast<int32_t>(sym0), static_cast<int32_t>(sym1));
52+ return HcclResult::HCCL_E_NOT_SUPPORT;
53+ }
54+ if (d0 == 0 || level1TotalSize == 0 || level1TotalSize % d0 != 0) {
55+ HCCL_INFO(
56+ "[TopoMatchThreeLevel] Rank [%u], level1TotalSize[%u] not divisible by d0[%u].", myRank,
57+ level1TotalSize, d0);
58+ return HcclResult::HCCL_E_NOT_SUPPORT;
59+ }
60+ d1 = level1TotalSize / d0;
61+ if (userRankSize % d0 != 0 || (userRankSize / d0) % d1 != 0) {
62+ HCCL_INFO(
63+ "[TopoMatchThreeLevel] Rank [%u], userRankSize[%u] not divisible by d0[%u]*d1[%u].", myRank,
64+ userRankSize, d0, d1);
65+ return HcclResult::HCCL_E_NOT_SUPPORT;
66+ }
67+ d2 = userRankSize / d0 / d1;
68+ return HcclResult::HCCL_SUCCESS;
69+ }
70+} // namespace
71+ 
72+TopoMatchThreeLevel::TopoMatchThreeLevel() {}
73+TopoMatchThreeLevel::~TopoMatchThreeLevel() {}
74+ 
75+HcclResult TopoMatchThreeLevel::MatchTopo(
76+ TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo, const AlgAttrs& profile)
77+{
78+ const auto& physicalLevels = topoInfo->physicalLevels;
79+ u32 myRank = topoInfo->userRank;
80+ u32 userRankSize = topoInfo->userRankSize;
81+ if (physicalLevels.empty() || userRankSize == 0 || profile.algoTypes.size() != ALGO_LEVEL_NUM_THREE) {
82+ HCCL_ERROR("[TopoMatchThreeLevel] Rank [%u], invalid input.", myRank);
83+ return HcclResult::HCCL_E_INTERNAL;
84+ }
85+ 
86+ // 引擎过滤 + 锚点匹配 + 分段 + 最高层校验
87+ std::vector<u32> effIdx;
88+ std::vector<u32> pIndices;
89+ CHK_RET(ResolveMapping(physicalLevels, profile, userRankSize, effIdx, pIndices));
90+ u32 phys0 = effIdx[pIndices[0]];
91+ u32 phys1 = effIdx[pIndices[1]];
92+ 
93+ // 非对称判定 + 维度计算(ThreeLevel 不支持非对称)
94+ u32 d0 = 0;
95+ u32 d1 = 0;
96+ u32 d2 = 0;
97+ CHK_RET(CalcDimsAndCheckSymmetry(physicalLevels, phys0, phys1, userRankSize, myRank, d0, d1, d2));
98+ 
99+ // 构造 infos;level1 代表环须落在 myRank 所在 level1 instance 内,故 offset 取 instance 基址 + 层内偏移
100+ std::vector<u32> group0 = physicalLevels[phys0].localRanks;
101+ u32 level1Base = (myRank / (d0 * d1)) * (d0 * d1);
102+ std::vector<u32> group1 = BuildRepresentativeGroup(d0, d1, level1Base + myRank % d0);
103+ std::vector<u32> group2 = BuildRepresentativeGroup(d0 * d1, d2, myRank % (d0 * d1));
104+ CHK_RET(ValidateGroup(group0, d0, myRank, "level0"));
105+ CHK_RET(ValidateGroup(group1, d1, myRank, "level1"));
106+ CHK_RET(ValidateGroup(group2, d2, myRank, "level2"));
107+ algHierarchyInfo.infos.resize(ALGO_LEVEL_NUM_THREE);
108+ for (u32 i = 0; i < ALGO_LEVEL_NUM_THREE; i++) {
109+ algHierarchyInfo.infos[i].resize(1);
110+ }
111+ algHierarchyInfo.infos[0][0] = std::move(group0);
112+ algHierarchyInfo.infos[1][0] = std::move(group1);
113+ algHierarchyInfo.infos[ALGO_LEVEL_NUM_TWO][0] = std::move(group2);
114+ 
115+ // 填充 physicalIdxForAlgoLevels(二级:MeshConcur 双层,普通单层)
116+ CHK_RET(FillPhysicalIdxForAlgoLevels(
117+ physicalLevels, effIdx, pIndices, profile.algoTypes, algHierarchyInfo.physicalIdxForAlgoLevels));
118+ HCCL_INFO(
119+ "[TopoMatchThreeLevel] Rank [%u], d0[%u] d1[%u] d2[%u], physicalIdxForAlgoLevels: [%s].", myRank, d0, d1, d2,
120+ FormatPhysicalIdxForAlgoLevels(algHierarchyInfo.physicalIdxForAlgoLevels).c_str());
121+ return HcclResult::HCCL_SUCCESS;
122+}
123+ 
124+} // namespace ops_hccl
@@ -0,0 +1,32 @@
1+/**
2+ * Copyright (c) 2025 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+#ifndef HCCLV2_TOPO_MATCH_THREE_LEVEL_H
12+#define HCCLV2_TOPO_MATCH_THREE_LEVEL_H
13+ 
14+#include "topo_match_base_v2.h"
15+ 
16+namespace ops_hccl {
17+ 
18+class TopoMatchThreeLevel : public TopoMatchBaseV2 {
19+public:
20+ explicit TopoMatchThreeLevel();
21+ ~TopoMatchThreeLevel() override;
22+ 
23+ std::string Describe() const override { return "Topo Match for Three Level Algorithm."; }
24+ 
25+ HcclResult MatchTopo(
26+ TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo,
27+ const AlgAttrs& profile) override;
28+};
29+ 
30+} // namespace ops_hccl
31+ 
32+#endif // !HCCLV2_TOPO_MATCH_THREE_LEVEL_H
@@ -0,0 +1,116 @@
1+/**
2+ * Copyright (c) 2025 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 "topo_match_two_level.h"
12+#include <algorithm>
13+#include "log.h"
14+ 
15+namespace ops_hccl {
16+ 
17+namespace {
18+ // 计算内层维度 d0:LOCAL 取 localRanks.size();GLOBAL 对称取 localRanks.size(),非对称 GCD 打平
19+ HcclResult CalcLevel0Dim(const PhysicalLevelInfo& level0, u32 myRank, u32& d0, bool& asymmetric, u32& gcd)
20+ {
21+ if (level0.view == PhysicalLevelView::LOCAL) {
22+ d0 = static_cast<u32>(level0.localRanks.size());
23+ return HcclResult::HCCL_SUCCESS;
24+ }
25+ if (level0.instSizeListByLayer.empty()) {
26+ HCCL_ERROR("[TopoMatchTwoLevel] netLayer [ref = %u] instSizeListByLayer is empty.", level0.ref.netLayer);
27+ return HcclResult::HCCL_E_INTERNAL;
28+ }
29+ if (IsInstListSymmetric(level0.instSizeListByLayer)) {
30+ d0 = static_cast<u32>(level0.localRanks.size());
31+ asymmetric = false;
32+ return HcclResult::HCCL_SUCCESS;
33+ }
34+ // GLOBAL 非对称:对 instSizeListByLayer 取 GCD 打平为对称子组
35+ asymmetric = true;
36+ gcd = CalcGcd(level0.instSizeListByLayer);
37+ HCCL_INFO("[TopoMatchTwoLevel] Rank [%u], asymmetric level0, instList GCD[%u], d0=gcd.", myRank, gcd);
38+ if (gcd == 1) {
39+ HCCL_INFO("[TopoMatchTwoLevel] Rank [%u], asymmetric GCD=1, not support.", myRank);
40+ return HcclResult::HCCL_E_NOT_SUPPORT;
41+ }
42+ d0 = gcd;
43+ return HcclResult::HCCL_SUCCESS;
44+ }
45+ 
46+ // 构造含 myRank 的内层组;非对称时按 gcd 从 localRanks 切子组
47+ std::vector<u32> BuildLevel0Group(const PhysicalLevelInfo& level0, u32 myRank, bool asymmetric, u32 gcd)
48+ {
49+ if (!asymmetric) {
50+ return level0.localRanks;
51+ }
52+ const auto& ranks = level0.localRanks;
53+ auto it = std::find(ranks.begin(), ranks.end(), myRank);
54+ if (it == ranks.end()) {
55+ return {};
56+ }
57+ u32 myIdx = static_cast<u32>(it - ranks.begin());
58+ u32 startIdx = (myIdx / gcd) * gcd;
59+ u32 endIdx = std::min(startIdx + gcd, static_cast<u32>(ranks.size()));
60+ return std::vector<u32>(ranks.begin() + startIdx, ranks.begin() + endIdx);
61+ }
62+} // namespace
63+ 
64+TopoMatchTwoLevel::TopoMatchTwoLevel() {}
65+TopoMatchTwoLevel::~TopoMatchTwoLevel() {}
66+ 
67+HcclResult TopoMatchTwoLevel::MatchTopo(
68+ TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo, const AlgAttrs& profile)
69+{
70+ const auto& physicalLevels = topoInfo->physicalLevels;
71+ u32 myRank = topoInfo->userRank;
72+ u32 userRankSize = topoInfo->userRankSize;
73+ if (physicalLevels.empty() || userRankSize == 0 || profile.algoTypes.size() != ALGO_LEVEL_NUM_TWO) {
74+ HCCL_ERROR("[TopoMatchTwoLevel] Rank [%u], invalid input.", myRank);
75+ return HcclResult::HCCL_E_INTERNAL;
76+ }
77+ 
78+ // 引擎过滤 + 锚点匹配 + 分段 + 最高层校验
79+ std::vector<u32> effIdx;
80+ std::vector<u32> pIndices;
81+ CHK_RET(ResolveMapping(physicalLevels, profile, userRankSize, effIdx, pIndices));
82+ u32 phys0 = effIdx[pIndices[0]];
83+ 
84+ // GCD 校验 p_0(TwoLevel 非对称打平),外层 d1 = userRankSize / d0
85+ u32 d0 = 0;
86+ bool asymmetric = false;
87+ u32 gcd = 0;
88+ CHK_RET(CalcLevel0Dim(physicalLevels[phys0], myRank, d0, asymmetric, gcd));
89+ if (d0 <= 1 || userRankSize % d0 != 0) {
90+ HCCL_INFO("[TopoMatchTwoLevel] userRankSize[%u] not divisible by d0[%u].", myRank, userRankSize, d0);
91+ return HcclResult::HCCL_E_NOT_SUPPORT;
92+ }
93+ u32 d1 = userRankSize / d0;
94+ 
95+ // 构造 infos
96+ std::vector<u32> group0 = BuildLevel0Group(physicalLevels[phys0], myRank, asymmetric, gcd);
97+ std::vector<u32> group1 = BuildRepresentativeGroup(d0, d1, myRank % d0);
98+ CHK_RET(ValidateGroup(group0, d0, myRank, "level0"));
99+ CHK_RET(ValidateGroup(group1, d1, myRank, "level1"));
100+ algHierarchyInfo.infos.resize(ALGO_LEVEL_NUM_TWO);
101+ algHierarchyInfo.infos[0].resize(1);
102+ algHierarchyInfo.infos[1].resize(1);
103+ algHierarchyInfo.infos[0][0] = std::move(group0);
104+ algHierarchyInfo.infos[1][0] = std::move(group1);
105+ 
106+ // 填充 physicalIdxForAlgoLevels(二级:MeshConcur 双层,普通单层)
107+ CHK_RET(FillPhysicalIdxForAlgoLevels(
108+ physicalLevels, effIdx, pIndices, profile.algoTypes, algHierarchyInfo.physicalIdxForAlgoLevels));
109+ HCCL_INFO(
110+ "[TopoMatchTwoLevel] Rank [%u], d0[%u] d1[%u] asym[%d], physicalIdxForAlgoLevels: [%s].", myRank, d0, d1,
111+ static_cast<int32_t>(asymmetric),
112+ FormatPhysicalIdxForAlgoLevels(algHierarchyInfo.physicalIdxForAlgoLevels).c_str());
113+ return HcclResult::HCCL_SUCCESS;
114+}
115+ 
116+} // namespace ops_hccl
@@ -0,0 +1,32 @@
1+/**
2+ * Copyright (c) 2025 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+#ifndef TOPO_MATCH_TWO_LEVEL
12+#define TOPO_MATCH_TWO_LEVEL
13+ 
14+#include "topo_match_base_v2.h"
15+ 
16+namespace ops_hccl {
17+ 
18+class TopoMatchTwoLevel : public TopoMatchBaseV2 {
19+public:
20+ explicit TopoMatchTwoLevel();
21+ ~TopoMatchTwoLevel() override;
22+ 
23+ std::string Describe() const override { return "Topo Match for Two Level Algorithm."; }
24+ 
25+ HcclResult MatchTopo(
26+ TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo,
27+ const AlgAttrs& profile) override;
28+};
29+ 
30+} // namespace ops_hccl
31+ 
32+#endif // !TOPO_MATCH_TWO_LEVEL
@@ -9,7 +9,9 @@
9 */9 */
10 10 
11#include "ins_v2_reduce_scatter_v_sole_executor.h"11#include "ins_v2_reduce_scatter_v_sole_executor.h"
12+#include "topo_match_one_level.h"
12#include "ins_temp_reduce_scatter_v_mesh_1D.h"13#include "ins_temp_reduce_scatter_v_mesh_1D.h"
14+#include "alg_attrs_registry.h"
13#if CANN_VERSION_NUM >= CANN_VERSION(9, 0, 0)15#if CANN_VERSION_NUM >= CANN_VERSION(9, 0, 0)
14#include "ccu_temp_reduce_scatter_v_mesh_1D_mem2mem.h"16#include "ccu_temp_reduce_scatter_v_mesh_1D_mem2mem.h"
15 17 
@@ -24,9 +26,18 @@ template <typename AlgTopoMatch, typename InsAlgTemplate>
24HcclResult InsV2ReduceScatterVSoleExecutor<AlgTopoMatch, InsAlgTemplate>::CalcAlgHierarchyInfo(26HcclResult InsV2ReduceScatterVSoleExecutor<AlgTopoMatch, InsAlgTemplate>::CalcAlgHierarchyInfo(
25 HcclComm comm, TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo)27 HcclComm comm, TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo)
26{28{
27- // 使用topo match计算AlgHierarchyInfoForAllLevel29+ (void)comm;
28 AlgTopoMatch topoMatch;30 AlgTopoMatch topoMatch;
29- CHK_RET(topoMatch.MatchTopo(comm, topoInfo, algHierarchyInfo));31+ CHK_RET(topoMatch.MatchTopo(topoInfo, algHierarchyInfo, AlgAttrs{}));
32+ return HCCL_SUCCESS;
33+}
34+ 
35+template <typename AlgTopoMatch, typename InsAlgTemplate>
36+HcclResult InsV2ReduceScatterVSoleExecutor<AlgTopoMatch, InsAlgTemplate>::CalcAlgHierarchyInfoV2(
37+ TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo, const AlgAttrs& algAttrs)
38+{
39+ AlgTopoMatch topoMatch;
40+ CHK_RET(topoMatch.MatchTopo(topoInfo, algHierarchyInfo, algAttrs));
30 return HCCL_SUCCESS;41 return HCCL_SUCCESS;
31}42}
32 43 
@@ -181,13 +192,16 @@ HcclResult InsV2ReduceScatterVSoleExecutor<AlgTopoMatch, InsAlgTemplate>::Orches
181 192 
182// 第二个参数是Reduce Scatter的template文件193// 第二个参数是Reduce Scatter的template文件
183REGISTER_EXEC_V2(194REGISTER_EXEC_V2(
184- HcclCMDType::HCCL_CMD_REDUCE_SCATTER_V, AicpuReduceScatterVSoleMesh, InsV2ReduceScatterVSoleExecutor, TopoMatch1D,195+ HcclCMDType::HCCL_CMD_REDUCE_SCATTER_V, AicpuReduceScatterVSoleMesh, InsV2ReduceScatterVSoleExecutor,
185- InsTempReduceScatterVMesh1D);196+ TopoMatchOneLevel, InsTempReduceScatterVMesh1D);
197+REGISTER_ALG_ATTRS(AicpuReduceScatterVSoleMesh, topo.maxTopoLevelNum = 3; topo.supportLevel0Topos = LEVEL0_TOPO_ANY;);
186#ifndef AICPU_COMPILE198#ifndef AICPU_COMPILE
187#if CANN_VERSION_NUM >= CANN_VERSION(9, 0, 0)199#if CANN_VERSION_NUM >= CANN_VERSION(9, 0, 0)
188REGISTER_EXEC_V2(200REGISTER_EXEC_V2(
189 HcclCMDType::HCCL_CMD_REDUCE_SCATTER_V, CcuSchedReduceScatterVSoleMesh, InsV2ReduceScatterVSoleExecutor,201 HcclCMDType::HCCL_CMD_REDUCE_SCATTER_V, CcuSchedReduceScatterVSoleMesh, InsV2ReduceScatterVSoleExecutor,
190- TopoMatch1D, CcuTempReduceScatterVMesh1DMem2Mem);202+ TopoMatchOneLevel, CcuTempReduceScatterVMesh1DMem2Mem);
203+REGISTER_ALG_ATTRS(CcuSchedReduceScatterVSoleMesh, topo.maxTopoLevelNum = 1;
204+ topo.supportLevel0Topos = LEVEL0_TOPO_MESH_1D; op.isSupportInplace = false;);
191#endif // CANN_VERSION_NUM >= CANN_VERSION(9, 0, 0)205#endif // CANN_VERSION_NUM >= CANN_VERSION(9, 0, 0)
192#endif206#endif
193} // namespace ops_hccl207} // namespace ops_hccl
@@ -12,7 +12,7 @@
12#define HCCLV2_INS_V2_REDUCE_SCATTER_V_SOLE_EXECUTOR_H12#define HCCLV2_INS_V2_REDUCE_SCATTER_V_SOLE_EXECUTOR_H
13 13 
14#include "executor_common_ops.h"14#include "executor_common_ops.h"
15-#include "topo_match_1d.h"15+#include "topo_match_one_level.h"
16#include "topo_match_base.h"16#include "topo_match_base.h"
17 17 
18namespace ops_hccl {18namespace ops_hccl {
@@ -32,6 +32,10 @@ public:
32 HcclResult CalcAlgHierarchyInfo(32 HcclResult CalcAlgHierarchyInfo(
33 HcclComm comm, TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo) override;33 HcclComm comm, TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo) override;
34 34 
35+ HcclResult CalcAlgHierarchyInfoV2(
36+ TopoInfoWithNetLayerDetails* topoInfo, AlgHierarchyInfoForAllLevel& algHierarchyInfo,
37+ const AlgAttrs& algAttrs) override;
38+ 
35protected:39protected:
36 /* *************** 算法编排 *************** */40 /* *************** 算法编排 *************** */
37 HcclResult OrchestrateLoop(const OpParam& param, const AlgResourceCtxSerializable& resCtx);41 HcclResult OrchestrateLoop(const OpParam& param, const AlgResourceCtxSerializable& resCtx);
@@ -167,6 +167,11 @@ if(NOT HCCL_CANN_COMPAT_850)
167 ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_common/topo/topo_match_pcie_mix.cc167 ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_common/topo/topo_match_pcie_mix.cc
168 ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_common/topo/topo_match_3_level.cc168 ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_common/topo/topo_match_3_level.cc
169 ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_common/topo/topo_match_squeeze_2d.cc169 ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_common/topo/topo_match_squeeze_2d.cc
170+ ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_common/topo/topo_match_base_v2.cc
171+ ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_common/topo/topo_match_one_level.cc
172+ ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_common/topo/topo_match_two_level.cc
173+ ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_common/topo/topo_match_three_level.cc
174+ ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_common/topo/topo_match_concurrent_v2.cc
170 ${CMAKE_CURRENT_SOURCE_DIR}/ops/reduce_scatter/executor/ins_reduce_scatter_concurrent_executor.cc175 ${CMAKE_CURRENT_SOURCE_DIR}/ops/reduce_scatter/executor/ins_reduce_scatter_concurrent_executor.cc
171 ${CMAKE_CURRENT_SOURCE_DIR}/ops/all_gather/executor/ins_v2_all_gather_concurrent_executor.cc176 ${CMAKE_CURRENT_SOURCE_DIR}/ops/all_gather/executor/ins_v2_all_gather_concurrent_executor.cc
172 ${CMAKE_CURRENT_SOURCE_DIR}/ops/all_to_all_v/executor/ins_v2_all_to_all_concurrent_executor.cc177 ${CMAKE_CURRENT_SOURCE_DIR}/ops/all_to_all_v/executor/ins_v2_all_to_all_concurrent_executor.cc
@@ -334,6 +334,15 @@ aclError aclrtRecordNotify(aclrtNotify notify, aclrtStream stream)
334 334 
335aclError aclrtGetDeviceInfo(uint32_t deviceId, aclrtDevAttr attr, int64_t* value)335aclError aclrtGetDeviceInfo(uint32_t deviceId, aclrtDevAttr attr, int64_t* value)
336{336{
337+// 老CANN的acl_rt.h没有这个枚举, 探测与它同批引入的宏, 口径同adapter_acl.h
338+#ifdef ACL_DEVICE_FORM_FACTOR_POD
339+ // 本函数其余分支不回写*value。调用方普遍把出参初始化为0, 而ACL_DEVICE_FORM_FACTOR_POD恰好是0,
340+ // 不显式给出非POD取值的话, ST会把每个用例都建模成POD机型
341+ if (attr == ACL_DEV_ATTR_DEVICE_FORM_FACTOR) {
342+ *value = ACL_DEVICE_FORM_FACTOR_A_X;
343+ return ACL_SUCCESS;
344+ }
345+#endif
337 HCCL_WARNING("[%s] not support.", __func__);346 HCCL_WARNING("[%s] not support.", __func__);
338 return ACL_SUCCESS;347 return ACL_SUCCESS;
339}348}
@@ -344,6 +353,13 @@ aclError aclrtGetLogicDevIdByPhyDevId(int32_t phyDevId, int32_t* const logicDevI
344 return ACL_SUCCESS;353 return ACL_SUCCESS;
345}354}
346 355 
356+aclError aclrtGetLogicDevIdByUserDevId(const int32_t userDevId, int32_t* const logicDevId)
357+{
358+ // SimWorld不区分user/logic设备号, aclrtGetDevice直接返回curr_dev_id, 这里原样透传
359+ *logicDevId = userDevId;
360+ return ACL_SUCCESS;
361+}
362+ 
347aclError aclrtGetPhyDevIdByLogicDevId(const int32_t logicDevId, int32_t* const phyDevId)363aclError aclrtGetPhyDevIdByLogicDevId(const int32_t logicDevId, int32_t* const phyDevId)
348{364{
349 auto npuPos = HcclSim::SimWorld::Global()->GetNpuPosByRankId(curr_dev_id);365 auto npuPos = HcclSim::SimWorld::Global()->GetNpuPosByRankId(curr_dev_id);