Files
TheRock/rocm-systems/projects/rccl/contrib/nccl_ep/device/low_latency.cu
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

1614 lines
71 KiB
Plaintext

/*
* Portions of this file are adapted from DeepEP (https://github.com/deepseek-ai/DeepEP).
* Copyright (c) 2025 DeepSeek. Licensed under the MIT License.
* SPDX-License-Identifier: MIT
*/
/*
* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
* See LICENSE.txt for more license information.
*/
#include <cooperative_groups.h>
#include "nccl_device.h"
#include "device_primitives.cuh"
namespace cg = cooperative_groups;
namespace nccl_ep {
namespace internode_ll {
template <bool useWarpSync = false>
__forceinline__ __device__ bool isRankMasked(int* rankMask, int rank) {
if (rankMask == nullptr) {
return false;
}
if constexpr (useWarpSync) {
return __shfl_sync(0xffffffff, ld_acquire_global(rankMask + rank), 0) != 0;
} else {
return ld_acquire_global(rankMask + rank) != 0;
}
}
__device__ __constant__ bool dP2pDisabled = false;
__forceinline__ __device__ uint64_t ncclGetP2pPtr(const uint64_t& dstPtr,
const size_t& offset,
const int& rank,
const int& dstRank,
const int& expertIdx,
const ncclWindow_t* ncclWindows,
ncclDevComm* devComms) {
// Local rank, no need for peer mapping
if (rank == dstRank) {
return dstPtr;
}
// If P2P is globally disabled, always use RDMA path
if (dP2pDisabled) {
return 0;
}
// P2P/NVLink only works between ranks on the same node (LSA team)
// Use NCCL team APIs to check if dstRank is in the same LSA team.
// Always use commId=0: single devComm with all GIN contexts (1-comm N-context design).
constexpr int commId = 0;
ncclTeam lsa = ncclTeamLsa(devComms[commId]);
ncclTeam world = ncclTeamWorld(devComms[commId]);
if (!ncclTeamRankIsMember(lsa, world, dstRank))
return 0; // Different nodes (not in same LSA team), must use RDMA
auto const p2pPtr = reinterpret_cast<uint64_t>(ncclGetPeerPointer(ncclWindows[commId],
offset, dstRank));
return p2pPtr ? p2pPtr : 0;
}
// ============================================================================
// Helper functions for dispatch kernel modularization
// ============================================================================
// Cast BF16 data to FP8 and write to send buffer, or copy BF16 data directly
template<bool kUseFP8>
__forceinline__ __device__ void castAndWriteToSendBuf(
const int4* srcDataInt4,
typename std::conditional<kUseFP8, int2, int4>::type* sendBufVec,
float* sendBufScales,
int threadId,
int numThreads,
int laneId,
int hiddenBf16Int4,
bool roundScale) {
constexpr int kNumElemsPerRead = sizeof(int4) / sizeof(nv_bfloat16);
constexpr int kNumPerChannels = 128;
EP_DEVICE_ASSERT(hiddenBf16Int4 % 32 == 0);
#pragma unroll
for (int i = threadId; i < hiddenBf16Int4; i += numThreads) {
auto dataInt4 = __ldg(srcDataInt4 + i);
if constexpr (kUseFP8) {
// Calculate local amax
auto bf16Data = reinterpret_cast<nv_bfloat16*>(&dataInt4);
float fp32Data[kNumElemsPerRead];
float amax = kFP8Margin, scale, scaleInv;
#pragma unroll
for (int j = 0; j < kNumElemsPerRead; ++j) {
fp32Data[j] = static_cast<float>(bf16Data[j]);
amax = fmaxf(amax, fabsf(fp32Data[j]));
}
// Reduce amax and scale
EP_STATIC_ASSERT(kNumElemsPerRead * 32 / kNumPerChannels == 2, "Invalid vectorization");
amax = warp_reduce_max<16>(amax);
calculate_fp8_scales(amax, scale, scaleInv, roundScale);
if (laneId == 0 or laneId == 16)
sendBufScales[i * kNumElemsPerRead / 128] = scaleInv;
// Cast into send buffer
typename std::conditional<kUseFP8, int2, int4>::type dataInt2;
auto fp8x2Data = reinterpret_cast<__nv_fp8x2_storage_t*>(&dataInt2);
#pragma unroll
for (int j = 0; j < kNumElemsPerRead; j += 2) {
float2 fp32x2 = {fp32Data[j] * scale, fp32Data[j + 1] * scale};
fp8x2Data[j / 2] = __nv_cvt_float2_to_fp8x2(fp32x2, __NV_SATFINITE, __NV_E4M3);
}
sendBufVec[i] = dataInt2;
} else {
// Reinterpret-cast is for C++14 compatibility
sendBufVec[i] = *reinterpret_cast<typename std::conditional<kUseFP8, int2, int4>::type*>(&dataInt4);
}
}
}
// Send a token via RDMA or P2P
__forceinline__ __device__ void sendToken(
const int* sendBufSrcIdx,
uint64_t recvPtr,
size_t expectedDstOffset,
int tokenIdx,
size_t sendOff,
size_t numBytesPerMsg,
int dstRank,
int dstExpertLocalIdx,
int currRank,
int* rankMask,
const ncclWindow_t* windows,
ncclDevComm* devComms,
int laneId,
size_t numInt4PerMsg) {
const auto dstP2pPtr = ncclGetP2pPtr(recvPtr, expectedDstOffset, currRank,
dstRank, dstExpertLocalIdx, windows, devComms);
if (not isRankMasked<true>(rankMask, dstRank)) {
if (dstP2pPtr == 0) {
if (laneId == 0) {
size_t expectedSrcOffset = sendOff + tokenIdx * numBytesPerMsg;
constexpr int commId = 0;
auto ctxId = dstExpertLocalIdx % MAX_NCCL_GIN_CTX_PER_COMM;
ncclGin net(devComms[commId], ctxId);
ncclTeam world = ncclTeamWorld(devComms[commId]);
auto ncclWindow = windows[commId];
net.put(world,
dstRank,
ncclWindow,
expectedDstOffset,
ncclWindow,
expectedSrcOffset,
numBytesPerMsg,
ncclGin_None{}, // no signal
ncclGin_None{}, // no counter
ncclCoopThread());
}
} else {
// NOTES: only 2 load iterations for 7K hidden with 8 unrolls
// Copy entire message including index from sendBufSrcIdx to dstP2pPtr
const auto* sendDataInt4 = reinterpret_cast<const int4*>(sendBufSrcIdx);
const auto* recvDataInt4 = reinterpret_cast<int4*>(dstP2pPtr);
UNROLLED_WARP_COPY(8, laneId, numInt4PerMsg, recvDataInt4, sendDataInt4, ld_nc_global, st_na_global);
}
}
}
// Clean next receive count buffer
__forceinline__ __device__ void cleanNextRecvCntBuf(
int* nextRecvCntBuf,
int nextRecvCntBufSize,
int laneId) {
if (!dP2pDisabled) {
#pragma unroll
for (int i = laneId; i < nextRecvCntBufSize; i += 32)
nextRecvCntBuf[i] = 0;
}
}
// Count tokens per expert from topk indices
__forceinline__ __device__ void countTokensPerExpert(
const int64_t* inTopkIdx,
int numTokens,
int numTopk,
int expertBeginIdx,
int expertEndIdx,
int* expertCount,
int* sharedNumTokensSentPerExpert,
int* expertDone,
int laneId) {
// Per lane count
#pragma unroll 8
for (int i = laneId; i < numTokens * numTopk; i += 32) {
auto idx = static_cast<int>(__ldg(inTopkIdx + i));
if (idx >= expertBeginIdx and idx < expertEndIdx)
expertCount[idx - expertBeginIdx]++;
}
// Warp reduce
#pragma unroll
for (int i = expertBeginIdx; i < expertEndIdx; ++i) {
auto sum = warp_reduce_sum(expertCount[i - expertBeginIdx]);
if (laneId == 0) {
sharedNumTokensSentPerExpert[i - expertBeginIdx] = sum;
atomic_add_release_global(expertDone + i, FINISHED_SUM_TAG - sum);
}
}
}
// Send expert count to destination rank
__forceinline__ __device__ void sendExpertCount(
int numTokensSent,
int dstRank,
int dstExpertLocalIdx,
int currRank,
int numRanks,
uint64_t recvCntPtr,
size_t recvCntOffset,
int* recvCntBuf,
int* rankMask,
unsigned signalsBase,
const ncclWindow_t* windows,
ncclDevComm* devComms) {
const auto dstP2pPtr = ncclGetP2pPtr(recvCntPtr, recvCntOffset, currRank, dstRank,
dstExpertLocalIdx, windows, devComms);
if (not isRankMasked(rankMask, dstRank)) {
if (dstP2pPtr == 0) {
constexpr int commId = 0;
auto ctxId = dstExpertLocalIdx % MAX_NCCL_GIN_CTX_PER_COMM;
auto signalId = signalsBase + dstExpertLocalIdx * numRanks + currRank;
ncclGin net(devComms[commId], ctxId);
ncclTeam world = ncclTeamWorld(devComms[commId]);
auto ncclWindow = windows[commId];
net.put(world,
dstRank,
ncclWindow,
recvCntOffset,
ncclWindow,
0,
0, // 0 bytes transfer
ncclGin_SignalAdd{signalId, static_cast<uint64_t>(numTokensSent) + 1},
ncclGin_None{}, // no counter
ncclCoopThread());
} else {
st_release_sys_global(reinterpret_cast<int*>(dstP2pPtr), -numTokensSent - 1);
}
}
}
// Wait for receive tokens to arrive and return count
__forceinline__ __device__ int waitForRecvTokens(
int srcRank,
int localExpertIdx,
int currRank,
int numRanks,
size_t recvCntOff,
int* recvCntBuf,
int* rankMask,
unsigned signalsBase,
const ncclWindow_t* windows,
ncclDevComm* devComms,
int* recvStats,
int64_t* waitStats) {
auto startTime = clock64();
uint64_t waitRecvCost = 0;
int numRecvTokens = 0;
if (not isRankMasked(rankMask, srcRank)) {
size_t srcOffset = recvCntOff + (localExpertIdx * numRanks + srcRank) * sizeof(int);
auto srcP2pPtr = ncclGetP2pPtr(0x01, srcOffset, currRank, srcRank, localExpertIdx, windows, devComms);
if (srcP2pPtr == 0) {
constexpr int commId = 0;
auto ctxId = localExpertIdx % MAX_NCCL_GIN_CTX_PER_COMM;
ncclGin net(devComms[commId], ctxId);
uint64_t curValue;
do {
curValue = net.readSignal(signalsBase + localExpertIdx * numRanks + srcRank);
} while (curValue < 1 // data not arrived
&& (waitRecvCost = clock64() - startTime) <= NUM_TIMEOUT_CYCLES // not timeout
);
net.resetSignal(signalsBase + localExpertIdx * numRanks + srcRank);
numRecvTokens = -(int)curValue;
} else {
while ((numRecvTokens = ld_acquire_sys_global((recvCntBuf + localExpertIdx * numRanks + srcRank))) ==
0 // data not arrived
&& (waitRecvCost = clock64() - startTime) <= NUM_TIMEOUT_CYCLES // not timeout
);
}
}
// Do not receive tokens if rank timeout or masked
if (numRecvTokens == 0)
numRecvTokens = -1;
// Mask rank if timeout
if (waitRecvCost > NUM_TIMEOUT_CYCLES) {
printf("Warning: NCCL EP timeout for dispatch receive, rank %d, local_expert_idx %d, src_rank %d\n",
currRank, localExpertIdx, srcRank);
if (rankMask == nullptr)
trap();
atomicExch(rankMask + srcRank, 1);
}
numRecvTokens = -numRecvTokens - 1;
// Add stats for diagnosis
if (recvStats != nullptr)
atomicAdd(recvStats + localExpertIdx, numRecvTokens);
if (waitStats != nullptr)
atomicAdd(reinterpret_cast<unsigned long long*>(waitStats + srcRank), waitRecvCost);
return numRecvTokens;
}
// Copy received token data and scales
template<bool kUseFP8, bool kUseUE8M0>
__forceinline__ __device__ void copyRecvTokenData(
const uint8_t* recvBufUint8,
int tokenIdx,
int recvTokenBeginIdx,
int4* outDataInt4,
int* outSrcInfo,
typename std::conditional<kUseUE8M0, uint8_t, float>::type* outScales,
int hiddenInt4,
int hiddenBytes,
int numScales,
int numBytesPerMsg,
int maxTokensPerRank,
int numRanks,
int laneId) {
using scale_t = std::conditional_t<kUseUE8M0, uint8_t, float>;
using packed_t = std::conditional_t<kUseUE8M0, uint32_t, float>;
// Copy source info
const auto recvBufSrcIdx = reinterpret_cast<const int*>(recvBufUint8 + tokenIdx * numBytesPerMsg);
if (laneId == 0)
outSrcInfo[recvTokenBeginIdx + tokenIdx] = ld_nc_global(recvBufSrcIdx);
__syncwarp();
// Copy data
// NOTES: only 2 load iterations for 7K hidden with 7 unrolls
const auto recvDataInt4 = reinterpret_cast<const int4*>(reinterpret_cast<const uint8_t*>(recvBufSrcIdx) + sizeof(int4));
const auto outDataInt4Ptr = outDataInt4 + (recvTokenBeginIdx + tokenIdx) * hiddenInt4;
UNROLLED_WARP_COPY(7, laneId, hiddenInt4, outDataInt4Ptr, recvDataInt4, ld_nc_global, st_na_global);
// Copy scales
if constexpr (kUseFP8) {
// Equivalent CuTe layout:
// (num_tokens, (num_packed, num_elems_per_pack)):(num_elems_per_pack, (num_tokens * num_elems_per_pack, 1))
const auto recvScales = reinterpret_cast<const float*>(reinterpret_cast<const uint8_t*>(recvDataInt4) + hiddenBytes);
const auto numElemsPerPack = static_cast<int>(sizeof(packed_t) / sizeof(scale_t));
const auto tokenIdxFinal = recvTokenBeginIdx + tokenIdx;
const auto tokenStride = numElemsPerPack;
const auto packStride = numRanks * maxTokensPerRank * numElemsPerPack;
if (laneId < numScales) {
const auto packIdx = laneId / numElemsPerPack;
const auto elemIdx = laneId % numElemsPerPack;
auto scale = extract_required_scale_format<kUseUE8M0>(ld_nc_global(recvScales + laneId));
outScales[tokenIdxFinal * tokenStride + packIdx * packStride + elemIdx] = scale;
}
if (laneId + 32 < numScales) {
const auto packIdx = (laneId + 32) / numElemsPerPack;
const auto elemIdx = (laneId + 32) % numElemsPerPack;
auto scale = extract_required_scale_format<kUseUE8M0>(ld_nc_global(recvScales + laneId + 32));
outScales[tokenIdxFinal * tokenStride + packIdx * packStride + elemIdx] = scale;
}
}
}
template <bool kUseFP8, bool kUseUE8M0, int kHidden>
__global__ __launch_bounds__(1024, 1) void dispatch(// INPUT
const void* inData,
const int64_t* inTopkIdx,
int* rankMask,
// OUTPUT
void* outDataBuf,
void* outScalesBuf,
int* outSrcInfo,
int64_t* outLayout,
int* outCnt,
// INTERMEDIATE
void* sendBuf,
void* recvBuf,
int* recvCntBuf,
size_t sendOff,
size_t recvOff,
size_t recvCntOff,
int* expertCnt,
int* expertDone,
int* nextRecvCntBuf,
int nextRecvCntBufSize,
int* recvStats,
int64_t* waitStats,
// CONFIG
int numTokens,
int maxTokensPerRank,
int numTopk,
int numExperts,
int currRank,
int numRanks,
int numWarpGroups,
int numWarpsPerGroup,
bool roundScale,
int phases,
int numComms,
ncclDevComm* devComms,
const ncclWindow_t* windows,
unsigned signalsBase) {
const auto smId = static_cast<int>(blockIdx.x);
const auto threadId = static_cast<int>(threadIdx.x);
const auto warpId = threadId / 32, laneId = get_lane_id();
const auto numSms = static_cast<int>(gridDim.x);
const auto numWarps = numWarpGroups * numWarpsPerGroup;
const auto numLocalExperts = numExperts / numRanks;
const auto warpGroupId = warpId / numWarpsPerGroup;
const auto subWarpId = warpId % numWarpsPerGroup;
const auto responsibleExpertIdx = smId * numWarpGroups + warpGroupId;
// May extract UE8M0 from the scales
using scale_t = std::conditional_t<kUseUE8M0, uint8_t, float>;
using packed_t = std::conditional_t<kUseUE8M0, uint32_t, float>;
EP_STATIC_ASSERT(sizeof(packed_t) % sizeof(scale_t) == 0, "Invalid vector length");
// FP8 staffs
constexpr int kNumPerChannels = 128;
const int numScales = kHidden / kNumPerChannels;
const size_t hiddenBytes = kHidden * (kUseFP8 ? sizeof(__nv_fp8_storage_t) : sizeof(nv_bfloat16));
const size_t hiddenInt4 = hiddenBytes / sizeof(int4);
// Message package: index at source (int), 3 reserved int fields, hidden data, FP8 scales
// NOTES: currently we have 3 reserved int fields for future use
using vec_t = std::conditional_t<kUseFP8, int2, int4>;
const size_t numBytesPerMsg = sizeof(int4) + (kUseFP8 ? (kHidden + numScales * sizeof(float)) : (kHidden * sizeof(nv_bfloat16)));
const size_t numInt4PerMsg = numBytesPerMsg / sizeof(int4);
EP_DEVICE_ASSERT(numBytesPerMsg % sizeof(int4) == 0);
// Expert counts
constexpr int kNumMaxWarpGroups = 32;
__shared__ int sharedNumTokensSentPerExpert[kNumMaxWarpGroups];
// Sending phase
if ((phases & LOW_LATENCY_SEND_PHASE) == 0)
goto LOW_LATENCY_DISPATCH_RECV;
// There are 2 kinds of warps in this part:
// 1. The first-kind warps for FP8 cast and sending top-k tokens
// 2. The last warp for reading `topk_idx` and count for per-expert information
if (warpId < numWarps - 1) {
constexpr int kNumElemsPerRead = sizeof(int4) / sizeof(nv_bfloat16);
EP_STATIC_ASSERT(kHidden % (32 * kNumElemsPerRead) == 0, "Invalid hidden");
EP_STATIC_ASSERT(kNumElemsPerRead * 32 % kNumPerChannels == 0, "Invalid vectorization");
const auto numThreads = (numWarps - 1) * 32;
const size_t hiddenBf16Int4 = kHidden / kNumElemsPerRead;
for (int tokenIdx = smId; tokenIdx < numTokens; tokenIdx += numSms) {
const auto srcDataInt4 = static_cast<const int4*>(inData) + tokenIdx * hiddenBf16Int4;
const auto sendBufSrcIdx = reinterpret_cast<int*>(static_cast<uint8_t*>(sendBuf) + tokenIdx * numBytesPerMsg);
const auto sendBufVec = reinterpret_cast<vec_t*>(reinterpret_cast<uint8_t*>(sendBufSrcIdx) + sizeof(int4));
const auto sendBufScales = reinterpret_cast<float*>(reinterpret_cast<uint8_t*>(sendBufVec) + hiddenBytes);
// Overlap top-k index read and source token index writes
auto dstExpertIdx = warpId < numTopk ? static_cast<int>(__ldg(inTopkIdx + tokenIdx * numTopk + warpId)) : -1;
threadId == 0 ? (*sendBufSrcIdx = tokenIdx) : 0;
// Cast and write data to send buffer
castAndWriteToSendBuf<kUseFP8>(
srcDataInt4, sendBufVec, sendBufScales,
threadId, numThreads, laneId, hiddenBf16Int4, roundScale);
asm volatile("bar.sync 1, %0;" :: "r"(numThreads));
// Issue IBGDA sends
if (dstExpertIdx >= 0) {
int slotIdx = laneId == 0 ? atomicAdd(expertCnt + dstExpertIdx, 1) : 0;
slotIdx = __shfl_sync(0xffffffff, slotIdx, 0);
const auto dstRank = dstExpertIdx / numLocalExperts;
const auto dstExpertLocalIdx = dstExpertIdx % numLocalExperts;
const auto recvPtr = reinterpret_cast<uint64_t>(recvBuf) +
dstExpertLocalIdx * numRanks * maxTokensPerRank * numBytesPerMsg +
currRank * maxTokensPerRank * numBytesPerMsg +
slotIdx * numBytesPerMsg;
size_t expectedDstOffset = recvOff +
dstExpertLocalIdx * numRanks * maxTokensPerRank * numBytesPerMsg +
currRank * maxTokensPerRank * numBytesPerMsg + slotIdx * numBytesPerMsg;
sendToken(
sendBufSrcIdx, recvPtr, expectedDstOffset, tokenIdx,
sendOff, numBytesPerMsg, dstRank, dstExpertLocalIdx,
currRank, rankMask, windows, devComms, laneId, numInt4PerMsg);
// Increase counter after finishing
__syncwarp();
laneId == 0 ? atomic_add_release_global(expertDone + dstExpertIdx, 1) : 0;
}
}
} else if (warpId == numWarps - 1) {
EP_DEVICE_ASSERT(numSms > 1);
if (smId == 0) {
cleanNextRecvCntBuf(nextRecvCntBuf, nextRecvCntBufSize, laneId);
// Notify before executing `int_p`
__syncwarp();
#pragma unroll
for (int i = laneId; i < numExperts; i += 32)
atomic_add_release_global(expertDone + i, FINISHED_SUM_TAG);
}
// Count tokens per expert from topk indices
int expertCount[kNumMaxWarpGroups] = {0};
const auto expertBeginIdx = smId * numWarpGroups;
const auto expertEndIdx = min(expertBeginIdx + numWarpGroups, numExperts);
countTokensPerExpert(
inTopkIdx, numTokens, numTopk, expertBeginIdx, expertEndIdx,
expertCount, sharedNumTokensSentPerExpert, expertDone, laneId);
}
__syncthreads();
// Issue count sends
if (responsibleExpertIdx < numExperts and subWarpId == 0 and laneId == 0) {
const auto dstRank = responsibleExpertIdx / numLocalExperts;
const auto dstExpertLocalIdx = responsibleExpertIdx % numLocalExperts;
const auto numTokensSent = sharedNumTokensSentPerExpert[responsibleExpertIdx - smId * numWarpGroups];
// Wait local sends issued and send expert counts
while (ld_acquire_global(expertDone + responsibleExpertIdx) != FINISHED_SUM_TAG * 2);
auto recvCntPtr = reinterpret_cast<uint64_t>(recvCntBuf + dstExpertLocalIdx * numRanks + currRank);
size_t recvCntOffset = recvCntOff + (dstExpertLocalIdx * numRanks + currRank) * sizeof(int);
sendExpertCount(
numTokensSent, dstRank, dstExpertLocalIdx, currRank, numRanks,
recvCntPtr, recvCntOffset, recvCntBuf, rankMask, signalsBase,
windows, devComms);
// Clean workspace for next use
expertCnt[responsibleExpertIdx] = 0;
expertDone[responsibleExpertIdx] = 0;
// Clean `packed_recv_count`
if (dstRank == 0)
outCnt[dstExpertLocalIdx] = 0;
}
__syncwarp();
// Receiving phase
LOW_LATENCY_DISPATCH_RECV:
if ((phases & LOW_LATENCY_RECV_PHASE) == 0)
return;
// For send-and-recv kernels, we need a grid sync for making `packed_recv_count` visible
if (phases & LOW_LATENCY_SEND_PHASE)
cg::this_grid().sync();
// Receiving and packing
if (responsibleExpertIdx < numExperts) {
const auto srcRank = responsibleExpertIdx / numLocalExperts;
const auto localExpertIdx = responsibleExpertIdx % numLocalExperts;
const auto recvBufUint8 = static_cast<uint8_t*>(recvBuf) +
localExpertIdx * numRanks * maxTokensPerRank * numBytesPerMsg +
srcRank * maxTokensPerRank * numBytesPerMsg;
const auto outDataInt4 = static_cast<int4*>(outDataBuf) +
localExpertIdx * numRanks * maxTokensPerRank * hiddenInt4;
const auto recvSrcInfo = outSrcInfo + localExpertIdx * numRanks * maxTokensPerRank;
const auto recvRange = outLayout + localExpertIdx * numRanks;
const auto numAlignedScales = align<int>(numScales, sizeof(float) / sizeof(scale_t));
const auto outScales = static_cast<scale_t*>(outScalesBuf) + localExpertIdx * numRanks * maxTokensPerRank * numAlignedScales;
// Shared between sub-warps in warp groups
__shared__ int sharedNumRecvTokens[kNumMaxWarpGroups], sharedRecvTokenBeginIdx[kNumMaxWarpGroups];
// Wait tokens to arrive
// NOTES: using sub-warp 1 to overlap with sub-warp 0
int numRecvTokens, recvTokenBeginIdx;
EP_DEVICE_ASSERT(numWarpsPerGroup > 1 and numWarpGroups < 15);
if (subWarpId == 1 and laneId == 0) {
numRecvTokens = waitForRecvTokens(
srcRank, localExpertIdx, currRank, numRanks, recvCntOff,
recvCntBuf, rankMask, signalsBase, windows, devComms,
recvStats, waitStats);
recvTokenBeginIdx = atomicAdd(outCnt + localExpertIdx, numRecvTokens);
sharedNumRecvTokens[warpGroupId] = numRecvTokens;
sharedRecvTokenBeginIdx[warpGroupId] = recvTokenBeginIdx;
recvRange[srcRank] = pack2<int, int64_t>(numRecvTokens, recvTokenBeginIdx);
}
asm volatile("bar.sync %0, %1;" :: "r"(warpGroupId + 2), "r"(numWarpsPerGroup * 32));
numRecvTokens = sharedNumRecvTokens[warpGroupId];
recvTokenBeginIdx = sharedRecvTokenBeginIdx[warpGroupId];
// Copy tokens
EP_DEVICE_ASSERT(numScales <= 64);
for (int i = subWarpId; i < numRecvTokens; i += numWarpsPerGroup) {
copyRecvTokenData<kUseFP8, kUseUE8M0>(
recvBufUint8, i, recvTokenBeginIdx, outDataInt4, recvSrcInfo, outScales,
hiddenInt4, hiddenBytes, numScales, numBytesPerMsg,
maxTokensPerRank, numRanks, laneId);
}
}
}
void dispatch(const void* inData,
const int64_t* inTopkIdx,
void* outDataBuf,
void* outScalesBuf,
int* outSrcInfo,
int64_t* outLayout,
int* outCnt,
void* sendBuf,
void* recvBuf,
int* recvCntBuf,
size_t sendOff,
size_t recvOff,
size_t recvCntOff,
int* nextRecvCntBuf,
int nextRecvCntBufSize,
int* recvStats,
int64_t* waitStats,
int numTokens,
int hidden,
int maxTokensPerRank,
int numTopk,
int numExperts,
int currRank,
int numRanks,
bool useFp8,
bool roundScale,
bool useUe8m0,
int phases,
int numComms,
ncclDevComm* devComms,
const ncclWindow_t* windows,
unsigned signalsBase,
void* workspace,
int numDeviceSms,
cudaStream_t stream) {
constexpr int kNumMaxTopK = 9;
const int numWarpGroups = ceil_div(numExperts, numDeviceSms);
const int numWarpsPerGroup = 32 / numWarpGroups;
EP_HOST_ASSERT(numWarpGroups > 0 and numWarpsPerGroup > 0);
EP_HOST_ASSERT(kNumMaxTopK + 1 <= numWarpGroups * numWarpsPerGroup);
const auto numWarps = numWarpGroups * numWarpsPerGroup;
const auto numSms = ceil_div(numExperts, numWarpGroups);
EP_HOST_ASSERT(numTopk <= kNumMaxTopK);
// Workspace checks
auto expertCnt = static_cast<int*>(workspace);
auto expertDone = expertCnt + numExperts;
EP_HOST_ASSERT(numExperts * sizeof(int) * 2 <= NUM_WORKSPACE_BYTES);
// FP8 checks
if (useUe8m0)
EP_HOST_ASSERT(roundScale and "UE8M0 SF requires `round_scale=True`");
#define DISPATCH_LAUNCH_CASE(hidden) { \
auto dispatchFunc = dispatch<false, false, hidden>; \
if (useFp8 and not useUe8m0) \
dispatchFunc = dispatch<true, false, hidden>; \
if (useFp8 and useUe8m0) \
dispatchFunc = dispatch<true, true, hidden>; \
LAUNCH_KERNEL(&cfg, dispatchFunc, \
inData, \
inTopkIdx, \
/*rankMask=*/nullptr, \
outDataBuf, \
outScalesBuf, \
outSrcInfo, \
outLayout, \
outCnt, \
sendBuf, \
recvBuf, \
recvCntBuf, \
sendOff, \
recvOff, \
recvCntOff, \
expertCnt, \
expertDone, \
nextRecvCntBuf, \
nextRecvCntBufSize, \
recvStats, \
waitStats, \
numTokens, \
maxTokensPerRank, \
numTopk, \
numExperts, \
currRank, \
numRanks, \
numWarpGroups, \
numWarpsPerGroup, \
roundScale, \
phases, \
numComms, \
devComms, \
windows, \
signalsBase); } break
SETUP_LAUNCH_CONFIG(numSms, numWarps * 32, stream);
SWITCH_HIDDEN(DISPATCH_LAUNCH_CASE);
#undef DISPATCH_LAUNCH_CASE
}
template <int kNumSendUnrolls>
__forceinline__ __device__ int logfmtEncode(void* buffer, nv_bfloat162 *sharedAmaxmin, const int& laneId) {
constexpr int kNumElemsPerInt4 = sizeof(int4) / sizeof(nv_bfloat16);
constexpr float kLogThreshold = 0;
constexpr float kMinClip = 32; // `== log_2(2 ^ (2 ^ 5))`
constexpr int kNumBits = 10;
constexpr int kNumValues = 1 << (kNumBits - 1);
int4 int4Data[kNumSendUnrolls];
const auto& uint32Data = reinterpret_cast<uint32_t*>(int4Data);
const auto& bf162Data = reinterpret_cast<nv_bfloat162*>(int4Data);
// Calculate lane offset
const auto& loadBuf = reinterpret_cast<uint32_t*>(static_cast<uint8_t*>(buffer) + laneId * (kNumSendUnrolls * sizeof(int4)));
const auto& storeBuf = reinterpret_cast<uint32_t*>(static_cast<uint8_t*>(buffer) + laneId * (kNumSendUnrolls * sizeof(int4) * 10 / 16));
// Local log amax
auto bf162Amax = __nv_bfloat162(CUDART_ZERO_BF16, CUDART_ZERO_BF16);
auto bf162Amin = __nv_bfloat162(CUDART_INF_BF16, CUDART_INF_BF16);
uint32_t localSigns = 0;
#pragma unroll
for (int k = 0; k < kNumSendUnrolls * kNumElemsPerInt4 / 2; ++ k) {
uint32Data[k] = loadBuf[k];
localSigns |= ((uint32Data[k] >> 15) & 1) << (k * 2);
localSigns |= ((uint32Data[k] >> 31) & 1) << (k * 2 + 1);
uint32Data[k] &= 0x7fff7fff;
bf162Amax = __hmax2(bf162Amax, bf162Data[k]);
bf162Amin = __hmin2(bf162Amin, bf162Data[k]);
}
// Reduce per 128 channels
auto amax = std::max(static_cast<float>(bf162Amax.x), static_cast<float>(bf162Amax.y));
auto amin = std::min(static_cast<float>(bf162Amin.x), static_cast<float>(bf162Amin.y));
constexpr static int kNumLanesToReduce = 128 * sizeof(nv_bfloat16) / (kNumSendUnrolls * sizeof(int4));
amax = warp_reduce_max<kNumLanesToReduce>(amax);
amin = warp_reduce_min<kNumLanesToReduce>(amin);
// Write min/max into the shared memory
if (sharedAmaxmin != nullptr)
*sharedAmaxmin = __nv_bfloat162(amax, amin);
__syncwarp();
// Calculate log amin/amax float
const auto& logAmax = log2f_approx(amax);
const auto& logAmin = fmaxf(log2f_approx(amin), logAmax - kMinClip);
const bool& enableCast = warp_reduce_and<kNumLanesToReduce, true>(logAmax < kLogThreshold and logAmin < logAmax);
// Case into LogFMT-10 if satisfied
if (enableCast) {
const auto step = (logAmax - logAmin) / static_cast<float>(kNumValues - 2);
const auto stepInv = 1.0f / step;
const auto rounding = 2.0f - log2f_approx((1.0f + exp2f_approx(step)) * 0.5f) * stepInv;
const auto fusedRounding = rounding - logAmin * stepInv;
// Pack every 256 bits into 160 bits
EP_STATIC_ASSERT(kNumSendUnrolls == 2 or kNumSendUnrolls == 4, "kNumSendUnrolls == 2 or 4 only");
uint32_t encodedData[kNumElemsPerInt4 * 2];
#pragma unroll 1
for (int i = 0; i < kNumSendUnrolls / 2; ++ i) {
#pragma unroll
for (int k = 0; k < kNumElemsPerInt4; ++ k) {
const auto& [x, y] = __bfloat1622float2(bf162Data[i * kNumElemsPerInt4 + k]);
encodedData[k * 2 + 0] = __float2uint_rd(fmaxf(log2f_approx(x) * stepInv + fusedRounding, 0));
encodedData[k * 2 + 1] = __float2uint_rd(fmaxf(log2f_approx(y) * stepInv + fusedRounding, 0));
}
storeBuf[i * 5 + 0] = (encodedData[ 0] >> 0) | (encodedData[ 1] << 9) | (encodedData[ 2] << 18) | (encodedData[ 3] << 27);
storeBuf[i * 5 + 1] = (encodedData[ 3] >> 5) | (encodedData[ 4] << 4) | (encodedData[ 5] << 13) | (encodedData[ 6] << 22) | (encodedData[7] << 31);
storeBuf[i * 5 + 2] = (encodedData[ 7] >> 1) | (encodedData[ 8] << 8) | (encodedData[ 9] << 17) | (encodedData[10] << 26);
storeBuf[i * 5 + 3] = (encodedData[10] >> 6) | (encodedData[11] << 3) | (encodedData[12] << 12) | (encodedData[13] << 21) | (encodedData[14] << 30);
storeBuf[i * 5 + 4] = (encodedData[14] >> 2) | (encodedData[15] << 7) | ((i == 0) ? (localSigns << 16) : (localSigns & 0xffff0000u));
}
tma_store_fence();
__syncwarp();
}
// Return TMA copy bytes
return enableCast ? (32 * (kNumSendUnrolls * sizeof(int4) * 8 * 10 / 16 / 8)):
(32 * (kNumSendUnrolls * sizeof(int4)));
}
template <int kNumLanes, int kNumSendUnrolls, int kNumRecvUnrolls>
__forceinline__ __device__ void logfmtCheckAmaxmin(uint8_t* metaBuffer, float2* sharedLogAmax,
float2* sharedLogAmin, int* sharedCastInfo,
const int laneId) {
constexpr float kLogThreshold = 0;
constexpr float kMinClip = 32; // `== log_2(2 ^ (2 ^ 5))`
bool enableCast = true;
if (laneId < kNumLanes) {
// Calculate log amin/amax float
auto amaxminData = reinterpret_cast<uint64_t*>(metaBuffer)[laneId];
const auto& bf162Amaxmin = reinterpret_cast<__nv_bfloat162*>(&amaxminData);
float logAmax[2], logAmin[2];
#pragma unroll
for (int i = 0; i < 2; ++ i) {
auto amax = static_cast<float>(bf162Amaxmin[i].x);
auto amin = static_cast<float>(bf162Amaxmin[i].y);
logAmax[i] = log2f_approx(amax);
logAmin[i] = amin == 0 ? logAmax[i] - kMinClip : fmaxf(log2f_approx(amin), logAmax[i] - kMinClip);
enableCast = enableCast and logAmax[i] < kLogThreshold and logAmin[i] < logAmax[i];
}
sharedLogAmax[laneId] = make_float2(logAmax[0], logAmax[1]);
sharedLogAmin[laneId] = make_float2(logAmin[0], logAmin[1]);
}
const auto& casted = warp_reduce_and<kNumSendUnrolls>(enableCast) ? 1u << (laneId / kNumRecvUnrolls): 0u;
const auto& numCastedPrefix = __popc(warp_reduce_or<kNumRecvUnrolls, true>(casted) & ((1u << (laneId / kNumRecvUnrolls)) - 1));
if (laneId < kNumLanes and laneId % kNumRecvUnrolls == 0)
sharedCastInfo[laneId / kNumRecvUnrolls] = (numCastedPrefix << 1) | (casted ? 1u : 0u);
__syncwarp();
}
template <int kNumRecvUnrolls>
__forceinline__ __device__ void decodeAndAccumulate(uint32_t* ldBuffer, float* accum,
const float& logAmax, const float& logAmin,
const bool& enableCast, const float& weight) {
if (enableCast) {
constexpr int kNumBits = 10;
constexpr int kNumValues = 1 << (kNumBits - 1);
const auto& step = (logAmax - logAmin) / static_cast<float>(kNumValues - 2);
auto decode = [=](const uint32_t &encoded, const uint32_t &sign) {
const auto decoded = encoded == 0 ? .0f : exp2f_approx((encoded - 1) * step + logAmin);
return sign ? -decoded : decoded;
};
EP_STATIC_ASSERT(kNumRecvUnrolls == 2 or kNumRecvUnrolls == 4, "kNumRecvUnrolls == 2 or 4 only");
#pragma unroll
for (int i = 0; i < kNumRecvUnrolls / 2; ++ i) {
uint32_t concatData[6];
concatData[0] = ldBuffer[i * 5];
#pragma unroll
for (int k = 1; k < 5; ++ k)
concatData[k] = (ldBuffer[i * 5 + k - 1] >> (32 - k * 5)) | (ldBuffer[i * 5 + k] << (k * 5));
concatData[5] = ldBuffer[i * 5 + 4] >> 7;
const uint32_t& localSigns = ldBuffer[i * 5 + 4] >> 16;
#pragma unroll
for (int k = 0; k < 5; ++ k) {
accum[i * 16 + k * 3 + 0] += decode((concatData[k] >> 0) & 0x1ff, (localSigns >> (k * 3 + 0)) & 1) * weight;
accum[i * 16 + k * 3 + 1] += decode((concatData[k] >> 9) & 0x1ff, (localSigns >> (k * 3 + 1)) & 1) * weight;
accum[i * 16 + k * 3 + 2] += decode((concatData[k] >> 18) & 0x1ff, (localSigns >> (k * 3 + 2)) & 1) * weight;
}
accum[i * 16 + 15] += decode(concatData[5] & 0x1ff, (localSigns >> 15) & 1) * weight;
}
} else {
#pragma unroll
for (int k = 0; k < kNumRecvUnrolls * 4; ++ k) {
auto bf16Pack = *reinterpret_cast<__nv_bfloat162*>(ldBuffer + k);
accum[k * 2 + 0] += static_cast<float>(bf16Pack.x) * weight;
accum[k * 2 + 1] += static_cast<float>(bf16Pack.y) * weight;
}
}
}
// Clean next receive count buffer and notify atomic clean flag
__forceinline__ __device__ void cleanNextRecvCntBufAndNotify(
int* nextRecvCntBuf,
int nextRecvCntBufSize,
int* atomicCleanFlag,
int numExperts,
int laneId) {
if (!dP2pDisabled) {
#pragma unroll
for (int i = laneId; i < nextRecvCntBufSize; i += 32)
nextRecvCntBuf[i] = 0;
}
// Notify before executing `int_p`
__syncwarp();
if (laneId == 0)
atomic_add_release_global(atomicCleanFlag, numExperts);
}
// Send finish flag to destination rank
__forceinline__ __device__ void sendFinishFlag(
int globalExpertIdx,
int dstRank,
int localExpertIdx,
int currRank,
int numLocalExperts,
uint64_t recvFlagPtr,
size_t recvFlagOffset,
int* recvFlagBuf,
int* rankMask,
unsigned signalsBase,
const ncclWindow_t* windows,
ncclDevComm* devComms) {
auto dstP2pPtr = ncclGetP2pPtr(
recvFlagPtr, recvFlagOffset, currRank, dstRank, localExpertIdx, windows, devComms);
if (not isRankMasked(rankMask, dstRank)) {
if (dstP2pPtr == 0) {
auto signalId = signalsBase + globalExpertIdx;
constexpr int commId = 0;
auto ctxId = localExpertIdx % MAX_NCCL_GIN_CTX_PER_COMM;
ncclGin net(devComms[commId], ctxId);
ncclTeam world = ncclTeamWorld(devComms[commId]);
auto ncclWindow = windows[commId];
net.put(world,
dstRank,
ncclWindow,
recvFlagOffset,
ncclWindow,
0,
0, // 0 bytes transfer
ncclGin_SignalAdd{signalId, 1},
ncclGin_None{}, // no counter
ncclCoopThread());
} else {
st_release_sys_global(reinterpret_cast<int*>(dstP2pPtr), 1);
}
}
}
// Wait for receive flag to arrive
__forceinline__ __device__ void waitForRecvFlag(
int responsibleExpertIdx,
int srcRank,
int currRank,
int numLocalExperts,
size_t recvFlagOff,
int* recvFlagBuf,
int* rankMask,
unsigned signalsBase,
const ncclWindow_t* windows,
ncclDevComm* devComms,
int64_t* waitStats) {
auto startTime = clock64();
uint64_t waitRecvCost = 0;
size_t srcOffset = recvFlagOff + responsibleExpertIdx * sizeof(int);
auto srcP2pPtr = ncclGetP2pPtr(
0x01, srcOffset, currRank, srcRank, (responsibleExpertIdx % numLocalExperts), windows, devComms);
if (not isRankMasked(rankMask, srcRank)) {
if (srcP2pPtr == 0) {
uint64_t curValue;
auto localExpertIdxWait = responsibleExpertIdx % numLocalExperts;
constexpr int commIdWait = 0;
auto ctxIdWait = localExpertIdxWait % MAX_NCCL_GIN_CTX_PER_COMM;
ncclGin net(devComms[commIdWait], ctxIdWait);
do {
curValue = net.readSignal(signalsBase + responsibleExpertIdx);
} while (curValue < 1 // signal not arrived
&& (waitRecvCost = clock64() - startTime) <= NUM_TIMEOUT_CYCLES // not timeout
);
net.resetSignal(signalsBase + responsibleExpertIdx);
} else {
while (ld_acquire_sys_global(recvFlagBuf + responsibleExpertIdx) == 0 // recv not ready
&& (waitRecvCost = clock64() - startTime) <= NUM_TIMEOUT_CYCLES // not timeout
);
}
}
// Mask rank if timeout
if (waitRecvCost > NUM_TIMEOUT_CYCLES) {
printf("Warning: NCCL EP timeout for combine receive, rank %d, local_expert_idx %d, src_rank %d\n",
currRank,
responsibleExpertIdx % numLocalExperts,
srcRank);
if (rankMask == nullptr)
trap();
atomicExch(rankMask + srcRank, 1);
}
if (waitStats != nullptr) {
atomicAdd(reinterpret_cast<unsigned long long*>(waitStats + srcRank), waitRecvCost);
}
}
// Send token via RDMA
__forceinline__ __device__ void sendTokenViaRdma(
int globalExpertIdx,
int dstRank,
int localExpertIdx,
int currRank,
int numRanks,
int maxTokensPerRank,
int tokenIdx,
int srcIdx,
size_t sendOff,
size_t recvOff,
size_t numBytesPerSlot,
int hidden,
ncclDevComm* devComms,
const ncclWindow_t* windows) {
const auto expectedDstOffset = recvOff + (globalExpertIdx * maxTokensPerRank + srcIdx) * numBytesPerSlot;
const auto expectedBufOffset = sendOff +
(localExpertIdx * numRanks * maxTokensPerRank * numBytesPerSlot) +
tokenIdx * numBytesPerSlot;
constexpr int commId = 0;
auto ctxId = localExpertIdx % MAX_NCCL_GIN_CTX_PER_COMM;
ncclGin net(devComms[commId], ctxId);
ncclTeam world = ncclTeamWorld(devComms[commId]);
auto ncclWindow = windows[commId];
net.put(world,
dstRank,
ncclWindow,
expectedDstOffset,
ncclWindow,
expectedBufOffset,
hidden * sizeof(nv_bfloat16),
ncclGin_None{}, // no signal
ncclGin_None{}, // no counter
ncclCoopThread());
}
// Process and send a single token with TMA copy and optional RDMA
template<bool kUseLogFMT, int kHidden, int kNumSendUnrolls, int kNumStages, int kNumPrefetch,
typename TmaBuffersT, typename FullBarriersT, typename TmaLoadAndArriveT, typename GetNumTmaBytesT>
__forceinline__ __device__ void processAndSendToken(
int tokenIdx,
int srcIdx,
const int4* srcDataInt4Ptr,
int64_t sendBufPtr,
uint64_t recvPtr,
size_t recvOff,
size_t sendOff,
int globalExpertIdx,
int dstRank,
int localExpertIdx,
int currRank,
int numRanks,
int maxTokensPerRank,
size_t numBytesPerSlot,
int hidden,
int hiddenBf16Int4,
int hiddenBf16Int4Pad,
int kNumMetaBytes,
int kNumTMABufferBytes,
bool zeroCopy,
uint64_t dstP2pPtr,
TmaBuffersT tmaBuffers,
FullBarriersT fullBarriers,
nv_bfloat162* metaBuffers,
uint32_t& tmaPhase,
TmaLoadAndArriveT& tmaLoadAndArrive,
GetNumTmaBytesT& getNumTmaBytes,
int laneId,
ncclDevComm* devComms,
const ncclWindow_t* windows) {
constexpr int kNumElemsPerInt4 = sizeof(int4) / sizeof(nv_bfloat16);
const int kNumIters = hiddenBf16Int4Pad / (32 * kNumSendUnrolls);
if (not zeroCopy or dstP2pPtr != 0) {
// Read from `copySrcPtr` and copy into `copyDstPtr`
const auto copySrcPtr = zeroCopy ? reinterpret_cast<int4*>(sendBufPtr) : srcDataInt4Ptr;
const auto copyDstPtr =
dstP2pPtr == 0 ? reinterpret_cast<int4*>(sendBufPtr) : reinterpret_cast<int4*>(dstP2pPtr);
// Prefetch
if (elect_one_sync())
tmaLoadAndArrive(0, copySrcPtr, getNumTmaBytes(0));
__syncwarp();
int tmaOffsetBytes = kNumMetaBytes;
#pragma unroll
for (int i = laneId * kNumSendUnrolls, iterIdx = 0; i < hiddenBf16Int4Pad; i += 32 * kNumSendUnrolls, ++iterIdx) {
// Load the next iteration
const int& stageIdx = iterIdx % kNumStages;
const int& nextStageIdx = (iterIdx + 1) % kNumStages;
if (iterIdx + 1 < kNumIters and elect_one_sync()) {
tma_store_wait<kNumStages - kNumPrefetch - 1>();
const auto& offsetInt4 = i + 32 * kNumSendUnrolls;
tmaLoadAndArrive(nextStageIdx, copySrcPtr + offsetInt4, getNumTmaBytes(offsetInt4));
}
__syncwarp();
// Wait the current TMA arrival
EP_STATIC_ASSERT(kNumStages < 32, "Too many stages");
mbarrier_wait<true>(fullBarriers[stageIdx], tmaPhase, stageIdx);
if constexpr (kUseLogFMT) {
// Cast if possible
constexpr int kNumInt4PerDivision = 128 / kNumElemsPerInt4;
int numTmaBytes = logfmtEncode<kNumSendUnrolls>(
tmaBuffers[stageIdx],
// NOTES: only the leader lane will write the result
(i % kNumInt4PerDivision == 0) ? metaBuffers + i / kNumInt4PerDivision : nullptr,
laneId);
if (elect_one_sync())
tma_store_1d(
tmaBuffers[stageIdx], reinterpret_cast<uint8_t*>(copyDstPtr) + tmaOffsetBytes, numTmaBytes);
tmaOffsetBytes += numTmaBytes;
} else {
// BF16 original values
if (elect_one_sync())
tma_store_1d(tmaBuffers[stageIdx], copyDstPtr + i, getNumTmaBytes(i));
}
__syncwarp();
}
// Store metadata (min/max values) for LogFMT
if constexpr (kUseLogFMT) {
if (elect_one_sync())
tma_store_1d(metaBuffers, copyDstPtr, kNumMetaBytes);
}
// Flush all stores
tma_store_wait<0>();
__syncwarp();
}
// Issue RDMA
// NOTES: for zero-copy mode, we assume the data is already in the send buffer
if (dstP2pPtr == 0) {
if (laneId == 0) {
sendTokenViaRdma(
globalExpertIdx, dstRank, localExpertIdx, currRank, numRanks,
maxTokensPerRank, tokenIdx, srcIdx, sendOff, recvOff,
numBytesPerSlot, hidden, devComms, windows);
}
}
}
template <bool kUseLogFMT, int kHidden, int kNumMaxTopk, int kNumMaxUnrolls>
__global__ __launch_bounds__(1024, 1) void combine(// INPUT
const void* inData,
const int* srcInfo,
const int64_t* layoutRange,
const int64_t* inTopkIdx,
const float* topkWeights,
int* rankMask,
// OUTPUT
void* outData,
// INTERMEDIATE
void* sendBuf,
void* recvBuf,
int* recvFlagBuf,
size_t sendOff,
size_t recvOff,
size_t recvFlagOff,
int* atomicCleanFlag,
int* nextRecvCntBuf,
int nextRecvCntBufSize,
int64_t* waitStats,
// CONFIG
int numCombinedTokens,
int hidden,
int numTopk,
int maxTokensPerRank,
int numExperts,
int currRank,
int numRanks,
int numWarpGroups,
int numWarpsPerGroup,
int phases,
bool zeroCopy,
int numComms,
ncclDevComm* devComms,
const ncclWindow_t* windows,
unsigned signalsBase) {
const auto smId = __shfl_sync(0xffffffff, static_cast<int>(blockIdx.x), 0);
const auto numSms = __shfl_sync(0xffffffff, static_cast<int>(gridDim.x), 0);
const auto threadId = static_cast<int>(threadIdx.x);
const auto numThreads = __shfl_sync(0xffffffff, static_cast<int>(blockDim.x), 0);
const auto warpId = __shfl_sync(0xffffffff, threadId / 32, 0), laneId = get_lane_id();
const auto numLocalExperts = numExperts / numRanks;
const auto warpGroupId = warpId / numWarpsPerGroup;
const auto subWarpId = warpId % numWarpsPerGroup;
const auto responsibleExpertIdx = smId * numWarpGroups + warpGroupId;
extern __shared__ __align__(1024) uint8_t smemBuffer[];
// Data type staffs
constexpr int kNumElemsPerInt4 = sizeof(int4) / sizeof(nv_bfloat16);
constexpr int64_t hiddenBf16Int4 = kHidden / kNumElemsPerInt4;
// Use different unroll factors for send and recv phases
constexpr int kNumSendUnrolls = kHidden % (32 * 4 * sizeof(int4) / sizeof(nv_bfloat16)) == 0 ? 4 : 2;
constexpr int kNumRecvUnrolls = 2;
constexpr int hiddenBf16Int4Pad = align(static_cast<int>(hiddenBf16Int4), 32 * kNumSendUnrolls);
EP_STATIC_ASSERT(kHidden % (32 * 2 * sizeof(int4) / sizeof(nv_bfloat16)) == 0, "Invalid hidden");
EP_STATIC_ASSERT(kNumSendUnrolls <= kNumMaxUnrolls and kNumRecvUnrolls <= kNumMaxUnrolls, "Invalid unrolls");
EP_STATIC_ASSERT(hiddenBf16Int4 % kNumSendUnrolls == 0, "Invalid hidden");
EP_STATIC_ASSERT(kNumSendUnrolls >= kNumRecvUnrolls, "Invalid unroll factors");
// Message package
EP_STATIC_ASSERT(kHidden % 128 == 0, "Invalid hidden");
constexpr int kNumDivisions = kHidden / 128;
constexpr int kNumMetaBytes = kNumDivisions * sizeof(nv_bfloat162);
constexpr size_t numBytesPerSlot = kHidden * sizeof(nv_bfloat16) + kNumMetaBytes;
EP_STATIC_ASSERT(numBytesPerSlot % sizeof(int4) == 0, "Invalid vectorization");
// Sending phase
if ((phases & LOW_LATENCY_SEND_PHASE) == 0)
goto LOW_LATENCY_COMBINE_RECV;
// Clean up next buffer
if (smId == 0 and warpGroupId == 0 and subWarpId == 0) {
cleanNextRecvCntBufAndNotify(
nextRecvCntBuf, nextRecvCntBufSize, atomicCleanFlag, numExperts, laneId);
}
// Issue IBGDA sends
if (responsibleExpertIdx < numExperts) {
const auto dstRank = responsibleExpertIdx / numLocalExperts;
const auto localExpertIdx = responsibleExpertIdx % numLocalExperts;
const auto globalExpertIdx = currRank * numLocalExperts + localExpertIdx;
const auto layout = __ldg(layoutRange + localExpertIdx * numRanks + dstRank);
const auto srcDataInt4 = static_cast<const int4*>(inData) +
localExpertIdx * numRanks * maxTokensPerRank * hiddenBf16Int4;
const auto localSrcInfo = srcInfo + localExpertIdx * numRanks * maxTokensPerRank;
const auto sendBufUint8 = static_cast<uint8_t*>(sendBuf) +
localExpertIdx * numRanks * maxTokensPerRank * numBytesPerSlot;
// Unpack layout
int offset, numTokensToSend;
unpack2(layout, numTokensToSend, offset);
// TMA stuffs
constexpr int kNumTMABufferBytes = sizeof(int4) * 32 * kNumSendUnrolls;
constexpr int kNumStages = 3;
constexpr int kNumPrefetch = 1;
EP_STATIC_ASSERT(kNumStages == 3 and kNumPrefetch == 1, "Invalid stages");
auto smemPtr = smemBuffer + warpId * (kNumStages * (kNumTMABufferBytes + 16) + kNumMetaBytes);
uint32_t tmaPhase = 0;
auto tmaBuffers = PatternVisitor([=](const int& i) { return reinterpret_cast<int4*>(smemPtr + i * (kNumTMABufferBytes + 16)); });
auto fullBarriers = PatternVisitor([=](const int& i) { return reinterpret_cast<uint64_t*>(smemPtr + i * (kNumTMABufferBytes + 16) + kNumTMABufferBytes); });
auto metaBuffers = kUseLogFMT ? reinterpret_cast<nv_bfloat162*>(smemPtr + kNumStages * (kNumTMABufferBytes + 16)) : nullptr;
EP_STATIC_ASSERT(kNumSendUnrolls * kNumStages <= 12, "TMA buffer size exceed limit");
// Initialize m-barriers
if (laneId < kNumStages) {
mbarrier_init(fullBarriers[laneId], 1);
fence_barrier_init();
}
__syncwarp();
auto tmaLoadAndArrive = [&](const int& stageIdx, const int4* gmemPtr, const int& numBytes) {
tma_load_1d(tmaBuffers[stageIdx], gmemPtr, fullBarriers[stageIdx], numBytes);
mbarrier_arrive_and_expect_tx(fullBarriers[stageIdx], numBytes);
};
auto getNumTmaBytes = [&](const int& offsetInt4) {
return min(kNumTMABufferBytes, static_cast<int>((hiddenBf16Int4 - offsetInt4) * sizeof(int4)));
};
// Issue IBGDA send
if (not isRankMasked<true>(rankMask, dstRank)) {
for (int tokenIdx = offset + subWarpId; tokenIdx < offset + numTokensToSend; tokenIdx += numWarpsPerGroup) {
const auto srcDataInt4Ptr = srcDataInt4 + tokenIdx * hiddenBf16Int4;
const auto sendBufTypeRow = reinterpret_cast<int*>(sendBufUint8 + tokenIdx * numBytesPerSlot);
const auto sendBufDataRow = reinterpret_cast<uint8_t*>(sendBufTypeRow);
// Copy directly to local rank, or copy to buffer and issue RDMA
const auto srcIdx = __shfl_sync(0xffffffff, __ldg(localSrcInfo + tokenIdx), 0);
const auto sendBufPtr = reinterpret_cast<int64_t>(sendBufDataRow);
const auto recvPtr = reinterpret_cast<uint64_t>(recvBuf) +
(globalExpertIdx * maxTokensPerRank + srcIdx) * numBytesPerSlot;
const auto expectedDstOffset =
recvOff + (globalExpertIdx * maxTokensPerRank + srcIdx) * numBytesPerSlot;
const auto dstP2pPtr =
ncclGetP2pPtr(recvPtr, expectedDstOffset, currRank, dstRank, localExpertIdx, windows, devComms);
processAndSendToken<kUseLogFMT, kHidden, kNumSendUnrolls, kNumStages, kNumPrefetch>(
tokenIdx, srcIdx, srcDataInt4Ptr, sendBufPtr, recvPtr,
recvOff, sendOff, globalExpertIdx, dstRank, localExpertIdx,
currRank, numRanks, maxTokensPerRank, numBytesPerSlot,
hidden, hiddenBf16Int4, hiddenBf16Int4Pad, kNumMetaBytes,
kNumTMABufferBytes, zeroCopy,
dstP2pPtr, tmaBuffers, fullBarriers, metaBuffers, tmaPhase,
tmaLoadAndArrive, getNumTmaBytes, laneId, devComms, windows);
}
}
// Put the finishing flag
EP_DEVICE_ASSERT(numWarpsPerGroup > 1 and numWarpGroups < 16);
asm volatile("bar.sync %0, %1;" :: "r"(warpGroupId + 1), "r"(numWarpsPerGroup * 32));
if (subWarpId == 1 and laneId == 0) {
while (ld_acquire_global(atomicCleanFlag) == 0);
auto recvFlagPtr = reinterpret_cast<uint64_t>(recvFlagBuf + globalExpertIdx);
size_t recvFlagOffset = recvFlagOff + globalExpertIdx * sizeof(int);
sendFinishFlag(
globalExpertIdx, dstRank, localExpertIdx, currRank, numLocalExperts,
recvFlagPtr, recvFlagOffset, recvFlagBuf, rankMask, signalsBase,
windows, devComms);
atomic_add_release_global(atomicCleanFlag, -1);
}
__syncwarp();
// Destroy m-barriers
if (laneId < kNumStages) {
mbarrier_inval(fullBarriers[laneId]);
fence_barrier_init();
}
__syncwarp();
}
// Receiving phase
LOW_LATENCY_COMBINE_RECV:
if ((phases & LOW_LATENCY_RECV_PHASE) == 0)
return;
// Wait all ranks to arrive
if (responsibleExpertIdx < numExperts) {
EP_DEVICE_ASSERT(numWarpsPerGroup > 1);
if (subWarpId == 0 and laneId == 0) {
const auto srcRank = responsibleExpertIdx / numLocalExperts;
waitForRecvFlag(
responsibleExpertIdx, srcRank, currRank, numLocalExperts,
recvFlagOff, recvFlagBuf, rankMask, signalsBase,
windows, devComms, waitStats);
}
}
cg::this_grid().sync();
// Reassign warp groups
constexpr int kMaxNumGroups = 2;
const int numDecodeWarps = hiddenBf16Int4Pad / (kNumRecvUnrolls * 32);
const int numGroups = min(kMaxNumGroups, (numThreads / 32) / (numDecodeWarps + 1));
const int decodeWarpIdx = __shfl_sync(0xffffffff, warpId % (numDecodeWarps + 1), 0);
const int groupIdx = __shfl_sync(0xffffffff, warpId / (numDecodeWarps + 1), 0);
EP_STATIC_ASSERT(kHidden % (32 * kNumElemsPerInt4) == 0, "Invalid vectorization");
EP_DEVICE_ASSERT(numTopk <= 32);
EP_DEVICE_ASSERT(numGroups > 0);
if (groupIdx < numGroups) {
constexpr int kNumStages = 3;
constexpr int kNumTMABufferBytes = 16 * 2 + kHidden * 2;
constexpr int kNumBF16PerWarpBytes = 32 * kNumRecvUnrolls * kNumElemsPerInt4 * 2;
constexpr int kNumLogFMTPerWarpBytes = kNumBF16PerWarpBytes / 16 * 10;
constexpr int kNumDivisionBytes = kNumDivisions * sizeof(uint32_t);
constexpr int kNumBytesPerGroup = kNumStages * kNumTMABufferBytes + kHidden * 2 + kNumStages * kNumDivisionBytes * 3;
// Reallocate shared memory
const auto smemGroupBuffer = smemBuffer + kNumBytesPerGroup * groupIdx;
auto fullBarriers = PatternVisitor([=](const int& i) { return reinterpret_cast<uint64_t*>(smemGroupBuffer + i * kNumTMABufferBytes); });
auto emptyBarriers = PatternVisitor([=](const int& i) { return reinterpret_cast<uint64_t*>(smemGroupBuffer + i * kNumTMABufferBytes + 8); });
auto tmaLdBuffers = PatternVisitor([=](const int& i) { return reinterpret_cast<uint8_t* >(smemGroupBuffer + i * kNumTMABufferBytes + 16); });
auto tmaStBuffers = PatternVisitor([=](const int& i) { return reinterpret_cast<uint32_t*>(smemGroupBuffer + kNumStages * kNumTMABufferBytes + i * kNumBF16PerWarpBytes); });
// Redundant when logfmt is disabled
const auto smemGroupPtr = smemGroupBuffer + kNumStages * kNumTMABufferBytes + kHidden * 2;
auto logAmaxBuffers = PatternVisitor([=](const int& i) { return reinterpret_cast<float*>(smemGroupPtr + i * kNumDivisionBytes); });
auto logAminBuffers = PatternVisitor([=](const int& i) { return reinterpret_cast<float*>(smemGroupPtr + kNumStages * kNumDivisionBytes + i * kNumDivisionBytes); });
auto castInfoBuffers = PatternVisitor([=](const int& i) { return reinterpret_cast<int*> (smemGroupPtr + kNumStages * kNumDivisionBytes * 2 + i * kNumDivisionBytes); });
uint32_t tmaPhase = 0;
EP_STATIC_ASSERT(kNumStages < 32, "Too many stages");
if (decodeWarpIdx == numDecodeWarps)
tmaPhase = (1 << kNumStages) - 1;
// Initialize m-barriers
if (decodeWarpIdx == numDecodeWarps and laneId < kNumStages) {
mbarrier_init(fullBarriers[laneId], 1);
mbarrier_init(emptyBarriers[laneId], numDecodeWarps);
}
asm volatile("bar.sync %0, %1;" :: "r"(groupIdx + 1), "r"((numDecodeWarps + 1) * 32));
int stageIdx = 0, topkIdxByLane = 0;
EP_STATIC_ASSERT(kNumMaxTopk <= 32, "Invalid number of topks");
if (decodeWarpIdx == numDecodeWarps) {
// TMA load warp
for (int tokenIdx = smId + numSms * groupIdx; tokenIdx < numCombinedTokens; tokenIdx += numSms * numGroups) {
if (laneId < numTopk)
topkIdxByLane = static_cast<int>(__ldg(inTopkIdx + tokenIdx * numTopk + laneId));
for (int i = 0; i < numTopk; ++ i) {
int topkIdxReg = __shfl_sync(0xffffffff, topkIdxByLane, i);
if (topkIdxReg < 0)
continue;
if (isRankMasked(rankMask, topkIdxReg / numLocalExperts))
continue;
mbarrier_wait<true>(emptyBarriers[stageIdx], tmaPhase, stageIdx);
auto recvBufData = static_cast<uint8_t*>(recvBuf) + (topkIdxReg * maxTokensPerRank + tokenIdx) * numBytesPerSlot;
if constexpr (kUseLogFMT) {
logfmtCheckAmaxmin<kNumDivisions / 2, kNumSendUnrolls, kNumRecvUnrolls>(
recvBufData,
reinterpret_cast<float2*>(logAmaxBuffers[stageIdx]),
reinterpret_cast<float2*>(logAminBuffers[stageIdx]),
castInfoBuffers[stageIdx],
laneId);
}
if (elect_one_sync()) {
int numCasted = 0;
if constexpr (kUseLogFMT) {
const auto& info = castInfoBuffers[stageIdx][numDecodeWarps - 1];
numCasted = (info >> 1) + (info & 1);
}
int numTmaBytes = numCasted * kNumLogFMTPerWarpBytes + (numDecodeWarps - numCasted) * kNumBF16PerWarpBytes;
tma_load_1d(tmaLdBuffers[stageIdx], recvBufData + (kUseLogFMT ? kNumMetaBytes : 0), fullBarriers[stageIdx], numTmaBytes);
mbarrier_arrive_and_expect_tx(fullBarriers[stageIdx], numTmaBytes);
}
__syncwarp();
stageIdx = (stageIdx + 1) % kNumStages;
}
}
} else {
// Reduction warps
float topkWeightsByLane;
for (int tokenIdx = smId + numSms * groupIdx; tokenIdx < numCombinedTokens; tokenIdx += numSms * numGroups) {
if (laneId < numTopk) {
topkIdxByLane = static_cast<int>(__ldg(inTopkIdx + tokenIdx * numTopk + laneId));
topkWeightsByLane = __ldg(topkWeights + tokenIdx * numTopk + laneId);
}
__syncwarp();
float combinedData[kNumElemsPerInt4 * kNumRecvUnrolls] = {0.0f};
for (int i = 0; i < numTopk; ++ i) {
int topkIdxReg = __shfl_sync(0xffffffff, topkIdxByLane, i);
if (topkIdxReg < 0)
continue;
if (isRankMasked(rankMask, topkIdxReg / numLocalExperts))
continue;
const auto& topkWeight = __shfl_sync(0xffffffff, topkWeightsByLane, i);
mbarrier_wait<true>(fullBarriers[stageIdx], tmaPhase, stageIdx);
if constexpr (kUseLogFMT) {
const auto& info = castInfoBuffers[stageIdx][decodeWarpIdx];
bool enableCast = info & 1;
int numCastedPrefix = info >> 1;
int tmaOffset = kNumLogFMTPerWarpBytes * numCastedPrefix + kNumBF16PerWarpBytes * (decodeWarpIdx - numCastedPrefix);
int divisionIdx = decodeWarpIdx * (kNumRecvUnrolls * 2) + laneId * kNumRecvUnrolls / 16;
decodeAndAccumulate<kNumRecvUnrolls>(
reinterpret_cast<uint32_t*>(tmaLdBuffers[stageIdx] + tmaOffset +
(enableCast ? kNumLogFMTPerWarpBytes : kNumBF16PerWarpBytes) / 32 * laneId),
combinedData,
logAmaxBuffers[stageIdx][divisionIdx],
logAminBuffers[stageIdx][divisionIdx],
enableCast,
topkWeight);
} else {
int tmaOffset = kNumBF16PerWarpBytes * decodeWarpIdx;
decodeAndAccumulate<kNumRecvUnrolls>(
reinterpret_cast<uint32_t*>(tmaLdBuffers[stageIdx] + tmaOffset + kNumBF16PerWarpBytes / 32 * laneId),
combinedData,
0,
0,
false,
topkWeight);
}
if (elect_one_sync())
mbarrier_arrive(emptyBarriers[stageIdx]);
stageIdx = (stageIdx + 1) % kNumStages;
}
tma_store_wait<0>();
#pragma unroll
for (int k = 0; k < kNumRecvUnrolls * 4; ++ k) {
auto combinedPack = __nv_bfloat162(combinedData[k * 2], combinedData[k * 2 + 1]);
tmaStBuffers[decodeWarpIdx][kNumRecvUnrolls * 4 * laneId + k] = *reinterpret_cast<uint32_t*>(&combinedPack);
}
tma_store_fence();
if (elect_one_sync()) {
tma_store_1d(tmaStBuffers[decodeWarpIdx],
static_cast<int4*>(outData) + tokenIdx * hiddenBf16Int4 + decodeWarpIdx * kNumRecvUnrolls * 32,
kNumBF16PerWarpBytes);
}
__syncwarp();
}
}
}
}
// Constants for combine kernel template parameters
constexpr int kCombineMaxTopk = 9;
constexpr int kCombineMaxUnrolls = 4;
void combine(const void* inData,
const int* srcInfo,
const int64_t* layoutRange,
const int64_t* inTopkIdx,
const float* topkWeights,
void* outData,
void* sendBuf,
void* recvBuf,
int* recvFlagBuf,
size_t sendOff,
size_t recvOff,
size_t recvFlagOff,
int* nextRecvCntBuf,
int nextRecvCntBufSize,
int64_t* waitStats,
int numCombinedTokens,
int hidden,
int maxTokensPerRank,
int numTopk,
int numExperts,
int currRank,
int numRanks,
bool useLogfmt,
int phases,
bool zeroCopy,
int numComms,
ncclDevComm* devComms,
const ncclWindow_t* windows,
unsigned signalsBase,
void* workspace,
int numDeviceSms,
cudaStream_t stream) {
const int numWarpGroups = ceil_div(numExperts, numDeviceSms);
const int numWarpsPerGroup = 32 / numWarpGroups;
const int numRecvPerSm = ceil_div(numCombinedTokens, numDeviceSms);
EP_HOST_ASSERT(numWarpGroups > 0 and numWarpsPerGroup > 0 and numRecvPerSm >= 0);
const auto numWarps = numWarpGroups * numWarpsPerGroup;
const auto numSms = max(ceil_div(numExperts, numWarpGroups),
numRecvPerSm == 0 ? 1 : ceil_div(numCombinedTokens, numRecvPerSm));
// Check workspace
auto atomicCleanFlag = static_cast<int*>(workspace);
EP_HOST_ASSERT(sizeof(int) <= NUM_WORKSPACE_BYTES);
EP_HOST_ASSERT(numTopk <= kCombineMaxTopk);
// Online cast cannot use zero-copy
EP_HOST_ASSERT(not (zeroCopy and useLogfmt));
constexpr int kNumStages = 3;
constexpr int kMaxNumGroups = 2;
// Send buffer size
const int numMetaBytes = hidden / 128 * 4;
const int numSendTmaBytes = 32 * sizeof(int4) * kCombineMaxUnrolls + 16;
const int smemSendSize = numWarps * (kNumStages * numSendTmaBytes + numMetaBytes);
// Receive buffer size
const int numRecvTmaBytes = 16 + hidden * 2;
const int smemRecvSize = kMaxNumGroups * (kNumStages * numRecvTmaBytes + hidden * 2 + kNumStages * numMetaBytes * 3);
// Total requirement
const int smem_size = max(smemSendSize, smemRecvSize);
#define COMBINE_LAUNCH_CASE(hidden) { \
if (useLogfmt) { \
SET_SHARED_MEMORY_FOR_TMA((combine<true, hidden, kCombineMaxTopk, kCombineMaxUnrolls>)); \
LAUNCH_KERNEL(&cfg, combine<true, hidden, kCombineMaxTopk, kCombineMaxUnrolls>, \
inData, \
srcInfo, \
layoutRange, \
inTopkIdx, \
topkWeights, \
/*rankMask=*/nullptr, \
outData, \
sendBuf, \
recvBuf, \
recvFlagBuf, \
sendOff, \
recvOff, \
recvFlagOff, \
atomicCleanFlag, \
nextRecvCntBuf, \
nextRecvCntBufSize, \
waitStats, \
numCombinedTokens, \
hidden, \
numTopk, \
maxTokensPerRank, \
numExperts, \
currRank, \
numRanks, \
numWarpGroups, \
numWarpsPerGroup, \
phases, \
zeroCopy, \
numComms, \
devComms, \
windows, \
signalsBase); \
} else { \
SET_SHARED_MEMORY_FOR_TMA((combine<false, hidden, kCombineMaxTopk, kCombineMaxUnrolls>)); \
LAUNCH_KERNEL(&cfg, combine<false, hidden, kCombineMaxTopk, kCombineMaxUnrolls>, \
inData, \
srcInfo, \
layoutRange, \
inTopkIdx, \
topkWeights, \
/*rankMask=*/nullptr, \
outData, \
sendBuf, \
recvBuf, \
recvFlagBuf, \
sendOff, \
recvOff, \
recvFlagOff, \
atomicCleanFlag, \
nextRecvCntBuf, \
nextRecvCntBufSize, \
waitStats, \
numCombinedTokens, \
hidden, \
numTopk, \
maxTokensPerRank, \
numExperts, \
currRank, \
numRanks, \
numWarpGroups, \
numWarpsPerGroup, \
phases, \
zeroCopy, \
numComms, \
devComms, \
windows, \
signalsBase); \
} } break
SETUP_LAUNCH_CONFIG(numSms, numWarps * 32, stream);
SWITCH_HIDDEN(COMBINE_LAUNCH_CASE);
#undef COMBINE_LAUNCH_CASE
}
} // namespace internode_ll
} // namespace nccl_ep