已合并
将 BIRS 批量不变性扩展至变长 ReduceScatterV(ReduceScatterVBIRS / ReduceScatterVBIRSInter) #2338
Davydov_Danil创建于 7月28日
将 BIRS 批量不变性扩展至变长 ReduceScatterV(ReduceScatterVBIRS / ReduceScatterVBIRSInter) #2338
已合并
Davydov_Danil创建于 7月28日
共 35 个文件变更+2799-7
@@ -531,6 +531,7 @@ function run_st() {
531 log "Info: run_st ST_TASKS=${ST_TASKS}"531 log "Info: run_st ST_TASKS=${ST_TASKS}"
532 export ENABLE_GCOV=${ENABLE_GCOV}532 export ENABLE_GCOV=${ENABLE_GCOV}
533 export ST_TASKS=${ST_TASKS}533 export ST_TASKS=${ST_TASKS}
534+ export ENABLE_EXPERIMENTAL=${ENABLE_EXPERIMENTAL}
534 # 编译 ST 用例535 # 编译 ST 用例
535 bash ${st_build_shell}536 bash ${st_build_shell}
536 local build_ret=$?537 local build_ret=$?
@@ -14,6 +14,7 @@ endif()
14 14 
15add_subdirectory(op_common)15add_subdirectory(op_common)
16add_subdirectory(reduce_scatter)16add_subdirectory(reduce_scatter)
17+add_subdirectory(reduce_scatter_v)
17add_subdirectory(all_reduce/example)18add_subdirectory(all_reduce/example)
18 19 
19set(EXPERIMENTAL_INCLUDE_LIST20set(EXPERIMENTAL_INCLUDE_LIST
@@ -25,6 +26,9 @@ set(EXPERIMENTAL_INCLUDE_LIST
25 ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter/26 ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter/
26 ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter/birs/27 ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter/birs/
27 ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter/birs/template/28 ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter/birs/template/
29+ ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter_v/
30+ ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter_v/birs/
31+ ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter_v/birs/template/
28 ${CMAKE_CURRENT_SOURCE_DIR}/all_reduce/32 ${CMAKE_CURRENT_SOURCE_DIR}/all_reduce/
29 ${CMAKE_CURRENT_SOURCE_DIR}/all_reduce/example/33 ${CMAKE_CURRENT_SOURCE_DIR}/all_reduce/example/
30 ${CMAKE_CURRENT_SOURCE_DIR}/all_reduce/example/template/34 ${CMAKE_CURRENT_SOURCE_DIR}/all_reduce/example/template/
@@ -43,6 +47,10 @@ if(TARGET scatter_aicpu_kernel)
43 ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter/birs/reduce_scatter_executor_base.cc47 ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter/birs/reduce_scatter_executor_base.cc
44 ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter/birs/template/reduce_scatter_birs.cc48 ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter/birs/template/reduce_scatter_birs.cc
45 ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter/birs/template/reduce_scatter_birs_inter.cc49 ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter/birs/template/reduce_scatter_birs_inter.cc
50+ ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter_v/birs/reduce_scatter_v_birs_executor.cc
51+ ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter_v/birs/reduce_scatter_v_executor_base.cc
52+ ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter_v/birs/template/reduce_scatter_v_birs.cc
53+ ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter_v/birs/template/reduce_scatter_v_birs_inter.cc
46 )54 )
47endif()55endif()
48# host 构建存在 hccl 目标; device 构建无 hccl 目标(仅 scatter_aicpu_kernel),此处需守护以兼容 device 复用本目录56# host 构建存在 hccl 目标; device 构建无 hccl 目标(仅 scatter_aicpu_kernel),此处需守护以兼容 device 复用本目录
@@ -19,6 +19,7 @@
19#include "topo_host.h"19#include "topo_host.h"
20#include "hcomm_host_profiling_dl.h"20#include "hcomm_host_profiling_dl.h"
21#include "reduce_scatter_birs_selector.h"21#include "reduce_scatter_birs_selector.h"
22+#include "reduce_scatter_v_birs_selector.h"
22#include <algorithm>23#include <algorithm>
23#include <future>24#include <future>
24#include <map>25#include <map>
@@ -31,6 +32,7 @@ using ops_hccl::AlgType;
31using ops_hccl::AlgTypeLevel1;32using ops_hccl::AlgTypeLevel1;
32using ops_hccl::CollAlgExecRegistry;33using ops_hccl::CollAlgExecRegistry;
33using ops_hccl::CUSTOM_TIMEOUT;34using ops_hccl::CUSTOM_TIMEOUT;
35+using ops_hccl::DATATYPE_SIZE_TABLE;
34using ops_hccl::ExecutorBase;36using ops_hccl::ExecutorBase;
35using ops_hccl::g_binKernelHandle;37using ops_hccl::g_binKernelHandle;
36using ops_hccl::g_notifiesMap;38using ops_hccl::g_notifiesMap;
@@ -41,7 +43,9 @@ using ops_hccl::haclrtMemcpy;
41using ops_hccl::HCCL_ALG;43using ops_hccl::HCCL_ALG;
42using ops_hccl::NotifyArray;44using ops_hccl::NotifyArray;
43using ops_hccl::OpParam;45using ops_hccl::OpParam;
46+using ops_hccl::REDUCE_SCATTER_V_VECTOR_NUM;
44using ops_hccl::TopoInfo;47using ops_hccl::TopoInfo;
48+ 
45bool IsStreamCapture(aclrtStream stream)49bool IsStreamCapture(aclrtStream stream)
46{50{
47 bool isCapture;51 bool isCapture;
@@ -148,13 +152,70 @@ SelectAlgReduceScatter(HcclComm comm, OpParam& param, TopoInfo* topoInfo, AlgTyp
148 return HCCL_SUCCESS;152 return HCCL_SUCCESS;
149}153}
150 154 
155+HcclResult
156+SelectAlgReduceScatterV(HcclComm comm, OpParam& param, TopoInfo* topoInfo, AlgType& algType, std::string& algName)
157+{
158+ (void)comm;
159+ ValidateAndResetAlgLevel1(algType, "Reduce_Scatter_V");
160+ 
161+ const BirsVSelectResult birsVResult = DecideReduceScatterVBirsAlg(*topoInfo, algName);
162+ const HcclResult birsVRet = BirsVSelectResultToCode(birsVResult);
163+ if (birsVRet != HCCL_SUCCESS) {
164+ if (birsVResult == BirsVSelectResult::vRejectServerNumZero) {
165+ HCCL_ERROR(
166+ "[%s] ReduceScatterVBIRS not supported: serverNum is 0, cannot compute ranks per server "
167+ "(userRankSize[%u])",
168+ __func__, topoInfo->userRankSize);
169+ } else if (birsVResult == BirsVSelectResult::vRejectRanksPerServerLT4) {
170+ HCCL_ERROR(
171+ "[%s] ReduceScatterVBIRS not supported: userRankSize[%u] / serverNum[%u] < 4", __func__,
172+ topoInfo->userRankSize, topoInfo->serverNum);
173+ }
174+ return birsVRet;
175+ }
176+ 
177+ CHK_RET(FillAlgTagAndDebugInfo(param, topoInfo, algType, algName, "Reduce_Scatter_V"));
178+ return HCCL_SUCCESS;
179+}
180+ 
151struct ThreadResources {181struct ThreadResources {
152 ThreadHandle cpuTsThread = 0;182 ThreadHandle cpuTsThread = 0;
153 ThreadHandle exportedAicpuTsThread = 0;183 ThreadHandle exportedAicpuTsThread = 0;
154 ThreadHandle exportedCpuTsThread = 0;184 ThreadHandle exportedCpuTsThread = 0;
155 AlgResourceCtx* resCtx = nullptr;185 AlgResourceCtx* resCtx = nullptr;
186+ void* vDataDesDevPtr = nullptr;
156};187};
157 188 
189+HcclResult PrepareReduceScatterVVarData(OpParam& param, TopoInfo* topoInfo, ThreadResources& threadRes)
190+{
191+ if (param.engine != COMM_ENGINE_AICPU_TS || param.opType != HCCL_CMD_REDUCE_SCATTER_V || param.varMemSize <= 0) {
192+ return HCCL_SUCCESS;
193+ }
194+ u32 userRankSize = topoInfo->userRankSize;
195+ CHK_PRT_RET(
196+ param.varMemSize != REDUCE_SCATTER_V_VECTOR_NUM * userRankSize * sizeof(u64),
197+ HCCL_ERROR(
198+ "[PrepareReduceScatterVVarData] param.varMemSize[%llu] invalid, expected[%llu]", param.varMemSize,
199+ REDUCE_SCATTER_V_VECTOR_NUM * userRankSize * sizeof(u64)),
200+ HCCL_E_PARA);
201+ ACLCHECK(aclrtMalloc(&threadRes.vDataDesDevPtr, param.varMemSize, ACL_MEM_MALLOC_HUGE_FIRST));
202+ u64* devCounts = static_cast<u64*>(threadRes.vDataDesDevPtr);
203+ aclError aclRet
204+ = aclrtMemcpy(devCounts, param.varMemSize, param.varData, param.varMemSize, ACL_MEMCPY_HOST_TO_DEVICE);
205+ if (aclRet != ACL_SUCCESS) {
206+ HCCL_ERROR("acl interface return err %s:%d, retcode: %d.", __FILE__, __LINE__, aclRet);
207+ aclError freeRet = aclrtFree(threadRes.vDataDesDevPtr);
208+ if (freeRet != ACL_SUCCESS) {
209+ HCCL_WARNING("[PrepareReduceScatterVVarData] aclrtFree vDataDesDevPtr failed, ret[%d]", freeRet);
210+ }
211+ threadRes.vDataDesDevPtr = nullptr;
212+ return HCCL_E_RUNTIME;
213+ }
214+ param.vDataDes.counts = devCounts;
215+ param.vDataDes.displs = devCounts + userRankSize;
216+ return HCCL_SUCCESS;
217+}
218+ 
158HcclResult PrepareThreadResources(219HcclResult PrepareThreadResources(
159 HcclComm comm, OpParam& param, std::unique_ptr<ExecutorBase>& executor, TopoInfo* topoInfo, AlgType& algType,220 HcclComm comm, OpParam& param, std::unique_ptr<ExecutorBase>& executor, TopoInfo* topoInfo, AlgType& algType,
160 ThreadResources& threadRes)221 ThreadResources& threadRes)
@@ -190,6 +251,9 @@ HcclResult PrepareThreadResources(
190 curPtr, sizeof(ThreadHandle), &threadRes.exportedAicpuTsThread, sizeof(ThreadHandle),251 curPtr, sizeof(ThreadHandle), &threadRes.exportedAicpuTsThread, sizeof(ThreadHandle),
191 ACL_MEMCPY_HOST_TO_DEVICE));252 ACL_MEMCPY_HOST_TO_DEVICE));
192 }253 }
254+ 
255+ CHK_RET(PrepareReduceScatterVVarData(param, topoInfo, threadRes));
256+ 
193 return HCCL_SUCCESS;257 return HCCL_SUCCESS;
194}258}
195 259 
@@ -322,6 +386,8 @@ HcclResult ExecOpBirs(HcclComm comm, OpParam& param)
322 std::string algName;386 std::string algName;
323 if (param.opType == HCCL_CMD_REDUCE_SCATTER) {387 if (param.opType == HCCL_CMD_REDUCE_SCATTER) {
324 CHK_RET(SelectAlgReduceScatter(comm, param, topoInfo, algType, algName));388 CHK_RET(SelectAlgReduceScatter(comm, param, topoInfo, algType, algName));
389+ } else if (param.opType == HCCL_CMD_REDUCE_SCATTER_V) {
390+ CHK_RET(SelectAlgReduceScatterV(comm, param, topoInfo, algType, algName));
325 }391 }
326 392 
327 std::unique_ptr<ExecutorBase> executor = CollAlgExecRegistry::Instance().GetAlgExec(algName);393 std::unique_ptr<ExecutorBase> executor = CollAlgExecRegistry::Instance().GetAlgExec(algName);
@@ -333,7 +399,16 @@ HcclResult ExecOpBirs(HcclComm comm, OpParam& param)
333 CHK_RET(PrepareThreadResources(comm, param, executor, topoInfo, algType, threadRes));399 CHK_RET(PrepareThreadResources(comm, param, executor, topoInfo, algType, threadRes));
334 400 
335 if (param.engine == COMM_ENGINE_AICPU_TS) {401 if (param.engine == COMM_ENGINE_AICPU_TS) {
336- CHK_RET(LaunchAicpuKernel(comm, param, topoInfo, algName, threadRes));402+ // 先保存返回值再释放 vDataDesDevPtr, 保证 LaunchAicpuKernel 失败路径同样释放设备内存
403+ HcclResult launchRet = LaunchAicpuKernel(comm, param, topoInfo, algName, threadRes);
404+ if (threadRes.vDataDesDevPtr != nullptr) {
405+ aclError aclRet = aclrtFree(threadRes.vDataDesDevPtr);
406+ if (aclRet != ACL_SUCCESS) {
407+ HCCL_WARNING("[ExecOpBirs] aclrtFree vDataDesDevPtr failed, ret[%d]", aclRet);
408+ }
409+ threadRes.vDataDesDevPtr = nullptr;
410+ }
411+ CHK_RET(launchRet);
337 } else {412 } else {
338 CHK_RET(executor->Orchestrate(param, threadRes.resCtx));413 CHK_RET(executor->Orchestrate(param, threadRes.resCtx));
339 param.resCtx = threadRes.resCtx;414 param.resCtx = threadRes.resCtx;
@@ -351,8 +426,13 @@ HcclResult ProcessA3(HcclComm comm, OpParam& param, uint64_t beginTime)
351 CHK_SAFETY_FUNC_RET(strcpy_s(profInfo.algType, sizeof(profInfo.algType), algTypeStr.c_str()));426 CHK_SAFETY_FUNC_RET(strcpy_s(profInfo.algType, sizeof(profInfo.algType), algTypeStr.c_str()));
352 CHK_SAFETY_FUNC_RET(strcpy_s(profInfo.commName, sizeof(profInfo.commName), param.commName));427 CHK_SAFETY_FUNC_RET(strcpy_s(profInfo.commName, sizeof(profInfo.commName), param.commName));
353 profInfo.beginTime = beginTime;428 profInfo.beginTime = beginTime;
354- profInfo.dataCount = param.DataDes.count;429+ if (param.opType == HCCL_CMD_REDUCE_SCATTER_V) {
355- profInfo.dataType = static_cast<uint8_t>(param.DataDes.dataType);430+ profInfo.dataCount = param.outputSize / DATATYPE_SIZE_TABLE[param.vDataDes.dataType];
431+ profInfo.dataType = static_cast<uint8_t>(param.vDataDes.dataType);
432+ } else {
433+ profInfo.dataCount = param.DataDes.count;
434+ profInfo.dataType = static_cast<uint8_t>(param.DataDes.dataType);
435+ }
356 profInfo.cmdType = static_cast<uint8_t>(param.opType);436 profInfo.cmdType = static_cast<uint8_t>(param.opType);
357 CHK_PRT(HcommProfilingReportOp(profInfo));437 CHK_PRT(HcommProfilingReportOp(profInfo));
358 438 
@@ -24,6 +24,8 @@ using ops_hccl::TemplateType;
24 24 
25constexpr TemplateType TEMPLATE_REDUCE_SCATTER_BIRS = static_cast<TemplateType>(1001);25constexpr TemplateType TEMPLATE_REDUCE_SCATTER_BIRS = static_cast<TemplateType>(1001);
26constexpr TemplateType TEMPLATE_REDUCE_SCATTER_BIRS_INTER = static_cast<TemplateType>(1002);26constexpr TemplateType TEMPLATE_REDUCE_SCATTER_BIRS_INTER = static_cast<TemplateType>(1002);
27+constexpr TemplateType TEMPLATE_REDUCE_SCATTER_V_BIRS = static_cast<TemplateType>(1003);
28+constexpr TemplateType TEMPLATE_REDUCE_SCATTER_V_BIRS_INTER = static_cast<TemplateType>(1004);
27 29 
28class AlgTemplateBaseExperimental : public ops_hccl::AlgTemplateBase {30class AlgTemplateBaseExperimental : public ops_hccl::AlgTemplateBase {
29public:31public:
@@ -0,0 +1,20 @@
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+add_subdirectory(birs)
11+ 
12+set(src_list
13+ ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter_v_op_experimental.cc
14+)
15+ 
16+if(TARGET hccl)
17+ target_sources(hccl PRIVATE
18+ ${src_list}
19+ )
20+endif()
@@ -0,0 +1,22 @@
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+add_subdirectory(template)
11+ 
12+set(src_list
13+ ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter_v_executor_base.cc
14+ ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter_v_birs_executor.cc
15+ ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter_v_birs_selector.cc
16+)
17+ 
18+if(TARGET hccl)
19+ target_sources(hccl PRIVATE
20+ ${src_list}
21+ )
22+endif()
@@ -0,0 +1,122 @@
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 <numeric>
12+#include "reduce_scatter_v_birs_executor.h"
13+#include "topo_experimental.h"
14+ 
15+namespace ops_hccl_experimental {
16+ReduceScatterVBIRSExecutor::ReduceScatterVBIRSExecutor() : ReduceScatterVExecutorBase()
17+{
18+ desc_.level1SupportedAlgos
19+ = {AlgTypeLevel1::ALG_LEVEL1_NHR, AlgTypeLevel1::ALG_LEVEL1_NB, AlgTypeLevel1::ALG_LEVEL1_RING};
20+ desc_.level2SupportedAlgos
21+ = {AlgTypeLevel2::ALG_LEVEL2_NHR, AlgTypeLevel2::ALG_LEVEL2_NB, AlgTypeLevel2::ALG_LEVEL2_RING};
22+}
23+ 
24+HcclResult ReduceScatterVBIRSExecutor::CalcResRequest(
25+ HcclComm comm, const OpParam& param, TopoInfo* topoInfo, AlgHierarchyInfo& algHierarchyInfo,
26+ AlgResourceRequest& resourceRequest, AlgType& algType)
27+{
28+ if (topoInfo->serverNum == 1) {
29+ CHK_RET(CalcGeneralTopoInfoForA3(comm, topoInfo, algHierarchyInfo));
30+ } else {
31+ CHK_RET(CalcGeneralTopoInfoInterServer(comm, topoInfo, algHierarchyInfo));
32+ }
33+ CHK_RET(RefreshAlgType(algType));
34+ algType.algoLevel0 = AlgTypeLevel0::ALG_LEVEL0_NP_MESH;
35+ 
36+ resourceRequest.slaveThreadNum = BIRS_THREAD_NUM;
37+ for (u32 index = 0; index < BIRS_THREAD_NUM; index++) {
38+ resourceRequest.notifyNumPerThread.push_back(1);
39+ }
40+ resourceRequest.notifyNumOnMainThread = BIRS_THREAD_NUM;
41+ 
42+ // level0 channel
43+ std::vector<HcclChannelDesc> level0Channels;
44+ CHK_RET(CalcLevel0ChannelRequest(param, topoInfo, algHierarchyInfo, algType, level0Channels));
45+ resourceRequest.channels.push_back(level0Channels);
46+ 
47+ HCCL_INFO(
48+ "[ReduceScatterVBIRSExecutor][CalcResRequest]slaveThreadNum[%u] notifyNumPerThread[%u]"
49+ " notifyNumOnMainThread[%u] level0Channels[%u]",
50+ resourceRequest.slaveThreadNum, resourceRequest.notifyNumPerThread.size(),
51+ resourceRequest.notifyNumOnMainThread, level0Channels.size());
52+ return HCCL_SUCCESS;
53+}
54+ 
55+HcclResult ReduceScatterVBIRSExecutor::KernelRun(const OpParam& param, ExecMem& execMem)
56+{
57+ HCCL_CONFIG_INFO(HCCL_ALG, "[ReduceScatterVBIRSExecutor][KernelRun] starts.");
58+ 
59+ CHK_RET(KernelRunLevel0(param, execMem));
60+ 
61+ HCCL_INFO("reduce scatter V BIRS run success");
62+ return HCCL_SUCCESS;
63+}
64+ 
65+HcclResult ReduceScatterVBIRSExecutor::SelectAndPrepareBirsVTemplate(
66+ bool isSingleServer, u32 localRank, u32 localRankSize, std::unique_ptr<AlgTemplateBase>& templatePtr)
67+{
68+ TemplateType tplType = isSingleServer ? TEMPLATE_REDUCE_SCATTER_V_BIRS : TEMPLATE_REDUCE_SCATTER_V_BIRS_INTER;
69+ templatePtr = AlgTemplateRegistry::Instance().GetAlgTemplate(tplType);
70+ CHK_SMART_PTR_NULL(templatePtr);
71+ if (isSingleServer) {
72+ CHK_RET(templatePtr->Prepare(localRank, localRankSize));
73+ HCCL_CONFIG_INFO(HCCL_ALG, "[%s] Run TEMPLATE_REDUCE_SCATTER_V_BIRS in COMM_LEVEL0", __func__);
74+ } else {
75+ CHK_RET(templatePtr->Prepare(topoInfo_->serverNum, topoInfo_->userRankSize));
76+ HCCL_CONFIG_INFO(HCCL_ALG, "[%s] Run TEMPLATE_REDUCE_SCATTER_V_BIRS_INTER in COMM_LEVEL0", __func__);
77+ }
78+ return HCCL_SUCCESS;
79+}
80+ 
81+HcclResult ReduceScatterVBIRSExecutor::KernelRunLevel0(const OpParam& param, ExecMem& execMem)
82+{
83+ SubCommInfo level0CommInfo;
84+ CHK_RET(GetSubCommInfo(COMM_LEVEL0, level0CommInfo));
85+ u32 level0LocalRank = level0CommInfo.localRank;
86+ u32 level0LocalRankSize = level0CommInfo.localRankSize;
87+ u32 sliceNum = level0LocalRankSize;
88+ 
89+ // V变体: 从param.vDataDes.counts/displs还原各rank的sendCounts与sendDispls
90+ // 注意: 使用vDataDes(常规字段, sizeof(OpParam)内)而非varData(柔性数组), 因为AICPU_TS
91+ // kernel下发只拷贝sizeof(OpParam)
92+ const u64* sendCounts = reinterpret_cast<const u64*>(param.vDataDes.counts);
93+ const u64* sendDispls = reinterpret_cast<const u64*>(param.vDataDes.displs);
94+ 
95+ std::vector<Slice> dataSegsSlice;
96+ CHK_RET(PrepareDataSliceV(sendCounts, sendDispls, unitSize_, sliceNum, dataSegsSlice));
97+ 
98+ std::unique_ptr<AlgTemplateBase> level0TempAlg = std::make_unique<AlgTemplateBaseExperimental>();
99+ bool isSingleServer = (topoInfo_->serverNum == 1);
100+ CHK_RET(SelectAndPrepareBirsVTemplate(isSingleServer, level0LocalRank, level0LocalRankSize, level0TempAlg));
101+ 
102+ // V变体: 用户输入区大小为param.inputSize(各rank按sendDispls布局), 输出区为param.outputSize(本rank接收量)
103+ HcclMem UsrInputMem{HCCL_MEM_TYPE_DEVICE, execMem.inputPtr, param.inputSize};
104+ HcclMem UsrOutputMem{HCCL_MEM_TYPE_DEVICE, execMem.outputPtr, param.outputSize};
105+ 
106+ if (auto exp = dynamic_cast<AlgTemplateBaseExperimental*>(level0TempAlg.get())) {
107+ // count传本rank接收量(execMem.count), slices传变长切片, 由模板内部按slices_[i].size处理各rank数据量
108+ CHK_RET(exp->Prepare(
109+ UsrInputMem, UsrOutputMem, execMem.inputMem, execMem.count, param.vDataDes.dataType, thread_, slaveThreads_,
110+ param.reduceType, 0, dataSegsSlice, 0, false));
111+ 
112+ CHK_RET(exp->RunAsync(level0LocalRank, level0LocalRankSize, channels_[COMM_LEVEL0]));
113+ } else {
114+ HCCL_ERROR("[KernelRunLevel0] dynamic_cast to AlgTemplateBaseExperimental failed");
115+ return HCCL_E_INTERNAL;
116+ }
117+ 
118+ return HCCL_SUCCESS;
119+}
120+ 
121+REGISTER_EXEC("ReduceScatterVBIRSExecutor", ReduceScatterVBIRS, ReduceScatterVBIRSExecutor);
122+} // namespace ops_hccl_experimental
@@ -0,0 +1,59 @@
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 REDUCE_SCATTER_V_BIRS_EXECUTOR_H
12+#define REDUCE_SCATTER_V_BIRS_EXECUTOR_H
13+#include "reduce_scatter_v_executor_base.h"
14+#include "alg_template_base_experimental.h"
15+#include "coll_alg_exec_registry.h"
16+ 
17+namespace ops_hccl_experimental {
18+using ops_hccl::AddAlgToAllAlgos;
19+using ops_hccl::AlgHierarchyInfo;
20+using ops_hccl::AlgResourceCtx;
21+using ops_hccl::AlgResourceRequest;
22+using ops_hccl::AlgTemplateBase;
23+using ops_hccl::AlgTemplateRegistry;
24+using ops_hccl::AlgType;
25+using ops_hccl::AlgTypeLevel0;
26+using ops_hccl::AlgTypeLevel1;
27+using ops_hccl::AlgTypeLevel2;
28+using ops_hccl::ChannelInfo;
29+using ops_hccl::CollAlgExecRegistry;
30+using ops_hccl::COMM_LEVEL0;
31+using ops_hccl::DefaultExecCreator;
32+using ops_hccl::ExecMem;
33+using ops_hccl::ExecutorBase;
34+using ops_hccl::HCCL_ALG;
35+using ops_hccl::OpParam;
36+using ops_hccl::Slice;
37+using ops_hccl::SubCommInfo;
38+using ops_hccl::TemplateType;
39+using ops_hccl::TopoInfo;
40+ 
41+class ReduceScatterVBIRSExecutor : public ReduceScatterVExecutorBase {
42+public:
43+ explicit ReduceScatterVBIRSExecutor();
44+ ~ReduceScatterVBIRSExecutor() override = default;
45+ 
46+ HcclResult CalcResRequest(
47+ HcclComm comm, const OpParam& param, TopoInfo* topoInfo, AlgHierarchyInfo& algHierarchyInfo,
48+ AlgResourceRequest& resourceRequest, AlgType& algType) override;
49+ 
50+private:
51+ HcclResult SelectAndPrepareBirsVTemplate(
52+ bool isSingleServer, u32 localRank, u32 localRankSize, std::unique_ptr<AlgTemplateBase>& templatePtr);
53+ HcclResult KernelRunLevel0(const OpParam& param, ExecMem& execMem);
54+ HcclResult KernelRun(const OpParam& param, ExecMem& execMem) override;
55+};
56+ 
57+} // namespace ops_hccl_experimental
58+ 
59+#endif
@@ -0,0 +1,48 @@
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+#include "reduce_scatter_v_birs_selector.h"
11+ 
12+namespace ops_hccl_experimental {
13+using ops_hccl::TopoInfo;
14+ 
15+BirsVSelectResult DecideReduceScatterVBirsAlg(const TopoInfo& topoInfo, std::string& algName)
16+{
17+ if (topoInfo.userRankSize == 1) {
18+ return BirsVSelectResult::vRejectRankSizeOne;
19+ }
20+ if (topoInfo.deviceType != HcclDevType::DEV_TYPE_910_93 || (topoInfo.userRankSize % 2 != 0)) {
21+ return BirsVSelectResult::vNotSelected;
22+ }
23+ if (topoInfo.serverNum == 0) {
24+ return BirsVSelectResult::vRejectServerNumZero;
25+ }
26+ if (topoInfo.userRankSize / topoInfo.serverNum < 4) {
27+ return BirsVSelectResult::vRejectRanksPerServerLT4;
28+ }
29+ algName = "ReduceScatterVBIRSExecutor";
30+ return BirsVSelectResult::vSelected;
31+}
32+ 
33+HcclResult BirsVSelectResultToCode(BirsVSelectResult result)
34+{
35+ switch (result) {
36+ case BirsVSelectResult::vSelected:
37+ case BirsVSelectResult::vNotSelected:
38+ return HCCL_SUCCESS;
39+ case BirsVSelectResult::vRejectRankSizeOne:
40+ return HCCL_E_INTERNAL;
41+ case BirsVSelectResult::vRejectServerNumZero:
42+ case BirsVSelectResult::vRejectRanksPerServerLT4:
43+ return HCCL_E_PARA;
44+ }
45+ return HCCL_E_INTERNAL;
46+}
47+ 
48+} // namespace ops_hccl_experimental
@@ -0,0 +1,30 @@
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+#ifndef OPS_HCCL_EXPERIMENTAL_REDUCE_SCATTER_V_BIRS_SELECTOR
11+#define OPS_HCCL_EXPERIMENTAL_REDUCE_SCATTER_V_BIRS_SELECTOR
12+ 
13+#include <string>
14+#include "alg_param.h"
15+ 
16+namespace ops_hccl_experimental {
17+enum class BirsVSelectResult {
18+ vNotSelected,
19+ vSelected,
20+ vRejectRankSizeOne,
21+ vRejectServerNumZero,
22+ vRejectRanksPerServerLT4,
23+};
24+ 
25+BirsVSelectResult DecideReduceScatterVBirsAlg(const ops_hccl::TopoInfo& topoInfo, std::string& algName);
26+ 
27+HcclResult BirsVSelectResultToCode(BirsVSelectResult result);
28+} // namespace ops_hccl_experimental
29+ 
30+#endif
@@ -0,0 +1,190 @@
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 "reduce_scatter_v_executor_base.h"
12+ 
13+namespace ops_hccl_experimental {
14+using ops_hccl::ExecMem;
15+using ops_hccl::HCCL_INTERNODE_MAX_DATA_RATE;
16+using ops_hccl::RDMA_SEND_MAX_SIZE;
17+using ops_hccl::SDMA_SEND_MAX_SIZE;
18+ 
19+ReduceScatterVExecutorBase::ReduceScatterVExecutorBase() : ExecutorBase() {}
20+ 
21+// 执行入口
22+HcclResult ReduceScatterVExecutorBase::Orchestrate(const OpParam& param, AlgResourceCtx* resCtx)
23+{
24+ HcclUs startut = TIME_NOW();
25+ topoInfo_ = &(resCtx->topoInfo);
26+ algResource_ = resCtx;
27+ tag_ = std::string(param.tag);
28+ algType_ = resCtx->algType;
29+ unitSize_ = HCCL_SIZE_TABLE[param.vDataDes.dataType];
30+ 
31+ CHK_PTR_NULL(param.inputPtr);
32+ CHK_PTR_NULL(param.outputPtr);
33+ 
34+ // 做参数的还原
35+ ThreadHandle* threadHandlePtr
36+ = reinterpret_cast<ThreadHandle*>(reinterpret_cast<char*>(algResource_) + sizeof(AlgResourceCtx));
37+ ChannelInfo* channelInfoPtr = reinterpret_cast<ChannelInfo*>(
38+ reinterpret_cast<char*>(threadHandlePtr) + sizeof(ThreadHandle) * (algResource_->slaveThreadNum + 1));
39+ 
40+ HCCL_DEBUG("[ReduceScatterVExecutorBase][Orchestrate] slaveThreadNum[%u]", algResource_->slaveThreadNum);
41+ for (u32 i = 0; i < algResource_->slaveThreadNum + 1; i++) {
42+ HCCL_DEBUG("[ReduceScatterVExecutorBase][Orchestrate] threadHandle[%u]=[%llu]", i, threadHandlePtr[i]);
43+ if (i == 0) {
44+ thread_ = threadHandlePtr[i];
45+ } else {
46+ slaveThreads_.push_back(threadHandlePtr[i]);
47+ }
48+ }
49+ AlgHierarchyInfo& algHierarchyInfo = resCtx->algHierarchyInfo;
50+ channels_.resize(algHierarchyInfo.levels);
51+ for (u32 level = 0; level < algHierarchyInfo.levels; level++) {
52+ u32 curLevelRankSize = algHierarchyInfo.infos[level].localRankSize;
53+ channels_[level].resize(curLevelRankSize);
54+ for (u32 rank = 0; rank < curLevelRankSize; rank++) {
55+ channels_[level][rank] = channelInfoPtr[rank];
56+ }
57+ channelInfoPtr += curLevelRankSize;
58+ }
59+ 
60+ HcclResult ret = RunLoop(param);
61+ CHK_PRT_RET(
62+ ret != HCCL_SUCCESS,
63+ HCCL_ERROR(
64+ "[ReduceScatterVExecutorBase][Orchestrate]errNo[0x%016llx]"
65+ " ReduceScatterV executor kernel run failed",
66+ HCCL_ERROR_CODE(ret)),
67+ ret);
68+ HCCL_INFO(
69+ "[ReduceScatterVExecutorBase][Orchestrate]tag[%s]"
70+ " ReduceScatterV executor orchestrate success, take time [%lld]us.",
71+ param.tag, DURATION_US(TIME_NOW() - startut));
72+ return HCCL_SUCCESS;
73+}
74+ 
75+bool ReduceScatterVExecutorBase::IsHugeData(u64 curSize) const
76+{
77+ bool hugeData = curSize * topoInfo_->userRankSize / HCCL_INTERNODE_MAX_DATA_RATE > RDMA_SEND_MAX_SIZE
78+ || curSize > SDMA_SEND_MAX_SIZE;
79+ return hugeData;
80+}
81+ 
82+HcclResult ReduceScatterVExecutorBase::AcquireCommIfNeeded(const OpParam& param)
83+{
84+ if (param.engine == CommEngine::COMM_ENGINE_CPU_TS || param.engine == CommEngine::COMM_ENGINE_CPU) {
85+ int32_t ret = HcommAcquireComm(param.commName);
86+ CHK_PRT_RET(
87+ ret != HCCL_SUCCESS, HCCL_ERROR("[%s] [%s] HcommAcquireComm failed ", __func__, param.commName),
88+ static_cast<HcclResult>(ret));
89+ }
90+ return HCCL_SUCCESS;
91+}
92+ 
93+HcclResult ReduceScatterVExecutorBase::ReleaseCommIfNeeded(const OpParam& param)
94+{
95+ if (param.engine == CommEngine::COMM_ENGINE_CPU_TS || param.engine == CommEngine::COMM_ENGINE_CPU) {
96+ int32_t ret = HcommReleaseComm(param.commName);
97+ CHK_PRT_RET(
98+ ret != HCCL_SUCCESS, HCCL_ERROR("[%s] [%s] HcommReleaseComm failed ", __func__, param.commName),
99+ static_cast<HcclResult>(ret));
100+ }
101+ return HCCL_SUCCESS;
102+}
103+ 
104+HcclResult ReduceScatterVExecutorBase::RunLoop(const OpParam& param)
105+{
106+ // V变体: 本rank接收量为outputSize/unitSize, 总发送量为inputSize
107+ u64 totalRecvCount = param.outputSize / unitSize_;
108+ u64 totalRecvSize = param.outputSize;
109+ 
110+ u8* curUserInputPtr = static_cast<u8*>(param.inputPtr);
111+ u8* curUserOutputPtr = static_cast<u8*>(param.outputPtr);
112+ auto cclInputMem = algResource_->cclInputMem;
113+ auto cclOutputMem = algResource_->cclOutputMem;
114+ CHK_PRT_RET(
115+ (cclInputMem.size == 0), HCCL_ERROR("[ReduceScatterVExecutorBase][RunLoop]cclBuffer size is zero"),
116+ HCCL_E_PARA);
117+ 
118+ CHK_RET(AcquireCommIfNeeded(param));
119+ 
120+ u64 curRecvCount = totalRecvCount;
121+ u64 curRecvSize = totalRecvSize;
122+ // V变体: 发送总量为inputSize (按各rank的sendCounts/sendDispls布局)
123+ u64 curSendSize = param.inputSize;
124+ 
125+#ifndef AICPU_COMPILE
126+ if (!IsHugeData(curRecvSize)) {
127+ CHK_RET(static_cast<HcclResult>(HcommBatchModeStart(param.algTag)));
128+ }
129+#endif
130+ // RunLoop 构造 curInputMem 前校验容量
131+ CHK_PRT_RET(
132+ curSendSize > cclInputMem.size,
133+ HCCL_ERROR("[RunLoop]inputSize[%llu] exceeds cclInputMem size[%llu]", curSendSize, cclInputMem.size),
134+ HCCL_E_PARA);
135+ // scratch 实际可用区域为整个 cclInputMem(CCL buffer 前半区), 声明大小必须传真实容量而非 curSendSize:
136+ // 模板以 rankSize*localStrideSize(按 max 切片对齐)为 scratch 需求, 小消息时该需求可大于 inputSize,
137+ // 若按 curSendSize 声明会使 CheckScratchCapacity 误报容量不足
138+ HcclMem curInputMem{cclInputMem.type, cclInputMem.addr, cclInputMem.size};
139+ HcclMem curOutputMem{cclOutputMem.type, cclOutputMem.addr, curRecvSize};
140+ 
141+ ExecMem execMem;
142+ execMem.count = curRecvCount;
143+ execMem.inputMem = curInputMem;
144+ execMem.outputMem = curOutputMem;
145+ execMem.inputPtr = curUserInputPtr;
146+ execMem.outputPtr = curUserOutputPtr;
147+ 
148+ HCCL_DEBUG(
149+ "[ReduceScatterVExecutorBase][RunLoop] curUserInputPtr[%p], curUserOutputPtr[%p],"
150+ " curRecvCount[%llu], curRecvSize[%llu], curSendSize[%llu], inputPtr[%p], outputPtr[%p]",
151+ curUserInputPtr, curUserOutputPtr, curRecvCount, curRecvSize, curSendSize, curInputMem.addr, curOutputMem.addr);
152+ 
153+ CHK_RET(KernelRun(param, execMem));
154+ 
155+#ifndef AICPU_COMPILE
156+ if (!IsHugeData(curRecvSize)) {
157+ CHK_RET(static_cast<HcclResult>(HcommBatchModeEnd(param.algTag)));
158+ }
159+#endif
160+ CHK_RET(ReleaseCommIfNeeded(param));
161+ return HCCL_SUCCESS;
162+}
163+ 
164+HcclResult ReduceScatterVExecutorBase::CalcResRequest(
165+ HcclComm comm, const OpParam& param, TopoInfo* topoInfo, AlgHierarchyInfo& algHierarchyInfo,
166+ AlgResourceRequest& resourceRequest, AlgType& algType)
167+{
168+ return HCCL_SUCCESS;
169+}
170+ 
171+HcclResult ReduceScatterVExecutorBase::PrepareDataSliceV(
172+ const u64* sendCounts, const u64* sendDispls, u32 unitSize, u32 sliceNum, std::vector<Slice>& dataSlice)
173+{
174+ CHK_PRT_RET(
175+ (sliceNum == 0), HCCL_ERROR("[ReduceScatterVExecutorBase][PrepareDataSliceV]sliceNum is zero."), HCCL_E_PARA);
176+ 
177+ CHK_PTR_NULL(sendCounts);
178+ CHK_PTR_NULL(sendDispls);
179+ dataSlice.resize(sliceNum);
180+ for (u32 i = 0; i < sliceNum; i++) {
181+ dataSlice[i].size = sendCounts[i] * unitSize;
182+ dataSlice[i].offset = sendDispls[i] * unitSize;
183+ HCCL_DEBUG(
184+ "[ReduceScatterVExecutorBase][PrepareDataSliceV] slice[%u]: offset[%llu] size[%llu]", i,
185+ dataSlice[i].offset, dataSlice[i].size);
186+ }
187+ return HCCL_SUCCESS;
188+}
189+ 
190+} // namespace ops_hccl_experimental
@@ -0,0 +1,66 @@
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 REDUCE_SCATTER_V_EXECUTOR_H
12+#define REDUCE_SCATTER_V_EXECUTOR_H
13+ 
14+#include "alg_param.h"
15+#include "executor_base.h"
16+#include "config_log.h"
17+#include "alg_template_base_experimental.h"
18+ 
19+namespace ops_hccl_experimental {
20+using ops_hccl::AlgHierarchyInfo;
21+using ops_hccl::AlgResourceCtx;
22+using ops_hccl::AlgResourceRequest;
23+using ops_hccl::AlgType;
24+using ops_hccl::ChannelInfo;
25+using ops_hccl::ExecutorBase;
26+using ops_hccl::OpParam;
27+using ops_hccl::Slice;
28+using ops_hccl::TopoInfo;
29+ 
30+// BIRS-V 线程数单一来源: executor 侧资源申请(slaveThreadNum/notify)与模板侧 notify 同步必须一致,
31+// 两侧统一引用本常量, 禁止再各自定义
32+constexpr u32 BIRS_THREAD_NUM = 2;
33+ 
34+class ReduceScatterVExecutorBase : public ExecutorBase {
35+public:
36+ explicit ReduceScatterVExecutorBase();
37+ ~ReduceScatterVExecutorBase() override = default;
38+ 
39+ HcclResult Orchestrate(const OpParam& param, AlgResourceCtx* resCtx) override;
40+ 
41+ /* *************** 资源计算 *************** */
42+ HcclResult CalcResRequest(
43+ HcclComm comm, const OpParam& param, TopoInfo* topoInfo, AlgHierarchyInfo& algHierarchyInfo,
44+ AlgResourceRequest& resourceRequest, AlgType& algType) override;
45+ 
46+protected:
47+ // 用于需要Loop的Executor
48+ virtual HcclResult RunLoop(const OpParam& param);
49+ 
50+ /* *************** 通用工具 *************** */
51+ // 根据变长counts/displs准备每个rank的数据切片(变长)
52+ HcclResult PrepareDataSliceV(
53+ const u64* sendCounts, const u64* sendDispls, u32 unitSize, u32 sliceNum, std::vector<Slice>& dataSlice);
54+ bool IsHugeData(u64 curSize) const;
55+ HcclResult AcquireCommIfNeeded(const OpParam& param);
56+ HcclResult ReleaseCommIfNeeded(const OpParam& param);
57+ 
58+ ThreadHandle thread_ = 0;
59+ std::vector<ThreadHandle> slaveThreads_;
60+ std::vector<std::vector<ChannelInfo>> channels_;
61+ u32 unitSize_;
62+};
63+ 
64+} // namespace ops_hccl_experimental
65+ 
66+#endif
@@ -0,0 +1,19 @@
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+set(src_list
11+ ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter_v_birs_inter.cc
12+ ${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter_v_birs.cc
13+)
14+ 
15+if(TARGET hccl)
16+ target_sources(hccl PRIVATE
17+ ${src_list}
18+ )
19+endif()
@@ -0,0 +1,304 @@
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 "alg_template_register.h"
12+#include "reduce_scatter_v_birs.h"
13+#include "reduce_scatter_v_executor_base.h"
14+#include <algorithm>
15+ 
16+namespace ops_hccl_experimental {
17+ReduceScatterVBIRS::ReduceScatterVBIRS() : AlgTemplateBaseExperimental() {}
18+ 
19+ReduceScatterVBIRS::~ReduceScatterVBIRS() {}
20+ 
21+HcclResult ReduceScatterVBIRS::Prepare(u32 interRank, u32 interRankSize)
22+{
23+ interRank_ = interRank;
24+ interRankSize_ = interRankSize;
25+ return HCCL_SUCCESS;
26+}
27+ 
28+HcclResult ReduceScatterVBIRS::Prepare(
29+ HcclMem& inputMem, HcclMem& outputMem, HcclMem& scratchMem, const u64 count, const HcclDataType dataType,
30+ ThreadHandle thread, const std::vector<ThreadHandle>& slaveThreads, const HcclReduceOp reductionOp, const u32 root,
31+ const std::vector<Slice>& slices, const u64 baseOffset, const bool disableDMAReduce)
32+{
33+ mainThread = thread;
34+ subThreads = slaveThreads;
35+ AlgTemplateBase::Prepare(
36+ inputMem, outputMem, scratchMem, count, dataType, thread, reductionOp, root, slices, baseOffset,
37+ disableDMAReduce);
38+ return HCCL_SUCCESS;
39+}
40+ 
41+HcclResult ReduceScatterVBIRS::LocalReduceCCLToCCL(u64 srcOffset, u64 dstOffset, u64 size, ThreadHandle thread)
42+{
43+ void* srcSlice = static_cast<void*>(static_cast<u8*>(scratchMem_.addr) + srcOffset);
44+ void* dstSlice = static_cast<void*>(static_cast<u8*>(scratchMem_.addr) + dstOffset);
45+ CHK_RET(static_cast<HcclResult>(HcommLocalReduceOnThread(
46+ thread, dstSlice, srcSlice, size / unitSize, static_cast<HcommDataType>(dataType_),
47+ static_cast<HcommReduceOp>(reductionOp_))));
48+ return HCCL_SUCCESS;
49+}
50+ 
51+void ReduceScatterVBIRS::CalcLocalStrideSize(const u32 rankSize)
52+{
53+ u64 maxSliceSize = 0;
54+ for (u32 i = 0; i < rankSize; i++) {
55+ maxSliceSize = std::max(maxSliceSize, slices_[i].size);
56+ }
57+ localStrideSize = RoundUpWithDivisor(maxSliceSize, HCCL_MIN_SLICE_ALIGN_910B);
58+}
59+ 
60+HcclResult ReduceScatterVBIRS::CheckScratchCapacity(u32 rankSize)
61+{
62+ u64 scratchDemand = static_cast<u64>(rankSize) * localStrideSize;
63+ CHK_PRT_RET(
64+ scratchDemand > scratchMem_.size,
65+ HCCL_ERROR(
66+ "[ReduceScatterVBIRS][CheckScratchCapacity]scratch demand[%llu] exceeds scratchMem size[%llu]",
67+ scratchDemand, scratchMem_.size),
68+ HCCL_E_PARA);
69+ return HCCL_SUCCESS;
70+}
71+ 
72+void ReduceScatterVBIRS::GetNotifyIdxMainToSub(std::vector<u32>& notifyIdxMainToSub)
73+{
74+ notifyIdxMainToSub.clear();
75+ u32 slaveThreadNum = BIRS_THREAD_NUM;
76+ for (u32 slaveThreadIdx = 0; slaveThreadIdx < slaveThreadNum; slaveThreadIdx++) {
77+ notifyIdxMainToSub.push_back(0);
78+ }
79+}
80+ 
81+void ReduceScatterVBIRS::GetNotifyIdxSubToMain(std::vector<u32>& notifyIdxSubToMain)
82+{
83+ notifyIdxSubToMain.clear();
84+ u32 notifyNum = BIRS_THREAD_NUM;
85+ for (u32 notifyIdx = 0; notifyIdx < notifyNum; notifyIdx++) {
86+ notifyIdxSubToMain.push_back(notifyIdx);
87+ }
88+}
89+ 
90+HcclResult ReduceScatterVBIRS::PrepareSlicesData(const u32 unitSize, const u64 totalCount, const u32 rankSize)
91+{
92+ // V变体: 切片由executor按sendCounts/sendDispls提前构造并经Prepare传入, 此处仅作兜底(均匀)
93+ slices_.resize(rankSize);
94+ u64 sliceSize = totalCount * unitSize;
95+ 
96+ for (u32 i = 0; i < rankSize; i++) {
97+ slices_[i].offset = i * sliceSize;
98+ slices_[i].size = sliceSize;
99+ HCCL_DEBUG(" default slice[%u]: offset: [%llu] size[%llu]", i, i * sliceSize, sliceSize);
100+ }
101+ return HCCL_SUCCESS;
102+}
103+ 
104+HcclResult ReduceScatterVBIRS::Preprocess(const u32 rank, const u32 rankSize, std::vector<ChannelInfo>& channels)
105+{
106+ if (rankSize == 1) {
107+ if (inputMem_.addr != outputMem_.addr) {
108+ // V变体: 本rank接收量为 slices_[rank].size
109+ CHK_RET(static_cast<HcclResult>(HcommLocalCopyOnThread(
110+ thread_, outputMem_.addr, static_cast<u8*>(inputMem_.addr) + slices_[rank].offset,
111+ slices_[rank].size)));
112+ }
113+ return HCCL_SUCCESS;
114+ }
115+ 
116+ if (channels.size() < rankSize) {
117+ HCCL_ERROR(
118+ "[ReduceScatterVBIRS][RunAsync]rank[%u] linksize[%llu] is less than rankSize[%u]", rank, channels.size(),
119+ rankSize);
120+ return HCCL_E_INTERNAL;
121+ }
122+ 
123+ unitSize = DataUnitSize(dataType_);
124+ if (unitSize == 0) {
125+ HCCL_ERROR("[ReduceScatterVBIRS][RunAsync]rank[%u] unit data size is zero", rank);
126+ return HCCL_E_INTERNAL;
127+ }
128+ if (slices_.size() == 0) {
129+ CHK_RET(PrepareSlicesData(unitSize, count_, rankSize));
130+ }
131+ return HCCL_SUCCESS;
132+}
133+ 
134+HcclResult ReduceScatterVBIRS::HCCSProcessMainLoop(
135+ u32 round, const u32 rank, const u32 rankSize, u32 rankSizeX_, u64 localStrideSize)
136+{
137+ if (round != 0) {
138+ CHK_RET(static_cast<HcclResult>(
139+ HcommChannelNotifyRecordOnThread(subThreads[0], hccs_links[round - 1].handle, NOTIFY_IDX_ACK)));
140+ CHK_RET(static_cast<HcclResult>(HcommChannelNotifyWaitOnThread(
141+ subThreads[0], hccs_links_reversed[round - 1].handle, NOTIFY_IDX_ACK, CUSTOM_TIMEOUT)));
142+ u64 localOffsetByte = hccs_ranks[round - 1] / rankSizeX_ * localStrideSize;
143+ u64 remoteOffsetByte = ((rankSize / rankSizeX_) + rank / rankSizeX_) * localStrideSize;
144+ void* src = static_cast<void*>(static_cast<u8*>(scratchMem_.addr) + localOffsetByte);
145+ void* dst = static_cast<void*>(static_cast<u8*>(hccs_links[round - 1].remoteOutput.addr) + remoteOffsetByte);
146+ 
147+ // V变体: 传输量为目标rank(hccs_ranks[round-1])对应的变长切片大小
148+ u64 curSliceSize = slices_[hccs_ranks[round - 1]].size;
149+ CHK_RET(static_cast<HcclResult>(
150+ HcommWriteOnThread(subThreads[0], hccs_links[round - 1].handle, dst, src, curSliceSize)));
151+ 
152+ CHK_RET(static_cast<HcclResult>(
153+ HcommChannelNotifyRecordOnThread(subThreads[0], hccs_links[round - 1].handle, NOTIFY_IDX_DATA_SIGNAL)));
154+ CHK_RET(static_cast<HcclResult>(HcommChannelNotifyWaitOnThread(
155+ subThreads[0], hccs_links_reversed[round - 1].handle, NOTIFY_IDX_DATA_SIGNAL, CUSTOM_TIMEOUT)));
156+ }
157+ return HCCL_SUCCESS;
158+}
159+ 
160+HcclResult ReduceScatterVBIRS::SIOProcessMainLoop(
161+ u32 round, const u32 rank, const u32 rankSize, u32 rankSizeX_, u64 localStrideSize)
162+{
163+ CHK_RET(static_cast<HcclResult>(HcommChannelNotifyRecordOnThread(mainThread, sio_link.handle, NOTIFY_IDX_ACK)));
164+ CHK_RET(static_cast<HcclResult>(
165+ HcommChannelNotifyWaitOnThread(mainThread, sio_link.handle, NOTIFY_IDX_ACK, CUSTOM_TIMEOUT)));
166+ 
167+ // V变体: 本轮处理的target rank及其变长切片
168+ u32 targetRank = (round != hccs_ranks.size()) ? hccs_neighbour_rank[round] : sio_rank;
169+ u64 localOffsetByte = slices_[targetRank].offset;
170+ u64 remoteOffsetByte = (round != hccs_ranks.size()) ? (hccs_ranks[round] / rankSizeX_ * localStrideSize) :
171+ rank / rankSizeX_ * localStrideSize;
172+ void* src = static_cast<void*>(static_cast<u8*>(inputMem_.addr) + localOffsetByte);
173+ void* dst = static_cast<void*>(static_cast<u8*>(sio_link.remoteOutput.addr) + remoteOffsetByte);
174+ 
175+ u64 curSliceSize = slices_[targetRank].size;
176+ CHK_RET(static_cast<HcclResult>(HcommWriteReduceOnThread(
177+ mainThread, sio_link.handle, dst, src, curSliceSize / unitSize, static_cast<HcommDataType>(dataType_),
178+ static_cast<HcommReduceOp>(reductionOp_))));
179+ 
180+ CHK_RET(
181+ static_cast<HcclResult>(HcommChannelNotifyRecordOnThread(mainThread, sio_link.handle, NOTIFY_IDX_DATA_SIGNAL)));
182+ CHK_RET(static_cast<HcclResult>(
183+ HcommChannelNotifyWaitOnThread(mainThread, sio_link.handle, NOTIFY_IDX_DATA_SIGNAL, CUSTOM_TIMEOUT)));
184+ 
185+ return HCCL_SUCCESS;
186+}
187+ 
188+HcclResult ReduceScatterVBIRS::LocalCopyMainLoop(
189+ u32 round, const u32 rank, const u32 rankSize, u32 rankSizeX_, u64 localStrideSize)
190+{
191+ if (round < hccs_ranks.size()) {
192+ u32 rank_idx = (round < hccs_ranks.size() - 1) ? hccs_ranks[round + 1] : rank;
193+ void* srcSlice = static_cast<void*>(static_cast<u8*>(inputMem_.addr) + slices_[rank_idx].offset);
194+ void* dstSlice
195+ = static_cast<void*>(static_cast<u8*>(scratchMem_.addr) + rank_idx / rankSizeX_ * localStrideSize);
196+ // V变体: 拷贝量为rank_idx对应的变长切片大小
197+ u64 curSliceSize = slices_[rank_idx].size;
198+ CHK_RET(static_cast<HcclResult>(HcommLocalCopyOnThread(subThreads[1], dstSlice, srcSlice, curSliceSize)));
199+ }
200+ return HCCL_SUCCESS;
201+}
202+ 
203+HcclResult ReduceScatterVBIRS::FinalStep(const u32 rank, const u32 rankSize, u32 rankSizeX_, u64 localStrideSize)
204+{
205+ // V变体: 本rank最终接收量为 slices_[rank].size
206+ u64 localSliceSize = slices_[rank].size;
207+ std::vector<u32> vec;
208+ for (u32 i = 0; i < (rankSize / rankSizeX_); i++) {
209+ if (i == (rank / rankSizeX_)) {
210+ vec.push_back((rank / rankSizeX_) * localStrideSize);
211+ } else {
212+ vec.push_back(((rankSize / rankSizeX_) + i) * localStrideSize);
213+ }
214+ }
215+ // Tree local reduce
216+ auto ind = rankSize / rankSizeX_;
217+ for (u32 stride = 1; stride < ind; stride *= 2) {
218+ for (u32 i = stride; i < ind; i += stride * 2) {
219+ CHK_RET(LocalReduceCCLToCCL(vec[i], vec[i - stride], localSliceSize, mainThread));
220+ }
221+ }
222+ // Local copy to output
223+ void* srcSlice = static_cast<void*>(static_cast<u8*>(scratchMem_.addr) + vec[0]);
224+ void* dstSlice = static_cast<void*>(static_cast<u8*>(outputMem_.addr));
225+ CHK_RET(static_cast<HcclResult>(HcommLocalCopyOnThread(mainThread, dstSlice, srcSlice, localSliceSize)));
226+ return HCCL_SUCCESS;
227+}
228+ 
229+HcclResult ReduceScatterVBIRS::RunAsync(const u32 rank, const u32 rankSize, std::vector<ChannelInfo>& channels)
230+{
231+ HCCL_INFO(
232+ "ReduceScatterVBIRS run: rank[%u] rankSize[%u] inputMem[%p] to outputMem[%p] count[%llu]", rank, rankSize,
233+ inputMem_.addr, outputMem_.addr, count_);
234+ 
235+ hccs_ranks.clear();
236+ hccs_neighbour_rank.clear();
237+ hccs_links.clear();
238+ 
239+ CHK_RET(Preprocess(rank, rankSize, channels));
240+ 
241+ if (rankSize % rankSizeX_ != 0) {
242+ HCCL_ERROR(
243+ "[ReduceScatterVBIRS][RunAsync]rankSize[%u] is not evenly divisible by rankSizeX_[%u]", rankSize,
244+ rankSizeX_);
245+ return HCCL_E_INTERNAL;
246+ }
247+ 
248+ sio_rank = rank ^ 1;
249+ sio_link = channels[sio_rank];
250+ 
251+ for (u32 i = 1; i < rankSize / rankSizeX_; ++i) {
252+ u32 current_hccs_rank = (rank + rankSizeX_ * i) % rankSize;
253+ hccs_ranks.push_back(current_hccs_rank);
254+ hccs_neighbour_rank.push_back(current_hccs_rank ^ 1);
255+ hccs_links.push_back(channels[hccs_ranks[i - 1]]);
256+ }
257+ hccs_links_reversed.assign(hccs_links.rbegin(), hccs_links.rend());
258+ 
259+ CalcLocalStrideSize(rankSize);
260+ 
261+ CHK_RET(CheckScratchCapacity(rankSize));
262+ 
263+ // MainRecordSub + SubWaitMain
264+ GetNotifyIdxMainToSub(notifyIdxMainToSub_);
265+ CHK_RET(PreSyncInterThreads(mainThread, subThreads, notifyIdxMainToSub_));
266+ 
267+ // V变体: 初始拷贝量取 hccs_ranks[0] 对应的变长切片
268+ if (!hccs_ranks.empty()) {
269+ u64 initSliceSize = slices_[hccs_ranks[0]].size;
270+ void* srcSlice = static_cast<void*>(static_cast<u8*>(inputMem_.addr) + slices_[hccs_ranks[0]].offset);
271+ void* dstSlice
272+ = static_cast<void*>(static_cast<u8*>(scratchMem_.addr) + hccs_ranks[0] / rankSizeX_ * localStrideSize);
273+ CHK_RET(static_cast<HcclResult>(HcommLocalCopyOnThread(mainThread, dstSlice, srcSlice, initSliceSize)));
274+ }
275+ 
276+ GetNotifyIdxSubToMain(notifyIdxSubToMain_);
277+ CHK_RET(PostSyncInterThreads(mainThread, subThreads, notifyIdxSubToMain_));
278+ for (u32 round = 0; round < hccs_ranks.size() + 1; round++) {
279+ // MainRecordSub + SubWaitMain
280+ CHK_RET(PreSyncInterThreads(mainThread, subThreads, notifyIdxMainToSub_));
281+ 
282+ CHK_RET(HCCSProcessMainLoop(round, rank, rankSize, rankSizeX_, localStrideSize));
283+ 
284+ CHK_RET(SIOProcessMainLoop(round, rank, rankSize, rankSizeX_, localStrideSize));
285+ 
286+ CHK_RET(LocalCopyMainLoop(round, rank, rankSize, rankSizeX_, localStrideSize));
287+ 
288+ // SubRecordMain + MainWaitSub
289+ CHK_RET(PostSyncInterThreads(mainThread, subThreads, notifyIdxSubToMain_));
290+ }
291+ 
292+ // MainRecordSub + SubWaitMain
293+ CHK_RET(PreSyncInterThreads(mainThread, subThreads, notifyIdxMainToSub_));
294+ 
295+ CHK_RET(FinalStep(rank, rankSize, rankSizeX_, localStrideSize));
296+ 
297+ CHK_RET(PostSyncInterThreads(mainThread, subThreads, notifyIdxSubToMain_));
298+ 
299+ HCCL_INFO("ReduceScatterVBIRS finished: rank[%u]", rank);
300+ return HCCL_SUCCESS;
301+}
302+ 
303+REGISTER_TEMPLATE(TEMPLATE_REDUCE_SCATTER_V_BIRS, ReduceScatterVBIRS);
304+} // namespace ops_hccl_experimental
@@ -0,0 +1,94 @@
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 REDUCE_SCATTER_V_BIRS_H
12+#define REDUCE_SCATTER_V_BIRS_H
13+ 
14+#include "alg_template_base.h"
15+#include "alg_data_trans_wrapper.h"
16+#include "alg_template_base_experimental.h"
17+ 
18+namespace ops_hccl_experimental {
19+using ops_hccl::AlgTemplateBase;
20+using ops_hccl::AlgTemplateRegistry;
21+using ops_hccl::ChannelInfo;
22+using ops_hccl::CUSTOM_TIMEOUT;
23+using ops_hccl::DefaultTemplateCreator;
24+using ops_hccl::HCCL_MIN_SLICE_ALIGN_910B;
25+using ops_hccl::NOTIFY_IDX_ACK;
26+using ops_hccl::NOTIFY_IDX_DATA_SIGNAL;
27+using ops_hccl::PostSyncInterThreads;
28+using ops_hccl::PreSyncInterThreads;
29+using ops_hccl::RoundUpWithDivisor;
30+using ops_hccl::Slice;
31+using ops_hccl::TemplateType;
32+ 
33+// ReduceScatterV 的 BIRS 算法模板(变长: 各rank接收量可不同)
34+// 与均匀 BIRS 的区别: slices_ 由外部传入变长切片(offset/size按sendDispls/sendCounts),
35+// 每轮传输使用目标rank对应的 slices_[rank].size, scratch统一以 maxSliceSize 向上对齐的 localStrideSize 为步长
36+// 前置条件: comm 内所有 rank 传入一致的 sendCounts/sendDispls 数组(集合通信语义约定, 与老流程一致)。
37+// 满足该条件时, SIO 链路两端针对同一目标 rank 的 slices_[target].size 必然相等,
38+// 每轮 write-reduce 两端长度一致, 对所有规约类型结果正确。
39+class ReduceScatterVBIRS : public AlgTemplateBaseExperimental {
40+public:
41+ explicit ReduceScatterVBIRS();
42+ 
43+ ~ReduceScatterVBIRS() override;
44+ 
45+ // should be called soon after template ReduceScatterVBIRS instance created
46+ HcclResult Prepare(u32 interRank, u32 interRankSize) override;
47+ 
48+ HcclResult Prepare(
49+ HcclMem& inputMem, HcclMem& outputMem, HcclMem& scratchMem, const u64 count, const HcclDataType dataType,
50+ ThreadHandle thread, const std::vector<ThreadHandle>& slaveThreads, const HcclReduceOp reductionOp,
51+ const u32 root, const std::vector<Slice>& slices, const u64 baseOffset, const bool disableDMAReduce) override;
52+ 
53+ HcclResult RunAsync(const u32 rank, const u32 rankSize, std::vector<ChannelInfo>& channels) override;
54+ 
55+protected:
56+ void GetNotifyIdxMainToSub(std::vector<u32>& notifyIdxMainToSub);
57+ void GetNotifyIdxSubToMain(std::vector<u32>& notifyIdxSubToMain);
58+ HcclResult LocalReduceCCLToCCL(u64 srcOffset, u64 dstOffset, u64 size, ThreadHandle thread);
59+ // V变体: scratch步长以各rank切片大小的最大值向上对齐, 保证任意rank数据放入一个步长槽
60+ virtual void CalcLocalStrideSize(const u32 rankSize);
61+ virtual HcclResult CheckScratchCapacity(u32 rankSize);
62+ 
63+ virtual HcclResult Preprocess(const u32 rank, const u32 rankSize, std::vector<ChannelInfo>& channels);
64+ HcclResult PrepareSlicesData(const u32 unitSize, const u64 totalCount, const u32 rankSize);
65+ // 主循环各步骤: localStrideSize 为 scratch 统一步长, 各rank实际传输量取自 slices_[target].size
66+ HcclResult HCCSProcessMainLoop(u32 round, const u32 rank, const u32 rankSize, u32 rankSizeX_, u64 localStrideSize);
67+ HcclResult SIOProcessMainLoop(u32 round, const u32 rank, const u32 rankSize, u32 rankSizeX_, u64 localStrideSize);
68+ HcclResult LocalCopyMainLoop(u32 round, const u32 rank, const u32 rankSize, u32 rankSizeX_, u64 localStrideSize);
69+ HcclResult FinalStep(const u32 rank, const u32 rankSize, u32 rankSizeX_, u64 localStrideSize);
70+ 
71+ u32 interRank_; // comm内的rank排序
72+ u32 interRankSize_; // 本comm内ranksize总数
73+ u32 sio_rank;
74+ std::vector<u32> hccs_ranks;
75+ std::vector<u32> hccs_neighbour_rank;
76+ 
77+ std::vector<ChannelInfo> hccs_links;
78+ std::vector<ChannelInfo> hccs_links_reversed;
79+ ChannelInfo sio_link;
80+ 
81+ u64 localStrideSize;
82+ 
83+ u32 unitSize;
84+ u32 rankSizeX_ = 2;
85+ ThreadHandle mainThread;
86+ std::vector<ThreadHandle> subThreads;
87+ std::vector<u32> notifyIdxMainToSub_;
88+ std::vector<u32> notifyIdxSubToMain_;
89+ 
90+private:
91+};
92+ 
93+} // namespace ops_hccl_experimental
94+#endif /* * REDUCE_SCATTER_V_BIRS_H */
@@ -0,0 +1,352 @@
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 "alg_template_register.h"
12+#include "reduce_scatter_v_birs_inter.h"
13+#include <algorithm>
14+ 
15+namespace ops_hccl_experimental {
16+ 
17+ReduceScatterVBIRSInter::ReduceScatterVBIRSInter() : ReduceScatterVBIRS() {}
18+ 
19+ReduceScatterVBIRSInter::~ReduceScatterVBIRSInter() {}
20+ 
21+HcclResult ReduceScatterVBIRSInter::Prepare(u32 serverNum, u32 interRankSize)
22+{
23+ serverNum_ = serverNum;
24+ CHK_PRT_RET(
25+ serverNum == 0 || interRankSize % serverNum != 0,
26+ HCCL_ERROR("[Prepare] interRankSize[%u] not divisible by serverNum[%u]", interRankSize, serverNum),
27+ HCCL_E_PARA);
28+ intraRankSize_ = interRankSize / serverNum_;
29+ return HCCL_SUCCESS;
30+}
31+ 
32+HcclResult ReduceScatterVBIRSInter::LocalCopyPreproc(ThreadHandle& thread, const u32 rank, u64 localStrideSize)
33+{
34+ void* src;
35+ void* dst;
36+ u32 cnt = 0;
37+ // V变体: 从input按各rank的slices_[rank].offset/size搬运到scratch(步长为localStrideSize)
38+ for (u32 round = 0; round < intraRankSize_ / 2; round++) {
39+ for (u32 i = 0; i < serverNum_; i++) {
40+ u32 rankIdx = 2 * round + i * intraRankSize_ + rank % 2;
41+ if (slices_[rankIdx].size != 0) {
42+ src = static_cast<void*>(static_cast<u8*>(inputMem_.addr) + slices_[rankIdx].offset);
43+ dst = static_cast<void*>(static_cast<u8*>(scratchMem_.addr) + cnt * localStrideSize);
44+ CHK_RET(static_cast<HcclResult>(HcommLocalCopyOnThread(thread, dst, src, slices_[rankIdx].size)));
45+ }
46+ cnt++;
47+ }
48+ }
49+ 
50+ for (u32 c = 0; c < serverNum_; c++) {
51+ u32 rankIdx = hccs_neighbour_rank[0] % intraRankSize_ + c * intraRankSize_;
52+ if (slices_[rankIdx].size == 0) {
53+ continue;
54+ }
55+ void* srcSlice = static_cast<void*>(static_cast<u8*>(inputMem_.addr) + slices_[rankIdx].offset);
56+ void* dstSlice = static_cast<void*>(
57+ static_cast<u8*>(scratchMem_.addr) + (intraRankSize_)*localStrideSize * serverNum_ + c * localStrideSize);
58+ CHK_RET(static_cast<HcclResult>(HcommLocalCopyOnThread(thread, dstSlice, srcSlice, slices_[rankIdx].size)));
59+ }
60+ 
61+ return HCCL_SUCCESS;
62+}
63+ 
64+void ReduceScatterVBIRSInter::CalcLocalStrideSize(const u32 rankSize)
65+{
66+ u64 maxSliceSize = 0;
67+ for (u32 i = 0; i < rankSize; i++) {
68+ maxSliceSize = std::max(maxSliceSize, slices_[i].size);
69+ }
70+ // 保持不对齐: 与均匀 BIRS inter 一致(reduce_scatter_birs_inter.cc RunAsync 中 localStrideSize = sliceSize
71+ // 同样未做对齐, 已验证可用)。HCCS WRITE 按字节、SIO write-reduce 按元素搬运, 长度均为 unitSize 整数倍,
72+ // 无额外对齐约束; 若按 HCCL_MIN_SLICE_ALIGN_910B(16KB) 对齐会成倍放大 scratch 需求,
73+ // 导致小消息场景 CheckScratchCapacity 误报容量错误
74+ localStrideSize = maxSliceSize;
75+}
76+ 
77+HcclResult ReduceScatterVBIRSInter::CheckScratchCapacity(u32 rankSize)
78+{
79+ u64 scratchDemand = (static_cast<u64>(rankSize) + static_cast<u64>(2) * serverNum_) * localStrideSize;
80+ CHK_PRT_RET(
81+ scratchDemand > scratchMem_.size,
82+ HCCL_ERROR(
83+ "[ReduceScatterVBIRSInter][CheckScratchCapacity]scratch demand[%llu] exceeds scratchMem size[%llu]",
84+ scratchDemand, scratchMem_.size),
85+ HCCL_E_PARA);
86+ return HCCL_SUCCESS;
87+}
88+ 
89+HcclResult ReduceScatterVBIRSInter::Preprocess(const u32 rank, const u32 rankSize, std::vector<ChannelInfo>& channels)
90+{
91+ HCCL_INFO(
92+ "ReduceScatterVBIRSInter run: rank[%u] rankSize[%u] inputMem[%p] to outputMem[%p] count[%llu]", rank, rankSize,
93+ inputMem_.addr, outputMem_.addr, count_);
94+ return ReduceScatterVBIRS::Preprocess(rank, rankSize, channels);
95+}
96+ 
97+HcclResult ReduceScatterVBIRSInter::HCCSIntraStep(
98+ u32 round, const u32 rank, const u32 rankSize, u32 rankSizeX_, u64 localStrideSize)
99+{
100+ // V变体: 按槽位精确WRITE(每槽只搬运 slices_[target].size, padding 区不参与传输),
101+ // 槽位 cnt 对应的目标 rank 为 hccs_ranks[round-1]%intra + cnt*intra
102+ if (round != 0) {
103+ CHK_RET(static_cast<HcclResult>(
104+ HcommChannelNotifyRecordOnThread(subThreads[0], hccs_links_reversed[round - 1].handle, NOTIFY_IDX_ACK)));
105+ CHK_RET(static_cast<HcclResult>(HcommChannelNotifyWaitOnThread(
106+ subThreads[0], hccs_links[round - 1].handle, NOTIFY_IDX_ACK, CUSTOM_TIMEOUT)));
107+ u64 localOffsetByte = hccs_ranks[round - 1] % intraRankSize_ / rankSizeX_ * localStrideSize * serverNum_;
108+ u64 remoteOffsetByte
109+ = ((intraRankSize_ / rankSizeX_) + rank % intraRankSize_ / rankSizeX_) * localStrideSize * serverNum_;
110+ u32 targetBase = hccs_ranks[round - 1] % intraRankSize_;
111+ for (u32 cnt = 0; cnt < serverNum_; cnt++) {
112+ u64 sliceSize = slices_[targetBase + cnt * intraRankSize_].size;
113+ if (sliceSize == 0) {
114+ continue;
115+ }
116+ void* src
117+ = static_cast<void*>(static_cast<u8*>(scratchMem_.addr) + localOffsetByte + cnt * localStrideSize);
118+ void* dst = static_cast<void*>(
119+ static_cast<u8*>(hccs_links[round - 1].remoteOutput.addr) + remoteOffsetByte + cnt * localStrideSize);
120+ 
121+ CHK_RET(static_cast<HcclResult>(
122+ HcommWriteOnThread(subThreads[0], hccs_links[round - 1].handle, dst, src, sliceSize)));
123+ }
124+ 
125+ CHK_RET(static_cast<HcclResult>(
126+ HcommChannelNotifyRecordOnThread(subThreads[0], hccs_links[round - 1].handle, NOTIFY_IDX_DATA_SIGNAL)));
127+ CHK_RET(static_cast<HcclResult>(HcommChannelNotifyWaitOnThread(
128+ subThreads[0], hccs_links_reversed[round - 1].handle, NOTIFY_IDX_DATA_SIGNAL, CUSTOM_TIMEOUT)));
129+ }
130+ return HCCL_SUCCESS;
131+}
132+ 
133+HcclResult ReduceScatterVBIRSInter::SIOIntraStep(
134+ u32 round, const u32 rank, const u32 rankSize, u32 rankSizeX_, u64 localStrideSize)
135+{
136+ // V变体: 按槽位精确write-reduce(每槽只规约 slices_[target].size, padding 区不参与规约,
137+ // 避免规约未初始化数据); 槽位 cnt 对应的目标 rank 为 targetRank%intra + cnt*intra,
138+ // 链路两端对同一目标 rank 使用相同切片长度(sendCounts/sendDispls 各rank一致)
139+ CHK_RET(static_cast<HcclResult>(HcommChannelNotifyRecordOnThread(mainThread, sio_link.handle, NOTIFY_IDX_ACK)));
140+ CHK_RET(static_cast<HcclResult>(
141+ HcommChannelNotifyWaitOnThread(mainThread, sio_link.handle, NOTIFY_IDX_ACK, CUSTOM_TIMEOUT)));
142+ 
143+ u64 localOffsetByte = intraRankSize_ + (round % 2);
144+ u64 remoteOffsetByte = (round != hccs_ranks.size()) ? ((hccs_ranks[round] % intraRankSize_) / rankSizeX_) :
145+ (rank % intraRankSize_ / rankSizeX_);
146+ localOffsetByte *= localStrideSize * serverNum_;
147+ remoteOffsetByte *= localStrideSize * serverNum_;
148+ u32 targetRank = (round != hccs_ranks.size()) ? hccs_neighbour_rank[round] : sio_rank;
149+ for (u32 cnt = 0; cnt < serverNum_; cnt++) {
150+ u64 sliceSize = slices_[targetRank % intraRankSize_ + cnt * intraRankSize_].size;
151+ if (sliceSize == 0) {
152+ continue;
153+ }
154+ void* src = static_cast<void*>(static_cast<u8*>(scratchMem_.addr) + localOffsetByte + cnt * localStrideSize);
155+ void* dst = static_cast<void*>(
156+ static_cast<u8*>(sio_link.remoteOutput.addr) + remoteOffsetByte + cnt * localStrideSize);
157+ 
158+ CHK_RET(static_cast<HcclResult>(HcommWriteReduceOnThread(
159+ mainThread, sio_link.handle, dst, src, sliceSize / unitSize, static_cast<HcommDataType>(dataType_),
160+ static_cast<HcommReduceOp>(reductionOp_))));
161+ }
162+ 
163+ CHK_RET(
164+ static_cast<HcclResult>(HcommChannelNotifyRecordOnThread(mainThread, sio_link.handle, NOTIFY_IDX_DATA_SIGNAL)));
165+ CHK_RET(static_cast<HcclResult>(
166+ HcommChannelNotifyWaitOnThread(mainThread, sio_link.handle, NOTIFY_IDX_DATA_SIGNAL, CUSTOM_TIMEOUT)));
167+ 
168+ return HCCL_SUCCESS;
169+}
170+ 
171+HcclResult ReduceScatterVBIRSInter::LocalCopyIntraStep(
172+ u32 round, const u32 rank, const u32 rankSize, u32 rankSizeX_, u64 localStrideSize)
173+{
174+ // V变体: 从input按各rank的slices_[rank].offset/size搬运到scratch(下一轮SIO用的ping-pong区)
175+ if (round < hccs_ranks.size()) {
176+ for (u32 cnt = 0; cnt < serverNum_; cnt++) {
177+ u32 rankIdx = (round < hccs_ranks.size() - 1) ?
178+ ((hccs_neighbour_rank[round + 1]) % intraRankSize_ + cnt * intraRankSize_) :
179+ ((sio_rank) % intraRankSize_ + cnt * intraRankSize_);
180+ if (slices_[rankIdx].size == 0) {
181+ continue;
182+ }
183+ u64 srcOffsetByte = slices_[rankIdx].offset;
184+ u64 dstOffsetByte
185+ = (intraRankSize_ + ((round + 1) % 2)) * localStrideSize * serverNum_ + cnt * localStrideSize;
186+ 
187+ void* srcSlice = static_cast<void*>(static_cast<u8*>(inputMem_.addr) + srcOffsetByte);
188+ void* dstSlice = static_cast<void*>(static_cast<u8*>(scratchMem_.addr) + dstOffsetByte);
189+ CHK_RET(static_cast<HcclResult>(
190+ HcommLocalCopyOnThread(subThreads[1], dstSlice, srcSlice, slices_[rankIdx].size)));
191+ }
192+ }
193+ 
194+ return HCCL_SUCCESS;
195+}
196+ 
197+HcclResult ReduceScatterVBIRSInter::PreprocInterServer(
198+ const u32 rank, const u32 rankSize, u32 rankSizeX_, u64 localStrideSize, std::vector<ChannelInfo>& channels)
199+{
200+ // V变体: 跨服务器前置阶段, 本rank最终接收量为 slices_[rank].size
201+ u64 localSliceSize = slices_[rank].size;
202+ vec_offsets.clear();
203+ for (u32 i = 0; i < (intraRankSize_ / rankSizeX_); i++) {
204+ if (i == (rank % intraRankSize_ / rankSizeX_)) {
205+ vec_offsets.push_back((rank % intraRankSize_ / rankSizeX_) * localStrideSize * serverNum_);
206+ } else {
207+ vec_offsets.push_back(((intraRankSize_ / rankSizeX_) + i) * localStrideSize * serverNum_);
208+ }
209+ }
210+ 
211+ // V变体: 树形规约按槽位精确执行(每槽只规约 slices_[target].size, padding 区不参与规约);
212+ // 各块槽位 cnt 对应的目标 rank 均为 rank%intra + cnt*intra, 切片长度跨块一致
213+ auto ind = intraRankSize_ / rankSizeX_;
214+ for (u32 stride = 1; stride < ind; stride *= 2) {
215+ for (u32 i = stride; i < ind; i += stride * 2) {
216+ for (u32 cnt = 0; cnt < serverNum_; cnt++) {
217+ u64 sliceSize = slices_[rank % intraRankSize_ + cnt * intraRankSize_].size;
218+ if (sliceSize == 0) {
219+ continue;
220+ }
221+ CHK_RET(LocalReduceCCLToCCL(
222+ vec_offsets[i] + cnt * localStrideSize, vec_offsets[i - stride] + cnt * localStrideSize, sliceSize,
223+ mainThread));
224+ }
225+ }
226+ }
227+ 
228+ u64 remoteOffsetByte = localStrideSize * serverNum_;
229+ u64 localOffsetByte = vec_offsets[0] + localStrideSize * (((rank) % rankSize) / intraRankSize_);
230+ void* src = static_cast<void*>(static_cast<u8*>(scratchMem_.addr) + localOffsetByte);
231+ void* dst = static_cast<void*>(static_cast<u8*>(scratchMem_.addr) + remoteOffsetByte);
232+ CHK_RET(static_cast<HcclResult>(HcommLocalCopyOnThread(mainThread, dst, src, localSliceSize)));
233+ return HCCL_SUCCESS;
234+}
235+ 
236+HcclResult ReduceScatterVBIRSInter::InterServer(
237+ const u32 rank, const u32 rankSize, u32 rankSizeX_, u64 localStrideSize, std::vector<ChannelInfo>& channels)
238+{
239+ // V变体: 跨服务器阶段, 本rank最终接收量为 slices_[rank].size
240+ u64 localSliceSize = slices_[rank].size;
241+ for (u32 round = 1; round < serverNum_; round++) {
242+ GetNotifyIdxMainToSub(notifyIdxMainToSub_);
243+ CHK_RET(PreSyncInterThreads(mainThread, subThreads, notifyIdxMainToSub_));
244+ CHK_RET(static_cast<HcclResult>(HcommChannelNotifyRecordOnThread(
245+ mainThread, channels[((serverNum_ - round) * intraRankSize_ + rank) % rankSize].handle, NOTIFY_IDX_ACK)));
246+ CHK_RET(static_cast<HcclResult>(HcommChannelNotifyWaitOnThread(
247+ mainThread, channels[((round)*intraRankSize_ + rank) % rankSize].handle, NOTIFY_IDX_ACK, CUSTOM_TIMEOUT)));
248+ u64 remoteOffsetByte = localStrideSize * serverNum_ + localStrideSize * round;
249+ // V变体: 本轮向 peerRank(同intra位置、其他server)转发其目标切片的本server内规约和,
250+ // 传输长度必须取 slices_[peerRank].size(目标rank的切片长度)而非本rank的 localSliceSize
251+ u32 peerRank = ((round)*intraRankSize_ + rank) % rankSize;
252+ u64 peerSliceSize = slices_[peerRank].size;
253+ u64 localOffsetByte = vec_offsets[0] + localStrideSize * (peerRank / intraRankSize_);
254+ void* src = static_cast<void*>(static_cast<u8*>(scratchMem_.addr) + localOffsetByte);
255+ void* dst = static_cast<void*>(static_cast<u8*>(channels[peerRank].remoteOutput.addr) + remoteOffsetByte);
256+ if (peerSliceSize != 0) {
257+ CHK_RET(static_cast<HcclResult>(
258+ HcommWriteOnThread(mainThread, channels[peerRank].handle, dst, src, peerSliceSize)));
259+ }
260+ CHK_RET(static_cast<HcclResult>(HcommChannelNotifyRecordOnThread(
261+ mainThread, channels[((round)*intraRankSize_ + rank) % rankSize].handle, NOTIFY_IDX_DATA_SIGNAL)));
262+ CHK_RET(static_cast<HcclResult>(HcommChannelNotifyWaitOnThread(
263+ mainThread, channels[((serverNum_ - round) * intraRankSize_ + rank) % rankSize].handle,
264+ NOTIFY_IDX_DATA_SIGNAL, CUSTOM_TIMEOUT)));
265+ GetNotifyIdxSubToMain(notifyIdxSubToMain_);
266+ CHK_RET(PostSyncInterThreads(mainThread, subThreads, notifyIdxSubToMain_));
267+ }
268+ u64 ptrSz = (serverNum_ == 1 ? vec_offsets[0] : localStrideSize * serverNum_);
269+ GetNotifyIdxMainToSub(notifyIdxMainToSub_);
270+ CHK_RET(PreSyncInterThreads(mainThread, subThreads, notifyIdxMainToSub_));
271+ for (u32 step = 1; step < serverNum_; step *= 2) {
272+ for (u32 i = 0; i + step < serverNum_; i += 2 * step) {
273+ CHK_RET(LocalReduceCCLToCCL(
274+ ptrSz + localStrideSize * (i + step), ptrSz + localStrideSize * i, localSliceSize, mainThread));
275+ }
276+ }
277+ void* srcSlice = static_cast<void*>(static_cast<u8*>(scratchMem_.addr) + ptrSz);
278+ void* dstSlice = static_cast<void*>(static_cast<u8*>(outputMem_.addr));
279+ CHK_RET(static_cast<HcclResult>(HcommLocalCopyOnThread(mainThread, dstSlice, srcSlice, localSliceSize)));
280+ return HCCL_SUCCESS;
281+}
282+ 
283+// scatter的入口函数
284+HcclResult ReduceScatterVBIRSInter::RunAsync(const u32 rank, const u32 rankSize, std::vector<ChannelInfo>& channels)
285+{
286+ hccs_ranks.clear();
287+ hccs_neighbour_rank.clear();
288+ hccs_links.clear();
289+ 
290+ CHK_RET(Preprocess(rank, rankSize, channels));
291+ 
292+ if (intraRankSize_ % rankSizeX_ != 0) {
293+ HCCL_ERROR(
294+ "[ReduceScatterVBIRSInter][RunAsync]intraRankSize_[%u] is not evenly divisible by rankSizeX_[%u]",
295+ intraRankSize_, rankSizeX_);
296+ return HCCL_E_INTERNAL;
297+ }
298+ 
299+ sio_rank = rank ^ 1;
300+ sio_link = channels[sio_rank];
301+ 
302+ for (u32 i = 1; i < intraRankSize_ / rankSizeX_; ++i) {
303+ u32 current_hccs_rank
304+ = (rank % intraRankSize_ + rankSizeX_ * i) % (intraRankSize_) + rank / intraRankSize_ * intraRankSize_;
305+ hccs_ranks.push_back(current_hccs_rank);
306+ hccs_neighbour_rank.push_back(current_hccs_rank ^ 1);
307+ hccs_links.push_back(channels[hccs_ranks[i - 1]]);
308+ }
309+ hccs_links_reversed.assign(hccs_links.rbegin(), hccs_links.rend());
310+ 
311+ CalcLocalStrideSize(rankSize);
312+ 
313+ CHK_RET(CheckScratchCapacity(rankSize));
314+ 
315+ // MainRecordSub + SubWaitMain
316+ GetNotifyIdxMainToSub(notifyIdxMainToSub_);
317+ CHK_RET(PreSyncInterThreads(mainThread, subThreads, notifyIdxMainToSub_));
318+ 
319+ if (!hccs_neighbour_rank.empty()) {
320+ CHK_RET(LocalCopyPreproc(mainThread, rank, localStrideSize));
321+ }
322+ 
323+ GetNotifyIdxSubToMain(notifyIdxSubToMain_);
324+ CHK_RET(PostSyncInterThreads(mainThread, subThreads, notifyIdxSubToMain_));
325+ for (u32 round = 0; round < hccs_ranks.size() + 1; round++) {
326+ // MainRecordSub + SubWaitMain
327+ CHK_RET(PreSyncInterThreads(mainThread, subThreads, notifyIdxMainToSub_));
328+ 
329+ CHK_RET(HCCSIntraStep(round, rank, rankSize, rankSizeX_, localStrideSize));
330+ CHK_RET(SIOIntraStep(round, rank, rankSize, rankSizeX_, localStrideSize));
331+ CHK_RET(LocalCopyIntraStep(round, rank, rankSize, rankSizeX_, localStrideSize));
332+ 
333+ // SubRecordMain + MainWaitSub
334+ CHK_RET(PostSyncInterThreads(mainThread, subThreads, notifyIdxSubToMain_));
335+ }
336+ 
337+ // MainRecordSub + SubWaitMain
338+ CHK_RET(PreSyncInterThreads(mainThread, subThreads, notifyIdxMainToSub_));
339+ 
340+ CHK_RET(PreprocInterServer(rank, rankSize, rankSizeX_, localStrideSize, channels));
341+ 
342+ CHK_RET(PostSyncInterThreads(mainThread, subThreads, notifyIdxSubToMain_));
343+ 
344+ CHK_RET(InterServer(rank, rankSize, rankSizeX_, localStrideSize, channels));
345+ 
346+ CHK_RET(PostSyncInterThreads(mainThread, subThreads, notifyIdxSubToMain_));
347+ HCCL_INFO("ReduceScatterVBIRSInter finished: rank[%u]", rank);
348+ return HCCL_SUCCESS;
349+}
350+ 
351+REGISTER_TEMPLATE(TEMPLATE_REDUCE_SCATTER_V_BIRS_INTER, ReduceScatterVBIRSInter);
352+} // namespace ops_hccl_experimental
@@ -0,0 +1,59 @@
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 REDUCE_SCATTER_V_BIRS_INTER_H
12+#define REDUCE_SCATTER_V_BIRS_INTER_H
13+ 
14+#include "reduce_scatter_v_birs.h"
15+#include "alg_template_base_experimental.h"
16+ 
17+namespace ops_hccl_experimental {
18+ 
19+// ReduceScatterV 的 BIRS 跨服务器(Inter-Server)算法模板
20+// 注意: scratch 以 localStrideSize*serverNum_ 为块、localStrideSize(各rank切片大小最大值, 不对齐)为槽位步长组织;
21+// HCCS WRITE / SIO write-reduce / 树形 local-reduce 均按槽位以实际切片长度(slices_[target].size)精确执行,
22+// padding 区不参与任何传输与规约(避免规约未初始化数据导致的不确定结果), 对各规约类型均正确。
23+// 前置条件与 ReduceScatterVBIRS 相同: 所有 rank 传入一致的 sendCounts/sendDispls 数组,
24+// 保证链路两端及各块间同一槽位的切片长度一致。当各rank接收量一致(均匀)时与原BIRS数据流等价。
25+class ReduceScatterVBIRSInter : public ReduceScatterVBIRS {
26+public:
27+ explicit ReduceScatterVBIRSInter();
28+ 
29+ ~ReduceScatterVBIRSInter() override;
30+ 
31+ HcclResult Prepare(u32 serverNum, u32 interRankSize) override;
32+ 
33+ HcclResult RunAsync(const u32 rank, const u32 rankSize, std::vector<ChannelInfo>& channels) override;
34+ 
35+protected:
36+ HcclResult LocalCopyPreproc(ThreadHandle& stream, const u32 rank, u64 localStrideSize);
37+ // V变体: inter 不对齐步长, 保持批量传输结构(各rank接收量一致时与原inter等价)
38+ void CalcLocalStrideSize(const u32 rankSize) override;
39+ HcclResult CheckScratchCapacity(u32 rankSize) override;
40+ 
41+ HcclResult Preprocess(const u32 rank, const u32 rankSize, std::vector<ChannelInfo>& channels) override;
42+ HcclResult HCCSIntraStep(u32 round, const u32 rank, const u32 rankSize, u32 rankSizeX_, u64 localStrideSize);
43+ HcclResult SIOIntraStep(u32 round, const u32 rank, const u32 rankSize, u32 rankSizeX_, u64 localStrideSize);
44+ HcclResult LocalCopyIntraStep(u32 round, const u32 rank, const u32 rankSize, u32 rankSizeX_, u64 localStrideSize);
45+ HcclResult PreprocInterServer(
46+ const u32 rank, const u32 rankSize, u32 rankSizeX_, u64 localStrideSize, std::vector<ChannelInfo>& channels);
47+ HcclResult InterServer(
48+ const u32 rank, const u32 rankSize, u32 rankSizeX_, u64 localStrideSize, std::vector<ChannelInfo>& channels);
49+ 
50+private:
51+ u32 serverNum_;
52+ u32 intraRankSize_;
53+ u32 rankSizeX_ = 2;
54+ std::vector<u32> vec_offsets;
55+ u64 localStrideSize;
56+};
57+ 
58+} // namespace ops_hccl_experimental
59+#endif /* * REDUCE_SCATTER_V_BIRS_INTER_H */
@@ -0,0 +1,175 @@
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 "reduce_scatter_v_op_experimental.h"
12+#include "topo_host.h"
13+#include <algorithm>
14+#include <future>
15+#include <map>
16+#include <string>
17+#include "op_common_experimental.h"
18+#include "param_check.h"
19+#include "reduce_scatter_v.h"
20+#include "load_kernel.h"
21+#include "hcomm_dlsym.h"
22+#include "hcomm_host_profiling_dl.h"
23+ 
24+using namespace std;
25+ 
26+extern "C" HcclResult HcclReduceScatterVInner(
27+ void* sendBuf, const void* sendCounts, const void* sendDispls, void* recvBuf, uint64_t recvCount,
28+ HcclDataType dataType, HcclReduceOp op, HcclComm comm, aclrtStream stream);
29+ 
30+namespace ops_hccl_experimental {
31+using ops_hccl::CheckCount;
32+using ops_hccl::CheckDataTypeRSV;
33+using ops_hccl::CheckReduceOp;
34+using ops_hccl::CheckReduceScatterVInputParam;
35+using ops_hccl::COMM_INDENTIFIER_MAX_LENGTH;
36+using ops_hccl::DATATYPE_SIZE_TABLE;
37+using ops_hccl::HcclCheckTag;
38+using ops_hccl::HcomCheckUserRank;
39+using ops_hccl::InitEnvConfig;
40+using ops_hccl::LoadAICPUKernel;
41+using ops_hccl::LogHcclExit;
42+using ops_hccl::OpMode;
43+using ops_hccl::PrepareReduceScatterVParam;
44+using ops_hccl::ReduceScatterVEntryLog;
45+ 
46+HcclResult ReduceScatterVExperimental(
47+ void* sendBuf, const void* sendCounts, const void* sendDispls, void* recvBuf, uint64_t recvCount,
48+ HcclDataType dataType, HcclReduceOp op, HcclComm comm, aclrtStream stream)
49+{
50+ HcclUs startut = TIME_NOW();
51+ CHK_RET(InitEnvConfig());
52+ 
53+ u32 rankSize = INVALID_VALUE_RANKSIZE;
54+ CHK_RET(HcclGetRankSize(comm, &rankSize));
55+ if (rankSize == 1 || !MatchBIRSV()) {
56+ return HcclReduceScatterVInner(sendBuf, sendCounts, sendDispls, recvBuf, recvCount, dataType, op, comm, stream);
57+ }
58+ // BIRS-V 仅适用于 A3(DEV_TYPE_910_93) 且 rank 数为偶数(与 DecideReduceScatterVBirsAlg 的 vNotSelected
59+ // 判定一致); 不满足时同样回退老流程, 保证开启 HCCL_BIRS_ENABLE 后非目标拓扑的行为与未开启时一致
60+ HcclDevType deviceType = HcclDevType::DEV_TYPE_COUNT;
61+ CHK_RET(HcclGetDeviceType(deviceType));
62+ if (rankSize % 2 != 0 || deviceType != HcclDevType::DEV_TYPE_910_93) {
63+ HCCL_INFO(
64+ "[ReduceScatterVExperimental] BIRS-V not applicable, deviceType[%d] rankSize[%u], fallback to inner flow",
65+ static_cast<int>(deviceType), rankSize);
66+ return HcclReduceScatterVInner(sendBuf, sendCounts, sendDispls, recvBuf, recvCount, dataType, op, comm, stream);
67+ }
68+ // 校验sendCounts全部为0的情况
69+ const u64* sendCountsAddr = reinterpret_cast<const u64*>(sendCounts);
70+ CHK_RET(CheckReduceScatterVInputParam(comm, sendBuf, recvBuf, recvCount, sendCounts, sendDispls, stream));
71+ CHK_PRT_RET(
72+ std::all_of(
73+ sendCountsAddr, sendCountsAddr + rankSize,
74+ [](auto count) {
75+ return count == 0;
76+ }),
77+ HCCL_WARNING("input all %u elements in sendCounts are 0, return success", rankSize), HCCL_SUCCESS);
78+ u32 userRank = INVALID_VALUE_RANKID;
79+ CHK_RET(HcclGetRankId(comm, &userRank));
80+ CHK_RET(HcomCheckUserRank(rankSize, userRank));
81+ CHK_RET(CheckCount(recvCount));
82+ CHK_RET(CheckDataTypeRSV(dataType));
83+ CHK_RET(CheckReduceOp(dataType, op));
84+ 
85+ char commName[COMM_INDENTIFIER_MAX_LENGTH];
86+ CHK_RET(HcclGetCommName(comm, commName));
87+ const string tag = "ReduceScatterV_" + string(commName);
88+ CHK_RET(HcclCheckTag(tag.c_str()));
89+ 
90+ /* 接口交互信息日志 */
91+ CHK_RET(ReduceScatterVEntryLog(
92+ sendBuf, sendCounts, sendDispls, recvBuf, recvCount, dataType, op, stream, tag, rankSize,
93+ "HcclReduceScatterV"));
94+ 
95+ // 申请OpParam参数结构体内存(包含变长varData区)
96+ u64 varMemSize = 2 * rankSize * sizeof(u64);
97+ void* paramMem = malloc(sizeof(OpParam) + varMemSize);
98+ if (!paramMem) {
99+ HCCL_ERROR("[ReduceScatterVExperimental] malloc OpParam failed!");
100+ return HCCL_E_INTERNAL;
101+ }
102+ auto deleter = [](OpParam* p) {
103+ if (p) {
104+ p->~OpParam();
105+ free(p);
106+ }
107+ };
108+ OpParam* tmpParamPtr = new (paramMem) OpParam();
109+ std::unique_ptr<OpParam, decltype(deleter)> paramPtr(tmpParamPtr, deleter);
110+ OpParam& param = *paramPtr;
111+ 
112+ CHK_RET(ReduceScatterVOutPlaceCustom(
113+ param, sendBuf, sendDispls, sendCounts, recvBuf, recvCount, dataType, op, comm, stream, rankSize));
114+ 
115+ CHK_RET(LogHcclExit("HcclReduceScatterV", tag.c_str(), startut));
116+ return HCCL_SUCCESS;
117+}
118+ 
119+HcclResult ReduceScatterVOutPlaceCustom(
120+ OpParam& param, void* sendBuf, const void* sendDispls, const void* sendCounts, void* recvBuf, uint64_t recvCount,
121+ HcclDataType dataType, HcclReduceOp op, HcclComm comm, aclrtStream stream, u32 userRankSize)
122+{
123+ HCCL_INFO("Start to execute ReduceScatterVOutPlaceCustom");
124+ char commName[COMM_INDENTIFIER_MAX_LENGTH];
125+ CHK_RET(HcclGetCommName(comm, commName));
126+ const std::string tag = "ReduceScatterV_" + std::string(commName);
127+ CHK_RET(HcclCheckTag(tag.c_str()));
128+ CHK_RET(PrepareReduceScatterVParam(
129+ sendBuf, sendDispls, sendCounts, recvBuf, recvCount, dataType, op, comm, stream, tag, OpMode::OPBASE,
130+ userRankSize, 2 * userRankSize * sizeof(u64), param));
131+ // host(COMM_ENGINE_CPU_TS)编排路径由 KernelRunLevel0 直接读取 param.vDataDes.counts/displs(host指针);
132+ // 在 experimental 侧补齐 displs, 不改动公共 PrepareReduceScatterVParam 的 production 路径。
133+ // AICPU_TS 路径下 PrepareReduceScatterVVarData 会用 device 指针覆写 counts/displs, 此赋值无副作用。
134+ param.vDataDes.displs = const_cast<void*>(static_cast<const void*>(sendDispls));
135+ 
136+ if (IsAiCpuMode(param.deviceType, userRankSize)) {
137+ HCCL_DEBUG("is aicpu mode");
138+ CHK_RET(LoadAICPUKernel());
139+ param.engine = CommEngine::COMM_ENGINE_AICPU_TS;
140+ } else {
141+ HCCL_DEBUG("is host mode");
142+ param.engine = CommEngine::COMM_ENGINE_CPU_TS;
143+ }
144+ 
145+ uint64_t beginTime = 0;
146+ if (HcommIsProfilingSupported()) {
147+ beginTime = HcommGetProfilingSysCycleTime();
148+ }
149+ 
150+ CHK_RET(ProcessA3(comm, param, beginTime));
151+ 
152+ HCCL_INFO("Execute ReduceScatterVOutPlaceCustom success.");
153+ return HCCL_SUCCESS;
154+}
155+ 
156+bool MatchBIRSV()
157+{
158+ const char* val = std::getenv("HCCL_BIRS_ENABLE");
159+ if (val == nullptr)
160+ return false;
161+ std::string str = val;
162+ std::string lower = str;
163+ std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
164+ if (lower == "true") {
165+ return true;
166+ }
167+ if (lower == "false") {
168+ return false;
169+ }
170+ HCCL_WARNING(
171+ "[MatchBIRSV] HCCL_BIRS_ENABLE[%s] is invalid, expected TRUE/FALSE, fallback to disabled", str.c_str());
172+ return false;
173+}
174+ 
175+} // namespace ops_hccl_experimental
@@ -0,0 +1,46 @@
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_REDUCE_SCATTER_V_OP_CUSTOM
12+#define OPS_HCCL_SRC_OPS_REDUCE_SCATTER_V_OP_CUSTOM
13+ 
14+#include <string>
15+#include <memory>
16+#include "hccl.h"
17+ 
18+#include "alg_param.h"
19+#include "executor_v2_base.h"
20+#include "alg_type.h"
21+#include "execute_selector.h"
22+ 
23+#ifdef __cplusplus
24+extern "C" {
25+#endif
26+ 
27+#ifdef __cplusplus
28+}
29+#endif
30+ 
31+namespace ops_hccl_experimental {
32+using ops_hccl::OpParam;
33+ 
34+HcclResult ReduceScatterVExperimental(
35+ void* sendBuf, const void* sendCounts, const void* sendDispls, void* recvBuf, uint64_t recvCount,
36+ HcclDataType dataType, HcclReduceOp op, HcclComm comm, aclrtStream stream);
37+ 
38+HcclResult ReduceScatterVOutPlaceCustom(
39+ OpParam& param, void* sendBuf, const void* sendDispls, const void* sendCounts, void* recvBuf, uint64_t recvCount,
40+ HcclDataType dataType, HcclReduceOp op, HcclComm comm, aclrtStream stream, u32 userRankSize);
41+ 
42+bool MatchBIRSV();
43+ 
44+} // namespace ops_hccl_experimental
45+ 
46+#endif
@@ -34,7 +34,12 @@ HcclResult HcclReduceScatterV(
34 CHK_RET(IsOutPlaceDevice(isOutPlace));34 CHK_RET(IsOutPlaceDevice(isOutPlace));
35 // 非95设备转到老流程35 // 非95设备转到老流程
36 if (!isOutPlace) {36 if (!isOutPlace) {
37+#ifdef ENABLE_EXPERIMENTAL
38+ return ops_hccl_experimental::ReduceScatterVExperimental(
39+ sendBuf, sendCounts, sendDispls, recvBuf, recvCount, dataType, op, comm, stream);
40+#else
37 return HcclReduceScatterVInner(sendBuf, sendCounts, sendDispls, recvBuf, recvCount, dataType, op, comm, stream);41 return HcclReduceScatterVInner(sendBuf, sendCounts, sendDispls, recvBuf, recvCount, dataType, op, comm, stream);
42+#endif
38 }43 }
39 HcclUs startut = TIME_NOW(); // 走老流程的判断时间不统计在内44 HcclUs startut = TIME_NOW(); // 走老流程的判断时间不统计在内
40 // 入口的地方先解析环境变量,在初始化环境变量的时候需要设置为AICPU展开45 // 入口的地方先解析环境变量,在初始化环境变量的时候需要设置为AICPU展开
@@ -19,6 +19,9 @@
19#include "executor_v2_base.h"19#include "executor_v2_base.h"
20#include "alg_type.h"20#include "alg_type.h"
21#include "execute_selector.h"21#include "execute_selector.h"
22+#ifdef ENABLE_EXPERIMENTAL
23+#include "reduce_scatter_v_op_experimental.h"
24+#endif
22 25 
23#ifdef __cplusplus26#ifdef __cplusplus
24extern "C" {27extern "C" {
@@ -20,6 +20,7 @@ set(TOP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../..)
20enable_testing()20enable_testing()
21 21 
22option(ENABLE_GCOV "Enable gcov/lcov coverage instrumentation" OFF)22option(ENABLE_GCOV "Enable gcov/lcov coverage instrumentation" OFF)
23+option(ENABLE_EXPERIMENTAL "Register ST testcases requiring experimental libhccl (a3 BIRS/BIRS-V)" OFF)
23include(${HCCL_DEV_BASE}/cmake/intf_pub_gccnative.cmake)24include(${HCCL_DEV_BASE}/cmake/intf_pub_gccnative.cmake)
24 25 
25if(NOT DEFINED ST_TASKS)26if(NOT DEFINED ST_TASKS)
@@ -34,6 +34,9 @@ CMAKE_ARGS="-DBUILD_OPEN_PROJECT=ON"
34if [ "${ENABLE_GCOV}" == "on" ]; then34if [ "${ENABLE_GCOV}" == "on" ]; then
35 CMAKE_ARGS="${CMAKE_ARGS} -DENABLE_GCOV=ON"35 CMAKE_ARGS="${CMAKE_ARGS} -DENABLE_GCOV=ON"
36fi36fi
37+if [ "${ENABLE_EXPERIMENTAL}" == "true" ]; then
38+ CMAKE_ARGS="${CMAKE_ARGS} -DENABLE_EXPERIMENTAL=ON"
39+fi
37if [ -n "${ST_TASKS}" ]; then40if [ -n "${ST_TASKS}" ]; then
38 CMAKE_ARGS="${CMAKE_ARGS} -DST_TASKS=${ST_TASKS}"41 CMAKE_ARGS="${CMAKE_ARGS} -DST_TASKS=${ST_TASKS}"
39fi42fi
@@ -129,6 +129,16 @@ endif()
129if("all" IN_LIST ST_TASKS OR "reduce_scatter" IN_LIST ST_TASKS)129if("all" IN_LIST ST_TASKS OR "reduce_scatter" IN_LIST ST_TASKS)
130 add_st_test(reduce_scatter reduce_scatter_testcase.cc)130 add_st_test(reduce_scatter reduce_scatter_testcase.cc)
131endif()131endif()
132+# a3(BIRS/BIRS-V) 用例依赖 experimental 构建的 libhccl(HCCL_BIRS_ENABLE 选路);
133+# 非 experimental 构建下 HcclReduceScatter(V)Inner 在 ST 打桩为 E_NOT_SUPPORT, 故仅在 ON 时注册
134+if(ENABLE_EXPERIMENTAL)
135+ if("all" IN_LIST ST_TASKS OR "reduce_scatter_v_a3" IN_LIST ST_TASKS)
136+ add_st_test(reduce_scatter_v_a3 reduce_scatter_v_testcase_a3.cc)
137+ endif()
138+ if("all" IN_LIST ST_TASKS OR "reduce_scatter_a3" IN_LIST ST_TASKS)
139+ add_st_test(reduce_scatter_a3 reduce_scatter_testcase_a3.cc)
140+ endif()
141+endif()
132if("all" IN_LIST ST_TASKS OR "reduce_scatter_3level" IN_LIST ST_TASKS)142if("all" IN_LIST ST_TASKS OR "reduce_scatter_3level" IN_LIST ST_TASKS)
133 add_st_test(reduce_scatter_3level reduce_scatter_3level_testcase.cc)143 add_st_test(reduce_scatter_3level reduce_scatter_3level_testcase.cc)
134endif()144endif()
@@ -0,0 +1,299 @@
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 "gtest/gtest.h"
12+#include "alg_env_config.h"
13+#include "v_testcase_common.h"
14+ 
15+constexpr u32 DATATYPE_SIZE_TABLE_RSV[HCCL_DATA_TYPE_RESERVED]
16+ = {sizeof(int8_t),
17+ sizeof(int16_t),
18+ sizeof(int32_t),
19+ 2,
20+ sizeof(float),
21+ sizeof(int64_t),
22+ sizeof(uint64_t),
23+ sizeof(uint8_t),
24+ sizeof(uint16_t),
25+ sizeof(uint32_t),
26+ 8,
27+ 2,
28+ 16,
29+ 2,
30+ 1,
31+ 1,
32+ 1,
33+ 1};
34+ 
35+class ST_REDUCE_SCATTER_V_TEST_A2A3 : public ::testing::Test {
36+protected:
37+ void SetUp() override { ResetAlgEnvConfigInitState(); }
38+ void TearDown() override
39+ {
40+ unsetenv("HCCL_OP_EXPANSION_MODE");
41+ unsetenv("HCCL_INDEPENDENT_OP");
42+ unsetenv("HCCL_ENABLE_OPEN_AICPU");
43+ }
44+ static void SetUpTestCase() {}
45+ static void TearDownTestCase() {}
46+};
47+ 
48+static HcclResult ReduceScatterVDispatchWithOp(
49+ HcclReduceOp reduceOp, u32 rankId, u64 totalCount, VDataDesTag vDataDes, HcclComm comm, aclrtStream stream)
50+{
51+ const u32 dataTypeSize = DATATYPE_SIZE_TABLE_RSV[vDataDes.dataType];
52+ void* sendBuf = nullptr;
53+ void* recvBuf = nullptr;
54+ u64 recvDataCount = vDataDes.counts[rankId];
55+ u64 sendBufSize = totalCount * dataTypeSize;
56+ u64 recvBufSize = recvDataCount * dataTypeSize;
57+ // 零接收 rank: 打桩 aclrtMalloc 拒绝 size=0, 分配单元素哑元缓冲; recvCount 仍按真实值 0 传入
58+ if (recvBufSize == 0) {
59+ recvBufSize = dataTypeSize;
60+ }
61+ aclError mallocRet = aclrtMalloc(&sendBuf, sendBufSize, static_cast<aclrtMemMallocPolicy>(BUFFER_INPUT_MARK));
62+ if (mallocRet != ACL_SUCCESS) {
63+ return HCCL_E_INTERNAL;
64+ }
65+ mallocRet = aclrtMalloc(&recvBuf, recvBufSize, static_cast<aclrtMemMallocPolicy>(BUFFER_OUTPUT_MARK));
66+ if (mallocRet != ACL_SUCCESS) {
67+ return HCCL_E_INTERNAL;
68+ }
69+ return HcclReduceScatterV(
70+ sendBuf, vDataDes.counts.data(), vDataDes.displs.data(), recvBuf, recvDataCount, vDataDes.dataType, reduceOp,
71+ comm, stream);
72+}
73+ 
74+static HcclResult
75+ReduceScatterVDispatch(u32 rankId, u64 totalCount, VDataDesTag vDataDes, HcclComm comm, aclrtStream stream)
76+{
77+ return ReduceScatterVDispatchWithOp(HcclReduceOp::HCCL_REDUCE_SUM, rankId, totalCount, vDataDes, comm, stream);
78+}
79+ 
80+static HcclResult
81+ReduceScatterVVerifyWithOp(HcclReduceOp reduceOp, AllRankTaskQueues& taskQueues, u32 rankSize, VDataDesTag vDataDes)
82+{
83+ return CheckReduceScatterV(taskQueues, rankSize, reduceOp, vDataDes);
84+}
85+ 
86+static HcclResult ReduceScatterVVerify(AllRankTaskQueues& taskQueues, u32 rankSize, VDataDesTag vDataDes)
87+{
88+ return ReduceScatterVVerifyWithOp(HcclReduceOp::HCCL_REDUCE_SUM, taskQueues, rankSize, vDataDes);
89+}
90+ 
91+static auto MakeReduceScatterVDispatch(HcclReduceOp reduceOp)
92+{
93+ return [reduceOp](u32 rankId, u64 totalCount, VDataDesTag vDataDes, HcclComm comm, aclrtStream stream) {
94+ return ReduceScatterVDispatchWithOp(reduceOp, rankId, totalCount, vDataDes, comm, stream);
95+ };
96+}
97+ 
98+static auto MakeReduceScatterVVerify(HcclReduceOp reduceOp)
99+{
100+ return [reduceOp](AllRankTaskQueues& taskQueues, u32 rankSize, VDataDesTag vDataDes) {
101+ return ReduceScatterVVerifyWithOp(reduceOp, taskQueues, rankSize, vDataDes);
102+ };
103+}
104+ 
105+static void SetIndependentOpEnv()
106+{
107+ setenv("HCCL_OP_EXPANSION_MODE", "AI_CPU", 1);
108+ setenv("HCCL_INDEPENDENT_OP", "1", 1);
109+ setenv("HCCL_BIRS_ENABLE", "TRUE", 1);
110+}
111+ 
112+static void RunReduceScatterVMultilevel(
113+ const TopoMeta& topoInfo, VDataDesTag vDataDes, HcclReduceOp reduceOp = HcclReduceOp::HCCL_REDUCE_SUM)
114+{
115+ RunVMultilevelTest(
116+ topoInfo, vDataDes, SetIndependentOpEnv, MakeReduceScatterVDispatch(reduceOp),
117+ MakeReduceScatterVVerify(reduceOp), HcclDevType::DEV_TYPE_910_93);
118+}
119+ 
120+static VDataDesTag MakeVDataDes(const std::vector<u64>& counts, const std::vector<u64>& displs, HcclDataType dataType)
121+{
122+ VDataDesTag vDataDes;
123+ vDataDes.counts = counts;
124+ vDataDes.displs = displs;
125+ vDataDes.dataType = dataType;
126+ return vDataDes;
127+}
128+ 
129+TEST_F(ST_REDUCE_SCATTER_V_TEST_A2A3, st_reduce_scatter_v_a3_114_test)
130+{
131+ TopoMeta topoMeta{{{0, 1, 2, 3}}};
132+ VDataDesTag vDataDes;
133+ vDataDes.counts = {100, 100, 100, 100};
134+ vDataDes.displs = {0, 100, 200, 300};
135+ vDataDes.dataType = HcclDataType::HCCL_DATA_TYPE_INT32;
136+ 
137+ RunReduceScatterVMultilevel(topoMeta, vDataDes);
138+}
139+ 
140+TEST_F(ST_REDUCE_SCATTER_V_TEST_A2A3, st_reduce_scatter_v_a3_114_100_test)
141+{
142+ TopoMeta topoMeta{{{0, 1, 2, 3}}};
143+ VDataDesTag vDataDes;
144+ vDataDes.counts = {100, 100, 100, 100};
145+ vDataDes.displs = {0, 100, 0, 0};
146+ vDataDes.dataType = HcclDataType::HCCL_DATA_TYPE_INT32;
147+ 
148+ RunReduceScatterVMultilevel(topoMeta, vDataDes);
149+}
150+ 
151+TEST_F(ST_REDUCE_SCATTER_V_TEST_A2A3, st_reduce_scatter_v_a3_124_test)
152+{
153+ TopoMeta topoMeta{{{0, 1, 2, 3}, {4, 5, 6, 7}}};
154+ VDataDesTag vDataDes;
155+ vDataDes.counts = {200, 200, 200, 200, 200, 200, 200, 200};
156+ vDataDes.displs = {0, 200, 400, 600, 800, 1000, 1200, 1400};
157+ vDataDes.dataType = HcclDataType::HCCL_DATA_TYPE_INT32;
158+ 
159+ RunReduceScatterVMultilevel(topoMeta, vDataDes);
160+}
161+ 
162+TEST_F(ST_REDUCE_SCATTER_V_TEST_A2A3, st_reduce_scatter_v_a3_114_varlen_test)
163+{
164+ // 非均匀 counts: 各目标 rank 接收量不同, 覆盖单服务器变体的变长切片与 localStrideSize(max对齐)路径
165+ TopoMeta topoMeta{{{0, 1, 2, 3}}};
166+ VDataDesTag vDataDes;
167+ vDataDes.counts = {100, 200, 300, 50};
168+ vDataDes.displs = {0, 100, 300, 600};
169+ vDataDes.dataType = HcclDataType::HCCL_DATA_TYPE_INT32;
170+ 
171+ RunReduceScatterVMultilevel(topoMeta, vDataDes);
172+}
173+ 
174+TEST_F(ST_REDUCE_SCATTER_V_TEST_A2A3, st_reduce_scatter_v_a3_14_16_test)
175+{
176+ // 仿真模型初始化
177+ TopoMeta topoMeta;
178+ std::vector<u32> args{1, 4, 16};
179+ for (int i = 0; i < args[0]; i++) {
180+ SuperPodMeta superPodMeta;
181+ for (int j = 0; j < args[1]; j++) {
182+ ServerMeta serverMate;
183+ for (int k = 0; k < args[2]; k++) {
184+ serverMate.push_back((unsigned int)k);
185+ }
186+ superPodMeta.push_back(serverMate);
187+ }
188+ topoMeta.push_back(superPodMeta);
189+ }
190+ 
191+ VDataDesTag vDataDes;
192+ 
193+ auto rankSize = 0;
194+ for (u32 i = 0; i < topoMeta[0].size(); i++)
195+ rankSize += topoMeta[0][i].size();
196+ int j = 0;
197+ for (u32 i = 0; i < rankSize; i++) {
198+ vDataDes.counts.push_back(200);
199+ vDataDes.displs.push_back(j);
200+ j += 200;
201+ }
202+ vDataDes.dataType = HcclDataType::HCCL_DATA_TYPE_INT32;
203+ 
204+ RunReduceScatterVMultilevel(topoMeta, vDataDes);
205+}
206+ 
207+TEST_F(ST_REDUCE_SCATTER_V_TEST_A2A3, st_reduce_scatter_v_a3_124_varlen_test)
208+{
209+ // 跨服务器 + 非均匀 counts: 覆盖 inter 变体的批量规约与步长 padding 路径
210+ TopoMeta topoMeta{{{0, 1, 2, 3}, {4, 5, 6, 7}}};
211+ VDataDesTag vDataDes;
212+ vDataDes.counts = {100, 200, 150, 250, 80, 120, 300, 160};
213+ vDataDes.displs = {0, 100, 300, 450, 700, 780, 900, 1200};
214+ vDataDes.dataType = HcclDataType::HCCL_DATA_TYPE_INT32;
215+ 
216+ RunReduceScatterVMultilevel(topoMeta, vDataDes);
217+}
218+ 
219+TEST_F(ST_REDUCE_SCATTER_V_TEST_A2A3, st_reduce_scatter_v_a3_114_fp16_sum_test)
220+{
221+ TopoMeta topoMeta{{{0, 1, 2, 3}}};
222+ auto vDataDes = MakeVDataDes({100, 200, 300, 50}, {0, 100, 300, 600}, HcclDataType::HCCL_DATA_TYPE_FP16);
223+ RunReduceScatterVMultilevel(topoMeta, vDataDes, HcclReduceOp::HCCL_REDUCE_SUM);
224+}
225+ 
226+TEST_F(ST_REDUCE_SCATTER_V_TEST_A2A3, st_reduce_scatter_v_a3_114_fp16_max_test)
227+{
228+ TopoMeta topoMeta{{{0, 1, 2, 3}}};
229+ auto vDataDes = MakeVDataDes({100, 200, 300, 50}, {0, 100, 300, 600}, HcclDataType::HCCL_DATA_TYPE_FP16);
230+ RunReduceScatterVMultilevel(topoMeta, vDataDes, HcclReduceOp::HCCL_REDUCE_MAX);
231+}
232+ 
233+TEST_F(ST_REDUCE_SCATTER_V_TEST_A2A3, st_reduce_scatter_v_a3_114_bf16_sum_test)
234+{
235+ TopoMeta topoMeta{{{0, 1, 2, 3}}};
236+ auto vDataDes = MakeVDataDes({100, 200, 300, 50}, {0, 100, 300, 600}, HcclDataType::HCCL_DATA_TYPE_BFP16);
237+ RunReduceScatterVMultilevel(topoMeta, vDataDes, HcclReduceOp::HCCL_REDUCE_SUM);
238+}
239+ 
240+TEST_F(ST_REDUCE_SCATTER_V_TEST_A2A3, st_reduce_scatter_v_a3_114_bf16_max_test)
241+{
242+ TopoMeta topoMeta{{{0, 1, 2, 3}}};
243+ auto vDataDes = MakeVDataDes({100, 200, 300, 50}, {0, 100, 300, 600}, HcclDataType::HCCL_DATA_TYPE_BFP16);
244+ RunReduceScatterVMultilevel(topoMeta, vDataDes, HcclReduceOp::HCCL_REDUCE_MAX);
245+}
246+ 
247+TEST_F(ST_REDUCE_SCATTER_V_TEST_A2A3, st_reduce_scatter_v_a3_114_fp32_sum_test)
248+{
249+ TopoMeta topoMeta{{{0, 1, 2, 3}}};
250+ auto vDataDes = MakeVDataDes({100, 200, 300, 50}, {0, 100, 300, 600}, HcclDataType::HCCL_DATA_TYPE_FP32);
251+ RunReduceScatterVMultilevel(topoMeta, vDataDes, HcclReduceOp::HCCL_REDUCE_SUM);
252+}
253+ 
254+TEST_F(ST_REDUCE_SCATTER_V_TEST_A2A3, st_reduce_scatter_v_a3_114_fp32_max_test)
255+{
256+ TopoMeta topoMeta{{{0, 1, 2, 3}}};
257+ auto vDataDes = MakeVDataDes({100, 200, 300, 50}, {0, 100, 300, 600}, HcclDataType::HCCL_DATA_TYPE_FP32);
258+ RunReduceScatterVMultilevel(topoMeta, vDataDes, HcclReduceOp::HCCL_REDUCE_MAX);
259+}
260+ 
261+TEST_F(ST_REDUCE_SCATTER_V_TEST_A2A3, st_reduce_scatter_v_a3_114_fp32_min_test)
262+{
263+ TopoMeta topoMeta{{{0, 1, 2, 3}}};
264+ auto vDataDes = MakeVDataDes({100, 200, 300, 50}, {0, 100, 300, 600}, HcclDataType::HCCL_DATA_TYPE_FP32);
265+ RunReduceScatterVMultilevel(topoMeta, vDataDes, HcclReduceOp::HCCL_REDUCE_MIN);
266+}
267+ 
268+TEST_F(ST_REDUCE_SCATTER_V_TEST_A2A3, st_reduce_scatter_v_a3_114_fp32_prod_test)
269+{
270+ TopoMeta topoMeta{{{0, 1, 2, 3}}};
271+ auto vDataDes = MakeVDataDes({100, 200, 300, 50}, {0, 100, 300, 600}, HcclDataType::HCCL_DATA_TYPE_FP32);
272+ RunReduceScatterVMultilevel(topoMeta, vDataDes, HcclReduceOp::HCCL_REDUCE_PROD);
273+}
274+ 
275+TEST_F(ST_REDUCE_SCATTER_V_TEST_A2A3, st_reduce_scatter_v_a3_124_fp32_max_varlen_test)
276+{
277+ // 跨服务器 + 非均匀 counts + 非 SUM 规约: 覆盖 inter 变体按槽位精确 write-reduce 的 op 透传
278+ TopoMeta topoMeta{{{0, 1, 2, 3}, {4, 5, 6, 7}}};
279+ auto vDataDes = MakeVDataDes(
280+ {100, 200, 150, 250, 80, 120, 300, 160}, {0, 100, 300, 450, 700, 780, 900, 1200},
281+ HcclDataType::HCCL_DATA_TYPE_FP32);
282+ RunReduceScatterVMultilevel(topoMeta, vDataDes, HcclReduceOp::HCCL_REDUCE_MAX);
283+}
284+ 
285+TEST_F(ST_REDUCE_SCATTER_V_TEST_A2A3, st_reduce_scatter_v_a3_114_zerocount_varlen_test)
286+{
287+ // 非均匀 + 零计数混合: rank0/rank2 接收量为 0, 覆盖空切片路径(零长度搬运不产生输出语义)
288+ TopoMeta topoMeta{{{0, 1, 2, 3}}};
289+ auto vDataDes = MakeVDataDes({0, 200, 0, 50}, {0, 0, 200, 200}, HcclDataType::HCCL_DATA_TYPE_INT32);
290+ RunReduceScatterVMultilevel(topoMeta, vDataDes, HcclReduceOp::HCCL_REDUCE_SUM);
291+}
292+ 
293+TEST_F(ST_REDUCE_SCATTER_V_TEST_A2A3, st_reduce_scatter_v_a3_114_skew_varlen_test)
294+{
295+ // 极端不均匀 counts(最大/最小比 4096:1): 覆盖 localStrideSize 按最大切片对齐后的大 padding 路径
296+ TopoMeta topoMeta{{{0, 1, 2, 3}}};
297+ auto vDataDes = MakeVDataDes({1, 4096, 2, 1}, {0, 1, 4097, 4099}, HcclDataType::HCCL_DATA_TYPE_INT32);
298+ RunReduceScatterVMultilevel(topoMeta, vDataDes, HcclReduceOp::HCCL_REDUCE_SUM);
299+}
@@ -37,9 +37,9 @@ static inline u32 AnalyseRankSize(const TopoMeta& topoInfo)
37template <typename DispatchFn, typename VerifyFn>37template <typename DispatchFn, typename VerifyFn>
38void RunVMultilevelTest(38void RunVMultilevelTest(
39 const TopoMeta& topoInfo, VDataDesTag vDataDes, std::function<void()> extraEnvSetup, DispatchFn dispatchFn,39 const TopoMeta& topoInfo, VDataDesTag vDataDes, std::function<void()> extraEnvSetup, DispatchFn dispatchFn,
40- VerifyFn verifyFn)40+ VerifyFn verifyFn, HcclDevType devType = HcclDevType::DEV_TYPE_950)
41{41{
42- SimWorld::Global()->Init(topoInfo, HcclDevType::DEV_TYPE_950);42+ SimWorld::Global()->Init(topoInfo, devType);
43 setenv("HCCL_OP_EXPANSION_MODE", "AI_CPU", 1);43 setenv("HCCL_OP_EXPANSION_MODE", "AI_CPU", 1);
44 if (extraEnvSetup) {44 if (extraEnvSetup) {
45 extraEnvSetup();45 extraEnvSetup();
@@ -376,3 +376,13 @@ target_link_libraries(scatter_aicpu_kernel PRIVATE
376 -Wl,--no-as-needed376 -Wl,--no-as-needed
377 $<BUILD_INTERFACE:intf_llt_pub>377 $<BUILD_INTERFACE:intf_llt_pub>
378)378)
379+ 
380+# 实验算法(ReduceScatter/ReduceScatterV BIRS、recursive_executor 等):
381+# 复用 experimental/ops/CMakeLists.txt 中 if(TARGET scatter_aicpu_kernel) 的注入逻辑
382+# (sources/includes/ENABLE_EXPERIMENTAL 定义), 与 host/device 构建保持单一来源;
383+# ST 构建无 hccl 目标, 其 host 侧源码注入(if(TARGET hccl))自动跳过
384+if(NOT DEFINED ASCEND_CANN_PACKAGE_PATH OR "${ASCEND_CANN_PACKAGE_PATH}" STREQUAL "")
385+ set(ASCEND_CANN_PACKAGE_PATH ${ASCEND_HOME_PATH})
386+endif()
387+set(ENABLE_EXPERIMENTAL ON)
388+add_subdirectory(${HCCL_DEV_BASE}/experimental/ops ${CMAKE_CURRENT_BINARY_DIR}/experimental_ops)
@@ -11,6 +11,8 @@
11#include <vector>11#include <vector>
12#include <atomic>12#include <atomic>
13#include <iostream>13#include <iostream>
14+#include <mutex>
15+#include <set>
14#include "acl/acl_rt.h"16#include "acl/acl_rt.h"
15#include "acl/acl_base.h"17#include "acl/acl_base.h"
16#include "hccl/hccl_types.h"18#include "hccl/hccl_types.h"
@@ -26,6 +28,13 @@ using namespace hccl;
26using namespace ops_hccl;28using namespace ops_hccl;
27thread_local uint32_t curr_dev_id = UINT32_MAX;29thread_local uint32_t curr_dev_id = UINT32_MAX;
28 30 
31+namespace {
32+// 登记 aclrtMalloc else 分支用宿主 malloc 模拟的"设备"指针, 使 aclrtFree 能真实释放, 避免仿真长跑泄漏
33+// (ST 各 rank 在独立 std::thread 中并发调用, 需加锁)
34+std::mutex g_stubMallocMutex;
35+std::set<void*> g_stubMallocPtrs;
36+} // namespace
37+ 
29extern "C" unsigned int HcclLaunchAicpuKernel(OpParam* param);38extern "C" unsigned int HcclLaunchAicpuKernel(OpParam* param);
30 39 
31#ifdef __cplusplus40#ifdef __cplusplus
@@ -58,19 +67,37 @@ aclError aclrtMallocHost(void** hostPtr, size_t size)
58// 打桩实现,仿真运行需标记内存是INPUT和OUTPUT67// 打桩实现,仿真运行需标记内存是INPUT和OUTPUT
59aclError aclrtMalloc(void** devPtr, size_t size, aclrtMemMallocPolicy policy)68aclError aclrtMalloc(void** devPtr, size_t size, aclrtMemMallocPolicy policy)
60{69{
70+ if (devPtr == nullptr || size == 0) {
71+ HCCL_ERROR("[aclrtMalloc] invalid input devPtr or size");
72+ return ACL_ERROR_INVALID_PARAM;
73+ }
61 u32 memType = static_cast<u32>(policy);74 u32 memType = static_cast<u32>(policy);
62 HcclSim::SimNpu& simNpu = HcclSim::SimWorld::Global()->GetSimNpuByRankId(curr_dev_id);75 HcclSim::SimNpu& simNpu = HcclSim::SimWorld::Global()->GetSimNpuByRankId(curr_dev_id);
63 if (memType == BUFFER_INPUT_MARK) {76 if (memType == BUFFER_INPUT_MARK) {
64 *devPtr = reinterpret_cast<void*>(simNpu.AllocMemory(BufferType::INPUT, size));77 *devPtr = reinterpret_cast<void*>(simNpu.AllocMemory(BufferType::INPUT, size));
65 } else if (memType == BUFFER_OUTPUT_MARK) {78 } else if (memType == BUFFER_OUTPUT_MARK) {
66 *devPtr = reinterpret_cast<void*>(simNpu.AllocMemory(BufferType::OUTPUT, size));79 *devPtr = reinterpret_cast<void*>(simNpu.AllocMemory(BufferType::OUTPUT, size));
80+ } else {
81+ *devPtr = malloc(size);
82+ if (*devPtr == nullptr) {
83+ HCCL_ERROR("[aclrtMalloc] malloc failed, size[%zu]", size);
84+ return ACL_ERROR_INTERNAL_ERROR;
85+ }
86+ std::lock_guard<std::mutex> lock(g_stubMallocMutex);
87+ g_stubMallocPtrs.insert(*devPtr);
67 }88 }
68 return ACL_SUCCESS;89 return ACL_SUCCESS;
69}90}
70 91 
71aclError aclrtFree(void* devPtr)92aclError aclrtFree(void* devPtr)
72{93{
73- (void)devPtr;94+ {
95+ std::lock_guard<std::mutex> lock(g_stubMallocMutex);
96+ if (g_stubMallocPtrs.erase(devPtr) > 0) {
97+ free(devPtr);
98+ return ACL_SUCCESS;
99+ }
100+ }
74 HCCL_WARNING("[%s] not support.", __func__);101 HCCL_WARNING("[%s] not support.", __func__);
75 return ACL_SUCCESS;102 return ACL_SUCCESS;
76}103}
@@ -11,9 +11,10 @@
11add_subdirectory(common/prepare_ut_env)11add_subdirectory(common/prepare_ut_env)
12add_subdirectory(common/alg_parse)12add_subdirectory(common/alg_parse)
13add_subdirectory(common/topo_match)13add_subdirectory(common/topo_match)
14-add_subdirectory(reduce_scatter_birs)
15add_subdirectory(alltoall_hier)14add_subdirectory(alltoall_hier)
16# recursive_executor 测试仅在 --experimental 构建(ENABLE_EXPERIMENTAL=ON)时编译15# recursive_executor 测试仅在 --experimental 构建(ENABLE_EXPERIMENTAL=ON)时编译
17if(ENABLE_EXPERIMENTAL)16if(ENABLE_EXPERIMENTAL)
18 add_subdirectory(recursive_executor)17 add_subdirectory(recursive_executor)
18+ add_subdirectory(reduce_scatter_birs)
19+ add_subdirectory(reduce_scatter_v_birs)
19endif()20endif()
@@ -0,0 +1,136 @@
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+set(_repo_root ${CMAKE_CURRENT_SOURCE_DIR}/../../..)
12+ 
13+set(_rsv_ut_include_dirs
14+ ${_repo_root}/experimental/ops/reduce_scatter_v/birs
15+ ${_repo_root}/experimental/ops/reduce_scatter_v/birs/template
16+ ${_repo_root}/experimental/ops/op_common/template
17+ ${_repo_root}/src/ops/op_common/inc
18+ ${_repo_root}/src/ops/op_common
19+ ${_repo_root}/src/ops/op_common/algorithm/template
20+ ${_repo_root}/src/ops/op_common/algorithm/template/registry
21+ ${_repo_root}/src/ops/op_common/algorithm/template/wrapper
22+ ${_repo_root}/src/ops/op_common/algorithm/executor
23+ ${_repo_root}/src/ops/op_common/algorithm/executor/registry
24+ ${_repo_root}/src/ops/op_common/algorithm/executor/channel
25+ ${_repo_root}/src/ops/op_common/algorithm/topo_match
26+ ${_repo_root}/src/ops/op_common/selector
27+ ${_repo_root}/src/ops/op_common/topo_info
28+ ${_repo_root}/src/common
29+ ${_repo_root}/src/common/hcomm_dlsym
30+ ${_repo_root}/src/common/hcomm_dlsym/ccu
31+)
32+ 
33+# 前向兼容:探测 CANN 版本号,与主构建/ST 保持一致(宏逻辑与 test/ut/recursive_executor 一致)。
34+# 缺少该宏时 dlsym_common.h 中的桩类型块会启用,与 CANN 公共头重复定义导致 conflicting declaration。
35+macro(_detect_cann_version _target)
36+ file(GLOB _cann_arch_dirs LIST_DIRECTORIES true "${ASCEND_CANN_PACKAGE_PATH}/*-linux")
37+ if(_cann_arch_dirs)
38+ set(_cann_arch_dir "")
39+ foreach(_dir ${_cann_arch_dirs})
40+ if(_dir MATCHES "/${CMAKE_SYSTEM_PROCESSOR}-linux$")
41+ set(_cann_arch_dir ${_dir})
42+ break()
43+ endif()
44+ endforeach()
45+ if(NOT _cann_arch_dir)
46+ list(GET _cann_arch_dirs 0 _cann_arch_dir)
47+ endif()
48+ target_include_directories(${_target} PRIVATE
49+ ${_cann_arch_dir}/include
50+ ${_cann_arch_dir}/include/hccl
51+ ${_cann_arch_dir}/include/hcomm
52+ ${_cann_arch_dir}/include/hcomm/ccu
53+ ${_cann_arch_dir}/pkg_inc
54+ ${_cann_arch_dir}/pkg_inc/hccl
55+ ${_cann_arch_dir}/pkg_inc/base
56+ ${_cann_arch_dir}/pkg_inc/hcomm/ccu
57+ )
58+ foreach(_arch_dir ${_cann_arch_dirs})
59+ set(_ut_cann_ver_header "${_arch_dir}/include/version/cann_version.h")
60+ if(EXISTS "${_ut_cann_ver_header}")
61+ file(STRINGS "${_ut_cann_ver_header}" _ut_cann_ver_line
62+ REGEX "^#define[ \t]+CANN_VERSION_NUM[ \t]+")
63+ if(_ut_cann_ver_line)
64+ string(REGEX REPLACE "^#define[ \t]+CANN_VERSION_NUM[ \t]+(.+)$" "\\1"
65+ _UT_CANN_VERSION_EXPR "${_ut_cann_ver_line}")
66+ math(EXPR _UT_CANN_VERSION_NUM "${_UT_CANN_VERSION_EXPR}" OUTPUT_FORMAT DECIMAL)
67+ target_compile_definitions(${_target}
68+ PRIVATE CANN_VERSION_NUM=${_UT_CANN_VERSION_NUM})
69+ message(STATUS "[UT] Detected CANN_VERSION_NUM = ${_UT_CANN_VERSION_NUM}")
70+ else()
71+ message(FATAL_ERROR "[UT] Failed to parse CANN_VERSION_NUM from ${_ut_cann_ver_header}: "
72+ "no '#define CANN_VERSION_NUM' line found. "
73+ "dlsym_common.h stub blocks would collide with CANN public headers.")
74+ endif()
75+ break()
76+ endif()
77+ endforeach()
78+ endif()
79+endmacro()
80+ 
81+# ---------- reduce_scatter_v_birs_selector_test: 选路判定纯逻辑 ----------
82+set(ut_src_list
83+ ${CMAKE_CURRENT_SOURCE_DIR}/birs_v_selector_test.cc
84+ ${_repo_root}/experimental/ops/reduce_scatter_v/birs/reduce_scatter_v_birs_selector.cc
85+)
86+ 
87+add_executable(reduce_scatter_v_birs_selector_test ${ut_src_list})
88+ 
89+target_compile_options(reduce_scatter_v_birs_selector_test PRIVATE
90+ -U_FORTIFY_SOURCE
91+)
92+ 
93+target_include_directories(reduce_scatter_v_birs_selector_test PRIVATE ${_rsv_ut_include_dirs})
94+ 
95+_detect_cann_version(reduce_scatter_v_birs_selector_test)
96+ 
97+target_link_libraries(reduce_scatter_v_birs_selector_test PRIVATE
98+ $<BUILD_INTERFACE:intf_llt_pub>
99+ GTest::gtest_main
100+ -lpthread
101+)
102+ 
103+run_llt_test(TARGET reduce_scatter_v_birs_selector_test TASK_NUM 1)
104+ 
105+# ---------- reduce_scatter_v_birs_core_test: 变长核心纯计算逻辑 ----------
106+# executor_base/template 源文件整体编译; HCOMM 原语与 AlgTemplateBase 运行时依赖由本目录桩提供, 不依赖硬件
107+set(core_src_list
108+ ${CMAKE_CURRENT_SOURCE_DIR}/birs_v_core_test.cc
109+ ${CMAKE_CURRENT_SOURCE_DIR}/log_stub.cc
110+ ${CMAKE_CURRENT_SOURCE_DIR}/hcomm_stub.cc
111+ ${CMAKE_CURRENT_SOURCE_DIR}/base_class_stub.cc
112+ ${_repo_root}/experimental/ops/reduce_scatter_v/birs/reduce_scatter_v_executor_base.cc
113+ ${_repo_root}/experimental/ops/reduce_scatter_v/birs/template/reduce_scatter_v_birs.cc
114+ ${_repo_root}/experimental/ops/reduce_scatter_v/birs/template/reduce_scatter_v_birs_inter.cc
115+ ${_repo_root}/experimental/ops/op_common/template/alg_template_base_experimental.cc
116+ ${_repo_root}/src/ops/op_common/algorithm/executor/executor_base.cc
117+ ${_repo_root}/src/ops/op_common/algorithm/template/registry/alg_template_register.cc
118+)
119+ 
120+add_executable(reduce_scatter_v_birs_core_test ${core_src_list})
121+ 
122+target_compile_options(reduce_scatter_v_birs_core_test PRIVATE
123+ -U_FORTIFY_SOURCE
124+)
125+ 
126+target_include_directories(reduce_scatter_v_birs_core_test PRIVATE ${_rsv_ut_include_dirs})
127+ 
128+_detect_cann_version(reduce_scatter_v_birs_core_test)
129+ 
130+target_link_libraries(reduce_scatter_v_birs_core_test PRIVATE
131+ $<BUILD_INTERFACE:intf_llt_pub>
132+ GTest::gtest_main
133+ -lpthread
134+)
135+ 
136+run_llt_test(TARGET reduce_scatter_v_birs_core_test TASK_NUM 1)
@@ -0,0 +1,109 @@
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+// UT 桩: AlgTemplateBase 的构造/析构/虚函数与线程同步原语。
12+// 真实实现(src/ops/op_common/algorithm/template/alg_template_base.cc)依赖 ExecTimeoutManager 与
13+// hcomm_primitives_dl 的 dlsym 运行时符号, 不适合 UT 独立编译; 本桩按真实实现的成员初始化语义等价提供,
14+// 保证被测的 BIRS-V 模板成员(slices_/scratchMem_ 等)行为与生产一致。
15+ 
16+#include "alg_template_base.h"
17+#include "alg_data_trans_wrapper.h"
18+ 
19+namespace ops_hccl {
20+ 
21+AlgTemplateBase::AlgTemplateBase()
22+ : slices_(slicesDummy_),
23+ count_(0),
24+ dataBytes_(0),
25+ dataType_(HCCL_DATA_TYPE_RESERVED),
26+ reductionOp_(HCCL_REDUCE_RESERVED),
27+ root_(INVALID_VALUE_RANKID),
28+ baseOffset_(0),
29+ barrierSwitchOn_(true)
30+{}
31+ 
32+AlgTemplateBase::~AlgTemplateBase() { slices_.clear(); }
33+ 
34+HcclResult AlgTemplateBase::Prepare(PrepareData& param)
35+{
36+ (void)param;
37+ return HCCL_E_PARA;
38+}
39+ 
40+HcclResult AlgTemplateBase::Prepare(
41+ HcclMem& inputMem, HcclMem& outputMem, HcclMem& scratchMem, const u64 count, const HcclDataType dataType,
42+ ThreadHandle thread, const HcclReduceOp reductionOp, const u32 root, const std::vector<Slice>& slices,
43+ const u64 baseOffset, const bool disableDMAReduce)
44+{
45+ inputMem_ = inputMem;
46+ outputMem_ = outputMem;
47+ scratchMem_ = scratchMem;
48+ thread_ = thread;
49+ count_ = count;
50+ dataType_ = dataType;
51+ dataBytes_ = count * DataUnitSize(dataType);
52+ reductionOp_ = reductionOp;
53+ root_ = root;
54+ disableDMAReduce_ = disableDMAReduce;
55+ baseOffset_ = baseOffset;
56+ if (slices.size() > 0) {
57+ slices_.resize(slices.size());
58+ slices_ = slices;
59+ }
60+ return HCCL_SUCCESS;
61+}
62+ 
63+HcclResult AlgTemplateBase::Prepare(u32 interRank, u32 interRankSize)
64+{
65+ (void)interRank;
66+ (void)interRankSize;
67+ return HCCL_E_PARA;
68+}
69+ 
70+HcclResult AlgTemplateBase::Prepare(
71+ HcclCollOpInfo* opInfo, const u32 userRank, const std::vector<u32>& ringsOrders,
72+ const std::vector<Slice>& userMemInputSlices)
73+{
74+ (void)opInfo;
75+ (void)userRank;
76+ (void)ringsOrders;
77+ (void)userMemInputSlices;
78+ return HCCL_E_PARA;
79+}
80+ 
81+HcclResult AlgTemplateBase::RunAsync(const u32 rank, const u32 rankSize, std::vector<ChannelInfo>& channels)
82+{
83+ (void)rank;
84+ (void)rankSize;
85+ (void)channels;
86+ return HCCL_SUCCESS;
87+}
88+ 
89+HcclResult PreSyncInterThreads(
90+ const ThreadHandle& mainThread, const std::vector<ThreadHandle>& subThreads,
91+ const std::vector<u32>& notifyIdxMainToSub)
92+{
93+ (void)mainThread;
94+ (void)subThreads;
95+ (void)notifyIdxMainToSub;
96+ return HCCL_SUCCESS;
97+}
98+ 
99+HcclResult PostSyncInterThreads(
100+ const ThreadHandle& mainThread, const std::vector<ThreadHandle>& subThreads,
101+ const std::vector<u32>& notifyIdxSubToMain)
102+{
103+ (void)mainThread;
104+ (void)subThreads;
105+ (void)notifyIdxSubToMain;
106+ return HCCL_SUCCESS;
107+}
108+ 
109+} // namespace ops_hccl
@@ -0,0 +1,253 @@
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 <vector>
12+#include <gtest/gtest.h>
13+#include "reduce_scatter_v_executor_base.h"
14+#include "reduce_scatter_v_birs.h"
15+#include "reduce_scatter_v_birs_inter.h"
16+ 
17+namespace {
18+ 
19+using ops_hccl::Slice;
20+using ops_hccl_experimental::ReduceScatterVBIRS;
21+using ops_hccl_experimental::ReduceScatterVBIRSInter;
22+using ops_hccl_experimental::ReduceScatterVExecutorBase;
23+ 
24+constexpr u64 UT_MIN_SLICE_ALIGN = 16384; // HCCL_MIN_SLICE_ALIGN_910B
25+ 
26+class TestableExecutor : public ReduceScatterVExecutorBase {
27+public:
28+ using ReduceScatterVExecutorBase::PrepareDataSliceV;
29+};
30+ 
31+class TestableBirs : public ReduceScatterVBIRS {
32+public:
33+ using ReduceScatterVBIRS::CalcLocalStrideSize;
34+ using ReduceScatterVBIRS::CheckScratchCapacity;
35+ 
36+ void SetSliceSizes(const std::vector<u64>& sizes)
37+ {
38+ slicesDummy_.resize(sizes.size());
39+ u64 offset = 0;
40+ for (size_t i = 0; i < sizes.size(); i++) {
41+ slicesDummy_[i].offset = offset;
42+ slicesDummy_[i].size = sizes[i];
43+ offset += sizes[i];
44+ }
45+ }
46+ 
47+ void SetScratch(u64 size) { scratchMem_ = HcclMem{HCCL_MEM_TYPE_DEVICE, nullptr, size}; }
48+ 
49+ u64 GetStride() const { return localStrideSize; }
50+};
51+ 
52+class TestableBirsInter : public ReduceScatterVBIRSInter {
53+public:
54+ using ReduceScatterVBIRSInter::CalcLocalStrideSize;
55+ using ReduceScatterVBIRSInter::CheckScratchCapacity;
56+ 
57+ void SetSliceSizes(const std::vector<u64>& sizes)
58+ {
59+ slicesDummy_.resize(sizes.size());
60+ u64 offset = 0;
61+ for (size_t i = 0; i < sizes.size(); i++) {
62+ slicesDummy_[i].offset = offset;
63+ slicesDummy_[i].size = sizes[i];
64+ offset += sizes[i];
65+ }
66+ }
67+ 
68+ void SetScratch(u64 size) { scratchMem_ = HcclMem{HCCL_MEM_TYPE_DEVICE, nullptr, size}; }
69+};
70+ 
71+Slice MakeSlice(u64 offset, u64 size)
72+{
73+ Slice slice;
74+ slice.offset = offset;
75+ slice.size = size;
76+ return slice;
77+}
78+ 
79+// ---------- PrepareDataSliceV: sendCounts/sendDispls -> slices 的 offset/size 换算 ----------
80+ 
81+TEST(ReduceScatterVBirsCore, PrepareDataSliceVNonUniformCounts)
82+{
83+ TestableExecutor executor;
84+ const u64 sendCounts[] = {100, 200, 300, 50};
85+ const u64 sendDispls[] = {0, 100, 300, 600};
86+ std::vector<Slice> dataSlice;
87+ ASSERT_EQ(executor.PrepareDataSliceV(sendCounts, sendDispls, 4, 4, dataSlice), HCCL_SUCCESS);
88+ ASSERT_EQ(dataSlice.size(), 4U);
89+ const Slice expected[] = {MakeSlice(0, 400), MakeSlice(400, 800), MakeSlice(1200, 1200), MakeSlice(2400, 200)};
90+ for (u32 i = 0; i < 4; i++) {
91+ EXPECT_EQ(dataSlice[i].offset, expected[i].offset) << "slice " << i;
92+ EXPECT_EQ(dataSlice[i].size, expected[i].size) << "slice " << i;
93+ }
94+}
95+ 
96+TEST(ReduceScatterVBirsCore, PrepareDataSliceVZeroCountRanks)
97+{
98+ TestableExecutor executor;
99+ const u64 sendCounts[] = {0, 200, 0, 50};
100+ const u64 sendDispls[] = {0, 0, 200, 200};
101+ std::vector<Slice> dataSlice;
102+ ASSERT_EQ(executor.PrepareDataSliceV(sendCounts, sendDispls, 4, 4, dataSlice), HCCL_SUCCESS);
103+ ASSERT_EQ(dataSlice.size(), 4U);
104+ EXPECT_EQ(dataSlice[0].size, 0U);
105+ EXPECT_EQ(dataSlice[1].offset, 0U);
106+ EXPECT_EQ(dataSlice[1].size, 800U);
107+ EXPECT_EQ(dataSlice[2].size, 0U);
108+ EXPECT_EQ(dataSlice[3].offset, 800U);
109+ EXPECT_EQ(dataSlice[3].size, 200U);
110+}
111+ 
112+TEST(ReduceScatterVBirsCore, PrepareDataSliceVSliceNumZeroRejected)
113+{
114+ TestableExecutor executor;
115+ const u64 sendCounts[] = {100};
116+ const u64 sendDispls[] = {0};
117+ std::vector<Slice> dataSlice;
118+ EXPECT_EQ(executor.PrepareDataSliceV(sendCounts, sendDispls, 4, 0, dataSlice), HCCL_E_PARA);
119+}
120+ 
121+TEST(ReduceScatterVBirsCore, PrepareDataSliceVNullPointerRejected)
122+{
123+ TestableExecutor executor;
124+ const u64 sendCounts[] = {100};
125+ const u64 sendDispls[] = {0};
126+ std::vector<Slice> dataSlice;
127+ EXPECT_NE(executor.PrepareDataSliceV(nullptr, sendDispls, 4, 1, dataSlice), HCCL_SUCCESS);
128+ EXPECT_NE(executor.PrepareDataSliceV(sendCounts, nullptr, 4, 1, dataSlice), HCCL_SUCCESS);
129+}
130+ 
131+TEST(ReduceScatterVBirsCore, PrepareDataSliceV64BitNoTruncation)
132+{
133+ TestableExecutor executor;
134+ const u64 sendCounts[] = {0x100000000ULL, 1};
135+ const u64 sendDispls[] = {0, 0x100000000ULL};
136+ std::vector<Slice> dataSlice;
137+ ASSERT_EQ(executor.PrepareDataSliceV(sendCounts, sendDispls, 8, 2, dataSlice), HCCL_SUCCESS);
138+ EXPECT_EQ(dataSlice[0].size, 0x800000000ULL);
139+ EXPECT_EQ(dataSlice[1].offset, 0x800000000ULL);
140+}
141+ 
142+TEST(ReduceScatterVBirsCore, PrepareDataSliceVResizesExistingVector)
143+{
144+ TestableExecutor executor;
145+ const u64 sendCounts[] = {10, 20};
146+ const u64 sendDispls[] = {0, 10};
147+ std::vector<Slice> dataSlice(7, MakeSlice(123, 456));
148+ ASSERT_EQ(executor.PrepareDataSliceV(sendCounts, sendDispls, 2, 2, dataSlice), HCCL_SUCCESS);
149+ ASSERT_EQ(dataSlice.size(), 2U);
150+ EXPECT_EQ(dataSlice[0].size, 20U);
151+ EXPECT_EQ(dataSlice[1].offset, 20U);
152+}
153+ 
154+// ---------- Intra: CalcLocalStrideSize(RoundUp 16KB) 与 CheckScratchCapacity ----------
155+ 
156+TEST(ReduceScatterVBirsCore, IntraStrideRoundsUpMaxSliceTo16K)
157+{
158+ TestableBirs birs;
159+ birs.SetSliceSizes({1000, 300, 16385, 7});
160+ birs.CalcLocalStrideSize(4);
161+ EXPECT_EQ(birs.GetStride(), 2 * UT_MIN_SLICE_ALIGN);
162+}
163+ 
164+TEST(ReduceScatterVBirsCore, IntraStrideExactMultipleUnchanged)
165+{
166+ TestableBirs birs;
167+ birs.SetSliceSizes({100, UT_MIN_SLICE_ALIGN, 512});
168+ birs.CalcLocalStrideSize(3);
169+ EXPECT_EQ(birs.GetStride(), UT_MIN_SLICE_ALIGN);
170+}
171+ 
172+TEST(ReduceScatterVBirsCore, IntraStrideMaxAtLastRank)
173+{
174+ TestableBirs birs;
175+ birs.SetSliceSizes({100, 200, 300, 5000});
176+ birs.CalcLocalStrideSize(4);
177+ EXPECT_EQ(birs.GetStride(), UT_MIN_SLICE_ALIGN);
178+}
179+ 
180+TEST(ReduceScatterVBirsCore, IntraStrideAllZeroSlices)
181+{
182+ // RoundUpWithDivisor(0, d) 按约定返回 d: 全零切片(生产流程被入口 all-zero 拦截, 不会到达模板)
183+ // 的兜底步长为一个对齐单位, 而非 0
184+ TestableBirs birs;
185+ birs.SetSliceSizes({0, 0, 0, 0});
186+ birs.CalcLocalStrideSize(4);
187+ EXPECT_EQ(birs.GetStride(), UT_MIN_SLICE_ALIGN);
188+}
189+ 
190+TEST(ReduceScatterVBirsCore, IntraScratchCapacityBoundary)
191+{
192+ TestableBirs birs;
193+ birs.SetSliceSizes({5000, 100, 200, 300});
194+ birs.CalcLocalStrideSize(4);
195+ ASSERT_EQ(birs.GetStride(), UT_MIN_SLICE_ALIGN);
196+ birs.SetScratch(4 * UT_MIN_SLICE_ALIGN);
197+ EXPECT_EQ(birs.CheckScratchCapacity(4), HCCL_SUCCESS);
198+ birs.SetScratch(4 * UT_MIN_SLICE_ALIGN - 1);
199+ EXPECT_EQ(birs.CheckScratchCapacity(4), HCCL_E_PARA);
200+}
201+ 
202+TEST(ReduceScatterVBirsCore, IntraScratchCapacityZeroSlicesFallbackStride)
203+{
204+ TestableBirs birs;
205+ birs.SetSliceSizes({0, 0, 0, 0});
206+ birs.CalcLocalStrideSize(4);
207+ birs.SetScratch(4 * UT_MIN_SLICE_ALIGN);
208+ EXPECT_EQ(birs.CheckScratchCapacity(4), HCCL_SUCCESS);
209+ birs.SetScratch(4 * UT_MIN_SLICE_ALIGN - 1);
210+ EXPECT_EQ(birs.CheckScratchCapacity(4), HCCL_E_PARA);
211+}
212+ 
213+// ---------- Inter: Prepare 校验 / 不对齐步长 / (rankSize + 2*serverNum) 容量公式 ----------
214+ 
215+TEST(ReduceScatterVBirsCore, InterPrepareDivisibility)
216+{
217+ TestableBirsInter birsInter;
218+ EXPECT_EQ(birsInter.Prepare(2, 8), HCCL_SUCCESS);
219+ EXPECT_EQ(birsInter.Prepare(3, 8), HCCL_E_PARA);
220+}
221+ 
222+TEST(ReduceScatterVBirsCore, InterPrepareZeroServerNumRejected)
223+{
224+ TestableBirsInter birsInter;
225+ EXPECT_EQ(birsInter.Prepare(0, 8), HCCL_E_PARA);
226+}
227+ 
228+TEST(ReduceScatterVBirsCore, InterStrideNotAlignedToMinSlice)
229+{
230+ TestableBirsInter birsInter;
231+ ASSERT_EQ(birsInter.Prepare(2, 8), HCCL_SUCCESS);
232+ birsInter.SetSliceSizes({1000, 300, 999, 7, 500, 100, 200, 300});
233+ birsInter.CalcLocalStrideSize(8);
234+ // demand = (rankSize + 2*serverNum) * stride = (8 + 4) * 1000;
235+ // 边界命中 12000 即证明 stride 未按 16KB 对齐(否则 demand 为 12*16384)
236+ birsInter.SetScratch(12 * 1000);
237+ EXPECT_EQ(birsInter.CheckScratchCapacity(8), HCCL_SUCCESS);
238+ birsInter.SetScratch(12 * 1000 - 1);
239+ EXPECT_EQ(birsInter.CheckScratchCapacity(8), HCCL_E_PARA);
240+}
241+ 
242+TEST(ReduceScatterVBirsCore, InterZeroSlicesDemandZero)
243+{
244+ TestableBirsInter birsInter;
245+ ASSERT_EQ(birsInter.Prepare(4, 16), HCCL_SUCCESS);
246+ std::vector<u64> zeroSizes(16, 0);
247+ birsInter.SetSliceSizes(zeroSizes);
248+ birsInter.CalcLocalStrideSize(16);
249+ birsInter.SetScratch(0);
250+ EXPECT_EQ(birsInter.CheckScratchCapacity(16), HCCL_SUCCESS);
251+}
252+ 
253+} // namespace
@@ -0,0 +1,119 @@
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+#include <string>
11+#include <gtest/gtest.h>
12+#include "reduce_scatter_v_birs_selector.h"
13+ 
14+using ops_hccl::TopoInfo;
15+using ops_hccl_experimental::BirsVSelectResult;
16+using ops_hccl_experimental::BirsVSelectResultToCode;
17+using ops_hccl_experimental::DecideReduceScatterVBirsAlg;
18+ 
19+namespace {
20+TopoInfo MakeTopo(u32 userRankSize, u32 serverNum, HcclDevType deviceType)
21+{
22+ TopoInfo topo{};
23+ topo.userRankSize = userRankSize;
24+ topo.serverNum = serverNum;
25+ topo.deviceType = deviceType;
26+ return topo;
27+}
28+ 
29+const std::string BIRS_V_ALG_NAME = "ReduceScatterVBIRSExecutor";
30+} // namespace
31+ 
32+TEST(ReduceScatterVBirsSelector, ServerNumZeroIsRejectedInsteadOfDivByZero)
33+{
34+ std::string algName;
35+ TopoInfo topo = MakeTopo(8, 0, HcclDevType::DEV_TYPE_910_93);
36+ EXPECT_EQ(DecideReduceScatterVBirsAlg(topo, algName), BirsVSelectResult::vRejectServerNumZero);
37+ EXPECT_TRUE(algName.empty());
38+}
39+ 
40+TEST(ReduceScatterVBirsSelector, DualCardSingleServerIsRejected)
41+{
42+ std::string algName;
43+ TopoInfo topo = MakeTopo(2, 1, HcclDevType::DEV_TYPE_910_93);
44+ EXPECT_EQ(DecideReduceScatterVBirsAlg(topo, algName), BirsVSelectResult::vRejectRanksPerServerLT4);
45+ EXPECT_TRUE(algName.empty());
46+}
47+ 
48+TEST(ReduceScatterVBirsSelector, ThreeRanksPerServerIsRejected)
49+{
50+ std::string algName;
51+ TopoInfo topo = MakeTopo(6, 2, HcclDevType::DEV_TYPE_910_93);
52+ EXPECT_EQ(DecideReduceScatterVBirsAlg(topo, algName), BirsVSelectResult::vRejectRanksPerServerLT4);
53+ EXPECT_TRUE(algName.empty());
54+}
55+ 
56+TEST(ReduceScatterVBirsSelector, RankSizeOneIsRejected)
57+{
58+ std::string algName;
59+ TopoInfo topo = MakeTopo(1, 1, HcclDevType::DEV_TYPE_910_93);
60+ EXPECT_EQ(DecideReduceScatterVBirsAlg(topo, algName), BirsVSelectResult::vRejectRankSizeOne);
61+ EXPECT_TRUE(algName.empty());
62+}
63+ 
64+TEST(ReduceScatterVBirsSelector, FourRanksPerServerIsSelected)
65+{
66+ std::string algName;
67+ TopoInfo topo = MakeTopo(8, 2, HcclDevType::DEV_TYPE_910_93);
68+ EXPECT_EQ(DecideReduceScatterVBirsAlg(topo, algName), BirsVSelectResult::vSelected);
69+ EXPECT_EQ(algName, BIRS_V_ALG_NAME);
70+}
71+ 
72+TEST(ReduceScatterVBirsSelector, ExactlyFourRanksPerServerBoundarySelected)
73+{
74+ std::string algName;
75+ TopoInfo topo = MakeTopo(4, 1, HcclDevType::DEV_TYPE_910_93);
76+ EXPECT_EQ(DecideReduceScatterVBirsAlg(topo, algName), BirsVSelectResult::vSelected);
77+ EXPECT_EQ(algName, BIRS_V_ALG_NAME);
78+}
79+ 
80+TEST(ReduceScatterVBirsSelector, MoreThanFourRanksSingleServerIsSelected)
81+{
82+ std::string algName;
83+ TopoInfo topo = MakeTopo(16, 1, HcclDevType::DEV_TYPE_910_93);
84+ EXPECT_EQ(DecideReduceScatterVBirsAlg(topo, algName), BirsVSelectResult::vSelected);
85+ EXPECT_EQ(algName, BIRS_V_ALG_NAME);
86+}
87+ 
88+TEST(ReduceScatterVBirsSelector, MoreThanFourRanksPerServerIsSelected)
89+{
90+ std::string algName;
91+ TopoInfo topo = MakeTopo(16, 4, HcclDevType::DEV_TYPE_910_93);
92+ EXPECT_EQ(DecideReduceScatterVBirsAlg(topo, algName), BirsVSelectResult::vSelected);
93+ EXPECT_EQ(algName, BIRS_V_ALG_NAME);
94+}
95+ 
96+TEST(ReduceScatterVBirsSelector, NonA3DeviceIsNotSelected)
97+{
98+ std::string algName;
99+ TopoInfo topo = MakeTopo(8, 2, HcclDevType::DEV_TYPE_910B);
100+ EXPECT_EQ(DecideReduceScatterVBirsAlg(topo, algName), BirsVSelectResult::vNotSelected);
101+ EXPECT_TRUE(algName.empty());
102+}
103+ 
104+TEST(ReduceScatterVBirsSelector, OddRankSizeIsNotSelected)
105+{
106+ std::string algName;
107+ TopoInfo topo = MakeTopo(9, 1, HcclDevType::DEV_TYPE_910_93);
108+ EXPECT_EQ(DecideReduceScatterVBirsAlg(topo, algName), BirsVSelectResult::vNotSelected);
109+ EXPECT_TRUE(algName.empty());
110+}
111+ 
112+TEST(ReduceScatterVBirsSelector, SelectResultToCodeMapsRejectsToErrorCodes)
113+{
114+ EXPECT_EQ(BirsVSelectResultToCode(BirsVSelectResult::vSelected), HCCL_SUCCESS);
115+ EXPECT_EQ(BirsVSelectResultToCode(BirsVSelectResult::vNotSelected), HCCL_SUCCESS);
116+ EXPECT_EQ(BirsVSelectResultToCode(BirsVSelectResult::vRejectRankSizeOne), HCCL_E_INTERNAL);
117+ EXPECT_EQ(BirsVSelectResultToCode(BirsVSelectResult::vRejectServerNumZero), HCCL_E_PARA);
118+ EXPECT_EQ(BirsVSelectResultToCode(BirsVSelectResult::vRejectRanksPerServerLT4), HCCL_E_PARA);
119+}
@@ -0,0 +1,105 @@
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+// BIRS-V 核心逻辑 UT 的 HCOMM 打桩: 被测函数(PrepareDataSliceV/CalcLocalStrideSize/CheckScratchCapacity)
12+// 为纯计算逻辑, 不触发数据搬运; 模板/executor 源文件整体编译所需的 Hcomm 符号在此提供空实现。
13+ 
14+#include "hcomm_primitives.h"
15+ 
16+extern "C" {
17+ 
18+int32_t HcommLocalCopyOnThread(ThreadHandle thread, void* dst, const void* src, uint64_t len)
19+{
20+ (void)thread;
21+ (void)dst;
22+ (void)src;
23+ (void)len;
24+ return 0;
25+}
26+ 
27+int32_t HcommLocalReduceOnThread(
28+ ThreadHandle thread, void* dst, const void* src, uint64_t count, HcommDataType dataType, HcommReduceOp reduceOp)
29+{
30+ (void)thread;
31+ (void)dst;
32+ (void)src;
33+ (void)count;
34+ (void)dataType;
35+ (void)reduceOp;
36+ return 0;
37+}
38+ 
39+int32_t HcommWriteOnThread(ThreadHandle thread, ChannelHandle channel, void* dst, const void* src, uint64_t len)
40+{
41+ (void)thread;
42+ (void)channel;
43+ (void)dst;
44+ (void)src;
45+ (void)len;
46+ return 0;
47+}
48+ 
49+int32_t HcommWriteReduceOnThread(
50+ ThreadHandle thread, ChannelHandle channel, void* dst, const void* src, uint64_t count, HcommDataType dataType,
51+ HcommReduceOp reduceOp)
52+{
53+ (void)thread;
54+ (void)channel;
55+ (void)dst;
56+ (void)src;
57+ (void)count;
58+ (void)dataType;
59+ (void)reduceOp;
60+ return 0;
61+}
62+ 
63+int32_t HcommChannelNotifyRecordOnThread(ThreadHandle thread, ChannelHandle channel, uint32_t remoteNotifyIdx)
64+{
65+ (void)thread;
66+ (void)channel;
67+ (void)remoteNotifyIdx;
68+ return 0;
69+}
70+ 
71+int32_t
72+HcommChannelNotifyWaitOnThread(ThreadHandle thread, ChannelHandle channel, uint32_t localNotifyIdx, uint32_t timeOut)
73+{
74+ (void)thread;
75+ (void)channel;
76+ (void)localNotifyIdx;
77+ (void)timeOut;
78+ return 0;
79+}
80+ 
81+int32_t HcommAcquireComm(const char* commId)
82+{
83+ (void)commId;
84+ return 0;
85+}
86+ 
87+int32_t HcommReleaseComm(const char* commId)
88+{
89+ (void)commId;
90+ return 0;
91+}
92+ 
93+int32_t HcommBatchModeStart(const char* batchTag)
94+{
95+ (void)batchTag;
96+ return 0;
97+}
98+ 
99+int32_t HcommBatchModeEnd(const char* batchTag)
100+{
101+ (void)batchTag;
102+ return 0;
103+}
104+ 
105+} // extern "C"
@@ -0,0 +1,14 @@
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 "log.h"
12+ 
13+bool HcclCheckLogLevel(int logType, int moduleId) { return false; }
14+bool IsErrorToWarn() { return false; }