Skip to content

Commit 89571fe

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. Reviewed By: JinghanHuang Differential Revision: D115998367
1 parent 4238c22 commit 89571fe

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
@@ -213,7 +213,8 @@ static ncclResult_t shardedRelayAllGather2Active(
213213
const int numChunks = numHelpers + 2;
214214

215215
size_t chunkSizes[SHARDED_RELAY_MAX_GROUPS];
216-
size_t relayTotals[SHARDED_RELAY_MAX_GROUPS]; // == direct chunk A's offset
216+
size_t dirAOffsets[SHARDED_RELAY_MAX_GROUPS];
217+
size_t dirASizes[SHARDED_RELAY_MAX_GROUPS];
217218
size_t dirBOffsets[SHARDED_RELAY_MAX_GROUPS];
218219
size_t dirBSizes[SHARDED_RELAY_MAX_GROUPS]; // absorbs the remainder
219220

@@ -223,22 +224,27 @@ static ncclResult_t shardedRelayAllGather2Active(
223224
// Zero-count groups are skipped by every loop below.
224225
if (count == 0) {
225226
chunkSizes[g] = 0;
226-
relayTotals[g] = 0;
227+
dirAOffsets[g] = 0;
228+
dirASizes[g] = 0;
227229
dirBOffsets[g] = 0;
228230
dirBSizes[g] = 0;
229231
continue;
230232
}
231233

232-
// A chunk size that rounds down to zero means the send buffer is too small
233-
// to scatter; the caller should fall back to a regular all-gather.
234234
size_t chunkSize = count / numChunks;
235235
chunkSize = (chunkSize / CHUNK_ALIGN_ELEMENTS) * CHUNK_ALIGN_ELEMENTS;
236+
chunkSizes[g] = chunkSize;
236237
if (chunkSize == 0) {
237-
return ncclInvalidArgument;
238+
dirAOffsets[g] = 0;
239+
dirASizes[g] = count / 2;
240+
dirBOffsets[g] = dirASizes[g];
241+
dirBSizes[g] = count - dirASizes[g];
242+
continue;
238243
}
239-
chunkSizes[g] = chunkSize;
240-
relayTotals[g] = static_cast<size_t>(numHelpers) * chunkSize;
241-
dirBOffsets[g] = relayTotals[g] + chunkSize;
244+
245+
dirAOffsets[g] = static_cast<size_t>(numHelpers) * chunkSize;
246+
dirASizes[g] = chunkSize;
247+
dirBOffsets[g] = dirAOffsets[g] + dirASizes[g];
242248
dirBSizes[g] = count - dirBOffsets[g];
243249
}
244250

@@ -294,7 +300,7 @@ static ncclResult_t shardedRelayAllGather2Active(
294300
char* recvbuff = static_cast<char*>(recvBuffs[g]);
295301

296302
// Scatter: chunk h of my sendBuff goes to helper h.
297-
for (int h = 0; h < cfg.numHelpers; h++) {
303+
for (int h = 0; h < cfg.numHelpers && chunkSize > 0; h++) {
298304
NCCLCHECK(ncclSend(
299305
sendbuff + static_cast<size_t>(h) * chunkSize * elementSize,
300306
chunkSize,
@@ -308,20 +314,20 @@ static ncclResult_t shardedRelayAllGather2Active(
308314
// gather slot never overlaps the send source, so this is safe in-place.
309315
int partner = cfg.activeRanks[1 - cfg.myActiveIndex];
310316
NCCLCHECK(ncclSend(
311-
sendbuff + relayTotals[g] * elementSize,
312-
chunkSize,
317+
sendbuff + dirAOffsets[g] * elementSize,
318+
dirASizes[g],
313319
datatype,
314320
partner,
315321
comm,
316322
stream));
317323
NCCLCHECK(ncclRecv(
318-
recvbuff + (gatherSlotOffset + relayTotals[g]) * elementSize,
319-
chunkSize,
324+
recvbuff + (gatherSlotOffset + dirAOffsets[g]) * elementSize,
325+
dirASizes[g],
320326
datatype,
321327
partner,
322328
comm,
323329
stream));
324-
} else {
330+
} else if (chunkSize > 0) {
325331
// Helper: receive active rank a's chunk into slot a.
326332
char* helperBuf = static_cast<char*>(recvBuffs[g]);
327333
for (int a = 0; a < cfg.nActiveRanks; a++) {
@@ -355,7 +361,7 @@ static ncclResult_t shardedRelayAllGather2Active(
355361
if (cfg.isActiveRank) {
356362
char* recvbuff = static_cast<char*>(recvBuffs[g]);
357363

358-
for (int h = 0; h < cfg.numHelpers; h++) {
364+
for (int h = 0; h < cfg.numHelpers && chunkSize > 0; h++) {
359365
NCCLCHECK(ncclRecv(
360366
recvbuff +
361367
(gatherSlotOffset + static_cast<size_t>(h) * chunkSize) *
@@ -383,7 +389,7 @@ static ncclResult_t shardedRelayAllGather2Active(
383389
partner,
384390
comm,
385391
stream));
386-
} else {
392+
} else if (chunkSize > 0) {
387393
const char* helperBuf = static_cast<const char*>(recvBuffs[g]);
388394
for (int a = 0; a < cfg.nActiveRanks; a++) {
389395
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
@@ -210,6 +210,7 @@ static ncclResult_t shardedRelayAllToAll2Active(
210210

211211
size_t chunkSizes[SHARDED_RELAY_MAX_GROUPS];
212212
size_t relayTotals[SHARDED_RELAY_MAX_GROUPS]; // == direct chunk A's offset
213+
size_t dirASizes[SHARDED_RELAY_MAX_GROUPS];
213214
size_t dirBOffsets[SHARDED_RELAY_MAX_GROUPS];
214215
size_t dirBSizes[SHARDED_RELAY_MAX_GROUPS]; // absorbs the remainder
215216

@@ -220,21 +221,23 @@ static ncclResult_t shardedRelayAllToAll2Active(
220221
if (count == 0) {
221222
chunkSizes[g] = 0;
222223
relayTotals[g] = 0;
224+
dirASizes[g] = 0;
223225
dirBOffsets[g] = 0;
224226
dirBSizes[g] = 0;
225227
continue;
226228
}
227229

228-
// A chunk size that rounds down to zero means the segment is too small to
229-
// scatter; the caller should fall back to a regular all-to-all.
230230
size_t chunkSize = count / numChunks;
231231
chunkSize = (chunkSize / CHUNK_ALIGN_ELEMENTS) * CHUNK_ALIGN_ELEMENTS;
232-
if (chunkSize == 0) {
233-
return ncclInvalidArgument;
234-
}
235232
chunkSizes[g] = chunkSize;
236233
relayTotals[g] = static_cast<size_t>(numHelpers) * chunkSize;
237-
dirBOffsets[g] = relayTotals[g] + chunkSize;
234+
if (chunkSize == 0) {
235+
dirASizes[g] = count / 2;
236+
dirBOffsets[g] = dirASizes[g];
237+
} else {
238+
dirASizes[g] = chunkSize;
239+
dirBOffsets[g] = relayTotals[g] + chunkSize;
240+
}
238241
dirBSizes[g] = count - dirBOffsets[g];
239242
}
240243

@@ -282,33 +285,35 @@ static ncclResult_t shardedRelayAllToAll2Active(
282285
static_cast<char*>(recvBuffs[g]) + exchangeSegOffset * elementSize;
283286

284287
// Scatter: chunk h of my exchange segment goes to helper h.
285-
for (int h = 0; h < cfg.numHelpers; h++) {
286-
NCCLCHECK(ncclSend(
287-
sendSeg + static_cast<size_t>(h) * chunkSize * elementSize,
288-
chunkSize,
289-
datatype,
290-
cfg.helperRanks[h],
291-
comm,
292-
stream));
288+
if (chunkSize > 0) {
289+
for (int h = 0; h < cfg.numHelpers; h++) {
290+
NCCLCHECK(ncclSend(
291+
sendSeg + static_cast<size_t>(h) * chunkSize * elementSize,
292+
chunkSize,
293+
datatype,
294+
cfg.helperRanks[h],
295+
comm,
296+
stream));
297+
}
293298
}
294299

295300
// Direct chunk A over the otherwise-idle active<->active link.
296301
int partner = cfg.activeRanks[1 - cfg.myActiveIndex];
297302
NCCLCHECK(ncclSend(
298303
sendSeg + relayTotals[g] * elementSize,
299-
chunkSize,
304+
dirASizes[g],
300305
datatype,
301306
partner,
302307
comm,
303308
stream));
304309
NCCLCHECK(ncclRecv(
305310
recvSeg + relayTotals[g] * elementSize,
306-
chunkSize,
311+
dirASizes[g],
307312
datatype,
308313
partner,
309314
comm,
310315
stream));
311-
} else {
316+
} else if (chunkSize > 0) {
312317
// Helper: receive active rank a's chunk into slot a.
313318
char* helperBuf = static_cast<char*>(recvBuffs[g]);
314319
for (int a = 0; a < cfg.nActiveRanks; a++) {
@@ -345,14 +350,16 @@ static ncclResult_t shardedRelayAllToAll2Active(
345350
char* recvSeg =
346351
static_cast<char*>(recvBuffs[g]) + exchangeSegOffset * elementSize;
347352

348-
for (int h = 0; h < cfg.numHelpers; h++) {
349-
NCCLCHECK(ncclRecv(
350-
recvSeg + static_cast<size_t>(h) * chunkSize * elementSize,
351-
chunkSize,
352-
datatype,
353-
cfg.helperRanks[h],
354-
comm,
355-
stream));
353+
if (chunkSize > 0) {
354+
for (int h = 0; h < cfg.numHelpers; h++) {
355+
NCCLCHECK(ncclRecv(
356+
recvSeg + static_cast<size_t>(h) * chunkSize * elementSize,
357+
chunkSize,
358+
datatype,
359+
cfg.helperRanks[h],
360+
comm,
361+
stream));
362+
}
356363
}
357364

358365
// Direct chunk B, again over the idle active<->active link.
@@ -371,7 +378,7 @@ static ncclResult_t shardedRelayAllToAll2Active(
371378
partner,
372379
comm,
373380
stream));
374-
} else {
381+
} else if (chunkSize > 0) {
375382
const char* helperBuf = static_cast<const char*>(recvBuffs[g]);
376383
for (int a = 0; a < cfg.nActiveRanks; a++) {
377384
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
@@ -707,6 +707,7 @@ static ncclResult_t shardedRelayAllReduce2Active(
707707

708708
size_t chunkSizes[SHARDED_RELAY_MAX_GROUPS];
709709
size_t relayTotals[SHARDED_RELAY_MAX_GROUPS]; // == direct chunk A's offset
710+
size_t dirASizes[SHARDED_RELAY_MAX_GROUPS];
710711
size_t dirBOffsets[SHARDED_RELAY_MAX_GROUPS];
711712
size_t dirBSizes[SHARDED_RELAY_MAX_GROUPS]; // absorbs the remainder
712713

@@ -718,21 +719,23 @@ static ncclResult_t shardedRelayAllReduce2Active(
718719
if (count == 0) {
719720
chunkSizes[g] = 0;
720721
relayTotals[g] = 0;
722+
dirASizes[g] = 0;
721723
dirBOffsets[g] = 0;
722724
dirBSizes[g] = 0;
723725
continue;
724726
}
725727

726-
// A chunk size that rounds down to zero means the buffer is too small to
727-
// scatter; the caller should fall back to a regular allreduce.
728728
size_t chunkSize = count / numChunks;
729729
chunkSize = (chunkSize / CHUNK_ALIGN_ELEMENTS) * CHUNK_ALIGN_ELEMENTS;
730-
if (chunkSize == 0) {
731-
return ncclInvalidArgument;
732-
}
733730
chunkSizes[g] = chunkSize;
734731
relayTotals[g] = static_cast<size_t>(numHelpers) * chunkSize;
735-
dirBOffsets[g] = relayTotals[g] + chunkSize;
732+
if (chunkSize == 0) {
733+
dirASizes[g] = count / 2;
734+
dirBOffsets[g] = dirASizes[g];
735+
} else {
736+
dirASizes[g] = chunkSize;
737+
dirBOffsets[g] = relayTotals[g] + chunkSize;
738+
}
736739
dirBSizes[g] = count - dirBOffsets[g];
737740
}
738741

@@ -786,7 +789,7 @@ static ncclResult_t shardedRelayAllReduce2Active(
786789
const char* sendbuff = static_cast<const char*>(sendBuffs[g]);
787790

788791
// Scatter: chunk h goes to helper h.
789-
for (int h = 0; h < cfg.numHelpers; h++) {
792+
for (int h = 0; h < cfg.numHelpers && chunkSize > 0; h++) {
790793
NCCLCHECK(ncclSend(
791794
sendbuff + static_cast<size_t>(h) * chunkSize * elementSize,
792795
chunkSize,
@@ -798,16 +801,18 @@ static ncclResult_t shardedRelayAllReduce2Active(
798801

799802
// Direct chunk A over the otherwise-idle active<->active link.
800803
int partner = cfg.activeRanks[1 - cfg.myActiveIndex];
801-
NCCLCHECK(ncclSend(
802-
sendbuff + relayTotals[g] * elementSize,
803-
chunkSize,
804-
datatype,
805-
partner,
806-
comm,
807-
stream));
808-
NCCLCHECK(
809-
ncclRecv(directDst(0), chunkSize, datatype, partner, comm, stream));
810-
} else {
804+
if (dirASizes[g] > 0) {
805+
NCCLCHECK(ncclSend(
806+
sendbuff + relayTotals[g] * elementSize,
807+
dirASizes[g],
808+
datatype,
809+
partner,
810+
comm,
811+
stream));
812+
NCCLCHECK(ncclRecv(
813+
directDst(0), dirASizes[g], datatype, partner, comm, stream));
814+
}
815+
} else if (chunkSize > 0) {
811816
// Helper: receive active rank a's chunk into slot a.
812817
char* helperBuf = static_cast<char*>(recvBuffs[g]);
813818
for (int a = 0; a < cfg.nActiveRanks; a++) {
@@ -834,7 +839,7 @@ static ncclResult_t shardedRelayAllReduce2Active(
834839
// fused add+scale over numHelpers/numChunks of the whole buffer. The work is
835840
// also spread over every helper GPU rather than piled onto the two actives.
836841
for (int g = 0; g < nGroups; g++) {
837-
if (counts[g] == 0 || configs[g].isActiveRank)
842+
if (counts[g] == 0 || chunkSizes[g] == 0 || configs[g].isActiveRank)
838843
continue;
839844
char* helperBuf = static_cast<char*>(recvBuffs[g]);
840845
size_t chunkSize = chunkSizes[g];
@@ -865,7 +870,7 @@ static ncclResult_t shardedRelayAllReduce2Active(
865870

866871
// The helper's chunk is already reduced, so it lands directly in its
867872
// final place in recvBuff — no active-side reduction for this region.
868-
for (int h = 0; h < cfg.numHelpers; h++) {
873+
for (int h = 0; h < cfg.numHelpers && chunkSize > 0; h++) {
869874
NCCLCHECK(ncclRecv(
870875
recvbuff + static_cast<size_t>(h) * chunkSize * elementSize,
871876
chunkSize,
@@ -885,8 +890,13 @@ static ncclResult_t shardedRelayAllReduce2Active(
885890
comm,
886891
stream));
887892
NCCLCHECK(ncclRecv(
888-
directDst(chunkSize), dirBSizes[g], datatype, partner, comm, stream));
889-
} else {
893+
directDst(dirASizes[g]),
894+
dirBSizes[g],
895+
datatype,
896+
partner,
897+
comm,
898+
stream));
899+
} else if (chunkSize > 0) {
890900
// Helper: hand the reduced chunk to both active ranks.
891901
const char* helperBuf = static_cast<const char*>(recvBuffs[g]);
892902
for (int a = 0; a < cfg.nActiveRanks; a++) {

0 commit comments

Comments
 (0)