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

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

parent 379c4128
......@@ -83,6 +83,7 @@ static void initOnceFunc() {
WARN("pfn_hsa_system_get_info failed with %d", res);
goto error;
}
res = pfn_hsa_system_get_info(HSA_SYSTEM_INFO_VERSION_MINOR, &version_minor);
if(res != 0) {
WARN("pfn_hsa_system_get_info failed with %d", res);
......@@ -162,6 +163,7 @@ static void initOnceFunc() {
*/
initResult = scclSuccess;
return;
error:
initResult = scclSystemError;
......@@ -182,6 +184,8 @@ scclResult_t rocmLibraryInit() {
return rocm_wrap::initResult;
}
int64_t getDmaBufEnable() { return rocm_wrap::scclParamDmaBufEnable(); }
} // namespace net
} // namespace hardware
} // namespace sccl
......@@ -25,6 +25,8 @@ DECLARE_ROCM_PFN_EXTERN(hsa_status_string);
// 初始化 ROCm 库
scclResult_t rocmLibraryInit(void);
// 获取 dma buf
int64_t getDmaBufEnable();
} // namespace net
} // namespace hardware
......
......@@ -7,37 +7,139 @@
#include "bootstrap_net.h"
#include "thread_pool.h"
#include "ipc_socket.h"
#include "physical_links.h"
namespace sccl {
namespace hardware {
namespace topology {
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 bootstrapCreateRoot(struct BootstrapHandle* handle);
scclResult_t bootstrapGetUniqueId(BootstrapHandle_t* 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 {
public:
Bootstrap(const struct BootstrapHandle*, int rank, int nRanks);
Bootstrap(const BootstrapHandle_t*, int rank, int nRanks);
~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:
// 创建根节点的数据广播
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);
// 广播节点信息
scclResult_t
bootstrapCommAllGather(std::vector<struct BootstrapNodeBasic>& all_node_basic, struct scclNodeInfo* node_info, struct scclNodeInfoSet* node_info_set);
// 实现rank_info信息的节点间通信的AllGather操作
scclResult_t bootstrapCommAllGather(scclRankInfo_t* rank_info, scclNodeInfo_t* node_info, scclRankPhysSet_t* rank_phys_set);
// 额外处理nRanks个nodes的连接关系
scclResult_t bootstrapNodesLink(void* node_info_vec, int node_info_total_bytes);
private:
int rank, nRanks; // 初始化阶段获取MPI的值
......@@ -48,7 +150,9 @@ private:
volatile uint32_t* abortFlag; // 中止标志,非阻塞套接字设置
// 外部传入的0号节点的基础信息
const struct BootstrapHandle* root_handle;
const BootstrapHandle_t* root_handle;
// 节点内所有进程的基础ip信息
BootstrapNodeBasic_t* all_node_basic = nullptr;
// 初始化标志
bool socketInitDone;
......@@ -60,6 +164,9 @@ private:
scclIpcSocket_t* ipcsocket = nullptr; // 指向scclIpcSocket类实例的指针,初始值为nullptr
};
// 打印唯一的拓扑信息
scclResult_t printRankInfo(const std::string& prefix, scclRankInfo_t* info);
} // namespace bootstrap
} // namespace topology
} // namespace hardware
......
......@@ -7,32 +7,6 @@ namespace hardware {
namespace topology {
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) {
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 topology
} // namespace hardware
......
#pragma once
#include <string.h>
#include <map>
#include <string>
#include <cstddef>
#include <vector>
#include "base.h"
......@@ -17,106 +18,31 @@ typedef union net::net_socket::scclSocketAddress scclSocketAddress_t;
typedef struct net::net_socket::scclSocket scclSocket_t;
typedef net::scclNet_t scclNet_t;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 用于初始化时广播0号rank的地址信息
struct BootstrapHandle {
typedef struct BootstrapHandle {
uint64_t magic = 0; // 随机码,用于socket通信
scclSocketAddress_t addr; // 地址,用于网络通信
};
} BootstrapHandle_t;
#define SCCL_UNIQUE_ID_BYTES (40) // sizeof(BootstrapHandle)
#define SCCL_UNIQUE_ID_BYTES (40) // sizeof(BootstrapHandle_t)
typedef struct {
char internal[SCCL_UNIQUE_ID_BYTES];
} scclUniqueId;
// 仅用于初始化的函数bootstrapCreateRoot,用于传递detach线程的参数
struct bootstrapRootArgs {
typedef struct bootstrapRootArgs {
uint64_t magic;
scclSocket_t* listenSock = nullptr; // 根节点的监听
};
} bootstrapRootArgs_t;
// 用于初始建立连接阶段,0号rank之外的进程向其传递的信息
struct BootstrapNodeBasic {
typedef struct BootstrapNodeBasic {
int rank;
int nRanks; // 进程的总数量
uint64_t hostHash; // 用于区分host的CPU编号
scclSocket_t sock; // 各个进程的监听套接字地址,用于网络通信
};
// 定义每个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;
};
} BootstrapNodeBasic_t;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 获取主机唯一标识的哈希值,该哈希值在裸机和容器实例中都是唯一的
......@@ -134,25 +60,6 @@ scclResult_t getBusId(int hipDev, int64_t* busId);
// 获取当前HIP设备的计算能力版本号
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 topology
} // namespace hardware
......
This diff is collapsed.
#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 {
namespace hardware {
namespace topology {
/**
* 将64位整数转换为PCI总线ID字符串
*
* @param id 输入的64位整数,包含PCI总线ID信息
* @param busId 输出缓冲区,用于存储格式化后的PCI总线ID字符串(格式为"域:总线:设备.功能")
* @return 返回scclSuccess表示成功
*/
scclResult_t int64ToBusId(int64_t id, char* busId) {
sprintf(busId, "%04lx:%02lx:%02lx.%01lx", (id) >> 20, (id & 0xff000) >> 12, (id & 0xff0) >> 4, (id & 0xf));
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) {
char hexStr[17]; // Longest possible int64 hex string + null terminator.
int hexOffset = 0;
......@@ -27,6 +44,40 @@ scclResult_t busIdToInt64(const char* busId, int64_t* id) {
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;
......
#pragma once
#include <string.h>
#include <map>
#include <string>
#include <cstddef>
#include "base.h"
#include "net.h"
#include "hardware_utils.h"
......@@ -9,9 +11,18 @@ namespace sccl {
namespace hardware {
namespace topology {
#define SCCL_TOPO_NODE_TYPES (6) // 硬件node的类型
#define SCCL_TOPO_MAX_NODE_PER_TYPE (4) // 每个硬件node类型中节点的数量,间接表明网络拓扑结构的最大层数
#define SCCL_TOPO_RANK_MAX_LINKS (8) // 每个rank中节点与当前rank中其他节点的链接数量
constexpr int SCCL_TOPO_NODE_TYPES = 6; // 硬件node的类型数量
constexpr int SCCL_TOPO_MAX_NODE_PER_TYPE = 4; // 每个硬件node类型中节点的数量,间接表明网络拓扑结构的最大层数
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 枚举类型,用于表示不同的路径类型。
enum topoPathType {
......@@ -58,6 +69,8 @@ scclResult_t int64ToBusId(int64_t id, char* busId);
// 将总线ID字符串转换为64位整数
scclResult_t busIdToInt64(const char* busId, int64_t* id);
scclResult_t getPciPath(const char* busId, char** path);
// 将PCI路径转换为64位整数,路径偏移量和最小偏移量作为参数
scclResult_t pciPathToInt64(char* path, int offset, int minOffset, int64_t* id);
......
......@@ -8,6 +8,7 @@
#include "alloc.h"
#include "utils.h"
#include "asm_ops.h"
#include "container.h"
/*
外部环境变量设置:
......
......@@ -74,13 +74,13 @@ static const char* scclGetErrorString(scclResult_t code) {
} \
} while(0);
#define SCCLCHECKGOTO(call, RES, label) \
do { \
RES = call; \
if(RES != scclSuccess && RES != scclInProgress) { \
INFO(SCCL_LOG_CODEALL, "%s:%d -> %d", __func__, __FILE__, __LINE__, RES); \
goto label; \
} \
#define SCCLCHECKGOTO(call, RES, label) \
do { \
RES = call; \
if(RES != scclSuccess && RES != scclInProgress) { \
INFO(SCCL_LOG_CODEALL, "%s:%d %s -> %d", __func__, __LINE__, __FILE__, RES); \
goto label; \
} \
} while(0);
#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