Skip to content

Commit a1fe343

Browse files
srinathb-metameta-codesync[bot]
authored andcommitted
Serve zero-chunk relay geometries with a direct fallback instead of failing (meta-pytorch#3678)
Summary: Pull Request resolved: meta-pytorch#3678 X-link: meta-pytorch/torchrec#4570 When a group's aligned relay chunk rounded down to zero the four sharded relay collectives returned `ncclInvalidArgument` and required the caller to retry on a plain collective. In a fused multi-group call that is worse than it sounds: a single small or awkwardly sized group forced the whole fused call to fail even when the other groups were large enough to relay, so heterogeneous group sizes were effectively unsupported. Give every collective a phase-symmetric per-group direct fallback instead. When `chunkSize` aligns down to zero the segment is covered by the two direct regions (`dirA` takes half, `dirB` absorbs the remainder) over the active-to-active link, the helper scatter/forward is skipped for that group, and the helper-side reduction is skipped as well. Groups that do have a workable chunk size keep the existing relay schedule unchanged, so each group in a fused call now picks its own schedule independently. - track `dirASizes` per group so the direct regions are sized from the selected schedule rather than assumed equal to `chunkSize` - guard the helper scatter, forward, and reduction on `chunkSize > 0` - all-gather additionally tracks `dirAOffsets`, which is no longer derivable from `relayTotals` once the fallback can move the direct region to offset 0 - correct the minimum helper geometry to H+2 chunks Also fixes a torchrec test that asserted the A>2 reduce-scatter helper buffer was `_passthrough_helper_size(...)` when production has always sized it through `_relay_helper_size()` (`2 * recvCount`); the expectation now matches the unchanged production contract. Behavior for buffers that already had a nonzero aligned chunk is unchanged. Differential Revision: D115998367
1 parent 3b3ad05 commit a1fe343

8 files changed

Lines changed: 1179 additions & 106 deletions

comms/rcclx/develop/meta/relay/sharded_relay_all_gather.cc

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,8 @@ static ncclResult_t shardedRelayAllGather2Active(
184184
const int numChunks = numHelpers + 2;
185185

186186
size_t chunkSizes[SHARDED_RELAY_MAX_GROUPS];
187-
size_t relayTotals[SHARDED_RELAY_MAX_GROUPS]; // == direct chunk A's offset
187+
size_t dirAOffsets[SHARDED_RELAY_MAX_GROUPS];
188+
size_t dirASizes[SHARDED_RELAY_MAX_GROUPS];
188189
size_t dirBOffsets[SHARDED_RELAY_MAX_GROUPS];
189190
size_t dirBSizes[SHARDED_RELAY_MAX_GROUPS]; // absorbs the remainder
190191

@@ -194,22 +195,27 @@ static ncclResult_t shardedRelayAllGather2Active(
194195
// Zero-count groups are skipped by every loop below.
195196
if (count == 0) {
196197
chunkSizes[g] = 0;
197-
relayTotals[g] = 0;
198+
dirAOffsets[g] = 0;
199+
dirASizes[g] = 0;
198200
dirBOffsets[g] = 0;
199201
dirBSizes[g] = 0;
200202
continue;
201203
}
202204

203-
// A chunk size that rounds down to zero means the send buffer is too small
204-
// to scatter; the caller should fall back to a regular all-gather.
205205
size_t chunkSize = count / numChunks;
206206
chunkSize = (chunkSize / CHUNK_ALIGN_ELEMENTS) * CHUNK_ALIGN_ELEMENTS;
207+
chunkSizes[g] = chunkSize;
207208
if (chunkSize == 0) {
208-
return ncclInvalidArgument;
209+
dirAOffsets[g] = 0;
210+
dirASizes[g] = count / 2;
211+
dirBOffsets[g] = dirASizes[g];
212+
dirBSizes[g] = count - dirASizes[g];
213+
continue;
209214
}
210-
chunkSizes[g] = chunkSize;
211-
relayTotals[g] = static_cast<size_t>(numHelpers) * chunkSize;
212-
dirBOffsets[g] = relayTotals[g] + chunkSize;
215+
216+
dirAOffsets[g] = static_cast<size_t>(numHelpers) * chunkSize;
217+
dirASizes[g] = chunkSize;
218+
dirBOffsets[g] = dirAOffsets[g] + dirASizes[g];
213219
dirBSizes[g] = count - dirBOffsets[g];
214220
}
215221

@@ -265,7 +271,7 @@ static ncclResult_t shardedRelayAllGather2Active(
265271
char* recvbuff = static_cast<char*>(recvBuffs[g]);
266272

267273
// Scatter: chunk h of my sendBuff goes to helper h.
268-
for (int h = 0; h < cfg.numHelpers; h++) {
274+
for (int h = 0; h < cfg.numHelpers && chunkSize > 0; h++) {
269275
NCCLCHECK(ncclSend(
270276
sendbuff + static_cast<size_t>(h) * chunkSize * elementSize,
271277
chunkSize,
@@ -279,20 +285,20 @@ static ncclResult_t shardedRelayAllGather2Active(
279285
// gather slot never overlaps the send source, so this is safe in-place.
280286
int partner = cfg.activeRanks[1 - cfg.myActiveIndex];
281287
NCCLCHECK(ncclSend(
282-
sendbuff + relayTotals[g] * elementSize,
283-
chunkSize,
288+
sendbuff + dirAOffsets[g] * elementSize,
289+
dirASizes[g],
284290
datatype,
285291
partner,
286292
comm,
287293
stream));
288294
NCCLCHECK(ncclRecv(
289-
recvbuff + (gatherSlotOffset + relayTotals[g]) * elementSize,
290-
chunkSize,
295+
recvbuff + (gatherSlotOffset + dirAOffsets[g]) * elementSize,
296+
dirASizes[g],
291297
datatype,
292298
partner,
293299
comm,
294300
stream));
295-
} else {
301+
} else if (chunkSize > 0) {
296302
// Helper: receive active rank a's chunk into slot a.
297303
char* helperBuf = static_cast<char*>(recvBuffs[g]);
298304
for (int a = 0; a < cfg.nActiveRanks; a++) {
@@ -326,7 +332,7 @@ static ncclResult_t shardedRelayAllGather2Active(
326332
if (cfg.isActiveRank) {
327333
char* recvbuff = static_cast<char*>(recvBuffs[g]);
328334

329-
for (int h = 0; h < cfg.numHelpers; h++) {
335+
for (int h = 0; h < cfg.numHelpers && chunkSize > 0; h++) {
330336
NCCLCHECK(ncclRecv(
331337
recvbuff +
332338
(gatherSlotOffset + static_cast<size_t>(h) * chunkSize) *
@@ -354,7 +360,7 @@ static ncclResult_t shardedRelayAllGather2Active(
354360
partner,
355361
comm,
356362
stream));
357-
} else {
363+
} else if (chunkSize > 0) {
358364
const char* helperBuf = static_cast<const char*>(recvBuffs[g]);
359365
for (int a = 0; a < cfg.nActiveRanks; a++) {
360366
NCCLCHECK(ncclSend(

comms/rcclx/develop/meta/relay/sharded_relay_all_to_all.cc

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ static ncclResult_t shardedRelayAllToAll2Active(
181181

182182
size_t chunkSizes[SHARDED_RELAY_MAX_GROUPS];
183183
size_t relayTotals[SHARDED_RELAY_MAX_GROUPS]; // == direct chunk A's offset
184+
size_t dirASizes[SHARDED_RELAY_MAX_GROUPS];
184185
size_t dirBOffsets[SHARDED_RELAY_MAX_GROUPS];
185186
size_t dirBSizes[SHARDED_RELAY_MAX_GROUPS]; // absorbs the remainder
186187

@@ -191,21 +192,23 @@ static ncclResult_t shardedRelayAllToAll2Active(
191192
if (count == 0) {
192193
chunkSizes[g] = 0;
193194
relayTotals[g] = 0;
195+
dirASizes[g] = 0;
194196
dirBOffsets[g] = 0;
195197
dirBSizes[g] = 0;
196198
continue;
197199
}
198200

199-
// A chunk size that rounds down to zero means the segment is too small to
200-
// scatter; the caller should fall back to a regular all-to-all.
201201
size_t chunkSize = count / numChunks;
202202
chunkSize = (chunkSize / CHUNK_ALIGN_ELEMENTS) * CHUNK_ALIGN_ELEMENTS;
203-
if (chunkSize == 0) {
204-
return ncclInvalidArgument;
205-
}
206203
chunkSizes[g] = chunkSize;
207204
relayTotals[g] = static_cast<size_t>(numHelpers) * chunkSize;
208-
dirBOffsets[g] = relayTotals[g] + chunkSize;
205+
if (chunkSize == 0) {
206+
dirASizes[g] = count / 2;
207+
dirBOffsets[g] = dirASizes[g];
208+
} else {
209+
dirASizes[g] = chunkSize;
210+
dirBOffsets[g] = relayTotals[g] + chunkSize;
211+
}
209212
dirBSizes[g] = count - dirBOffsets[g];
210213
}
211214

@@ -253,33 +256,35 @@ static ncclResult_t shardedRelayAllToAll2Active(
253256
static_cast<char*>(recvBuffs[g]) + exchangeSegOffset * elementSize;
254257

255258
// Scatter: chunk h of my exchange segment goes to helper h.
256-
for (int h = 0; h < cfg.numHelpers; h++) {
257-
NCCLCHECK(ncclSend(
258-
sendSeg + static_cast<size_t>(h) * chunkSize * elementSize,
259-
chunkSize,
260-
datatype,
261-
cfg.helperRanks[h],
262-
comm,
263-
stream));
259+
if (chunkSize > 0) {
260+
for (int h = 0; h < cfg.numHelpers; h++) {
261+
NCCLCHECK(ncclSend(
262+
sendSeg + static_cast<size_t>(h) * chunkSize * elementSize,
263+
chunkSize,
264+
datatype,
265+
cfg.helperRanks[h],
266+
comm,
267+
stream));
268+
}
264269
}
265270

266271
// Direct chunk A over the otherwise-idle active<->active link.
267272
int partner = cfg.activeRanks[1 - cfg.myActiveIndex];
268273
NCCLCHECK(ncclSend(
269274
sendSeg + relayTotals[g] * elementSize,
270-
chunkSize,
275+
dirASizes[g],
271276
datatype,
272277
partner,
273278
comm,
274279
stream));
275280
NCCLCHECK(ncclRecv(
276281
recvSeg + relayTotals[g] * elementSize,
277-
chunkSize,
282+
dirASizes[g],
278283
datatype,
279284
partner,
280285
comm,
281286
stream));
282-
} else {
287+
} else if (chunkSize > 0) {
283288
// Helper: receive active rank a's chunk into slot a.
284289
char* helperBuf = static_cast<char*>(recvBuffs[g]);
285290
for (int a = 0; a < cfg.nActiveRanks; a++) {
@@ -316,14 +321,16 @@ static ncclResult_t shardedRelayAllToAll2Active(
316321
char* recvSeg =
317322
static_cast<char*>(recvBuffs[g]) + exchangeSegOffset * elementSize;
318323

319-
for (int h = 0; h < cfg.numHelpers; h++) {
320-
NCCLCHECK(ncclRecv(
321-
recvSeg + static_cast<size_t>(h) * chunkSize * elementSize,
322-
chunkSize,
323-
datatype,
324-
cfg.helperRanks[h],
325-
comm,
326-
stream));
324+
if (chunkSize > 0) {
325+
for (int h = 0; h < cfg.numHelpers; h++) {
326+
NCCLCHECK(ncclRecv(
327+
recvSeg + static_cast<size_t>(h) * chunkSize * elementSize,
328+
chunkSize,
329+
datatype,
330+
cfg.helperRanks[h],
331+
comm,
332+
stream));
333+
}
327334
}
328335

329336
// Direct chunk B, again over the idle active<->active link.
@@ -342,7 +349,7 @@ static ncclResult_t shardedRelayAllToAll2Active(
342349
partner,
343350
comm,
344351
stream));
345-
} else {
352+
} else if (chunkSize > 0) {
346353
const char* helperBuf = static_cast<const char*>(recvBuffs[g]);
347354
for (int a = 0; a < cfg.nActiveRanks; a++) {
348355
NCCLCHECK(ncclSend(

comms/rcclx/develop/meta/relay/sharded_relay_allreduce.cc

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,7 @@ static ncclResult_t shardedRelayAllReduce2Active(
674674

675675
size_t chunkSizes[SHARDED_RELAY_MAX_GROUPS];
676676
size_t relayTotals[SHARDED_RELAY_MAX_GROUPS]; // == direct chunk A's offset
677+
size_t dirASizes[SHARDED_RELAY_MAX_GROUPS];
677678
size_t dirBOffsets[SHARDED_RELAY_MAX_GROUPS];
678679
size_t dirBSizes[SHARDED_RELAY_MAX_GROUPS]; // absorbs the remainder
679680

@@ -685,21 +686,23 @@ static ncclResult_t shardedRelayAllReduce2Active(
685686
if (count == 0) {
686687
chunkSizes[g] = 0;
687688
relayTotals[g] = 0;
689+
dirASizes[g] = 0;
688690
dirBOffsets[g] = 0;
689691
dirBSizes[g] = 0;
690692
continue;
691693
}
692694

693-
// A chunk size that rounds down to zero means the buffer is too small to
694-
// scatter; the caller should fall back to a regular allreduce.
695695
size_t chunkSize = count / numChunks;
696696
chunkSize = (chunkSize / CHUNK_ALIGN_ELEMENTS) * CHUNK_ALIGN_ELEMENTS;
697-
if (chunkSize == 0) {
698-
return ncclInvalidArgument;
699-
}
700697
chunkSizes[g] = chunkSize;
701698
relayTotals[g] = static_cast<size_t>(numHelpers) * chunkSize;
702-
dirBOffsets[g] = relayTotals[g] + chunkSize;
699+
if (chunkSize == 0) {
700+
dirASizes[g] = count / 2;
701+
dirBOffsets[g] = dirASizes[g];
702+
} else {
703+
dirASizes[g] = chunkSize;
704+
dirBOffsets[g] = relayTotals[g] + chunkSize;
705+
}
703706
dirBSizes[g] = count - dirBOffsets[g];
704707
}
705708

@@ -753,7 +756,7 @@ static ncclResult_t shardedRelayAllReduce2Active(
753756
const char* sendbuff = static_cast<const char*>(sendBuffs[g]);
754757

755758
// Scatter: chunk h goes to helper h.
756-
for (int h = 0; h < cfg.numHelpers; h++) {
759+
for (int h = 0; h < cfg.numHelpers && chunkSize > 0; h++) {
757760
NCCLCHECK(ncclSend(
758761
sendbuff + static_cast<size_t>(h) * chunkSize * elementSize,
759762
chunkSize,
@@ -765,16 +768,18 @@ static ncclResult_t shardedRelayAllReduce2Active(
765768

766769
// Direct chunk A over the otherwise-idle active<->active link.
767770
int partner = cfg.activeRanks[1 - cfg.myActiveIndex];
768-
NCCLCHECK(ncclSend(
769-
sendbuff + relayTotals[g] * elementSize,
770-
chunkSize,
771-
datatype,
772-
partner,
773-
comm,
774-
stream));
775-
NCCLCHECK(
776-
ncclRecv(directDst(0), chunkSize, datatype, partner, comm, stream));
777-
} else {
771+
if (dirASizes[g] > 0) {
772+
NCCLCHECK(ncclSend(
773+
sendbuff + relayTotals[g] * elementSize,
774+
dirASizes[g],
775+
datatype,
776+
partner,
777+
comm,
778+
stream));
779+
NCCLCHECK(ncclRecv(
780+
directDst(0), dirASizes[g], datatype, partner, comm, stream));
781+
}
782+
} else if (chunkSize > 0) {
778783
// Helper: receive active rank a's chunk into slot a.
779784
char* helperBuf = static_cast<char*>(recvBuffs[g]);
780785
for (int a = 0; a < cfg.nActiveRanks; a++) {
@@ -801,7 +806,7 @@ static ncclResult_t shardedRelayAllReduce2Active(
801806
// fused add+scale over numHelpers/numChunks of the whole buffer. The work is
802807
// also spread over every helper GPU rather than piled onto the two actives.
803808
for (int g = 0; g < nGroups; g++) {
804-
if (counts[g] == 0 || configs[g].isActiveRank)
809+
if (counts[g] == 0 || chunkSizes[g] == 0 || configs[g].isActiveRank)
805810
continue;
806811
char* helperBuf = static_cast<char*>(recvBuffs[g]);
807812
size_t chunkSize = chunkSizes[g];
@@ -832,7 +837,7 @@ static ncclResult_t shardedRelayAllReduce2Active(
832837

833838
// The helper's chunk is already reduced, so it lands directly in its
834839
// final place in recvBuff — no active-side reduction for this region.
835-
for (int h = 0; h < cfg.numHelpers; h++) {
840+
for (int h = 0; h < cfg.numHelpers && chunkSize > 0; h++) {
836841
NCCLCHECK(ncclRecv(
837842
recvbuff + static_cast<size_t>(h) * chunkSize * elementSize,
838843
chunkSize,
@@ -852,8 +857,13 @@ static ncclResult_t shardedRelayAllReduce2Active(
852857
comm,
853858
stream));
854859
NCCLCHECK(ncclRecv(
855-
directDst(chunkSize), dirBSizes[g], datatype, partner, comm, stream));
856-
} else {
860+
directDst(dirASizes[g]),
861+
dirBSizes[g],
862+
datatype,
863+
partner,
864+
comm,
865+
stream));
866+
} else if (chunkSize > 0) {
857867
// Helper: hand the reduced chunk to both active ranks.
858868
const char* helperBuf = static_cast<const char*>(recvBuffs[g]);
859869
for (int a = 0; a < cfg.nActiveRanks; a++) {

0 commit comments

Comments
 (0)