paths.cpp 18.9 KB
Newer Older
1
2
3
4
5
6
7
8
#include <unordered_set>
#include "paths.h"

namespace sccl {
namespace hardware {
namespace topology {
namespace graph {

9
PathFinder::PathFinder(const BootstrapComm_t* bootstrap_comm, std::vector<char>& node_info_vec, size_t node_info_total_bytes)
10
11
12
13
14
15
    : rank(bootstrap_comm->rank),
      nRanks(bootstrap_comm->nRanks),
      localRank(bootstrap_comm->localRank),
      nLocalRanks(bootstrap_comm->nLocalRanks),
      interRank(bootstrap_comm->interRank),
      nInterRanks(bootstrap_comm->nInterRanks),
16
17
      node_container_(node_info_vec.data(), bootstrap_comm->nRanks * node_info_total_bytes) { // 初始化NodeContainer对象

18
19
20
21
22
23
    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
24
            graph_node_neighbors_[node->id] = std::vector<uint64_t>(node->neighbors.begin(), node->neighbors.begin() + node->neighborCount);
25
26
27
28
29
30
31
32
33
34
35
36
37
38
            // 构建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;
39
            physical_links::getIdComponents(node_id, &interRank, &deviceValue, &terminalType, &hipDev, &numaId);
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
            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) {
57
                    physical_links::getIdComponents(neighbor_id, &interRank, &deviceValue, &terminalType, &hipDev, &numaId);
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
                    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
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
    // 查找当前rank对应的GPU的node,并执行BFS搜索,查找到其他所有GPU node的路径
    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 nodeInterRank, nodeHipDev;
        physical_links::getIdComponents(node->id, &nodeInterRank, nullptr, nullptr, &nodeHipDev, nullptr);
        if(node->type == GPU && nodeInterRank == this->interRank && nodeHipDev == this->localRank) {
            // printf("bfsFindGpuPaths start_node_id=%lu, running\n", node->id);
            bfsFindGpuPaths(node->id);
        }
    }
#if 1
    if(rank == 1) {
        printGpuPaths();
    }
#endif
}

int getGpuRankFromNodeId(uint64_t node_id, int nLocalRanks) {
    int interRank, hipDev;
    // 调用 getIdComponents 函数获取 interRank 和 hipDev
    physical_links::getIdComponents(node_id, &interRank, nullptr, nullptr, &hipDev, nullptr);
    // 计算并返回 gpu_rank
    int gpu_rank = interRank * nLocalRanks + hipDev;

    printf("node_id=%lu, interRank=%d, hipDev=%d, gpu_rank=%d\n", node_id, interRank, hipDev, gpu_rank);
    return gpu_rank;
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
}

/**
 * @brief 计算拓扑图中GPU节点之间的点对点映射
 *
 * 该函数用于计算拓扑图中GPU节点之间的点对点映射。它遍历`gpu_paths_`中的所有路径,
 * 对于每一条路径,它将路径中的每个节点添加到`topo_graph`的`graph_nodes`中。然后,它根据路径中途径的节点点确定连接方式的类型,
 * 并将连接方式的类型存储在`topo_graph`的`transport_map`中。最后,它将路径添加到`topo_graph`的`gpu_paths`中。
 *
 * @param topo_graph 指向拓扑图的指针
 * @return scclResult_t 计算结果
 */
scclResult_t PathFinder::computeTopoGpuP2pMap(scclTopoGraph_t* topo_graph) {
    // 遍历gpu_paths_中的所有路径
    for(const auto& start_node_pair : gpu_paths_) {
        uint64_t start_node_id = start_node_pair.first;
        const auto& paths      = start_node_pair.second;

        // 遍历从start_node_id到其他GPU节点的所有路径
        for(const auto& path : paths) {
#if 0
            printf("paths len=%zu, path=%zu\n", paths.size(), path.size());
#endif
            if(path.size() == 0)
                continue;

            // 遍历路径中的每个节点,将其添加到graph_nodes中
            for(uint64_t node_id : path) {
                // 查找node_container_中对应的节点
                const scclTopoNode_t* node = findNodeById(node_id);
                if(node != nullptr) {
                    // 检查节点是否已经存在于graph_nodes中
                    auto it = topo_graph->graph_nodes.find(node_id);
                    if(it == topo_graph->graph_nodes.end()) {
                        // 如果节点不存在于graph_nodes中,则将其拷贝到graph_nodes
                        topo_graph->graph_nodes[node_id] = *node;
                    }
                }
            }

            // 将路径添加到topo_graph中的gpu_paths
            uint64_t end_node_id = path.back(); // 获取路径的最后一个节点的ID

            // 记录bitmap
            LinkType_t link_type;
153
154
155
156
157
            // 根据路径中途径的节点点确定连接方式的类型
            SCCLCHECK(determineLinkType(path, &link_type));
            // 获取gpu的rank
            int start_gpu_rank = getGpuRankFromNodeId(start_node_id, nLocalRanks);
            int end_gpu_rank   = getGpuRankFromNodeId(end_node_id, nLocalRanks);
158

159
160
            // 查找transport_map中的起始和结束节点
            uint8_t* transport_map_pt = topo_graph->getTransportMapData(start_gpu_rank, end_gpu_rank);
161
162

            // 将连接方式的类型存储在transport_map中
163
164
165
            if(*transport_map_pt > 0 && link_type > 0) {
                if(link_type < static_cast<LinkType_t>(*transport_map_pt)) {
                    *transport_map_pt = link_type;
166
167
168
169
                    // 清空之前的路径
                    topo_graph->gpu_paths[start_node_id][end_node_id].clear();
                    // 添加新的路径
                    topo_graph->gpu_paths[start_node_id][end_node_id].push_back(path);
170
                } else if(link_type == static_cast<LinkType_t>(*transport_map_pt)) {
171
172
173
174
                    // 添加新的路径
                    topo_graph->gpu_paths[start_node_id][end_node_id].push_back(path);
                }
            } else {
175
                *transport_map_pt = static_cast<uint8_t>(link_type);
176
177
178
                // 添加新的路径
                topo_graph->gpu_paths[start_node_id][end_node_id].push_back(path);
            }
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
#if 0
            {
                char start_busIdStr[17] = ""; // 用于存储总线ID字符串
                // 根据起始节点的ID查找对应的节点对象
                const scclTopoNode_t* start_node = findNodeById(start_node_id);
                // 如果找到了对应的节点对象,则将其总线ID转换为字符串
                if(start_node) {
                    int64ToBusId(start_node->busId, start_busIdStr);
                }
                char end_busIdStr[17] = ""; // 用于存储总线ID字符串
                // 根据起始节点的ID查找对应的节点对象
                const scclTopoNode_t* end_node = findNodeById(end_node_id);
                // 如果找到了对应的节点对象,则将其总线ID转换为字符串
                if(end_node) {
                    int64ToBusId(end_node->busId, end_busIdStr);
                }
                printf("nLocalRanks=%d, start_node_id=%lu, busIdStr=%s, end_node_id=%lu, busIdStr=%s\n"
                       "start_gpu_rank: %d, end_gpu_rank: %d, link_type: %d, paths count: %zu\n",
                       nLocalRanks,
                       start_node_id,
                       start_busIdStr,
                       end_node_id,
                       end_busIdStr,
                       start_gpu_rank,
                       end_gpu_rank,
                       *(topo_graph->getTransportMapData(start_gpu_rank, end_gpu_rank)),
                       topo_graph->gpu_paths[start_node_id][end_node_id].size());
            }
#endif
208
209
210
211
        }
    }

    return scclSuccess;
212
213
}

214
/////////////////////////////////////////////////////////////////////////////////////////////
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
/**
 * @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
}

232
// TODO: 当nRanks特别大时,可以考虑采用kernel实现
233
234
235
236
237
238
239
240
241
242
243
/**
 * @brief 使用广度优先搜索(BFS)查找从起始GPU节点到其他GPU节点的所有路径
 *
 * 1.该函数从指定的起始GPU节点开始,使用广度优先搜索算法查找所有能够到达的GPU节点,并记录从起始节点到每个目标GPU节点的所有路径。
 * 每条路径中的所有节点最多使用一次。
 *
 * 2.该函数还添加了一个限制,以防止在路径中出现`interRank`在变化后又变回来的情况。
 * 也就是说,如果路径从`interRank == 0`连接到`interRank == 1`的节点后,则不能再连接回`interRank == 0`。
 *
 * @param start_node_id 起始GPU节点的ID
 */
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
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<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;
271
            physical_links::getIdComponents(current_node->id, nullptr, nullptr, nullptr, &hipDev, nullptr);
272
273
274
275
276
277
            // 仅当节点内的device id小于等于nLocalRanks时,才是有效GPU,才将路径加入结果
            if(hipDev < nLocalRanks) {
                gpu_paths_[start_node_id].push_back(path);
            }
        } else {
            int nodeInterRank;
278
            physical_links::getIdComponents(nodeId, &nodeInterRank);
279
280
281
282
283
284
285
            // 遍历当前节点的所有邻居节点
            for(uint64_t neighbor_id : graph_node_neighbors_.at(nodeId)) {
                if(findNodeById(neighbor_id) == nullptr) {
                    continue;
                }
                // 获取邻居节点的interRank
                int neighbor_inter_rank;
286
                physical_links::getIdComponents(neighbor_id, &neighbor_inter_rank);
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

                // 检查邻居节点是否已在当前路径中访问过
                bool visited = std::find(path.begin(), path.end(), neighbor_id) != path.end();
                // 检查interRank是否已经存在(仅当interRank改变时)
                bool inter_rank_exists = neighbor_inter_rank != nodeInterRank && std::find(path.begin(), path.end(), neighbor_id) != path.end();
                // 如果邻居节点未访问过且interRank未存在,则扩展路径
                if(!visited && !inter_rank_exists) {
                    std::vector<uint64_t> new_path = path;
                    new_path.push_back(neighbor_id);

                    // 如果新路径比已有的最短路径更短,或者长度相同但尚未记录,则更新最短路径
                    auto& paths = shortest_paths[neighbor_id];
                    if(paths.empty() || paths.front().size() > new_path.size() ||
                       (paths.front().size() == new_path.size() && std::find(paths.begin(), paths.end(), new_path) == paths.end())) {
                        if(paths.empty() || paths.front().size() > new_path.size()) {
                            paths = {new_path};
                        } else {
                            paths.push_back(new_path);
                        }
                        queue.push(new_path);
                    }
                }
            }
        }
    }
}

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
/**
 * @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
340
        physical_links::getIdComponents(start_node_id, &interRank, &deviceValue, &terminalType, &hipDev, &numaId);
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
        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
363
                    physical_links::getIdComponents(node->id, &interRank, &deviceValue, &terminalType, &hipDev, &numaId);
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
                    // 将节点的总线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");
        }
    }
}

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
scclResult_t PathFinder::determineLinkType(const std::vector<uint64_t>& path, LinkType_t* link_type) {
    if(path.size() == 1) {
        *link_type = LINK_LOC;
    }

    bool has_gpu = false, has_pix = false, has_nic = false, has_cpu = false;
    // 遍历路径中的每个节点,从第2个点开始
    for(int i = 1; i < path.size(); i++) {
        uint64_t node_id = path[i];

        // 查找node_container_中对应的节点
        const scclTopoNode_t* node = findNodeById(node_id);
        if(node == nullptr) {
            WARN("cannot find node from id: %lu", node_id);
            return scclInternalError;
        }

        // 根据节点的类型确定连接方式的类型
        switch(node->type) {
            case GPU: has_gpu = true; break;
            case PCI: has_pix = true; break;
            case NIC: has_nic = true; break;
            case CPU: has_cpu = true; break;
            default: break;
        }
    }

    // 根据路径中节点的类型确定连接方式的类型
    if(has_cpu) {
        *link_type = LINK_NET;
    } else if(has_nic) {
        *link_type = LINK_PXN;
    } else if(has_pix) {
        *link_type = LINK_PIX;
    } else if(has_gpu) {
        *link_type = LINK_NVL;
    } else {
        *link_type = LINK_NONE; // 默认返回0
    }

    return scclSuccess;
}

431
432
433
434
} // namespace graph
} // namespace topology
} // namespace hardware
} // namespace sccl