Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions projects/rccl/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ set(SRC_FILES
include/tuner.h
include/tuning.h
include/utils.h
include/warp_speed_plan.h
init.cc
init_nvtx.cc
algorithms/dda/ipc/ipc_gpu_barrier.cu
Expand Down
116 changes: 83 additions & 33 deletions projects/rccl/src/enqueue/enqueue.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "api_trace.h"
#include "rccl_common.h"
#include "archinfo.h"
#include "warp_speed_plan.h"
#include "rccl_float8.h"
#include "net.h"
#include "compiler.h"
Expand Down Expand Up @@ -359,7 +360,7 @@ static void finishPlan(struct ncclComm* comm, struct ncclKernelPlan* plan) {
plan->kernelArgs->channelMask = plan->channelMask;
plan->kernelArgs->workStorageType = plan->workStorageType;
#ifdef ENABLE_WARP_SPEED
plan->kernelArgs->warpLevelComm = rcclWarpSpeedSupported(comm, plan);
plan->kernelArgs->warpLevelComm = plan->warpSpeedPlan;
#endif

// Put batches into the kernel arguments. The first batch for each channel
Expand Down Expand Up @@ -874,6 +875,11 @@ ncclResult_t ncclAddProxyOpIfNeeded(struct ncclComm* comm, struct ncclKernelPlan

RCCL_PARAM(IntraNetThreshold, "INTRANET_THRESHOLD", 8388608);

#ifdef RCCL_TEST_ENQUEUE_HOOKS
static size_t g_rcclScheduleCollEstimateVisits;
static bool g_rcclLaunchPrepareStopAfterPlanning;
#endif

static ncclResult_t scheduleCollTasksToPlan(struct ncclComm* comm, struct ncclKernelPlan* plan,
struct ncclKernelPlanBudget* budget) {
struct ncclKernelPlanner* planner = &comm->planner;
Expand All @@ -884,11 +890,30 @@ static ncclResult_t scheduleCollTasksToPlan(struct ncclComm* comm, struct ncclKe
int const nMaxChannels[2 * 2] = {comm->nChannels, comm->nvlsChannels, // [collnet][nvls]
comm->nChannels, std::min(comm->nChannels, comm->nvlsChannels)};
constexpr size_t MinTrafficPerChannel = 16 << 10;
auto accountTask = [&](struct ncclTaskColl* task) {
int kind = 2 * task->isCollnet + task->isNvls;
trafficBytes[kind] += std::max(MinTrafficPerChannel, task->trafficBytes);
// minCTAs/maxCTAs are resolved (env > per-call > comm) at task-append time.
// Check the minimum first because a per-call max may be below the comm minimum.
task->nMaxChannels = std::max<int>(task->nMaxChannels, task->minCTAs);
task->nMaxChannels = std::min<int>(task->nMaxChannels, task->maxCTAs);
// nvlsCTAs has no comm default, so apply it only when resolved.
if (task->isNvls && task->nvlsCTAs != NCCL_CONFIG_UNDEF_INT)
task->nMaxChannels = std::min<int>(task->nMaxChannels, task->nvlsCTAs);
nChannels[kind] += task->nMaxChannels;
nChannels[kind] = std::min(nChannels[kind], nMaxChannels[kind]);
};
#ifdef ENABLE_WARP_SPEED
bool capProven = false;
#endif
do {
size_t workBytes = 0;
struct ncclTaskColl* task = ncclIntruQueueHead(&planner->collTaskQueue);
struct ncclWorkList* workNode = ncclIntruQueueHead(&planner->collWorkQueue);
while (task != nullptr) {
#ifdef RCCL_TEST_ENQUEUE_HOOKS
g_rcclScheduleCollEstimateVisits += 1;
#endif
int nBatches = divUp(nPlanColls, 4); // Rough guess: 4 colls per batch.
if (!ncclTestBudget(budget, nBatches, workBytes + workNode->size)) goto plan_full;

Expand All @@ -897,35 +922,40 @@ static ncclResult_t scheduleCollTasksToPlan(struct ncclComm* comm, struct ncclKe
// per-kind channel budget.
bool taskAggIsolate = task->aggIsolate;
if (taskAggIsolate && nPlanColls > 0) goto plan_full; // leave it to start a fresh plan
// gfx950 + ENABLE_WARP_SPEED: fusing 4 equal 1M-element AllReduces into one
// kernel drops rank nRanks-1's contribution on a subset of elements
// (GroupCall.Multistream, 8 GPU, delta 1/nRanks). Identical (3 colls) and
// 2-coll Multistream pass. Cap at 3 colls per plan.
if (nPlanColls >= 3) goto plan_full;

nPlanColls += 1;
workBytes += workNode->size;
int kind = 2 * task->isCollnet + task->isNvls;
trafficBytes[kind] += std::max(MinTrafficPerChannel, task->trafficBytes);
// minCTAs/maxCTAs are resolved (env > per-call > comm) at task-append time, so they are
// applied unconditionally; comm defaults (minCTAs=1, maxCTAs=MAXCHANNELS) are no-ops on the base.
// We must check the minCTAs first to avoid the case where task->minCTAs > task->maxCTAs.
// For example, comm's min/max CTAs set to [8, 32] and config only set maxCTAs=4.
// We would have task->minCTAs=8 and task->maxCTAs=4.
task->nMaxChannels = std::max<int>(task->nMaxChannels, task->minCTAs);
task->nMaxChannels = std::min<int>(task->nMaxChannels, task->maxCTAs);
// nvlsCTAs has no comm default (UNDEF == no cap), so it is applied only when resolved to a value.
if (task->isNvls && task->nvlsCTAs != NCCL_CONFIG_UNDEF_INT)
task->nMaxChannels = std::min<int>(task->nMaxChannels, task->nvlsCTAs);
nChannels[kind] += task->nMaxChannels;
nChannels[kind] = std::min(nChannels[kind], nMaxChannels[kind]);
accountTask(task);
#ifdef ENABLE_WARP_SPEED
if (nPlanColls == RCCL_WARP_SPEED_MAX_PLAN_COLLS + 1 &&
rcclShouldCapGroupedPlan(
comm, plan, ncclIntruQueueHead(&planner->collTaskQueue), nPlanColls)) {
capProven = true;
goto plan_full;
}
#endif
task = task->next;
workNode = workNode->next;
if (taskAggIsolate) goto plan_full; // configured collective is alone in this plan
}
plan_full:;
} while (0);

#ifdef ENABLE_WARP_SPEED
// Preserve the three-task limit for WarpSpeed and the historical ordinary signature.
if (capProven) {
nPlanColls = RCCL_WARP_SPEED_MAX_PLAN_COLLS;
for (int kind = 0; kind < 2 * 2; kind++) {
trafficBytes[kind] = 0;
nChannels[kind] = 0;
}
struct ncclTaskColl* task = ncclIntruQueueHead(&planner->collTaskQueue);
for (int i = 0; i < nPlanColls; i++) {
accountTask(task);
task = task->next;
}
}
#endif

int kindPrev = -1;
size_t trafficPerChannel = 0;
int channelId = 0;
Expand All @@ -939,10 +969,10 @@ static ncclResult_t scheduleCollTasksToPlan(struct ncclComm* comm, struct ncclKe
int const planCollCount = nPlanColls;

#ifdef ENABLE_WARP_SPEED
// The plan queue is populated below, so evaluate the exact budgeted prefix
// now with the same plan-wide predicate used at launch.
bool warpSpeedPlan = rcclWarpSpeedSupported(
comm, plan, ncclIntruQueueHead(&planner->collTaskQueue), planCollCount);
// Classify the budgeted prefix once; finishPlan and launch reuse plan->warpSpeedPlan.
plan->warpSpeedPlan =
rcclWarpSpeedSupported(comm, plan, ncclIntruQueueHead(&planner->collTaskQueue), planCollCount);
bool const warpSpeedPlan = plan->warpSpeedPlan;
#endif

while (nPlanColls != 0 && !ncclIntruQueueEmpty(&planner->collTaskQueue)) {
Expand Down Expand Up @@ -2414,6 +2444,14 @@ static ncclResult_t getImplicitOrder(enum ncclImplicitOrder* mode, struct ncclCo
return ncclSuccess;
}

static bool ncclPlanCanScheduleP2p(struct ncclKernelPlan* plan) {
#ifdef ENABLE_WARP_SPEED
return !plan->warpSpeedPlan;
#else
return true;
#endif
}

ncclResult_t ncclLaunchPrepare(struct ncclComm* comm) {
ncclResult_t result = ncclSuccess;
struct ncclKernelPlanner* planner = &comm->planner;
Expand Down Expand Up @@ -2504,8 +2542,11 @@ ncclResult_t ncclLaunchPrepare(struct ncclComm* comm) {
if (planner->nTasksColl == 0 && planner->nTasksBcast != 0) {
NCCLCHECKGOTO(ncclScheduleBcastTasksToPlan(comm, plan, &budget), result, failure);
}
// And only drain p2p tasks once colls are depleted.
if (planner->nTasksColl == 0 && planner->nTasksBcast == 0 && planner->nTasksP2p != 0) {
// And only drain p2p tasks once colls are depleted. WarpSpeed mode
// is collective-global while P2P counts are rank-local, so never let
// rank-local P2P presence change the collective plan's mode.
if (planner->nTasksColl == 0 && planner->nTasksBcast == 0 &&
planner->nTasksP2p != 0 && ncclPlanCanScheduleP2p(plan)) {
Comment thread
i-kosarev marked this conversation as resolved.
Resolved
NCCLCHECKGOTO(scheduleP2pTasksToPlan(comm, &p2pEpoch, &p2pRound, plan, &budget), result, failure);
}
}
Expand All @@ -2525,6 +2566,10 @@ ncclResult_t ncclLaunchPrepare(struct ncclComm* comm) {

if (nPlans == 0) return ncclSuccess;

#ifdef RCCL_TEST_ENQUEUE_HOOKS
if (g_rcclLaunchPrepareStopAfterPlanning) return ncclSuccess;
#endif

cudaStream_t launchStream = planner->streams->stream;
cudaStream_t deviceStream, launchOrder;
bool capturing = ncclCudaGraphValid(planner->capturingGraph);
Expand Down Expand Up @@ -2637,18 +2682,23 @@ ncclResult_t ncclLaunchKernelBefore_NoUncapturedCuda(struct ncclComm* comm, stru
NCCL_PARAM(MemSyncDomain, "MEM_SYNC_DOMAIN", cudaLaunchMemSyncDomainRemote);
#endif

static int ncclPlanLaunchChannels(struct ncclComm* comm, struct ncclKernelPlan* plan, int nChannels) {
#ifdef ENABLE_WARP_SPEED
if (plan->warpSpeedPlan) {
int const warpsPerBlock = plan->threadPerBlock / comm->WarpSize;
return divUp(nChannels, warpsPerBlock);
}
#endif
return nChannels;
}

ncclResult_t ncclLaunchKernel(struct ncclComm* comm, struct ncclKernelPlan* plan) {
ncclResult_t ret = ncclSuccess;
struct ncclKernelPlanner* planner = &comm->planner;
int nChannels = 0;
for (int i = 0; i < MAXCHANNELS / CHANNELS_PER_MASK_WORD; i++) nChannels += countOneBits(plan->channelMask.masks[i]);
void* sym = plan->kernelFn;
#ifdef ENABLE_WARP_SPEED
int warpsPerBlock = plan->threadPerBlock / comm->WarpSize;
nChannels = rcclWarpSpeedSupported(comm, plan) ?
(nChannels / warpsPerBlock + ((nChannels % warpsPerBlock) != 0 ? 1 : 0)) :
nChannels; // each CU can handle warpsPerBlock
#endif
nChannels = ncclPlanLaunchChannels(comm, plan, nChannels);
dim3 grid = {(unsigned)nChannels, 1, 1};
dim3 block = {(unsigned)plan->threadPerBlock, 1, 1};
int smem = plan->isSymColl ? plan->kernelDynSmem : rcclShmemDynamicSize(comm->cudaArch, comm->WarpSize);
Expand Down
3 changes: 3 additions & 0 deletions projects/rccl/src/include/comm.h
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,9 @@ struct ncclKernelPlan {
bool isSymColl;
bool isCeColl;
bool isRma;
#ifdef ENABLE_WARP_SPEED
bool warpSpeedPlan;
#endif
enum ncclDevWorkStorageType workStorageType;
bool kernelSpecialized;
int kernelDynSmem; // only for symmetric kernels
Expand Down
1 change: 0 additions & 1 deletion projects/rccl/src/include/rccl_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,6 @@ ncclResult_t rcclDevrGinSegmentRegType(bool globalHasSysmemSegment, int maxGloba
RCCL_PARAM_DECLARE(WarpSpeedARThreshold);
RCCL_PARAM_DECLARE(WarpSpeedAutoMode);
void rcclSetWarpSpeedCUs(struct ncclComm* comm, int algo, int threadsPerBlock, int& rcclWarpSpeedChannels);
bool rcclWarpSpeedSupported(struct ncclComm* comm, struct ncclKernelPlan* plan);
bool rcclWarpSpeedSupported(struct ncclComm* comm, struct ncclKernelPlan* plan,
struct ncclTaskColl* collHead, int nCollTasks);
ncclResult_t rcclSetWarpSpeedAuto(struct ncclComm* comm, struct ncclTaskColl* info, size_t nBytes);
Expand Down
28 changes: 28 additions & 0 deletions projects/rccl/src/include/warp_speed_plan.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*************************************************************************
* Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/

#ifndef RCCL_WARP_SPEED_PLAN_H_
#define RCCL_WARP_SPEED_PLAN_H_

#include "archinfo.h"
#include "comm.h"

#ifdef ENABLE_WARP_SPEED
constexpr int RCCL_WARP_SPEED_MAX_PLAN_COLLS = 3;

inline bool rcclShouldCapGroupedPlan(struct ncclComm* comm, struct ncclKernelPlan* plan,
struct ncclTaskColl* collHead, int nPlanColls) {
if (nPlanColls <= RCCL_WARP_SPEED_MAX_PLAN_COLLS) return false;

// The single-node gfx950 corruption boundary is not characterized narrowly enough
// to safely depend on task ordering, datatype, operation, count, or auto mode.
if (comm->nNodes == 1 && IsArchMatch(comm->archName, "gfx950")) return true;
Comment thread
i-kosarev marked this conversation as resolved.
Resolved

return rcclWarpSpeedSupported(comm, plan, collHead, RCCL_WARP_SPEED_MAX_PLAN_COLLS);
}
#endif

#endif
5 changes: 0 additions & 5 deletions projects/rccl/src/rccl_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2862,11 +2862,6 @@ bool rcclWarpSpeedSupported(struct ncclComm* comm, struct ncclKernelPlan* plan,
return !hasP2p && !hasBcast && nCollTasks <= 0;
}

bool rcclWarpSpeedSupported(struct ncclComm* comm, struct ncclKernelPlan* plan) {
return rcclWarpSpeedSupported(comm, plan, ncclIntruQueueHead(&plan->collTaskQueue),
/*nCollTasks=*/-1);
}

bool rcclIsAboveWarpSpeedThreshold(struct ncclComm* comm, struct ncclTaskColl* info, size_t nBytes) {
// single node, full subscription thresholds for AllGather and ReduceScatter
if (info->func == ncclFuncAllReduce && nBytes >= rcclParamWarpSpeedARThreshold()) {
Expand Down
Loading
Loading