1. 概述
NCCL 根据 Group 边界、集合通信操作参数和单次调用配置,对用户登记的集合通信任务进行分类、聚合与调优。同一聚合组内的任务共享调优结果;后续编排为各任务分配 Channel,并将对应的工作项分组,供设备执行。下图采用第 2.1 节的 A/B/C/D 示例,展示同一 communicator 内 AllReduce 传统内核路径从任务登记到设备执行的过程,其中 Plan 组合与 Channel 分段仅作结构示意。
- 接口输入。 调用方在
ncclGroupStart与ncclGroupEnd之间发起一批集合通信调用。每次ncclAllReduce调用指定归约操作、数据类型和元素数量,ncclAllReduceConfig还可设置本次调用的资源约束。NCCL 根据这些输入确定任务类别和聚合范围;图中 D 设置maxCTAs=4,因此被标记为需要独立调优的任务。 - 任务分类。 每次调用对应一个
ncclTaskColl。collSorter根据trafficBytes将任务放入不同的流量区间,再按区间从大到小取出任务;同一区间内采用头插法,因此得到的是近似排序。随后,NCCL 将选用对称内核的任务移入独立队列,再将其余任务按(func, opDev.op, datatype)归类。图中 A、B、D 属于同一类别,C 因数据类型不同进入另一类别;两条执行路径的分流依据见第 4.3 节。 - 聚合调优。 NCCL 在同一类别的链表中划分连续的聚合区间。候选任务的流量严格小于区间首任务流量的 4 倍,且两者均未设置隔离标记时,候选任务可以加入当前区间。对于图中的 A、B,NCCL 将首任务复制为
ncclTaskColl类型的临时聚合任务agg,再累加两者的count和trafficBytes,共享一次ncclGetAlgoInfo调用;C、D 则分别调用。三次调优的输入nBytes分别为 18、12、18 KiB,均按聚合后的元素数量与类型大小计算。在多 rank 场景下,ncclGetAlgoInfo根据配置、拓扑和算法能力筛选可用的算法与协议组合,估算各组合的通信耗时,并在存在 tuner 时由插件调整估算耗时;随后选择估算耗时最短的组合,计算 Channel 上限和 warp 数,将结果写入agg。随后,ncclPrepareTasks补齐设备函数标识和调度标志,写回各原任务,再按调度约束组织collTaskQueue。各任务仍保留自己的元素数量和缓冲区引用。 - 工作编排。 主机为各任务构造或复用 Work,保存缓冲区地址和归约参数。计划编排按容量和资源约束将 Work 组织到 Plan 中,并为任务分配 Channel 和数据分段。主机为每个 Channel 组织待执行的工作项,并用 Batch 记录这些工作项的位置及其共用的设备函数。图中 D 独占一个 Plan,其
maxCTAs=4在这一阶段限制 Channel 数量;Plan P 的两个 Batch 都引用 Work A 和 Work B,每份 Work 在 Plan 中只保存一次。 - 主机传递。 主机通过
kernelArgs传入 Channel 掩码和 Batch 描述,并根据参数区容量与图捕获状态,将 Work 存放在参数区、FIFO 或持久设备缓冲区中。Work 存放在参数区之外时,kernelArgs中的workBuf提供其访问地址。设备通过这些描述取得执行参数,再通过 Work 中的地址访问各任务的用户缓冲区。 - 设备执行。 设备内核将每个 Block 映射到一个 Channel,按 Batch 加载 Work,再依据 Batch 中的
funcId选择相应的设备函数。设备函数逐项执行 Work,使用各任务自己的输入输出缓冲区,处理当前 Channel 负责的数据分段。

2. 关键接口与对象
用户通过 Group 边界、集合通信调用参数和单次调用配置影响任务分类和聚合。本文将任务整理、分类、聚合调优和连接需求记录统称为任务预处理。
2.1 用户配置接口
应用通过集合通信操作参数、Group 边界和单次调用配置影响任务分类与聚合。任务类别和聚合区间由 NCCL 根据这些输入确定,公开接口不提供直接指定分类 ID 或聚合组编号的字段。
| 接口定义 | 功能 |
|---|---|
ncclResult_t ncclGroupStart() |
开始一层 Group,收集后续 NCCL 调用;支持嵌套。 |
ncclResult_t ncclGroupEnd() |
结束一层 Group;最外层结束时触发已登记调用的后续处理,GPU 操作的完成时机仍由 CUDA stream 语义确定。 |
ncclResult_t ncclAllReduce(const void* sendbuff, void* recvbuff, size_t count, ncclDataType_t datatype, ncclRedOp_t op, ncclComm_t comm, cudaStream_t stream) |
登记一次 AllReduce 请求,使用默认的单次调用配置。 |
ncclResult_t ncclAllReduceConfig(const void* sendbuff, void* recvbuff, size_t count, ncclDataType_t datatype, ncclRedOp_t op, ncclComm_t comm, cudaStream_t stream, const ncclCollConfig_t* config) |
登记一次 AllReduce 请求,并传入本次调用的配置;config 为空时等价于普通接口。 |
ncclCollConfig_t 描述单次集合通信调用的资源约束、算法选择和附加设置,由调用方创建,并通过配置接口的 config 参数传入。配置对象及其扩展链表在使用它们的 API 调用期间必须保持有效,各 rank 应使用一致的配置。创建对象时必须使用 NCCL_COLLCONFIG_INITIALIZER 初始化,再修改需要设置的字段。
cpp
typedef struct ncclCollConfig_v23200 {
// 结构大小,由初始化宏填写,用户不应修改。
size_t size;
// 配置校验标识,由初始化宏填写,用户不应修改。
unsigned int magic;
// 配置版本,由初始化宏填写,用户不应修改。
unsigned int version;
// 厂商扩展配置链表,链表不能包含环。
ncclConfigExt_t* ext;
// Channel/CTA 数量下限。
// NCCL_CONFIG_UNDEF_INT 表示未显式设置。
int minCTAs;
// Channel/CTA 数量上限,受 communicator 上限约束。
// NCCL_CONFIG_UNDEF_INT 表示未显式设置。
int maxCTAs;
// NVLS 专用 Channel 资源上限。
// NCCL_CONFIG_UNDEF_INT 表示未显式设置。
int nvlsCTAs;
// CUDA 线程块集群大小,适用于 Hopper 及更新架构。
// NCCL_CONFIG_UNDEF_INT 表示未显式设置。
// 同一 Group 中各集合调用的设置应保持一致。
int cgaClusterSize;
// 本次调用允许使用的算法或内核选择字符串。
// NULL、NCCL_CONFIG_UNDEF_PTR 或空字符串表示自动选择。
const char* algSelection;
// 是否要求满足 algSelection。
// 默认值为 1:无法满足时返回错误。
// 设为 0:允许回退到自动选择。
int forceAlgSelection;
// NCCL_CTA_POLICY_* 策略位掩码。
// NCCL_CONFIG_UNDEF_INT 表示继承 communicator 的策略。
int CTAPolicy;
// 传递给 profiler 插件的用户标签,默认值为 0。
// 最高位由 NCCL 保留,应用设置的标签应保持该位为 0。
uint64_t userProfilerTag;
// 调用方持有的、rank 本地的 CUDA event。
// 在集合内核启动完成时记录;CUDA < 12.3 时在启动前记录。
// 创建时使用 cudaEventDisableTiming,不支持进程间和互操作事件。
// 各 rank 必须同时提供事件或同时不提供事件。
// 同一 Group 中每个 communicator 最多支持一个非空事件。
// 事件须保持有效,直到所有相关等待及 Graph 执行结束。
cudaEvent_t launchCompletionEvent;
} ncclCollConfig_t;
这些输入分别决定候选任务范围、任务类别和共享调优范围。
- Group 与 communicator 确定候选范围。 同一 Group 可以收集多个 communicator 的请求,各 communicator 使用自己的
planner,分类和聚合在各自的任务集合内完成。嵌套 Group 在最外层ncclGroupEnd才触发处理。 - 操作参数确定类别和规模。 调用的集合类型、归约操作和数据类型形成内部分类键
(func, opDev.op, datatype),其中opDev.op是转换后的设备归约操作。AllReduce 的流量估计为trafficBytes = count × ncclTypeSize(datatype) × 2;count影响规模判断,不属于分类键。 - 单次调用配置约束共享范围。 显式设置
minCTAs、maxCTAs、nvlsCTAs、非空algSelection或launchCompletionEvent,以及解析后的CTAPolicy不同于 communicator 默认值,都会使任务设置aggIsolate。这类任务独立调优,使本次调用的配置只作用于自己的调优描述。仅设置userProfilerTag或cgaClusterSize本身不触发隔离;cgaClusterSize是整个 launch 的属性,同一 Group 应保持一致。
本节示例假设在同一 Group 内,使用同一 communicator 和同一 CUDA stream 依次登记 A、B、C、D 四个请求,且均使用 ncclSum 并进入传统任务路径。各任务使用各自的设备缓冲区,各 rank 使用一致的调用顺序、操作参数和配置;communicator 允许至少 4 个 CTA,且没有环境变量覆盖单次调用配置。D 的配置先按公开接口要求初始化,再设置上限:
cpp
ncclCollConfig_t config = NCCL_COLLCONFIG_INITIALIZER;
config.maxCTAs = 4;
调用方在 ncclGroupStart 与 ncclGroupEnd 之间发出下表请求,D 向配置接口传入 &config。
| 任务 | 调用接口 | datatype |
count |
单次调用配置 | trafficBytes |
|---|---|---|---|---|---|
| A | ncclAllReduce |
ncclFloat32 |
1536 | 默认 | 12 KiB |
| B | ncclAllReduce |
ncclFloat32 |
3072 | 默认 | 24 KiB |
| C | ncclAllReduce |
ncclFloat64 |
1536 | 默认 | 24 KiB |
| D | ncclAllReduceConfig |
ncclFloat32 |
4608 | maxCTAs=4 |
36 KiB |
A、B、D 进入同一操作类别,C 因数据类型不同进入另一类别。A 与 B 满足严格的 4 倍规模条件,使用临时 agg 共享一次调优;D 虽然规模接近,也因配置隔离而独立调优。各原任务继续保留自己的元素数量和缓冲区引用。
2.2 任务预处理接口
| 接口定义 | 功能 |
|---|---|
static ncclResult_t ncclPrepareTasksAndCollPreconnect(struct ncclComm* comm, ncclSimInfo_t* simInfo, struct ncclIntruQueue<struct ncclAsyncJob, &ncclAsyncJob::next>* asyncCollJobs) |
根据配置,直接执行任务预处理,或将相应的 job 加入队列。直接执行时,根据预处理结果按需追加连接 job。 |
ncclResult_t ncclPrepareTasksAndCollPreconnectFunc(struct ncclAsyncJob* job_) |
作为预处理 job 的回调,设置设备上下文并执行任务预处理,再按条件调用 Collective 连接入口。 |
ncclResult_t ncclPrepareTasks(struct ncclComm* comm, bool* algoNeedConnect, bool* needConnect, ncclSimInfo_t* simInfo) |
整理并调优任务,更新 planner 和任务字段,按需置位连接输出;返回主机预处理阶段的状态码。 |
static ncclResult_t asyncJobLaunch(struct ncclIntruQueue<struct ncclAsyncJob, &ncclAsyncJob::next>* asyncJobsMain, volatile bool* groupAbortFlag) |
根据 job 数量选择当前线程或多个线程执行,等待本批工作完成并汇总状态。 |
ncclResult_t ncclCollPreconnect(struct ncclComm* comm, bool* algoNeedConnect) |
遍历已置位的算法需求,调用相应连接或缓冲区准备入口。 |
static ncclResult_t collTaskAppend(struct ncclComm* comm, struct ncclInfo* info, struct ncclDevRedOpFull opDev) |
将调用参数写入新建的集合任务节点,解析逐任务配置;普通集合路径将节点接入 collSorter,批量 Broadcast 分支另存到对应 root 的队列。 |
inline void ncclTaskCollSorterInsert(struct ncclTaskCollSorter* me, struct ncclTaskColl* x, size_t size) |
根据 size 所在的流量区间确定任务分组,将任务插入该组的链表首部。 |
inline struct ncclTaskColl* ncclTaskCollSorterDequeueAll(struct ncclTaskCollSorter* me) |
返回整个任务链表;链表非空时清空 sorter 自身状态,任务节点交由调用方继续使用。 |
ncclResult_t ncclGetAlgoInfo(struct ncclComm* comm, struct ncclTaskColl* task, int collNetSupport, int nvlsSupport, int numPipeOps, ncclSimInfo_t* simInfo = NULL) |
根据聚合任务和能力输入选择算法、协议及资源参数,通过 task 写回结果;提供 simInfo 时写入所选算法与协议执行该聚合任务的预计集合通信耗时(微秒)。 |
ncclResult_t ncclGetRegBuff(struct ncclComm* comm, struct ncclTaskColl* info, int* regBuff) |
根据任务描述的缓冲区范围查询注册记录,并结合 Graph 注册条件写入 regBuff;实际缓冲区注册由后续阶段处理。 |
ncclResult_t ncclTuningCompute(struct ncclTuningInput_t* const input, struct ncclTuningResult_t* const result) |
按输入评估候选,存在 tuner 时接受插件调整,选择结果并计算资源参数,通过 result 输出;本文详细介绍多 rank 场景下的传统内核路径。 |
ncclResult_t ncclTuningGetChannels(struct ncclTuningInput_t* const input, struct ncclTuningResult_t* const result) |
根据已选算法、协议、消息规模和 communicator 资源计算 Channel 与 warp 数,结合插件上限更新 result;具体 Channel 区间由后续编排确定。 |
algoNeedConnect 按算法索引,长度为 NCCL_NUM_ALGORITHMS;needConnect 汇总本次是否提出连接请求。这两个输出与函数返回的 ncclResult_t 分别表示连接需求和执行状态。ncclPrepareTasks 的连接需求登记规则见第 5 节。
2.3 任务与规划器
下图展示任务从登记到预处理完成时的数据结构关系:ncclKernelPlanner 持有跨阶段使用的任务队列,ncclPrepareTasks 中的局部数组、临时聚合任务和分类队列用于重组任务与查询调优结果。分类与聚合过程中,各原任务继续保留,变化的是链表链接和调优字段;图中同名节点表示同一任务在不同阶段的状态。
示例沿用第 2.1 节的 A/B/C/D,假设四个任务均留在传统路径,并最终选用 Ring,以便展示确定的队列顺序。
- 登记到排序器。
collTaskAppend创建并填写ncclTaskColl,通过ncclTaskCollSorterInsert将任务接入planner->collSorter。按本例的流量估计与登记顺序,登记完成后的链表为 D → C → B → A。 - 重组类别链表。
ncclPrepareTasks先调用ncclTaskCollSorterDequeueAll,取得整个任务链表的首指针,同时清空collSorter的首尾指针和索引状态;原任务节点继续保留。本例均沿传统 AllReduce 路径处理。函数逐个遍历任务,根据集合操作类型、设备归约操作类型和数据类型(func, opDev.op, datatype)确定类别,将任务插入tasksByFnOpTy中对应类别链表的首部。本例得到 A → B → D 和 C 两条链表,fnOpTyIndices记录已出现的类别索引;图中以 i、j 代指索引值。 - 构造临时聚合任务。 A、B 满足共享调优条件,函数将首任务复制到临时
agg,再累计两者的count和trafficBytes;D 因隔离标记独立调优,C 在另一类别中独立调优。每个聚合区间分别构造agg,原任务节点仍留在待处理链表中。 - 写回后按约束入队。
ncclGetAlgoInfo更新agg的调优字段。调用方逐个处理原任务:先保存next指向的后继,写回调优结果,再通过ncclIntruQueueEnqueue将当前任务追加到局部队列collBins[isCollnet][isNvls]的尾部。入队会改写当前任务的next,后续遍历使用此前保存的后继。此时collSorter早已在第 2 步取出整个链表时清空;分类队列接入的是原任务节点,临时agg本身不入队。本例假设均选 Ring,四个任务依次进入[0][0]队列。 - 拼接为后续输入。
ncclIntruQueueTransfer按二维索引顺序,将四个分类队列中的任务转移到planner->collTaskQueue,并清空来源队列。本例输出 A → B → D → C;后续注册与编排仍逐项使用这些任务,为其构造或复用 Work,再组织执行计划。

ncclTaskColl 表示一次已登记的集合通信操作,包含用户缓冲区引用、元素数量、归约描述和逐任务配置。预处理阶段重排任务链接并写入调优字段。
cpp
struct ncclTaskColl {
// 当前任务所在侵入式链表的后继。
struct ncclTaskColl* next;
// 集合操作类型。
ncclFunc_t func;
// 用户输入缓冲区引用。
void const* sendbuff;
// 用户输出缓冲区引用。
void* recvbuff;
// 以 datatype 为单位的元素数量。
size_t count;
// 有根集合操作使用的 rank。
int root;
// 任务元素类型。
ncclDataType_t datatype;
// 主机归约操作描述。
ncclRedOp_t opHost;
// 设备归约操作及其参数。
struct ncclDevRedOpFull opDev;
// 用于任务排序与后续调度的流量估计。
size_t trafficBytes;
// 调优给出的 Channel 数量上限。
int32_t nMaxChannels:8;
// 调优给出的 warp 数量。
int32_t nWarps:8;
// 选定的算法。
int32_t algorithm:8;
// 选定的协议。
int32_t protocol:8;
// 是否使用 CollNet 调度约束。
uint32_t isCollnet:1;
// 是否使用 NVLS 调度约束。
uint32_t isNvls:1;
// 设备函数标识。
uint32_t devFuncId:29;
// 是否需要独立调优。
bool aggIsolate;
};
ncclKernelPlanner 是每个 communicator 持有的主机端任务与执行计划管理对象,通过 comm->planner 访问。它保存 Group 内登记的任务、任务计数和流上下文,并持有预处理后的分类队列,供后续注册与工作编排使用。编排过程中,它保存 Work 队列、当前 Plan 的构建状态和待启动的 Plan 队列,衔接任务登记、预处理、计划构建与内核启动。下面仅摘录与本文任务预处理相关的成员。
cpp
struct ncclKernelPlanner {
struct Peer {
// 按 Broadcast root 索引的任务队列。
struct ncclIntruQueue<struct ncclTaskBcast, &ncclTaskBcast::next> bcastQueue;
};
// 按流量规模近似排序的集合通信任务。
struct ncclTaskCollSorter collSorter;
// 长度为 nRanks 的 peer 数组。
struct Peer* peers;
// 传统集合通信任务数量。
int nTasksColl;
// 保留在 Broadcast 队列中的任务数量。
int nTasksBcast;
// Broadcast root 的范围和不同 root 的数量。
struct {
// 已登记 Broadcast 的最小 root。
int minBcastPeer;
// 已登记 Broadcast 的最大 root。
int maxBcastPeer;
// 已登记任务涉及的不同 root 数量。
int BcastPeers;
} bcast_info;
// 当前任务是否属于有效的 CUDA Graph 捕获。
bool persistent;
// 当前捕获上下文。
struct ncclCudaGraph capturingGraph;
// 分类、聚合调优后供传统计划使用的任务队列。
struct ncclIntruQueue<struct ncclTaskColl, &ncclTaskColl::next> collTaskQueue;
// 被筛入对称 kernel 路径的任务队列。
struct ncclIntruQueue<struct ncclTaskColl, &ncclTaskColl::next> collSymTaskQueue;
// NVLS 分支提前构造的临时 Work 队列。
struct ncclIntruQueue<struct ncclWorkList, &ncclWorkList::next> tmpCollWorkQueue;
// 注册等操作关联的清理回调队列。
struct ncclIntruQueue<struct ncclCommCallback, &ncclCommCallback::next> collCleanupQueue;
};
调优对象连接临时聚合任务与 communicator 的共享 Cost Model。下图区分两种作用域:ncclTuningContext_t 随 communicator 保留,input 与 bestTuning 则是一次 ncclGetAlgoInfo 调用中的局部对象。虚线表示字段传递或 Cost Model 参数读取;图中的 agg 与前图的临时聚合任务相同。
- 复用 Cost Model 状态。
comm->tuningContext保存已初始化的候选启用状态、延迟、带宽和线程阈值。调优通过input.comm访问当前 communicator 的 Cost Model 状态。 - 形成单次输入。
ncclPrepareTasks持有agg,ncclGetAlgoInfo将其操作、规模和配置转换为ncclTuningInput_t,并补充注册状态和能力输入;input.comm是引用,Cost Model 参数数组保留在 communicator 中。 - 接收并写回结果。
ncclTuningCompute评估候选、按需调用插件并选择结果,再把算法、协议和资源参数写入bestTuning。ncclGetAlgoInfo根据该结果更新agg,调用方随后将字段写回区间内的原任务,具体过程见第 4.2、4.3 节。

ncclTuningContext_t 是 ncclComm 的成员,通过 comm->tuningContext 访问。初始化阶段建立其中的 Cost Model 参数,后续调优复用这些状态;逐任务候选掩码保存在单次输入中。下面摘录与传统内核调优相关的成员。
cpp
struct ncclTuningContext_t {
// 调优常量,初始化时可由 tuner 插件调整。
ncclTunerConstants_t tuningConstants;
// 各集合操作是否由环境配置强制选择实现。
int forced[NCCL_NUM_FUNCTIONS];
// 各 tuning ID 对各集合操作的启用状态。
int enabled[NCCL_TUNING_COUNT][NCCL_NUM_FUNCTIONS];
// 按集合操作、算法和协议索引的基础延迟。
float generalLatencies[NCCL_NUM_FUNCTIONS][NCCL_NUM_ALGORITHMS][NCCL_NUM_PROTOCOLS];
// 按集合操作、算法和协议索引的基础带宽。
float generalBandwidths[NCCL_NUM_FUNCTIONS][NCCL_NUM_ALGORITHMS][NCCL_NUM_PROTOCOLS];
// 按算法和协议索引的线程工作量阈值。
ssize_t threadThresholds[NCCL_NUM_ALGORITHMS][NCCL_NUM_PROTOCOLS];
// 按算法和协议索引的最大线程数。
int maxThreads[NCCL_NUM_ALGORITHMS][NCCL_NUM_PROTOCOLS];
};
ncclTuningInput_t 保存一次调优的输入,comm 引用初始化阶段形成的拓扑与 Cost Model 参数。下面摘录 ncclGetAlgoInfo 调用点填写的字段;同一结构中供 symmetric/CE 路径使用的其他成员不在此列出。
cpp
struct ncclTuningInput_t {
// 所属 communicator,提供拓扑、Cost Model 状态和 tuner 上下文。
struct ncclComm* comm;
// 本次参与评估的实现集合,每一位对应一个 tuning ID。
uint64_t tuningMask;
// 集合操作类型。
ncclFunc_t func;
// 主机归约操作。
ncclRedOp_t redOp;
// 设备归约操作类型。
ncclDevRedOp_t devRedOp;
// 元素类型。
ncclDataType_t datatype;
// 本次调优的消息字节数。
size_t nBytes;
// 调用方估计的每 Channel 任务数。
int numPipeOps;
// 临时聚合任务中的元素数量。
size_t count;
// 用户缓冲区注册状态及 Graph 注册条件的查询结果。
int regBuff;
// 本操作类别的 CollNet 能力标记。
int collNetSupport;
// 本操作类别的 NVLS 能力标记。
int nvlsSupport;
// 已解析的逐任务 CTA 策略。
int CTAPolicy;
};
ncclTuningResult_t 保存候选评估和资源计算的结果。
cpp
struct ncclTuningResult_t {
// 候选 ID。
int id;
// Cost Model 评估是否有效。
int valid;
// Cost Model 或插件给出的估算通信耗时,单位为微秒。
float timeUs;
// 可选的耗时比较值,单位为微秒;正值时优先用于选择。
float selectionTimeUs;
// 选定的算法。
int algo;
// 选定的协议。
int proto;
// 本次资源计算得到的 Channel 数。
int nChannels;
// 写回任务时使用的 Channel 数量约束。
int maxChannels;
// 每个 Block 的 warp 数。
int nWarps;
};
2.4 任务排序结构
ncclTaskCollSorter 按流量区间对任务分组,用一条链表串联所有分组。每组任务在链表中连续存放,bins 按分组编号记录访问该组首任务的入口。入口保存的是首任务指针的地址,可能是 &head,也可能是前一任务的 next 字段地址。这种二级指针使插入操作可以直接修改指向该组首任务的链接。
cpp
struct ncclTaskCollSorter {
// 编码前将流量字节数换算为 KiB,并向下取整。
static constexpr int UnitLog2 = 10;
static constexpr size_t UnitSize = 1 << UnitLog2;
// 用于确定流量分组的编码上限为 1 GiB。
static constexpr int MaxLog2 = 30;
static constexpr size_t MaxSize = 1ull << MaxLog2;
// 流量编码保留的尾数位数。
static constexpr int BitsPerPow2 = 2;
// 每个较大二进制数量级划分为四个流量区间。
static constexpr int BinsPerPow2 = 1 << BitsPerPow2;
// 流量分组索引,共 81 项。
static constexpr int BinCount = 1 + (MaxLog2 - UnitLog2) * BinsPerPow2;
// 全部任务组成的链表首节点。
struct ncclTaskColl* head;
// 全部任务组成的链表尾节点。
struct ncclTaskColl* tail;
// 此编号及后续编号对应的分组均无任务。
int binEdge;
// 每项保存指向该组首任务的指针地址。
struct ncclTaskColl** bins[BinCount];
};
由于 bins 可能指向 sorter 自身的 head,该结构包含自引用地址,源码明确要求不能通过 memcpy 拷贝。第 3.2 节用具体流量值和分组索引说明它如何组织任务。
3. 任务登记与排序
collTaskAppend 把一次集合通信调用记录为主机任务节点,并通过 ncclTaskCollSorterInsert 将普通集合任务接入 planner->collSorter。登记阶段填写操作参数和任务约束,排序器按流量区间组织待处理的链表;第 4 节的 ncclPrepareTasks 再取出这些节点进行分类与聚合。
3.1 调用参数登记
以普通 AllReduce 路径为例,collTaskAppend 在将 communicator 接入当前 Group、记录 stream 的捕获上下文后,依次完成以下工作。
- 取得任务节点。
ncclMemoryPoolAlloc<ncclTaskColl>从comm->memPool_ncclTaskColl复用空闲节点,或向comm->memPermanent申请空间,再用memset清零。底层ncclMemoryStack::allocate按对齐要求推进当前内存块的分配位置;空间不足时由allocateSpilled复用其他内存块,或调用malloc扩充主机存储。 - 填写任务描述。 将
info中的集合类型、缓冲区引用、元素数量、root 和数据类型写入节点,同时保存主机归约操作opHost和已转换的设备描述opDev。AllReduce 使用trafficBytes = count × ncclTypeSize(datatype) × 2估计流量。 - 保存配置并接入排序器。 根据单次调用配置生成
aggIsolate,解析 CTA 资源边界和算法约束,保存事件及 profiler 信息;随后增加planner->nTasksColl,调用ncclTaskCollSorterInsert(&planner->collSorter, t, t->trafficBytes)进行排序。
ncclTypeSize(datatype) 返回每个元素的字节数;AllReduce 的系数 2 来自 ncclFuncTrafficPerByte,用于统一估计调度规模。以 Ring 为例,设 P 个 rank、单个输入缓冲区大小为 S,在均分且忽略填充和协议开销的假设下,每个 rank 在 ReduceScatter 与 AllGather 阶段合计发送 2 × (P - 1) / P × S 字节,P 较大时接近 2 × S。
满足批量 Broadcast 优化条件的调用另建 ncclTaskBcast,追加到 planner->peers[root].bcastQueue。
3.2 流量区间排序
ncclTaskCollSorterInsert 根据 trafficBytes 确定流量区间,再将任务插到该区间对应链表段的首部。所有区间共用一条链表,较大流量区间排在前面,同一区间内按登记顺序反向排列。因此,排序器给出近似顺序;操作类别和共享调优范围由第 4 节确定。
-
确定区间。 排序器先限制编码输入的大小,再按二进制数量级确定流量分组。下面的上下限公式用于解释
u32fpEncode的分组规则;源码直接计算编码,不单独保存区间边界。- 截断并换算单位。 计算
x = min(MaxSize, size) >> UnitLog2,其中MaxSize=1 GiB、UnitLog2=10。min将参与编码的流量限制在 1 GiB 以内,右移将字节数向下取整换算为整数 KiB;原任务字段仍保存实际流量。 - 计算区间宽度。 当
x >= 4时,令k = floor(log2(x)),则x位于[2^k, 2^(k+1))这一数量级。BitsPerPow2=2将该数量级等分为四段,每段宽度为Δ = 2^(k-2)KiB;当x < 4时,每段宽度为 1 KiB。 - 确定上下限。 对未达到 1 GiB 的流量,区间下限为
L = floor(x / Δ) × Δ,上限为U = L + Δ,两者的单位均为 KiB。区间采用左闭右开的形式[L, U):流量达到U时进入下一组。原始流量达到或超过 1 GiB 时,编码输入均被截断为 1 GiB,共用对应原始流量范围[1 GiB, +∞)的饱和分组。 - 编码并反转编号。
u32fpEncode(x, 2)返回所属区间的code,再用bin = BinCount - 1 - code得到分组索引。这里BinCount=81,因此bin=80-code;反转编号使较大流量区间排在前面,区间边界保持不变。
- 截断并换算单位。 计算
-
维护分组入口。 区间首次加入任务时,排序器记录该组入口,并按需更新后继分组的入口地址和链表尾指针。已有任务的区间再次插入时,复用原入口。
-
接入原节点。 执行
x->next = *me->bins[bin]和*me->bins[bin] = x,把任务接到该组首部。bins[bin]保存入口指针的地址,可能指向head,也可能指向前一任务的next字段。
沿用第 2.3 节的 A/B/C/D 任务,四个任务依次登记,流量估计分别为 12、24、24、36 KiB。A 所在数量级为 [8, 16) KiB,区间宽度为 2 KiB;B、C 所在数量级为 [16, 32) KiB,区间宽度为 4 KiB;D 所在数量级为 [32, 64) KiB,区间宽度为 8 KiB。各任务的流量区间、编码与插入结果如下。
| 登记任务 | 流量估计 | 所属流量区间 | code |
分组索引 | 插入后的完整链表 |
|---|---|---|---|---|---|
| A | 12 KiB | [12, 14) KiB | 10 | 70 | A |
| B | 24 KiB | [24, 28) KiB | 14 | 66 | B → A |
| C | 24 KiB | [24, 28) KiB | 14 | 66 | C → B → A |
| D | 36 KiB | [32, 40) KiB | 16 | 64 | D → C → B → A |
下图展示登记完成后的分组入口、指针存储位置和完整任务链表。B、C 虽然数据类型不同,仍因流量估计相同进入同一排序分组;数据类型在第 4.1 节的操作分类中才用于区分类别。
- 链表顺序。
head指向 D,之后依次为 C、B、A,tail指向 A。C 后于 B 登记,在分组 66 中排到 B 前面。 - 分组入口。
bins[64]保存&head,bins[66]保存&D.next,bins[70]保存&B.next,分别访问 D、C、A。C、B 在同一组内连续排列,C.next连接 B。 - 稀疏索引。 本例只有 64、66、70 三个非空分组,其余
bins项为空,binEdge=71。新建分组且后面已有任务时,插入函数从bin+1向后跳过空项,找到第一个非空分组,再调整入口引用;预处理时直接沿完整链表遍历。

4. 任务分类、聚合与调优
ncclPrepareTasks 将排序后的任务按操作属性分类,再构造共享调优结果的聚合区间,最终生成供后续调度使用的任务队列。本节沿用 A/B/C/D,分析传统 AllReduce 路径;输入检查完成后的主要处理分为四步,对应第 2.3 节的步骤 2---5。
- 任务分类。 按
(func, opDev.op, datatype)将任务重新分类。本例从 D → C → B → A 得到 Float32 的 A → B → D 和 Float64 的 C,见第 4.1 节。 - 调优。 在各类别中,先确定 CollNet/NVLS 能力标记并估计每个 Channel 的任务数量,再按流量阈值和隔离标记划分聚合区间,累计规模后调用
ncclGetAlgoInfo。本例形成 A/B、D、C 三个区间,见第 4.2 节。 - 结果写回。 将当前区间的调优结果写回各原任务,再按
isCollnet、isNvls追加到collBins,见第 4.3 节。 - 输出任务队列。 全部类别处理结束后,按二维索引顺序把
collBins拼接到planner->collTaskQueue。本例假设均选 Ring,输出 A → B → D → C,见第 4.4 节。
步骤 2、3 在每个聚合区间内连续执行;所有类别处理完成后,才执行步骤 4。函数随后记录连接需求,这部分见第 5 节。
4.1 任务分类
tasksByFnOpTy 是局部首指针数组,使用前由 memset 清零,每个有效入口指向一个类别的任务链表。取出和分类的流程如下:
-
保存后继。 读取当前
task->next并存入局部next,避免改接链接后丢失原链表的遍历位置。 -
确定类别。 按
(func, opDev.op, datatype)三元组分类,三个字段全部相同的任务进入同一条类别链表。- 集合通信操作类型
func。 区分 AllReduce、AllGather、ReduceScatter 等操作;Ring、Tree 是后续调优选择的算法。 - 设备归约操作类型
opDev.op。 使用用户归约操作转换后的设备操作类型,如ncclDevSum、ncclDevProd;归约标量等参数仍由各任务保存。 - 数据类型
datatype。 区分ncclFloat32、ncclFloat64等元素类型。
公式
((func * ncclNumDevRedOps + opDev.op) * ncclNumTypes + datatype)将三元组展开为一维数组索引。该类别首次出现时,把索引追加到fnOpTyIndices,供后续按首次出现顺序遍历。 - 集合通信操作类型
-
插入类别链表首部。 将
task->next改为该类别原有的首指针,再把tasksByFnOpTy[index]改为当前任务,最后从此前保存的next继续遍历。因此,每个类别内的原遍历顺序反转,较小流量区间优先。
本例中 A、B、D 的分类键均为 AllReduce + ncclDevSum + ncclFloat32,因此归入同类;C 的操作类型和设备归约操作相同,但数据类型为 ncclFloat64,进入另一类。从 D → C → B → A 开始遍历时,依次遇到这两个类别:Float32 链表依次形成 D、B → D、A → B → D,Float64 链表只有 C。

4.2 调优
ncclPrepareTasks 先为当前任务类别准备能力标记和任务数估计,再逐个构造聚合区间,调用 ncclGetAlgoInfo 选择算法、协议和资源数量。下面依次分析参数准备、同类任务聚合,以及 ncclGetAlgoInfo 对算法与执行参数的选择。
4.2.1 准备调优参数
进入一个任务类别时,ncclPrepareTasks 先用该类别的首任务查询 CollNet/NVLS 能力,再估计每个 Channel 的任务数量。这些参数在开始任务聚合前确定。
cpp
struct ncclTaskColl* aggBeg = tasksByFnOpTy[fnOpTyIndices[cursor]];
int collNetSupport = 0;
NCCLCHECK(ncclGetCollNetSupport(comm, aggBeg, &collNetSupport));
int nvlsTransportEnabled =
ncclNvlsTransportEnabled(comm) &&
(ncclNvlsSupported(aggBeg->opDev.op, aggBeg->datatype) ||
aggBeg->func == ncclFuncAllGather);
int nTasksPerChannel = divUp(comm->planner.nTasksColl, comm->nChannels);
- 查询 CollNet 能力。 对本文的 AllReduce,
ncclGetCollNetSupport结合comm->config.collnetEnable与操作、数据类型对应的能力矩阵项设置collNetSupport。其中ncclDevPreMulSum、ncclDevSumPostDiv按ncclSum查询。 - 判断 NVLS 能力。
nvlsTransportEnabled要求 NVLS transport 已启用,且当前设备归约操作与数据类型受到支持;共用代码还允许 AllGather 通过后一项判断。该值随后作为nvlsSupport传入调优函数,供候选组合的 Cost Model 检查。 - 估计每 Channel 的任务数。
nTasksPerChannel等于传统任务总数planner->nTasksColl除以comm->nChannels后向上取整。它使用当前 communicator 的 Channel 数,并非某个聚合区间的任务数;此时算法尚未选定,NVLS 也沿用这一粗略估计。
能力标记用于后续筛选算法与协议组合,任务数估计参与通信的耗时计算。参数准备完成后,函数进入 do 循环,为当前类别逐个构造聚合区间。
4.2.2 聚合同类任务
每轮聚合从 aggBeg 开始,先将首任务复制到临时 agg,再累计满足条件的相邻任务的 count 和 trafficBytes。aggEnd 指向尚未纳入区间的第一个任务,聚合范围为 [aggBeg, aggEnd);各原任务继续保存自己的元素数量和缓冲区引用。下面是决定聚合范围的源码片段。
cpp
struct ncclTaskColl* aggEnd = aggBeg->next;
struct ncclTaskColl agg = *aggBeg;
while (aggEnd != nullptr &&
aggEnd->trafficBytes < 4 * aggBeg->trafficBytes &&
!aggBeg->aggIsolate && !aggEnd->aggIsolate) {
agg.count += aggEnd->count;
agg.trafficBytes += aggEnd->trafficBytes;
aggEnd = aggEnd->next;
}
比较基准始终是区间首任务的 trafficBytes。候选任务恰好达到该基准的 4 倍时,当前区间结束;阈值也不随 agg.trafficBytes 的累加而增大。由于输入只是近似排序,函数沿类别链表逐个检查相邻任务,满足条件就继续累计,遇到首个不满足条件的任务便结束当前区间。
aggIsolate 标记任务是否独立成组,若是独立任务,则单独调优,不聚合。collTaskAppend 在登记时根据单次调用配置生成该标记。
下图继续使用第 2.3 节的 A/B/C/D 四个任务,展示如何从两条类别链表中构造三个用于调优的临时聚合任务。Float32 类别先处理 A/B,再处理隔离任务 D;随后处理 Float64 类别中的 C。
- A/B 共享调优。 A 的流量估计为 12 KiB,B 为 24 KiB,满足
24 < 4 × 12,且两者均未隔离。D 虽然也满足流量条件,却因aggIsolate=true结束当前区间。A/B 的临时agg.count=4608,聚合流量为 36 KiB。 - D 独立调优。 D 显式设置
maxCTAs=4,独立构造agg,其中count=4608,流量为 36 KiB。它与 A/B 聚合后的规模相同,但保留自己的资源约束。 - C 在另一类别调优。 C 使用 Float64,独立构造
agg,其中count=1536,流量为 24 KiB。最终有三次调优,仍保留四个原任务节点。

区间确定后,调用方把临时 agg 与第 4.2.1 节准备的参数传给调优函数:
cpp
NCCLCHECK(ncclGetAlgoInfo(comm, &agg, collNetSupport,
nvlsTransportEnabled, nTasksPerChannel, simInfo));
4.2.3 选择算法与执行参数
ncclGetAlgoInfo 根据临时聚合任务的规模、配置和 communicator 能力选择算法与协议,计算 Channel 上限和 warp 数,并把结果写回 agg。执行流程如下:
- 准备调优输入。 计算消息大小,初始化算法与协议字段,确定候选掩码,填写操作属性、能力标记和任务数估计。
- 查询缓冲区注册状态。 查询已有注册记录及 Graph 注册条件,将结果写入
input.regBuff。 - 执行调优计算。 调用
ncclTuningCompute,评估算法与协议组合,同时接受插件调整,选择最优组合并计算资源参数。 - 扩大候选范围并重新调优。 按任务指定的候选范围进行首次调优时,若未选出可用算法,函数取消这一范围限制,在完整的传统内核候选范围内重新调优;环境配置、能力和 Cost Model 限制仍然生效。重算成功后,若
forceAlgSelection=0,则采用重算结果;否则因无法满足原候选限制而返回错误。 - 返回调优结果。 更新
agg的算法、协议、Channel 上限和 warp 数;调用方提供simInfo时,同时写入所选算法与协议执行当前聚合任务的预计集合通信耗时,最后返回执行状态。
4.2.3.1 准备调优输入
ncclGetAlgoInfo 先根据元素类型和集合操作计算 nBytes,把临时聚合任务的 algorithm、protocol 置为未定义,再填写局部 ncclTuningInput_t input。
-
计算消息大小。
ncclFuncMaxSendRecvCount根据集合操作类型确定发送与接收两侧较大的元素数量:AllGather 返回nRanks × count,对应接收端汇总的元素数量;ReduceScatter 返回nRanks × count,对应发送端参与归约的元素数量;其他操作返回count。随后乘以ncclTypeSize(info->datatype),得到用于调优的消息字节数nBytes。本例 AllReduce 的 A/B、D、C 三次调用依次为 18 KiB、18 KiB、12 KiB,均按聚合后的count × 元素大小计算;排序与聚合使用的trafficBytes另行保留。 -
确定算法候选范围。
algMask保存任务指定的算法候选范围,tuningMask保存本次调优实际遍历的算法候选范围。两者使用相同的 64 位位图编码,环境配置与 Cost Model 的支持范围还会进一步限制可用候选算法。-
按算法与协议组合确定候选位号。
algo是算法编号,proto是协议编号;每个组合对应的位图位号为i = algo × 3 + proto。位号从最低位的 0 开始,1ULL << i表示该位的掩码,置 1 表示将该组合纳入候选范围。下表行标题括号内为算法编号,列标题括号内为协议编号,单元格中的数字为组合对应的位图位号。例如,Ring 的算法编号为 1,Simple 的协议编号为 2,对应位号为1 × 3 + 2 = 5。NCCL_TUNING_MASK_GENERAL_KERNELS=(1ULL << 21)-1=0x1FFFFF,覆盖位 0~20;带"未实现"的位保留在编号空间中,但当前没有对应的可用组合。算法( algo)LL( proto=0)LL128( proto=1)Simple( proto=2)支持的集合通信操作 Tree(0) 0 1 2 AllReduce Ring(1) 3 4 5 AllReduce、Broadcast、Reduce、AllGather、ReduceScatter CollNetDirect(2) 6(未实现) 7(未实现) 8 AllReduce、AllGather、ReduceScatter CollNetChain(3) 9(未实现) 10(未实现) 11 AllReduce NVLS(4) 12(未实现) 13(未实现) 14 AllReduce、AllGather、ReduceScatter NVLSTree(5) 15(未实现) 16(未实现) 17 AllReduce PAT(6) 18(未实现) 19(未实现) 20 AllGather、ReduceScatter -
高位编码对称 kernel 与 CE 实现。 位 21~38 按
21 + symKernelId编码对称 kernel,位 39~40 按39 + ceMethodId编码 Copy Engine(CE)方法,位 41~63 在当前版本中未分配。NCCL_TUNING_MASK_SYM_KERNELS覆盖位 21~38,NCCL_TUNING_MASK_CE覆盖位 39~40,NCCL_TUNING_MASK_ALL覆盖位 0~40。下表列出对应的完整枚举名称。对称 kernel 由 GPU 的 SM 执行;CE 实现通过复制引擎完成通信,使用cudaMemcpyAsync、cudaMemcpyBatchAsync等接口提交复制操作,不是 SM 上执行的集合通信 kernel。位号 对应 kernel 或 CE 实现 实现方式 21 ncclSymkKernelId_AllReduce_AGxLL_RLL:低延迟实现。 22 ncclSymkKernelId_AllReduce_AGxLLMC_RLLMC:低延迟实现的多播变体。 23 ncclSymkKernelId_AllReduce_RSxTmaLD_AGxTmaSTTmaLD、TmaST:使用 TMA 的 Load、Store 实现。 24 ncclSymkKernelId_AllReduce_RSxLD_AGxSTLD、ST:基于 Load、Store 的实现。 25 ncclSymkKernelId_AllReduce_RSxLDMC_AGxSTMCLDMC、STMC:基于 Load、Store 的实现,使用多播相关机制。 26 ncclSymkKernelId_AllGather_LLLL:低延迟实现。 27 ncclSymkKernelId_AllGather_LLMCLLMC:低延迟实现的多播变体。 28 ncclSymkKernelId_AllGather_TmaSTTmaST:使用 TMA 的 Store 实现。 29 ncclSymkKernelId_AllGather_STST:基于 Store 的实现。 30 ncclSymkKernelId_AllGather_TmaSTMCTmaSTMC:使用 TMA 的 Store 实现的多播变体。 31 ncclSymkKernelId_AllGather_STMCSTMC:基于 Store 的实现的多播变体。 32 ncclSymkKernelId_AllGather_RailRing_LsaSTMCRailRing:跨节点环形通信;LsaSTMC:本地 Store 实现的多播变体。 33 ncclSymkKernelId_ReduceScatter_LLLL:低延迟实现。 34 ncclSymkKernelId_ReduceScatter_TmaLDTmaLD:使用 TMA 的 Load 实现。 35 ncclSymkKernelId_ReduceScatter_LDLD:基于 Load 的实现。 36 ncclSymkKernelId_ReduceScatter_LDMCLDMC:基于 Load 的实现,使用多播相关机制。 37 ncclSymkKernelId_ReduceScatter_RailA2A_LsaLDRailA2A:跨节点全互连通信;LsaLD:基于本地 Load 的实现。 38 ncclSymkKernelId_ReduceScatter_RailA2A_LsaLDMCRailA2A:跨节点全互连通信;LsaLDMC:基于本地 Load 的实现,使用多播相关机制。 39 ncclCeMethodId_AllGather_UC(单播)UC:基于 CE 的单播实现。 40 ncclCeMethodId_AllGather_MC(多播)MC:基于 CE 的多播实现。 -
从单次调用配置生成
algMask。 任务登记时,ncclCollConfigGetAlgMask将algSelection解析为位图,再与当前集合操作支持的候选集合取交集。例如,RING对应位 3、4、5,RING_SIMPLE只对应位 5。当前名称注册表包含已实现的传统组合与对称 kernel,不包含 CE 实现。未指定选择时,algMask=0表示自动选择;若配置无法解析,或交集为空,则forceAlgSelection!=0时直接返回错误,forceAlgSelection=0时保留零掩码,允许自动选择。这一步发生在ncclGetAlgoInfo之前,因此环境选择不会跳过任务配置的合法性检查。 -
环境变量。 直接限定候选名称的环境变量有三个,均支持按集合操作指定列表,例如
NCCL_ALGO="allreduce:Tree,Ring"。环境变量 选择对象及作用 NCCL_ALGO传统算法: Tree、Ring、CollNetDirect、CollNetChain、NVLS、NVLSTree、PAT。NCCL_PROTO传统协议: LL、LL128、Simple;与算法列表共同限定传统组合。NCCL_SYM_KERNEL具体对称 kernel,使用配置名称,例如 AllReduce_AGxLL_R;该名称对应枚举ncclSymkKernelId_AllReduce_AGxLL_R。三者分别设置算法、协议和对称 kernel 列表。传统组合须同时满足算法与协议列表;对称 kernel 使用自己的列表。只要
NCCL_ALGO或NCCL_SYM_KERNEL中至少一个为非空字符串,初始化时就先清空传统算法与对称 kernel 列表,再按各自配置启用,因此只设置NCCL_SYM_KERNEL时,传统候选可能全部被禁用。 -
按"环境变量 → 任务选择 → 默认范围"确定
tuningMask。 优先采用对当前集合操作生效的环境变量配置;没有环境选择时,采用任务通过algSelection指定的候选范围;两者均未指定时,使用完整的传统 kernel 候选范围。所有候选仍须满足环境配置、拓扑和能力要求。 -
候选可用条件与回退规则。 候选算法是否可用,取决于集合操作、拓扑、运行时能力及相关配置,包括
NCCL_COLLNET_ENABLE、NCCL_NVLS_ENABLE等能力开关。任务指定的候选范围内没有可用算法时,forceAlgSelection=0允许回退到满足环境与能力约束的其他传统 kernel 候选;非零则返回错误。该配置不改变环境选择优先于任务选择的顺序,具体分支见第 4.2.3.4 节。
-
-
补充操作与能力信息。 调优输入
input包含聚合任务agg的集合操作、主机与设备归约操作、数据类型、元素数量和 CTA 策略,并引用当前 communicator。collNetSupport、nvlsSupport分别表示 CollNet、NVLS 的支持状态;numPipeOps表示每个 Channel 的任务数量估计值。这三个参数均来自第 4.2.1 节的参数准备。
4.2.3.2 查询缓冲区注册状态
ncclGetAlgoInfo 通过 ncclGetRegBuff 查询缓冲区注册条件,将结果写入 input.regBuff,供后续调优使用。
- 查询目的。 已注册的用户缓冲区可能支持 GPU 直接访问、RDMA 或 NVLS 通信,减少中间缓冲区搬运,影响估算通信耗时和 Channel 需求。
regBuff会传给 tuner 插件,也用于满足条件时的 AllGather、ReduceScatter NVLS 效率策略选择。 - 查询范围与判定条件。 查询使用
agg保留的首任务缓冲区地址,并按聚合后的元素数量计算发送与接收范围。两端范围均有有效的用户注册记录,或者当前 CUDA Graph 捕获有效且启用了 Graph 注册时,regBuff为真。该值表示调优所依据的注册条件,不保证各项底层注册已经完成。 - NCCL 层的注册位置。 用户通过
ncclCommRegister登记缓冲区后,当前 communicator 的comm->regCache保存对应地址范围及引用信息。ncclGetRegBuff查询的就是这些记录,要求记录覆盖本次发送、接收范围,并具有有效的用户注册引用;此处只查询,不执行注册。 - 底层注册按通信路径完成。 后续阶段为各原任务按需建立底层资源:节点内 GPU 直接通信使用 CUDA IPC/VMM 映射;NET/RDMA 使用网络插件注册对象,RDMA 实现通常对应 NIC 可访问的 MR;CollNet 使用其插件注册对象;NVLS 使用 CUDA multicast 对象及地址映射。同一缓冲区可关联多种底层资源。
- 用户注册并非普通 AllReduce 的前提。 未预先调用
ncclCommRegister时,NCCL 可以使用普通缓冲区路径;符合条件的 CUDA Graph 路径还可自动注册。这里的普通缓冲区注册与ncclCommWindowRegister建立对称窗口属于不同机制。
4.2.3.3 执行调优计算
下图展开多 rank、传统内核 AllReduce 的调优计算。子调用按从上到下的顺序执行;插件分支仅在已加载 tuner 时进入。图中展开 Ring、Tree Cost Model,省略分发接口 ncclTuningComputeTuning。
- 评估算法与协议组合。 按 Tuning ID 确定对应的算法与协议组合,检查配置、能力和 Cost Model 支持,计算有效组合的估算耗时,形成临时候选链表。
- 调用调优插件。 已加载 tuner 插件时,由插件调整候选组合的估算通信耗时和 Channel 上限,为后续选择提供依据。
- 选择最优组合。
ncclTuningSelectBestTuning比较各候选组合的估算通信耗时,选择耗时最短的算法与协议组合。 - 计算资源并清理。 根据选定组合和消息规模计算 Channel、warp 数,将结果复制给调用方,再释放临时候选链表。

单 rank 时,ncclTuningCompute 直接选择 Ring/Simple,无需逐一评估其他算法与协议组合,也不调用调优插件;随后仍计算适用的资源参数。以下重点分析多 rank 分支。
-
筛选可用的算法与协议组合。
传统 kernel 路径以算法与协议组合作为调优对象,每个组合的 Tuning ID 与位图位号一一对应。例如,Tree/LL、Tree/LL128、Tree/Simple 分别对应 ID 0、1、2,完整映射见第 4.2.3.1 节。
ncclTuningComputeAllTunings根据掩码、配置和运行时能力,筛选 Cost Model 支持的算法与协议组合。通过检查且估算通信耗时大于零的组合保存在候选链表中,供后续比较和选择。AllReduce 的主要筛选条件如下。候选范围 当前 AllReduce 路径的关键条件 Ring、Tree 的 LL/LL128/Simple Cost Model 初始化后启用 AllReduce 组合;LL128 的默认启用还检查 GPU 能力、链路类型和驱动等条件。显式协议配置会影响默认筛选,具体规则见 isLL128Enabled的调用点。CollNet Direct/Chain Cost Model 表只提供 Simple,实现检查 collNetSupport、有效带宽和本地 rank 数等条件。NVLS、NVLS_TREE Cost Model 表只提供 Simple,实现检查 nvlsSupport和有效带宽;多节点 NVLS 还需要collNetSupport。PAT 当前 Cost Model 表为 AllGather/ReduceScatter 启用,AllReduce 不进入其候选。 symmetric kernel、CE 本调用的传统内核掩码将它们排除;共用调优引擎对其他调用者支持的实现不等于本路径候选。 -
估算通信耗时。
Ring 和 Tree 分别使用 NCCL 内置的 Ring Cost Model 和 Tree Cost Model。两者都按"延迟项+数据传输项"估算集合通信耗时,区别在于通信阶段、有效带宽和修正系数的计算方式。以下先说明 AllReduce 的计算规则,再用 A/B 聚合任务代入数值;忽略调优插件和自定义规则。
-
确定 Cost Model。
modelMap按 Tuning ID 选择对应的初始化函数和估算函数。初始化函数根据 communicator 的拓扑与协议保存generalLatencies、generalBandwidths;估算函数再结合本次消息大小计算timeUs。函数名中的Sim表示执行模型计算,本次调用不会启动 GPU 通信来测量时间。算法与协议 Tuning ID 初始化函数 估算函数 Tree / LL、LL128、Simple 0、1、2 ncclTuningTreeModelInitncclTuningTreeModelSimRing / LL、LL128、Simple 3、4、5 ncclTuningRingModelInitncclTuningRingModelSim两种 Cost Model 都含有协议效率、带宽上限等经验参数。它们在主机上计算预计集合通信耗时,供算法选择使用;该值不表示
ncclGetAlgoInfo自身的执行耗时。分发与默认常量见 src/tuning/cost_model.cc。 -
使用统一公式计算通信耗时。 Ring、Tree 分别计算各自的延迟与有效带宽,再代入公共函数
ncclTuningGetTime的统一公式。记S=input.nBytes,单位为 byte;L为对应 Cost Model 修正后的延迟,单位为 µs;B为对应 Cost Model 修正后的算法有效带宽,单位为 GB/s。对本文 Float32 任务,计算如下:textT = L × q + S / (1000 × B) // µs Ring: L = L_Ring, B = B_Ring, q = numPipeOps Tree: L = L_Tree, B = B_Tree, q = ceil(numPipeOps / NCCL_MAX_DEV_WORK_BATCH_COLLS)T是预计集合通信总耗时,包含延迟项L×q和数据传输项S/(1000×B),单位为 µs。后文分别说明L_Ring、B_Ring和L_Tree、B_Tree的计算方法。q表示模型累计延迟的次数。S使用聚合任务的消息字节数;Ring 的重复传输和 Tree 的归约、广播已折算进B,代入公式时不再给S乘通信量系数。numPipeOps表示每 Channel 的集合通信任务数估计值。NCCL_MAX_DEV_WORK_BATCH_COLLS表示一批最多可容纳多少个ncclDevWorkColl工作描述。1000用于把 byte 除以 GB/s 的结果换算成 µs。实现见 src/tuning/tuning_general.cc。 -
计算 Ring 的延迟与有效带宽。 AllReduce 沿环执行 ReduceScatter 和 AllGather。对
P=nRanks个 rank,每个阶段有P−1步,共K=2×(P−1)步;均分时每步发送S/P,每 rank 总发送量为S×K/P。设
C=graphs[Ring].nChannels,b为 Cost Model 从拓扑图中选取的每 Channel 带宽,先得到B0=b×C。单节点,或节点数不超过 2 且 GPU 计算能力小于 100 时,使用bwIntra;其余情况使用bwInter。协议按下表把B0修正为Bbus,再折算为算法有效带宽:Ring 协议 Bbus的计算LL min(llMaxBw, B0 × 0.5)LL128 min(B0 × 0.92, C × perChMaxRingLL128Bw)Simple B0textB_Ring = Bbus × P / K 单节点:L_Ring = Lbase + K × Lintra 多节点:L_Ring = Lbase + (K − Kinter) × Llocal + Kinter × Linter Kinter = 2 × (nNodes − 1)Lbase是算法与协议对应的基础延迟,Lintra是按 NVLink 或 PCIe 类别查得的节点内单步延迟。多节点的Llocal还取网络提交开销与Lintra的较大值,Linter包含网络延迟及拓扑图的附加延迟。对单节点,数据传输项等价于S×K/P ÷ Bbus,因此环上的重复传输只计入一次。ncclTuningRingModelSim还会在多节点 AllReduce、单节点 Blackwell NVLink 等条件下修正 Simple 的延迟;后面的单节点 PCIe 示例不触发该分支。实现见 src/tuning/ring.cc。 -
计算 Tree 的延迟与有效带宽。 Tree 先向上归约,再向下广播。模型分别计算节点内链与节点间树的深度,用往返两遍的路径估算延迟:
textDlocal = nRanks / nNodes − 1 Dinter = floor(log2(nNodes)) L_Tree = Lbase + 2 × (Dlocal × Lintra + Dinter × Linter)nRanks为 rank 总数,nNodes为节点数。Dlocal表示节点内链的估计深度,按每节点平均 rank 数减一计算,其中nRanks / nNodes使用整数除法;Dinter表示节点间树的估计深度,取节点数以 2 为底的对数并向下取整。节点内链的初始化见 src/graph/connect.cc 的ncclTopoPreset。Lbase是算法与协议对应的基础延迟,Lintra、Linter分别为模型采用的节点内、节点间单步延迟;系数2表示向上归约和向下广播两个方向的延迟累计。带宽同样从
B0=b×C出发,其中C=graphs[Tree].nChannels。计算能力小于 100 时,至多两个节点使用bwIntra,更多节点使用bwInter;计算能力至少为 100 时,取两者较小值。AllReduce 先执行B1=min(B0×0.92, C×perChMaxTreeBw),再按协议修正:Tree 协议 协议修正后的 B2LL min(B1 / 3.8, llMaxBw)LL128 min(B1 × r, C × perChMaxTreeLL128Bw);单节点r=7/9,多节点r=120/128Simple B1若
maxTreePattern==NCCL_TOPO_PATTERN_TREE,再将B2乘以0.85。随后乘以0.5,把归约、广播两个方向折算为算法有效带宽。每次估算时,还按消息大小查treeCorrectionFactor:textBstored = B2 × patternFactor × 0.5 k = floor(log2(S >> 6)) B_Tree = Bstored × treeCorrectionFactor[proto][k] // 0 ≤ k < 23patternFactor在上述 Tree pattern 条件下为0.85,否则为1;k不在查表范围内时,直接使用Bstored。树深度影响延迟项,带宽项采用包含流水传输效果的经验模型,因此不会再按链深度把整条消息的传输时间乘三次。实现见 src/tuning/tree.cc;修正表见 src/tuning/cost_model.cc。 -
设置 A/B 聚合任务的示例参数。 以下仅比较 Ring / Simple 与 Tree / Simple,采用单节点四块 Hopper GPU,每 GPU 一个 rank,四块 GPU 分别通过 PCIe 5.0 x16 链路连接到同一个 PCIe switch。假设 P2P 路径可用,并采用下表中的拓扑图参数。
24 GB/s是为演算设置的模型输入,不是 PCIe 5.0 的标称带宽或实测结果;实际分析应读取 communicator 已生成的拓扑图。参数 示例取值与来源 每 rank 的 A/B 数据 A: 1536×4=6144 byte;B:3072×4=12288 byte聚合消息大小 S(1536+3072)×4=18432 byte=18 KiB;第 3 节用于排序的聚合流量为 36 KiB,本处使用消息大小 18 KiB节点与 rank 数 nNodes=1,nRanks=4,无 NVLink 路径,延迟常量使用NCCL_HW_PCI类别拓扑带宽与 Channel 数 假设 Ring、Tree 均为 bwIntra=24 GB/s、graphs[algo].nChannels=2,因此B0=48 GB/s延迟累计次数 假设 comm->nChannels=4;A/B/C/D 共四个原任务,numPipeOps=ceil(4/4)=1,Ring、Tree 的q均为 1Tree pattern 假设 maxTreePattern=NCCL_TOPO_PATTERN_BALANCED_TREE,patternFactor=1默认 Simple 延迟 Ring: Lbase=8.4 µs、Lintra=5.7 µs;Tree:Lbase=8.4 µs、Lintra=4.0 µs默认 Tree 带宽上限 单节点 Hopper 的 perChMaxTreeBw=38.7 GB/sgraphs[algo].nChannels用于模型的带宽计算,comm->nChannels用于调用前的任务数估计,二者作用不同;后续资源选择还会计算实际使用的 Channel 数。PCIe 类别的默认延迟常量按算法与协议区分,本例的5.7 µs和4.0 µs是模型参数,不能解释为 PCIe 5.0 链路的实测单跳延迟。下图把物理连接与调优规模分开表示:四个 rank 使用相同的任务规模,A/B 只在调优描述中累加元素数量,原任务与缓冲区仍分别保留。
- 上半部分表示通信载体。 四块 GPU 等距连接到同一个 PCIe switch,后续 Ring、Tree 图中的逻辑邻接均经过这一物理拓扑。
- 下半部分表示消息规模。 A 与 B 按 1∶2 的比例组成 18 KiB 的调优规模;
S取每 rank 的输入大小,不再乘四个 rank。

-
代入 Ring / Simple。 四个 rank 的 ReduceScatter、AllGather 各三步,共六步。Simple 保留
Bbus=48 GB/s,按P/K=4/6折算后,B_Ring=32 GB/s。textL_Ring = 8.4 + 6 × 5.7 = 42.6 µs B_Ring = (24 × 2) × 4 / 6 = 32 GB/s 传输项 = 18432 / (1000 × 32) = 0.576 µs T_Ring = 42.6 × 1 + 0.576 = 43.176 µs下图用环形连接、等宽数据块与六个阶段解释 Ring 的计算。环序取 GPU 0→1→2→3→0;实际顺序由拓扑搜索确定。为解释流量比例,按两个模型 Channel 均分数据;该均分仅用于推导,不代表后续 kernel 的实际分段结果。
- 沿环分块传输。 每个模型 Channel 分担
18/2=9 KiB,再按四个 rank 分成四块,每块2.25 KiB;每步中各 rank 同时向下一个 rank 发送一块。 - 累计两阶段的发送量。 每 rank 每 Channel 共发送
6×2.25=13.5 KiB,两个 Channel 合计 27 KiB;27648/(1000×48)=0.576 µs,与算法有效带宽公式一致。 - 合并延迟项与传输项。 六步的延迟加上基础延迟得到 42.6 µs,再加 0.576 µs。图中的六步用于解释模型的阶段数,不表示六次独立的 kernel 启动。

- 沿环分块传输。 每个模型 Channel 分担
-
代入 Tree / Simple。 单节点四 GPU 的链深度为三层,向上归约和向下广播各经过三层,因此延迟为
8.4+2×3×4.0=32.4 µs。Simple 不增加协议带宽折扣,但仍保留 Tree 的 AllReduce 修正和消息大小修正。textB0 = 24 × 2 = 48 GB/s B1 = min(48 × 0.92, 2 × 38.7) = 44.16 GB/s Bstored = 44.16 × 1 × 0.5 = 22.08 GB/s S >> 6 = 18432 / 64 = 288 k = floor(log2(288)) = 8 treeCorrectionFactor[Simple][8] = 0.7 B_Tree = 22.08 × 0.7 = 15.456 GB/s L_Tree = 8.4 + 2 × 3 × 4.0 = 32.4 µs 传输项 = 18432 / (1000 × 15.456) ≈ 1.193 µs T_Tree = 32.4 × 1 + 1.193 ≈ 33.593 µs下图左右两列是同一条节点内链的两个通信方向,示例取 GPU 0 为根、GPU 3 为叶子;下方用逐渐缩短的带宽条表示模型的各次折算。
- 先归约,再广播。 左列从叶子向根归约,右列从根向叶子广播;每列三条边,对应延迟公式中的
2×3。这两列表示同一个 Channel 的两个方向。 - 逐项折算有效带宽。 带宽从 48 降到 44.16 GB/s;本例未触发 77.4 GB/s 的上限。再经双向折算和
k=8的消息大小修正,得到 15.456 GB/s。 - 计算最终耗时。 用修正后的带宽计算传输项,与 32.4 µs 延迟项相加。
0.92、0.5和0.7分别来自不同规则,计算时均须保留。

- 先归约,再广播。 左列从叶子向根归约,右列从根向叶子广播;每列三条边,对应延迟公式中的
-
比较本例结果。 在上述输入和默认常量下,Tree / Simple 的估算耗时较短。它的有效带宽低于 Ring / Simple,但本例消息只有 18 KiB,较低的延迟项主导了结果。
组合 延迟项(µs) 有效带宽(GB/s) 传输项(µs) 总耗时(µs) Ring / Simple 42.600 32.000 0.576 43.176 Tree / Simple 32.400 15.456 1.193 33.593 该比较只覆盖两个 Simple 组合。实际调优还会评估通过筛选的 LL、LL128 等组合,因此本例不能据此确定完整候选集合的最终选择;表中结果也属于 Cost Model 演算值,实际耗时需通过运行测量。
-
-
调整估算耗时。
有效的算法与协议组合形成后,若已加载 tuner 插件,
ncclTuningCompute调用comm->tuner->getCollInfo,由插件调整表中各组合的估算通信耗时,并设置 Channel 上限。未加载插件时,保留 Cost Model 给出的估算耗时,直接进入组合选择。cppncclResult_t (*getCollInfo)(void* context, ncclFunc_t collType, size_t nBytes, int numPipeOps, float** collCostTable, int numAlgo, int numProto, int regBuff, int* nChannels); -
选择最优组合。
ncclTuningSelectBestTuning遍历候选链表,比较各组合的估算通信耗时,将耗时最短的算法与协议组合保存到bestTuning,供后续资源计算使用。已加载插件时,比较插件调整后的耗时;否则比较 Cost Model 给出的耗时。通用选择器在
selectionTimeUs>0时优先使用该比较值,否则使用timeUs;本文传统 Ring/Tree 路径使用timeUs。比较值相等时,保留链表遍历中先遇到的组合。 -
计算资源并清理。
选中算法和协议后,
ncclTuningGetChannels从 Channel 数、该算法/协议的最大线程数和工作量阈值出发,优化资源数量。它对 Ring/Tree、NVLS 和 CollNet 使用不同规则;这里先沿本文常用的 Ring/Tree 分支说明。- 缩减 Channel。
nc表示本次任务拟使用的 Channel 数,初始取 communicator 可用的 Channel 数comm->nChannels,作为 Ring/Tree 调优的起始上限;nt表示每个 Block 的线程数,初始取当前算法与协议对应的最大线程数maxThreads[algo][proto]。当nBytes < nc × nt × threadThreshold且nc>=2时,逐次减少nc,使小消息使用较少的 Channel。 - 缩减工作线程。 使用调整后的
nc,重新检查nBytes < nc × nt × threadThreshold。若条件成立且nt % 128 == 0,则将nt减半,再用更新后的nt检查该条件;当工作量条件不成立,或nt无法被 128 整除时,结束线程缩减。NVLS、NVLS_TREE 和 CollNet Direct 跳过这一通用缩减循环。 - 补充协议线程。 Simple 的 Ring 增加一个同步 warp,Tree 增加四个 warp;随后保证至少三个 warp。当前 Tree 实现最终将
nt设为NCCL_MAX_NTHREADS,Tree 的最终线程数以这次覆盖为准。 - 应用插件上限。 若插件给出的
maxChannels为正且小于nc,把nc限制到该值;否则把maxChannels更新为nc。结果同时写入nChannels=nc、nWarps=nt/WARP_SIZE。
threadThreshold表示每线程工作量的调优阈值,按字节计。条件nBytes < nc × nt × threadThreshold表示:按当前 Channel 数和每 Block 线程数分摊后,每线程的估计工作量低于阈值,需要考虑缩减资源。Ring/Tree 的默认值如下。
协议 默认常量 Tree 阈值(byte/线程) Ring 阈值(byte/线程) LL NCCL_LL_THREAD_THRESHOLD=88 8 × nRanksLL128 NCCL_LL128_THREAD_THRESHOLD=88 8 Simple NCCL_SIMPLE_THREAD_THRESHOLD=6464 64 - 缩减 Channel。
4.2.3.4 扩大候选范围并重新调优
任务指定的算法范围内未选出算法时,ncclGetAlgoInfo 可扩大候选范围重新调优;是否允许采用范围之外的结果,由 forceAlgSelection 决定。
- 重算的触发条件。 本次调优采用任务指定的算法范围(
effAlgMask!=0),且首次未选出算法时,扩大到完整传统 kernel 候选范围重新调优。未采用任务指定范围,或首次已选出算法时,不触发这一重算。 - 重算仍受环境与能力约束。 扩大范围只解除本次任务的算法范围限制,环境配置、拓扑、运行时能力和 Cost Model 的支持条件继续生效。
- 允许回退时采用新结果。 重算成功且
forceAlgSelection=0时,采用自动选择的结果;forceAlgSelection!=0时,因原指定范围无法满足要求,即使重算成功,也返回ncclInvalidArgument。 - 重算失败时返回错误。 重算中发生的内存分配失败、插件错误等会向上传递,本次调用不进入调优结果写回。
4.2.3.5 返回调优结果
计算成功且通过候选限制检查后,ncclGetAlgoInfo 将 bestTuning 的字段写回临时聚合任务 agg。
-
写入算法与协议。
bestTuning.algo、bestTuning.proto分别写入agg.algorithm、agg.protocol。 -
写入资源参数。
bestTuning.nWarps表示每个 CUDA Block 配置的 warp 数,写入agg.nWarps;每个 warp 包含 32 个线程,因此每 Block 的线程数为nWarps×32。bestTuning.maxChannels表示调优给出的 Channel 数量上限,非零时更新agg.nMaxChannels,为零时保留传入值。后续编排还会根据minCTAs、maxCTAs等约束调整该上限,再确定任务实际使用的 Channel 数及编号。 -
写入预计集合通信耗时。 调用方提供
simInfo时,将bestTuning.timeUs写入simInfo->estimatedTime,单位为微秒(μs)。该值表示所选算法与协议执行当前聚合任务的预计集合通信耗时,由 Cost Model 估算,并可由调优插件调整。 -
示例中的四个任务。 沿用第 4.2.3.3 节的单节点四块 Hopper GPU、PCIe 5.0 拓扑及参数:Ring、Tree 的
bwIntra=24 GB/s、graphs[algo].nChannels=2,comm->nChannels=4,Tree pattern 为NCCL_TOPO_PATTERN_BALANCED_TREE。本例将协议限定为 Simple,其余未指定的软件配置采用默认值,忽略调优插件。拓扑带宽和 Channel 数沿用前文的示例假设,不属于软件默认常量。-
确定可用组合。 本例没有 NVLink/NVSwitch 路径,NVLS、NVLS_TREE 缺少可用拓扑;CollNet 在默认配置和本例拓扑下不可用,PAT 不支持 AllReduce,symmetric kernel 与 CE 不在本次传统 kernel 候选范围内。因此,在 Simple 协议约束下比较 Ring / Simple 与 Tree / Simple。
-
比较三次调优的耗时。 A/B 共享一次调优,规模为
(1536+3072)×4=18 KiB;C 独立调优,规模为1536×8=12 KiB;D 独立调优,规模为4608×4=18 KiB。四个原任务使numPipeOps=ceil(4/4)=1,三次调用均沿用这一值。调优调用 消息大小 Ring / Simple(µs) Tree / Simple(µs) 选中组合 A/B 共享 18 KiB 43.176 33.593 Tree / Simple C 独立 12 KiB 42.984 33.096 Tree / Simple D 独立 18 KiB 43.176 33.593 Tree / Simple -
计算 Channel 上限与 warp 数。 默认初始值为
nc=4、nt=NCCL_SIMPLE_MAX_NTHREADS=512,Simple 的threadThreshold=64。两种消息大小均使nc从 4 依次缩减到 1;此时工作量阈值为1×512×64=32 KiB,仍需进入线程缩减。调优调用 消息大小 线程缩减后的 ntSimple 补充线程后 Tree 最终覆盖值 A/B、D 18 KiB 256 256+4×32=384640 C 12 KiB 128 128+4×32=256640 当前 Tree 实现最后统一设置
nt=NCCL_MAX_NTHREADS=640,所以三次调优均得到bestTuning.nWarps=640/32=20、bestTuning.nChannels=1、bestTuning.maxChannels=1。 -
按原任务列出返回结果。
ncclGetAlgoInfo先将结果写入各次调用的临时agg,第 4.3 节再把算法、协议和资源字段写回原任务。下表中的耗时属于该任务参与的调优调用。原任务 所属调优调用 算法 / 协议 nWarpsnMaxChannels该次调优耗时(µs) A A/B 共享,18 KiB Tree / Simple 20 1 33.593 B A/B 共享,18 KiB Tree / Simple 20 1 33.593 C C 独立,12 KiB Tree / Simple 20 1 33.096 D D 独立,18 KiB Tree / Simple 20 1 33.593 nWarps=20表示每 Block 配置 640 个线程。D 的maxCTAs=4是后续编排的上限约束,不要求使用 4 个 Channel;本例调优得到的上限 1 无需因该配置再次缩减。A/B 两行的 33.593 µs 是同一次聚合调优的估算值,不能分别当作 A、B 单独执行的耗时。timeUs保存在bestTuning中,仅当提供simInfo时写入estimatedTime。
-
4.3 结果写回
ncclPrepareTasks 将调优结果写回各原任务,再按选定算法的资源类别,将任务追加到 collBins[isCollnet][isNvls]。四个队列用于组织后续调度所需的任务类别。
-
共享调优结果,保留独立任务。 同一聚合区间内的任务获得相同的算法、协议、Channel 上限、每 Block 的 warp 数和设备函数标识,同时保留各自的元素数量、缓冲区和 CTA 配置。
-
按算法确定目标队列。
isCollnet、isNvls分别作为第一、第二维索引,表示任务所属的 CollNet、NVLS 资源类别。AllReduce 的分类规则如下。选定算法及条件 isCollnetisNvls目标队列 Ring、Tree 0 0 collBins[0][0]NVLS(单节点)或 NVLS_TREE 0 1 collBins[0][1]CollNet Direct、CollNet Chain 1 0 collBins[1][0]多节点 NVLS 1 1 collBins[1][1] -
按处理顺序追加原任务。 各原任务依次加入对应队列的尾部,因此同一队列保留任务的处理顺序。采用不同算法、数据类型或归约操作的任务,只要两个分类标志相同,就可以进入同一队列。选用 LL 协议时,任务的
trafficBytes另乘以 4,供后续调度估算使用。
沿用第 4.2.3.5 节的结果,A、B、C、D 均选择 Tree / Simple,两个分类标志均为 0。按本例的任务处理顺序,collBins[0][0] 中的队列为 A → B → D → C ,其余三个队列为空。A/B 共享调优后仍保留两个任务节点,D 的 maxCTAs=4 继续作为后续编排约束。
4.4 输出任务队列
所有任务分类完成后,ncclPrepareTasks 通过 ncclIntruQueueTransfer,将 collBins[2][2] 中的任务依次追加到 planner->collTaskQueue。
- 按固定顺序拼接。 四个来源队列的顺序为
collBins[0][0] → collBins[0][1] → collBins[1][0] → collBins[1][1],空队列不增加任务。 - 保留队列内部顺序。 每个来源队列整体追加到目标队列尾部,队内任务的相对顺序保持不变;目标队列原有任务仍位于前面。
- 转移后清空分类队列。 四个
collBins队列均变为空队列,原任务由planner->collTaskQueue持有,供后续准备与编排使用。
沿用第 4.3 节的示例,只有 collBins[0][0] 非空。目标队列初始为空时,最终 planner->collTaskQueue 的任务顺序为 A → B → D → C,仍保留四个独立任务。
5. 连接需求与 Work 准备
ncclPrepareTasks 遍历 planner->collTaskQueue,根据算法和注册结果汇总连接需求,并为 NVLS 类任务提前准备 Work。
-
更新 NVLS 注册信息。
ncclRegisterCollNvlsBuffers在满足条件时完成 NVLS 缓冲区注册,更新任务的注册类型和 Channel 上限,并返回注册地址。NVLS 注册成功时,regNeedConnect=false,表示后续无需再为该任务提出 NVLS 缓冲区资源需求;普通 Ring/Tree 任务不在此处执行 NVLS 注册。 -
按算法登记连接需求。 仅在
runtimeConn=true且当前算法的initAlgoChannels尚未置位时,才登记新需求。需要连接的算法写入algoNeedConnect,并将needConnect置为真;initAlgoChannels同时置位,避免同一算法重复登记。该标记表示已登记需求,实际连接由后续阶段建立。当前算法 需要登记的连接需求 Ring、Tree、CollNet 等其他算法 登记当前算法。 NVLS 仅在 regNeedConnect=true时登记 NVLS。NVLS_TREE 登记 NVLS_TREE;若 regNeedConnect=true且 NVLS 尚未置位,还需补充 NVLS 需求。 -
为 NVLS 类任务保留 Work。 NVLS、NVLS_TREE 任务将缓冲区、归约参数、warp 数和注册状态等执行信息保存到 Work:带有
NCCL_NVLS_REG_BUFFER标记时使用ncclDevWorkCollReg,并附带 NVLS 注册地址;其余情况使用ncclDevWorkColl。这些 Work 按任务顺序进入planner->tmpCollWorkQueue,供后续ncclTasksRegAndEnqueue复用。Ring/Tree 等任务的 Work 留到后续阶段构造。
沿用本文四个任务均选择 Tree / Simple 的示例:若启用了运行时连接且 Tree 尚未置位,只需登记一次 Tree 连接需求;这四个任务均不会在本段代码中向 tmpCollWorkQueue 追加 Work。
6. 总结
任务预处理将独立的集合通信请求整理为带有算法、协议和资源参数的任务队列,供后续建立通信连接和生成执行计划使用。
- 任务类别与聚合条件决定共享调优范围。 集合操作、设备归约操作和数据类型相同的任务归为一类;同类任务中,流量规模满足条件且未被隔离的连续任务可以共享一次调优。共享的是调优结果,各任务仍保留独立的元素数量、缓冲区和调用配置。
- 算法选择受配置、能力和估算耗时共同约束。 环境配置、任务要求、拓扑及运行时能力决定可用的算法与协议组合,Cost Model 估算其通信耗时,插件可调整估算值。选择器据此确定最优组合;任务指定范围无法满足时,是否允许回退由
forceAlgSelection决定。 - 调优参数与实际资源分配分阶段确定。
nWarps表示每 Block 的 warp 数,每个 warp 包含 32 个线程;调优给出的 Channel 上限还要结合逐任务 CTA 配置和可用资源,由后续编排确定实际数量与编号。 - 最终队列保留原任务并按资源类别组织。 调优结果写回各原任务后,按
isCollnet、isNvls分类,再按固定顺序汇入planner->collTaskQueue。分类与队列拼接改变任务的组织顺序,共享调优的任务仍是独立节点。 - 记录连接需求,提前准备 NVLS Work。
algoNeedConnect、needConnect汇总待建立的算法连接需求;NVLS 类任务还会提前处理注册并保留可复用的 Work。这些主机侧结果供后续阶段使用,预处理完成不代表 GPU 集合通信已经执行完成。
在本文四 GPU PCIe 拓扑、限定 Simple 协议且忽略插件的示例中,A/B 共享调优,C、D 分别调优;三次调用均选择 Tree / Simple,返回每 Block 20 个 warp 和 Channel 上限 1。四个原任务最终按 A → B → D → C 排入 planner->collTaskQueue。
6.1 关键参数与作用
下表保留影响任务分类、共享调优和资源分配的主要参数。单次调用配置通过 ncclCollConfig_t 传入,communicator 配置使用 ncclConfig_t。
| 参数或输入 | 主要影响 | 设置方式 |
|---|---|---|
Group 边界与 comm |
确定批次范围;不同 communicator 分别整理任务。 | ncclGroupStart、ncclGroupEnd 及集合通信接口的 comm 参数。 |
集合通信操作、op、datatype |
决定任务类别;集合通信操作、设备归约操作和数据类型相同,才可归为一类。 | 选择集合通信接口,传入 op、datatype。 |
count |
决定流量估计和消息大小,影响排序、聚合范围及估算通信耗时。 | 集合通信接口的 count 参数。 |
minCTAs、maxCTAs、nvlsCTAs |
约束后续 CTA/Channel 分配;单次调用显式设置会使任务独立调优。 | 单次调用或 communicator 配置;对应环境变量为 NCCL_MIN_CTAS、NCCL_MAX_CTAS、NCCL_NVLS_NCHANNELS。 |
algSelection、forceAlgSelection |
前者限定候选实现,非空时使任务独立调优;指定范围无法满足时,后者为 0 允许回退,非零则报错。 | 单次调用配置。 |
CTAPolicy |
影响执行路径和调优策略;解析后的策略不同于 communicator 默认策略时,使任务独立调优。 | 单次调用或 communicator 配置;环境变量 NCCL_CTA_POLICY。 |
NCCL_ALGO、NCCL_PROTO、NCCL_SYM_KERNEL |
限定算法、协议或 symmetric kernel;对当前集合通信操作生效的环境强制选择优先于 algSelection。 |
程序启动前设置环境变量。 |
| 用户缓冲区及注册状态 | 影响可用通信实现、估算耗时和资源需求。 | 传入 sendbuff、recvbuff,通过 ncclCommRegister 等接口注册缓冲区。 |
| 线程数与工作量阈值 | 影响 Channel 数和线程数的缩减;最终 nWarps 还受算法与协议规则约束。 |
环境变量 NCCL_NTHREADS、NCCL_LL128_NTHREADS、NCCL_THREAD_THRESHOLDS。 |
此外,rank 数、节点数、硬件拓扑和算法能力共同决定候选组合是否可用及其估算耗时。trafficBytes、aggIsolate 等字段由 NCCL 根据任务输入生成。
6.2 算法总结
Ring、Tree 的选择取决于延迟项与数据传输项的相对大小:小消息重点比较延迟,大消息重点比较有效带宽。下表依据第 4.2.3.3 节的通信组织与 Cost Model,归纳传统 AllReduce 路径中两种算法的特点和选择倾向。这里的消息大小指本次调优的 input.nBytes;共享调优时,使用聚合后的规模。
| 影响因素或场景 | Ring 的特点 | Tree 的特点 | 选择倾向与判断依据 |
|---|---|---|---|
| 组网与通信组织 | 沿逻辑环执行 ReduceScatter 和 AllGather,各 rank 分块收发,通过流水线持续传输数据。 | 沿树向上归约、向下广播;模型分别估算同一节点内各 rank 沿链式连接通信的开销,以及不同节点之间沿树形连接通信的开销。 | 环上路径能提供较高有效带宽时,大消息倾向 Ring;树形路径能减少跨节点延迟时,小消息倾向 Tree。 |
| 小消息,延迟项占主导 | AllReduce 共经过 2×(nRanks−1) 个通信步骤,步骤累计的延迟对小消息影响较大。 |
模型按归约、广播经过的路径深度累计延迟。节点间树的深度随节点数呈对数增长,相比 Ring 按节点数累计的跨节点步骤,更有利于节点较多时的小消息通信。 | 通常倾向 Tree。 小消息对步骤延迟更敏感,Tree 的延迟项较低时,更有利于缩短总耗时。 |
| 大消息,数据传输项占主导 | 将数据分块并采用流水线传输,有利于利用链路带宽;较高的有效带宽可缩短大消息的数据传输时间。 | 支持按数据块组织流水线传输;模型将归约、广播两个阶段的传输开销计入有效带宽。有效带宽低于 Ring 时,相同消息大小对应的数据传输时间更长。 | 倾向 Ring。 前提是 Ring 的有效带宽更高,且传输时间的节省超过额外的步骤延迟。 |
| 节点数增加,或跨节点延迟较高 | 模型中的跨节点延迟按 2×(nNodes−1) 步累计,节点增多会放大这一开销。 |
模型用 2×floor(log2(nNodes)) 乘以跨节点单步延迟,估算归约、广播的跨节点延迟;节点内链式传递的延迟另行累计。 |
延迟敏感时倾向 Tree。 节点增多时,Tree 的跨节点延迟增长较慢;大消息仍需结合有效带宽比较总耗时。 |
| NVLink、PCIe 或跨节点网络的带宽与延迟不同 | 使用 Ring 拓扑图的带宽、Channel 数和对应链路类别的延迟参数,路径带宽下降会增加数据传输时间。 | 节点内采用 NVLink 或 PCIe 的单步延迟,节点间采用网络延迟并计入拓扑附加延迟。模型先将每 Channel 的拓扑带宽乘以 Channel 数,再按协议效率、树形结构和消息大小修正,得到有效带宽。 | 小消息优先考虑延迟项较低者,大消息优先考虑有效带宽较高者。 将当前链路的延迟与带宽代入模型,选择估算总耗时较低的组合。 |
| 同一 Group 中有多个可共享调优的小任务 | 模型按 numPipeOps 次累计延迟。 |
模型将每 Channel 的任务数估计值换算为批次数,再按批次累计延迟,次数为 ceil(numPipeOps / NCCL_MAX_DEV_WORK_BATCH_COLLS)。 |
当 numPipeOps>1 且延迟项占主导时,倾向 Tree。 其模型累计延迟的次数少于 Ring。 |
| 本文四 GPU PCIe 示例:18 KiB、限定 Simple、无插件 | 模型估算为 43.176 µs,有效带宽较高,但延迟项较大。 | 模型的延迟项为 32.4 µs,数据传输项约为 1.193 µs,合计 33.593 µs;耗时主要来自延迟项。 | 选择 Tree / Simple。 较低的延迟项使估算总耗时比 Ring / Simple 少约 9.583 µs。 |
"小消息"和"大消息"的分界随拓扑、rank 数、协议及 Channel 资源变化。实际调优会比较配置允许且能力满足的算法与协议组合,并接受 tuner 插件调整;表中的倾向用于理解选择原因,具体结果以当前输入下的调优输出为准。
7. 参考资料
| 源码位置 | 核对内容 |
|---|---|
| src/nccl.h.in、src/collectives.cc | Group 与 AllReduce 公开接口、单次调用配置及初始化约定。 |
| src/group.cc | 预处理入口、回调、asyncJobLaunch、job 结果交接和连接需求的消费。 |
| src/enqueue/enqueue.cc | collTaskAppend 的任务登记,ncclPrepareTasks 的输入整理、分类聚合、结果写回、队列拼接和需求记录;ncclGetAlgoInfo 的输入输出。 |
| src/include/comm.h | task、planner、sorter 的结构和排序器插入、取出操作。 |
| src/include/enqueue.h、src/include/device.h | 接口声明、数量换算及 ncclDevFuncId 的主机表索引。 |
| src/include/utils.h、src/misc/utils.cc | 队列、内存池与内存栈操作,以及 ncclThreadJoin。 |
| src/include/alloc.h、src/include/checks.h | 主机分配、错误返回、NEW_NOTHROW 和线程创建宏。 |
| src/os/linux.cc、src/include/compiler/gcc.h | Linux CPU 亲和性接口、原子状态读写和位操作内建函数。 |
| src/include/bitops.h、src/include/strongstream.h | 流量区间编码及已记录 Graph 状态的有效性检查。 |
| src/include/tuning.h、src/tuning/tuning_int.h | 调优输入、结果、候选链表和接口声明。 |
| src/include/plugin/nccl_tuner.h、src/include/sym_kernels.h、src/include/ce_coll.h | 位图使用的算法、协议、对称内核与 CE 方法编号。 |
| src/tuning/tuning.cc、src/tuning/tuning_general.cc | 候选遍历、插件调用、估算耗时比较、Channel/warp 参数及结果清理。 |
| src/tuning/ring.cc、src/tuning/tree.cc、src/tuning/collnet.cc、src/tuning/nvls.cc | 各算法的 Cost Model 输入、能力检查和耗时估算。 |
| src/include/plugin/tuner/tuner_v6.h、src/plugin/tuner.cc | tuner 回调定义、加载与生命周期。 |
| src/init.cc、src/graph/connect.cc、src/graph/trees.cc | ring index、Channel 拓扑和双树构造。 |
| src/device/all_reduce.h、src/device/prims_simple.h | Ring/Tree 执行组织、chunk 尾块与 Simple slice 推进。 |
| src/config/collconfig.cc、src/include/config/collconfig.h | 聚合隔离条件和资源配置解析优先级。 |
| src/config/algorithm_registry.cc、src/config/algorithm_parser.cc、src/tuning/cost_model.cc | 候选位图、名称解析及环境配置对候选集合的影响。 |
| src/register/coll_reg.cc | 提前注册接口的 regNeedConnect 输出及资源参数调整。 |