Compute Fabric Transport

Starting with NCCL 2.31, the Device API includes Compute Fabric Transport (CFT) helpers for kernels that communicate through CUDA fabric logical endpoints. CFT is useful when an application launches custom device-side communication kernels on CFT-capable systems and needs direct fabric put, get, reduction, or barrier operations.

Requirements

CFT kernels require hardware, driver, and CUDA Toolkit support for fabric logical endpoints and fabric PTX instructions. In this release, the example CFT barrier program documents the practical requirements as CUDA Toolkit 13.3 and SM_100 architectures.

Before using CFT in an application:

  • Allocate and register symmetric memory windows with NCCL.

  • Create a device communicator with CFT capabilities in ncclDevCommRequirements.

  • Use CFT team helpers to address peers by CFT team rank.

  • Query logical endpoint IDs and offsets before issuing CFT operations.

  • Match the number of requested CFT barriers to the number of barrier slots used by the kernel.

Device Communicator Setup

The host code requests CFT capability when creating the device communicator.

ncclDevComm devComm;
ncclDevCommRequirements reqs = NCCL_DEV_COMM_REQUIREMENTS_INITIALIZER;

reqs.cftCaps = NCCL_CFT;
reqs.cftBarrierCount = nCTAs;

NCCLCHECK(ncclDevCommCreate(comm, &reqs, &devComm));

Request NCCL_CFT_MULTIMEM in addition to NCCL_CFT when a kernel uses multicast CFT operations or multimem CFT barriers:

reqs.cftCaps = NCCL_CFT | NCCL_CFT_MULTIMEM;

If a communicator cannot provide the requested CFT capability on all ranks, ncclDevCommCreate() fails.

Teams and Endpoints

CFT operations may address peers through CFT teams rather than directly through world ranks. Use ncclTeamCft() or ncclTeamCftMultimem() on the host side, and the corresponding device overloads in kernels, to obtain the team layout.

Use logical endpoint query helpers to translate a registered window, byte offset, and peer into the values consumed by ncclCft operations. For device-side kernels, ncclGetCftLeInfo() accepts a CFT-team peer rank and ncclGetPeerLeInfo() accepts a world-rank peer.

ncclTeam cftTeam = ncclTeamCft(devComm);
ncclCftLeId leId;
size_t leOffset;

ncclGetCftLeInfo(win, byteOffset, peerCft, cftTeam, devComm,
                 &leId, &leOffset);

Host code can use ncclGetCftDeviceLeInfo(), ncclGetPeerDeviceLeInfo(), and ncclGetMultimemDeviceLeInfo(). If host-side endpoint queries are needed before creating a CFT-enabled device communicator, configure hostCftMode in ncclConfig_t during communicator initialization.

CFT Operations

A CFT kernel stages data through shared memory, creates a ncclCft object, issues operations, and then submits and flushes the work.

__global__ void cftPutKernel(ncclDevComm devComm, ncclWindow_t win) {
  __shared__ ncclCftSmem cftSmem;
  __shared__ alignas(16) char smem[128];

  ncclCoopCta coop;
  ncclCft<ncclCoopCta> cft{coop, cftSmem};

  ncclTeam cftTeam = ncclTeamCft(devComm);
  int peer = (cftTeam.rank + 1) % cftTeam.nRanks;

  ncclCftLeId leId;
  size_t leOffset;
  ncclGetCftLeInfo(win, 0, peer, cftTeam, devComm, &leId, &leOffset);

  cft.put(coop, leId, leOffset, smem, sizeof(smem));
  cft.submit(coop);
  cft.flush(coop);
}

CFT source and destination shared memory pointers must be 16-byte aligned, and the byte count must be a multiple of 16. See Device API - CFT for the full list of put, get, multicast, reduction, and pull-reduce helpers.

CFT Barriers

CFT barriers synchronize device threads across the CFT team. The common setup is to request one barrier per CTA and use blockIdx.x as the barrier index.

__global__ void cftBarrierKernel(ncclDevComm devComm) {
  ncclCoopCta coop;
  ncclCftBarrierSession<ncclCoopCta> bar{coop, devComm, blockIdx.x};

  bar.sync(coop, cuda::memory_order_acq_rel,
           ncclMemProxyType::Generic, ncclMemProxyType::Fabric);
}

For multicast CFT barriers, create the device communicator with NCCL_CFT_MULTIMEM and pass multimem=true to the barrier session constructor.

Examples

See the CFT barrier example under docs/examples/06_device_api/04_cft_barrier for a complete runnable CFT setup and kernel.

Cross-proxy Fences

Cross-proxy fences enforce memory ordering between operations issued by different producer and consumer proxies. The common use case for fence is ordering of data and flag updates to establish a happens-before relationship between producer stores and consumer loads to the same memory region. In the following example, the producer is the Fabric proxy, while the consumer (ommitted) is the Generic proxy. The consumer polls on the flag using relaxed loads and, after observing the flag update, issues a fence with memory_order_acquire, Fabric producer, Generic consumer and memory scope Sys.

__global__ void cftPutKernel(ncclDevComm devComm, ncclWindow_t win) {
  __shared__ ncclCftSmem cftSmem;
  __shared__ alignas(16) char smem[128];
  __shared__ alignas(16) int flag[4];

  ncclCoopCta coop;
  ncclCft<ncclCoopCta> cft{coop, cftSmem};

  ncclTeam cftTeam = ncclTeamCft(devComm);
  int peer = (cftTeam.rank + 1) % cftTeam.nRanks;

  ncclCftLeId leId;
  size_t leOffset;
  ncclGetCftLeInfo(win, 0, peer, cftTeam, devComm, &leId, &leOffset);

  cft.put(coop, leId, leOffset, smem, sizeof(smem));
  cft.submit(coop);
  cft.flush(coop);

  ncclMemFence(coop, cuda::memory_order_release, ncclMemProxyType::Fabric, ncclMemProxyType::Generic, ncclMemFenceScope::Sys);

  cft.put(coop, leId, leOffset + flagOffset, flag, 16);
  cft.submit(coop);
  cft.flush(coop);
}

Another use case for fence is ordering accesses to shared memory between the Generic proxy and the Fabric proxy. In the following example, the Generic proxy updates shared memory and makes the updates visible to the Fabric proxy using a fence.

__shared__ncclCftSmem cftSmem;
ncclCoopCta coop;
ncclCft<ncclCoopCta> cft{coop, cftSmem};

__shared__ alignas(16) payload[4];

payload[0] = payload[1] = payload[2] = payload[3] = 1;

ncclMemFence(coop, cuda::memory_order_release, ncclMemProxyType::Generic, ncclMemProxyType::Fabric, ncclMemFenceScope::Cta);

cft.put(coop, leId, leOffset, payload, 16);
cft.submit(coop);
cft.flush(coop);