f1308997d0
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.
438 linhas
16 KiB
Python
Arquivo Executável
438 linhas
16 KiB
Python
Arquivo Executável
#!/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"]
|
|
all_redops = ["Sum","Prod","MinMax","PreMulSum","SumPostDiv"]
|
|
all_tys = ["i8","u8","i32","u32","i64","u64","f16","f32","f64","bf16","f8e4m3","f8e5m2"]
|
|
all_protos = ["LL","LL128","SIMPLE"]
|
|
all_algos = ["TREE","RING","COLLNET_DIRECT","COLLNET_CHAIN","NVLS","NVLS_TREE","PAT"]
|
|
|
|
################################################################################
|
|
# The first command line argument is the path to the directory to generate and
|
|
# populate.
|
|
|
|
gensrc = sys.argv[1]
|
|
|
|
if os.path.exists(gensrc):
|
|
for name in os.listdir(gensrc):
|
|
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)
|
|
|
|
################################################################################
|
|
# The second command line argument is used as a regex to filter the functions
|
|
# which make it into libnccl. This is helpful for reducing the binary when
|
|
# developing device code. The regex supports non-space containing globs '*',
|
|
# parentheses '(x)', and union 'a|b'. The string representing the function has
|
|
# one of the forms:
|
|
#
|
|
# SendRecv
|
|
# (AllGather|Broadcast) <algo> <proto>
|
|
# (AlLReduce|Reduce|ReduceScatter) <redop> <type> <algo> <proto>
|
|
#
|
|
# The possible values for redop, type, algo, proto can be found in the all_<foo>
|
|
# lists at the top of this file.
|
|
#
|
|
# Since the Makefile forwards this from the ONLY_FUNCS variable, useful command
|
|
# line examples are given:
|
|
"""
|
|
# Only send/recv:
|
|
make ONLY_FUNCS="SendRecv"
|
|
|
|
# Only non-reductions:
|
|
make ONLY_FUNCS="AllGather * *|Broadcast * *|SendRecv"
|
|
|
|
# Only AllReduce sum f32 (but all algos, protos)
|
|
make ONLY_FUNCS="AllReduce Sum f32 * *"
|
|
|
|
# Only AllReduce minmax i32 NVLS (but all protos)
|
|
make ONLY_FUNCS="AllReduce MinMax i32 NVLS *"
|
|
|
|
# AllReduce sum <all floats> RING LL128
|
|
make ONLY_FUNCS="AllReduce Sum f32 RING LL128"
|
|
"""
|
|
|
|
# Paste all non-None arguments together with `sep`.
|
|
def paste(sep, *args):
|
|
return sep.join(x for x in args if x is not None)
|
|
|
|
func_pattern = sys.argv[2:3]
|
|
if func_pattern and func_pattern[0]:
|
|
import re
|
|
func_pattern = func_pattern[0]
|
|
func_pattern = func_pattern.replace("*", "[^ ]*")
|
|
func_pattern += "$"
|
|
def func_filter(*fn):
|
|
return None is not re.match(func_pattern, paste(" ", *fn), flags=re.IGNORECASE)
|
|
else:
|
|
def func_filter(coll, redop, ty, algo, proto):
|
|
return True
|
|
|
|
################################################################################
|
|
|
|
algos_of_coll = {
|
|
"AllGather": ["RING","COLLNET_DIRECT","NVLS","PAT"],
|
|
"AllReduce": ["TREE","RING","COLLNET_DIRECT","COLLNET_CHAIN","NVLS","NVLS_TREE"],
|
|
"Broadcast": ["RING"],
|
|
"Reduce": ["RING"],
|
|
"ReduceScatter": ["RING","COLLNET_DIRECT","NVLS","PAT"],
|
|
"SendRecv": [None]
|
|
}
|
|
|
|
coll_camel_to_lower = {
|
|
"AllGather": "all_gather",
|
|
"AllReduce": "all_reduce",
|
|
"Broadcast": "broadcast",
|
|
"Reduce": "reduce",
|
|
"ReduceScatter": "reduce_scatter",
|
|
"SendRecv": "sendrecv"
|
|
}
|
|
coll_lower_to_camel = {coll_camel_to_lower[x]: x for x in coll_camel_to_lower}
|
|
|
|
################################################################################
|
|
|
|
# Returns pair of minimum required values for (CUDART_VERSION, __CUDA_ARCH__)
|
|
# or None if function is never supported. Note that (0, 0) encodes universal
|
|
# support.
|
|
def required_cuda(coll, redop, ty, algo, proto):
|
|
cudart, arch = 0, 0
|
|
# kernels mapped to by coll="Nop" functions have coll="Generic"
|
|
if coll in ("SendRecv", "Generic", "Nop"): return (cudart, arch)
|
|
|
|
if proto!="SIMPLE" and algo not in ("RING","TREE"): return None
|
|
|
|
if coll in ("AllReduce","Reduce","ReduceScatter"):
|
|
if redop=="SumPostDiv" and ty[0] not in ("i","u"): return None
|
|
if ty=="bf16": cudart = max(cudart, 11000)
|
|
if ty.startswith("f8"):
|
|
cudart = max(cudart, 11080)
|
|
arch = max(arch, 900)
|
|
|
|
if "NVLS" in algo:
|
|
if coll in ("AllReduce","Reduce","ReduceScatter"):
|
|
# Must match ncclNvlsSupported() in src/include/device.h
|
|
nvls_ok = ((ty in ("i32","u32","i64","u64") and redop in ("Sum","MinMax")) or
|
|
(ty in ("f32","f64") and redop=="Sum") or
|
|
(ty in ("f16","bf16") and redop in ("Sum","MinMax")))
|
|
if not nvls_ok: return None
|
|
cudart = max(cudart, 12010)
|
|
arch = max(arch, 900)
|
|
|
|
return (cudart, arch)
|
|
|
|
# Maps functions to the chosen representative for the equivalence class it
|
|
# belongs to. For instance (sum, signed int) maps to (sum, unsigned int).
|
|
def equivalent_primary(coll, redop, ty, algo, proto):
|
|
if coll in ("AllReduce", "Reduce", "ReduceScatter"):
|
|
# map signed integer sum/prod to unsigned
|
|
if redop in ("Sum","Prod","PreMulSum","SumPostDiv") and ty[0]=="i":
|
|
return (coll, redop, "u"+ty[1:], algo, proto)
|
|
# map signed integer min/max to unsigned for non-NVLS
|
|
if redop=="MinMax" and ty[0]=="i" and ("NVLS" not in algo):
|
|
return (coll, redop, "u"+ty[1:], algo, proto)
|
|
return (coll, redop, ty, algo, proto)
|
|
|
|
# Map to another func representing the best kernel to use. Every distinct value
|
|
# returned will instantiate a ncclDevKernel specialized to run this func
|
|
# without function call overhead.
|
|
def best_kernel(coll, redop, ty, algo, proto):
|
|
def best(coll, redop, ty, algo, proto):
|
|
# Modify this logic to control how many kernels are specialized.
|
|
if coll=="Nop": return ("Generic", None, None, None, None)
|
|
if coll=="SendRecv": return ("SendRecv", None, None, None, None)
|
|
if coll in ("AllGather","Broadcast"): return (coll, None, None, "RING", "LL")
|
|
return (coll, "Sum", ty, ("TREE" if algo=="TREE" else "RING"), "LL")
|
|
# Need to ensure kernel is specialize for a primary function
|
|
kfn = equivalent_primary(*best(coll, redop, ty, algo, proto))
|
|
# And isn't filtered out.
|
|
if not func_filter(*kfn): return ("Generic", None, None, None, None)
|
|
return kfn
|
|
|
|
# Order rows are enumerated must match formula of `ncclDevFuncId()`:
|
|
def enumerate_func_rows():
|
|
yield ("SendRecv", None, None, None, None)
|
|
for coll in ("AllGather", "Broadcast"):
|
|
algos = algos_of_coll[coll]
|
|
for algo in algos:
|
|
for proto in all_protos:
|
|
yield (coll, None, None, algo, proto)
|
|
for coll in ("AllReduce", "Reduce", "ReduceScatter"):
|
|
algos = algos_of_coll[coll]
|
|
for redop in all_redops:
|
|
for ty in all_tys:
|
|
for algo in algos:
|
|
for proto in all_protos:
|
|
yield (coll, redop, ty, algo, proto)
|
|
|
|
################################################################################
|
|
|
|
def is_built(coll, redop, ty, algo, proto):
|
|
built = required_cuda(coll, redop, ty, algo, proto)
|
|
built = built and func_filter(coll, redop, ty, algo, proto)
|
|
return built
|
|
|
|
# Returns None if required_cuda(...) is None.
|
|
# Returns the coll="Nop" function if developer has filtered it out.
|
|
# Otherwise just returns func it was given.
|
|
def validate(coll, redop, ty, algo, proto):
|
|
valid = required_cuda(coll, redop, ty, algo, proto)
|
|
built = valid and func_filter(coll, redop, ty, algo, proto)
|
|
if built: return (coll, redop, ty, algo, proto)
|
|
if valid: return ("Nop", None, None, None, None)
|
|
return None
|
|
|
|
# Corresponds to ncclDevFuncRowToId[]
|
|
func_rows = [validate(*fn) for fn in enumerate_func_rows()]
|
|
|
|
# Corresponds to ncclDevFuncTable[]
|
|
primary_funcs = sorted(set(equivalent_primary(*fn) for fn in func_rows if fn is not None))
|
|
|
|
# primary_to_index[primary_funcs[i]] == i
|
|
primary_to_index = {fn: i for (i,fn) in zip(range(len(primary_funcs)), primary_funcs)}
|
|
|
|
kernel_funcs = sorted(set(best_kernel(*fn) for fn in primary_funcs))
|
|
|
|
################################################################################
|
|
|
|
# Generate <gensrc>/device_table.cu
|
|
with open(os.path.join(gensrc, "device_table.cu"), "w") as f:
|
|
out = f.write
|
|
out('#include "common.h"\n')
|
|
out("\n")
|
|
|
|
for fn in primary_funcs:
|
|
sym = paste("_", "ncclDevFunc", *fn)
|
|
cudart, arch = required_cuda(*fn)
|
|
if (cudart, arch) != (0, 0):
|
|
out("#if CUDART_VERSION >= %d && __CUDA_ARCH__ >= %d\n" % (cudart, arch))
|
|
out("__device__ void %s();\n" % sym)
|
|
if (cudart, arch) != (0, 0):
|
|
out("#endif\n")
|
|
out("\n")
|
|
|
|
out("__device__ ncclDevFuncPtr_t const ncclDevFuncTable[] = {\n");
|
|
index = 0
|
|
for fn in primary_funcs:
|
|
sym = paste("_", "ncclDevFunc", *fn)
|
|
cudart, arch = required_cuda(*fn)
|
|
if (cudart, arch) != (0, 0):
|
|
out("#if CUDART_VERSION >= %d && __CUDA_ARCH__ >= %d\n" % (cudart ,arch))
|
|
out("/*%4d*/ %s,\n" % (index, sym))
|
|
if (cudart, arch) != (0, 0):
|
|
out("#else\n" "/*%4d*/ nullptr,\n" "#endif\n" % index)
|
|
index += 1
|
|
out("nullptr};\n")
|
|
out("\n")
|
|
|
|
out("// Workaround for https://reviews.llvm.org/D55580\n"
|
|
"__device__ void ncclWorkaroundClangD55580() {}\n")
|
|
|
|
# Generate <gensrc>/host_table.cc
|
|
with open(os.path.join(gensrc, "host_table.cc"), "w") as f:
|
|
out = f.write
|
|
out('#include "device.h"\n')
|
|
out("\n")
|
|
|
|
out("extern int const ncclDevFuncIdCount = %d;\n" % len(primary_funcs))
|
|
|
|
# The mapping from function rows to valid primary function ids.
|
|
out("extern int const ncclDevFuncRowToId[] = {\n")
|
|
index = 0
|
|
for fn in func_rows:
|
|
fn_id, comment = -1, ""
|
|
if fn is not None:
|
|
fn_id = primary_to_index[equivalent_primary(*fn)]
|
|
comment = " // " + paste(" ", *fn)
|
|
out("/*%4d*/ %d,%s\n" % (index, fn_id, comment))
|
|
index += 1
|
|
out("-1};\n")
|
|
out("\n")
|
|
|
|
# Forward declarations of kernels.
|
|
for kfn in kernel_funcs:
|
|
cudart, _ = required_cuda(*kfn)
|
|
sym = paste("_", "ncclDevKernel", *kfn)
|
|
if cudart != 0: out("#if CUDART_VERSION >= %d\n" % cudart)
|
|
# __global__ below gets removed by the host compiler, which results in
|
|
# Coverity diagnosing a specifiers inconsistency.
|
|
out("// coverity[declaration]\n")
|
|
out("__global__ void %s(ncclDevKernelArgs4K const);\n" % sym)
|
|
if cudart != 0: out("#endif\n")
|
|
out("\n")
|
|
|
|
# List of all kernel function pointers.
|
|
out("extern int const ncclDevKernelCount = %d;\n" % len(kernel_funcs))
|
|
out("extern void* const ncclDevKernelList[] = {\n")
|
|
index = 0
|
|
for kfn in kernel_funcs:
|
|
cudart, _ = required_cuda(*kfn)
|
|
sym = paste("_", "ncclDevKernel", *kfn)
|
|
if cudart != 0: out("#if CUDART_VERSION >= %d\n" % cudart)
|
|
out("/*%4d*/ (void*)%s,\n" % (index, sym));
|
|
if cudart != 0: out("#else\n" "/*%4d*/ nullptr,\n" "#endif\n" % index)
|
|
index += 1
|
|
out("nullptr};\n")
|
|
out("\n")
|
|
|
|
# Maps primary id to kernel function pointer.
|
|
out("extern void* const ncclDevKernelForFunc[] = {\n")
|
|
index = 0
|
|
for fn in primary_funcs:
|
|
kfn = best_kernel(*fn)
|
|
sym = paste("_", "ncclDevKernel", *kfn)
|
|
cudart, _ = required_cuda(*kfn)
|
|
if cudart != 0: out("#if CUDART_VERSION >= %d\n" % cudart)
|
|
out("/*%4d*/ (void*)%s,\n" % (index, sym))
|
|
if cudart != 0: out("#else\n" "/*%4d*/ nullptr,\n" "#endif\n" % index)
|
|
index += 1
|
|
out("nullptr};\n")
|
|
out("\n")
|
|
|
|
# Does the prior map use an explicitly specialized kernel.
|
|
out("extern bool const ncclDevKernelForFuncIsSpecialized[] = {\n")
|
|
index = 0
|
|
for fn in primary_funcs:
|
|
kfn = best_kernel(*fn)
|
|
specialized = "1" if fn == kfn else "0"
|
|
out("/*%4d*/ %s,\n" % (index, specialized))
|
|
index += 1
|
|
out("0};\n")
|
|
|
|
# Maps to .cu filename which implements this func. The only constraint is that
|
|
# "coll" is reflected in the name: formally that no two funcs having different
|
|
# coll's map to the same filename.
|
|
def impl_filename(coll, redop, ty, algo, proto):
|
|
return "%s.cu" % paste("_", coll_camel_to_lower[coll], redop and redop.lower(), ty)
|
|
|
|
# Partition the functions and kernels to the .cu filenames. The partition is
|
|
# a dictionary mapping filename to (coll, func-tuple list)
|
|
def partition_by_name(fns):
|
|
ans = {}
|
|
for fn in fns:
|
|
name = impl_filename(*fn)
|
|
coll = fn[0]
|
|
if name not in ans:
|
|
ans[name] = (coll, [])
|
|
ans[name][1].append(fn)
|
|
return ans
|
|
|
|
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
|
|
impl_names = sorted(name_to_funcs.keys())
|
|
names = impl_names + ["host_table.cc", "device_table.cu"]
|
|
out("LIB_OBJS_GEN = $(patsubst %,$(OBJDIR)/genobj/%.o,{names})\n"
|
|
.format(names=" ".join(names)))
|
|
out("\n")
|
|
|
|
# For each <coll>_<op>_<ty>.cu compile to a .cu.o file. Notice the dependencies
|
|
# come from the suffix-erased file (e.g. 'gensrc/all_reduce.cu')
|
|
for name in impl_names:
|
|
coll = name_to_funcs[name][0]
|
|
out(
|
|
"$(OBJDIR)/genobj/{name}.o: $(OBJDIR)/gensrc $(OBJDIR)/genobj/{lower_coll}.cu.d\n"
|
|
"\t" "$(call COMPILE,$@,$(OBJDIR)/gensrc/{name})\n"
|
|
"\n"
|
|
.format(name=name, lower_coll=coll_camel_to_lower[coll])
|
|
)
|
|
|
|
# Add the suffix-erased .cu's which are used only for dependency scraping.
|
|
for coll in set(coll for (coll,_,_,_,_) in primary_funcs if coll!="Nop"):
|
|
name = impl_filename(coll, None, None, None, None)
|
|
if name not in name_to_funcs:
|
|
name_to_funcs[name] = (coll, [])
|
|
|
|
redop_to_cxx = {
|
|
None: "FuncCopy",
|
|
"Sum": "FuncSum",
|
|
"Prod": "FuncProd",
|
|
"MinMax": "FuncMinMax",
|
|
"PreMulSum": "FuncPreMulSum",
|
|
"SumPostDiv": "FuncSumPostDiv"
|
|
}
|
|
|
|
ty_to_cxx = {
|
|
None: "int8_t",
|
|
"i8": "int8_t",
|
|
"u8": "uint8_t",
|
|
"i32": "int32_t",
|
|
"u32": "uint32_t",
|
|
"i64": "int64_t",
|
|
"u64": "uint64_t",
|
|
"f16": "half",
|
|
"f32": "float",
|
|
"f64": "double",
|
|
"bf16": "__nv_bfloat16",
|
|
"f8e4m3": "__nv_fp8_e4m3",
|
|
"f8e5m2": "__nv_fp8_e5m2"
|
|
}
|
|
|
|
# Generate each <gensrc>/<impl>.cu:
|
|
for name in name_to_funcs.keys():
|
|
(coll, fns) = name_to_funcs[name]
|
|
with open(os.path.join(gensrc, name), "w") as f:
|
|
out = f.write
|
|
out(
|
|
'#include "common.h"\n'
|
|
'#include "{lower_coll}.h"\n'
|
|
.format(lower_coll=coll_camel_to_lower[coll])
|
|
)
|
|
|
|
(_, kfns) = name_to_kernels.get(name) or (None, [])
|
|
for kfn in kfns:
|
|
(coll, redop, ty, algo, proto) = kfn
|
|
sym = paste("_", coll, redop, ty, algo, proto)
|
|
fn_id = primary_to_index[kfn]
|
|
cudart, arch = required_cuda(*kfn)
|
|
s = "DEFINE_ncclDevKernel({sym}, ncclFunc{coll}, {redop_cxx}, {ty_cxx}, NCCL_ALGO_{algo}, NCCL_PROTO_{proto}, {fn_id})\n"
|
|
if (cudart, arch) != (0, 0):
|
|
# Add conditional compilation logic around s. If CUDART_VERSION is satisfactory
|
|
# we must compile a kernel regardless of __CUDA_ARCH__ since the host code has
|
|
# to link against some stub.
|
|
s = "#if CUDART_VERSION >= {cudart}\n" \
|
|
" #if __CUDA_ARCH__ < {arch}\n" \
|
|
" DEFINE_ncclDevKernel_nop({sym}, ncclFunc{coll}, {redop_cxx}, {ty_cxx}, NCCL_ALGO_{algo}, NCCL_PROTO_{proto}, {fn_id})\n" \
|
|
" #else\n" \
|
|
" " + s + \
|
|
" #endif\n" \
|
|
"#endif\n"
|
|
out(s.format(
|
|
cudart=cudart, arch=arch, sym=sym, coll=coll,
|
|
redop_cxx=redop_to_cxx[redop], ty_cxx=ty_to_cxx[ty],
|
|
algo=(algo or "RING"), proto=(proto or "SIMPLE"), fn_id=fn_id
|
|
))
|
|
|
|
for fn in fns:
|
|
(coll, redop, ty, algo, proto) = fn
|
|
sym = paste("_", coll, redop, ty, algo, proto)
|
|
cudart, arch = required_cuda(*fn)
|
|
if (cudart, arch) != (0, 0):
|
|
out("#if CUDART_VERSION >= %d && __CUDA_ARCH__ >= %d\n" % (cudart, arch))
|
|
out(
|
|
"DEFINE_ncclDevFunc({sym}, ncclFunc{coll}, {redop_cxx}, {ty_cxx}, NCCL_ALGO_{algo}, NCCL_PROTO_{proto})\n"
|
|
.format(sym=sym, coll=coll, redop_cxx=redop_to_cxx[redop], ty_cxx=ty_to_cxx[ty],
|
|
algo=(algo or "RING"), proto=(proto or "SIMPLE"))
|
|
)
|
|
if (cudart, arch) != (0, 0):
|
|
out("#endif\n")
|