nvmath-python 1.0.0__cp314-cp314t-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nvmath/__init__.pxd +0 -0
- nvmath/__init__.py +45 -0
- nvmath/_internal/__init__.py +0 -0
- nvmath/_internal/attribute_ifc_factory.py +330 -0
- nvmath/_internal/layout.py +70 -0
- nvmath/_internal/templates.py +130 -0
- nvmath/_internal/threadsafe.py +106 -0
- nvmath/_internal/utils.py +43 -0
- nvmath/_internal/workspace.py +490 -0
- nvmath/_utils.py +147 -0
- nvmath/bindings/__init__.py +60 -0
- nvmath/bindings/_internal/__init__.pxd +0 -0
- nvmath/bindings/_internal/__init__.py +0 -0
- nvmath/bindings/_internal/common_types.pxd +31 -0
- nvmath/bindings/_internal/cublas.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cublas.pxd +530 -0
- nvmath/bindings/_internal/cublasLt.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cublasLt.pxd +59 -0
- nvmath/bindings/_internal/cublasMp.pxd +52 -0
- nvmath/bindings/_internal/cudss.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cudss.pxd +54 -0
- nvmath/bindings/_internal/cufft.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cufft.pxd +70 -0
- nvmath/bindings/_internal/cufftMp.pxd +77 -0
- nvmath/bindings/_internal/curand.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/curand.pxd +42 -0
- nvmath/bindings/_internal/cusolver.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusolver.pxd +15 -0
- nvmath/bindings/_internal/cusolverDn.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusolverDn.pxd +406 -0
- nvmath/bindings/_internal/cusolverMp.pxd +71 -0
- nvmath/bindings/_internal/cusolverSp.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusolverSp.pxd +75 -0
- nvmath/bindings/_internal/cusparse.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusparse.pxd +471 -0
- nvmath/bindings/_internal/cusparseLt.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusparseLt.pxd +48 -0
- nvmath/bindings/_internal/cutensor.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cutensor.pxd +58 -0
- nvmath/bindings/_internal/mathdx.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/mathdx.pxd +116 -0
- nvmath/bindings/_internal/nvshmem.pxd +29 -0
- nvmath/bindings/_internal/utils.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/utils.pxd +174 -0
- nvmath/bindings/_internal/utils.pyi +10 -0
- nvmath/bindings/cublas.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cublas.pxd +558 -0
- nvmath/bindings/cublas.pyi +812 -0
- nvmath/bindings/cublasLt.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cublasLt.pxd +109 -0
- nvmath/bindings/cublasLt.pyi +1461 -0
- nvmath/bindings/cublasMp.pxd +85 -0
- nvmath/bindings/cublasMp.pyi +267 -0
- nvmath/bindings/cudss.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cudss.pxd +98 -0
- nvmath/bindings/cudss.pyi +443 -0
- nvmath/bindings/cufft.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cufft.pxd +118 -0
- nvmath/bindings/cufft.pyi +301 -0
- nvmath/bindings/cufftMp.pxd +124 -0
- nvmath/bindings/cufftMp.pyi +326 -0
- nvmath/bindings/curand.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/curand.pxd +71 -0
- nvmath/bindings/curand.pyi +189 -0
- nvmath/bindings/cusolver.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cusolver.pxd +62 -0
- nvmath/bindings/cusolver.pyi +320 -0
- nvmath/bindings/cusolverDn.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cusolverDn.pxd +430 -0
- nvmath/bindings/cusolverDn.pyi +422 -0
- nvmath/bindings/cusolverMp.pxd +98 -0
- nvmath/bindings/cusolverMp.pyi +114 -0
- nvmath/bindings/cusolverSp.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cusolverSp.pxd +95 -0
- nvmath/bindings/cusolverSp.pyi +70 -0
- nvmath/bindings/cusparse.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cusparse.pxd +546 -0
- nvmath/bindings/cusparse.pyi +1017 -0
- nvmath/bindings/cusparseLt.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cusparseLt.pxd +99 -0
- nvmath/bindings/cusparseLt.pyi +252 -0
- nvmath/bindings/cutensor.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cutensor.pxd +98 -0
- nvmath/bindings/cutensor.pyi +324 -0
- nvmath/bindings/cycublas.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cycublas.pxd +664 -0
- nvmath/bindings/cycublasLt.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cycublasLt.pxd +1045 -0
- nvmath/bindings/cycublasMp.pxd +171 -0
- nvmath/bindings/cycudss.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cycudss.pxd +277 -0
- nvmath/bindings/cycufft.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cycufft.pxd +333 -0
- nvmath/bindings/cycufftMp.pxd +342 -0
- nvmath/bindings/cycurand.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cycurand.pxd +141 -0
- nvmath/bindings/cycusolver.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cycusolver.pxd +137 -0
- nvmath/bindings/cycusolverDn.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cycusolverDn.pxd +443 -0
- nvmath/bindings/cycusolverMp.pxd +107 -0
- nvmath/bindings/cycusolverSp.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cycusolverSp.pxd +93 -0
- nvmath/bindings/cycusparse.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cycusparse.pxd +679 -0
- nvmath/bindings/cycusparseLt.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cycusparseLt.pxd +135 -0
- nvmath/bindings/cycutensor.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cycutensor.pxd +189 -0
- nvmath/bindings/cymathdx.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cymathdx.pxd +552 -0
- nvmath/bindings/cynvshmem.pxd +118 -0
- nvmath/bindings/mathdx.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/mathdx.pxd +182 -0
- nvmath/bindings/mathdx.pyi +1562 -0
- nvmath/bindings/nvpl/__init__.pxd +0 -0
- nvmath/bindings/nvpl/__init__.py +13 -0
- nvmath/bindings/nvpl/_internal/__init__.pxd +0 -0
- nvmath/bindings/nvpl/_internal/__init__.py +0 -0
- nvmath/bindings/nvpl/_internal/blas.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/_internal/blas.pxd +237 -0
- nvmath/bindings/nvpl/_internal/fft.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/_internal/fft.pxd +36 -0
- nvmath/bindings/nvpl/blas.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/blas.pxd +131 -0
- nvmath/bindings/nvpl/blas.pyi +168 -0
- nvmath/bindings/nvpl/cyblas.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/cyblas.pxd +280 -0
- nvmath/bindings/nvpl/cyfft.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/cyfft.pxd +93 -0
- nvmath/bindings/nvpl/fft.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/fft.pxd +100 -0
- nvmath/bindings/nvpl/fft.pyi +168 -0
- nvmath/bindings/nvshmem.pxd +54 -0
- nvmath/bindings/nvshmem.pyi +191 -0
- nvmath/device/__init__.py +38 -0
- nvmath/device/_deprecated.py +33 -0
- nvmath/device/common.py +315 -0
- nvmath/device/common_backend.py +131 -0
- nvmath/device/common_cuda.py +201 -0
- nvmath/device/common_numba.py +300 -0
- nvmath/device/common_numba_cuda_mlir.py +202 -0
- nvmath/device/common_opaque_tensor.py +201 -0
- nvmath/device/cublasdx.py +1606 -0
- nvmath/device/cublasdx_backend.py +860 -0
- nvmath/device/cublasdx_numba.py +1534 -0
- nvmath/device/cublasdx_numba_cuda_mlir.py +208 -0
- nvmath/device/cufftdx.py +373 -0
- nvmath/device/cufftdx_backend.py +220 -0
- nvmath/device/cufftdx_numba.py +140 -0
- nvmath/device/cufftdx_numba_cuda_mlir.py +79 -0
- nvmath/device/curand_kernel.py +9147 -0
- nvmath/device/cusolverdx.py +2708 -0
- nvmath/device/cusolverdx_backend.py +440 -0
- nvmath/device/cusolverdx_numba.py +567 -0
- nvmath/device/cusolverdx_numba_cuda_mlir.py +604 -0
- nvmath/device/cusolverdx_overload_backend.py +1029 -0
- nvmath/device/llvm_array.py +29 -0
- nvmath/device/random.py +441 -0
- nvmath/device/random_helpers.py +23 -0
- nvmath/device/random_states.py +187 -0
- nvmath/device/types.py +138 -0
- nvmath/device/vector_types_numba.py +259 -0
- nvmath/distributed/__init__.py +200 -0
- nvmath/distributed/_internal/__init__.py +0 -0
- nvmath/distributed/_internal/nccl.py +86 -0
- nvmath/distributed/_internal/nvshmem.py +307 -0
- nvmath/distributed/_internal/symmetric_memory.py +35 -0
- nvmath/distributed/_internal/tensor_ifc.py +70 -0
- nvmath/distributed/_internal/tensor_ifc_cupy.py +68 -0
- nvmath/distributed/_internal/tensor_ifc_host_device.py +172 -0
- nvmath/distributed/_internal/tensor_ifc_numpy.py +46 -0
- nvmath/distributed/_internal/tensor_ifc_torch.py +162 -0
- nvmath/distributed/_internal/tensor_wrapper.py +81 -0
- nvmath/distributed/_utils.py +167 -0
- nvmath/distributed/distribution/__init__.py +30 -0
- nvmath/distributed/distribution/_configuration.py +39 -0
- nvmath/distributed/distribution/distributions.py +1024 -0
- nvmath/distributed/distribution/redistribute.py +1284 -0
- nvmath/distributed/fft/__init__.py +7 -0
- nvmath/distributed/fft/_configuration.py +82 -0
- nvmath/distributed/fft/fft.py +2742 -0
- nvmath/distributed/linalg/__init__.py +22 -0
- nvmath/distributed/linalg/_internal/__init__.py +3 -0
- nvmath/distributed/linalg/_internal/epilog_protocol.py +586 -0
- nvmath/distributed/linalg/_internal/matmul_desc_ifc.py +28 -0
- nvmath/distributed/linalg/advanced/__init__.py +8 -0
- nvmath/distributed/linalg/advanced/_configuration.py +171 -0
- nvmath/distributed/linalg/advanced/matmulmod.py +3573 -0
- nvmath/distributed/linalg/generic/__init__.py +8 -0
- nvmath/distributed/linalg/generic/_caching.py +66 -0
- nvmath/distributed/linalg/generic/_configuration.py +61 -0
- nvmath/distributed/linalg/generic/_factorization.py +172 -0
- nvmath/distributed/linalg/generic/_initialization.py +966 -0
- nvmath/distributed/linalg/generic/_problem_spec.py +511 -0
- nvmath/distributed/linalg/generic/solvermod.py +1368 -0
- nvmath/distributed/process_group.py +408 -0
- nvmath/fft/__init__.py +7 -0
- nvmath/fft/_configuration.py +189 -0
- nvmath/fft/_exec_utils.py +82 -0
- nvmath/fft/_helpers.py +237 -0
- nvmath/fft/fft.py +3122 -0
- nvmath/internal/__init__.pxd +3 -0
- nvmath/internal/__init__.py +10 -0
- nvmath/internal/_bindings.cp314t-win_amd64.pyd +0 -0
- nvmath/internal/_bindings.pxd +18 -0
- nvmath/internal/_device_utils.py +45 -0
- nvmath/internal/_layout/__init__.pxd +3 -0
- nvmath/internal/_layout/__init__.py +7 -0
- nvmath/internal/_layout/_layout.cp314t-win_amd64.pyd +0 -0
- nvmath/internal/_layout/_layout.pxd +1303 -0
- nvmath/internal/_layout/_layout.pyi +1145 -0
- nvmath/internal/enum_utils.py +142 -0
- nvmath/internal/formatters.py +87 -0
- nvmath/internal/mem_limit.py +51 -0
- nvmath/internal/memory.cp314t-win_amd64.pyd +0 -0
- nvmath/internal/memory.pxd +13 -0
- nvmath/internal/memory.pyi +50 -0
- nvmath/internal/ndbuffer/__init__.pxd +3 -0
- nvmath/internal/ndbuffer/__init__.py +9 -0
- nvmath/internal/ndbuffer/_copy_kernel.cp314t-win_amd64.pyd +0 -0
- nvmath/internal/ndbuffer/_copy_kernel.pxd +10 -0
- nvmath/internal/ndbuffer/_jit.cp314t-win_amd64.pyd +0 -0
- nvmath/internal/ndbuffer/_jit.pxd +7 -0
- nvmath/internal/ndbuffer/_ndbuffer.cp314t-win_amd64.pyd +0 -0
- nvmath/internal/ndbuffer/_ndbuffer.pxd +40 -0
- nvmath/internal/ndbuffer/_ndbuffer.pyi +463 -0
- nvmath/internal/ndbuffer/copy_kernel/args.h +34 -0
- nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/array_view.h +52 -0
- nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/elementwise.h +68 -0
- nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/grid_indexer.h +69 -0
- nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/transposed.h +242 -0
- nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/type_utils.h +39 -0
- nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/utils.h +132 -0
- nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/vec.h +159 -0
- nvmath/internal/ndbuffer/copy_kernel/elementwise.h +53 -0
- nvmath/internal/ndbuffer/copy_kernel/transposed.h +58 -0
- nvmath/internal/package_ifc.py +168 -0
- nvmath/internal/package_ifc_cuda.py +57 -0
- nvmath/internal/package_ifc_cupy.py +67 -0
- nvmath/internal/package_ifc_torch.py +69 -0
- nvmath/internal/package_wrapper.py +14 -0
- nvmath/internal/tensor_ifc.py +179 -0
- nvmath/internal/tensor_ifc_cupy.py +234 -0
- nvmath/internal/tensor_ifc_ndbuffer.py +147 -0
- nvmath/internal/tensor_ifc_numpy.py +184 -0
- nvmath/internal/tensor_ifc_torch.py +178 -0
- nvmath/internal/tensor_wrapper.py +160 -0
- nvmath/internal/typemaps.py +113 -0
- nvmath/internal/utils.py +805 -0
- nvmath/linalg/__init__.py +56 -0
- nvmath/linalg/_internal/__init__.py +3 -0
- nvmath/linalg/_internal/algo_cap_ifc.py +82 -0
- nvmath/linalg/_internal/algo_config_ifc.py +43 -0
- nvmath/linalg/_internal/batch.py +234 -0
- nvmath/linalg/_internal/enum_to_tuples.py +64 -0
- nvmath/linalg/_internal/epilog_protocol.py +766 -0
- nvmath/linalg/_internal/layout.py +624 -0
- nvmath/linalg/_internal/matmul_desc_ifc.py +28 -0
- nvmath/linalg/_internal/matmul_pref_ifc.py +27 -0
- nvmath/linalg/_internal/matrix_layout_ifc.py +26 -0
- nvmath/linalg/_internal/solver_utils.py +432 -0
- nvmath/linalg/_internal/typemaps.py +144 -0
- nvmath/linalg/_internal/utils.py +157 -0
- nvmath/linalg/advanced/__init__.py +8 -0
- nvmath/linalg/advanced/_algorithmmod.py +170 -0
- nvmath/linalg/advanced/_configuration.py +351 -0
- nvmath/linalg/advanced/helpers/__init__.py +5 -0
- nvmath/linalg/advanced/helpers/matmul.py +1316 -0
- nvmath/linalg/advanced/matmulmod.py +3734 -0
- nvmath/linalg/generic/__init__.py +53 -0
- nvmath/linalg/generic/_configuration/__init__.py +39 -0
- nvmath/linalg/generic/_configuration/layout.py +263 -0
- nvmath/linalg/generic/_configuration/match.py +734 -0
- nvmath/linalg/generic/_configuration/qualifiers.py +493 -0
- nvmath/linalg/generic/_configuration/solver_configuration.py +59 -0
- nvmath/linalg/generic/_configuration/wrap.py +217 -0
- nvmath/linalg/generic/_dtype.py +15 -0
- nvmath/linalg/generic/matmulmod.py +2094 -0
- nvmath/linalg/generic/solvermod.py +1301 -0
- nvmath/memory.py +279 -0
- nvmath/sparse/__init__.py +38 -0
- nvmath/sparse/_internal/__init__.py +21 -0
- nvmath/sparse/_internal/common_utils.py +147 -0
- nvmath/sparse/_internal/cudss_config_ifc.py +702 -0
- nvmath/sparse/_internal/cudss_data_ifc.py +399 -0
- nvmath/sparse/_internal/cudss_utils.py +506 -0
- nvmath/sparse/_internal/cusparse_utils.py +382 -0
- nvmath/sparse/_internal/sparse_bsc_ifc.py +303 -0
- nvmath/sparse/_internal/sparse_bsr_ifc.py +305 -0
- nvmath/sparse/_internal/sparse_coo_ifc.py +256 -0
- nvmath/sparse/_internal/sparse_csc_ifc.py +268 -0
- nvmath/sparse/_internal/sparse_csr_ifc.py +288 -0
- nvmath/sparse/_internal/sparse_dia_ifc.py +242 -0
- nvmath/sparse/_internal/sparse_format_helpers.py +601 -0
- nvmath/sparse/_internal/sparse_tensor_ifc.py +133 -0
- nvmath/sparse/_internal/sparse_ust_ifc.py +141 -0
- nvmath/sparse/_internal/utils.py +56 -0
- nvmath/sparse/advanced/__init__.py +7 -0
- nvmath/sparse/advanced/_configuration.py +227 -0
- nvmath/sparse/advanced/direct_solver.py +2069 -0
- nvmath/sparse/generic/__init__.py +7 -0
- nvmath/sparse/generic/_configuration.py +129 -0
- nvmath/sparse/generic/_helpers.py +137 -0
- nvmath/sparse/generic/_thunks.py +21 -0
- nvmath/sparse/generic/matmulmod.py +2353 -0
- nvmath/sparse/ust/__init__.py +7 -0
- nvmath/sparse/ust/_converters.py +422 -0
- nvmath/sparse/ust/_cpp.py +28 -0
- nvmath/sparse/ust/_drawer.py +565 -0
- nvmath/sparse/ust/_emitter.py +1033 -0
- nvmath/sparse/ust/_jit.py +188 -0
- nvmath/sparse/ust/_kernel.py +282 -0
- nvmath/sparse/ust/_utils.py +149 -0
- nvmath/sparse/ust/interfaces/__init__.py +0 -0
- nvmath/sparse/ust/interfaces/torch_interface.py +476 -0
- nvmath/sparse/ust/tensor.py +1016 -0
- nvmath/sparse/ust/tensor_format.py +957 -0
- nvmath/tensor/__init__.py +6 -0
- nvmath/tensor/_configuration.py +120 -0
- nvmath/tensor/_internal/__init__.py +3 -0
- nvmath/tensor/_internal/cutensor_config_ifc.py +279 -0
- nvmath/tensor/_internal/cutensor_utils.py +230 -0
- nvmath/tensor/_internal/data.py +43 -0
- nvmath/tensor/_internal/einsum_parser.py +444 -0
- nvmath/tensor/_internal/typemaps.py +96 -0
- nvmath/tensor/contract.py +1900 -0
- nvmath_python-1.0.0.dist-info/METADATA +134 -0
- nvmath_python-1.0.0.dist-info/RECORD +332 -0
- nvmath_python-1.0.0.dist-info/WHEEL +5 -0
- nvmath_python-1.0.0.dist-info/licenses/LICENSE +177 -0
- nvmath_python-1.0.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"initialize",
|
|
9
|
+
"finalize",
|
|
10
|
+
"is_initialized",
|
|
11
|
+
"nvshmem_empty_dlpack",
|
|
12
|
+
"free",
|
|
13
|
+
"NvshmemMemoryManager",
|
|
14
|
+
"NvshmemNDBufferAllocator",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
import atexit
|
|
18
|
+
import logging
|
|
19
|
+
from typing import TYPE_CHECKING
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
from cuda.core import Buffer, Device, MemoryResource, Stream
|
|
23
|
+
|
|
24
|
+
from nvmath import memory
|
|
25
|
+
from nvmath.bindings import nvshmem # type: ignore
|
|
26
|
+
from nvmath.distributed.process_group import ProcessGroup
|
|
27
|
+
from nvmath.internal.utils import device_ctx
|
|
28
|
+
|
|
29
|
+
from .symmetric_memory import calculate_symmetric_size
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from nvmath.distributed import DistributedContext
|
|
33
|
+
|
|
34
|
+
# Indicates if this module has initialized NVSHMEM
|
|
35
|
+
_nvshmem_initialized_here = False
|
|
36
|
+
|
|
37
|
+
_atexit_registered = False
|
|
38
|
+
_exiting = False
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def initialize(device_id: int, process_group: ProcessGroup) -> None:
|
|
42
|
+
"""Initialize NVSHMEM runtime if not initialized, otherwise do nothing.
|
|
43
|
+
NOTE: device_id must be set as the current device, and a CUDA context must have
|
|
44
|
+
been created for this device.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
global _nvshmem_initialized_here
|
|
48
|
+
|
|
49
|
+
rank = process_group.rank
|
|
50
|
+
nranks = process_group.nranks
|
|
51
|
+
|
|
52
|
+
status = nvshmem.init_status()
|
|
53
|
+
if status > nvshmem.STATUS_IS_BOOTSTRAPPED:
|
|
54
|
+
# NVSHMEM is already initialized
|
|
55
|
+
# NOTE: We assume that the user has passed the same communicator and device_id
|
|
56
|
+
# on which NVSHMEM was initialized (we have no way of checking here).
|
|
57
|
+
assert nvshmem.n_pes() == nranks
|
|
58
|
+
return
|
|
59
|
+
elif status == nvshmem.STATUS_IS_BOOTSTRAPPED:
|
|
60
|
+
# A value of 0 indicates an initialization that is similar to when nvshmem_init
|
|
61
|
+
# is used.
|
|
62
|
+
nvshmem.hostlib_init_attr(0, 0)
|
|
63
|
+
assert nvshmem.n_pes() == nranks
|
|
64
|
+
# Sanity check, can eventually remove
|
|
65
|
+
assert nvshmem.init_status() > nvshmem.STATUS_IS_BOOTSTRAPPED
|
|
66
|
+
_nvshmem_initialized_here = True
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
attr = nvshmem.InitAttr()
|
|
70
|
+
unique_id = nvshmem.UniqueId()
|
|
71
|
+
# PE 0 queries the unique ID
|
|
72
|
+
if rank == 0:
|
|
73
|
+
nvshmem.get_uniqueid(unique_id.ptr)
|
|
74
|
+
# PE 0 broadcasts the unique ID
|
|
75
|
+
process_group.broadcast_buffer(unique_id._data.view(np.int8), root=0) # type: ignore[attr-defined]
|
|
76
|
+
nvshmem.set_attr_uniqueid_args(rank, nranks, unique_id.ptr, attr.ptr)
|
|
77
|
+
nvshmem.hostlib_init_attr(nvshmem.Flags.INIT_WITH_UNIQUEID, attr.ptr)
|
|
78
|
+
|
|
79
|
+
# sanity check
|
|
80
|
+
assert nvshmem.init_status() > nvshmem.STATUS_IS_BOOTSTRAPPED
|
|
81
|
+
_nvshmem_initialized_here = True
|
|
82
|
+
_register_atexit_maybe()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _register_atexit_maybe() -> None:
|
|
86
|
+
global _atexit_registered
|
|
87
|
+
if not _atexit_registered:
|
|
88
|
+
atexit.register(_detect_exit)
|
|
89
|
+
_atexit_registered = True
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _detect_exit() -> None:
|
|
93
|
+
global _exiting
|
|
94
|
+
_exiting = True
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def finalize(device_id: int) -> None:
|
|
98
|
+
"""Finalize NVSHMEM runtime if initialized"""
|
|
99
|
+
global _nvshmem_initialized_here
|
|
100
|
+
if not _nvshmem_initialized_here or (nvshmem.init_status() < nvshmem.STATUS_IS_INITIALIZED):
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
with device_ctx(device_id):
|
|
104
|
+
nvshmem.hostlib_finalize()
|
|
105
|
+
_nvshmem_initialized_here = False
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def is_initialized() -> bool:
|
|
109
|
+
return nvshmem.init_status() >= nvshmem.STATUS_IS_INITIALIZED
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _check_initialized():
|
|
113
|
+
if nvshmem.init_status() < nvshmem.STATUS_IS_INITIALIZED:
|
|
114
|
+
raise RuntimeError("NVSHMEM is not initialized. Please initialize nvmath.distributed with NVSHMEM backend")
|
|
115
|
+
_register_atexit_maybe()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# Keeps track of memory allocated with nvshmem_empty_dlpack. This is used to report memory
|
|
119
|
+
# leaks and double free errors. This functionality might be handled by nvshmem4py in the
|
|
120
|
+
# future. More precisely, how it works is that when a buffer allocated via
|
|
121
|
+
# nvshmem_empty_dlpack is garbage-collected and found to not have been freed explicitly
|
|
122
|
+
# with the `free` function in this module, we report it.
|
|
123
|
+
_resource_registry = {}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class _NvshmemResource(MemoryResource):
|
|
127
|
+
def __init__(self, device):
|
|
128
|
+
self.device = device
|
|
129
|
+
|
|
130
|
+
def allocate(self, size, *, stream=None) -> Buffer:
|
|
131
|
+
# NOTE: setting the device is left to the caller
|
|
132
|
+
ptr = nvshmem.malloc(size)
|
|
133
|
+
if ptr == 0 and size != 0:
|
|
134
|
+
raise MemoryError("nvshmem_malloc returned NULL")
|
|
135
|
+
self.freed = False
|
|
136
|
+
return Buffer.from_handle(ptr=ptr, size=size, mr=self)
|
|
137
|
+
|
|
138
|
+
def deallocate(self, ptr, size, stream=None, *, manual=False):
|
|
139
|
+
# `stream` must NOT be keyword-only: cuda.core < 1 calls deallocate with all
|
|
140
|
+
# arguments positionally (Buffer.close -> mr.deallocate(ptr, size, stream)).
|
|
141
|
+
# NOTE: setting the device is left to the caller
|
|
142
|
+
if not manual:
|
|
143
|
+
if self.freed:
|
|
144
|
+
return # this is fine
|
|
145
|
+
else:
|
|
146
|
+
# We can't call nvshmem_free when deallocate is triggered by the GC, since
|
|
147
|
+
# the GC has non-deterministic behavior and nvshmem_free is a collective
|
|
148
|
+
# call.
|
|
149
|
+
if not _exiting:
|
|
150
|
+
logging.error("Symmetric heap memory needs to be deallocated explicitly")
|
|
151
|
+
else:
|
|
152
|
+
logging.error(
|
|
153
|
+
"Symmetric heap memory was not deallocated explicitly (you may have "
|
|
154
|
+
"forgotten to clean up before exit, or an unrelated exception "
|
|
155
|
+
"crashed the program)"
|
|
156
|
+
)
|
|
157
|
+
return
|
|
158
|
+
if self.freed:
|
|
159
|
+
raise RuntimeError("This memory resource was already deallocated")
|
|
160
|
+
nvshmem.free(ptr)
|
|
161
|
+
self.freed = True
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def is_device_accessible(self) -> bool:
|
|
165
|
+
return True
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def is_host_accessible(self) -> bool:
|
|
169
|
+
return False
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def device_id(self) -> int:
|
|
173
|
+
return self.device.device_id
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def nvshmem_empty_dlpack(
|
|
177
|
+
size: int,
|
|
178
|
+
device_id: int,
|
|
179
|
+
process_group: ProcessGroup,
|
|
180
|
+
make_symmetric: bool = False,
|
|
181
|
+
skip_symmetric_check: bool = False,
|
|
182
|
+
logger: logging.Logger | None = None,
|
|
183
|
+
) -> Buffer:
|
|
184
|
+
"""Return uninitialized DLPack buffer of given size in bytes, allocated using
|
|
185
|
+
nvshmem_malloc (which makes this a *collective* call). Note that the DLPack
|
|
186
|
+
buffer currently does not include any shape, dtype, or stride information.
|
|
187
|
+
|
|
188
|
+
IMPORTANT: device_id must be the one with which NVSHMEM was initialized, and
|
|
189
|
+
setting the device is left to the caller.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
size: Number of bytes to allocate.
|
|
193
|
+
|
|
194
|
+
device_id: Device on which to allocate.
|
|
195
|
+
|
|
196
|
+
process_group: nvmath.distributed process group.
|
|
197
|
+
|
|
198
|
+
make_symmetric: Use ``make_symmetric=True`` to allocate the same size on every
|
|
199
|
+
process (padding the buffer to the maximum size given by all processes).
|
|
200
|
+
|
|
201
|
+
skip_symmetric_check: True if buffer is already known to be of the same size
|
|
202
|
+
on every process.
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
_check_initialized()
|
|
206
|
+
|
|
207
|
+
global _resource_registry
|
|
208
|
+
|
|
209
|
+
logger = logger if logger is not None else logging.getLogger()
|
|
210
|
+
|
|
211
|
+
size = calculate_symmetric_size(size, process_group, make_symmetric, skip_symmetric_check, logger)
|
|
212
|
+
|
|
213
|
+
mem = _NvshmemResource(Device(device_id))
|
|
214
|
+
mem_buffer = mem.allocate(size)
|
|
215
|
+
pointer = int(mem_buffer.handle)
|
|
216
|
+
assert pointer not in _resource_registry
|
|
217
|
+
_resource_registry[pointer] = (mem, size)
|
|
218
|
+
return mem_buffer
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def free(pointer):
|
|
222
|
+
"""This is a *collective* call (invokes nvshmem_free), used to free buffers
|
|
223
|
+
allocated with nvshmem_empty_dlpack.
|
|
224
|
+
Setting the device is left to the caller"""
|
|
225
|
+
|
|
226
|
+
_check_initialized()
|
|
227
|
+
|
|
228
|
+
global _resource_registry
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
resource, size = _resource_registry.pop(pointer)
|
|
232
|
+
except KeyError as e:
|
|
233
|
+
raise RuntimeError(
|
|
234
|
+
"Unknown pointer to free. Possible causes:\n"
|
|
235
|
+
" - This memory was not allocated with nvmath.distributed helpers.\n"
|
|
236
|
+
" - This memory was already freed. Possible causes:\n"
|
|
237
|
+
" - Free was called multiple times on the same tensor.\n"
|
|
238
|
+
" - You have multiple tensors sharing the same symmetric memory buffer,\n"
|
|
239
|
+
" e.g. as a result of inplace operations, or tensor operations that\n"
|
|
240
|
+
" result in views such as slicing."
|
|
241
|
+
) from e
|
|
242
|
+
resource.deallocate(pointer, size, manual=True)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class NvshmemMemoryManager(memory.BaseCUDAMemoryManager):
|
|
246
|
+
"""
|
|
247
|
+
Nvshmem memory allocator.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
device_id: The ID (int) of the device on which memory is to be allocated.
|
|
251
|
+
logger (logging.Logger): Python Logger object.
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
def __init__(self, device_id, logger):
|
|
255
|
+
"""
|
|
256
|
+
__init__(device_id)
|
|
257
|
+
"""
|
|
258
|
+
_check_initialized()
|
|
259
|
+
self.device_id = device_id
|
|
260
|
+
self.logger = logger
|
|
261
|
+
|
|
262
|
+
def memalloc(self, size):
|
|
263
|
+
"""This is a *collective* call (invokes nvshmem_malloc)"""
|
|
264
|
+
mem = _NvshmemResource(Device(self.device_id))
|
|
265
|
+
mem_buffer = mem.allocate(size)
|
|
266
|
+
pointer = int(mem_buffer.handle)
|
|
267
|
+
assert pointer not in _resource_registry
|
|
268
|
+
_resource_registry[pointer] = (mem, size)
|
|
269
|
+
|
|
270
|
+
self.logger.debug(
|
|
271
|
+
f"NvshmemMemoryManager (allocate memory): size = {size}, pointer = {pointer}, device_id = {self.device_id}"
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
return SymmetricMemoryPointer(mem_buffer)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class NvshmemNDBufferAllocator:
|
|
278
|
+
__slots__ = ("ctx", "make_symmetric", "skip_symmetric_check")
|
|
279
|
+
|
|
280
|
+
def __init__(self, device_id: int, ctx: DistributedContext, make_symmetric: bool, skip_symmetric_check: bool):
|
|
281
|
+
assert ctx.device_id == device_id, (
|
|
282
|
+
"Internal error: attempting to allocate symmetric memory on a device not used "
|
|
283
|
+
"by the NVSHMEM runtime on this process"
|
|
284
|
+
)
|
|
285
|
+
self.ctx = ctx
|
|
286
|
+
self.make_symmetric = make_symmetric
|
|
287
|
+
self.skip_symmetric_check = skip_symmetric_check
|
|
288
|
+
|
|
289
|
+
def allocate(self, size: int, stream: Stream, logger: logging.Logger | None = None) -> Buffer:
|
|
290
|
+
return nvshmem_empty_dlpack(
|
|
291
|
+
size,
|
|
292
|
+
self.ctx.device_id,
|
|
293
|
+
self.ctx.process_group,
|
|
294
|
+
make_symmetric=self.make_symmetric,
|
|
295
|
+
skip_symmetric_check=self.skip_symmetric_check,
|
|
296
|
+
logger=logger,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class SymmetricMemoryPointer(memory.MemoryPointer):
|
|
301
|
+
def __init__(self, mem_buffer):
|
|
302
|
+
super().__init__(int(mem_buffer.handle), mem_buffer.size, finalizer=None)
|
|
303
|
+
self.mem_buffer = mem_buffer
|
|
304
|
+
|
|
305
|
+
def free(self):
|
|
306
|
+
"""This is a *collective* call (invokes nvshmem_free)"""
|
|
307
|
+
free(self.device_ptr)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
__all__ = ["calculate_symmetric_size"]
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from nvmath.distributed.process_group import ReductionOp
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def calculate_symmetric_size(size, process_group, make_symmetric, skip_symmetric_check, logger):
|
|
13
|
+
if make_symmetric and skip_symmetric_check:
|
|
14
|
+
raise ValueError("skip_symmetric_check is incompatible with make_symmetric=True")
|
|
15
|
+
|
|
16
|
+
if not skip_symmetric_check:
|
|
17
|
+
max_size = np.array([-size, size], dtype=np.int64)
|
|
18
|
+
process_group.allreduce_buffer(max_size, op=ReductionOp.MAX)
|
|
19
|
+
if -max_size[0] != max_size[1]:
|
|
20
|
+
# The buffer size is not the same on all processes.
|
|
21
|
+
if make_symmetric:
|
|
22
|
+
logger.info(
|
|
23
|
+
"Symmetric memory allocator: the buffer will be padded on some processes to "
|
|
24
|
+
f"satisfy symmetric requirement (make_symmetric=True), size={size} max_size={max_size[1]}."
|
|
25
|
+
)
|
|
26
|
+
else:
|
|
27
|
+
raise ValueError(
|
|
28
|
+
"The buffer size for symmetric memory allocation is not the same on all processes. "
|
|
29
|
+
"Consider using make_symmetric=True if you have uneven data distribution."
|
|
30
|
+
)
|
|
31
|
+
else:
|
|
32
|
+
logger.info(f"Symmetric memory allocator: the requested buffer size ({size}) is the same on all processes.")
|
|
33
|
+
# Sizes are equal or make_symmetric=True.
|
|
34
|
+
size = max_size[1]
|
|
35
|
+
return size
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Common distributed tensor interface.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations # allows typehint of class methods to return the self class
|
|
10
|
+
|
|
11
|
+
__all__ = ["DistributedTensor"]
|
|
12
|
+
|
|
13
|
+
from abc import abstractmethod
|
|
14
|
+
from collections.abc import Sequence
|
|
15
|
+
from typing import Literal
|
|
16
|
+
|
|
17
|
+
from nvmath.bindings import nvshmem # type: ignore
|
|
18
|
+
from nvmath.distributed._internal.nvshmem import free
|
|
19
|
+
from nvmath.distributed._internal.nvshmem import is_initialized as is_nvshmem_initialized
|
|
20
|
+
from nvmath.internal.package_ifc import StreamHolder
|
|
21
|
+
from nvmath.internal.tensor_ifc import Tensor, TensorHolder
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DistributedTensor(TensorHolder[Tensor]):
|
|
25
|
+
"""Base class for distributed tensors.
|
|
26
|
+
|
|
27
|
+
Sets flag during construction to indicate if the tensor is on NVSHMEM symmetric memory
|
|
28
|
+
or not.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, tensor):
|
|
32
|
+
super().__init__(tensor)
|
|
33
|
+
self._is_symmetric_memory = False
|
|
34
|
+
if self.device == "cuda" and is_nvshmem_initialized():
|
|
35
|
+
self._is_symmetric_memory = nvshmem.ptr(self.data_ptr, nvshmem.my_pe()) != 0
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def is_symmetric_memory(self):
|
|
39
|
+
"""True if the tensor is on NVSHMEM symmetric heap.
|
|
40
|
+
|
|
41
|
+
This doesn't apply to NCCL because window registrations in NCCL are
|
|
42
|
+
on a per-communicator basis, not global.
|
|
43
|
+
"""
|
|
44
|
+
return self._is_symmetric_memory
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def to(
|
|
48
|
+
self,
|
|
49
|
+
device_id: int | Literal["cpu"],
|
|
50
|
+
stream_holder: StreamHolder | None,
|
|
51
|
+
symmetric_memory: None | Literal["nvshmem", "nccl"] = None,
|
|
52
|
+
) -> DistributedTensor:
|
|
53
|
+
"""Copy the TensorHolder to a different device.
|
|
54
|
+
|
|
55
|
+
No copy is performed if the TensorHolder is already on the requested device.
|
|
56
|
+
"""
|
|
57
|
+
raise NotImplementedError
|
|
58
|
+
|
|
59
|
+
@abstractmethod
|
|
60
|
+
def reshape(self, shape: Sequence[int], *, copy: bool | None = None) -> DistributedTensor:
|
|
61
|
+
raise NotImplementedError
|
|
62
|
+
|
|
63
|
+
def free_symmetric(self) -> None:
|
|
64
|
+
"""
|
|
65
|
+
Release this tensor's allocation on NVSHMEM symmetric memory heap.
|
|
66
|
+
"""
|
|
67
|
+
if not self._is_symmetric_memory:
|
|
68
|
+
raise TypeError("tensor is not on symmetric memory")
|
|
69
|
+
|
|
70
|
+
free(self.data_ptr)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Interface to seamlessly use distributed Cupy ndarray objects.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations # allows typehint of class methods to return the self class
|
|
10
|
+
|
|
11
|
+
__all__ = ["CupyDistributedTensor", "HostDistributedTensor"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
import cupy
|
|
16
|
+
except ImportError:
|
|
17
|
+
cupy = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
from nvmath.internal.ndbuffer import NDBuffer
|
|
21
|
+
from nvmath.internal.tensor_ifc_cupy import CupyTensor, HostTensor
|
|
22
|
+
|
|
23
|
+
from .tensor_ifc import DistributedTensor
|
|
24
|
+
from .tensor_ifc_host_device import CudaDistributedTensorMixIn, HostDistributedTensorMixIn
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class HostDistributedTensor(HostDistributedTensorMixIn, HostTensor, DistributedTensor):
|
|
28
|
+
host_tensor_class: type[HostDistributedTensor] # set at the end of the file
|
|
29
|
+
device_tensor_class: type[CupyDistributedTensor] # set at the end of the file
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Most methods aren't redefined, because they simply act on the local array
|
|
33
|
+
class CupyDistributedTensor(CudaDistributedTensorMixIn, CupyTensor, DistributedTensor):
|
|
34
|
+
"""
|
|
35
|
+
Tensor wrapper for distributed cupy ndarrays.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
host_tensor_class: type[HostDistributedTensor] # set at the end of the file
|
|
39
|
+
device_tensor_class: type[CupyDistributedTensor] # set at the end of the file
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def wrap_ndbuffer(cls, ndbuffer: NDBuffer) -> CupyDistributedTensor:
|
|
43
|
+
"""
|
|
44
|
+
Wraps NDBuffer into a cupy.ndarray, the method assumes the
|
|
45
|
+
NDBuffer is backed by CUDA device memory.
|
|
46
|
+
"""
|
|
47
|
+
base_ptr, size_in_bytes, offset_in_bytes = ndbuffer.raw_memory_range_info
|
|
48
|
+
mem = cupy.cuda.UnownedMemory(
|
|
49
|
+
base_ptr,
|
|
50
|
+
size_in_bytes,
|
|
51
|
+
owner=ndbuffer.data,
|
|
52
|
+
device_id=ndbuffer.device_id,
|
|
53
|
+
)
|
|
54
|
+
memptr = cupy.cuda.MemoryPointer(mem, offset=offset_in_bytes)
|
|
55
|
+
dtype = cls.name_to_dtype[ndbuffer.dtype_name]
|
|
56
|
+
tensor = cupy.ndarray(
|
|
57
|
+
ndbuffer.shape,
|
|
58
|
+
dtype=dtype,
|
|
59
|
+
strides=ndbuffer.strides_in_bytes,
|
|
60
|
+
memptr=memptr,
|
|
61
|
+
)
|
|
62
|
+
return cls(tensor)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
HostDistributedTensor.host_tensor_class = HostDistributedTensor
|
|
66
|
+
CupyDistributedTensor.host_tensor_class = HostDistributedTensor
|
|
67
|
+
HostDistributedTensor.device_tensor_class = CupyDistributedTensor
|
|
68
|
+
CupyDistributedTensor.device_tensor_class = CupyDistributedTensor
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Mixins for handling host <-> device symmetry for distributed tensors
|
|
7
|
+
for packages that support only host or device tensors.
|
|
8
|
+
|
|
9
|
+
Most of the symmetric-memory logic is not package-specific, but handling
|
|
10
|
+
regular allocations or local tensor operations is, and can be delegated
|
|
11
|
+
to the non-distributed tensor implementation.
|
|
12
|
+
For this reason, the functionality is implemented as mixins, so that
|
|
13
|
+
we can delegate the `super()` calls without specifying the parent class.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from abc import ABC, abstractmethod
|
|
17
|
+
from collections.abc import Sequence
|
|
18
|
+
from typing import Literal
|
|
19
|
+
|
|
20
|
+
__all__ = ["HostDistributedTensorMixIn", "CudaDistributedTensorMixIn"]
|
|
21
|
+
|
|
22
|
+
import nvmath.distributed
|
|
23
|
+
from nvmath.distributed._internal.nccl import NcclNDBufferAllocator
|
|
24
|
+
from nvmath.distributed._internal.nvshmem import NvshmemNDBufferAllocator
|
|
25
|
+
from nvmath.internal.ndbuffer import NDBuffer, StridedLayout
|
|
26
|
+
from nvmath.internal.package_ifc import StreamHolder
|
|
27
|
+
from nvmath.internal.tensor_ifc import TensorHolder
|
|
28
|
+
from nvmath.internal.typemaps import NAME_TO_ITEM_SIZE
|
|
29
|
+
from nvmath.internal.utils import device_ctx
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class HostDistributedTensorMixIn(ABC): # noqa: B024
|
|
33
|
+
"""
|
|
34
|
+
Host counterpart for distributed tensor wrapping package that
|
|
35
|
+
does not support host memory space (e.g. cupy). The class is marked
|
|
36
|
+
as abstract, because the mixin is not meant to be instantiated directly.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def to(self, device_id, stream_holder, symmetric_memory: None | Literal["nvshmem", "nccl"] = None):
|
|
40
|
+
"""
|
|
41
|
+
In addition to the base class semantics:
|
|
42
|
+
- If symmetric_memory != None, target device must be the one used to initialize
|
|
43
|
+
NVSHMEM or NCCL on this process.
|
|
44
|
+
- Strides must be dense non-overlapping.
|
|
45
|
+
- Memory layout is preserved (if strides are dense non-overlapping,
|
|
46
|
+
the base class guarantees that)
|
|
47
|
+
"""
|
|
48
|
+
if not symmetric_memory or device_id == "cpu":
|
|
49
|
+
tensor = super().to(device_id, stream_holder) # type: ignore
|
|
50
|
+
elif isinstance(device_id, int):
|
|
51
|
+
device_cls = self.device_tensor_class # type: ignore
|
|
52
|
+
tensor = device_cls.create_from_host(self, device_id, stream_holder, symmetric_memory)
|
|
53
|
+
else:
|
|
54
|
+
raise ValueError(f"The device must be specified as an integer or 'cpu', not '{device_id}'.")
|
|
55
|
+
assert tensor.is_symmetric_memory == (symmetric_memory == "nvshmem")
|
|
56
|
+
return tensor
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _get_symmetric_memory_allocator(backend: Literal["nvshmem", "nccl"]):
|
|
60
|
+
if backend == "nvshmem":
|
|
61
|
+
return NvshmemNDBufferAllocator
|
|
62
|
+
elif backend == "nccl":
|
|
63
|
+
return NcclNDBufferAllocator
|
|
64
|
+
raise AssertionError(f"Internal error: unsupported backend {backend}.")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class CudaDistributedTensorMixIn(ABC):
|
|
68
|
+
@classmethod
|
|
69
|
+
def empty(
|
|
70
|
+
cls,
|
|
71
|
+
shape,
|
|
72
|
+
device_id="cpu",
|
|
73
|
+
*,
|
|
74
|
+
dtype="float32",
|
|
75
|
+
strides=None,
|
|
76
|
+
stream_holder: StreamHolder | None = None,
|
|
77
|
+
**context,
|
|
78
|
+
):
|
|
79
|
+
"""
|
|
80
|
+
Create an empty tensor of the specified shape and data type.
|
|
81
|
+
|
|
82
|
+
Note, that the strides, if specified, MUST correspond to a dense (possibly permuted)
|
|
83
|
+
tensor, otherwise the created tensor may be corrupted.
|
|
84
|
+
"""
|
|
85
|
+
symmetric_memory = context.pop("symmetric_memory", None)
|
|
86
|
+
make_symmetric = context.pop("make_symmetric", False)
|
|
87
|
+
skip_symmetric_check = context.pop("skip_symmetric_check", False)
|
|
88
|
+
|
|
89
|
+
if not symmetric_memory:
|
|
90
|
+
if make_symmetric or skip_symmetric_check:
|
|
91
|
+
raise ValueError("Use of symmetric memory option with symmetric_memory=None")
|
|
92
|
+
return super().empty( # type: ignore
|
|
93
|
+
shape,
|
|
94
|
+
device_id=device_id,
|
|
95
|
+
dtype=dtype,
|
|
96
|
+
strides=strides,
|
|
97
|
+
stream_holder=stream_holder,
|
|
98
|
+
**context,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
allocator_cls = _get_symmetric_memory_allocator(symmetric_memory)
|
|
102
|
+
|
|
103
|
+
logger = context.get("logger")
|
|
104
|
+
with device_ctx(device_id):
|
|
105
|
+
ctx = nvmath.distributed.get_context()
|
|
106
|
+
assert ctx is not None, "nvmath.distributed has not been initialized"
|
|
107
|
+
allocator = allocator_cls(device_id, ctx, make_symmetric=make_symmetric, skip_symmetric_check=skip_symmetric_check)
|
|
108
|
+
layout = StridedLayout(shape, strides, NAME_TO_ITEM_SIZE[dtype])
|
|
109
|
+
if not layout.is_contiguous_c and not layout.is_contiguous_f:
|
|
110
|
+
raise ValueError("CudaDistributedTensor only supports 'C' or 'F' order")
|
|
111
|
+
nd_dst = NDBuffer.empty(layout, dtype, device_id, stream=stream_holder, memory_resource=allocator, logger=logger)
|
|
112
|
+
return cls.wrap_ndbuffer(nd_dst)
|
|
113
|
+
|
|
114
|
+
@classmethod
|
|
115
|
+
def create_from_host(
|
|
116
|
+
cls,
|
|
117
|
+
tensor: TensorHolder,
|
|
118
|
+
device_id: int,
|
|
119
|
+
stream_holder: StreamHolder,
|
|
120
|
+
symmetric_memory: None | Literal["nvshmem", "nccl"] = None,
|
|
121
|
+
):
|
|
122
|
+
if not symmetric_memory:
|
|
123
|
+
return super().create_from_host(tensor, device_id, stream_holder) # type: ignore
|
|
124
|
+
|
|
125
|
+
allocator_cls = _get_symmetric_memory_allocator(symmetric_memory)
|
|
126
|
+
|
|
127
|
+
with device_ctx(device_id):
|
|
128
|
+
ctx = nvmath.distributed.get_context()
|
|
129
|
+
assert ctx is not None, "nvmath.distributed has not been initialized"
|
|
130
|
+
allocator = allocator_cls(device_id, ctx, make_symmetric=True, skip_symmetric_check=False)
|
|
131
|
+
src_ndbuffer = tensor.asndbuffer()
|
|
132
|
+
dst_ndbuffer = NDBuffer.empty_like(
|
|
133
|
+
src_ndbuffer,
|
|
134
|
+
device_id=device_id,
|
|
135
|
+
stream=stream_holder,
|
|
136
|
+
memory_resource=allocator,
|
|
137
|
+
)
|
|
138
|
+
layout = dst_ndbuffer.layout
|
|
139
|
+
if not layout.is_contiguous_c and not layout.is_contiguous_f:
|
|
140
|
+
raise ValueError("CudaDistributedTensor only supports 'C' or 'F' order")
|
|
141
|
+
dst_ndbuffer.copy_(src_ndbuffer, stream=stream_holder)
|
|
142
|
+
return cls.wrap_ndbuffer(dst_ndbuffer)
|
|
143
|
+
|
|
144
|
+
@classmethod
|
|
145
|
+
@abstractmethod
|
|
146
|
+
def wrap_ndbuffer(cls, ndbuffer: NDBuffer):
|
|
147
|
+
"""
|
|
148
|
+
Defines how to wrap NDBuffer into a distributed tensor instance.
|
|
149
|
+
The exact implementation depends on `self.tensor` type
|
|
150
|
+
expected by the TensorHolder implementation to be mixed in.
|
|
151
|
+
E.g. for NDBufferTensor, it suffices to call `cls(ndbuffer)`,
|
|
152
|
+
while for CupyTensor, we need to wrap the NDBuffer into a cupy.ndarray first.
|
|
153
|
+
"""
|
|
154
|
+
raise NotImplementedError
|
|
155
|
+
|
|
156
|
+
def to(self, device_id, stream_holder, symmetric_memory: None | Literal["nvshmem", "nccl"] = None):
|
|
157
|
+
"""
|
|
158
|
+
In addition to the base class semantics:
|
|
159
|
+
- Target device must be the one used to initialize NVSHMEM or NCCL on
|
|
160
|
+
this process.
|
|
161
|
+
- Strides must be dense non-overlapping.
|
|
162
|
+
- Memory layout is preserved (if strides are dense non-overlapping,
|
|
163
|
+
the base class guarantees that)
|
|
164
|
+
"""
|
|
165
|
+
tensor = super().to(device_id, stream_holder) # type: ignore
|
|
166
|
+
assert tensor.is_symmetric_memory == (symmetric_memory == "nvshmem")
|
|
167
|
+
return tensor
|
|
168
|
+
|
|
169
|
+
def reshape(self, shape: Sequence[int], *, copy: bool | None = None):
|
|
170
|
+
if copy:
|
|
171
|
+
raise NotImplementedError("reshape with copy=True is not supported for CUDA distributed tensor")
|
|
172
|
+
return super().reshape(shape, copy=copy) # type: ignore
|