| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | |
| 4 | # This file is a pure Python wrapper for the NCCL library. |
| 5 | # The main purpose is to use NCCL combined with CUDA graph. |
| 6 | # Before writing this script, we tried the following approach: |
| 7 | # 1. We tried to use `cupy`, it calls NCCL correctly, but `cupy` itself |
| 8 | # often gets stuck when initializing the NCCL communicator. |
| 9 | # 2. We tried to use `torch.distributed`, but `torch.distributed.all_reduce` |
| 10 | # contains many other potential cuda APIs, that are not allowed during |
| 11 | # capturing the CUDA graph. For further details, please check |
| 12 | # https://discuss.pytorch.org/t/pytorch-cudagraph-with-nccl-operation-failed/ . |
| 13 | # |
| 14 | # Another rejected idea is to write a C/C++ binding for NCCL. It is usually |
| 15 | # doable, but we often encounter issues related with nccl versions, and need |
| 16 | # to switch between different versions of NCCL. See |
| 17 | # https://github.com/NVIDIA/nccl/issues/1234 for more details. |
| 18 | # A C/C++ binding is not flexible enough to handle this. It requires |
| 19 | # recompilation of the code every time we want to switch between different |
| 20 | # versions. This current implementation, with a **pure** Python wrapper, is |
| 21 | # more flexible. We can easily switch between different versions of NCCL by |
| 22 | # changing the environment variable `VLLM_NCCL_SO_PATH`, or the `so_file` |
| 23 | # variable in the code. |
| 24 | |
| 25 | import ctypes |
| 26 | import functools |
| 27 | import platform |
| 28 | from dataclasses import dataclass |
| 29 | from typing import Any |
| 30 | |
| 31 | import torch |
| 32 | from torch.distributed import ReduceOp |
| 33 | |
| 34 | from vllm import envs |
| 35 | from vllm.logger import init_logger |
| 36 | from vllm.platforms import current_platform |
| 37 | from vllm.utils.nccl import find_nccl_library |
| 38 | |
| 39 | logger = init_logger(__name__) |
| 40 | |
| 41 | # === export types and functions from nccl to Python === |
| 42 | # for the original nccl definition, please check |
| 43 | # https://github.com/NVIDIA/nccl/blob/master/src/nccl.h.in |
| 44 | |
| 45 | ncclResult_t = ctypes.c_int |
| 46 | ncclComm_t = ctypes.c_void_p |
| 47 | ncclWindow_t = ctypes.c_void_p |
| 48 | |
| 49 | |
| 50 | class ncclUniqueId(ctypes.Structure): |
| 51 | _fields_ = [("internal", ctypes.c_byte * 128)] |
| 52 | |
| 53 | |
| 54 | NCCL_UNIQUE_ID_BYTES = ctypes.sizeof(ncclUniqueId) |
| 55 | |
| 56 | |
| 57 | # Mirror of NCCL's versioned ncclCommProperties_t (nccl_device/core.h, |
| 58 | # v2.31.2-1, 144 bytes). ncclCommQueryProperties fills fields gated by the |
| 59 | # version the caller declares in props.version, not by props.size, so never |
| 60 | # declare a version newer than this layout: clamp to |
| 61 | # NCCL_COMM_PROPERTIES_LAYOUT_VERSION. To use fields added in a newer NCCL, |
| 62 | # extend the layout and bump the constant. |
| 63 | NCCL_COMM_PROPERTIES_LAYOUT_VERSION = 23102 # NCCL_VERSION(2, 31, 2) |
| 64 | |
| 65 | |
| 66 | class ncclCommProperties(ctypes.Structure): |
| 67 | _fields_ = [ |
| 68 | ("size", ctypes.c_size_t), |
| 69 | ("magic", ctypes.c_uint), |
| 70 | ("version", ctypes.c_uint), |
| 71 | ("rank", ctypes.c_int), |
| 72 | ("nRanks", ctypes.c_int), |
| 73 | ("cudaDev", ctypes.c_int), |
| 74 | ("nvmlDev", ctypes.c_int), |
| 75 | ("deviceApiSupport", ctypes.c_bool), |
| 76 | ("multimemSupport", ctypes.c_bool), |
| 77 | ("ginType", ctypes.c_int), |
| 78 | ("nLsaTeams", ctypes.c_int), |
| 79 | ("hostRmaSupport", ctypes.c_bool), |
| 80 | ("railedGinType", ctypes.c_int), |
| 81 | # Filled only when the declared version is >= NCCL_VERSION(2, 31, 0). |
| 82 | ("commHash", ctypes.c_uint64), |
| 83 | ("ginMinStride", ctypes.c_int), |
| 84 | ("ginConnectionType", ctypes.c_int), |
| 85 | ("ginSupport", ctypes.c_bool * 64), |
| 86 | ("devCommRuntimeVersionSize", ctypes.c_size_t), |
| 87 | ] |
| 88 | |
| 89 | |
| 90 | cudaStream_t = ctypes.c_void_p |
| 91 | buffer_type = ctypes.c_void_p |
| 92 | |
| 93 | ncclDataType_t = ctypes.c_int |
| 94 | |
| 95 | |
| 96 | class ncclDataTypeEnum: |
| 97 | ncclInt8 = 0 |
| 98 | ncclChar = 0 |
| 99 | ncclUint8 = 1 |
| 100 | ncclInt32 = 2 |
| 101 | ncclInt = 2 |
| 102 | ncclUint32 = 3 |
| 103 | ncclInt64 = 4 |
| 104 | ncclUint64 = 5 |
| 105 | ncclFloat16 = 6 |
| 106 | ncclHalf = 6 |
| 107 | ncclFloat32 = 7 |
| 108 | ncclFloat = 7 |
| 109 | ncclFloat64 = 8 |
| 110 | ncclDouble = 8 |
| 111 | ncclBfloat16 = 9 |
| 112 | ncclFloat8e4m3 = 10 |
| 113 | ncclNumTypes = 11 |
| 114 | |
| 115 | @classmethod |
| 116 | @functools.lru_cache(maxsize=1) |
| 117 | def _torch_to_nccl_map(cls) -> dict[torch.dtype, int]: |
| 118 | return { |
| 119 | torch.int8: cls.ncclInt8, |
| 120 | torch.uint8: cls.ncclUint8, |
| 121 | torch.int32: cls.ncclInt32, |
| 122 | torch.int64: cls.ncclInt64, |
| 123 | torch.float16: cls.ncclFloat16, |
| 124 | torch.float32: cls.ncclFloat32, |
| 125 | torch.float64: cls.ncclFloat64, |
| 126 | torch.bfloat16: cls.ncclBfloat16, |
| 127 | current_platform.fp8_dtype(): cls.ncclFloat8e4m3, |
| 128 | } |
| 129 | |
| 130 | @classmethod |
| 131 | def supports_torch_dtype(cls, dtype: torch.dtype) -> bool: |
| 132 | return dtype in cls._torch_to_nccl_map() |
| 133 | |
| 134 | @classmethod |
| 135 | def try_from_torch(cls, dtype: torch.dtype) -> int | None: |
| 136 | return cls._torch_to_nccl_map().get(dtype) |
| 137 | |
| 138 | @classmethod |
| 139 | def from_torch(cls, dtype: torch.dtype) -> int: |
| 140 | nccl_dtype = cls.try_from_torch(dtype) |
| 141 | if nccl_dtype is not None: |
| 142 | return nccl_dtype |
| 143 | raise ValueError( |
| 144 | f"Unsupported dtype {dtype}: should be one of " |
| 145 | f"int8, uint8, int32, int64, float16, float32, float64, bfloat16," |
| 146 | " float8e4m3." |
| 147 | ) |
| 148 | |
| 149 | |
| 150 | ncclRedOp_t = ctypes.c_int |
| 151 | |
| 152 | |
| 153 | class ncclRedOpTypeEnum: |
| 154 | ncclSum = 0 |
| 155 | ncclProd = 1 |
| 156 | ncclMax = 2 |
| 157 | ncclMin = 3 |
| 158 | ncclAvg = 4 |
| 159 | ncclNumOps = 5 |
| 160 | |
| 161 | @classmethod |
| 162 | def from_torch(cls, op: ReduceOp) -> int: |
| 163 | if op == ReduceOp.SUM: |
| 164 | return cls.ncclSum |
| 165 | if op == ReduceOp.PRODUCT: |
| 166 | return cls.ncclProd |
| 167 | if op == ReduceOp.MAX: |
| 168 | return cls.ncclMax |
| 169 | if op == ReduceOp.MIN: |
| 170 | return cls.ncclMin |
| 171 | if op == ReduceOp.AVG: |
| 172 | return cls.ncclAvg |
| 173 | raise ValueError(f"Unsupported op: {op}") |
| 174 | |
| 175 | |
| 176 | @dataclass |
| 177 | class Function: |
| 178 | name: str |
| 179 | restype: Any |
| 180 | argtypes: list[Any] |
| 181 | |
| 182 | |
| 183 | class NCCLLibrary: |
| 184 | exported_functions = [ |
| 185 | # const char* ncclGetErrorString(ncclResult_t result) |
| 186 | Function("ncclGetErrorString", ctypes.c_char_p, [ncclResult_t]), |
| 187 | # ncclResult_t ncclGetVersion(int *version); |
| 188 | Function("ncclGetVersion", ncclResult_t, [ctypes.POINTER(ctypes.c_int)]), |
| 189 | # ncclResult_t ncclGetUniqueId(ncclUniqueId* uniqueId); |
| 190 | Function("ncclGetUniqueId", ncclResult_t, [ctypes.POINTER(ncclUniqueId)]), |
| 191 | # ncclResult_t ncclCommInitRank( |
| 192 | # ncclComm_t* comm, int nranks, ncclUniqueId commId, int rank); |
| 193 | # note that ncclComm_t is a pointer type, so the first argument |
| 194 | # is a pointer to a pointer |
| 195 | Function( |
| 196 | "ncclCommInitRank", |
| 197 | ncclResult_t, |
| 198 | [ctypes.POINTER(ncclComm_t), ctypes.c_int, ncclUniqueId, ctypes.c_int], |
| 199 | ), |
| 200 | # ncclResult_t ncclAllReduce( |
| 201 | # const void* sendbuff, void* recvbuff, size_t count, |
| 202 | # ncclDataType_t datatype, ncclRedOp_t op, ncclComm_t comm, |
| 203 | # cudaStream_t stream); |
| 204 | # note that cudaStream_t is a pointer type, so the last argument |
| 205 | # is a pointer |
| 206 | Function( |
| 207 | "ncclAllReduce", |
| 208 | ncclResult_t, |
| 209 | [ |
| 210 | buffer_type, |
| 211 | buffer_type, |
| 212 | ctypes.c_size_t, |
| 213 | ncclDataType_t, |
| 214 | ncclRedOp_t, |
| 215 | ncclComm_t, |
| 216 | cudaStream_t, |
| 217 | ], |
| 218 | ), |
| 219 | # ncclResult_t ncclReduce( |
| 220 | # const void* sendbuff, void* recvbuff, size_t count, |
| 221 | # ncclDataType_t datatype, ncclRedOp_t op, int root, |
| 222 | # ncclComm_t comm, cudaStream_t stream); |
| 223 | # note that cudaStream_t is a pointer type, so the last argument |
| 224 | # is a pointer |
| 225 | Function( |
| 226 | "ncclReduce", |
| 227 | ncclResult_t, |
| 228 | [ |
| 229 | buffer_type, |
| 230 | buffer_type, |
| 231 | ctypes.c_size_t, |
| 232 | ncclDataType_t, |
| 233 | ncclRedOp_t, |
| 234 | ctypes.c_int, |
| 235 | ncclComm_t, |
| 236 | cudaStream_t, |
| 237 | ], |
| 238 | ), |
| 239 | # ncclResult_t ncclAllGather( |
| 240 | # const void* sendbuff, void* recvbuff, size_t count, |
| 241 | # ncclDataType_t datatype, ncclComm_t comm, |
| 242 | # cudaStream_t stream); |
| 243 | # note that cudaStream_t is a pointer type, so the last argument |
| 244 | # is a pointer |
| 245 | Function( |
| 246 | "ncclAllGather", |
| 247 | ncclResult_t, |
| 248 | [ |
| 249 | buffer_type, |
| 250 | buffer_type, |
| 251 | ctypes.c_size_t, |
| 252 | ncclDataType_t, |
| 253 | ncclComm_t, |
| 254 | cudaStream_t, |
| 255 | ], |
| 256 | ), |
| 257 | # ncclResult_t ncclReduceScatter( |
| 258 | # const void* sendbuff, void* recvbuff, size_t count, |
| 259 | # ncclDataType_t datatype, ncclRedOp_t op, ncclComm_t comm, |
| 260 | # cudaStream_t stream); |
| 261 | # note that cudaStream_t is a pointer type, so the last argument |
| 262 | # is a pointer |
| 263 | Function( |
| 264 | "ncclReduceScatter", |
| 265 | ncclResult_t, |
| 266 | [ |
| 267 | buffer_type, |
| 268 | buffer_type, |
| 269 | ctypes.c_size_t, |
| 270 | ncclDataType_t, |
| 271 | ncclRedOp_t, |
| 272 | ncclComm_t, |
| 273 | cudaStream_t, |
| 274 | ], |
| 275 | ), |
| 276 | # ncclResult_t ncclSend( |
| 277 | # const void* sendbuff, size_t count, ncclDataType_t datatype, |
| 278 | # int dest, ncclComm_t comm, cudaStream_t stream); |
| 279 | Function( |
| 280 | "ncclSend", |
| 281 | ncclResult_t, |
| 282 | [ |
| 283 | buffer_type, |
| 284 | ctypes.c_size_t, |
| 285 | ncclDataType_t, |
| 286 | ctypes.c_int, |
| 287 | ncclComm_t, |
| 288 | cudaStream_t, |
| 289 | ], |
| 290 | ), |
| 291 | # ncclResult_t ncclRecv( |
| 292 | # void* recvbuff, size_t count, ncclDataType_t datatype, |
| 293 | # int src, ncclComm_t comm, cudaStream_t stream); |
| 294 | Function( |
| 295 | "ncclRecv", |
| 296 | ncclResult_t, |
| 297 | [ |
| 298 | buffer_type, |
| 299 | ctypes.c_size_t, |
| 300 | ncclDataType_t, |
| 301 | ctypes.c_int, |
| 302 | ncclComm_t, |
| 303 | cudaStream_t, |
| 304 | ], |
| 305 | ), |
| 306 | # ncclResult_t ncclBroadcast( |
| 307 | # const void* sendbuff, void* recvbuff, size_t count, |
| 308 | # ncclDataType_t datatype, int root, ncclComm_t comm, |
| 309 | # cudaStream_t stream); |
| 310 | Function( |
| 311 | "ncclBroadcast", |
| 312 | ncclResult_t, |
| 313 | [ |
| 314 | buffer_type, |
| 315 | buffer_type, |
| 316 | ctypes.c_size_t, |
| 317 | ncclDataType_t, |
| 318 | ctypes.c_int, |
| 319 | ncclComm_t, |
| 320 | cudaStream_t, |
| 321 | ], |
| 322 | ), |
| 323 | # be cautious! this is a collective call, it will block until all |
| 324 | # processes in the communicator have called this function. |
| 325 | # because Python object destruction can happen in random order, |
| 326 | # it is better not to call it at all. |
| 327 | # ncclResult_t ncclCommDestroy(ncclComm_t comm); |
| 328 | Function("ncclCommDestroy", ncclResult_t, [ncclComm_t]), |
| 329 | # ncclCommAbort frees resources associated with the communicator |
| 330 | # without requiring a collective synchronization. Unlike |
| 331 | # ncclCommDestroy, it is safe to call during an uncoordinated |
| 332 | # shutdown when peer ranks may already be gone. |
| 333 | # ncclResult_t ncclCommAbort(ncclComm_t comm); |
| 334 | Function("ncclCommAbort", ncclResult_t, [ncclComm_t]), |
| 335 | # ncclResult_t ncclCommSuspend(ncclComm_t comm, int flags); |
| 336 | Function("ncclCommSuspend", ncclResult_t, [ncclComm_t, ctypes.c_int]), |
| 337 | # ncclResult_t ncclCommResume(ncclComm_t comm); |
| 338 | Function("ncclCommResume", ncclResult_t, [ncclComm_t]), |
| 339 | # ncclResult_t ncclGroupStart(); |
| 340 | Function("ncclGroupStart", ncclResult_t, []), |
| 341 | # ncclResult_t ncclGroupEnd(); |
| 342 | Function("ncclGroupEnd", ncclResult_t, []), |
| 343 | # ncclResult_t ncclCommWindowRegister( |
| 344 | # ncclComm_t comm, void* buff, size_t size, |
| 345 | # ncclWindow_t* win, int winFlags); |
| 346 | Function( |
| 347 | "ncclCommWindowRegister", |
| 348 | ncclResult_t, |
| 349 | [ |
| 350 | ncclComm_t, |
| 351 | buffer_type, |
| 352 | ctypes.c_size_t, |
| 353 | ctypes.POINTER(ncclWindow_t), |
| 354 | ctypes.c_int, |
| 355 | ], |
| 356 | ), |
| 357 | # ncclResult_t ncclCommWindowDeregister( |
| 358 | # ncclComm_t comm, ncclWindow_t win); |
| 359 | Function("ncclCommWindowDeregister", ncclResult_t, [ncclComm_t, ncclWindow_t]), |
| 360 | # Query runtime properties of a specific initialized communicator. |
| 361 | Function( |
| 362 | "ncclCommQueryProperties", |
| 363 | ncclResult_t, |
| 364 | [ncclComm_t, ctypes.POINTER(ncclCommProperties)], |
| 365 | ), |
| 366 | ] |
| 367 | |
| 368 | # class attribute to store the mapping from the path to the library |
| 369 | # to avoid loading the same library multiple times |
| 370 | path_to_library_cache: dict[str, Any] = {} |
| 371 | |
| 372 | # class attribute to store the mapping from library path |
| 373 | # to the corresponding dictionary |
| 374 | path_to_dict_mapping: dict[str, dict[str, Any]] = {} |
| 375 | |
| 376 | def __init__(self, so_file: str | None = None): |
| 377 | so_file = so_file or find_nccl_library() |
| 378 | |
| 379 | try: |
| 380 | if so_file not in NCCLLibrary.path_to_dict_mapping: |
| 381 | lib = ctypes.CDLL(so_file) |
| 382 | NCCLLibrary.path_to_library_cache[so_file] = lib |
| 383 | self.lib = NCCLLibrary.path_to_library_cache[so_file] |
| 384 | except Exception as e: |
| 385 | logger.error( |
| 386 | "Failed to load NCCL library from %s. " |
| 387 | "It is expected if you are not running on NVIDIA/AMD GPUs." |
| 388 | "Otherwise, the nccl library might not exist, be corrupted " |
| 389 | "or it does not support the current platform %s. " |
| 390 | "If you already have the library, please set the " |
| 391 | "environment variable VLLM_NCCL_SO_PATH" |
| 392 | " to point to the correct nccl library path.", |
| 393 | so_file, |
| 394 | platform.platform(), |
| 395 | ) |
| 396 | raise e |
| 397 | |
| 398 | if so_file not in NCCLLibrary.path_to_dict_mapping: |
| 399 | _funcs: dict[str, Any] = {} |
| 400 | for func in NCCLLibrary.exported_functions: |
| 401 | try: |
| 402 | f = getattr(self.lib, func.name) |
| 403 | f.restype = func.restype |
| 404 | f.argtypes = func.argtypes |
| 405 | _funcs[func.name] = f |
| 406 | except AttributeError: |
| 407 | if func.name in [ |
| 408 | "ncclCommWindowRegister", |
| 409 | "ncclCommWindowDeregister", |
| 410 | ]: |
| 411 | if envs.VLLM_USE_NCCL_SYMM_MEM: |
| 412 | logger.warning_once( |
| 413 | "The symbol %s is not found in the NCCL " |
| 414 | "library %s. To enable VLLM_USE_NCCL_SYMM_MEM " |
| 415 | " please update your NCCL version to >= " |
| 416 | "2.27.03.", |
| 417 | func.name, |
| 418 | so_file, |
| 419 | ) |
| 420 | if current_platform.is_rocm(): |
| 421 | # Having an exception here on ROCm platform is |
| 422 | # not allowed during graph capturing |
| 423 | continue |
| 424 | elif func.name == "ncclCommQueryProperties": |
| 425 | # Optional on NCCL versions older than 2.29. |
| 426 | continue |
| 427 | elif func.name in ("ncclCommSuspend", "ncclCommResume"): |
| 428 | # RCCL doesn't export these; NCCL >= 2.29.7 does, and |
| 429 | # vLLM's CUDA path already requires that version. |
| 430 | # PyNcclCommunicator checks has_symbol() before |
| 431 | # calling either. |
| 432 | if current_platform.is_rocm(): |
| 433 | continue |
| 434 | raise |
| 435 | NCCLLibrary.path_to_dict_mapping[so_file] = _funcs |
| 436 | self._funcs = NCCLLibrary.path_to_dict_mapping[so_file] |
| 437 | |
| 438 | def has_symbol(self, name: str) -> bool: |
| 439 | """Whether the loaded NCCL/RCCL library exports the given symbol.""" |
| 440 | return name in self._funcs |
| 441 | |
| 442 | def ncclGetErrorString(self, result: ncclResult_t) -> str: |
| 443 | return self._funcs["ncclGetErrorString"](result).decode("utf-8") |
| 444 | |
| 445 | def NCCL_CHECK(self, result: ncclResult_t) -> None: |
| 446 | if result != 0: |
| 447 | error_str = self.ncclGetErrorString(result) |
| 448 | raise RuntimeError(f"NCCL error: {error_str}") |
| 449 | |
| 450 | def ncclGetRawVersion(self) -> int: |
| 451 | version = ctypes.c_int() |
| 452 | self.NCCL_CHECK(self._funcs["ncclGetVersion"](ctypes.byref(version))) |
| 453 | # something like 21903 |
| 454 | return version.value |
| 455 | |
| 456 | def ncclGetVersion(self) -> str: |
| 457 | version_str = str(self.ncclGetRawVersion()) |
| 458 | # something like 21903 --> "2.19.3" |
| 459 | major = version_str[0].lstrip("0") |
| 460 | minor = version_str[1:3].lstrip("0") |
| 461 | patch = version_str[3:].lstrip("0") |
| 462 | return f"{major}.{minor}.{patch}" |
| 463 | |
| 464 | def ncclGetUniqueId(self) -> ncclUniqueId: |
| 465 | unique_id = ncclUniqueId() |
| 466 | self.NCCL_CHECK(self._funcs["ncclGetUniqueId"](ctypes.byref(unique_id))) |
| 467 | return unique_id |
| 468 | |
| 469 | def unique_id_from_bytes(self, data: bytes) -> ncclUniqueId: |
| 470 | if len(data) != 128: |
| 471 | raise ValueError( |
| 472 | f"Expected 128 bytes for ncclUniqueId, got {len(data)} bytes" |
| 473 | ) |
| 474 | unique_id = ncclUniqueId() |
| 475 | ctypes.memmove(ctypes.addressof(unique_id.internal), data, 128) |
| 476 | return unique_id |
| 477 | |
| 478 | def ncclCommInitRank( |
| 479 | self, world_size: int, unique_id: ncclUniqueId, rank: int |
| 480 | ) -> ncclComm_t: |
| 481 | comm = ncclComm_t() |
| 482 | self.NCCL_CHECK( |
| 483 | self._funcs["ncclCommInitRank"]( |
| 484 | ctypes.byref(comm), world_size, unique_id, rank |
| 485 | ) |
| 486 | ) |
| 487 | return comm |
| 488 | |
| 489 | def ncclAllReduce( |
| 490 | self, |
| 491 | sendbuff: buffer_type, |
| 492 | recvbuff: buffer_type, |
| 493 | count: int, |
| 494 | datatype: int, |
| 495 | op: int, |
| 496 | comm: ncclComm_t, |
| 497 | stream: cudaStream_t, |
| 498 | ) -> None: |
| 499 | # `datatype` actually should be `ncclDataType_t` |
| 500 | # and `op` should be `ncclRedOp_t` |
| 501 | # both are aliases of `ctypes.c_int` |
| 502 | # when we pass int to a function, it will be converted to `ctypes.c_int` |
| 503 | # by ctypes automatically |
| 504 | self.NCCL_CHECK( |
| 505 | self._funcs["ncclAllReduce"]( |
| 506 | sendbuff, recvbuff, count, datatype, op, comm, stream |
| 507 | ) |
| 508 | ) |
| 509 | |
| 510 | def ncclReduce( |
| 511 | self, |
| 512 | sendbuff: buffer_type, |
| 513 | recvbuff: buffer_type, |
| 514 | count: int, |
| 515 | datatype: int, |
| 516 | op: int, |
| 517 | root: int, |
| 518 | comm: ncclComm_t, |
| 519 | stream: cudaStream_t, |
| 520 | ) -> None: |
| 521 | # `datatype` actually should be `ncclDataType_t` |
| 522 | # and `op` should be `ncclRedOp_t` |
| 523 | # both are aliases of `ctypes.c_int` |
| 524 | # when we pass int to a function, it will be converted to `ctypes.c_int` |
| 525 | # by ctypes automatically |
| 526 | self.NCCL_CHECK( |
| 527 | self._funcs["ncclReduce"]( |
| 528 | sendbuff, recvbuff, count, datatype, op, root, comm, stream |
| 529 | ) |
| 530 | ) |
| 531 | |
| 532 | def ncclReduceScatter( |
| 533 | self, |
| 534 | sendbuff: buffer_type, |
| 535 | recvbuff: buffer_type, |
| 536 | count: int, |
| 537 | datatype: int, |
| 538 | op: int, |
| 539 | comm: ncclComm_t, |
| 540 | stream: cudaStream_t, |
| 541 | ) -> None: |
| 542 | # `datatype` actually should be `ncclDataType_t` |
| 543 | # and `op` should be `ncclRedOp_t` |
| 544 | # both are aliases of `ctypes.c_int` |
| 545 | # when we pass int to a function, it will be converted to `ctypes.c_int` |
| 546 | # by ctypes automatically |
| 547 | self.NCCL_CHECK( |
| 548 | self._funcs["ncclReduceScatter"]( |
| 549 | sendbuff, recvbuff, count, datatype, op, comm, stream |
| 550 | ) |
| 551 | ) |
| 552 | |
| 553 | def ncclAllGather( |
| 554 | self, |
| 555 | sendbuff: buffer_type, |
| 556 | recvbuff: buffer_type, |
| 557 | count: int, |
| 558 | datatype: int, |
| 559 | comm: ncclComm_t, |
| 560 | stream: cudaStream_t, |
| 561 | ) -> None: |
| 562 | # `datatype` actually should be `ncclDataType_t` |
| 563 | # which is an aliases of `ctypes.c_int` |
| 564 | # when we pass int to a function, it will be converted to `ctypes.c_int` |
| 565 | # by ctypes automatically |
| 566 | self.NCCL_CHECK( |
| 567 | self._funcs["ncclAllGather"]( |
| 568 | sendbuff, recvbuff, count, datatype, comm, stream |
| 569 | ) |
| 570 | ) |
| 571 | |
| 572 | def ncclSend( |
| 573 | self, |
| 574 | sendbuff: buffer_type, |
| 575 | count: int, |
| 576 | datatype: int, |
| 577 | dest: int, |
| 578 | comm: ncclComm_t, |
| 579 | stream: cudaStream_t, |
| 580 | ) -> None: |
| 581 | self.NCCL_CHECK( |
| 582 | self._funcs["ncclSend"](sendbuff, count, datatype, dest, comm, stream) |
| 583 | ) |
| 584 | |
| 585 | def ncclRecv( |
| 586 | self, |
| 587 | recvbuff: buffer_type, |
| 588 | count: int, |
| 589 | datatype: int, |
| 590 | src: int, |
| 591 | comm: ncclComm_t, |
| 592 | stream: cudaStream_t, |
| 593 | ) -> None: |
| 594 | self.NCCL_CHECK( |
| 595 | self._funcs["ncclRecv"](recvbuff, count, datatype, src, comm, stream) |
| 596 | ) |
| 597 | |
| 598 | def ncclBroadcast( |
| 599 | self, |
| 600 | sendbuff: buffer_type, |
| 601 | recvbuff: buffer_type, |
| 602 | count: int, |
| 603 | datatype: int, |
| 604 | root: int, |
| 605 | comm: ncclComm_t, |
| 606 | stream: cudaStream_t, |
| 607 | ) -> None: |
| 608 | self.NCCL_CHECK( |
| 609 | self._funcs["ncclBroadcast"]( |
| 610 | sendbuff, recvbuff, count, datatype, root, comm, stream |
| 611 | ) |
| 612 | ) |
| 613 | |
| 614 | def ncclCommDestroy(self, comm: ncclComm_t) -> None: |
| 615 | self.NCCL_CHECK(self._funcs["ncclCommDestroy"](comm)) |
| 616 | |
| 617 | def ncclCommAbort(self, comm: ncclComm_t) -> None: |
| 618 | self.NCCL_CHECK(self._funcs["ncclCommAbort"](comm)) |
| 619 | |
| 620 | def ncclCommSuspend(self, comm: ncclComm_t, flags: int) -> None: |
| 621 | self.NCCL_CHECK(self._funcs["ncclCommSuspend"](comm, flags)) |
| 622 | |
| 623 | def ncclCommResume(self, comm: ncclComm_t) -> None: |
| 624 | self.NCCL_CHECK(self._funcs["ncclCommResume"](comm)) |
| 625 | |
| 626 | def ncclGroupStart(self) -> None: |
| 627 | self.NCCL_CHECK(self._funcs["ncclGroupStart"]()) |
| 628 | |
| 629 | def ncclGroupEnd(self) -> None: |
| 630 | self.NCCL_CHECK(self._funcs["ncclGroupEnd"]()) |
| 631 | |
| 632 | def ncclCommWindowRegister( |
| 633 | self, comm: ncclComm_t, buff: buffer_type, size: int, win_flags: int |
| 634 | ) -> ncclWindow_t: |
| 635 | window = ncclWindow_t() |
| 636 | self.NCCL_CHECK( |
| 637 | self._funcs["ncclCommWindowRegister"]( |
| 638 | comm, buff, size, ctypes.byref(window), win_flags |
| 639 | ) |
| 640 | ) |
| 641 | return window |
| 642 | |
| 643 | def ncclCommWindowDeregister(self, comm: ncclComm_t, window: ncclWindow_t) -> None: |
| 644 | self.NCCL_CHECK(self._funcs["ncclCommWindowDeregister"](comm, window)) |
| 645 | |
| 646 | |
| 647 | __all__ = [ |
| 648 | "NCCLLibrary", |
| 649 | "ncclDataTypeEnum", |
| 650 | "ncclRedOpTypeEnum", |
| 651 | "ncclUniqueId", |
| 652 | "ncclComm_t", |
| 653 | "cudaStream_t", |
| 654 | "buffer_type", |
| 655 | ] |