"tests/vscode:/vscode.git/clone" did not exist on "d61cb493d4f652d753e2c5bf21f2391cccbe2f30"
physical_links.cpp 31.3 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
#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进行特殊处理,不确定是否具有普适性
/**
447
 * @brief 根据给定的PCI路径生成拓扑节点,核心代码
448
449
 *
 * 该函数通过解析给定的PCI路径,生成相应的拓扑节点,并将它们添加到节点向量中。
450
451
452
453
454
455
456
457
458
459
460
461
462
 * 创建拓扑节点时,需要注意以下几点:
 * 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相同,则合并节点,否则创建新节点。
463
464
465
466
467
468
469
470
471
472
473
474
475
476
 *
 * @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;
    }

    int terminalNumaId = -1;
477
478
479
480
481
482
483
484
    {
        char numaIdStr[numaIdStrLen];
        scclTopoGetStrFromSys(pciPath, "numa_node", numaIdStr);
        terminalNumaId = static_cast<int>(strtoul(numaIdStr, nullptr, 10));
    }

    // TODO: 根据rccl的xml文件修改的代码,后续查看有没有更好的方法
    int adjustedNumaId = -1;
485
486
487
488
    int terminalType   = getDeviceTypeFromPciPath(pciPath);
    { // 如果node的type类型为GPU,则整条path修改numaId
        char numaIdStr[numaIdStrLen];
        getNumaIdStr(pciPath, hipDev, terminalType, numaIdStr);
489
        adjustedNumaId = static_cast<int>(strtoul(numaIdStr, nullptr, 10));
490
491
492
    }

    // 创建numa图点
493
    scclTopoNode_t numaNode(adjustedNumaId, interRank);
494
    if(numaNode.type <= 0) {
495
        WARN("Cannot find correct numa node:%d from pciPath:%s", pciPath, adjustedNumaId);
496
497
498
499
500
501
502
503
504
505
506
507
508
        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 {
            // 处理容量不足的情况
509
            WARN("Cannot add numa node:%d from pciPath:%s, nodes full!", pciPath, adjustedNumaId);
510
511
512
513
514
515
516
517
518
519
520
521
            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 + "/";
522
523
524
525
526
527
528
529
530
#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
531
            //// 1.创建拓扑图点
532
533
534
535
536
537
538
            // 仅当到达终端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是否是有效的,即不是空的
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
            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))) {
563
                uint64_t parentId = getIdFromPciPath(parentPath.c_str(), interRank, terminalType, hipDev, adjustedNumaId);
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
                // 如果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"
 *
594
595
 * 当函数返回时,affinityStr的生命周期结束,但它所包含的数据被复制到了一个新的std::string对象中。栈上分配的,会自动释放
 *
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
 * @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);
}

611
// 函数输出id分解后的所有数据。与函数 getIdFromPciPathWithNuma 相对应。
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
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