Merge remote-tracking branch 'nccl/master' into develop
[ROCm/rccl commit: 858b4e76eb]
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
# Source files
|
||||
set(LIBSRCFILES
|
||||
bootstrap.cc
|
||||
channel.cc
|
||||
ce_coll.cc
|
||||
collectives.cc
|
||||
debug.cc
|
||||
enqueue.cc
|
||||
group.cc
|
||||
init.cc
|
||||
init_nvtx.cc
|
||||
proxy.cc
|
||||
transport.cc
|
||||
mnnvl.cc
|
||||
allocator.cc
|
||||
sym_kernels.cc
|
||||
dev_runtime.cc
|
||||
)
|
||||
|
||||
# Add compatibility shim if using static cudart
|
||||
if(CUDARTLIB STREQUAL "cudart_static")
|
||||
list(APPEND LIBSRCFILES enhcompat.cc)
|
||||
endif()
|
||||
|
||||
# Configure pkg-config file
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/nccl.pc.in
|
||||
${CMAKE_BINARY_DIR}/lib/pkgconfig/nccl.pc
|
||||
@ONLY
|
||||
)
|
||||
|
||||
# Add files from subdirectories
|
||||
add_subdirectory(transport)
|
||||
add_subdirectory(misc)
|
||||
add_subdirectory(register)
|
||||
add_subdirectory(graph)
|
||||
add_subdirectory(plugin)
|
||||
add_subdirectory(device)
|
||||
add_subdirectory(nccl_device)
|
||||
add_subdirectory(ras)
|
||||
add_subdirectory(scheduler)
|
||||
|
||||
add_compile_options(-fmacro-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}/=)
|
||||
|
||||
# Add all source files
|
||||
list(APPEND LIBSRCFILES
|
||||
${TRANSPORT_SOURCES}
|
||||
${MISC_SOURCES}
|
||||
${REGISTER_SOURCES}
|
||||
${GRAPH_SOURCES}
|
||||
${PLUGIN_SOURCES}
|
||||
${RAS_SOURCES}
|
||||
${SYM_SOURCES}
|
||||
${SCHEDULER_SOURCES}
|
||||
)
|
||||
|
||||
###################### Create a shared NCCL library ############################
|
||||
add_library(nccl SHARED)
|
||||
|
||||
target_sources(nccl PRIVATE ${LIBSRCFILES})
|
||||
|
||||
# Include directories
|
||||
target_include_directories(nccl PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/device
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/plugin
|
||||
${CUDAToolkit_INCLUDE_DIRS}
|
||||
${CUDAToolkit_INCLUDE_DIRS}/cccl
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_BINARY_DIR}/include/nccl.h
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/include
|
||||
COMMAND sed -e "s/\\\$$\\{nccl:Major\\}/${NCCL_MAJOR}/g"
|
||||
-e "s/\\\$$\\{nccl:Minor\\}/${NCCL_MINOR}/g"
|
||||
-e "s/\\\$$\\{nccl:Patch\\}/${NCCL_PATCH}/g"
|
||||
-e "s/\\\$$\\{nccl:Suffix\\}/${NCCL_SUFFIX}/g"
|
||||
-e "s/\\\$$\\{nccl:Version\\}/${NCCL_VERSION_CODE}/g"
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/nccl.h.in > ${CMAKE_BINARY_DIR}/include/nccl.h
|
||||
BYPRODUCTS ${CMAKE_BINARY_DIR}/include/nccl.h
|
||||
)
|
||||
|
||||
add_custom_target(nccl_header DEPENDS ${CMAKE_BINARY_DIR}/include/nccl.h)
|
||||
|
||||
add_dependencies(nccl nccl_header)
|
||||
|
||||
# Set version and output name
|
||||
set_target_properties(nccl PROPERTIES
|
||||
VERSION ${NCCL_MAJOR}.${NCCL_MINOR}.${NCCL_PATCH}
|
||||
SOVERSION ${NCCL_MAJOR}
|
||||
OUTPUT_NAME "nccl"
|
||||
PREFIX "lib"
|
||||
)
|
||||
|
||||
# Set CUDA specific flags
|
||||
set_target_properties(nccl PROPERTIES
|
||||
CUDA_SEPARABLE_COMPILATION ON
|
||||
CUDA_RESOLVE_DEVICE_SYMBOLS ON
|
||||
CUDA_ARCHITECTURES "${CMAKE_CUDA_ARCHITECTURES}"
|
||||
POSITION_INDEPENDENT_CODE ON
|
||||
)
|
||||
|
||||
# Link libraries
|
||||
target_link_libraries(nccl
|
||||
PRIVATE
|
||||
nccl_device
|
||||
pthread
|
||||
rt
|
||||
dl
|
||||
${CUDAToolkit_LIBRARIES}
|
||||
${EXTRA_LIBS}
|
||||
)
|
||||
|
||||
# Set output directories for nccl shared library
|
||||
set_target_properties(nccl PROPERTIES
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||
)
|
||||
|
||||
###################### Create a ras binary executable ############################
|
||||
set(RAS_BINSRCFILES ras/client.cc)
|
||||
|
||||
add_executable(ncclras ${RAS_BINSRCFILES})
|
||||
|
||||
target_include_directories(ncclras PUBLIC
|
||||
${CMAKE_BINARY_DIR}/include
|
||||
${CUDAToolkit_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
add_dependencies(ncclras nccl_header)
|
||||
|
||||
target_link_libraries(ncclras
|
||||
PRIVATE
|
||||
pthread
|
||||
rt
|
||||
dl
|
||||
)
|
||||
|
||||
# Set output directory for ncclras executable
|
||||
set_target_properties(ncclras PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
||||
)
|
||||
|
||||
###################### Create a static NCCL library ############################
|
||||
add_library(nccl_static STATIC ${LIBSRCFILES})
|
||||
|
||||
# Include directories
|
||||
target_include_directories(nccl_static PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/device
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/plugin
|
||||
${CUDAToolkit_INCLUDE_DIRS}
|
||||
${CUDAToolkit_INCLUDE_DIRS}/cccl
|
||||
)
|
||||
|
||||
# Add dependency on nccl_header
|
||||
add_dependencies(nccl_static nccl_header)
|
||||
|
||||
# Link libraries
|
||||
target_link_libraries(nccl_static
|
||||
PRIVATE
|
||||
nccl_device
|
||||
pthread
|
||||
rt
|
||||
dl
|
||||
${CUDAToolkit_LIBRARIES}
|
||||
${EXTRA_LIBS}
|
||||
)
|
||||
|
||||
# Set CUDA specific flags
|
||||
set_target_properties(nccl_static PROPERTIES
|
||||
CUDA_SEPARABLE_COMPILATION ON
|
||||
CUDA_RESOLVE_DEVICE_SYMBOLS ON
|
||||
CUDA_ARCHITECTURES "${CMAKE_CUDA_ARCHITECTURES}"
|
||||
POSITION_INDEPENDENT_CODE ON
|
||||
)
|
||||
|
||||
# Set output directory for nccl_static library
|
||||
set_target_properties(nccl_static PROPERTIES
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||
)
|
||||
@@ -7,10 +7,12 @@ include ../makefiles/common.mk
|
||||
include ../makefiles/version.mk
|
||||
|
||||
##### src files
|
||||
INCEXPORTS := nccl.h
|
||||
INCEXPORTS := nccl.h nccl_device.h \
|
||||
$(patsubst include/%,%,$(wildcard include/nccl_device/*.h include/nccl_device/impl/*.h))
|
||||
|
||||
LIBSRCFILES := \
|
||||
bootstrap.cc channel.cc collectives.cc debug.cc enqueue.cc group.cc \
|
||||
init.cc init_nvtx.cc proxy.cc transport.cc mnnvl.cc allocator.cc symmetric.cc \
|
||||
init.cc init_nvtx.cc proxy.cc transport.cc mnnvl.cc allocator.cc dev_runtime.cc sym_kernels.cc ce_coll.cc \
|
||||
$(wildcard graph/*.cc) \
|
||||
$(wildcard misc/*.cc) \
|
||||
$(wildcard transport/*.cc) \
|
||||
@@ -19,6 +21,8 @@ LIBSRCFILES := \
|
||||
$(wildcard plugin/net/*.cc) \
|
||||
$(wildcard plugin/tuner/*.cc) \
|
||||
$(wildcard plugin/profiler/*.cc) \
|
||||
$(wildcard nccl_device/*.cc) \
|
||||
$(wildcard scheduler/*.cc) \
|
||||
$(filter-out ras/client.cc,$(wildcard ras/*.cc))
|
||||
BINSRCFILES := ras/client.cc
|
||||
|
||||
@@ -123,6 +127,16 @@ $(INCDIR)/nccl_%.h : include/nccl_%.h
|
||||
mkdir -p $(INCDIR)
|
||||
install -m 644 $< $@
|
||||
|
||||
$(INCDIR)/nccl_device/%.h: include/nccl_device/%.h
|
||||
@printf "Grabbing %-35s > %s\n" $< $@
|
||||
mkdir -p $(INCDIR)/nccl_device
|
||||
install -m 644 $< $@
|
||||
|
||||
$(INCDIR)/nccl_device/impl/%.h: include/nccl_device/impl/%.h
|
||||
@printf "Grabbing %-35s > %s\n" $< $@
|
||||
mkdir -p $(INCDIR)/nccl_device/impl
|
||||
install -m 644 $< $@
|
||||
|
||||
$(PKGDIR)/%.pc : %.pc
|
||||
@printf "Grabbing %-35s > %s\n" $< $@
|
||||
mkdir -p $(PKGDIR)
|
||||
@@ -149,7 +163,7 @@ install : build
|
||||
mkdir -p $(PREFIX)/bin
|
||||
cp -P -v $(BUILDDIR)/lib/lib* $(PREFIX)/lib/
|
||||
cp -P -v $(BUILDDIR)/lib/pkgconfig/* $(PREFIX)/lib/pkgconfig/
|
||||
cp -v $(BUILDDIR)/include/* $(PREFIX)/include/
|
||||
cp -v -r $(BUILDDIR)/include/* $(PREFIX)/include/
|
||||
cp -v $(BUILDDIR)/bin/ncclras $(PREFIX)/bin/
|
||||
|
||||
FILESTOFORMAT := $(shell find . -name ".\#*" -prune -o \( -name "*.cc" -o -name "*.h" \) -print | grep -v -E 'ibvwrap.h|nvmlwrap.h|gdrwrap.h|nccl.h')
|
||||
|
||||
+332
-62
@@ -7,10 +7,11 @@
|
||||
#include "comm.h"
|
||||
#include "transport.h"
|
||||
#include "group.h"
|
||||
#include "nvtx.h"
|
||||
|
||||
NCCL_API(ncclResult_t, ncclMemAlloc, void **ptr, size_t size);
|
||||
ncclResult_t ncclMemAlloc_impl(void **ptr, size_t size) {
|
||||
NVTX3_FUNC_RANGE_IN(nccl_domain);
|
||||
NCCL_NVTX3_FUNC_RANGE;
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
|
||||
#if ROCM_VERSION >= 70000
|
||||
@@ -99,7 +100,7 @@ fail:
|
||||
|
||||
NCCL_API(ncclResult_t, ncclMemFree, void *ptr);
|
||||
ncclResult_t ncclMemFree_impl(void *ptr) {
|
||||
NVTX3_FUNC_RANGE_IN(nccl_domain);
|
||||
NCCL_NVTX3_FUNC_RANGE;
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
int saveDevice;
|
||||
|
||||
@@ -129,70 +130,339 @@ fail:
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// This is a collective function and should be called by all ranks in the communicator
|
||||
ncclResult_t ncclCommSymmetricAllocInternal(struct ncclComm* comm, size_t size, size_t alignment, void** symPtr) {
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
void* regSymAddr = NULL;
|
||||
size_t allocSize = size;
|
||||
size_t granularity;
|
||||
CUdevice cuDev;
|
||||
CUmemAllocationProp memprop = {};
|
||||
CUmemGenericAllocationHandle memHandle;
|
||||
int bit = 0, cnt = 0;
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// ncclSpace:
|
||||
//
|
||||
// This datastructure "cuts" the line of non-negative integers into segments
|
||||
// which alternate between "full" (allocated) and "empty" (not allocated). The
|
||||
// cuts are sorted ascending. The segment after the last cut must be empty
|
||||
// (the unallocated frontier). Knwoing this we can deduce whether the segment
|
||||
// ending at cut[i] is full or empty with this formula:
|
||||
// isFull(i) = (i%2 != ncuts%2)
|
||||
|
||||
// aligment must be power of 2 as an input
|
||||
while (bit < sizeof(size_t) * 8) {
|
||||
if (alignment & (1L << bit)) cnt++;
|
||||
if (cnt == 2) {
|
||||
WARN("rank %d alignment %ld is not power of 2", comm->rank, alignment);
|
||||
goto fail;
|
||||
void ncclSpaceConstruct(struct ncclSpace* a) {
|
||||
memset(a, 0, sizeof(*a));
|
||||
}
|
||||
|
||||
void ncclSpaceDestruct(struct ncclSpace* a) {
|
||||
free(a->cuts);
|
||||
}
|
||||
|
||||
static void insertSegment(struct ncclSpace* a, int index, int64_t lo, int64_t hi) {
|
||||
// Insert space for two cuts in `a->cuts[]` before `index`.
|
||||
if (a->count + 2 > a->capacity) {
|
||||
a->capacity *= 2;
|
||||
if (a->capacity == 0) a->capacity = 16;
|
||||
int64_t* cuts1 = (int64_t*)malloc(a->capacity*sizeof(int64_t));
|
||||
for (int i=0; i < index; i++) cuts1[i] = a->cuts[i];
|
||||
for (int i=index; i < a->count; i++) cuts1[i+2] = a->cuts[i];
|
||||
free(a->cuts);
|
||||
a->cuts = cuts1;
|
||||
} else {
|
||||
for (int i=a->count-1; index <= i; i--) a->cuts[i+2] = a->cuts[i];
|
||||
}
|
||||
a->cuts[index+0] = lo;
|
||||
a->cuts[index+1] = hi;
|
||||
a->count += 2;
|
||||
|
||||
// Filter pairs of adjacent repeated values from cuts[]. Since these mark
|
||||
// boundaries where segments transition between full<->empty, dropping such a
|
||||
// pair fuses two adjacent segments together. Examples:
|
||||
// [1,2,3,3,4] -> [1,2,4]
|
||||
// [1,2,3,3,3,4] -> [1,2,3,4] // have to leave one 3 because its a full<->empty transition
|
||||
// [1,2,3,3,3,3,4] -> [1,2,4]
|
||||
// Leading zeros don't have to be in pairs, they are always dropped:
|
||||
// [0,1,2] -> [1,2]
|
||||
// [0,0,1,2] -> [1,2]
|
||||
int r = index, w = index; // Read and write cursors.
|
||||
int64_t prev = r==0 ? 0 : a->cuts[r-1];
|
||||
while (r < a->count) {
|
||||
int64_t cur = a->cuts[r++];
|
||||
a->cuts[w++] = cur;
|
||||
if (prev == cur) { // Repeated value is an empty segment which can be deleted.
|
||||
// Erase last two cuts or just one if we're at the start.
|
||||
w -= w==1 ? 1 : 2;
|
||||
// Zeros can only occur at the beginning (due to being sorted). We want to
|
||||
// drop any number of zeros, but only even numbers of other repeated values.
|
||||
// So set to zero here, which will make prev=0, thus if next value is zero
|
||||
// it will be dropped but if its not zero then it will need to begin a new
|
||||
// pair to be dropped.
|
||||
cur = 0;
|
||||
}
|
||||
bit++;
|
||||
prev = cur;
|
||||
}
|
||||
// temporarily align the alignment to NCCL_REC_PAGE_SIZE
|
||||
ALIGN_SIZE(alignment, NCCL_REC_PAGE_SIZE);
|
||||
|
||||
CUCHECKGOTO(cuDeviceGet(&cuDev, comm->cudaDev), ret, fail);
|
||||
memprop.type = CU_MEM_ALLOCATION_TYPE_PINNED;
|
||||
memprop.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
|
||||
memprop.requestedHandleType = ncclCuMemHandleType;
|
||||
memprop.location.id = cuDev;
|
||||
CUCHECKGOTO(cuMemGetAllocationGranularity(&granularity, &memprop, CU_MEM_ALLOC_GRANULARITY_RECOMMENDED), ret, fail);
|
||||
ALIGN_SIZE(allocSize, granularity);
|
||||
|
||||
CUCHECKGOTO(cuMemCreate(&memHandle, allocSize, &memprop, 0), ret, fail);
|
||||
ALIGN_SIZE(comm->symAllocHead, alignment);
|
||||
NCCLCHECKGOTO(ncclIpcSymmetricMap(comm, comm->symAllocHead, allocSize, memHandle, ®SymAddr), ret, fail);
|
||||
NCCLCHECKGOTO(ncclNvlsSymmetricMap(comm, comm->symAllocHead, allocSize, regSymAddr), ret, fail);
|
||||
NCCLCHECKGOTO(bootstrapIntraNodeBarrier(comm->bootstrap, comm->localRankToRank, comm->localRank, comm->localRanks, comm->localRankToRank[0]), ret, fail);
|
||||
comm->symAllocHead += allocSize;
|
||||
*symPtr = regSymAddr;
|
||||
|
||||
exit:
|
||||
return ret;
|
||||
fail:
|
||||
*symPtr = NULL;
|
||||
goto exit;
|
||||
a->count = w;
|
||||
}
|
||||
|
||||
ncclResult_t ncclCommSymmetricFreeInternal(struct ncclComm* comm, void* symPtr) {
|
||||
CUmemGenericAllocationHandle handle;
|
||||
size_t size = 0;
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
int saveDev = comm->cudaDev;
|
||||
CUDACHECKGOTO(cudaGetDevice(&saveDev), ret, fail);
|
||||
if (ncclCuMemEnable()) {
|
||||
CUDACHECKGOTO(cudaSetDevice(comm->cudaDev), ret, fail);
|
||||
CUCHECKGOTO(cuMemRetainAllocationHandle(&handle, symPtr), ret, fail);
|
||||
CUCHECKGOTO(cuMemRelease(handle), ret, fail);
|
||||
CUCHECKGOTO(cuMemGetAddressRange(NULL, &size, (CUdeviceptr)symPtr), ret, fail);
|
||||
NCCLCHECKGOTO(ncclNvlsSymmetricFree(comm, size, symPtr), ret, fail);
|
||||
NCCLCHECKGOTO(ncclIpcSymmetricFree(comm, size, symPtr), ret, fail);
|
||||
CUCHECKGOTO(cuMemRelease(handle), ret, fail);
|
||||
ncclResult_t ncclSpaceAlloc(
|
||||
struct ncclSpace* a, int64_t limit, int64_t size, int align,
|
||||
int64_t* outOffset
|
||||
) {
|
||||
// When allocating we try to locate the first empty segment which can hold
|
||||
// the allocation and move its lower cut upward.
|
||||
int i = a->count%2; // First empty segment ends at cuts[i]
|
||||
size_t off;
|
||||
while (i <= a->count) {
|
||||
size_t lo = i == 0 ? 0 : a->cuts[i-1];
|
||||
size_t hi = i == a->count ? limit : a->cuts[i];
|
||||
off = alignUp(lo, align);
|
||||
if (off + size <= hi) {
|
||||
*outOffset = off;
|
||||
if (i == 0 || off + size == hi) { // Slow path required.
|
||||
insertSegment(a, i, off, off+size);
|
||||
} else { // We can just append to the end of a full segment.
|
||||
a->cuts[i-1] = off + size;
|
||||
}
|
||||
return ncclSuccess;
|
||||
}
|
||||
i += 2; // Next empty segment
|
||||
}
|
||||
exit:
|
||||
CUDACHECK(cudaSetDevice(saveDev));
|
||||
return ret;
|
||||
fail:
|
||||
goto exit;
|
||||
WARN("Allocation failed. No suitable space found to accommodate size=0x%lx within limit=0x%lx", (long)size, (long)limit);
|
||||
return ncclInternalError;
|
||||
}
|
||||
|
||||
ncclResult_t ncclSpaceFree(struct ncclSpace* a, int64_t offset, int64_t size) {
|
||||
if (a->count == 0 || a->cuts[a->count-1] <= offset) {
|
||||
WARN("No allocation found at offset=0x%lx", (long)offset);
|
||||
return ncclInternalError;
|
||||
}
|
||||
|
||||
// This could be binary search, but since allocate is linear there's no point.
|
||||
int i = 1 - a->count%2; // First full segment ends at cuts[i]
|
||||
while (a->cuts[i] <= offset) i += 2;
|
||||
|
||||
int64_t lo = i==0 ? 0 : a->cuts[i-1];
|
||||
int64_t hi = a->cuts[i];
|
||||
|
||||
if (offset < lo || hi < offset + size) {
|
||||
WARN("Given size=0x%lx extends beyond allocation.", (long)size);
|
||||
return ncclInternalError;
|
||||
}
|
||||
|
||||
// First try the two fast cases which just shrink a segment from one side.
|
||||
if (i != 0 && lo == offset && offset + size != hi) {
|
||||
a->cuts[i-1] = offset + size; // Bring bottom up.
|
||||
} else if (lo != offset && offset + size == hi) {
|
||||
a->cuts[i] = offset; // Bring top down.
|
||||
} else { // Slow path.
|
||||
insertSegment(a, i, offset, offset+size);
|
||||
}
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// ncclShadowPool:
|
||||
|
||||
struct ncclShadowPage { // A contiguous block of (at most) 64 objects
|
||||
struct ncclShadowPage* next;
|
||||
int objSize;
|
||||
uint64_t freeMask;
|
||||
void* devObjs;
|
||||
};
|
||||
struct ncclShadowObject {
|
||||
struct ncclShadowObject* next;
|
||||
void* devObj;
|
||||
void* hostObj;
|
||||
struct ncclShadowPage* page; // null if not allocated in page but directly in CUDA mempool.
|
||||
};
|
||||
|
||||
void ncclShadowPoolConstruct(struct ncclShadowPool* pool) {
|
||||
pool->hbits = 0;
|
||||
pool->count = 0;
|
||||
pool->table = nullptr;
|
||||
pool->pages = nullptr;
|
||||
}
|
||||
|
||||
ncclResult_t ncclShadowPoolDestruct(struct ncclShadowPool* pool) {
|
||||
if (pool->hbits != 0) {
|
||||
cudaStream_t stream;
|
||||
CUDACHECK(cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking));
|
||||
|
||||
if (pool->count != 0) {
|
||||
for (int i=0; i < 1<<pool->hbits; i++) {
|
||||
struct ncclShadowObject* obj = pool->table[i];
|
||||
while (obj != nullptr) {
|
||||
struct ncclShadowPage* page = obj->page;
|
||||
if (page != nullptr) {
|
||||
if (page->freeMask == 0) { // Put full pages back into page list.
|
||||
page->freeMask = 1;
|
||||
page->next = pool->pages;
|
||||
pool->pages = page;
|
||||
}
|
||||
} else {
|
||||
cudaFreeAsync(obj->devObj, stream);
|
||||
}
|
||||
struct ncclShadowObject* next = obj->next;
|
||||
free(obj);
|
||||
obj = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(pool->table);
|
||||
|
||||
while (pool->pages != nullptr) {
|
||||
cudaFreeAsync(pool->pages->devObjs, stream);
|
||||
struct ncclShadowPage* next = pool->pages->next;
|
||||
free(pool->pages);
|
||||
pool->pages = next;
|
||||
}
|
||||
|
||||
cudaStreamSynchronize(stream);
|
||||
cudaStreamDestroy(stream);
|
||||
cudaMemPoolDestroy(pool->memPool);
|
||||
}
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
static int hashBucket(int hbits, void* devObj) {
|
||||
uintptr_t h = reinterpret_cast<uintptr_t>(devObj);
|
||||
h ^= h>>32;
|
||||
h *= 0x9e3779b97f4a7c13;
|
||||
return (uint64_t)h >> (64-hbits);
|
||||
}
|
||||
|
||||
static void hashInsert(struct ncclShadowPool* pool, struct ncclShadowObject* obj) {
|
||||
int b = hashBucket(pool->hbits, obj->devObj);
|
||||
obj->next = pool->table[b];
|
||||
pool->table[b] = obj;
|
||||
}
|
||||
|
||||
ncclResult_t ncclShadowPoolAlloc(
|
||||
struct ncclShadowPool* pool, size_t size, void** outDevObj, void** outHostObj,
|
||||
cudaStream_t stream
|
||||
) {
|
||||
if (size == 0) {
|
||||
if (outDevObj) *outDevObj = nullptr;
|
||||
if (outHostObj) *outHostObj = nullptr;
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
int hbits = pool->hbits;
|
||||
if (hbits == 0) {
|
||||
cudaMemPoolProps props = {};
|
||||
props.allocType = cudaMemAllocationTypePinned;
|
||||
props.handleTypes = cudaMemHandleTypeNone;
|
||||
props.location.type = cudaMemLocationTypeDevice;
|
||||
cudaGetDevice(&props.location.id);
|
||||
CUDACHECK(cudaMemPoolCreate(&pool->memPool, &props));
|
||||
|
||||
pool->hbits = hbits = 4;
|
||||
pool->table = (struct ncclShadowObject**)malloc(sizeof(struct ncclShadowObject*)<<hbits);
|
||||
for (int i=0; i < 1<<hbits; i++) pool->table[i] = nullptr;
|
||||
}
|
||||
|
||||
// Check for hash table size increase before inserting. Maintain 2:1 object:bucket ratio.
|
||||
if (pool->count+1 > 2<<hbits) {
|
||||
struct ncclShadowObject** table0 = pool->table;
|
||||
struct ncclShadowObject** table1 = (struct ncclShadowObject**)malloc(sizeof(struct ncclShadowObject*)<<(hbits+1));
|
||||
pool->table = table1;
|
||||
pool->hbits = hbits+1;
|
||||
for (int i1=0; i1 < 2<<hbits; i1++) table1[i1] = nullptr;
|
||||
for (int i0=0; i0 < 1<<hbits; i0++) {
|
||||
struct ncclShadowObject* obj = table0[i0];
|
||||
while (obj) {
|
||||
struct ncclShadowObject* next = obj->next;
|
||||
hashInsert(pool, obj);
|
||||
obj = next;
|
||||
}
|
||||
}
|
||||
hbits += 1; // match pool->hbits
|
||||
free(table0);
|
||||
}
|
||||
|
||||
struct ncclShadowPage* page;
|
||||
void *devObj;
|
||||
if ((64<<10)/size >= 3) {
|
||||
int shift = std::max<int>(0, (int)log2Down(size) + 1 - 4);
|
||||
int pageObjSize = ((size + (1<<shift)-1)>>shift)<<shift;
|
||||
struct ncclShadowPage** pagePtr = &pool->pages;
|
||||
while (true) {
|
||||
page = *pagePtr;
|
||||
if (page == nullptr) {
|
||||
size_t pageSize = std::min<size_t>(64<<10, 64*pageObjSize);
|
||||
page = (struct ncclShadowPage*)malloc(sizeof(struct ncclShadowPage));
|
||||
page->objSize = pageObjSize;
|
||||
page->freeMask = uint64_t(-1)>>(64 - pageSize/pageObjSize);
|
||||
page->next = pool->pages;
|
||||
pool->pages = page;
|
||||
CUDACHECK(cudaMallocFromPoolAsync(&page->devObjs, pageSize, pool->memPool, stream));
|
||||
CUDACHECK(cudaMemsetAsync(page->devObjs, 0, pageSize, stream));
|
||||
// fall through...
|
||||
}
|
||||
if (page->objSize == pageObjSize) {
|
||||
int slot = popFirstOneBit(&page->freeMask);
|
||||
devObj = (char*)page->devObjs + slot*pageObjSize;
|
||||
if (page->freeMask == 0) *pagePtr = page->next; // Remove full page from list.
|
||||
break;
|
||||
}
|
||||
pagePtr = &page->next;
|
||||
}
|
||||
} else {
|
||||
page = nullptr;
|
||||
CUDACHECK(cudaMallocFromPoolAsync(&devObj, size, pool->memPool, stream));
|
||||
CUDACHECK(cudaMemsetAsync(devObj, 0, size, stream));
|
||||
}
|
||||
|
||||
struct ncclShadowObject* obj = (struct ncclShadowObject*)malloc(
|
||||
sizeof(struct ncclShadowObject) + /*padding=*/alignof(max_align_t)-1 + size
|
||||
);
|
||||
obj->page = page;
|
||||
obj->devObj = devObj;
|
||||
obj->hostObj = alignUp((char*)(obj+1), alignof(max_align_t));
|
||||
memset(obj->hostObj, 0, size);
|
||||
hashInsert(pool, obj);
|
||||
pool->count += 1;
|
||||
if (outDevObj) *outDevObj = devObj;
|
||||
if (outHostObj) *outHostObj = obj->hostObj;
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
ncclResult_t ncclShadowPoolFree(struct ncclShadowPool* pool, void* devObj, cudaStream_t stream) {
|
||||
if (devObj == nullptr) return ncclSuccess;
|
||||
|
||||
int b = hashBucket(pool->hbits, devObj);
|
||||
struct ncclShadowObject** pobj = &pool->table[b];
|
||||
while (true) {
|
||||
if (*pobj == nullptr) {
|
||||
WARN("Device object does not exist in shadow pool.");
|
||||
return ncclInternalError;
|
||||
}
|
||||
if ((*pobj)->devObj == devObj) break;
|
||||
pobj = &(*pobj)->next;
|
||||
}
|
||||
struct ncclShadowObject* obj = *pobj;
|
||||
*pobj = obj->next;
|
||||
if (obj->page != nullptr) {
|
||||
if (obj->page->freeMask == 0) {
|
||||
obj->page->next = pool->pages;
|
||||
pool->pages = obj->page;
|
||||
}
|
||||
int slot = ((char*)obj->devObj - (char*)obj->page->devObjs)/obj->page->objSize;
|
||||
obj->page->freeMask |= uint64_t(1)<<slot;
|
||||
} else {
|
||||
CUDACHECK(cudaFreeAsync(devObj, stream));
|
||||
}
|
||||
free(obj);
|
||||
pool->count -= 1;
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
ncclResult_t ncclShadowPoolToHost(struct ncclShadowPool* pool, void* devObj, void** hostObj) {
|
||||
if (devObj == nullptr) {
|
||||
*hostObj = nullptr;
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
int b = hashBucket(pool->hbits, devObj);
|
||||
struct ncclShadowObject* obj = pool->table[b];
|
||||
while (true) {
|
||||
if (obj == nullptr) {
|
||||
WARN("Device object does not exist in shadow pool.");
|
||||
return ncclInternalError;
|
||||
}
|
||||
if (obj->devObj == devObj) break;
|
||||
obj = obj->next;
|
||||
}
|
||||
*hostObj = obj->hostObj;
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "signals.h" // [RCCL]
|
||||
#include "param.h"
|
||||
#include "ras.h"
|
||||
#include <mutex>
|
||||
|
||||
#define BOOTSTRAP_N_CHECK_ABORT 10000
|
||||
#define BOOTSTRAP_TAG_CONNECT (0x1 << 31)
|
||||
@@ -86,13 +87,13 @@ struct bootstrapRootArgs {
|
||||
static char bootstrapNetIfName[MAX_IF_NAME_SIZE+1];
|
||||
static union ncclSocketAddress bootstrapNetIfAddr;
|
||||
static int bootstrapNetInitDone = 0;
|
||||
pthread_mutex_t bootstrapNetLock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static std::mutex bootstrapNetMutex;
|
||||
|
||||
NCCL_PARAM(BootstrapNetEnable,"OOB_NET_ENABLE", 0);
|
||||
|
||||
ncclResult_t bootstrapNetInit() {
|
||||
if (bootstrapNetInitDone == 0) {
|
||||
pthread_mutex_lock(&bootstrapNetLock);
|
||||
std::lock_guard<std::mutex> lock(bootstrapNetMutex);
|
||||
if (bootstrapNetInitDone == 0) {
|
||||
const char* env = ncclGetEnv("NCCL_COMM_ID");
|
||||
int nIfs = 0;
|
||||
@@ -100,21 +101,18 @@ ncclResult_t bootstrapNetInit() {
|
||||
union ncclSocketAddress remoteAddr;
|
||||
if (ncclSocketGetAddrFromString(&remoteAddr, env) != ncclSuccess) {
|
||||
WARN("Invalid NCCL_COMM_ID, please use format: <ipv4>:<port> or [<ipv6>]:<port> or <hostname>:<port>");
|
||||
pthread_mutex_unlock(&bootstrapNetLock);
|
||||
return ncclInvalidArgument;
|
||||
}
|
||||
NCCLCHECK(ncclFindInterfaceMatchSubnet(bootstrapNetIfName, &bootstrapNetIfAddr, &remoteAddr, MAX_IF_NAME_SIZE,
|
||||
&nIfs));
|
||||
if (nIfs <= 0) {
|
||||
WARN("NET/Socket : No usable listening interface found");
|
||||
pthread_mutex_unlock(&bootstrapNetLock);
|
||||
return ncclSystemError;
|
||||
}
|
||||
} else {
|
||||
NCCLCHECK(ncclFindInterfaces(bootstrapNetIfName, &bootstrapNetIfAddr, MAX_IF_NAME_SIZE, 1, &nIfs));
|
||||
if (nIfs <= 0) {
|
||||
WARN("Bootstrap : no socket interface found");
|
||||
pthread_mutex_unlock(&bootstrapNetLock);
|
||||
return ncclInvalidUsage;
|
||||
}
|
||||
}
|
||||
@@ -124,7 +122,6 @@ ncclResult_t bootstrapNetInit() {
|
||||
INFO(NCCL_BOOTSTRAP, "Bootstrap: Using%s", line);
|
||||
bootstrapNetInitDone = 1;
|
||||
}
|
||||
pthread_mutex_unlock(&bootstrapNetLock);
|
||||
}
|
||||
return ncclSuccess;
|
||||
}
|
||||
@@ -486,7 +483,7 @@ static ncclResult_t getUDS(uint64_t* peerUDS) {
|
||||
static ncclResult_t netGetDevice(int rank, struct ncclComm* comm, int* dev) {
|
||||
static int devOOB = -1;
|
||||
if (devOOB < 0) {
|
||||
pthread_mutex_lock(&bootstrapNetLock);
|
||||
std::lock_guard<std::mutex> lock(bootstrapNetMutex);
|
||||
if (devOOB < 0) {
|
||||
const char* userIfEnv = ncclGetEnv("NCCL_OOB_NET_IFNAME");
|
||||
if (userIfEnv && strlen(userIfEnv) > 0) {
|
||||
@@ -517,7 +514,6 @@ static ncclResult_t netGetDevice(int rank, struct ncclComm* comm, int* dev) {
|
||||
WARN("no device found matching %s%s, verify NCCL_OOB_NET_IFNAME", searchExact ? "exactly " : "", userIfEnv);
|
||||
else
|
||||
WARN("no device found after excluding %s%s, verify NCCL_OOB_NET_IFNAME", searchExact ? "exactly " : "", userIfEnv);
|
||||
pthread_mutex_unlock(&bootstrapNetLock);
|
||||
return ncclInvalidArgument;
|
||||
}
|
||||
} else {
|
||||
@@ -530,13 +526,12 @@ static ncclResult_t netGetDevice(int rank, struct ncclComm* comm, int* dev) {
|
||||
bool hasProp = res == ncclSuccess;
|
||||
INFO(NCCL_BOOTSTRAP, "Bootstrap: Using %s:%d", (hasProp) ? props.name : "N/A", (hasProp) ? props.port : -1);
|
||||
}
|
||||
pthread_mutex_unlock(&bootstrapNetLock);
|
||||
}
|
||||
*dev = devOOB;
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
static ncclResult_t netRingConnect(ncclNet_t* net, struct bootstrapListen_t* listen, char peerHandle[NCCL_NET_HANDLE_MAXSIZE],
|
||||
static ncclResult_t netRingConnect(void* ctx, ncclNet_t* net, struct bootstrapListen_t* listen, char peerHandle[NCCL_NET_HANDLE_MAXSIZE],
|
||||
void** sendComm, ncclNetDeviceHandle_t** sendDevHandle,
|
||||
void** recvComm, ncclNetDeviceHandle_t** recvDevHandle, volatile uint32_t* abortFlag) {
|
||||
|
||||
@@ -544,7 +539,7 @@ static ncclResult_t netRingConnect(ncclNet_t* net, struct bootstrapListen_t* lis
|
||||
do {
|
||||
NCCLCHECK(checkAbort(abortFlag, &abortCounter));
|
||||
if (!*sendComm)
|
||||
NCCLCHECK(net->connect(listen->net.dev, NULL, peerHandle, sendComm, sendDevHandle));
|
||||
NCCLCHECK(net->connect(ctx, listen->net.dev, peerHandle, sendComm, sendDevHandle));
|
||||
if (!*recvComm)
|
||||
NCCLCHECK(net->accept(listen->net.comm, recvComm, recvDevHandle));
|
||||
} while (!*sendComm || !*recvComm);
|
||||
@@ -660,7 +655,7 @@ ncclResult_t bootstrapInit(int nHandles, void* handles, struct ncclComm* comm) {
|
||||
if (ncclParamBootstrapNetEnable()) {
|
||||
// Create net interface for other ranks to contact me (all gather)
|
||||
NCCLCHECK(netGetDevice(rank, comm, &STATE_LISTEN(state, net.dev)));
|
||||
NCCLCHECK(state->net->listen(STATE_LISTEN(state, net.dev), STATE_LISTEN(state, net.handle), &STATE_LISTEN(state, net.comm)));
|
||||
NCCLCHECK(state->net->listen(comm->netContext, STATE_LISTEN(state, net.dev), STATE_LISTEN(state, net.handle), &STATE_LISTEN(state, net.comm)));
|
||||
memcpy(info.connectInfo.handle, STATE_LISTEN(state, net.handle), NCCL_NET_HANDLE_MAXSIZE);
|
||||
} else {
|
||||
// create socket for ring neightbor to contact mee
|
||||
@@ -714,7 +709,7 @@ ncclResult_t bootstrapInit(int nHandles, void* handles, struct ncclComm* comm) {
|
||||
|
||||
// accept and connect the ring network
|
||||
if (ncclParamBootstrapNetEnable()) {
|
||||
NCCLCHECK(netRingConnect(state->net, &state->listen, nextPeer.handle,
|
||||
NCCLCHECK(netRingConnect(comm->netContext, state->net, &state->listen, nextPeer.handle,
|
||||
&STATE_RING(state, net.sendComm), &STATE_RING(state, net.sendDevHandle),
|
||||
&STATE_RING(state, net.recvComm), &STATE_RING(state, net.recvDevHandle), state->abortFlag));
|
||||
} else {
|
||||
@@ -807,7 +802,7 @@ ncclResult_t bootstrapSplit(uint64_t magic, struct ncclComm* comm, struct ncclCo
|
||||
// create a handle for the others to reach out to me
|
||||
if (ncclParamBootstrapNetEnable()) {
|
||||
NCCLCHECKGOTO(netGetDevice(rank, comm, &STATE_LISTEN(state, net.dev)), ret, fail);
|
||||
NCCLCHECKGOTO(state->net->listen(STATE_LISTEN(state, net.dev), STATE_LISTEN(state, net.handle), &STATE_LISTEN(state, net.comm)), ret, fail);
|
||||
NCCLCHECKGOTO(state->net->listen(comm->netContext, STATE_LISTEN(state, net.dev), STATE_LISTEN(state, net.handle), &STATE_LISTEN(state, net.comm)), ret, fail);
|
||||
memcpy(info.handle, STATE_LISTEN(state, net.handle), NCCL_NET_HANDLE_MAXSIZE);
|
||||
} else {
|
||||
// create socket for ring neightbor to contact mee
|
||||
@@ -826,7 +821,7 @@ ncclResult_t bootstrapSplit(uint64_t magic, struct ncclComm* comm, struct ncclCo
|
||||
NCCLCHECKGOTO(bootstrapSend(parent->bootstrap, prev, BOOTSTRAP_TAG_COMMSPLIT, &info, sizeof(union ringConnectInfo)), ret, fail);
|
||||
NCCLCHECKGOTO(bootstrapRecv(parent->bootstrap, next, BOOTSTRAP_TAG_COMMSPLIT, &nextPeer, sizeof(union ringConnectInfo)), ret, fail);
|
||||
if (ncclParamBootstrapNetEnable()) {
|
||||
NCCLCHECKGOTO(netRingConnect(state->net, &state->listen, nextPeer.handle,
|
||||
NCCLCHECKGOTO(netRingConnect(comm->netContext, state->net, &state->listen, nextPeer.handle,
|
||||
&STATE_RING(state, net.sendComm), &STATE_RING(state, net.sendDevHandle),
|
||||
&STATE_RING(state, net.recvComm), &STATE_RING(state, net.recvDevHandle), state->abortFlag),
|
||||
ret, fail);
|
||||
|
||||
@@ -0,0 +1,615 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#include "comm.h"
|
||||
#include "register_inline.h"
|
||||
#include <cuda.h>
|
||||
#include "rocmwrap.h"
|
||||
#include "ce_coll.h"
|
||||
#include "alloc.h"
|
||||
|
||||
// Static constant for graph synchronization
|
||||
static const uint32_t GRAPH_SYNC_VALUE = 1;
|
||||
|
||||
// Static constants for intra-batch synchronization to improve CE collective performance with large scale
|
||||
// Frequency of intra-batch synchronization
|
||||
static const uint32_t CE_COLL_INTRA_BATCH_SYNC_FREQ = 8;
|
||||
// Message threshold for intra-batch synchronization
|
||||
static const uint64_t CE_COLL_INTRA_BATCH_SYNC_MSG_THRESHOLD = 512*1024*1024;
|
||||
|
||||
ncclResult_t ncclCeInit(struct ncclComm* comm) {
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
|
||||
uint8_t* ceDevBase;
|
||||
size_t ceDevBaseSize = alignUp(comm->nRanks*sizeof(uint32_t), 16) * 2;
|
||||
ncclWindow_vidmem* ceWinDev;
|
||||
ncclWindow_vidmem* ceWinDevHost;
|
||||
|
||||
// Ensure symmetric memory runtime is initialized
|
||||
NCCLCHECKGOTO(ncclDevrInitOnce(comm), ret, fail);
|
||||
// Allocate and register memory for the symmetric memory
|
||||
NCCLCHECKGOTO(ncclMemAlloc((void**)&ceDevBase, ceDevBaseSize), ret, fail);
|
||||
NCCLCHECKGOTO(ncclDevrWindowRegisterInGroup(comm, ceDevBase, ceDevBaseSize, NCCL_WIN_COLL_SYMMETRIC, &ceWinDev), ret, fail);
|
||||
NCCLCHECKGOTO(ncclShadowPoolToHost(&comm->devrState.shadows, ceWinDev, &ceWinDevHost), ret, fail);
|
||||
// Get the ncclDevrWindow from the winHost field
|
||||
comm->ceColl.ceSyncWin = (struct ncclDevrWindow*)ceWinDevHost->winHost;
|
||||
|
||||
comm->ceColl.baseUCSymReadyOffset = 0;
|
||||
comm->ceColl.baseUCSymComplOffset = alignUp(comm->nRanks*sizeof(uint32_t), 16);
|
||||
comm->ceColl.baseUCSymReadyPtr = (uint8_t*)comm->ceColl.ceSyncWin->userPtr + comm->ceColl.baseUCSymReadyOffset;
|
||||
comm->ceColl.baseUCSymComplPtr = (uint8_t*)comm->ceColl.ceSyncWin->userPtr + comm->ceColl.baseUCSymComplOffset;
|
||||
comm->ceColl.ceSeqNum = 0;
|
||||
comm->ceColl.useCompletePtr = false;
|
||||
comm->ceColl.intraBatchSyncFreq = CE_COLL_INTRA_BATCH_SYNC_FREQ;
|
||||
comm->ceColl.intraBatchSyncMsgThreshold = CE_COLL_INTRA_BATCH_SYNC_MSG_THRESHOLD;
|
||||
INFO(NCCL_INIT, "Init CE, rank %d baseUCSymReadyPtr %p, baseUCSymComplPtr %p, seq num %d", comm->rank, comm->ceColl.baseUCSymReadyPtr, comm->ceColl.baseUCSymComplPtr, comm->ceColl.ceSeqNum);
|
||||
|
||||
exit:
|
||||
return ret;
|
||||
fail:
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ncclResult_t ncclCeFinalize(struct ncclComm* comm) {
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
|
||||
// Clean up ceInitTaskQueue
|
||||
while (!ncclIntruQueueEmpty(&comm->ceInitTaskQueue)) {
|
||||
struct ncclCeInitTask* task = ncclIntruQueueDequeue(&comm->ceInitTaskQueue);
|
||||
free(task);
|
||||
}
|
||||
|
||||
// Clean up CE resources
|
||||
if (comm->ceColl.baseUCSymReadyPtr != NULL) {
|
||||
if (comm->ceColl.ceSyncWin && comm->ceColl.ceSyncWin->vidmem) {
|
||||
NCCLCHECKGOTO(ncclCommWindowDeregister(comm, comm->ceColl.ceSyncWin->vidmem), ret, fail);
|
||||
NCCLCHECKGOTO(ncclMemFree(comm->ceColl.baseUCSymReadyPtr), ret, fail);
|
||||
}
|
||||
comm->ceColl.baseUCSymReadyPtr = NULL;
|
||||
comm->ceColl.baseUCSymComplPtr = NULL;
|
||||
comm->ceColl.ceSyncWin = NULL;
|
||||
}
|
||||
|
||||
exit:
|
||||
return ret;
|
||||
fail:
|
||||
goto exit;
|
||||
}
|
||||
|
||||
bool ncclCeImplemented(ncclFunc_t coll, int/*ncclDevRedOp_t*/ red, ncclDataType_t ty) {
|
||||
int driverVersion;
|
||||
if (ncclCudaDriverVersion(&driverVersion) != ncclSuccess) return false;
|
||||
|
||||
// CE is supported in CUDA 12.5 and later
|
||||
if (driverVersion >= 12050) {
|
||||
switch (coll) {
|
||||
case ncclFuncAllGather:
|
||||
case ncclFuncAlltoAll:
|
||||
case ncclFuncScatter:
|
||||
case ncclFuncGather:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ncclResult_t ncclPrepMCSync(struct ncclComm* comm, bool isComplete, hipStreamBatchMemOpParams* batchParams, size_t* opIdx, cudaStream_t stream) {
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
|
||||
uint32_t* readyPtrs = (uint32_t*)comm->ceColl.baseUCSymReadyPtr;
|
||||
uint32_t* completePtrs = (uint32_t*)comm->ceColl.baseUCSymComplPtr;
|
||||
|
||||
bool capturing = ncclCudaGraphValid(comm->planner.capturingGraph);
|
||||
uint32_t currentSeq = ++comm->ceColl.ceSeqNum;
|
||||
|
||||
// Source pointer is either the constant graph sync value or the sequence number
|
||||
void* srcPtr = capturing ? (void*)&GRAPH_SYNC_VALUE : (void*)¤tSeq;
|
||||
// Wait value is either the constant graph sync value or the sequence number
|
||||
uint32_t waitValue = capturing ? GRAPH_SYNC_VALUE : currentSeq;
|
||||
|
||||
// Use multi-cast address as destination pointer
|
||||
void* mcDstPtr;
|
||||
void* dstPtr = isComplete ? (void*)&completePtrs[comm->rank] : (void*)&readyPtrs[comm->rank];
|
||||
size_t offset = (uint8_t*)dstPtr - (uint8_t*)comm->ceColl.ceSyncWin->userPtr;
|
||||
NCCLCHECKGOTO(ncclDevrGetLsaTeamPtrMC(comm, comm->ceColl.ceSyncWin, offset, ncclTeamLsa(comm), &mcDstPtr), ret, fail);
|
||||
|
||||
// Write our own ready/complete flag to the multi-cast address
|
||||
CUDACHECKGOTO(cudaMemcpyAsync(
|
||||
mcDstPtr,
|
||||
srcPtr,
|
||||
sizeof(uint32_t),
|
||||
cudaMemcpyHostToDevice,
|
||||
stream), ret, fail);
|
||||
|
||||
// Add local wait operations for every other rank
|
||||
for (int r = 0; r < comm->nRanks; ++r) {
|
||||
if (r == comm->rank) continue;
|
||||
batchParams[*opIdx] = {};
|
||||
// batchParams[*opIdx].waitValue.operation = CU_STREAM_MEM_OP_WAIT_VALUE_32;
|
||||
batchParams[*opIdx].waitValue.address = (CUdeviceptr)(isComplete ? (void*)&completePtrs[r] : (void*)&readyPtrs[r]);
|
||||
batchParams[*opIdx].waitValue.value = waitValue;
|
||||
batchParams[*opIdx].waitValue.flags = CU_STREAM_WAIT_VALUE_EQ;
|
||||
(*opIdx)++;
|
||||
}
|
||||
|
||||
exit:
|
||||
return ret;
|
||||
fail:
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ncclResult_t ncclPrepUCSync(struct ncclComm* comm, bool isComplete,
|
||||
hipStreamBatchMemOpParams* batchParams,
|
||||
size_t* opIdx) {
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
|
||||
uint32_t* readyPtrs = (uint32_t*)comm->ceColl.baseUCSymReadyPtr;
|
||||
uint32_t* completePtrs = (uint32_t*)comm->ceColl.baseUCSymComplPtr;
|
||||
|
||||
bool capturing = ncclCudaGraphValid(comm->planner.capturingGraph);
|
||||
uint32_t currentSeq = ++comm->ceColl.ceSeqNum;
|
||||
|
||||
// Write our own ready/complete flag to remote ranks
|
||||
uint32_t waitValue = capturing ? GRAPH_SYNC_VALUE : currentSeq;
|
||||
for (int r = 0; r < comm->nRanks; ++r) {
|
||||
if (r == comm->rank) continue;
|
||||
void * peerDstPtr;
|
||||
void* dstPtr = isComplete ? (void*)&completePtrs[comm->rank] : (void*)&readyPtrs[comm->rank];
|
||||
size_t offset = (uint8_t*)dstPtr - (uint8_t*)comm->ceColl.ceSyncWin->userPtr;
|
||||
NCCLCHECKGOTO(ncclDevrGetLsaRankPtr(comm, comm->ceColl.ceSyncWin, offset, r, &peerDstPtr), ret, fail);
|
||||
batchParams[*opIdx] = {};
|
||||
// batchParams[*opIdx].writeValue.operation = CU_STREAM_MEM_OP_WRITE_VALUE_32;
|
||||
batchParams[*opIdx].writeValue.address = (CUdeviceptr)peerDstPtr;
|
||||
batchParams[*opIdx].writeValue.value = waitValue;
|
||||
// batchParams[*opIdx].writeValue.flags = CU_STREAM_WRITE_VALUE_DEFAULT;
|
||||
(*opIdx)++;
|
||||
}
|
||||
|
||||
// Add local wait operations for every other rank
|
||||
for (int r = 0; r < comm->nRanks; ++r) {
|
||||
if (r == comm->rank) continue;
|
||||
batchParams[*opIdx] = {};
|
||||
// batchParams[*opIdx].waitValue.operation = CU_STREAM_MEM_OP_WAIT_VALUE_32;
|
||||
batchParams[*opIdx].waitValue.address = (CUdeviceptr)(isComplete ? (void*)&completePtrs[r] : (void*)&readyPtrs[r]);
|
||||
batchParams[*opIdx].waitValue.value = waitValue;
|
||||
batchParams[*opIdx].waitValue.flags = CU_STREAM_WAIT_VALUE_EQ;
|
||||
(*opIdx)++;
|
||||
}
|
||||
|
||||
exit:
|
||||
return ret;
|
||||
fail:
|
||||
goto exit;
|
||||
}
|
||||
|
||||
|
||||
ncclResult_t ncclMemOpSync(struct ncclComm* comm, cudaStream_t stream) {
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
|
||||
// Get pointers to the ready and complete synchronization arrays
|
||||
uint32_t* readyPtrs = (uint32_t*)comm->ceColl.baseUCSymReadyPtr;
|
||||
uint32_t* completePtrs = (uint32_t*)comm->ceColl.baseUCSymComplPtr;
|
||||
|
||||
// Allocate enough slots for all possible ops
|
||||
size_t batchSize = (comm->nvlsSupport ? NCCL_CE_SYNC_OPS_PER_RANK_MC : NCCL_CE_SYNC_OPS_PER_RANK_UC) * comm->nRanks;
|
||||
size_t opIdx = 0;
|
||||
|
||||
// Prepare batch memory operations for synchronization
|
||||
hipStreamBatchMemOpParams* batchParams = nullptr;
|
||||
NCCLCHECKGOTO(ncclCalloc(&batchParams, batchSize), ret, fail);
|
||||
|
||||
if (comm->nvlsSupport) {
|
||||
NCCLCHECKGOTO(ncclPrepMCSync(comm, comm->ceColl.useCompletePtr, batchParams, &opIdx, stream), ret, fail);
|
||||
} else {
|
||||
NCCLCHECKGOTO(ncclPrepUCSync(comm, comm->ceColl.useCompletePtr, batchParams, &opIdx), ret, fail);
|
||||
}
|
||||
|
||||
// For CUDA graph capture, add reset operation
|
||||
if (ncclCudaGraphValid(comm->planner.capturingGraph)) {
|
||||
for (int i = 0; i < comm->nRanks; i++) {
|
||||
batchParams[opIdx] = {};
|
||||
// batchParams[opIdx].writeValue.operation = CU_STREAM_MEM_OP_WRITE_VALUE_32;
|
||||
batchParams[opIdx].writeValue.address = (CUdeviceptr)(comm->ceColl.useCompletePtr ? (void*)&completePtrs[i] : (void*)&readyPtrs[i]);
|
||||
batchParams[opIdx].writeValue.value = 0;
|
||||
// batchParams[opIdx].writeValue.flags = CU_STREAM_WRITE_VALUE_DEFAULT;
|
||||
opIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
// Execute all memory operations in a single batch
|
||||
CUCHECKGOTO(hipStreamBatchMemOp(stream, opIdx, batchParams, 0), ret, fail);
|
||||
|
||||
// Toggle the flag for next call
|
||||
comm->ceColl.useCompletePtr = !comm->ceColl.useCompletePtr;
|
||||
|
||||
exit:
|
||||
if (batchParams) free(batchParams);
|
||||
return ret;
|
||||
fail:
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ncclResult_t ncclCeInitBatchOpsParams(struct ncclCeBatchOpsParams* params, int nRanks) {
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
|
||||
params->srcs = nullptr;
|
||||
params->dsts = nullptr;
|
||||
params->sizes = nullptr;
|
||||
params->numOps = 0;
|
||||
params->intraBatchSync = false;
|
||||
#if CUDART_VERSION >= 12080
|
||||
params->attrs = nullptr;
|
||||
params->attrIdxs = nullptr;
|
||||
params->numAttrs = 0;
|
||||
#endif
|
||||
|
||||
NCCLCHECKGOTO(ncclCalloc(¶ms->srcs, nRanks), ret, fail);
|
||||
NCCLCHECKGOTO(ncclCalloc(¶ms->dsts, nRanks), ret, fail);
|
||||
NCCLCHECKGOTO(ncclCalloc(¶ms->sizes, nRanks), ret, fail);
|
||||
#if CUDART_VERSION >= 12080
|
||||
NCCLCHECKGOTO(ncclCalloc(¶ms->attrs, nRanks), ret, fail);
|
||||
NCCLCHECKGOTO(ncclCalloc(¶ms->attrIdxs, nRanks), ret, fail);
|
||||
#endif
|
||||
exit:
|
||||
return ret;
|
||||
fail:
|
||||
goto exit;
|
||||
}
|
||||
|
||||
void ncclCeFreeBatchOpsParams(struct ncclCeBatchOpsParams* params) {
|
||||
if (params->srcs) free(params->srcs);
|
||||
if (params->dsts) free(params->dsts);
|
||||
if (params->sizes) free(params->sizes);
|
||||
#if CUDART_VERSION >= 12080
|
||||
if (params->attrs) free(params->attrs);
|
||||
if (params->attrIdxs) free(params->attrIdxs);
|
||||
#endif
|
||||
}
|
||||
|
||||
ncclResult_t ncclCeLaunchBatchOps(struct ncclComm* comm, struct ncclCeBatchOpsParams* params, cudaStream_t stream) {
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
|
||||
// Check if there are any operations to perform
|
||||
if (params->numOps == 0) {
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
// Check if we are in a CUDA graph capture
|
||||
bool capturing = ncclCudaGraphValid(comm->planner.capturingGraph);
|
||||
|
||||
int driverVersion;
|
||||
NCCLCHECKGOTO(ncclCudaDriverVersion(&driverVersion), ret, fail);
|
||||
|
||||
//--------------Graph capture--------------
|
||||
// cudaMemcpyBatchAsync is not supported during CUDA graph capture
|
||||
if (capturing) {
|
||||
for (int i =0; i < params->numOps; i++) {
|
||||
CUDACHECKGOTO(cudaMemcpyAsync(
|
||||
(void*)params->dsts[i],
|
||||
(void*)params->srcs[i],
|
||||
params->sizes[i],
|
||||
cudaMemcpyDeviceToDevice,
|
||||
stream), ret, fail);
|
||||
|
||||
if (params->intraBatchSync && ((i+1) % comm->ceColl.intraBatchSyncFreq == 0) && ((i+1) < params->numOps)) {
|
||||
NCCLCHECKGOTO(ncclMemOpSync(comm, stream), ret, fail);
|
||||
}
|
||||
}
|
||||
}
|
||||
//--------------No graph capture--------------
|
||||
else {
|
||||
if (/*CUDART_VERSION >= 12080 &&*/ driverVersion >= 12080) {
|
||||
#if CUDART_VERSION >= 12080
|
||||
// For CUDA 12.8+, use batch memory copy for better performance
|
||||
params->attrs[0] = {};
|
||||
params->attrs[0].srcAccessOrder = cudaMemcpySrcAccessOrderStream;
|
||||
params->attrs[0].flags = cudaMemcpyFlagPreferOverlapWithCompute;
|
||||
params->attrIdxs[0] = 0;
|
||||
params->numAttrs = 1;
|
||||
|
||||
if (params->intraBatchSync) {
|
||||
// Break into multiple batches with sync between them
|
||||
int batchSize = comm->ceColl.intraBatchSyncFreq;
|
||||
for (int i = 0; i < params->numOps; i += batchSize) {
|
||||
int currentBatchSize = (i + batchSize <= params->numOps) ? batchSize : params->numOps - i;
|
||||
|
||||
#if CUDART_VERSION >= 13000
|
||||
CUDACHECKGOTO(cudaMemcpyBatchAsync(
|
||||
¶ms->dsts[i], ¶ms->srcs[i], ¶ms->sizes[i], currentBatchSize,
|
||||
params->attrs, params->attrIdxs, params->numAttrs, stream), ret, fail);
|
||||
#else
|
||||
CUDACHECKGOTO(cudaMemcpyBatchAsync(
|
||||
¶ms->dsts[i], ¶ms->srcs[i], ¶ms->sizes[i], currentBatchSize,
|
||||
params->attrs, params->attrIdxs, params->numAttrs, nullptr, stream), ret, fail);
|
||||
#endif
|
||||
|
||||
// Sync after each batch
|
||||
if (i + batchSize < params->numOps) {
|
||||
NCCLCHECKGOTO(ncclMemOpSync(comm, stream), ret, fail);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Use single batch for all operations
|
||||
#if CUDART_VERSION >= 13000
|
||||
CUDACHECKGOTO(cudaMemcpyBatchAsync(
|
||||
params->dsts, params->srcs, params->sizes, params->numOps,
|
||||
params->attrs, params->attrIdxs, params->numAttrs, stream), ret, fail);
|
||||
#else
|
||||
CUDACHECKGOTO(cudaMemcpyBatchAsync(
|
||||
params->dsts, params->srcs, params->sizes, params->numOps,
|
||||
params->attrs, params->attrIdxs, params->numAttrs, nullptr, stream), ret, fail);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
// For older CUDA versions, fall back to individual transfers
|
||||
for (int i = 0; i < params->numOps; i++) {
|
||||
CUDACHECKGOTO(cudaMemcpyAsync(
|
||||
(void*)params->dsts[i],
|
||||
(void*)params->srcs[i],
|
||||
params->sizes[i],
|
||||
cudaMemcpyDeviceToDevice,
|
||||
stream), ret, fail);
|
||||
|
||||
if (params->intraBatchSync && ((i+1) % comm->ceColl.intraBatchSyncFreq == 0) && ((i+1) < params->numOps)) {
|
||||
NCCLCHECKGOTO(ncclMemOpSync(comm, stream), ret, fail);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
return ret;
|
||||
fail:
|
||||
goto exit;
|
||||
}
|
||||
|
||||
|
||||
ncclResult_t ncclCeAllGather(struct ncclComm* comm, struct ncclCeCollArgs* args, cudaStream_t stream) {
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
|
||||
// Calculate the size of each rank's data chunk
|
||||
const size_t chunkBytes = args->nElts * args->eltSize;
|
||||
uint8_t* mySendBuff = (uint8_t*)args->sendBuff;
|
||||
uint8_t* myRecvBuff = (uint8_t*)args->recvBuff + comm->rank * chunkBytes;
|
||||
void* peerRecvBuff;
|
||||
size_t offset;
|
||||
|
||||
struct ncclCeBatchOpsParams batchOpsParams = {};
|
||||
NCCLCHECKGOTO(ncclCeInitBatchOpsParams(&batchOpsParams, comm->nRanks), ret, fail);
|
||||
|
||||
// Ensure all ranks are ready before starting transfers
|
||||
NCCLCHECKGOTO(ncclMemOpSync(comm, stream), ret, fail);
|
||||
|
||||
// Copy own data to receive buffer if operation is out-of-place
|
||||
if (myRecvBuff != mySendBuff) {
|
||||
batchOpsParams.srcs[batchOpsParams.numOps] = (void*)mySendBuff;
|
||||
batchOpsParams.dsts[batchOpsParams.numOps] = (void*)myRecvBuff;
|
||||
batchOpsParams.sizes[batchOpsParams.numOps] = chunkBytes;
|
||||
batchOpsParams.numOps++;
|
||||
}
|
||||
|
||||
// Copy data to other ranks
|
||||
for (int r = 1; r < comm->nRanks; r++) {
|
||||
int targetRank = (comm->rank + r) % comm->nRanks;
|
||||
offset = myRecvBuff - (uint8_t*)args->recvWin->userPtr;
|
||||
NCCLCHECKGOTO(ncclDevrGetLsaRankPtr(comm, args->recvWin, offset, targetRank, &peerRecvBuff), ret, fail);
|
||||
batchOpsParams.srcs[batchOpsParams.numOps] = (void*)mySendBuff;
|
||||
batchOpsParams.dsts[batchOpsParams.numOps] = (void*)peerRecvBuff;
|
||||
batchOpsParams.sizes[batchOpsParams.numOps] = chunkBytes;
|
||||
batchOpsParams.numOps++;
|
||||
}
|
||||
|
||||
// Check if we need to perform intra-batch synchronization
|
||||
batchOpsParams.intraBatchSync = (batchOpsParams.numOps > comm->ceColl.intraBatchSyncFreq && chunkBytes*batchOpsParams.numOps >= comm->ceColl.intraBatchSyncMsgThreshold);
|
||||
|
||||
// Launch the batch operations
|
||||
NCCLCHECKGOTO(ncclCeLaunchBatchOps(comm, &batchOpsParams, stream), ret, fail);
|
||||
|
||||
// Ensure all transfers are complete across all ranks
|
||||
NCCLCHECKGOTO(ncclMemOpSync(comm, stream), ret, fail);
|
||||
|
||||
exit:
|
||||
ncclCeFreeBatchOpsParams(&batchOpsParams);
|
||||
return ret;
|
||||
fail:
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ncclResult_t ncclCeAlltoAll(struct ncclComm* comm, struct ncclCeCollArgs* args, cudaStream_t stream) {
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
|
||||
// Calculate the size of data each rank sends to every other rank
|
||||
const size_t chunkBytes = args->nElts * args->eltSize;
|
||||
uint8_t* mySendBuff = (uint8_t*)args->sendBuff;
|
||||
uint8_t* myRecvBuff = (uint8_t*)args->recvBuff;
|
||||
void* peerRecvBuff;
|
||||
size_t offset;
|
||||
|
||||
struct ncclCeBatchOpsParams batchOpsParams = {};
|
||||
NCCLCHECKGOTO(ncclCeInitBatchOpsParams(&batchOpsParams, comm->nRanks * comm->nRanks), ret, fail);
|
||||
|
||||
// Ensure all ranks are ready before starting transfers
|
||||
NCCLCHECKGOTO(ncclMemOpSync(comm, stream), ret, fail);
|
||||
|
||||
// Copy data to other ranks: send data chunk for each destination rank
|
||||
for (int r = 0; r < comm->nRanks; r++) {
|
||||
int dstRank = (comm->rank + r) % comm->nRanks;
|
||||
uint8_t* srcPtr = mySendBuff + dstRank * chunkBytes;
|
||||
uint8_t* dstPtr = myRecvBuff + comm->rank * chunkBytes;
|
||||
|
||||
if (dstRank == comm->rank) {
|
||||
// Local copy for own data
|
||||
batchOpsParams.srcs[batchOpsParams.numOps] = (void*)srcPtr;
|
||||
batchOpsParams.dsts[batchOpsParams.numOps] = (void*)dstPtr;
|
||||
batchOpsParams.sizes[batchOpsParams.numOps] = chunkBytes;
|
||||
batchOpsParams.numOps++;
|
||||
} else {
|
||||
// Remote copy to other ranks: send to rank dstRank's receive buffer at position comm->rank
|
||||
offset = dstPtr - (uint8_t*)args->recvWin->userPtr;
|
||||
NCCLCHECKGOTO(ncclDevrGetLsaRankPtr(comm, args->recvWin, offset, dstRank, &peerRecvBuff), ret, fail);
|
||||
batchOpsParams.srcs[batchOpsParams.numOps] = (void*)srcPtr;
|
||||
batchOpsParams.dsts[batchOpsParams.numOps] = (void*)peerRecvBuff;
|
||||
batchOpsParams.sizes[batchOpsParams.numOps] = chunkBytes;
|
||||
batchOpsParams.numOps++;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we need to perform intra-batch synchronization
|
||||
batchOpsParams.intraBatchSync = (batchOpsParams.numOps > comm->ceColl.intraBatchSyncFreq && chunkBytes*batchOpsParams.numOps >= comm->ceColl.intraBatchSyncMsgThreshold);
|
||||
|
||||
// Launch the batch operations
|
||||
NCCLCHECKGOTO(ncclCeLaunchBatchOps(comm, &batchOpsParams, stream), ret, fail);
|
||||
|
||||
// Ensure all transfers are complete across all ranks
|
||||
NCCLCHECKGOTO(ncclMemOpSync(comm, stream), ret, fail);
|
||||
|
||||
exit:
|
||||
ncclCeFreeBatchOpsParams(&batchOpsParams);
|
||||
return ret;
|
||||
fail:
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ncclResult_t ncclCeScatter(struct ncclComm* comm, struct ncclCeCollArgs* args, cudaStream_t stream) {
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
|
||||
// Calculate the size of data root sends to each rank
|
||||
const size_t chunkBytes = args->nElts * args->eltSize;
|
||||
uint8_t* mySendBuff = (uint8_t*)args->sendBuff;
|
||||
uint8_t* myRecvBuff = (uint8_t*)args->recvBuff;
|
||||
int rootRank = args->rootRank;
|
||||
void* peerDstPtr;
|
||||
size_t offset;
|
||||
|
||||
struct ncclCeBatchOpsParams batchOpsParams = {};
|
||||
NCCLCHECKGOTO(ncclCeInitBatchOpsParams(&batchOpsParams, comm->nRanks), ret, fail);
|
||||
|
||||
// Ensure all ranks are ready before starting transfers
|
||||
NCCLCHECKGOTO(ncclMemOpSync(comm, stream), ret, fail);
|
||||
|
||||
if (comm->rank == rootRank) {
|
||||
// Check if this is an in-place scatter operation
|
||||
bool isInPlace = (myRecvBuff == mySendBuff + comm->rank * chunkBytes);
|
||||
|
||||
// Copy root's own data first if not in-place
|
||||
if (!isInPlace) {
|
||||
uint8_t* srcPtr = mySendBuff + comm->rank * chunkBytes;
|
||||
uint8_t* dstPtr = myRecvBuff;
|
||||
batchOpsParams.srcs[batchOpsParams.numOps] = (void*)srcPtr;
|
||||
batchOpsParams.dsts[batchOpsParams.numOps] = (void*)dstPtr;
|
||||
batchOpsParams.sizes[batchOpsParams.numOps] = chunkBytes;
|
||||
batchOpsParams.numOps++;
|
||||
}
|
||||
|
||||
// Root rank distributes data to other ranks
|
||||
for (int r = 1; r < comm->nRanks; r++) {
|
||||
int dstRank = (comm->rank + r) % comm->nRanks;
|
||||
uint8_t* srcPtr = mySendBuff + dstRank * chunkBytes;
|
||||
uint8_t* dstPtr = isInPlace ? myRecvBuff + dstRank * chunkBytes : myRecvBuff;
|
||||
|
||||
offset = dstPtr - (uint8_t*)args->recvWin->userPtr;
|
||||
NCCLCHECKGOTO(ncclDevrGetLsaRankPtr(comm, args->recvWin, offset, dstRank, &peerDstPtr), ret, fail);
|
||||
batchOpsParams.srcs[batchOpsParams.numOps] = (void*)srcPtr;
|
||||
batchOpsParams.dsts[batchOpsParams.numOps] = (void*)peerDstPtr;
|
||||
batchOpsParams.sizes[batchOpsParams.numOps] = chunkBytes;
|
||||
batchOpsParams.numOps++;
|
||||
}
|
||||
}
|
||||
// Non-root ranks don't need to perform any copy operations
|
||||
|
||||
// Launch the batch operations
|
||||
NCCLCHECKGOTO(ncclCeLaunchBatchOps(comm, &batchOpsParams, stream), ret, fail);
|
||||
|
||||
// Ensure all transfers are complete across all ranks
|
||||
NCCLCHECKGOTO(ncclMemOpSync(comm, stream), ret, fail);
|
||||
|
||||
exit:
|
||||
ncclCeFreeBatchOpsParams(&batchOpsParams);
|
||||
return ret;
|
||||
fail:
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ncclResult_t ncclCeGather(struct ncclComm* comm, struct ncclCeCollArgs* args, cudaStream_t stream) {
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
|
||||
// Calculate the size of data each rank sends to root
|
||||
const size_t chunkBytes = args->nElts * args->eltSize;
|
||||
uint8_t* mySendBuff = (uint8_t*)args->sendBuff;
|
||||
uint8_t* myRecvBuff = (uint8_t*)args->recvBuff;
|
||||
int rootRank = args->rootRank;
|
||||
void* peerRecvBuff;
|
||||
size_t offset;
|
||||
|
||||
struct ncclCeBatchOpsParams batchOpsParams = {};
|
||||
NCCLCHECKGOTO(ncclCeInitBatchOpsParams(&batchOpsParams, 1), ret, fail);
|
||||
|
||||
// Ensure all ranks are ready before starting transfers
|
||||
NCCLCHECKGOTO(ncclMemOpSync(comm, stream), ret, fail);
|
||||
|
||||
if (comm->rank == rootRank) {
|
||||
// Root rank copies its own data to the correct position in receive buffer
|
||||
uint8_t* dstPtr = myRecvBuff + comm->rank * chunkBytes;
|
||||
if (mySendBuff != dstPtr) {
|
||||
batchOpsParams.srcs[batchOpsParams.numOps] = (void*)mySendBuff;
|
||||
batchOpsParams.dsts[batchOpsParams.numOps] = (void*)dstPtr;
|
||||
batchOpsParams.sizes[batchOpsParams.numOps] = chunkBytes;
|
||||
batchOpsParams.numOps++;
|
||||
}
|
||||
} else {
|
||||
// Non-root ranks send their data to root's receive buffer
|
||||
uint8_t* rootRecvPtr = (uint8_t*)args->recvBuff + comm->rank * chunkBytes;
|
||||
offset = rootRecvPtr - (uint8_t*)args->recvWin->userPtr;
|
||||
NCCLCHECKGOTO(ncclDevrGetLsaRankPtr(comm, args->recvWin, offset, rootRank, &peerRecvBuff), ret, fail);
|
||||
batchOpsParams.srcs[batchOpsParams.numOps] = (void*)mySendBuff;
|
||||
batchOpsParams.dsts[batchOpsParams.numOps] = (void*)peerRecvBuff;
|
||||
batchOpsParams.sizes[batchOpsParams.numOps] = chunkBytes;
|
||||
batchOpsParams.numOps++;
|
||||
}
|
||||
|
||||
// Launch the batch operations
|
||||
NCCLCHECKGOTO(ncclCeLaunchBatchOps(comm, &batchOpsParams, stream), ret, fail);
|
||||
|
||||
// Ensure all transfers are complete across all ranks
|
||||
NCCLCHECKGOTO(ncclMemOpSync(comm, stream), ret, fail);
|
||||
|
||||
exit:
|
||||
ncclCeFreeBatchOpsParams(&batchOpsParams);
|
||||
return ret;
|
||||
fail:
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ncclResult_t ncclLaunchCeColl(struct ncclComm* comm, struct ncclKernelPlan* plan) {
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
cudaStream_t stream = comm->planner.streams->stream;
|
||||
struct ncclCeCollArgs* args = plan->ceCollArgs;
|
||||
|
||||
switch (args->func) {
|
||||
case ncclFuncAllGather:
|
||||
NCCLCHECKGOTO(ncclCeAllGather(comm, args, stream), ret, fail);
|
||||
break;
|
||||
case ncclFuncAlltoAll:
|
||||
NCCLCHECKGOTO(ncclCeAlltoAll(comm, args, stream), ret, fail);
|
||||
break;
|
||||
case ncclFuncScatter:
|
||||
NCCLCHECKGOTO(ncclCeScatter(comm, args, stream), ret, fail);
|
||||
break;
|
||||
case ncclFuncGather:
|
||||
NCCLCHECKGOTO(ncclCeGather(comm, args, stream), ret, fail);
|
||||
break;
|
||||
default:
|
||||
ret = ncclInvalidUsage;
|
||||
}
|
||||
|
||||
exit:
|
||||
return ret;
|
||||
fail:
|
||||
goto exit;
|
||||
}
|
||||
+118
-166
@@ -23,10 +23,13 @@ const char* ncclFuncToString(ncclFunc_t fn) {
|
||||
switch (fn) {
|
||||
case ncclFuncAllGather: return "AllGather";
|
||||
case ncclFuncAllReduce: return "AllReduce";
|
||||
case ncclFuncAlltoAll: return "AlltoAll";
|
||||
case ncclFuncBroadcast: return "Broadcast";
|
||||
case ncclFuncGather: return "Gather";
|
||||
case ncclFuncRecv: return "Recv";
|
||||
case ncclFuncReduce: return "Reduce";
|
||||
case ncclFuncReduceScatter: return "ReduceScatter";
|
||||
case ncclFuncScatter: return "Scatter";
|
||||
case ncclFuncSendRecv: return "SendRecv";
|
||||
case ncclFuncSend: return "Send";
|
||||
default: return "Invalid";
|
||||
@@ -85,7 +88,6 @@ const char* ncclProtoToString(int proto) {
|
||||
|
||||
NCCL_API(ncclResult_t, ncclAllGather, const void* sendbuff, void* recvbuff, size_t sendcount,
|
||||
ncclDataType_t datatype, ncclComm_t comm, cudaStream_t stream);
|
||||
|
||||
ncclResult_t ncclAllGather_impl(const void* sendbuff, void* recvbuff, size_t sendcount,
|
||||
ncclDataType_t datatype, ncclComm_t comm, cudaStream_t stream) {
|
||||
NVTX3_FUNC_WITH_PARAMS(AllGather, NcclNvtxParamsAllGather,
|
||||
@@ -148,10 +150,101 @@ ncclResult_t ncclAllGather_impl(const void* sendbuff, void* recvbuff, size_t sen
|
||||
}
|
||||
}
|
||||
|
||||
RCCL_PARAM(AlltoAllPivotEnable, "ALL_TO_ALL_PIVOT_ENABLE", 0);
|
||||
|
||||
NCCL_API(ncclResult_t, ncclAlltoAll, const void* sendbuff, void* recvbuff, size_t count,
|
||||
ncclDataType_t datatype, ncclComm* comm, cudaStream_t stream);
|
||||
ncclResult_t ncclAlltoAll_impl(const void* sendbuff, void* recvbuff, size_t count,
|
||||
ncclDataType_t datatype, ncclComm* comm, cudaStream_t stream) {
|
||||
NVTX3_FUNC_WITH_PARAMS(AlltoAll, NcclNvtxParamsAlltoAll,
|
||||
NVTX3_PAYLOAD(comm ? comm->commHash : 0, count * ncclTypeSize(datatype), datatype));
|
||||
|
||||
if (!mscclIsCaller()) // when msccl falls back to
|
||||
{
|
||||
NCCLCHECK(Recorder::instance().record(rrAllToAll, sendbuff, recvbuff, count, datatype, comm, stream));
|
||||
}
|
||||
|
||||
if (mscclAvailable(comm) && !mscclIsCaller()) {
|
||||
return mscclEnqueueCheck(
|
||||
sendbuff, nullptr, nullptr, recvbuff, nullptr, nullptr,
|
||||
count, datatype, 0, 0, ncclSum, mscclFuncAllToAll, comm, stream);
|
||||
}
|
||||
|
||||
size_t rankOffset = count * ncclTypeSize(datatype);
|
||||
size_t rankAlign = rankOffset & ((~rankOffset) + 1);
|
||||
size_t msgSize = count * ncclTypeSize(datatype) * comm->nRanks;
|
||||
|
||||
struct ncclInfo info;
|
||||
if (comm->topo->pivotA2AEnabled && comm->nChannels >= comm->topo->pivotA2ANumBiRings * 2 &&
|
||||
rankOffset >= 744 * 1024 && rankAlign != 4 && rcclParamAlltoAllPivotEnable()) {
|
||||
info = { ncclFuncAlltoAllPivot, "AlltoAllPivot",
|
||||
sendbuff, recvbuff, count, datatype, ncclSum, 0, comm, stream, /* Args */
|
||||
ALLTOALL_PIVOT_CHUNKSTEPS, ALLTOALL_PIVOT_SLICESTEPS, nullptr };
|
||||
} else {
|
||||
#ifdef ENABLE_ROCSHMEM
|
||||
if (rcclUseAllToAllGda(comm) && msgSize <= comm->rocshmemThreshold) {
|
||||
struct ncclInfo info = { ncclFuncAllToAllGda, "AllToAllGda",
|
||||
sendbuff, recvbuff, count, datatype, ncclSum, 0, comm, stream,
|
||||
ALLTOALL_PIVOT_CHUNKSTEPS, ALLTOALL_PIVOT_SLICESTEPS, nullptr };
|
||||
|
||||
return ncclEnqueueCheck(&info);
|
||||
}
|
||||
#endif ENABLE_ROCSHMEM
|
||||
info = { ncclFuncAlltoAll, "AlltoAll",
|
||||
sendbuff, recvbuff, count, datatype, ncclSum, 0, comm, stream, /* Args */
|
||||
ALLTOALL_CHUNKSTEPS, ALLTOALL_SLICESTEPS };
|
||||
}
|
||||
return ncclEnqueueCheck(&info);
|
||||
}
|
||||
|
||||
NCCL_API(ncclResult_t, ncclAlltoAllv, const void *sendbuff, const size_t sendcounts[], const size_t sdispls[],
|
||||
void *recvbuff, const size_t recvcounts[], const size_t rdispls[],
|
||||
ncclDataType_t datatype, ncclComm_t comm, hipStream_t stream);
|
||||
ncclResult_t ncclAlltoAllv_impl(const void *sendbuff, const size_t sendcounts[], const size_t sdispls[],
|
||||
void *recvbuff, const size_t recvcounts[], const size_t rdispls[],
|
||||
ncclDataType_t datatype, ncclComm_t comm, hipStream_t stream) {
|
||||
NVTX3_FUNC_WITH_PARAMS(AlltoAllv, NcclNvtxParamsAlltoAllv,
|
||||
NVTX3_PAYLOAD(comm ? comm->commHash : 0, sendcounts[comm->rank] * ncclTypeSize(datatype),
|
||||
recvcounts[comm->rank] * ncclTypeSize(datatype), datatype));
|
||||
|
||||
if (!mscclIsCaller()) // when msccl falls back to
|
||||
{
|
||||
NCCLCHECK(Recorder::instance().record(rrAllToAllv, sendbuff, recvbuff, 0, datatype, comm, stream, -1, sendcounts, sdispls, recvcounts, rdispls));
|
||||
}
|
||||
|
||||
if (mscclAvailable(comm) && !mscclIsCaller()) {
|
||||
return mscclEnqueueCheck(
|
||||
sendbuff, sendcounts, sdispls, recvbuff, recvcounts, rdispls,
|
||||
0, datatype, 0, 0, ncclSum, mscclFuncAllToAllv, comm, stream);
|
||||
}
|
||||
|
||||
int nRanks;
|
||||
NCCLCHECK(ncclCommCount(comm, &nRanks));
|
||||
if (!mscclIsCaller()) Recorder::instance().skip(true);
|
||||
NCCLCHECK(ncclGroupStart());
|
||||
for (int r=0; r<nRanks; r++) {
|
||||
NCCLCHECK(ncclSend(
|
||||
((char*)sendbuff) + sdispls[r]*ncclTypeSize(datatype),
|
||||
sendcounts[r],
|
||||
datatype,
|
||||
r,
|
||||
comm,
|
||||
stream));
|
||||
NCCLCHECK(ncclRecv(
|
||||
((char*)recvbuff) + rdispls[r]*ncclTypeSize(datatype),
|
||||
recvcounts[r],
|
||||
datatype,
|
||||
r,
|
||||
comm,
|
||||
stream));
|
||||
}
|
||||
NCCLCHECK(ncclGroupEnd());
|
||||
if (!mscclIsCaller()) Recorder::instance().skip(false);
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
NCCL_API(ncclResult_t, ncclAllReduce, const void* sendbuff, void* recvbuff, size_t count,
|
||||
ncclDataType_t datatype, ncclRedOp_t op, ncclComm* comm, cudaStream_t stream);
|
||||
|
||||
|
||||
ncclResult_t ncclAllReduce_impl(const void* sendbuff, void* recvbuff, size_t count,
|
||||
ncclDataType_t datatype, ncclRedOp_t op, ncclComm* comm, cudaStream_t stream) {
|
||||
NVTX3_FUNC_WITH_PARAMS(AllReduce, NcclNvtxParamsAllReduce,
|
||||
@@ -202,116 +295,8 @@ ncclResult_t ncclAllReduceWithBias_impl(const void* sendbuff, void* recvbuff, si
|
||||
return ncclEnqueueCheck(&info);
|
||||
}
|
||||
|
||||
RCCL_PARAM(AllToAllPivotEnable, "ALL_TO_ALL_PIVOT_ENABLE", 0);
|
||||
|
||||
NCCL_API(ncclResult_t, ncclAllToAll, const void* sendbuff, void* recvbuff, size_t count, ncclDataType_t datatype,
|
||||
ncclComm_t comm, hipStream_t stream);
|
||||
|
||||
|
||||
ncclResult_t ncclAllToAll_impl(const void* sendbuff, void* recvbuff, size_t count, ncclDataType_t datatype,
|
||||
ncclComm_t comm, hipStream_t stream) {
|
||||
NVTX3_FUNC_WITH_PARAMS(AllToAll, NcclNvtxParamsAllToAll,
|
||||
NVTX3_PAYLOAD(comm ? comm->commHash : 0, count * ncclTypeSize(datatype), datatype));
|
||||
|
||||
if (!mscclIsCaller()) // when msccl falls back to
|
||||
{
|
||||
NCCLCHECK(Recorder::instance().record(rrAllToAll, sendbuff, recvbuff, count, datatype, comm, stream));
|
||||
}
|
||||
|
||||
if (mscclAvailable(comm) && !mscclIsCaller()) {
|
||||
return mscclEnqueueCheck(
|
||||
sendbuff, nullptr, nullptr, recvbuff, nullptr, nullptr,
|
||||
count, datatype, 0, 0, ncclSum, mscclFuncAllToAll, comm, stream);
|
||||
}
|
||||
|
||||
size_t rankOffset = count * ncclTypeSize(datatype);
|
||||
size_t rankAlign = rankOffset & ((~rankOffset) + 1);
|
||||
size_t msgSize = count * ncclTypeSize(datatype) * comm->nRanks;
|
||||
|
||||
// Determine Pivot A2A support now that we know number of channels
|
||||
if (comm->topo->pivotA2AEnabled && comm->nChannels >= comm->topo->pivotA2ANumBiRings * 2 &&
|
||||
rankOffset >= 744 * 1024 && rankAlign != 4 && rcclParamAllToAllPivotEnable()) {
|
||||
struct ncclInfo info = { ncclFuncAllToAllPivot, "AllToAllPivot",
|
||||
sendbuff, recvbuff, count, datatype, ncclSum, 0, comm, stream, /* Args */
|
||||
ALLTOALL_PIVOT_CHUNKSTEPS, ALLTOALL_PIVOT_SLICESTEPS, nullptr };
|
||||
return ncclEnqueueCheck(&info);
|
||||
} else {
|
||||
#ifdef ENABLE_ROCSHMEM
|
||||
if (rcclUseAllToAllGda(comm) && msgSize <= comm->rocshmemThreshold) {
|
||||
struct ncclInfo info = { ncclFuncAllToAllGda, "AllToAllGda",
|
||||
sendbuff, recvbuff, count, datatype, ncclSum, 0, comm, stream,
|
||||
ALLTOALL_PIVOT_CHUNKSTEPS, ALLTOALL_PIVOT_SLICESTEPS, nullptr };
|
||||
|
||||
return ncclEnqueueCheck(&info);
|
||||
}
|
||||
#endif
|
||||
int nRanks;
|
||||
//comm->isA2a = 0;
|
||||
NCCLCHECK(ncclCommCount(comm, &nRanks));
|
||||
if (count == 0) return ncclSuccess;
|
||||
if (!mscclIsCaller()) Recorder::instance().skip(true);
|
||||
NCCLCHECK(ncclGroupStart());
|
||||
for (int r=0; r<nRanks; r++) {
|
||||
NCCLCHECK(ncclSend(((char*)sendbuff)+r*rankOffset, count, datatype, r, comm, stream));
|
||||
NCCLCHECK(ncclRecv(((char*)recvbuff)+r*rankOffset, count, datatype, r, comm, stream));
|
||||
}
|
||||
NCCLCHECK(ncclGroupEnd());
|
||||
if (!mscclIsCaller()) Recorder::instance().skip(false);
|
||||
return ncclSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
NCCL_API(ncclResult_t, ncclAllToAllv, const void *sendbuff, const size_t sendcounts[], const size_t sdispls[],
|
||||
void *recvbuff, const size_t recvcounts[], const size_t rdispls[],
|
||||
ncclDataType_t datatype, ncclComm_t comm, hipStream_t stream);
|
||||
|
||||
|
||||
ncclResult_t ncclAllToAllv_impl(const void *sendbuff, const size_t sendcounts[], const size_t sdispls[],
|
||||
void *recvbuff, const size_t recvcounts[], const size_t rdispls[],
|
||||
ncclDataType_t datatype, ncclComm_t comm, hipStream_t stream) {
|
||||
NVTX3_FUNC_WITH_PARAMS(AllToAllv, NcclNvtxParamsAllToAllv,
|
||||
NVTX3_PAYLOAD(comm ? comm->commHash : 0, sendcounts[comm->rank] * ncclTypeSize(datatype),
|
||||
recvcounts[comm->rank] * ncclTypeSize(datatype), datatype));
|
||||
|
||||
if (!mscclIsCaller()) // when msccl falls back to
|
||||
{
|
||||
NCCLCHECK(Recorder::instance().record(rrAllToAllv, sendbuff, recvbuff, 0, datatype, comm, stream, -1, sendcounts, sdispls, recvcounts, rdispls));
|
||||
}
|
||||
|
||||
if (mscclAvailable(comm) && !mscclIsCaller()) {
|
||||
return mscclEnqueueCheck(
|
||||
sendbuff, sendcounts, sdispls, recvbuff, recvcounts, rdispls,
|
||||
0, datatype, 0, 0, ncclSum, mscclFuncAllToAllv, comm, stream);
|
||||
}
|
||||
|
||||
int nRanks;
|
||||
NCCLCHECK(ncclCommCount(comm, &nRanks));
|
||||
if (!mscclIsCaller()) Recorder::instance().skip(true);
|
||||
NCCLCHECK(ncclGroupStart());
|
||||
for (int r=0; r<nRanks; r++) {
|
||||
NCCLCHECK(ncclSend(
|
||||
((char*)sendbuff) + sdispls[r]*ncclTypeSize(datatype),
|
||||
sendcounts[r],
|
||||
datatype,
|
||||
r,
|
||||
comm,
|
||||
stream));
|
||||
NCCLCHECK(ncclRecv(
|
||||
((char*)recvbuff) + rdispls[r]*ncclTypeSize(datatype),
|
||||
recvcounts[r],
|
||||
datatype,
|
||||
r,
|
||||
comm,
|
||||
stream));
|
||||
}
|
||||
NCCLCHECK(ncclGroupEnd());
|
||||
if (!mscclIsCaller()) Recorder::instance().skip(false);
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
NCCL_API(ncclResult_t, ncclBroadcast, const void* sendbuff, void* recvbuff, size_t count, ncclDataType_t datatype, int root,
|
||||
ncclComm_t comm, cudaStream_t stream);
|
||||
|
||||
ncclResult_t ncclBroadcast_impl(const void* sendbuff, void* recvbuff, size_t count, ncclDataType_t datatype, int root,
|
||||
ncclComm_t comm, cudaStream_t stream) {
|
||||
NVTX3_FUNC_WITH_PARAMS(Broadcast, NcclNvtxParamsBroadcast,
|
||||
@@ -343,46 +328,32 @@ ncclResult_t ncclBcast(void* buff, size_t count, ncclDataType_t datatype, int ro
|
||||
return ncclBroadcast(buff, buff, count, datatype, root, comm, stream);
|
||||
}
|
||||
|
||||
NCCL_API(ncclResult_t, ncclGather, const void* sendbuff, void* recvbuff, size_t sendcount,
|
||||
ncclDataType_t datatype, int root, ncclComm_t comm, hipStream_t stream);
|
||||
|
||||
ncclResult_t ncclGather_impl(const void* sendbuff, void* recvbuff, size_t sendcount,
|
||||
ncclDataType_t datatype, int root, ncclComm_t comm, hipStream_t stream) {
|
||||
NCCL_API(ncclResult_t, ncclGather, const void* sendbuff, void* recvbuff, size_t count, ncclDataType_t datatype, int root,
|
||||
ncclComm* comm, cudaStream_t stream);
|
||||
ncclResult_t ncclGather_impl(const void* sendbuff, void* recvbuff, size_t count, ncclDataType_t datatype, int root,
|
||||
ncclComm* comm, cudaStream_t stream) {
|
||||
NVTX3_FUNC_WITH_PARAMS(Gather, NcclNvtxParamsGather,
|
||||
NVTX3_PAYLOAD(comm ? comm->commHash : 0, sendcount * ncclTypeSize(datatype), root, datatype));
|
||||
NVTX3_PAYLOAD(comm ? comm->commHash : 0, count * ncclTypeSize(datatype), root));
|
||||
|
||||
if (!mscclIsCaller()) // when msccl falls back to
|
||||
{
|
||||
NCCLCHECK(Recorder::instance().record(rrGather, sendbuff, recvbuff, sendcount, datatype, comm, stream, root));
|
||||
NCCLCHECK(Recorder::instance().record(rrGather, sendbuff, recvbuff, count, datatype, comm, stream, root));
|
||||
}
|
||||
|
||||
if (mscclAvailable(comm) && !mscclIsCaller()) {
|
||||
return mscclEnqueueCheck(
|
||||
sendbuff, nullptr, nullptr, recvbuff, nullptr, nullptr,
|
||||
sendcount, datatype, root, 0, ncclSum, mscclFuncGather, comm, stream);
|
||||
count, datatype, root, 0, ncclSum, mscclFuncGather, comm, stream);
|
||||
}
|
||||
|
||||
int nRanks;
|
||||
NCCLCHECK(ncclCommCount(comm, &nRanks));
|
||||
size_t rankOffset = sendcount * ncclTypeSize(datatype);
|
||||
if (sendcount == 0) return ncclSuccess;
|
||||
int rank;
|
||||
NCCLCHECK(ncclCommUserRank(comm, &rank));
|
||||
if (!mscclIsCaller()) Recorder::instance().skip(true);
|
||||
NCCLCHECK(ncclGroupStart());
|
||||
if (rank == root) {
|
||||
for (int r=0; r<nRanks; r++)
|
||||
NCCLCHECK(ncclRecv(((char*)recvbuff)+r*rankOffset, sendcount, datatype, r, comm, stream));
|
||||
}
|
||||
NCCLCHECK(ncclSend(sendbuff, sendcount, datatype, root, comm, stream));
|
||||
NCCLCHECK(ncclGroupEnd());
|
||||
if (!mscclIsCaller()) Recorder::instance().skip(false);
|
||||
return ncclSuccess;
|
||||
struct ncclInfo info = { ncclFuncGather, "Gather",
|
||||
sendbuff, recvbuff, count, datatype, ncclSum, root, comm, stream, /* Args */
|
||||
GATHER_CHUNKSTEPS, GATHER_SLICESTEPS };
|
||||
return ncclEnqueueCheck(&info);
|
||||
}
|
||||
|
||||
NCCL_API(ncclResult_t, ncclReduce, const void* sendbuff, void* recvbuff, size_t count,
|
||||
ncclDataType_t datatype, ncclRedOp_t op, int root, ncclComm_t comm, cudaStream_t stream);
|
||||
|
||||
ncclResult_t ncclReduce_impl(const void* sendbuff, void* recvbuff, size_t count,
|
||||
ncclDataType_t datatype, ncclRedOp_t op, int root, ncclComm_t comm, cudaStream_t stream) {
|
||||
NVTX3_FUNC_WITH_PARAMS(Reduce, NcclNvtxParamsReduce,
|
||||
@@ -408,8 +379,6 @@ ncclResult_t ncclReduce_impl(const void* sendbuff, void* recvbuff, size_t count,
|
||||
|
||||
NCCL_API(ncclResult_t, ncclReduceScatter, const void* sendbuff, void* recvbuff, size_t recvcount,
|
||||
ncclDataType_t datatype, ncclRedOp_t op, ncclComm* comm, cudaStream_t stream);
|
||||
|
||||
|
||||
ncclResult_t ncclReduceScatter_impl(const void* sendbuff, void* recvbuff, size_t recvcount,
|
||||
ncclDataType_t datatype, ncclRedOp_t op, ncclComm* comm, cudaStream_t stream) {
|
||||
NVTX3_FUNC_WITH_PARAMS(ReduceScatter, NcclNvtxParamsReduceScatter,
|
||||
@@ -433,48 +402,32 @@ ncclResult_t ncclReduceScatter_impl(const void* sendbuff, void* recvbuff, size_t
|
||||
return ncclEnqueueCheck(&info);
|
||||
}
|
||||
|
||||
NCCL_API(ncclResult_t, ncclScatter, const void* sendbuff, void* recvbuff, size_t recvcount, ncclDataType_t datatype, int root,
|
||||
ncclComm_t comm, hipStream_t stream);
|
||||
|
||||
|
||||
ncclResult_t ncclScatter_impl(const void* sendbuff, void* recvbuff, size_t recvcount, ncclDataType_t datatype, int root,
|
||||
ncclComm_t comm, hipStream_t stream) {
|
||||
NCCL_API(ncclResult_t, ncclScatter, const void* sendbuff, void* recvbuff, size_t count,
|
||||
ncclDataType_t datatype, int root, ncclComm* comm, cudaStream_t stream);
|
||||
ncclResult_t ncclScatter_impl(const void* sendbuff, void* recvbuff, size_t count,
|
||||
ncclDataType_t datatype, int root, ncclComm* comm, cudaStream_t stream) {
|
||||
NVTX3_FUNC_WITH_PARAMS(Scatter, NcclNvtxParamsScatter,
|
||||
NVTX3_PAYLOAD(comm ? comm->commHash : 0, recvcount * ncclTypeSize(datatype), root, datatype));
|
||||
NVTX3_PAYLOAD(comm ? comm->commHash : 0, count * ncclTypeSize(datatype), root, datatype));
|
||||
|
||||
if (!mscclIsCaller()) // when msccl falls back to
|
||||
{
|
||||
NCCLCHECK(Recorder::instance().record(rrScatter, sendbuff, recvbuff, recvcount, datatype, comm, stream, root));
|
||||
NCCLCHECK(Recorder::instance().record(rrScatter, sendbuff, recvbuff, count, datatype, comm, stream, root));
|
||||
}
|
||||
|
||||
if (mscclAvailable(comm) && !mscclIsCaller()) {
|
||||
return mscclEnqueueCheck(
|
||||
sendbuff, nullptr, nullptr, recvbuff, nullptr, nullptr,
|
||||
recvcount, datatype, root, 0, ncclSum, mscclFuncScatter, comm, stream);
|
||||
count, datatype, root, 0, ncclSum, mscclFuncScatter, comm, stream);
|
||||
}
|
||||
|
||||
int nRanks;
|
||||
NCCLCHECK(ncclCommCount(comm, &nRanks));
|
||||
size_t rankOffset = recvcount * ncclTypeSize(datatype);
|
||||
if (recvcount == 0) return ncclSuccess;
|
||||
int rank;
|
||||
NCCLCHECK(ncclCommUserRank(comm, &rank));
|
||||
if (!mscclIsCaller()) Recorder::instance().skip(true);
|
||||
NCCLCHECK(ncclGroupStart());
|
||||
if (rank == root) {
|
||||
for (int r=0; r<nRanks; r++)
|
||||
NCCLCHECK(ncclSend(((char*)sendbuff)+r*rankOffset, recvcount, datatype, r, comm, stream));
|
||||
}
|
||||
NCCLCHECK(ncclRecv(recvbuff, recvcount, datatype, root, comm, stream));
|
||||
NCCLCHECK(ncclGroupEnd());
|
||||
if (!mscclIsCaller()) Recorder::instance().skip(false);
|
||||
return ncclSuccess;
|
||||
struct ncclInfo info = { ncclFuncScatter, "Scatter",
|
||||
sendbuff, recvbuff, count, datatype, ncclSum, root, comm, stream, /* Args */
|
||||
SCATTER_CHUNKSTEPS, SCATTER_SLICESTEPS };
|
||||
return ncclEnqueueCheck(&info);
|
||||
}
|
||||
|
||||
NCCL_API(ncclResult_t, ncclSend, const void* sendbuff, size_t count, ncclDataType_t datatype, int peer,
|
||||
ncclComm_t comm, cudaStream_t stream);
|
||||
|
||||
|
||||
ncclResult_t ncclSend_impl(const void* sendbuff, size_t count, ncclDataType_t datatype, int peer,
|
||||
ncclComm_t comm, cudaStream_t stream) {
|
||||
NVTX3_FUNC_WITH_PARAMS(Send, NcclNvtxParamsSendRecv,
|
||||
@@ -500,7 +453,6 @@ ncclResult_t ncclSend_impl(const void* sendbuff, size_t count, ncclDataType_t da
|
||||
|
||||
NCCL_API(ncclResult_t, ncclRecv, void* recvbuff, size_t count, ncclDataType_t datatype, int peer,
|
||||
ncclComm_t comm, cudaStream_t stream);
|
||||
|
||||
ncclResult_t ncclRecv_impl(void* recvbuff, size_t count, ncclDataType_t datatype, int peer,
|
||||
ncclComm_t comm, cudaStream_t stream) {
|
||||
NVTX3_FUNC_WITH_PARAMS(Recv, NcclNvtxParamsSendRecv,
|
||||
|
||||
@@ -28,7 +28,7 @@ static int pid = -1;
|
||||
static char hostname[1024];
|
||||
thread_local int ncclDebugNoWarn = 0;
|
||||
char ncclLastError[1024] = ""; // Global string for the last error in human readable form
|
||||
static uint64_t ncclDebugMask = 0;
|
||||
uint64_t ncclDebugMask = 0;
|
||||
FILE *ncclDebugFile = stdout;
|
||||
static pthread_mutex_t ncclDebugLock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static std::chrono::steady_clock::time_point ncclEpoch;
|
||||
@@ -419,4 +419,4 @@ void ncclSetThreadName(pthread_t thread, const char *fmt, ...) {
|
||||
va_end(vargs);
|
||||
pthread_setname_np(thread, threadName);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,60 @@
|
||||
# Run the scripts once during configuration to get the file lists
|
||||
execute_process(
|
||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/generate.py ${CMAKE_CURRENT_BINARY_DIR}/gensrc "${ONLY_FUNCS}"
|
||||
OUTPUT_VARIABLE files
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
string(STRIP "${files}" files)
|
||||
list(TRANSFORM files PREPEND ${CMAKE_CURRENT_BINARY_DIR}/gensrc/)
|
||||
|
||||
execute_process(
|
||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/symmetric/generate.py ${CMAKE_CURRENT_BINARY_DIR}/gensrc/symmetric "${ONLY_FUNCS}"
|
||||
OUTPUT_VARIABLE symmetric_files
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
string(STRIP "${symmetric_files}" symmetric_files)
|
||||
list(TRANSFORM symmetric_files PREPEND ${CMAKE_CURRENT_BINARY_DIR}/gensrc/symmetric/)
|
||||
|
||||
# Create custom commands to generate source files with proper dependencies
|
||||
add_custom_command(
|
||||
OUTPUT ${files}
|
||||
BYPRODUCTS ${files}
|
||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/generate.py ${CMAKE_CURRENT_BINARY_DIR}/gensrc "${ONLY_FUNCS}"
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/generate.py
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMENT "Generating device source files"
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${symmetric_files}
|
||||
BYPRODUCTS ${symmetric_files}
|
||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/symmetric/generate.py ${CMAKE_CURRENT_BINARY_DIR}/gensrc/symmetric "${ONLY_FUNCS}"
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/symmetric/generate.py
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMENT "Generating symmetric device source files"
|
||||
)
|
||||
|
||||
# Add library target
|
||||
add_library(nccl_device OBJECT
|
||||
${files}
|
||||
${symmetric_files}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/common.cu
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/onerank.cu
|
||||
)
|
||||
|
||||
set_target_properties(nccl_device PROPERTIES
|
||||
CUDA_SEPARABLE_COMPILATION ON
|
||||
CUDA_RESOLVE_DEVICE_SYMBOLS ON
|
||||
)
|
||||
|
||||
# Set include directories for the target
|
||||
target_include_directories(nccl_device PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_SOURCE_DIR}/src/include
|
||||
${CMAKE_SOURCE_DIR}/src/include/plugin
|
||||
${CMAKE_BINARY_DIR}/include
|
||||
${CUDAToolkit_INCLUDE_DIRS}
|
||||
${CUDAToolkit_INCLUDE_DIRS}/cccl
|
||||
)
|
||||
|
||||
add_dependencies(nccl_device nccl_header)
|
||||
@@ -19,7 +19,7 @@ OBJDIR := $(BUILDDIR)/obj/device
|
||||
MANIFEST := $(OBJDIR)/manifest
|
||||
DEVGLUE_OBJ := $(OBJDIR)/device_glue.o
|
||||
|
||||
INCFLAGS = -I. -I.. -I$(BUILDDIR)/include -I../include
|
||||
INCFLAGS = -I. -I.. -I$(BUILDDIR)/include -I../include -I../include/plugin
|
||||
NVCUFLAGS += $(INCFLAGS) --compiler-options "-fPIC -fvisibility=hidden"
|
||||
CXXFLAGS += $(INCFLAGS)
|
||||
|
||||
@@ -47,7 +47,11 @@ endif
|
||||
define COMPILE_SYM
|
||||
@$(SAY) "Compiling" $2;\
|
||||
mkdir -p $(dir $1);\
|
||||
$(NVCC) $(NVCUFLAGS_SYM) $3 -dw $2 -o $1
|
||||
if [[ -n "$3" ]]; then\
|
||||
$(NVCC) $(NVCUFLAGS_SYM) $3 -dw $2 -o $1;\
|
||||
else\
|
||||
touch $2.empty.cu; $(NVCC) $(NVCUFLAGS_SYM) -dw $2.empty.cu -o $1; rm $2.empty.cu;\
|
||||
fi
|
||||
endef
|
||||
|
||||
DEPENDS.cu = $(NVCC) $(NVCUFLAGS) -M -dc $1
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace {
|
||||
}
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkColl<ncclFuncAllToAllPivot, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_SIMPLE> {
|
||||
struct RunWorkColl<ncclFuncAlltoAllPivot, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(int tid, int nThreads, struct ncclDevWorkColl* work) {
|
||||
using Proto = ProtoSimple<ALLTOALL_PIVOT_CHUNKSTEPS/ALLTOALL_PIVOT_SLICESTEPS, ALLTOALL_PIVOT_SLICESTEPS>;
|
||||
runRing<T, RedOp, Proto>(tid, nThreads, work);
|
||||
|
||||
@@ -150,7 +150,7 @@ struct ncclShmemData {
|
||||
struct ncclDevKernelArgs args;
|
||||
int channelId;
|
||||
int aborted;
|
||||
alignas(16) struct ncclDevComm comm;
|
||||
alignas(16) struct ncclKernelComm comm;
|
||||
alignas(16) struct ncclDevChannel channel;
|
||||
#ifdef ENABLE_WARP_SPEED
|
||||
int warpComm;
|
||||
@@ -502,7 +502,7 @@ __device__ __forceinline__ void profiler(int action) {
|
||||
ncclShmem.comm.workCompleted[ncclShmem.channelId].data[wc%MAX_PROFILER_EVENTS_PER_CHANNEL].counter = wc;
|
||||
}
|
||||
ncclShmem.channel.workCounter += ncclShmem.nWorks;
|
||||
if (action == FINI) ((ncclDevCommAndChannels*)ncclShmem.args.comm)->channels[ncclShmem.channelId].workCounter = ncclShmem.channel.workCounter;
|
||||
if (action == FINI) ((ncclKernelCommAndChannels*)ncclShmem.args.comm)->channels[ncclShmem.channelId].workCounter = ncclShmem.channel.workCounter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -579,7 +579,7 @@ __device__ __forceinline__ void ncclKernelMain(struct ncclDevKernelArgs const* a
|
||||
/* set abort flag to 0 */
|
||||
if (tid == 0) {
|
||||
ncclShmem.aborted = 0;
|
||||
ncclShmem.channel.workCounter = ((ncclDevCommAndChannels*)ncclShmem.args.comm)->channels[ncclShmem.channelId].workCounter;
|
||||
ncclShmem.channel.workCounter = ((ncclKernelCommAndChannels*)ncclShmem.args.comm)->channels[ncclShmem.channelId].workCounter;
|
||||
}
|
||||
|
||||
// Use first 2 warps to load comm and channel, and remaining load work batch.
|
||||
@@ -587,14 +587,14 @@ __device__ __forceinline__ void ncclKernelMain(struct ncclDevKernelArgs const* a
|
||||
case 0:
|
||||
{ void* dst = &ncclShmem.comm;
|
||||
void* src = ncclShmem.args.comm;
|
||||
int bytes = sizeof(ncclDevComm);
|
||||
static_assert(sizeof(ncclDevComm) <= 16*WARP_SIZE, "ncclDevComm cannot be loaded by a single warp in one insn.");
|
||||
int bytes = sizeof(ncclKernelComm);
|
||||
static_assert(sizeof(ncclKernelComm) <= 16*WARP_SIZE, "ncclKernelComm cannot be loaded by a single warp in one insn.");
|
||||
copyToShmem16(tid, dst, src, bytes);
|
||||
} break;
|
||||
case 1:
|
||||
{ // Get address of channel without incurring indirect load from ncclDevComm::channels
|
||||
{ // Get address of channel without incurring indirect load from ncclKernelComm::channels
|
||||
void* dst = &ncclShmem.channel;
|
||||
void* src = &((ncclDevCommAndChannels*)ncclShmem.args.comm)->channels[ncclShmem.channelId];
|
||||
void* src = &((ncclKernelCommAndChannels*)ncclShmem.args.comm)->channels[ncclShmem.channelId];
|
||||
int bytes = sizeof(ncclDevChannel);
|
||||
static_assert(sizeof(ncclDevChannel) <= 16*WARP_SIZE, "ncclDevChannel cannot be loaded by a single warp in one insn.");
|
||||
copyToShmem16(tid-WARP_SIZE, dst, src, bytes);
|
||||
@@ -641,7 +641,7 @@ __device__ __forceinline__ void ncclKernelMain(struct ncclDevKernelArgs const* a
|
||||
__syncthreads();
|
||||
if(ncclShmem.warpChannelId[localWarpId] >= 0) {
|
||||
void* dst = &ncclShmem.warpChannel[localWarpId];
|
||||
void* src = &((ncclDevCommAndChannels*)ncclShmem.args.comm)->channels[ncclShmem.warpChannelId[localWarpId]];
|
||||
void* src = &((ncclKernelCommAndChannels*)ncclShmem.args.comm)->channels[ncclShmem.warpChannelId[localWarpId]];
|
||||
int bytes = sizeof(ncclDevChannel);
|
||||
static_assert(sizeof(ncclDevChannel) <= 16*WARP_SIZE, "ncclDevChannel cannot be loaded by a single warp in one insn.");
|
||||
// assert((tid-localWarpId*WARP_SIZE) >= 0 && (tid-localWarpId*WARP_SIZE) < WARP_SIZE);
|
||||
|
||||
@@ -3,9 +3,10 @@ import os
|
||||
import sys
|
||||
import subprocess
|
||||
from dataclasses import dataclass
|
||||
import shutil
|
||||
|
||||
# Order of colls, redops, tys, protos, algos must match src/include/device.h
|
||||
all_colls = ["Broadcast", "Reduce", "AllGather", "ReduceScatter", "AllReduce", "SendRecv", "", "", "AllToAllPivot", "AllToAllGda"]
|
||||
all_colls = ["Broadcast", "Reduce", "AllGather", "ReduceScatter", "AllReduce", "SendRecv", "", "", "AlltoAllPivot", "AllToAllGda"]
|
||||
all_redops = ["Sum","Prod","MinMax","PreMulSum","SumPostDiv"]
|
||||
all_tys = ["i8","u8","i32","u32","i64","u64","f16","f32","f64","bf16","f8e4m3","f8e5m2"]
|
||||
all_protos = ["LL","LL128","SIMPLE"]
|
||||
@@ -24,8 +25,11 @@ gensrc = sys.argv[1]
|
||||
|
||||
if os.path.exists(gensrc):
|
||||
for name in os.listdir(gensrc):
|
||||
os.remove(os.path.join(gensrc, name))
|
||||
#os.truncate(os.path.join(gensrc, name), 0)
|
||||
path = os.path.join(gensrc, name)
|
||||
if os.path.isfile(path):
|
||||
os.remove(path)
|
||||
elif os.path.isdir(path):
|
||||
shutil.rmtree(path)
|
||||
else:
|
||||
os.makedirs(gensrc)
|
||||
|
||||
@@ -64,7 +68,7 @@ else:
|
||||
# make ONLY_FUNCS="AllReduce RING SIMPLE * *|ReduceScatter RING LL * f32"
|
||||
# --- or ---
|
||||
# make ONLY_FUNCS="AllReduce RING SIMPLE|ReduceScatter RING LL * f32"
|
||||
# make ONLY_FUNCS="AllReduce RING/TREE LL/SIMPLE Sum/MinMax i8/u8/f16/f32/f64/bf16/f8e4m3/f8e5m2|AllGather RING LL/SIMPLE Sum i8|AllToAllPivot RING SIMPLE Sum i8|Broadcast RING LL/SIMPLE Sum i8|Reduce RING LL/SIMPLE Sum/MinMax i8/u8/f16/f32/f64/bf16/f8e4m3/f8e5m2|ReduceScatter RING LL/SIMPLE Sum/MinMax i8/u8/f16/f32/f64/bf16/f8e4m3/f8e5m2|SendRecv RING SIMPLE Sum i8"
|
||||
# make ONLY_FUNCS="AllReduce RING/TREE LL/SIMPLE Sum/MinMax i8/u8/f16/f32/f64/bf16/f8e4m3/f8e5m2|AllGather RING LL/SIMPLE Sum i8|AlltoAllPivot RING SIMPLE Sum i8|Broadcast RING LL/SIMPLE Sum i8|Reduce RING LL/SIMPLE Sum/MinMax i8/u8/f16/f32/f64/bf16/f8e4m3/f8e5m2|ReduceScatter RING LL/SIMPLE Sum/MinMax i8/u8/f16/f32/f64/bf16/f8e4m3/f8e5m2|SendRecv RING SIMPLE Sum i8"
|
||||
|
||||
# Paste all non-None arguments together with `sep`.
|
||||
def paste(sep, *args):
|
||||
@@ -79,14 +83,14 @@ func_pattern = sys.argv[6:7]
|
||||
if func_pattern and func_pattern[0]:
|
||||
func_pattern = func_pattern[0]
|
||||
else:
|
||||
func_pattern = "AllGather|AllReduce|AllToAllPivot|AllToAllGda|Broadcast|Reduce|ReduceScatter|SendRecv"
|
||||
func_pattern = "AllGather|AllReduce|AlltoAllPivot|AllToAllGda|Broadcast|Reduce|ReduceScatter|SendRecv"
|
||||
|
||||
################################################################################
|
||||
|
||||
algos_of_coll = {
|
||||
"AllGather": ["RING", "PAT"],
|
||||
"AllReduce": ["RING", "TREE"],
|
||||
"AllToAllPivot": ["RING"],
|
||||
"AlltoAllPivot": ["RING"],
|
||||
"AllToAllGda": ["RING"],
|
||||
"Broadcast": ["RING"],
|
||||
"Reduce": ["RING"],
|
||||
@@ -97,7 +101,7 @@ algos_of_coll = {
|
||||
protos_of_coll = {
|
||||
"AllGather": all_protos,
|
||||
"AllReduce": all_protos,
|
||||
"AllToAllPivot": ["SIMPLE"],
|
||||
"AlltoAllPivot": ["SIMPLE"],
|
||||
"AllToAllGda": ["SIMPLE"],
|
||||
"Broadcast": all_protos,
|
||||
"Reduce": all_protos,
|
||||
@@ -108,7 +112,7 @@ protos_of_coll = {
|
||||
redops_of_coll = {
|
||||
"AllGather": ["Sum"],
|
||||
"AllReduce": all_redops,
|
||||
"AllToAllPivot": ["Sum"],
|
||||
"AlltoAllPivot": ["Sum"],
|
||||
"AllToAllGda": ["Sum"],
|
||||
"Broadcast": ["Sum"],
|
||||
"Reduce": all_redops,
|
||||
@@ -119,7 +123,7 @@ redops_of_coll = {
|
||||
tys_of_coll = {
|
||||
"AllGather": ["i8"],
|
||||
"AllReduce": all_tys,
|
||||
"AllToAllPivot": ["i8"],
|
||||
"AlltoAllPivot": ["i8"],
|
||||
"AllToAllGda": ["i8"],
|
||||
"Broadcast": ["i8"],
|
||||
"Reduce": all_tys,
|
||||
@@ -130,7 +134,7 @@ tys_of_coll = {
|
||||
acc_of_coll = {
|
||||
"AllGather": ["0"],
|
||||
"AllReduce": all_accs,
|
||||
"AllToAllPivot": ["0"],
|
||||
"AlltoAllPivot": ["0"],
|
||||
"AllToAllGda": ["0"],
|
||||
"Broadcast": ["0"],
|
||||
"Reduce": ["0"],
|
||||
@@ -141,7 +145,7 @@ acc_of_coll = {
|
||||
pipelines_of_coll = {
|
||||
"AllGather": ["0"],
|
||||
"AllReduce": all_pipelines,
|
||||
"AllToAllPivot": ["0"],
|
||||
"AlltoAllPivot": ["0"],
|
||||
"AllToAllGda": ["0"],
|
||||
"Broadcast": ["0"],
|
||||
"Reduce": all_pipelines,
|
||||
@@ -153,7 +157,7 @@ pipelined_types = ["bf16"]
|
||||
coll_camel_to_lower = {
|
||||
"AllGather": "all_gather",
|
||||
"AllReduce": "all_reduce",
|
||||
"AllToAllPivot": "alltoall_pivot",
|
||||
"AlltoAllPivot": "alltoall_pivot",
|
||||
"AllToAllGda": "alltoall_gda",
|
||||
"Broadcast": "broadcast",
|
||||
"Reduce": "reduce",
|
||||
@@ -510,7 +514,7 @@ with open(os.path.join(gensrc, "host_table.cpp"), "w") as f:
|
||||
)
|
||||
if fn.coll == "Broadcast":
|
||||
key = ((coll_idx & 0x3F) | ((proto_idx & 0x3F) << 8))
|
||||
if fn.coll in ["SendRecv", "AllToAllPivot", "AllToAllGda"]:
|
||||
if fn.coll in ["SendRecv", "AlltoAllPivot", "AllToAllGda"]:
|
||||
key = ((coll_idx & 0x3F))
|
||||
|
||||
out(f' {{{key}, {fn_id}}}, {comment}\n')
|
||||
|
||||
@@ -93,7 +93,7 @@ __device__ __forceinline__ static void mscclReduce(int c, int numReductions, int
|
||||
|
||||
template<typename T, typename RedOp, typename Proto, bool fullOps>
|
||||
__device__ __forceinline__ void mscclRunInterpreter(
|
||||
struct ncclDevComm* comm, struct mscclAlgo* algo, struct mscclWork* work) {
|
||||
struct ncclKernelComm* comm, struct mscclAlgo* algo, struct mscclWork* work) {
|
||||
const int tid = threadIdx.x;
|
||||
const int bid = blockIdx.x;
|
||||
const int nthreads = MSCCL_MAX_NTHREADS;
|
||||
@@ -120,12 +120,12 @@ __device__ __forceinline__ void mscclRunInterpreter(
|
||||
case 0:
|
||||
dst = &ncclShmem.comm;
|
||||
src = comm;
|
||||
bytes = sizeof(ncclDevComm);
|
||||
bytes = sizeof(ncclKernelComm);
|
||||
break;
|
||||
case 1:
|
||||
// Get address of channel without incurring indirect load from ncclDevComm::channels
|
||||
// Get address of channel without incurring indirect load from ncclKernelComm::channels
|
||||
dst = &ncclShmem.channel;
|
||||
src = &((ncclDevCommAndChannels*)comm)->channels[channelId];
|
||||
src = &((ncclKernelCommAndChannels*)comm)->channels[channelId];
|
||||
bytes = sizeof(ncclDevChannel);
|
||||
break;
|
||||
case 2:
|
||||
@@ -372,13 +372,13 @@ __device__ __forceinline__ void mscclRunInterpreter(
|
||||
}
|
||||
|
||||
#define MSCCL_IMPL_KERNEL_ENTRY_FUNC_DEVREDOP_TYPE(devredop, type, fullOps) \
|
||||
__global__ void MSCCL_KERNEL_ENTRY_NAME(devredop, type, LL, fullOps)(struct ncclDevComm* comm, struct mscclAlgo* algo, struct mscclWork* work) { \
|
||||
__global__ void MSCCL_KERNEL_ENTRY_NAME(devredop, type, LL, fullOps)(struct ncclKernelComm* comm, struct mscclAlgo* algo, struct mscclWork* work) { \
|
||||
mscclRunInterpreter<type, Func##devredop<type>, ProtoLL, fullOps>(comm, algo, work); \
|
||||
} \
|
||||
__global__ void MSCCL_KERNEL_ENTRY_NAME(devredop, type, LL128, fullOps)(struct ncclDevComm* comm, struct mscclAlgo* algo, struct mscclWork* work) { \
|
||||
__global__ void MSCCL_KERNEL_ENTRY_NAME(devredop, type, LL128, fullOps)(struct ncclKernelComm* comm, struct mscclAlgo* algo, struct mscclWork* work) { \
|
||||
mscclRunInterpreter<type, Func##devredop<type>, ProtoLL128, fullOps>(comm, algo, work); \
|
||||
} \
|
||||
__global__ void MSCCL_KERNEL_ENTRY_NAME(devredop, type, Simple, fullOps)(struct ncclDevComm* comm, struct mscclAlgo* algo, struct mscclWork* work) { \
|
||||
__global__ void MSCCL_KERNEL_ENTRY_NAME(devredop, type, Simple, fullOps)(struct ncclKernelComm* comm, struct mscclAlgo* algo, struct mscclWork* work) { \
|
||||
mscclRunInterpreter<type, Func##devredop<type>, ProtoSimple<MSCCL_CHUNKSTEPS/MSCCL_SLICESTEPS, MSCCL_SLICESTEPS, 0, 2>, fullOps>(comm, algo, work); \
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +1,36 @@
|
||||
// Modification Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "symmetric.h"
|
||||
#include "sym_kernels.h"
|
||||
#include "symmetric/kernel.h"
|
||||
#include "symmetric/primitives.h"
|
||||
|
||||
template<int BytePerPack, int UnrollPacks, int UnrollPeers>
|
||||
static __device__ void bcastDeep(
|
||||
ncclSymPrims& prim, int tn, int t, bool waitNeeded,
|
||||
char* inputHere, char* outputRank0, bool inPlace, int nIters
|
||||
ncclSymkArgsHandler const& handler, int tn, int t,
|
||||
bool waitNeeded, ncclLsaBarrierSession<ncclCoopCta>& bar,
|
||||
ncclSymPtr<char> input, ncclSymPtr<char> output, bool inPlace, int nIters
|
||||
) {
|
||||
using Pack = BytePack<BytePerPack>;
|
||||
int wn = tn/WARP_SIZE;
|
||||
int w = t/WARP_SIZE;
|
||||
int lane = t%WARP_SIZE;
|
||||
int const& rank = prim.rank;
|
||||
int const& nRanks = prim.nRanks;
|
||||
uint32_t const& stride4G = prim.stride4G;
|
||||
Pack* inpHere = (Pack*)inputHere + intptr_t(w)*UnrollPacks*WARP_SIZE + lane;
|
||||
Pack* outRank0 = (Pack*)outputRank0 + intptr_t(w)*UnrollPacks*WARP_SIZE + lane;
|
||||
int const& rank = handler.comm.rank;
|
||||
int const& nRanks = handler.comm.nRanks;
|
||||
|
||||
Pack* inpPacks = (Pack*)input.localPtr() + intptr_t(w)*UnrollPacks*WARP_SIZE + lane;
|
||||
ncclSymPtr<Pack> outPacks = (ncclSymPtr<Pack>)output + intptr_t(w)*UnrollPacks*WARP_SIZE + lane;
|
||||
Pack tmp[UnrollPacks];
|
||||
|
||||
nIters -= w;
|
||||
if (0 < nIters) {
|
||||
#pragma unroll
|
||||
for (int u=0; u < UnrollPacks; u++) {
|
||||
tmp[u] = inpHere[u*WARP_SIZE];
|
||||
tmp[u] = inpPacks[u*WARP_SIZE];
|
||||
}
|
||||
}
|
||||
|
||||
if (waitNeeded) prim.barrierWait(ncclCoopCta(), /*acquire=*/false);
|
||||
if (waitNeeded) bar.wait(ncclCoopCta(), cuda::memory_order_relaxed);
|
||||
|
||||
if (0 < nIters) {
|
||||
while (true) {
|
||||
@@ -47,21 +48,21 @@ static __device__ void bcastDeep(
|
||||
if (partial && dr == nRanks) break;
|
||||
#pragma unroll UnrollPacks
|
||||
for (int u=0; u < UnrollPacks; u++) {
|
||||
add4G(outRank0, r*stride4G)[u*WARP_SIZE] = tmp[u];
|
||||
outPacks.lsaPtr(r)[u*WARP_SIZE] = tmp[u];
|
||||
}
|
||||
if (++r == nRanks) r = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
inpHere += intptr_t(wn)*UnrollPacks*WARP_SIZE;
|
||||
outRank0 += intptr_t(wn)*UnrollPacks*WARP_SIZE;
|
||||
inpPacks += intptr_t(wn)*UnrollPacks*WARP_SIZE;
|
||||
outPacks += intptr_t(wn)*UnrollPacks*WARP_SIZE;
|
||||
nIters -= wn;
|
||||
if (nIters <= 0) break;
|
||||
|
||||
// Load data for next iteration.
|
||||
#pragma unroll
|
||||
for (int u=0; u < UnrollPacks; u++) {
|
||||
tmp[u] = inpHere[u*WARP_SIZE];
|
||||
tmp[u] = inpPacks[u*WARP_SIZE];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,18 +70,17 @@ static __device__ void bcastDeep(
|
||||
|
||||
template<int UnrollPeers, typename T>
|
||||
static __device__ void bcastEnds(
|
||||
ncclSymPrims& prim, int tn, int t,
|
||||
T* inputHere, T* outputRank0, bool inPlace, size_t nElts, uint32_t nPreElts, size_t nSufElts
|
||||
ncclSymkArgsHandler const& handler, int tn, int t,
|
||||
ncclSymPtr<T> input, ncclSymPtr<T> output, bool inPlace, size_t nElts, uint32_t nPreElts, size_t nSufElts
|
||||
) {
|
||||
int const& rank = prim.rank;
|
||||
int const& nRanks = prim.nRanks;
|
||||
uint32_t const& stride4G = prim.stride4G;
|
||||
BytePack<sizeof(T)>* inpHere = (BytePack<sizeof(T)>*)inputHere;
|
||||
BytePack<sizeof(T)>* outRank0 = (BytePack<sizeof(T)>*)outputRank0;
|
||||
int const& rank = handler.comm.rank;
|
||||
int const& nRanks = handler.comm.nRanks;
|
||||
BytePack<sizeof(T)>* inpPacks = (BytePack<sizeof(T)>*)input.localPtr();
|
||||
ncclSymPtr<BytePack<sizeof(T)>> outPacks = (ncclSymPtr<BytePack<sizeof(T)>>)output;
|
||||
#pragma unroll 1
|
||||
for (size_t i = t; i < nPreElts+nSufElts; i += tn) {
|
||||
size_t elt = i < nPreElts ? i : nElts-nPreElts-nSufElts+i;
|
||||
BytePack<sizeof(T)> tmp = inpHere[elt];
|
||||
BytePack<sizeof(T)> tmp = inpPacks[elt];
|
||||
int dr = inPlace ? 1 : 0;
|
||||
int r = rank + dr;
|
||||
if (r == nRanks) r = 0;
|
||||
@@ -88,14 +88,14 @@ static __device__ void bcastEnds(
|
||||
for (; dr + UnrollPeers <= nRanks; dr += UnrollPeers) {
|
||||
#pragma unroll UnrollPeers
|
||||
for (int u=0; u < UnrollPeers; u++) {
|
||||
*add4G(outRank0+elt, r*stride4G) = tmp;
|
||||
outPacks.lsaPtr(r)[elt] = tmp;
|
||||
if (++r == nRanks) r = 0;
|
||||
}
|
||||
}
|
||||
#pragma unroll UnrollPeers
|
||||
for (int u=0; u < UnrollPeers; u++) {
|
||||
if (dr+u == nRanks) break;
|
||||
*add4G(outRank0+elt, r*stride4G) = tmp;
|
||||
outPacks.lsaPtr(r)[elt] = tmp;
|
||||
if (++r == nRanks) r = 0;
|
||||
}
|
||||
}
|
||||
@@ -103,95 +103,95 @@ static __device__ void bcastEnds(
|
||||
|
||||
template<typename T>
|
||||
static __device__ void bcast(
|
||||
ncclSymPrims& prim, int tn, int t, bool waitNeeded, T* input, T* output, size_t nElts
|
||||
ncclSymkArgsHandler const& handler, int tn, int t, int nBlocks,
|
||||
bool waitNeeded, ncclLsaBarrierSession<ncclCoopCta>& bar,
|
||||
ncclSymPtr<T> input, ncclSymPtr<T> output, size_t nElts
|
||||
) {
|
||||
bool inPlace = (input == output);
|
||||
// Mpve to rank=0
|
||||
output = prim.peerPtr(0, output);
|
||||
|
||||
uintptr_t inputUptr = reinterpret_cast<uintptr_t>(input);
|
||||
uintptr_t outputUptr = reinterpret_cast<uintptr_t>(output);
|
||||
size_t nBytes = nElts*sizeof(T);
|
||||
uint32_t nBlocks_rcp32 = nccl::utility::idivRcp32_upto64(nBlocks);
|
||||
|
||||
uint32_t nPreBytes = (128u - inputUptr)%128u;
|
||||
uint32_t nPreBytes = (16 - input.offset)%16;
|
||||
nPreBytes = min((size_t)nPreBytes, nBytes);
|
||||
uintptr_t cursor = nPreBytes;
|
||||
|
||||
constexpr int MinWarpPerBlock = 4;
|
||||
|
||||
if ((inputUptr-outputUptr)%16 == 0) {
|
||||
constexpr int BytePerPack = 16, UnrollPacks = 1, UnrollPeers = 1;
|
||||
if ((input.offset - output.offset)%16 == 0) {
|
||||
constexpr int BytePerPack = 16, UnrollPacks = 4, UnrollPeers = 2;
|
||||
constexpr int BytePerChunk = MinWarpPerBlock*UnrollPacks*WARP_SIZE*BytePerPack;
|
||||
uint32_t chunks = (nBytes-cursor)/BytePerChunk;
|
||||
chunks -= imodFast32(chunks, prim.nBlocks, prim.nBlocks_rcp32);
|
||||
chunks -= imodFast32(chunks, nBlocks, nBlocks_rcp32);
|
||||
if (chunks != 0) {
|
||||
uintptr_t cursorAfter = cursor + uintptr_t(chunks)*BytePerChunk;
|
||||
bcastDeep<BytePerPack, UnrollPacks, UnrollPeers>(
|
||||
prim, tn, t, waitNeeded,
|
||||
(char*)input + cursor, (char*)output + cursor, inPlace,
|
||||
chunks*MinWarpPerBlock
|
||||
handler, tn, t, waitNeeded, bar,
|
||||
(ncclSymPtr<char>)input + cursor,
|
||||
(ncclSymPtr<char>)output + cursor,
|
||||
inPlace, chunks*MinWarpPerBlock
|
||||
);
|
||||
cursor = cursorAfter;
|
||||
waitNeeded = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sizeof(T) == 4 || (sizeof(T) < 4 && (inputUptr-outputUptr)%4 == 0)) {
|
||||
constexpr int BytePerPack = 4, UnrollPacks = 1, UnrollPeers = 1;
|
||||
constexpr int BytePerChunk = MinWarpPerBlock*UnrollPacks*WARP_SIZE*BytePerPack;
|
||||
uint32_t chunks = (nBytes-cursor)/BytePerChunk;
|
||||
chunks -= imodFast32(chunks, prim.nBlocks, prim.nBlocks_rcp32);
|
||||
if (sizeof(T) == 4 || (sizeof(T) < 4 && (input.offset - output.offset)%4 == 0)) {
|
||||
chunks -= imodFast32(chunks, nBlocks, nBlocks_rcp32);
|
||||
if (chunks != 0) {
|
||||
uintptr_t cursorAfter = cursor + uintptr_t(chunks)*BytePerChunk;
|
||||
bcastDeep<(sizeof(T) <= BytePerPack ? BytePerPack : 0), UnrollPacks, UnrollPeers>(
|
||||
prim, tn, t, waitNeeded,
|
||||
(char*)input + cursor, (char*)output + cursor, inPlace,
|
||||
chunks*MinWarpPerBlock
|
||||
handler, tn, t, waitNeeded, bar,
|
||||
(ncclSymPtr<char>)input + cursor,
|
||||
(ncclSymPtr<char>)output + cursor,
|
||||
inPlace, chunks*MinWarpPerBlock
|
||||
);
|
||||
cursor = cursorAfter;
|
||||
waitNeeded = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (waitNeeded) prim.barrierWait(ncclCoopCta(), /*acquire=*/false);
|
||||
if (waitNeeded) bar.wait(ncclCoopCta(), cuda::memory_order_relaxed);
|
||||
|
||||
constexpr int UnrollPeers = 8;
|
||||
size_t nSufElts = (nBytes-cursor)/sizeof(T);
|
||||
bcastEnds<UnrollPeers>(prim, tn, t, input, output, inPlace, nElts, nPreBytes/sizeof(T), nSufElts);
|
||||
bcastEnds<UnrollPeers>(handler, tn, t, input, output, inPlace, nElts, nPreBytes/sizeof(T), nSufElts);
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void ncclSymRun_AllGather_ST(ncclSymDevArgs const* args) {
|
||||
ncclSymPrims prim(args->comm, ncclSymPrims_UseBarrier);
|
||||
int const& rank = prim.rank;
|
||||
__device__ __forceinline__ void ncclSymkRun_AllGather_ST(ncclSymkDevWorkArgs const* args) {
|
||||
ncclSymkArgsHandler handler{args};
|
||||
ncclLsaBarrierSession<ncclCoopCta> bar{
|
||||
ncclCoopCta(), handler.comm, ncclTeamTagLsa(), blockIdx.x
|
||||
};
|
||||
int const& rank = handler.comm.rank;
|
||||
|
||||
// Threads numbered over rank.
|
||||
int bt = flattenIx(threadIdx.x%WARP_SIZE, WARP_SIZE,
|
||||
prim.block, prim.nBlocks,
|
||||
threadIdx.x/WARP_SIZE, blockDim.x/WARP_SIZE);
|
||||
int btn = prim.nBlocks*blockDim.x;
|
||||
bar.arrive(ncclCoopCta(), cuda::memory_order_relaxed);
|
||||
|
||||
prim.barrierArrive(ncclCoopCta(), /*release=*/false);
|
||||
//prim.barrierWait(ncclCoopCta(), /*acquire=*/false);
|
||||
bool waitNeeded = true;
|
||||
handler.forEachWork<char>(
|
||||
[&]__device__(int block, int nBlocks, size_t nElts, size_t nAllElts,
|
||||
ncclSymPtr<char> input, ncclSymPtr<char> output) {
|
||||
// Threads numbered over rank.
|
||||
int bt = flattenIx(threadIdx.x%WARP_SIZE, WARP_SIZE,
|
||||
block, nBlocks,
|
||||
threadIdx.x/WARP_SIZE, blockDim.x/WARP_SIZE);
|
||||
int btn = nBlocks*blockDim.x;
|
||||
|
||||
bcast(prim, btn, bt, /*waitNeeded=*/true, (char*)args->input, (char*)args->output + rank*args->nElts, args->nElts);
|
||||
bcast(handler, btn, bt, nBlocks, waitNeeded, bar, input, output + rank*nAllElts, nElts);
|
||||
|
||||
prim.barrierArrive(ncclCoopCta(), /*release=*/true);
|
||||
prim.barrierWait(ncclCoopCta(), /*acquire=*/false);
|
||||
waitNeeded = false;
|
||||
}
|
||||
);
|
||||
|
||||
bar.sync(ncclCoopCta(), cuda::memory_order_release);
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
static __device__ void bcastMultimem(
|
||||
ncclSymPrims& prim, int tn, int t, T* input, T* output, size_t nElts
|
||||
ncclSymkArgsHandler& handler, int tn, int t, ncclSymPtr<T> input, ncclSymPtr<T> output, size_t nElts
|
||||
) {
|
||||
// Move output to multimem
|
||||
output = prim.multimemPtr(output);
|
||||
|
||||
uintptr_t inputUptr = reinterpret_cast<uintptr_t>(input);
|
||||
uintptr_t outputUptr = reinterpret_cast<uintptr_t>(output);
|
||||
size_t nBytes = nElts*sizeof(T);
|
||||
|
||||
uint32_t nPreBytes = (16-inputUptr)%16;
|
||||
uintptr_t inputUptr = reinterpret_cast<uintptr_t>(input.localPtr());
|
||||
uintptr_t outputUptr = reinterpret_cast<uintptr_t>(output.multimemPtr(handler.comm.lsaMultimem));
|
||||
uint32_t nPreBytes = (16 - input.offset)%16;
|
||||
nPreBytes = min((size_t)nPreBytes, nBytes);
|
||||
uintptr_t nSufBytes;
|
||||
|
||||
@@ -230,51 +230,52 @@ static __device__ void bcastMultimem(
|
||||
uintptr_t cursor = i < nPreBytes ? i : nBytes-nSufBytes+(i-nPreBytes);
|
||||
BytePack<sizeof(T)> val = *reinterpret_cast<BytePack<sizeof(T)>*>(inputUptr + cursor);
|
||||
multimem_st_global(outputUptr + cursor, val);
|
||||
cursor += tn*sizeof(T);
|
||||
}
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void ncclSymRun_AllGather_STMC(ncclSymDevArgs const* args) {
|
||||
ncclSymPrims prim(args->comm, ncclSymPrims_UseBarrier|ncclSymPrims_UseMultimem);
|
||||
int const& rank = prim.rank;
|
||||
__device__ __forceinline__ void ncclSymkRun_AllGather_STMC(ncclSymkDevWorkArgs const* args) {
|
||||
ncclSymkArgsHandler handler{args};
|
||||
ncclLsaBarrierSession<ncclCoopCta> bar(
|
||||
ncclCoopCta(), handler.comm, ncclTeamTagLsa(), blockIdx.x, /*multimem=*/true
|
||||
);
|
||||
int const& rank = handler.comm.rank;
|
||||
|
||||
char* input = args->input;
|
||||
char* output = args->output;
|
||||
size_t bytes = args->nElts;
|
||||
// Round robin memory to blocks.
|
||||
int t = flattenIx(threadIdx.x%WARP_SIZE, WARP_SIZE,
|
||||
prim.block, prim.nBlocks,
|
||||
threadIdx.x/WARP_SIZE, blockDim.x/WARP_SIZE);
|
||||
int tn = prim.nBlocks*blockDim.x;
|
||||
bar.sync(ncclCoopCta(), cuda::memory_order_relaxed);
|
||||
|
||||
prim.barrierArrive(ncclCoopCta(), /*release=*/false);
|
||||
prim.barrierWait(ncclCoopCta(), /*acquire=*/false);
|
||||
handler.forEachWork<char>(
|
||||
[&]__device__(int block, int nBlocks, size_t nElts, size_t nAllElts,
|
||||
ncclSymPtr<char> input, ncclSymPtr<char> output) {
|
||||
// Round robin memory to blocks.
|
||||
int t = flattenIx(threadIdx.x%WARP_SIZE, WARP_SIZE,
|
||||
block, nBlocks,
|
||||
threadIdx.x/WARP_SIZE, blockDim.x/WARP_SIZE);
|
||||
int tn = nBlocks*blockDim.x;
|
||||
|
||||
bcastMultimem(prim, tn, t, input, output + rank*bytes, bytes);
|
||||
bcastMultimem(handler, tn, t, input, output + rank*nAllElts, nElts);
|
||||
}
|
||||
);
|
||||
|
||||
prim.barrierArrive(ncclCoopCta(), /*release=*/true);
|
||||
prim.barrierWait(ncclCoopCta(), /*acquire=*/false);
|
||||
bar.sync(ncclCoopCta(), cuda::memory_order_release);
|
||||
}
|
||||
|
||||
template<typename EltType>
|
||||
static __device__ void allgather_LL_body(
|
||||
ncclSymPrims &prim, EltType* input, EltType* output, int nElts, int nPacks, int nStrideElts
|
||||
ncclSymkArgsHandler& handler, ncclLLA2ASession<ncclCoopCta>& lla2a,
|
||||
EltType* input, EltType* output, int nElts, int nPacks, int nStrideElts
|
||||
) {
|
||||
using Pack = BytePack<8>;
|
||||
constexpr int EltPerPack = 8/sizeof(EltType);
|
||||
|
||||
ncclCoopCta cta;
|
||||
int rank = prim.rank;
|
||||
int nRanks = prim.nRanks;
|
||||
constexpr int tn = ncclSymMaxThreads;
|
||||
int const& rank = handler.comm.rank;
|
||||
int const& nRanks = handler.comm.nRanks;
|
||||
int t = threadIdx.x;
|
||||
constexpr int tn = ncclSymkMaxThreads;
|
||||
|
||||
#pragma unroll 1
|
||||
while (0 < nElts) {
|
||||
int nIterPacks = min(nPacks, tn);
|
||||
if (t < nIterPacks) {
|
||||
Pack x = loadPack<Pack>(input, t*EltPerPack, nElts);
|
||||
prim.bcastLL(/*slot=*/nIterPacks*rank + t, x);
|
||||
lla2a.bcast(/*slot=*/nIterPacks*rank + t, x);
|
||||
}
|
||||
|
||||
int tn_div_nPacks = tn/nIterPacks;
|
||||
@@ -287,7 +288,7 @@ static __device__ void allgather_LL_body(
|
||||
#pragma unroll 1
|
||||
for (int i = t; i < (nRanks*nIterPacks & -(Unroll*tn)); i += Unroll*tn) {
|
||||
Pack got[Unroll];
|
||||
prim.template recvLL<Unroll, Unroll>(i, Unroll, tn, /*&*/got);
|
||||
lla2a.template recvUnrolled<Unroll, Unroll>(i, Unroll, tn, /*&*/got);
|
||||
#pragma unroll
|
||||
for (int u=0; u < Unroll; u++) {
|
||||
storePack<Pack>(output + peer*nStrideElts, pack*EltPerPack, nElts, got[u]);
|
||||
@@ -302,7 +303,7 @@ static __device__ void allgather_LL_body(
|
||||
if (i + n*tn < nRanks*nIterPacks) n += 1;
|
||||
if (n != 0) {
|
||||
Pack got[Unroll];
|
||||
prim.template recvLL<1, Unroll>(i, n, tn, /*&*/got);
|
||||
lla2a.template recvUnrolled<1, Unroll>(i, n, tn, /*&*/got);
|
||||
#pragma unroll
|
||||
for (int u=0; u < Unroll; u++) {
|
||||
if (u != 0 && u == n) break;
|
||||
@@ -316,7 +317,7 @@ static __device__ void allgather_LL_body(
|
||||
// The non-unrolled but "obviously correct" implementation for reference.
|
||||
#pragma unroll 1
|
||||
for (int i = t; i < nRanks*nIterPacks; i += tn) {
|
||||
Pack got = prim.template recvLL<Pack>(i);
|
||||
Pack got = lla2a.template recv<Pack>(i);
|
||||
storePack(output + peer*nStrideElts, pack*EltPerPack, nElts, got);
|
||||
peer += tn_div_nPacks;
|
||||
pack += tn_mod_nPacks;
|
||||
@@ -324,7 +325,7 @@ static __device__ void allgather_LL_body(
|
||||
}
|
||||
#endif
|
||||
|
||||
prim.endLL(cta);
|
||||
lla2a.endEpoch(ncclCoopCta());
|
||||
|
||||
input += tn*EltPerPack;
|
||||
output += tn*EltPerPack;
|
||||
@@ -333,38 +334,41 @@ static __device__ void allgather_LL_body(
|
||||
}
|
||||
}
|
||||
|
||||
static __device__ void ncclSymRun_AllGather_LL_impl(ncclSymDevArgs const* args, bool multimem) {
|
||||
ncclSymPrims prim(args->comm, ncclSymPrims_UseLL | multimem*ncclSymPrims_UseMultimem);
|
||||
static __device__ void ncclSymkRun_AllGather_LL_impl(ncclSymkDevWorkArgs const* args, bool multimem) {
|
||||
ncclSymkArgsHandler handler{args};
|
||||
ncclLLA2ASession<ncclCoopCta> lla2a(
|
||||
ncclCoopCta(), handler.comm, ncclTeamLsa(handler.comm), handler.lsaLLA2A, blockIdx.x, /*maxElts=*/ncclSymkMaxThreads, multimem, handler.comm.lsaMultimem
|
||||
);
|
||||
|
||||
using Pack = BytePack<8>;
|
||||
constexpr int BytePerPack = 8;
|
||||
int nElts = args->nElts;
|
||||
int nPacks = divUp(nElts, BytePerPack);
|
||||
|
||||
uint32_t nPackPerBlock, nPackModBlock;
|
||||
idivmodFast32(&nPackPerBlock, &nPackModBlock, nPacks, prim.nBlocks, prim.nBlocks_rcp32);
|
||||
int blockPackBegin = prim.block*nPackPerBlock + minval<int>(prim.block, nPackModBlock);
|
||||
int blockPackEnd = blockPackBegin + nPackPerBlock + (prim.block < nPackModBlock ? 1 : 0);
|
||||
int nBlockPacks = blockPackEnd - blockPackBegin;
|
||||
int nBlockElts = nElts - blockPackBegin*BytePerPack;
|
||||
nBlockElts = min(nBlockElts, nBlockPacks*BytePerPack);
|
||||
char* blockInput = args->input + blockPackBegin*BytePerPack;
|
||||
char* blockOutput = args->output + blockPackBegin*BytePerPack;
|
||||
handler.singleWork<char>(
|
||||
[&]__device__(int nElts, int nAllElts,
|
||||
ncclSymPtr<char> input, ncclSymPtr<char> output) {
|
||||
int nPacks = divUp(nElts, BytePerPack);
|
||||
|
||||
uint32_t lowBits = args->nElts;
|
||||
lowBits |= (uint32_t)reinterpret_cast<uintptr_t>(args->input);
|
||||
lowBits |= (uint32_t)reinterpret_cast<uintptr_t>(args->output);
|
||||
if (__builtin_expect(lowBits%8 == 0, true)) {
|
||||
// NOTE: Specializing for 8-byte alignment in one case help at size=65K: 8.9us vs 5.6us
|
||||
allgather_LL_body(prim, (BytePack<8>*)blockInput, (BytePack<8>*)blockOutput, nBlockElts/8, nBlockPacks, nElts/8);
|
||||
} else {
|
||||
allgather_LL_body(prim, blockInput, blockOutput, nBlockElts, nBlockPacks, nElts);
|
||||
}
|
||||
char* blockInput = input.localPtr();
|
||||
char* blockOutput = output.localPtr();
|
||||
|
||||
uint32_t lowBits = nElts;
|
||||
lowBits |= (uintptr_t)blockInput;
|
||||
lowBits |= (uintptr_t)blockOutput;
|
||||
if (__builtin_expect(lowBits%8 == 0, true)) {
|
||||
// NOTE: Specializing for 8-byte alignment in one case help at size=65K: 8.9us vs 5.6us
|
||||
allgather_LL_body(handler, lla2a, (BytePack<8>*)blockInput, (BytePack<8>*)blockOutput,
|
||||
nElts/8, nPacks, nAllElts/8);
|
||||
} else {
|
||||
allgather_LL_body(handler, lla2a, blockInput, blockOutput, nElts, nPacks, nAllElts);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void ncclSymRun_AllGather_LL(ncclSymDevArgs const* args) {
|
||||
ncclSymRun_AllGather_LL_impl(args, /*multimem=*/false);
|
||||
__device__ __forceinline__ void ncclSymkRun_AllGather_LL(ncclSymkDevWorkArgs const* args) {
|
||||
ncclSymkRun_AllGather_LL_impl(args, /*multimem=*/false);
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void ncclSymRun_AllGather_LLMC(ncclSymDevArgs const* args) {
|
||||
ncclSymRun_AllGather_LL_impl(args, /*multimem=*/true);
|
||||
__device__ __forceinline__ void ncclSymkRun_AllGather_LLMC(ncclSymkDevWorkArgs const* args) {
|
||||
ncclSymkRun_AllGather_LL_impl(args, /*multimem=*/true);
|
||||
}
|
||||
|
||||
@@ -1,38 +1,41 @@
|
||||
// Modification Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "symmetric.h"
|
||||
#include "sym_kernels.h"
|
||||
#include "nccl_device.h"
|
||||
#include "symmetric/kernel.h"
|
||||
#include "symmetric/primitives.h"
|
||||
|
||||
template<int BytePerPack, int UnrollPacks, int UnrollPeers, typename T, typename Red>
|
||||
static __device__ __forceinline__ void allreduceDeep(
|
||||
ncclSymPrims& prim, int tn, int t, bool waitNeeded,
|
||||
Red red, char* inputRank0, char* outputRank0, int32_t nIters
|
||||
ncclSymkArgsHandler const& handler, int tn, int t,
|
||||
bool waitNeeded, ncclLsaBarrierSession<ncclCoopCta>& bar,
|
||||
Red red, ncclSymPtr<char> input, ncclSymPtr<char> output, int32_t nIters
|
||||
) {
|
||||
using Pack = BytePack<BytePerPack>;
|
||||
using Acc = typename Red::EltType;
|
||||
using AccPack = BytePack<BytePerPack*sizeof(Acc)/sizeof(T)>;
|
||||
|
||||
ncclTeam world = ncclTeamWorld(handler.comm);
|
||||
int wn = tn/WARP_SIZE;
|
||||
int w = t/WARP_SIZE;
|
||||
int lane = t%WARP_SIZE;
|
||||
int const& rank = prim.rank;
|
||||
int const& nRanks = prim.nRanks;
|
||||
uint32_t const& stride4G = prim.stride4G;
|
||||
Pack* inpRank0 = (Pack*)inputRank0 + intptr_t(w)*UnrollPacks*WARP_SIZE + lane;
|
||||
Pack* outRank0 = (Pack*)outputRank0 + intptr_t(w)*UnrollPacks*WARP_SIZE + lane;
|
||||
int const& rank = handler.comm.rank;
|
||||
int const& nRanks = handler.comm.nRanks;
|
||||
|
||||
ncclSymPtr<Pack> inpPacks = (ncclSymPtr<Pack>)input + intptr_t(w)*UnrollPacks*WARP_SIZE + lane;
|
||||
ncclSymPtr<Pack> outPacks = (ncclSymPtr<Pack>)output + intptr_t(w)*UnrollPacks*WARP_SIZE + lane;
|
||||
Pack acc0[UnrollPacks];
|
||||
|
||||
nIters -= w;
|
||||
if (0 < nIters) {
|
||||
#pragma unroll
|
||||
for (int u=0; u < UnrollPacks; u++) {
|
||||
acc0[u] = add4G(inpRank0, rank*stride4G)[u*WARP_SIZE];
|
||||
acc0[u] = inpPacks.peerPtr(world, rank)[u*WARP_SIZE];
|
||||
}
|
||||
}
|
||||
|
||||
if (waitNeeded) prim.barrierWait(ncclCoopCta(), /*acquire=*/false);
|
||||
if (waitNeeded) bar.wait(ncclCoopCta(), cuda::memory_order_relaxed);
|
||||
|
||||
if (0 < nIters) {
|
||||
while (true) {
|
||||
@@ -42,7 +45,7 @@ static __device__ __forceinline__ void allreduceDeep(
|
||||
{ Pack tmp1[UnrollPacks];
|
||||
#pragma unroll
|
||||
for (int u=0; u < UnrollPacks; u++) {
|
||||
tmp1[u] = add4G(inpRank0, r*stride4G)[u*WARP_SIZE];
|
||||
tmp1[u] = inpPacks.peerPtr(world, r)[u*WARP_SIZE];
|
||||
}
|
||||
#pragma unroll
|
||||
for (int u=0; u < UnrollPacks; u++) {
|
||||
@@ -67,7 +70,7 @@ static __device__ __forceinline__ void allreduceDeep(
|
||||
if (partial && ur!=0 && dr+ur == nRanks) break;
|
||||
#pragma unroll UnrollPacks
|
||||
for (int u=0; u < UnrollPacks; u++) {
|
||||
tmp1[ur][u] = add4G(inpRank0, r*stride4G)[u*WARP_SIZE];
|
||||
tmp1[ur][u] = inpPacks.peerPtr(world, r)[u*WARP_SIZE];
|
||||
}
|
||||
if (++r == nRanks) r = 0;
|
||||
}
|
||||
@@ -98,22 +101,22 @@ static __device__ __forceinline__ void allreduceDeep(
|
||||
if (partial && dr == nRanks) break;
|
||||
#pragma unroll UnrollPacks
|
||||
for (int u=0; u < UnrollPacks; u++) {
|
||||
add4G(outRank0, r*stride4G)[u*WARP_SIZE] = acc0[u];
|
||||
outPacks.peerPtr(world, r)[u*WARP_SIZE] = acc0[u];
|
||||
}
|
||||
if (++r == nRanks) r = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inpRank0 += intptr_t(wn)*UnrollPacks*WARP_SIZE;
|
||||
outRank0 += intptr_t(wn)*UnrollPacks*WARP_SIZE;
|
||||
inpPacks += intptr_t(wn)*UnrollPacks*WARP_SIZE;
|
||||
outPacks += intptr_t(wn)*UnrollPacks*WARP_SIZE;
|
||||
nIters -= wn;
|
||||
if (nIters <= 0) break;
|
||||
|
||||
// Load data for next iteration.
|
||||
#pragma unroll
|
||||
for (int u=0; u < UnrollPacks; u++) {
|
||||
acc0[u] = add4G(inpRank0, rank*stride4G)[u*WARP_SIZE];
|
||||
acc0[u] = inpPacks.peerPtr(world, rank)[u*WARP_SIZE];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,21 +124,23 @@ static __device__ __forceinline__ void allreduceDeep(
|
||||
|
||||
template<int UnrollPeers, typename Red, typename T>
|
||||
static __device__ __forceinline__ void allreduceEnds(
|
||||
ncclSymPrims& prim, int tn, int t, Red red,
|
||||
T* inputRank0, T* outputRank0, size_t nElts, uint32_t nPreElts, size_t nSufElts
|
||||
ncclSymkArgsHandler const& handler, int tn, int t, Red red,
|
||||
ncclSymPtr<T> input, ncclSymPtr<T> output,
|
||||
size_t nElts, uint32_t nPreElts, size_t nSufElts
|
||||
) {
|
||||
using Acc = typename Red::EltType;
|
||||
|
||||
int const& rank = prim.rank;
|
||||
int const& nRanks = prim.nRanks;
|
||||
uint32_t const& stride4G = prim.stride4G;
|
||||
BytePack<sizeof(T)>* inpRank0 = (BytePack<sizeof(T)>*)inputRank0;
|
||||
BytePack<sizeof(T)>* outRank0 = (BytePack<sizeof(T)>*)outputRank0;
|
||||
ncclTeam world = ncclTeamWorld(handler.comm);
|
||||
int const& rank = handler.comm.rank;
|
||||
int const& nRanks = handler.comm.nRanks;
|
||||
|
||||
ncclSymPtr<BytePack<sizeof(T)>> inpPacks = (ncclSymPtr<BytePack<sizeof(T)>>)input;
|
||||
ncclSymPtr<BytePack<sizeof(T)>> outPacks = (ncclSymPtr<BytePack<sizeof(T)>>)output;
|
||||
|
||||
#pragma unroll 1
|
||||
for (size_t i = t; i < nPreElts+nSufElts; i += tn) {
|
||||
size_t elt = i < nPreElts ? i : nElts-nSufElts-nPreElts+i;
|
||||
BytePack<sizeof(T)> acc0 = *add4G(inpRank0+elt, rank*stride4G);
|
||||
BytePack<sizeof(T)> acc0 = inpPacks.peerPtr(world, rank)[elt];
|
||||
BytePack<sizeof(Acc)> acc1;
|
||||
BytePack<sizeof(T)> tmp[UnrollPeers];
|
||||
int dr = 1;
|
||||
@@ -154,7 +159,7 @@ static __device__ __forceinline__ void allreduceEnds(
|
||||
#pragma unroll
|
||||
for (int u=0; u < UnrollPeers-partial; u++) {
|
||||
if (partial && u!=0 && dr+u == nRanks) break;
|
||||
tmp[u] = *add4G(inpRank0+elt, r*stride4G);
|
||||
tmp[u] = inpPacks.peerPtr(world, r)[elt];
|
||||
r += 1;
|
||||
if (r == nRanks) r = 0;
|
||||
}
|
||||
@@ -182,7 +187,7 @@ static __device__ __forceinline__ void allreduceEnds(
|
||||
#pragma unroll
|
||||
for (int u=0; u < UnrollPeers-partial; u++) {
|
||||
if (partial && dr+u == nRanks) break;
|
||||
*add4G(outRank0+elt, r*stride4G) = acc0;
|
||||
outPacks.peerPtr(world, r)[elt] = acc0;
|
||||
r += 1;
|
||||
if (r == nRanks) r = 0;
|
||||
}
|
||||
@@ -193,35 +198,33 @@ static __device__ __forceinline__ void allreduceEnds(
|
||||
|
||||
template<typename Red, typename T>
|
||||
static __device__ void allreduce(
|
||||
ncclSymPrims& prim, int tn, int t, bool waitNeeded,
|
||||
Red red, T* input, T* output, size_t nElts
|
||||
ncclSymkArgsHandler const& handler, int tn, int t, int nBlocks,
|
||||
bool waitNeeded, ncclLsaBarrierSession<ncclCoopCta>& bar,
|
||||
Red red, ncclSymPtr<T> input, ncclSymPtr<T> output, size_t nElts
|
||||
) {
|
||||
int nRanks = prim.nRanks;
|
||||
int nBlocks = prim.nBlocks;
|
||||
// Mpve to rank=0
|
||||
input = prim.peerPtr(0, input);
|
||||
output = prim.peerPtr(0, output);
|
||||
|
||||
uintptr_t inputUptr = reinterpret_cast<uintptr_t>(input);
|
||||
uintptr_t outputUptr = reinterpret_cast<uintptr_t>(output);
|
||||
int const& nRanks = handler.comm.nRanks;
|
||||
int const& nRanks_rcp32 = handler.nRanks_rcp32;
|
||||
size_t nBytes = nElts*sizeof(T);
|
||||
uint32_t nBlocks_rcp32 = nccl::utility::idivRcp32_upto64(nBlocks);
|
||||
uint32_t nRanks_nBlocks_rcp32 = nccl::utility::imulRcp32(nRanks, nRanks_rcp32, nBlocks, nBlocks_rcp32);
|
||||
|
||||
uint32_t nPreBytes = (16u - inputUptr)%16u;
|
||||
uint32_t nPreBytes = (16u - input.offset)%16u;
|
||||
nPreBytes = min((size_t)nPreBytes, nBytes);
|
||||
uintptr_t cursor = nPreBytes;
|
||||
|
||||
constexpr int MinWarpPerBlock = 4;
|
||||
|
||||
if ((inputUptr-outputUptr)%16 == 0) {
|
||||
if ((input.offset - output.offset)%16 == 0) {
|
||||
constexpr int BytePerPack = 16, UnrollPacks = 4, UnrollPeers = 2;
|
||||
constexpr int BytePerChunk = MinWarpPerBlock*UnrollPacks*WARP_SIZE*BytePerPack;
|
||||
uint32_t chunks = (nBytes-cursor)/BytePerChunk;
|
||||
chunks -= imodFast32(chunks, nRanks*nBlocks, prim.nRanks_nBlocks_rcp32);
|
||||
chunks -= imodFast32(chunks, nRanks*nBlocks, nRanks_nBlocks_rcp32);
|
||||
if (chunks != 0) {
|
||||
uintptr_t cursorAfter = cursor + uintptr_t(chunks)*BytePerChunk;
|
||||
allreduceDeep<BytePerPack, UnrollPacks, UnrollPeers, T>(
|
||||
prim, tn, t, waitNeeded, red,
|
||||
(char*)input + cursor, (char*)output + cursor,
|
||||
handler, tn, t, waitNeeded, bar, red,
|
||||
(ncclSymPtr<char>)input + cursor,
|
||||
(ncclSymPtr<char>)output + cursor,
|
||||
chunks*MinWarpPerBlock
|
||||
);
|
||||
cursor = cursorAfter;
|
||||
@@ -229,16 +232,17 @@ static __device__ void allreduce(
|
||||
}
|
||||
}
|
||||
|
||||
if (sizeof(T) == 4 || (sizeof(T) < 4 && (inputUptr-outputUptr)%4 == 0)) {
|
||||
if (sizeof(T) == 4 || (sizeof(T) < 4 && (input.offset - output.offset)%4 == 0)) {
|
||||
constexpr int BytePerPack = 4, UnrollPacks = 4, UnrollPeers = 4;
|
||||
constexpr int BytePerChunk = MinWarpPerBlock*UnrollPacks*WARP_SIZE*BytePerPack;
|
||||
uint32_t chunks = (nBytes-cursor)/BytePerChunk;
|
||||
chunks -= imodFast32(chunks, nRanks*nBlocks, prim.nRanks_nBlocks_rcp32);
|
||||
chunks -= imodFast32(chunks, nRanks*nBlocks, nRanks_nBlocks_rcp32);
|
||||
if (chunks != 0) {
|
||||
uintptr_t cursorAfter = cursor + uintptr_t(chunks)*BytePerChunk;
|
||||
allreduceDeep<(sizeof(T) <= BytePerPack ? BytePerPack : 0), UnrollPacks, UnrollPeers, T>(
|
||||
prim, tn, t, waitNeeded, red,
|
||||
(char*)input + cursor, (char*)output + cursor,
|
||||
handler, tn, t, waitNeeded, bar, red,
|
||||
(ncclSymPtr<char>)input + cursor,
|
||||
(ncclSymPtr<char>)output + cursor,
|
||||
chunks*MinWarpPerBlock
|
||||
);
|
||||
cursor = cursorAfter;
|
||||
@@ -246,46 +250,51 @@ static __device__ void allreduce(
|
||||
}
|
||||
}
|
||||
|
||||
if (waitNeeded) prim.barrierWait(ncclCoopCta(), /*acquire=*/false);
|
||||
if (waitNeeded) bar.wait(ncclCoopCta(), cuda::memory_order_relaxed);
|
||||
|
||||
constexpr int UnrollPeers = 8;
|
||||
size_t nSufElts = (nBytes-cursor)/sizeof(T);
|
||||
allreduceEnds<UnrollPeers>(prim, tn, t, red, input, output, nElts, nPreBytes/sizeof(T), nSufElts);
|
||||
allreduceEnds<UnrollPeers>(handler, tn, t, red, input, output, nElts, nPreBytes/sizeof(T), nSufElts);
|
||||
}
|
||||
|
||||
|
||||
template<template<typename> typename Red, typename T>
|
||||
__device__ __forceinline__ void ncclSymRun_AllReduce_RSxLD_AGxST(ncclSymDevArgs const* args) {
|
||||
ncclSymPrims prim(args->comm, ncclSymPrims_UseBarrier);
|
||||
int /*const&*/ rank = prim.rank;
|
||||
int /*const&*/ nRanks = prim.nRanks;
|
||||
Red<typename ncclSymAccumType<Red, T, /*nvls=*/false>::Type> red(args->redOpArg);
|
||||
__device__ __forceinline__ void ncclSymkRun_AllReduce_RSxLD_AGxST(ncclSymkDevWorkArgs const* args) {
|
||||
ncclSymkArgsHandler handler{args};
|
||||
ncclLsaBarrierSession<ncclCoopCta> bar{
|
||||
ncclCoopCta(), handler.comm, ncclTeamTagLsa(), blockIdx.x
|
||||
};
|
||||
|
||||
// Threads numbered globally such that we round robin warps by rank then block.
|
||||
int gt = flattenIx(threadIdx.x%WARP_SIZE, WARP_SIZE,
|
||||
rank, nRanks,
|
||||
prim.block, prim.nBlocks,
|
||||
threadIdx.x/WARP_SIZE, blockDim.x/WARP_SIZE);
|
||||
int gtn = nRanks*prim.nBlocks*blockDim.x;
|
||||
Red<typename ncclSymkAccumType<Red, T, /*nvls=*/false>::Type> red(handler.devWork->redOpArg);
|
||||
|
||||
prim.barrierArrive(ncclCoopCta(), /*release=*/false);
|
||||
//prim.barrierWait(ncclCoopCta(), /*acquire=*/false);
|
||||
int const& rank = handler.comm.rank;
|
||||
int const& nRanks = handler.comm.nRanks;
|
||||
|
||||
allreduce(prim, gtn, gt, /*waitNeeded=*/true, red, (T*)args->input, (T*)args->output, args->nElts);
|
||||
bar.arrive(ncclCoopCta(), cuda::memory_order_relaxed);
|
||||
|
||||
prim.barrierArrive(ncclCoopCta(), /*release=*/true);
|
||||
prim.barrierWait(ncclCoopCta(), /*acquire=*/false);
|
||||
bool waitNeeded = true;
|
||||
handler.forEachWork<T>(
|
||||
[&]__device__(int block, int nBlocks, size_t nElts, size_t nAllElts,
|
||||
ncclSymPtr<T> input, ncclSymPtr<T> output) {
|
||||
// Threads numbered globally such that we round robin warps by rank then block.
|
||||
int gt = flattenIx(threadIdx.x%WARP_SIZE, WARP_SIZE,
|
||||
rank, nRanks,
|
||||
block, nBlocks,
|
||||
threadIdx.x/WARP_SIZE, blockDim.x/WARP_SIZE);
|
||||
int gtn = nRanks*nBlocks*blockDim.x;
|
||||
|
||||
allreduce(handler, gtn, gt, nBlocks, waitNeeded, bar, red, input, output, nElts);
|
||||
|
||||
waitNeeded = false;
|
||||
}
|
||||
);
|
||||
|
||||
bar.sync(ncclCoopCta(), cuda::memory_order_release);
|
||||
}
|
||||
|
||||
|
||||
template<typename Red, typename T>
|
||||
static __device__ void allreduceMultimem(
|
||||
ncclSymPrims& prim, int tn, int t, Red red, T* input, T* output, size_t nElts
|
||||
int tn, int t, Red red, T* input, T* output, size_t nElts
|
||||
) {
|
||||
// Mpve to multimem
|
||||
input = prim.multimemPtr(input);
|
||||
output = prim.multimemPtr(output);
|
||||
|
||||
uintptr_t inputUptr = reinterpret_cast<uintptr_t>(input);
|
||||
uintptr_t outputUptr = reinterpret_cast<uintptr_t>(output);
|
||||
size_t nBytes = nElts*sizeof(T);
|
||||
@@ -330,106 +339,132 @@ static __device__ void allreduceMultimem(
|
||||
uintptr_t cursor = i < nPreBytes ? i : nBytes-nSufBytes+(i-nPreBytes);
|
||||
BytePack<sizeof(T)> val = applyLoadMultimem<Red, sizeof(T)>(red, inputUptr + cursor);
|
||||
multimem_st_global(outputUptr + cursor, val);
|
||||
cursor += tn*sizeof(T);
|
||||
}
|
||||
}
|
||||
|
||||
template<template<typename> typename Red, typename T>
|
||||
__device__ __forceinline__ void ncclSymRun_AllReduce_RSxLDMC_AGxSTMC(ncclSymDevArgs const* args) {
|
||||
ncclSymPrims prim(args->comm, ncclSymPrims_UseBarrier|ncclSymPrims_UseMultimem);
|
||||
Red<typename ncclSymAccumType<Red, T, /*nvls=*/true>::Type> red(args->redOpArg);
|
||||
__device__ __forceinline__ void ncclSymkRun_AllReduce_RSxLDMC_AGxSTMC(ncclSymkDevWorkArgs const* args) {
|
||||
ncclSymkArgsHandler handler{args};
|
||||
ncclLsaBarrierSession<ncclCoopCta> bar{
|
||||
ncclCoopCta(), handler.comm, ncclTeamTagLsa(), blockIdx.x, /*multimem=*/true
|
||||
};
|
||||
|
||||
// Threads numbered globally such that we round robin warps by rank then block.
|
||||
int gt = flattenIx(threadIdx.x%WARP_SIZE, WARP_SIZE,
|
||||
prim.rank, prim.nRanks,
|
||||
prim.block, prim.nBlocks,
|
||||
threadIdx.x/WARP_SIZE, blockDim.x/WARP_SIZE);
|
||||
int gtn = prim.nRanks*prim.nBlocks*blockDim.x;
|
||||
Red<typename ncclSymkAccumType<Red, T, /*nvls=*/true>::Type> red(handler.devWork->redOpArg);
|
||||
|
||||
prim.barrierArrive(ncclCoopCta(), /*release=*/false);
|
||||
prim.barrierWait(ncclCoopCta(), /*acquire=*/false);
|
||||
int const& rank = handler.comm.rank;
|
||||
int const& nRanks = handler.comm.nRanks;
|
||||
auto const& multimem = handler.comm.lsaMultimem;
|
||||
|
||||
allreduceMultimem(prim, gtn, gt, red, (T*)args->input, (T*)args->output, args->nElts);
|
||||
bar.sync(ncclCoopCta(), cuda::memory_order_relaxed);
|
||||
|
||||
prim.barrierArrive(ncclCoopCta(), /*release=*/true);
|
||||
prim.barrierWait(ncclCoopCta(), /*acquire=*/false);
|
||||
handler.forEachWork<T>(
|
||||
[&]__device__(int block, int nBlocks, size_t nElts, size_t nAllElts,
|
||||
ncclSymPtr<T> input, ncclSymPtr<T> output) {
|
||||
// Threads numbered globally such that we round robin warps by rank then block.
|
||||
int gt = flattenIx(threadIdx.x%WARP_SIZE, WARP_SIZE,
|
||||
rank, nRanks,
|
||||
block, nBlocks,
|
||||
threadIdx.x/WARP_SIZE, blockDim.x/WARP_SIZE);
|
||||
int gtn = nRanks*nBlocks*blockDim.x;
|
||||
|
||||
allreduceMultimem(gtn, gt, red, input.multimemPtr(multimem), output.multimemPtr(multimem), nElts);
|
||||
}
|
||||
);
|
||||
|
||||
bar.sync(ncclCoopCta(), cuda::memory_order_release);
|
||||
}
|
||||
|
||||
template<template<typename> typename Red, typename T>
|
||||
__device__ __forceinline__ void ncclSymRun_AllReduce_AGxLL_R_impl(ncclSymDevArgs const* args, bool multimem) {
|
||||
ncclSymPrims prim(args->comm, ncclSymPrims_UseLL | multimem*ncclSymPrims_UseMultimem);
|
||||
int /*const&*/ rank = prim.rank;
|
||||
using Acc = typename ncclSymAccumType<Red, T, /*nvls=*/false>::Type;
|
||||
Red<Acc> red(args->redOpArg);
|
||||
__device__ __forceinline__ void ncclSymkRun_AllReduce_AGxLL_R_impl(ncclSymkDevWorkArgs const* args, bool multimem) {
|
||||
ncclSymkArgsHandler handler{args};
|
||||
ncclLLA2ASession<ncclCoopCta> lla2a(
|
||||
ncclCoopCta(), handler.comm, ncclTeamLsa(handler.comm), handler.lsaLLA2A,
|
||||
blockIdx.x, ncclSymkMaxThreads, multimem, handler.comm.lsaMultimem
|
||||
);
|
||||
|
||||
int const& rank = handler.comm.rank;
|
||||
int const& nRanks = handler.comm.nRanks;
|
||||
using Acc = typename ncclSymkAccumType<Red, T, /*nvls=*/false>::Type;
|
||||
Red<Acc> red(handler.devWork->redOpArg);
|
||||
|
||||
using Pack = BytePack<8>;
|
||||
using AccPack = BytePack<8*sizeof(Acc)/sizeof(T)>;
|
||||
constexpr int EltPerPack = 8/sizeof(T);
|
||||
int nElts = args->nElts;
|
||||
int nPacks = divUp(nElts, EltPerPack);
|
||||
|
||||
bool packAligned = 8 <= alignof(T) || (
|
||||
args->nElts*sizeof(T) |
|
||||
(uint32_t)reinterpret_cast<uintptr_t>(args->input) |
|
||||
(uint32_t)reinterpret_cast<uintptr_t>(args->output)
|
||||
)%8 == 0;
|
||||
handler.singleWork<T>(
|
||||
[&]__device__(int nElts, int nAllElts,
|
||||
ncclSymPtr<T> inputPtr, ncclSymPtr<T> outputPtr) {
|
||||
int nPacks = divUp(nElts, EltPerPack);
|
||||
|
||||
uint32_t nPackPerBlock, nPackModBlock;
|
||||
idivmodFast32(&nPackPerBlock, &nPackModBlock, nPacks, prim.nBlocks, prim.nBlocks_rcp32);
|
||||
int begin = prim.block*nPackPerBlock + minval<int>(prim.block, nPackModBlock);
|
||||
int end = begin + nPackPerBlock + (prim.block < nPackModBlock ? 1 : 0);
|
||||
T* input = (T*)inputPtr.localPtr();
|
||||
T* output = (T*)outputPtr.localPtr();
|
||||
|
||||
nPacks = end - begin;
|
||||
nElts -= begin*EltPerPack;
|
||||
nElts = min(nElts, nPacks*EltPerPack);
|
||||
T* input = (T*)args->input + begin*EltPerPack;
|
||||
T* output = (T*)args->output + begin*EltPerPack;
|
||||
bool packAligned = 8 <= alignof(T) || (nElts*sizeof(T) | (uintptr_t)input | (uintptr_t)output)%8 == 0;
|
||||
|
||||
ncclCoopCta cta;
|
||||
int t = threadIdx.x;
|
||||
int tn = ncclSymMaxThreads;
|
||||
ncclCoopCta cta;
|
||||
int t = threadIdx.x;
|
||||
int tn = ncclSymkMaxThreads;
|
||||
|
||||
if (__builtin_expect(packAligned, true)) {
|
||||
#pragma unroll 1
|
||||
while (0 < nPacks) {
|
||||
if (t < nPacks) {
|
||||
int nIterPacks = min(nPacks, tn);
|
||||
Pack inp = loadPack<Pack>((Pack*)input, t, nPacks);
|
||||
prim.bcastLL(/*slot=*/nIterPacks*rank + t, inp);
|
||||
Pack out = prim.template recvReduceLL<Pack, T>(t, nIterPacks, red);
|
||||
storePack((Pack*)output, t, nPacks, out);
|
||||
if (__builtin_expect(packAligned, true)) {
|
||||
#pragma unroll 1
|
||||
while (0 < nPacks) {
|
||||
if (t < nPacks) {
|
||||
int nIterPacks = min(nPacks, tn);
|
||||
Pack inp = loadPack<Pack>((Pack*)input, t, nPacks);
|
||||
lla2a.bcast(/*slot=*/nIterPacks*rank + t, inp);
|
||||
AccPack out = lla2a.template recvReduce</*Unroll=*/8, Pack>(
|
||||
/*slotStart=*/t, /*slotCount=*/nRanks, /*slotStride=*/nIterPacks,
|
||||
/*eltToAcc=*/[&] __device__ (Pack x)->AccPack {
|
||||
return applyCast<T, Acc>(x);
|
||||
},
|
||||
/*reduce=*/[&] __device__ (AccPack a, AccPack b)->AccPack {
|
||||
return applyReduce(red, a, b);
|
||||
}
|
||||
);
|
||||
storePack((Pack*)output, t, nPacks, applyCast<Acc, T>(out));
|
||||
}
|
||||
lla2a.endEpoch(cta);
|
||||
|
||||
input += tn*EltPerPack;
|
||||
output += tn*EltPerPack;
|
||||
nPacks -= tn;
|
||||
}
|
||||
} else {
|
||||
#pragma unroll 1
|
||||
while (0 < nElts) {
|
||||
if (t*EltPerPack < nElts) {
|
||||
int nIterPacks = min(nPacks, tn);
|
||||
Pack inp = loadPack<Pack>(input, t*EltPerPack, nElts);
|
||||
lla2a.bcast(/*slot=*/nIterPacks*rank + t, inp);
|
||||
AccPack out = lla2a.template recvReduce</*Unroll=*/8, Pack>(
|
||||
/*slotStart=*/t, /*slotCount=*/nRanks, /*slotStride=*/nIterPacks,
|
||||
/*eltToAcc=*/[&] __device__ (Pack x)->AccPack {
|
||||
return applyCast<T, Acc>(x);
|
||||
},
|
||||
/*reduce=*/[&] __device__ (AccPack a, AccPack b)->AccPack {
|
||||
return applyReduce(red, a, b);
|
||||
}
|
||||
);
|
||||
storePack(output, t*EltPerPack, nElts, applyCast<Acc, T>(out));
|
||||
}
|
||||
lla2a.endEpoch(cta);
|
||||
|
||||
input += tn*EltPerPack;
|
||||
output += tn*EltPerPack;
|
||||
nElts -= tn*EltPerPack;
|
||||
nPacks -= tn;
|
||||
}
|
||||
}
|
||||
}
|
||||
prim.endLL(cta);
|
||||
|
||||
input += tn*EltPerPack;
|
||||
output += tn*EltPerPack;
|
||||
nPacks -= tn;
|
||||
}
|
||||
} else {
|
||||
#pragma unroll 1
|
||||
while (0 < nElts) {
|
||||
if (t*EltPerPack < nElts) {
|
||||
int nIterPacks = min(nPacks, tn);
|
||||
Pack inp = loadPack<Pack>(input, t*EltPerPack, nElts);
|
||||
prim.bcastLL(/*slot=*/nIterPacks*rank + t, inp);
|
||||
Pack out = prim.template recvReduceLL<Pack, T>(t, nIterPacks, red);
|
||||
storePack(output, t*EltPerPack, nElts, out);
|
||||
}
|
||||
prim.endLL(cta);
|
||||
|
||||
input += tn*EltPerPack;
|
||||
output += tn*EltPerPack;
|
||||
nElts -= tn*EltPerPack;
|
||||
nPacks -= tn;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
template<template<typename> typename Red, typename T>
|
||||
__device__ __forceinline__ void ncclSymRun_AllReduce_AGxLL_R(ncclSymDevArgs const* args) {
|
||||
ncclSymRun_AllReduce_AGxLL_R_impl<Red, T>(args, /*multimem=*/false);
|
||||
__device__ __forceinline__ void ncclSymkRun_AllReduce_AGxLL_R(ncclSymkDevWorkArgs const* args) {
|
||||
ncclSymkRun_AllReduce_AGxLL_R_impl<Red, T>(args, /*multimem=*/false);
|
||||
}
|
||||
|
||||
template<template<typename> typename Red, typename T>
|
||||
__device__ __forceinline__ void ncclSymRun_AllReduce_AGxLLMC_R(ncclSymDevArgs const* args) {
|
||||
ncclSymRun_AllReduce_AGxLL_R_impl<Red, T>(args, /*multimem=*/true);
|
||||
__device__ __forceinline__ void ncclSymkRun_AllReduce_AGxLLMC_R(ncclSymkDevWorkArgs const* args) {
|
||||
ncclSymkRun_AllReduce_AGxLL_R_impl<Red, T>(args, /*multimem=*/true);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
|
||||
################################################################################
|
||||
# The first command line argument is the path to the directory to generate and
|
||||
@@ -13,8 +14,11 @@ gensrc = sys.argv[1]
|
||||
|
||||
if os.path.exists(gensrc):
|
||||
for name in os.listdir(gensrc):
|
||||
os.remove(os.path.join(gensrc, name))
|
||||
#os.truncate(os.path.join(gensrc, name), 0)
|
||||
path = os.path.join(gensrc, name)
|
||||
if os.path.isfile(path):
|
||||
os.remove(path)
|
||||
elif os.path.isdir(path):
|
||||
shutil.rmtree(path)
|
||||
else:
|
||||
os.mkdir(gensrc)
|
||||
|
||||
@@ -97,7 +101,7 @@ def enumerate_kernels():
|
||||
yield Rec(coll="ReduceScatter", algo=algo, red=red, ty=ty)
|
||||
|
||||
def required_cuda(k):
|
||||
cudart, arch, specific_sms = 0, 0, None
|
||||
cudart, arch, specific_sms = 0, 600, None
|
||||
is_nvls = k.algo in nvls_algos_by_coll.get(k.coll, [])
|
||||
if is_nvls:
|
||||
cudart = max(cudart, 12010)
|
||||
@@ -136,13 +140,13 @@ def kernel_gencode(k):
|
||||
|
||||
def kernel_cname(k):
|
||||
if k.coll in reductions:
|
||||
return paste("_", "ncclSymDevKernel", k.coll, k.algo, k.red, k.ty)
|
||||
return paste("_", "ncclSymkDevKernel", k.coll, k.algo, k.red, k.ty)
|
||||
else:
|
||||
return paste("_", "ncclSymDevKernel", k.coll, k.algo)
|
||||
return paste("_", "ncclSymkDevKernel", k.coll, k.algo)
|
||||
|
||||
def kernel_conds(k):
|
||||
cudart, arch, specific_sms = required_cuda(k)
|
||||
if cudart == 0: return (None, None)
|
||||
if cudart == 0 and arch == 0: return (None, None)
|
||||
|
||||
cudart_cond = "CUDART_VERSION >= %d"%cudart
|
||||
if not specific_sms:
|
||||
@@ -153,13 +157,13 @@ def kernel_conds(k):
|
||||
|
||||
def instantiate(k):
|
||||
form_red_ty = (
|
||||
"__global__ void {cname}(ncclSymDevArgs NCCL_GRID_CONSTANT const *args) {{\n"
|
||||
" ncclSymRun_{id}<{red}, {ty}>(args);\n"
|
||||
"__global__ void {cname}(ncclSymkDevWorkArgs4K NCCL_GRID_CONSTANT const *args4K) {{\n"
|
||||
" ncclSymkRun_{id}<{red}, {ty}>(args4K->args);\n"
|
||||
"}}"
|
||||
)
|
||||
form = (
|
||||
"__global__ void {cname}(ncclSymDevArgs NCCL_GRID_CONSTANT const *args) {{\n"
|
||||
" ncclSymRun_{id}(args);\n"
|
||||
"__global__ void {cname}(ncclSymkDevWorkArgs4K NCCL_GRID_CONSTANT const *args4K) {{\n"
|
||||
" ncclSymkRun_{id}(args4K->args);\n"
|
||||
"}}"
|
||||
)
|
||||
|
||||
@@ -172,7 +176,7 @@ def instantiate(k):
|
||||
return inst
|
||||
|
||||
def prototype(k):
|
||||
return "__global__ void {cname}(ncclSymDevArgs const *args);".format(cname=kernel_cname(k))
|
||||
return "__global__ void {cname}(ncclSymkDevWorkArgs4K const *args4K);".format(cname=kernel_cname(k))
|
||||
|
||||
################################################################################
|
||||
|
||||
@@ -194,20 +198,22 @@ for coll in set(k.coll for k in enumerate_kernels()):
|
||||
if (fname, coll) not in kernels_by_file:
|
||||
kernels_by_file[fname, coll] = []
|
||||
|
||||
files_to_print = ""
|
||||
# Generate each kernel instantiation file
|
||||
for (fname, coll), ks in kernels_by_file.items():
|
||||
files_to_print += fname + ";"
|
||||
with open(os.path.join(gensrc, fname), "w") as f:
|
||||
print("-- Generating %s" % os.path.join(gensrc, fname))
|
||||
emitln(f, '#include "symmetric.h"')
|
||||
emitln(f, '#include "sym_kernels.h"')
|
||||
emitln(f, '#include "symmetric/kernel.h"')
|
||||
emitln(f, '#include "symmetric/{coll}.h"'.format(coll=coll_to_lower[coll]))
|
||||
for k in ks:
|
||||
emitln(f, instantiate(k))
|
||||
|
||||
# Generate <gensrc>/symmetric_host.cc
|
||||
with open(os.path.join(gensrc, "symmetric_kernels.cc"), "w") as f:
|
||||
# Generate <gensrc>/sym_kernels_host.cc
|
||||
with open(os.path.join(gensrc, "sym_kernels_host.cc"), "w") as f:
|
||||
print("-- Generating %s" % os.path.join(gensrc, "symmetric_kernels.cc"))
|
||||
emitln(f, '#include "symmetric.h"')
|
||||
emitln(f, '#include "sym_kernels.h"')
|
||||
emitln(f, '#include "device.h"')
|
||||
emitln(f, '')
|
||||
|
||||
@@ -215,19 +221,19 @@ with open(os.path.join(gensrc, "symmetric_kernels.cc"), "w") as f:
|
||||
emitln(f, prototype(k))
|
||||
emitln(f, '')
|
||||
|
||||
emitln(f, 'extern int const ncclSymKernelCount = %d;' % len(list(enumerate_kernels())))
|
||||
emitln(f, 'extern void* const ncclSymKernelList[] = {')
|
||||
emitln(f, 'extern int const ncclSymkKernelCount = %d;' % len(list(enumerate_kernels())))
|
||||
emitln(f, 'extern void* const ncclSymkKernelList[] = {')
|
||||
for k in enumerate_kernels():
|
||||
emitln(f, '(void*){cname},'.format(cname=kernel_cname(k)))
|
||||
emitln(f, 'nullptr};')
|
||||
emitln(f, '')
|
||||
|
||||
emitln(f, 'void* ncclSymGetKernelPtr(ncclSymKernelId id, int red, ncclDataType_t ty) {')
|
||||
emitln(f, 'void* ncclSymkGetKernelPtr(ncclSymkKernelId id, int red, ncclDataType_t ty) {')
|
||||
indents += 1
|
||||
emitln(f, 'switch (id) {')
|
||||
emitln(f, 'default: return nullptr;')
|
||||
for (coll, algo), coll_algo_ks in partition(enumerate_kernels(), lambda k: (k.coll, k.algo)).items():
|
||||
emitln(f, 'case ncclSymKernelId_'+coll+'_'+algo+':')
|
||||
emitln(f, 'case ncclSymkKernelId_'+coll+'_'+algo+':')
|
||||
indents += 1
|
||||
if len(coll_algo_ks) == 1:
|
||||
emitln(f, 'return (void*)&'+kernel_cname(coll_algo_ks[0])+';')
|
||||
|
||||
@@ -4,27 +4,27 @@
|
||||
#ifndef NCCL_DEVICE_SYMMETRIC_KERNEL_H_
|
||||
#define NCCL_DEVICE_SYMMETRIC_KERNEL_H_
|
||||
|
||||
#include "symmetric.h"
|
||||
#include "sym_kernels.h"
|
||||
|
||||
template<template<typename> typename Red, typename T>
|
||||
__device__ __forceinline__ void ncclSymRun_AllReduce_AGxLL_R(struct ncclSymDevArgs const* args);
|
||||
__device__ __forceinline__ void ncclSymkRun_AllReduce_AGxLL_R(struct ncclSymkDevWorkArgs const* args);
|
||||
template<template<typename> typename Red, typename T>
|
||||
__device__ __forceinline__ void ncclSymRun_AllReduce_AGxLLMC_R(struct ncclSymDevArgs const* args);
|
||||
__device__ __forceinline__ void ncclSymkRun_AllReduce_AGxLLMC_R(struct ncclSymkDevWorkArgs const* args);
|
||||
|
||||
template<template<typename> typename Red, typename T>
|
||||
__device__ __forceinline__ void ncclSymRun_AllReduce_RSxLD_AGxST(struct ncclSymDevArgs const* args);
|
||||
__device__ __forceinline__ void ncclSymkRun_AllReduce_RSxLD_AGxST(struct ncclSymkDevWorkArgs const* args);
|
||||
template<template<typename> typename Red, typename T>
|
||||
__device__ __forceinline__ void ncclSymRun_AllReduce_RSxLDMC_AGxSTMC(struct ncclSymDevArgs const* args);
|
||||
__device__ __forceinline__ void ncclSymkRun_AllReduce_RSxLDMC_AGxSTMC(struct ncclSymkDevWorkArgs const* args);
|
||||
|
||||
__device__ __forceinline__ void ncclSymRun_AllGather_LL(struct ncclSymDevArgs const* args);
|
||||
__device__ __forceinline__ void ncclSymRun_AllGather_LLMC(struct ncclSymDevArgs const* args);
|
||||
__device__ __forceinline__ void ncclSymRun_AllGather_ST(struct ncclSymDevArgs const* args);
|
||||
__device__ __forceinline__ void ncclSymRun_AllGather_STMC(struct ncclSymDevArgs const* args);
|
||||
__device__ __forceinline__ void ncclSymkRun_AllGather_LL(struct ncclSymkDevWorkArgs const* args);
|
||||
__device__ __forceinline__ void ncclSymkRun_AllGather_LLMC(struct ncclSymkDevWorkArgs const* args);
|
||||
__device__ __forceinline__ void ncclSymkRun_AllGather_ST(struct ncclSymkDevWorkArgs const* args);
|
||||
__device__ __forceinline__ void ncclSymkRun_AllGather_STMC(struct ncclSymkDevWorkArgs const* args);
|
||||
|
||||
template<template<typename> typename Red, typename T>
|
||||
__device__ __forceinline__ void ncclSymRun_ReduceScatter_LL(struct ncclSymDevArgs const* args);
|
||||
__device__ __forceinline__ void ncclSymkRun_ReduceScatter_LL(struct ncclSymkDevWorkArgs const* args);
|
||||
template<template<typename> typename Red, typename T>
|
||||
__device__ __forceinline__ void ncclSymRun_ReduceScatter_LD(struct ncclSymDevArgs const* args);
|
||||
__device__ __forceinline__ void ncclSymkRun_ReduceScatter_LD(struct ncclSymkDevWorkArgs const* args);
|
||||
template<template<typename> typename Red, typename T>
|
||||
__device__ __forceinline__ void ncclSymRun_ReduceScatter_LDMC(struct ncclSymDevArgs const* args);
|
||||
__device__ __forceinline__ void ncclSymkRun_ReduceScatter_LDMC(struct ncclSymkDevWorkArgs const* args);
|
||||
#endif
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#ifndef NCCL_DEVICE_SYMMETRIC_PRIMITIVES_H_
|
||||
#define NCCL_DEVICE_SYMMETRIC_PRIMITIVES_H_
|
||||
|
||||
#include "symmetric.h"
|
||||
#include "sym_kernels.h"
|
||||
#include "bitops.h"
|
||||
#include "collectives.h"
|
||||
#include "op128.h"
|
||||
@@ -28,453 +28,124 @@ static __device__ Int0 flattenIx(Int0 pos, Int1 size, Ints ...more) {
|
||||
return pos + size*flattenIx(more...);
|
||||
}
|
||||
|
||||
// Precomputed integer reciprocoals for denominator values 1..64 inclusive.
|
||||
// Pass these to idivFast64() for fast division on the GPU.
|
||||
static __device__ uint64_t idivRcp64_upto64(int x) {
|
||||
static constexpr uint64_t table[65] = {
|
||||
idivRcp64(0x01), idivRcp64(0x01), idivRcp64(0x02), idivRcp64(0x03),
|
||||
idivRcp64(0x04), idivRcp64(0x05), idivRcp64(0x06), idivRcp64(0x07),
|
||||
idivRcp64(0x08), idivRcp64(0x09), idivRcp64(0x0a), idivRcp64(0x0b),
|
||||
idivRcp64(0x0c), idivRcp64(0x0d), idivRcp64(0x0e), idivRcp64(0x0f),
|
||||
idivRcp64(0x10), idivRcp64(0x11), idivRcp64(0x12), idivRcp64(0x13),
|
||||
idivRcp64(0x14), idivRcp64(0x15), idivRcp64(0x16), idivRcp64(0x17),
|
||||
idivRcp64(0x18), idivRcp64(0x19), idivRcp64(0x1a), idivRcp64(0x1b),
|
||||
idivRcp64(0x1c), idivRcp64(0x1d), idivRcp64(0x1e), idivRcp64(0x1f),
|
||||
idivRcp64(0x20), idivRcp64(0x21), idivRcp64(0x22), idivRcp64(0x23),
|
||||
idivRcp64(0x24), idivRcp64(0x25), idivRcp64(0x26), idivRcp64(0x27),
|
||||
idivRcp64(0x28), idivRcp64(0x29), idivRcp64(0x2a), idivRcp64(0x2b),
|
||||
idivRcp64(0x2c), idivRcp64(0x2d), idivRcp64(0x2e), idivRcp64(0x2f),
|
||||
idivRcp64(0x30), idivRcp64(0x31), idivRcp64(0x32), idivRcp64(0x33),
|
||||
idivRcp64(0x34), idivRcp64(0x35), idivRcp64(0x36), idivRcp64(0x37),
|
||||
idivRcp64(0x38), idivRcp64(0x39), idivRcp64(0x3a), idivRcp64(0x3b),
|
||||
idivRcp64(0x3c), idivRcp64(0x3d), idivRcp64(0x3e), idivRcp64(0x3f),
|
||||
idivRcp64(0x40)
|
||||
};
|
||||
return table[x];
|
||||
}
|
||||
|
||||
static __device__ uint32_t idivRcp32_upto64(int x) {
|
||||
return idivRcp64_upto64(x)>>32;
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct ncclCoopCta {
|
||||
__device__ void sync() { __syncthreads(); }
|
||||
__device__ int self() { return threadIdx.x; }
|
||||
__device__ int count() { return blockDim.x; }
|
||||
};
|
||||
struct ncclCoopWarps {
|
||||
int log2_nWarps;
|
||||
__device__ void sync() {
|
||||
asm volatile("barrier.sync %0, %1;" :: "r"(1 + (threadIdx.x>>(5+log2_nWarps))), "r"(32<<log2_nWarps) : "memory");
|
||||
}
|
||||
__device__ int self() { return threadIdx.x & ((32<<log2_nWarps)-1); }
|
||||
__device__ int count() { return 32<<log2_nWarps; }
|
||||
};
|
||||
struct ncclCoopWarp {
|
||||
__device__ void sync() { __syncwarp(); }
|
||||
__device__ int self() { return threadIdx.x%32; }
|
||||
__device__ int count() { return 32; }
|
||||
};
|
||||
}
|
||||
struct ncclSymkArgsHandler {
|
||||
ncclDevComm const& comm;
|
||||
ncclLLA2AHandle const& lsaLLA2A;
|
||||
struct ncclSymkChannelWorkRange* channelWorkRange;
|
||||
struct ncclSymkDevWork* devWork;
|
||||
uint32_t nRanks_rcp32;
|
||||
|
||||
namespace {
|
||||
static constexpr int ncclSymPrims_UseBarrier = 1;
|
||||
static constexpr int ncclSymPrims_UseLL = 2;
|
||||
static constexpr int ncclSymPrims_UseMultimem = 4;
|
||||
struct ncclSymPrims {
|
||||
int flags;
|
||||
int const &rank;
|
||||
int const &nRanks;
|
||||
uint32_t const &nRanks_rcp32;
|
||||
int block, nBlocks;
|
||||
uint32_t nBlocks_rcp32;
|
||||
uint32_t nBlocks_nWarps_rcp32;
|
||||
uint32_t nRanks_nBlocks_rcp32;
|
||||
uint32_t nWarpPerRank, nWarpPerRank_rcp32;
|
||||
struct ncclSymDevBase* const &base;
|
||||
uintptr_t offsetMc;
|
||||
__device__ ncclSymkArgsHandler(ncclSymkDevWorkArgs const* args):
|
||||
comm(args->kcomm.devComm),
|
||||
lsaLLA2A(args->kcomm.lsaLLA2A) {
|
||||
channelWorkRange = args->getWorkRange();
|
||||
|
||||
uint32_t const &stride4G;
|
||||
uint32_t barEpoch;
|
||||
uint32_t llEpoch;
|
||||
|
||||
__device__ ncclSymPrims(ncclSymDevComm const &comm, int flags):
|
||||
flags(flags),
|
||||
rank(comm.rank),
|
||||
nRanks(comm.nRanks),
|
||||
nRanks_rcp32(comm.nRanks_rcp32),
|
||||
block(blockIdx.x),
|
||||
nBlocks(gridDim.x),
|
||||
nBlocks_rcp32(idivRcp32_upto64(nBlocks)),
|
||||
nBlocks_nWarps_rcp32(imulRcp32(nBlocks, nBlocks_rcp32, blockDim.x/32, idivRcp32_upto64(blockDim.x/32))),
|
||||
nRanks_nBlocks_rcp32(imulRcp32(nRanks, nRanks_rcp32, gridDim.x, nBlocks_rcp32)),
|
||||
nWarpPerRank(idivFast32(nBlocks*blockDim.x/32, nRanks, nRanks_rcp32)),
|
||||
nWarpPerRank_rcp32(idivRcp32_upto64(nWarpPerRank)),
|
||||
base(comm.base),
|
||||
offsetMc((flags & ncclSymPrims_UseMultimem) ? (char*)comm.baseMc - (char*)base : 0x0),
|
||||
stride4G(comm.stride4G) {
|
||||
|
||||
#if CUDART_VERSION >= 12030 && __CUDA_ARCH__ >= 900
|
||||
cudaGridDependencySynchronize();
|
||||
#endif
|
||||
|
||||
if ((flags & ncclSymPrims_UseBarrier) && threadIdx.x < nRanks) {
|
||||
barEpoch = (flags & ncclSymPrims_UseMultimem) ? base->barEpochMc[block] : base->barEpochUc[block];
|
||||
}
|
||||
if (flags & ncclSymPrims_UseLL) llEpoch = base->llEpoch[block] + 2;
|
||||
}
|
||||
__device__ ~ncclSymPrims() {
|
||||
if (threadIdx.x == 0) {
|
||||
if (flags & ncclSymPrims_UseBarrier) {
|
||||
((flags & ncclSymPrims_UseMultimem) ? base->barEpochMc : base->barEpochUc)[block] = barEpoch;
|
||||
}
|
||||
if (flags & ncclSymPrims_UseLL) base->llEpoch[block] = llEpoch - 2;
|
||||
}
|
||||
devWork = args->getWorks(args->nMaxChannels);
|
||||
nRanks_rcp32 = comm.nRanks_rcp32;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__device__ T* peerPtr(int peer, T* selfPtr) {
|
||||
return add4G(selfPtr, (peer-rank)*stride4G);
|
||||
__device__ void getWorkRange(int block,
|
||||
uint16_t& workLo, size_t& indexLo, uint16_t& workHi, size_t& indexHi) {
|
||||
constexpr int EltPerCell = NCCL_SYM_KERNEL_CELL_SIZE / sizeof(T);
|
||||
uint32_t fracLo, fracHi;
|
||||
|
||||
// Where the work begins
|
||||
workLo = (block==0) ? 0 : channelWorkRange[block-1].workHi; // start where predecessor ends
|
||||
fracLo = (block==0) ? 0 : channelWorkRange[block-1].fracHi + 1;
|
||||
// If the predecessor ended on the work boundary, then we step to the beginning of the next work.
|
||||
// This ensures we never have empty parts.
|
||||
if (fracLo == 0x10000) {
|
||||
workLo++;
|
||||
fracLo = 0;
|
||||
}
|
||||
struct ncclSymkDevWork const& dw = devWork[workLo];
|
||||
indexLo = ((fracLo * divUp(dw.nElts, EltPerCell)) >> 16) * EltPerCell;
|
||||
|
||||
// Where the work ends
|
||||
workHi = channelWorkRange[block].workHi;
|
||||
fracHi = channelWorkRange[block].fracHi + 1;
|
||||
indexHi = min(((fracHi * divUp(dw.nElts, EltPerCell)) >> 16) * EltPerCell, dw.nElts);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__device__ T* multimemPtr(T* selfPtr) {
|
||||
return reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(selfPtr) + offsetMc);
|
||||
__device__ void getWorkRangeFused(int blockIdx, int w,
|
||||
int& block, int& nBlocks, size_t& indexLo, size_t& indexHi) {
|
||||
constexpr int EltPerCell = NCCL_SYM_KERNEL_CELL_SIZE / sizeof(T);
|
||||
struct ncclSymkDevWork const& dw = devWork[w];
|
||||
uint32_t fracLo, fracHi;
|
||||
int lastBlock;
|
||||
|
||||
block = blockIdx - dw.sChannelId;
|
||||
nBlocks = dw.nChannels;
|
||||
lastBlock = dw.sChannelId+dw.nChannels-1;
|
||||
|
||||
// Where the work begins
|
||||
fracLo = (dw.sChannelId==0) ? 0 : ((channelWorkRange[dw.sChannelId-1].fracHi + 1) & 0xFFFF);
|
||||
indexLo = ((fracLo * divUp(dw.nElts, EltPerCell)) >> 16) * EltPerCell;
|
||||
fracHi = (channelWorkRange[lastBlock].workHi == w) ? channelWorkRange[lastBlock].fracHi + 1 : 0x10000;
|
||||
indexHi = min(((fracHi * divUp(dw.nElts, EltPerCell)) >> 16) * EltPerCell, dw.nElts);
|
||||
}
|
||||
|
||||
__device__ void barrierArrive(ncclCoopCta cta, bool release) {
|
||||
cta.sync();
|
||||
#if __CUDA_ARCH__ < 700
|
||||
if (release) {
|
||||
if (cta.self() == 0) __threadfence_system();
|
||||
cta.sync();
|
||||
}
|
||||
#endif
|
||||
if (flags & ncclSymPrims_UseMultimem) {
|
||||
#if __CUDA_ARCH__ >= 900 && CUDART_VERSION >= 12010
|
||||
if (cta.self() == 0) {
|
||||
uint32_t* inbox = &multimemPtr(base)->barInboxMc[block];
|
||||
if (release) {
|
||||
asm volatile("multimem.red.release.sys.add.u32 [%0],1;" :: "l"(inbox));
|
||||
template<typename T, typename Fn>
|
||||
__device__ void forEachWork(Fn const& fn) {
|
||||
uint16_t workLo, workHi;
|
||||
size_t indexLo, indexHi;
|
||||
|
||||
getWorkRange<T>(blockIdx.x, workLo, indexLo, workHi, indexHi);
|
||||
|
||||
size_t currentIndexLo = indexLo;
|
||||
#pragma unroll 1
|
||||
for (int w = workLo; w <= workHi; w++) {
|
||||
struct ncclSymkDevWork const& dw = devWork[w];
|
||||
size_t const& nAllElts = dw.nElts;
|
||||
size_t currentIndexHi;
|
||||
int block, nBlocks;
|
||||
if (blockIdx.x >= dw.sChannelId && blockIdx.x < dw.sChannelId + dw.nChannels) {
|
||||
getWorkRangeFused<T>(blockIdx.x, w, block, nBlocks, currentIndexLo, currentIndexHi);
|
||||
} else {
|
||||
asm volatile("multimem.red.relaxed.sys.add.u32 [%0],1;" :: "l"(inbox));
|
||||
currentIndexHi = (w < workHi) ? nAllElts : indexHi;
|
||||
block = 0;
|
||||
nBlocks = 1;
|
||||
}
|
||||
|
||||
fn(block, nBlocks, currentIndexHi - currentIndexLo, nAllElts,
|
||||
ncclSymPtr<T>(dw.inputWin, dw.inputOff) + currentIndexLo,
|
||||
ncclSymPtr<T>(dw.outputWin, dw.outputOff) + currentIndexLo);
|
||||
|
||||
currentIndexLo = 0;
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
int r = cta.self();
|
||||
if (r != rank && r < nRanks) {
|
||||
uint32_t* inbox = &peerPtr(r, base)->barInboxPerPeer[block*nRanks + rank];
|
||||
#if __CUDA_ARCH__ >= 700
|
||||
if (release) {
|
||||
asm volatile("st.release.sys.u32 [%0],%1;" :: "l"(inbox), "r"(barEpoch+1));
|
||||
} else {
|
||||
asm volatile("st.relaxed.sys.u32 [%0],%1;" :: "l"(inbox), "r"(barEpoch+1));
|
||||
}
|
||||
#else
|
||||
if (release) {
|
||||
__atomic_store_n(inbox, barEpoch + 1, __ATOMIC_RELEASE);
|
||||
} else {
|
||||
__atomic_store_n(inbox, barEpoch + 1, __ATOMIC_RELAXED);
|
||||
}
|
||||
// asm volatile("st.volatile.u32 [%0],%1;" :: "l"(inbox), "r"(barEpoch+1));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__device__ void barrierWait(ncclCoopCta cta, bool acquire) {
|
||||
if (flags & ncclSymPrims_UseMultimem) {
|
||||
#if __CUDA_ARCH__ >= 900
|
||||
if (cta.self() == 0) {
|
||||
uint32_t* inbox = &base->barInboxMc[block];
|
||||
while (true) {
|
||||
uint32_t got;
|
||||
if (acquire) {
|
||||
asm volatile("ld.acquire.sys.u32 %0,[%1];" : "=r"(got) : "l"(inbox));
|
||||
} else {
|
||||
asm volatile("ld.relaxed.sys.u32 %0,[%1];" : "=r"(got) : "l"(inbox));
|
||||
}
|
||||
if (got-(barEpoch+nRanks) <= uint32_t(-1)>>1) break;
|
||||
}
|
||||
barEpoch += nRanks;
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
int r = cta.self();
|
||||
if (r != rank && r < nRanks) {
|
||||
uint32_t* inbox = &base->barInboxPerPeer[block*nRanks + r];
|
||||
while (true) {
|
||||
uint32_t got;
|
||||
#if __CUDA_ARCH__ >= 700
|
||||
if (acquire) {
|
||||
asm volatile("ld.acquire.sys.u32 %0,[%1];" : "=r"(got) : "l"(inbox));
|
||||
} else {
|
||||
asm volatile("ld.relaxed.sys.u32 %0,[%1];" : "=r"(got) : "l"(inbox));
|
||||
}
|
||||
#else
|
||||
if (acquire) {
|
||||
got = __atomic_load_n(inbox, __ATOMIC_ACQUIRE);
|
||||
} else {
|
||||
got = __atomic_load_n(inbox, __ATOMIC_RELAXED);
|
||||
}
|
||||
// asm volatile("ld.volatile.u32 %0,[%1];" : "=r"(got) : "l"(inbox));
|
||||
#endif
|
||||
if (got-(barEpoch+1) <= uint32_t(-1)>>1) break;
|
||||
}
|
||||
}
|
||||
#if __CUDA_ARCH__ < 700
|
||||
if (acquire) {
|
||||
cta.sync();
|
||||
if (cta.self() == 0) __threadfence();
|
||||
}
|
||||
#endif
|
||||
barEpoch += 1;
|
||||
}
|
||||
cta.sync();
|
||||
}
|
||||
template<typename T, typename Fn>
|
||||
__device__ void singleWork(Fn const& fn) {
|
||||
uint16_t w;
|
||||
size_t indexLo, indexHi;
|
||||
|
||||
__device__ void endLL(ncclCoopCta cta) {
|
||||
if (__builtin_expect(llEpoch >= -2u, false)) {
|
||||
cta.sync();
|
||||
uint4* buf = ncclSymDevBase_getLLBuf(base, nRanks, block, llEpoch);
|
||||
int epochSize = ncclSymLLEpochSize(nRanks);
|
||||
#pragma unroll 4
|
||||
for (int i=cta.self(); i*16 < epochSize; i += cta.count()) {
|
||||
buf[i] = uint4{0, 0, 0, 0};
|
||||
}
|
||||
}
|
||||
cta.sync();
|
||||
llEpoch += (llEpoch == -1u) ? 3 : 1;
|
||||
}
|
||||
getWorkRange<T>(blockIdx.x, w, indexLo, w, indexHi);
|
||||
|
||||
template<typename T>
|
||||
__device__ void sendLL(int peer, int slot, T val) {
|
||||
union { T tmp; uint32_t u32[divUp(sizeof(T),8)][2]; };
|
||||
tmp = val;
|
||||
uint4* buf = ncclSymDevBase_getLLBuf(peerPtr(peer, base), nRanks, block, llEpoch) + slot;
|
||||
#pragma unroll
|
||||
for (int u=0; u < divUp(sizeof(T),8); u++) {
|
||||
using Vec = uint32_t __attribute__((ext_vector_type(4)));
|
||||
Vec i4;
|
||||
i4[0] = u32[u][0];
|
||||
i4[1] = llEpoch;
|
||||
i4[2] = u32[u][1];
|
||||
i4[3] = llEpoch;
|
||||
#if defined(__gfx950__)
|
||||
asm volatile ("flat_store_dwordx4 %0, %1 sc0 sc1 nt" :: "v"(buf + ncclSymLLMaxSlots(sizeof(T))*u), "v"(i4));
|
||||
#else
|
||||
__builtin_nontemporal_store(i4, (Vec*)(buf + ncclSymLLMaxSlots(sizeof(T))*u));
|
||||
#endif
|
||||
// asm volatile("st.volatile.v4.u32 [%0],{%1,%3,%2,%3};" :: "l"(buf + ncclSymLLMaxSlots(sizeof(T))*u), "r"(u32[u][0]), "r"(u32[u][1]), "r"(llEpoch));
|
||||
}
|
||||
}
|
||||
struct ncclSymkDevWork const& dw = devWork[w];
|
||||
|
||||
template<typename T>
|
||||
__device__ void bcastLL(int slot, T val) {
|
||||
if (flags & ncclSymPrims_UseMultimem) {
|
||||
union { T tmp; uint32_t u32[divUp(sizeof(T),8)][2]; };
|
||||
tmp = val;
|
||||
uint4* bufmc = ncclSymDevBase_getLLBuf(multimemPtr(base), nRanks, block, llEpoch) + slot;
|
||||
#pragma unroll
|
||||
for (int u=0; u < divUp(sizeof(T),8); u++) {
|
||||
using Vec = uint32_t __attribute__((ext_vector_type(4)));
|
||||
Vec i4;
|
||||
i4[0] = u32[u][0];
|
||||
i4[1] = llEpoch;
|
||||
i4[2] = u32[u][1];
|
||||
i4[3] = llEpoch;
|
||||
#if defined(__gfx950__)
|
||||
asm volatile ("flat_store_dwordx4 %0, %1 sc0 sc1 nt" :: "v"(bufmc + ncclSymLLMaxSlots(sizeof(T))*u), "v"(i4));
|
||||
#else
|
||||
__builtin_nontemporal_store(i4, (Vec*)(bufmc + ncclSymLLMaxSlots(sizeof(T))*u));
|
||||
#endif
|
||||
// asm volatile("st.volatile.v4.u32 [%0],{%1,%3,%2,%3};" :: "l"(bufmc + ncclSymLLMaxSlots(sizeof(T))*u), "r"(u32[u][0]), "r"(u32[u][1]), "r"(llEpoch));
|
||||
}
|
||||
} else {
|
||||
union { T tmp; uint32_t u32[divUp(sizeof(T),8)][2]; };
|
||||
tmp = val;
|
||||
uint4* buf0 = ncclSymDevBase_getLLBuf(peerPtr(0, base), nRanks, block, llEpoch) + slot;
|
||||
int dr = 0;
|
||||
int r = rank;
|
||||
#pragma unroll 1
|
||||
for (; dr+8 <= nRanks; dr += 8) {
|
||||
#pragma unroll
|
||||
for (int ur=0; ur < 8; ur++) {
|
||||
uint4* buf = add4G(buf0, r*stride4G);
|
||||
#pragma unroll
|
||||
for (int u=0; u < divUp(sizeof(T),8); u++) {
|
||||
using Vec = uint32_t __attribute__((ext_vector_type(4)));
|
||||
Vec i4;
|
||||
i4[0] = u32[u][0];
|
||||
i4[1] = llEpoch;
|
||||
i4[2] = u32[u][1];
|
||||
i4[3] = llEpoch;
|
||||
#if defined(__gfx950__)
|
||||
asm volatile ("flat_store_dwordx4 %0, %1 sc0 sc1 nt" :: "v"(buf + ncclSymLLMaxSlots(sizeof(T))*u), "v"(i4));
|
||||
#else
|
||||
__builtin_nontemporal_store(i4, (Vec*)((buf + ncclSymLLMaxSlots(sizeof(T))*u)));
|
||||
#endif
|
||||
// asm volatile("st.volatile.v4.u32 [%0],{%1,%3,%2,%3};" :: "l"(buf + ncclSymLLMaxSlots(sizeof(T))*u), "r"(u32[u][0]), "r"(u32[u][1]), "r"(llEpoch));
|
||||
}
|
||||
r += 1;
|
||||
if (r == nRanks) r = 0;
|
||||
}
|
||||
}
|
||||
#pragma unroll
|
||||
for (int ur=0; ur < 8; ur++, dr++) {
|
||||
if (dr == nRanks) break;
|
||||
uint4* buf = add4G(buf0, r*stride4G);
|
||||
#pragma unroll
|
||||
for (int u=0; u < divUp(sizeof(T),8); u++) {
|
||||
using Vec = uint32_t __attribute__((ext_vector_type(4)));
|
||||
Vec i4;
|
||||
i4[0] = u32[u][0];
|
||||
i4[1] = llEpoch;
|
||||
i4[2] = u32[u][1];
|
||||
i4[3] = llEpoch;
|
||||
#if defined(__gfx950__)
|
||||
asm volatile ("flat_store_dwordx4 %0, %1 sc0 sc1 nt" :: "v"(buf + ncclSymLLMaxSlots(sizeof(T))*u), "v"(i4));
|
||||
#else
|
||||
__builtin_nontemporal_store(i4, (Vec*)(buf + ncclSymLLMaxSlots(sizeof(T))*u));
|
||||
#endif
|
||||
// asm volatile("st.volatile.v4.u32 [%0],{%1,%3,%2,%3};" :: "l"(buf + ncclSymLLMaxSlots(sizeof(T))*u), "r"(u32[u][0]), "r"(u32[u][1]), "r"(llEpoch));
|
||||
}
|
||||
r += 1;
|
||||
if (r == nRanks) r = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<int nSlotsMin, int nSlotsMax, typename T>
|
||||
__device__ void recvLL(int slot0, int nSlots, int stride, T(&elts)[nSlotsMax]) {
|
||||
uint4* buf = ncclSymDevBase_getLLBuf(base, nRanks, block, llEpoch) + slot0;
|
||||
uint4 tmp[nSlotsMax][divUp(sizeof(T),8)];
|
||||
//int spins=0;
|
||||
while (true) {
|
||||
#pragma unroll
|
||||
for (int u=0; u < nSlotsMax; u++) {
|
||||
if (u < nSlotsMin || u < nSlots) {
|
||||
#pragma unroll
|
||||
for (int v=0; v < divUp(sizeof(T),8); v++) {
|
||||
tmp[u][v] = *(buf + u * stride + v * ncclSymLLMaxSlots(sizeof(T)));
|
||||
// asm volatile("ld.volatile.v4.u32 {%0,%1,%2,%3},[%4];" : "=r"(tmp[u][v].x), "=r"(tmp[u][v].y), "=r"(tmp[u][v].z), "=r"(tmp[u][v].w) : "l"(buf + u*stride + v*ncclSymLLMaxSlots(sizeof(T))));
|
||||
}
|
||||
}
|
||||
}
|
||||
bool okAll = true;
|
||||
#pragma unroll
|
||||
for (int u=0; u < nSlotsMax; u++) {
|
||||
#pragma unroll
|
||||
for (int v=0; v < divUp(sizeof(T),8); v++) {
|
||||
if (u < nSlotsMin || u < nSlots) {
|
||||
bool ok = tmp[u][v].y == llEpoch &&
|
||||
tmp[u][v].w == llEpoch;
|
||||
okAll &= ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (__builtin_expect(okAll, true)) break;
|
||||
//if (spins++ == 10<<20) spins=0;
|
||||
}
|
||||
#pragma unroll
|
||||
for (int u=0; u < nSlotsMax; u++) {
|
||||
if (nSlotsMin <= u && u == nSlots) break;
|
||||
union { T val; uint32_t u32[divUp(sizeof(T),8)][2]; };
|
||||
#pragma unroll
|
||||
for (int v=0; v < divUp(sizeof(T),8); v++) {
|
||||
u32[v][0] = tmp[u][v].x;
|
||||
u32[v][1] = tmp[u][v].z;
|
||||
}
|
||||
elts[u] = val;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Pack, typename T, typename Red, int Unroll=8>
|
||||
__device__ Pack recvReduceLL(int slot, int stride, Red red) {
|
||||
using Acc = typename Red::EltType;
|
||||
using AccPack = BytePack<sizeof(Pack)*sizeof(Acc)/sizeof(T)>;
|
||||
AccPack acc;
|
||||
bool first = true;
|
||||
int r = 0;
|
||||
#pragma unroll 1
|
||||
for (; r+Unroll <= nRanks; r += Unroll) {
|
||||
Pack got[Unroll];
|
||||
this->template recvLL</*Min=*/Unroll>(slot + r*stride, Unroll, stride, got);
|
||||
AccPack acc0 = applyCast<T, Acc>(got[0]);
|
||||
acc = first ? acc0 : applyReduce(red, acc, acc0);
|
||||
first = false;
|
||||
#pragma unroll
|
||||
for (int i=1; i < Unroll; i++) acc = applyReduce(red, acc, applyCast<T, Acc>(got[i]));
|
||||
}
|
||||
if (r < nRanks) {
|
||||
Pack got[Unroll];
|
||||
this->template recvLL</*Min=*/1>(slot + r*stride, nRanks-r, stride, got);
|
||||
AccPack acc0 = applyCast<T, Acc>(got[0]);
|
||||
acc = first ? acc0 : applyReduce(red, acc, acc0);
|
||||
#pragma unroll
|
||||
for (int i=1; i < Unroll-1; i++) {
|
||||
if (r+i < nRanks) acc = applyReduce(red, acc, applyCast<T, Acc>(got[i]));
|
||||
}
|
||||
}
|
||||
return applyCast<Acc, T>(acc);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__device__ T recvLL(int slot) {
|
||||
T one[1];
|
||||
this->template recvLL<1, 1, T>(slot, 1, 0, one);
|
||||
return one[0];
|
||||
}
|
||||
|
||||
template<typename Coop, typename T>
|
||||
__device__ void coopRecvLL(Coop coop, int slot0, int nSlots, T* dst) {
|
||||
int me = coop.self();
|
||||
if (me < nSlots) {
|
||||
uint4* buf = ncclSymDevBase_getLLBuf(base, nRanks, block, llEpoch) + slot0 + me;
|
||||
uint4 got[divUp(sizeof(T), 8)];
|
||||
//int spins=0;
|
||||
#pragma unroll 1
|
||||
while (true) {
|
||||
#pragma unroll
|
||||
for (int u=0; u < divUp(sizeof(T), 8); u++) {
|
||||
got[u] = *((buf + u * ncclSymLLMaxSlots(sizeof(T))));
|
||||
// asm volatile("ld.volatile.v4.u32 {%0,%1,%2,%3},[%4];" : "=r"(got[u].x), "=r"(got[u].y), "=r"(got[u].z), "=r"(got[u].w) : "l"(buf + u*ncclSymLLMaxSlots(sizeof(T))));
|
||||
}
|
||||
bool ok = true;
|
||||
#pragma unroll
|
||||
for (int u=0; u < divUp(sizeof(T), 8); u++) {
|
||||
ok &= got[u].y == llEpoch;
|
||||
ok &= got[u].w == llEpoch;
|
||||
}
|
||||
if (__builtin_expect(ok, true)) break;
|
||||
//if (++spins == 10<<20) { spins=0; printf("r=%d LL spin @ ix=%d got=%d want=%d\n", rank, slot0+me, got[0].y, llEpoch); }
|
||||
}
|
||||
union { T val; uint32_t u32[divUp(sizeof(T), 8)][2]; };
|
||||
#pragma unroll
|
||||
for (int u=0; u < divUp(sizeof(T), 8); u++) {
|
||||
u32[u][0] = got[u].x;
|
||||
u32[u][1] = got[u].z;
|
||||
}
|
||||
dst[slot0 + me] = val;
|
||||
}
|
||||
fn(indexHi - indexLo, dw.nElts,
|
||||
ncclSymPtr<T>(dw.inputWin, dw.inputOff) + indexLo,
|
||||
ncclSymPtr<T>(dw.outputWin, dw.outputOff) + indexLo);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template<template<typename> typename Red, typename T, bool nvls>
|
||||
struct ncclSymAccumType { using Type = T; };
|
||||
struct ncclSymkAccumType { using Type = T; };
|
||||
|
||||
// Only Red's whose opArg is invariant w.r.t. the datatype can have a different
|
||||
// accumulator type. At the moment this excludes integer min/max, sumpostdiv,
|
||||
// and premulsum.
|
||||
template<> struct ncclSymAccumType<FuncSum, __half, false> { using Type = float; };
|
||||
template<> struct ncclSymkAccumType<FuncSum, __half, false> { using Type = float; };
|
||||
#if defined(__CUDA_BF16_TYPES_EXIST__)
|
||||
template<> struct ncclSymAccumType<FuncSum, __nv_bfloat16, false> { using Type = float; };
|
||||
template<> struct ncclSymkAccumType<FuncSum, __nv_bfloat16, false> { using Type = float; };
|
||||
#endif
|
||||
#if defined(__CUDA_FP8_TYPES_EXIST__)
|
||||
template<> struct ncclSymAccumType<FuncSum, __nv_fp8_e4m3, false> { using Type = float; };
|
||||
template<> struct ncclSymAccumType<FuncSum, __nv_fp8_e5m2, false> { using Type = float; };
|
||||
template<> struct ncclSymkAccumType<FuncSum, __nv_fp8_e4m3, false> { using Type = float; };
|
||||
template<> struct ncclSymkAccumType<FuncSum, __nv_fp8_e5m2, false> { using Type = float; };
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -1,38 +1,39 @@
|
||||
// Modification Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "symmetric.h"
|
||||
#include "sym_kernels.h"
|
||||
#include "symmetric/kernel.h"
|
||||
#include "symmetric/primitives.h"
|
||||
|
||||
template<int BytePerPack, int UnrollPacks, int UnrollPeers, typename T, typename Red>
|
||||
static __device__ void reduceDeep(
|
||||
ncclSymPrims& prim, int tn, int t, bool waitNeeded,
|
||||
Red red, char* inputRank0, char* outputHere, int32_t nIters
|
||||
ncclSymkArgsHandler const& handler, int tn, int t,
|
||||
bool waitNeeded, ncclLsaBarrierSession<ncclCoopCta>& bar,
|
||||
Red red, ncclSymPtr<char> input, ncclSymPtr<char> output, int32_t nIters
|
||||
) {
|
||||
using Pack = BytePack<BytePerPack>;
|
||||
using Acc = typename Red::EltType;
|
||||
using AccPack = BytePack<BytePerPack*sizeof(Acc)/sizeof(T)>;
|
||||
|
||||
ncclTeam world = ncclTeamWorld(handler.comm);
|
||||
int wn = tn/WARP_SIZE;
|
||||
int w = t/WARP_SIZE;
|
||||
int lane = t%WARP_SIZE;
|
||||
int const& rank = prim.rank;
|
||||
int const& nRanks = prim.nRanks;
|
||||
uint32_t const& stride4G = prim.stride4G;
|
||||
Pack* inpRank0 = (Pack*)inputRank0 + intptr_t(w)*UnrollPacks*WARP_SIZE + lane;
|
||||
Pack* outHere = (Pack*)outputHere + intptr_t(w)*UnrollPacks*WARP_SIZE + lane;
|
||||
int const& rank = handler.comm.rank;
|
||||
int const& nRanks = handler.comm.nRanks;
|
||||
ncclSymPtr<Pack> inpPacks = (ncclSymPtr<Pack>)input + intptr_t(w)*UnrollPacks*WARP_SIZE + lane;
|
||||
ncclSymPtr<Pack> outPacks = (ncclSymPtr<Pack>)output + intptr_t(w)*UnrollPacks*WARP_SIZE + lane;
|
||||
Pack acc0[UnrollPacks];
|
||||
|
||||
nIters -= w;
|
||||
if (0 < nIters) {
|
||||
#pragma unroll
|
||||
for (int u=0; u < UnrollPacks; u++) {
|
||||
acc0[u] = add4G(inpRank0, rank*stride4G)[u*WARP_SIZE];
|
||||
acc0[u] = inpPacks.peerPtr(world, rank)[u*WARP_SIZE];
|
||||
}
|
||||
}
|
||||
|
||||
if (waitNeeded) prim.barrierWait(ncclCoopCta(), /*acquire=*/false);
|
||||
if (waitNeeded) bar.wait(ncclCoopCta(), cuda::memory_order_relaxed);
|
||||
|
||||
if (0 < nIters) {
|
||||
while (true) {
|
||||
@@ -42,7 +43,7 @@ static __device__ void reduceDeep(
|
||||
{ Pack tmp1[UnrollPacks];
|
||||
#pragma unroll
|
||||
for (int u=0; u < UnrollPacks; u++) {
|
||||
tmp1[u] = add4G(inpRank0, r*stride4G)[u*WARP_SIZE];
|
||||
tmp1[u] = inpPacks.peerPtr(world, r)[u*WARP_SIZE];
|
||||
}
|
||||
#pragma unroll
|
||||
for (int u=0; u < UnrollPacks; u++) {
|
||||
@@ -68,7 +69,7 @@ static __device__ void reduceDeep(
|
||||
if (partial && ur!=0 && dr+ur == nRanks) break;
|
||||
#pragma unroll UnrollPacks
|
||||
for (int u=0; u < UnrollPacks; u++) {
|
||||
tmp1[ur][u] = add4G(inpRank0, r*stride4G)[u*WARP_SIZE];
|
||||
tmp1[ur][u] = inpPacks.peerPtr(world, r)[u*WARP_SIZE];
|
||||
}
|
||||
r += 1;
|
||||
if (r == nRanks) r = 0;
|
||||
@@ -88,17 +89,17 @@ static __device__ void reduceDeep(
|
||||
for (int u=0; u < UnrollPacks; u++) acc0[u] = applyCast<Acc, T>(acc1[u]);
|
||||
|
||||
#pragma unroll UnrollPacks
|
||||
for (int u=0; u < UnrollPacks; u++) outHere[u*WARP_SIZE] = acc0[u];
|
||||
for (int u=0; u < UnrollPacks; u++) outPacks.localPtr()[u*WARP_SIZE] = acc0[u];
|
||||
|
||||
inpRank0 += intptr_t(wn)*UnrollPacks*WARP_SIZE;
|
||||
outHere += intptr_t(wn)*UnrollPacks*WARP_SIZE;
|
||||
inpPacks += intptr_t(wn)*UnrollPacks*WARP_SIZE;
|
||||
outPacks += intptr_t(wn)*UnrollPacks*WARP_SIZE;
|
||||
nIters -= wn;
|
||||
if (nIters <= 0) break;
|
||||
|
||||
// Load data for next iteration.
|
||||
#pragma unroll
|
||||
for (int u=0; u < UnrollPacks; u++) {
|
||||
acc0[u] = add4G(inpRank0, rank*stride4G)[u*WARP_SIZE];
|
||||
acc0[u] = inpPacks.peerPtr(world, rank)[u*WARP_SIZE];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,20 +107,22 @@ static __device__ void reduceDeep(
|
||||
|
||||
template<int UnrollPeers, typename Red, typename T>
|
||||
static __device__ void reduceEnds(
|
||||
ncclSymPrims& prim, int tn, int t, Red red,
|
||||
T* inputRank0, T* outputHere, size_t nElts, uint32_t nPreElts, size_t nSufElts
|
||||
ncclSymkArgsHandler const& handler, int tn, int t, Red red,
|
||||
ncclSymPtr<T> input, ncclSymPtr<T> output,
|
||||
size_t nElts, uint32_t nPreElts, size_t nSufElts
|
||||
) {
|
||||
using Acc = typename Red::EltType;
|
||||
|
||||
int const& rank = prim.rank;
|
||||
int const& nRanks = prim.nRanks;
|
||||
uint32_t const& stride4G = prim.stride4G;
|
||||
BytePack<sizeof(T)>* inpRank0 = (BytePack<sizeof(T)>*)inputRank0;
|
||||
BytePack<sizeof(T)>* outHere = (BytePack<sizeof(T)>*)outputHere;
|
||||
ncclTeam world = ncclTeamWorld(handler.comm);
|
||||
int const& rank = handler.comm.rank;
|
||||
int const& nRanks = handler.comm.nRanks;
|
||||
|
||||
ncclSymPtr<BytePack<sizeof(T)>> inpPacks = (ncclSymPtr<BytePack<sizeof(T)>>)input;
|
||||
ncclSymPtr<BytePack<sizeof(T)>> outPacks = (ncclSymPtr<BytePack<sizeof(T)>>)output;
|
||||
#pragma unroll 1
|
||||
for (size_t i = t; i < nPreElts+nSufElts; i += tn) {
|
||||
size_t elt = i < nPreElts ? i : nElts-nSufElts-nPreElts+i;
|
||||
BytePack<sizeof(T)> acc0 = *add4G(inpRank0+elt, rank*stride4G);
|
||||
BytePack<sizeof(T)> acc0 = inpPacks.peerPtr(world, rank)[elt];
|
||||
BytePack<sizeof(Acc)> acc1;
|
||||
BytePack<sizeof(T)> tmp[UnrollPeers];
|
||||
int dr = 1;
|
||||
@@ -138,7 +141,7 @@ static __device__ void reduceEnds(
|
||||
#pragma unroll
|
||||
for (int u=0; u < UnrollPeers-partial; u++) {
|
||||
if (partial && u!=0 && dr+u == nRanks) break;
|
||||
tmp[u] = *add4G(inpRank0+elt, r*stride4G);
|
||||
tmp[u] = inpPacks.peerPtr(world, r)[elt];
|
||||
r += 1;
|
||||
if (r == nRanks) r = 0;
|
||||
}
|
||||
@@ -155,26 +158,25 @@ static __device__ void reduceEnds(
|
||||
}
|
||||
|
||||
acc0 = applyCast<Acc, T>(acc1);
|
||||
outHere[elt] = acc0;
|
||||
outPacks.localPtr()[elt] = acc0;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Red, typename T>
|
||||
static __device__ void reduce(
|
||||
ncclSymPrims& prim, int tn, int t, bool waitNeeded,
|
||||
Red red, T* input, T* output, size_t nElts
|
||||
ncclSymkArgsHandler const& handler, int tn, int t, int nBlocks,
|
||||
bool waitNeeded, ncclLsaBarrierSession<ncclCoopCta>& bar,
|
||||
Red red, ncclSymPtr<T> input, ncclSymPtr<T> output, size_t nElts
|
||||
) {
|
||||
int nRanks = prim.nRanks;
|
||||
int nBlocks = prim.nBlocks;
|
||||
// Mpve input to rank=0
|
||||
input = prim.peerPtr(0, input);
|
||||
int const& nRanks = handler.comm.nRanks;
|
||||
int const& nRanks_rcp32 = handler.nRanks_rcp32;
|
||||
uint32_t nBlocks_rcp32 = nccl::utility::idivRcp32_upto64(nBlocks);
|
||||
uint32_t nRanks_nBlocks_rcp32 = nccl::utility::imulRcp32(nRanks, nRanks_rcp32, nBlocks, nBlocks_rcp32);
|
||||
|
||||
uintptr_t inputUptr = reinterpret_cast<uintptr_t>(input);
|
||||
uintptr_t outputUptr = reinterpret_cast<uintptr_t>(output);
|
||||
uint32_t alignment = uint32_t(inputUptr - outputUptr);
|
||||
uint32_t alignment = uint32_t(input.offset - output.offset);
|
||||
size_t nBytes = nElts*sizeof(T);
|
||||
|
||||
uint32_t nPreBytes = (16u - inputUptr)%16u;
|
||||
uint32_t nPreBytes = (16u - input.offset)%16u;
|
||||
nPreBytes = min((size_t)nPreBytes, nBytes);
|
||||
uintptr_t cursor = nPreBytes;
|
||||
|
||||
@@ -184,12 +186,12 @@ static __device__ void reduce(
|
||||
constexpr int BytePerPack = 16, UnrollPacks = 4, UnrollPeers = 2;
|
||||
constexpr int BytePerChunk = MinWarpPerBlock*UnrollPacks*WARP_SIZE*BytePerPack;
|
||||
uint32_t chunks = (nBytes-cursor)/BytePerChunk;
|
||||
chunks -= imodFast32(chunks, nRanks*nBlocks, prim.nRanks_nBlocks_rcp32);
|
||||
chunks -= imodFast32(chunks, nRanks*nBlocks, nRanks_nBlocks_rcp32);
|
||||
if (chunks != 0) {
|
||||
uintptr_t cursorAfter = cursor + uintptr_t(chunks)*BytePerChunk;
|
||||
reduceDeep<BytePerPack, UnrollPacks, UnrollPeers, T>(
|
||||
prim, tn, t, waitNeeded, red,
|
||||
(char*)input + cursor, (char*)output + cursor,
|
||||
handler, tn, t, waitNeeded, bar, red,
|
||||
(ncclSymPtr<char>)input + cursor, (ncclSymPtr<char>)output + cursor,
|
||||
chunks*MinWarpPerBlock
|
||||
);
|
||||
cursor = cursorAfter;
|
||||
@@ -201,12 +203,12 @@ static __device__ void reduce(
|
||||
constexpr int BytePerPack = 4, UnrollPacks = 4, UnrollPeers = 4;
|
||||
constexpr int BytePerChunk = MinWarpPerBlock*UnrollPacks*WARP_SIZE*BytePerPack;
|
||||
uint32_t chunks = (nBytes-cursor)/BytePerChunk;
|
||||
chunks -= imodFast32(chunks, nRanks*nBlocks, prim.nRanks_nBlocks_rcp32);
|
||||
chunks -= imodFast32(chunks, nRanks*nBlocks, nRanks_nBlocks_rcp32);
|
||||
if (chunks != 0) {
|
||||
uintptr_t cursorAfter = cursor + uintptr_t(chunks)*BytePerChunk;
|
||||
reduceDeep<(sizeof(T) <= BytePerPack ? BytePerPack : 0), UnrollPacks, UnrollPeers, T>(
|
||||
prim, tn, t, waitNeeded, red,
|
||||
(char*)input + cursor, (char*)output + cursor,
|
||||
handler, tn, t, waitNeeded, bar, red,
|
||||
(ncclSymPtr<char>)input + cursor, (ncclSymPtr<char>)output + cursor,
|
||||
chunks*MinWarpPerBlock
|
||||
);
|
||||
cursor = cursorAfter;
|
||||
@@ -214,42 +216,47 @@ static __device__ void reduce(
|
||||
}
|
||||
}
|
||||
|
||||
if (waitNeeded) prim.barrierWait(ncclCoopCta(), /*acquire=*/false);
|
||||
if (waitNeeded) bar.wait(ncclCoopCta(), cuda::memory_order_relaxed);
|
||||
|
||||
constexpr int UnrollPeers = 8;
|
||||
size_t nSufElts = (nBytes-cursor)/sizeof(T);
|
||||
reduceEnds<UnrollPeers>(prim, tn, t, red, input, output, nElts, nPreBytes/sizeof(T), nSufElts);
|
||||
reduceEnds<UnrollPeers>(handler, tn, t, red, input, output, nElts, nPreBytes/sizeof(T), nSufElts);
|
||||
}
|
||||
|
||||
|
||||
template<template<typename> typename Red, typename T>
|
||||
__device__ __forceinline__ void ncclSymRun_ReduceScatter_LD(ncclSymDevArgs const* args) {
|
||||
ncclSymPrims prim(args->comm, ncclSymPrims_UseBarrier);
|
||||
Red<typename ncclSymAccumType<Red, T, /*nvls=*/false>::Type> red(args->redOpArg);
|
||||
__device__ __forceinline__ void ncclSymkRun_ReduceScatter_LD(ncclSymkDevWorkArgs const* args) {
|
||||
ncclSymkArgsHandler handler{args};
|
||||
ncclLsaBarrierSession<ncclCoopCta> bar{
|
||||
ncclCoopCta(), handler.comm, ncclTeamTagLsa(), blockIdx.x
|
||||
};
|
||||
Red<typename ncclSymkAccumType<Red, T, /*nvls=*/false>::Type> red(handler.devWork->redOpArg);
|
||||
int const& rank = handler.comm.rank;
|
||||
|
||||
// Round robin warps over blocks.
|
||||
int t = flattenIx(threadIdx.x%WARP_SIZE, WARP_SIZE,
|
||||
prim.block, prim.nBlocks,
|
||||
threadIdx.x/WARP_SIZE, blockDim.x/WARP_SIZE);
|
||||
int tn = prim.nBlocks*blockDim.x;
|
||||
bar.arrive(ncclCoopCta(), cuda::memory_order_relaxed);
|
||||
|
||||
prim.barrierArrive(ncclCoopCta(), /*release=*/false);
|
||||
//prim.barrierWait(ncclCoopCta(), /*acquire=*/false);
|
||||
bool waitNeeded = true;
|
||||
handler.forEachWork<T>(
|
||||
[&]__device__(int block, int nBlocks, size_t nElts, size_t nAllElts,
|
||||
ncclSymPtr<T> input, ncclSymPtr<T> output) {
|
||||
// Round robin warps over blocks.
|
||||
int t = flattenIx(threadIdx.x%WARP_SIZE, WARP_SIZE,
|
||||
block, nBlocks,
|
||||
threadIdx.x/WARP_SIZE, blockDim.x/WARP_SIZE);
|
||||
int tn = nBlocks*blockDim.x;
|
||||
|
||||
reduce(prim, tn, t, /*waitNeeded=*/true, red, (T*)args->input + prim.rank*args->nElts, (T*)args->output, args->nElts);
|
||||
reduce(handler, tn, t, nBlocks, waitNeeded, bar, red, input + rank*nElts, output, nElts);
|
||||
|
||||
prim.barrierArrive(ncclCoopCta(), /*release=*/false);
|
||||
prim.barrierWait(ncclCoopCta(), /*acquire=*/false);
|
||||
waitNeeded = false;
|
||||
}
|
||||
);
|
||||
|
||||
bar.sync(ncclCoopCta(), cuda::memory_order_relaxed);
|
||||
}
|
||||
|
||||
|
||||
template<typename Red, typename T>
|
||||
static __device__ void reduceMultimem(
|
||||
ncclSymPrims& prim, int tn, int t, Red red, T* input, T* output, size_t nElts
|
||||
int tn, int t, Red red, T* input, T* output, size_t nElts
|
||||
) {
|
||||
// Mpve input to multimem
|
||||
input = prim.multimemPtr(input);
|
||||
|
||||
uintptr_t inputUptr = reinterpret_cast<uintptr_t>(input);
|
||||
uintptr_t outputUptr = reinterpret_cast<uintptr_t>(output);
|
||||
size_t nBytes = nElts*sizeof(T);
|
||||
@@ -294,41 +301,52 @@ static __device__ void reduceMultimem(
|
||||
uintptr_t cursor = i < nPreBytes ? i : nBytes-nSufBytes+(i-nPreBytes);
|
||||
BytePack<sizeof(T)> val = applyLoadMultimem<Red, sizeof(T)>(red, inputUptr + cursor);
|
||||
*reinterpret_cast<BytePack<sizeof(T)>*>(outputUptr + cursor) = val;
|
||||
cursor += tn*sizeof(T);
|
||||
}
|
||||
}
|
||||
|
||||
template<template<typename> typename Red, typename T>
|
||||
__device__ __forceinline__ void ncclSymRun_ReduceScatter_LDMC(ncclSymDevArgs const* args) {
|
||||
ncclSymPrims prim(args->comm, ncclSymPrims_UseBarrier|ncclSymPrims_UseMultimem);
|
||||
Red<typename ncclSymAccumType<Red, T, /*nvls=*/true>::Type> red(args->redOpArg);
|
||||
__device__ __forceinline__ void ncclSymkRun_ReduceScatter_LDMC(ncclSymkDevWorkArgs const* args) {
|
||||
ncclSymkArgsHandler handler{args};
|
||||
ncclLsaBarrierSession<ncclCoopCta> bar{
|
||||
ncclCoopCta(), handler.comm, ncclTeamTagLsa(), blockIdx.x, /*multimem=*/true
|
||||
};
|
||||
Red<typename ncclSymkAccumType<Red, T, /*nvls=*/true>::Type> red(handler.devWork->redOpArg);
|
||||
|
||||
// Round robin warps over blocks.
|
||||
int t = flattenIx(threadIdx.x%WARP_SIZE, WARP_SIZE,
|
||||
prim.block, prim.nBlocks,
|
||||
threadIdx.x/WARP_SIZE, blockDim.x/WARP_SIZE);
|
||||
int tn = prim.nBlocks*blockDim.x;
|
||||
int const& rank = handler.comm.rank;
|
||||
auto const& multimem = handler.comm.lsaMultimem;
|
||||
|
||||
prim.barrierArrive(ncclCoopCta(), /*release=*/false);
|
||||
prim.barrierWait(ncclCoopCta(), /*acquire=*/false);
|
||||
bar.sync(ncclCoopCta(), cuda::memory_order_relaxed);
|
||||
|
||||
reduceMultimem(prim, tn, t, red, (T*)args->input + prim.rank*args->nElts, (T*)args->output, args->nElts);
|
||||
handler.forEachWork<T>(
|
||||
[&]__device__(int block, int nBlocks, size_t nElts, size_t nAllElts,
|
||||
ncclSymPtr<T> input, ncclSymPtr<T> output) {
|
||||
// Round robin warps over blocks.
|
||||
int t = flattenIx(threadIdx.x%WARP_SIZE, WARP_SIZE,
|
||||
block, nBlocks,
|
||||
threadIdx.x/WARP_SIZE, blockDim.x/WARP_SIZE);
|
||||
int tn = nBlocks*blockDim.x;
|
||||
|
||||
prim.barrierArrive(ncclCoopCta(), /*release=*/false);
|
||||
prim.barrierWait(ncclCoopCta(), /*acquire=*/false);
|
||||
reduceMultimem(tn, t, red, input.multimemPtr(multimem) + rank*nElts, output.localPtr(), nElts);
|
||||
}
|
||||
);
|
||||
|
||||
bar.sync(ncclCoopCta(), cuda::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// T is user type, EltType is the most aligned type
|
||||
template<typename T, typename Red, typename EltType>
|
||||
__device__ __forceinline__ void ncclSymRun_ReduceScatter_LL_body(
|
||||
ncclSymPrims &prim, Red red, EltType* input, EltType* output, int nElts, int nPacks, int nStrideElts) {
|
||||
__device__ __forceinline__ void ncclSymkRun_ReduceScatter_LL_body(
|
||||
ncclSymkArgsHandler& handler, ncclLLA2ASession<ncclCoopCta>& lla2a,
|
||||
Red red, EltType* input, EltType* output, int nElts, int nPacks, int nStrideElts) {
|
||||
using Pack = BytePack<8>;
|
||||
using Acc = typename Red::EltType;
|
||||
using AccPack = BytePack<8*sizeof(Acc)/sizeof(T)>;
|
||||
constexpr int EltPerPack = 8/sizeof(EltType);
|
||||
|
||||
int nRanks = prim.nRanks;
|
||||
int rank = prim.rank;
|
||||
int const& nRanks = handler.comm.nRanks;
|
||||
int const& rank = handler.comm.rank;
|
||||
int t = threadIdx.x;
|
||||
int tn = ncclSymMaxThreads;
|
||||
constexpr int tn = ncclSymkMaxThreads;
|
||||
ncclCoopCta cta;
|
||||
|
||||
#pragma unroll 1
|
||||
@@ -342,17 +360,25 @@ __device__ __forceinline__ void ncclSymRun_ReduceScatter_LL_body(
|
||||
#pragma unroll 1
|
||||
for (int i = t; i < nRanks*nIterPacks; i += tn) {
|
||||
Pack got = loadPack<Pack>(input + peer*nStrideElts, pack*EltPerPack, nElts);
|
||||
prim.sendLL(peer, rank*nIterPacks + pack, got);
|
||||
lla2a.send(peer, rank*nIterPacks + pack, got);
|
||||
peer += tn_div_nPacks;
|
||||
pack += tn_mod_nPacks;
|
||||
if (nIterPacks <= pack) { peer += 1; pack -= nIterPacks; }
|
||||
}
|
||||
|
||||
if (t < nIterPacks) {
|
||||
Pack got = prim.template recvReduceLL<Pack, T>(t, nIterPacks, red);
|
||||
storePack(output, t*EltPerPack, nElts, got);
|
||||
AccPack got = lla2a.template recvReduce</*Unroll=*/8, Pack>(
|
||||
/*slotStart=*/t, /*slotCount=*/nRanks, /*slotStride=*/nIterPacks,
|
||||
/*eltToAcc=*/[&] __device__ (Pack x)->AccPack {
|
||||
return applyCast<T, Acc>(x);
|
||||
},
|
||||
/*reduce=*/[&] __device__ (AccPack a, AccPack b)->AccPack {
|
||||
return applyReduce(red, a, b);
|
||||
}
|
||||
);
|
||||
storePack(output, t*EltPerPack, nElts, applyCast<Acc, T>(got));
|
||||
}
|
||||
prim.endLL(cta);
|
||||
lla2a.endEpoch(cta);
|
||||
|
||||
input += tn*EltPerPack;
|
||||
output += tn*EltPerPack;
|
||||
@@ -360,31 +386,34 @@ __device__ __forceinline__ void ncclSymRun_ReduceScatter_LL_body(
|
||||
nPacks -= tn;
|
||||
}
|
||||
}
|
||||
template<template<typename> typename Red, typename T>
|
||||
__device__ __forceinline__ void ncclSymRun_ReduceScatter_LL(ncclSymDevArgs const* args) {
|
||||
ncclSymPrims prim(args->comm, ncclSymPrims_UseLL);
|
||||
Red<typename ncclSymAccumType<Red, T, /*nvls=*/false>::Type> red(args->redOpArg);
|
||||
|
||||
template<template<typename> typename Red, typename T>
|
||||
__device__ __forceinline__ void ncclSymkRun_ReduceScatter_LL(ncclSymkDevWorkArgs const* args) {
|
||||
ncclSymkArgsHandler handler{args};
|
||||
ncclLLA2ASession<ncclCoopCta> lla2a(
|
||||
ncclCoopCta(), handler.comm, ncclTeamLsa(handler.comm), handler.lsaLLA2A, blockIdx.x, ncclSymkMaxThreads
|
||||
);
|
||||
Red<typename ncclSymkAccumType<Red, T, /*nvls=*/false>::Type> red(handler.devWork->redOpArg);
|
||||
using Pack = BytePack<8>;
|
||||
constexpr int EltPerPack = 8/sizeof(T);
|
||||
int nAllElts = args->nElts;
|
||||
int nAllPacks = divUp(nAllElts, EltPerPack);
|
||||
uint32_t nPackPerBlock, nPackModBlock;
|
||||
idivmodFast32(&nPackPerBlock, &nPackModBlock, nAllPacks, prim.nBlocks, prim.nBlocks_rcp32);
|
||||
int blockPackBegin = prim.block*nPackPerBlock + minval<int>(prim.block, nPackModBlock);
|
||||
int blockPackEnd = blockPackBegin + nPackPerBlock + (prim.block < nPackModBlock ? 1 : 0);
|
||||
int nPacks = blockPackEnd - blockPackBegin;
|
||||
int nElts = nAllElts - blockPackBegin*EltPerPack;
|
||||
nElts = min(nElts, nPacks*EltPerPack);
|
||||
T* input = (T*)args->input + blockPackBegin*EltPerPack;
|
||||
T* output = (T*)args->output + blockPackBegin*EltPerPack;
|
||||
|
||||
uint32_t lowBits = args->nElts*sizeof(T);
|
||||
lowBits |= (uint32_t)reinterpret_cast<uintptr_t>(args->input);
|
||||
lowBits |= (uint32_t)reinterpret_cast<uintptr_t>(args->output);
|
||||
if (__builtin_expect(lowBits%8 == 0, true)) {
|
||||
ncclSymRun_ReduceScatter_LL_body<T>(prim, red, (Pack*)input, (Pack*)output, nPacks, nPacks, nAllElts/EltPerPack);
|
||||
} else {
|
||||
ncclSymRun_ReduceScatter_LL_body<T>(prim, red, input, output, nElts, nPacks, nAllElts);
|
||||
}
|
||||
handler.singleWork<T>(
|
||||
[&]__device__(int nElts, int nAllElts,
|
||||
ncclSymPtr<T> inputPtr, ncclSymPtr<T> outputPtr) {
|
||||
int nPacks = divUp(nElts, EltPerPack);
|
||||
|
||||
T* input = (T*)inputPtr.localPtr();
|
||||
T* output = (T*)outputPtr.localPtr();
|
||||
|
||||
uint32_t lowBits = nElts*sizeof(T);
|
||||
lowBits |= (uintptr_t)input;
|
||||
lowBits |= (uintptr_t)output;
|
||||
if (__builtin_expect(lowBits%8 == 0, true)) {
|
||||
ncclSymkRun_ReduceScatter_LL_body<T>(handler, lla2a, red, (Pack*)input, (Pack*)output,
|
||||
nPacks, nPacks, divUp(nAllElts, EltPerPack));
|
||||
} else {
|
||||
ncclSymkRun_ReduceScatter_LL_body<T>(handler, lla2a, red, input, output, nElts, nPacks, nAllElts);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
+417
-215
@@ -22,6 +22,9 @@
|
||||
#include "profiler.h"
|
||||
#include "transport.h"
|
||||
#include "register_inline.h"
|
||||
#include "ce_coll.h"
|
||||
#include "nvtx.h"
|
||||
#include "scheduler.h"
|
||||
#include "common.h"
|
||||
#include "api_trace.h"
|
||||
|
||||
@@ -248,6 +251,7 @@ static void finishPlan(struct ncclComm* comm, struct ncclKernelPlan* plan) {
|
||||
size_t workBytes = plan->workBytes;
|
||||
size_t batchBytes = plan->nWorkBatches*sizeof(struct ncclDevWorkBatch);
|
||||
|
||||
if (plan->isSymColl) return;
|
||||
#if defined(__HIP_PLATFORM_AMD__) || defined(__HIPCC__)
|
||||
#else
|
||||
plan->threadPerBlock = std::max(plan->threadPerBlock, 256 /*NCCL_MIN_NTHREADS*/);
|
||||
@@ -364,7 +368,6 @@ bool gfx9CheapFenceOff(const ncclDevWorkColl& devWork, bool disabledByPrecheck){
|
||||
|
||||
ncclResult_t ncclTasksRegAndEnqueue(struct ncclComm* comm) {
|
||||
struct ncclKernelPlanner* planner = &comm->planner;
|
||||
if (planner->isSymColl) return ncclSuccess;
|
||||
struct ncclTaskColl *task;
|
||||
task = ncclIntruQueueHead(&planner->collTaskQueue);
|
||||
while (task != nullptr) {
|
||||
@@ -448,6 +451,7 @@ next:
|
||||
ncclResult_t ncclPrepareTasks(struct ncclComm* comm, bool* algoNeedConnect, bool* needConnect, ncclSimInfo_t* simInfo) {
|
||||
struct ncclKernelPlanner* planner = &comm->planner;
|
||||
planner->persistent = ncclCudaGraphValid(planner->capturingGraph);
|
||||
|
||||
// Tasks from the sorter come out ordered size descending.
|
||||
struct ncclTaskColl* task = ncclTaskCollSorterDequeueAll(&planner->collSorter);
|
||||
// Tasks are assembled by (fn,op,ty) size ascending.
|
||||
@@ -456,36 +460,8 @@ ncclResult_t ncclPrepareTasks(struct ncclComm* comm, bool* algoNeedConnect, bool
|
||||
int fnOpTyIndices[ncclNumFuncs*ncclNumDevRedOps*ncclNumTypes];
|
||||
int fnOpTyCount = 0;
|
||||
|
||||
if (comm->nNodes == 1 && planner->nTasksColl == 1 && planner->nTasksP2p == 0) {
|
||||
void* sendSymPtr;
|
||||
void* recvSymPtr;
|
||||
struct ncclReg* sendReg;
|
||||
struct ncclReg* recvReg;
|
||||
size_t size = task->count*ncclTypeSize(task->datatype);
|
||||
NCCLCHECK(ncclRegFindSymmetric(comm, task->sendbuff, size, &sendSymPtr, &sendReg));
|
||||
NCCLCHECK(ncclRegFindSymmetric(comm, task->recvbuff, size, &recvSymPtr, &recvReg));
|
||||
bool implemented = ncclSymImplemented(task->func, task->opDev.op, task->datatype);
|
||||
|
||||
if (sendReg && recvReg && (sendReg->winFlags & recvReg->winFlags & NCCL_WIN_COLL_SYMMETRIC) && implemented) {
|
||||
enum ncclSymKernelId kernel;
|
||||
int nChannels, nWarps;
|
||||
float estTimeUs = 1.e18;
|
||||
NCCLCHECK(ncclSymPickKernel(comm, task->func, task->opDev.op, task->datatype, task->count, &estTimeUs, &kernel, &nChannels, &nWarps));
|
||||
|
||||
// We should only use symmetric kernel if it beats the asymmetric kernel. But the
|
||||
// perf model accuracy from asymmetric kernels is too inaccurate and reports too high
|
||||
// of a bandwidth. For now just always use symmetric if available.
|
||||
if (kernel != ncclSymKernelId_Count) {
|
||||
task->sendbuff = sendSymPtr;
|
||||
task->recvbuff = recvSymPtr;
|
||||
task->devFuncId = (int)kernel;
|
||||
task->nMaxChannels = nChannels;
|
||||
task->nWarps = nWarps;
|
||||
ncclIntruQueueEnqueue(&planner->collTaskQueue, task);
|
||||
planner->isSymColl = true;
|
||||
return ncclSuccess;
|
||||
}
|
||||
}
|
||||
if (comm->symmetricSupport) {
|
||||
NCCLCHECK(ncclMakeSymmetricTaskList(comm, task, &planner->collSymTaskQueue, &task));
|
||||
}
|
||||
|
||||
// Walk the size sorted tasks, binning them by (fn,op,ty).
|
||||
@@ -677,7 +653,7 @@ static ncclResult_t scheduleCollTasksToPlan(
|
||||
size_t trafficBytes[2*2] = {0, 0, 0, 0}; // [collnet][nvls]
|
||||
int nChannels[2*2] = {0, 0, 0, 0}; // [collnet][nvls]
|
||||
int const nMaxChannels[2*2] = {comm->nChannels, comm->nvlsChannels, // [collnet][nvls]
|
||||
comm->nChannels, comm->nvlsChannels};
|
||||
comm->nChannels, std::min(comm->nChannels, comm->nvlsChannels)};
|
||||
constexpr size_t MinTrafficPerChannel = 16 << 10; // 16K traffic as minimal
|
||||
do {
|
||||
size_t workBytes = 0;
|
||||
@@ -888,6 +864,7 @@ static ncclResult_t scheduleCollTasksToPlan(
|
||||
}
|
||||
proxyOp->eActivationMask = task->eActivationMask;
|
||||
proxyOp->incWorkCounter = true;
|
||||
proxyOp->nChannels = nChannels;
|
||||
proxyOp->connIndex = 0;
|
||||
if (task->protocol == NCCL_PROTO_SIMPLE && task->algorithm == NCCL_ALGO_RING) {
|
||||
if (comm->useIntraNet && nBytes > rcclParamIntraNetThreshold()) {
|
||||
@@ -920,6 +897,8 @@ static ncclResult_t scheduleCollTasksToPlan(
|
||||
plan->kernelFn = ncclKerns[ncclGetKernelIndex(comm)].kernelFn;
|
||||
plan->kernelSpecialized = ncclKerns[ncclGetKernelIndex(comm)].specialized;
|
||||
}
|
||||
// Profiler
|
||||
plan->groupApiEventHandle = task->groupApiEventHandle;
|
||||
|
||||
if (comm->rank == 0) {
|
||||
INFO(NCCL_TUNING, "%s: %ld Bytes -> Algo %s proto %s channel{Lo..Hi}={%d..%d}",
|
||||
@@ -993,8 +972,9 @@ static ncclResult_t addP2pToPlan(
|
||||
int sendRank, void* sendAddr, ssize_t sendBytes,
|
||||
int recvRank, void* recvAddr, ssize_t recvBytes,
|
||||
uint64_t sendOpCount, uint64_t recvOpCount,
|
||||
struct ncclTaskP2p** p2pTasks
|
||||
const int planTotalTasks[], struct ncclTaskP2p** p2pTasks
|
||||
) {
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
int connIndex[2] = {1, 1};
|
||||
bool selfSend = (sendRank == comm->rank);
|
||||
// recv: dir=0, send: dir=1
|
||||
@@ -1012,6 +992,8 @@ static ncclResult_t addP2pToPlan(
|
||||
//replacing line below with ncclP2pChannelBaseForRound(comm, p2pRound, batchP2P) can cause issues due to ncclP2pChannelBaseForRound calling the same routine
|
||||
//channel base computed in taskAppend and here must be the same, but in taskAppend the call happens once and is cached for later usage, which is why it wouldn't be consistent with the call below
|
||||
uint8_t base = ncclP2pChannelBaseForRound(comm, p2pRound, batchP2PEnableEnv);
|
||||
struct ncclProxyOp proxyOps[2] = {};
|
||||
int nProxyOps = selfSend ? 0 : 2;
|
||||
if (comm->p2pNet) {
|
||||
for (int dir = 0; dir <= 1; dir++) {
|
||||
if (bytes[dir] > rcclParamP2pNetThreshold())
|
||||
@@ -1072,7 +1054,7 @@ static ncclResult_t addP2pToPlan(
|
||||
bool pxnUsed = !ncclPxnDisable(comm) && comm->isAllNvlink && comm->maxLocalRanks > 1;
|
||||
if (bytes[dir] > 0 && proxySameProcess[dir] && protocol[dir] == NCCL_PROTO_SIMPLE && (!pxnUsed)) {
|
||||
int regFlag = 0;
|
||||
NCCLCHECK(ncclCalloc(&handles[dir], nChannelsMax));
|
||||
NCCLCHECKGOTO(ncclCalloc(&handles[dir], nChannelsMax), ret, cleanup);
|
||||
for (int part = 0; part < nChannelsMax; part++) {
|
||||
int channelId = ncclP2pChannelForPart(comm->p2pnChannels, base, part, nChannelsMax, comm->nNodes);
|
||||
struct ncclChannelPeer** channelPeers = comm->channels[channelId].peers;
|
||||
@@ -1095,7 +1077,7 @@ static ncclResult_t addP2pToPlan(
|
||||
void* regAddr = NULL;
|
||||
if (conn->conn.flags & (NCCL_P2P_WRITE | NCCL_P2P_READ)) {
|
||||
// We require users registering buffers on both sides
|
||||
NCCLCHECK(ncclRegisterP2pIpcBuffer(comm, addrs[dir], bytes[dir], peerRank, ®Flag, ®Addr, &plan->cleanupQueue));
|
||||
NCCLCHECKGOTO(ncclRegisterP2pIpcBuffer(comm, addrs[dir], bytes[dir], peerRank, ®Flag, ®Addr, &plan->cleanupQueue), ret, cleanup);
|
||||
if (regFlag) {
|
||||
if (dir == 0 && (conn->conn.flags & NCCL_P2P_WRITE)) recvAddr = regAddr;
|
||||
else if (dir == 1 && (conn->conn.flags & NCCL_P2P_READ)) sendAddr = regAddr;
|
||||
@@ -1120,14 +1102,17 @@ static ncclResult_t addP2pToPlan(
|
||||
if (p2pTasks[dir]) p2pTasks[dir]->nChannels = nChannels[dir];
|
||||
}
|
||||
|
||||
struct ncclWorkList* workNode = ncclMemoryStackAllocInlineArray<ncclWorkList, ncclDevWorkP2p>(&comm->memScoped, 1);
|
||||
struct ncclWorkList* workNode;
|
||||
workNode = ncclMemoryStackAllocInlineArray<ncclWorkList, ncclDevWorkP2p>(&comm->memScoped, 1);
|
||||
workNode->workType = ncclDevWorkTypeP2p;
|
||||
workNode->size = sizeof(struct ncclDevWorkP2p);
|
||||
ncclIntruQueueEnqueue(&plan->workQueue, workNode);
|
||||
uint32_t workOffset = plan->workBytes;
|
||||
uint32_t workOffset;
|
||||
workOffset = plan->workBytes;
|
||||
plan->workBytes += sizeof(struct ncclDevWorkP2p);
|
||||
|
||||
struct ncclDevWorkP2p* work = (struct ncclDevWorkP2p*)(workNode+1);
|
||||
struct ncclDevWorkP2p* work;
|
||||
work = (struct ncclDevWorkP2p*)(workNode+1);
|
||||
work->nP2pChannels = comm->p2pnChannels;
|
||||
work->channelBase = base;
|
||||
work->nSendChannels = nChannels[1];
|
||||
@@ -1152,8 +1137,6 @@ static ncclResult_t addP2pToPlan(
|
||||
work->recvConnIndex = connIndex[0];
|
||||
work->recvOpCount = recvOpCount;
|
||||
|
||||
struct ncclProxyOp proxyOps[2] = {};
|
||||
int nProxyOps = selfSend ? 0 : 2;
|
||||
for (int dir=0; dir < nProxyOps; dir++) {
|
||||
struct ncclProxyOp* op = &proxyOps[dir];
|
||||
op->root = dir ? sendRank : recvRank;
|
||||
@@ -1166,6 +1149,7 @@ static ncclResult_t addP2pToPlan(
|
||||
op->chunkSize = chunkSize[dir];
|
||||
op->reg = netRegistered[dir];
|
||||
op->coll = p2pTasks[dir] ? p2pTasks[dir]->func : 0;
|
||||
op->collAPI = p2pTasks[dir] ? p2pTasks[dir]->collAPI : 0;
|
||||
op->task.p2p = p2pTasks[dir];
|
||||
op->rank = comm->rank;
|
||||
op->eActivationMask = p2pTasks[dir] ? p2pTasks[dir]->eActivationMask : 0;
|
||||
@@ -1178,6 +1162,15 @@ static ncclResult_t addP2pToPlan(
|
||||
}
|
||||
|
||||
nChannelsMax = std::max(nChannels[0], nChannels[1]);
|
||||
// Determine how many peers this plan will target concurrently. Make a
|
||||
// simplifying assumption that each task targets a different peer.
|
||||
// Each task is striped across 'nChannelsMax' of 'p2pnChannels' channels.
|
||||
// Each channel runs up to NCCL_MAX_DEV_WORK_P2P_PER_BATCH tasks concurrently.
|
||||
int maxConcurrent;
|
||||
int concurrentTasks[2];
|
||||
maxConcurrent = comm->p2pnChannels / nChannelsMax * NCCL_MAX_DEV_WORK_P2P_PER_BATCH;
|
||||
concurrentTasks[0] = std::min(planTotalTasks[0], maxConcurrent);
|
||||
concurrentTasks[1] = std::min(planTotalTasks[1], maxConcurrent);
|
||||
for (int part=0; part < nChannelsMax; part++) {
|
||||
int incWorkCounter = -1;
|
||||
int channelId = ncclP2pChannelForPart(comm->p2pnChannels, base, part, comm->p2pnChannelsPerPeer, comm->nNodes);
|
||||
@@ -1234,13 +1227,17 @@ static ncclResult_t addP2pToPlan(
|
||||
// equal one plus the batch index this p2p settled in.
|
||||
proxyOps[dir].channelId = channelId;
|
||||
proxyOps[dir].opCount = uint64_t(comm->planner.wipPlan.channels[channelId].nWorkBatchesP2p)<<1 | 1;
|
||||
NCCLCHECK(addProxyOpIfNeeded(comm, plan, &proxyOps[dir]));
|
||||
NCCLCHECK(addProfilerProxyOpIfNeeded(comm, plan, &proxyOps[dir]));
|
||||
proxyOps[dir].nChannels = nChannels[dir];
|
||||
proxyOps[dir].nPeers = concurrentTasks[dir];
|
||||
NCCLCHECKGOTO(addProxyOpIfNeeded(comm, plan, &proxyOps[dir]), ret, cleanup);
|
||||
NCCLCHECKGOTO(addProfilerProxyOpIfNeeded(comm, plan, &proxyOps[dir]), ret, cleanup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ncclSuccess;
|
||||
cleanup:
|
||||
free(handles[0]);
|
||||
free(handles[1]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int calcP2pChannelCount(size_t totalSize, int minChannels, int maxChannels, size_t minSize, size_t maxSize) {
|
||||
@@ -1275,6 +1272,8 @@ static ncclResult_t scheduleP2pTasksToPlan(
|
||||
// Try to use all channels, but one channel per operation.
|
||||
while (nChannelsMin*nRanks > comm->p2pnChannels && nChannelsMin > 1) nChannelsMin /= 2;
|
||||
|
||||
// Save the total count of send/recv tasks in the plan
|
||||
int planTotalTasks[2] = {comm->planner.nTasksP2pRecv, comm->planner.nTasksP2pSend};
|
||||
while (comm->planner.nTasksP2p != 0) {
|
||||
for (int round=0; round < nRanks; round++) {
|
||||
int sendRank = comm->p2pSchedule[round].sendRank;
|
||||
@@ -1306,22 +1305,30 @@ static ncclResult_t scheduleP2pTasksToPlan(
|
||||
ncclMemoryPoolFree(&comm->memPool_ncclTaskP2p, send);
|
||||
ncclMemoryPoolFree(&comm->memPool_ncclTaskP2p, recv);
|
||||
comm->planner.nTasksP2p -= 2;
|
||||
comm->planner.nTasksP2pSend -= 1;
|
||||
comm->planner.nTasksP2pRecv -= 1;
|
||||
} else {
|
||||
// Ensure room for worst case of one new batch per channel.
|
||||
if (!testBudget(budget, plan->nWorkBatches+nChannelsMax, plan->workBytes + sizeof(struct ncclDevWorkP2p))) {
|
||||
return ncclSuccess;
|
||||
}
|
||||
struct ncclTaskP2p* p2pTasks[2] = { recv, send };
|
||||
NCCLCHECK(addP2pToPlan(comm, plan, nChannelsMin, nChannelsMax, round, sendRank, sendBuff, sendBytes, recvRank, recvBuff, recvBytes, send ? send->opCount : 0, recv ? recv->opCount : 0, p2pTasks));
|
||||
NCCLCHECK(addP2pToPlan(comm, plan, nChannelsMin, nChannelsMax, round, sendRank, sendBuff, sendBytes, recvRank, recvBuff, recvBytes, send ? send->opCount : 0, recv ? recv->opCount : 0, planTotalTasks, p2pTasks));
|
||||
if (send != nullptr) {
|
||||
ncclIntruQueueDequeue(&peers[sendRank].sendQueue);
|
||||
// Profiler - We can overwrite groupAPI event handles here since all operations here belong to the same group
|
||||
plan->groupApiEventHandle = send->groupApiEventHandle;
|
||||
ncclIntruQueueEnqueue(&plan->p2pTaskQueue, send);
|
||||
comm->planner.nTasksP2p -= 1;
|
||||
comm->planner.nTasksP2pSend -= 1;
|
||||
}
|
||||
if (recv != nullptr) {
|
||||
ncclIntruQueueDequeue(&peers[recvRank].recvQueue);
|
||||
// Profiler - We can overwrite groupAPI event handles here since all operations here belong to the same group
|
||||
plan->groupApiEventHandle = recv->groupApiEventHandle;
|
||||
ncclIntruQueueEnqueue(&plan->p2pTaskQueue, recv);
|
||||
comm->planner.nTasksP2p -= 1;
|
||||
comm->planner.nTasksP2pRecv -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1372,7 +1379,7 @@ namespace {
|
||||
}
|
||||
|
||||
static ncclResult_t uploadWork(struct ncclComm* comm, struct ncclKernelPlan* plan) {
|
||||
if (plan->isSymColl) return ncclSuccess;
|
||||
if (plan->isSymColl || plan->isCeColl) return ncclSuccess;
|
||||
|
||||
size_t workBytes = plan->workBytes;
|
||||
size_t batchBytes = plan->nWorkBatches*sizeof(struct ncclDevWorkBatch);
|
||||
@@ -1544,7 +1551,7 @@ static ncclResult_t hostStreamPlanTask(struct ncclComm* comm, struct ncclKernelP
|
||||
}
|
||||
|
||||
static void HIPRT_CB hostStreamPlanCallback(void *plan_) {
|
||||
NVTX3_FUNC_RANGE_IN(nccl_domain);
|
||||
NCCL_NVTX3_FUNC_RANGE;
|
||||
struct ncclKernelPlan* plan = (struct ncclKernelPlan*)plan_;
|
||||
ncclResult_t result = hostStreamPlanTask(plan->comm, plan);
|
||||
if (result != ncclSuccess) {
|
||||
@@ -1565,6 +1572,9 @@ static ncclResult_t reclaimPlan(struct ncclComm* comm, struct ncclCommCallback*
|
||||
CUDACHECK(cudaThreadExchangeStreamCaptureMode(&mode));
|
||||
}
|
||||
}
|
||||
if (plan->isSymColl) {
|
||||
free(plan->kernelSymArgs);
|
||||
}
|
||||
// Free coll tasks
|
||||
struct ncclTaskColl* ct = ncclIntruQueueHead(&plan->collTaskQueue);
|
||||
while (ct != nullptr) {
|
||||
@@ -1645,7 +1655,9 @@ ncclResult_t ncclLaunchPrepare(struct ncclComm* comm) {
|
||||
planner->persistent = persistent;
|
||||
int nPlans = 0;
|
||||
|
||||
if (planner->nTasksColl + planner->nTasksP2p != 0) {
|
||||
if (planner->nTasksColl + planner->nTasksP2p != 0 ||
|
||||
!ncclIntruQueueEmpty(&planner->collSymTaskQueue) ||
|
||||
!ncclIntruQueueEmpty(&planner->collCeTaskQueue)) {
|
||||
do {
|
||||
memset(&planner->wipPlan, 0, sizeof(planner->wipPlan));
|
||||
|
||||
@@ -1657,55 +1669,55 @@ ncclResult_t ncclLaunchPrepare(struct ncclComm* comm) {
|
||||
plan->workStorageType = persistent ? ncclDevWorkStorageTypePersistent
|
||||
: ncclDevWorkStorageTypeFifo;
|
||||
|
||||
if (planner->isSymColl) {
|
||||
plan->workStorageType = ncclDevWorkStorageTypeArgs;
|
||||
if (!ncclIntruQueueEmpty(&planner->collCeTaskQueue)) {
|
||||
struct ncclTaskColl* task = ncclIntruQueueHead(&planner->collCeTaskQueue);
|
||||
plan->isCeColl = true;
|
||||
plan->ceCollArgs = ncclMemoryStackAlloc<struct ncclCeCollArgs>(&comm->memScoped);
|
||||
plan->ceCollArgs->rootRank = task->root;
|
||||
plan->ceCollArgs->nElts = task->count;
|
||||
plan->ceCollArgs->eltSize = ncclTypeSize(task->datatype);
|
||||
plan->ceCollArgs->sendBuff = (uint8_t*)task->sendbuff;
|
||||
plan->ceCollArgs->recvBuff = (uint8_t*)task->recvbuff;
|
||||
plan->ceCollArgs->func = task->func;
|
||||
plan->ceCollArgs->sendWin = task->sendWin;
|
||||
plan->ceCollArgs->recvWin = task->recvWin;
|
||||
|
||||
struct ncclTaskColl* task = ncclIntruQueueHead(&planner->collTaskQueue);
|
||||
plan->isSymColl = true;
|
||||
plan->kernelFn = ncclSymGetKernelPtr((ncclSymKernelId)task->devFuncId, task->opDev.op, task->datatype);
|
||||
plan->threadPerBlock = task->nWarps*WARP_SIZE;
|
||||
for (int i = 0; i < MAXCHANNELS/64; i++)
|
||||
plan->channelMask.masks[i] = uint64_t(-1) >> (64-task->nMaxChannels);
|
||||
// plan->channelMask = uint64_t(-1) >> (64-task->nMaxChannels);
|
||||
|
||||
plan->kernelArgsSize = sizeof(struct ncclSymDevArgs);
|
||||
plan->kernelSymArgs = ncclMemoryStackAlloc<struct ncclSymDevArgs>(&comm->memScoped);
|
||||
plan->kernelSymArgs->comm = comm->symDevComm;
|
||||
plan->kernelSymArgs->rootRank = task->root;
|
||||
plan->kernelSymArgs->redOpArg = task->opDev.scalarArg;
|
||||
plan->kernelSymArgs->nElts = task->count;
|
||||
plan->kernelSymArgs->input = (char*)task->sendbuff;
|
||||
plan->kernelSymArgs->output = (char*)task->recvbuff;
|
||||
|
||||
planner->nTasksColl -= 1;
|
||||
ncclIntruQueueEnqueue(&planner->planQueue, plan);
|
||||
INFO(NCCL_TUNING, "%s [Symmetric]: %ld Bytes -> Kernel %s nchannels %d nthreads %d",
|
||||
ncclFuncToString(task->func), task->count * ncclTypeSize(task->datatype), ncclSymKernelIdToString(task->devFuncId), task->nMaxChannels, plan->threadPerBlock);
|
||||
ncclIntruQueueDequeue(&planner->collCeTaskQueue);
|
||||
ncclMemoryPoolFree(&comm->memPool_ncclTaskColl, task);
|
||||
nPlans += 1;
|
||||
} else {
|
||||
struct ncclKernelPlanBudget budget;
|
||||
budget.inArgsBytes = comm->workArgsBytes - sizeof(struct ncclDevKernelArgs);
|
||||
// Non-persistent kernels fill up at most half of our fifo per kernel.
|
||||
budget.outArgsBytes = plan->persistent ? (1<<30) : comm->workFifoBytes/2;
|
||||
if (!ncclIntruQueueEmpty(&planner->collSymTaskQueue)) {
|
||||
NCCLCHECKGOTO(ncclSymmetricTaskScheduler(comm, &planner->collSymTaskQueue, plan), result, failure);
|
||||
}
|
||||
else {
|
||||
struct ncclKernelPlanBudget budget;
|
||||
budget.inArgsBytes = comm->workArgsBytes - sizeof(struct ncclDevKernelArgs);
|
||||
// Non-persistent kernels fill up at most half of our fifo per kernel.
|
||||
budget.outArgsBytes = plan->persistent ? (1<<30) : comm->workFifoBytes/2;
|
||||
|
||||
// Drain coll tasks first. This is essential since we partition tasks based
|
||||
// on the work budget and p2p work isn't collective. If we were to drain p2p
|
||||
// first, the place where we cut the kernel could vary by rank which would
|
||||
// cause the "shortest channel first" channel picker to have divergent results.
|
||||
if (planner->nTasksColl != 0) {
|
||||
NCCLCHECKGOTO(scheduleCollTasksToPlan(comm, plan, &budget), result, failure);
|
||||
}
|
||||
// And only drain p2p tasks once colls are depleted.
|
||||
if (planner->nTasksColl == 0 && planner->nTasksP2p != 0) {
|
||||
NCCLCHECKGOTO(scheduleP2pTasksToPlan(comm, plan, &budget), result, failure);
|
||||
// Drain coll tasks first. This is essential since we partition tasks based
|
||||
// on the work budget and p2p work isn't collective. If we were to drain p2p
|
||||
// first, the place where we cut the kernel could vary by rank which would
|
||||
// cause the "shortest channel first" channel picker to have divergent results.
|
||||
if (planner->nTasksColl != 0) {
|
||||
NCCLCHECKGOTO(scheduleCollTasksToPlan(comm, plan, &budget), result, failure);
|
||||
}
|
||||
// And only drain p2p tasks once colls are depleted.
|
||||
if (planner->nTasksColl == 0 && planner->nTasksP2p != 0) {
|
||||
NCCLCHECKGOTO(scheduleP2pTasksToPlan(comm, plan, &budget), result, failure);
|
||||
}
|
||||
}
|
||||
|
||||
finishPlan(comm, plan);
|
||||
if (plan->workBytes != 0) {
|
||||
ncclIntruQueueEnqueue(&planner->planQueue, plan);
|
||||
nPlans += 1;
|
||||
}
|
||||
}
|
||||
} while (planner->nTasksColl + planner->nTasksP2p != 0);
|
||||
} while (planner->nTasksColl + planner->nTasksP2p != 0 ||
|
||||
!ncclIntruQueueEmpty(&planner->collSymTaskQueue) ||
|
||||
!ncclIntruQueueEmpty(&planner->collCeTaskQueue));
|
||||
|
||||
struct ncclKernelPlan* planHead = ncclIntruQueueHead(&planner->planQueue);
|
||||
planner->unlaunchedPlansHead = planHead;
|
||||
@@ -1789,7 +1801,6 @@ ncclResult_t ncclLaunchKernelBefore_NoUncapturedCuda(struct ncclComm* comm, stru
|
||||
NCCL_PARAM(MemSyncDomain, "MEM_SYNC_DOMAIN", cudaLaunchMemSyncDomainRemote);
|
||||
#endif
|
||||
|
||||
NCCL_PARAM(NvlinkUtilCentricSchedEnable, "NVLINK_UTIL_CENTRIC_SCHED_ENABLE", 0);
|
||||
ncclResult_t ncclLaunchKernel(struct ncclComm* comm, struct ncclKernelPlan* plan) {
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
struct ncclKernelPlanner* planner = &comm->planner;
|
||||
@@ -1804,6 +1815,9 @@ ncclResult_t ncclLaunchKernel(struct ncclComm* comm, struct ncclKernelPlan* plan
|
||||
dim3 block = {(unsigned)plan->threadPerBlock, 1, 1};
|
||||
int smem = rcclShmemDynamicSize(comm->cudaArch, comm->WarpSize);
|
||||
cudaStream_t launchStream = planner->streams->stream;
|
||||
|
||||
NCCLCHECK(ncclProfilerStartKernelLaunchEvent(plan, launchStream));
|
||||
|
||||
void* extra[] = {plan->kernelArgs, &plan->kernelArgsSize};
|
||||
|
||||
auto event = latency_profiler::collTraceAquireEventBaseline(plan, launchStream);
|
||||
@@ -1860,25 +1874,24 @@ ncclResult_t ncclLaunchKernel(struct ncclComm* comm, struct ncclKernelPlan* plan
|
||||
}
|
||||
#endif
|
||||
#if CUDART_VERSION >= 12030
|
||||
bool capturing = ncclCudaGraphValid(planner->capturingGraph);
|
||||
enum ncclImplicitOrder implicitOrder;
|
||||
NCCLCHECKGOTO(getImplicitOrder(&implicitOrder, capturing, driverVersion), ret, do_return);
|
||||
NCCLCHECKGOTO(getImplicitOrder(&implicitOrder, plan->persistent, driverVersion), ret, do_return);
|
||||
if (implicitOrder == ncclImplicitOrderLaunch) {
|
||||
launchAttrs[attrs].id = CU_LAUNCH_ATTRIBUTE_LAUNCH_COMPLETION_EVENT;
|
||||
launchAttrs[attrs].value.launchCompletionEvent.event = comm->sharedRes->launchEvent;
|
||||
launchAttrs[attrs].value.launchCompletionEvent.flags = 0;
|
||||
attrs++;
|
||||
}
|
||||
if (comm->planner.isSymColl && compCap >= 90 && driverVersion >= 12030) {
|
||||
if (plan->isSymColl && compCap >= 90 && driverVersion >= 12030) {
|
||||
launchAttrs[attrs].id = CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION;
|
||||
launchAttrs[attrs].value.programmaticStreamSerializationAllowed = 1;
|
||||
attrs++;
|
||||
}
|
||||
#endif
|
||||
#if CUDART_VERSION >= 13000
|
||||
if (compCap >= 90 && driverVersion >= 13000) {
|
||||
if (compCap >= 100 && driverVersion >= 13000) {
|
||||
launchAttrs[attrs].id = CU_LAUNCH_ATTRIBUTE_NVLINK_UTIL_CENTRIC_SCHEDULING;
|
||||
launchAttrs[attrs].value.nvlinkUtilCentricScheduling = ncclParamNvlinkUtilCentricSchedEnable();
|
||||
launchAttrs[attrs].value.nvlinkUtilCentricScheduling = comm->config.nvlinkCentricSched;
|
||||
attrs++;
|
||||
}
|
||||
#endif
|
||||
@@ -1911,6 +1924,7 @@ ncclResult_t ncclLaunchKernel(struct ncclComm* comm, struct ncclKernelPlan* plan
|
||||
latency_profiler::collTraceRecordEndEvent(comm, plan, launchStream, std::move(event));
|
||||
|
||||
do_return:
|
||||
NCCLCHECK(ncclProfilerStopKernelLaunchEvent(plan));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -2047,7 +2061,7 @@ static ncclResult_t updateCollCostTable(
|
||||
float** collCostTable) {
|
||||
float (*table)[NCCL_NUM_PROTOCOLS] = (float (*)[NCCL_NUM_PROTOCOLS])collCostTable;
|
||||
|
||||
if (comm->nRanks == 1 || info->func == ncclFuncAllToAllPivot || info->func == ncclFuncAllToAllGda) {
|
||||
if (comm->nRanks == 1 || info->func == ncclFuncAlltoAllPivot || info->func == ncclFuncAllToAllGda) {
|
||||
table[NCCL_ALGO_RING][NCCL_PROTO_SIMPLE] = 0.0;
|
||||
return ncclSuccess;
|
||||
}
|
||||
@@ -2056,6 +2070,8 @@ static ncclResult_t updateCollCostTable(
|
||||
if ((a == NCCL_ALGO_COLLNET_DIRECT || a == NCCL_ALGO_COLLNET_CHAIN) && collNetSupport != 1) continue;
|
||||
// CollNetDirect is only supported for up to 8 local GPUs
|
||||
if (a == NCCL_ALGO_COLLNET_DIRECT && comm->maxLocalRanks > NCCL_MAX_DIRECT_ARITY+1) continue;
|
||||
// Disable CollNet Chain for more than 8 local GPUs
|
||||
if (a == NCCL_ALGO_COLLNET_CHAIN && comm->maxLocalRanks > NCCL_MAX_DIRECT_ARITY+1) continue;
|
||||
if ((a == NCCL_ALGO_NVLS || a == NCCL_ALGO_NVLS_TREE) && (!nvlsSupport || (info->func != ncclFuncAllReduce && comm->localRanks > NCCL_MAX_NVLS_ARITY))) continue;
|
||||
if (a == NCCL_ALGO_NVLS && collNetSupport != 1 && comm->nNodes > 1) continue;
|
||||
/* Tree reduceScatter doesn't support scaling yet */
|
||||
@@ -2160,7 +2176,11 @@ static ncclResult_t topoGetAlgoInfo(
|
||||
}
|
||||
} else if (info->algorithm == NCCL_ALGO_NVLS || info->algorithm == NCCL_ALGO_NVLS_TREE) {
|
||||
// NVLS should not need more than 16 channels to get peak BW.
|
||||
nc = comm->nvlsChannels;
|
||||
if (comm->nNodes > 1 && info->algorithm == NCCL_ALGO_NVLS) {
|
||||
nc = std::min(comm->nvlsChannels, comm->nChannels);
|
||||
} else {
|
||||
nc = comm->nvlsChannels;
|
||||
}
|
||||
} else {
|
||||
rcclUpdateThreadThreshold(comm, nBytes, info, threadThreshold);
|
||||
INFO(NCCL_INIT, "pre-adjustment threadThreshold:%i nBytes:%lu nc:%i", threadThreshold, nBytes, nc);
|
||||
@@ -2348,7 +2368,7 @@ static ncclResult_t calcCollChunking(
|
||||
info->algorithm == NCCL_ALGO_COLLNET_DIRECT ? ncclPatternCollnetDirect :
|
||||
ncclPatternRing;
|
||||
break;
|
||||
case ncclFuncAllToAllPivot:
|
||||
case ncclFuncAlltoAllPivot:
|
||||
pattern = ncclPatternRing;
|
||||
break;
|
||||
case ncclFuncAllToAllGda:
|
||||
@@ -2510,6 +2530,7 @@ static ncclResult_t calcCollChunking(
|
||||
}
|
||||
proxyOp->pattern = pattern;
|
||||
proxyOp->coll = info->func;
|
||||
proxyOp->collAPI = info->func;
|
||||
proxyOp->root = info->root;
|
||||
proxyOp->isOneRPN = comm->isOneRPN;
|
||||
// This is used by P2P to reduce the receive buffer size. We don't use it in collectives
|
||||
@@ -2573,6 +2594,35 @@ static ncclResult_t calcCollChunking(
|
||||
proxyOp->nbytes = DIVUP(nBytes, nChannels);
|
||||
}
|
||||
|
||||
// Set peer count hints used by network plugin
|
||||
switch (proxyOp->pattern) {
|
||||
case ncclPatternRing:
|
||||
case ncclPatternRingTwice:
|
||||
case ncclPatternPipelineFrom:
|
||||
case ncclPatternPipelineTo:
|
||||
case ncclPatternPatUp:
|
||||
case ncclPatternPatDown:
|
||||
proxyOp->nPeers = 1;
|
||||
break;
|
||||
case ncclPatternTreeUp:
|
||||
case ncclPatternTreeDown:
|
||||
case ncclPatternTreeUpDown:
|
||||
case ncclPatternNvlsTree:
|
||||
proxyOp->nPeers = (NCCL_MAX_TREE_ARITY - 1) * 2;
|
||||
break;
|
||||
case ncclPatternCollnetChain:
|
||||
case ncclPatternCollnetDirect:
|
||||
case ncclPatternNvls:
|
||||
case ncclPatternProfiler:
|
||||
// Peer count hints unused
|
||||
break;
|
||||
case ncclPatternSend:
|
||||
case ncclPatternRecv:
|
||||
default:
|
||||
WARN("Unknown pattern %d", pattern);
|
||||
return ncclInternalError;
|
||||
}
|
||||
|
||||
*outChunkSize = proxyOp->chunkSize;
|
||||
return ncclSuccess;
|
||||
}
|
||||
@@ -2673,128 +2723,8 @@ static ncclResult_t hostToDevRedOp(
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
// Converts `info` to a task and adds it to `comm->planner`. The exception is with
|
||||
// single rank communicators, collectives are issued as `ncclMemcpyAsync`s and
|
||||
// thus don't need a task.
|
||||
static ncclResult_t taskAppend(struct ncclComm* comm, struct ncclInfo* info) {
|
||||
static ncclResult_t ncclPlannerSetCapturingGraph(struct ncclComm* comm, struct ncclInfo* info) {
|
||||
struct ncclKernelPlanner *planner = &comm->planner;
|
||||
|
||||
if (info->coll == ncclFuncSend || info->coll == ncclFuncRecv) {
|
||||
int peer = info->root;
|
||||
ssize_t nBytes = info->count*ncclTypeSize(info->datatype);
|
||||
bool isSendNotRecv = info->coll == ncclFuncSend;
|
||||
|
||||
// Must be in thread local group before tasks can be alloc'd in `comm->memScoped`.
|
||||
ncclGroupCommJoin(info->comm, ncclGroupTaskTypeCollective);
|
||||
struct ncclTaskP2p* p2p = ncclMemoryPoolAlloc<struct ncclTaskP2p>(&comm->memPool_ncclTaskP2p, &comm->memPermanent);
|
||||
p2p->func = info->coll;
|
||||
p2p->buff = (void*)info->recvbuff;
|
||||
p2p->count = info->count;
|
||||
p2p->datatype = info->datatype;
|
||||
p2p->root = info->root;
|
||||
p2p->bytes = nBytes;
|
||||
p2p->eActivationMask = __atomic_load_n(&ncclProfilerEventMask, __ATOMIC_RELAXED);
|
||||
p2p->opCount = comm->opCount;
|
||||
ncclIntruQueueEnqueue(
|
||||
isSendNotRecv ? &planner->peers[peer].sendQueue : &planner->peers[peer].recvQueue,
|
||||
p2p);
|
||||
planner->nTasksP2p += 1;
|
||||
|
||||
// Mark channels that need pre-connect
|
||||
if (comm->rank != peer) {
|
||||
if (!(isSendNotRecv ? planner->peers[peer].sendSeen : planner->peers[peer].recvSeen)) {
|
||||
// planner->peers[peer].send/recvSeen is private to each comm, so we need to set it anyway.
|
||||
(isSendNotRecv ? planner->peers[peer].sendSeen : planner->peers[peer].recvSeen) = true;
|
||||
int round = 0;
|
||||
while (peer != (isSendNotRecv ? comm->p2pSchedule[round].sendRank
|
||||
: comm->p2pSchedule[round].recvRank)) {
|
||||
round += 1;
|
||||
}
|
||||
uint8_t base = ncclP2pChannelBaseForRound(comm, round, rcclParamP2pBatchEnable());
|
||||
for (int c=0; c < comm->p2pnChannelsPerPeer; c++) {
|
||||
int channelId = ncclP2pChannelForPart(comm->p2pnChannels, base, c, comm->p2pnChannelsPerPeer, comm->nNodes);
|
||||
if (isSendNotRecv) {
|
||||
if (comm->channels[channelId].peers[peer]->send[1].hasSeen == 0) { // P2P uses only 1 connector
|
||||
// the send/recv connector is shared among split shared comms. We need to set hasSeen to
|
||||
// 1 in order to avoid duplicate connection setup if user group sendrecv ops with split
|
||||
// shared comms together.
|
||||
comm->channels[channelId].peers[peer]->send[1].hasSeen = 1;
|
||||
//comm->connectSend[peer] |= (1UL<<channelId);
|
||||
comm->connectSend[peer].masks[channelId/64] |= (1UL<<(channelId%64));
|
||||
ncclGroupCommPreconnect(comm);
|
||||
}
|
||||
if (comm->p2pNet && comm->channels[channelId].peers[peer]->send[NCCL_CONN_IDX_P2P_NET].hasSeen == 0) {
|
||||
comm->channels[channelId].peers[peer]->send[1].hasSeen = 1;
|
||||
//comm->connectSend[peer+comm->nRanks*NCCL_CONN_IDX_P2P_NET] |= (1UL<<channelId);
|
||||
comm->connectSend[peer+comm->nRanks*NCCL_CONN_IDX_P2P_NET].masks[channelId/64] |= (1UL<<(channelId%64));
|
||||
ncclGroupCommPreconnect(comm);
|
||||
}
|
||||
} else {
|
||||
if (comm->channels[channelId].peers[peer]->recv[1].hasSeen == 0) { // P2P uses only 1 connector
|
||||
comm->channels[channelId].peers[peer]->recv[1].hasSeen = 1;
|
||||
//comm->connectRecv[peer] |= (1UL<<channelId);
|
||||
comm->connectRecv[peer].masks[channelId/64] |= (1UL<<(channelId%64));
|
||||
ncclGroupCommPreconnect(comm);
|
||||
}
|
||||
if (comm->p2pNet && comm->channels[channelId].peers[peer]->recv[NCCL_CONN_IDX_P2P_NET].hasSeen == 0) {
|
||||
comm->channels[channelId].peers[peer]->recv[1].hasSeen = 1;
|
||||
//comm->connectRecv[peer+comm->nRanks*NCCL_CONN_IDX_P2P_NET] |= (1UL<<channelId);
|
||||
comm->connectRecv[peer+comm->nRanks*NCCL_CONN_IDX_P2P_NET].masks[channelId/64] |= (1UL<<(channelId%64));
|
||||
ncclGroupCommPreconnect(comm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Empty collectives can be discarded.
|
||||
if (info->count == 0) return ncclSuccess;
|
||||
|
||||
if (info->datatype == ncclFloat8e4m3 || info->datatype == ncclFloat8e5m2) {
|
||||
if (comm->minCompCap < 90) {
|
||||
WARN("FP8 reduction support begins with sm90 capable devices.");
|
||||
return ncclInvalidArgument;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy reduction op state from op handle into info struct here since the
|
||||
// op handle may be destroyed before ncclGroupEnd().
|
||||
struct ncclDevRedOpFull opDev;
|
||||
NCCLCHECK(hostToDevRedOp(&opDev, info->op, info->datatype, comm));
|
||||
|
||||
if (comm->nRanks == 1) {
|
||||
NCCLCHECK(ncclLaunchOneRank(info->recvbuff, info->sendbuff, info->count, opDev, info->datatype, info->stream));
|
||||
return ncclSuccess;
|
||||
} else {
|
||||
// Must be in thread local group before tasks can be alloc'd in `comm->memScoped`.
|
||||
ncclGroupCommJoin(info->comm, ncclGroupTaskTypeCollective);
|
||||
struct ncclTaskColl* t = ncclMemoryPoolAlloc<struct ncclTaskColl>(&comm->memPool_ncclTaskColl, &comm->memPermanent);
|
||||
t->func = info->coll;
|
||||
t->sendbuff = info->sendbuff;
|
||||
t->recvbuff = info->recvbuff;
|
||||
t->count = info->count;
|
||||
t->root = info->root;
|
||||
t->datatype = info->datatype;
|
||||
size_t elementSize = ncclTypeSize(t->datatype);
|
||||
if (t->func == ncclFuncAllGather || t->func == ncclFuncBroadcast || t->func == ncclFuncAllToAllPivot || t->func == ncclFuncAllToAllGda) {
|
||||
t->count *= elementSize;
|
||||
t->datatype = ncclInt8;
|
||||
elementSize = 1;
|
||||
}
|
||||
t->trafficBytes = t->count*elementSize*ncclFuncTrafficPerByte(t->func, comm->nRanks);
|
||||
t->opHost = info->op;
|
||||
t->opDev = opDev; // C++ struct assignment
|
||||
t->chunkSteps = info->chunkSteps;
|
||||
t->sliceSteps = info->sliceSteps;
|
||||
t->eActivationMask = __atomic_load_n(&ncclProfilerEventMask, __ATOMIC_RELAXED);
|
||||
t->opCount = comm->opCount;
|
||||
t->acc = info->acc;
|
||||
|
||||
planner->nTasksColl += 1;
|
||||
ncclTaskCollSorterInsert(&planner->collSorter, t, t->trafficBytes);
|
||||
}
|
||||
}
|
||||
|
||||
if (info->stream != planner->streamRecent || planner->streams == nullptr) {
|
||||
planner->streamRecent = info->stream;
|
||||
struct ncclCudaStreamList* l = planner->streams;
|
||||
@@ -2823,7 +2753,279 @@ static ncclResult_t taskAppend(struct ncclComm* comm, struct ncclInfo* info) {
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
static ncclResult_t p2pTaskAppend(
|
||||
struct ncclComm* comm,
|
||||
struct ncclInfo* info,
|
||||
ncclFunc_t coll,
|
||||
ncclFunc_t collAPI,
|
||||
void* buff,
|
||||
size_t count,
|
||||
ncclDataType_t datatype,
|
||||
int peer) {
|
||||
struct ncclKernelPlanner *planner = &comm->planner;
|
||||
|
||||
// Determine peer and basic parameters.
|
||||
ssize_t nBytes = count*ncclTypeSize(datatype);
|
||||
bool isSendNotRecv = coll == ncclFuncSend;
|
||||
|
||||
// Must be in thread local group before tasks can be alloc'd in `comm->memScoped`.
|
||||
ncclGroupCommJoin(comm, ncclGroupTaskTypeCollective);
|
||||
info->coll = coll;
|
||||
// Set capturing graph. Called here so that profiler can emit a group API event with this information
|
||||
NCCLCHECK(ncclPlannerSetCapturingGraph(comm, info));
|
||||
bool isGraphCaptured = ncclCudaGraphValid(planner->capturingGraph);
|
||||
NCCLCHECK(ncclProfilerStartGroupApiEvent(info, isGraphCaptured));
|
||||
NCCLCHECK(ncclProfilerRecordGroupApiEventState(ncclProfilerGroupStartApiStop));
|
||||
|
||||
NCCLCHECK(ncclProfilerStartP2pApiEvent(info, isGraphCaptured));
|
||||
|
||||
struct ncclTaskP2p* p2p = ncclMemoryPoolAlloc<struct ncclTaskP2p>(&comm->memPool_ncclTaskP2p, &comm->memPermanent);
|
||||
p2p->func = coll;
|
||||
p2p->collAPI = collAPI;
|
||||
p2p->buff = buff;
|
||||
p2p->count = count;
|
||||
p2p->datatype = datatype;
|
||||
p2p->root = peer;
|
||||
p2p->bytes = nBytes;
|
||||
p2p->eActivationMask = ncclProfilerApiState.eActivationMask;
|
||||
p2p->groupApiEventHandle = ncclProfilerApiState.groupApiEventHandle;
|
||||
p2p->p2pApiEventHandle = ncclProfilerApiState.p2pApiEventHandle;
|
||||
ncclIntruQueueEnqueue(
|
||||
isSendNotRecv ? &planner->peers[peer].sendQueue : &planner->peers[peer].recvQueue,
|
||||
p2p);
|
||||
planner->nTasksP2p += 1;
|
||||
if (isSendNotRecv)
|
||||
planner->nTasksP2pSend += 1;
|
||||
else
|
||||
planner->nTasksP2pRecv += 1;
|
||||
|
||||
// Mark channels that need pre-connect
|
||||
if (comm->rank != peer) {
|
||||
if (!(isSendNotRecv ? planner->peers[peer].sendSeen : planner->peers[peer].recvSeen)) {
|
||||
// planner->peers[peer].send/recvSeen is private to each comm, so we need to set it anyway.
|
||||
(isSendNotRecv ? planner->peers[peer].sendSeen : planner->peers[peer].recvSeen) = true;
|
||||
int round = 0;
|
||||
while (peer != (isSendNotRecv ? comm->p2pSchedule[round].sendRank
|
||||
: comm->p2pSchedule[round].recvRank)) {
|
||||
round += 1;
|
||||
}
|
||||
uint8_t base = ncclP2pChannelBaseForRound(comm, round);
|
||||
for (int c=0; c < comm->p2pnChannelsPerPeer; c++) {
|
||||
int channelId = ncclP2pChannelForPart(comm->p2pnChannels, base, c, comm->p2pnChannelsPerPeer, comm->nNodes);
|
||||
if (isSendNotRecv) {
|
||||
if (comm->channels[channelId].peers[peer]->send[1].hasSeen == 0) { // P2P uses only 1 connector
|
||||
// the send/recv connector is shared among split shared comms. We need to set hasSeen to
|
||||
// 1 in order to avoid duplicate connection setup if user group sendrecv ops with split
|
||||
// shared comms together.
|
||||
comm->channels[channelId].peers[peer]->send[1].hasSeen = 1;
|
||||
comm->channels[channelId].peers[peer]->send[1].p2pOnly = 1;
|
||||
// comm->connectSend[peer] |= (1UL<<channelId);
|
||||
comm->connectSend[peer].masks[channelId/64] |= (1UL<<(channelId%64));
|
||||
ncclGroupCommPreconnect(comm);
|
||||
}
|
||||
if (comm->p2pNet && comm->channels[channelId].peers[peer]->send[NCCL_CONN_IDX_P2P_NET].hasSeen == 0) {
|
||||
comm->channels[channelId].peers[peer]->send[1].hasSeen = 1;
|
||||
//comm->connectSend[peer+comm->nRanks*NCCL_CONN_IDX_P2P_NET] |= (1UL<<channelId);
|
||||
comm->connectSend[peer+comm->nRanks*NCCL_CONN_IDX_P2P_NET].masks[channelId/64] |= (1UL<<(channelId%64));
|
||||
ncclGroupCommPreconnect(comm);
|
||||
}
|
||||
} else {
|
||||
if (comm->channels[channelId].peers[peer]->recv[1].hasSeen == 0) { // P2P uses only 1 connector
|
||||
comm->channels[channelId].peers[peer]->recv[1].hasSeen = 1;
|
||||
comm->channels[channelId].peers[peer]->recv[1].p2pOnly = 1;
|
||||
// comm->connectRecv[peer] |= (1UL<<channelId);
|
||||
comm->connectRecv[peer].masks[channelId/64] |= (1UL<<(channelId%64));
|
||||
ncclGroupCommPreconnect(comm);
|
||||
}
|
||||
if (comm->p2pNet && comm->channels[channelId].peers[peer]->recv[NCCL_CONN_IDX_P2P_NET].hasSeen == 0) {
|
||||
comm->channels[channelId].peers[peer]->recv[1].hasSeen = 1;
|
||||
//comm->connectRecv[peer+comm->nRanks*NCCL_CONN_IDX_P2P_NET] |= (1UL<<channelId);
|
||||
comm->connectRecv[peer+comm->nRanks*NCCL_CONN_IDX_P2P_NET].masks[channelId/64] |= (1UL<<(channelId%64));
|
||||
ncclGroupCommPreconnect(comm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ncclProfilerStopP2pApiEvent();
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
static ncclResult_t collTaskAppend(
|
||||
struct ncclComm* comm,
|
||||
struct ncclInfo* info,
|
||||
struct ncclDevRedOpFull opDev) {
|
||||
struct ncclKernelPlanner *planner = &comm->planner;
|
||||
|
||||
// Must be in thread local group before tasks can be alloc'd in `comm->memScoped`.
|
||||
ncclGroupCommJoin(info->comm, ncclGroupTaskTypeCollective);
|
||||
// Set capturing graph. Called here so that profiler can emit a group API event with this information
|
||||
NCCLCHECK(ncclPlannerSetCapturingGraph(comm, info));
|
||||
bool isGraphCaptured = ncclCudaGraphValid(planner->capturingGraph);
|
||||
NCCLCHECK(ncclProfilerStartGroupApiEvent(info, isGraphCaptured));
|
||||
NCCLCHECK(ncclProfilerRecordGroupApiEventState(ncclProfilerGroupStartApiStop));
|
||||
NCCLCHECK(ncclProfilerStartCollApiEvent(info, isGraphCaptured));
|
||||
|
||||
struct ncclTaskColl* t = ncclMemoryPoolAlloc<struct ncclTaskColl>(&comm->memPool_ncclTaskColl, &comm->memPermanent);
|
||||
t->func = info->coll;
|
||||
t->sendbuff = info->sendbuff;
|
||||
t->recvbuff = info->recvbuff;
|
||||
t->count = info->count;
|
||||
t->root = info->root;
|
||||
t->datatype = info->datatype;
|
||||
size_t elementSize = ncclTypeSize(t->datatype);
|
||||
if (t->func == ncclFuncAllGather || t->func == ncclFuncBroadcast || t->func == ncclFuncAlltoAllPivot) {
|
||||
t->count *= elementSize;
|
||||
t->datatype = ncclInt8;
|
||||
elementSize = 1;
|
||||
}
|
||||
t->trafficBytes = t->count*elementSize*ncclFuncTrafficPerByte(t->func, comm->nRanks);
|
||||
t->opHost = info->op;
|
||||
t->opDev = opDev; // C++ struct assignment
|
||||
t->chunkSteps = info->chunkSteps;
|
||||
t->sliceSteps = info->sliceSteps;
|
||||
t->eActivationMask = ncclProfilerApiState.eActivationMask;
|
||||
t->groupApiEventHandle = ncclProfilerApiState.groupApiEventHandle;
|
||||
t->collApiEventHandle = ncclProfilerApiState.collApiEventHandle;
|
||||
t->opCount = comm->opCount;
|
||||
t->acc = info->acc;
|
||||
|
||||
planner->nTasksColl += 1;
|
||||
ncclTaskCollSorterInsert(&planner->collSorter, t, t->trafficBytes);
|
||||
|
||||
ncclProfilerStopCollApiEvent();
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
static ncclResult_t ceCollTaskAppend(
|
||||
struct ncclComm* comm,
|
||||
struct ncclInfo* info,
|
||||
struct ncclDevrWindow* sendWin,
|
||||
struct ncclDevrWindow* recvWin,
|
||||
struct ncclDevRedOpFull opDev) {
|
||||
struct ncclKernelPlanner *planner = &comm->planner;
|
||||
|
||||
// Check if CE needs initialization
|
||||
if (comm->ceColl.baseUCSymReadyPtr == NULL && ncclIntruQueueEmpty(&comm->ceInitTaskQueue)) {
|
||||
struct ncclCeInitTask* ceTask;
|
||||
NCCLCHECK(ncclCalloc(&ceTask, 1));
|
||||
ceTask->comm = comm;
|
||||
ncclIntruQueueEnqueue(&comm->ceInitTaskQueue, ceTask);
|
||||
ncclGroupCommJoin(comm, ncclGroupTaskTypeSymRegister);
|
||||
}
|
||||
|
||||
// Must be in thread local group before tasks can be alloc'd in `comm->memScoped`.
|
||||
ncclGroupCommJoin(info->comm, ncclGroupTaskTypeCollective);
|
||||
NCCLCHECK(ncclPlannerSetCapturingGraph(comm, info));
|
||||
struct ncclTaskColl* t = ncclMemoryPoolAlloc<struct ncclTaskColl>(&comm->memPool_ncclTaskColl, &comm->memPermanent);
|
||||
|
||||
t->func = info->coll;
|
||||
t->sendbuff = info->sendbuff;
|
||||
t->recvbuff = info->recvbuff;
|
||||
t->count = info->count;
|
||||
t->root = info->root;
|
||||
t->datatype = info->datatype;
|
||||
size_t elementSize = ncclTypeSize(t->datatype);
|
||||
if (t->func == ncclFuncAllGather || t->func == ncclFuncBroadcast) {
|
||||
t->count *= elementSize;
|
||||
t->datatype = ncclInt8;
|
||||
elementSize = 1;
|
||||
}
|
||||
t->trafficBytes = t->count*elementSize*ncclFuncTrafficPerByte(t->func, comm->nRanks);
|
||||
t->opHost = info->op;
|
||||
t->opDev = opDev; // C++ struct assignment
|
||||
t->chunkSteps = info->chunkSteps;
|
||||
t->sliceSteps = info->sliceSteps;
|
||||
t->eActivationMask = __atomic_load_n(&ncclProfilerEventMask, __ATOMIC_RELAXED);
|
||||
t->sendWin = sendWin;
|
||||
t->recvWin = recvWin;
|
||||
|
||||
ncclIntruQueueEnqueue(&planner->collCeTaskQueue, t);
|
||||
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
// Converts `info` to a task and adds it to `comm->planner`. The exception is with
|
||||
// single rank communicators, collectives are issued as `ncclMemcpyAsync`s and
|
||||
// thus don't need a task.
|
||||
static ncclResult_t taskAppend(struct ncclComm* comm, struct ncclInfo* info) {
|
||||
ncclFunc_t collAPI = info->coll;
|
||||
|
||||
if (info->coll == ncclFuncSend || info->coll == ncclFuncRecv) {
|
||||
NCCLCHECK(p2pTaskAppend(comm, info, info->coll, collAPI, (void*)info->recvbuff, info->count, info->datatype, info->root));
|
||||
} else {
|
||||
// Empty collectives can be discarded.
|
||||
if (info->count == 0) return ncclSuccess;
|
||||
|
||||
if (info->datatype == ncclFloat8e4m3 || info->datatype == ncclFloat8e5m2) {
|
||||
if (comm->minCompCap < 90 && info->coll != ncclFuncAllGather && info->coll != ncclFuncBroadcast && info->coll != ncclFuncAlltoAll && info->coll != ncclFuncScatter && info->coll != ncclFuncGather) {
|
||||
WARN("FP8 reduction support begins with sm90 capable devices.");
|
||||
return ncclInvalidArgument;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy reduction op state from op handle into info struct here since the
|
||||
// op handle may be destroyed before ncclGroupEnd().
|
||||
struct ncclDevRedOpFull opDev;
|
||||
NCCLCHECK(hostToDevRedOp(&opDev, info->op, info->datatype, comm));
|
||||
|
||||
if (comm->nRanks == 1) {
|
||||
NCCLCHECK(ncclLaunchOneRank(info->recvbuff, info->sendbuff, info->count, opDev, info->datatype, info->stream));
|
||||
return ncclSuccess;
|
||||
} else {
|
||||
struct ncclDevrWindow* sendWin;
|
||||
struct ncclDevrWindow* recvWin;
|
||||
ncclDevrFindWindow(comm, info->sendbuff, &sendWin);
|
||||
ncclDevrFindWindow(comm, info->recvbuff, &recvWin);
|
||||
bool ceImplemented = ncclCeImplemented(info->coll, info->op, info->datatype);
|
||||
|
||||
// Append CE collective task if CE is supported and requested by user
|
||||
if (comm->symmetricSupport && comm->nNodes == 1 && sendWin && recvWin && (sendWin->winFlags & recvWin->winFlags & NCCL_WIN_COLL_SYMMETRIC) && comm->config.CTAPolicy == NCCL_CTA_POLICY_ZERO && ceImplemented) {
|
||||
NCCLCHECK(ceCollTaskAppend(comm, info, sendWin, recvWin, opDev));
|
||||
}
|
||||
// Append kernel-based collective
|
||||
else {
|
||||
if (info->coll == ncclFuncAlltoAll) {
|
||||
for (int r=0; r<comm->nRanks; r++) {
|
||||
NCCLCHECK(p2pTaskAppend(comm, info, ncclFuncSend, collAPI, (void*)((char*)info->sendbuff+r*info->count*ncclTypeSize(info->datatype)), info->count, info->datatype, r));
|
||||
NCCLCHECK(p2pTaskAppend(comm, info, ncclFuncRecv, collAPI, (void*)((char*)info->recvbuff+r*info->count*ncclTypeSize(info->datatype)), info->count, info->datatype, r));
|
||||
}
|
||||
} else if (info->coll == ncclFuncGather){
|
||||
size_t offset = 0;
|
||||
NCCLCHECK(p2pTaskAppend(comm, info, ncclFuncSend, collAPI, (void*)info->sendbuff, info->count, info->datatype, info->root));
|
||||
if (comm->rank == info->root) {
|
||||
for (int r=0; r<comm->nRanks; r++) {
|
||||
void* buff = (void*)((char*)info->recvbuff + offset);
|
||||
NCCLCHECK(p2pTaskAppend(comm, info, ncclFuncRecv, collAPI, buff, info->count, info->datatype, r));
|
||||
offset += info->count * ncclTypeSize(info->datatype);
|
||||
}
|
||||
}
|
||||
} else if (info->coll == ncclFuncScatter) {
|
||||
size_t offset = 0;
|
||||
if (comm->rank == info->root) {
|
||||
for (int r = 0; r < comm->nRanks; r++) {
|
||||
void* buff = (void*)((char*)info->sendbuff + offset);
|
||||
NCCLCHECK(p2pTaskAppend(comm, info, ncclFuncSend, collAPI, buff, info->count, info->datatype, r));
|
||||
offset += info->count * ncclTypeSize(info->datatype);
|
||||
}
|
||||
}
|
||||
NCCLCHECK(p2pTaskAppend(comm, info, ncclFuncRecv, collAPI, (void*)info->recvbuff, info->count, info->datatype, info->root));
|
||||
} else {
|
||||
NCCLCHECK(collTaskAppend(comm, info, opDev));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
ncclResult_t ncclEnqueueCheck(struct ncclInfo* info) {
|
||||
// Profiler - If a group API event has already started, update the profilerGroupDepth so that the depth
|
||||
// updates correctly for implicit ncclGroupStartInternal and ncclGroupEndInternal calls
|
||||
if (ncclProfilerApiState.profilerGroupDepth > 0) {
|
||||
ncclProfilerApiState.profilerGroupDepth++;
|
||||
}
|
||||
NCCLCHECK(ncclGroupStartInternal());
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
int devOld = -1;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# Graph sources
|
||||
set(GRAPH_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/topo.cc
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tuning.cc
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/xml.cc
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/search.cc
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/paths.cc
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/connect.cc
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/rings.cc
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/trees.cc
|
||||
)
|
||||
|
||||
# Add graph sources to parent scope
|
||||
set(GRAPH_SOURCES ${GRAPH_SOURCES} PARENT_SCOPE)
|
||||
@@ -24,6 +24,7 @@ ncclResult_t ncclTopoPreset(struct ncclComm* comm, struct ncclTopoGraph** graphs
|
||||
int localRanks = comm->topo->nodes[GPU].count;
|
||||
int nChannels = comm->nChannels;
|
||||
|
||||
topoRanks->crossNicRing = graphs[NCCL_ALGO_RING]->crossNic;
|
||||
topoRanks->nvlsHeadNum = 0;
|
||||
for (int c=0; c<nChannels; c++) {
|
||||
struct ncclChannel* channel = comm->channels+c;
|
||||
@@ -430,7 +431,6 @@ static ncclResult_t connectCollNet(struct ncclComm* comm, struct ncclTopoGraph*
|
||||
sprintf(line+strlen(line), "nUp %d nHeads %d ", nUp, nHeads);
|
||||
sprintf(line+strlen(line), "headRank %d out %d shift %d", channel->collnetDirect.headRank, channel->collnetDirect.out, channel->collnetDirect.shift);
|
||||
INFO(NCCL_GRAPH, "%s", line);
|
||||
channel->collnetChain.depth = comm->nRanks/comm->nNodes;
|
||||
}
|
||||
free(heads);
|
||||
return ncclSuccess;
|
||||
@@ -447,7 +447,7 @@ static ncclResult_t connectNvls(struct ncclComm* comm, int* nvlsHeads, int nHead
|
||||
if (nvlsHeads[h * comm->nNodes + comm->node] == comm->rank) headRank = h;
|
||||
}
|
||||
|
||||
for (int c=0; c<comm->nChannels; c++) {
|
||||
for (int c=0; c<comm->nvlsChannels; c++) {
|
||||
struct ncclChannel* channel = comm->channels+c;
|
||||
channel->nvls.nHeads = nHeads;
|
||||
for (int h=0; h<nHeads; h++) channel->nvls.up[h] = comm->nRanks+1+h;
|
||||
@@ -499,7 +499,7 @@ static ncclResult_t connectNvls(struct ncclComm* comm, int* nvlsHeads, int nHead
|
||||
}
|
||||
// Set prev/next in all channels (NVLS compute channels work
|
||||
// orthogonally to NVLS search channels).
|
||||
for (int c=0; c<comm->nChannels; c++) {
|
||||
for (int c=0; c<comm->nvlsChannels; c++) {
|
||||
struct ncclChannel* channel = comm->channels+c;
|
||||
channel->nvls.treeUp = treeUp[c%2];
|
||||
channel->nvls.treeDown[0] = channel->nvls.down;
|
||||
@@ -731,17 +731,17 @@ ncclResult_t ncclTopoPostset(struct ncclComm* comm, int* firstRanks, int* treePa
|
||||
NCCLCHECKGOTO(ncclCalloc(&treeToChild1, nNodes*MAXCHANNELS), ret, fail);
|
||||
NCCLCHECKGOTO(ncclCalloc(&nvlsHeads, nNodes*MAXCHANNELS), ret, fail);
|
||||
|
||||
// Alternate rings to avoid crossing rails
|
||||
if (graphs[NCCL_ALGO_RING]->crossNic == 2 && (nChannels % 2) == 0) {
|
||||
for (int r=0; r<comm->nRanks; r++) {
|
||||
if (comm->rankToNode[r] % 2 == 1) {
|
||||
// Exchange rings
|
||||
for (int c=0; c<nChannels; c+=2) {
|
||||
exchangeValues(allTopoRanks[r]->ringRecv+c, allTopoRanks[r]->ringRecv+(c^1));
|
||||
exchangeValues(allTopoRanks[r]->ringSend+c, allTopoRanks[r]->ringSend+(c^1));
|
||||
exchangeValues(allTopoRanks[r]->ringPrev+c, allTopoRanks[r]->ringPrev+(c^1));
|
||||
exchangeValues(allTopoRanks[r]->ringNext+c, allTopoRanks[r]->ringNext+(c^1));
|
||||
}
|
||||
// Alternate rings to avoid crossing rails.
|
||||
// CrossNic values could be not the same on all nodes as it depends on the number of net devs and the NVLink bandwidth.
|
||||
// Therefore, it's only done if the rank obtained a solution with crossNic=2.
|
||||
for (int r = 0; r < comm->nRanks; r++) {
|
||||
if (allTopoRanks[r]->crossNicRing == 2 && (nChannels % 2) == 0 && (comm->rankToNode[r] % 2) == 1) {
|
||||
// Exchange rings
|
||||
for (int c=0; c<nChannels; c+=2) {
|
||||
exchangeValues(allTopoRanks[r]->ringRecv+c, allTopoRanks[r]->ringRecv+(c^1));
|
||||
exchangeValues(allTopoRanks[r]->ringSend+c, allTopoRanks[r]->ringSend+(c^1));
|
||||
exchangeValues(allTopoRanks[r]->ringPrev+c, allTopoRanks[r]->ringPrev+(c^1));
|
||||
exchangeValues(allTopoRanks[r]->ringNext+c, allTopoRanks[r]->ringNext+(c^1));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -858,7 +858,14 @@ ncclResult_t ncclTopoPostset(struct ncclComm* comm, int* firstRanks, int* treePa
|
||||
int collNetNchannels = std::min(maxChannels, nChannels+nChannels/2);
|
||||
nChannels = comm->nChannels = copyChannels(comm, nChannels, collNetNchannels, ringPrev, ringNext);
|
||||
}
|
||||
NCCLCHECKGOTO(connectCollNet(comm, graphs[NCCL_ALGO_COLLNET_DIRECT]), ret, fail);
|
||||
|
||||
for (int c = 0; c < comm->nChannels; c++) {
|
||||
comm->channels[c].collnetChain.depth = comm->nRanks/comm->nNodes;
|
||||
}
|
||||
|
||||
if (comm->maxLocalRanks <= NCCL_MAX_DIRECT_ARITY+1) {
|
||||
NCCLCHECKGOTO(connectCollNet(comm, graphs[NCCL_ALGO_COLLNET_DIRECT]), ret, fail);
|
||||
}
|
||||
}
|
||||
|
||||
// Use 4 compute channels per search channel to reach peak BW on <8 PPN
|
||||
@@ -910,9 +917,6 @@ ncclResult_t ncclTopoPostset(struct ncclComm* comm, int* firstRanks, int* treePa
|
||||
if (shared && comm->nvlsChannels > parent->nvlsResources->nChannels) {
|
||||
comm->nvlsChannels = parent->nvlsResources->nChannels;
|
||||
}
|
||||
if (comm->nChannels < comm->nvlsChannels) {
|
||||
nChannels = comm->nChannels = copyChannels(comm, comm->nChannels, comm->nvlsChannels, ringPrev, ringNext);
|
||||
}
|
||||
NCCLCHECKGOTO(connectNvls(comm, nvlsHeads, minHeadNum), ret, fail);
|
||||
#endif
|
||||
if (shared && comm->nChannels > parent->sharedRes->tpNChannels) {
|
||||
|
||||
@@ -391,11 +391,15 @@ ncclResult_t ncclTopoCheckMNNVL(struct ncclTopoSystem* system, struct ncclPeerIn
|
||||
nvmlGpuFabricInfoV_t *fabricInfo1 = &info1->fabricInfo;
|
||||
nvmlGpuFabricInfoV_t *fabricInfo2 = &info2->fabricInfo;
|
||||
// A zero UUID means we don't have MNNVL fabric info
|
||||
if ((((long *)&fabricInfo2->clusterUuid)[0]|((long *)fabricInfo2->clusterUuid)[1]) == 0) return ncclSuccess;
|
||||
unsigned long uuid0 = 0;
|
||||
unsigned long uuid1 = 0;
|
||||
memcpy(&uuid0, fabricInfo2->clusterUuid, sizeof(uuid0));
|
||||
memcpy(&uuid1, fabricInfo2->clusterUuid + sizeof(uuid0), sizeof(uuid1));
|
||||
if ((uuid0 | uuid1) == 0) return ncclSuccess;
|
||||
if ((memcmp(fabricInfo1->clusterUuid, fabricInfo2->clusterUuid, NVML_GPU_FABRIC_UUID_LEN) == 0) &&
|
||||
(fabricInfo1->cliqueId == fabricInfo2->cliqueId)) {
|
||||
TRACE(NCCL_NET, "MNNVL matching peer 0x%lx UUID %lx.%lx cliqueId 0x%x",
|
||||
info2->busId, ((long *)fabricInfo2->clusterUuid)[0], ((long *)fabricInfo2->clusterUuid)[1], fabricInfo2->cliqueId);
|
||||
info2->busId, uuid0, uuid1, fabricInfo2->cliqueId);
|
||||
*ret = 1;
|
||||
}
|
||||
return ncclSuccess;
|
||||
@@ -936,9 +940,6 @@ void ncclTopoFree(struct ncclTopoSystem* system) {
|
||||
free(system);
|
||||
}
|
||||
|
||||
NCCL_PARAM(NChannelsPerNetPeer, "NCHANNELS_PER_NET_PEER", -1);
|
||||
NCCL_PARAM(NChannelsPerPeer, "NCHANNELS_PER_PEER", -2);
|
||||
|
||||
static ncclResult_t ncclTopoGetNchannels(struct ncclComm* comm, int g /*local gpu index*/, int peerRank, int* nChannels) {
|
||||
int peer;
|
||||
struct ncclTopoSystem* system = comm->topo;
|
||||
@@ -959,10 +960,10 @@ static ncclResult_t ncclTopoGetNchannels(struct ncclComm* comm, int g /*local gp
|
||||
}
|
||||
} else {
|
||||
// Remote rank, use network
|
||||
int nNetChannels = ncclParamNChannelsPerNetPeer();
|
||||
if (nNetChannels == -1) {
|
||||
//start from 2 channels per NIC and reduce with scale
|
||||
nNetChannels = 2;
|
||||
int nNetChannels = comm->config.nChannelsPerNetPeer;
|
||||
if (nNetChannels == NCCL_CONFIG_UNDEF_INT) {
|
||||
//start from 2 channels per NIC and reduce with scale
|
||||
nNetChannels = 2;
|
||||
|
||||
// check if we need to use more than one NIC, hence more than one channel
|
||||
int netCountByBw = 1, nChannelsMax = nNetChannels;
|
||||
@@ -1014,7 +1015,7 @@ ncclResult_t ncclTopoComputeP2pChannels(struct ncclComm* comm) {
|
||||
comm->p2pnChannels = std::min(pow2Up(comm->p2pnChannels), pow2Down(ncclDevMaxChannelsForArgsBytes(ncclParamWorkArgsBytes())));
|
||||
} else {
|
||||
// Round to next pow2 nChannelsPerPeer and nChannels
|
||||
comm->p2pnChannelsPerPeer = (ncclParamNChannelsPerPeer() == -2 ? pow2Up(minChannels) : ncclParamNChannelsPerPeer());
|
||||
comm->p2pnChannelsPerPeer = pow2Up(minChannels);
|
||||
// Doubling P2P channels per peer on single node
|
||||
if (comm->topo->nodes[GPU].count == comm->topo->nRanks && (IsArchMatch(comm->topo->nodes[GPU].nodes[0].gpu.gcn, "gfx942") || IsArchMatch(comm->topo->nodes[GPU].nodes[0].gpu.gcn, "gfx950"))) comm->p2pnChannelsPerPeer *= 2;
|
||||
comm->p2pnChannels = std::min(pow2Up(comm->p2pnChannels), 4*CHANNEL_LIMIT);
|
||||
|
||||
+254
-118
@@ -9,6 +9,7 @@
|
||||
#include "graph.h"
|
||||
#include "topo.h"
|
||||
#include "comm.h"
|
||||
#include "nccl.h"
|
||||
#include "nvmlwrap.h"
|
||||
#include "coll_net.h"
|
||||
#include "transport.h"
|
||||
@@ -16,6 +17,7 @@
|
||||
#include <fcntl.h>
|
||||
#include "cpuset.h"
|
||||
#include "bootstrap.h"
|
||||
#include <mutex>
|
||||
|
||||
#define BUSID_SIZE (sizeof("0000:00:00.0"))
|
||||
#define BUSID_REDUCED_SIZE (sizeof("0000:00"))
|
||||
@@ -427,6 +429,7 @@ ncclResult_t ncclTopoAddGpu(struct ncclXmlNode* xmlGpu, struct ncclTopoSystem* s
|
||||
|
||||
#define PCI_BRIDGE_DEVICE_CLASS "0x060400"
|
||||
|
||||
// struct kvDict kvDictPciClass[] = { { PCI_BRIDGE_DEVICE_CLASS, PCI }, {"0x080100", /*CX8 data direct*/PCI}, { "0x068000", NVS }, { "0x068001", CPU }, { "0x03", GPU }, { "0x02", NIC }, { NULL, PCI /* Default fallback value */ } };
|
||||
struct kvDict kvDictPciClass[] = { { PCI_BRIDGE_DEVICE_CLASS, PCI }, { "0x068000", NVS }, { "0x068001", CPU }, { "0x03", GPU }, { "0x02", NIC }, { "0x120000", GPU }, { NULL, PCI /* Default fallback value */ } };
|
||||
struct kvDict kvDictPciGen[] = {
|
||||
{ "2.5 GT/s", 15 }, { "5 GT/s", 30 }, { "8 GT/s", 60 }, { "16 GT/s", 120 }, { "32 GT/s", 240 }, /* Kernel 5.6 and earlier */
|
||||
@@ -1069,8 +1072,7 @@ ncclResult_t ncclTopoMakePciParent(struct ncclXml* xml, struct ncclXmlNode** par
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
ncclResult_t ncclTopoMakeVnic(struct ncclXml* xml, ncclNetVDeviceProps_t* vProps,
|
||||
struct ncclXmlNode** physNetNodes, ncclResult_t (*makeVDevice)(int*, ncclNetVDeviceProps_t*)) {
|
||||
ncclResult_t ncclTopoMakeVnic(struct ncclXml* xml, struct ncclTopoNetInfo* netInfo, ncclNetVDeviceProps_t* vProps, struct ncclXmlNode** physNetNodes) {
|
||||
if (vProps->ndevs > NCCL_NET_MAX_DEVS_PER_NIC) {
|
||||
WARN("TOPO/NET : Tried to merge too many NICs. %d > %d", vProps->ndevs, NCCL_NET_MAX_DEVS_PER_NIC);
|
||||
return ncclInternalError;
|
||||
@@ -1084,7 +1086,7 @@ struct ncclXmlNode** physNetNodes, ncclResult_t (*makeVDevice)(int*, ncclNetVDev
|
||||
|
||||
// Trigger the merge, then get the new device's properties
|
||||
int vDevIndex = 0;
|
||||
ncclResult_t ret = makeVDevice(&vDevIndex, vProps);
|
||||
ncclResult_t ret = netInfo->makeVDevice(&vDevIndex, vProps);
|
||||
if (ret != ncclSuccess) {
|
||||
INFO(NCCL_GRAPH|NCCL_INIT|NCCL_NET, "TOPO/NET : Tried merging multiple devices together and failed. vProps={ndevs=%d, devs=[%d %d %d %d]}. Set NCCL_NET_MERGE_LEVEL=LOC to disable NIC fusion.",
|
||||
vProps->ndevs, vProps->devs[0], vProps->devs[1], vProps->devs[2], vProps->devs[3]);
|
||||
@@ -1102,9 +1104,10 @@ struct ncclXmlNode** physNetNodes, ncclResult_t (*makeVDevice)(int*, ncclNetVDev
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
ncclResult_t ncclTopoForceMerge(struct ncclXml* xml, char* str, int* placedDevs, ncclNetProperties_t* propsList, struct ncclXmlNode** physNetNodes, int nPhysDevs, ncclResult_t (*makeVDevice)(int*, ncclNetVDeviceProps_t*)) {
|
||||
ncclResult_t ncclTopoForceMerge(struct ncclXml* xml, struct ncclTopoNetInfo* netInfo, int* placedDevs, ncclNetProperties_t* propsList, struct ncclXmlNode** physNetNodes, int nPhysDevs) {
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
INFO(NCCL_ENV|NCCL_NET, "TOPO/NET : Force-fusing NICs using NCCL_NET_FORCE_MERGE=%s", str);
|
||||
const char* str = netInfo->forceMerge;
|
||||
INFO(NCCL_ENV | NCCL_NET, "TOPO/NET : Force-fusing NICs using NCCL_NET_FORCE_MERGE=%s", str);
|
||||
char* ncStr;
|
||||
NCCLCHECK(ncclCalloc(&ncStr, strlen(str)+1));
|
||||
strcpy(ncStr, str);
|
||||
@@ -1140,7 +1143,7 @@ ncclResult_t ncclTopoForceMerge(struct ncclXml* xml, char* str, int* placedDevs,
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = ncclTopoMakeVnic(xml, &vProps, physNetNodes, makeVDevice);
|
||||
ret = ncclTopoMakeVnic(xml, netInfo, &vProps, physNetNodes);
|
||||
if (ret == ncclSuccess) {
|
||||
// Only set that a device is "placed" after successfully making a vNic (it's possible to exit before this)
|
||||
for (int i = 0; i < vProps.ndevs; i++) {
|
||||
@@ -1162,7 +1165,7 @@ fail:
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ncclResult_t ncclTopoAutoMerge(struct ncclXml* xml, int mergeLevel, int* placedDevs, ncclNetProperties_t* propsList, struct ncclXmlNode** physNetNodes, int nPhysDevs, ncclResult_t (*makeVDevice)(int*, ncclNetVDeviceProps_t*)) {
|
||||
ncclResult_t ncclTopoAutoMerge(struct ncclXml* xml, struct ncclTopoNetInfo* netInfo, int* placedDevs, ncclNetProperties_t* propsList, struct ncclXmlNode** physNetNodes, int nPhysDevs) {
|
||||
// Compute the path type between each device
|
||||
int* paths = NULL;
|
||||
ncclResult_t res = ncclSuccess;
|
||||
@@ -1192,7 +1195,7 @@ ncclResult_t ncclTopoAutoMerge(struct ncclXml* xml, int mergeLevel, int* placedD
|
||||
// Select each unplaced device "j" which is at most "mergeLevel" distance from "i", but not equal to "i"
|
||||
// (Don't merge the same device with itself)
|
||||
for (int j = 0; j < nPhysDevs; j++) {
|
||||
if (paths[i*nPhysDevs + j] <= mergeLevel &&
|
||||
if (paths[i*nPhysDevs + j] <= netInfo->mergeLevel &&
|
||||
placedDevs[j] == 0 && j != i) {
|
||||
vProps.devs[vProps.ndevs++] = j;
|
||||
placedDevs[j] = 1;
|
||||
@@ -1206,7 +1209,7 @@ ncclResult_t ncclTopoAutoMerge(struct ncclXml* xml, int mergeLevel, int* placedD
|
||||
return ncclInternalError;
|
||||
}
|
||||
|
||||
ncclResult_t ret = ncclTopoMakeVnic(xml, &vProps, physNetNodes, makeVDevice);
|
||||
ncclResult_t ret = ncclTopoMakeVnic(xml, netInfo, &vProps, physNetNodes);
|
||||
|
||||
// Merging failed.
|
||||
// Mark all as unplaced and increase their distance to disconnected (PATH_DIS)
|
||||
@@ -1244,6 +1247,92 @@ struct kvDict nicPathKvList[] = {
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
|
||||
ncclResult_t ncclTopoFindLinkWidthRec(ncclXmlNode* node, ncclXmlNode** physNetNodes, int ndevs, int* foundPhysNet, int* linkWidth) {
|
||||
int myLinkWidth = 0;
|
||||
if (strcmp(node->name, "pci") == 0) {
|
||||
NCCLCHECK(xmlGetAttrInt(node, "link_width", &myLinkWidth));
|
||||
#ifdef ENABLE_TRACE
|
||||
const char *busidAttr, *linkAttr;
|
||||
NCCLCHECK(xmlGetAttrStr(node, "busid", &busidAttr));
|
||||
NCCLCHECK(xmlGetAttr(node, "link_width", &linkAttr));
|
||||
TRACE(NCCL_GRAPH, "Found link_width (%s)=%d for busid=%s", linkAttr, myLinkWidth, busidAttr);
|
||||
#endif
|
||||
}
|
||||
|
||||
*foundPhysNet = 0;
|
||||
// Detect if a physical child is found. This information will be propagated up the stack.
|
||||
int devId = 0;
|
||||
while (devId < ndevs && !(*foundPhysNet)) *foundPhysNet = (node == physNetNodes[devId++]);
|
||||
|
||||
int totalChildLinkWidth = 0;
|
||||
for (int i = 0; i < node->nSubs; i++) {
|
||||
ncclXmlNode* child = node->subs[i];
|
||||
int found = 0;
|
||||
int tempLinkWidth = 0;
|
||||
NCCLCHECK(ncclTopoFindLinkWidthRec(child, physNetNodes, ndevs, &found, &tempLinkWidth));
|
||||
if (found) {
|
||||
*foundPhysNet = 1;
|
||||
totalChildLinkWidth += tempLinkWidth;
|
||||
}
|
||||
}
|
||||
|
||||
if (*foundPhysNet == 0) {
|
||||
// No child NICs were found, do not accrue any detected link_width
|
||||
*linkWidth = 0;
|
||||
INFO(NCCL_GRAPH, "Did not find child net device. Returning link_width=%d totalChildLinkWidth=%d", *linkWidth, totalChildLinkWidth);
|
||||
} else if (totalChildLinkWidth == 0) {
|
||||
// If A child NIC was found but no link_width was detected among children, assign the link_width to mine (I am the first pci node right above the physNetNode).
|
||||
*linkWidth = myLinkWidth;
|
||||
INFO(NCCL_GRAPH, "Found child net device for %s. Returning link_width=%d totalChildLinkWidth=%d", node->name, *linkWidth, totalChildLinkWidth);
|
||||
} else {
|
||||
// Standard recursive accrual of link_width. The link_width is either the bottleneck of this PCI node's width or the sum of its children's width.
|
||||
*linkWidth = myLinkWidth > 0 ? std::min(myLinkWidth, totalChildLinkWidth) : totalChildLinkWidth;
|
||||
INFO(NCCL_GRAPH, "Found child net device for %s. Returning link_width=%d totalChildLinkWidth=%d", node->name, *linkWidth, totalChildLinkWidth);
|
||||
}
|
||||
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
// DFS over nodes under common parent
|
||||
// Exclude link widths of non-physNetNodes chains
|
||||
ncclResult_t ncclTopoFindLinkWidth(ncclXmlNode* parent, ncclXmlNode** physNetNodes, int ndevs, int* linkWidth) {
|
||||
*linkWidth = 0;
|
||||
for (int i = 0; i < parent->nSubs; i++) {
|
||||
ncclXmlNode* child = parent->subs[i];
|
||||
int foundPhysNet = 0;
|
||||
int childLinkWidth = 0;
|
||||
NCCLCHECK(ncclTopoFindLinkWidthRec(child, physNetNodes, ndevs, &foundPhysNet, &childLinkWidth));
|
||||
if (foundPhysNet) {
|
||||
*linkWidth += childLinkWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
ncclResult_t ncclTopoWidenLinks(ncclXmlNode** physNetNodes, int ndevs, ncclXmlNode* parent) {
|
||||
int sumLinkWidth = 0;
|
||||
NCCLCHECK(ncclTopoFindLinkWidth(parent, physNetNodes, ndevs, &sumLinkWidth));
|
||||
for (int i = 0; i < ndevs; i++) {
|
||||
ncclXmlNode* temp = physNetNodes[i];
|
||||
while (temp != parent) {
|
||||
if (strcmp(temp->name, "pci") == 0) {
|
||||
NCCLCHECK(xmlSetAttrInt(temp, "link_width", sumLinkWidth));
|
||||
TRACE(NCCL_GRAPH, "Set link_width to %d for node %s", sumLinkWidth, temp->name);
|
||||
}
|
||||
temp = temp->parent;
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(parent->name, "pci") == 0) {
|
||||
NCCLCHECK(xmlSetAttrInt(parent, "link_width", sumLinkWidth));
|
||||
TRACE(NCCL_GRAPH, "Set link_width to %d for node %s", sumLinkWidth, parent->name);
|
||||
}
|
||||
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
ncclResult_t ncclTopoGetVNicParent(struct ncclXml* xml, ncclResult_t (*getProperties)(int, ncclNetProperties_t*), ncclNetVDeviceProps_t* vProps, ncclXmlNode** parent) {
|
||||
ncclNetProperties_t props[NCCL_NET_MAX_DEVS_PER_NIC];
|
||||
ncclXmlNode* physNetNodes[NCCL_NET_MAX_DEVS_PER_NIC];
|
||||
@@ -1257,54 +1346,50 @@ ncclResult_t ncclTopoGetVNicParent(struct ncclXml* xml, ncclResult_t (*getProper
|
||||
|
||||
int path = PATH_LOC;
|
||||
NCCLCHECK(ncclTopoGetPath(physNetNodes, vProps->ndevs, &path, parent));
|
||||
if (path == PATH_LOC) {
|
||||
*parent = NULL;
|
||||
} else if (parent && strcmp((*parent)->name, "pci") == 0) {
|
||||
// Compare PCI class here to avoid NCCL WARN when the "class" attribute doesn't exist
|
||||
const char* c;
|
||||
NCCLCHECK(xmlGetAttrStr(*parent, "class", &c));
|
||||
if (strcmp(c, PCI_BRIDGE_DEVICE_CLASS) == 0) {
|
||||
if (path == PATH_PHB || path == PATH_PXB || path == PATH_PIX) {
|
||||
INFO(NCCL_GRAPH, "Widening links");
|
||||
NCCLCHECK(ncclTopoWidenLinks(physNetNodes, vProps->ndevs, *parent));
|
||||
}
|
||||
|
||||
if (*parent) {
|
||||
if (strcmp((*parent)->name, "pci") == 0) {
|
||||
// Compare PCI class here to avoid NCCL WARN when the "class" attribute doesn't exist
|
||||
const char* c;
|
||||
NCCLCHECK(xmlGetAttrStr(*parent, "class", &c));
|
||||
if (c && strcmp(c, PCI_BRIDGE_DEVICE_CLASS) == 0) {
|
||||
// If the common parent is a PCI switch, we must reparent the new NIC under a made up pci device with a unique busid
|
||||
NCCLCHECK(ncclTopoMakePciParent(xml, parent, physNetNodes[0]));
|
||||
}
|
||||
} else if (strcmp((*parent)->name, "cpu") == 0) {
|
||||
// If the common parent is a PCI switch, we must reparent the new NIC under a made up pci device with a unique busid
|
||||
NCCLCHECK(ncclTopoMakePciParent(xml, parent, physNetNodes[0]));
|
||||
}
|
||||
}
|
||||
|
||||
TRACE(NCCL_GRAPH, "Selected parent %s with path %d", (*parent)->name, path);
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
ncclResult_t ncclTopoMakeVNics(struct ncclXml* xml, ncclResult_t (*makeVDevice)(int*, ncclNetVDeviceProps_t*), ncclResult_t (*getProperties)(int, ncclNetProperties_t*), int physicalDevs) {
|
||||
ncclResult_t ncclTopoMakeVNics(struct ncclXml* xml, struct ncclTopoNetInfo* netInfo, int physicalDevs) {
|
||||
int* placedDevs = NULL;
|
||||
struct ncclXmlNode** physNetNodes = NULL;
|
||||
ncclNetProperties_t* props = NULL;
|
||||
ncclResult_t res = ncclSuccess;
|
||||
if (physicalDevs == 0) return ncclSuccess;
|
||||
|
||||
ncclCalloc(&physNetNodes, physicalDevs);
|
||||
ncclResult_t res = ncclSuccess;
|
||||
|
||||
ncclNetProperties_t* props = NULL;
|
||||
ncclCalloc(&props, physicalDevs);
|
||||
NCCLCHECK(ncclCalloc(&physNetNodes, physicalDevs));
|
||||
NCCLCHECK(ncclCalloc(&placedDevs, physicalDevs));
|
||||
NCCLCHECK(ncclCalloc(&props, physicalDevs));
|
||||
for (int i = 0; i < physicalDevs; i++) {
|
||||
NCCLCHECKGOTO(getProperties(i, props + i), res, out);
|
||||
NCCLCHECKGOTO(netInfo->getProperties(i, props + i), res, out);
|
||||
struct ncclXmlNode* physNetNode;
|
||||
NCCLCHECKGOTO(xmlFindTagKv(xml, "net", &physNetNode, "name", props[i].name), res, out);
|
||||
physNetNodes[i] = physNetNode;
|
||||
TRACE(NCCL_GRAPH, "Found physical ncclNet node %d %s", i, props[i].name);
|
||||
}
|
||||
|
||||
// By default, don't merge any devices
|
||||
int mergeLevel;
|
||||
mergeLevel = PATH_PORT;
|
||||
{ // Avoids warnings related to jumping to "out"
|
||||
const char* mergeLevelEnv = ncclGetEnv("NCCL_NET_MERGE_LEVEL");
|
||||
if (mergeLevelEnv) kvConvertToInt(mergeLevelEnv, &mergeLevel, nicPathKvList);
|
||||
char* forceMerge = (char*) ncclGetEnv("NCCL_NET_FORCE_MERGE");
|
||||
NCCLCHECK(ncclCalloc(&placedDevs, physicalDevs));
|
||||
memset(placedDevs, 0, sizeof(int)*physicalDevs);
|
||||
|
||||
if (forceMerge) {
|
||||
NCCLCHECKGOTO(ncclTopoForceMerge(xml, forceMerge, placedDevs, props, physNetNodes, physicalDevs, makeVDevice), res, out);
|
||||
}
|
||||
}
|
||||
NCCLCHECKGOTO(ncclTopoAutoMerge(xml, mergeLevel, placedDevs, props, physNetNodes, physicalDevs, makeVDevice), res, out);
|
||||
if (netInfo->forceMerge) NCCLCHECKGOTO(ncclTopoForceMerge(xml, netInfo, placedDevs, props, physNetNodes, physicalDevs), res, out);
|
||||
NCCLCHECKGOTO(ncclTopoAutoMerge(xml, netInfo, placedDevs, props, physNetNodes, physicalDevs), res, out);
|
||||
|
||||
out:
|
||||
free(physNetNodes);
|
||||
@@ -1313,10 +1398,10 @@ out:
|
||||
return res;
|
||||
}
|
||||
|
||||
static ncclResult_t ncclTopoPopulateNics(ncclXml* xml, int startIndex, int endIndex, ncclResult_t (*getProperties)(int, ncclNetProperties_t*), const char* netName, int coll, int virtualNics, bool dmaBufSupport) {
|
||||
static ncclResult_t ncclTopoPopulateNics(ncclXml* xml, int startIndex, int endIndex, struct ncclTopoNetInfo* netInfo, int virtualNics) {
|
||||
for (int n = startIndex; n < endIndex; n++) {
|
||||
ncclNetProperties_t props;
|
||||
NCCLCHECK(getProperties(n, &props));
|
||||
NCCLCHECK(netInfo->getProperties(n, &props));
|
||||
struct ncclXmlNode* netNode = NULL;
|
||||
struct ncclXmlNode* parent = NULL;
|
||||
if (virtualNics) {
|
||||
@@ -1324,7 +1409,7 @@ static ncclResult_t ncclTopoPopulateNics(ncclXml* xml, int startIndex, int endIn
|
||||
NCCLCHECK(xmlFindTagKv(xml, "net", &net, "name", props.name));
|
||||
// In the event of multithreaded use case, we need to re-discover the shared parent of the given devices for this vNIC
|
||||
// Only run this if the net doesn't exist locally - this may alter the XML state
|
||||
if (net == NULL) NCCLCHECK(ncclTopoGetVNicParent(xml, getProperties, &props.vProps, &parent));
|
||||
if (net == NULL) NCCLCHECK(ncclTopoGetVNicParent(xml, netInfo->getProperties, &props.vProps, &parent));
|
||||
}
|
||||
|
||||
NCCLCHECK(ncclTopoFillNet(xml, props.pciPath, props.name, &netNode, parent));
|
||||
@@ -1335,18 +1420,18 @@ static ncclResult_t ncclTopoPopulateNics(ncclXml* xml, int startIndex, int endIn
|
||||
NCCLCHECK(xmlSetAttrInt(netNode, "keep", 1));
|
||||
int dev;
|
||||
xmlGetAttrIntDefault(netNode, "dev", &dev, -1);
|
||||
if (dev != -1 && dev != n) INFO(NCCL_GRAPH, "TOPO/NET : Changing %s dev index from %d to %d", netName, dev, n);
|
||||
if (dev != -1 && dev != n) INFO(NCCL_GRAPH, "TOPO/NET : Changing %s dev index from %d to %d", netInfo->name, dev, n);
|
||||
NCCLCHECK(xmlSetAttrInt(netNode, "dev", n));
|
||||
NCCLCHECK(xmlInitAttrInt(netNode, "latency", props.latency));
|
||||
NCCLCHECK(xmlInitAttrInt(netNode, "speed", props.speed));
|
||||
NCCLCHECK(xmlInitAttrInt(netNode, "port", props.port));
|
||||
NCCLCHECK(xmlInitAttrUint64(netNode, "guid", props.guid));
|
||||
NCCLCHECK(xmlInitAttrInt(netNode, "maxconn", props.maxComms));
|
||||
bool gdrSupport = (props.ptrSupport & NCCL_PTR_CUDA) || (dmaBufSupport && (props.ptrSupport & NCCL_PTR_DMABUF));
|
||||
INFO(NCCL_NET,"NET/%s : GPU Direct RDMA %s for HCA %d '%s'", netName, gdrSupport ? "Enabled" : "Disabled", n, props.name);
|
||||
bool gdrSupport = (props.ptrSupport & NCCL_PTR_CUDA) || (netInfo->dmaBufSupport && (props.ptrSupport & NCCL_PTR_DMABUF));
|
||||
INFO(NCCL_NET,"NET/%s : GPU Direct RDMA %s for HCA %d '%s'", netInfo->name, gdrSupport ? "Enabled" : "Disabled", n, props.name);
|
||||
NCCLCHECK(xmlInitAttrInt(netNode, "gdr", gdrSupport));
|
||||
// Only set coll if it's not 0
|
||||
if (coll) NCCLCHECK(xmlInitAttrInt(netNode, "coll", coll));
|
||||
if (netInfo->coll) NCCLCHECK(xmlInitAttrInt(netNode, "coll", netInfo->coll));
|
||||
|
||||
const char* keepAttr;
|
||||
NCCLCHECK(xmlGetAttr(netNode, "coll", &colAttr));
|
||||
@@ -1359,51 +1444,45 @@ static ncclResult_t ncclTopoPopulateNics(ncclXml* xml, int startIndex, int endIn
|
||||
}
|
||||
|
||||
// Calls to network plugin APIs should be protected. This function should be called inside a per-process lock.
|
||||
ncclResult_t ncclTopoProcessNet(ncclXml* xml, int coll, const char* dumpXmlFile, ncclTopoNetState* state, ncclResult_t (*getProperties)(int, ncclNetProperties_t*), ncclResult_t (*makeVDevice)(int*, ncclNetVDeviceProps_t*), ncclResult_t (*devices)(int*), const char* netName, bool dmaBufSupport) {
|
||||
int usePhysicalDevices = (dumpXmlFile || makeVDevice == NULL);
|
||||
if (state->nPhysicalNics == -1) NCCLCHECK(devices(&state->nPhysicalNics));
|
||||
// Enumerate physical devices
|
||||
NCCLCHECK(ncclTopoPopulateNics(xml, 0, state->nPhysicalNics, getProperties, netName, coll, false, dmaBufSupport));
|
||||
ncclResult_t ncclTopoProcessNet(ncclXml* xml, const char* dumpXmlFile, struct ncclTopoNetInfo* net) {
|
||||
bool usePhysicalDevices = (dumpXmlFile || net->makeVDevice == NULL);
|
||||
int nPhysicalNics, nVirtualNics;
|
||||
NCCLCHECK(net->getDevCount(net->netPluginIndex, &nPhysicalNics, &nVirtualNics));
|
||||
// List the physical devices in the topo
|
||||
NCCLCHECK(ncclTopoPopulateNics(xml, 0, nPhysicalNics, net, /*virtual=*/false));
|
||||
if (!usePhysicalDevices) {
|
||||
if (state->nVirtualNics == -1) {
|
||||
NCCLCHECK(ncclTopoMakeVNics(xml, makeVDevice, getProperties, state->nPhysicalNics));
|
||||
// Virtual devices are only created once per network
|
||||
if (nVirtualNics == NCCL_UNDEF_DEV_COUNT) {
|
||||
NCCLCHECK(ncclTopoMakeVNics(xml, net, nPhysicalNics));
|
||||
// Update the number of virtual devices both locally and in the state tracking the plugin.
|
||||
// Note: 0 is a valid number of virtual devices
|
||||
int nDevs;
|
||||
NCCLCHECK(devices(&nDevs));
|
||||
state->nVirtualNics = nDevs - state->nPhysicalNics;
|
||||
NCCLCHECK(net->devices(&nDevs));
|
||||
nVirtualNics = nDevs - nPhysicalNics;
|
||||
NCCLCHECK(net->setVirtDevCount(net->netPluginIndex, nVirtualNics));
|
||||
}
|
||||
if (state->nVirtualNics > 0) {
|
||||
// Populate new devices
|
||||
NCCLCHECK(ncclTopoPopulateNics(xml, state->nPhysicalNics, state->nPhysicalNics+state->nVirtualNics, getProperties, netName, coll, true, dmaBufSupport));
|
||||
// populate the virtual devices if any
|
||||
if (nVirtualNics > 0) {
|
||||
NCCLCHECK(ncclTopoPopulateNics(xml, nPhysicalNics, nPhysicalNics + nVirtualNics, net, /*virtual=*/true));
|
||||
}
|
||||
}
|
||||
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
static pthread_mutex_t netLock = PTHREAD_MUTEX_INITIALIZER;
|
||||
ncclTopoNetState netStates[NCCL_NET_MAX_PLUGINS] = {};
|
||||
ncclTopoNetState collNetStates[NCCL_NET_MAX_PLUGINS] = {};
|
||||
ncclResult_t ncclTopoGetSharedState(ncclTopoNetState** state, const char* name, ncclTopoNetState* states) {
|
||||
INFO(NCCL_GRAPH, "Retrieving state for %s", name);
|
||||
for (int i = 0; i < NCCL_NET_MAX_PLUGINS; i++) {
|
||||
// Empty slot
|
||||
if (states[i].name == NULL) {
|
||||
states[i].nVirtualNics = -1;
|
||||
states[i].nPhysicalNics = -1;
|
||||
states[i].name = strdup(name);
|
||||
*state = states + i;
|
||||
INFO(NCCL_GRAPH, "Initialized state %d for %s", i, name);
|
||||
return ncclSuccess;
|
||||
// Found my slot
|
||||
} else if (strcmp(states[i].name, name) == 0) {
|
||||
*state = states + i;
|
||||
return ncclSuccess;
|
||||
}
|
||||
ncclResult_t ncclTopoGetFusionEnv(int* mergeLevel, const char** forceMerge) {
|
||||
if (forceMerge) *forceMerge = ncclGetEnv("NCCL_NET_FORCE_MERGE");
|
||||
const char* mergeLevelEnv = ncclGetEnv("NCCL_NET_MERGE_LEVEL");
|
||||
if (mergeLevelEnv) {
|
||||
kvConvertToInt(mergeLevelEnv, mergeLevel, nicPathKvList);
|
||||
} else {
|
||||
*mergeLevel = PATH_PORT;
|
||||
}
|
||||
WARN("NET/TOPO : Couldn't find net with name %s", name);
|
||||
return ncclInternalError;
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
static std::mutex netMutex;
|
||||
|
||||
ncclResult_t ncclTopoGetSystem(struct ncclComm* comm, struct ncclTopoSystem** system, const char* dumpXmlFile) {
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
struct ncclXml* xml;
|
||||
@@ -1411,7 +1490,7 @@ ncclResult_t ncclTopoGetSystem(struct ncclComm* comm, struct ncclTopoSystem** sy
|
||||
int* localRanks = NULL;
|
||||
struct ncclXml* rankXml;
|
||||
int localRank = -1, nLocalRanks = 0;
|
||||
int netLockHeld = 0;
|
||||
struct ncclTopoNetInfo netInfo = {0};
|
||||
NCCLCHECK(xmlAlloc(&xml, NCCL_TOPO_XML_MAX_NODES));
|
||||
const char* xmlTopoFile = ncclGetEnv("NCCL_TOPO_FILE");
|
||||
if (xmlTopoFile) {
|
||||
@@ -1451,21 +1530,35 @@ ncclResult_t ncclTopoGetSystem(struct ncclComm* comm, struct ncclTopoSystem** sy
|
||||
|
||||
// Auto-detect NICs if needed. net/collnet share the same xml/graph nodes,
|
||||
// so we start with collnet so that it has precedence.
|
||||
pthread_mutex_lock(&netLock);
|
||||
netLockHeld = 1;
|
||||
INFO(NCCL_GRAPH, "TOPO/NET : Importing network plugins to topology");
|
||||
ncclTopoNetState* state;
|
||||
state = NULL;
|
||||
if (collNetSupport(comm)) {
|
||||
NCCLCHECKGOTO(ncclTopoGetSharedState(&state, comm->ncclCollNet->name, collNetStates), ret, fail);
|
||||
NCCLCHECKGOTO(ncclTopoProcessNet(xml, 1, dumpXmlFile, state,
|
||||
comm->ncclCollNet->getProperties, comm->ncclCollNet->makeVDevice, comm->ncclCollNet->devices, comm->ncclCollNet->name, comm->dmaBufSupport), ret, fail);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(netMutex);
|
||||
INFO(NCCL_GRAPH, "TOPO/NET : Importing network plugins to topology");
|
||||
if (collNetSupport(comm)) {
|
||||
netInfo.coll = 1;
|
||||
netInfo.netPluginIndex = comm->netPluginIndex;
|
||||
netInfo.dmaBufSupport = comm->dmaBufSupport;
|
||||
netInfo.getDevCount = ncclCollNetGetDevCount;
|
||||
netInfo.setVirtDevCount = ncclCollNetSetVirtDevCount;
|
||||
netInfo.name = comm->ncclCollNet->name;
|
||||
netInfo.getProperties = comm->ncclCollNet->getProperties;
|
||||
netInfo.makeVDevice = comm->ncclCollNet->makeVDevice;
|
||||
netInfo.devices = comm->ncclCollNet->devices;
|
||||
NCCLCHECK(ncclTopoGetFusionEnv(&netInfo.mergeLevel, &netInfo.forceMerge));
|
||||
NCCLCHECKGOTO(ncclTopoProcessNet(xml, dumpXmlFile, &netInfo), ret, fail);
|
||||
}
|
||||
|
||||
netInfo.coll = 0;
|
||||
netInfo.netPluginIndex = comm->netPluginIndex;
|
||||
netInfo.dmaBufSupport = comm->dmaBufSupport;
|
||||
netInfo.getDevCount = ncclNetGetDevCount;
|
||||
netInfo.setVirtDevCount = ncclNetSetVirtDevCount;
|
||||
netInfo.name = comm->ncclNet->name;
|
||||
netInfo.getProperties = comm->ncclNet->getProperties;
|
||||
netInfo.makeVDevice = comm->ncclNet->makeVDevice;
|
||||
netInfo.devices = comm->ncclNet->devices;
|
||||
NCCLCHECK(ncclTopoGetFusionEnv(&netInfo.mergeLevel, &netInfo.forceMerge));
|
||||
NCCLCHECKGOTO(ncclTopoProcessNet(xml, dumpXmlFile, &netInfo), ret, fail);
|
||||
}
|
||||
NCCLCHECKGOTO(ncclTopoGetSharedState(&state, comm->ncclNet->name, netStates), ret, fail);
|
||||
NCCLCHECKGOTO(ncclTopoProcessNet(xml, 0, dumpXmlFile, state,
|
||||
comm->ncclNet->getProperties, comm->ncclNet->makeVDevice, comm->ncclNet->devices, comm->ncclNet->name, comm->dmaBufSupport), ret, fail);
|
||||
pthread_mutex_unlock(&netLock);
|
||||
netLockHeld = 0;
|
||||
|
||||
// Remove XML branches which don't have a node with keep="1" (typically when importing a topology)
|
||||
NCCLCHECKGOTO(ncclTopoTrimXml(xml), ret, fail);
|
||||
@@ -1523,7 +1616,6 @@ exit:
|
||||
free(xml);
|
||||
return ret;
|
||||
fail:
|
||||
if (netLockHeld) pthread_mutex_unlock(&netLock);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
@@ -1578,6 +1670,38 @@ ncclResult_t getLocalNetCountByBw(struct ncclTopoSystem* system, int gpu, int *c
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
enum netDevsPolicy {
|
||||
NETDEVS_POLICY_AUTO = 0x0,
|
||||
NETDEVS_POLICY_ALL = 0x1,
|
||||
NETDEVS_POLICY_MAX = 0x2,
|
||||
NETDEVS_POLICY_UNDEF = 0xffffffff
|
||||
};
|
||||
|
||||
static enum netDevsPolicy netDevsPolicy = NETDEVS_POLICY_UNDEF;
|
||||
static int netDevsPolicyNum = -1;
|
||||
|
||||
static void getNetDevsPolicyOnce() {
|
||||
const char* envStr = ncclGetEnv("NCCL_NETDEVS_POLICY");
|
||||
if (envStr) {
|
||||
if (strcasecmp(envStr, "AUTO") == 0) {
|
||||
netDevsPolicy = NETDEVS_POLICY_AUTO;
|
||||
} else if (strcasecmp(envStr, "ALL") == 0) {
|
||||
netDevsPolicy = NETDEVS_POLICY_ALL;
|
||||
} else if (strncasecmp(envStr, "MAX:", strlen("MAX:")) == 0) {
|
||||
int envNum = atoi(envStr + strlen("MAX:"));
|
||||
if (envNum > 0) {
|
||||
netDevsPolicy = NETDEVS_POLICY_MAX;
|
||||
netDevsPolicyNum = envNum;
|
||||
}
|
||||
}
|
||||
if (netDevsPolicy == NETDEVS_POLICY_UNDEF)
|
||||
INFO(NCCL_ENV, "Unable to recognize NCCL_NETDEVS_POLICY=%s, using NCCL_NETDEVS_POLICY_AUTO instead.", envStr);
|
||||
else
|
||||
INFO(NCCL_ENV, "NCCL_NETDEVS_POLICY set by environment to %s", envStr);
|
||||
}
|
||||
if (netDevsPolicy == NETDEVS_POLICY_UNDEF) netDevsPolicy = NETDEVS_POLICY_AUTO;
|
||||
}
|
||||
|
||||
ncclResult_t ncclTopoGetLocalNet(struct ncclTopoSystem* system, int rank, int channelId, int64_t* id, int* dev) {
|
||||
int gpu;
|
||||
NCCLCHECK(ncclTopoRankToIndex(system, rank, &gpu, /*showWarn=*/true));
|
||||
@@ -1592,13 +1716,30 @@ ncclResult_t ncclTopoGetLocalNet(struct ncclTopoSystem* system, int rank, int ch
|
||||
return ncclInternalError;
|
||||
}
|
||||
|
||||
int localGpus[NCCL_TOPO_MAX_NODES];
|
||||
int localGpuCount;
|
||||
NCCLCHECK(ncclTopoGetLocal(system, NET, localNets[0], GPU, localGpus, &localGpuCount, NULL));
|
||||
static pthread_once_t once = PTHREAD_ONCE_INIT;
|
||||
pthread_once(&once,getNetDevsPolicyOnce);
|
||||
int netsPerGpu = 0;
|
||||
if (netDevsPolicy == NETDEVS_POLICY_AUTO) {
|
||||
int localGpus[NCCL_TOPO_MAX_NODES];
|
||||
int localGpuCount;
|
||||
NCCLCHECK(ncclTopoGetLocal(system, NET, localNets[0], GPU, localGpus, &localGpuCount, NULL));
|
||||
netsPerGpu = DIVUP(localNetCount, localGpuCount);
|
||||
} else if (netDevsPolicy == NETDEVS_POLICY_ALL) {
|
||||
netsPerGpu = localNetCount;
|
||||
} else if (netDevsPolicy == NETDEVS_POLICY_MAX) {
|
||||
if (netDevsPolicyNum <= 0) {
|
||||
WARN("Invalid number of network devices = %d for policy MAX", netDevsPolicyNum);
|
||||
return ncclInternalError;
|
||||
}
|
||||
netsPerGpu = std::min(netDevsPolicyNum, localNetCount);
|
||||
} else {
|
||||
WARN("Unknown netDevs policy");
|
||||
return ncclInternalError;
|
||||
}
|
||||
|
||||
int net = system->nodes[GPU].nodes[gpu].gpu.dev;
|
||||
if (isPow2(localNetCount)) net = mirrorBits(net, localNetCount);
|
||||
net += channelId%(DIVUP(localNetCount,localGpuCount));
|
||||
net += channelId%(netsPerGpu);
|
||||
if (id) *id = system->nodes[NET].nodes[localNets[net%localNetCount]].id;
|
||||
if (dev) *dev = system->nodes[NET].nodes[localNets[net%localNetCount]].net.dev;
|
||||
return ncclSuccess;
|
||||
@@ -1656,25 +1797,10 @@ ncclResult_t ncclTopoGetCpuAffinity(struct ncclTopoSystem* system, int rank, cpu
|
||||
cpu_set_t mask;
|
||||
SYSCHECK(sched_getaffinity(0, sizeof(cpu_set_t), &mask), "sched_getaffinity");
|
||||
|
||||
#ifdef ENABLE_TRACE
|
||||
{
|
||||
char affinityStr[sizeof(cpu_set_t)*2];
|
||||
TRACE(NCCL_INIT, "Current affinity for GPU %d is %s", gpu->gpu.dev,
|
||||
ncclCpusetToRangeStr(&mask, affinityStr, sizeof(affinityStr)));
|
||||
}
|
||||
#endif
|
||||
|
||||
// Get the affinity of the CPU close to our GPU.
|
||||
cpu_set_t cpuMask = cpu->cpu.affinity;
|
||||
|
||||
#ifdef ENABLE_TRACE
|
||||
{
|
||||
char affinityStr[sizeof(cpu_set_t)*2];
|
||||
TRACE(NCCL_INIT, "CPU GPU affinity for GPU %d is %s", gpu->gpu.dev,
|
||||
ncclCpusetToRangeStr(&cpuMask, affinityStr, sizeof(affinityStr)));
|
||||
}
|
||||
#endif
|
||||
|
||||
// Get the final affinity
|
||||
cpu_set_t finalMask;
|
||||
if (ncclParamIgnoreCpuAffinity())
|
||||
// Ignore the CPU affinity set and use the GPU one instead
|
||||
@@ -1685,12 +1811,22 @@ ncclResult_t ncclTopoGetCpuAffinity(struct ncclTopoSystem* system, int rank, cpu
|
||||
|
||||
memcpy(affinity, &finalMask, sizeof(cpu_set_t));
|
||||
|
||||
// If there is a non empty set, use it to set affinity
|
||||
// display the final affinity
|
||||
char msg[1024] = "";
|
||||
snprintf(msg + strlen(msg), sizeof(msg) - strlen(msg), "Affinity for GPU %d is ", gpu->gpu.dev);
|
||||
if (CPU_COUNT(&finalMask)) {
|
||||
char affinityStr[sizeof(cpu_set_t)*2];
|
||||
INFO(NCCL_INIT, "Setting affinity for GPU %d to %s", gpu->gpu.dev,
|
||||
ncclCpusetToRangeStr(&finalMask, affinityStr, sizeof(affinityStr)));
|
||||
(void)ncclCpusetToRangeStr(&finalMask, msg + strlen(msg), sizeof(msg) - strlen(msg));
|
||||
} else {
|
||||
snprintf(msg + strlen(msg), sizeof(msg) - strlen(msg), "empty, ignoring");
|
||||
}
|
||||
snprintf(msg + strlen(msg), sizeof(msg) - strlen(msg), ". (GPU affinity = ");
|
||||
(void)ncclCpusetToRangeStr(&cpuMask, msg + strlen(msg), sizeof(msg) - strlen(msg));
|
||||
if (!ncclParamIgnoreCpuAffinity()) {
|
||||
snprintf(msg + strlen(msg), sizeof(msg) - strlen(msg), " ; CPU affinity = ");
|
||||
(void)ncclCpusetToRangeStr(&mask, msg + strlen(msg), sizeof(msg) - strlen(msg));
|
||||
}
|
||||
snprintf(msg + strlen(msg), sizeof(msg) - strlen(msg), ").");
|
||||
INFO(NCCL_INIT, "%s: %s", __func__, msg);
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
|
||||
@@ -229,12 +229,26 @@ ncclResult_t ncclTopoGetGpuMinPath(struct ncclTopoSystem* system, int type, int*
|
||||
ncclResult_t ncclTopoGetGpuMaxPath(struct ncclTopoSystem* system, int type, int* max);
|
||||
ncclResult_t ncclTopoSplitNvLink(struct ncclTopoSystem* system, int* splitNvLink);
|
||||
|
||||
struct ncclTopoNetState {
|
||||
int nVirtualNics;
|
||||
int nPhysicalNics;
|
||||
struct ncclTopoNetInfo {
|
||||
bool coll;
|
||||
// communicator-specific information
|
||||
int netPluginIndex;
|
||||
bool dmaBufSupport;
|
||||
// NIC fusion
|
||||
int mergeLevel;
|
||||
const char* forceMerge;
|
||||
// dev count tracking functions (not part of ncclNet)
|
||||
ncclResult_t (*getDevCount)(int, int*, int*);
|
||||
ncclResult_t (*setVirtDevCount)(int, int);
|
||||
// ncclNet API functions
|
||||
const char* name;
|
||||
ncclResult_t (*getProperties)(int, ncclNetProperties_t*);
|
||||
ncclResult_t (*makeVDevice)(int*, ncclNetVDeviceProps_t*);
|
||||
ncclResult_t (*devices)(int*);
|
||||
};
|
||||
ncclResult_t ncclTopoProcessNet(ncclXml* xml, int coll, const char* dumpXmlFile, ncclTopoNetState* state, ncclResult_t (*getProperties)(int, ncclNetProperties_t*), ncclResult_t (*makeVDevice)(int*, ncclNetVDeviceProps_t*), ncclResult_t (*devices)(int*), const char* netName, bool dmaBufSupport);
|
||||
|
||||
ncclResult_t ncclTopoProcessNet(ncclXml* xml, const char* dumpXmlFile, struct ncclTopoNetInfo* net);
|
||||
ncclResult_t ncclTopoGetFusionEnv(int* mergeLevel, const char** forceMerge);
|
||||
|
||||
#define NCCL_TOPO_XML_MAX_NODES 8192
|
||||
#define NCCL_GRAPH_XML_MAX_NODES 8192
|
||||
@@ -279,6 +293,8 @@ static ncclResult_t ncclTopoDevToRank(struct ncclTopoSystem* system, int dev, in
|
||||
return ncclInternalError;
|
||||
}
|
||||
|
||||
extern struct kvDict nicPathKvList[];
|
||||
|
||||
static ncclResult_t ncclTopoIdToNetDev(struct ncclTopoSystem* system, int64_t id, int* netDev) {
|
||||
*netDev = -1;
|
||||
for (int i=0; i<system->nodes[NET].count; i++) {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "device.h"
|
||||
#include "comm.h"
|
||||
#include "topo.h"
|
||||
#include "nccl_tuner.h"
|
||||
|
||||
NCCL_PARAM(Nthreads, "NTHREADS", -2);
|
||||
NCCL_PARAM(Ll128Nthreads, "LL128_NTHREADS", -2);
|
||||
@@ -484,40 +485,73 @@ static struct tuningModel rcclTuningModel[] = {
|
||||
tuning_model_7,
|
||||
};
|
||||
|
||||
/* Array indexes used below */
|
||||
#define VOLTA_COMPCAP_IDX 0
|
||||
#define AMPERE_COMPCAP_IDX 1
|
||||
#define HOPPER_COMPCAP_IDX 2
|
||||
#define BLACKWELL_COMPCAP_IDX 3
|
||||
|
||||
#if !defined(__HIP_PLATFORM_AMD__) && !defined(__HIPCC__)
|
||||
// LL128 max BW per channel
|
||||
static const double llMaxBws[][3] = {
|
||||
/* Volta-N1/Intel-N2/Intel-N4) */ {39.0, 39.0, 20.4},
|
||||
/* Ampere-N1/AMD-N2/AMD-N4) */ {87.7, 22.5 /*avg of ring & tree*/, 19.0},
|
||||
/* Hopper-N1/AMD-N2/AMD-N4) */ {141.0, 45.0 /*avg of ring & tree*/, 35.0},
|
||||
/* Blackwell-N1/AMD-N2/AMD-N4) */ {2*141.0, 2*45.0 /*avg of ring & tree*/, 2*35.0},
|
||||
// NVLS efficiency factor.
|
||||
static const float nvlsEfficiency[NCCL_NUM_COMPCAPS] = {
|
||||
0.0f, // Volta
|
||||
0.0f, // Ampere
|
||||
0.85f, // Hopper
|
||||
0.74f, // Blackwell
|
||||
};
|
||||
|
||||
static const double perChMaxRingLL128Bws[][3] = {
|
||||
/* Volta (N1/N2/N4) */ {20.0, 20.0, 20.0},
|
||||
/* Ampere (N1/N2/N4) */ {20.0, 20.0, 20.0},
|
||||
/* Hopper (N1/N2/N4) */ {36.7, 36.7, 36.7},
|
||||
/* Blackwell (N1/N2/N4) */ {2*36.7, 2*36.7, 2*36.7},
|
||||
// Default tuner constants
|
||||
static const ncclTunerConstants_t ncclTunerConstantsDefaults = {
|
||||
.baseLatencies = {
|
||||
{ 6.8, 14.0, 8.4 }, { 6.6, 14.0, 8.4 }, // Tree, Ring
|
||||
{ 0, 0, 0 }, { 0, 0, 0 }, // Collnet Direct, Chain
|
||||
{ 0, 0, 0 }, { 0, 0, 0 }, // NVLS, NVLS Tree
|
||||
{ 8.0, 8.0, 8.0 } // PAT
|
||||
},
|
||||
.hwLatencies = {
|
||||
/* NVLINK */
|
||||
{ { .6, 1.25, 4.0 }, { .6, 1.9, 3.4 }, /* Tree (LL/LL128/Simple), Ring (LL/LL128/Simple)*/
|
||||
{ 0, 0, 3.7 }, { 0, 0, 2.8 }, /* CollNetDirect (LL/LL128/Simple), CollNetChain (LL/LL128/Simple)*/
|
||||
{ 0, 0, 25 }, { 0, 0, 25 }, /* NVLS (LL/LL128/Simple), NVLSTree (LL/LL128/Simple)*/
|
||||
{ 0, 0, 4.0 } /* PAT (LL/LL128/Simple)*/
|
||||
},
|
||||
/* PCI */
|
||||
{ { 1.0, 1.9, 4.0 }, { 1.0, 2.5, 5.7 }, /* Tree (LL/LL128/Simple), Ring (LL/LL128/Simple)*/
|
||||
{ 0, 0, 3.7 }, { 0, 0, 2.8 }, /* CollNetDirect (LL/LL128/Simple), CollNetChain (LL/LL128/Simple)*/
|
||||
{ 0, 0, 0 }, { 0, 0, 0 }, /* NVLS (LL/LL128/Simple), NVLSTree (LL/LL128/Simple)*/
|
||||
{ 0, 0, 4.0 } /* PAT (LL/LL128/Simple)*/
|
||||
},
|
||||
/* NET */
|
||||
{ { 5.0, 8.5, 14 }, { 2.7, 4.0, 14.0 }, /* Tree (LL/LL128/Simple), Ring (LL/LL128/Simple)*/
|
||||
{ 0, 0, 31 }, { 0, 0, 30 }, /* CollNetDirect (LL/LL128/Simple), CollNetChain (LL/LL128/Simple)*/
|
||||
{ 0, 0, 18 }, { 0, 0, 14 }, /* NVLS (LL/LL128/Simple), NVLSTree (LL/LL128/Simple)*/
|
||||
{ 0, 0, 14 } /* PAT (LL/LL128/Simple)*/
|
||||
},
|
||||
},
|
||||
.llMaxBws = {
|
||||
{39.0, 39.0, 20.4}, /* Volta-N1/Intel-N2/Intel-N4) */
|
||||
{87.7, 22.5 /*avg of ring & tree*/, 19.0}, /* Ampere-N1/AMD-N2/AMD-N4) */
|
||||
{141.0, 45.0 /*avg of ring & tree*/, 35.0}, /* Hopper-N1/AMD-N2/AMD-N4) */
|
||||
{2*141.0, 2*45.0 /*avg of ring & tree*/, 2*35.0}, /* Blackwell-N1/AMD-N2/AMD-N4) */
|
||||
},
|
||||
.perChMaxRingLL128Bws = {
|
||||
{20.0, 20.0, 20.0}, /* Volta (N1/N2/N4) */
|
||||
{20.0, 20.0, 20.0}, /* Ampere (N1/N2/N4) */
|
||||
{36.7, 36.7, 36.7}, /* Hopper (N1/N2/N4) */
|
||||
{2*36.7, 2*36.7, 2*36.7}, /* Blackwell (N1/N2/N4) */
|
||||
},
|
||||
.perChMaxTreeLL128Bws = {
|
||||
{20.0, 20.0, 20.0}, /* Volta (N1/N2/N4) */
|
||||
{20.0, 20.0, 20.0}, /* Ampere (N1/N2/N4) */
|
||||
{36.7, 36.7, 29.0}, /* Hopper (N1/N2/N4) */
|
||||
{55.6, 31.67, 20.0}, /* Blackwell (N1/N2/N4) */
|
||||
},
|
||||
.perChMaxTreeBws = {
|
||||
{26.5, 18.5, 10.0}, /* Volta (N1/N2/N4) */
|
||||
{24.0, 23.6, 17.8}, /* Ampere (N1/N2/N4) */
|
||||
{38.7, 41.4, 36.0}, /* Hopper (N1/N2/N4) */
|
||||
{70.0, 42.8, 24.0}, /* Blackwell (N1/N2/N4) */
|
||||
},
|
||||
.perChMaxNVLSTreeBws = {
|
||||
{26.5, 18.5, 10.0}, /* Volta (N1/N2/N4) */
|
||||
{24.0, 23.6, 17.8}, /* Ampere (N1/N2/N4) */
|
||||
{0.0, 57.7, 45.5}, /* Hopper (N1/N2/N4) */
|
||||
{0.0, 96.0, 43.1} /* Blackwell (N1/N2/N4) */
|
||||
}
|
||||
};
|
||||
static const double perChMaxTreeLL128Bws[][3] = {
|
||||
/* Volta (N1/N2/N4) */ {20.0, 20.0, 20.0},
|
||||
/* Ampere (N1/N2/N4) */ {20.0, 20.0, 20.0},
|
||||
/* Hopper (N1/N2/N4) */ {36.7, 36.7, 29.0},
|
||||
/* Blackwell (N1/N2/N4) */ {2*36.7, 2*36.7, 2*29.0},
|
||||
};
|
||||
static const double perChMaxTreeBws[][3] = {
|
||||
/* Volta (N1/N2/N4) */ {26.5, 18.5, 10.0},
|
||||
/* Ampere (N1/N2/N4) */ {24.0, 23.6, 17.8},
|
||||
/* Hopper (N1/N2/N4) */ {38.7, 41.4, 36.0},
|
||||
/* Blackwell (N1/N2/N4) */ {2*38.7, 2*41.4, 2*36.0},
|
||||
};
|
||||
#endif
|
||||
|
||||
NCCL_PARAM(PatEnable, "PAT_ENABLE", 0);
|
||||
static int ncclPatEnable(struct ncclComm* comm) {
|
||||
@@ -542,6 +576,13 @@ static float getNetOverhead(struct ncclComm* comm) {
|
||||
|
||||
NCCL_PARAM(Ll128C2c, "LL128_C2C", 1);
|
||||
|
||||
ncclResult_t ncclTopoInitTunerConstants(struct ncclComm* comm) {
|
||||
|
||||
comm->tunerConstants = ncclTunerConstantsDefaults;
|
||||
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
ncclResult_t ncclTopoTuneModel(struct ncclComm* comm, int minCompCap, int maxCompCap, struct ncclTopoGraph** graphs) {
|
||||
#if defined(__HIP_PLATFORM_AMD__) || defined(__HIPCC__)
|
||||
static int rcclMaxThreads[NCCL_NUM_PROTOCOLS] = {0};
|
||||
@@ -576,18 +617,19 @@ ncclResult_t ncclTopoTuneModel(struct ncclComm* comm, int minCompCap, int maxCom
|
||||
int nRanks = comm->nRanks;
|
||||
if (nRanks <= 1) return ncclSuccess;
|
||||
#if !defined(__HIP_PLATFORM_AMD__) && !defined(__HIPCC__)
|
||||
int compCapIndex = minCompCap >= 100 ? BLACKWELL_COMPCAP_IDX : (minCompCap >= 90 ? HOPPER_COMPCAP_IDX : minCompCap >= 80 ? AMPERE_COMPCAP_IDX : VOLTA_COMPCAP_IDX);
|
||||
int compCapIndex = minCompCap >= 100 ? NCCL_BLACKWELL_COMPCAP_IDX : (minCompCap >= 90 ? NCCL_HOPPER_COMPCAP_IDX : minCompCap >= 80 ? NCCL_AMPERE_COMPCAP_IDX : NCCL_VOLTA_COMPCAP_IDX);
|
||||
int index2 = nNodes <= 2 ? nNodes-1 : 2;
|
||||
// LL: for single node, we look at GPU type; for multi-node, we look at CPU type
|
||||
int index1 = nNodes == 1 ? compCapIndex :
|
||||
(comm->cpuVendor == NCCL_TOPO_CPU_VENDOR_AMD || comm->cpuVendor == NCCL_TOPO_CPU_VENDOR_MIXED) ? 1 : 0;
|
||||
double llMaxBw = llMaxBws[index1][index2];
|
||||
double perChMaxTreeBw = perChMaxTreeBws[compCapIndex][index2];
|
||||
double perChMaxRingLL128Bw = perChMaxRingLL128Bws[compCapIndex][index2];
|
||||
double perChMaxTreeLL128Bw = perChMaxTreeLL128Bws[compCapIndex][index2];
|
||||
#endif
|
||||
double llMaxBw = comm->tunerConstants.llMaxBws[index1][index2];
|
||||
double perChMaxTreeBw = comm->tunerConstants.perChMaxTreeBws[compCapIndex][index2];
|
||||
double perChMaxRingLL128Bw = comm->tunerConstants.perChMaxRingLL128Bws[compCapIndex][index2];
|
||||
double perChMaxTreeLL128Bw = comm->tunerConstants.perChMaxTreeLL128Bws[compCapIndex][index2];
|
||||
double perChMaxNVLSTreeBw = comm->tunerConstants.perChMaxNVLSTreeBws[compCapIndex][index2];
|
||||
// De-penalize Tree/Simple latency on Power systems to favor Tree than Ring
|
||||
//if (comm->cpuArch == NCCL_TOPO_CPU_ARCH_POWER) hwLat[NCCL_HW_PCI][NCCL_ALGO_TREE][NCCL_PROTO_SIMPLE] = hwLat[NCCL_HW_PCI][NCCL_ALGO_RING][NCCL_PROTO_SIMPLE];
|
||||
if (comm->cpuArch == NCCL_TOPO_CPU_ARCH_POWER) comm->tunerConstants.hwLatencies[NCCL_HW_PCI][NCCL_ALGO_TREE][NCCL_PROTO_SIMPLE] = comm->tunerConstants.hwLatencies[NCCL_HW_PCI][NCCL_ALGO_RING][NCCL_PROTO_SIMPLE];
|
||||
#endif
|
||||
float ppn = (float)nRanks / nNodes;
|
||||
|
||||
int intraHw[NCCL_NUM_ALGORITHMS], hw[NCCL_NUM_ALGORITHMS];
|
||||
@@ -621,18 +663,25 @@ ncclResult_t ncclTopoTuneModel(struct ncclComm* comm, int minCompCap, int maxCom
|
||||
&& a == NCCL_ALGO_PAT && (p != NCCL_PROTO_SIMPLE || ncclPatEnable(comm) == 0)) continue;
|
||||
int collnet = (a == NCCL_ALGO_COLLNET_DIRECT || a == NCCL_ALGO_COLLNET_CHAIN) ? 1 : 0;
|
||||
float bw = nNodes <= 2 || collnet ? graphs[a]->bwIntra : graphs[a]->bwInter;
|
||||
float busBw = comm->topo->baseBw != 0.0 ? comm->topo->baseBw : graphs[a]->nChannels * bw;
|
||||
//INFO(NCCL_INIT, "algo %s proto %s busBw %f baseBw %f bw %f nChannels %d bwIntra %f bwInter %f", ncclAlgoStr[a], ncclProtoStr[p], busBw, comm->topo->baseBw, bw, graphs[a]->nChannels, graphs[a]->bwIntra, graphs[a]->bwInter);
|
||||
|
||||
if (a == NCCL_ALGO_NVLS) {
|
||||
#if !defined(__HIP_PLATFORM_AMD__) && !defined(__HIPCC__)
|
||||
if (a == NCCL_ALGO_NVLS_TREE || a == NCCL_ALGO_NVLS)
|
||||
{
|
||||
// NVLS/NVLStree needs at least 2 channels
|
||||
if (graphs[a]->nChannels < 2 ) continue;
|
||||
// Convert to NVLS busBW/channel
|
||||
float intraBw = graphs[a]->bwIntra * nvlsEfficiency[compCapIndex] * (graphs[a]->nChannels - 1) / graphs[a]->nChannels;
|
||||
// AllReduce pipelines two operations.
|
||||
if (coll == ncclFuncAllReduce) {
|
||||
bw = std::min(graphs[a]->bwIntra, graphs[a]->bwInter);
|
||||
intraBw *= 2.0f;
|
||||
} else {
|
||||
// allgather and reducescatter
|
||||
bw = std::min(graphs[a]->bwIntra * (ppn - 1.0f) / ppn, graphs[a]->bwInter * 0.9f);
|
||||
intraBw *= (ppn - 1) / ppn;
|
||||
}
|
||||
}
|
||||
if (a == NCCL_ALGO_NVLS_TREE) bw = std::min(graphs[a]->bwIntra, nNodes <= 2 ? graphs[a]->bwInter : graphs[a]->bwInter/2);
|
||||
// Handle 2 node case of NVLSTree
|
||||
float interBw = graphs[a]->bwInter * ((nNodes <= 2 && a == NCCL_ALGO_NVLS_TREE) ? 2 : 1);
|
||||
bw = std::min( {intraBw, interBw, a == NCCL_ALGO_NVLS_TREE ? (float)perChMaxNVLSTreeBw : std::numeric_limits<float>::max()} );
|
||||
};
|
||||
#endif
|
||||
float busBw = graphs[a]->nChannels * bw;
|
||||
|
||||
// Various model refinements
|
||||
#if defined(__HIP_PLATFORM_AMD__) || defined(__HIPCC__)
|
||||
@@ -686,8 +735,7 @@ ncclResult_t ncclTopoTuneModel(struct ncclComm* comm, int minCompCap, int maxCom
|
||||
// Convert bus BW to algorithm BW
|
||||
if (!(a != NCCL_ALGO_RING && (coll == ncclFuncAllGather || coll == ncclFuncReduceScatter))) {
|
||||
float ratio = 1.0f;
|
||||
if (a == NCCL_ALGO_RING) ratio *= (1.0 * nRanks) / nsteps;
|
||||
else if (a == NCCL_ALGO_NVLS || a == NCCL_ALGO_NVLS_TREE) ratio *= 5.0/6.0;
|
||||
if (a == NCCL_ALGO_RING || a == NCCL_ALGO_NVLS || a == NCCL_ALGO_NVLS_TREE) ratio *= (1.0 * nRanks) / nsteps;
|
||||
else ratio *= .5;
|
||||
busBw *= ratio;
|
||||
}
|
||||
@@ -735,8 +783,7 @@ ncclResult_t ncclTopoTuneModel(struct ncclComm* comm, int minCompCap, int maxCom
|
||||
comm->latencies[coll][a][p] += 2*(nNodes-1)*rcclTuningModel[comm->topo->tuning].hwLat[NCCL_HW_NET][a][p];
|
||||
} else if (a == NCCL_ALGO_PAT) {
|
||||
if (coll == ncclFuncAllGather || coll == ncclFuncReduceScatter) {
|
||||
comm->latencies[coll][a][p] = 8 // Base time
|
||||
+ log2i(nNodes) * (interLat/3.5) // Log latency
|
||||
comm->latencies[coll][a][p] += log2i(nNodes) * (interLat/3.5) // Log latency
|
||||
+ nRanks * 2.8; // Still a linear part; hopefully we'll manage to remove it at some point.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1008,31 +1008,33 @@ ncclResult_t ncclTopoFillNet(struct ncclXml* xml, const char* pciPath, const cha
|
||||
|
||||
if (*netNode != NULL) return ncclSuccess;
|
||||
|
||||
const char* pciSysPath = pciPath;
|
||||
if (pciSysPath) {
|
||||
char subSystem[PATH_MAX];
|
||||
NCCLCHECK(ncclTopoGetSubsystem(pciSysPath, subSystem));
|
||||
// This is not a PCI device (virtual, usb, ...).
|
||||
if (strcmp(subSystem, "pci") != 0) {
|
||||
INFO(NCCL_NET|NCCL_GRAPH, "Topology detection: network path %s is not a PCI device (%s). Attaching to first CPU", pciSysPath, subSystem);
|
||||
pciSysPath = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
struct ncclXmlNode* parent = NULL;
|
||||
if (forceParent) {
|
||||
parent = forceParent;
|
||||
} else if (pciSysPath) {
|
||||
int offset;
|
||||
for (offset=strlen(pciSysPath)-1; pciSysPath[offset] != '/'; offset--);
|
||||
char busId[NVML_DEVICE_PCI_BUS_ID_BUFFER_SIZE];
|
||||
strcpy(busId, pciSysPath+offset+1);
|
||||
NCCLCHECK(ncclTopoGetPciNode(xml, busId, &parent));
|
||||
NCCLCHECK(xmlSetAttrIfUnset(parent, "class", "0x02"));
|
||||
NCCLCHECK(ncclTopoGetXmlFromSys(parent, xml));
|
||||
} else {
|
||||
// Virtual NIC, no PCI device, attach to first CPU
|
||||
NCCLCHECK(xmlFindTag(xml, "cpu", &parent));
|
||||
const char* pciSysPath = pciPath;
|
||||
if (pciSysPath) {
|
||||
char subSystem[PATH_MAX];
|
||||
NCCLCHECK(ncclTopoGetSubsystem(pciSysPath, subSystem));
|
||||
// This is not a PCI device (virtual, usb, ...).
|
||||
if (strcmp(subSystem, "pci") != 0 && !forceParent) {
|
||||
INFO(NCCL_NET | NCCL_GRAPH, "Topology detection: network path (name = %s) %s is not a PCI device (%s). Attaching to first CPU", netName, pciSysPath, subSystem);
|
||||
pciSysPath = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (pciSysPath) {
|
||||
int offset;
|
||||
for (offset = strlen(pciSysPath) - 1; pciSysPath[offset] != '/'; offset--);
|
||||
char busId[NVML_DEVICE_PCI_BUS_ID_BUFFER_SIZE];
|
||||
strcpy(busId, pciSysPath + offset + 1);
|
||||
NCCLCHECK(ncclTopoGetPciNode(xml, busId, &parent));
|
||||
NCCLCHECK(xmlSetAttrIfUnset(parent, "class", "0x02"));
|
||||
NCCLCHECK(ncclTopoGetXmlFromSys(parent, xml));
|
||||
} else {
|
||||
// Virtual NIC, no PCI device, attach to first CPU
|
||||
NCCLCHECK(xmlFindTag(xml, "cpu", &parent));
|
||||
}
|
||||
}
|
||||
|
||||
struct ncclXmlNode* nicNode = NULL;
|
||||
|
||||
@@ -128,6 +128,13 @@ static ncclResult_t xmlGetAttrUint64(struct ncclXmlNode* node, const char* attrN
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
static ncclResult_t xmlGetAttrUint64Default(struct ncclXmlNode* node, const char* attrName, uint64_t* value, uint64_t defaultValue) {
|
||||
const char* str;
|
||||
NCCLCHECK(xmlGetAttr(node, attrName, &str));
|
||||
*value = str ? strtoull(str, NULL, 0) : defaultValue;
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
static ncclResult_t xmlGetAttrLong(struct ncclXmlNode* node, const char* attrName, int64_t* value) {
|
||||
const char* str;
|
||||
NCCLCHECK(xmlGetAttrStr(node, attrName, &str));
|
||||
|
||||
+158
-107
@@ -14,6 +14,9 @@
|
||||
#include "api_trace.h"
|
||||
#include <assert.h>
|
||||
#include "bootstrap.h"
|
||||
#include "ce_coll.h"
|
||||
#include "profiler.h"
|
||||
#include "nvtx.h"
|
||||
|
||||
#include "msccl/msccl_lifecycle.h"
|
||||
|
||||
@@ -101,7 +104,7 @@ ncclResult_t ncclGroupStart_impl() {
|
||||
NCCLCHECK(Recorder::instance().record(rrGroupStart, ncclGroupDepth));
|
||||
}
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
NVTX3_FUNC_RANGE_IN(nccl_domain);
|
||||
NCCL_NVTX3_FUNC_RANGE;
|
||||
|
||||
NCCLCHECK(ncclGroupStartInternal());
|
||||
TRACE_CALL("ncclGroupStart()");
|
||||
@@ -123,7 +126,7 @@ ncclResult_t ncclGroupEnd_impl() {
|
||||
NCCLCHECK(Recorder::instance().record(rrGroupEnd, ncclGroupDepth));
|
||||
}
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
NVTX3_FUNC_RANGE_IN(nccl_domain);
|
||||
NCCL_NVTX3_FUNC_RANGE;
|
||||
NCCLCHECKGOTO(ncclGroupEndInternal(), ret, exit);
|
||||
TRACE_CALL("ncclGroupEnd()");
|
||||
exit:
|
||||
@@ -137,7 +140,7 @@ ncclResult_t ncclGroupSimulateEnd(ncclSimInfo_t* simInfo) {
|
||||
Recorder::instance().record(ncclGroupDepth, simInfo);
|
||||
}
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
NVTX3_FUNC_RANGE_IN(nccl_domain);
|
||||
NCCL_NVTX3_FUNC_RANGE;
|
||||
NCCLCHECKGOTO(ncclGroupEndInternal(simInfo), ret, exit);
|
||||
TRACE_CALL("ncclGroupSimulateEnd()");
|
||||
exit:
|
||||
@@ -150,65 +153,88 @@ struct ncclPreconnectJob {
|
||||
bool* algoNeedConnect;
|
||||
};
|
||||
|
||||
struct ncclPrepareTasksAndCollPreconnectJob {
|
||||
struct ncclAsyncJob base;
|
||||
struct ncclComm* comm;
|
||||
ncclSimInfo_t* simInfo;
|
||||
};
|
||||
|
||||
ncclResult_t ncclP2PPreconnectFunc(struct ncclAsyncJob* job_) {
|
||||
struct ncclPreconnectJob* job = (struct ncclPreconnectJob*)job_;
|
||||
struct ncclComm* comm = job->comm;
|
||||
CUDACHECK(cudaSetDevice(comm->cudaDev));
|
||||
if (CPU_COUNT(&comm->cpuAffinity)) sched_setaffinity(0, sizeof(cpu_set_t), &comm->cpuAffinity);
|
||||
if (!job_->isThreadMain && CPU_COUNT(&comm->cpuAffinity)) sched_setaffinity(0, sizeof(cpu_set_t), &comm->cpuAffinity);
|
||||
NCCLCHECK(ncclTransportP2pSetup(comm, NULL, 1));
|
||||
if (comm->p2pNet) NCCLCHECK(ncclTransportP2pSetup(comm, NULL, NCCL_CONN_IDX_P2P_NET));
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
static ncclResult_t ncclCollPreconnect(struct ncclComm* comm, bool* algoNeedConnect) {
|
||||
for (int i = 0; i < NCCL_NUM_ALGORITHMS; ++i) {
|
||||
if (algoNeedConnect[i]) {
|
||||
switch (i) {
|
||||
case NCCL_ALGO_RING: {
|
||||
NCCLCHECK(ncclTransportRingConnect(comm));
|
||||
break;
|
||||
}
|
||||
case NCCL_ALGO_TREE: {
|
||||
NCCLCHECK(ncclTransportTreeConnect(comm));
|
||||
break;
|
||||
}
|
||||
case NCCL_ALGO_NVLS: {
|
||||
/* If we are using NVLS_TREE algo, we must mark NVLS algo to set up
|
||||
* NVLS intra-node buffer */
|
||||
NCCLCHECK(ncclNvlsBufferSetup(comm));
|
||||
break;
|
||||
}
|
||||
case NCCL_ALGO_NVLS_TREE: {
|
||||
NCCLCHECK(ncclNvlsTreeConnect(comm));
|
||||
break;
|
||||
}
|
||||
case NCCL_ALGO_COLLNET_CHAIN: {
|
||||
NCCLCHECK(ncclCollNetChainBufferSetup(comm));
|
||||
break;
|
||||
}
|
||||
case NCCL_ALGO_COLLNET_DIRECT: {
|
||||
NCCLCHECK(ncclCollNetDirectBufferSetup(comm));
|
||||
break;
|
||||
}
|
||||
case NCCL_ALGO_PAT: {
|
||||
NCCLCHECK(ncclTransportPatConnect(comm));
|
||||
break;
|
||||
}
|
||||
// Yes, it's a dead code. That's fine...
|
||||
// coverity[dead_error_begin]
|
||||
default: {
|
||||
NCCLCHECK(ncclInternalError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
ncclResult_t ncclPrepareTasksAndCollPreconnectFunc(struct ncclAsyncJob* job_) {
|
||||
struct ncclPrepareTasksAndCollPreconnectJob* job = (ncclPrepareTasksAndCollPreconnectJob*)job_;
|
||||
struct ncclComm* comm = job->comm;
|
||||
bool needConnect;
|
||||
bool algoNeedConnect[NCCL_NUM_ALGORITHMS];
|
||||
memset(algoNeedConnect, 0, sizeof(bool)*NCCL_NUM_ALGORITHMS);
|
||||
CUDACHECK(cudaSetDevice(comm->cudaDev));
|
||||
if (!job_->isThreadMain && CPU_COUNT(&comm->cpuAffinity)) sched_setaffinity(0, sizeof(cpu_set_t), &comm->cpuAffinity);
|
||||
NCCLCHECK(ncclPrepareTasks(comm, algoNeedConnect, &needConnect, job->simInfo));
|
||||
if (comm->cuMemSupport && needConnect) NCCLCHECK(ncclCollPreconnect(comm, algoNeedConnect));
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
ncclResult_t ncclCollPreconnectFunc(struct ncclAsyncJob* job_) {
|
||||
struct ncclPreconnectJob* job = (struct ncclPreconnectJob*)job_;
|
||||
struct ncclComm* comm = job->comm;
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
|
||||
CUDACHECK(cudaSetDevice(comm->cudaDev));
|
||||
if (CPU_COUNT(&comm->cpuAffinity)) sched_setaffinity(0, sizeof(cpu_set_t), &comm->cpuAffinity);
|
||||
for (int i = 0; i < NCCL_NUM_ALGORITHMS; ++i) {
|
||||
if (job->algoNeedConnect[i]) {
|
||||
switch (i) {
|
||||
case NCCL_ALGO_RING: {
|
||||
NCCLCHECKGOTO(ncclTransportRingConnect(comm), ret, fail);
|
||||
break;
|
||||
}
|
||||
case NCCL_ALGO_TREE: {
|
||||
NCCLCHECKGOTO(ncclTransportTreeConnect(comm), ret, fail);
|
||||
break;
|
||||
}
|
||||
case NCCL_ALGO_NVLS: {
|
||||
/* If we are using NVLS_TREE algo, we must mark NVLS algo to set up
|
||||
* NVLS intra-node buffer */
|
||||
NCCLCHECKGOTO(ncclNvlsBufferSetup(comm), ret, fail);
|
||||
break;
|
||||
}
|
||||
case NCCL_ALGO_NVLS_TREE: {
|
||||
NCCLCHECKGOTO(ncclNvlsTreeConnect(comm), ret, fail);
|
||||
break;
|
||||
}
|
||||
case NCCL_ALGO_COLLNET_CHAIN: {
|
||||
NCCLCHECKGOTO(ncclCollNetChainBufferSetup(comm), ret, fail);
|
||||
break;
|
||||
}
|
||||
case NCCL_ALGO_COLLNET_DIRECT: {
|
||||
NCCLCHECKGOTO(ncclCollNetDirectBufferSetup(comm), ret, fail);
|
||||
break;
|
||||
}
|
||||
case NCCL_ALGO_PAT: {
|
||||
NCCLCHECKGOTO(ncclTransportPatConnect(comm), ret, fail);
|
||||
break;
|
||||
}
|
||||
// Yes, it's a dead code. That's fine...
|
||||
// coverity[dead_error_begin]
|
||||
default: {
|
||||
ret = ncclInternalError;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!job_->isThreadMain) CUDACHECK(cudaSetDevice(comm->cudaDev));
|
||||
if (!job_->isThreadMain && CPU_COUNT(&comm->cpuAffinity)) sched_setaffinity(0, sizeof(cpu_set_t), &comm->cpuAffinity);
|
||||
NCCLCHECKGOTO(ncclCollPreconnect(comm, job->algoNeedConnect), ret, fail);
|
||||
|
||||
exit:
|
||||
free(job->algoNeedConnect);
|
||||
@@ -222,52 +248,33 @@ struct ncclGroupSymmetricJob {
|
||||
struct ncclComm* comm;
|
||||
};
|
||||
|
||||
NCCL_PARAM(WinStride, "WIN_STRIDE", -1);
|
||||
|
||||
ncclResult_t ncclCommGroupRegisterSymmetric(struct ncclAsyncJob* job_) {
|
||||
struct ncclGroupSymmetricJob* job = (struct ncclGroupSymmetricJob*)job_;
|
||||
struct ncclComm* comm = job->comm;
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
|
||||
CUDACHECKGOTO(cudaSetDevice(comm->cudaDev), ret, fail);
|
||||
if (comm->baseStride == 0) {
|
||||
cudaStream_t hostStream;
|
||||
// first time to allocate symmetric VA space.
|
||||
// calling into this function means symmetric is supported.
|
||||
struct ncclSymDevBase* symBase = NULL;
|
||||
size_t size = ncclSymDevBase::size(comm->localRanks);
|
||||
if (ncclParamWinStride() != -1) {
|
||||
comm->baseStride = ncclParamWinStride();
|
||||
} else {
|
||||
size_t maxStride = 0;
|
||||
for (int r = 0; r < comm->nRanks; ++r)
|
||||
if (comm->peerInfo[r].totalGlobalMem > maxStride) maxStride = comm->peerInfo[r].totalGlobalMem;
|
||||
comm->baseStride = maxStride;
|
||||
}
|
||||
INFO(NCCL_INIT, "rank %d base stride %zuGB total VM %zuGB", comm->rank, comm->baseStride >> 30, (comm->baseStride * comm->localRanks) >> 30);
|
||||
NCCLCHECKGOTO(ncclIpcSymmetricInit(comm), ret, fail);
|
||||
NCCLCHECKGOTO(ncclNvlsSymmetricInit(comm), ret, fail);
|
||||
comm->symAllocHead = 0;
|
||||
|
||||
// Allocate symmetric memory for NCCL internal usage
|
||||
NCCLCHECKGOTO(ncclCommSymmetricAllocInternal(comm, size, alignof(struct ncclSymDevBase), (void**)&symBase), ret, fail);
|
||||
assert((void*)symBase == (void*)(comm->baseUCSymPtr + comm->localRank * comm->baseStride));
|
||||
NCCLCHECKGOTO(ncclStrongStreamAcquire(ncclCudaGraphNone(), &comm->sharedRes->hostStream, /*concurrent=*/false, &hostStream), ret, fail);
|
||||
CUDACHECKGOTO(cudaMemsetAsync(symBase, 0, size, hostStream), ret, fail);
|
||||
CUDACHECKGOTO(cudaStreamSynchronize(hostStream), ret, fail);
|
||||
NCCLCHECKGOTO(ncclStrongStreamRelease(ncclCudaGraphNone(), &comm->sharedRes->hostStream, /*concurrent=*/false), ret, fail);
|
||||
|
||||
comm->symDevComm.base = (struct ncclSymDevBase*)(comm->baseUCSymPtr + comm->localRank * comm->baseStride);
|
||||
comm->symDevComm.baseMc = (struct ncclSymDevBase*)comm->baseMCSymPtr;
|
||||
comm->symDevComm.nRanks = comm->localRanks;
|
||||
comm->symDevComm.nRanks_rcp32 = idivRcp32(comm->localRanks);
|
||||
comm->symDevComm.rank = comm->localRank;
|
||||
comm->symDevComm.stride4G = comm->baseStride >> 32;
|
||||
while (!ncclIntruQueueEmpty(&comm->devrState.regTaskQueue)) {
|
||||
struct ncclDevrRegTask* task = ncclIntruQueueDequeue(&comm->devrState.regTaskQueue);
|
||||
NCCLCHECKGOTO(ncclDevrWindowRegisterInGroup(
|
||||
comm, task->userPtr, task->userSize, task->winFlags, task->outWinDev),
|
||||
ret, fail);
|
||||
free(task);
|
||||
}
|
||||
|
||||
while (!ncclIntruQueueEmpty(&comm->symRegTaskQueue)) {
|
||||
struct ncclSymRegTask* task = ncclIntruQueueDequeue(&comm->symRegTaskQueue);
|
||||
NCCLCHECKGOTO(ncclCommSymmetricRegisterInternal(comm, task->buff, task->baseSize, task->alignment, task->memHandle, task->regHandle), ret, fail);
|
||||
while (!ncclIntruQueueEmpty(&comm->devrState.commCreateTaskQueue)) {
|
||||
struct ncclDevrCommCreateTask* task = ncclIntruQueueDequeue(&comm->devrState.commCreateTaskQueue);
|
||||
NCCLCHECKGOTO(ncclDevrCommCreateInternal(
|
||||
comm, (struct ncclDevCommRequirements const*)task->reqs, task->outDevComm),
|
||||
ret, fail);
|
||||
freeDevCommRequirements(task->reqs); // free additional task memory for reqs
|
||||
free(task);
|
||||
}
|
||||
|
||||
while (!ncclIntruQueueEmpty(&comm->ceInitTaskQueue)) {
|
||||
struct ncclCeInitTask* task = ncclIntruQueueDequeue(&comm->ceInitTaskQueue);
|
||||
NCCLCHECKGOTO(ncclCeInit(task->comm), ret, fail);
|
||||
free(task);
|
||||
}
|
||||
|
||||
@@ -324,7 +331,11 @@ static ncclResult_t doLaunches(struct ncclComm* head) {
|
||||
comm->planner.unlaunchedPlansHead = plan->next;
|
||||
CUDACHECKGOTO(cudaSetDevice(comm->cudaDev), result, failure);
|
||||
NCCLCHECKGOTO(ncclLaunchKernelBefore_NoUncapturedCuda(comm, plan), result, failure);
|
||||
NCCLCHECKGOTO(ncclLaunchKernel(comm, plan), result, failure);
|
||||
if (plan->isCeColl) {
|
||||
NCCLCHECKGOTO(ncclLaunchCeColl(comm, plan), result, failure);
|
||||
} else {
|
||||
NCCLCHECKGOTO(ncclLaunchKernel(comm, plan), result, failure);
|
||||
}
|
||||
}
|
||||
// Barrier reduction input indicates if we require further rounds.
|
||||
if (useBarrier) ncclCommIntraBarrierIn(comm, comm->planner.unlaunchedPlansHead != nullptr ? 1 : 0);
|
||||
@@ -422,6 +433,12 @@ static ncclResult_t asyncJobLaunch(struct ncclIntruQueue<struct ncclAsyncJob, &n
|
||||
|
||||
if (!ncclIntruQueueEmpty(asyncJobsMain)) {
|
||||
struct ncclAsyncJob* job = ncclIntruQueueHead(asyncJobsMain);
|
||||
if (job->next == nullptr) {
|
||||
job->isThreadMain = true;
|
||||
ncclAsyncJobMain(job);
|
||||
job->state = ncclGroupJobJoined;
|
||||
return job->result;
|
||||
}
|
||||
do {
|
||||
PTHREADCHECKGOTO(pthread_create(&job->thread, nullptr, ncclAsyncJobMain, job), "pthread_create", ret, fail);
|
||||
job = job->next;
|
||||
@@ -474,6 +491,51 @@ fail:
|
||||
goto exit;
|
||||
}
|
||||
|
||||
NCCL_PARAM(SingleProcMemRegEnable, "SINGLE_PROC_MEM_REG_ENABLE", 0);
|
||||
|
||||
static ncclResult_t ncclPrepareTasksAndCollPreconnect(struct ncclComm* comm, ncclSimInfo_t* simInfo, struct ncclIntruQueue<struct ncclAsyncJob, &ncclAsyncJob::next>* asyncCollJobs) {
|
||||
if (ncclParamSingleProcMemRegEnable()) {
|
||||
struct ncclPrepareTasksAndCollPreconnectJob* job;
|
||||
NCCLCHECK(ncclCalloc(&job, 1));
|
||||
job->base.func = ncclPrepareTasksAndCollPreconnectFunc;
|
||||
job->base.undo = nullptr;
|
||||
job->base.destructor = free;
|
||||
job->base.state = ncclGroupJobRunning;
|
||||
job->base.abortFlag = comm->abortFlag;
|
||||
job->base.abortFlagDev = comm->abortFlagDev;
|
||||
job->comm = comm;
|
||||
job->simInfo = simInfo;
|
||||
ncclIntruQueueEnqueue(asyncCollJobs, &job->base);
|
||||
} else {
|
||||
bool needConnect = false;
|
||||
bool algoNeedConnect[NCCL_NUM_ALGORITHMS];
|
||||
memset(algoNeedConnect, 0, sizeof(bool) * NCCL_NUM_ALGORITHMS);
|
||||
|
||||
CUDACHECK(cudaSetDevice(comm->cudaDev));
|
||||
NCCLCHECK(ncclPrepareTasks(comm, algoNeedConnect, &needConnect, simInfo));
|
||||
|
||||
if (comm->cuMemSupport && needConnect) {
|
||||
ncclResult_t ret;
|
||||
struct ncclPreconnectJob* job;
|
||||
NCCLCHECK(ncclCalloc(&job, 1));
|
||||
job->base.func = ncclCollPreconnectFunc;
|
||||
job->base.undo = nullptr;
|
||||
job->base.destructor = free;
|
||||
job->base.state = ncclGroupJobRunning;
|
||||
job->base.abortFlag = comm->abortFlag;
|
||||
job->base.abortFlagDev = comm->abortFlagDev;
|
||||
job->comm = comm;
|
||||
if ((ret = ncclCalloc(&job->algoNeedConnect, NCCL_NUM_ALGORITHMS))) {
|
||||
free(job);
|
||||
NCCLCHECK(ret);
|
||||
}
|
||||
memcpy(job->algoNeedConnect, algoNeedConnect, sizeof(bool) * NCCL_NUM_ALGORITHMS);
|
||||
ncclIntruQueueEnqueue(asyncCollJobs, &job->base);
|
||||
}
|
||||
}
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
static ncclResult_t groupLaunch(struct ncclAsyncJob *job_, ncclSimInfo_t* simInfo = NULL) {
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
struct ncclGroupJob *gjob = (struct ncclGroupJob*) job_;
|
||||
@@ -548,27 +610,7 @@ static ncclResult_t groupLaunch(struct ncclAsyncJob *job_, ncclSimInfo_t* simInf
|
||||
// at the same time.
|
||||
comm = cliqueHead;
|
||||
do {
|
||||
bool needConnect = false;
|
||||
bool algoNeedConnect[NCCL_NUM_ALGORITHMS];
|
||||
memset(algoNeedConnect, 0, sizeof(bool) * NCCL_NUM_ALGORITHMS);
|
||||
|
||||
CUDACHECKGOTO(cudaSetDevice(comm->cudaDev), ret, fail);
|
||||
NCCLCHECKGOTO(ncclPrepareTasks(comm, algoNeedConnect, &needConnect, simInfo), ret, fail);
|
||||
|
||||
if (comm->cuMemSupport && needConnect) {
|
||||
struct ncclPreconnectJob* job;
|
||||
NCCLCHECKGOTO(ncclCalloc(&job, 1), ret, fail);
|
||||
job->base.func = ncclCollPreconnectFunc;
|
||||
job->base.undo = nullptr;
|
||||
job->base.destructor = free;
|
||||
job->base.state = ncclGroupJobRunning;
|
||||
job->base.abortFlag = comm->abortFlag;
|
||||
job->base.abortFlagDev = comm->abortFlagDev;
|
||||
job->comm = comm;
|
||||
NCCLCHECKGOTO(ncclCalloc(&job->algoNeedConnect, NCCL_NUM_ALGORITHMS), ret, fail);
|
||||
memcpy(job->algoNeedConnect, algoNeedConnect, sizeof(bool) * NCCL_NUM_ALGORITHMS);
|
||||
ncclIntruQueueEnqueue(&asyncCollJobs, &job->base);
|
||||
}
|
||||
NCCLCHECKGOTO(ncclPrepareTasksAndCollPreconnect(comm, simInfo, &asyncCollJobs), ret, fail);
|
||||
comm = comm->groupNext[ncclGroupTaskTypeCollective];
|
||||
} while (comm != nullptr && comm->intraComm0 == cliqueHead->intraComm0);
|
||||
// connect
|
||||
@@ -650,6 +692,13 @@ ncclResult_t ncclGroupEndInternal(ncclSimInfo_t* simInfo) {
|
||||
if (mscclAvailable() && !mscclIsCaller()) {
|
||||
NCCLCHECK(mscclGroupEnd());
|
||||
}
|
||||
|
||||
if (ncclProfilerApiState.profilerGroupDepth > 0) {
|
||||
ncclProfilerApiState.profilerGroupDepth--;
|
||||
}
|
||||
if (ncclProfilerApiState.profilerGroupDepth == 0) {
|
||||
NCCLCHECK(ncclProfilerRecordGroupApiEventState(ncclProfilerGroupEndApiStart));
|
||||
}
|
||||
|
||||
if ((--ncclGroupDepth) > 0) goto exit;
|
||||
|
||||
@@ -735,6 +784,8 @@ ncclResult_t ncclGroupEndInternal(ncclSimInfo_t* simInfo) {
|
||||
groupLocalResetJobState();
|
||||
|
||||
exit:
|
||||
// Profiler group API start is called inside taskAppend to get graph capture information for the event
|
||||
NCCLCHECK(ncclProfilerStopGroupApiEvent());
|
||||
return ret;
|
||||
fail:
|
||||
if (groupJob) {
|
||||
|
||||
@@ -7,7 +7,55 @@
|
||||
#ifndef NCCL_ALLOCATOR_H_
|
||||
#define NCCL_ALLOCATOR_H_
|
||||
|
||||
ncclResult_t ncclCommSymmetricAllocInternal(struct ncclComm* comm, size_t size, size_t alignment, void** symPtr);
|
||||
ncclResult_t ncclCommSymmetricFreeInternal(struct ncclComm* comm, void* symPtr);
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// ncclSpace: Allocates contiguous segments of non-negative integers. Useful
|
||||
// as a memory allocator when we can't put allocator state within the memory
|
||||
// being allocated.
|
||||
|
||||
struct ncclSpace {
|
||||
int count;
|
||||
int capacity;
|
||||
int64_t* cuts;
|
||||
};
|
||||
|
||||
void ncclSpaceConstruct(struct ncclSpace* a);
|
||||
void ncclSpaceDestruct(struct ncclSpace* a);
|
||||
ncclResult_t ncclSpaceAlloc(struct ncclSpace* a, int64_t spaceLimit, int64_t objSize, int objAlign, int64_t* outObjOffset);
|
||||
ncclResult_t ncclSpaceFree(struct ncclSpace* a, int64_t objOffset, int64_t objSize);
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// ncclShadowPool: Allocates device-side objects, their host-side shadows, and
|
||||
// maintains the device->host object address mapping.
|
||||
|
||||
struct ncclShadowObject;
|
||||
struct ncclShadowPage;
|
||||
struct ncclShadowPool {
|
||||
int count, hbits;
|
||||
struct ncclShadowObject** table;
|
||||
cudaMemPool_t memPool;
|
||||
struct ncclShadowPage* pages;
|
||||
};
|
||||
|
||||
void ncclShadowPoolConstruct(struct ncclShadowPool*);
|
||||
ncclResult_t ncclShadowPoolDestruct(struct ncclShadowPool*);
|
||||
ncclResult_t ncclShadowPoolAlloc(struct ncclShadowPool*, size_t size, void** outDevObj, void** outHostObj, cudaStream_t stream);
|
||||
ncclResult_t ncclShadowPoolFree(struct ncclShadowPool*, void* devObj, cudaStream_t stream);
|
||||
ncclResult_t ncclShadowPoolToHost(struct ncclShadowPool*, void* devObj, void** outHostObj);
|
||||
|
||||
template<typename T>
|
||||
static inline ncclResult_t ncclShadowPoolAlloc(struct ncclShadowPool* pool, T** outDevObj, T** outHostObj, cudaStream_t stream) {
|
||||
void* devObj;
|
||||
void* hostObj;
|
||||
ncclResult_t got = ncclShadowPoolAlloc(pool, sizeof(T), &devObj, &hostObj, stream);
|
||||
if (outDevObj) *outDevObj = (T*)devObj;
|
||||
if (outHostObj) *outHostObj = (T*)hostObj;
|
||||
return got;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static inline ncclResult_t ncclShadowPoolToHost(struct ncclShadowPool* pool, T* devObj, T** hostObj) {
|
||||
return ncclShadowPoolToHost(pool, (void*)devObj, (void**)hostObj);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
#define RCCL_API_TRACE_VERSION_MAJOR 0
|
||||
|
||||
// should be increased every time new members are added to existing dispatch tables
|
||||
#define RCCL_API_TRACE_VERSION_PATCH 2
|
||||
#define RCCL_API_TRACE_VERSION_PATCH 3
|
||||
|
||||
#if !defined(RCCL_EXTERN_C_INIT)
|
||||
# ifdef __cplusplus
|
||||
@@ -65,10 +65,10 @@ typedef ncclResult_t (*ncclAllReduceWithBias_fn_t)(const void* sendbuff, void* r
|
||||
size_t count, ncclDataType_t datatype,
|
||||
ncclRedOp_t op, struct ncclComm* comm,
|
||||
hipStream_t stream, const void* acc);
|
||||
typedef ncclResult_t (*ncclAllToAll_fn_t)(const void* sendbuff, void* recvbuff,
|
||||
typedef ncclResult_t (*ncclAlltoAll_fn_t)(const void* sendbuff, void* recvbuff,
|
||||
size_t count, ncclDataType_t datatype,
|
||||
ncclComm_t comm, hipStream_t stream);
|
||||
typedef ncclResult_t (*ncclAllToAllv_fn_t)(
|
||||
typedef ncclResult_t (*ncclAlltoAllv_fn_t)(
|
||||
const void* sendbuff, const size_t sendcounts[], const size_t sdispls[],
|
||||
void* recvbuff, const size_t recvcounts[], const size_t rdispls[],
|
||||
ncclDataType_t datatype, ncclComm_t comm, hipStream_t stream);
|
||||
@@ -162,7 +162,7 @@ typedef ncclResult_t (*ncclCommRegister_fn_t)(const ncclComm_t comm, void* buff,
|
||||
|
||||
typedef ncclResult_t (*ncclCommDeregister_fn_t)(const ncclComm_t comm, void* handle);
|
||||
|
||||
typedef ncclResult_t (*ncclCommWindowRegister_fn_t)(ncclComm_t comm, void* buff, size_t size, ncclWindow_t* win, int winFlags);
|
||||
typedef ncclResult_t (*ncclCommWindowRegister_fn_t)(ncclComm_t comm, void* userPtr, size_t userSize, ncclWindow_t* outWinDev, int winFlags);
|
||||
|
||||
typedef ncclResult_t (*ncclCommWindowDeregister_fn_t)(ncclComm_t comm, ncclWindow_t win);
|
||||
|
||||
@@ -172,8 +172,8 @@ typedef struct rcclApiFuncTable
|
||||
uint64_t size;
|
||||
ncclAllGather_fn_t ncclAllGather_fn;
|
||||
ncclAllReduce_fn_t ncclAllReduce_fn;
|
||||
ncclAllToAll_fn_t ncclAllToAll_fn;
|
||||
ncclAllToAllv_fn_t ncclAllToAllv_fn;
|
||||
ncclAlltoAll_fn_t ncclAllToAll_fn;
|
||||
ncclAlltoAllv_fn_t ncclAllToAllv_fn;
|
||||
ncclBroadcast_fn_t ncclBroadcast_fn;
|
||||
ncclGather_fn_t ncclGather_fn;
|
||||
ncclReduce_fn_t ncclReduce_fn;
|
||||
@@ -211,6 +211,8 @@ typedef struct rcclApiFuncTable
|
||||
ncclCommShrink_fn_t ncclCommShrink_fn;
|
||||
ncclCommWindowRegister_fn_t ncclCommWindowRegister_fn;
|
||||
ncclCommWindowDeregister_fn_t ncclCommWindowDeregister_fn;
|
||||
ncclAlltoAll_fn_t ncclAlltoAll_fn;
|
||||
ncclAlltoAllv_fn_t ncclAlltoAllv_fn;
|
||||
// ADD NEW FUNCTIONS HERE ONLY
|
||||
} rcclApiFuncTable;
|
||||
|
||||
|
||||
@@ -41,6 +41,9 @@ constexpr static __host__ __device__ Int maxval(Int a, Int b, More ...more) {
|
||||
#endif
|
||||
}
|
||||
|
||||
#define BIT(x) (1UL << (x))
|
||||
#define MASK(x) ((1UL << x) - 1UL)
|
||||
|
||||
#define DIVUP(x, y) \
|
||||
(((x)+(y)-1)/(y))
|
||||
|
||||
@@ -68,14 +71,26 @@ static __host__ __device__ constexpr Z roundDown(X x, Y y) {
|
||||
}
|
||||
|
||||
// assumes second argument is a power of 2
|
||||
template<typename X, typename Z = decltype(X()+int())>
|
||||
static __host__ __device__ constexpr Z alignUp(X x, int a) {
|
||||
return (x + a-1) & Z(-a);
|
||||
template<typename X, typename Y, typename Z = decltype(X()+Y())>
|
||||
static __host__ __device__ constexpr Z alignUp(X x, Y a) {
|
||||
return (x + a-1) & -Z(a);
|
||||
}
|
||||
template<typename T>
|
||||
static __host__ __device__ T* alignUp(T* x, size_t a) {
|
||||
static_assert(sizeof(T) == 1, "Only single byte types allowed.");
|
||||
return reinterpret_cast<T*>((reinterpret_cast<uintptr_t>(x) + a-1) & -uintptr_t(a));
|
||||
}
|
||||
|
||||
// assumes second argument is a power of 2
|
||||
template<typename X, typename Z = decltype(X()+int())>
|
||||
static __host__ __device__ constexpr Z alignDown(X x, int a) {
|
||||
return x & Z(-a);
|
||||
template<typename X, typename Y, typename Z = decltype(X()+Y())>
|
||||
static __host__ __device__ constexpr Z alignDown(X x, Y a) {
|
||||
return x & -Z(a);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static __host__ __device__ T* alignDown(T* x, size_t a) {
|
||||
static_assert(sizeof(T) == 1, "Only single byte types allowed.");
|
||||
return reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(x) & -uintptr_t(a));
|
||||
}
|
||||
|
||||
template<typename Int>
|
||||
@@ -341,7 +356,7 @@ static __host__ __device__ UInt reverseSubBits(UInt x) {
|
||||
default: static_assert(8*sizeof(UInt) <= 64, "Unsupported integer type.");
|
||||
}
|
||||
return reverseSubBits<UInt, 8>(x);
|
||||
} else if (nSubBits == 1) {
|
||||
} else if (nSubBits <= 1) {
|
||||
return x;
|
||||
} else {
|
||||
UInt m = UInt(-1)/((UInt(1)<<(nSubBits/2))+1);
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef NCCL_CE_COLL_H_
|
||||
#define NCCL_CE_COLL_H_
|
||||
|
||||
#include "nccl.h"
|
||||
#include "nccl_common.h"
|
||||
#include "bitops.h"
|
||||
|
||||
// Memory operations per rank for different synchronization protocols
|
||||
#define NCCL_CE_SYNC_OPS_PER_RANK_MC 2
|
||||
#define NCCL_CE_SYNC_OPS_PER_RANK_UC 3
|
||||
|
||||
struct ncclCeColl {
|
||||
uint8_t* baseUCSymReadyPtr;
|
||||
uint8_t* baseUCSymComplPtr;
|
||||
size_t baseUCSymReadyOffset;
|
||||
size_t baseUCSymComplOffset;
|
||||
uint32_t ceSeqNum;
|
||||
bool useCompletePtr;
|
||||
uint32_t intraBatchSyncFreq;
|
||||
uint64_t intraBatchSyncMsgThreshold;
|
||||
struct ncclDevrWindow* ceSyncWin;
|
||||
};
|
||||
|
||||
struct ncclCeInitTask {
|
||||
struct ncclCeInitTask *next;
|
||||
struct ncclComm* comm;
|
||||
};
|
||||
|
||||
struct alignas(16) ncclCeCollArgs {
|
||||
ncclFunc_t func;
|
||||
int rootRank;
|
||||
size_t nElts;
|
||||
size_t eltSize;
|
||||
uint8_t* sendBuff;
|
||||
uint8_t* recvBuff;
|
||||
struct ncclDevrWindow* sendWin;
|
||||
struct ncclDevrWindow* recvWin;
|
||||
};
|
||||
|
||||
struct ncclCeBatchOpsParams {
|
||||
void** dsts;
|
||||
void** srcs;
|
||||
size_t* sizes;
|
||||
size_t numOps;
|
||||
bool intraBatchSync;
|
||||
#if CUDART_VERSION >= 12080
|
||||
cudaMemcpyAttributes* attrs;
|
||||
size_t* attrIdxs;
|
||||
size_t numAttrs;
|
||||
#endif
|
||||
};
|
||||
|
||||
bool ncclCeImplemented(ncclFunc_t coll, int/*ncclDevRedOp_t*/ red, ncclDataType_t ty);
|
||||
|
||||
ncclResult_t ncclCeInit(struct ncclComm* comm);
|
||||
|
||||
ncclResult_t ncclCeFinalize(struct ncclComm* comm);
|
||||
|
||||
ncclResult_t ncclMemOpSync(struct ncclComm* comm, cudaStream_t stream);
|
||||
|
||||
ncclResult_t ncclLaunchCeColl(struct ncclComm* comm, struct ncclKernelPlan* plan);
|
||||
|
||||
ncclResult_t ncclCeAllGather(struct ncclComm* comm, struct ncclCeCollArgs* args, cudaStream_t stream);
|
||||
|
||||
ncclResult_t ncclCeScatter(struct ncclComm* comm, struct ncclCeCollArgs* args, cudaStream_t stream);
|
||||
|
||||
ncclResult_t ncclCeGather(struct ncclComm* comm, struct ncclCeCollArgs* args, cudaStream_t stream);
|
||||
|
||||
ncclResult_t ncclCeAlltoAll(struct ncclComm* comm, struct ncclCeCollArgs* args, cudaStream_t stream);
|
||||
#endif /* NCCL_CE_COLL_H_ */
|
||||
@@ -17,16 +17,17 @@ ncclResult_t initCollnetChannel(struct ncclComm* comm, int channelId, struct ncc
|
||||
ncclResult_t freeChannel(struct ncclChannel* channel, int nRanks, int collnetNRanks, int nvlsNRanks);
|
||||
|
||||
inline uint8_t ncclP2pChannelBaseForRound(struct ncclComm* comm, int p2pRound, int p2pBatchEnable = 0) {
|
||||
int base;
|
||||
if (comm->nNodes > 1) {
|
||||
int nodeDelta = p2pRound/comm->maxLocalRanks;
|
||||
int localDelta = p2pRound%comm->maxLocalRanks;
|
||||
int batchSize = (comm->nNodes > 2 && p2pBatchEnable) ? NCCL_MAX_DEV_WORK_P2P_PER_BATCH : 1;
|
||||
int base = nodeDelta*divUp(comm->maxLocalRanks, batchSize);
|
||||
base = nodeDelta*divUp(comm->maxLocalRanks, batchSize);
|
||||
base += localDelta/batchSize;
|
||||
return base & 0xff;
|
||||
} else {
|
||||
return p2pRound & 0xff;
|
||||
base = p2pRound;
|
||||
}
|
||||
return base & 0xff;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -16,7 +16,7 @@ typedef char collNetHandle_t[NCCL_NET_HANDLE_MAXSIZE];
|
||||
static const char* collNetName(struct ncclComm* comm) { return comm->ncclCollNet->name; }
|
||||
static ncclResult_t collNetDevices(struct ncclComm* comm, int* ndev) { NCCLCHECK(comm->ncclCollNet->devices(ndev)); return ncclSuccess; }
|
||||
static ncclResult_t collNetGetProperties(struct ncclComm* comm, int dev, ncclNetProperties_t* props) { NCCLCHECK(comm->ncclCollNet->getProperties(dev, props)); return ncclSuccess; }
|
||||
static ncclResult_t collNetListen(struct ncclComm* comm, int dev, void* handle, void** listenComm) { NCCLCHECK(comm->ncclCollNet->listen(dev, handle, listenComm)); return ncclSuccess; }
|
||||
static ncclResult_t collNetListen(struct ncclComm* comm, int dev, void* handle, void** listenComm) { NCCLCHECK(comm->ncclCollNet->listen(comm->collNetContext, dev, handle, listenComm)); return ncclSuccess; }
|
||||
static ncclResult_t collNetConnect(struct ncclComm* comm, void* handles[], int nranks, int rank, void* listenComm, void** collComm) { NCCLCHECK(comm->ncclCollNet->connect(handles, nranks, rank, listenComm, collComm)); return ncclSuccess; }
|
||||
static ncclResult_t collNetReduceSupport(struct ncclComm* comm, ncclDataType_t dataType, ncclRedOp_t redOp, int* supported) { NCCLCHECK(comm->ncclCollNet->reduceSupport(dataType, redOp, supported)); return ncclSuccess; }
|
||||
static ncclResult_t collNetRegMr(struct ncclComm* comm, void* collComm, void* data, size_t size, int type, void** mhandle) { NCCLCHECK(comm->ncclCollNet->regMr(collComm, data, size, type, mhandle)); return ncclSuccess; }
|
||||
@@ -29,6 +29,7 @@ static ncclResult_t collNetIflush(struct ncclComm* comm, void* collComm, void* d
|
||||
static ncclResult_t collNetTest(struct ncclComm* comm, void* request, int* done, int* size) { NCCLCHECK(comm->ncclCollNet->test(request, done, size)); return ncclSuccess; }
|
||||
static ncclResult_t collNetCloseColl(struct ncclComm* comm, void* collComm) { NCCLCHECK(comm->ncclCollNet->closeColl(collComm)); return ncclSuccess; }
|
||||
static ncclResult_t collNetCloseListen(struct ncclComm* comm, void* listenComm) { NCCLCHECK(comm->ncclCollNet->closeListen(listenComm)); return ncclSuccess; }
|
||||
static ncclResult_t collNetFinalize(struct ncclComm* comm, void* ctx) { NCCLCHECK(comm->ncclCollNet->finalize(ctx)); return ncclSuccess; }
|
||||
|
||||
static int collNetSupport(struct ncclComm* comm) { return comm->ncclCollNet != nullptr ? 1 : 0; }
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#define NCCL_COLLECTIVES_H_
|
||||
|
||||
#include "nccl.h"
|
||||
#include "nccl_common.h"
|
||||
#include "nccl_tuner.h"
|
||||
#include "device.h"
|
||||
|
||||
#define NCCL_MAX_NET_SIZE (1024*1024*1024L) // Rather than send INT_MAX which is 2G-1, send a power of two.
|
||||
@@ -25,11 +25,17 @@
|
||||
#define ALLGATHER_SLICESTEPS (NCCL_STEPS/4)
|
||||
#define ALLGATHER_SLICESTEPS_SINGLE_NODE (NCCL_STEPS/2)
|
||||
#define ALLGATHER_CHUNKSTEPS (NCCL_STEPS/2)
|
||||
#define ALLTOALL_SLICESTEPS 1
|
||||
#define ALLTOALL_CHUNKSTEPS 1
|
||||
#define REDUCESCATTER_SLICESTEPS (NCCL_STEPS/4)
|
||||
#define REDUCESCATTER_SLICESTEPS_SINGLE_NODE (NCCL_STEPS/2)
|
||||
#define REDUCESCATTER_CHUNKSTEPS (NCCL_STEPS/2)
|
||||
#define BROADCAST_SLICESTEPS 1
|
||||
#define BROADCAST_CHUNKSTEPS 1
|
||||
#define GATHER_SLICESTEPS 1
|
||||
#define GATHER_CHUNKSTEPS 1
|
||||
#define SCATTER_SLICESTEPS 1
|
||||
#define SCATTER_CHUNKSTEPS 1
|
||||
#define REDUCE_SLICESTEPS 1
|
||||
#define REDUCE_CHUNKSTEPS 1
|
||||
#define NCCL_MAX_SLICE_PER_CHUNK 2 // max value for CHUNKSTEPS/SLICESTEPS, must accord with above
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
#include "nvmlwrap.h"
|
||||
#include "profiler.h"
|
||||
#include "allocator.h"
|
||||
#include "dev_runtime.h"
|
||||
#include "sym_kernels.h"
|
||||
#include "ce_coll.h"
|
||||
#include "latency_profiler/CollTrace.h"
|
||||
#include "rccl_common.h"
|
||||
#include "recorder.h"
|
||||
@@ -217,13 +220,15 @@ struct ncclTaskColl {
|
||||
#endif
|
||||
int32_t nWarps:8;
|
||||
int32_t algorithm:8, protocol:8, pipeline:8;
|
||||
uint32_t isCollnet:1, isNvls:1;
|
||||
uint32_t devFuncId:30;
|
||||
uint32_t isCollnet:1, isNvls:1, isSymLast:1;
|
||||
uint32_t devFuncId:29;
|
||||
int regBufType;
|
||||
uint64_t opCount;
|
||||
// number of elements in planner->ipcMemQueue associated with this collective
|
||||
int nCleanupQueueElts;
|
||||
|
||||
struct ncclDevrWindow* sendWin;
|
||||
struct ncclDevrWindow* recvWin;
|
||||
void* sendMhandle;
|
||||
void* recvMhandle;
|
||||
void** sendNetHandles;
|
||||
@@ -237,12 +242,16 @@ struct ncclTaskColl {
|
||||
|
||||
// Profiler plugin
|
||||
int eActivationMask;
|
||||
void* groupApiEventHandle;
|
||||
void* collApiEventHandle;
|
||||
void* eventHandle;
|
||||
uint8_t nChannels;
|
||||
};
|
||||
|
||||
struct ncclTaskP2p {
|
||||
struct ncclTaskP2p* next;
|
||||
ncclFunc_t func;
|
||||
ncclFunc_t collAPI;
|
||||
void* buff;
|
||||
size_t count;
|
||||
ncclDataType_t datatype;
|
||||
@@ -252,6 +261,8 @@ struct ncclTaskP2p {
|
||||
|
||||
// Profiler plugin
|
||||
int eActivationMask;
|
||||
void* groupApiEventHandle;
|
||||
void* p2pApiEventHandle;
|
||||
void* eventHandle;
|
||||
uint8_t nChannels;
|
||||
};
|
||||
@@ -267,12 +278,14 @@ struct ncclKernelPlan {
|
||||
bool persistent; // aka captured in a graph
|
||||
bool isHostCbEnq;
|
||||
bool isSymColl;
|
||||
bool isCeColl;
|
||||
enum ncclDevWorkStorageType workStorageType;
|
||||
bool kernelSpecialized;
|
||||
void* kernelFn;
|
||||
union {
|
||||
struct ncclDevKernelArgs* kernelArgs;
|
||||
struct ncclSymDevArgs* kernelSymArgs;
|
||||
void* kernelSymArgs;
|
||||
struct ncclCeCollArgs* ceCollArgs;
|
||||
};
|
||||
size_t kernelArgsSize;
|
||||
struct channelMasks channelMask;
|
||||
@@ -291,6 +304,8 @@ struct ncclKernelPlan {
|
||||
struct ncclIntruQueue<struct ncclProxyOp, &ncclProxyOp::enqNext> proxyOpQueue;
|
||||
|
||||
// Profiler plugin
|
||||
void* groupApiEventHandle;
|
||||
void* kernelLaunchEventHandle;
|
||||
void* groupEventHandle;
|
||||
};
|
||||
|
||||
@@ -381,9 +396,8 @@ struct ncclKernelPlanner {
|
||||
struct ncclTaskCollSorter collSorter;
|
||||
struct Peer* peers/*[nRanks]*/;
|
||||
int nTasksColl, nTasksP2p;
|
||||
int nTasksP2pSend, nTasksP2pRecv;
|
||||
bool persistent;
|
||||
bool isSymColl;
|
||||
|
||||
// The list of user streams aggregated over all tasks present.
|
||||
struct ncclCudaStreamList* streams;
|
||||
// Keep track of the number of user streams
|
||||
@@ -401,6 +415,8 @@ struct ncclKernelPlanner {
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct ncclIntruQueue<struct ncclTaskColl, &ncclTaskColl::next> collTaskQueue;
|
||||
struct ncclIntruQueue<struct ncclTaskColl, &ncclTaskColl::next> collCeTaskQueue;
|
||||
struct ncclIntruQueue<struct ncclTaskColl, &ncclTaskColl::next> collSymTaskQueue;
|
||||
struct ncclIntruQueue<struct ncclWorkList, &ncclWorkList::next> collWorkQueue;
|
||||
struct ncclIntruQueue<struct ncclWorkList, &ncclWorkList::next> tmpCollWorkQueue;
|
||||
struct ncclIntruQueue<struct ncclCommCallback, &ncclCommCallback::next> collCleanupQueue;
|
||||
@@ -459,6 +475,8 @@ typedef enum ncclGroupTaskType {
|
||||
ncclGroupTaskTypeNum = 2,
|
||||
} ncclGroupTaskType_t;
|
||||
|
||||
struct ncclCommSymTeams;
|
||||
|
||||
struct ncclComm {
|
||||
uint64_t startMagic;
|
||||
struct ncclMemoryStack memPermanent, memScoped;
|
||||
@@ -478,10 +496,12 @@ struct ncclComm {
|
||||
bool peerInfoValid;
|
||||
|
||||
ncclNet_t* ncclNet;
|
||||
void* netContext;
|
||||
int netPluginIndex;
|
||||
int ncclNetVer;
|
||||
ncclNetDeviceType netDeviceType;
|
||||
ncclCollNet_t* ncclCollNet;
|
||||
void* collNetContext;
|
||||
void* bootstrap;
|
||||
// Bitmasks for ncclTransportP2pSetup
|
||||
struct channelMasks* connectSend;
|
||||
@@ -517,6 +537,7 @@ struct ncclComm {
|
||||
int localRank;
|
||||
int localRanks;
|
||||
int maxLocalRanks;
|
||||
int minLocalRanks;
|
||||
int* rankToNode;
|
||||
int* rankToLocalRank;
|
||||
int* localRankToRank;
|
||||
@@ -527,6 +548,9 @@ struct ncclComm {
|
||||
struct cliqueInfo clique; // Our MNNVL clique information
|
||||
int cliqueRank; // Our rank within the MNNVL clique
|
||||
|
||||
// NVL Domain info
|
||||
ncclNvlDomainInfo_v5_t nvlDomainInfo;
|
||||
|
||||
bool checkPointers;
|
||||
bool dmaBufSupport;
|
||||
|
||||
@@ -553,7 +577,8 @@ struct ncclComm {
|
||||
int p2pChunkSize;
|
||||
int nvlsChunkSize;
|
||||
|
||||
// Algorithm/Protocols thresholds
|
||||
// Tuner values
|
||||
ncclTunerConstants_t tunerConstants;
|
||||
ssize_t threadThresholds[NCCL_NUM_ALGORITHMS][NCCL_NUM_PROTOCOLS];
|
||||
float latencies[NCCL_NUM_FUNCTIONS][NCCL_NUM_ALGORITHMS][NCCL_NUM_PROTOCOLS];
|
||||
float bandwidths[NCCL_NUM_FUNCTIONS][NCCL_NUM_ALGORITHMS][NCCL_NUM_PROTOCOLS];
|
||||
@@ -579,8 +604,7 @@ struct ncclComm {
|
||||
bool hasFineGrain;
|
||||
|
||||
// Device side of the communicator (for cudaFree's)
|
||||
struct ncclDevComm* devComm; // actually = &ncclDevCommAndChannels::comm
|
||||
struct ncclSymDevComm symDevComm;
|
||||
struct ncclKernelComm* devComm; // actually = &ncclKernelCommAndChannels::comm
|
||||
|
||||
uint32_t workArgsBytes; // max size of kernel args
|
||||
uint32_t workFifoBytes; // size of workFifoBuf, power of 2
|
||||
@@ -703,6 +727,10 @@ struct ncclComm {
|
||||
uint64_t seqNumber[NCCL_NUM_FUNCTIONS];
|
||||
struct ncclProfilerProxy profiler;
|
||||
|
||||
// CE Collective
|
||||
struct ncclCeColl ceColl;
|
||||
struct ncclIntruQueue<struct ncclCeInitTask, &ncclCeInitTask::next> ceInitTaskQueue;
|
||||
|
||||
// buffer registration cache
|
||||
struct ncclRegCache regCache;
|
||||
int isAllNvlink;
|
||||
@@ -712,13 +740,8 @@ struct ncclComm {
|
||||
bool useGdr;
|
||||
int splitCount;
|
||||
|
||||
// symmetric buffer
|
||||
uint8_t* baseUCSymPtr;
|
||||
uint8_t* baseMCSymPtr;
|
||||
size_t baseStride;
|
||||
size_t symAllocHead;
|
||||
CUmemGenericAllocationHandle symMCHandle;
|
||||
struct ncclIntruQueue<struct ncclSymRegTask, &ncclSymRegTask::next> symRegTaskQueue;
|
||||
struct ncclDevrState devrState; // The symmetric runtime state
|
||||
struct ncclSymkState symkState; // The symmetric kernels state (built on previous)
|
||||
|
||||
// unroll factor for comm [RCCL]
|
||||
int unroll;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#ifdef PROFAPI
|
||||
#define NCCL_API(ret, func, args...) \
|
||||
extern "C" \
|
||||
__attribute__ ((visibility("default"))) \
|
||||
__attribute__ ((alias(#func))) \
|
||||
ret p##func (args); \
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2018-2020, NVIDIA CORPORATION. All rights reserved.
|
||||
* Copyright (c) 2018-2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
@@ -7,54 +7,38 @@
|
||||
#ifndef NCCL_CPUSET_H_
|
||||
#define NCCL_CPUSET_H_
|
||||
|
||||
// Convert local_cpus, e.g. 0003ff,f0003fff to cpu_set_t
|
||||
#include "nccl.h"
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <sched.h>
|
||||
|
||||
static int hexToInt(char c) {
|
||||
int v = c - '0';
|
||||
if (v < 0) return -1;
|
||||
if (v > 9) v = 10 + c - 'a';
|
||||
if ((v < 0) || (v > 15)) return -1;
|
||||
return v;
|
||||
}
|
||||
// Convert local_cpus, e.g. 0003ff,f0003fff to cpu_set_t.
|
||||
// The bitmask is divided into chunks of 32 bits, each of them represented by 8 hex number.
|
||||
#define U32_LEN 32 // using uint32_t
|
||||
#define CPU_SET_N_U32 (CPU_SETSIZE / U32_LEN)
|
||||
|
||||
#define CPU_SET_N_U32 (sizeof(cpu_set_t)/sizeof(uint32_t))
|
||||
static ncclResult_t ncclStrToCpuset(const char* maskStr, cpu_set_t* set) {
|
||||
uint32_t cpumasks[CPU_SET_N_U32] = {0};
|
||||
|
||||
static ncclResult_t ncclStrToCpuset(const char* str, cpu_set_t* mask) {
|
||||
uint32_t cpumasks[CPU_SET_N_U32];
|
||||
int m = CPU_SET_N_U32-1;
|
||||
cpumasks[m] = 0;
|
||||
for (int o=0; o<strlen(str); o++) {
|
||||
char c = str[o];
|
||||
if (c == ',') {
|
||||
m--;
|
||||
cpumasks[m] = 0;
|
||||
} else {
|
||||
int v = hexToInt(c);
|
||||
if (v == -1) break;
|
||||
cpumasks[m] <<= 4;
|
||||
cpumasks[m] += v;
|
||||
// transform the string into an array of 32 bit masks, starting with the highest mask
|
||||
int m = CPU_SET_N_U32;
|
||||
char* str = strdup(maskStr);
|
||||
char* token = strtok(str, ",");
|
||||
while (token != NULL && m > 0) {
|
||||
cpumasks[--m] = strtoul(token, NULL, /*base = hex*/ 16);
|
||||
token = strtok(NULL, ",");
|
||||
}
|
||||
free(str);
|
||||
|
||||
// list all the CPUs as part of the CPU set, starting with the lowest mask (= current value of m)
|
||||
CPU_ZERO(set);
|
||||
for (int a = 0; (a + m) < CPU_SET_N_U32; a++) {
|
||||
// each mask is U32_LEN CPUs, list them all if the bit is on
|
||||
for (int i = 0; i < U32_LEN; ++i) {
|
||||
if (cpumasks[a + m] & (1UL << i)) CPU_SET(i + a * U32_LEN, set);
|
||||
}
|
||||
}
|
||||
// Copy cpumasks to mask
|
||||
for (int a=0; m<CPU_SET_N_U32; a++,m++) {
|
||||
memcpy(((uint32_t*)mask)+a, cpumasks+m, sizeof(uint32_t));
|
||||
}
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
static ncclResult_t ncclCpusetToStr(cpu_set_t* mask, char* str) {
|
||||
int c = 0;
|
||||
uint8_t* m8 = (uint8_t*)mask;
|
||||
for (int o=sizeof(cpu_set_t)-1; o>=0; o--) {
|
||||
if (c == 0 && m8[o] == 0) continue;
|
||||
sprintf(str+c, "%02x", m8[o]);
|
||||
c+=2;
|
||||
if (o && o%4 == 0) {
|
||||
sprintf(str+c, ",");
|
||||
c++;
|
||||
}
|
||||
}
|
||||
str[c] = '\0';
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
@@ -83,4 +67,31 @@ static char* ncclCpusetToRangeStr(cpu_set_t* mask, char* str, size_t len) {
|
||||
return str;
|
||||
}
|
||||
|
||||
static ncclResult_t ncclStrListToCpuset(const char* userStr, cpu_set_t* mask) {
|
||||
// reset the CPU set
|
||||
CPU_ZERO(mask);
|
||||
const char delim[] = ",";
|
||||
char* str = strdup(userStr);
|
||||
char* token = strtok(str, delim);
|
||||
while (token != NULL) {
|
||||
uint64_t cpu = strtoull(token, NULL, 0);
|
||||
CPU_SET(cpu, mask);
|
||||
token = strtok(NULL, delim);
|
||||
}
|
||||
free(str);
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
static ncclResult_t ncclCpusetToStrList(cpu_set_t* mask, char* str, size_t len) {
|
||||
if (len == 0) return ncclSuccess;
|
||||
str[0] = '\0';
|
||||
int count = 0;
|
||||
for (uint64_t id = 0; id < CPU_SETSIZE; ++id) {
|
||||
if (CPU_ISSET(id, mask)) {
|
||||
snprintf(str + strlen(str), len - strlen(str), "%s%lu", (count++ == 0) ? "" : ",", id);
|
||||
}
|
||||
}
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -114,6 +114,12 @@ DECLARE_CUDA_PFN_EXTERN(cuMulticastCreate, 12010);
|
||||
DECLARE_CUDA_PFN_EXTERN(cuMulticastGetGranularity, 12010);
|
||||
DECLARE_CUDA_PFN_EXTERN(cuMulticastUnbind, 12010);
|
||||
#endif
|
||||
/* Stream-MemOp support */
|
||||
DECLARE_CUDA_PFN_EXTERN(cuStreamBatchMemOp, 11070);
|
||||
DECLARE_CUDA_PFN_EXTERN(cuStreamWaitValue32, 11070);
|
||||
DECLARE_CUDA_PFN_EXTERN(cuStreamWaitValue64, 11070);
|
||||
DECLARE_CUDA_PFN_EXTERN(cuStreamWriteValue32, 11070);
|
||||
DECLARE_CUDA_PFN_EXTERN(cuStreamWriteValue64, 11070);
|
||||
#endif
|
||||
|
||||
ncclResult_t ncclCudaLibraryInit(void);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#define NCCL_THREAD_NAMELEN 16
|
||||
|
||||
extern int ncclDebugLevel;
|
||||
extern uint64_t ncclDebugMask;
|
||||
extern FILE *ncclDebugFile;
|
||||
|
||||
void ncclDebugLog(ncclDebugLogLevel level, unsigned long flags, const char *filefunc, int line, const char *fmt, ...) __attribute__ ((format (printf, 5, 6)));
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef NCCL_DEVICE_RUNTIME_H_
|
||||
#define NCCL_DEVICE_RUNTIME_H_
|
||||
#include "nccl.h"
|
||||
#include "nccl_device.h"
|
||||
#include "nccl_common.h"
|
||||
#include "allocator.h"
|
||||
#include "bitops.h"
|
||||
#include "utils.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// ncclDevr[_]: runtime implements for symmetric API.
|
||||
|
||||
struct ncclDevrMemory;
|
||||
struct ncclDevrWindow {
|
||||
struct ncclDevrMemory* memory;
|
||||
void* userPtr;
|
||||
size_t size;
|
||||
size_t bigOffset; // Offset in big VA space.
|
||||
int winFlags;
|
||||
void* localRegHandle;
|
||||
struct ncclWindow_vidmem* vidmem;
|
||||
};
|
||||
struct ncclDevrWindowSorted;
|
||||
struct ncclDevrTeam;
|
||||
|
||||
struct ncclDevrRegTask {
|
||||
struct ncclDevrRegTask *next;
|
||||
void* userPtr;
|
||||
size_t userSize;
|
||||
int winFlags;
|
||||
ncclWindow_t* outWinDev;
|
||||
};
|
||||
|
||||
struct ncclDevrCommCreateTask {
|
||||
struct ncclDevrCommCreateTask *next;
|
||||
struct ncclDevCommRequirements* reqs;
|
||||
struct ncclDevComm* outDevComm;
|
||||
};
|
||||
|
||||
struct ncclDevrState {
|
||||
// Like localRank/localRanks except "lsa" ranks must be consecutive in the world
|
||||
// and all lsa subsets have the same number of ranks. If any condition is
|
||||
// false then the lsa team is just the singleton of self.
|
||||
int lsaSelf;
|
||||
int lsaSize;
|
||||
int* lsaRankList;
|
||||
|
||||
size_t granularity; // cuMemGetAllocationGranularity
|
||||
struct ncclDevrMemory* memHead;
|
||||
struct ncclDevrWindowSorted* winSorted;
|
||||
int winSortedCapacity, winSortedCount;
|
||||
struct ncclDevrTeam* teamHead;
|
||||
size_t bigSize; // size of our big logical space (128GB?)
|
||||
struct ncclSpace bigSpace; // allocates our big VA space.
|
||||
void* lsaFlatBase; // base ptr for all lsa ranks big VA's concatenated together: size = lsaRanks*bigSize
|
||||
struct ncclShadowPool shadows;
|
||||
struct ncclDevCommWindowTable* windowTable;
|
||||
|
||||
struct ncclIntruQueue<struct ncclDevrRegTask, &ncclDevrRegTask::next> regTaskQueue;
|
||||
struct ncclIntruQueue<struct ncclDevrCommCreateTask, &ncclDevrCommCreateTask::next> commCreateTaskQueue;
|
||||
};
|
||||
|
||||
// We assume ncclComm has a `ncclDevrState symState` member.
|
||||
ncclResult_t ncclDevrInitOnce(struct ncclComm* comm);
|
||||
ncclResult_t ncclDevrFinalize(struct ncclComm* comm);
|
||||
|
||||
// If found *outWinHost will be populated and *outWinId >= 0, otherwise *outWinId == -1
|
||||
ncclResult_t ncclDevrFindWindow(struct ncclComm* comm, void const* userPtr, struct ncclDevrWindow** outWin);
|
||||
|
||||
ncclResult_t ncclDevrWindowRegisterInGroup(
|
||||
struct ncclComm* comm, void* ptr, size_t size, int winFlags, ncclWindow_t* outWinDev
|
||||
);
|
||||
|
||||
ncclResult_t ncclDevrCommCreateInternal(
|
||||
struct ncclComm* comm, struct ncclDevCommRequirements const* reqs, struct ncclDevComm* outDevComm
|
||||
);
|
||||
void freeDevCommRequirements(
|
||||
struct ncclDevCommRequirements* reqs
|
||||
);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
#endif
|
||||
@@ -25,9 +25,8 @@
|
||||
#else
|
||||
#include <hip/hip_bfloat16.h>
|
||||
#endif
|
||||
#include "nccl_common.h"
|
||||
#include "nccl_tuner.h"
|
||||
#include "bitops.h"
|
||||
#include "symmetric.h"
|
||||
#if defined(ENABLE_NPKIT)
|
||||
#include "npkit/npkit_struct.h"
|
||||
#endif
|
||||
@@ -230,6 +229,7 @@ struct ncclProxyConnector {
|
||||
struct ncclConnector {
|
||||
int connected;
|
||||
int hasSeen;
|
||||
int p2pOnly;
|
||||
struct ncclProxyConnector proxyConn;
|
||||
struct ncclTransportComm* transportComm;
|
||||
void* transportResources;
|
||||
@@ -300,7 +300,7 @@ struct ncclChannelPeer {
|
||||
int refCount;
|
||||
};
|
||||
|
||||
struct ncclDevComm;
|
||||
struct ncclKernelComm;
|
||||
|
||||
#pragma pack(push) /* push current alignment to stack */
|
||||
#pragma pack(8) /* set alignment to 8 bytes boundary */
|
||||
@@ -591,7 +591,7 @@ struct ncclDevProfiler {
|
||||
} data[MAX_PROFILER_EVENTS_PER_CHANNEL];
|
||||
};
|
||||
|
||||
struct ncclDevComm {
|
||||
struct ncclKernelComm {
|
||||
int rank;
|
||||
int nRanks;
|
||||
int node;
|
||||
@@ -639,8 +639,8 @@ struct ncclDevComm {
|
||||
#define RANDOM_DELAY_ON_WARP_START 0x1L
|
||||
#endif
|
||||
|
||||
struct alignas(16) ncclDevCommAndChannels {
|
||||
struct ncclDevComm comm;
|
||||
struct alignas(16) ncclKernelCommAndChannels {
|
||||
struct ncclKernelComm comm;
|
||||
struct ncclDevChannel channels[MAXCHANNELS];
|
||||
};
|
||||
|
||||
@@ -655,7 +655,7 @@ struct channelMasks {
|
||||
};
|
||||
|
||||
struct alignas(16) ncclDevKernelArgs {
|
||||
struct ncclDevComm* comm;
|
||||
struct ncclKernelComm* comm;
|
||||
struct channelMasks channelMask;
|
||||
enum ncclDevWorkStorageType workStorageType;
|
||||
uint32_t workMask;
|
||||
@@ -796,7 +796,7 @@ inline int ncclDevFuncId(int coll, int devRedOp, int type, int algo, int proto,
|
||||
if (coll == ncclFuncBroadcast) {
|
||||
key = ((uint64_t)(coll & RCCL_FUNC_ID_MASK) << RCCL_COLL_SHIFT ) |
|
||||
((uint64_t)(proto & RCCL_FUNC_ID_MASK) << RCCL_PROTO_SHIFT);
|
||||
} else if (coll == ncclFuncSendRecv || coll == ncclFuncAllToAllPivot || coll == ncclFuncAllToAllGda) {
|
||||
} else if (coll == ncclFuncSendRecv || coll == ncclFuncAlltoAllPivot || coll == ncclFuncAllToAllGda) {
|
||||
key = ((uint64_t)(coll & RCCL_FUNC_ID_MASK) << RCCL_COLL_SHIFT );
|
||||
} else {
|
||||
key = ((uint64_t)(coll & RCCL_FUNC_ID_MASK) << RCCL_COLL_SHIFT ) |
|
||||
|
||||
@@ -125,6 +125,7 @@ ncclResult_t ncclTopoPrintGraph(struct ncclTopoSystem* system, struct ncclTopoGr
|
||||
ncclResult_t ncclTopoDumpGraphs(struct ncclTopoSystem* system, int ngraphs, struct ncclTopoGraph** graphs);
|
||||
|
||||
struct ncclTopoRanks {
|
||||
int crossNicRing;
|
||||
int ringRecv[MAXCHANNELS];
|
||||
int ringSend[MAXCHANNELS];
|
||||
int ringPrev[MAXCHANNELS];
|
||||
@@ -142,6 +143,7 @@ ncclResult_t ncclTopoPostset(struct ncclComm* comm, int* firstRanks, int* treePa
|
||||
struct ncclTopoRanks** allTopoRanks, int* rings, struct ncclTopoGraph** graphs, struct ncclComm* parent, int nc);
|
||||
ncclResult_t ncclTreeBasePostset(struct ncclComm* comm, struct ncclTopoGraph* treeGraph);
|
||||
|
||||
ncclResult_t ncclTopoInitTunerConstants(struct ncclComm* comm);
|
||||
ncclResult_t ncclTopoTuneModel(struct ncclComm* comm, int minCompCap, int maxCompCap, struct ncclTopoGraph** graphs);
|
||||
ncclResult_t ncclTopoGetAlgoTime(struct ncclComm* comm, int coll, int algorithm, int protocol, size_t nBytes, int numPipeOps, float* time);
|
||||
int rcclGetTuningIndexForArch(const char* gfxarch);
|
||||
|
||||
@@ -44,6 +44,7 @@ struct ncclAsyncJob {
|
||||
uint32_t* childAbortFlagDev; /* point to child abortFlagDev */
|
||||
ncclComm_t comm;
|
||||
int destroyFlag;
|
||||
bool isThreadMain;
|
||||
};
|
||||
|
||||
ncclResult_t ncclAsyncLaunch(
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#define MSCCL_KERNEL_ENTRY_NAME(devredop, type, proto, fullOps) mscclKernel_##devredop##_##type##_##proto##_##fullOps
|
||||
|
||||
#define MSCCL_DECL_KERNEL_ENTRY_FUNC_DEVREDOP_TYPE_PROTO(devredop, type, proto, fullOps) \
|
||||
__global__ void MSCCL_KERNEL_ENTRY_NAME(devredop, type, proto, fullOps)(struct ncclDevComm* comm, struct mscclAlgo* algo, struct mscclWork* work);
|
||||
__global__ void MSCCL_KERNEL_ENTRY_NAME(devredop, type, proto, fullOps)(struct ncclKernelComm* comm, struct mscclAlgo* algo, struct mscclWork* work);
|
||||
|
||||
#define MSCCL_DECL_KERNEL_ENTRY_FUNC_DEVREDOP_TYPE(devredop, type, fullOps) \
|
||||
MSCCL_DECL_KERNEL_ENTRY_FUNC_DEVREDOP_TYPE_PROTO(devredop, type, LL, fullOps) \
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
#ifndef NCCL_DEBUG_H_
|
||||
#define NCCL_DEBUG_H_
|
||||
|
||||
// Workaround for libstdc++ trying to force public visibility of std:: symbols. We don't want to do that in libnccl.so.
|
||||
#include <bits/c++config.h>
|
||||
#undef _GLIBCXX_VISIBILITY
|
||||
#define _GLIBCXX_VISIBILITY(V)
|
||||
|
||||
#include <cstdint>
|
||||
#include "nccl.h"
|
||||
|
||||
@@ -63,34 +68,13 @@ typedef enum {
|
||||
ncclFuncSendRecv = 5,
|
||||
ncclFuncSend = 6,
|
||||
ncclFuncRecv = 7,
|
||||
ncclFuncAllToAllPivot = 8,
|
||||
ncclFuncAllToAllGda = 9,
|
||||
ncclNumFuncs = 10
|
||||
ncclFuncAlltoAll = 8,
|
||||
ncclFuncScatter = 9,
|
||||
ncclFuncGather = 10,
|
||||
ncclFuncAlltoAllPivot = 11,
|
||||
ncclFuncAllToAllGda = 12,
|
||||
ncclNumFuncs = 13
|
||||
} ncclFunc_t;
|
||||
|
||||
#define NCCL_NUM_ALGORITHMS 7 // Tree/Ring/CollNet*/PAT
|
||||
#define NCCL_ALGO_UNDEF -1
|
||||
#define NCCL_ALGO_TREE 0
|
||||
#define NCCL_ALGO_RING 1
|
||||
#define NCCL_ALGO_COLLNET_DIRECT 2
|
||||
#define NCCL_ALGO_COLLNET_CHAIN 3
|
||||
#define NCCL_ALGO_NVLS 4
|
||||
#define NCCL_ALGO_NVLS_TREE 5
|
||||
#define NCCL_ALGO_PAT 6
|
||||
|
||||
#define NCCL_NUM_PROTOCOLS 3 // Simple/LL/LL128
|
||||
#define NCCL_PROTO_UNDEF -1
|
||||
#define NCCL_PROTO_LL 0
|
||||
#define NCCL_PROTO_LL128 1
|
||||
#define NCCL_PROTO_SIMPLE 2
|
||||
|
||||
#define NCCL_ALGO_PROTO_IGNORE -1.0
|
||||
|
||||
#define NCCL_NUM_UNROLLS 3 // 1/2/4
|
||||
#define NCCL_UNROLL_1 0
|
||||
#define NCCL_UNROLL_2 1
|
||||
#define NCCL_UNROLL_4 2
|
||||
|
||||
#define NCCL_NUM_FLOATS 6 // half/float/double/rccl_bfloat16/rccl_float8/rccl_bfloat8
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#include "nccl_device/impl/comm__funcs.h"
|
||||
#include "nccl_device/coop.h"
|
||||
#include "nccl_device/impl/core__funcs.h"
|
||||
#include "nccl_device/impl/ll_a2a__funcs.h"
|
||||
#include "nccl_device/impl/mem_barrier__funcs.h"
|
||||
//#include "nccl_device/net_barrier__funcs.h"
|
||||
//#include "nccl_device/net_scratch_a2a__funcs.h"
|
||||
//#include "nccl_device/barrier__funcs.h"
|
||||
#include "nccl_device/impl/ptr__funcs.h"
|
||||
@@ -0,0 +1,32 @@
|
||||
This directory has been structured to make it easy for user to read the headers to learn the API. The files adjacent
|
||||
to this README are meant for humans. They contain the essential declarations like which types exist and function prototypes and comments
|
||||
indicating the contract/usage. Everything else goes into the "impl/" subdirectory. Most modules are stratified into three layers:
|
||||
|
||||
1) "foo.h" Public API declarations.
|
||||
2) "impl/foo__types.h" struct definitions. Has #include of layer 1.
|
||||
3) "impl/foo_funcs.h" inline functions. Has #include of layer 2.
|
||||
|
||||
The include dependencies should be acyclic for layers 1 and 2 since order matters for declarations and types. Layer 3 though
|
||||
can freely have cycles amongst itself ("impl/foo__funcs.h" and "impl/bar__funcs.h" can mutually include each other) since
|
||||
functions can be defined in any order once declared.
|
||||
|
||||
Translation units should just include "nccl_device.h" to ensure they get all the "impl/foo__funcs.h". But if a translation unit wants
|
||||
to be more specific as to which module it pulls in it should include "impl/foo__funcs.h".
|
||||
|
||||
One of the nasty reasons this was required is because of C++ defaulted function parameters:
|
||||
|
||||
```
|
||||
// +++ in foo.h +++
|
||||
struct Foo; // defined in some __types.h
|
||||
|
||||
// +++ in "impl/foo__types.h" +++
|
||||
struct Foo { int x; };
|
||||
|
||||
// +++ in "bar.h" +++
|
||||
// Prototype function where default value is default construction of Foo. Since
|
||||
// Foo would be incomplete if just including "foo.h" the compiler errors because
|
||||
// it can't reason about the {}.
|
||||
// I was able to solve this by including "impl/foo__types.h" instead.
|
||||
#include "impl/foo__types.h"
|
||||
void bar(Foo arg = {});
|
||||
```
|
||||
@@ -0,0 +1,10 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef _NCCL_DEVICE_COMM_H_
|
||||
#define _NCCL_DEVICE_COMM_H_
|
||||
#include "core_tmp.h"
|
||||
#endif
|
||||
@@ -0,0 +1,154 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef _NCCL_DEVICE_COOP_H_
|
||||
#define _NCCL_DEVICE_COOP_H_
|
||||
#include "utility.h"
|
||||
|
||||
#define __CUDACC__ 0
|
||||
|
||||
// ncclCoop[Foo]: NCCL's versions of CUDA's Cooperative Groups. They conform
|
||||
// to just this subset of the CUDA API:
|
||||
// int Coop::thread_rank();
|
||||
// int Coop::size();
|
||||
// int Coop::num_threads(); // same as size()
|
||||
// void Coop::sync();
|
||||
|
||||
#if __CUDACC__
|
||||
template<int nThreadsPow2>
|
||||
struct ncclCoopTile { // An aligned pow2 set of threads within the warp.
|
||||
static_assert(nccl::utility::isPow2(nThreadsPow2) && nThreadsPow2 <= 32, "Condition required");
|
||||
|
||||
NCCL_DEVICE_INLINE int thread_rank() const {
|
||||
return nccl::utility::lane() % nThreadsPow2;
|
||||
}
|
||||
NCCL_DEVICE_INLINE constexpr int size() const { return nThreadsPow2; }
|
||||
NCCL_DEVICE_INLINE constexpr int num_threads() const { return nThreadsPow2; }
|
||||
|
||||
NCCL_DEVICE_INLINE uint32_t laneMask() const {
|
||||
return (-1u>>(32-nThreadsPow2))<<(nccl::utility::lane() & -nThreadsPow2);
|
||||
}
|
||||
NCCL_DEVICE_INLINE void sync() {
|
||||
__syncwarp(laneMask());
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
typedef ncclCoopTile<1> ncclCoopThread;
|
||||
typedef ncclCoopTile<32> ncclCoopWarp;
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
struct ncclCoopLanes { // Some lanes of this warp.
|
||||
uint32_t lmask;
|
||||
|
||||
NCCL_DEVICE_INLINE constexpr ncclCoopLanes(uint32_t lmask=-1u): lmask(lmask) {}
|
||||
|
||||
NCCL_DEVICE_INLINE int thread_rank() const {
|
||||
return __popc(lmask & nccl::utility::lanemask_lt());
|
||||
}
|
||||
NCCL_DEVICE_INLINE int size() const {
|
||||
return __popc(lmask);
|
||||
}
|
||||
NCCL_DEVICE_INLINE int num_threads() const {
|
||||
return __popc(lmask);
|
||||
}
|
||||
NCCL_DEVICE_INLINE void sync() {
|
||||
__syncwarp(lmask);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
// A set of consecutive warps that the user has also supplied with a unique
|
||||
// id from [0..15]. It is an error for two different warp spans with the same
|
||||
// id to be in a collective concurrently.
|
||||
struct ncclCoopWarpSpan {
|
||||
uint32_t warp0:8, nWarps:8, id:8;
|
||||
|
||||
NCCL_DEVICE_INLINE constexpr ncclCoopWarpSpan(int warp0, int nWarps, int id):
|
||||
warp0(warp0), nWarps(nWarps), id(id) {
|
||||
}
|
||||
|
||||
NCCL_DEVICE_INLINE int thread_rank() const {
|
||||
return threadIdx.x - 32*warp0;
|
||||
}
|
||||
NCCL_DEVICE_INLINE int size() const {
|
||||
return 32*nWarps;
|
||||
}
|
||||
NCCL_DEVICE_INLINE int num_threads() const {
|
||||
return 32*nWarps;
|
||||
}
|
||||
|
||||
NCCL_DEVICE_INLINE void sync() {
|
||||
//asm volatile("barrier.sync %0, %1;" :: "r"(1+id), "r"(32*nWarps) : "memory");
|
||||
__barrier_sync_count(1+id, 32*nWarps);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
struct ncclCoopCta {
|
||||
NCCL_DEVICE_INLINE int thread_rank() const { return threadIdx.x; }
|
||||
NCCL_DEVICE_INLINE int size() const { return blockDim.x; }
|
||||
NCCL_DEVICE_INLINE int num_threads() const { return blockDim.x; }
|
||||
NCCL_DEVICE_INLINE void sync() { __syncthreads(); }
|
||||
};
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
template<int nThreadsPow2>
|
||||
NCCL_DEVICE_INLINE uint32_t ncclCoopLaneMask(ncclCoopTile<nThreadsPow2> coop) {
|
||||
return coop.laneMask();
|
||||
}
|
||||
NCCL_DEVICE_INLINE uint32_t ncclCoopLaneMask(ncclCoopLanes coop) {
|
||||
return coop.lmask;
|
||||
}
|
||||
NCCL_DEVICE_INLINE uint32_t ncclCoopLaneMask(ncclCoopWarpSpan coop) {
|
||||
return -1u;
|
||||
}
|
||||
NCCL_DEVICE_INLINE uint32_t ncclCoopLaneMask(ncclCoopCta coop) {
|
||||
return -1u;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
// ncclCoopIsThread:
|
||||
// At compile time do we know the given coop is a single thread only.
|
||||
template<int nThreads>
|
||||
NCCL_DEVICE_INLINE constexpr bool ncclCoopIsThread(ncclCoopTile<nThreads>) {
|
||||
return nThreads == 1;
|
||||
}
|
||||
NCCL_DEVICE_INLINE constexpr bool ncclCoopIsThread(ncclCoopLanes) { return false; }
|
||||
NCCL_DEVICE_INLINE constexpr bool ncclCoopIsThread(ncclCoopWarpSpan) { return false; }
|
||||
NCCL_DEVICE_INLINE constexpr bool ncclCoopIsThread(ncclCoopCta) { return false; }
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
// Pick threads of our warp that are safe to use collectively.
|
||||
NCCL_DEVICE_INLINE ncclCoopLanes ncclCoopCoalesced() {
|
||||
return ncclCoopLanes{__activemask()};
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
// Pick threads of our warp that are safe to use collectively given that this
|
||||
// is a collective on the provided cooperative group.
|
||||
template<typename Coop>
|
||||
NCCL_DEVICE_INLINE ncclCoopTile<32> ncclCoopCoalesced(Coop) {
|
||||
return ncclCoopTile<32>();
|
||||
}
|
||||
NCCL_DEVICE_INLINE ncclCoopLanes ncclCoopCoalesced(ncclCoopLanes coop) {
|
||||
return coop;
|
||||
}
|
||||
template<int nThreads>
|
||||
NCCL_DEVICE_INLINE ncclCoopTile<nThreads> ncclCoopCoalesced(ncclCoopTile<nThreads> coop) {
|
||||
return coop;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,152 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef _NCCL_DEVICE_CORE_H_
|
||||
#define _NCCL_DEVICE_CORE_H_
|
||||
#include <nccl.h>
|
||||
#include "coop.h"
|
||||
#include "utility.h"
|
||||
|
||||
#define __CUDACC__ 0
|
||||
|
||||
struct ncclDevComm;
|
||||
typedef struct ncclDevComm ncclDevComm_t;
|
||||
|
||||
struct ncclTeam;
|
||||
typedef struct ncclTeam ncclTeam_t;
|
||||
|
||||
// typedef struct ncclWindow_vidmem* ncclWindow_t; // in nccl.h
|
||||
|
||||
struct ncclMultimemHandle;
|
||||
typedef struct ncclMultimemHandle ncclMultimemHandle_t;
|
||||
|
||||
typedef uint32_t ncclDevResourceHandle;
|
||||
typedef ncclDevResourceHandle ncclDevResourceHandle_t;
|
||||
|
||||
struct ncclLsaBarrierHandle;
|
||||
typedef struct ncclLsaBarrierHandle ncclLsaBarrierHandle_t;
|
||||
|
||||
struct ncclLLA2AHandle;
|
||||
typedef struct ncclLLA2AHandle ncclLLA2AHandle_t;
|
||||
|
||||
struct ncclTeam {
|
||||
int nRanks, rank, stride;
|
||||
};
|
||||
|
||||
#if __cplusplus
|
||||
template<typename T> struct ncclSymPtr;
|
||||
#endif
|
||||
|
||||
#if __cplusplus
|
||||
struct ncclTeamTagWorld {};
|
||||
struct ncclTeamTagLsa {};
|
||||
struct ncclTeamTagRail {};
|
||||
#endif
|
||||
|
||||
struct ncclDevCommRequirements;
|
||||
typedef struct ncclDevCommRequirements ncclDevCommRequirements_t;
|
||||
|
||||
struct ncclDevResourceRequirements;
|
||||
typedef struct ncclDevResourceRequirements ncclDevResourceRequirements_t;
|
||||
|
||||
struct ncclTeamRequirements;
|
||||
typedef struct ncclTeamRequirements ncclTeamRequirements_t;
|
||||
|
||||
struct ncclDevCommRequirements {
|
||||
ncclDevResourceRequirements_t* resourceRequirementsList;
|
||||
ncclTeamRequirements_t* teamRequirementsList;
|
||||
|
||||
bool lsaMultimem; // Enable multimem on lsa team
|
||||
|
||||
int lsaBarrierCount;
|
||||
};
|
||||
|
||||
struct ncclDevResourceRequirements {
|
||||
ncclDevResourceRequirements_t* next;
|
||||
size_t bufferSize, bufferAlign;
|
||||
ncclDevResourceHandle_t* outBufferHandle; // If non-null, target assigned during ncclDevCommCreate.
|
||||
};
|
||||
|
||||
struct ncclTeamRequirements {
|
||||
ncclTeamRequirements_t* next;
|
||||
ncclTeam_t team;
|
||||
bool multimem;
|
||||
ncclMultimemHandle_t* outMultimemHandle; // If non-null, target assigned during ncclDevCommCreate.
|
||||
};
|
||||
|
||||
NCCL_EXTERN_C __host__ ncclResult_t ncclDevCommCreate(ncclComm_t, ncclDevCommRequirements_t const*, ncclDevComm_t* outDevComm);
|
||||
NCCL_EXTERN_C __host__ ncclResult_t ncclDevCommDestroy(ncclComm_t, ncclDevComm_t const* devComm);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Team API:
|
||||
|
||||
#if __cplusplus
|
||||
NCCL_HOST_DEVICE_INLINE ncclTeam ncclTeamWorld(ncclDevComm const&);
|
||||
#endif
|
||||
NCCL_EXTERN_C __host__ ncclTeam_t ncclTeamWorld(ncclComm_t);
|
||||
|
||||
#if __cplusplus
|
||||
NCCL_HOST_DEVICE_INLINE ncclTeam ncclTeamLsa(ncclDevComm const&);
|
||||
#endif
|
||||
NCCL_EXTERN_C __host__ ncclTeam_t ncclTeamLsa(ncclComm_t);
|
||||
|
||||
NCCL_EXTERN_C NCCL_HOST_DEVICE_INLINE bool ncclTeamRankIsMember(ncclTeam_t a, ncclTeam_t b, int bPeer);
|
||||
NCCL_EXTERN_C NCCL_HOST_DEVICE_INLINE int ncclTeamRankToTeam(ncclTeam_t a, ncclTeam_t b, int bPeer);
|
||||
|
||||
#if __cplusplus
|
||||
NCCL_HOST_DEVICE_INLINE int ncclTeamRankToWorld(ncclDevComm const&, ncclTeam, int rank);
|
||||
#endif
|
||||
NCCL_EXTERN_C __host__ int ncclTeamRankToWorld(ncclComm_t, ncclTeam_t, int rank);
|
||||
|
||||
#if __cplusplus
|
||||
NCCL_HOST_DEVICE_INLINE int ncclTeamRankToLsa(ncclDevComm const&, ncclTeam, int rank);
|
||||
#endif
|
||||
NCCL_EXTERN_C __host__ int ncclTeamRankToLsa(ncclComm_t, ncclTeam_t, int rank);
|
||||
|
||||
NCCL_EXTERN_C NCCL_HOST_DEVICE_INLINE ncclTeam_t ncclTeamInnerFactor(ncclTeam_t parent, int innerSize);
|
||||
NCCL_EXTERN_C NCCL_HOST_DEVICE_INLINE ncclTeam_t ncclTeamOuterFactor(ncclTeam_t parent, int innerSize);
|
||||
|
||||
// Interpret each team as a set of ranks. This function assumes that `subset`
|
||||
// is a subset of `parent`. Thus the number of ranks in the set difference of
|
||||
// `parent` minus `subset` is `super.nRanks - subset.nRanks`. Given `index` this
|
||||
// function returns the index'th element of `parent` minus `subset`.
|
||||
NCCL_EXTERN_C NCCL_HOST_DEVICE_INLINE int ncclTeamRankInDifference(ncclTeam_t parent, ncclTeam_t subset, int index);
|
||||
|
||||
// Equivalent to ncclTeamOuterFactor of lsa team.
|
||||
#if __cplusplus
|
||||
NCCL_HOST_DEVICE_INLINE ncclTeam ncclTeamRail(ncclDevComm const&);
|
||||
#endif
|
||||
NCCL_EXTERN_C __host__ ncclTeam_t ncclTeamRail(ncclComm_t);
|
||||
|
||||
// Get offset of resource buffer within `comm.resourceWindow`.
|
||||
NCCL_EXTERN_C NCCL_HOST_DEVICE_INLINE size_t ncclGetResourceBufferOffset(ncclDevResourceHandle_t);
|
||||
|
||||
#if __CUDACC__
|
||||
NCCL_DEVICE_INLINE ncclSymPtr<char> ncclGetResourceBuffer(ncclDevComm const&, ncclDevResourceHandle);
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Window API:
|
||||
|
||||
#if __CUDACC__
|
||||
NCCL_DEVICE_INLINE void* ncclGetLocalPointer(ncclWindow_t w, size_t offset);
|
||||
NCCL_DEVICE_INLINE void* ncclGetLsaPointer(ncclWindow_t w, size_t offset, int peer);
|
||||
NCCL_DEVICE_INLINE void* ncclGetPeerPointer(ncclWindow_t w, size_t offset, int peer);
|
||||
NCCL_DEVICE_INLINE void* ncclGetPeerPointer(ncclWindow_t w, size_t offset, ncclTeam tm, int peer);
|
||||
NCCL_DEVICE_INLINE void* ncclGetMultimemPointer(ncclWindow_t w, size_t offset, ncclMultimemHandle mmHandle);
|
||||
NCCL_DEVICE_INLINE void* ncclGetLsaMultimemPointer(ncclWindow_t w, size_t offset, ncclDevComm const&);
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
// Convenience for combining ncclGet***Pointer() with resource handle.
|
||||
NCCL_DEVICE_INLINE void* ncclGetResourceBufferLocalPointer(ncclDevComm const&, ncclDevResourceHandle);
|
||||
NCCL_DEVICE_INLINE void* ncclGetResourceBufferLsaPointer(ncclDevComm const&, ncclDevResourceHandle, int peer);
|
||||
NCCL_DEVICE_INLINE void* ncclGetResourceBufferPeerPointer(ncclDevComm const&, ncclDevResourceHandle, ncclTeam, int peer);
|
||||
NCCL_DEVICE_INLINE void* ncclGetResourceBufferMultimemPointer(ncclDevComm const&, ncclDevResourceHandle, ncclMultimemHandle);
|
||||
NCCL_DEVICE_INLINE void* ncclGetResourceBufferLsaMultimemPointer(ncclDevComm const&, ncclDevResourceHandle);
|
||||
#endif
|
||||
|
||||
#endif // _NCCL_DEVICE_CORE_H_
|
||||
@@ -0,0 +1,10 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef _NCCL_DEVICE_COMM__FUNCS_H_
|
||||
#define _NCCL_DEVICE_COMM__FUNCS_H_
|
||||
#include "comm__types.h"
|
||||
#endif // _NCCL_DEVICE_COMM__FUNCS_H_
|
||||
@@ -0,0 +1,40 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef _NCCL_DEVICE_COMM__TYPES_H_
|
||||
#define _NCCL_DEVICE_COMM__TYPES_H_
|
||||
#include "../comm_tmp.h"
|
||||
#include "core__types.h"
|
||||
#include "mem_barrier__types.h"
|
||||
#include "ll_a2a__types.h"
|
||||
|
||||
struct ncclDevCommWindowTable;
|
||||
#if __cplusplus
|
||||
struct ncclDevCommWindowTable {
|
||||
struct Entry {
|
||||
uintptr_t base, size;
|
||||
ncclWindow_t window;
|
||||
} entries[32];
|
||||
struct ncclDevCommWindowTable* next;
|
||||
};
|
||||
#endif
|
||||
|
||||
struct ncclDevComm {
|
||||
int rank, nRanks;
|
||||
uint32_t nRanks_rcp32;
|
||||
int lsaRank, lsaSize;
|
||||
uint32_t lsaSize_rcp32;
|
||||
|
||||
struct ncclDevCommWindowTable* windowTable;
|
||||
|
||||
ncclWindow_t resourceWindow;
|
||||
struct ncclWindow_vidmem resourceWindow_inlined;
|
||||
|
||||
ncclMultimemHandle_t lsaMultimem;
|
||||
ncclLsaBarrierHandle_t lsaBarrier;
|
||||
};
|
||||
|
||||
#endif // _NCCL_DEVICE_COMM__TYPES_H_
|
||||
@@ -0,0 +1,212 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef _NCCL_DEVICE_CORE__FUNCS_H_
|
||||
#define _NCCL_DEVICE_CORE__FUNCS_H_
|
||||
#include "core__types.h"
|
||||
#include "comm__types.h"
|
||||
#include "ptr__types.h"
|
||||
|
||||
#define __CUDACC__ 0
|
||||
|
||||
#if __cplusplus
|
||||
NCCL_HOST_DEVICE_INLINE ncclTeam ncclTeamWorld(ncclDevComm const &comm) {
|
||||
ncclTeam ans;
|
||||
ans.nRanks = comm.nRanks;
|
||||
ans.rank = comm.rank;
|
||||
ans.stride = 1;
|
||||
return ans;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __cplusplus
|
||||
NCCL_HOST_DEVICE_INLINE ncclTeam ncclTeamLsa(ncclDevComm const &comm) {
|
||||
ncclTeam ans;
|
||||
ans.nRanks = comm.lsaSize;
|
||||
ans.rank = comm.lsaRank;
|
||||
ans.stride = 1;
|
||||
return ans;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __cplusplus
|
||||
NCCL_HOST_DEVICE_INLINE ncclTeam ncclTeamRail(ncclDevComm const& comm) {
|
||||
ncclTeam ans;
|
||||
ans.nRanks = nccl::utility::idivFast32(comm.nRanks, comm.lsaSize, comm.lsaSize_rcp32);
|
||||
ans.rank = nccl::utility::idivFast32(comm.rank, comm.lsaSize, comm.lsaSize_rcp32);
|
||||
ans.stride = comm.lsaSize;
|
||||
return ans;
|
||||
}
|
||||
#endif
|
||||
|
||||
NCCL_HOST_DEVICE_INLINE bool ncclTeamRankIsMember(ncclTeam_t a, ncclTeam_t b, int brank) {
|
||||
int wrank = (brank - b.rank)*b.stride;
|
||||
uint32_t adelta = wrank/a.stride;
|
||||
uint32_t amod = wrank%a.stride;
|
||||
int arank = a.rank + adelta;
|
||||
return 0 <= arank && arank < a.nRanks && amod == 0;
|
||||
}
|
||||
|
||||
NCCL_HOST_DEVICE_INLINE int ncclTeamRankToTeam(ncclTeam_t a, ncclTeam_t b, int brank) {
|
||||
int wrank = (brank - b.rank)*b.stride;
|
||||
uint32_t adelta = wrank/a.stride;
|
||||
//uint32_t amod = wrank%a.stride;
|
||||
int arank = a.rank + adelta;
|
||||
return arank;
|
||||
}
|
||||
|
||||
#if __cplusplus
|
||||
NCCL_HOST_DEVICE_INLINE int ncclTeamRankToWorld(ncclDevComm const& comm, ncclTeam tm, int rank) {
|
||||
return comm.rank + (rank - tm.rank)*tm.stride;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __cplusplus
|
||||
NCCL_HOST_DEVICE_INLINE int ncclTeamRankToLsa(ncclDevComm const& comm, ncclTeam tm, int rank) {
|
||||
return comm.lsaRank + (rank - tm.rank)*tm.stride;
|
||||
}
|
||||
#endif
|
||||
|
||||
NCCL_HOST_DEVICE_INLINE ncclTeam_t ncclTeamInnerFactor(ncclTeam_t parent, int innerSize) {
|
||||
ncclTeam_t ans;
|
||||
ans.nRanks = innerSize;
|
||||
ans.rank = parent.rank%innerSize;
|
||||
ans.stride = parent.stride;
|
||||
return ans;
|
||||
}
|
||||
|
||||
NCCL_HOST_DEVICE_INLINE ncclTeam_t ncclTeamOuterFactor(ncclTeam_t parent, int innerSize) {
|
||||
ncclTeam_t ans;
|
||||
ans.nRanks = parent.nRanks/innerSize;
|
||||
ans.rank = parent.rank/innerSize;
|
||||
ans.stride = parent.stride*innerSize;
|
||||
return ans;
|
||||
}
|
||||
|
||||
NCCL_HOST_DEVICE_INLINE int ncclTeamRankInDifference(ncclTeam_t parent, ncclTeam_t subset, int index) {
|
||||
int stride = subset.stride/parent.stride;
|
||||
int below = parent.rank - subset.rank*stride;
|
||||
if (stride < 0) {
|
||||
stride = -stride;
|
||||
below -= (subset.nRanks-1)*stride;
|
||||
}
|
||||
if (index < below) {
|
||||
return index;
|
||||
} else if (index-below < (subset.nRanks-1)*(stride-1)) {
|
||||
return below + 1 + ((index-below)/(stride-1))*stride + (index-below)%(stride-1);
|
||||
} else {
|
||||
return below + 1 + (subset.nRanks-1)*stride + (index - below - (subset.nRanks-1)*(stride-1));
|
||||
}
|
||||
}
|
||||
|
||||
#if __CUDACC__
|
||||
NCCL_DEVICE_INLINE void* ncclGetLocalPointer(ncclWindow_t w, size_t offset) {
|
||||
char* base = nccl::utility::loadConst(&w->lsaFlatBase);
|
||||
uint32_t stride4G = nccl::utility::loadConst(&w->stride4G);
|
||||
int i = nccl::utility::loadConst(&w->lsaRank);
|
||||
return (void*)(nccl::utility::add4G(base, i*stride4G) + offset);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
NCCL_DEVICE_INLINE void* ncclGetLsaPointer(ncclWindow_t w, size_t offset, int peer) {
|
||||
char* base = nccl::utility::loadConst(&w->lsaFlatBase);
|
||||
uint32_t stride4G = nccl::utility::loadConst(&w->stride4G);
|
||||
int i = peer;
|
||||
return (void*)(nccl::utility::add4G(base, i*stride4G) + offset);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
NCCL_DEVICE_INLINE void* ncclGetPeerPointer(ncclWindow_t w, size_t offset, int peer) {
|
||||
char* base = nccl::utility::loadConst(&w->lsaFlatBase);
|
||||
uint32_t stride4G = nccl::utility::loadConst(&w->stride4G);
|
||||
int worldRank = nccl::utility::loadConst(&w->worldRank);
|
||||
int lsaRank = nccl::utility::loadConst(&w->lsaRank);
|
||||
int i = lsaRank + (peer - worldRank);
|
||||
return (void*)(nccl::utility::add4G(base, i*stride4G) + offset);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
NCCL_DEVICE_INLINE void* ncclGetPeerPointer(ncclWindow_t w, size_t offset, ncclTeam tm, int peer) {
|
||||
char* base = nccl::utility::loadConst(&w->lsaFlatBase);
|
||||
uint32_t stride4G = nccl::utility::loadConst(&w->stride4G);
|
||||
int lsaRank = nccl::utility::loadConst(&w->lsaRank);
|
||||
int i = lsaRank + (peer - tm.rank)*tm.stride;
|
||||
return (void*)(nccl::utility::add4G(base, i*stride4G) + offset);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
NCCL_DEVICE_INLINE void* ncclGetMultimemPointer(ncclWindow_t w, size_t offset, ncclMultimemHandle mm) {
|
||||
void* ptr = mm.mcBasePtr;
|
||||
ptr = reinterpret_cast<char(*)[4096]>(ptr) + nccl::utility::loadConst(&w->mcOffset4K);
|
||||
return (void*)((char*)ptr + offset);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
NCCL_DEVICE_INLINE void* ncclGetLsaMultimemPointer(ncclWindow_t w, size_t offset, ncclDevComm const& comm) {
|
||||
return ncclGetMultimemPointer(w, offset, comm.lsaMultimem);
|
||||
}
|
||||
#endif
|
||||
|
||||
NCCL_HOST_DEVICE_INLINE size_t ncclGetResourceBufferOffset(ncclDevResourceHandle_t h) {
|
||||
return ((size_t)h)*128;
|
||||
}
|
||||
|
||||
#if __CUDACC__
|
||||
NCCL_DEVICE_INLINE void* ncclGetResourceBufferLocalPointer(ncclDevComm const& comm, ncclDevResourceHandle h) {
|
||||
void* lsaFlatBase = comm.resourceWindow_inlined.lsaFlatBase;
|
||||
uint32_t stride4G = comm.resourceWindow_inlined.stride4G;
|
||||
void* local = nccl::utility::add4G(lsaFlatBase, comm.lsaRank*stride4G);
|
||||
return (void*)(reinterpret_cast<char(*)[128]>(local) + h);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
NCCL_DEVICE_INLINE void* ncclGetResourceBufferLsaPointer(ncclDevComm const& comm, ncclDevResourceHandle h, int peer) {
|
||||
int r = peer;
|
||||
void* lsaFlatBase = comm.resourceWindow_inlined.lsaFlatBase;
|
||||
uint32_t stride4G = comm.resourceWindow_inlined.stride4G;
|
||||
void* local = nccl::utility::add4G(lsaFlatBase, r*stride4G);
|
||||
return (void*)(reinterpret_cast<char(*)[128]>(local) + h);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
NCCL_DEVICE_INLINE void* ncclGetResourceBufferPeerPointer(ncclDevComm const& comm, ncclDevResourceHandle h, ncclTeam team, int peer) {
|
||||
int r = comm.lsaRank + (peer - team.rank)*team.stride;
|
||||
void* lsaFlatBase = comm.resourceWindow_inlined.lsaFlatBase;
|
||||
uint32_t stride4G = comm.resourceWindow_inlined.stride4G;
|
||||
void* local = nccl::utility::add4G(lsaFlatBase, r*stride4G);
|
||||
return (void*)(reinterpret_cast<char(*)[128]>(local) + h);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
NCCL_DEVICE_INLINE void* ncclGetResourceBufferMultimemPointer(ncclDevComm const& comm, ncclDevResourceHandle h, ncclMultimemHandle mm) {
|
||||
void* ptr = mm.mcBasePtr;
|
||||
ptr = reinterpret_cast<char(*)[4096]>(ptr) + comm.resourceWindow_inlined.mcOffset4K;
|
||||
ptr = reinterpret_cast<char(*)[128]>(ptr) + h;
|
||||
return ptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
NCCL_DEVICE_INLINE void* ncclGetResourceBufferLsaMultimemPointer(ncclDevComm const& comm, ncclDevResourceHandle h) {
|
||||
return ncclGetResourceBufferMultimemPointer(comm, h, comm.lsaMultimem);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
NCCL_DEVICE_INLINE ncclSymPtr<char> ncclGetResourceBuffer(ncclDevComm const& comm, ncclDevResourceHandle h) {
|
||||
return ncclSymPtr<char>(comm.resourceWindow, size_t(h)*128);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef _NCCL_DEVICE_CORE__TYPES_H_
|
||||
#define _NCCL_DEVICE_CORE__TYPES_H_
|
||||
#include "../core_tmp.h"
|
||||
|
||||
// nccl.h has: typedef ncclWindow_vidmem* ncclWindow_t;
|
||||
struct ncclWindow_vidmem {
|
||||
void* winHost;
|
||||
//ncclGinWindow_t ginWin;
|
||||
char* lsaFlatBase; // pointer to first byte for rank 0 of lsa team
|
||||
int lsaRank;
|
||||
int worldRank;
|
||||
uint32_t stride4G;
|
||||
uint32_t mcOffset4K;
|
||||
};
|
||||
|
||||
struct ncclMultimemHandle {
|
||||
void* mcBasePtr;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,231 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef _NCCL_DEVICE_LL_A2A__FUNCS_H_
|
||||
#define _NCCL_DEVICE_LL_A2A__FUNCS_H_
|
||||
#include "ll_a2a__types.h"
|
||||
#include "comm__types.h"
|
||||
#include "../utility.h"
|
||||
|
||||
#define __CUDACC__ 0
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename Coop>
|
||||
NCCL_DEVICE_INLINE ncclLLA2ASession<Coop>::ncclLLA2ASession(
|
||||
Coop coop, ncclDevComm const& comm, ncclTeam team,
|
||||
ncclLLA2AHandle handle, uint32_t block, int maxElts,
|
||||
bool multimem, ncclMultimemHandle mmHandle
|
||||
):
|
||||
ncclLLA2ASession_internal<Coop>{
|
||||
coop, comm, team, handle, (int)block, /*pitch=*/maxElts,
|
||||
multimem, mmHandle, /*epoch=*/0, /*slotsOffset=*/0
|
||||
} {
|
||||
uint4* line = (uint4*)ncclGetResourceBufferLocalPointer(comm, handle.bufHandle);
|
||||
line += block*(1 + 2*handle.nSlots);
|
||||
this->epoch = line->x + 2;
|
||||
this->slotsOffset = this->calcSlotOffset();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename Coop>
|
||||
NCCL_DEVICE_INLINE ncclLLA2ASession<Coop>::~ncclLLA2ASession() {
|
||||
uint4* line = (uint4*)ncclGetResourceBufferLocalPointer(this->comm, this->handle.bufHandle);
|
||||
line += this->block*(1 + 2*this->handle.nSlots);
|
||||
if (this->coop.thread_rank() == 0) line->x = this->epoch - 2;
|
||||
this->coop.sync();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename Coop>
|
||||
template<typename T>
|
||||
NCCL_DEVICE_INLINE void ncclLLA2ASession<Coop>::send(int peer, int elt, T data) {
|
||||
using nccl::utility::divUp;
|
||||
union { T tmp; uint32_t u32[divUp(sizeof(T), 8)][2]; };
|
||||
tmp = data;
|
||||
uint4* buf = (uint4*)ncclGetResourceBufferPeerPointer(this->comm, this->handle.bufHandle, this->team, peer);
|
||||
buf += this->slotsOffset + elt;
|
||||
#pragma unroll
|
||||
for (int u=0; u < divUp(sizeof(T), 8); u++) {
|
||||
asm volatile("st.volatile.v4.u32 [%0],{%1,%3,%2,%3};" ::
|
||||
"l"(buf + u*this->pitch),
|
||||
"r"(u32[u][0]), "r"(u32[u][1]), "r"(this->epoch)
|
||||
);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename Coop>
|
||||
template<typename T>
|
||||
NCCL_DEVICE_INLINE void ncclLLA2ASession<Coop>::bcast(int elt, T data) {
|
||||
using nccl::utility::divUp;
|
||||
if (this->multimem) {
|
||||
union { T tmp; uint32_t u32[divUp(sizeof(T),8)][2]; };
|
||||
tmp = data;
|
||||
uint4* bufmc = (uint4*)ncclGetResourceBufferMultimemPointer(this->comm, this->handle.bufHandle, this->mmHandle);
|
||||
bufmc += this->slotsOffset + elt;
|
||||
#pragma unroll
|
||||
for (int u=0; u < divUp(sizeof(T), 8); u++) {
|
||||
asm volatile("st.volatile.v4.u32 [%0],{%1,%3,%2,%3};" ::
|
||||
"l"(bufmc + this->pitch*u),
|
||||
"r"(u32[u][0]), "r"(u32[u][1]), "r"(this->epoch)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
union { T tmp; uint32_t u32[divUp(sizeof(T), 8)][2]; };
|
||||
tmp = data;
|
||||
int dr = 0;
|
||||
int r = this->team.rank;
|
||||
#pragma unroll 1
|
||||
for (; dr+8 <= this->team.nRanks; dr += 8) {
|
||||
#pragma unroll
|
||||
for (int ur=0; ur < 8; ur++) {
|
||||
uint4* buf = (uint4*)ncclGetResourceBufferPeerPointer(this->comm, this->handle.bufHandle, this->team, r);
|
||||
buf += this->slotsOffset + elt;
|
||||
#pragma unroll
|
||||
for (int u=0; u < divUp(sizeof(T),8); u++) {
|
||||
asm volatile("st.volatile.v4.u32 [%0],{%1,%3,%2,%3};" ::
|
||||
"l"(buf + u*this->pitch),
|
||||
"r"(u32[u][0]), "r"(u32[u][1]), "r"(this->epoch)
|
||||
);
|
||||
}
|
||||
r += 1;
|
||||
if (r == this->team.nRanks) r = 0;
|
||||
}
|
||||
}
|
||||
#pragma unroll
|
||||
for (int ur=0; ur < 8; ur++, dr++) {
|
||||
if (dr == this->team.nRanks) break;
|
||||
uint4* buf = (uint4*)ncclGetResourceBufferPeerPointer(this->comm, this->handle.bufHandle, this->team, r);
|
||||
buf += this->slotsOffset + elt;
|
||||
#pragma unroll
|
||||
for (int u=0; u < divUp(sizeof(T),8); u++) {
|
||||
asm volatile("st.volatile.v4.u32 [%0],{%1,%3,%2,%3};" ::
|
||||
"l"(buf + u*this->pitch),
|
||||
"r"(u32[u][0]), "r"(u32[u][1]), "r"(this->epoch)
|
||||
);
|
||||
}
|
||||
r += 1;
|
||||
if (r == this->team.nRanks) r = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename Coop>
|
||||
template<typename T>
|
||||
NCCL_DEVICE_INLINE T ncclLLA2ASession<Coop>::recv(int elt) {
|
||||
T ret[1];
|
||||
this->template recvUnrolled</*MinEltCount=*/1, /*MaxEltCount=*/1>(elt, 1, 0, ret);
|
||||
return ret[0];
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename Coop>
|
||||
template<int MinEltCount, int MaxEltCount, typename T>
|
||||
NCCL_DEVICE_INLINE void ncclLLA2ASession<Coop>::recvUnrolled(int eltStart, int eltCount, int eltStride, T(&elts)[MaxEltCount]) {
|
||||
using nccl::utility::divUp;
|
||||
uint4* buf = (uint4*)ncclGetResourceBufferLocalPointer(this->comm, this->handle.bufHandle);
|
||||
buf += this->slotsOffset + eltStart;
|
||||
|
||||
uint4 tmp[MaxEltCount][divUp(sizeof(T), 8)];
|
||||
#pragma unroll 1
|
||||
while (true) {
|
||||
#pragma unroll
|
||||
for (int u=0; u < MaxEltCount; u++) {
|
||||
if (u < MinEltCount || u < eltCount) {
|
||||
#pragma unroll
|
||||
for (int v=0; v < divUp(sizeof(T), 8); v++) {
|
||||
asm volatile("ld.volatile.v4.u32 {%0,%1,%2,%3},[%4];"
|
||||
: "=r"(tmp[u][v].x), "=r"(tmp[u][v].y), "=r"(tmp[u][v].z), "=r"(tmp[u][v].w)
|
||||
: "l"(buf + u*eltStride + v*this->pitch));
|
||||
}
|
||||
}
|
||||
}
|
||||
bool okAll = true;
|
||||
#pragma unroll
|
||||
for (int u=0; u < MaxEltCount; u++) {
|
||||
#pragma unroll
|
||||
for (int v=0; v < divUp(sizeof(T), 8); v++) {
|
||||
if (u < MinEltCount || u < eltCount) {
|
||||
bool ok = tmp[u][v].y == this->epoch &&
|
||||
tmp[u][v].w == this->epoch;
|
||||
okAll &= ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (__builtin_expect(okAll, true)) break;
|
||||
}
|
||||
|
||||
#pragma unroll
|
||||
for (int u=0; u < MaxEltCount; u++) {
|
||||
if (MinEltCount <= u && u == eltCount) break;
|
||||
union { T val; uint32_t u32[divUp(sizeof(T), 8)][2]; };
|
||||
#pragma unroll
|
||||
for (int v=0; v < divUp(sizeof(T), 8); v++) {
|
||||
u32[v][0] = tmp[u][v].x;
|
||||
u32[v][1] = tmp[u][v].z;
|
||||
}
|
||||
elts[u] = val;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename Coop>
|
||||
template<int Unroll, typename Elt, typename EltToAcc, typename Reduce>
|
||||
NCCL_DEVICE_INLINE auto ncclLLA2ASession<Coop>::recvReduce(
|
||||
int eltStart, int eltCount, int eltStride, EltToAcc eltToAcc, Reduce reduce
|
||||
) -> decltype(eltToAcc(nccl::utility::declval<Elt>())) {
|
||||
using Acc = decltype(eltToAcc(nccl::utility::declval<Elt>()));
|
||||
Acc acc;
|
||||
int i = 0;
|
||||
#pragma unroll 1
|
||||
for (; i+Unroll <= eltCount; i += Unroll) {
|
||||
Elt got[Unroll];
|
||||
this->template recvUnrolled</*Min=*/Unroll>(eltStart + i*eltStride, Unroll, eltStride, got);
|
||||
Acc acc0 = eltToAcc(got[0]);
|
||||
acc = i==0 ? acc0 : reduce(acc, acc0);
|
||||
#pragma unroll
|
||||
for (int j=1; j < Unroll; j++) acc = reduce(acc, eltToAcc(got[j]));
|
||||
}
|
||||
if (i < eltCount) {
|
||||
Elt got[Unroll];
|
||||
this->template recvUnrolled</*Min=*/1>(eltStart + i*eltStride, eltCount-i, eltStride, got);
|
||||
Acc acc0 = eltToAcc(got[0]);
|
||||
acc = i==0 ? acc0 : reduce(acc, acc0);
|
||||
#pragma unroll
|
||||
for (int j=1; j < Unroll-1; j++) {
|
||||
if (i+j < eltCount) acc = reduce(acc, eltToAcc(got[j]));
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename Coop>
|
||||
NCCL_DEVICE_INLINE void ncclLLA2ASession<Coop>::endEpoch(Coop) {
|
||||
if (__builtin_expect(this->epoch >= -2u, false)) {
|
||||
this->coop.sync();
|
||||
uint4* buf = (uint4*)ncclGetResourceBufferLocalPointer(this->comm, this->handle.bufHandle);
|
||||
buf += this->slotsOffset;
|
||||
#pragma unroll 4
|
||||
for (int i=this->coop.thread_rank(); i < this->handle.nSlots; i += this->coop.size()) {
|
||||
buf[i] = uint4{0, 0, 0, 0};
|
||||
}
|
||||
}
|
||||
this->coop.sync();
|
||||
this->epoch += (this->epoch == -1u) ? 3 : 1;
|
||||
this->slotsOffset = this->calcSlotOffset();
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _NCCL_DEVICE_LL_A2A__FUNCS_H_
|
||||
@@ -0,0 +1,39 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef _NCCL_DEVICE_LL_A2A__TYPES_H_
|
||||
#define _NCCL_DEVICE_LL_A2A__TYPES_H_
|
||||
#include "../ll_a2a.h"
|
||||
#include "core__types.h"
|
||||
|
||||
#define __CUDACC__ 0
|
||||
|
||||
struct ncclLLA2AHandle {
|
||||
ncclDevResourceHandle_t bufHandle;
|
||||
uint32_t nSlots;
|
||||
};
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename Coop>
|
||||
struct ncclLLA2ASession_internal {
|
||||
Coop coop;
|
||||
ncclDevComm const& comm;
|
||||
ncclTeam team;
|
||||
ncclLLA2AHandle handle;
|
||||
int block;
|
||||
int pitch;
|
||||
bool multimem;
|
||||
ncclMultimemHandle mmHandle;
|
||||
uint32_t epoch;
|
||||
uint32_t slotsOffset;
|
||||
|
||||
NCCL_DEVICE_INLINE uint32_t calcSlotOffset() const {
|
||||
return block*(1 + 2*handle.nSlots) + 1 + (epoch & 1)*handle.nSlots;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // _NCCL_DEVICE_LL_A2A__TYPES_H_
|
||||
@@ -0,0 +1,128 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef _NCCL_DEVICE_MEM_BARRIER__FUNCS_H_
|
||||
#define _NCCL_DEVICE_MEM_BARRIER__FUNCS_H_
|
||||
#include "mem_barrier__types.h"
|
||||
#include "comm__types.h"
|
||||
|
||||
#define __CUDACC__ 0
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename Coop>
|
||||
NCCL_DEVICE_INLINE ncclLsaBarrierSession<Coop>::ncclLsaBarrierSession(
|
||||
Coop coop, ncclDevComm const& comm, ncclTeam team,
|
||||
ncclLsaBarrierHandle handle, uint32_t index,
|
||||
bool multimem, ncclMultimemHandle mmHandle
|
||||
):
|
||||
ncclLsaBarrierSession_internal<Coop>{
|
||||
coop, comm, team, handle, (int)index,
|
||||
#if CUDART_VERSION >= 12060
|
||||
multimem,
|
||||
#else // WAR for an issue with ptxas in CTK < 12.6
|
||||
/*multimem=*/false,
|
||||
#endif
|
||||
mmHandle, /*epoch=*/0
|
||||
} {
|
||||
uint32_t* state = (uint32_t*)ncclGetResourceBufferLocalPointer(comm, handle.bufHandle);
|
||||
this->epoch = state[(this->multimem ? 0 : 1)*this->handle.nBarriers + this->index];
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename Coop>
|
||||
NCCL_DEVICE_INLINE ncclLsaBarrierSession<Coop>::ncclLsaBarrierSession(
|
||||
Coop coop, ncclDevComm const& comm, ncclTeamTagLsa, uint32_t index, bool multimem
|
||||
): ncclLsaBarrierSession(
|
||||
coop, comm, ncclTeamLsa(comm), comm.lsaBarrier, index, multimem, comm.lsaMultimem
|
||||
) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename Coop>
|
||||
NCCL_DEVICE_INLINE ncclLsaBarrierSession<Coop>::~ncclLsaBarrierSession() {
|
||||
uint32_t* state = (uint32_t*)ncclGetResourceBufferLocalPointer(this->comm, this->handle.bufHandle);
|
||||
if (this->coop.thread_rank() == 0) {
|
||||
#if __CUDA_ARCH__ == 1200 && CUDART_VERSION < 13000
|
||||
// WAR for a compiler issue with CTK < 13.0
|
||||
if (this->index == 0)
|
||||
state[(this->multimem ? 0 : 1)*this->handle.nBarriers] = this->epoch;
|
||||
else
|
||||
#endif
|
||||
state[(this->multimem ? 0 : 1)*this->handle.nBarriers + this->index] = this->epoch;
|
||||
}
|
||||
this->coop.sync();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename Coop>
|
||||
NCCL_DEVICE_INLINE void ncclLsaBarrierSession<Coop>::arrive(Coop, cuda::memory_order order) {
|
||||
this->coop.sync();
|
||||
if (this->multimem) {
|
||||
#if __CUDA_ARCH__ >= 900
|
||||
if (this->coop.thread_rank() == 0) {
|
||||
uint32_t* inbox = this->mcInbox(/*multimem=*/true);
|
||||
if (nccl::utility::releaseOrderOf(order) != cuda::memory_order_relaxed) {
|
||||
asm volatile("multimem.red.release.sys.add.u32 [%0],1;" :: "l"(inbox));
|
||||
} else {
|
||||
asm volatile("multimem.red.relaxed.sys.add.u32 [%0],1;" :: "l"(inbox));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
#pragma unroll 1
|
||||
for (int i = this->coop.thread_rank(); i < this->team.nRanks-1; i += this->coop.size()) {
|
||||
int peer = i + (this->team.rank <= i ? 1 : 0);
|
||||
cuda::atomic_ref<uint32_t> inbox(*this->ucInbox(peer, this->team.rank));
|
||||
inbox.store(this->epoch+1, nccl::utility::releaseOrderOf(order));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename Coop>
|
||||
NCCL_DEVICE_INLINE void ncclLsaBarrierSession<Coop>::wait(Coop, cuda::memory_order order) {
|
||||
if (this->multimem) {
|
||||
#if __CUDA_ARCH__ >= 900
|
||||
if (this->coop.thread_rank() == 0) {
|
||||
cuda::atomic_ref<uint32_t> inbox(*this->mcInbox(/*multimem=*/false));
|
||||
#pragma unroll 1
|
||||
while (true) {
|
||||
uint32_t got = inbox.load(nccl::utility::acquireOrderOf(order));
|
||||
if (got - (this->epoch + this->team.nRanks) <= uint32_t(-1)>>1) break;
|
||||
}
|
||||
this->epoch += this->team.nRanks;
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
#pragma unroll 1
|
||||
for (int i = this->coop.thread_rank(); i < this->team.nRanks-1; i += this->coop.size()) {
|
||||
int peer = i + (this->team.rank <= i ? 1 : 0);
|
||||
cuda::atomic_ref<uint32_t> inbox(*this->ucInbox(this->team.rank, peer));
|
||||
#pragma unroll 1
|
||||
while (true) {
|
||||
uint32_t got = inbox.load(nccl::utility::acquireOrderOf(order));
|
||||
if (got - (this->epoch + 1) <= uint32_t(-1)>>1) break;
|
||||
}
|
||||
}
|
||||
this->epoch += 1;
|
||||
}
|
||||
this->coop.sync();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename Coop>
|
||||
NCCL_DEVICE_INLINE void ncclLsaBarrierSession<Coop>::sync(Coop coop, cuda::memory_order order) {
|
||||
this->arrive(coop, order);
|
||||
this->wait(coop, order);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _NCCL_DEVICE_MEM_BARRIER__FUNCS_H_
|
||||
@@ -0,0 +1,48 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef _NCCL_DEVICE_MEM_BARRIER__TYPES_H_
|
||||
#define _NCCL_DEVICE_MEM_BARRIER__TYPES_H_
|
||||
#include "../mem_barrier.h"
|
||||
#include "core__types.h"
|
||||
|
||||
#define __CUDACC__ 0
|
||||
|
||||
struct ncclLsaBarrierHandle {
|
||||
ncclDevResourceHandle_t bufHandle;
|
||||
int nBarriers;
|
||||
};
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename Coop>
|
||||
struct ncclLsaBarrierSession_internal {
|
||||
Coop coop;
|
||||
ncclDevComm const& comm;
|
||||
ncclTeam team;
|
||||
ncclLsaBarrierHandle handle;
|
||||
int index;
|
||||
bool multimem;
|
||||
ncclMultimemHandle mmHandle;
|
||||
uint32_t epoch;
|
||||
|
||||
NCCL_DEVICE_INLINE uint32_t* mcInbox(bool multimem) {
|
||||
uint32_t* state;
|
||||
if (multimem) { // multicast
|
||||
state = (uint32_t*)ncclGetResourceBufferMultimemPointer(comm, handle.bufHandle, mmHandle);
|
||||
} else { // unicast
|
||||
state = (uint32_t*)ncclGetResourceBufferLocalPointer(comm, handle.bufHandle);
|
||||
}
|
||||
return state + 2*handle.nBarriers + index;
|
||||
}
|
||||
|
||||
NCCL_DEVICE_INLINE uint32_t* ucInbox(int owner, int peer) {
|
||||
uint32_t* state = (uint32_t*)ncclGetResourceBufferPeerPointer(comm, handle.bufHandle, team, owner);
|
||||
return state + 3*handle.nBarriers + index*team.nRanks + peer;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // _NCCL_DEVICE_MEM_BARRIER__TYPES_H_
|
||||
@@ -0,0 +1,159 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef _NCCL_DEVICE_PTR__FUNCS_H_
|
||||
#define _NCCL_DEVICE_PTR__FUNCS_H_
|
||||
#include "ptr__types.h"
|
||||
#include "core__funcs.h"
|
||||
#include "comm__types.h"
|
||||
|
||||
#define __CUDACC__ 0
|
||||
|
||||
#if __cplusplus
|
||||
|
||||
template<typename T>
|
||||
NCCL_HOST_DEVICE_INLINE constexpr ncclSymPtr<T>::ncclSymPtr(ncclWindow_t window, size_t offset):
|
||||
window(window), offset(offset) {
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
template<typename U>
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>::operator ncclSymPtr<U>() const {
|
||||
return {window, offset};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& ncclSymPtr<T>::operator+=(int d) {
|
||||
offset = reinterpret_cast<size_t>(reinterpret_cast<T*>(offset) + d);
|
||||
return *this;
|
||||
}
|
||||
template<typename T>
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& ncclSymPtr<T>::operator+=(unsigned int d) {
|
||||
offset = reinterpret_cast<size_t>(reinterpret_cast<T*>(offset) + d);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& ncclSymPtr<T>::operator+=(long d) {
|
||||
offset = reinterpret_cast<size_t>(reinterpret_cast<T*>(offset) + d);
|
||||
return *this;
|
||||
}
|
||||
template<typename T>
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& ncclSymPtr<T>::operator+=(unsigned long d) {
|
||||
offset = reinterpret_cast<size_t>(reinterpret_cast<T*>(offset) + d);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& ncclSymPtr<T>::operator+=(long long d) {
|
||||
offset = reinterpret_cast<size_t>(reinterpret_cast<T*>(offset) + d);
|
||||
return *this;
|
||||
}
|
||||
template<typename T>
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& ncclSymPtr<T>::operator+=(unsigned long long d) {
|
||||
offset = reinterpret_cast<size_t>(reinterpret_cast<T*>(offset) + d);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& ncclSymPtr<T>::operator-=(int d) {
|
||||
offset = reinterpret_cast<size_t>(reinterpret_cast<T*>(offset) - d);
|
||||
return *this;
|
||||
}
|
||||
template<typename T>
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& ncclSymPtr<T>::operator-=(unsigned int d) {
|
||||
offset = reinterpret_cast<size_t>(reinterpret_cast<T*>(offset) - d);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& ncclSymPtr<T>::operator-=(long d) {
|
||||
offset = reinterpret_cast<size_t>(reinterpret_cast<T*>(offset) - d);
|
||||
return *this;
|
||||
}
|
||||
template<typename T>
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& ncclSymPtr<T>::operator-=(unsigned long d) {
|
||||
offset = reinterpret_cast<size_t>(reinterpret_cast<T*>(offset) - d);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& ncclSymPtr<T>::operator-=(long long d) {
|
||||
offset = reinterpret_cast<size_t>(reinterpret_cast<T*>(offset) - d);
|
||||
return *this;
|
||||
}
|
||||
template<typename T>
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& ncclSymPtr<T>::operator-=(unsigned long long d) {
|
||||
offset = reinterpret_cast<size_t>(reinterpret_cast<T*>(offset) - d);
|
||||
return *this;
|
||||
}
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename T>
|
||||
NCCL_DEVICE_INLINE T* ncclSymPtr<T>::localPtr() const {
|
||||
return (T*)ncclGetLocalPointer(window, offset);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename T>
|
||||
NCCL_DEVICE_INLINE T* ncclSymPtr<T>::lsaPtr(int peer) const {
|
||||
return (T*)ncclGetLsaPointer(window, offset, peer);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename T>
|
||||
NCCL_DEVICE_INLINE T* ncclSymPtr<T>::peerPtr(int peer) const {
|
||||
return (T*)ncclGetPeerPointer(window, offset, peer);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename T>
|
||||
NCCL_DEVICE_INLINE T* ncclSymPtr<T>::peerPtr(ncclTeam team, int peer) const {
|
||||
return (T*)ncclGetPeerPointer(window, offset, team, peer);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename T>
|
||||
NCCL_DEVICE_INLINE T* ncclSymPtr<T>::multimemPtr(ncclMultimemHandle mmHandle) const {
|
||||
return (T*)ncclGetMultimemPointer(window, offset, mmHandle);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename T>
|
||||
NCCL_DEVICE_INLINE T* ncclSymPtr<T>::lsaMultimemPtr(ncclDevComm const& comm) const {
|
||||
return (T*)ncclGetLsaMultimemPointer(window, offset, comm);
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename T, typename Int>
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T> operator+(ncclSymPtr<T> p, Int d) {
|
||||
return p += d;
|
||||
}
|
||||
template<typename T, typename Int>
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T> operator-(ncclSymPtr<T> p, Int d) {
|
||||
return p -= d;
|
||||
}
|
||||
template<typename T>
|
||||
NCCL_HOST_DEVICE_INLINE ptrdiff_t operator-(ncclSymPtr<T> a, ncclSymPtr<T> b) {
|
||||
return reinterpret_cast<T*>(a.offset) - reinterpret_cast<T*>(b.offset);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
NCCL_HOST_DEVICE_INLINE bool operator==(ncclSymPtr<T> a, ncclSymPtr<T> b) {
|
||||
return a.window == b.window && a.offset == b.offset;
|
||||
}
|
||||
template<typename T>
|
||||
NCCL_HOST_DEVICE_INLINE bool operator!=(ncclSymPtr<T> a, ncclSymPtr<T> b) {
|
||||
return a.window != b.window || a.offset != b.offset;
|
||||
}
|
||||
|
||||
#endif // __cplusplus
|
||||
#endif // _NCCL_DEVICE_PTR__FUNCS_H_
|
||||
@@ -0,0 +1,11 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef _NCCL_DEVICE_PTR__TYPES_H_
|
||||
#define _NCCL_DEVICE_PTR__TYPES_H_
|
||||
#include "../ptr.h"
|
||||
#include "core__types.h"
|
||||
#endif // _NCCL_DEVICE_PTR__TYPES_H_
|
||||
@@ -0,0 +1,55 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef _NCCL_DEVICE_LL_A2A_H_
|
||||
#define _NCCL_DEVICE_LL_A2A_H_
|
||||
#include "impl/core__types.h"
|
||||
|
||||
#define __CUDACC__ 0
|
||||
|
||||
struct ncclLLA2AHandle;
|
||||
|
||||
NCCL_EXTERN_C __host__ int ncclLLA2ACalcSlots(int maxElts, int maxEltSize);
|
||||
|
||||
NCCL_EXTERN_C __host__ ncclResult_t ncclLLA2ACreateRequirement(int nBlocks, int nSlots, ncclLLA2AHandle_t* outHandle, ncclDevResourceRequirements_t* outReq);
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename Coop>
|
||||
struct ncclLLA2ASession_internal;
|
||||
|
||||
template<typename Coop>
|
||||
struct ncclLLA2ASession: ncclLLA2ASession_internal<Coop> {
|
||||
NCCL_DEVICE_INLINE ncclLLA2ASession(Coop, ncclDevComm const&, ncclTeam, ncclLLA2AHandle, uint32_t block, int maxElts, bool multimem=false, ncclMultimemHandle mmHandle={});
|
||||
|
||||
NCCL_DEVICE_INLINE ~ncclLLA2ASession();
|
||||
|
||||
ncclLLA2ASession(ncclLLA2ASession const&) = delete; // Sessions are not copyable
|
||||
|
||||
template<typename T>
|
||||
NCCL_DEVICE_INLINE void send(int peer, int slot, T data);
|
||||
|
||||
template<typename T>
|
||||
NCCL_DEVICE_INLINE void bcast(int slot, T data);
|
||||
|
||||
template<typename T>
|
||||
NCCL_DEVICE_INLINE T recv(int slot);
|
||||
|
||||
template<int MinEltCount, int MaxEltCount, typename T>
|
||||
NCCL_DEVICE_INLINE void recvUnrolled(int eltStart, int eltCount, int eltStride, T(&vals)[MaxEltCount]);
|
||||
|
||||
template<int Unroll, typename Elt, typename EltToAcc, typename Reduce>
|
||||
NCCL_DEVICE_INLINE auto recvReduce(int eltStart, int eltCount, int eltStride, EltToAcc eltToAcc, Reduce red)
|
||||
-> decltype(eltToAcc(nccl::utility::declval<Elt>())) ;
|
||||
|
||||
// End an alltoall region. For every peer in team you must have done both of the
|
||||
// following each of which can be accomplished using any thread in coop:
|
||||
// 1. Targeted that peer with at least one send().
|
||||
// 2. Received from a slot targeted by that peer.
|
||||
NCCL_DEVICE_INLINE void endEpoch(Coop);
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // _NCCL_DEVICE_LL_A2A_H_
|
||||
@@ -0,0 +1,38 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef _NCCL_DEVICE_MEM_BARRIER_H_
|
||||
#define _NCCL_DEVICE_MEM_BARRIER_H_
|
||||
#include "impl/core__types.h"
|
||||
#include "core_tmp.h"
|
||||
|
||||
#define __CUDACC__ 0
|
||||
|
||||
struct ncclLsaBarrierHandle;
|
||||
|
||||
NCCL_EXTERN_C __host__ ncclResult_t ncclLsaBarrierCreateRequirement(ncclTeam_t, int nBarriers, ncclLsaBarrierHandle_t* outHandle, ncclDevResourceRequirements_t* outReq);
|
||||
|
||||
#if __CUDACC__
|
||||
template<typename Coop>
|
||||
struct ncclLsaBarrierSession_internal;
|
||||
|
||||
template<typename Coop>
|
||||
struct ncclLsaBarrierSession: ncclLsaBarrierSession_internal<Coop> {
|
||||
NCCL_DEVICE_INLINE ncclLsaBarrierSession(Coop, ncclDevComm const&, ncclTeam, ncclLsaBarrierHandle, uint32_t index, bool multimem=false, ncclMultimemHandle mmHandle={});
|
||||
|
||||
NCCL_DEVICE_INLINE ncclLsaBarrierSession(Coop, ncclDevComm const&, ncclTeamTagLsa, uint32_t index, bool multimem=false);
|
||||
|
||||
NCCL_DEVICE_INLINE ~ncclLsaBarrierSession();
|
||||
|
||||
ncclLsaBarrierSession(ncclLsaBarrierSession const&) = delete; // Sessions are not copyable
|
||||
|
||||
NCCL_DEVICE_INLINE void arrive(Coop, cuda::memory_order);
|
||||
NCCL_DEVICE_INLINE void wait(Coop, cuda::memory_order);
|
||||
NCCL_DEVICE_INLINE void sync(Coop, cuda::memory_order);
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // _NCCL_DEVICE_MEM_BARRIER_H_
|
||||
@@ -0,0 +1,63 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef _NCCL_DEVICE_PTR_H_
|
||||
#define _NCCL_DEVICE_PTR_H_
|
||||
#include "core.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#define __CUDACC__ 0
|
||||
|
||||
#if __cplusplus
|
||||
template<typename T>
|
||||
struct ncclSymPtr {
|
||||
using ElementType = T;
|
||||
ncclWindow_t window;
|
||||
size_t offset;
|
||||
|
||||
NCCL_HOST_DEVICE_INLINE constexpr ncclSymPtr(ncclWindow_t window=nullptr, size_t offset=0);
|
||||
|
||||
template<typename U>
|
||||
NCCL_HOST_DEVICE_INLINE operator ncclSymPtr<U>() const;
|
||||
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& operator+=(int d);
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& operator+=(unsigned int d);
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& operator+=(long d);
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& operator+=(unsigned long d);
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& operator+=(long long d);
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& operator+=(unsigned long long d);
|
||||
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& operator-=(int d);
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& operator-=(unsigned int d);
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& operator-=(long d);
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& operator-=(unsigned long d);
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& operator-=(long long d);
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T>& operator-=(unsigned long long d);
|
||||
|
||||
#if __CUDACC__
|
||||
NCCL_DEVICE_INLINE T* localPtr() const;
|
||||
NCCL_DEVICE_INLINE T* lsaPtr(int peer) const;
|
||||
NCCL_DEVICE_INLINE T* peerPtr(int peer) const;
|
||||
NCCL_DEVICE_INLINE T* peerPtr(ncclTeam team, int peer) const;
|
||||
NCCL_DEVICE_INLINE T* multimemPtr(ncclMultimemHandle mmHandle) const;
|
||||
NCCL_DEVICE_INLINE T* lsaMultimemPtr(ncclDevComm const&) const;
|
||||
#endif
|
||||
};
|
||||
|
||||
template<typename T, typename Int>
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T> operator+(ncclSymPtr<T> p, Int d);
|
||||
template<typename T, typename Int>
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T> operator-(ncclSymPtr<T> p, Int d);
|
||||
template<typename T>
|
||||
NCCL_HOST_DEVICE_INLINE ptrdiff_t operator-(ncclSymPtr<T> a, ncclSymPtr<T> b);
|
||||
|
||||
template<typename T, typename Int>
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T> operator==(ncclSymPtr<T> a, ncclSymPtr<T> b);
|
||||
template<typename T, typename Int>
|
||||
NCCL_HOST_DEVICE_INLINE ncclSymPtr<T> operator!=(ncclSymPtr<T> a, ncclSymPtr<T> b);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,354 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef _NCCL_DEVICE_UTILITY_H_
|
||||
#define _NCCL_DEVICE_UTILITY_H_
|
||||
|
||||
#define __CUDACC__ 0
|
||||
|
||||
#if __CUDACC__
|
||||
#define NCCL_DEVICE_INLINE __device__ __forceinline__
|
||||
#define NCCL_HOST_DEVICE_INLINE __host__ __device__ __forceinline__
|
||||
#else
|
||||
#ifndef __host__
|
||||
#define __host__
|
||||
#endif
|
||||
#define NCCL_DEVICE_INLINE
|
||||
#define NCCL_HOST_DEVICE_INLINE inline __attribute__((always_inline))
|
||||
#endif
|
||||
|
||||
#if __cplusplus
|
||||
#define NCCL_EXTERN_C extern "C"
|
||||
#else
|
||||
#define NCCL_EXTERN_C
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#if __CUDACC__
|
||||
// #include <cuda/atomic>
|
||||
#endif
|
||||
|
||||
#if __cplusplus
|
||||
namespace nccl {
|
||||
namespace utility {
|
||||
|
||||
template<typename T>
|
||||
T&& declval() noexcept {
|
||||
static_assert(sizeof(T)!=sizeof(T), "You can't evaluate declval.");
|
||||
}
|
||||
|
||||
template<typename X, typename Y, typename Z = decltype(X()+Y())>
|
||||
NCCL_HOST_DEVICE_INLINE constexpr Z divUp(X x, Y y) {
|
||||
return (x+y-1)/y;
|
||||
}
|
||||
|
||||
template<typename X, typename Y, typename Z = decltype(X()+Y())>
|
||||
NCCL_HOST_DEVICE_INLINE constexpr Z roundUp(X x, Y y) {
|
||||
return (x+y-1) - (x+y-1)%y;
|
||||
}
|
||||
template<typename X, typename Y, typename Z = decltype(X()+Y())>
|
||||
NCCL_HOST_DEVICE_INLINE constexpr Z roundDown(X x, Y y) {
|
||||
return x - x%y;
|
||||
}
|
||||
|
||||
// assumes second argument is a power of 2
|
||||
template<typename X, typename Y, typename Z = decltype(X()+Y())>
|
||||
NCCL_HOST_DEVICE_INLINE constexpr Z alignUp(X x, Y a) {
|
||||
return (x + a-1) & -Z(a);
|
||||
}
|
||||
template<typename T>
|
||||
NCCL_HOST_DEVICE_INLINE T* alignUp(T* x, size_t a) {
|
||||
static_assert(sizeof(T) == 1, "Only single byte types allowed.");
|
||||
return reinterpret_cast<T*>((reinterpret_cast<uintptr_t>(x) + a-1) & -uintptr_t(a));
|
||||
}
|
||||
template<typename T>
|
||||
NCCL_HOST_DEVICE_INLINE void* alignUp(void const* x, size_t a) {
|
||||
return reinterpret_cast<void*>((reinterpret_cast<uintptr_t>(x) + a-1) & -uintptr_t(a));
|
||||
}
|
||||
|
||||
// assumes second argument is a power of 2
|
||||
template<typename X, typename Y, typename Z = decltype(X()+int())>
|
||||
NCCL_HOST_DEVICE_INLINE constexpr Z alignDown(X x, Y a) {
|
||||
return x & -Z(a);
|
||||
}
|
||||
template<typename T>
|
||||
NCCL_HOST_DEVICE_INLINE T* alignDown(T* x, size_t a) {
|
||||
static_assert(sizeof(T) == 1, "Only single byte types allowed.");
|
||||
return reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(x) & -uintptr_t(a));
|
||||
}
|
||||
template<typename T>
|
||||
NCCL_HOST_DEVICE_INLINE void* alignDown(void const* x, size_t a) {
|
||||
return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(x) & -uintptr_t(a));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
NCCL_HOST_DEVICE_INLINE T add4G(T base, int delta4G) {
|
||||
union { uint32_t u32[2]; T tmp; };
|
||||
tmp = base;
|
||||
u32[1] += delta4G;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
|
||||
template<typename Int>
|
||||
NCCL_HOST_DEVICE_INLINE constexpr bool isPow2(Int x) {
|
||||
return (x & (x-1)) == 0;
|
||||
}
|
||||
|
||||
// Produce the reciprocal of x for use in idivByRcp
|
||||
NCCL_HOST_DEVICE_INLINE constexpr uint32_t idivRcp32(uint32_t x) {
|
||||
return uint32_t(-1)/x + isPow2(x);
|
||||
}
|
||||
NCCL_HOST_DEVICE_INLINE constexpr uint64_t idivRcp64(uint64_t x) {
|
||||
return uint64_t(-1)/x + isPow2(x);
|
||||
}
|
||||
|
||||
NCCL_HOST_DEVICE_INLINE uint32_t mul32hi(uint32_t a, uint32_t b) {
|
||||
#if __CUDA_ARCH__
|
||||
return __umulhi(a, b);
|
||||
#else
|
||||
return uint64_t(a)*b >> 32;
|
||||
#endif
|
||||
}
|
||||
NCCL_HOST_DEVICE_INLINE uint64_t mul64hi(uint64_t a, uint64_t b) {
|
||||
#if __CUDA_ARCH__
|
||||
return __umul64hi(a, b);
|
||||
#else
|
||||
return (uint64_t)(((unsigned __int128)a)*b >> 64);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Produce the reciprocal of x*y given their respective reciprocals. This incurs
|
||||
// no integer division on device.
|
||||
NCCL_HOST_DEVICE_INLINE uint32_t imulRcp32(uint32_t x, uint32_t xrcp, uint32_t y, uint32_t yrcp) {
|
||||
if (xrcp == 0) return yrcp;
|
||||
if (yrcp == 0) return xrcp;
|
||||
uint32_t rcp = mul32hi(xrcp, yrcp);
|
||||
uint32_t rem = -x*y*rcp;
|
||||
if (x*y <= rem) rcp += 1;
|
||||
return rcp;
|
||||
}
|
||||
NCCL_HOST_DEVICE_INLINE uint64_t imulRcp64(uint64_t x, uint64_t xrcp, uint64_t y, uint64_t yrcp) {
|
||||
if (xrcp == 0) return yrcp;
|
||||
if (yrcp == 0) return xrcp;
|
||||
uint64_t rcp = mul64hi(xrcp, yrcp);
|
||||
uint64_t rem = -x*y*rcp;
|
||||
if (x*y <= rem) rcp += 1;
|
||||
return rcp;
|
||||
}
|
||||
|
||||
// Fast unsigned integer division where divisor has precomputed reciprocal.
|
||||
// idivFast(x, y, idivRcp(y)) == x/y
|
||||
NCCL_HOST_DEVICE_INLINE void idivmodFast32(uint32_t *quo, uint32_t *rem, uint32_t x, uint32_t y, uint32_t yrcp) {
|
||||
uint32_t q = yrcp == 0 ? x : mul32hi(x, yrcp);
|
||||
uint32_t r = x - y*q;
|
||||
if (r >= y) { q += 1; r -= y; }
|
||||
*quo = q;
|
||||
*rem = r;
|
||||
}
|
||||
NCCL_HOST_DEVICE_INLINE void idivmodFast64(uint64_t *quo, uint64_t *rem, uint64_t x, uint64_t y, uint64_t yrcp) {
|
||||
uint32_t q = yrcp == 0 ? x : mul64hi(x, yrcp);
|
||||
uint32_t r = x - y*q;
|
||||
if (r >= y) { q += 1; r -= y; }
|
||||
*quo = q;
|
||||
*rem = r;
|
||||
}
|
||||
|
||||
NCCL_HOST_DEVICE_INLINE uint32_t idivFast32(uint32_t x, uint32_t y, uint32_t yrcp) {
|
||||
uint32_t q, r;
|
||||
idivmodFast32(&q, &r, x, y, yrcp);
|
||||
return q;
|
||||
}
|
||||
NCCL_HOST_DEVICE_INLINE uint32_t idivFast64(uint64_t x, uint64_t y, uint64_t yrcp) {
|
||||
uint64_t q, r;
|
||||
idivmodFast64(&q, &r, x, y, yrcp);
|
||||
return q;
|
||||
}
|
||||
|
||||
NCCL_HOST_DEVICE_INLINE uint32_t imodFast32(uint32_t x, uint32_t y, uint32_t yrcp) {
|
||||
uint32_t q, r;
|
||||
idivmodFast32(&q, &r, x, y, yrcp);
|
||||
return r;
|
||||
}
|
||||
NCCL_HOST_DEVICE_INLINE uint32_t imodFast64(uint64_t x, uint64_t y, uint64_t yrcp) {
|
||||
uint64_t q, r;
|
||||
idivmodFast64(&q, &r, x, y, yrcp);
|
||||
return r;
|
||||
}
|
||||
|
||||
#if __CUDACC__
|
||||
// Precomputed integer reciprocoals for denominator values 1..64 inclusive.
|
||||
// Pass these to idivFast64() for fast division on the GPU.
|
||||
NCCL_DEVICE_INLINE uint64_t idivRcp64_upto64(int x) {
|
||||
static constexpr uint64_t table[65] = {
|
||||
idivRcp64(0x01), idivRcp64(0x01), idivRcp64(0x02), idivRcp64(0x03),
|
||||
idivRcp64(0x04), idivRcp64(0x05), idivRcp64(0x06), idivRcp64(0x07),
|
||||
idivRcp64(0x08), idivRcp64(0x09), idivRcp64(0x0a), idivRcp64(0x0b),
|
||||
idivRcp64(0x0c), idivRcp64(0x0d), idivRcp64(0x0e), idivRcp64(0x0f),
|
||||
idivRcp64(0x10), idivRcp64(0x11), idivRcp64(0x12), idivRcp64(0x13),
|
||||
idivRcp64(0x14), idivRcp64(0x15), idivRcp64(0x16), idivRcp64(0x17),
|
||||
idivRcp64(0x18), idivRcp64(0x19), idivRcp64(0x1a), idivRcp64(0x1b),
|
||||
idivRcp64(0x1c), idivRcp64(0x1d), idivRcp64(0x1e), idivRcp64(0x1f),
|
||||
idivRcp64(0x20), idivRcp64(0x21), idivRcp64(0x22), idivRcp64(0x23),
|
||||
idivRcp64(0x24), idivRcp64(0x25), idivRcp64(0x26), idivRcp64(0x27),
|
||||
idivRcp64(0x28), idivRcp64(0x29), idivRcp64(0x2a), idivRcp64(0x2b),
|
||||
idivRcp64(0x2c), idivRcp64(0x2d), idivRcp64(0x2e), idivRcp64(0x2f),
|
||||
idivRcp64(0x30), idivRcp64(0x31), idivRcp64(0x32), idivRcp64(0x33),
|
||||
idivRcp64(0x34), idivRcp64(0x35), idivRcp64(0x36), idivRcp64(0x37),
|
||||
idivRcp64(0x38), idivRcp64(0x39), idivRcp64(0x3a), idivRcp64(0x3b),
|
||||
idivRcp64(0x3c), idivRcp64(0x3d), idivRcp64(0x3e), idivRcp64(0x3f),
|
||||
idivRcp64(0x40)
|
||||
};
|
||||
return table[x];
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
NCCL_DEVICE_INLINE uint32_t idivRcp32_upto64(int x) {
|
||||
return idivRcp64_upto64(x)>>32;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
NCCL_DEVICE_INLINE void fenceAcquireGpu() {
|
||||
static __device__ int dummy;
|
||||
int tmp;
|
||||
asm volatile("ld.acquire.gpu.s32 %0,[%1];" : "=r"(tmp) : "l"(&dummy) : "memory");
|
||||
dummy = tmp;
|
||||
}
|
||||
NCCL_DEVICE_INLINE void fenceReleaseGpu() {
|
||||
cuda::atomic_thread_fence(cuda::memory_order_release, cuda::thread_scope_device);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
NCCL_DEVICE_INLINE cuda::memory_order acquireOrderOf(cuda::memory_order ord) {
|
||||
return ord == cuda::memory_order_release ? cuda::memory_order_relaxed :
|
||||
ord == cuda::memory_order_acq_rel ? cuda::memory_order_acquire :
|
||||
ord;
|
||||
}
|
||||
NCCL_DEVICE_INLINE cuda::memory_order releaseOrderOf(cuda::memory_order ord) {
|
||||
return ord == cuda::memory_order_acquire ? cuda::memory_order_relaxed :
|
||||
ord == cuda::memory_order_acq_rel ? cuda::memory_order_release :
|
||||
ord;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
NCCL_DEVICE_INLINE int lane() {
|
||||
int ret;
|
||||
asm("mov.u32 %0, %%laneid;" : "=r"(ret));
|
||||
return ret;
|
||||
}
|
||||
NCCL_DEVICE_INLINE unsigned int lanemask_lt() {
|
||||
unsigned int ret;
|
||||
asm("mov.u32 %0, %%lanemask_lt;" : "=r"(ret));
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __CUDACC__
|
||||
// Load anything, but cache like its constant memory.
|
||||
template<typename T>
|
||||
NCCL_DEVICE_INLINE T loadConst(T const *p) {
|
||||
if (alignof(T) == 1) {
|
||||
union { uint8_t part[sizeof(T)]; T ret; };
|
||||
for (int i=0; i < (int)sizeof(T); i++) part[i] = __ldg((uint8_t const*)p + i);
|
||||
return ret;
|
||||
} else if (alignof(T) == 2) {
|
||||
union { uint16_t part[sizeof(T)/2]; T ret; };
|
||||
for (int i=0; i < (int)sizeof(T)/2; i++) part[i] = __ldg((uint16_t const*)p + i);
|
||||
return ret;
|
||||
} else if (alignof(T) == 4) {
|
||||
union { uint32_t part[sizeof(T)/4]; T ret; };
|
||||
for (int i=0; i < (int)sizeof(T)/4; i++) part[i] = __ldg((uint32_t const*)p + i);
|
||||
return ret;
|
||||
} else if (alignof(T) == 8) {
|
||||
union { uint64_t part[sizeof(T)/8]; T ret; };
|
||||
for (int i=0; i < (int)sizeof(T)/8; i++) part[i] = __ldg((uint64_t const*)p + i);
|
||||
return ret;
|
||||
} else { // alignof(T) >= 16
|
||||
union { ulonglong2 part[sizeof(T)/16]; T ret; };
|
||||
for (int i=0; i < (int)sizeof(T)/16; i++) part[i] = __ldg((ulonglong2 const*)p + i);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Optional<T>: Holds a T that may or may not be constructed. An Optional
|
||||
// constructed with a Present<Arg...> will have its T constructed via the
|
||||
// T::T(Arg...) constructor. An Optional constructed with a Absent will not
|
||||
// have its T constructed.
|
||||
|
||||
template<int ...vals>
|
||||
struct IntSeq {};
|
||||
|
||||
template<int n, int m, int ...i>
|
||||
struct IntSeqUpTo: IntSeqUpTo<n, m+1, i..., m> {};
|
||||
template<int n, int ...i>
|
||||
struct IntSeqUpTo<n, n, i...> { using Type = IntSeq<i...>; };
|
||||
|
||||
// Present<Arg...>: Packs a list of arguments together to be passed to Optional<T>.
|
||||
template<typename ...Arg>
|
||||
struct Present;
|
||||
template<>
|
||||
struct Present<> {};
|
||||
template<typename H, typename ...T>
|
||||
struct Present<H, T...> {
|
||||
H h;
|
||||
Present<T...> t;
|
||||
|
||||
NCCL_HOST_DEVICE_INLINE H get(IntSeq<0>) {
|
||||
return static_cast<H>(h);
|
||||
}
|
||||
template<int i>
|
||||
NCCL_HOST_DEVICE_INLINE decltype(auto) get(IntSeq<i>) {
|
||||
return t.get(IntSeq<i-1>{});
|
||||
}
|
||||
};
|
||||
|
||||
NCCL_HOST_DEVICE_INLINE Present<> present() {
|
||||
return Present<>{};
|
||||
}
|
||||
template<typename H, typename ...T>
|
||||
NCCL_HOST_DEVICE_INLINE Present<H&&, T&&...> present(H&& h, T&& ...t) {
|
||||
return Present<H&&, T&&...>{static_cast<H&&>(h), present(static_cast<T&&>(t)...)};
|
||||
}
|
||||
|
||||
struct Absent {};
|
||||
|
||||
template<typename T>
|
||||
struct Optional {
|
||||
bool present; // Is `thing` constructed.
|
||||
union { T thing; };
|
||||
|
||||
// Construct with absent thing:
|
||||
NCCL_HOST_DEVICE_INLINE constexpr Optional(): present(false) {}
|
||||
NCCL_HOST_DEVICE_INLINE constexpr Optional(Absent): present(false) {}
|
||||
|
||||
// Helper constructor
|
||||
template<int ...i, typename ...Arg>
|
||||
NCCL_HOST_DEVICE_INLINE Optional(Present<Arg...> args, IntSeq<i...>):
|
||||
present(true),
|
||||
thing{args.get(IntSeq<i>())...} {
|
||||
}
|
||||
// Construct with present thing:
|
||||
template<typename ...Arg>
|
||||
NCCL_HOST_DEVICE_INLINE Optional(Present<Arg...> args):
|
||||
Optional(args, IntSeqUpTo<sizeof...(Arg), 0>::Type()) {
|
||||
}
|
||||
|
||||
NCCL_HOST_DEVICE_INLINE ~Optional() {
|
||||
if (present) thing.~T();
|
||||
}
|
||||
};
|
||||
|
||||
}}
|
||||
#endif // __cplusplus
|
||||
#endif
|
||||
@@ -12,10 +12,16 @@
|
||||
#include "comm.h"
|
||||
#include "checks.h"
|
||||
|
||||
#define NCCL_UNDEF_DEV_COUNT -1
|
||||
|
||||
typedef char ncclNetHandle_t[NCCL_NET_HANDLE_MAXSIZE];
|
||||
|
||||
ncclResult_t ncclNetInit(struct ncclComm* comm);
|
||||
ncclResult_t ncclNetFinalize(struct ncclComm* comm);
|
||||
ncclResult_t ncclNetGetDevCount(int netPluginIndex, int* nPhysDev, int* nVirtDev);
|
||||
ncclResult_t ncclNetSetVirtDevCount(int netPluginIndex, int nVirtDev);
|
||||
ncclResult_t ncclCollNetGetDevCount(int netPluginIndex, int* nPhysDev, int* nVirtDev);
|
||||
ncclResult_t ncclCollNetSetVirtDevCount(int netPluginIndex, int nVirtDev);
|
||||
|
||||
// Test whether the current GPU support GPU Direct RDMA.
|
||||
ncclResult_t ncclGpuGdrSupport(struct ncclComm* comm, int* gdrSupport);
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
// Arbitrary version number - A given NCCL build will only be compatible with a single device networking plugin
|
||||
// version. NCCL will check the supplied version number from net->getProperties() and compare to its internal version.
|
||||
#define NCCL_NET_DEVICE_UNPACK_VERSION 0x7
|
||||
#define NCCL_NET_DEVICE_UNPACK_VERSION 0x7
|
||||
|
||||
typedef enum {NCCL_NET_DEVICE_HOST=0, NCCL_NET_DEVICE_UNPACK=1} ncclNetDeviceType;
|
||||
|
||||
@@ -27,6 +27,7 @@ typedef struct {
|
||||
typedef ncclNetDeviceHandle_v7_t ncclNetDeviceHandle_v8_t;
|
||||
typedef ncclNetDeviceHandle_v8_t ncclNetDeviceHandle_v9_t;
|
||||
typedef ncclNetDeviceHandle_v9_t ncclNetDeviceHandle_v10_t;
|
||||
typedef ncclNetDeviceHandle_v10_t ncclNetDeviceHandle_t;
|
||||
typedef ncclNetDeviceHandle_v10_t ncclNetDeviceHandle_v11_t;
|
||||
typedef ncclNetDeviceHandle_v11_t ncclNetDeviceHandle_t;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -253,6 +253,24 @@ typedef nvmlGpuFabricInfo_v2_t nvmlGpuFabricInfoV_t;
|
||||
*/
|
||||
#define nvmlGpuFabricInfo_v2 NVML_STRUCT_VERSION(GpuFabricInfo, 2)
|
||||
|
||||
/**
|
||||
* Structure to store platform information (v2)
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
unsigned int version; //!< the API version number
|
||||
unsigned char ibGuid[16]; //!< Infiniband GUID reported by platform (for Blackwell, ibGuid is 8 bytes so indices 8-15 are zero)
|
||||
unsigned char chassisSerialNumber[16]; //!< Serial number of the chassis containing this GPU (for Blackwell it is 13 bytes so indices 13-15 are zero)
|
||||
unsigned char slotNumber; //!< The slot number in the chassis containing this GPU (includes switches)
|
||||
unsigned char trayIndex; //!< The tray index within the compute slots in the chassis containing this GPU (does not include switches)
|
||||
unsigned char hostId; //!< Index of the node within the slot containing this GPU
|
||||
unsigned char peerType; //!< Platform indicated NVLink-peer type (e.g. switch present or not)
|
||||
unsigned char moduleId; //!< ID of this GPU within the node
|
||||
} nvmlPlatformInfo_v2_t;
|
||||
|
||||
typedef nvmlPlatformInfo_v2_t nvmlPlatformInfo_t;
|
||||
#define nvmlPlatformInfo_v2 NVML_STRUCT_VERSION(PlatformInfo, 2)
|
||||
|
||||
/**
|
||||
* Confidential Compute Feature Status values
|
||||
*/
|
||||
@@ -270,6 +288,7 @@ typedef struct nvmlConfComputeSystemState_st {
|
||||
*/
|
||||
#define NVML_CC_SYSTEM_MULTIGPU_NONE 0
|
||||
#define NVML_CC_SYSTEM_MULTIGPU_PROTECTED_PCIE 1
|
||||
#define NVML_CC_SYSTEM_MULTIGPU_NVLE 2
|
||||
|
||||
/**
|
||||
* Confidential Compute System settings
|
||||
@@ -303,6 +322,7 @@ extern ncclNvmlDevicePairInfo ncclNvmlDevicePairs[ncclNvmlMaxDevices][ncclNvmlMa
|
||||
struct ncclNvmlCCStatus {
|
||||
bool CCEnabled;
|
||||
bool multiGpuProtectedPCIE;
|
||||
bool multiGpuNVLE;
|
||||
};
|
||||
|
||||
// All ncclNvmlFoo() functions call ncclNvmlEnsureInitialized() implicitly.
|
||||
@@ -320,6 +340,7 @@ ncclResult_t ncclNvmlDeviceGetCudaComputeCapability(nvmlDevice_t device, int* ma
|
||||
ncclResult_t ncclNvmlDeviceGetP2PStatus(nvmlDevice_t device1, nvmlDevice_t device2, nvmlGpuP2PCapsIndex_t p2pIndex, nvmlGpuP2PStatus_t* p2pStatus);
|
||||
ncclResult_t ncclNvmlDeviceGetFieldValues(nvmlDevice_t device, int valuesCount, nvmlFieldValue_t *values);
|
||||
ncclResult_t ncclNvmlDeviceGetGpuFabricInfoV(nvmlDevice_t device, nvmlGpuFabricInfoV_t *gpuFabricInfo);
|
||||
ncclResult_t ncclNvmlDeviceGetPlatformInfo(nvmlDevice_t device, nvmlPlatformInfo_t *plaformInfo);
|
||||
ncclResult_t ncclNvmlGetCCStatus(struct ncclNvmlCCStatus *status);
|
||||
|
||||
#endif // End include guard
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include "nvtx3/nvtx3.hpp"
|
||||
#include "roctx.h"
|
||||
|
||||
#include "param.h"
|
||||
|
||||
#if __cpp_constexpr >= 201304L && !defined(NVTX3_CONSTEXPR_IF_CPP14)
|
||||
#define NVTX3_CONSTEXPR_IF_CPP14 constexpr
|
||||
#else
|
||||
@@ -23,8 +25,8 @@
|
||||
#define NVTX_SID_CommAbort 3 // same schema as NVTX_SID_CommInitRank
|
||||
#define NVTX_SID_AllGather 4
|
||||
#define NVTX_SID_AllReduce 5
|
||||
#define NVTX_SID_AllToAll 6
|
||||
#define NVTX_SID_AllToAllv 7
|
||||
#define NVTX_SID_AlltoAll 6
|
||||
#define NVTX_SID_AlltoAllv 7
|
||||
#define NVTX_SID_Broadcast 8
|
||||
#define NVTX_SID_Gather 9
|
||||
#define NVTX_SID_MSCCL 10
|
||||
@@ -47,6 +49,8 @@ extern const nvtxDomainHandle_t ncclNvtxDomainHandle;
|
||||
|
||||
struct nccl_domain{static constexpr char const* name{"NCCL"};};
|
||||
|
||||
extern int64_t ncclParamNvtxDisable();
|
||||
|
||||
/// @brief Register an NVTX payload schema for static-size payloads.
|
||||
class payload_schema {
|
||||
public:
|
||||
@@ -80,6 +84,32 @@ private:
|
||||
nullptr, 0, 0, 0, 0, nullptr};
|
||||
};
|
||||
|
||||
class ncclOptionalNvtxScopedRange
|
||||
{
|
||||
public:
|
||||
void push(const nvtx3::event_attributes& attr) noexcept {
|
||||
// pushed must not be true already, but it's too expensive to check
|
||||
pushed = true;
|
||||
nvtxDomainRangePushEx(nvtx3::domain::get<nccl_domain>(), attr.get());
|
||||
}
|
||||
|
||||
~ncclOptionalNvtxScopedRange() noexcept {
|
||||
if (!pushed) {
|
||||
return;
|
||||
}
|
||||
nvtxDomainRangePop(nvtx3::domain::get<nccl_domain>());
|
||||
}
|
||||
|
||||
ncclOptionalNvtxScopedRange() = default;
|
||||
ncclOptionalNvtxScopedRange(ncclOptionalNvtxScopedRange const&) = delete;
|
||||
ncclOptionalNvtxScopedRange& operator=(ncclOptionalNvtxScopedRange const&) = delete;
|
||||
ncclOptionalNvtxScopedRange(ncclOptionalNvtxScopedRange&&) = delete;
|
||||
ncclOptionalNvtxScopedRange& operator=(ncclOptionalNvtxScopedRange&&) = delete;
|
||||
|
||||
private:
|
||||
bool pushed = false;
|
||||
};
|
||||
|
||||
// Convenience macro to give the payload parameters a scope.
|
||||
#define NVTX3_PAYLOAD(...) __VA_ARGS__
|
||||
|
||||
@@ -95,28 +125,48 @@ private:
|
||||
const T _payload = {P}; \
|
||||
nvtxPayloadData_t nvtx3_bpl__[] = {{schemaId, sizeof(_payload), &_payload}}; \
|
||||
roctx_scoped_range_in const roctx_range__{T##Schema, nvtx3_bpl__, std::extent<decltype(T##Schema)>::value - 1, "RCCL_" #N};
|
||||
|
||||
#define NCCL_NVTX3_FUNC_RANGE \
|
||||
roctx_scoped_range_in const roctx_range__(("RCCL_"))
|
||||
#else
|
||||
#define NVTX3_FUNC_WITH_PARAMS(N, T, P) \
|
||||
constexpr uint64_t schemaId = NVTX_PAYLOAD_ENTRY_TYPE_SCHEMA_ID_STATIC_START + NVTX_SID_##N; \
|
||||
static const payload_schema schema{T##Schema, std::extent<decltype(T##Schema)>::value - 1, \
|
||||
schemaId, sizeof(T)}; \
|
||||
static ::nvtx3::v1::registered_string_in<nccl_domain> const nvtx3_func_name__{__func__}; \
|
||||
const T _payload = {P}; \
|
||||
nvtxPayloadData_t nvtx3_bpl__[] = {{schemaId, sizeof(_payload), &_payload}}; \
|
||||
::nvtx3::v1::event_attributes const nvtx3_func_attr__{nvtx3_func_name__, nvtx3_bpl__}; \
|
||||
::nvtx3::v1::scoped_range_in<nccl_domain> const nvtx3_range__{nvtx3_func_attr__};
|
||||
#define NVTX3_FUNC_WITH_PARAMS(N, T, P) \
|
||||
ncclOptionalNvtxScopedRange nvtx3_range__; \
|
||||
if (!ncclParamNvtxDisable()) \
|
||||
{ \
|
||||
constexpr uint64_t schemaId = NVTX_PAYLOAD_ENTRY_TYPE_SCHEMA_ID_STATIC_START + NVTX_SID_##N; \
|
||||
static const payload_schema \
|
||||
schema{T##Schema, std::extent<decltype(T##Schema)>::value - 1, schemaId, sizeof(T)}; \
|
||||
static ::nvtx3::v1::registered_string_in<nccl_domain> const nvtx3_func_name__{__func__}; \
|
||||
const T _payload = {P}; \
|
||||
nvtxPayloadData_t nvtx3_bpl__[] = {{schemaId, sizeof(_payload), &_payload}}; \
|
||||
::nvtx3::v1::event_attributes const nvtx3_func_attr__{nvtx3_func_name__, nvtx3_bpl__}; \
|
||||
nvtx3_range__.push(nvtx3_func_attr__); \
|
||||
}
|
||||
|
||||
#define NCCL_NVTX3_FUNC_RANGE \
|
||||
ncclOptionalNvtxScopedRange nvtx3_range__; \
|
||||
if (!ncclParamNvtxDisable()) { \
|
||||
static ::nvtx3::v1::registered_string_in<nccl_domain> const nvtx3_func_name__{__func__}; \
|
||||
static ::nvtx3::v1::event_attributes const nvtx3_func_attr__{nvtx3_func_name__}; \
|
||||
nvtx3_range__.push(nvtx3_func_attr__); \
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @brief Creates an NVTX range with extended payload using the RAII pattern.
|
||||
/// @tparam PayloadType Data type of the payload.
|
||||
template <typename PayloadType>
|
||||
class ncclNvtxRange {
|
||||
class ncclOptionalNvtxPayloadRange {
|
||||
public:
|
||||
explicit ncclNvtxRange(const nvtxEventAttributes_t* evtAttr) noexcept {
|
||||
nvtxDomainRangePushEx(nvtx3::domain::get<nccl_domain>(), evtAttr);
|
||||
void push(const nvtx3::event_attributes& attr) noexcept {
|
||||
// pushed must not be true already, but it's too expensive to check
|
||||
pushed = true;
|
||||
nvtxDomainRangePushEx(nvtx3::domain::get<nccl_domain>(), attr.get());
|
||||
}
|
||||
|
||||
~ncclNvtxRange() noexcept {
|
||||
~ncclOptionalNvtxPayloadRange() noexcept {
|
||||
if (!pushed) {
|
||||
return;
|
||||
}
|
||||
if (payloadData.payload) {
|
||||
nvtxRangePopPayload(nvtx3::domain::get<nccl_domain>(), &payloadData, 1);
|
||||
} else {
|
||||
@@ -129,24 +179,34 @@ class ncclNvtxRange {
|
||||
payloadData = {schemaId, sizeof(PayloadType), &payload};
|
||||
}
|
||||
|
||||
ncclNvtxRange() = delete;
|
||||
ncclNvtxRange(ncclNvtxRange const&) = default;
|
||||
ncclNvtxRange& operator=(ncclNvtxRange const&) = default;
|
||||
ncclNvtxRange(ncclNvtxRange&&) = default;
|
||||
ncclNvtxRange& operator=(ncclNvtxRange&&) = default;
|
||||
ncclOptionalNvtxPayloadRange() = default;
|
||||
ncclOptionalNvtxPayloadRange(ncclOptionalNvtxPayloadRange const&) = delete;
|
||||
ncclOptionalNvtxPayloadRange& operator=(ncclOptionalNvtxPayloadRange const&) = delete;
|
||||
ncclOptionalNvtxPayloadRange(ncclOptionalNvtxPayloadRange&&) = delete;
|
||||
ncclOptionalNvtxPayloadRange& operator=(ncclOptionalNvtxPayloadRange&&) = delete;
|
||||
|
||||
// Holds the payload data.
|
||||
PayloadType payload{};
|
||||
|
||||
nvtxPayloadData_t payloadData = {NVTX_PAYLOAD_ENTRY_TYPE_INVALID, 0, NULL};
|
||||
|
||||
bool isPushed() const noexcept {
|
||||
return pushed;
|
||||
}
|
||||
|
||||
private:
|
||||
bool pushed = false;
|
||||
};
|
||||
|
||||
// Create an NVTX range with the function name as the range name. Use RAII pattern.
|
||||
// @param T Type ID of the NVTX payload (pointer for variable-size payloads).
|
||||
#define NVTX3_RANGE(T) \
|
||||
static ::nvtx3::v1::registered_string_in<nccl_domain> const nvtx3_func_name__{__func__}; \
|
||||
::nvtx3::v1::event_attributes const nvtx3_func_attr__{nvtx3_func_name__}; \
|
||||
ncclNvtxRange<T> nvtx3_range__{nvtx3_func_attr__.get()};
|
||||
#define NVTX3_RANGE(T) \
|
||||
ncclOptionalNvtxPayloadRange<T> nvtx3_range__; \
|
||||
if (!ncclParamNvtxDisable()) \
|
||||
{ \
|
||||
static ::nvtx3::v1::registered_string_in<nccl_domain> const nvtx3_func_name__{__func__}; \
|
||||
::nvtx3::v1::event_attributes const nvtx3_func_attr__{nvtx3_func_name__}; \
|
||||
nvtx3_range__.push(nvtx3_func_attr__); \
|
||||
}
|
||||
|
||||
// Add static-size payload to the NVTX range created with `NVTX3_RANGE()`,
|
||||
// which must be in this or an outer scope.
|
||||
@@ -165,6 +225,9 @@ class ncclNvtxRange {
|
||||
} while (0)
|
||||
#else
|
||||
#define NVTX3_RANGE_ADD_PAYLOAD(N, S, P) do { \
|
||||
if (!nvtx3_range__.isPushed()) { \
|
||||
break; \
|
||||
} \
|
||||
constexpr uint64_t schema_id = NVTX_PAYLOAD_ENTRY_TYPE_SCHEMA_ID_STATIC_START + NVTX_SID_##N; \
|
||||
static const payload_schema schema{S, std::extent<decltype(S)>::value - 1, schema_id, \
|
||||
sizeof(nvtx3_range__.payload)}; \
|
||||
|
||||
@@ -332,4 +332,4 @@ NVTX_DECLSPEC void NVTX_API nvtxCountersSubmitBatchEx(
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* NVTOOLSEXT_COUNTERS_H */
|
||||
#endif /* NVTOOLSEXT_COUNTERS_H */
|
||||
|
||||
@@ -85,4 +85,4 @@ typedef struct nvtxSemanticsCounter_v1 {
|
||||
} limits;
|
||||
} nvtxSemanticsCounter_t;
|
||||
|
||||
#endif /* NVTX_SEMANTIC_ID_COUNTERS_V1 */
|
||||
#endif /* NVTX_SEMANTIC_ID_COUNTERS_V1 */
|
||||
|
||||
@@ -27,4 +27,4 @@ typedef struct nvtxSemanticsScope_v1
|
||||
uint64_t scopeId;
|
||||
} nvtxSemanticsScope_t;
|
||||
|
||||
#endif /* NVTX_SEMANTIC_ID_SCOPE_V1 */
|
||||
#endif /* NVTX_SEMANTIC_ID_SCOPE_V1 */
|
||||
|
||||
@@ -28,4 +28,4 @@
|
||||
#define NVTX_EXT_HELPER_UNUSED_ARGS(...) \
|
||||
NVTX_EXT_CONCAT(_NVTX_EXT_VOIDIFY, NVTX_EXT_NUM_ARGS(__VA_ARGS__))(__VA_ARGS__)
|
||||
|
||||
#endif /* NVTX_EXT_HELPER_MACROS_H */
|
||||
#endif /* NVTX_EXT_HELPER_MACROS_H */
|
||||
|
||||
@@ -96,4 +96,4 @@ NVTX_LINKONCE_DEFINE_GLOBAL nvtxExtGlobals1_t NVTX_VERSIONED_IDENTIFIER(nvtxExtG
|
||||
} /* extern "C" */
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* NVTX_EXT_IMPL_H */
|
||||
#endif /* NVTX_EXT_IMPL_H */
|
||||
|
||||
@@ -145,4 +145,4 @@ NVTX_EXT_COUNTERS_IMPL_FN_V1(void, nvtxCountersSubmitBatchEx,
|
||||
} /* extern "C" */
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* NVTX_EXT_IMPL_COUNTERS_V1 */
|
||||
#endif /* NVTX_EXT_IMPL_COUNTERS_V1 */
|
||||
|
||||
@@ -269,4 +269,4 @@
|
||||
|
||||
/*** END: Helper for `NVTX_PAYLOAD_STATIC_SCHEMA_{INIT,CREATE}` */
|
||||
|
||||
#endif /* NVTX_EXT_PAYLOAD_HELPER_INTERNAL_H */
|
||||
#endif /* NVTX_EXT_PAYLOAD_HELPER_INTERNAL_H */
|
||||
|
||||
@@ -148,4 +148,4 @@ NVTX_EXT_PAYLOAD_VERSIONED_ID(nvtxExtPayloadTypeInfo)[NVTX_PAYLOAD_ENTRY_TYPE_IN
|
||||
};
|
||||
|
||||
#undef nvtx_alignof
|
||||
#undef nvtx_alignof2
|
||||
#undef nvtx_alignof2
|
||||
|
||||
@@ -41,4 +41,4 @@ typedef struct nvtxExtModuleInfo_t
|
||||
|
||||
typedef int (NVTX_API * NvtxExtInitializeInjectionFunc_t)(nvtxExtModuleInfo_t* moduleInfo);
|
||||
|
||||
#endif /* NVTXEXTTYPES_H */
|
||||
#endif /* NVTXEXTTYPES_H */
|
||||
|
||||
@@ -94,6 +94,23 @@ NCCL_NVTX_DEFINE_STRUCT_WITH_SCHEMA_ENTRIES(NcclNvtxParamsAllGather, static cons
|
||||
)
|
||||
)
|
||||
|
||||
NCCL_NVTX_DEFINE_STRUCT_WITH_SCHEMA_ENTRIES(NcclNvtxParamsAlltoAll, static constexpr,
|
||||
NCCL_NVTX_PAYLOAD_ENTRIES(
|
||||
(uint64_t, comm, TYPE_UINT64, nccl_nvtxCommStr),
|
||||
(size_t, bytes, TYPE_SIZE, nccl_nvtxMsgSizeStr),
|
||||
(ncclDataType_t, datatype, TYPE_DATATYPE, nccl_nvtxDataTypeStr)
|
||||
)
|
||||
)
|
||||
|
||||
NCCL_NVTX_DEFINE_STRUCT_WITH_SCHEMA_ENTRIES(NcclNvtxParamsAlltoAllv, static constexpr,
|
||||
NCCL_NVTX_PAYLOAD_ENTRIES(
|
||||
(uint64_t, comm, TYPE_UINT64, nccl_nvtxCommStr),
|
||||
(size_t, sendBytes, TYPE_SIZE, nccl_nvtxMsgSizeSendStr),
|
||||
(size_t, recvBytes, TYPE_SIZE, nccl_nvtxMsgSizeRecvStr),
|
||||
(ncclDataType_t, datatype, TYPE_DATATYPE, nccl_nvtxDataTypeStr)
|
||||
)
|
||||
)
|
||||
|
||||
NCCL_NVTX_DEFINE_STRUCT_WITH_SCHEMA_ENTRIES(NcclNvtxParamsAllReduce, static constexpr,
|
||||
NCCL_NVTX_PAYLOAD_ENTRIES(
|
||||
(uint64_t, comm, TYPE_UINT64, nccl_nvtxCommStr),
|
||||
@@ -103,23 +120,6 @@ NCCL_NVTX_DEFINE_STRUCT_WITH_SCHEMA_ENTRIES(NcclNvtxParamsAllReduce, static cons
|
||||
)
|
||||
)
|
||||
|
||||
NCCL_NVTX_DEFINE_STRUCT_WITH_SCHEMA_ENTRIES(NcclNvtxParamsAllToAll, static constexpr,
|
||||
NCCL_NVTX_PAYLOAD_ENTRIES(
|
||||
(uint64_t, comm, TYPE_UINT64, nccl_nvtxCommStr),
|
||||
(size_t, bytes, TYPE_SIZE, nccl_nvtxMsgSizeStr),
|
||||
(ncclDataType_t, datatype, TYPE_DATATYPE, nccl_nvtxDataTypeStr)
|
||||
)
|
||||
)
|
||||
|
||||
NCCL_NVTX_DEFINE_STRUCT_WITH_SCHEMA_ENTRIES(NcclNvtxParamsAllToAllv, static constexpr,
|
||||
NCCL_NVTX_PAYLOAD_ENTRIES(
|
||||
(uint64_t, comm, TYPE_UINT64, nccl_nvtxCommStr),
|
||||
(size_t, sendBytes, TYPE_SIZE, nccl_nvtxMsgSizeSendStr),
|
||||
(size_t, recvBytes, TYPE_SIZE, nccl_nvtxMsgSizeRecvStr),
|
||||
(ncclDataType_t, datatype, TYPE_DATATYPE, nccl_nvtxDataTypeStr)
|
||||
)
|
||||
)
|
||||
|
||||
NCCL_NVTX_DEFINE_STRUCT_WITH_SCHEMA_ENTRIES(NcclNvtxParamsBroadcast, static constexpr,
|
||||
NCCL_NVTX_PAYLOAD_ENTRIES(
|
||||
(uint64_t, comm, TYPE_UINT64, nccl_nvtxCommStr),
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
//Maximum value NCCL can accept for maxP2pBytes and maxCollBytes net properties
|
||||
#define NCCL_MAX_NET_SIZE_BYTES (1*1024*1024*1024*1024L)
|
||||
#define NCCL_NET_OPTIONAL_RECV_COMPLETION 0x1
|
||||
#define NCCL_NET_MULTI_REQUEST 0x2
|
||||
|
||||
#define MAX_NET_SIZE (1024*1024*1024L) // Rather than send INT_MAX which is 2G-1, send a power of two.
|
||||
#define MAX_COLLNET_SIZE (512*1024*1024L) //Set for initial collent plugins when size was not dynamically queried
|
||||
@@ -33,23 +34,25 @@
|
||||
#define NCCL_NET_MAX_PLUGINS 16
|
||||
#endif
|
||||
|
||||
#define NCCL_NET_MAX_DEVS_PER_NIC 4
|
||||
|
||||
#include "net/net_v11.h"
|
||||
#include "net/net_v10.h"
|
||||
#include "net/net_v9.h"
|
||||
#include "net/net_v8.h"
|
||||
#include "net/net_v7.h"
|
||||
#include "net/net_v6.h"
|
||||
|
||||
typedef ncclNet_v10_t ncclNet_t;
|
||||
typedef ncclCollNet_v10_t ncclCollNet_t;
|
||||
typedef ncclNetSGE_v10_t ncclNetSGE_t;
|
||||
typedef ncclNetProperties_v10_t ncclNetProperties_t;
|
||||
typedef ncclNetVDeviceProps_v10_t ncclNetVDeviceProps_t;
|
||||
typedef ncclNetCommConfig_v10_t ncclNetCommConfig_t;
|
||||
typedef ncclNet_v11_t ncclNet_t;
|
||||
typedef ncclCollNet_v11_t ncclCollNet_t;
|
||||
typedef ncclNetSGE_v11_t ncclNetSGE_t;
|
||||
typedef ncclNetProperties_v11_t ncclNetProperties_t;
|
||||
typedef ncclNetAttr_v11_t ncclNetAttr_t;
|
||||
typedef ncclNetVDeviceProps_v11_t ncclNetVDeviceProps_t;
|
||||
typedef ncclNetCommConfig_v11_t ncclNetCommConfig_t;
|
||||
|
||||
#define NCCL_NET_MAX_DEVS_PER_NIC NCCL_NET_MAX_DEVS_PER_NIC_V10
|
||||
|
||||
#define NCCL_NET_PLUGIN_SYMBOL ncclNetPlugin_v10
|
||||
#define NCCL_COLLNET_PLUGIN_SYMBOL ncclCollNetPlugin_v10
|
||||
#define NCCL_NET_PLUGIN_SYMBOL ncclNetPlugin_v11
|
||||
#define NCCL_COLLNET_PLUGIN_SYMBOL ncclCollNetPlugin_v11
|
||||
|
||||
// context passed from RCCL lib to n/w plugin
|
||||
typedef struct {
|
||||
|
||||
@@ -8,14 +8,18 @@
|
||||
#define NCCL_PROFILER_H_
|
||||
|
||||
enum {
|
||||
ncclProfileGroup = (1 << 0), // group event type
|
||||
ncclProfileColl = (1 << 1), // host collective call event type
|
||||
ncclProfileP2p = (1 << 2), // host point-to-point call event type
|
||||
ncclProfileProxyOp = (1 << 3), // proxy operation event type
|
||||
ncclProfileProxyStep = (1 << 4), // proxy step event type
|
||||
ncclProfileProxyCtrl = (1 << 5), // proxy control event type
|
||||
ncclProfileKernelCh = (1 << 6), // kernel channel event type
|
||||
ncclProfileNetPlugin = (1 << 7), // network plugin-defined, events
|
||||
ncclProfileGroup = (1 << 0), // group event type
|
||||
ncclProfileColl = (1 << 1), // host collective call event type
|
||||
ncclProfileP2p = (1 << 2), // host point-to-point call event type
|
||||
ncclProfileProxyOp = (1 << 3), // proxy operation event type
|
||||
ncclProfileProxyStep = (1 << 4), // proxy step event type
|
||||
ncclProfileProxyCtrl = (1 << 5), // proxy control event type
|
||||
ncclProfileKernelCh = (1 << 6), // kernel channel event type
|
||||
ncclProfileNetPlugin = (1 << 7), // network plugin-defined, events
|
||||
ncclProfileGroupApi = (1 << 8), // Group API events
|
||||
ncclProfileCollApi = (1 << 9), // Collective API events
|
||||
ncclProfileP2pApi = (1 << 10), // Point-to-Point API events
|
||||
ncclProfileKernelLaunch = (1 << 11), // Kernel launch events
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
@@ -50,22 +54,28 @@ typedef enum {
|
||||
|
||||
/* Kernel event states */
|
||||
ncclProfilerKernelChStop = 22,
|
||||
|
||||
/* Group API States */
|
||||
ncclProfilerGroupStartApiStop = 23,
|
||||
ncclProfilerGroupEndApiStart = 24
|
||||
} ncclProfilerEventState_t;
|
||||
|
||||
typedef ncclProfilerEventState_t ncclProfilerEventState_v1_t;
|
||||
typedef ncclProfilerEventState_t ncclProfilerEventState_v2_t;
|
||||
typedef ncclProfilerEventState_t ncclProfilerEventState_v3_t;
|
||||
typedef ncclProfilerEventState_t ncclProfilerEventState_v4_t;
|
||||
typedef ncclProfilerEventState_t ncclProfilerEventState_v5_t;
|
||||
|
||||
#include <cstdint>
|
||||
#include "profiler/profiler_v5.h"
|
||||
#include "profiler/profiler_v4.h"
|
||||
#include "profiler/profiler_v3.h"
|
||||
#include "profiler/profiler_v2.h"
|
||||
#include "profiler/profiler_v1.h"
|
||||
|
||||
typedef ncclProfiler_v4_t ncclProfiler_t;
|
||||
typedef ncclProfilerEventDescr_v4_t ncclProfilerEventDescr_t;
|
||||
typedef ncclProfilerEventStateArgs_v4_t ncclProfilerEventStateArgs_t;
|
||||
typedef ncclProfiler_v5_t ncclProfiler_t;
|
||||
typedef ncclProfilerEventDescr_v5_t ncclProfilerEventDescr_t;
|
||||
typedef ncclProfilerEventStateArgs_v5_t ncclProfilerEventStateArgs_t;
|
||||
|
||||
#define NCCL_PROFILER_NET_VER_BITS (16)
|
||||
#define NCCL_PROFILER_NET_VER_MASK (~0U >> NCCL_PROFILER_NET_VER_BITS)
|
||||
|
||||
@@ -11,12 +11,56 @@
|
||||
#include "nccl.h"
|
||||
#include "nccl_common.h"
|
||||
|
||||
#include "tuner/tuner_v5.h"
|
||||
#include "tuner/tuner_v4.h"
|
||||
#include "tuner/tuner_v3.h"
|
||||
#include "tuner/tuner_v2.h"
|
||||
|
||||
typedef ncclTuner_v4_t ncclTuner_t;
|
||||
typedef ncclTuner_v5_t ncclTuner_t;
|
||||
typedef ncclTunerConstants_v5_t ncclTunerConstants_t;
|
||||
typedef ncclNvlDomainInfo_v5_t ncclNvlDomainInfo_t;
|
||||
|
||||
#define NCCL_TUNER_PLUGIN_SYMBOL "ncclTunerPlugin_v4"
|
||||
#define NCCL_TUNER_PLUGIN_SYMBOL "ncclTunerPlugin_v5"
|
||||
|
||||
#define NCCL_ALGO_UNDEF -1
|
||||
#define NCCL_ALGO_TREE 0
|
||||
#define NCCL_ALGO_RING 1
|
||||
#define NCCL_ALGO_COLLNET_DIRECT 2
|
||||
#define NCCL_ALGO_COLLNET_CHAIN 3
|
||||
#define NCCL_ALGO_NVLS 4
|
||||
#define NCCL_ALGO_NVLS_TREE 5
|
||||
#define NCCL_ALGO_PAT 6
|
||||
#define NCCL_NUM_ALGORITHMS NCCL_NUM_ALGORITHMS_V5 // Tree/Ring/CollNet*/PAT
|
||||
|
||||
#define NCCL_PROTO_UNDEF -1
|
||||
#define NCCL_PROTO_LL 0
|
||||
#define NCCL_PROTO_LL128 1
|
||||
#define NCCL_PROTO_SIMPLE 2
|
||||
#define NCCL_NUM_PROTOCOLS NCCL_NUM_PROTOCOLS_V5 // Simple/LL/LL128
|
||||
|
||||
#define NCCL_ALGO_PROTO_IGNORE -1.0
|
||||
|
||||
#define NCCL_NUM_UNROLLS 3 // 1/2/4
|
||||
#define NCCL_UNROLL_1 0
|
||||
#define NCCL_UNROLL_2 1
|
||||
#define NCCL_UNROLL_4 2
|
||||
|
||||
#define NCCL_NUM_FLOATS 6 // half/float/double/rccl_bfloat16/rccl_float8/rccl_bfloat8
|
||||
|
||||
#define NCCL_HW_NVLINK 0
|
||||
#define NCCL_HW_PCI 1
|
||||
#define NCCL_HW_NET 2
|
||||
#define NCCL_NUM_HW_LINKS NCCL_NUM_HW_LINKS_V5
|
||||
|
||||
#define NCCL_VOLTA_COMPCAP_IDX 0
|
||||
#define NCCL_AMPERE_COMPCAP_IDX 1
|
||||
#define NCCL_HOPPER_COMPCAP_IDX 2
|
||||
#define NCCL_BLACKWELL_COMPCAP_IDX 3
|
||||
#define NCCL_NUM_COMPCAPS NCCL_NUM_COMPCAPS_V5
|
||||
|
||||
#define NCCL_TUNING_SCALE_1NODE 0
|
||||
#define NCCL_TUNING_SCALE_2NODES 1
|
||||
#define NCCL_TUNING_SCALE_4NODES 2
|
||||
#define NCCL_NUM_TUNING_SCALES NCCL_NUM_TUNING_SCALES_V5
|
||||
|
||||
#endif
|
||||
|
||||
@@ -5,11 +5,9 @@
|
||||
#ifndef NET_V10_H_
|
||||
#define NET_V10_H_
|
||||
|
||||
#define NCCL_NET_MAX_DEVS_PER_NIC_V10 4
|
||||
|
||||
typedef struct {
|
||||
int ndevs;
|
||||
int devs[NCCL_NET_MAX_DEVS_PER_NIC_V10];
|
||||
int devs[NCCL_NET_MAX_DEVS_PER_NIC];
|
||||
} ncclNetVDeviceProps_v10_t;
|
||||
|
||||
#define NCCL_NET_TRAFFIC_CLASS_UNDEF -1
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2022, NVIDIA CORPORATION. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef NET_V11_H_
|
||||
#define NET_V11_H_
|
||||
|
||||
typedef struct {
|
||||
int ndevs;
|
||||
int devs[NCCL_NET_MAX_DEVS_PER_NIC];
|
||||
} ncclNetVDeviceProps_v11_t;
|
||||
|
||||
#define NCCL_NET_TRAFFIC_CLASS_UNDEF -1
|
||||
|
||||
typedef struct {
|
||||
// Plugin-specific TC value
|
||||
int trafficClass;
|
||||
} ncclNetCommConfig_v11_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
char* name; // Used mostly for logging.
|
||||
char* pciPath; // Path to the PCI device in /sys.
|
||||
uint64_t guid; // Unique identifier for the NIC chip. Important for
|
||||
// cards with multiple PCI functions (Physical or virtual).
|
||||
int ptrSupport; // [NCCL_PTR_HOST|NCCL_PTR_CUDA|NCCL_PTR_DMABUF]
|
||||
int regIsGlobal; // regMr is not tied to a particular comm
|
||||
int forceFlush; // Force a flush on receives
|
||||
int speed; // Port speed in Mbps.
|
||||
int port; // Port number.
|
||||
float latency; // Network latency
|
||||
int maxComms; // Maximum number of comms we can create
|
||||
int maxRecvs; // Maximum number of grouped receives.
|
||||
ncclNetDeviceType netDeviceType; // Network offload type
|
||||
int netDeviceVersion; // Version number for network offload
|
||||
ncclNetVDeviceProps_v11_t vProps;
|
||||
size_t maxP2pBytes; // Max transfer size for point-to-point operations
|
||||
size_t maxCollBytes; // Max transfer size for collective operations
|
||||
int maxMultiRequestSize; // Maximum number of requests supported in a single multi-request.
|
||||
} ncclNetProperties_v11_t;
|
||||
|
||||
#define NCCL_NET_ATTR_UNDEF -1
|
||||
|
||||
#define NCCL_NET_ATTR_INIT { \
|
||||
{ NCCL_NET_ATTR_UNDEF, NCCL_NET_ATTR_UNDEF, NCCL_NET_ATTR_UNDEF, NCCL_NET_ATTR_UNDEF }, /* sendCommAttr */ \
|
||||
{ NCCL_NET_ATTR_UNDEF, NCCL_NET_ATTR_UNDEF, NCCL_NET_ATTR_UNDEF, NCCL_NET_ATTR_UNDEF }, /* recvCommAttr */ \
|
||||
(uint32_t)NCCL_NET_ATTR_UNDEF, /* op */ \
|
||||
(uint32_t)NCCL_NET_ATTR_UNDEF, /* algo */ \
|
||||
(uint32_t)NCCL_NET_ATTR_UNDEF, /* proto */ \
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int32_t maxConcurrentPeers;
|
||||
int32_t minConcurrentPeers;
|
||||
int32_t maxFlowsPerPeer;
|
||||
int32_t minFlowsPerPeer;
|
||||
} ncclNetCommAttr_v11_t;
|
||||
|
||||
typedef struct {
|
||||
ncclNetCommAttr_v11_t sendCommAttr;
|
||||
ncclNetCommAttr_v11_t recvCommAttr;
|
||||
uint32_t op;
|
||||
uint32_t algo;
|
||||
uint32_t proto;
|
||||
} ncclNetAttr_v11_t;
|
||||
|
||||
typedef struct {
|
||||
// Name of the network (mainly for logs)
|
||||
const char* name;
|
||||
// Initialize the network.
|
||||
ncclResult_t (*init)(void** ctx, uint64_t commId, ncclNetCommConfig_v11_t* config, ncclDebugLogger_t logFunction, ncclProfilerCallback_t profFunction);
|
||||
// Return the number of adapters.
|
||||
ncclResult_t (*devices)(int* ndev);
|
||||
// Get various device properties.
|
||||
ncclResult_t (*getProperties)(int dev, ncclNetProperties_v11_t* props);
|
||||
// Create a receiving object and provide a handle to connect to it. The
|
||||
// handle can be up to NCCL_NET_HANDLE_MAXSIZE bytes and will be exchanged
|
||||
// between ranks to create a connection.
|
||||
ncclResult_t (*listen)(void* ctx, int dev, void* handle, void** listenComm);
|
||||
// Connect to a handle and return a sending comm object for that peer.
|
||||
// This call must not block for the connection to be established, and instead
|
||||
// should return successfully with sendComm == NULL with the expectation that
|
||||
// it will be called again until sendComm != NULL.
|
||||
// If *sendDevComm points to a valid object, then NCCL is requesting device offload for this connection
|
||||
ncclResult_t (*connect)(void* ctx, int dev, void* handle, void** sendComm, ncclNetDeviceHandle_v11_t** sendDevComm);
|
||||
// Finalize connection establishment after remote peer has called connect.
|
||||
// This call must not block for the connection to be established, and instead
|
||||
// should return successfully with recvComm == NULL with the expectation that
|
||||
// it will be called again until recvComm != NULL.
|
||||
// If *recvDevComm points to a valid object, then NCCL is requesting device offload for this connection
|
||||
ncclResult_t (*accept)(void* listenComm, void** recvComm, ncclNetDeviceHandle_v11_t** recvDevComm);
|
||||
// Register/Deregister memory. Comm can be either a sendComm or a recvComm.
|
||||
// Type is either NCCL_PTR_HOST or NCCL_PTR_CUDA.
|
||||
ncclResult_t (*regMr)(void* comm, void* data, size_t size, int type, void** mhandle);
|
||||
/* DMA-BUF support */
|
||||
ncclResult_t (*regMrDmaBuf)(void* comm, void* data, size_t size, int type, uint64_t offset, int fd, void** mhandle);
|
||||
ncclResult_t (*deregMr)(void* comm, void* mhandle);
|
||||
// Asynchronous send to a peer.
|
||||
// May return request == NULL if the call cannot be performed (or would block)
|
||||
ncclResult_t (*isend)(void* sendComm, void* data, size_t size, int tag, void* mhandle, void* phandle, void** request);
|
||||
// Asynchronous recv from a peer.
|
||||
// May return request == NULL if the call cannot be performed (or would block)
|
||||
ncclResult_t (*irecv)(void* recvComm, int n, void** data, size_t* sizes, int* tags, void** mhandles, void** phandles, void** request);
|
||||
// Perform a flush/fence to make sure all data received with NCCL_PTR_CUDA is
|
||||
// visible to the GPU
|
||||
ncclResult_t (*iflush)(void* recvComm, int n, void** data, int* sizes, void** mhandles, void** request);
|
||||
// Test whether a request is complete. If size is not NULL, it returns the
|
||||
// number of bytes sent/received.
|
||||
ncclResult_t (*test)(void* request, int* done, int* sizes);
|
||||
// Close and free send/recv comm objects
|
||||
ncclResult_t (*closeSend)(void* sendComm);
|
||||
ncclResult_t (*closeRecv)(void* recvComm);
|
||||
ncclResult_t (*closeListen)(void* listenComm);
|
||||
|
||||
// Copy the given mhandle to a dptr in a format usable by this plugin's device code
|
||||
ncclResult_t (*getDeviceMr)(void* comm, void* mhandle, void** dptr_mhandle);
|
||||
|
||||
// Notify the plugin that a recv has completed by the device
|
||||
ncclResult_t (*irecvConsumed)(void* recvComm, int n, void* request);
|
||||
|
||||
// Virtual NIC APIs. makeVDevice will create a virtual NIC given the specified properties, and tell the caller
|
||||
// what index this new vNIC exists at
|
||||
ncclResult_t (*makeVDevice)(int* d, ncclNetVDeviceProps_v11_t* props);
|
||||
// Finalize the network.
|
||||
ncclResult_t (*finalize)(void* ctx);
|
||||
|
||||
ncclResult_t (*setNetAttr)(void* ctx, ncclNetAttr_v11_t* netAttr);
|
||||
} ncclNet_v11_t;
|
||||
|
||||
typedef struct {
|
||||
void* mhandle;
|
||||
void* address;
|
||||
size_t size;
|
||||
} ncclNetSGE_v11_t;
|
||||
|
||||
typedef struct {
|
||||
// Name of the collective network (mainly for logs)
|
||||
const char* name;
|
||||
// Initialize the collective network.
|
||||
ncclResult_t (*init)(void** ctx, uint64_t commId, ncclDebugLogger_t logFunction);
|
||||
// Return the number of adapters capable of doing collective operations.
|
||||
// If ndev returns 0, all other functions might be set to NULL.
|
||||
ncclResult_t (*devices)(int* ndev);
|
||||
// Get various device properties.
|
||||
ncclResult_t (*getProperties)(int dev, ncclNetProperties_v11_t* props);
|
||||
// Create a receiving object and provide a handle to connect to it. The
|
||||
// handle can be up to NCCL_NET_HANDLE_MAXSIZE bytes and will be exchanged
|
||||
// between ranks to create connections.
|
||||
ncclResult_t (*listen)(void* ctx, int dev, void* handle, void** listenComm);
|
||||
// Create a group for collective operations. handles have been created
|
||||
// using listen() above. rank indicates caller's rank in the collective network.
|
||||
ncclResult_t (*connect)(void* handles[], int nranks, int rank, void* listenComm, void** collComm);
|
||||
// Returns whether a reduction operation on a data type is supported.
|
||||
// 1 for supported, 0 otherwise.
|
||||
ncclResult_t (*reduceSupport)(ncclDataType_t dataType, ncclRedOp_t redOp, int* supported);
|
||||
// Register/Deregister memory. Type is either NCCL_PTR_HOST or NCCL_PTR_CUDA.
|
||||
ncclResult_t (*regMr)(void* collComm, void* data, size_t size, int type, void** mhandle);
|
||||
/* DMA-BUF support */
|
||||
ncclResult_t (*regMrDmaBuf)(void* collComm, void* data, size_t size, int type, uint64_t offset, int fd, void** mhandle);
|
||||
ncclResult_t (*deregMr)(void* collComm, void* mhandle);
|
||||
// Performs an asynchronous allreduce operation on the collective group.
|
||||
// May return request == NULL if the call cannot be performed (or would block).
|
||||
ncclResult_t (*iallreduce)(void* collComm, void* sendData, void* recvData, size_t count,
|
||||
ncclDataType_t dataType, ncclRedOp_t redOp, void* sendMhandle, void* recvMhandle, void** request);
|
||||
ncclResult_t (*iallgather)(void* collComm, void* sendData, int nRecvParts, ncclNetSGE_v11_t* recvParts,
|
||||
size_t bytesPerRank, size_t windowOffset, size_t windowBytes,
|
||||
void* sendMhandle, void** request);
|
||||
ncclResult_t (*ireducescatter)(void* collComm, int nSendParts, ncclNetSGE_v11_t* sendParts, void* recvData,
|
||||
size_t bytesPerRank, size_t windowOffset, size_t windowBytes,
|
||||
ncclDataType_t dataType, ncclRedOp_t redOp,
|
||||
void* recvMhandle, void** request);
|
||||
// Perform a flush/fence to make sure all data received with NCCL_PTR_CUDA is
|
||||
// visible to the GPU
|
||||
ncclResult_t (*iflush)(void* collComm, void* data, int size, void* mhandle, void** request);
|
||||
// Test whether a request is complete. If size is not NULL, it returns the
|
||||
// number of bytes sent/received.
|
||||
ncclResult_t (*test)(void* request, int* done, int* size);
|
||||
// Close and free collective comm objects
|
||||
ncclResult_t (*closeColl)(void* collComm);
|
||||
ncclResult_t (*closeListen)(void* listenComm);
|
||||
|
||||
// Create a virtual NIC given the specified properties, which can be accessed at device index d
|
||||
ncclResult_t (*makeVDevice)(int* d, ncclNetVDeviceProps_v11_t* props);
|
||||
// Finalize the collective network.
|
||||
ncclResult_t (*finalize)(void* ctx);
|
||||
} ncclCollNet_v11_t;
|
||||
|
||||
#endif // end include guard
|
||||
@@ -7,11 +7,9 @@
|
||||
#ifndef NET_V9_H_
|
||||
#define NET_V9_H_
|
||||
|
||||
#define NCCL_NET_MAX_DEVS_PER_NIC_V9 4
|
||||
|
||||
typedef struct {
|
||||
int ndevs;
|
||||
int devs[NCCL_NET_MAX_DEVS_PER_NIC_V9];
|
||||
int devs[NCCL_NET_MAX_DEVS_PER_NIC];
|
||||
} ncclNetVDeviceProps_v9_t;
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -21,4 +21,6 @@ void* ncclOpenProfilerPluginLib(const char* name);
|
||||
void* ncclGetNetPluginLib(enum ncclPluginType type);
|
||||
ncclResult_t ncclClosePluginLib(void* handle, enum ncclPluginType type);
|
||||
|
||||
extern char* ncclPluginLibPaths[];
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef PROFILER_V5_H_
|
||||
#define PROFILER_V5_H_
|
||||
|
||||
typedef struct {
|
||||
uint64_t type; // event type descriptor: ncclProfileColl, ...
|
||||
void* parentObj; // pointer to the profiler parent object (for coll is the group)
|
||||
int rank; // originating rank
|
||||
union {
|
||||
struct {
|
||||
bool graphCaptured;
|
||||
int groupDepth;
|
||||
} groupApi;
|
||||
|
||||
struct {
|
||||
const char* func;
|
||||
size_t count;
|
||||
const char* datatype;
|
||||
int root;
|
||||
void* stream;
|
||||
bool graphCaptured;
|
||||
} collApi;
|
||||
|
||||
struct {
|
||||
const char* func;
|
||||
size_t count;
|
||||
const char* datatype;
|
||||
void* stream;
|
||||
bool graphCaptured;
|
||||
} p2pApi;
|
||||
|
||||
struct {
|
||||
void* stream;
|
||||
} kernelLaunch;
|
||||
|
||||
struct {
|
||||
uint64_t seqNumber;
|
||||
const char* func;
|
||||
void const* sendBuff;
|
||||
void* recvBuff;
|
||||
size_t count;
|
||||
int root;
|
||||
const char* datatype;
|
||||
uint8_t nChannels;
|
||||
uint8_t nWarps;
|
||||
const char* algo;
|
||||
const char* proto;
|
||||
void* parentGroup; // for backward compatibility with v4
|
||||
} coll;
|
||||
|
||||
struct {
|
||||
const char* func;
|
||||
void* buff;
|
||||
const char* datatype;
|
||||
size_t count;
|
||||
int peer;
|
||||
uint8_t nChannels;
|
||||
void* parentGroup; // for backward compatibility with v4
|
||||
} p2p;
|
||||
|
||||
struct {
|
||||
pid_t pid; // pid of the originating process
|
||||
uint8_t channelId; // channel id for this proxy operation
|
||||
int peer; // remote rank for send/recv
|
||||
int nSteps; // number of steps for this proxy operation
|
||||
int chunkSize; // amount of data transferred by this proxy operation
|
||||
int isSend;
|
||||
} proxyOp;
|
||||
|
||||
struct {
|
||||
int step;
|
||||
} proxyStep;
|
||||
|
||||
struct {
|
||||
uint8_t channelId;
|
||||
uint64_t pTimer; // start timestamp from GPU globaltimer
|
||||
} kernelCh;
|
||||
|
||||
struct {
|
||||
int64_t id;
|
||||
void* data;
|
||||
} netPlugin;
|
||||
};
|
||||
} ncclProfilerEventDescr_v5_t;
|
||||
|
||||
typedef union {
|
||||
struct {
|
||||
size_t transSize;
|
||||
} proxyStep;
|
||||
|
||||
struct {
|
||||
int appendedProxyOps;
|
||||
} proxyCtrl;
|
||||
|
||||
struct {
|
||||
void* data;
|
||||
} netPlugin;
|
||||
|
||||
struct {
|
||||
uint64_t pTimer;
|
||||
} kernelCh;
|
||||
} ncclProfilerEventStateArgs_v5_t;
|
||||
|
||||
typedef struct {
|
||||
const char* name;
|
||||
|
||||
// init - initialize the profiler plugin
|
||||
// Input
|
||||
// - context : opaque profiler context object for separating profiler behavior across comms
|
||||
// - commId : communicator id
|
||||
// - commName : user assigned communicator name
|
||||
// - nNodes : number of nodes in communicator
|
||||
// - nranks : number of ranks in communicator
|
||||
// - rank : rank identifier in communicator
|
||||
// - logfn : logger function
|
||||
// Output
|
||||
// - eActivationMask: bitmask of active events set by the plugin
|
||||
ncclResult_t (*init)(void** context, uint64_t commId, int* eActivationMask, const char* commName, int nNodes, int nranks, int rank, ncclDebugLogger_t logfn);
|
||||
|
||||
// startEvent - initialize and start a new event for the supplied event descriptor inside the eventset
|
||||
// Input
|
||||
// - context: opaque profiler context object
|
||||
// - eDescr : pointer to ncclProfilerEventDescr_t object
|
||||
// Output
|
||||
// - eHandle: return event handle for supplied event descriptor object
|
||||
ncclResult_t (*startEvent)(void* context, void** eHandle, ncclProfilerEventDescr_v5_t* eDescr);
|
||||
|
||||
// stopEvent - stop/finalize an event inside and event set
|
||||
// Input
|
||||
// - eHandle: handle to event object
|
||||
ncclResult_t (*stopEvent)(void* eHandle);
|
||||
|
||||
// recordEventState - record event state transitions and event attribute updates
|
||||
// Input
|
||||
// - eHandle : handle to event object created through startEvent
|
||||
// - eStateArgs: optional argument used to capture event attribute updates associated with the state transition
|
||||
// - eState : event state transition
|
||||
ncclResult_t (*recordEventState)(void* eHandle, ncclProfilerEventState_v5_t eState, ncclProfilerEventStateArgs_v5_t* eStateArgs);
|
||||
|
||||
// finalize - finalize the profiler plugin
|
||||
// Input
|
||||
// - context: opaque profiler context object
|
||||
ncclResult_t (*finalize)(void* context);
|
||||
} ncclProfiler_v5_t;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,87 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
|
||||
* Copyright (c) 2023, Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef TUNER_V5_H_
|
||||
#define TUNER_V5_H_
|
||||
|
||||
// NVL domain information struct
|
||||
typedef struct {
|
||||
int nNvlDomains; // number of NVLink domains
|
||||
int minRanksPerNvlDomain; // minimum ranks across all NVLink domains
|
||||
int maxRanksPerNvlDomain; // maximum ranks across all NVLink domains
|
||||
} ncclNvlDomainInfo_v5_t;
|
||||
|
||||
#define NCCL_NUM_ALGORITHMS_V5 7 // Tree/Ring/CollNet*/PAT
|
||||
#define NCCL_NUM_PROTOCOLS_V5 3 // Simple/LL/LL128
|
||||
#define NCCL_NUM_HW_LINKS_V5 3
|
||||
#define NCCL_NUM_COMPCAPS_V5 4
|
||||
#define NCCL_NUM_TUNING_SCALES_V5 3
|
||||
|
||||
typedef struct {
|
||||
double baseLatencies [NCCL_NUM_ALGORITHMS_V5][NCCL_NUM_PROTOCOLS_V5];
|
||||
double hwLatencies [NCCL_NUM_HW_LINKS_V5][NCCL_NUM_ALGORITHMS_V5][NCCL_NUM_PROTOCOLS_V5];
|
||||
|
||||
double llMaxBws [NCCL_NUM_COMPCAPS_V5][NCCL_NUM_TUNING_SCALES_V5];
|
||||
double perChMaxRingLL128Bws [NCCL_NUM_COMPCAPS_V5][NCCL_NUM_TUNING_SCALES_V5];
|
||||
double perChMaxTreeLL128Bws [NCCL_NUM_COMPCAPS_V5][NCCL_NUM_TUNING_SCALES_V5];
|
||||
double perChMaxTreeBws [NCCL_NUM_COMPCAPS_V5][NCCL_NUM_TUNING_SCALES_V5];
|
||||
double perChMaxNVLSTreeBws [NCCL_NUM_COMPCAPS_V5][NCCL_NUM_TUNING_SCALES_V5];
|
||||
|
||||
|
||||
} ncclTunerConstants_v5_t;
|
||||
|
||||
// API to be implemented by external tuner
|
||||
typedef struct {
|
||||
// Name of the tuner
|
||||
const char* name;
|
||||
|
||||
// Initializes tuner states.
|
||||
// Inputs:
|
||||
// - commId: communicator identifier
|
||||
// - nRanks: number of ranks in current communicator. Each communicator initialize its own tuner.
|
||||
// - nNodes: number of nodes in current communicator.
|
||||
// - logFunction: a logFunction can be useful to integrate logging together with NCCL core.
|
||||
// - nvlDomainInfo: NVL domain information struct
|
||||
// Outputs:
|
||||
// - context: tuner context object
|
||||
// Input/Output:
|
||||
// - constants: tuner constants
|
||||
ncclResult_t (*init)(void** ctx, uint64_t commId, size_t nRanks, size_t nNodes, ncclDebugLogger_t logFunction,
|
||||
ncclNvlDomainInfo_v5_t* nvlDomainInfo, ncclTunerConstants_v5_t* constants);
|
||||
|
||||
// Gets info (algo, protocol, number of ctas and threads) for a given collective.
|
||||
// Inputs:
|
||||
// - context: tuner context object
|
||||
// - collType: collective type , e.g., allreduce, allgather…
|
||||
// - nBytes: collective size in bytes
|
||||
// - numPipeOps: number of operations in the group
|
||||
// - numAlgo: number of algorithms in collCostTable
|
||||
// - numProto: number of protocols in collCostTable
|
||||
// - regBuff: can register user buffer
|
||||
//
|
||||
// Outputs:
|
||||
// - nChannels: number of channels (hence SMs) to be used.
|
||||
//
|
||||
// InOut:
|
||||
// - collCostTable: collective cost table, generated by NCCL core, containing algo|proto|time entries for collType.
|
||||
// NCCL core sets ignored algo/proto cost table entries to -1.0 (NCCL_ALGO_PROTO_IGNORE).
|
||||
//
|
||||
// If getCollInfo() does not return ncclSuccess, NCCL will fall back to the
|
||||
// default tuning for the given collective.
|
||||
// Also, the plugin is allowed to not set any output, or set only the
|
||||
// algorithm and protocol, but not only the algorithm or only the protocol.
|
||||
// Unset fields will be set automatically by NCCL.
|
||||
ncclResult_t (*getCollInfo)(void* context, ncclFunc_t collType, size_t nBytes,
|
||||
int numPipeOps, float** collCostTable, int numAlgo, int numProto,
|
||||
int regBuff, int* nChannels);
|
||||
|
||||
// Terminates the plugin and cleans up any resources that the plugin allocated.
|
||||
// context: tuner context object
|
||||
ncclResult_t (*finalize)(void* context);
|
||||
} ncclTuner_v5_t;
|
||||
|
||||
#endif
|
||||
@@ -28,12 +28,48 @@ struct ncclProfilerProxy {
|
||||
struct ncclProxyConnector recvProxyConn[MAXCHANNELS];
|
||||
};
|
||||
|
||||
enum groupApiState {
|
||||
ncclProfilerGroupApiStartStateReset = 0,
|
||||
ncclProfilerGroupApiStartStateStarted = 1,
|
||||
ncclProfilerGroupApiStartStateStopped = 2,
|
||||
};
|
||||
|
||||
// Used by the profiler to track state for API events
|
||||
typedef struct ncclProfilerApiState {
|
||||
int profilerGroupDepth;
|
||||
int eActivationMask;
|
||||
groupApiState state;
|
||||
void *groupApiEventHandle;
|
||||
// Tracks the latest API event handles for p2p/collectives
|
||||
void* p2pApiEventHandle;
|
||||
void *collApiEventHandle;
|
||||
} ncclProfilerApiState_t;
|
||||
|
||||
extern __thread ncclProfilerApiState_t ncclProfilerApiState;
|
||||
|
||||
extern int ncclProfilerEventMask;
|
||||
|
||||
// Plugin Init/Finalize Wrappers
|
||||
ncclResult_t ncclProfilerPluginInit(struct ncclComm* comm);
|
||||
ncclResult_t ncclProfilerPluginFinalize(struct ncclComm* comm);
|
||||
|
||||
// Profiler Start/Stop/Record wrappers for ncclGroupStart and ncclGroupEnd API calls
|
||||
ncclResult_t ncclProfilerStartGroupApiEvent(struct ncclInfo *info, bool isGraphCaptured);
|
||||
ncclResult_t ncclProfilerStopGroupApiEvent();
|
||||
ncclResult_t ncclProfilerRecordGroupApiEventState(ncclProfilerEventState_t eState);
|
||||
|
||||
//Profiler Start/Stop wrappers for P2p API calls
|
||||
ncclResult_t ncclProfilerStartP2pApiEvent(struct ncclInfo *info, bool isGraphCaptured);
|
||||
ncclResult_t ncclProfilerStopP2pApiEvent();
|
||||
|
||||
//Profiler Start/Stop wrappers for Collective API calls
|
||||
ncclResult_t ncclProfilerStartCollApiEvent(struct ncclInfo *info, bool isGraphCaptured);
|
||||
ncclResult_t ncclProfilerStopCollApiEvent();
|
||||
|
||||
// Kernel Launch Start/Stop Event Wrappers
|
||||
ncclResult_t ncclProfilerStartKernelLaunchEvent(struct ncclKernelPlan* plan, cudaStream_t stream);
|
||||
ncclResult_t ncclProfilerStopKernelLaunchEvent(struct ncclKernelPlan* plan);
|
||||
|
||||
// Profiler Start/Stop Group Wrappers
|
||||
ncclResult_t ncclProfilerStartGroupEvent(struct ncclKernelPlan* plan);
|
||||
ncclResult_t ncclProfilerStopGroupEvent(struct ncclKernelPlan* plan);
|
||||
|
||||
@@ -74,6 +74,7 @@ struct ncclProxyOp {
|
||||
uint8_t /*ncclDataType_t*/ dtype;
|
||||
uint8_t /*ncclDevRedOp_t*/ redOp;
|
||||
uint8_t /*ncclFunc_t*/ coll;
|
||||
uint8_t /*ncclFunc_t*/ collAPI;
|
||||
uint8_t /*ncclPattern_t*/ pattern;
|
||||
uint8_t protocol;
|
||||
uint8_t algorithm;
|
||||
@@ -88,6 +89,8 @@ struct ncclProxyOp {
|
||||
int nextRank;
|
||||
int prevRank;
|
||||
union ncclProxyOpSpecifics specifics;
|
||||
int nChannels;
|
||||
int nPeers;
|
||||
|
||||
// Profiler plugin
|
||||
union {
|
||||
@@ -197,11 +200,14 @@ struct ncclProxyArgs {
|
||||
uint8_t /*ncclDevRedOp_t*/ redOp;
|
||||
uint8_t /*ncclPattern_t*/ pattern;
|
||||
uint8_t /*ncclFunc_t*/ coll;
|
||||
uint8_t /*ncclFunc_t*/ collAPI;
|
||||
uint8_t protocol;
|
||||
uint8_t algorithm;
|
||||
int state;
|
||||
char* sharedBuff[NCCL_STEPS];
|
||||
int sharedSize[NCCL_STEPS];
|
||||
int nChannels;
|
||||
int nPeers;
|
||||
|
||||
int idle;
|
||||
uint64_t hdp_flushed;
|
||||
@@ -366,6 +372,11 @@ struct ncclProxyState {
|
||||
// Progress thread
|
||||
struct ncclProxyProgressState progressState;
|
||||
|
||||
// Network plugin
|
||||
void* netContext;
|
||||
ncclNetAttr_t netAttr;
|
||||
void* collNetContext;
|
||||
|
||||
// Profiler plugin
|
||||
void* profilerContext;
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2024-2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef NCCL_REGISTER_H_
|
||||
#define NCCL_REGISTER_H_
|
||||
|
||||
@@ -29,15 +35,6 @@ struct ncclRegNetHandles {
|
||||
struct ncclRegNetHandles* next;
|
||||
};
|
||||
|
||||
struct ncclSymRegTask {
|
||||
struct ncclSymRegTask *next;
|
||||
void* buff;
|
||||
size_t baseSize;
|
||||
CUmemGenericAllocationHandle memHandle;
|
||||
struct ncclReg* regHandle;
|
||||
size_t alignment;
|
||||
};
|
||||
|
||||
struct ncclReg {
|
||||
// common attributes
|
||||
uintptr_t begAddr, endAddr; // page aligned
|
||||
@@ -58,10 +55,6 @@ struct ncclReg {
|
||||
// general ipc reg
|
||||
struct ncclPeerRegIpcAddr regIpcAddrs;
|
||||
struct ncclIpcRegInfo* ipcInfos[NCCL_MAX_LOCAL_RANKS];
|
||||
// symmetric reg
|
||||
void* baseSymPtr;
|
||||
size_t symSize;
|
||||
int winFlags;
|
||||
};
|
||||
|
||||
struct ncclRegCache {
|
||||
@@ -70,14 +63,9 @@ struct ncclRegCache {
|
||||
uintptr_t pageSize;
|
||||
};
|
||||
|
||||
struct ncclWindow {
|
||||
struct ncclReg* handle;
|
||||
};
|
||||
|
||||
ncclResult_t ncclRegCleanup(struct ncclComm* comm);
|
||||
ncclResult_t ncclCommGraphRegister(const ncclComm_t comm, void* buff, size_t size, void** handle);
|
||||
ncclResult_t ncclCommGraphDeregister(const ncclComm_t comm, struct ncclReg *handle);
|
||||
ncclResult_t ncclRegLocalIsValid(struct ncclReg *reg, bool *isValid);
|
||||
ncclResult_t ncclCommSymmetricRegisterInternal(struct ncclComm* comm, void* buff, size_t baseSize, size_t alignment, CUmemGenericAllocationHandle memHandle, struct ncclReg* regHandle);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// Modification Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
|
||||
// SPDX-License-Identifier: MIT
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2024-2025, NVIDIA CORPORATION. All rights reserved.
|
||||
* Modification Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef NCCL_REGISTER_INLINE_H_
|
||||
#define NCCL_REGISTER_INLINE_H_
|
||||
@@ -21,16 +26,5 @@ static inline ncclResult_t ncclRegFind(struct ncclComm* comm, const void* data,
|
||||
}
|
||||
}
|
||||
|
||||
static inline ncclResult_t ncclRegFindSymmetric(struct ncclComm* comm, const void* data, size_t size, void** symPtr, struct ncclReg** outReg) {
|
||||
struct ncclReg* regRecord = NULL;
|
||||
*symPtr = NULL;
|
||||
*outReg = NULL;
|
||||
NCCLCHECK(ncclRegFind(comm, data, size, ®Record));
|
||||
if (regRecord && regRecord->baseSymPtr) {
|
||||
*symPtr = (void*)((uintptr_t)regRecord->baseSymPtr + (uintptr_t)data - (uintptr_t)regRecord->begAddr);
|
||||
*outReg = regRecord;
|
||||
}
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -52,11 +52,9 @@ typedef hsa_status_t (*PFN_hsa_amd_portable_export_dmabuf)(const void* ptr, size
|
||||
|
||||
// Report failure but clear error and continue
|
||||
#define CUCHECKIGNORE(cmd) do { \
|
||||
hsa_status_t err = pfn_##cmd; \
|
||||
if( err != HSA_STATUS_SUCCESS ) { \
|
||||
const char *errStr; \
|
||||
pfn_hsa_status_string(err, &errStr); \
|
||||
INFO(NCCL_ALL,"%s:%d HIP failure '%s'", __FILE__, __LINE__, errStr); \
|
||||
hipError_t err = cmd; \
|
||||
if( err != hipSuccess ) { \
|
||||
INFO(NCCL_ALL,"%s:%d HIP failure '%s'", __FILE__, __LINE__, hipGetErrorString(err)); \
|
||||
} \
|
||||
} while(false)
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2015-2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef NCCL_SCHEDULER_H_
|
||||
#define NCCL_SCHEDULER_H_
|
||||
|
||||
#include "nccl.h"
|
||||
#include "comm.h"
|
||||
#include "sym_kernels.h"
|
||||
|
||||
ncclResult_t ncclMakeSymmetricTaskList(struct ncclComm* comm, struct ncclTaskColl* task, struct ncclIntruQueue<struct ncclTaskColl, &ncclTaskColl::next>* symTaskQueue, struct ncclTaskColl** remainTasksHead);
|
||||
ncclResult_t ncclSymmetricTaskScheduler(struct ncclComm* comm, struct ncclIntruQueue<struct ncclTaskColl, &ncclTaskColl::next>* symTaskQueue, struct ncclKernelPlan* plan);
|
||||
|
||||
#endif // NCCL_SCHEDULER_H_
|
||||
@@ -1,3 +1,9 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2016-2024, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef NCCL_SHM_H_
|
||||
#define NCCL_SHM_H_
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ ncclResult_t ncclShmClose(ncclShmHandle_t handle);
|
||||
ncclResult_t ncclShmUnlink(ncclShmHandle_t handle);
|
||||
|
||||
struct ncclShmemCollBuff {
|
||||
volatile size_t *cnt[2];
|
||||
volatile void *ptr[2];
|
||||
size_t *cnt[2];
|
||||
void *ptr[2];
|
||||
int round;
|
||||
size_t maxTypeSize;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* See LICENSE.txt for license information
|
||||
************************************************************************/
|
||||
|
||||
#ifndef NCCL_SYM_KERNELS_H_
|
||||
#define NCCL_SYM_KERNELS_H_
|
||||
#include "nccl.h"
|
||||
#include "nccl_device.h"
|
||||
#include "nccl_common.h"
|
||||
#include "device.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// ncclSymk[Foo]: Kernels built on the device API
|
||||
|
||||
#define NCCL_SYM_KERNEL_CELL_SIZE 1024 // no less than 16 bytes minimal cell size
|
||||
|
||||
constexpr int ncclSymkMaxBlocks = 64;
|
||||
constexpr int ncclSymkMaxThreads = 512;
|
||||
constexpr int ncclSymkLLMaxEltSize = 8;
|
||||
|
||||
constexpr __host__ __device__ int ncclSymkLLMaxSlots(int eltSize = ncclSymkLLMaxEltSize) {
|
||||
return ncclSymkMaxThreads*ncclSymkLLMaxEltSize/eltSize;
|
||||
}
|
||||
|
||||
enum ncclSymkKernelId {
|
||||
ncclSymkKernelId_AllReduce_AGxLL_R,
|
||||
ncclSymkKernelId_AllReduce_AGxLLMC_R,
|
||||
ncclSymkKernelId_AllReduce_RSxLD_AGxST,
|
||||
ncclSymkKernelId_AllReduce_RSxLDMC_AGxSTMC,
|
||||
ncclSymkKernelId_AllReduce_RSxNet_ARxMC_AGxNet,
|
||||
|
||||
ncclSymkKernelId_AllGather_LL,
|
||||
ncclSymkKernelId_AllGather_LLMC,
|
||||
ncclSymkKernelId_AllGather_ST,
|
||||
ncclSymkKernelId_AllGather_STMC,
|
||||
|
||||
ncclSymkKernelId_ReduceScatter_LL,
|
||||
ncclSymkKernelId_ReduceScatter_LD,
|
||||
ncclSymkKernelId_ReduceScatter_LDMC,
|
||||
|
||||
ncclSymkKernelId_Count
|
||||
};
|
||||
|
||||
struct ncclSymkDevComm {
|
||||
struct ncclDevComm devComm;
|
||||
struct ncclLLA2AHandle lsaLLA2A;
|
||||
};
|
||||
|
||||
struct ncclSymkState {
|
||||
bool initialized;
|
||||
struct ncclSymkDevComm kcomm;
|
||||
};
|
||||
|
||||
struct ncclSymkChannelWorkRange {
|
||||
uint16_t workHi; // inclusive index of my ending work
|
||||
uint16_t fracHi; // 16-bit fraction in (0.0, 1.0] indicating where my part ends
|
||||
};
|
||||
|
||||
// 16 bytes aligned
|
||||
struct alignas(16) ncclSymkDevWork {
|
||||
uint64_t redOpArg; // must be collectively uniform
|
||||
size_t nElts;
|
||||
struct ncclWindow_vidmem* inputWin, *outputWin;
|
||||
size_t inputOff, outputOff; // these = origUserOffset + cbdPartOffset
|
||||
uint64_t rootRank;
|
||||
uint64_t sChannelId:16, nChannels:16, padding:32;
|
||||
};
|
||||
|
||||
struct alignas(16) ncclSymkDevWorkArgs {
|
||||
struct ncclSymkDevComm kcomm;
|
||||
int nMaxChannels;
|
||||
// starting of channelWorkRange will be aligned to 16 bytes
|
||||
// channelWorkRange[nChannels];
|
||||
// ncclSymDevWork[nWorks];
|
||||
// aux functions
|
||||
__host__ static constexpr size_t calcArgsSize(int nChannels, int nWorks) {
|
||||
return alignUp(sizeof(struct ncclSymkDevWorkArgs), 16) + alignUp(nChannels * sizeof(struct ncclSymkChannelWorkRange), 16) + nWorks * sizeof(struct ncclSymkDevWork);
|
||||
}
|
||||
__host__ __device__ struct ncclSymkChannelWorkRange* getWorkRange() const {
|
||||
return (struct ncclSymkChannelWorkRange*)((uint8_t*)this + alignUp(sizeof(struct ncclSymkDevWorkArgs), 16));
|
||||
}
|
||||
__host__ __device__ struct ncclSymkDevWork* getWorks(int nChannels) const {
|
||||
return (struct ncclSymkDevWork*)((uint8_t*)this->getWorkRange() + alignUp(nChannels * sizeof(struct ncclSymkChannelWorkRange), 16));
|
||||
}
|
||||
};
|
||||
|
||||
union ncclSymkDevWorkArgs4K {
|
||||
struct ncclSymkDevWorkArgs args;
|
||||
char buf4K[4096];
|
||||
};
|
||||
|
||||
// We assume ncclComm contains a field: `ncclSymkState symkState`
|
||||
ncclResult_t ncclSymkInitOnce(struct ncclComm* comm);
|
||||
ncclResult_t ncclSymkFinalize(struct ncclComm* comm);
|
||||
|
||||
bool ncclSymkAvailable(struct ncclComm* comm, ncclFunc_t coll, int/*ncclDevRedOp_t*/ red,
|
||||
ncclDataType_t ty, size_t nElts);
|
||||
ncclResult_t ncclSymkPickKernel(struct ncclComm* comm, ncclFunc_t coll, int/*ncclDevRedOp_t*/ red, ncclDataType_t ty,
|
||||
size_t nEltsTotal, size_t nEltsMax, int nWorks,
|
||||
float* estTimeUs, ncclSymkKernelId* kernelId, int* nBlocks, int* nWarps);
|
||||
|
||||
ncclResult_t ncclSymkMakeDevWork(struct ncclComm* comm, struct ncclTaskColl* task, struct ncclSymkDevWork* outDevWork);
|
||||
|
||||
// Generated by src/device/symmetric/generate.py
|
||||
extern int const ncclSymkKernelCount;
|
||||
extern void* const ncclSymkKernelList[];
|
||||
void* ncclSymkGetKernelPtr(ncclSymkKernelId kernelId, int/*ncclDevRedOp_t*/ red, ncclDataType_t ty);
|
||||
const char* ncclSymkKernelIdToString(int kernelId);
|
||||
|
||||
#endif
|
||||
@@ -1,93 +0,0 @@
|
||||
// Modification Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef NCCL_DEVICE_SYMMETRIC_H_
|
||||
#define NCCL_DEVICE_SYMMETRIC_H_
|
||||
|
||||
#include "nccl.h"
|
||||
#include "nccl_common.h"
|
||||
#include "bitops.h"
|
||||
|
||||
constexpr int ncclSymMaxBlocks = 64;
|
||||
constexpr int ncclSymMaxThreads = 512;
|
||||
constexpr int ncclSymLLMaxEltSize = 64;
|
||||
|
||||
constexpr __host__ __device__ int ncclSymLLMaxSlots(int eltSize = ncclSymLLMaxEltSize) {
|
||||
return ncclSymMaxThreads*ncclSymLLMaxEltSize/eltSize;
|
||||
}
|
||||
|
||||
constexpr __host__ __device__ int ncclSymLLEpochSize(int nRanks) {
|
||||
return /*LL Overhead*/2 * maxval(ncclSymMaxThreads*nRanks*8, ncclSymLLMaxSlots(ncclSymLLMaxEltSize)*ncclSymLLMaxEltSize);
|
||||
}
|
||||
|
||||
struct alignas(16) ncclSymDevBase {
|
||||
uint32_t llEpoch[ncclSymMaxBlocks];
|
||||
uint32_t barEpochMc[ncclSymMaxBlocks], barEpochUc[ncclSymMaxBlocks];
|
||||
uint32_t barInboxMc[ncclSymMaxBlocks];
|
||||
uint32_t barInboxPerPeer[];
|
||||
|
||||
static constexpr size_t size(int nRanks) {
|
||||
return sizeof(ncclSymDevBase) +
|
||||
alignUp(ncclSymMaxBlocks*nRanks*sizeof(uint32_t), 16) +
|
||||
ncclSymMaxBlocks * /*epochs=*/2 * ncclSymLLEpochSize(nRanks);
|
||||
}
|
||||
};
|
||||
|
||||
static __device__ uint4* ncclSymDevBase_getLLBuf(struct ncclSymDevBase* base, int nRanks, int block, uint32_t epoch) {
|
||||
// Get pointer to buffer trailing the header struct.
|
||||
char* ans = (char*)(base + 1);
|
||||
// Skip over barInboxPerPeer[]
|
||||
ans += alignUp(ncclSymMaxBlocks*nRanks*sizeof(uint32_t), 16);
|
||||
// Skip to our block
|
||||
int epochSize = ncclSymLLEpochSize(nRanks);
|
||||
ans += block * /*epochs=*/2 * epochSize;
|
||||
ans += (epoch & 1)*epochSize;
|
||||
return (uint4*)ans;
|
||||
}
|
||||
|
||||
struct ncclSymDevComm {
|
||||
ncclSymDevBase* base;
|
||||
ncclSymDevBase* baseMc;
|
||||
uint32_t stride4G;
|
||||
int nRanks, rank;
|
||||
uint32_t nRanks_rcp32; // idivRcp32(nRanks)
|
||||
};
|
||||
|
||||
struct alignas(16) ncclSymDevArgs {
|
||||
struct ncclSymDevComm comm;
|
||||
int rootRank;
|
||||
uint64_t redOpArg; // must be collectively uniform
|
||||
size_t nElts;
|
||||
char* input;
|
||||
char* output;
|
||||
};
|
||||
|
||||
enum ncclSymKernelId {
|
||||
ncclSymKernelId_AllReduce_AGxLL_R,
|
||||
ncclSymKernelId_AllReduce_AGxLLMC_R,
|
||||
ncclSymKernelId_AllReduce_RSxLD_AGxST,
|
||||
ncclSymKernelId_AllReduce_RSxLDMC_AGxSTMC,
|
||||
|
||||
ncclSymKernelId_AllGather_LL,
|
||||
ncclSymKernelId_AllGather_LLMC,
|
||||
ncclSymKernelId_AllGather_ST,
|
||||
ncclSymKernelId_AllGather_STMC,
|
||||
|
||||
ncclSymKernelId_ReduceScatter_LL,
|
||||
ncclSymKernelId_ReduceScatter_LD,
|
||||
ncclSymKernelId_ReduceScatter_LDMC,
|
||||
|
||||
ncclSymKernelId_Count
|
||||
};
|
||||
|
||||
bool ncclSymImplemented(ncclFunc_t fn, int/*ncclDevRedOp_t*/ red, ncclDataType_t ty);
|
||||
|
||||
ncclResult_t ncclSymPickKernel(struct ncclComm* comm, ncclFunc_t fn, int/*ncclDevRedOp_t*/ red, ncclDataType_t ty, size_t nElts, float* estTimeUs, ncclSymKernelId* kernelId, int* nBlocks, int* nWarps);
|
||||
|
||||
// Generated by src/device/symmetric/generate.py
|
||||
extern int const ncclSymKernelCount;
|
||||
extern void* const ncclSymKernelList[];
|
||||
void* ncclSymGetKernelPtr(ncclSymKernelId kernelId, int/*ncclDevRedOp_t*/ red, ncclDataType_t ty);
|
||||
const char* ncclSymKernelIdToString(int kernelId);
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user