Skip to content

Commit 7348a94

Browse files
committed
tree.
1 parent 06e82ba commit 7348a94

4 files changed

Lines changed: 66 additions & 35 deletions

File tree

src/collective/allreduce.h

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2023-2024, XGBoost Contributors
2+
* Copyright 2023-2026, XGBoost Contributors
33
*/
44
#pragma once
55
#include <cstdint> // for int8_t
@@ -147,25 +147,24 @@ AllreduceV(Comm const& comm, std::vector<T>* data, Fn redop) {
147147
};
148148
};
149149

150-
auto shifted_rank = rank;
151150
std::vector<T> incoming;
152151
std::vector<T> out;
153152
bool continue_reduce = true;
154-
for (std::int32_t step = 1; step < world; step <<= 1) {
153+
for (std::int32_t level = 0; (std::int32_t{1} << level) < world; ++level) {
155154
if (!continue_reduce) {
156155
continue;
157156
}
158-
if (shifted_rank % (step * 2) == step) {
159-
auto parent = shifted_rank - step;
157+
if (rank > 0 && binomial_tree::ParentLevel(rank) == level) {
158+
auto parent = binomial_tree::Parent(rank);
160159
auto rc = send(parent, *data);
161160
if (!rc.OK()) {
162161
return Fail("AllreduceV failed to send data to parent.", std::move(rc));
163162
}
164163
continue_reduce = false;
165164
continue;
166165
}
167-
if (shifted_rank % (step * 2) == 0 && shifted_rank + step < world) {
168-
auto child = shifted_rank + step;
166+
if (binomial_tree::HasChild(rank, level, world)) {
167+
auto child = binomial_tree::Child(rank, level);
169168
auto rc = recv(child, &incoming);
170169
if (!rc.OK()) {
171170
return Fail("AllreduceV failed to receive data from child.", std::move(rc));

src/collective/broadcast.cc

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@
33
*/
44
#include "broadcast.h"
55

6-
#include <cmath> // for ceil, log2
76
#include <cstdint> // for int32_t, int8_t
87
#include <utility> // for move
98
#include <vector> // for vector
109

11-
#include "../common/bitfield.h" // for TrailingZeroBits, RBitField32
12-
#include "comm.h" // for Comm
10+
#include "comm.h" // for Comm, binomial_tree
1311
#include "xgboost/collective/result.h" // for Result
1412
#include "xgboost/span.h" // for Span
1513

@@ -19,11 +17,9 @@ namespace {
1917
Result BroadcastTree(Comm const& comm, common::Span<std::int8_t> data) {
2018
auto rank = comm.Rank();
2119
auto world = comm.World();
22-
std::int32_t depth = std::ceil(std::log2(static_cast<double>(world))) - 1;
2320

2421
if (rank != 0) {
25-
auto k = TrailingZeroBits(static_cast<std::uint32_t>(rank));
26-
auto parent = rank - (1 << k);
22+
auto parent = binomial_tree::Parent(rank);
2723
auto rc = Success() << [&] {
2824
return comm.Chan(parent)->RecvAll(data);
2925
} << [&] {
@@ -34,9 +30,9 @@ Result BroadcastTree(Comm const& comm, common::Span<std::int8_t> data) {
3430
}
3531
}
3632

37-
for (std::int32_t i = depth; i >= 0; --i) {
38-
if (rank % (1 << (i + 1)) == 0 && rank + (1 << i) < world) {
39-
auto child = rank + (1 << i);
33+
for (std::int32_t level = binomial_tree::Depth(world); level >= 0; --level) {
34+
if (binomial_tree::HasChild(rank, level, world)) {
35+
auto child = binomial_tree::Child(rank, level);
4036
auto rc = comm.Chan(child)->SendAll(data);
4137
if (!rc.OK()) {
4238
return rc;
@@ -48,27 +44,25 @@ Result BroadcastTree(Comm const& comm, common::Span<std::int8_t> data) {
4844
}
4945

5046
// Compute the path from `src` to rank 0 through the binomial tree (excluding 0).
51-
std::vector<std::int32_t> TreePathToRoot(std::int32_t src) {
47+
std::vector<std::int32_t> TreePathToRoot(std::int32_t node) {
5248
std::vector<std::int32_t> path;
53-
auto cursor = src;
49+
auto cursor = node;
5450
while (cursor > 0) {
5551
path.push_back(cursor);
56-
auto k = TrailingZeroBits(static_cast<std::uint32_t>(cursor));
57-
cursor -= (1 << k);
52+
cursor = binomial_tree::Parent(cursor);
5853
}
5954
return path;
6055
}
6156

62-
// Relay data from `root` up to rank 0 through the binomial tree.
63-
// Only nodes on the path from root to 0 participate; all others skip.
64-
Result RelayToRoot(Comm const& comm, common::Span<std::int8_t> data, std::int32_t root) {
57+
// Relay data from `node` up to rank 0 through the binomial tree.
58+
// Only nodes on the path from `node` to 0 participate; all others skip.
59+
Result RelayToRoot(Comm const& comm, common::Span<std::int8_t> data, std::int32_t node) {
6560
auto rank = comm.Rank();
66-
auto path = TreePathToRoot(root);
61+
auto path = TreePathToRoot(node);
6762

6863
for (auto node : path) {
6964
CHECK_GT(node, 0);
70-
auto k = TrailingZeroBits(static_cast<std::uint32_t>(node));
71-
auto parent = node - (std::int32_t{1} << k);
65+
auto parent = binomial_tree::Parent(node);
7266

7367
if (rank == node) {
7468
auto rc = Success() << [&] {

src/collective/comm.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,9 @@ Result ConnectTrackerImpl(proto::PeerInfo info, std::chrono::seconds timeout, st
217217
workers[prev_rank] = std::move(prev);
218218
}
219219

220+
// For each peer pair, the lower-ranked worker initiates (connect) and the higher-ranked
221+
// worker accepts. Ring sockets are already in place, so only tree-only peers need new
222+
// connections.
220223
for (auto r : my_peers) {
221224
if (r > comm.Rank() && !workers[r]) {
222225
rc = ConnectPeer(peers[r], comm.Rank(), timeout, retry, &workers[r]);
@@ -226,6 +229,9 @@ Result ConnectTrackerImpl(proto::PeerInfo info, std::chrono::seconds timeout, st
226229
}
227230
}
228231

232+
// Accept connections from lower-ranked tree peers that weren't already covered by the
233+
// ring. The exact arrival order is unspecified, so we accept n_accept times and use the
234+
// rank sent by each peer to place the socket in the right slot.
229235
std::int32_t n_accept = 0;
230236
for (auto r : my_peers) {
231237
if (r < comm.Rank() && !workers[r]) {

src/collective/comm.h

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ namespace xgboost::collective {
2525
inline constexpr std::int64_t DefaultTimeoutSec() { return 60 * 30; } // 30min
2626
inline constexpr std::int32_t DefaultRetry() { return 3; }
2727

28-
// indexing into the ring
28+
// Indexing into the ring
2929
inline std::int32_t BootstrapNext(std::int32_t r, std::int32_t world) {
3030
auto nrank = (r + world + 1) % world;
3131
return nrank;
@@ -36,6 +36,41 @@ inline std::int32_t BootstrapPrev(std::int32_t r, std::int32_t world) {
3636
return nrank;
3737
}
3838

39+
/**
40+
* @brief Helpers for the binomial tree rooted at rank 0.
41+
*
42+
* References
43+
* - https://people.mpi-inf.mpg.de/~mehlhorn/ftp/NewToolbox/collective.pdf
44+
* - https://en.wikipedia.org/wiki/Broadcast_(parallel_pattern)
45+
*/
46+
namespace binomial_tree {
47+
inline std::int32_t ParentLevel(std::int32_t rank) {
48+
CHECK_GT(rank, 0);
49+
return static_cast<std::int32_t>(TrailingZeroBits(static_cast<std::uint32_t>(rank)));
50+
}
51+
52+
inline std::int32_t Parent(std::int32_t rank) {
53+
return rank - (std::int32_t{1} << ParentLevel(rank));
54+
}
55+
56+
inline std::int32_t Child(std::int32_t rank, std::int32_t level) {
57+
return rank + (std::int32_t{1} << level);
58+
}
59+
60+
inline bool HasChild(std::int32_t rank, std::int32_t level, std::int32_t world) {
61+
return rank % (std::int32_t{1} << (level + 1)) == 0 && Child(rank, level) < world;
62+
}
63+
64+
inline std::int32_t Depth(std::int32_t world) {
65+
if (world <= 1) return -1;
66+
std::int32_t depth = 0;
67+
while ((std::int32_t{1} << (depth + 1)) < world) {
68+
++depth;
69+
}
70+
return depth;
71+
}
72+
} // namespace binomial_tree
73+
3974
/**
4075
* @brief Compute the sparse peer set for a given rank: ring neighbors union binomial tree
4176
* neighbors (rooted at rank 0).
@@ -49,17 +84,14 @@ inline std::vector<std::int32_t> SparsePeers(std::int32_t rank, std::int32_t wor
4984
peers.insert(BootstrapNext(rank, world));
5085
peers.insert(BootstrapPrev(rank, world));
5186

87+
// Connect tree parents and children
5288
if (rank > 0) {
53-
auto k = TrailingZeroBits(static_cast<std::uint32_t>(rank));
54-
peers.insert(rank - (1 << k));
89+
peers.insert(binomial_tree::Parent(rank));
5590
}
5691

57-
for (std::int32_t k = 0; (1 << k) < world; ++k) {
58-
if (rank % (1 << (k + 1)) == 0) {
59-
auto child = rank + (1 << k);
60-
if (child < world) {
61-
peers.insert(child);
62-
}
92+
for (std::int32_t level = 0; (std::int32_t{1} << level) < world; ++level) {
93+
if (binomial_tree::HasChild(rank, level, world)) {
94+
peers.insert(binomial_tree::Child(rank, level));
6395
}
6496
}
6597

0 commit comments

Comments
 (0)