Files
rocm-systems/src/device/common.h
T

397 lines
15 KiB
C++
Raw Normal View History

2018-09-24 16:06:59 -07:00
/*************************************************************************
2022-01-07 06:39:55 -08:00
* Copyright (c) 2017-2022, NVIDIA CORPORATION. All rights reserved.
* Modifications Copyright (c) 2019-2023 Advanced Micro Devices, Inc. All rights reserved.
2018-09-24 16:06:59 -07:00
*
* See LICENSE.txt for license information
************************************************************************/
#ifndef NCCL_DEVICE_COMMON_H_
#define NCCL_DEVICE_COMMON_H_
2019-11-19 14:57:39 -08:00
#include "collectives.h"
2023-09-26 05:47:28 -07:00
#include "device.h"
2022-01-07 06:39:55 -08:00
#include "op128.h"
#include "device_table.h"
2023-09-26 05:47:28 -07:00
#include "network/unpack/unpack_defs.h"
2018-09-24 16:06:59 -07:00
#if defined(__gfx908__) || defined(__gfx940__) || defined(__gfx941__) || defined(__gfx942__)
#define COLL_UNROLL 2
#else
2023-05-25 15:42:18 -05:00
#define COLL_UNROLL 4
#endif
#define NCCL_MAX_DEV_ARITY (NCCL_MAX_TREE_ARITY-1) // Using balanced tree instead of split tree
2019-11-26 16:33:13 -08:00
#define __syncwarp()
2019-07-05 15:43:00 -07:00
#define __synclds() \
asm volatile("s_waitcnt lgkmcnt(0) \n s_barrier");
#ifdef __GFX9__
2022-09-28 16:49:29 -07:00
#define STORE(DST, SRC) \
{ __atomic_store_n((DST), (SRC), __ATOMIC_RELAXED); }
2022-09-28 16:49:29 -07:00
#else
#define STORE(DST, SRC) \
{ __atomic_store_n((DST), (SRC), __ATOMIC_SEQ_CST); }
#endif
2022-09-29 14:53:55 -06:00
#if defined(__gfx1100__) || defined(__gfx1101__) || defined(__gfx1102__)
#define __trace_hwreg()
#else
#define __trace_hwreg() \
asm volatile ("s_getreg_b32 %0, hwreg(HW_REG_HW_ID)" : "=s" (collTrace->data_0));
#endif
#ifdef ENABLE_COLLTRACE
2023-08-03 07:16:12 -07:00
#define INC_COLL_TRACE \
uint32_t pos = atomicAdd(&ncclShmem.collTraceTail->tail, 1)%COLLTRACE_NUM_ITEMS; \
struct ncclCollTrace* collTrace = ncclShmem.collTrace+pos; \
2022-09-29 14:53:55 -06:00
collTrace->timeStamp = wall_clock64(); \
2023-08-03 07:16:12 -07:00
collTrace->bid = blockIdx.x;
// TODO: switch to atomicInc after llvm crash is fixed
// uint32_t pos = atomicInc(&ncclShmem.collTraceTail->tail, COLLTRACE_NUM_ITEMS)
#define traceKernelLaunch(launch_type) { \
INC_COLL_TRACE \
2022-09-20 09:00:20 -07:00
collTrace->funcIndex = ncclShmem.work.header.funcIndex; \
__trace_hwreg()\
2022-09-20 09:00:20 -07:00
if (ncclShmem.work.header.type == ncclWorkTypeP2p) { \
struct ncclWorkElemP2p *p2pElems = ncclShmem.work.p2pElems; \
collTrace->p2p[0].connIndex = 0; \
collTrace->p2pOpCount[0] = p2pElems[0].opCount; \
collTrace->p2p[0].ngroups = p2pElems[0].ngroups; \
collTrace->p2p[0].nWarps = p2pElems[0].nWarps; \
collTrace->p2p[0].warpStart = p2pElems[0].warpStart; \
collTrace->p2p[0].peer = p2pElems[0].p2pType == ncclWorkP2pTypeRecv ? (uint16_t)(p2pElems[0].peer) : -1; \
collTrace->p2p[1].connIndex = 0; \
collTrace->p2pOpCount[1] = p2pElems[1].opCount; \
collTrace->p2p[1].ngroups = p2pElems[1].ngroups; \
collTrace->p2p[1].nWarps = p2pElems[1].nWarps; \
collTrace->p2p[1].warpStart = p2pElems[1].warpStart; \
collTrace->p2p[1].peer = p2pElems[1].p2pType == ncclWorkP2pTypeSend ? (uint16_t)(p2pElems[1].peer) : -1; \
collTrace->type = (launch_type) | ncclCollTraceP2pElemType; \
2022-09-20 09:00:20 -07:00
} else if (ncclShmem.work.header.type == ncclWorkTypeColl) { \
struct ncclWorkElem *elems = ncclShmem.work.elems; \
collTrace->opCount = elems[0].opCount; \
collTrace->coll.nWarps = elems[0].nWarps; \
collTrace->coll.bid = elems[0].bid; \
collTrace->coll.nChannels = elems[0].nChannels; \
collTrace->type = (launch_type) | ncclCollTraceCollElemType; \
} \
}
2023-08-03 07:16:12 -07:00
#define traceKernelEnd(end_type) { \
INC_COLL_TRACE \
if (ncclShmem.work.header.type == ncclWorkTypeP2p) { \
struct ncclWorkElemP2p *p2pElems = ncclShmem.work.p2pElems; \
collTrace->p2pOpCount[0] = p2pElems[0].opCount; \
collTrace->p2pOpCount[1] = p2pElems[1].opCount; \
} else if (ncclShmem.work.header.type == ncclWorkTypeColl) { \
struct ncclWorkElem *elems = ncclShmem.work.elems; \
collTrace->opCount = elems[0].opCount; \
} \
collTrace->type = end_type; \
2022-09-29 14:53:55 -06:00
}
#define traceData(data2, data4, data8_0, data8_1) { \
2023-08-03 07:16:12 -07:00
INC_COLL_TRACE \
2022-09-29 14:53:55 -06:00
collTrace->funcIndex = data2; \
collTrace->data_0 = data4; \
collTrace->opCount = data8_0; \
collTrace->data_1 = data8_1; \
collTrace->type = ncclCollTraceDataType; \
}
2019-11-26 16:33:13 -08:00
#else
2023-08-03 07:16:12 -07:00
#define traceKernelLaunch(launch_type)
#define traceKernelEnd(end_type)
#define traceData(data2, data4, data8_0, data8_1)
2019-11-26 16:33:13 -08:00
#endif
2022-05-24 02:02:31 -07:00
struct ncclShmemGroup {
2023-02-27 02:48:21 -08:00
ncclConnInfo *recvConns[NCCL_MAX_NVLS_ARITY];
ncclConnInfo *sendConns[NCCL_MAX_NVLS_ARITY];
void* srcs[NCCL_MAX_NVLS_ARITY+1];
void* dsts[NCCL_MAX_NVLS_ARITY+1];
uint64_t barrier;
uint64_t barrier_next[NCCL_MAX_GROUPS];
2023-09-26 05:47:28 -07:00
union {
unpackGroupShmem unpack;
} devicePlugin;
2024-02-05 05:06:02 -08:00
int32_t dstSizes[NCCL_MAX_NVLS_ARITY+1];
2022-05-24 02:02:31 -07:00
};
#define LDS_NUM_EVENTS 64
2022-05-24 02:02:31 -07:00
struct ncclShmemData {
2023-02-27 02:48:21 -08:00
struct ncclShmemGroup groups[NCCL_MAX_GROUPS];
uint64_t redOpArgs[NCCL_MAX_NVLS_ARITY+1];
2022-05-24 02:02:31 -07:00
int channelId;
2022-11-29 04:27:46 -08:00
int aborted;
2022-05-24 02:02:31 -07:00
alignas(16) struct ncclDevComm comm;
alignas(16) struct ncclDevChannel channel;
alignas(16) struct ncclWork work;
2023-09-26 05:47:28 -07:00
alignas(16) union {
unpackShmem unpack;
} devicePlugin;
2023-08-03 07:16:12 -07:00
#ifdef ENABLE_COLLTRACE
struct ncclCollTrace* collTrace;
union ncclCollTraceTail* collTraceTail;
#endif
#ifdef ENABLE_PROFILING
struct ncclProf prof;
#endif
#if defined(ENABLE_NPKIT)
NpKitEvent event_buffer[LDS_NUM_EVENTS];
uint64_t event_buffer_head;
#endif
2022-05-24 02:02:31 -07:00
};
2022-09-20 09:00:20 -07:00
static_assert(offsetof(struct ncclShmemData, work)%16 == 0, "ncclShmem.work needs to be 16B aligned");
2022-05-24 02:02:31 -07:00
extern __shared__ ncclShmemData ncclShmem;
2023-02-27 02:48:21 -08:00
#if __CUDA_ARCH__ >= 700
extern __shared__ ulong2 ncclShmemPerWarp[/*ncclShmemDynamicSize()/sizeof(ulong2)*/];
#else
extern __shared__ ulong2 ncclShmemPerWarp[ncclShmemScratchWarpSize()*(NCCL_MAX_NTHREADS/WARP_SIZE)/sizeof(ulong2)];
#endif
__device__ inline void* ncclScratchForWarp(int warp) {
return (char*)ncclShmemPerWarp + warp*ncclShmemScratchWarpSize();
}
2022-05-24 02:02:31 -07:00
#ifdef ENABLE_PROFILING
#define __insert_timestamp(line_num) do { \
2022-09-20 09:00:20 -07:00
if (ncclShmem.prof.count < PROFILE_NUM_ITEMS) { \
ncclShmem.prof.elem[ncclShmem.prof.count].line = line_num; \
ncclShmem.prof.elem[ncclShmem.prof.count].timeStamp = wall_clock64(); \
2022-09-20 09:00:20 -07:00
ncclShmem.prof.count++; \
} \
} while(0);
#else
#define __insert_timestamp(line_num)
#endif
2022-05-24 02:02:31 -07:00
// Copy 16-byte aligned data. You must call with at least `(bytes+15)/16` threads.
inline __device__ void copyToShmem16(int tid, void* dst, void const* src, int bytes) {
int offset = 16*tid;
if (offset < bytes) {
ulong2 *src2, *dst2;
src2 = (ulong2*)((char const*)src + offset);
dst2 = (ulong2*)((char*)dst + offset);
dst2->x = src2->x;
dst2->y = src2->y;
}
2022-01-07 06:39:55 -08:00
}
2021-07-08 14:12:04 -07:00
template<ncclFunc_t Fn, typename T, typename RedOp, int Algo, int Proto>
struct RunWorkElement {
__device__ void run(ncclWorkElem*) {
// Put NOT IMPLEMENTED behavior here.
}
};
2018-09-24 16:06:59 -07:00
2021-07-08 14:12:04 -07:00
template<ncclFunc_t Fn, typename T, typename RedOp, int Algo, int Proto>
struct RunWork {
2021-09-08 13:56:25 -07:00
// This __forceinline__ is necessary. The compiler was inserting a function call
// here from the LL ncclKernel.
__device__ __forceinline__ void run(ncclWork *w) {
2022-01-07 06:39:55 -08:00
int wid = threadIdx.x / WARP_SIZE;
2022-05-24 02:02:31 -07:00
ncclWorkElem* we = w->header.type == ncclWorkTypeRegColl ? &w->regElems[0].elem : &w->elems[0];
int stride = w->header.type == ncclWorkTypeRegColl ? sizeof(ncclWorkElemReg) : sizeof(ncclWorkElem);
2022-01-07 06:39:55 -08:00
#pragma unroll 1
2022-05-24 02:02:31 -07:00
while ((char*)we + stride <= (char*)(w+1) && we->isUsed) {
if (wid < we->nWarps) {
RunWorkElement<Fn, T, RedOp, Algo, Proto>().run(we);
}
we = (ncclWorkElem*)((char*)we + stride);
2021-07-08 14:12:04 -07:00
}
}
2020-09-04 14:35:05 -07:00
};
2022-09-20 09:00:20 -07:00
static __forceinline__ __device__ void ncclRedopPtrDeref(struct ncclWorkElem* we) {
2022-05-24 02:02:31 -07:00
if (we->isUsed && we->redOpArgIsPtr) {
2022-01-07 06:39:55 -08:00
/* redOpArg is a pointer to the scalar value, so we'll dereference it
* here so that redOpArg holds the bits of the scalar going forward.
* The tricky thing is we don't know its type T since that's encoded in
* the funcIndex. Because it would be difficult to get sizeof(T) from
* funcIndex, we'll cheat and just dereference the largest possible size
* given the alignment of the pointer. We might be reading in more bytes
* than we need but that's harmless.
*/
if (we->redOpArg%2 != 0)
we->redOpArg = *reinterpret_cast<uint8_t*>(we->redOpArg);
else if (we->redOpArg%4 != 0)
we->redOpArg = *reinterpret_cast<uint16_t*>(we->redOpArg);
else if (we->redOpArg%8 != 0)
we->redOpArg = *reinterpret_cast<uint32_t*>(we->redOpArg);
else
we->redOpArg = *reinterpret_cast<uint64_t*>(we->redOpArg);
}
}
2019-11-19 14:57:39 -08:00
template<int SpecializedFnId, typename SpecializedRunWork, bool COLLTRACE>
2024-01-31 17:09:07 -08:00
__forceinline__ __device__ void ncclKernelMain(struct ncclDevComm* comm, uint64_t channelMask, struct ncclWork* workHead) {
const int tid = threadIdx.x;
int x = tid;
switch (tid/WARP_SIZE) {
case 0:
2022-05-24 02:02:31 -07:00
if (channelMask & (1ull<<x)) {
int y = __popcll(channelMask & ((1ull<<x)-1));
2022-09-20 09:00:20 -07:00
if (blockIdx.x == y) ncclShmem.channelId = x;
2022-05-24 02:02:31 -07:00
}
2023-12-01 16:01:11 -08:00
if (WARP_SIZE < MAXCHANNELS) {
x = WARP_SIZE + tid;
2022-05-24 02:02:31 -07:00
if (channelMask & (1ull<<x)) {
int y = __popcll(channelMask & ((1ull<<x)-1));
2022-09-20 09:00:20 -07:00
if (blockIdx.x == y) ncclShmem.channelId = x;
2022-05-24 02:02:31 -07:00
}
}
break;
case 1:
if (tid < WARP_SIZE + NCCL_MAX_GROUPS)
ncclShmem.groups[tid-WARP_SIZE].barrier = 0;
break;
case 2:
if (tid < 2*WARP_SIZE + NCCL_MAX_GROUPS*NCCL_MAX_GROUPS)
ncclShmem.groups[(tid-2*WARP_SIZE)/NCCL_MAX_GROUPS].barrier_next[(tid-2*WARP_SIZE)%NCCL_MAX_GROUPS] = 0;
break;
case 3:
/* set abort flag to 0 */
if (tid == 3*WARP_SIZE) ncclShmem.aborted = 0;
break;
default:
break;
2022-05-24 02:02:31 -07:00
}
__synclds(); // publish ncclShmem.channelId
// To map blockId to channelId, we need the n'th set bit of channelMask which
// is the inverse of counting the number of set bits among the the first n.
2022-09-20 09:00:20 -07:00
int channelId = ncclShmem.channelId;
2022-05-24 02:02:31 -07:00
if (true) {
void *dst, *src;
int bytes;
// Use first 3 warps to load comm, channel, and work into shmem
2022-05-24 02:02:31 -07:00
switch (tid/WARP_SIZE) {
case 0:
2022-09-20 09:00:20 -07:00
dst = &ncclShmem.comm;
2022-05-24 02:02:31 -07:00
src = comm;
bytes = sizeof(ncclDevComm);
static_assert(sizeof(ncclDevComm) <= 16*WARP_SIZE, "ncclDevComm cannot be loaded by a single warp in one insn.");
break;
case 1:
// Get address of channel without incurring indirect load from ncclDevComm::channels
2022-09-20 09:00:20 -07:00
dst = &ncclShmem.channel;
2022-05-24 02:02:31 -07:00
src = &((ncclDevCommAndChannels*)comm)->channels[channelId];
bytes = sizeof(ncclDevChannel);
static_assert(sizeof(ncclDevChannel) <= 16*WARP_SIZE, "ncclDevChannel cannot be loaded by a single warp in one insn.");
break;
case 2:
2022-09-20 09:00:20 -07:00
dst = &ncclShmem.work;
2022-05-24 02:02:31 -07:00
src = workHead + blockIdx.x;
bytes = sizeof(ncclWork);
static_assert(sizeof(ncclWork) <= 16*WARP_SIZE, "ncclWork cannot be loaded by a single warp in one insn.");
break;
default:
bytes = 0;
break;
}
2023-09-26 05:47:28 -07:00
if (bytes) copyToShmem16(tid%WARP_SIZE, dst, src, bytes);
2021-07-08 14:12:04 -07:00
}
2023-08-03 07:16:12 -07:00
#ifdef ENABLE_COLLTRACE
if (tid == 0) {
ncclShmem.collTrace = comm->collTrace + COLLTRACE_NUM_ITEMS*ncclShmem.channelId;
ncclShmem.collTraceTail = comm->collTraceTail + ncclShmem.channelId;
}
#endif
__synclds(); // publish shmem
#ifdef ENABLE_PROFILING
if (tid == 0) {
2022-09-20 09:00:20 -07:00
ncclShmem.prof.count = 0;
ncclShmem.prof.seq = ncclShmem.comm.devProf[blockIdx.x].seq;
}
#endif
if (tid == 0) __insert_timestamp(__LINE__);
2023-08-03 07:16:12 -07:00
if (COLLTRACE && tid == 0) traceKernelLaunch(ncclCollTraceKernelLaunchType);
2021-10-28 07:26:11 -07:00
while (true) {
2022-05-24 02:02:31 -07:00
// Notify host that all fifo reads are complete.
2022-09-20 09:00:20 -07:00
if (tid == 0 && ncclShmem.work.header.isLast && ncclShmem.work.header.inFifo) {
*ncclShmem.channel.workFifoDone = ncclShmem.work.header.doneAcks;
2020-09-04 14:35:05 -07:00
}
2021-10-28 07:26:11 -07:00
2022-01-07 06:39:55 -08:00
__syncwarp();
2022-09-20 09:00:20 -07:00
if (ncclShmem.work.header.type == ncclWorkTypeColl) {
if (tid < NCCL_MAX_WORK_ELEMENTS) ncclRedopPtrDeref(&ncclShmem.work.elems[tid]);
} else if (ncclShmem.work.header.type == ncclWorkTypeRegColl) {
if (tid < NCCL_MAX_WORK_ELEMENTS_REG) ncclRedopPtrDeref(&ncclShmem.work.regElems[tid].elem);
2021-09-08 13:56:25 -07:00
}
__synclds();
2021-09-08 13:56:25 -07:00
if (tid == 0) __insert_timestamp(__LINE__);
2021-09-08 13:56:25 -07:00
2023-09-26 05:47:28 -07:00
if (0 <= SpecializedFnId && ncclShmem.work.header.funcIndex == (unsigned)SpecializedFnId) {
SpecializedRunWork().run(&ncclShmem.work);
2022-05-24 02:02:31 -07:00
} else {
2024-01-18 15:07:16 -07:00
#ifdef USE_INDIRECT_FUNCTION_CALL
2023-09-26 05:47:28 -07:00
ncclDevFuncTable[ncclShmem.work.header.funcIndex]();
2023-07-21 07:31:27 -07:00
#else
NCCL_CALL_FUNCTIONS(ncclShmem.work.header.funcIndex);
#endif
2022-05-24 02:02:31 -07:00
}
2021-10-28 07:26:11 -07:00
2022-09-20 09:00:20 -07:00
int workIxNext = ncclShmem.work.header.workNext;
__synclds();
2022-09-20 09:00:20 -07:00
if (ncclShmem.work.header.isLast) break;
2022-05-24 02:02:31 -07:00
2022-09-20 09:00:20 -07:00
copyToShmem16(tid, &ncclShmem.work, workHead + workIxNext, sizeof(ncclWork));
2022-05-24 02:02:31 -07:00
{ // Check whether the last operation was aborted and make sure all threads exit
int aborted = tid == 0 ? *comm->abortFlag : 0;
2022-09-20 09:00:20 -07:00
if (__any(aborted)) { // publish ncclShmem.work
2023-08-03 07:16:12 -07:00
traceKernelEnd(ncclCollTraceAbortType);
2022-05-24 02:02:31 -07:00
break;
}
2022-05-24 02:02:31 -07:00
}
2023-08-03 07:16:12 -07:00
if (COLLTRACE && tid == 0) traceKernelLaunch(ncclCollTraceCollLaunchType);
2020-09-04 14:35:05 -07:00
}
2023-08-03 07:16:12 -07:00
if (COLLTRACE && tid == 0) traceKernelEnd(ncclCollTraceKernelEndType);
2023-06-21 16:16:09 -04:00
#ifdef ENABLE_PROFILING
2022-09-20 09:00:20 -07:00
if (ncclShmem.comm.devProf->seq < PROFILE_NUM_LAUNCHES) {
__synclds();
2022-09-20 09:00:20 -07:00
copyToShmem16(tid, ncclShmem.comm.devProf+MAXCHANNELS*ncclShmem.prof.seq+blockIdx.x, &ncclShmem.prof, sizeof(struct ncclProf));
if (tid == 0) ncclShmem.comm.devProf[blockIdx.x].seq++;
}
#endif
2018-09-24 16:06:59 -07:00
}
2018-12-13 15:56:12 -08:00
2023-09-26 05:47:28 -07:00
__global__ void ncclDevKernel_Generic(struct ncclDevComm* comm, uint64_t channelMask, struct ncclWork* workHead);
2023-06-21 16:16:09 -04:00
#ifdef ENABLE_COLLTRACE
__global__ void ncclDevKernelDebug_Generic(struct ncclDevComm* comm, uint64_t channelMask, struct ncclWork* workHead);
2023-06-21 16:16:09 -04:00
#endif
2019-09-24 17:01:12 -07:00
#ifdef ENABLE_COLLTRACE
2023-09-26 05:47:28 -07:00
#define DEFINE_ncclDevKernel(suffix, coll, redop, ty, algo, proto, specializedFnId) \
__global__ void ncclDevKernel_##suffix(struct ncclDevComm* comm, uint64_t channelMask, struct ncclWork* workHead) { \
ncclKernelMain<specializedFnId, RunWork<coll, ty, redop<ty>, algo, proto>, false>(comm, channelMask, workHead); \
} \
\
__global__ void ncclDevKernelDebug_##suffix(struct ncclDevComm* comm, uint64_t channelMask, struct ncclWork* workHead) { \
ncclKernelMain<specializedFnId, RunWork<coll, ty, redop<ty>, algo, proto>, true>(comm, channelMask, workHead); \
2023-09-26 05:47:28 -07:00
}
#else
#define DEFINE_ncclDevKernel(suffix, coll, redop, ty, algo, proto, specializedFnId) \
__global__ void ncclDevKernel_##suffix(struct ncclDevComm* comm, uint64_t channelMask, struct ncclWork* workHead) { \
ncclKernelMain<specializedFnId, RunWork<coll, ty, redop<ty>, algo, proto>, false>(comm, channelMask, workHead); \
}
#endif
2018-12-13 15:56:12 -08:00
#ifdef USE_INDIRECT_FUNCTION_CALL
2023-09-26 05:47:28 -07:00
#define DEFINE_ncclDevFunc(suffix, coll, redop, ty, algo, proto) \
__device__ void ncclDevFunc_##suffix() { \
RunWork<coll, ty, redop<ty>, algo, proto>().run(&ncclShmem.work); \
}
#else
#define DEFINE_ncclDevFunc(suffix, coll, redop, ty, algo, proto) \
__device__ __attribute__((noinline)) void ncclDevFunc_##suffix() { \
RunWork<coll, ty, redop<ty>, algo, proto>().run(&ncclShmem.work); \
}
2018-09-24 16:06:59 -07:00
#endif
2023-02-27 02:48:21 -08:00
2024-01-18 15:07:16 -07:00
#endif