Skip to content

Commit 1839a86

Browse files
ezhulenevtensorflower-gardener
authored andcommitted
PR tensorflow#36901: [xla:gpu] Add support for NCCL scalable initialization
Imported from GitHub PR openxla/xla#36901 Remove `root_device` from `GpuCliqueKey`: root device is not a property of the gpu clique key, but has meaning only during clique initialization. 1. Change CliqueId callback type to return `CliqueIds` 2. Update NCCL Id store to set and get multiple clique ids for all root devices 3. Use NCCL scalable initialization API when XLA generates more that 1 clique id 4. Improve logging and add timing to critical places that tend to deadlock or get stuck 5. Improve logging by adding HumanReadable Devices and Processes APIs for printing large number of devices and processes Copybara import of the project: -- 7c239c26b18d3a4c86f83dd9ea8b5b2b5d7ccbee by Eugene Zhulenev <ezhulenev@openxla.org>: [xla:gpu] Add support for NCCL scalable initialization Merging this change closes tensorflow#36901 PiperOrigin-RevId: 862348585
1 parent c346393 commit 1839a86

18 files changed

Lines changed: 270 additions & 147 deletions

third_party/xla/xla/backends/gpu/collectives/BUILD

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,6 @@ cc_library(
397397
"//xla/pjrt/distributed:key_value_store_interface",
398398
"//xla/runtime:device_id",
399399
"//xla/runtime:process_id",
400-
"//xla/service/gpu:gpu_executable_run_options",
401400
"//xla/stream_executor:stream_executor_h",
402401
"//xla/stream_executor/cuda:nccl_memory_allocator", # buildcleaner: keep (static registration)
403402
"//xla/tsl/cuda:nccl",
@@ -408,6 +407,7 @@ cc_library(
408407
"@com_google_absl//absl/algorithm:container",
409408
"@com_google_absl//absl/base",
410409
"@com_google_absl//absl/base:core_headers",
410+
"@com_google_absl//absl/container:btree",
411411
"@com_google_absl//absl/container:flat_hash_map",
412412
"@com_google_absl//absl/status",
413413
"@com_google_absl//absl/status:statusor",

third_party/xla/xla/backends/gpu/collectives/gpu_clique_key.cc

Lines changed: 25 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,11 @@ CollectiveStreamId GetCollectiveStreamId(bool is_async,
6464
GpuCliqueKey::GpuCliqueKey(
6565
std::vector<GlobalDeviceId> devices, int64_t num_local_participants,
6666
bool is_p2p, std::vector<std::vector<GlobalDeviceId>> participant_groups,
67-
GlobalDeviceId root_device, std::vector<IncarnationId> incarnations)
67+
std::vector<IncarnationId> incarnations)
6868
: CliqueKey(std::move(devices)),
6969
num_local_participants_(num_local_participants),
7070
is_p2p_(is_p2p),
7171
participant_groups_(std::move(participant_groups)),
72-
root_device_(root_device),
7372
incarnations_(std::move(incarnations)) {
7473
for (std::vector<GlobalDeviceId>& group : participant_groups_) {
7574
absl::c_sort(group);
@@ -86,8 +85,6 @@ GpuCliqueKey::GpuCliqueKey(
8685

8786
bool GpuCliqueKey::is_p2p() const { return is_p2p_; }
8887

89-
GlobalDeviceId GpuCliqueKey::root_device() const { return root_device_; }
90-
9188
std::vector<std::vector<GlobalDeviceId>> GpuCliqueKey::ParticipantGroups()
9289
const {
9390
return participant_groups_;
@@ -105,35 +102,27 @@ bool GpuCliqueKey::IsSubsetOf(const CliqueKey& other) const {
105102
});
106103
}
107104

108-
std::vector<GpuCliqueKey> GpuCliqueKey::GetSubKeys(int64_t nroots) const {
109-
const auto& devs = devices();
110-
int64_t nranks = devs.size();
111-
CHECK(nroots <= nranks);
112-
int64_t rank_per_root = nranks / nroots;
113-
int64_t rank_rem = nranks % nroots;
114-
std::vector<GpuCliqueKey> subkeys;
105+
std::vector<GlobalDeviceId> GpuCliqueKey::GetRootDevices(int64_t nroots) const {
106+
int64_t nranks = devices().size();
107+
CHECK_LE(nroots, nranks) << "Can't select more root devices than available";
108+
109+
// If nroots evenly divides nranks, then every root is assigned to
110+
// ranks_per_root devices. If nroots doesn't divide nranks, then we increase
111+
// the size of the first ranks_rem roots by 1.
112+
int64_t ranks_per_root = nranks / nroots;
113+
int64_t ranks_rem = nranks % nroots;
114+
115+
std::vector<GlobalDeviceId> roots;
116+
roots.reserve(nroots);
115117
for (int64_t i = 0; i < nroots; ++i) {
116-
GpuCliqueKey subkey(*this);
117-
if (i < rank_rem) {
118-
subkey.root_device_ = devs[i * (rank_per_root + 1)];
118+
if (i < ranks_rem) {
119+
roots.push_back(devices()[i * (ranks_per_root + 1)]);
119120
} else {
120-
subkey.root_device_ =
121-
devs[rank_rem * (rank_per_root + 1) + (i - rank_rem) * rank_per_root];
121+
roots.push_back(devices()[ranks_rem * (ranks_per_root + 1) +
122+
(i - ranks_rem) * ranks_per_root]);
122123
}
123-
subkeys.push_back(subkey);
124-
}
125-
return subkeys;
126-
}
127-
128-
// StrJoin for devices that shortens long list of devices for readbility.
129-
static std::string StrJoinDevices(absl::Span<const GlobalDeviceId> devices,
130-
absl::string_view separator = ",",
131-
size_t first = 10, size_t last = 4) {
132-
if (devices.size() > first + last) {
133-
return absl::StrCat(absl::StrJoin(devices.first(first), separator), "...",
134-
absl::StrJoin(devices.last(last), separator));
135124
}
136-
return absl::StrJoin(devices, separator);
125+
return roots;
137126
}
138127

139128
std::string GpuCliqueKey::ToString() const {
@@ -142,15 +131,15 @@ std::string GpuCliqueKey::ToString() const {
142131
std::vector<std::string> values;
143132
values.reserve(participant_groups_.size());
144133
for (const auto& group : participant_groups_) {
145-
values.push_back(absl::StrFormat("[%s]", StrJoinDevices(group)));
134+
values.push_back(absl::StrFormat("[%s]", HumanReadableDevices(group)));
146135
}
147136
group_string = absl::StrFormat("; groups=[%s]", absl::StrJoin(values, ","));
148137
}
149138
return absl::StrFormat(
150-
"devices=%d:[%s]; is_p2p=%v%s; root=%lld; local_participants=%lld; "
139+
"devices=%d:[%s]; is_p2p=%v%s; local_participants=%lld; "
151140
"incarnations=[%s]",
152-
devices().size(), StrJoinDevices(devices()), is_p2p_, group_string,
153-
root_device_.value(), num_local_participants_,
141+
devices().size(), HumanReadableDevices(devices()), is_p2p_, group_string,
142+
num_local_participants_,
154143
absl::StrJoin(incarnations_, ", ",
155144
[](std::string* out, IncarnationId id) {
156145
absl::StrAppend(out, id.value());
@@ -159,19 +148,19 @@ std::string GpuCliqueKey::ToString() const {
159148

160149
void GpuCliqueKey::HashValue(absl::HashState state) const {
161150
absl::HashState::combine(std::move(state), devices(), participant_groups_,
162-
root_device_, incarnations_);
151+
incarnations_);
163152
}
164153

165154
bool operator==(const GpuCliqueKey& a, const GpuCliqueKey& b) {
166155
return a.devices() == b.devices() &&
167156
a.participant_groups_ == b.participant_groups_ &&
168157
a.num_local_participants_ == b.num_local_participants_ &&
169-
a.root_device_ == b.root_device_ && a.incarnations_ == b.incarnations_;
158+
a.incarnations_ == b.incarnations_;
170159
}
171160

172161
// Constructs a tuple from the clique key for comparison purposes.
173162
static auto CmpKey(const GpuCliqueKey& key) {
174-
return std::make_tuple(key.devices().size(), key.devices(), key.root_device(),
163+
return std::make_tuple(key.devices().size(), key.devices(),
175164
key.num_local_participants(), key.incarnations());
176165
}
177166

third_party/xla/xla/backends/gpu/collectives/gpu_clique_key.h

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ class GpuCliqueKey : public CliqueKey {
5151
std::vector<GlobalDeviceId> devices, int64_t num_local_participants,
5252
bool is_p2p = false,
5353
std::vector<std::vector<GlobalDeviceId>> participant_groups = {},
54-
GlobalDeviceId root_device = GlobalDeviceId(-1),
5554
std::vector<IncarnationId> incarnations = {});
5655

5756
GpuCliqueKey(const GpuCliqueKey&) = default;
@@ -64,21 +63,19 @@ class GpuCliqueKey : public CliqueKey {
6463

6564
std::vector<std::vector<GlobalDeviceId>> ParticipantGroups() const;
6665

67-
// Device generating the unique id for this key
68-
GlobalDeviceId root_device() const;
69-
7066
// Returns true if this clique is a subset of `other`: both cliques have the
7167
// same `stream_id` and all clique devices are part of `other` clique.
7268
bool IsSubsetOf(const CliqueKey& other) const final;
7369

7470
// Returns true if this clique will be used with p2p communicators.
7571
bool is_p2p() const;
7672

77-
// For multi-root initialization, generate `nroots` copies (subkeys) of the
78-
// key each with a different root device. Root devices are distributed evenly
79-
// across the ranks. The subkeys are used to exchange the CliqueIds during
80-
// clique initialization.
81-
std::vector<GpuCliqueKey> GetSubKeys(int64_t nroots) const;
73+
// Returns root devices that are responsible for bootstrapping the GPU clique
74+
// during initialization. Root devices are distributed evenly across all ranks
75+
// in the clique. XLA processes owning the root devices are responsible for
76+
// generating clique id and exchanging it with other ranks that share the same
77+
// root device (this is done via shared KV store).
78+
std::vector<GlobalDeviceId> GetRootDevices(int64_t nroots) const;
8279

8380
// The number of participant devices that are local to the current process (in
8481
// multi-host environments this likely to be all devices on the same host).
@@ -124,8 +121,6 @@ class GpuCliqueKey : public CliqueKey {
124121
// situations
125122
std::vector<std::vector<GlobalDeviceId>> participant_groups_;
126123

127-
GlobalDeviceId root_device_;
128-
129124
std::vector<IncarnationId> incarnations_;
130125
};
131126

third_party/xla/xla/backends/gpu/collectives/gpu_clique_key_test.cc

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ static GpuCliqueKey GetBaseCliqueKey() {
4040
/*participant_groups=*/
4141
std::vector<std::vector<GlobalDeviceId>>{
4242
{GlobalDeviceId(0), GlobalDeviceId(1)},
43-
{GlobalDeviceId(2), GlobalDeviceId(3)}},
44-
/*root_device=*/GlobalDeviceId(0));
43+
{GlobalDeviceId(2), GlobalDeviceId(3)}});
4544
}
4645

4746
TEST(GpuCliqueKeyTest, IsSubsetOf) {
@@ -60,29 +59,36 @@ TEST(GpuCliqueKeyTest, IsSubsetOf) {
6059
EXPECT_FALSE(key0.IsSubsetOf(key3));
6160
}
6261

63-
TEST(GpuCliqueKeyTest, GetSubKeys) {
62+
TEST(GpuCliqueKeyTest, GetRootDevices) {
6463
GlobalDeviceId id0 = GlobalDeviceId(0);
6564
GlobalDeviceId id1 = GlobalDeviceId(1);
6665
GlobalDeviceId id2 = GlobalDeviceId(2);
6766
GlobalDeviceId id3 = GlobalDeviceId(3);
6867

69-
GpuCliqueKey key({id0, id1, id2, id3}, /*num_local_participants=*/1, true);
70-
std::array<int64_t, 4> nroots{1, 2, 3, 4};
71-
std::vector<std::vector<GlobalDeviceId>> exp_root_devs{
72-
{id0}, {id0, id2}, {id0, id2, id3}, {id0, id1, id2, id3}};
73-
for (int ridx = 0; ridx < nroots.size(); ++ridx) {
74-
int64_t n = nroots[ridx];
75-
const auto& subkeys = key.GetSubKeys(n);
76-
EXPECT_EQ(subkeys.size(), exp_root_devs[ridx].size());
77-
for (int kidx = 0; kidx < subkeys.size(); ++kidx) {
78-
GpuCliqueKey exp_subkey(
79-
/*devices=*/{id0, id1, id2, id3},
80-
/*num_local_participants=*/1,
81-
/*is_p2p=*/true,
82-
/*participant_groups=*/{},
83-
/*root_device=*/exp_root_devs[ridx][kidx]);
84-
EXPECT_EQ(subkeys[kidx], exp_subkey);
85-
}
68+
GpuCliqueKey key({id0, id1, id2, id3}, /*num_local_participants=*/1);
69+
70+
{
71+
std::vector<GlobalDeviceId> roots = key.GetRootDevices(1);
72+
std::vector<GlobalDeviceId> expected = {id0};
73+
ASSERT_EQ(roots, expected);
74+
}
75+
76+
{
77+
std::vector<GlobalDeviceId> roots = key.GetRootDevices(2);
78+
std::vector<GlobalDeviceId> expected = {id0, id2};
79+
ASSERT_EQ(roots, expected);
80+
}
81+
82+
{
83+
std::vector<GlobalDeviceId> roots = key.GetRootDevices(3);
84+
std::vector<GlobalDeviceId> expected = {id0, id2, id3};
85+
ASSERT_EQ(roots, expected);
86+
}
87+
88+
{
89+
std::vector<GlobalDeviceId> roots = key.GetRootDevices(4);
90+
std::vector<GlobalDeviceId> expected = {id0, id1, id2, id3};
91+
ASSERT_EQ(roots, expected);
8692
}
8793
}
8894

@@ -179,7 +185,7 @@ TEST(GpuCliqueKeyGettersTest, IsP2P) {
179185

180186
TEST(GpuCliqueKeyGetterTest, ToString) {
181187
EXPECT_EQ(GetBaseCliqueKey().ToString(),
182-
"devices=2:[0,1]; is_p2p=false; groups=[[0,1],[2,3]]; root=0; "
188+
"devices=2:[0,1]; is_p2p=false; groups=[[0,1],[2,3]]; "
183189
"local_participants=2; incarnations=[]");
184190
}
185191

@@ -194,7 +200,7 @@ TEST(GpuCliqueKeyGetterTest, ToStringManyDevices) {
194200

195201
EXPECT_EQ(key.ToString(),
196202
"devices=100:[0,1,2,3,4,5,6,7,8,9...96,97,98,99]; is_p2p=false; "
197-
"root=-1; local_participants=100; incarnations=[]");
203+
"local_participants=100; incarnations=[]");
198204
}
199205

200206
TEST(GpuCliqueIdGettersTest, Data) {

third_party/xla/xla/backends/gpu/collectives/gpu_cliques.cc

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ limitations under the License.
2525
#include <vector>
2626

2727
#include "absl/algorithm/container.h"
28-
#include "absl/base/const_init.h"
2928
#include "absl/base/no_destructor.h"
3029
#include "absl/base/thread_annotations.h"
3130
#include "absl/container/btree_map.h"
@@ -322,30 +321,13 @@ InitializeGpuClique(GpuCollectives* collectives, se::StreamExecutor* device,
322321
bool synchronized;
323322
};
324323

325-
// Check how many roots are needed to initialize the GpuClique
326-
static const int64_t nccl_init_rank_per_root_ratio =
327-
xla::GetDebugOptionsFromFlags()
328-
.xla_gpu_nccl_init_max_rank_per_root_ratio();
329-
int64_t nranks = clique_key.num_devices();
330-
int64_t nroots = nccl_init_rank_per_root_ratio != 0
331-
? CeilOfRatio(nranks, nccl_init_rank_per_root_ratio)
332-
: 1;
333-
334324
// Initializes a GpuClique for given device ranks and returns a lock that
335325
// gives access to clique communicators.
336326
auto initialize = [&](absl::Span<const RendezvousArg* const> args)
337327
-> absl::StatusOr<LockableGpuClique::Lock> {
338328
tsl::profiler::TraceMe trace("InitializeGpuClique");
339329

340-
CliqueIds clique_ids;
341-
std::vector<GpuCliqueKey> subkeys = clique_key.GetSubKeys(nroots);
342-
for (const auto& subkey : subkeys) {
343-
VLOG(5) << absl::StreamFormat(
344-
"Get CliqueId for sub clique key %s; nroots=%lld", subkey.ToString(),
345-
nroots);
346-
TF_ASSIGN_OR_RETURN(auto clique_id, clique_id_callback(subkey));
347-
clique_ids.Add(clique_id);
348-
}
330+
TF_ASSIGN_OR_RETURN(CliqueIds clique_ids, clique_id_callback(clique_key));
349331

350332
// Check that all ranks successfully synchronized device activity before
351333
// trying to instantiate GPU communicators.
@@ -375,7 +357,7 @@ InitializeGpuClique(GpuCollectives* collectives, se::StreamExecutor* device,
375357
"[%s] [ranks=%s] Create GPU communicators for clique %v; "
376358
"nroots=%lld; fingerprint(id)=%d, peer_access_enabled=%d",
377359
DeviceOrdinalsToString(ranks), DeviceRanksToString(ranks), clique_key,
378-
nroots, clique_ids.fingerprint(), peer_access_enabled);
360+
clique_ids.size(), clique_ids.fingerprint(), peer_access_enabled);
379361

380362
ProcessGpuCliques& state = GetProcessGpuCliques();
381363

@@ -426,7 +408,7 @@ InitializeGpuClique(GpuCollectives* collectives, se::StreamExecutor* device,
426408
"[%s] [ranks=%s] Created GPU communicators for clique %v; "
427409
"nroots=%lld; fingerprint(id)=%d, peer_access_enabled=%d",
428410
DeviceOrdinalsToString(ranks), DeviceRanksToString(ranks), clique_key,
429-
nroots, clique_ids.fingerprint(), peer_access_enabled);
411+
clique_ids.size(), clique_ids.fingerprint(), peer_access_enabled);
430412

431413
// Put constructed clique into the per-process state.
432414
absl::MutexLock lock(state.mu);

third_party/xla/xla/backends/gpu/collectives/gpu_collectives.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ class GpuCollectives : public Collectives {
5050
// Returns the default collectives implementation for the given platform.
5151
static GpuCollectives* Default(absl::string_view platform_name);
5252

53-
// A callback to get a unique clique id.
53+
// A callback to get a unique clique ids.
5454
using CliqueIdCallback = // NOLINT
55-
std::function<absl::StatusOr<CliqueId>(const CliqueKey&)>;
55+
std::function<absl::StatusOr<CliqueIds>(const CliqueKey&)>;
5656

5757
// Topology describes how exactly the current process fits into the collection
5858
// of distributed processes, and based on this information the underlying

third_party/xla/xla/backends/gpu/collectives/gpu_collectives_test.cc

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ limitations under the License.
1515

1616
#include "xla/backends/gpu/collectives/gpu_collectives.h"
1717

18+
#include <cstddef>
1819
#include <memory>
1920
#include <utility>
2021
#include <vector>
@@ -55,14 +56,19 @@ namespace {
5556
// Creates a pair of communicators for the given executors.
5657
static absl::StatusOr<std::vector<std::unique_ptr<GpuCommunicator>>>
5758
CreateCommunicators(se::StreamExecutor* executor0,
58-
se::StreamExecutor* executor1, bool blocking = true) {
59+
se::StreamExecutor* executor1, bool blocking = true,
60+
size_t num_ids = 1) {
5961
GpuCollectives::Device device0(executor0);
6062
GpuCollectives::Device device1(executor1);
6163

6264
GpuCollectives* collectives = GpuCollectives::Default("GPU");
6365

64-
TF_ASSIGN_OR_RETURN(CliqueId clique_id, collectives->CreateUniqueCliqueId());
65-
CliqueIds clique_ids(clique_id);
66+
CliqueIds clique_ids;
67+
for (size_t i = 0; i < num_ids; ++i) {
68+
TF_ASSIGN_OR_RETURN(CliqueId clique_id,
69+
collectives->CreateUniqueCliqueId());
70+
clique_ids.Add(clique_id);
71+
}
6672

6773
GpuCliqueKey clique_key({GlobalDeviceId(0), GlobalDeviceId(1)},
6874
/*num_local_participants=*/2);
@@ -86,6 +92,27 @@ CreateCommunicators(se::StreamExecutor* executor0,
8692
return gpu_comms;
8793
}
8894

95+
TEST(GpuCollectivesTest, CreateWithMultipleIds) {
96+
ASSERT_OK_AND_ASSIGN(se::Platform * platform,
97+
se::PlatformManager::PlatformWithName("CUDA"));
98+
99+
if (platform->VisibleDeviceCount() < 2) {
100+
GTEST_SKIP() << "Test requires at least 2 GPUs";
101+
}
102+
103+
ASSERT_OK_AND_ASSIGN(se::StreamExecutor * executor0,
104+
platform->ExecutorForDevice(0));
105+
ASSERT_OK_AND_ASSIGN(se::StreamExecutor * executor1,
106+
platform->ExecutorForDevice(1));
107+
108+
ASSERT_OK_AND_ASSIGN(
109+
auto comms, CreateCommunicators(executor0, executor1, /*blocking=*/true,
110+
/*num_ids=*/2));
111+
112+
EXPECT_TRUE(comms[0]->platform_comm().handle);
113+
EXPECT_TRUE(comms[1]->platform_comm().handle);
114+
}
115+
89116
TEST(GpuCollectivesTest, CreateSymmetricMemory) {
90117
ASSERT_OK_AND_ASSIGN(se::Platform * platform,
91118
se::PlatformManager::PlatformWithName("CUDA"));

0 commit comments

Comments
 (0)