"vscode:/vscode.git/clone" did not exist on "5822ede66ee834f406d475ce28a1d8f666458a48"
Commit 571a75b5 authored by lishen's avatar lishen
Browse files

完成全部网络的node建立,以及GPU到GPU的path物理路径搜索

parent 379c4128
...@@ -83,6 +83,7 @@ static void initOnceFunc() { ...@@ -83,6 +83,7 @@ static void initOnceFunc() {
WARN("pfn_hsa_system_get_info failed with %d", res); WARN("pfn_hsa_system_get_info failed with %d", res);
goto error; goto error;
} }
res = pfn_hsa_system_get_info(HSA_SYSTEM_INFO_VERSION_MINOR, &version_minor); res = pfn_hsa_system_get_info(HSA_SYSTEM_INFO_VERSION_MINOR, &version_minor);
if(res != 0) { if(res != 0) {
WARN("pfn_hsa_system_get_info failed with %d", res); WARN("pfn_hsa_system_get_info failed with %d", res);
...@@ -162,6 +163,7 @@ static void initOnceFunc() { ...@@ -162,6 +163,7 @@ static void initOnceFunc() {
*/ */
initResult = scclSuccess; initResult = scclSuccess;
return;
error: error:
initResult = scclSystemError; initResult = scclSystemError;
...@@ -182,6 +184,8 @@ scclResult_t rocmLibraryInit() { ...@@ -182,6 +184,8 @@ scclResult_t rocmLibraryInit() {
return rocm_wrap::initResult; return rocm_wrap::initResult;
} }
int64_t getDmaBufEnable() { return rocm_wrap::scclParamDmaBufEnable(); }
} // namespace net } // namespace net
} // namespace hardware } // namespace hardware
} // namespace sccl } // namespace sccl
...@@ -25,6 +25,8 @@ DECLARE_ROCM_PFN_EXTERN(hsa_status_string); ...@@ -25,6 +25,8 @@ DECLARE_ROCM_PFN_EXTERN(hsa_status_string);
// 初始化 ROCm 库 // 初始化 ROCm 库
scclResult_t rocmLibraryInit(void); scclResult_t rocmLibraryInit(void);
// 获取 dma buf
int64_t getDmaBufEnable();
} // namespace net } // namespace net
} // namespace hardware } // namespace hardware
......
#include <unistd.h> #include <unistd.h>
#include <sys/types.h> #include <sys/types.h>
#include <string.h> #include <cstring>
#include <sys/resource.h> #include <sys/resource.h>
#include <iostream> #include <iostream>
#include <iomanip> #include <iomanip>
...@@ -15,20 +15,6 @@ ...@@ -15,20 +15,6 @@
namespace sccl { namespace sccl {
namespace hardware { namespace hardware {
namespace topology { namespace topology {
/**
* @brief 执行根节点的数据收集和广播操作
*
* 该函数负责以下操作:
* 1. 设置本地监听服务
* 2. 向根节点发送本节点的基本数据
* 3. 从根节点接收本地rank数量信息
* 4. 当本地rank为0时,从根节点接收所有rank的IP数据
* 5. 将收集到的所有rank数据广播给节点内其他rank
*
* @param send_data_basic 发送给根节点的节点基础数据
* @param recv_data_basic 接收广播数据的缓冲区向量
* @return scclResult_t 返回操作结果,成功返回scclSuccess
*/
namespace bootstrap { namespace bootstrap {
//////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////
...@@ -101,7 +87,7 @@ static scclResult_t basicInit() { ...@@ -101,7 +87,7 @@ static scclResult_t basicInit() {
return scclSuccess; return scclSuccess;
} }
scclResult_t bootstrapGetUniqueId(struct BootstrapHandle* handle) { scclResult_t bootstrapGetUniqueId(BootstrapHandle_t* handle) {
SCCLCHECK(basicInit()); SCCLCHECK(basicInit());
// 在每个进程中设置 handle 的值 // 在每个进程中设置 handle 的值
getRandomData(&handle->magic, sizeof(handle->magic)); getRandomData(&handle->magic, sizeof(handle->magic));
...@@ -152,18 +138,18 @@ static scclResult_t setFilesLimit() { ...@@ -152,18 +138,18 @@ static scclResult_t setFilesLimit() {
* @note 函数使用线程池加速消息分发,并通过日志记录关键操作步骤 * @note 函数使用线程池加速消息分发,并通过日志记录关键操作步骤
*/ */
static void* bootstrapRoot(void* rargs) { static void* bootstrapRoot(void* rargs) {
struct bootstrapRootArgs* args = (struct bootstrapRootArgs*)rargs; bootstrapRootArgs_t* args = (bootstrapRootArgs_t*)rargs;
scclSocket_t* listenSock = args->listenSock; // 用于监听的套接字 scclSocket_t* listenSock = args->listenSock; // 用于监听的套接字
uint64_t magic = args->magic; // 用于验证的魔数 uint64_t magic = args->magic; // 用于验证的魔数
scclResult_t res = scclSuccess; // 函数结果 scclResult_t res = scclSuccess; // 函数结果
class ThreadPool* pthread_pool = nullptr; // 用于根节点分发消息的线程池 class ThreadPool* pthread_pool = nullptr; // 用于根节点分发消息的线程池
int nRanks = 0; // nRanks: 进程总数; int nRanks = 0; // nRanks: 进程总数;
int nLocalRanks = 1; int nLocalRanks = 1;
int c = 0; // c: 已连接的进程计数 int c = 0; // c: 已连接的进程计数
uint64_t rootHostHash = 0; uint64_t rootHostHash = 0;
struct BootstrapNodeBasic node_basic = {}; // 用于存储扩展信息的结构体 BootstrapNodeBasic_t node_basic = {}; // 用于存储扩展信息的结构体
struct BootstrapNodeBasic* all_rank_node_basic = nullptr; // 所有进程的地址 BootstrapNodeBasic_t* all_rank_node_basic = nullptr; // 所有进程的地址
// 定义一个函数或者一个函数对象,用于执行实际的发送数据操作。在后面执行 // 定义一个函数或者一个函数对象,用于执行实际的发送数据操作。在后面执行
auto send_task = [](BootstrapNodeBasic& node_basic, uint64_t magic, int rank, void* data, size_t size) { auto send_task = [](BootstrapNodeBasic& node_basic, uint64_t magic, int rank, void* data, size_t size) {
...@@ -196,7 +182,7 @@ static void* bootstrapRoot(void* rargs) { ...@@ -196,7 +182,7 @@ static void* bootstrapRoot(void* rargs) {
} }
// 保存该rank的连接句柄 // 保存该rank的连接句柄
memcpy(all_rank_node_basic + node_basic.rank, &node_basic, sizeof(struct BootstrapNodeBasic)); memcpy(all_rank_node_basic + node_basic.rank, &node_basic, sizeof(BootstrapNodeBasic_t));
++c; // 增加已连接的进程计数 ++c; // 增加已连接的进程计数
INFO(SCCL_LOG_BOOTSTRAP, "Received connect from rank %d total %d/%d", node_basic.rank, c, nRanks); // 日志 INFO(SCCL_LOG_BOOTSTRAP, "Received connect from rank %d total %d/%d", node_basic.rank, c, nRanks); // 日志
} while(c < nRanks); // 当已连接的进程数小于总数时循环 } while(c < nRanks); // 当已连接的进程数小于总数时循环
...@@ -222,7 +208,7 @@ static void* bootstrapRoot(void* rargs) { ...@@ -222,7 +208,7 @@ static void* bootstrapRoot(void* rargs) {
} }
// 等待所有任务完成 // 等待所有任务完成
while(!pthread_pool->allTasksCompleted()) { while(!pthread_pool->allTasksCompleted()) {
usleep(1000); // 每1毫秒检查一次任务完成状态 usleep(100); // 每1毫秒检查一次任务完成状态
} }
// --------------------- 3.给所有localRank==0的rank发送all_rank_node_basic数据 --------------------- // // --------------------- 3.给所有localRank==0的rank发送all_rank_node_basic数据 --------------------- //
...@@ -231,12 +217,11 @@ static void* bootstrapRoot(void* rargs) { ...@@ -231,12 +217,11 @@ static void* bootstrapRoot(void* rargs) {
int dst_rank = r * nLocalRanks; // 计算目标rank int dst_rank = r * nLocalRanks; // 计算目标rank
auto dst_node_basic = all_rank_node_basic[dst_rank]; auto dst_node_basic = all_rank_node_basic[dst_rank];
net::net_socket::scclSocketClientManager client_manager(&dst_node_basic.sock.addr, magic, net::net_socket::scclSocketTypeBootstrap); net::net_socket::scclSocketClientManager client_manager(&dst_node_basic.sock.addr, magic, net::net_socket::scclSocketTypeBootstrap);
bootstrapNet::bootstrapNetSend(client_manager.getSocket(), all_rank_node_basic, sizeof(struct BootstrapNodeBasic) * nRanks); bootstrapNet::bootstrapNetSend(client_manager.getSocket(), all_rank_node_basic, sizeof(BootstrapNodeBasic_t) * nRanks);
printf("root send nLocalRanks value to rank=%d\n", r);
} }
// 等待所有任务完成 // 等待所有任务完成
while(!pthread_pool->allTasksCompleted()) { while(!pthread_pool->allTasksCompleted()) {
usleep(1000); // 每1毫秒检查一次任务完成状态 usleep(100); // 每1毫秒检查一次任务完成状态
} }
INFO(SCCL_LOG_BOOTSTRAP, "bootstrap send out all %d handles", nRanks); // 日志:发送出所有句柄 INFO(SCCL_LOG_BOOTSTRAP, "bootstrap send out all %d handles", nRanks); // 日志:发送出所有句柄
...@@ -268,8 +253,8 @@ out: ...@@ -268,8 +253,8 @@ out:
* @param handle 包含bootstrap配置信息的句柄 * @param handle 包含bootstrap配置信息的句柄
* @return 成功返回scclSuccess,失败返回相应的错误码 * @return 成功返回scclSuccess,失败返回相应的错误码
*/ */
scclResult_t bootstrapCreateRoot(struct BootstrapHandle* handle) { scclResult_t bootstrapCreateRoot(BootstrapHandle_t* handle) {
struct bootstrapRootArgs* args; bootstrapRootArgs_t* args;
pthread_t thread; pthread_t thread;
// 设置根节点socket监听 // 设置根节点socket监听
...@@ -291,12 +276,46 @@ scclResult_t bootstrapCreateRoot(struct BootstrapHandle* handle) { ...@@ -291,12 +276,46 @@ scclResult_t bootstrapCreateRoot(struct BootstrapHandle* handle) {
return scclSuccess; return scclSuccess;
} }
////////////////////////////// 结构体定义 //////////////////////////////
// scclRankPhysSet构造函数定义
scclRankPhysSet::scclRankPhysSet(int nRanks, int nLocalRanks)
: nRanks(nRanks), nLocalRanks(nLocalRanks), node_info_total_bytes(sizeof(scclTopoNode_t) * topoNodeMaxLocalNodes / nLocalRanks) {
printf("scclRankPhysSet 构造函数\n");
rank_info_vec.reserve(nRanks); // 预留空间
rank_info_vec.clear();
// 与scclNodeInfo_t中的定义一致
node_info_vec.reserve(nRanks * node_info_total_bytes); // 预留空间
node_info_vec.clear();
printf("scclRankPhysSet 预留空间并初始化node_info_vec, nRanks * node_info_total_bytes=%zu\n", nRanks * node_info_total_bytes);
}
void BootstrapComm::init(int rank, int nRanks, int localRank, int nLocalRanks) {
printf("BootstrapComm 构造函数, rank=%d\n", rank);
this->rank = rank;
this->nRanks = nRanks;
this->localRank = localRank;
this->nLocalRanks = nLocalRanks;
this->interRank = rank / nLocalRanks;
this->nInterRanks = nRanks / nLocalRanks;
rank_phys_set = new scclRankPhysSet(nRanks, nLocalRanks); // 假设需要动态分配
};
void BootstrapComm::destroy() {
printf("BootstrapComm 析构函数, rank=%d\n", rank);
if(rank_phys_set) {
delete rank_phys_set; // 释放动态分配的内存
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////
// 构造函数 // 构造函数
Bootstrap::Bootstrap(const struct BootstrapHandle* handle, int rank, int nRanks) Bootstrap::Bootstrap(const BootstrapHandle_t* handle, int rank, int nRanks)
: root_handle(handle), rank(rank), nRanks(nRanks), localRank(-1), nLocalRanks(0), socketInitDone(false) { : root_handle(handle), rank(rank), nRanks(nRanks), localRank(-1), nLocalRanks(0), socketInitDone(false) {
printf("Bootstrap 构造函数\n"); printf("Bootstrap 构造函数\n");
scclCalloc(&all_node_basic, nRanks);
} }
Bootstrap::~Bootstrap() { Bootstrap::~Bootstrap() {
...@@ -304,9 +323,11 @@ Bootstrap::~Bootstrap() { ...@@ -304,9 +323,11 @@ Bootstrap::~Bootstrap() {
if(ipcsocket) { if(ipcsocket) {
delete ipcsocket; delete ipcsocket;
} }
if(all_node_basic)
free(all_node_basic);
} }
scclResult_t Bootstrap::init(struct BootstrapComm* bootstrap_comm) { scclResult_t Bootstrap::init(BootstrapComm_t* bootstrap_comm) {
// 如果已经初始化,直接返回成功 // 如果已经初始化,直接返回成功
if(asm_ops::ld_acquire_sys_global(&socketInitDone)) if(asm_ops::ld_acquire_sys_global(&socketInitDone))
return scclSuccess; return scclSuccess;
...@@ -317,13 +338,11 @@ scclResult_t Bootstrap::init(struct BootstrapComm* bootstrap_comm) { ...@@ -317,13 +338,11 @@ scclResult_t Bootstrap::init(struct BootstrapComm* bootstrap_comm) {
// -------------------------- 1.获取自身基础信息 ----------------------------------- // // -------------------------- 1.获取自身基础信息 ----------------------------------- //
SCCLCHECK(basicInit()); SCCLCHECK(basicInit());
// 设置基础信息 // 设置基础信息
struct BootstrapNodeBasic node_basic = {}; BootstrapNodeBasic_t node_basic = {};
// -------------------------- 2.设置0号rank搜集的CPU信息和localRank信息 ----------------------------------- // // -------------------------- 2.设置0号rank搜集的CPU信息和localRank信息 ----------------------------------- //
// 创建根节点的数据收集 // 创建根节点的数据收集
std::vector<struct BootstrapNodeBasic> all_node_basic; SCCLCHECK(bootstrapRootGatherAndBroadcast(&node_basic));
all_node_basic.reserve(nRanks);
SCCLCHECK(bootstrapRootGatherAndBroadcast(&node_basic, all_node_basic));
// -------------------------- 3.设置本地localRank的BootstrapComm信息 ----------------------------------- // // -------------------------- 3.设置本地localRank的BootstrapComm信息 ----------------------------------- //
// 初始化BootstrapComm类 // 初始化BootstrapComm类
...@@ -342,53 +361,123 @@ scclResult_t Bootstrap::init(struct BootstrapComm* bootstrap_comm) { ...@@ -342,53 +361,123 @@ scclResult_t Bootstrap::init(struct BootstrapComm* bootstrap_comm) {
LTCHECK(devices_num, 0); // 检查设备数量是否 devices_num>0 LTCHECK(devices_num, 0); // 检查设备数量是否 devices_num>0
LTCHECK(devices_num, nLocalRanks); // 检查设备数量是否 devices_num>nLocalRanks LTCHECK(devices_num, nLocalRanks); // 检查设备数量是否 devices_num>nLocalRanks
bootstrap_comm->deviceCnt = static_cast<int>(devices_num); // 将设备数量转换为int并赋值给的deviceCnt bootstrap_comm->deviceCnt = static_cast<int>(devices_num); // 将设备数量转换为int并赋值给的deviceCnt
#if 0 LECHECK(devices_num, bootstrap_comm->hipDev); // 检查hipDev是否小于deviceCnt
printf("devices_num=%d\n", bootstrap_comm->deviceCnt); HIPCHECK(hipSetDevice(bootstrap_comm->hipDev)); // 设置当前设备为hipDev
#endif
LECHECK(devices_num, bootstrap_comm->hipDev); // 检查hipDev是否小于deviceCnt
HIPCHECK(hipSetDevice(bootstrap_comm->hipDev)); // 设置当前设备为hipDev
//////// 设置启动通信的scclNet //////// //////// 设置启动通信的scclNet ////////
// 获取环境变量SCCL_NET_NAME的值,如果不存在则默认使用"IB" // 获取环境变量SCCL_NET_NAME的值,如果不存在则默认使用"IB"
const char* envNetName = getenv("SCCL_NET_NAME"); const char* envNetName = getenv("SCCL_NET_NAME");
char* netName = (envNetName != NULL) ? strdup(envNetName) : strdup("IB"); // char* netName = (envNetName != NULL) ? strdup(envNetName) : strdup("IB");
#if 0 char* netName = strdup("IB");
printf("netName=%s\n", netName);
#endif
// 初始化网络和引导网络 // 初始化网络和引导网络
SCCLCHECK(net::scclNetInit(netName, bootstrap_comm->scclNet)); SCCLCHECK(net::scclNetInit(netName, bootstrap_comm->scclNet));
// 释放分配的网络名称字符串 // 释放分配的网络名称字符串
free(netName); free(netName);
//////// 初始化唯一信息结构体 //////// //////// 初始化唯一信息结构体 ////////
struct scclNodeInfo local_node_info; scclRankInfo_t local_rank_info;
// 补充定义 local_rank_info.hostHash = node_basic.hostHash;
local_node_info.hostHash = node_basic.hostHash; SCCLCHECK(bootstrapCommInitNodeInfo(bootstrap_comm->scclNet, &local_rank_info));
SCCLCHECK(bootstrapCommInitNodeInfo(bootstrap_comm->scclNet, &local_node_info)); memcpy(&(local_rank_info.cpu.listen_sock), &(node_basic.sock), sizeof(scclSocket_t));
// 设置CPU信息
memcpy(&(local_node_info.localNode.cpu.listen_sock), &(node_basic.sock), sizeof(scclSocket_t)); //////// 初始化topo node ////////
scclNodeInfo_t local_topo_nodes(nLocalRanks);
// 使用ByteSpan替代std::vector,并指定容量为pNodes_len
ByteSpanVector<scclTopoNode_t> nodes_span((void*)local_topo_nodes.nodes, local_topo_nodes.totalByteSize);
#if 1
printf("devices_num=%d, local_rank_info.net.count=%d\n", bootstrap_comm->deviceCnt, local_rank_info.net.count);
#endif
// 遍历所有的GPU的pciPath,添加topo node
for(int r = localRank; r < devices_num; r += nLocalRanks) {
auto gpu_path = physical_links::getGpuPciPath(r);
physical_links::generate_topo_nodes(gpu_path, interRank, r, nodes_span);
delete(gpu_path);
}
// 遍历所有的NIC的pciPath,添加topo node
for(int r = localRank; r < local_rank_info.net.count; r += nLocalRanks) {
auto net_path = physical_links::getNetPciPath(bootstrap_comm->scclNet, r);
physical_links::generate_topo_nodes(net_path, interRank, r, nodes_span);
delete(net_path);
}
#if 0 #if 0
{ if(interRank == 0) {
char line[20]; ByteSpanArray<scclTopoNode_t> nodes_span_array(nodes_span.data(), local_topo_nodes.totalByteSize);
sprintf(line, "11111 print rank=%d", rank); printf("print rank=%d, nodes_span size=%zu\n", rank, nodes_span.size());
std::string prefix(line); // 创建prefix字符串 char line[30];
printNodeInfo(prefix, &local_node_info); // 正确的调用方式 sprintf(line, "print rank=%d: ", rank);
for(int i = 0; i < nodes_span.size(); i++) {
printf("============================**============================\n");
physical_links::printTopoNode(nodes_span_array, i, line);
printf("============================**============================\n");
}
}
#endif
#if 0
// 尝试采用软件识别GPU之间互联
for(int i = 0; i < bootstrap_comm->deviceCnt; i++) {
// if(i != bootstrap_comm->hipDev) {
RSMI_IO_LINK_TYPE rsmi_type;
int hops, count;
if(rocm_smi_getLinkInfo(bootstrap_comm->hipDev, i, &rsmi_type, &hops, &count) == scclSuccess) {
printf("rank=%d, i=%d, dev=%d, rsmi_type=%d, hops=%d, count=%d\n", rank, i, bootstrap_comm->hipDev, rsmi_type, hops, count);
// if(rsmi_type == RSMI_IOLINK_TYPE_XGMI && hops <= 2) {
// if(1) {
// char busIdStr[] = "00000000:00:00.0";
// SCCLCHECK(rocm_smi_getDevicePciBusIdString(i, busIdStr, sizeof(busIdStr)));
// char lowerId[16];
// for(int c = 0; c < 16; c++) {
// lowerId[c] = tolower(busIdStr[c]);
// if(busIdStr[c] == 0)
// break;
// }
// }
} else {
printf("rsmi get type fail\n");
}
// }
} }
#endif #endif
// -------------------------- 4.BootstrapComm信息的allgather ----------------------------------- // // -------------------------- 4.BootstrapComm信息的allgather ----------------------------------- //
bootstrapCommAllGather(all_node_basic, &local_node_info, bootstrap_comm->node_info_set); bootstrapCommAllGather(&local_rank_info, &local_topo_nodes, bootstrap_comm->rank_phys_set);
if(1) { // TODO: 目前手动将节点内的GPU进行mesh连接,因为无法从/sys/device中获取NIC的拓扑信息,rsmi函数也无法获取NIC的拓扑信息。后续优化
char line[20]; bootstrapNodesLink(bootstrap_comm->rank_phys_set->node_info_vec.data(), bootstrap_comm->rank_phys_set->node_info_total_bytes);
sprintf(line, "print rank=%d", rank); #if 0
std::string prefix(line); // 创建prefix字符串 if(rank == 1) {
for(int r = 0; r < nRanks; r++) { size_t dataLen = bootstrap_comm->rank_phys_set->node_info_total_bytes;
struct scclNodeInfo node_basic = bootstrap_comm->node_info_set->node_info_vec[r]; printf("nRanks * bootstrap_comm->rank_phys_set->node_info_total_bytes=%zu, %lu\n", dataLen, nRanks * dataLen);
printNodeInfo(prefix, &node_basic); // 正确的调用方式 auto node_info_data = reinterpret_cast<char*>(bootstrap_comm->rank_phys_set->node_info_vec.data());
ByteSpanArray<scclTopoNode_t> nodes_span_all(node_info_data, nRanks * dataLen);
printf("print rank=%d, nodes_span_all size=%zu, scclTopoNode_t size=%zu\n", rank, nodes_span_all.size(), sizeof(scclTopoNode_t));
char line[30];
sprintf(line, "print rank=%d: ", rank);
int node_cnt = 0;
for(int i = 0; i < nodes_span_all.size(); i++) {
if(nodes_span_all[i] && nodes_span_all[i]->type > 0) {
if(i < 64) {
printf("============================&&============================\n");
physical_links::printTopoNode(nodes_span_all, i, line);
printf("============================&&============================\n");
} else if(i < 128) {
printf("============================((============================\n");
physical_links::printTopoNode(nodes_span_all, i, line);
printf("============================))============================\n");
} else {
printf("============================@@============================\n");
physical_links::printTopoNode(nodes_span_all, i, line);
printf("============================@@============================\n");
}
node_cnt += 1;
}
} }
printf("print rank=%d, node_cnt=%d\n", rank, node_cnt);
} }
#endif
// 设置初始化标志 // 设置初始化标志
asm_ops::st_release_sys_global(&socketInitDone, true); asm_ops::st_release_sys_global(&socketInitDone, true);
...@@ -400,22 +489,20 @@ scclResult_t Bootstrap::init(struct BootstrapComm* bootstrap_comm) { ...@@ -400,22 +489,20 @@ scclResult_t Bootstrap::init(struct BootstrapComm* bootstrap_comm) {
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
/** /**
* @brief 执行根节点的数据收集和广播操作 * @brief 执行根节点的集和广播操作
* *
* 该函数负责以下操作: * 该函数负责在bootstrap过程中完成以下操作:
* 1. 设置本地监听服务 * 1. 各rank首先设置监听
* 2. 向根节点发送本节点的基本数据 * 2. 向根节点发送基础数据
* 3. 从根节点接收本地rank数量信息 * 3. 从根节点接收nLocalRanks值
* 4. 当本地rank为0时,从根节点接收所有rank的IP数据 * 4. 当localRank为0时,从根节点接收所有rank的IP数据
* 5. 将收集到的所有rank数据广播给节点内其他rank * 5. 将IP数据广播给节点内其他rank
* *
* @param send_data 发送给根节点的数据指针 * @param send_data_basic 指向要发送的基础数据的指针
* @param recv_data 接收广播数据的缓冲区指针
* @return scclResult_t 返回操作结果,成功返回scclSuccess * @return scclResult_t 返回操作结果,成功返回scclSuccess
*/ */
scclResult_t Bootstrap::bootstrapRootGatherAndBroadcast(struct BootstrapNodeBasic* send_data_basic, std::vector<struct BootstrapNodeBasic>& recv_data_basic) { scclResult_t Bootstrap::bootstrapRootGatherAndBroadcast(BootstrapNodeBasic_t* send_data_basic) {
// 总的需要广播的数据 // 总的需要广播的数据
int recv_data_basic_size = nRanks * sizeof(struct BootstrapNodeBasic);
scclSocketAddress_t root_addr = root_handle->addr; scclSocketAddress_t root_addr = root_handle->addr;
scclSocketAddress_t localSocketAddr = bootstrapNet::getLocalSocketAddr(); scclSocketAddress_t localSocketAddr = bootstrapNet::getLocalSocketAddr();
...@@ -433,7 +520,7 @@ scclResult_t Bootstrap::bootstrapRootGatherAndBroadcast(struct BootstrapNodeBasi ...@@ -433,7 +520,7 @@ scclResult_t Bootstrap::bootstrapRootGatherAndBroadcast(struct BootstrapNodeBasi
// ------------- 2.各个节点向根节点发送数据 ------------- // // ------------- 2.各个节点向根节点发送数据 ------------- //
{ {
net::net_socket::scclSocketClientManager client_manager(&root_addr, root_handle->magic, net::net_socket::scclSocketTypeBootstrap); net::net_socket::scclSocketClientManager client_manager(&root_addr, root_handle->magic, net::net_socket::scclSocketTypeBootstrap);
SCCLCHECK(bootstrapNet::bootstrapNetSend(client_manager.getSocket(), send_data_basic, sizeof(struct BootstrapNodeBasic))); SCCLCHECK(bootstrapNet::bootstrapNetSend(client_manager.getSocket(), send_data_basic, sizeof(BootstrapNodeBasic_t)));
} }
// ------------- 3.从根节点接收nLocalRanks值 ------------- // // ------------- 3.从根节点接收nLocalRanks值 ------------- //
// 接收nLocalRanks信息 // 接收nLocalRanks信息
...@@ -450,15 +537,17 @@ scclResult_t Bootstrap::bootstrapRootGatherAndBroadcast(struct BootstrapNodeBasi ...@@ -450,15 +537,17 @@ scclResult_t Bootstrap::bootstrapRootGatherAndBroadcast(struct BootstrapNodeBasi
this->interRank = rank / nLocalRanks; this->interRank = rank / nLocalRanks;
this->nInterRanks = nRanks / nLocalRanks; this->nInterRanks = nRanks / nLocalRanks;
int all_node_basic_size = nRanks * sizeof(BootstrapNodeBasic_t);
// 从根节点接收数据,对应到函数 bootstrapRoot // 从根节点接收数据,对应到函数 bootstrapRoot
if(localRank == 0) { if(localRank == 0) {
net::net_socket::scclSocketAcceptManager accept_manager(local_server_sock); net::net_socket::scclSocketAcceptManager accept_manager(local_server_sock);
SCCLCHECK(bootstrapNet::bootstrapNetRecv(accept_manager.getSocket(), recv_data_basic.data(), recv_data_basic_size)); SCCLCHECK(bootstrapNet::bootstrapNetRecv(accept_manager.getSocket(), all_node_basic, all_node_basic_size));
} }
// ------------- 5.nLocalRanks==0时,将所有rank的ip数据广播给节点内其他rank ------------- // // ------------- 5.nLocalRanks==0时,将所有rank的ip数据广播给节点内其他rank ------------- //
ipcsocket = new scclIpcSocket_t(localRank, nLocalRanks, /*hash*/ root_handle->magic); ipcsocket = new scclIpcSocket_t(localRank, nLocalRanks, /*hash*/ root_handle->magic);
ipcsocket->scclIpcSocketBroadcast(recv_data_basic.data(), recv_data_basic_size, /*localRank root*/ 0); ipcsocket->scclIpcSocketBroadcast(all_node_basic, all_node_basic_size, /*localRank root*/ 0);
return scclSuccess; return scclSuccess;
} }
...@@ -466,88 +555,135 @@ scclResult_t Bootstrap::bootstrapRootGatherAndBroadcast(struct BootstrapNodeBasi ...@@ -466,88 +555,135 @@ scclResult_t Bootstrap::bootstrapRootGatherAndBroadcast(struct BootstrapNodeBasi
/** /**
* @brief 初始化节点通信信息 * @brief 初始化节点通信信息
* *
* 该函数用于初始化节点的通信信息,包括: * 该函数用于初始化节点的通信信息,包括基础信息和硬件信息。
* - 设置节点的全局排名和本地排名
* - 获取并设置进程ID哈希值
* - 设置GPU设备属性(名称、GCN架构、计算能力)
* - 设置RDMA网络属性
* - 设置PCI总线ID
* - 设置CPU套接字地址
* *
* @param scclNet 网络句柄 * @param scclNet 网络设备句柄
* @param socket_addr 套接字地址 * @param rank_info 节点信息结构体指针
* @param node_info 节点信息结构体指针
* @return scclResult_t 返回操作结果,成功返回scclSuccess * @return scclResult_t 返回操作结果,成功返回scclSuccess
*
* @note 基础信息包括:
* - rank: 当前节点的全局排名
* - localRank: 本地计算节点中的排名
* - pidHash: 进程ID哈希值
*
* @note 硬件信息包括:
* - GPU信息: 设备号、名称、GCN架构、计算能力、PCI总线ID
* - RDMA信息: 网卡数量、名称、PCI路径、GUID、指针支持类型、端口速度、端口号、延迟、最大通信数和接收数
*
* @todo 更多硬件信息可参考ncclTopoGetXmlFromSys函数实现
*/ */
scclResult_t Bootstrap::bootstrapCommInitNodeInfo(scclNet_t* scclNet, struct scclNodeInfo* node_info) { scclResult_t Bootstrap::bootstrapCommInitNodeInfo(scclNet_t* scclNet, scclRankInfo_t* rank_info) {
////////////////// 设置基础信息 ////////////////// ////////////////// 设置基础信息 //////////////////
node_info->rank = rank; // 当前节点的全局排名 rank_info->rank = rank; // 当前节点的全局排名
node_info->localRank = localRank; // 当前节点在本地计算节点中的排名 rank_info->localRank = localRank; // 当前节点在本地计算节点中的排名
node_info->pidHash = getPidHash(); // 获取进程ID哈希值并赋值给的pidHash rank_info->pidHash = getPidHash(); // 获取进程ID哈希值并赋值给的pidHash
int hipDev = localRank; int hipDev = localRank;
////////////////// 设置硬件信息 ////////////////// ////////////////// 设置硬件信息 //////////////////
struct topoLocalNode* p_localNode = &node_info->localNode; //// 1.设置GPU信息
rank_info->gpu.dev = hipDev;
// 设置PCI信息
SCCLCHECK(getBusId(hipDev, &p_localNode->pci.busId));
// 设置GPU信息
p_localNode->gpu.dev = hipDev;
hipDeviceProp_t deviceProp; hipDeviceProp_t deviceProp;
HIPCHECK(hipGetDeviceProperties(&deviceProp, hipDev)); HIPCHECK(hipGetDeviceProperties(&deviceProp, hipDev));
snprintf(p_localNode->gpu.name, sizeof(p_localNode->gpu.name), "%s", deviceProp.name); snprintf(rank_info->gpu.name, sizeof(rank_info->gpu.name), "%s", deviceProp.name);
snprintf(p_localNode->gpu.gcn, sizeof(p_localNode->gpu.gcn), "%s", deviceProp.gcnArchName); snprintf(rank_info->gpu.gcn, sizeof(rank_info->gpu.gcn), "%s", deviceProp.gcnArchName);
p_localNode->gpu.compCap = deviceProp.major * 10 + deviceProp.minor; rank_info->gpu.compCap = deviceProp.major * 10 + deviceProp.minor;
// 设置GPU的busId
// 设置RDMA信息 SCCLCHECK(getBusId(hipDev, &rank_info->gpu.pciBusId));
// 根据GPU的busId设置pci路径
char busIdStr[] = "00000000:00:00.0";
char* gpuPath = NULL;
SCCLCHECK(int64ToBusId(rank_info->gpu.pciBusId, busIdStr));
SCCLCHECK(getPciPath(busIdStr, &gpuPath));
snprintf(rank_info->gpu.pciPath, sizeof(rank_info->gpu.pciPath), "%s", gpuPath); // 设备在/sys中的路径。
//// 2.设置RDMA信息
net::scclNetProperties_t props; net::scclNetProperties_t props;
SCCLCHECK(scclNet->getProperties(hipDev, &props)); SCCLCHECK(scclNet->getProperties(hipDev, &props));
SCCLCHECK(scclNet->devices(&p_localNode->net.count)); // 节点内网卡数量 SCCLCHECK(scclNet->devices(&rank_info->net.count)); // 节点内网卡数量
snprintf(p_localNode->net.name, sizeof(p_localNode->net.name), "%s", props.name); // 主要用于日志记录。 snprintf(rank_info->net.name, sizeof(rank_info->net.name), "%s", props.name); // 主要用于日志记录。
snprintf(p_localNode->net.pciPath, sizeof(p_localNode->net.pciPath), "%s", props.pciPath); // PCI设备在/sys中的路径。 snprintf(rank_info->net.pciPath, sizeof(rank_info->net.pciPath), "%s", props.pciPath); // PCI设备在/sys中的路径。
#if 0 #if 0
printf("p_localNode->net.pciPath len=%zu\n", strlen(p_localNode->net.pciPath)); printf("rank_info->net.pciPath len=%zu\n", strlen(rank_info->net.pciPath));
#endif #endif
p_localNode->net.guid = props.guid; // NIC芯片的唯一标识符。对于具有多个PCI功能(物理或虚拟)的卡非常重要。
p_localNode->net.ptrSupport = props.ptrSupport; // [SCCL_PTR_HOST|SCCL_PTR_CUDA|SCCL_PTR_DMABUF]
p_localNode->net.speed = props.speed; // 端口速度,单位为Mbps。
p_localNode->net.port = props.port; // 端口号。
p_localNode->net.latency = props.latency; // 网络延迟
p_localNode->net.maxComms = props.maxComms; // 可以创建的最大通信数量
p_localNode->net.maxRecvs = props.maxRecvs; // 最大分组接收数量。
// TODO: 更多硬件信息参考ncclTopoGetXmlFromSys函数写法,可以通过 "/sys/class/drm/card1/device" 等路径读取
rank_info->net.guid = props.guid; // NIC芯片的唯一标识符。对于具有多个PCI功能(物理或虚拟)的卡非常重要。
rank_info->net.ptrSupport = props.ptrSupport; // [SCCL_PTR_HOST|SCCL_PTR_CUDA|SCCL_PTR_DMABUF]
rank_info->net.speed = props.speed; // 端口速度,单位为Mbps。
rank_info->net.port = props.port; // 端口号。
rank_info->net.latency = props.latency; // 网络延迟
rank_info->net.maxComms = props.maxComms; // 可以创建的最大通信数量
rank_info->net.maxRecvs = props.maxRecvs; // 最大分组接收数量。
return scclSuccess;
}
/**
* @brief 实现节点间通信的AllGather操作
*
* 该函数通过调用`bootstrapAllGather`函数,实现节点间通信的AllGather操作。
* 它将每个节点的`rank_info`信息和`node_info`信息收集到`rank_phys_set`中,以便后续使用。
*
* @param rank_info 指向当前节点的`rank_info`信息的指针
* @param node_info 指向当前节点的`node_info`信息的指针
* @param rank_phys_set 指向节点信息集合的指针,用于存储所有节点的`rank_info`和`node_info`信息
* @return scclResult_t 返回操作结果状态码:
*     - scclSuccess: 操作成功
*     - 其他错误码: 表示操作失败
*
* @note 该函数是一个简化的接口,用于调用`bootstrapAllGather`函数来实现节点间通信的AllGather操作。
*     在调用`bootstrapAllGather`函数之前,需要确保`rank_info`、`node_info`和`rank_phys_set`已经正确初始化。
*/
scclResult_t Bootstrap::bootstrapCommAllGather(scclRankInfo_t* rank_info, scclNodeInfo_t* node_info, scclRankPhysSet_t* rank_phys_set) {
SCCLCHECK(bootstrapAllGather(rank_info, rank_phys_set->rank_info_vec.data(), sizeof(scclRankInfo_t)));
SCCLCHECK(bootstrapAllGather(node_info->nodes, rank_phys_set->node_info_vec.data(), rank_phys_set->node_info_total_bytes));
return scclSuccess; return scclSuccess;
} }
scclResult_t Bootstrap::bootstrapCommAllGather(std::vector<struct BootstrapNodeBasic>& all_node_basic, // TODO: 后续可以采用优化,先节点内allgather,再节点间的allgather,最后节点内的Broadcast。优化的算法并保证正确性
struct scclNodeInfo* node_info, /**
struct scclNodeInfoSet* node_info_set) { * @brief 实现跨节点的AllGather通信操作
*
* 该函数实现了一个跨节点的AllGather通信操作,包括节点内通信和节点间通信。
* 在节点内通信中,使用IPC套接字进行AllGather操作;在节点间通信中,使用Ring AllGather算法进行数据传输。
* 最后,通过节点内通信的Broadcast操作,将收集到的数据分发给所有节点内的进程。
*
* @param src_data 源数据指针,表示每个节点要发送的数据
* @param dst_data 目标数据指针,表示所有节点收集到的数据将存储在此处
* @param data_size 每个节点要发送的数据大小(以字节为单位)
* @return scclResult_t 返回操作结果状态码:
* - scclSuccess: 操作成功
* - 其他错误码: 表示操作失败
*
* @note 该函数假设所有节点的本地秩(localRank)和节点间秩(interRank)已经正确设置。
* 此外,该函数还假设所有节点的基本信息(如套接字地址)已经通过其他途径正确获取并存储在all_node_basic向量中。
* 在节点间通信中,使用了Ring AllGather算法,该算法在nRanks特别大的时候可能不是最优的选择,可以考虑进一步优化算法以减少通信次数。
*/
scclResult_t Bootstrap::bootstrapAllGather(const void* src_data, void* dst_data, int data_size) {
// 数据准备 // 数据准备
size_t inter_data_len = nLocalRanks * sizeof(struct scclNodeInfo); // 节点间传输时每个子块的大小 size_t inter_data_len = nLocalRanks * data_size; // 节点间传输时每个子块的大小
auto all_recv_data = reinterpret_cast<char*>(node_info_set->node_info_vec.data()); auto all_recv_data = reinterpret_cast<char*>(dst_data);
// 1.节点内通信 allgather //// 1.节点内通信 allgather
auto local_recv_data = all_recv_data + this->interRank * inter_data_len; auto local_recv_data = all_recv_data + this->interRank * inter_data_len;
ipcsocket->scclIpcSocketAllgatherSync(node_info, (void*)local_recv_data, sizeof(struct scclNodeInfo)); ipcsocket->scclIpcSocketAllgather(src_data, (void*)local_recv_data, data_size);
// 2.节点间通信,ring allgather if(nInterRanks <= 1) {
// TODO: 后续nRanks特别大的时候,可以进一步优化算法,减少通信次数 return scclSuccess;
}
//// 2.节点间通信,ring allgather
// TODO: 后续nRanks特别大的时候,可以进一步优化算法,减少通信次数。
// 因为节点内信息是已经 allgather了的,所以节点间的传输可以不仅仅依赖localRank == 0,每个localRank都可以用上,即分组
if(localRank == 0) { if(localRank == 0) {
int prev_interRank = (this->interRank - 1 + this->nInterRanks) % this->nInterRanks * this->nLocalRanks;
int next_interRank = (this->interRank + 1 + this->nInterRanks) % this->nInterRanks * this->nLocalRanks; int next_interRank = (this->interRank + 1 + this->nInterRanks) % this->nInterRanks * this->nLocalRanks;
// scclSocket_t prev_rank_sock = all_node_basic[prev_interRank].sock; //// 对于prev,当前rank是客户端;对于next,当前rank是服务器端
scclSocket_t next_rank_sock = all_node_basic[next_interRank].sock;
scclSocket_t self_rank_sock = all_node_basic[rank].sock;
printf("bootstrap allgather 11: rank %d, prev_interRank=%d, next_interRank=%d\n", rank, prev_interRank, next_interRank);
// 对于prev,当前rank是客户端;对于next,当前rank是服务器端
// 客户端:用于发送数据 // 客户端:用于发送数据
scclSocket_t next_rank_sock = all_node_basic[next_interRank].sock;
net::net_socket::scclSocketClientManager client_manager(&next_rank_sock.addr, root_handle->magic, net::net_socket::scclSocketTypeBootstrap); net::net_socket::scclSocketClientManager client_manager(&next_rank_sock.addr, root_handle->magic, net::net_socket::scclSocketTypeBootstrap);
// 服务器端:用于接收数据 // 服务器端:用于接收数据
scclSocket_t self_rank_sock = all_node_basic[rank].sock;
net::net_socket::scclSocketAcceptManager accept_manager(&self_rank_sock); net::net_socket::scclSocketAcceptManager accept_manager(&self_rank_sock);
/////////////////// 实现数据传输 /////////////////// /////////////////// 实现数据传输 ///////////////////
for(int r = 0; r < nInterRanks - 1; r++) { for(int r = 0; r < nInterRanks - 1; r++) {
int prev_rank = (this->interRank - r - 1 + nInterRanks) % nInterRanks; int prev_rank = (this->interRank - r - 1 + nInterRanks) % nInterRanks;
...@@ -555,37 +691,303 @@ scclResult_t Bootstrap::bootstrapCommAllGather(std::vector<struct BootstrapNodeB ...@@ -555,37 +691,303 @@ scclResult_t Bootstrap::bootstrapCommAllGather(std::vector<struct BootstrapNodeB
// 准备发送/接收的数据 // 准备发送/接收的数据
auto send_data = all_recv_data + next_rank * inter_data_len; auto send_data = all_recv_data + next_rank * inter_data_len;
auto recv_data = all_recv_data + prev_rank * inter_data_len; auto recv_data = all_recv_data + prev_rank * inter_data_len;
#if 0
printf("bootstrapCommAllGather rank=%d, interRank=%d, r=%d, prev_rank=%d, next_rank=%d, inter_data_len=%zu\n",
this->rank,
this->interRank,
r,
prev_rank,
next_rank,
inter_data_len);
#endif
// 发送/接收数据 // 发送/接收数据
SCCLCHECK(bootstrapNet::bootstrapNetSend(client_manager.getSocket(), send_data, inter_data_len)); SCCLCHECK(bootstrapNet::bootstrapNetSend(client_manager.getSocket(), send_data, inter_data_len));
SCCLCHECK(bootstrapNet::bootstrapNetRecv(accept_manager.getSocket(), recv_data, inter_data_len)); SCCLCHECK(bootstrapNet::bootstrapNetRecv(accept_manager.getSocket(), recv_data, inter_data_len));
} }
} }
//// 3.节点内通信 broadcast
ipcsocket->scclIpcSocketBroadcast(all_recv_data, nRanks * data_size, 0);
return scclSuccess;
}
// TODO: 当前实现使用了较多的for循环,在节点数量较大时速度较慢,可以考虑采用cuda kernel
/**
* @brief 初始化并连接节点之间的链接
*
* 该函数接收一个指向节点信息的字节数组的指针和节点信息的总字节数,用于初始化并连接节点之间的链接。
* 1.创建一个`ByteSpanArray`对象来管理节点信息的内存,然后根据节点的类型(GPU、PCI或NIC)将它们分类存储。
* 2.它使相同`interRank`下的GPU节点两两互联
* 3.遍历所有的`interRank`来合并具有相同`id`、`type`和`busIdStr`的PCI节点。
* 4.使CPU node即numa node的neighbors两两互联。
* 5.它使相同`deviceId`下的NIC节点两两互联。
*
* @param node_info_vec 指向节点信息的字节数组的指针
* @param node_info_total_bytes 节点信息的总字节数
* @return scclResult_t 返回操作结果状态码:
* - scclSuccess: 操作成功
* - scclError: 操作失败
*/
scclResult_t Bootstrap::bootstrapNodesLink(void* node_info_vec, int node_info_total_bytes) {
// 创建一个ByteSpanArray对象,用于管理节点信息的内存
ByteSpanArray<scclTopoNode_t> node_info_span(node_info_vec, nRanks * node_info_total_bytes);
// 用于将nodes的deviceId对应的node
std::unordered_map<uint64_t, std::vector<scclTopoNode_t*>> nodes_map_by_deviceId;
// 用于将interRank内nodes的deviceSig对应的NIC节点连接
std::unordered_map<uint64_t, std::vector<scclTopoNode_t*>> nic_nodes_by_deviceId;
// 用于识别并连接节点内的GPU node
std::vector<std::vector<scclTopoNode_t*>> gpu_nodes_by_interRank(nInterRanks);
// -------------------------- 1.遍历所有的节点信息,记录node -------------------------- //
for(size_t i = 0; i < node_info_span.size(); ++i) {
scclTopoNode_t* node = node_info_span[i];
// 跳过空节点、跳过没有busId的节点(如空节点或CPU)
if(node->type <= 0) {
continue;
}
uint64_t id = node->id;
int interRank;
physical_links::getIdComponents(id, &interRank);
uint64_t deviceSig = id & 0xFFFFFFFFFF; // 计算 interRank(24bit) + hipDev(8bit) + deviceId(16bit) + terminalType(8bit) + numaId(8bit)
// 选择type为GPU的节点
if(node->type == GPU) {
if(interRank >= gpu_nodes_by_interRank.size()) {
gpu_nodes_by_interRank.resize(interRank + 1);
}
gpu_nodes_by_interRank[interRank].push_back(node);
} else if(node->type == NIC) {
nic_nodes_by_deviceId[deviceSig].push_back(node);
}
nodes_map_by_deviceId[id].push_back(node);
}
// 合并id相同和busId相同的node
for(auto& pair : nodes_map_by_deviceId) {
auto& nodes = pair.second;
for(size_t i = 0; i < nodes.size(); ++i) {
for(size_t j = i + 1; j < nodes.size(); ++j) {
// if(nodes[i]->id == nodes[j]->id && nodes[i]->type == nodes[j]->type && nodes[i]->busId == nodes[j]->busId) {
// SCCLCHECK(nodes[i]->combineNode(nodes[j]));
// }
if(nodes[i]->id == nodes[j]->id) {
if(nodes[i]->type == nodes[j]->type && nodes[i]->busId == nodes[j]->busId) {
SCCLCHECK(nodes[i]->combineNode(nodes[j]));
} else {
#if 0 #if 0
printf("222222\n"); int tmpi_interRank, tmpi_deviceValue, tmpi_terminalType, tmpi_hipDev, tmpi_numaId;
if(rank == 0) { physical_links::getIdComponents(nodes[i]->id, &tmpi_interRank, &tmpi_deviceValue, &tmpi_terminalType, &tmpi_hipDev, &tmpi_numaId);
char line[20]; int tmpj_interRank, tmpj_deviceValue, tmpj_terminalType, tmpj_hipDev, tmpj_numaId;
sprintf(line, "print rank=%d", rank); physical_links::getIdComponents(nodes[j]->id, &tmpj_interRank, &tmpj_deviceValue, &tmpj_terminalType, &tmpj_hipDev, &tmpj_numaId);
std::string prefix(line); // 创建prefix字符串 char busIdStr_i[17], busIdStr_j[17];
for(int r = 0; r < nRanks; r++) { int64ToBusId(nodes[i]->busId, busIdStr_i);
struct scclNodeInfo node_basic = node_info_set->node_info_vec[r]; int64ToBusId(nodes[j]->busId, busIdStr_j);
printNodeInfo(prefix, &node_basic); // 正确的调用方式
printf("same Id but different type or busId: %lu(InterRank:%d, V:%d, T:%d, H:%d, N:%d, busIdStr:%s) and %lu(InterRank:%d, V:%d, T:%d, "
"H:%d, N:%d, busIdStr:%s)\n",
nodes[i]->id,
tmpi_interRank,
tmpi_deviceValue,
tmpi_terminalType,
tmpi_hipDev,
tmpi_numaId,
busIdStr_i,
nodes[j]->id,
tmpj_interRank,
tmpj_deviceValue,
tmpj_terminalType,
tmpj_hipDev,
tmpj_numaId,
busIdStr_j);
#endif
}
}
}
} }
} }
// 遍历所有的节点信息,将CPU的所有neighbor node两两互联
for(size_t i = 0; i < node_info_span.size(); ++i) {
scclTopoNode_t* node = node_info_span[i];
// 跳过空节点、跳过没有busId的节点(如空节点或CPU)
if(node->type == CPU) {
for(size_t i = 0; i < node->neighborCount; ++i) {
for(size_t j = i + 1; j < node->neighborCount; ++j) {
// 使用unordered_map来加速查找
auto it_i = nodes_map_by_deviceId.find(node->neighbors[i]);
auto it_j = nodes_map_by_deviceId.find(node->neighbors[j]);
if(it_i != nodes_map_by_deviceId.end() && it_j != nodes_map_by_deviceId.end()) {
scclTopoNode_t* neighbor_i = nullptr;
scclTopoNode_t* neighbor_j = nullptr;
for(auto& n : it_i->second) {
if(n->type > 0) {
neighbor_i = n;
break;
}
}
for(auto& n : it_j->second) {
if(n->type > 0) {
neighbor_j = n;
break;
}
}
if(neighbor_i && neighbor_j) {
neighbor_i->addNeighbor(neighbor_j->id);
neighbor_j->addNeighbor(neighbor_i->id);
#if 0
{
int tmpi_interRank, tmpi_deviceValue, tmpi_terminalType, tmpi_hipDev, tmpi_numaId;
physical_links::getIdComponents(
neighbor_i->id, &tmpi_interRank, &tmpi_deviceValue, &tmpi_terminalType, &tmpi_hipDev, &tmpi_numaId);
int tmpj_interRank, tmpj_deviceValue, tmpj_terminalType, tmpj_hipDev, tmpj_numaId;
physical_links::getIdComponents(
neighbor_j->id, &tmpj_interRank, &tmpj_deviceValue, &tmpj_terminalType, &tmpj_hipDev, &tmpj_numaId);
char busIdStr_i[17], busIdStr_j[17];
int64ToBusId(neighbor_i->busId, busIdStr_i);
int64ToBusId(neighbor_j->busId, busIdStr_j);
printf("connect CPU neighbors %lu(InterRank:%d, V:%d, T:%d, H:%d, N:%d, busIdStr:%s) and %lu(InterRank:%d, V:%d, T:%d, H:%d, "
"N:%d, busIdStr:%s)\n",
neighbor_i->id,
tmpi_interRank,
tmpi_deviceValue,
tmpi_terminalType,
tmpi_hipDev,
tmpi_numaId,
busIdStr_i,
neighbor_j->id,
tmpj_interRank,
tmpj_deviceValue,
tmpj_terminalType,
tmpj_hipDev,
tmpj_numaId,
busIdStr_j);
}
#endif
}
}
}
}
}
}
// 使相同interRank下的GPU node两两互联
for(const auto& nodes : gpu_nodes_by_interRank) {
for(size_t i = 0; i < nodes.size(); ++i) {
for(size_t j = i + 1; j < nodes.size(); ++j) {
nodes[i]->addNeighbor(nodes[j]->id);
nodes[j]->addNeighbor(nodes[i]->id);
#if 0
{
int tmpi_interRank, tmpi_deviceValue, tmpi_terminalType, tmpi_hipDev, tmpi_numaId;
physical_links::getIdComponents(nodes[i]->id, &tmpi_interRank, &tmpi_deviceValue, &tmpi_terminalType, &tmpi_hipDev, &tmpi_numaId);
int tmpj_interRank, tmpj_deviceValue, tmpj_terminalType, tmpj_hipDev, tmpj_numaId;
physical_links::getIdComponents(nodes[j]->id, &tmpj_interRank, &tmpj_deviceValue, &tmpj_terminalType, &tmpj_hipDev, &tmpj_numaId);
char busIdStr_i[17], busIdStr_j[17];
int64ToBusId(nodes[i]->busId, busIdStr_i);
int64ToBusId(nodes[j]->busId, busIdStr_j);
printf("connect GPU %lu(InterRank:%d, V:%d, T:%d, H:%d, N:%d, busIdStr:%s) and %lu(InterRank:%d, V:%d, T:%d, H:%d, N:%d, busIdStr:%s)\n",
nodes[i]->id,
tmpi_interRank,
tmpi_deviceValue,
tmpi_terminalType,
tmpi_hipDev,
tmpi_numaId,
busIdStr_i,
nodes[j]->id,
tmpj_interRank,
tmpj_deviceValue,
tmpj_terminalType,
tmpj_hipDev,
tmpj_numaId,
busIdStr_j);
}
#endif #endif
}
}
}
// 3.节点内通信 broadcast // 使相同deviceId下的NIC节点两两互联
ipcsocket->scclIpcSocketBroadcast(all_recv_data, nRanks * sizeof(struct scclNodeInfo), 0); for(const auto& pair : nic_nodes_by_deviceId) {
const auto& nodes = pair.second;
for(size_t i = 0; i < nodes.size(); ++i) {
for(size_t j = i + 1; j < nodes.size(); ++j) {
// 在deviceId相同的情况下,比较busIdStr
if(nodes[i]->busId == nodes[j]->busId) {
nodes[i]->addNeighbor(nodes[j]->id);
nodes[j]->addNeighbor(nodes[i]->id);
#if 0
{
int tmpi_interRank, tmpi_deviceValue, tmpi_terminalType, tmpi_hipDev, tmpi_numaId;
physical_links::getIdComponents(nodes[i]->id, &tmpi_interRank, &tmpi_deviceValue, &tmpi_terminalType, &tmpi_hipDev, &tmpi_numaId);
int tmpj_interRank, tmpj_deviceValue, tmpj_terminalType, tmpj_hipDev, tmpj_numaId;
physical_links::getIdComponents(nodes[j]->id, &tmpj_interRank, &tmpj_deviceValue, &tmpj_terminalType, &tmpj_hipDev, &tmpj_numaId);
char busIdStr_i[17], busIdStr_j[17];
int64ToBusId(nodes[i]->busId, busIdStr_i);
int64ToBusId(nodes[j]->busId, busIdStr_j);
printf("connect NIC interRank %lu(InterRank:%d, V:%d, T:%d, H:%d, N:%d, busIdStr:%s) and %lu(InterRank:%d, V:%d, T:%d, H:%d, N:%d, "
"busIdStr:%s)\n",
nodes[i]->id,
tmpi_interRank,
tmpi_deviceValue,
tmpi_terminalType,
tmpi_hipDev,
tmpi_numaId,
busIdStr_i,
nodes[j]->id,
tmpj_interRank,
tmpj_deviceValue,
tmpj_terminalType,
tmpj_hipDev,
tmpj_numaId,
busIdStr_j);
}
#endif
}
}
}
}
return scclSuccess;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
// 函数:打印 scclRankInfo 结构体的信息
scclResult_t printRankInfo(const std::string& prefix, scclRankInfo_t* info) {
char addrline[net::SOCKET_NAME_MAXLEN + 1];
// if(info->localRank == 0) {
if(1) {
// 将GPU的pciBusId转换为字符串格式
char busIdhip[16];
SCCLCHECK(int64ToBusId(info->gpu.pciBusId, busIdhip));
printf("==========================================\n"
"%s, Total Rank: %d, Local Rank: %d, Host Hash: %lu, PID Hash: %lu\n"
"gpu: dev=%d, gpu.name=%s, gcn=%s, compCap=%d\n"
"gpu: busId=%s, gpuPath=%s\n"
"net: count=%d, device name=%s, pciPath=%s, guid=%lu, ptrSupport=%u, speed=%d, port=%d, latency=%f, maxComms=%d, maxRecvs=%d\n"
"cpu: socketAddr=%s\npci: busId=%ld"
"\n==========================================\n",
prefix.c_str(),
info->rank,
info->localRank,
info->hostHash,
info->pidHash,
info->gpu.dev,
info->gpu.name,
info->gpu.gcn,
info->gpu.compCap,
busIdhip,
info->gpu.pciPath,
info->net.count,
info->net.name,
info->net.pciPath,
info->net.guid,
static_cast<unsigned int>(info->net.ptrSupport),
info->net.speed,
info->net.port,
info->net.latency,
info->net.maxComms,
info->net.maxRecvs,
net::net_socket::scclSocketToString(&info->cpu.listen_sock.addr, addrline),
info->gpu.pciBusId);
}
return scclSuccess; return scclSuccess;
} }
......
...@@ -7,37 +7,139 @@ ...@@ -7,37 +7,139 @@
#include "bootstrap_net.h" #include "bootstrap_net.h"
#include "thread_pool.h" #include "thread_pool.h"
#include "ipc_socket.h" #include "ipc_socket.h"
#include "physical_links.h"
namespace sccl { namespace sccl {
namespace hardware { namespace hardware {
namespace topology { namespace topology {
namespace bootstrap { namespace bootstrap {
typedef class sccl::hardware::net::ipc_socket::scclIpcSocket scclIpcSocket_t; typedef sccl::hardware::net::ipc_socket::scclIpcSocket_t scclIpcSocket_t;
typedef physical_links::scclTopoNode_t scclTopoNode_t;
///////////////////////////////////// 用于初始化时的功能函数 ////////////////////////////////////////// ///////////////////////////////////// 用于初始化时的功能函数 //////////////////////////////////////////
scclResult_t bootstrapGetUniqueId(struct BootstrapHandle* handle); scclResult_t bootstrapGetUniqueId(BootstrapHandle_t* handle);
scclResult_t bootstrapCreateRoot(struct BootstrapHandle* handle); scclResult_t bootstrapCreateRoot(BootstrapHandle_t* handle);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 定义结构体 scclRankInfo,用于存储每个rank的通信节点的信息
// 每个rank必定选定一个GPU、一个RDMA设备、一个CPU,作为通信节点
typedef struct scclRankInfo {
struct {
scclSocket_t listen_sock; // 监听套接字
} cpu; // CPU节点
struct {
int dev; // NVML设备编号
char name[8]; // 设备名称
char gcn[8]; // GCN架构名称
int compCap; // CUDA计算能力
int64_t pciBusId; // PCI总线ID以int64_t格式表示
char pciPath[128]; // PCI设备在/sys中的路径。
} gpu; // GPU节点
struct {
int count; // 网卡数量
char name[8]; // 主要用于日志记录。
char pciPath[128]; // PCI设备在/sys中的路径。
uint64_t guid; // NIC芯片的唯一标识符。对于具有多个PCI功能(物理或虚拟)的卡非常重要。
uint8_t ptrSupport; // [SCCL_PTR_HOST|SCCL_PTR_CUDA|SCCL_PTR_DMABUF]
int speed; // 端口速度,单位为Mbps。
int port; // 端口号。
float latency; // 网络延迟
int maxComms; // 可以创建的最大通信数量
int maxRecvs; // 最大分组接收数量。
} net; // 网络节点
int rank = -1; // 当前节点的全局排名
int localRank = -1; // 当前节点在本地计算节点中的排名
uint64_t hostHash = 0; // 主机哈希值
uint64_t pidHash = 0; // 进程 ID 哈希值
} scclRankInfo_t;
// 定义结构体 scclNodeInfo,用于存储每个rank的图连接信息
// TODO: 目前每个rank需要的node_info大小为4k+,当卡数较大时占用内存较大,可以优化。或者不作为全局变量
typedef struct scclNodeInfo {
scclTopoNode_t* nodes; // 指向scclTopoNode_t对象数组的指针
int nLocalRanks;
int totalByteSize; // 表示占用的总字节数
// 带参数的构造函数,用于初始化nodes的大小
scclNodeInfo(int nLocalRanks) : nodes(nullptr), nLocalRanks(nLocalRanks), totalByteSize(sizeof(scclTopoNode_t) * topoNodeMaxLocalNodes / nLocalRanks) {
nodes = reinterpret_cast<scclTopoNode_t*>(malloc(totalByteSize));
if(nodes) {
memset(nodes, 0, totalByteSize);
}
}
// 析构函数,用于释放申请的数组空间
virtual ~scclNodeInfo() {
if(nodes) {
free(nodes);
}
}
} scclNodeInfo_t;
// 所有节点的信息
typedef struct scclRankPhysSet {
// 构造函数声明
scclRankPhysSet(int nRanks, int nLocalRanks);
std::vector<scclRankInfo_t> rank_info_vec;
std::vector<char> node_info_vec; // 实际为std::vector<scclNodeInfo_t>,vector不支持scclNodeInfo_t变长
public:
int nRanks = 0; // 总的节点数量
int nLocalRanks = 0; // 本地计算节点中的节点总数
size_t node_info_total_bytes = 0; // 记录可变长度scclNodeInfo_t类型数据的实际大小
} scclRankPhysSet_t;
// BootstrapComm 结构体定义,用于存储引导通信信息
typedef struct BootstrapComm {
void init(int rank, int nRanks, int localRank, int nLocalRanks);
void destroy();
public:
scclNet_t* scclNet = nullptr;
scclRankPhysSet_t* rank_phys_set = nullptr;
cpu_set_t cpuAffinity; // CPU亲和性
int rank = -1; // 当前节点的全局排名
int nRanks = 0; // 总的节点数量
int localRank = -1; // 当前节点在本地计算节点中的排名
int nLocalRanks = 0; // 本地计算节点中的节点总数
int interRank = -1; // 整个节点在全部节点中的位置
int nInterRanks = 0; // 全局拥有节点的个数
int hipDev = -1; // CUDA 设备 ID
int deviceCnt = 0; // 设备数量
uint64_t magic; // 魔术数,用于验证结构体
} BootstrapComm_t;
///////////////////////////////////// 用于初始化时的类 ////////////////////////////////////////// ///////////////////////////////////// 用于初始化时的类 //////////////////////////////////////////
class Bootstrap { class Bootstrap {
public: public:
Bootstrap(const struct BootstrapHandle*, int rank, int nRanks); Bootstrap(const BootstrapHandle_t*, int rank, int nRanks);
~Bootstrap(); ~Bootstrap();
// 初始化bootstrap通信环境 // 初始化bootstrap通信环境
scclResult_t init(struct BootstrapComm* bootstrap_comm); scclResult_t init(BootstrapComm_t* bootstrap_comm);
// 实现跨节点的AllGather通信操作
scclResult_t bootstrapAllGather(const void* src_data, void* dst_data, int data_size);
private: private:
// 创建根节点的数据广播 // 执行根节点的聚集和广播操作
scclResult_t bootstrapRootGatherAndBroadcast(struct BootstrapNodeBasic* send_data_basic, std::vector<struct BootstrapNodeBasic>& recv_data_basic); scclResult_t bootstrapRootGatherAndBroadcast(BootstrapNodeBasic_t* send_data_basic);
// 初始化唯一ID信息结构体 // 初始化节点通信信息
scclResult_t bootstrapCommInitNodeInfo(scclNet_t* scclNet, struct scclNodeInfo* node_info); scclResult_t bootstrapCommInitNodeInfo(scclNet_t* scclNet, scclRankInfo_t* rank_info);
// 广播节点信息 // 实现rank_info信息的节点间通信的AllGather操作
scclResult_t scclResult_t bootstrapCommAllGather(scclRankInfo_t* rank_info, scclNodeInfo_t* node_info, scclRankPhysSet_t* rank_phys_set);
bootstrapCommAllGather(std::vector<struct BootstrapNodeBasic>& all_node_basic, struct scclNodeInfo* node_info, struct scclNodeInfoSet* node_info_set);
// 额外处理nRanks个nodes的连接关系
scclResult_t bootstrapNodesLink(void* node_info_vec, int node_info_total_bytes);
private: private:
int rank, nRanks; // 初始化阶段获取MPI的值 int rank, nRanks; // 初始化阶段获取MPI的值
...@@ -48,7 +150,9 @@ private: ...@@ -48,7 +150,9 @@ private:
volatile uint32_t* abortFlag; // 中止标志,非阻塞套接字设置 volatile uint32_t* abortFlag; // 中止标志,非阻塞套接字设置
// 外部传入的0号节点的基础信息 // 外部传入的0号节点的基础信息
const struct BootstrapHandle* root_handle; const BootstrapHandle_t* root_handle;
// 节点内所有进程的基础ip信息
BootstrapNodeBasic_t* all_node_basic = nullptr;
// 初始化标志 // 初始化标志
bool socketInitDone; bool socketInitDone;
...@@ -60,6 +164,9 @@ private: ...@@ -60,6 +164,9 @@ private:
scclIpcSocket_t* ipcsocket = nullptr; // 指向scclIpcSocket类实例的指针,初始值为nullptr scclIpcSocket_t* ipcsocket = nullptr; // 指向scclIpcSocket类实例的指针,初始值为nullptr
}; };
// 打印唯一的拓扑信息
scclResult_t printRankInfo(const std::string& prefix, scclRankInfo_t* info);
} // namespace bootstrap } // namespace bootstrap
} // namespace topology } // namespace topology
} // namespace hardware } // namespace hardware
......
...@@ -7,32 +7,6 @@ namespace hardware { ...@@ -7,32 +7,6 @@ namespace hardware {
namespace topology { namespace topology {
namespace bootstrap { namespace bootstrap {
////////////////////////////// 结构体定义 //////////////////////////////
// 构造函数定义
scclNodeInfoSet::scclNodeInfoSet(int nRanks) {
printf("scclNodeInfoSet 构造函数\n");
node_info_vec.reserve(nRanks); // 预留空间
printf("scclNodeInfoSet 预留空间\n");
}
void BootstrapComm::init(int rank, int nRanks, int localRank, int nLocalRanks) {
printf("BootstrapComm 构造函数, rank=%d\n", rank);
this->rank = rank;
this->nRanks = nRanks;
this->localRank = localRank;
this->nLocalRanks = nLocalRanks;
this->interRank = rank / nLocalRanks;
this->nInterRanks = nRanks / nLocalRanks;
node_info_set = new scclNodeInfoSet(nRanks); // 假设需要动态分配
};
void BootstrapComm::destroy() {
printf("BootstrapComm 析构函数, rank=%d\n", rank);
if(node_info_set) {
delete node_info_set; // 释放动态分配的内存
}
}
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
/** /**
...@@ -193,44 +167,6 @@ scclResult_t getBusId(int hipDev, int64_t* busId) { ...@@ -193,44 +167,6 @@ scclResult_t getBusId(int hipDev, int64_t* busId) {
return scclSuccess; return scclSuccess;
} }
// 函数:打印 scclNodeInfo 结构体的信息
scclResult_t printNodeInfo(const std::string& prefix, struct scclNodeInfo* info) {
char addrline[net::SOCKET_NAME_MAXLEN + 1];
// if(info->localRank == 0) {
if(1) {
printf("==========================================\n"
"%s, Total Rank: %d, Local Rank: %d, Host Hash: %lu, PID Hash: %lu\n"
"gpu: dev=%d, gpu.name=%s, gcn=%s, compCap=%d\n"
"net: count=%d, device name=%s, pciPath=%s, guid=%lu, ptrSupport=%u, speed=%d, port=%d, latency=%f, maxComms=%d, maxRecvs=%d\n"
"cpu: socketAddr=%s\npci: busId=%ld"
"\n==========================================\n",
prefix.c_str(),
info->rank,
info->localRank,
info->hostHash,
info->pidHash,
info->localNode.gpu.dev,
info->localNode.gpu.name,
info->localNode.gpu.gcn,
info->localNode.gpu.compCap,
info->localNode.net.count,
info->localNode.net.name,
info->localNode.net.pciPath,
info->localNode.net.guid,
static_cast<unsigned int>(info->localNode.net.ptrSupport),
info->localNode.net.speed,
info->localNode.net.port,
info->localNode.net.latency,
info->localNode.net.maxComms,
info->localNode.net.maxRecvs,
net::net_socket::scclSocketToString(&info->localNode.cpu.listen_sock.addr, addrline),
info->localNode.pci.busId);
}
return scclSuccess;
}
} // namespace bootstrap } // namespace bootstrap
} // namespace topology } // namespace topology
} // namespace hardware } // namespace hardware
......
#pragma once #pragma once
#include <string.h> #include <map>
#include <string>
#include <cstddef> #include <cstddef>
#include <vector> #include <vector>
#include "base.h" #include "base.h"
...@@ -17,106 +18,31 @@ typedef union net::net_socket::scclSocketAddress scclSocketAddress_t; ...@@ -17,106 +18,31 @@ typedef union net::net_socket::scclSocketAddress scclSocketAddress_t;
typedef struct net::net_socket::scclSocket scclSocket_t; typedef struct net::net_socket::scclSocket scclSocket_t;
typedef net::scclNet_t scclNet_t; typedef net::scclNet_t scclNet_t;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 用于初始化时广播0号rank的地址信息 // 用于初始化时广播0号rank的地址信息
struct BootstrapHandle { typedef struct BootstrapHandle {
uint64_t magic = 0; // 随机码,用于socket通信 uint64_t magic = 0; // 随机码,用于socket通信
scclSocketAddress_t addr; // 地址,用于网络通信 scclSocketAddress_t addr; // 地址,用于网络通信
}; } BootstrapHandle_t;
#define SCCL_UNIQUE_ID_BYTES (40) // sizeof(BootstrapHandle) #define SCCL_UNIQUE_ID_BYTES (40) // sizeof(BootstrapHandle_t)
typedef struct { typedef struct {
char internal[SCCL_UNIQUE_ID_BYTES]; char internal[SCCL_UNIQUE_ID_BYTES];
} scclUniqueId; } scclUniqueId;
// 仅用于初始化的函数bootstrapCreateRoot,用于传递detach线程的参数 // 仅用于初始化的函数bootstrapCreateRoot,用于传递detach线程的参数
struct bootstrapRootArgs { typedef struct bootstrapRootArgs {
uint64_t magic; uint64_t magic;
scclSocket_t* listenSock = nullptr; // 根节点的监听 scclSocket_t* listenSock = nullptr; // 根节点的监听
}; } bootstrapRootArgs_t;
// 用于初始建立连接阶段,0号rank之外的进程向其传递的信息 // 用于初始建立连接阶段,0号rank之外的进程向其传递的信息
struct BootstrapNodeBasic { typedef struct BootstrapNodeBasic {
int rank; int rank;
int nRanks; // 进程的总数量 int nRanks; // 进程的总数量
uint64_t hostHash; // 用于区分host的CPU编号 uint64_t hostHash; // 用于区分host的CPU编号
scclSocket_t sock; // 各个进程的监听套接字地址,用于网络通信 scclSocket_t sock; // 各个进程的监听套接字地址,用于网络通信
}; } BootstrapNodeBasic_t;
// 定义每个rank所持有的所有拓扑节点
struct topoLocalNode {
struct {
scclSocket_t listen_sock; // 监听套接字
} cpu; // CPU节点
struct {
int64_t busId; // PCI总线ID以int64_t格式表示
} pci; // pci节点
struct {
int dev; // NVML设备编号
char name[8]; // 设备名称
char gcn[8]; // GCN架构名称
int compCap; // CUDA计算能力
} gpu; // GPU节点
struct {
int count; // 网卡数量
char name[8]; // 主要用于日志记录。
char pciPath[128]; // PCI设备在/sys中的路径。
uint64_t guid; // NIC芯片的唯一标识符。对于具有多个PCI功能(物理或虚拟)的卡非常重要。
uint8_t ptrSupport; // [SCCL_PTR_HOST|SCCL_PTR_CUDA|SCCL_PTR_DMABUF]
int speed; // 端口速度,单位为Mbps。
int port; // 端口号。
float latency; // 网络延迟
int maxComms; // 可以创建的最大通信数量
int maxRecvs; // 最大分组接收数量。
} net; // 网络节点
};
// 定义结构体 scclNodeInfo,用于存储每个rank的通信节点的信息
struct scclNodeInfo {
struct topoLocalNode localNode;
int rank = -1; // 当前节点的全局排名
int localRank = -1; // 当前节点在本地计算节点中的排名
uint64_t hostHash = 0; // 主机哈希值
uint64_t pidHash = 0; // 进程 ID 哈希值
};
// 每个节点的信息
struct scclNodeInfoSet {
std::vector<struct scclNodeInfo> node_info_vec;
// 构造函数声明
scclNodeInfoSet(int nRanks);
};
// BootstrapComm 结构体定义,用于存储引导通信信息
struct BootstrapComm {
void init(int rank, int nRanks, int localRank, int nLocalRanks);
void destroy();
public:
scclNet_t* scclNet;
struct scclNodeInfoSet* node_info_set;
cpu_set_t cpuAffinity; // CPU亲和性
int rank = -1; // 当前节点的全局排名
int nRanks = 0; // 总的节点数量
int localRank = -1; // 当前节点在本地计算节点中的排名
int nLocalRanks = 0; // 本地计算节点中的节点总数
int interRank = -1; // 整个节点在全部节点中的位置
int nInterRanks = 0; // 全局拥有节点的个数
int hipDev = -1; // CUDA 设备 ID
int deviceCnt = 0; // 设备数量
// proxy通信
uint64_t magic; // 魔术数,用于验证结构体
volatile uint32_t* abortFlag; // 中止标志,非阻塞套接字设置
// int splitShare; // 是否使用共享内存进行分割
// int* topParentRanks; // 顶级父节点的rank
// /* 与代理相关的共享资源 */
// struct scclProxyState* proxyState;
};
//////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 获取主机唯一标识的哈希值,该哈希值在裸机和容器实例中都是唯一的 // 获取主机唯一标识的哈希值,该哈希值在裸机和容器实例中都是唯一的
...@@ -134,25 +60,6 @@ scclResult_t getBusId(int hipDev, int64_t* busId); ...@@ -134,25 +60,6 @@ scclResult_t getBusId(int hipDev, int64_t* busId);
// 获取当前HIP设备的计算能力版本号 // 获取当前HIP设备的计算能力版本号
int scclCudaCompCap(void); int scclCudaCompCap(void);
// 打印唯一的拓扑信息
scclResult_t printNodeInfo(const std::string& prefix, struct scclNodeInfo* info);
// 实现类似于std::span的功能,将字节数组转换为类型数组
template <typename T>
class ByteSpan {
public:
ByteSpan(const char* data, std::size_t size) : data_(reinterpret_cast<const T*>(data)), size_(size / sizeof(T)) {}
const T* data() const { return data_; }
std::size_t size() const { return size_; }
const T& operator[](std::size_t index) const { return data_[index]; }
private:
const T* data_;
std::size_t size_;
};
} // namespace bootstrap } // namespace bootstrap
} // namespace topology } // namespace topology
} // namespace hardware } // namespace hardware
......
#include <cstring> // 为了使用strlen
#include "physical_links.h"
namespace sccl {
namespace hardware {
namespace topology {
namespace bootstrap {
namespace physical_links {
constexpr int numaIdStrLen = 10;
constexpr int speedStrLen = 20;
constexpr int widthStrLen = 10;
constexpr int busIdStrLen = 17;
// 定义一个映射,用于将PCI类代码映射到设备类型
static std::map<std::string, int> pciClassMap = {
{"0x060400", PCI}, {"0x068000", NVS}, {"0x068001", CPU}, {"0x03", GPU}, {"0x02", NIC}, {"0x120000", GPU}, {"0x0b4000", GPU}};
// 定义一个映射,用于将PCI代代码映射到带宽
static std::map<std::string, int> pciSpeedMap = {{"2.5 GT/s", 15},
{"5 GT/s", 30},
{"8 GT/s", 60},
{"16 GT/s", 120},
{"32 GT/s", 240}, // Kernel 5.6 and earlier
{"2.5 GT/s PCIe", 15},
{"5.0 GT/s PCIe", 30},
{"8.0 GT/s PCIe", 60},
{"16.0 GT/s PCIe", 120},
{"32.0 GT/s PCIe", 240},
{"64.0 GT/s PCIe", 480}};
/**
* 检查给定路径是否为有效的PCI设备路径
*
* @param pciPath 要检查的PCI设备路径
* @return true 如果路径包含PCI设备所需的class/device/max_link_speed/max_link_width文件
* @return false 如果路径不包含上述文件
*/
static bool isPciDevice(const std::string& pciPath) {
// 检查type和device等文件是否存在
if(std::filesystem::exists(pciPath + "class") && std::filesystem::exists(pciPath + "device") && std::filesystem::exists(pciPath + "max_link_speed") &&
std::filesystem::exists(pciPath + "max_link_width") && std::filesystem::exists(pciPath + "numa_node")) {
return true;
}
return false;
}
/**
* @brief 获取给定字符串中通过指定分隔符分割的最后一个非空子字符串。
*
* 该函数从给定字符串的末尾开始,查找由指定分隔符分割的最后一个非空子字符串。
* 如果找到的子字符串为空,则继续向前查找,直到找到一个非空的子字符串或回到字符串的开头。
* 如果最后依然没有找到非空的子字符串,则返回输入的字符串。
*
* @param str 要查找的字符串。
* @param delimiter 用于分割字符串的分隔符。
* @return std::string 最后一个非空的子字符串。如果没有找到,则返回输入的字符串。
*/
static std::string getLastNonEmptySegment(const std::string& str, char delimiter) {
size_t lastSlashPos = str.find_last_of(delimiter);
while(lastSlashPos != std::string::npos) {
size_t start = lastSlashPos + 1;
size_t end = str.find(delimiter, start);
std::string segment = str.substr(start, end - start);
if(!segment.empty()) {
return segment;
}
lastSlashPos = str.find_last_of(delimiter, lastSlashPos - 1);
}
return str; // 如果没有找到非空的子字符串,返回原始字符串
}
/**
* @brief 在映射表中查找与给定键最接近的匹配项
*
* 通过比较键的前缀长度来寻找最佳匹配,返回具有最长公共前缀的键。
*
* @tparam T 映射表值的类型
* @param key 要匹配的键
* @param map 要搜索的映射表
* @return std::string 返回映射表中与输入键具有最长公共前缀的键
*/
template <typename T>
static std::string findClosestMatch(const std::string& key, const std::map<std::string, T>& map) {
std::string bestMatch;
size_t longestCommonPrefix = 0;
for(const auto& pair : map) {
size_t commonPrefix = 0;
while(commonPrefix < key.size() && commonPrefix < pair.first.size() && key[commonPrefix] == pair.first[commonPrefix]) {
++commonPrefix;
}
if(commonPrefix > longestCommonPrefix) {
longestCommonPrefix = commonPrefix;
bestMatch = pair.first;
}
}
return bestMatch;
}
// 定义一个函数,用于从指定路径下的"class"文件中读取设备类型字符串,并根据查找到的匹配项,获取设备类型
static int getDeviceTypeFromPciPath(const char* pciPath) {
char typeStr[9];
scclTopoGetStrFromSys(pciPath, "class", typeStr);
std::string class_map_key = findClosestMatch(std::string(typeStr), pciClassMap);
return pciClassMap[class_map_key];
}
// 根据节点ID在ByteSpan中查找节点
static scclTopoNode_t* findNodeById(uint64_t id, ByteSpanArray<scclTopoNode_t>& nodes) {
for(size_t i = 0; i < nodes.size(); ++i) {
if(nodes[i]->id == id) {
return nodes[i];
}
}
return nullptr;
}
static scclTopoNode_t* findNodeById(uint64_t id, ByteSpanVector<scclTopoNode_t>& nodes) {
for(size_t i = 0; i < nodes.size(); ++i) {
if(nodes[i]->id == id) {
return nodes[i];
}
}
return nullptr;
}
/**
* @brief 合并两个节点的信息
*
* @param existingNode 指向现有节点的迭代器
* @param newNode 要合并的新节点
*/
static void mergeNodes(scclTopoNode* existingNode, const scclTopoNode* newNode) {
// 合并busIdStr,保留较小的那个
if(newNode->busId < existingNode->busId) {
existingNode->busId = newNode->busId;
}
// 合并neighbors
for(size_t i = 0; i < newNode->neighborCount; ++i) {
existingNode->addNeighbor(newNode->neighbors[i]);
}
}
/**
* @brief 检查指定的邻居ID是否存在于邻居数组中
*
* 该函数接收一个邻居数组、邻居计数和一个邻居ID,并检查指定的邻居ID是否存在于邻居数组中。
*
* @param neighbors 邻居数组
* @param neighborCount 邻居计数
* @param neighborId 要检查的邻居ID
* @return 如果找到了指定的邻居ID,则返回`true`;否则返回`false`
*/
static bool containsNeighbor(const std::array<uint64_t, topoNodeMaxNeighbors>& neighbors, size_t neighborCount, uint64_t neighborId) {
return std::find(neighbors.begin(), neighbors.begin() + neighborCount, neighborId) != neighbors.begin() + neighborCount;
}
/**
* @brief 根据rccl的xml设置获取调整后的hipDev值
*
* 该函数接收一个hipDev值,并根据rccl的xml设置返回调整后的hipDev值。
*
* @param hipDev 原始hipDev值
* @return 调整后的hipDev值
*/
static int getAdjustedGpuHipDev(int hipDev) {
static const std::unordered_map<int, int> hipDevAdjustmentMap = {{1, 0}, {2, 3}, {5, 4}, {6, 7}};
auto it = hipDevAdjustmentMap.find(hipDev);
if(it != hipDevAdjustmentMap.end()) {
return it->second;
}
return hipDev;
}
// TODO: 特殊处理GPU,根据rccl的xml设置numaId,1->0,2->3,5->4,6->7。将来可能有更好的方法
// 定义一个函数,用于获取numa node,并可能修改hipDev的值
/**
* @brief 根据PCI路径和设备类型获取NUMA节点ID字符串
*
* 该函数根据给定的PCI路径和设备类型(GPU或其他),获取对应的NUMA节点ID字符串。
* 如果设备类型是GPU,则首先调整HIP设备号,然后获取调整后的GPU的PCI路径,并从中获取NUMA节点ID字符串。
* 如果设备类型不是GPU,则直接从给定的PCI路径中获取NUMA节点ID字符串。
*
* @param pciPath 指向PCI路径的指针
* @param hipDev 指向HIP设备号的指针
* @param type 设备类型(GPU或其他)
* @param numaIdStr 用于存储NUMA节点ID字符串的字符数组
*/
static void getNumaIdStr(const char* pciPath, int hipDev, int type, char* numaIdStr) {
if(type == GPU) {
hipDev = getAdjustedGpuHipDev(hipDev);
char* dstPciPath = getGpuPciPath(hipDev);
scclTopoGetStrFromSys(dstPciPath, "numa_node", numaIdStr);
delete(dstPciPath);
} else {
scclTopoGetStrFromSys(pciPath, "numa_node", numaIdStr);
}
}
/**
* @brief 从指定的PCI路径和节点ID(interRank)生成唯一的设备ID
*
* 该函数通过从指定的PCI路径下的"device"文件中读取设备ID字符串,并将其转换为一个无符号64位整数。
* 然后,函数将节点ID(interRank)编码到生成的ID的前32位,以确保在不同节点上的相同设备具有不同的ID。
* 该函数还接受设备类型(terminalType)和GPU设备标识符(hipDev),并将它们编码到生成的ID的中间12位。
* 设备类型(terminalType)占用中间12位的高4位,GPU设备标识符(hipDev)占用中间12位的低8位。
*
* @param pciPath 指向PCI设备的路径
* @param interRank 节点ID,用于区分不同节点上的相同设备
* @param terminalType 设备类型,例如GPU、NIC等
* @param hipDev GPU/NIC设备的标识符
* @param numaId NUMA节点的标识符
* @return 生成的唯一设备ID
*/
static uint64_t getIdFromPciPathWithNuma(const char* pciPath, int interRank, int terminalType, int hipDev, int numaId) {
uint64_t id = 0;
char deviceStr[9]; // 假设deviceStr的格式为"xxxx",即4个16进制字符
// 从指定路径下的"device"文件中读取设备ID字符串
scclTopoGetStrFromSys(pciPath, "device", deviceStr);
// 将设备ID字符串转换为无符号16位整数
uint16_t deviceValue = static_cast<uint16_t>(strtoul(deviceStr, nullptr, 16));
// 组合id
id = (static_cast<uint64_t>(interRank & 0xFFFFFF) << 40) | // 最开始24位为interRank
(static_cast<uint64_t>(hipDev & 0xFF) << 32) | // 之后8位为hipDev
(static_cast<uint64_t>(deviceValue & 0xFFFF) << 16) | // 之后16位为deviceValue
(static_cast<uint64_t>(terminalType & 0xFF) << 8) | // 之后8位表示terminalType
(static_cast<uint64_t>(numaId & 0xFF)); // 最后8bit表示numaId
return id;
}
// TODO: 各种特殊情况的修改均根据rccl千卡节点xml文件进行修改,后续需要根据具体适配需要再完善
/**
* @brief 从指定的PCI路径和节点ID(interRank)生成唯一的设备ID。
* 不输入numa节点ID的情况下,函数将自动获取当前设备的numa节点ID。
*
* 该函数首先根据PCI路径获取设备类型,然后根据设备类型和hipDev值调整hipDev值。
* 接着,函数从PCI路径中获取NUMA节点ID,并将其与interRank、hipDev值一起传递给getIdFromPciPath函数,生成唯一的设备ID。
*
* @param pciPath 指向PCI设备的路径
* @param interRank 节点ID,用于区分不同节点上的相同设备
* @param hipDev 设备的标识符
* @param numaId 设备的numa_node值
* @return 生成的唯一设备ID
*/
static uint64_t getIdFromPciPath(const char* pciPath, int interRank, int terminalType, int hipDev = -1, int numaId = -1) {
int type = getDeviceTypeFromPciPath(pciPath);
if(numaId < 0) {
char numaIdStr[numaIdStrLen];
getNumaIdStr(pciPath, hipDev, type, numaIdStr);
// 将最大链接带宽字符串转换为长整型数值
numaId = strtol(numaIdStr, nullptr, 10);
}
// 对于PCI路径,设置对CPU设备和PCI设备不设置hipDev
if(type == PCI && terminalType == NIC) {
hipDev = -1;
}
uint64_t id = getIdFromPciPathWithNuma(pciPath, interRank, terminalType, hipDev, numaId);
return id;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
scclTopoNode::scclTopoNode() : id(0), type(0), busId(0), speed(0), width(0), neighborCount(0) {}
/**
* @brief 构造函数,用于根据给定的PCI路径和进程间排名创建一个scclTopoNode对象。
*
* 该构造函数首先检查给定的PCI路径是否为一个有效的PCI设备路径。
* 如果是,则从该路径下的相关文件中读取设备的信息,包括设备ID、设备类型、总线ID字符串、速度和带宽。
* 然后,根据读取到的信息初始化scclTopoNode对象的成员变量。
*
* @param pciPath 指向PCI路径的字符指针。
* @param interRank 进程间排名,用于区分不同的节点。
* @param terminalType 终端设备类型,例如GPU、NIC等。
* @param hipDev HIP设备号,用于标识GPU设备或NIC设备。
* @param numaId NUMA节点ID,用于标识设备所属的NUMA节点。
*/
scclTopoNode::scclTopoNode(const char* pciPath, int interRank, int terminalType, int hipDev, int numaId)
: id(0), type(0), busId(0), speed(0), width(0), neighborCount(0) {
// 检查type和device文件是否存在
// 如果不存在,则说明该路径不是一个有效的PCI设备路径,直接返回
if(!isPciDevice(std::string(pciPath))) {
return;
}
//// 1.获取type
this->type = getDeviceTypeFromPciPath(pciPath);
//// 2.获取busId
// 从PCI路径字符串中获取最后一个非空子字符串,作为总线ID字符串
std::string lastBusIdStr = getLastNonEmptySegment(pciPath, '/');
busIdToInt64(lastBusIdStr.c_str(), &(this->busId));
//// 3.获取numa node和id
// 从指定路径下的"device"文件中读取设备ID字符串
this->id = getIdFromPciPath(pciPath, interRank, terminalType, hipDev, numaId);
#if 0
int tmp_interRank, tmp_deviceValue, tmp_terminalType, temp_hipDev, tmp_numaId;
getIdComponents(this->id, &tmp_interRank, &tmp_deviceValue, &tmp_terminalType, &temp_hipDev, &tmp_numaId);
printf("new node id:%lu, (InterRank:%d, T:%d, H:%d, N:%d, busIdStr:%s), decompose: (InterRank:%d, V:%d, T:%d, H:%d, N:%d), hipDev=%d\n",
this->id,
interRank,
terminalType,
this->hipDev,
numaId,
lastBusIdStr.c_str(),
tmp_interRank,
tmp_deviceValue,
tmp_terminalType,
temp_hipDev,
tmp_numaId,
hipDev);
#endif
//// 4.获取速度
// 从指定路径下的"max_link_speed"文件中读取最大链接速度字符串
char speedStr[speedStrLen];
scclTopoGetStrFromSys(pciPath, "max_link_speed", speedStr);
// 在pciSpeedMap映射表中查找与最大链接速度字符串最接近的匹配项
std::string speed_map_key = findClosestMatch(std::string(speedStr), pciSpeedMap);
// 根据查找到的匹配项,获取速度
this->speed = pciSpeedMap[speed_map_key];
//// 5.获取带宽
char widthStr[widthStrLen];
// 从指定路径下的"max_link_width"文件中读取最大链接带宽字符串
scclTopoGetStrFromSys(pciPath, "max_link_width", widthStr);
// 将最大链接带宽字符串转换为长整型数值
this->width = strtol(widthStr, nullptr, 10);
}
/**
* @brief 构造函数,用于根据给定的NUMA ID和进程间排名创建一个表示CPU的scclTopoNode对象。
*
* 该构造函数首先调用generate_topo_node_numa_info函数生成与给定NUMA ID对应的CPU亲和力字符串。
* 如果生成的CPU亲和力字符串不为空,则将其设置为当前对象的cpuAffinity成员变量,
* 并根据给定的进程间排名和NUMA ID设置当前对象的id成员变量。
*
* @param numaId NUMA ID,用于标识不同的NUMA节点。
* @param interRank 进程间排名,用于区分不同的节点。
*/
scclTopoNode::scclTopoNode(int numaId, int interRank) : id(0), type(0), busId(0), speed(0), width(0), neighborCount(0) {
// 设置cpu的affinity
auto cpuAffinityStr = generate_topo_node_numa_info(numaId);
if(!cpuAffinityStr.empty()) {
this->id = (static_cast<uint64_t>(interRank) << 32) | // 前32位为interRank
(static_cast<uint64_t>(numaId)); // 后32位的后16位为deviceValue
this->type = CPU;
}
}
/**
* @brief 向当前节点添加一个邻居节点。
*
* 该函数将给定的邻居节点ID添加到当前节点的neighbors数组中,并增加neighborCount计数器。
* 如果neighbors数组已满,则不会添加新的邻居节点。
*
* @param neighborId 要添加的邻居节点的ID。
*/
void scclTopoNode::addNeighbor(uint64_t neighborId) {
if(!containsNeighbor(neighbors, neighborCount, neighborId) && neighborCount < neighbors.size()) {
neighbors[neighborCount++] = neighborId;
}
}
/**
* @brief 合并两个具有相同id、type和busId的节点,并将它们的neighbors进行合并
*
* 该函数接收一个指向另一个节点的指针,并检查当前节点和该节点的id、type和busId是否相等。
* 如果它们相等,则表示这两个节点是相同的节点,我们可以将它们的neighbors进行合并。
* 合并的过程是将另一个节点的neighbors数组中的每个邻居节点添加到当前节点的neighbors数组中
* (如果当前节点的neighbors数组中尚未包含该邻居节点)。
*
* @param other_node 指向另一个节点的指针
*/
scclResult_t scclTopoNode::combineNode(struct scclTopoNode* other_node) {
// 检查两个节点的id、type和busId是否相等
if(this->id == other_node->id && this->type == other_node->type && this->busId == other_node->busId) {
// 合并neighbors
for(size_t i = 0; i < other_node->neighborCount; ++i) {
if(!containsNeighbor(this->neighbors, this->neighborCount, other_node->neighbors[i])) {
if(this->neighborCount < this->neighbors.size()) {
this->neighbors[this->neighborCount++] = other_node->neighbors[i];
} else {
// 如果neighbors数组已满,则返回错误代码
return scclInternalError;
}
}
}
other_node->clearNode(); // 清空被合并的node
}
return scclSuccess;
}
void scclTopoNode::clearNode() {
this->id = 0;
this->type = 0;
this->speed = 0;
this->width = 0;
this->neighborCount = 0;
this->busId = 0;
}
/**
* @brief 打印当前节点的信息。
*
* 该函数将当前节点的ID、类型、总线ID字符串、速度、带宽、CPU亲和力和邻居节点数量等信息打印到标准输出。
* 然后,它将当前节点的所有邻居节点的ID逐一打印到标准输出。
*
* @param prefix 打印信息的前缀字符串。
*/
void scclTopoNode::printNodeInfo(const char* prefix, bool printNeighbor) {
int interRank, deviceValue, terminalType, hipDev, numaId;
getIdComponents(this->id, &interRank, &deviceValue, &terminalType, &hipDev, &numaId);
char busIdStr[busIdStrLen];
int64ToBusId(this->busId, busIdStr);
printf("%s: node: (InterRank:%d, V:%d, T:%d, H:%d, N:%d, type=%d, busIdStr=%s, speed=%d, width=%d), neighborCount=%zu\n",
prefix,
interRank,
deviceValue,
terminalType,
hipDev,
numaId,
this->type,
busIdStr,
this->speed,
this->width,
this->neighborCount);
if(printNeighbor) {
for(size_t i = 0; i < neighborCount; ++i) {
getIdComponents(neighbors[i], &interRank, &deviceValue, &terminalType, &hipDev, &numaId);
printf("neighbor[%zu] -- (InterRank:%d, V:%d, T:%d, H:%d, N:%d\n", i, interRank, deviceValue, terminalType, hipDev, numaId);
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
// TODO: 当前根据千卡节点的rccl的xml进行特殊处理,不确定是否具有普适性
/**
* @brief 根据给定的PCI路径生成拓扑节点
*
* 该函数通过解析给定的PCI路径,生成相应的拓扑节点,并将它们添加到节点向量中。
* 1.检查路径下存在NUMA节点,首先创建NUMA节点。
* 2.根据rccl的xml进行特殊处理:
* - 对于GPU设备,整条path修改其numaId为映射关系的值,不修改hipDev值
* 3.遍历PCI路径的各个部分,为每个部分创建相应的拓扑节点,并根据需要添加节点间的连接关系。
*
* @param pciPath PCI设备路径
* @param interRank 跨节点的排名
* @param nodes 节点向量,函数将生成的节点添加到此向量中
* @return 如果成功,返回scclSuccess;否则返回相应的错误代码
*/
scclResult_t generate_topo_nodes(const char* pciPath, int interRank, int hipDev, ByteSpanVector<scclTopoNode_t>& nodes) {
////---- 1.首先确定传入的pciPath路径下有numa图点,表示存在cpu连接关系,创建numa node ----////
if(!std::filesystem::exists(std::string(pciPath) + "/numa_node")) {
WARN("Cannot find numa_node under pciPath: %s. No cpu connection detected.", pciPath);
return scclInternalError;
}
// TODO: 根据rccl的xml文件修改的代码,后续查看有没有更好的方法
int terminalNumaId = -1;
int terminalType = getDeviceTypeFromPciPath(pciPath);
{ // 如果node的type类型为GPU,则整条path修改numaId
char numaIdStr[numaIdStrLen];
getNumaIdStr(pciPath, hipDev, terminalType, numaIdStr);
terminalNumaId = static_cast<int>(strtoul(numaIdStr, nullptr, 10));
}
// 创建numa图点
scclTopoNode_t numaNode(terminalNumaId, interRank);
if(numaNode.type <= 0) {
WARN("Cannot find correct numa node:%d from pciPath:%s", pciPath, terminalNumaId);
return scclInternalError;
}
// 检查numaNode是否已经存在
auto numaIt = findNodeById(numaNode.id, nodes);
if(numaIt != nullptr && numaIt->type == numaNode.type) {
mergeNodes(numaIt, &numaNode);
} else {
if(!nodes.full()) {
nodes.push_back(numaNode);
numaIt = nodes[nodes.size() - 1]; // 更新numaIt指针
} else {
// 处理容量不足的情况
WARN("Cannot add numa node:%d from pciPath:%s, nodes full!", pciPath, terminalNumaId);
return scclInternalError;
}
}
////---- 2.遍历pciPath,生成拓扑图点 ----////
std::istringstream gpuPathStream(pciPath);
std::string segment;
std::string parentPath = "/";
bool isFirstValidNode = true; // 标志变量,用于跟踪是否已经添加了numaNode和第一个有效node之间的连接
while(std::getline(gpuPathStream, segment, '/')) {
if(!segment.empty()) {
std::string currentPath = parentPath + segment + "/";
//// 1.创建拓扑图点
scclTopoNode_t node(currentPath.c_str(), interRank, terminalType, hipDev, terminalNumaId);
if(node.id == 0) {
parentPath = currentPath;
continue;
}
//// 2.检查是否已经存在相同的id
auto it = findNodeById(node.id, nodes);
if(it != nullptr && it->type == node.type) {
// 如果存在相同的id,合并节点
mergeNodes(it, &node);
} else {
if(!nodes.full()) {
nodes.push_back(node);
it = nodes[nodes.size() - 1]; // 更新it指针
} else {
// 处理容量不足的情况
WARN("ByteSpanVector capacity exceeded");
return scclInternalError;
}
}
//// 3.给parent和当前node添加neighbor
// 如果存在device文件,则尝试添加连接关系
if(isPciDevice(std::string(parentPath))) {
uint64_t parentId = getIdFromPciPath(parentPath.c_str(), interRank, terminalType, hipDev, terminalNumaId);
// 如果parentId和node.id不相等,则添加双向边。否则不添加边
if(parentId != node.id) {
auto parentIt = findNodeById(parentId, nodes);
if(parentIt != nullptr) {
parentIt->addNeighbor(node.id);
it->addNeighbor(parentId); // 使用更新后的it指针
}
}
}
//// 4.如果这是第一个有效的node,则添加它和numaNode之间的连接
if(isFirstValidNode) {
numaIt->addNeighbor(node.id); // 使用更新后的numaIt指针
it->addNeighbor(numaIt->id); // 使用更新后的it指针
isFirstValidNode = false; // 设置标志变量为false,表示已经添加了连接
}
parentPath = currentPath;
}
}
return scclSuccess;
}
/**
* @brief 获取指定NUMA节点的CPU映射信息
*
* 该函数通过读取Linux系统文件系统中的NUMA节点信息,获取指定NUMA节点的CPU映射(cpumap)。
* 它访问 /sys/devices/system/node/nodeX 目录下的cpumap文件,其中X是NUMA节点ID。
* affinity,例如 "00000000,00000000,ffff0000,00000000"
*
* @param numaId NUMA节点的ID,用于构建系统文件路径
* @return std::string 返回表示CPU映射的字符串,格式为系统提供的cpumap内容
*
* @note 该函数依赖于Linux系统的/sys文件系统结构
* @note 函数内部使用scclTopoGetStrFromSys函数从系统文件中读取信息
*/
std::string generate_topo_node_numa_info(int numaId) {
char cpumaskPath[] = "/sys/devices/system/node/node0000";
sprintf(cpumaskPath, "/sys/devices/system/node/node%d", numaId);
char affinityStr[36] = {0};
scclTopoGetStrFromSys(cpumaskPath, "cpumap", affinityStr);
return std::string(affinityStr);
}
// 函数输出id分解后的所有数据。与函数getIdFromPciPathWithNuma相对应
void getIdComponents(uint64_t idToDecompose, int* interRank, int* deviceValue, int* terminalType, int* hipDev, int* numaId) {
if(interRank) {
*interRank = static_cast<int>((idToDecompose >> 40) & 0xFFFFFF); // 提取interRank
}
if(hipDev) {
*hipDev = static_cast<int>((idToDecompose >> 32) & 0xFF); // 提取hipDev
}
if(deviceValue) {
*deviceValue = static_cast<int>((idToDecompose >> 16) & 0xFFFF); // 提取deviceValue
}
if(terminalType) {
*terminalType = static_cast<int>((idToDecompose >> 8) & 0xFF); // 提取terminalType
}
if(numaId) {
*numaId = static_cast<int>(idToDecompose & 0xFF); // 提取numaId
}
}
/**
* @brief 根据hipDev获取GPU的PCI路径
*
* 该函数接收一个hipDev值,并返回对应的GPU的PCI路径。
*
* @param hipDev hipDev值
* @return 如果成功获取PCI路径,则返回指向PCI路径的指针;否则返回`nullptr`
*/
char* getGpuPciPath(int hipDev) {
char busIdStr[128]; // 增加数组的大小以容纳更大的PCI域
(void)hipDeviceGetPCIBusId(busIdStr, sizeof(busIdStr), hipDev);
char* gpuPath = nullptr;
if(getPciPath(busIdStr, &gpuPath) != 0) {
return nullptr; // 返回nullptr而不是空字符串
}
char* result = new char[strlen(gpuPath) + 1];
std::strcpy(result, gpuPath);
return result;
}
/**
* @brief 根据scclNet和hipDev获取网络设备的PCI路径
*
* 该函数接收一个scclNet对象和一个hipDev值,并返回对应的网络设备的PCI路径。
*
* @param scclNet 指向scclNet对象的指针
* @param hipDev hipDev值
* @return 如果成功获取PCI路径,则返回指向PCI路径的指针;否则返回`nullptr`
*/
char* getNetPciPath(scclNet_t* scclNet, int hipDev) {
net::scclNetProperties_t props;
if(scclNet->getProperties(hipDev, &props) != 0) {
return nullptr; // 返回nullptr而不是空字符串
}
char* result = new char[strlen(props.pciPath) + 1];
std::strcpy(result, props.pciPath);
return result;
}
/**
* @brief 打印拓扑节点的信息
*
* 该函数接收一个节点向量、一个节点索引和一个前缀字符串,打印指定索引处节点的详细信息,
* 包括节点ID、类型、总线ID字符串、速度、宽度、CPU亲和性以及邻居节点的数量。
* 然后,对于每个邻居节点,函数查找并打印其详细信息。
*
* @param nodes 节点向量,Container可以是ByteSpanVector<scclTopoNode_t>类型,也可以是ByteSpanArray<scclTopoNode_t>类型
* @param nodeIndex 要打印的节点在向量中的索引
* @param prefix 打印信息的前缀字符串
*/
void printTopoNode(ByteSpanArray<scclTopoNode_t>& nodes, int nodeIndex, const char* prefix) {
scclTopoNode_t* node = nodes[nodeIndex];
if(node == nullptr || node->type <= 0)
return;
int interRank, deviceValue, terminalType, hipDev, numaId;
getIdComponents(node->id, &interRank, &deviceValue, &terminalType, &hipDev, &numaId);
char busIdStr[busIdStrLen];
int64ToBusId(node->busId, busIdStr);
printf("%s: Node:%lu (InterRank:%d, V:%d, T:%d, H:%d, N:%d, Type:%d, BusIdStr: %ld/%s), Speed:%d, Width:%d, Neighbor "
"Count: %zu\n",
prefix,
node->id,
interRank,
deviceValue,
terminalType,
hipDev,
numaId,
node->type,
node->busId,
busIdStr,
node->speed,
node->width,
node->neighborCount);
for(size_t i = 0; i < node->neighborCount; ++i) {
const scclTopoNode_t* neighborNode = findNodeById(node->neighbors[i], nodes);
if(neighborNode != nullptr) {
getIdComponents(neighborNode->id, &interRank, &deviceValue, &terminalType, &hipDev, &numaId);
int64ToBusId(neighborNode->busId, busIdStr);
printf("- Node ID: %lu, Neighbor[%zu]:%lu (InterRank:%d, V:%d, T:%d, H:%d, N:%d, Type:%d, BusIdStr:%ld/%s, Speed:%d, Width: %d)\n",
node->id,
i,
neighborNode->id,
interRank,
deviceValue,
terminalType,
hipDev,
numaId,
neighborNode->type,
neighborNode->busId,
busIdStr,
neighborNode->speed,
neighborNode->width);
}
}
}
} // namespace physical_links
} // namespace bootstrap
} // namespace topology
} // namespace hardware
} // namespace sccl
\ No newline at end of file
#pragma once
#include <iostream>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <string>
#include <array>
#include <fstream>
#include <sstream>
#include <unordered_map>
#include <vector>
#include <filesystem> // 需要C++17支持
#include "container.h"
#include "bootstrap_utils.h"
namespace sccl {
namespace hardware {
namespace topology {
namespace bootstrap {
constexpr size_t topoNodeMaxLocalNodes = 128; // 每个节点最多的node数量
constexpr size_t topoNodeMaxNeighbors = 16; // 每个node最多neighbor数量
namespace physical_links {
/*
用于生成硬件之间的图点连接关系
依赖于读取/sys/devices 文件夹下的文件来获取信息
例如: /sys/devices/pci0000:98/0000:98:00.0 文件夹下包含各种信息
*/
typedef struct scclTopoNode {
// 构造函数声明
scclTopoNode();
scclTopoNode(const char* pciPath, int interRank, int terminalType, int hipDev = -1, int numaId = -1);
scclTopoNode(int numaId, int interRank);
// 添加邻近图点
void addNeighbor(uint64_t neighborId);
// 设置busId的字符串
void setBusIdStr(const char* busIdStr);
// 合并两个具有相同id、type和busIdStr的节点
scclResult_t combineNode(struct scclTopoNode* other_node);
// 清空节点
void clearNode();
// 打印图点信息
void printNodeInfo(const char* prefix, bool printNeighbor = false);
public:
uint64_t id; // 图点id标志
int type; // 图点类型,对应 nodeType_t
int64_t busId; // 总线ID字符串 "0000:00:00.0",例如 "0000:98:03.7",通过int64ToBusId和busIdToInt64变化
int speed; // 速度
int width; // 带宽
std::array<uint64_t, topoNodeMaxNeighbors> neighbors; // 邻居图点
size_t neighborCount; // 邻居图点的数量
} scclTopoNode_t;
// 根据给定的PCI路径生成拓扑节点
scclResult_t generate_topo_nodes(const char* pciPath, int interRank, int hipDev, ByteSpanVector<scclTopoNode_t>& nodes_span);
// 根据numaId获取pci路径
std::string generate_topo_node_numa_info(int numaId);
// 输出id分解后的所有数据
void getIdComponents(
uint64_t idToDecompose, int* interRank = nullptr, int* deviceValue = nullptr, int* terminalType = nullptr, int* hipDev = nullptr, int* numaId = nullptr);
// 获取GPU和NIC的dev对应路径
char* getGpuPciPath(int hipDev);
char* getNetPciPath(scclNet_t* scclNet, int hipDev);
// 打印拓扑节点的信息
void printTopoNode(ByteSpanArray<scclTopoNode_t>& nodes, int nodeIndex, const char* prefix);
} // namespace physical_links
} // namespace bootstrap
} // namespace topology
} // namespace hardware
} // namespace sccl
# SCCL 引导模块文档
## 概述
SCCL引导模块负责在分布式系统中跨多个节点初始化通信环境。该模块处理节点的唯一标识、通信的根节点建立以及通信结构的初始化。核心功能封装在[bootstrap.h](bootstrap.h)
头文件中,其对应的实现位于
[bootstrap.cpp](bootstrap.cpp)。辅助功能由
[bootstrap_net.cpp](bootstrap_net.cpp)
[bootstrap_utils.cpp](bootstrap_utils.cpp)
[physical_links.cpp](physical_links.cpp)
提供。
## 核心组件
### 1. BootstrapHandle_t 结构体
[BootstrapHandle_t](bootstrap_utils.h#26)
结构体用于存储节点的唯一识别信息,包括用于套接字通信的魔数和用于网络通信的套接字地址。
```c++
typedef struct BootstrapHandle {
uint64_t magic; // 随机数,用于套接字通信
scclSocketAddress_t addr; // 地址,用于网络通信
} BootstrapHandle_t;
```
### 2. scclRankInfo_t 结构体
`scclRankInfo_t`
结构体存储每个排名的通信节点信息,包括GPU、RDMA(网络)和CPU节点。
```c++
typedef struct scclRankInfo {
struct {
scclSocket_t listen_sock; // 监听套接字
} cpu;
struct {
int dev;
char name[8];
char gcn[8];
int compCap;
int64_t pciBusId;
char pciPath[128];
} gpu;
struct {
int count;
char name[8];
char pciPath[128];
uint64_t guid;
uint8_t ptrSupport;
int speed;
int port;
float latency;
int maxComms;
int maxRecvs;
} net;
int rank;
int localRank;
uint64_t hostHash;
uint64_t pidHash;
} scclRankInfo_t;
```
### 3. scclNodeInfo_t 结构体
`scclNodeInfo_t`结构体用于存储每个排名的拓扑连接信息。
```c++
typedef struct scclNodeInfo {
scclTopoNode_t nodes[topoNodeMaxLocalNodes];
} scclNodeInfo_t;
```
### 4. BootstrapComm 结构体
`BootstrapComm`结构体存储引导通信信息,包括网络属性、排名信息及其他相关数据。
```c++
typedef struct BootstrapComm {
void init(int rank, int nRanks, int localRank, int nLocalRanks);
void destroy();
public:
scclNet_t* scclNet;
scclRankPhysSet_t* rank_phys_set;
cpu_set_t cpuAffinity;
int rank;
int nRanks;
int localRank;
int nLocalRanks;
int interRank;
int nInterRanks;
int hipDev;
int deviceCnt;
uint64_t magic;
} BootstrapComm_t;
```
### 5. Bootstrap 类
`Bootstrap`类封装了引导初始化过程,包括唯一ID生成、根创建和通信初始化。
```c++
class Bootstrap {
public:
Bootstrap(const BootstrapHandle_t*, int rank, int nRanks);
~Bootstrap();
scclResult_t init(BootstrapComm_t* bootstrap_comm);
scclResult_t bootstrapAllGather(const void* src_data, void* dst_data, int data_size);
private:
scclResult_t bootstrapRootGatherAndBroadcast(BootstrapNodeBasic_t* send_data_basic);
scclResult_t bootstrapCommInitNodeInfo(scclNet_t* scclNet, scclRankInfo_t* rank_info);
scclResult_t bootstrapCommAllGather(scclRankInfo_t* rank_info, scclNodeInfo_t* node_info, scclRankPhysSet_t* rank_phys_set);
scclResult_t bootstrapNodesLink(scclNodeInfo_t* node_infos);
private:
int rank, nRanks;
int localRank, nLocalRanks;
int interRank, nInterRanks;
volatile uint32_t* abortFlag;
const BootstrapHandle_t* root_handle;
BootstrapNodeBasic_t* all_node_basic;
bool socketInitDone;
pthread_mutex_t bootstrapMutex;
pthread_cond_t bootstrapCond;
scclIpcSocket_t* ipcsocket;
};
```
## 功能描述
### bootstrapGetUniqueId
该函数为引导过程生成一个唯一ID,利用`BootstrapHandle_t`结构体,它初始化引导网络并为通信设置根句柄。
```c++
scclResult_t bootstrapGetUniqueId(BootstrapHandle_t* handle);
```
### bootstrapCreateRoot
负责为引导过程创建根节点。该函数设置根节点的监听套接字并启动引导通信。
```c++
scclResult_t bootstrapCreateRoot(BootstrapHandle_t* handle);
```
### Bootstrap::init
`Bootstrap`类的init方法是核心初始化函数。它设置通信环境,收集并广播排名信息,初始化节点信息,并建立节点连接。
```c++
scclResult_t Bootstrap::init(BootstrapComm_t* bootstrap_comm);
```
## 辅助模块
- [bootstrap_net.cpp](bootstrap_net.cpp)
处理与引导相关的网络操作,包括节点之间的套接字通信和数据传输。
- [bootstrap_utils.cpp](bootstrap_utils.cpp)
为引导过程提供实用函数,例如哈希、随机数据生成和PCI设备ID检索。
- [physical_links.cpp](physical_links.cpp)
定义`scclTopoNode`结构体及关联函数,用于生成和管理拓扑节点及其连接。
## 结论
SCCL引导模块为在分布式计算环境中初始化和管理通信提供了一个强大的框架。
通过封装网络通信的复杂性、唯一ID生成和拓扑节点管理,它简化了可扩展和高效通信集合的开发。
#include <iostream>
#include "graph.h"
#include "paths.h"
namespace sccl {
namespace hardware {
namespace topology {
namespace graph {
Graph::Graph(int rank, int nRanks) {
// 构造函数的实现
}
Graph::~Graph() {
// 析构函数的实现
}
scclResult_t Graph::calculateCommunicationPaths(const BootstrapComm_t* bootstrap_comm) {
// 通信路径计算的实现
std::cout << "Calculating communication paths..." << std::endl;
// 具体的实现细节
auto path_finder = PathFinder(bootstrap_comm);
path_finder.findGpuPaths();
return scclSuccess;
}
scclResult_t Graph::buildLogicalTopology() {
// 逻辑拓扑构建的实现
std::cout << "Building logical topology..." << std::endl;
// 具体的实现细节
return scclSuccess;
}
scclResult_t Graph::calculateTopoChannels() {
// 根据无向图计算topo路径的实现
std::cout << "Calculating topo paths based on undirected graph..." << std::endl;
// 具体的实现细节
return scclSuccess;
}
} // namespace graph
} // namespace topology
} // namespace hardware
} // namespace sccl
#pragma once
#include <vector>
#include "base.h"
#include "graph_utils.h"
namespace sccl {
namespace hardware {
namespace topology {
namespace graph {
class Graph {
public:
Graph(int rank, int nRanks);
virtual ~Graph();
// 通信路径计算
scclResult_t calculateCommunicationPaths(const BootstrapComm_t* bootstrap_comm);
// 逻辑拓扑构建
scclResult_t buildLogicalTopology();
// 根据无向图计算topo路径
scclResult_t calculateTopoChannels();
private:
std::vector<std::vector<int>> adjacencyMatrix; // 使用邻接矩阵表示图
// 你可以根据需要添加更多的私有成员变量和函数
};
} // namespace graph
} // namespace topology
} // namespace hardware
} // namespace sccl
#include <string.h>
#include "graph_utils.h"
namespace sccl {
namespace hardware {
namespace topology {
namespace graph {
///
} // namespace graph
} // namespace topology
} // namespace hardware
} // namespace sccl
#pragma once
#include <string.h>
#include "bootstrap.h"
#include "graph_utils.h"
namespace sccl {
namespace hardware {
namespace topology {
namespace graph {
typedef bootstrap::physical_links::scclTopoNode_t scclTopoNode_t;
typedef bootstrap::scclNodeInfo_t scclNodeInfo_t;
typedef bootstrap::BootstrapComm_t BootstrapComm_t;
} // namespace graph
} // namespace topology
} // namespace hardware
} // namespace sccl
#include <unordered_set>
#include "paths.h"
namespace sccl {
namespace hardware {
namespace topology {
namespace graph {
PathFinder::PathFinder(const BootstrapComm_t* bootstrap_comm)
: rank(bootstrap_comm->rank),
nRanks(bootstrap_comm->nRanks),
localRank(bootstrap_comm->localRank),
nLocalRanks(bootstrap_comm->nLocalRanks),
interRank(bootstrap_comm->interRank),
nInterRanks(bootstrap_comm->nInterRanks),
node_container_(bootstrap_comm->rank_phys_set->node_info_vec.data(),
bootstrap_comm->nRanks * bootstrap_comm->rank_phys_set->node_info_total_bytes) { // 初始化NodeContainer对象
printf("get PathFinder, node_container_=%zu\n", node_container_.size());
for(size_t i = 0; i < node_container_.size(); ++i) {
scclTopoNode_t* node = node_container_[i];
// 检查node是否有效
if(node->type > CPU) {
// 将有效的node添加到graph_nodes中,并保存其neighbor的id
graph_nodes_[node->id] = std::vector<uint64_t>(node->neighbors.begin(), node->neighbors.begin() + node->neighborCount);
// 构建id到index的映射
id_to_index_[node->id] = i;
}
}
#if 0
if(rank == 0) {
// 遍历id_to_index_进行打印
for(const auto& pair : id_to_index_) {
uint64_t node_id = pair.first;
size_t index = pair.second;
const scclTopoNode_t* node = node_container_[index];
int interRank, deviceValue, terminalType, hipDev, numaId;
bootstrap::physical_links::getIdComponents(node_id, &interRank, &deviceValue, &terminalType, &hipDev, &numaId);
char busIdStr[17];
int64ToBusId(node->busId, busIdStr);
printf("rank=%d, node=(InterRank:%d, V:%d, T:%d, H:%d, N:%d, type:%d, busIdStr:%s), neighbor_count=%zu",
rank,
interRank,
deviceValue,
terminalType,
hipDev,
numaId,
node->type,
busIdStr,
node->neighborCount);
for(int n = 0; n < node->neighborCount; ++n) {
uint64_t neighbor_id = node->neighbors[n];
const scclTopoNode_t* neighbor_node = findNodeById(neighbor_id);
if(neighbor_node) {
bootstrap::physical_links::getIdComponents(neighbor_id, &interRank, &deviceValue, &terminalType, &hipDev, &numaId);
int64ToBusId(neighbor_node->busId, busIdStr);
printf(", neighbor[%d]=(InterRank:%d, V:%d, T:%d, H:%d, N:%d, type:%d, busIdStr:%s)",
n,
interRank,
deviceValue,
terminalType,
hipDev,
numaId,
neighbor_node->type,
busIdStr);
} else {
printf(", neighbor[%d]=unknown", n);
}
}
printf("\n");
}
}
#endif
}
scclResult_t PathFinder::findGpuPaths() {
// 查找所有type为GPU的节点,并执行BFS搜索
for(const auto& pair : id_to_index_) {
uint64_t id = pair.first;
size_t index = pair.second;
// 定位到node
scclTopoNode_t* node = node_container_[index];
int hipDev;
bootstrap::physical_links::getIdComponents(node->id, nullptr, nullptr, nullptr, &hipDev, nullptr);
if(node->type == GPU && hipDev < nLocalRanks) {
printf("bfsFindGpuPaths start_node_id=%lu, running\n", node->id);
bfsFindGpuPaths(node->id);
}
}
if(rank == 1) {
printGpuPaths();
}
return scclSuccess;
}
/////////////////////////////////////////////////////////////////////////////////////////////
/**
* @brief 根据节点ID查找节点
*
* 该函数接收一个节点ID,并在`node_container_`中查找具有该ID的节点。如果找到了具有指定ID的节点,则返回指向该节点的指针;否则返回`nullptr`。
*
* @param id 要查找的节点ID
* @return 如果找到了具有指定ID的节点,则返回指向该节点的指针;否则返回`nullptr`
*/
const scclTopoNode_t* PathFinder::findNodeById(uint64_t id) const {
// 使用id_to_index_映射查找具有指定id的节点的索引
auto it = id_to_index_.find(id);
if(it != id_to_index_.end()) {
return node_container_[it->second];
}
return nullptr; // 如果未找到具有指定id的节点,则返回nullptr
}
// 为std::vector<uint64_t>类型定义一个相等比较函数
struct VectorEqual {
bool operator()(const std::vector<uint64_t>& lhs, const std::vector<uint64_t>& rhs) const {
if(lhs.size() != rhs.size()) {
return false;
}
for(size_t i = 0; i < lhs.size(); ++i) {
if(lhs[i] != rhs[i]) {
return false;
}
}
return true;
}
};
// 为std::vector<uint64_t>类型定义一个哈希函数
struct VectorHash {
size_t operator()(const std::vector<uint64_t>& vec) const {
size_t seed = vec.size();
for(const auto& i : vec) {
seed ^= i + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
return seed;
}
};
/**
* @brief 使用广度优先搜索(BFS)查找从起始GPU节点到其他GPU节点的所有路径
*
* 1.该函数从指定的起始GPU节点开始,使用广度优先搜索算法查找所有能够到达的GPU节点,并记录从起始节点到每个目标GPU节点的所有路径。
* 每条路径中的所有节点最多使用一次。
*
* 2.该函数还添加了一个限制,以防止在路径中出现`interRank`在变化后又变回来的情况。
* 也就是说,如果路径从`interRank == 0`连接到`interRank == 1`的节点后,则不能再连接回`interRank == 0`。
*
* @param start_node_id 起始GPU节点的ID
*/
#if 1
void PathFinder::bfsFindGpuPaths(uint64_t start_node_id) {
// 使用一个队列来存储当前路径
std::queue<std::vector<uint64_t>> queue;
// 使用一个unordered_map来存储每个node的最短路径
std::unordered_map<uint64_t, std::vector<uint64_t>> shortest_paths;
// 将起始节点加入队列
queue.push({start_node_id});
shortest_paths[start_node_id] = {start_node_id};
// 当队列不为空时,继续搜索
while(!queue.empty()) {
// 从队列中取出一个路径
auto path = queue.front();
queue.pop();
// 获取当前路径的最后一个节点的ID
uint64_t nodeId = path.back();
// 根据节点ID查找对应的节点
const scclTopoNode_t* current_node = findNodeById(nodeId);
if(current_node == nullptr) {
continue;
}
// 如果当前节点是GPU节点且不是起始节点,则将当前路径加入结果
if(current_node->type == GPU && nodeId != start_node_id) {
int hipDev;
bootstrap::physical_links::getIdComponents(current_node->id, nullptr, nullptr, nullptr, &hipDev, nullptr);
if(hipDev < nLocalRanks) {
gpu_paths_[start_node_id].push_back(path);
}
} else {
int nodeInterRank;
bootstrap::physical_links::getIdComponents(nodeId, &nodeInterRank);
// 遍历当前节点的所有邻居节点
for(uint64_t neighbor_id : graph_nodes_.at(nodeId)) {
if(findNodeById(neighbor_id) == nullptr) {
continue;
}
// 获取邻居节点的interRank
int neighbor_inter_rank;
bootstrap::physical_links::getIdComponents(neighbor_id, &neighbor_inter_rank);
// 检查邻居节点是否已在当前路径中访问过
bool visited = std::find(path.begin(), path.end(), neighbor_id) != path.end();
// 检查interRank是否已经存在(仅当interRank改变时)
bool inter_rank_exists = false;
if(neighbor_inter_rank != nodeInterRank) {
for(uint64_t node_id : path) {
if(node_id == neighbor_id) {
inter_rank_exists = true;
break;
}
}
}
// 如果邻居节点未访问过且interRank未存在,则扩展路径
if(!visited && !inter_rank_exists) {
std::vector<uint64_t> new_path = path;
new_path.push_back(neighbor_id);
// 如果新路径比已有的最短路径更短,则更新最短路径
if(shortest_paths.find(neighbor_id) == shortest_paths.end() || shortest_paths[neighbor_id].size() > new_path.size()) {
shortest_paths[neighbor_id] = new_path;
queue.push(new_path);
}
}
}
}
}
}
#else
void PathFinder::bfsFindGpuPaths(uint64_t start_node_id) {
// 使用一个队列来存储当前路径
std::queue<std::vector<uint64_t>> queue;
// 将起始节点加入队列
queue.push({start_node_id});
// 当队列不为空时,继续搜索
while(!queue.empty()) {
// 从队列中取出一个路径
auto path = queue.front();
queue.pop();
// 获取当前路径的最后一个节点的ID
uint64_t nodeId = path.back();
// 根据节点ID查找对应的节点
const scclTopoNode_t* current_node = findNodeById(nodeId);
if(current_node == nullptr) {
continue;
}
// 如果当前节点是GPU节点且不是起始节点,则将当前路径加入结果
if(current_node->type == GPU && nodeId != start_node_id) {
int hipDev;
bootstrap::physical_links::getIdComponents(current_node->id, nullptr, nullptr, nullptr, &hipDev, nullptr);
if(hipDev < nLocalRanks) {
gpu_paths_[start_node_id].push_back(path);
}
} else {
int nodeInterRank;
bootstrap::physical_links::getIdComponents(nodeId, &nodeInterRank);
// 遍历当前节点的所有邻居节点
for(uint64_t neighbor_id : graph_nodes_.at(nodeId)) {
if(findNodeById(nodeId) == nullptr) {
continue;
}
// 获取邻居节点的interRank
int neighbor_inter_rank;
bootstrap::physical_links::getIdComponents(neighbor_id, &neighbor_inter_rank);
// 检查邻居节点是否已在当前路径中访问过
bool visited = std::find(path.begin(), path.end(), neighbor_id) != path.end();
// 检查interRank是否已经存在(仅当interRank改变时)
bool inter_rank_exists = false;
if(neighbor_inter_rank != (nodeInterRank)) {
for(uint64_t node_id : path) {
if((nodeInterRank) == neighbor_inter_rank) {
inter_rank_exists = true;
break;
}
}
}
// 如果邻居节点未访问过且interRank未存在,则扩展路径
if(!visited && !inter_rank_exists) {
std::vector<uint64_t> new_path = path;
new_path.push_back(neighbor_id);
queue.push(new_path);
}
}
}
}
}
#endif
/**
* @brief 打印GPU路径信息
*
* 该函数用于打印`gpu_paths_`中存储的所有GPU路径信息。对于每一条路径,
* 它会打印路径的长度以及路径中每个节点的详细信息,包括节点的`interRank`、
* `deviceValue`、`terminalType`和`numaId`。
*/
void PathFinder::printGpuPaths() {
// 遍历gpu_paths_中的每一对(start_node_id, paths)
for(const auto& start_node_pair : gpu_paths_) {
uint64_t start_node_id = start_node_pair.first; // 获取起始节点的ID
char busIdStr[17] = ""; // 用于存储总线ID字符串
// 根据起始节点的ID查找对应的节点对象
const scclTopoNode_t* start_node = findNodeById(start_node_id);
// 如果找到了对应的节点对象,则将其总线ID转换为字符串
if(start_node) {
int64ToBusId(start_node->busId, busIdStr);
} else {
return;
}
const auto& paths = start_node_pair.second; // 获取与起始节点关联的所有路径
size_t path_count = paths.size(); // 计算路径的数量
int interRank, deviceValue, terminalType, hipDev, numaId;
// 根据起始节点的ID获取其interRank、deviceValue、terminalType和numaId
bootstrap::physical_links::getIdComponents(start_node_id, &interRank, &deviceValue, &terminalType, &hipDev, &numaId);
printf("GPU node ID:%lu (InterRank:%d, V:%d, T:%d, H:%d, N:%d) (Path count: %zu)\n",
start_node_id,
interRank,
deviceValue,
terminalType,
hipDev,
numaId,
path_count);
// 遍历与起始节点关联的所有路径
for(const auto& path : paths) {
size_t path_length = path.size(); // 计算路径的长度
// 打印路径的长度
printf("Path (length: %zu): ", path_length);
// 遍历路径中的每个节点
for(size_t i = 0; i < path.size(); ++i) {
uint64_t node_id = path[i]; // 获取节点的ID
// 使用findNodeById函数查找节点的详细信息
const scclTopoNode_t* node = findNodeById(node_id);
if(node) {
// 根据节点的ID获取其interRank、deviceValue、terminalType和numaId
bootstrap::physical_links::getIdComponents(node->id, &interRank, &deviceValue, &terminalType, &hipDev, &numaId);
// 将节点的总线ID转换为字符串
int64ToBusId(node->busId, busIdStr);
// 打印节点的信息,包括其interRank、deviceValue、terminalType、numaId、类型和总线ID字符串
printf("ID:%lu (InterRank:%d, V:%d, T:%d, H:%d, N:%d, type:%d, busIdStr:%s)",
node->id,
interRank,
deviceValue,
terminalType,
hipDev,
numaId,
node->type,
busIdStr);
}
// 如果当前节点不是路径中的最后一个节点,则打印" -> "以分隔节点
if(i != path.size() - 1) {
printf(" -> ");
}
}
// 换行,准备打印下一条路径
printf("\n=============================================\n");
}
}
}
} // namespace graph
} // namespace topology
} // namespace hardware
} // namespace sccl
#pragma once
#include <vector>
#include <queue>
#include <unordered_map>
#include <cstring> // 为了使用strlen
#include "base.h"
#include "graph_utils.h"
namespace sccl {
namespace hardware {
namespace topology {
namespace graph {
class PathFinder {
public:
// 构造函数
PathFinder(const BootstrapComm_t* bootstrap_comm);
// 打印函数
scclResult_t findGpuPaths();
// 打印函数
void printGpuPaths();
private:
ByteSpanArray<scclTopoNode_t> node_container_; // 使用NodeContainer来存储nodes数据
std::unordered_map<uint64_t, std::vector<uint64_t>> graph_nodes_; // 使用无序映射存储图的节点和它们的邻居
std::unordered_map<uint64_t, std::vector<std::vector<uint64_t>>> gpu_paths_; // 使用无序映射存储从每个GPU节点到其他GPU节点的所有路径
// 存储node.id到nodes_span索引的映射
std::unordered_map<uint64_t, size_t> id_to_index_;
// 使用广度优先搜索(BFS)查找从起始GPU节点到其他GPU节点的所有路径
void bfsFindGpuPaths(uint64_t start_node_id);
// 根据node.id查找节点的函数
const scclTopoNode_t* findNodeById(uint64_t id) const;
int rank = -1; // 当前节点的全局排名
int nRanks = 0; // 总的节点数量
int localRank = -1; // 当前节点在本地计算节点中的排名
int nLocalRanks = 0; // 本地计算节点中的节点总数
int interRank = -1; // 整个节点在全部节点中的位置
int nInterRanks = 0; // 全局拥有节点的个数
};
} // namespace graph
} // namespace topology
} // namespace hardware
} // namespace sccl
## 用于构建拓扑图
包括3个部分:
- 物理拓扑构建:CPU、GPU、PCIe、NVLink等物理连接
- 通信路径计算:GPU到其他GPU的连接通信路径(生成无向图)
- 逻辑拓扑构建:生成各种形状的逻辑拓扑,如ring、tree、mesh、等
其中,物理拓扑构建表示硬件节点间的连接,已经在bootstrap文件夹中完成
本文件完成通信路径计算和逻辑拓扑构建
按照文件夹内功能分类如下:
hardware_topo:
- 通信路径计算
logical_topo:
- 逻辑拓扑构建
- 根据无向图计算topo路径
...@@ -5,11 +5,28 @@ namespace sccl { ...@@ -5,11 +5,28 @@ namespace sccl {
namespace hardware { namespace hardware {
namespace topology { namespace topology {
/**
* 将64位整数转换为PCI总线ID字符串
*
* @param id 输入的64位整数,包含PCI总线ID信息
* @param busId 输出缓冲区,用于存储格式化后的PCI总线ID字符串(格式为"域:总线:设备.功能")
* @return 返回scclSuccess表示成功
*/
scclResult_t int64ToBusId(int64_t id, char* busId) { scclResult_t int64ToBusId(int64_t id, char* busId) {
sprintf(busId, "%04lx:%02lx:%02lx.%01lx", (id) >> 20, (id & 0xff000) >> 12, (id & 0xff0) >> 4, (id & 0xf)); sprintf(busId, "%04lx:%02lx:%02lx.%01lx", (id) >> 20, (id & 0xff000) >> 12, (id & 0xff0) >> 4, (id & 0xf));
return scclSuccess; return scclSuccess;
} }
/**
* 将总线ID字符串转换为64位整数值
*
* @param busId 输入的总线ID字符串,可能包含分隔符(.或:)
* @param id 输出参数,用于存储转换后的64位整数值
* @return 返回操作结果,scclSuccess表示成功
*
* @note 总线ID字符串中的非十六进制字符(0-9,a-f,A-F)和分隔符将被忽略
* 转换后的值通过strtol以16进制解析获得
*/
scclResult_t busIdToInt64(const char* busId, int64_t* id) { scclResult_t busIdToInt64(const char* busId, int64_t* id) {
char hexStr[17]; // Longest possible int64 hex string + null terminator. char hexStr[17]; // Longest possible int64 hex string + null terminator.
int hexOffset = 0; int hexOffset = 0;
...@@ -27,6 +44,40 @@ scclResult_t busIdToInt64(const char* busId, int64_t* id) { ...@@ -27,6 +44,40 @@ scclResult_t busIdToInt64(const char* busId, int64_t* id) {
return scclSuccess; return scclSuccess;
} }
#define BUSID_SIZE (sizeof("0000:00:00.0"))
#define BUSID_REDUCED_SIZE (sizeof("0000:00"))
static void memcpylower(char* dst, const char* src, const size_t size) {
for(int i = 0; i < size; i++)
dst[i] = tolower(src[i]);
}
/**
* @brief 获取给定PCI设备的路径。
*
* 此函数根据提供的PCI设备的总线ID(busId),生成该设备在系统中的实际路径。
* 它首先构造一个可能的路径字符串,然后使用`realpath`函数将其转换为实际的文件系统路径。
* 如果路径无法解析,函数将返回一个错误代码。
*
* @param busId 一个C字符串,表示PCI设备的总线ID。例如:"0000:00:00.0"。
* @param path 一个指向字符指针的指针,用于存储解析后的实际路径。调用者负责释放此内存。
*
* @return scclResult_t 返回操作结果。如果成功,返回`scclSuccess`;如果失败,返回`scclSystemError`。
*
* @note 此函数内部使用了`realpath`函数,该函数可能因路径不存在或其他系统错误而失败。
* 在这种情况下,函数将输出一个警告信息,并返回错误代码。
*/
scclResult_t getPciPath(const char* busId, char** path) {
char busPath[] = "/sys/class/pci_bus/0000:00/../../0000:00:00.0";
memcpylower(busPath + sizeof("/sys/class/pci_bus/") - 1, busId, BUSID_REDUCED_SIZE - 1);
memcpylower(busPath + sizeof("/sys/class/pci_bus/0000:00/../../") - 1, busId, BUSID_SIZE - 1);
*path = realpath(busPath, NULL);
if(*path == NULL) {
WARN("Could not find real path of %s", busPath);
return scclSystemError;
}
return scclSuccess;
}
// 定义一个常量,表示最大字符串长度 // 定义一个常量,表示最大字符串长度
static constexpr int MAX_STR_LEN = 255; static constexpr int MAX_STR_LEN = 255;
......
#pragma once #pragma once
#include <string.h> #include <map>
#include <string>
#include <cstddef>
#include "base.h" #include "base.h"
#include "net.h" #include "net.h"
#include "hardware_utils.h" #include "hardware_utils.h"
...@@ -9,9 +11,18 @@ namespace sccl { ...@@ -9,9 +11,18 @@ namespace sccl {
namespace hardware { namespace hardware {
namespace topology { namespace topology {
#define SCCL_TOPO_NODE_TYPES (6) // 硬件node的类型 constexpr int SCCL_TOPO_NODE_TYPES = 6; // 硬件node的类型数量
#define SCCL_TOPO_MAX_NODE_PER_TYPE (4) // 每个硬件node类型中节点的数量,间接表明网络拓扑结构的最大层数 constexpr int SCCL_TOPO_MAX_NODE_PER_TYPE = 4; // 每个硬件node类型中节点的数量,间接表明网络拓扑结构的最大层数
#define SCCL_TOPO_RANK_MAX_LINKS (8) // 每个rank中节点与当前rank中其他节点的链接数量 constexpr int SCCL_TOPO_RANK_MAX_LINKS = 8; // 每个rank中节点与当前rank中其他节点的链接数量
typedef enum : int {
CPU = 1, // 实际是numa域
PCI = 2,
GPU = 3,
NVS = 4, // 包括XGMI和NVLink
NIC = 5,
NET = 6 // 主要是RDMA网卡
} nodeType_t;
// 定义 topoPathType_t 枚举类型,用于表示不同的路径类型。 // 定义 topoPathType_t 枚举类型,用于表示不同的路径类型。
enum topoPathType { enum topoPathType {
...@@ -58,6 +69,8 @@ scclResult_t int64ToBusId(int64_t id, char* busId); ...@@ -58,6 +69,8 @@ scclResult_t int64ToBusId(int64_t id, char* busId);
// 将总线ID字符串转换为64位整数 // 将总线ID字符串转换为64位整数
scclResult_t busIdToInt64(const char* busId, int64_t* id); scclResult_t busIdToInt64(const char* busId, int64_t* id);
scclResult_t getPciPath(const char* busId, char** path);
// 将PCI路径转换为64位整数,路径偏移量和最小偏移量作为参数 // 将PCI路径转换为64位整数,路径偏移量和最小偏移量作为参数
scclResult_t pciPathToInt64(char* path, int offset, int minOffset, int64_t* id); scclResult_t pciPathToInt64(char* path, int offset, int minOffset, int64_t* id);
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "alloc.h" #include "alloc.h"
#include "utils.h" #include "utils.h"
#include "asm_ops.h" #include "asm_ops.h"
#include "container.h"
/* /*
外部环境变量设置: 外部环境变量设置:
......
...@@ -74,13 +74,13 @@ static const char* scclGetErrorString(scclResult_t code) { ...@@ -74,13 +74,13 @@ static const char* scclGetErrorString(scclResult_t code) {
} \ } \
} while(0); } while(0);
#define SCCLCHECKGOTO(call, RES, label) \ #define SCCLCHECKGOTO(call, RES, label) \
do { \ do { \
RES = call; \ RES = call; \
if(RES != scclSuccess && RES != scclInProgress) { \ if(RES != scclSuccess && RES != scclInProgress) { \
INFO(SCCL_LOG_CODEALL, "%s:%d -> %d", __func__, __FILE__, __LINE__, RES); \ INFO(SCCL_LOG_CODEALL, "%s:%d %s -> %d", __func__, __LINE__, __FILE__, RES); \
goto label; \ goto label; \
} \ } \
} while(0); } while(0);
#define HIPCHECK(cmd) \ #define HIPCHECK(cmd) \
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment