#include // 为了使用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 pciClassMap = { {"0x060400", PCI}, {"0x068000", NVS}, {"0x068001", CPU}, {"0x03", GPU}, {"0x02", NIC}, {"0x120000", GPU}, {"0x0b4000", GPU}}; // 定义一个映射,用于将PCI代代码映射到带宽 static std::map 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 static std::string findClosestMatch(const std::string& key, const std::map& 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& 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& 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& 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 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(strtoul(deviceStr, nullptr, 16)); // 组合id id = (static_cast(interRank & 0xFFFFFF) << 40) | // 最开始24位为interRank (static_cast(hipDev & 0xFF) << 32) | // 之后8位为hipDev (static_cast(deviceValue & 0xFFFF) << 16) | // 之后16位为deviceValue (static_cast(terminalType & 0xFF) << 8) | // 之后8位表示terminalType (static_cast(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(interRank) << 32) | // 前32位为interRank (static_cast(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.对于每个拓扑节点,需要其id包括: * - interRank(节点间的编号) * - hipDev(设备编号) * - devValue(设备编码) * - terminalType(连接终端的类型) * - numaId(NUMA节点编号) * 2.对于PCI路径中的每个部分,需要注意: * - 对于GPU设备,整条path修改其numaId为映射关系的值,不修改hipDev值。GPU设备本身的numaId值不修改。 * - 对于NIC设备,整条path的pci type的node,其hipDev为-1(8bit表示为255),不修改numaId值。 * 3.建立初步连接过程中: * - 检查路径下存在NUMA节点,首先创建NUMA节点。 * - 检查id相同且type相同,则合并节点,否则创建新节点。 * * @param pciPath PCI设备路径 * @param interRank 跨节点的排名 * @param nodes 节点向量,函数将生成的节点添加到此向量中 * @return 如果成功,返回scclSuccess;否则返回相应的错误代码 */ scclResult_t generate_topo_nodes(const char* pciPath, int interRank, int hipDev, ByteSpanVector& 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; } int terminalNumaId = -1; { char numaIdStr[numaIdStrLen]; scclTopoGetStrFromSys(pciPath, "numa_node", numaIdStr); terminalNumaId = static_cast(strtoul(numaIdStr, nullptr, 10)); } // TODO: 根据rccl的xml文件修改的代码,后续查看有没有更好的方法 int adjustedNumaId = -1; int terminalType = getDeviceTypeFromPciPath(pciPath); { // 如果node的type类型为GPU,则整条path修改numaId char numaIdStr[numaIdStrLen]; getNumaIdStr(pciPath, hipDev, terminalType, numaIdStr); adjustedNumaId = static_cast(strtoul(numaIdStr, nullptr, 10)); } // 创建numa图点 scclTopoNode_t numaNode(adjustedNumaId, interRank); if(numaNode.type <= 0) { WARN("Cannot find correct numa node:%d from pciPath:%s", pciPath, adjustedNumaId); 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, adjustedNumaId); 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 + "/"; #if 0 printf("currentPath:%s, interRank:%d, terminalType:%d, hipDev:%d, adjustedNumaId:%d, terminalNumaId:%d\n", currentPath.c_str(), interRank, terminalType, hipDev, adjustedNumaId, terminalNumaId); #endif //// 1.创建拓扑图点 // 仅当到达终端node时,终端设备的numaId为其pci路径下读取的值 int nodeNumaId = adjustedNumaId; // 新创建一个变量,不影响后面的给parent和当前node添加neighbor部分 if(terminalType == GPU && currentPath.length() > strlen(pciPath) && strncmp(currentPath.c_str(), pciPath, strlen(pciPath)) == 0) { nodeNumaId = terminalNumaId; } scclTopoNode_t node(currentPath.c_str(), interRank, terminalType, hipDev, nodeNumaId); // 检查node是否是有效的,即不是空的 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, adjustedNumaId); // 如果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" * * 当函数返回时,affinityStr的生命周期结束,但它所包含的数据被复制到了一个新的std::string对象中。栈上分配的,会自动释放 * * @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((idToDecompose >> 40) & 0xFFFFFF); // 提取interRank } if(hipDev) { *hipDev = static_cast((idToDecompose >> 32) & 0xFF); // 提取hipDev } if(deviceValue) { *deviceValue = static_cast((idToDecompose >> 16) & 0xFFFF); // 提取deviceValue } if(terminalType) { *terminalType = static_cast((idToDecompose >> 8) & 0xFF); // 提取terminalType } if(numaId) { *numaId = static_cast(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类型,也可以是ByteSpanArray类型 * @param nodeIndex 要打印的节点在向量中的索引 * @param prefix 打印信息的前缀字符串 */ void printTopoNode(ByteSpanArray& 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