NCCL 2.28.3-1
Device API (Experimental)
* Introduces device-side APIs to integrate NCCL communication directly into application kernels.
* Supports LSA (Load/Store Access) for CUDA P2P communication over NVLink and some PCIe platforms.
* Supports Multimem for hardware multicast using NVLink SHARP.
* Adds initial framework for GIN (GPU-Initiated Networking), currently under development.
* Introduces device communicators created using ncclDevCommCreate.
* Enables device-side communication operations with synchronization (ncclLsaBarrierSession) and memory accessors (ncclGetLsaPointer, ncclGetLsaMultimemPointer).
* Experimental APIs - signatures and functionality may evolve in future releases.
* No ABI compatibility is guaranteed — applications must be recompiled with each new NCCL release.
Symmetric memory improvements
* Support for aggregating symmetric operations using ncclGroupStart/End APIs.
* Reimplement symmetric kernels using device API.
New Host APIs
* Introduce new host collective APIs: ncclAlltoAll, ncclScatter, ncclGather.
CE (Copy Engine) Collectives
* Reduce SM utilization for alltoall, scatter, gather, and allgather within a single (MN)NVL domain.
* Free up SM capacity for the application to do computation at the same time.
* To enable the feature for ncclAllGather, ncclAlltoAll, ncclGather, ncclScatter, register buffers into symmetric windows and use the NCCL_CTA_POLICY_ZERO flag in the communicator config_t.
NCCL Inspector Plugin
* Introduces an Inspector plugin for always-on performance monitoring.
* Produces structured JSON output with metadata, execution time, bandwidth, and optional event traces for each NCCL operation.
* Enables integration with analysis tools such as Performance Exporter to visualize NCCL performance bottlenecks.
* Lightweight to enable via environment variables NCCL_PROFILER_PLUGIN and NCCL_INSPECTOR_ENABLE.
CMake support (Experiemental)
* Adds a CMake build system as an alternative to existing Makefiles.
* Known issues: pkg.build and Device API currently do not work with CMake.
* The known issues will be addressed in a future release.
Decreased max CTA count from 32 to 16 on Blackwell
* SM overhead is decreased by 50% with this improvement.
* This may cause some perf drop on Blackwell because of the reduced SM usage.
* If the extra SM capacity is not desired, two options are available to restore to previous behavior: 1) Setting NCCL_MIN_CTAS=32 NCCL_MAX_CTAS=32 environment variables; 2) setting communicator config to over-write max CTA count to 32.
* Based on community feedback, future versions may consider different trade-offs between performance and SM overhead.
Plugins
* Network
* App-aware Network plugin. NCCL passes information about communication operations to be executed on the network end point. This allows for better tuning of network end points and their use in the plugins.
* Improve handling of physical and virtual network devices and load/unload.
* Network plugin version 11 - add explicit context and communication ID support for per communicator init/finalize.
* Add Multi-Request Net API. Using this will help NCCL to anticipate multiple send/recv requests and optimize for it. See maxMultiRequestSize field in ncclNetProperties_v11_t.
* Profiler
* Add support for API events (group, collective, and p2p) and for tracking kernel launches in the profiler plugin.
* Add Inspector Profiler Plugin (see section above).
* Add a hook to Google’s CoMMA profiler on github.
* Tuner
* Expose NCCL tuning constants at tuner initialization via ncclTunerConstants_v5_t.
* Add NVL Domain Information API.
* Support multiple plugin types from a single shared object.
New Parameterization and ncclConfig changes:
* Add new option NCCL_MNNVL_CLIQUE_ID=-2 which will use rack serial number to partition the MNNVL clique. This will limit NVLink domains to GPUs within a single rack.
* Add NCCL_NETDEVS_POLICY to control how NET devices are assigned to GPUs. The default (AUTO) is the policy used in previous versions.
* Add NCCL_SINGLE_PROC_MEM_REG_ENABLE control variable to enable NVLS UB registration in the “one process, multiple ranks” case as opt in.
* Move nChannelsPerNetPeer into ncclConfig. NCCL_NCHANNELS_PER_NET_PEER can override the value in ncclConfig.
* Enable PxN over C2C by default
* PxN over C2C will improve performance for Grace-Blackwell platforms by allowing NCCL to leverage the NIC attached to a peer GPU over NVLINK, C2C, and PCIe.
* This behavior can be overridden by setting NCCL_PXN_C2C=0.
Other Improvements:
* Allow FP8 support for non-reductive operations on pre sm90 devices. (See https://github.com/pytorch/pytorch/pull/151594#discussion_r2135777776)
* Fix NVLS+CollNet and temporarily disables COLLNET_CHAIN for >8 GPUs.
* Only consider running interfaces for socket traffic. NCCL will not attempt to use interfaces that do not have the IFF_RUNNING bit. (https://github.com/NVIDIA/nccl/issues/1798)
* Modernize mutex management. Convert to std::mutex and std::lock_guard.
* Remove sm35 and sm50 GENCODE targets which have long been deprecated and were causing issues with the latest NCCL release builds.
* Improved NVLS/NVLSTree tuning prediction to improve algorithm and protocol selection.
* NVLSTree Tuning Fixes. Update tuning data for H100, GB200-NV72.
* Respond better to RoCE link flaps. Instead of reporting an “unknown event” it will now report “GID table changed”.
* Move libvirt bridge interface to the end of possible interfaces so that they are considered last. These interfaces are usually virtual bridges to relay traffic to containers running on the host and cannot be used for traffic to a remote node and are therefore unsuitable.
[ROCm/rccl commit: f1308997d0]
Esse commit está contido em:
@@ -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
|
||||
|
||||
@@ -43,7 +43,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;
|
||||
|
||||
int batchIx, nextBatchIx;
|
||||
@@ -323,7 +323,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -351,7 +351,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.
|
||||
@@ -359,14 +359,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);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
|
||||
# Order of redops, tys, protos, algos must match src/include/device.h
|
||||
all_colls = ["Broadcast","Reduce","AllGather","ReduceScatter","AllReduce","SendRecv"]
|
||||
@@ -17,8 +18,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)
|
||||
|
||||
@@ -322,6 +326,16 @@ def partition_by_name(fns):
|
||||
name_to_funcs = partition_by_name(fn for fn in primary_funcs if fn[0]!="Nop")
|
||||
name_to_kernels = partition_by_name(kfn for kfn in kernel_funcs if kfn[0]!="Generic")
|
||||
|
||||
files = ""
|
||||
for name in sorted(name_to_funcs.keys()):
|
||||
files += name + ";"
|
||||
files += "device_table.cu;"
|
||||
files += "host_table.cc"
|
||||
|
||||
# Do not print files when running make
|
||||
if os.environ.get("NCCL_USE_CMAKE", "0") == "1":
|
||||
print(files)
|
||||
|
||||
# Generate <gensrc>/rules.mk
|
||||
with open(os.path.join(gensrc, "rules.mk"), "w") as f:
|
||||
out = f.write
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
#include "symmetric.h"
|
||||
#include "symmetric/kernel.cuh"
|
||||
#include "symmetric/primitives.cuh"
|
||||
#include "sym_kernels.h"
|
||||
#include "kernel.cuh"
|
||||
#include "primitives.cuh"
|
||||
|
||||
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) {
|
||||
@@ -44,21 +45,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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,18 +67,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;
|
||||
@@ -85,14 +85,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;
|
||||
}
|
||||
}
|
||||
@@ -100,95 +100,99 @@ 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) {
|
||||
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)) {
|
||||
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, prim.nBlocks, prim.nBlocks_rcp32);
|
||||
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;
|
||||
|
||||
@@ -227,51 +231,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;
|
||||
@@ -284,7 +289,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]);
|
||||
@@ -299,7 +304,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;
|
||||
@@ -313,7 +318,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;
|
||||
@@ -321,7 +326,7 @@ static __device__ void allgather_LL_body(
|
||||
}
|
||||
#endif
|
||||
|
||||
prim.endLL(cta);
|
||||
lla2a.endEpoch(ncclCoopCta());
|
||||
|
||||
input += tn*EltPerPack;
|
||||
output += tn*EltPerPack;
|
||||
@@ -330,38 +335,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,35 +1,38 @@
|
||||
#include "symmetric.h"
|
||||
#include "symmetric/kernel.cuh"
|
||||
#include "symmetric/primitives.cuh"
|
||||
#include "sym_kernels.h"
|
||||
#include "nccl_device.h"
|
||||
#include "kernel.cuh"
|
||||
#include "primitives.cuh"
|
||||
|
||||
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) {
|
||||
@@ -39,7 +42,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++) {
|
||||
@@ -64,7 +67,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;
|
||||
}
|
||||
@@ -95,22 +98,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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,21 +121,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;
|
||||
@@ -151,7 +156,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;
|
||||
}
|
||||
@@ -179,7 +184,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;
|
||||
}
|
||||
@@ -190,35 +195,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;
|
||||
@@ -226,16 +229,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;
|
||||
@@ -243,46 +247,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);
|
||||
@@ -327,106 +336,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);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,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
|
||||
@@ -10,8 +11,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)
|
||||
|
||||
@@ -94,7 +98,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)
|
||||
@@ -133,13 +137,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:
|
||||
@@ -152,30 +156,30 @@ def instantiate(k):
|
||||
cudart_cond, arch_cond = kernel_conds(k)
|
||||
if (cudart_cond, arch_cond) == (None, None):
|
||||
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"
|
||||
"}}"
|
||||
)
|
||||
else:
|
||||
form_red_ty = (
|
||||
"#if {cudart_cond}\n"
|
||||
" __global__ void {cname}(ncclSymDevArgs NCCL_GRID_CONSTANT const args) {{\n"
|
||||
" __global__ void {cname}(ncclSymkDevWorkArgs4K NCCL_GRID_CONSTANT const args4K) {{\n"
|
||||
" #if {arch_cond}\n"
|
||||
" ncclSymRun_{id}<{red}, {ty}>(&args);\n"
|
||||
" ncclSymkRun_{id}<{red}, {ty}>(&args4K.args);\n"
|
||||
" #endif\n"
|
||||
" }}\n"
|
||||
"#endif"
|
||||
)
|
||||
form = (
|
||||
"#if {cudart_cond}\n"
|
||||
" __global__ void {cname}(ncclSymDevArgs NCCL_GRID_CONSTANT const args) {{\n"
|
||||
" __global__ void {cname}(ncclSymkDevWorkArgs4K NCCL_GRID_CONSTANT const args4K) {{\n"
|
||||
" #if {arch_cond}\n"
|
||||
" ncclSymRun_{id}(&args);\n"
|
||||
" ncclSymkRun_{id}(&args4K.args);\n"
|
||||
" #endif\n"
|
||||
" }}\n"
|
||||
"#endif"
|
||||
@@ -192,11 +196,11 @@ def instantiate(k):
|
||||
def prototype(k):
|
||||
cudart_cond, arch_cond = kernel_conds(k)
|
||||
if cudart_cond is None:
|
||||
form = "__global__ void {cname}(ncclSymDevArgs const);"
|
||||
form = "__global__ void {cname}(ncclSymkDevWorkArgs4K const);"
|
||||
else:
|
||||
form = (
|
||||
"#if {cudart_cond}\n"
|
||||
" __global__ void {cname}(ncclSymDevArgs const);\n"
|
||||
" __global__ void {cname}(ncclSymkDevWorkArgs4K const);\n"
|
||||
"#else\n"
|
||||
" constexpr void* {cname} = nullptr;\n"
|
||||
"#endif"
|
||||
@@ -223,18 +227,20 @@ 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:
|
||||
emitln(f, '#include "symmetric.h"')
|
||||
emitln(f, '#include "sym_kernels.h"')
|
||||
emitln(f, '#include "symmetric/kernel.cuh"')
|
||||
emitln(f, '#include "symmetric/{coll}.cuh"'.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:
|
||||
emitln(f, '#include "symmetric.h"')
|
||||
# Generate <gensrc>/sym_kernels_host.cc
|
||||
with open(os.path.join(gensrc, "sym_kernels_host.cc"), "w") as f:
|
||||
emitln(f, '#include "sym_kernels.h"')
|
||||
emitln(f, '#include "device.h"')
|
||||
emitln(f, '')
|
||||
|
||||
@@ -242,19 +248,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])+';')
|
||||
@@ -277,9 +283,15 @@ with open(os.path.join(gensrc, "symmetric_kernels.cc"), "w") as f:
|
||||
emitln(f, '}')
|
||||
|
||||
# Generate <gensrc>/rules.mk
|
||||
files_to_print += "rules.mk;"
|
||||
files_to_print += "sym_kernels_host.cc;"
|
||||
|
||||
if os.environ.get("NCCL_USE_CMAKE", "0") == "1":
|
||||
print(files_to_print)
|
||||
|
||||
with open(os.path.join(gensrc, "rules.mk"), "w") as f:
|
||||
inst_names = sorted(set(kernel_fname(k) for k in enumerate_kernels()))
|
||||
names = inst_names + ["symmetric_kernels.cc"]
|
||||
names = inst_names + ["sym_kernels_host.cc"]
|
||||
f.write("LIB_OBJS_SYM_GEN = $(patsubst %,$(OBJDIR)/genobj/symmetric/%.o,{names})\n"
|
||||
.format(names=" ".join(names)))
|
||||
f.write("\n")
|
||||
|
||||
@@ -1,27 +1,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
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#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"
|
||||
#include "reduce_kernel.h"
|
||||
#include "../op128.h"
|
||||
#include "../reduce_kernel.h"
|
||||
|
||||
#if __CUDA_ARCH__ >= 700
|
||||
// __grid_constant__ appears to break cuda-gdb
|
||||
@@ -24,397 +24,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
|
||||
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
|
||||
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++) {
|
||||
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++) {
|
||||
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++) {
|
||||
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++) {
|
||||
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++) {
|
||||
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++) {
|
||||
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,35 +1,36 @@
|
||||
#include "symmetric.h"
|
||||
#include "symmetric/kernel.cuh"
|
||||
#include "symmetric/primitives.cuh"
|
||||
#include "sym_kernels.h"
|
||||
#include "kernel.cuh"
|
||||
#include "primitives.cuh"
|
||||
|
||||
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) {
|
||||
@@ -39,7 +40,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++) {
|
||||
@@ -65,7 +66,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;
|
||||
@@ -85,17 +86,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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,20 +104,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;
|
||||
@@ -135,7 +138,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;
|
||||
}
|
||||
@@ -152,26 +155,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;
|
||||
|
||||
@@ -181,12 +183,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;
|
||||
@@ -198,12 +200,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;
|
||||
@@ -211,42 +213,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);
|
||||
@@ -291,41 +298,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
|
||||
@@ -339,17 +357,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;
|
||||
@@ -357,31 +383,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);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário