파일
rocm-systems/src/dev_runtime.cc
T

996 라인
35 KiB
C++
Raw 일반 보기 히스토리

2025-09-02 13:21:14 -07:00
/*************************************************************************
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/
#include "dev_runtime.h"
#include "comm.h"
#include "device.h"
#include "transport.h"
#include "group.h"
#include "nccl_device.h"
NCCL_PARAM(WinStride, "WIN_STRIDE", -1);
// Complete types from src/include/dev_runtime.h
struct ncclDevrMemory {
int refCount;
struct ncclDevrMemory* next;
CUmemGenericAllocationHandle memHandle;
size_t size;
size_t bigOffset; // offset in big VA space
};
struct ncclDevrWindowSorted {
uintptr_t userAddr;
size_t size;
struct ncclDevrWindow* win;
};
struct ncclDevrTeam {
struct ncclDevrTeam* next;
struct ncclTeam team;
CUmemGenericAllocationHandle mcHandle;
void* mcBasePtr;
int worldRankList[];
};
////////////////////////////////////////////////////////////////////////////////
// Helpers at the bottom:
// Find least index such that `arg < sorted[i].key` (least upper bound)
template<typename Obj, typename Key>
static int listFindSortedLub(Key Obj::*key, Obj* sorted, int count, Key arg);
template<typename Obj>
static void listInsert(Obj** list, int* capacity, int* count, int index, Obj val);
template<typename Obj>
static void listRemove(Obj* list, int* count, int index);
////////////////////////////////////////////////////////////////////////////////
ncclResult_t ncclDevrInitOnce(struct ncclComm* comm) {
ncclResult_t ret = ncclSuccess;
struct ncclDevrState* devr = &comm->devrState;
if (devr->bigSize != 0) return ncclSuccess;
bool lsaIsLocal = true;
for (int i=0; i < comm->localRanks; i++) {
lsaIsLocal &= comm->localRankToRank[i] == comm->localRankToRank[0] + i;
}
devr->lsaSelf = lsaIsLocal ? comm->localRank : 0;
devr->lsaSize = lsaIsLocal ? comm->localRanks : 1;
devr->lsaRankList = (int*)malloc(devr->lsaSize*sizeof(int));
for (int i=0; i < devr->lsaSize; i++) {
devr->lsaRankList[i] = comm->rank + (i - devr->lsaSelf);
}
CUmemAllocationProp memProp = {};
memProp.type = CU_MEM_ALLOCATION_TYPE_PINNED;
memProp.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
memProp.requestedHandleTypes = ncclCuMemHandleType;
memProp.location.id = comm->cudaDev;
CUCHECKGOTO(cuMemGetAllocationGranularity(&devr->granularity, &memProp, CU_MEM_ALLOC_GRANULARITY_RECOMMENDED), ret, fail_lsaRankList);
devr->bigSize = ncclParamWinStride();
if (-devr->bigSize <= 1) {
devr->bigSize = 1;
for (int r=0; r < comm->nRanks; ++r) {
devr->bigSize = std::max<size_t>(devr->bigSize, comm->peerInfo[r].totalGlobalMem);
}
}
devr->bigSize = alignUp(devr->bigSize, size_t(1)<<32);
INFO(NCCL_INIT, "Symmetric VA size=%ldGB", (long)devr->bigSize>>30);
ncclSpaceConstruct(&devr->bigSpace);
ncclShadowPoolConstruct(&devr->shadows);
return ncclSuccess;
fail_lsaRankList:
free(devr->lsaRankList);
return ret;
}
static void symTeamDestroyAll(struct ncclComm* comm); // Further down
ncclResult_t ncclDevrFinalize(struct ncclComm* comm) {
struct ncclDevrState* devr = &comm->devrState;
if (devr->bigSize == 0) return ncclSuccess;
while (!ncclIntruQueueEmpty(&devr->regTaskQueue)) {
struct ncclDevrRegTask* task = ncclIntruQueueDequeue(&devr->regTaskQueue);
free(task);
}
symTeamDestroyAll(comm);
{ // delete windowTable
cudaStream_t stream;
if (cudaSuccess == cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking)) {
struct ncclDevCommWindowTable* tableDev = devr->windowTable;
while (tableDev != nullptr) {
struct ncclDevCommWindowTable* tableHost;
if (ncclSuccess != ncclShadowPoolToHost(&devr->shadows, tableDev, &tableHost)) break;
struct ncclDevCommWindowTable* next = tableHost->next;
ncclShadowPoolFree(&devr->shadows, tableDev, stream);
tableDev = next;
}
cudaStreamSynchronize(stream);
cudaStreamDestroy(stream);
}
}
CUdeviceptr flatAddr = reinterpret_cast<CUdeviceptr>(devr->lsaFlatBase);
CUCHECKIGNORE(cuMemUnmap(flatAddr, devr->lsaSize*devr->bigSize));
CUCHECKIGNORE(cuMemAddressFree(flatAddr, devr->lsaSize*devr->bigSize));
ncclShadowPoolDestruct(&devr->shadows);
ncclSpaceDestruct(&devr->bigSpace);
free(devr->lsaRankList);
free(devr->winSorted);
return ncclSuccess;
}
////////////////////////////////////////////////////////////////////////////////
static ncclResult_t symMemoryMapLsaTeam(
struct ncclComm* comm, CUmemGenericAllocationHandle memHandle, size_t size, size_t bigOffset
) {
ncclResult_t ret = ncclSuccess;
struct ncclDevrState* devr = &comm->devrState;
CUmemAccessDesc accessDesc = {};
union Message {
CUmemGenericAllocationHandle memHandle;
CUmemFabricHandle fabricHandle;
};
Message* messages = (Message*)calloc(devr->lsaSize, sizeof(Message));
if (ncclCuMemHandleType == CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR) {
messages[devr->lsaSelf].memHandle = memHandle;
} else {
CUCHECKGOTO(cuMemExportToShareableHandle(&messages[devr->lsaSelf].fabricHandle, memHandle, ncclCuMemHandleType, 0), ret, fail);
}
NCCLCHECKGOTO(bootstrapIntraNodeAllGather(comm->bootstrap, devr->lsaRankList, devr->lsaSelf, devr->lsaSize, messages, sizeof(Message)), ret, fail);
if (devr->lsaFlatBase == nullptr) { // Create on first need.
CUdeviceptr addr;
CUCHECKGOTO(cuMemAddressReserve(&addr, devr->lsaSize*devr->bigSize, NCCL_MAX_PAGE_SIZE, 0, 0), ret, fail);
devr->lsaFlatBase = reinterpret_cast<void*>(addr);
}
accessDesc.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
accessDesc.location.id = comm->cudaDev;
accessDesc.flags = CU_MEM_ACCESS_FLAGS_PROT_READWRITE;
for (int r = 0; r < devr->lsaSize; r++) {
CUmemGenericAllocationHandle impHandle;
if (r == devr->lsaSelf) {
impHandle = memHandle;
} else {
if (ncclCuMemHandleType == CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR) {
int fd = -1;
NCCLCHECKGOTO(ncclProxyClientGetFdBlocking(comm, devr->lsaRankList[r], &messages[r], &fd), ret, fail);
CUCHECKGOTO(cuMemImportFromShareableHandle(&impHandle, reinterpret_cast<void*>((uintptr_t)fd), ncclCuMemHandleType), ret, fail);
SYSCHECKGOTO(close(fd), "close", ret, fail);
} else {
CUCHECKGOTO(cuMemImportFromShareableHandle(&impHandle, (void*)&messages[r].fabricHandle, ncclCuMemHandleType), ret, fail);
}
}
CUdeviceptr addr = reinterpret_cast<uintptr_t>((char*)devr->lsaFlatBase + r*devr->bigSize + bigOffset);
CUCHECKGOTO(cuMemMap(addr, size, 0, impHandle, 0), ret, fail);
CUCHECKGOTO(cuMemSetAccess(addr, size, &accessDesc, 1), ret, fail);
if (r != devr->lsaSelf) {
CUCHECKGOTO(cuMemRelease(impHandle), ret, fail);
}
}
// Ensure everyone has imported my mem handle.
NCCLCHECKGOTO(bootstrapIntraNodeBarrier(comm->bootstrap, devr->lsaRankList, devr->lsaSelf, devr->lsaSize, 0xbeef), ret, fail);
leave:
free(messages);
return ret;
fail:
goto leave;
}
static ncclResult_t symBindTeamMemory(
struct ncclComm* comm, struct ncclDevrTeam* tm, struct ncclDevrMemory* mem
) {
if (comm->nvlsSupport && tm->mcBasePtr != nullptr) {
#if CUDART_VERSION >= 12010
INFO(NCCL_NVLS, "Binding multicast memory at big=%lx to team {%d x %d}", mem->bigOffset, tm->team.nRanks, tm->team.stride);
CUCHECK(cuMulticastBindMem(tm->mcHandle, mem->bigOffset, mem->memHandle, 0, mem->size, 0));
#endif
}
return ncclSuccess;
}
static ncclResult_t symUnbindTeamMemory(
struct ncclComm* comm, struct ncclDevrTeam* tm, struct ncclDevrMemory* mem
) {
if (comm->nvlsSupport && tm->mcBasePtr != nullptr) {
#if CUDART_VERSION >= 12010
CUCHECK(cuMulticastUnbind(tm->mcHandle, comm->cudaDev, mem->bigOffset, mem->size));
#endif
}
return ncclSuccess;
}
// Caller must barrier the team afterward.
static ncclResult_t symTeamObtain(
struct ncclComm* comm, struct ncclTeam team, bool multimem,
struct ncclDevrTeam** outTeam
) {
ncclResult_t ret = ncclSuccess;
struct ncclDevrState* devr = &comm->devrState;
struct ncclDevrTeam* t = devr->teamHead;
bool teamIsNew = false;
while (true) {
if (t == nullptr) {
teamIsNew = true;
t = (struct ncclDevrTeam*)malloc(sizeof(struct ncclDevrTeam) + team.nRanks*sizeof(int));
t->team = team;
t->mcHandle = 0x0;
t->mcBasePtr = nullptr;
for (int i=0; i < team.nRanks; i++) {
t->worldRankList[i] = comm->rank + (i - team.rank)*team.stride;
}
break;
} else if (t->team.rank == team.rank && t->team.nRanks == team.nRanks && t->team.stride == team.stride) {
if (!multimem || t->mcBasePtr != nullptr) {
// Matching team is sufficient
if (outTeam) *outTeam = t;
return ncclSuccess;
}
break; // Need to enable multimem
}
}
if (multimem) {
if (!comm->nvlsSupport) {
WARN("Multicast support requested for team but none available on system.");
ret = ncclInvalidArgument;
goto fail;
} else {
#if CUDART_VERSION >= 12010
CUmemGenericAllocationHandle mcHandle = 0;
CUdeviceptr mcAddr = 0;
CUmulticastObjectProp mcProp = {};
char shareableHandle[NVLS_HANDLE_SIZE];
mcProp.numDevices = team.nRanks;
mcProp.handleTypes = ncclCuMemHandleType;
mcProp.flags = 0;
mcProp.size = devr->bigSize;
if (team.rank == 0) {
NCCLCHECKGOTO(ncclNvlsGroupCreate(comm, &mcProp, team.rank, team.nRanks, &mcHandle, shareableHandle), ret, fail);
NCCLCHECKGOTO(bootstrapIntraNodeBroadcast(comm->bootstrap, t->worldRankList, team.rank, team.nRanks, 0, shareableHandle, NVLS_HANDLE_SIZE), ret, fail_mcHandle);
} else {
NCCLCHECKGOTO(bootstrapIntraNodeBroadcast(comm->bootstrap, t->worldRankList, team.rank, team.nRanks, 0, shareableHandle, NVLS_HANDLE_SIZE), ret, fail);
NCCLCHECKGOTO(ncclNvlsGroupConnect(comm, shareableHandle, t->worldRankList[0], &mcHandle), ret, fail);
}
CUCHECKGOTO(cuMulticastAddDevice(mcHandle, comm->cudaDev), ret, fail_mcHandle);
CUCHECKGOTO(cuMemAddressReserve(&mcAddr, devr->bigSize, NCCL_MAX_PAGE_SIZE, 0, 0), ret, fail_mcHandle);
CUCHECKGOTO(cuMemMap(mcAddr, devr->bigSize, 0, mcHandle, 0), ret, fail_mcHandle_mcAddr);
{ CUmemAccessDesc accessDesc = {};
accessDesc.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
accessDesc.location.id = comm->cudaDev;
accessDesc.flags = CU_MEM_ACCESS_FLAGS_PROT_READWRITE;
CUCHECKGOTO(cuMemSetAccess(mcAddr, devr->bigSize, &accessDesc, 1), ret, fail_mcHandle_mcAddr_unmap);
}
t->mcHandle = mcHandle;
t->mcBasePtr = reinterpret_cast<void*>(mcAddr);
// Bind new team with all existing memories.
for (struct ncclDevrMemory* mem = devr->memHead; mem != nullptr; mem = mem->next) {
NCCLCHECKGOTO(symBindTeamMemory(comm, t, mem), ret, fail_mcHandle_mcAddr_unmap_mems);
}
if (false) { // Error labels:
fail_mcHandle_mcAddr_unmap_mems:
for (struct ncclDevrMemory* mem = devr->memHead; mem != nullptr; mem = mem->next) {
symUnbindTeamMemory(comm, t, mem);
}
fail_mcHandle_mcAddr_unmap:
CUCHECKIGNORE(cuMemUnmap(mcAddr, devr->bigSize));
goto fail_mcHandle_mcAddr; // silence unused label warning
fail_mcHandle_mcAddr:
CUCHECKIGNORE(cuMemAddressFree(mcAddr, devr->bigSize));
goto fail_mcHandle; // silence unused label warning
fail_mcHandle:
CUCHECKIGNORE(cuMemRelease(mcHandle));
goto fail; // silence unused label warning
}
#else
goto fail; // silence unused label warning
#endif
}
}
if (teamIsNew) {
// Add to list
t->next = devr->teamHead;
devr->teamHead = t;
}
if (outTeam) *outTeam = t;
return ret;
fail:
if (teamIsNew) free(t);
return ret;
}
static void symTeamDestroyAll(struct ncclComm* comm) {
struct ncclDevrState* devr = &comm->devrState;
while (devr->teamHead != nullptr) {
struct ncclDevrTeam* t = devr->teamHead;
devr->teamHead = t->next;
if (t->mcBasePtr != nullptr) {
for (struct ncclDevrMemory* m = devr->memHead; m != nullptr; m = m->next) {
symUnbindTeamMemory(comm, t, m);
}
CUdeviceptr mcAddr = reinterpret_cast<CUdeviceptr>(t->mcBasePtr);
CUCHECKIGNORE(cuMemUnmap(mcAddr, devr->bigSize));
CUCHECKIGNORE(cuMemAddressFree(mcAddr, devr->bigSize));
CUCHECKIGNORE(cuMemRelease(t->mcHandle));
}
free(t);
}
}
// On success we take caller's reference on memHandle.
// Due to multicast binds for each pre-exiting team, this function requires
// caller do a world barrier before returning to user.
static ncclResult_t symMemoryObtain(
struct ncclComm* comm, CUmemGenericAllocationHandle memHandle, size_t size,
struct ncclDevrMemory** outMem
) {
ncclResult_t ret = ncclSuccess;
struct ncclDevrState* devr = &comm->devrState;
int64_t bigOffset = 0;
struct ncclDevrMemory* mem = devr->memHead;
while (mem != nullptr) {
if (mem->memHandle == memHandle) {
CUCHECKIGNORE(cuMemRelease(memHandle));
goto leave;
}
mem = mem->next;
}
// New memory.
mem = (struct ncclDevrMemory*)malloc(sizeof(struct ncclDevrMemory));
mem->refCount = 0;
mem->memHandle = memHandle;
mem->size = size;
// Grab offset in the big space.
NCCLCHECKGOTO(ncclSpaceAlloc(&devr->bigSpace, devr->bigSize, size, devr->granularity, &bigOffset), ret, fail_mem);
mem->bigOffset = bigOffset;
// Map unicast addresses into flat VA space for lsa team.
NCCLCHECKGOTO(symMemoryMapLsaTeam(comm, memHandle, size, bigOffset), ret, fail_mem_space);
// Bind new memory with each existing team.
for (struct ncclDevrTeam* t = devr->teamHead; t != nullptr; t = t->next) {
NCCLCHECKGOTO(symBindTeamMemory(comm, t, mem), ret, fail_mem_space_teams);
}
// Add to list of mems.
mem->next = devr->memHead;
devr->memHead = mem;
leave:
mem->refCount += 1;
*outMem = mem;
return ret;
fail_mem_space_teams:
for (struct ncclDevrTeam* t = devr->teamHead; t != nullptr; t = t->next) {
symUnbindTeamMemory(comm, t, mem);
}
fail_mem_space:
ncclSpaceFree(&devr->bigSpace, bigOffset, size);
fail_mem:
free(mem);
//fail:
return ret;
}
static void symMemoryDropRef(
struct ncclComm* comm, struct ncclDevrMemory* mem
) {
if (mem != nullptr && 0 == --mem->refCount) {
struct ncclDevrState* devr = &comm->devrState;
for (struct ncclDevrTeam* t = devr->teamHead; t != nullptr; t = t->next) {
symUnbindTeamMemory(comm, t, mem);
}
for (int r = 0; r < devr->lsaSize; r++) {
CUdeviceptr addr = reinterpret_cast<uintptr_t>((char*)devr->lsaFlatBase + r*devr->bigSize + mem->bigOffset);
CUCHECKIGNORE(cuMemUnmap(addr, mem->size));
}
ncclSpaceFree(&devr->bigSpace, mem->bigOffset, mem->size);
CUCHECKIGNORE(cuMemRelease(mem->memHandle));
struct ncclDevrMemory** ptr = &devr->memHead;
while (*ptr != mem) ptr = &(*ptr)->next;
*ptr = mem->next; // Remove from list.
free(mem);
}
}
static ncclResult_t symWindowTableInitOnce(struct ncclComm* comm, cudaStream_t stream) {
struct ncclDevrState* devr = &comm->devrState;
struct ncclDevCommWindowTable* tableDev = devr->windowTable;
if (tableDev == nullptr) { // Create on first need.
NCCLCHECK(ncclShadowPoolAlloc<ncclDevCommWindowTable>(&devr->shadows, &tableDev, nullptr, stream));
devr->windowTable = tableDev;
}
return ncclSuccess;
}
// On success we take callers reference on `mem`.
static ncclResult_t symWindowCreate(
struct ncclComm* comm, struct ncclDevrMemory* mem,
size_t memOffset, void* userPtr, size_t userSize, int winFlags, void* localReg,
struct ncclWindow_vidmem** outWinDev, struct ncclDevrWindow** outWin,
cudaStream_t stream
) {
uintptr_t userAddr = reinterpret_cast<uintptr_t>(userPtr);
struct ncclDevrState* devr = &comm->devrState;
struct ncclDevrWindow* win;
win = (struct ncclDevrWindow*)malloc(sizeof(struct ncclDevrWindow));
memset(win, 0, sizeof(*win));
win->memory = mem;
win->size = userSize;
win->bigOffset = mem->bigOffset + memOffset;
win->winFlags = winFlags;
win->localRegHandle = localReg;
if (userPtr == nullptr) {
// Null means caller has no VA and will use the lsa team flat VA address.
win->userPtr = (char*)devr->lsaFlatBase + (devr->lsaSelf*devr->bigSize) + mem->bigOffset;
} else {
win->userPtr = userPtr;
}
struct ncclWindow_vidmem* winDev;
struct ncclWindow_vidmem* winDevHost;
NCCLCHECK(ncclShadowPoolAlloc(&devr->shadows, &winDev, &winDevHost, stream));
win->vidmem = winDev;
winDevHost->lsaFlatBase = (char*)devr->lsaFlatBase + win->bigOffset;
winDevHost->mcOffset4K = win->bigOffset>>12;
winDevHost->stride4G = devr->bigSize>>32;
winDevHost->lsaRank = devr->lsaSelf;
winDevHost->worldRank = comm->rank;
winDevHost->winHost = (void*)win;
CUDACHECK(cudaMemcpyAsync(winDev, winDevHost, sizeof(struct ncclWindow_vidmem), cudaMemcpyHostToDevice, stream));
NCCLCHECK(symWindowTableInitOnce(comm, stream)); // ensure devr->windowTable exists
struct ncclDevCommWindowTable* tableDev = devr->windowTable;
struct ncclDevCommWindowTable* tableHost;
NCCLCHECK(ncclShadowPoolToHost(&devr->shadows, tableDev, &tableHost));
while (true) {
int i = 0;
while (i < 32 && tableHost->entries[i].window != nullptr) i += 1;
if (i < 32) {
tableHost->entries[i].base = userAddr;
tableHost->entries[i].size = userAddr + userSize;
tableHost->entries[i].window = winDev;
CUDACHECK(cudaMemcpyAsync(&tableDev->entries[i], &tableHost->entries[i], sizeof(tableHost->entries[i]), cudaMemcpyHostToDevice, stream));
break;
}
if (tableHost->next == nullptr) {
NCCLCHECK(ncclShadowPoolAlloc<ncclDevCommWindowTable>(&devr->shadows, &tableHost->next, nullptr, stream));
CUDACHECK(cudaMemcpyAsync(&tableDev->next, &tableHost->next, sizeof(tableHost->next), cudaMemcpyHostToDevice, stream));
}
tableDev = tableHost->next;
NCCLCHECK(ncclShadowPoolToHost(&devr->shadows, tableHost->next, &tableHost));
}
{ // insert into winSorted[]
int i = listFindSortedLub(&ncclDevrWindowSorted::userAddr, devr->winSorted, devr->winSortedCount, userAddr);
struct ncclDevrWindowSorted winSort;
winSort.userAddr = userAddr;
winSort.size = userSize;
winSort.win = win;
listInsert(&devr->winSorted, &devr->winSortedCapacity, &devr->winSortedCount, i, winSort);
}
if (outWinDev) *outWinDev = winDev;
if (outWin) *outWin = win;
return ncclSuccess;
}
static ncclResult_t symWindowDestroy(struct ncclComm* comm, struct ncclWindow_vidmem* winDev, cudaStream_t stream) {
ncclResult_t ret = ncclSuccess;
struct ncclDevrState* devr = &comm->devrState;
struct ncclWindow_vidmem* winDevHost;
struct ncclDevrWindow* winHost;
NCCLCHECKGOTO(ncclShadowPoolToHost(&devr->shadows, winDev, &winDevHost), ret, fail);
winHost = (struct ncclDevrWindow*)winDevHost->winHost;
symMemoryDropRef(comm, winHost->memory);
{ struct ncclDevCommWindowTable* tableDev = devr->windowTable;
struct ncclDevCommWindowTable* tableHost;
NCCLCHECKGOTO(ncclShadowPoolToHost(&devr->shadows, tableDev, &tableHost), ret, remove_winSorted);
while (true) {
int i = 0;
while (i < 32 && tableHost->entries[i].window != winDev) i += 1;
if (i < 32) {
memset(&tableHost->entries[i], 0, sizeof(tableHost->entries[i]));
CUDACHECKGOTO(cudaMemsetAsync(&tableDev->entries[i], 0, sizeof(tableDev->entries[i]), stream), ret, remove_winSorted);
break;
}
if (tableHost->next == nullptr) break; // Error didn't find window in table
tableDev = tableHost->next;
NCCLCHECKGOTO(ncclShadowPoolToHost(&devr->shadows, tableHost->next, &tableHost), ret, remove_winSorted);
}
}
NCCLCHECKGOTO(ncclShadowPoolFree(&devr->shadows, winDev, stream), ret, remove_winSorted);
NCCLCHECKGOTO(ncclCommDeregister(comm, winHost->localRegHandle), ret, remove_winSorted);
remove_winSorted:
{ int i = listFindSortedLub(&ncclDevrWindowSorted::userAddr, devr->winSorted, devr->winSortedCount, reinterpret_cast<uintptr_t>(winHost->userPtr));
i -= 1; // least upper bound is just after ours.
listRemove(devr->winSorted, &devr->winSortedCount, i);
}
free(winHost);
fail:
return ret;
}
ncclResult_t ncclDevrWindowRegisterInGroup(
struct ncclComm* comm,
void* userPtr, size_t userSize, int winFlags, ncclWindow_t* outWinDev
) {
ncclResult_t ret = ncclSuccess;
CUdeviceptr memAddr = 0;
size_t memSize = 0;
CUmemGenericAllocationHandle memHandle = 0x0;
size_t memOffset;
struct ncclDevrMemory* mem = nullptr;
cudaStream_t stream = nullptr;
void* localRegHandle = nullptr;
NCCLCHECKGOTO(ncclCommRegister(comm, userPtr, userSize, &localRegHandle), ret, fail);
if (!comm->symmetricSupport) {
// We just return the local registration handle directly in this case, as there's no reason to allocate the
// ncclWindow_vidmem structure on the device, etc.
*outWinDev = reinterpret_cast<struct ncclWindow_vidmem*>(localRegHandle);
return ncclSuccess;
}
if (winFlags & NCCL_WIN_COLL_SYMMETRIC) {
// Defer symmetric kernel init until at least one window with that flag exists.
NCCLCHECKGOTO(ncclSymkInitOnce(comm), ret, fail);
}
// Get underlying cumem handle:
CUCHECKGOTO(cuMemGetAddressRange(&memAddr, &memSize, reinterpret_cast<CUdeviceptr>(userPtr)), ret, fail_locReg);
memOffset = reinterpret_cast<CUdeviceptr>(userPtr) - memAddr;
if (memOffset%NCCL_WIN_REQUIRED_ALIGNMENT != 0) {
WARN("Window address must be suitably aligned.");
ret = ncclInvalidArgument;
goto fail;
}
CUCHECKGOTO(cuMemRetainAllocationHandle(&memHandle, reinterpret_cast<void*>(memAddr)), ret, fail_locReg);
// Trade cumem handle for ncclDevrMemory*
NCCLCHECKGOTO(symMemoryObtain(comm, memHandle, memSize, &mem), ret, fail_locReg_memHandle);
memHandle = 0x0; // symMemoryObtain took our reference
CUDACHECKGOTO(cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking), ret, fail);
NCCLCHECKGOTO(symWindowCreate(
comm, mem, memOffset, userPtr, userSize, winFlags, localRegHandle, outWinDev, nullptr, stream
), ret, fail_locReg_memHandle_mem_stream);
mem = nullptr; // symWindowCreate took our reference
CUDACHECKGOTO(cudaStreamSynchronize(stream), ret, fail_locReg_memHandle_mem_stream_win);
// symWindowCreate needs barrier.
NCCLCHECKGOTO(bootstrapBarrier(comm->bootstrap, comm->rank, comm->nRanks, 0xbeef), ret, fail_locReg_memHandle_mem_stream_win);
cudaStreamDestroy(stream);
return ret;
fail_locReg_memHandle_mem_stream_win:
symWindowDestroy(comm, *outWinDev, stream);
*outWinDev = nullptr;
cudaStreamSynchronize(stream);
fail_locReg_memHandle_mem_stream:
cudaStreamDestroy(stream);
symMemoryDropRef(comm, mem);
fail_locReg_memHandle:
if (memHandle != 0x0) { CUCHECKIGNORE(cuMemRelease(memHandle)); }
fail_locReg:
ncclCommDeregister(comm, localRegHandle);
fail:
*outWinDev = nullptr;
return ret;
}
static ncclResult_t deepCopyDevCommRequirements(
struct ncclDevCommRequirements const* src,
struct ncclDevCommRequirements** dst
) {
ncclResult_t ret = ncclSuccess;
struct ncclDevResourceRequirements **dstRes;
struct ncclTeamRequirements **dstTeam;
NCCLCHECK(ncclCalloc(dst, 1));
/* copy the entire struct now and update linked lists later */
**dst = *src;
dstRes = &(*dst)->resourceRequirementsList;
for (struct ncclDevResourceRequirements* rr = src->resourceRequirementsList; rr != nullptr; rr = rr->next) {
NCCLCHECKGOTO(ncclCalloc(dstRes, 1), ret, fail);
(*dstRes)->bufferSize = rr->bufferSize;
(*dstRes)->bufferAlign = rr->bufferAlign;
(*dstRes)->outBufferHandle = rr->outBufferHandle;
dstRes = &(*dstRes)->next;
}
dstTeam = &(*dst)->teamRequirementsList;
for (struct ncclTeamRequirements* tr = src->teamRequirementsList; tr != nullptr; tr = tr->next) {
NCCLCHECKGOTO(ncclCalloc(dstTeam, 1), ret, fail);
(*dstTeam)->team = tr->team;
(*dstTeam)->multimem = tr->multimem;
(*dstTeam)->outMultimemHandle = tr->outMultimemHandle;
dstTeam = &(*dstTeam)->next;
}
exit:
return ret;
fail:
freeDevCommRequirements(*dst);
*dst = nullptr;
goto exit;
}
void freeDevCommRequirements(
struct ncclDevCommRequirements* reqs
) {
if (reqs) {
while (reqs->resourceRequirementsList) {
struct ncclDevResourceRequirements* rr_next = reqs->resourceRequirementsList->next;
free(reqs->resourceRequirementsList);
reqs->resourceRequirementsList = rr_next;
}
while (reqs->teamRequirementsList) {
struct ncclTeamRequirements* tr_next = reqs->teamRequirementsList->next;
free(reqs->teamRequirementsList);
reqs->teamRequirementsList = tr_next;
}
free(reqs);
}
}
ncclResult_t ncclDevrCommCreateInternal(
struct ncclComm* comm,
struct ncclDevCommRequirements const* reqs, struct ncclDevComm* outDevComm
) {
ncclResult_t ret = ncclSuccess;
struct ncclDevrState* devr = &comm->devrState;
struct ncclTeam world = ncclTeamWorld(comm);
struct ncclTeam lsa = ncclTeamInnerFactor(world, devr->lsaSize);
struct ncclDevrTeam* tmLsa;
size_t bufSizeTotal;
struct ncclDevResourceRequirements* resReqsHead;
struct ncclDevResourceRequirements lsaBarReq;
cudaStream_t stream = nullptr;
CUmemGenericAllocationHandle memHandle = 0x0;
struct ncclDevrMemory* mem = nullptr;
struct ncclDevrWindow* win = nullptr;
struct ncclWindow_vidmem* winHost = nullptr;
memset(outDevComm, 0, sizeof(*outDevComm));
outDevComm->rank = comm->rank;
outDevComm->nRanks = comm->nRanks;
outDevComm->nRanks_rcp32 = idivRcp32(comm->nRanks);
outDevComm->lsaRank = devr->lsaSelf;
outDevComm->lsaSize = devr->lsaSize;
outDevComm->lsaSize_rcp32 = idivRcp32(devr->lsaSize);
NCCLCHECKGOTO(symTeamObtain(comm, lsa, /*multicast=*/reqs->lsaMultimem, &tmLsa), ret, fail);
outDevComm->lsaMultimem.mcBasePtr = tmLsa->mcBasePtr;
{ struct ncclTeamRequirements* tr = reqs->teamRequirementsList;
while (tr != nullptr) {
if (tr->multimem) {
struct ncclDevrTeam* tm;
NCCLCHECKGOTO(symTeamObtain(comm, tr->team, tr->multimem, &tm), ret, fail);
if (tr->outMultimemHandle != nullptr) tr->outMultimemHandle->mcBasePtr = tm->mcBasePtr;
}
tr = tr->next;
}
}
resReqsHead = reqs->resourceRequirementsList;
ncclLsaBarrierCreateRequirement(lsa, reqs->lsaBarrierCount, &outDevComm->lsaBarrier, &lsaBarReq);
lsaBarReq.next = resReqsHead;
resReqsHead = &lsaBarReq;
{ struct ncclDevResourceRequirements* rr = resReqsHead;
bufSizeTotal = 0;
while (rr != nullptr) {
bufSizeTotal = alignUp(bufSizeTotal, std::max<size_t>(128, rr->bufferAlign));
if (rr->outBufferHandle != nullptr) *rr->outBufferHandle = bufSizeTotal/128;
bufSizeTotal += rr->bufferSize;
rr = rr->next;
}
bufSizeTotal = alignUp(bufSizeTotal, devr->granularity);
}
CUDACHECKGOTO(cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking), ret, fail);
NCCLCHECKGOTO(symWindowTableInitOnce(comm, stream), ret, fail); // ensure devr->windowTable exists
outDevComm->windowTable = comm->devrState.windowTable;
if (bufSizeTotal == 0) {
outDevComm->resourceWindow = nullptr;
outDevComm->resourceWindow_inlined = {};
} else {
CUmemAllocationProp memProp = {};
memProp.type = CU_MEM_ALLOCATION_TYPE_PINNED;
memProp.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
memProp.requestedHandleTypes = ncclCuMemHandleType;
memProp.location.id = comm->cudaDev;
CUCHECKGOTO(cuMemCreate(&memHandle, bufSizeTotal, &memProp, 0), ret, fail);
NCCLCHECKGOTO(symMemoryObtain(comm, memHandle, bufSizeTotal, &mem), ret, fail);
memHandle = 0x0; // Reference given to symMemoryObtain
NCCLCHECKGOTO(symWindowCreate( // Requires world barrier afterward.
comm, mem, /*memOffset=*/0, nullptr, bufSizeTotal, /*winFlags=*/0,
/*localReg=*/nullptr, &outDevComm->resourceWindow, &win,
stream), ret, fail);
mem = nullptr; // Reference given to symWindowCreate
NCCLCHECKGOTO(ncclShadowPoolToHost(&comm->devrState.shadows, win->vidmem, &winHost), ret, fail);
outDevComm->resourceWindow_inlined = *winHost;
CUDACHECKGOTO(cudaMemsetAsync(win->userPtr, 0, bufSizeTotal, stream), ret, fail);
}
CUDACHECKGOTO(cudaStreamSynchronize(stream), ret, fail);
NCCLCHECKGOTO(bootstrapBarrier(comm->bootstrap, comm->rank, comm->nRanks, 0xbeef), ret, fail);
cudaStreamDestroy(stream);
return ret;
fail:
if (win != nullptr) {
symWindowDestroy(comm, win->vidmem, stream);
cudaStreamSynchronize(stream);
}
if (mem != nullptr) {
symMemoryDropRef(comm, mem);
}
if (memHandle != 0x0) {
CUCHECKIGNORE(cuMemRelease(memHandle));
}
if (stream != nullptr) {
cudaStreamDestroy(stream);
}
return ret;
}
////////////////////////////////////////////////////////////////////////////////
NCCL_API(ncclResult_t, ncclCommWindowRegister, ncclComm_t comm, void* ptr, size_t size, ncclWindow_t* win, int winFlags);
ncclResult_t ncclCommWindowRegister(
struct ncclComm* comm, void* userPtr, size_t userSize,
struct ncclWindow_vidmem** outWinDev, int winFlags
) {
ncclResult_t ret = ncclSuccess;
int saveDev;
struct ncclDevrRegTask* task;
CUDACHECK(cudaGetDevice(&saveDev));
NCCLCHECK(ncclGroupStartInternal());
if (userPtr == nullptr || userSize == 0 || !(comm->symmetricSupport || ncclParamLocalRegister())) goto exit;
NCCLCHECKGOTO(ncclCommEnsureReady(comm), ret, fail);
CUDACHECKGOTO(cudaSetDevice(comm->cudaDev), ret, fail);
NCCLCHECKGOTO(ncclDevrInitOnce(comm), ret, fail);
NCCLCHECKGOTO(ncclCalloc(&task, 1), ret, fail);
task->userPtr = userPtr;
task->userSize = userSize;
task->winFlags = winFlags;
task->outWinDev = outWinDev;
ncclIntruQueueEnqueue(&comm->devrState.regTaskQueue, task);
ncclGroupCommJoin(comm, ncclGroupTaskTypeSymRegister);
exit:
ncclGroupErrCheck(ret);
NCCLCHECK(ncclGroupEndInternal());
cudaSetDevice(saveDev);
return ret;
fail:
goto exit;
}
NCCL_API(ncclResult_t, ncclCommWindowDeregister, ncclComm_t comm, ncclWindow_t win);
ncclResult_t ncclCommWindowDeregister(struct ncclComm* comm, struct ncclWindow_vidmem* winDev) {
ncclResult_t ret = ncclSuccess;
int saveDev;
cudaStream_t stream;
if (winDev == nullptr) goto exit;
if (!comm->symmetricSupport) {
NCCLCHECKGOTO(ncclCommDeregister(comm, winDev), ret, fail);
goto exit;
}
CUDACHECKGOTO(cudaGetDevice(&saveDev), ret, fail);
CUDACHECKGOTO(cudaSetDevice(comm->cudaDev), ret, fail);
CUDACHECKGOTO(cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking), ret, fail_dev);
NCCLCHECKGOTO(symWindowDestroy(comm, winDev, stream), ret, fail_dev_stream);
fail_dev_stream:
cudaStreamSynchronize(stream);
cudaStreamDestroy(stream);
fail_dev:
cudaSetDevice(saveDev);
fail:
exit:
return ret;
}
ncclResult_t ncclDevrFindWindow(
struct ncclComm* comm, void const* userPtr, struct ncclDevrWindow** outWin
) {
struct ncclDevrState* devr = &comm->devrState;
uintptr_t userAddr = reinterpret_cast<uintptr_t>(userPtr);
int i = listFindSortedLub(&ncclDevrWindowSorted::userAddr, devr->winSorted, devr->winSortedCount, userAddr);
if (0 < i && (userAddr - devr->winSorted[i-1].userAddr < devr->winSorted[i-1].size)) {
*outWin = devr->winSorted[i-1].win;
} else {
*outWin = nullptr;
}
return ncclSuccess;
}
NCCL_API(ncclResult_t, ncclDevCommCreate, ncclComm_t comm, ncclDevCommRequirements_t const* reqs, ncclDevComm_t* outDevComm);
ncclResult_t ncclDevCommCreate(
ncclComm_t comm, struct ncclDevCommRequirements const* reqs,
struct ncclDevComm* outDevComm
) {
ncclResult_t ret = ncclSuccess;
int saveDev;
struct ncclDevrCommCreateTask* task = nullptr;
CUDACHECK(cudaGetDevice(&saveDev));
NCCLCHECK(ncclGroupStartInternal());
if (!comm->symmetricSupport) {
WARN("Communicator does not support symmetric memory!");
ret = ncclInvalidUsage;
goto fail;
}
NCCLCHECKGOTO(ncclCommEnsureReady(comm), ret, fail);
CUDACHECKGOTO(cudaSetDevice(comm->cudaDev), ret, fail);
NCCLCHECKGOTO(ncclDevrInitOnce(comm), ret, fail);
NCCLCHECKGOTO(ncclCalloc(&task, 1), ret, fail);
// reqs must be deep copied to the task so background threads can safely access it
NCCLCHECKGOTO(deepCopyDevCommRequirements(reqs, &task->reqs), ret, fail);
task->outDevComm = outDevComm;
ncclIntruQueueEnqueue(&comm->devrState.commCreateTaskQueue, task);
ncclGroupCommJoin(comm, ncclGroupTaskTypeSymRegister);
exit:
ncclGroupErrCheck(ret);
NCCLCHECK(ncclGroupEndInternal());
cudaSetDevice(saveDev);
return ret;
fail:
free(task);
goto exit;
}
NCCL_API(ncclResult_t, ncclDevCommDestroy, ncclComm_t comm, ncclDevComm_t const* devComm);
ncclResult_t ncclDevCommDestroy(
struct ncclComm* comm, struct ncclDevComm const* devComm
) {
//struct ncclDevrState* devr = &comm->devrState;
if (devComm->resourceWindow != nullptr) {
NCCLCHECK(ncclCommWindowDeregister(comm, devComm->resourceWindow));
}
return ncclSuccess;
}
// Get the corresponding pointer in another lsa rank's symmetric memory window
ncclResult_t ncclDevrGetLsaRankPtr(struct ncclComm* comm, struct ncclDevrWindow* winHost, size_t offset, int lsaRank, void** outPtr) {
if (winHost == nullptr || outPtr == nullptr) {
return ncclInvalidArgument;
}
struct ncclDevrState* devr = &comm->devrState;
// Validate lsaRank is within bounds
if (lsaRank < 0 || lsaRank >= devr->lsaSize) {
return ncclInvalidArgument;
}
// Validate offset is within bounds
if (offset < 0 || offset >= winHost->size) {
return ncclInvalidArgument;
}
// Calculate the address with offset for the specified lsa rank
*outPtr = (void*)((uintptr_t)devr->lsaFlatBase + lsaRank * devr->bigSize + winHost->bigOffset + offset);
return ncclSuccess;
}
// Get the multicast address for a given team
ncclResult_t ncclDevrGetLsaTeamPtrMC(struct ncclComm* comm, struct ncclDevrWindow* winHost, size_t offset, struct ncclTeam lsaTeam, void** outPtr){
if (winHost == nullptr || outPtr == nullptr) {
return ncclInvalidArgument;
}
if (!comm->nvlsSupport) {
return ncclInvalidUsage;
}
bool multimem = true;
struct ncclDevrTeam* tm;
NCCLCHECK(symTeamObtain(comm, lsaTeam, multimem, &tm));
// Return the base multicast address for this team with offset
*outPtr = (void*)((uintptr_t)tm->mcBasePtr + winHost->bigOffset + offset);
return ncclSuccess;
}
////////////////////////////////////////////////////////////////////////////////
// Find the least index strictly greater than arg.
template<typename Obj, typename Key>
static int listFindSortedLub(Key Obj::*key, Obj* sorted, int count, Key arg) {
int lo = 0, hi = count;
while (lo + 16 < hi) {
int i = (lo + hi)/2;
if (sorted[i].*key <= arg) lo = i+1;
else hi = i;
}
int i = lo;
while (i < hi && sorted[i].*key <= arg) i++;
return i;
}
template<typename Obj>
static void listInsert(Obj** list, int* capacity, int* count, int index, Obj val) {
if (*capacity < *count + 1) {
*capacity *= 2;
if (*capacity == 0) *capacity = 16;
*list = (Obj*)realloc(*list, (*capacity)*sizeof(Obj));
}
for (int j = *count; j != index; j--) {
(*list)[j] = (*list)[j-1];
}
(*list)[index] = val;
*count += 1;
}
template<typename Obj>
static void listRemove(Obj* list, int* count, int index) {
for (int i = index; i+1 < *count; i++) {
list[i] = list[i+1];
}
*count -= 1;
}