nvmath-python 0.9.0__cp314-cp314-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 +383 -0
- nvmath/_utils.py +150 -0
- nvmath/bindings/__init__.py +51 -0
- nvmath/bindings/_internal/__init__.pxd +0 -0
- nvmath/bindings/_internal/__init__.py +0 -0
- nvmath/bindings/_internal/cublas.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cublas.pxd +530 -0
- nvmath/bindings/_internal/cublasLt.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cublasLt.pxd +59 -0
- nvmath/bindings/_internal/cublasMp.pxd +52 -0
- nvmath/bindings/_internal/cudss.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cudss.pxd +47 -0
- nvmath/bindings/_internal/cufft.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cufft.pxd +70 -0
- nvmath/bindings/_internal/cufftMp.pxd +77 -0
- nvmath/bindings/_internal/curand.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/curand.pxd +42 -0
- nvmath/bindings/_internal/cusolver.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusolver.pxd +15 -0
- nvmath/bindings/_internal/cusolverDn.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusolverDn.pxd +406 -0
- nvmath/bindings/_internal/cusparse.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusparse.pxd +277 -0
- nvmath/bindings/_internal/cusparseLt.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusparseLt.pxd +48 -0
- nvmath/bindings/_internal/cutensor.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cutensor.pxd +58 -0
- nvmath/bindings/_internal/mathdx.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/mathdx.pxd +116 -0
- nvmath/bindings/_internal/nvshmem.pxd +29 -0
- nvmath/bindings/_internal/utils.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/utils.pxd +179 -0
- nvmath/bindings/_internal/utils.pyi +10 -0
- nvmath/bindings/cublas.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cublas.pxd +558 -0
- nvmath/bindings/cublas.pyi +746 -0
- nvmath/bindings/cublasLt.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cublasLt.pxd +109 -0
- nvmath/bindings/cublasLt.pyi +1337 -0
- nvmath/bindings/cublasMp.pxd +85 -0
- nvmath/bindings/cublasMp.pyi +219 -0
- nvmath/bindings/cudss.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cudss.pxd +86 -0
- nvmath/bindings/cudss.pyi +282 -0
- nvmath/bindings/cufft.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cufft.pxd +118 -0
- nvmath/bindings/cufft.pyi +241 -0
- nvmath/bindings/cufftMp.pxd +124 -0
- nvmath/bindings/cufftMp.pyi +260 -0
- nvmath/bindings/curand.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/curand.pxd +71 -0
- nvmath/bindings/curand.pyi +159 -0
- nvmath/bindings/cusolver.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cusolver.pxd +62 -0
- nvmath/bindings/cusolver.pyi +242 -0
- nvmath/bindings/cusolverDn.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cusolverDn.pxd +430 -0
- nvmath/bindings/cusolverDn.pyi +416 -0
- nvmath/bindings/cusparse.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cusparse.pxd +338 -0
- nvmath/bindings/cusparse.pyi +659 -0
- nvmath/bindings/cusparseLt.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cusparseLt.pxd +99 -0
- nvmath/bindings/cusparseLt.pyi +198 -0
- nvmath/bindings/cutensor.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cutensor.pxd +98 -0
- nvmath/bindings/cutensor.pyi +294 -0
- nvmath/bindings/cycublas.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycublas.pxd +684 -0
- nvmath/bindings/cycublasLt.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycublasLt.pxd +1055 -0
- nvmath/bindings/cycublasMp.pxd +183 -0
- nvmath/bindings/cycudss.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycudss.pxd +224 -0
- nvmath/bindings/cycufft.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycufft.pxd +326 -0
- nvmath/bindings/cycufftMp.pxd +334 -0
- nvmath/bindings/cycurand.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycurand.pxd +146 -0
- nvmath/bindings/cycusolver.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycusolver.pxd +154 -0
- nvmath/bindings/cycusolverDn.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycusolverDn.pxd +446 -0
- nvmath/bindings/cycusparse.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycusparse.pxd +470 -0
- nvmath/bindings/cycusparseLt.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycusparseLt.pxd +150 -0
- nvmath/bindings/cycutensor.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycutensor.pxd +192 -0
- nvmath/bindings/cymathdx.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cymathdx.pxd +552 -0
- nvmath/bindings/cynvshmem.pxd +126 -0
- nvmath/bindings/mathdx.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/mathdx.pxd +182 -0
- nvmath/bindings/mathdx.pyi +1244 -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.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/_internal/blas.pxd +125 -0
- nvmath/bindings/nvpl/_internal/fft.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/_internal/fft.pxd +36 -0
- nvmath/bindings/nvpl/blas.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/blas.pxd +85 -0
- nvmath/bindings/nvpl/blas.pyi +122 -0
- nvmath/bindings/nvpl/cyblas.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/cyblas.pxd +166 -0
- nvmath/bindings/nvpl/cyfft.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/cyfft.pxd +92 -0
- nvmath/bindings/nvpl/fft.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/fft.pxd +100 -0
- nvmath/bindings/nvpl/fft.pyi +100 -0
- nvmath/bindings/nvshmem.pxd +54 -0
- nvmath/bindings/nvshmem.pyi +179 -0
- nvmath/device/__init__.py +21 -0
- nvmath/device/_deprecated.py +33 -0
- nvmath/device/common.py +313 -0
- nvmath/device/common_backend.py +131 -0
- nvmath/device/common_cuda.py +201 -0
- nvmath/device/common_numba.py +310 -0
- nvmath/device/common_opaque_tensor.py +201 -0
- nvmath/device/cublasdx.py +1805 -0
- nvmath/device/cublasdx_backend.py +807 -0
- nvmath/device/cublasdx_numba.py +1612 -0
- nvmath/device/cufftdx.py +510 -0
- nvmath/device/cufftdx_backend.py +196 -0
- nvmath/device/cufftdx_numba.py +196 -0
- nvmath/device/curand_kernel.py +9147 -0
- nvmath/device/cusolverdx.py +2690 -0
- nvmath/device/cusolverdx_backend.py +440 -0
- nvmath/device/cusolverdx_numba.py +1624 -0
- nvmath/device/llvm_array.py +29 -0
- nvmath/device/random.py +445 -0
- nvmath/device/random_helpers.py +23 -0
- nvmath/device/random_states.py +187 -0
- nvmath/device/types.py +147 -0
- nvmath/device/vector_types_numba.py +203 -0
- nvmath/distributed/__init__.py +205 -0
- nvmath/distributed/_internal/__init__.py +0 -0
- nvmath/distributed/_internal/nvshmem.py +302 -0
- nvmath/distributed/_internal/tensor_ifc.py +67 -0
- nvmath/distributed/_internal/tensor_ifc_cupy.py +62 -0
- nvmath/distributed/_internal/tensor_ifc_host_device.py +165 -0
- nvmath/distributed/_internal/tensor_ifc_numpy.py +41 -0
- nvmath/distributed/_internal/tensor_ifc_torch.py +141 -0
- nvmath/distributed/_internal/tensor_wrapper.py +78 -0
- nvmath/distributed/_utils.py +167 -0
- nvmath/distributed/distribution.py +770 -0
- nvmath/distributed/fft/__init__.py +7 -0
- nvmath/distributed/fft/_configuration.py +79 -0
- nvmath/distributed/fft/fft.py +2801 -0
- nvmath/distributed/linalg/__init__.py +12 -0
- nvmath/distributed/linalg/_internal/__init__.py +3 -0
- nvmath/distributed/linalg/_internal/epilog_protocol.py +496 -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 +166 -0
- nvmath/distributed/linalg/advanced/matmulmod.py +2908 -0
- nvmath/distributed/process_group.py +408 -0
- nvmath/distributed/reshape/__init__.py +6 -0
- nvmath/distributed/reshape/_configuration.py +39 -0
- nvmath/distributed/reshape/reshape.py +1256 -0
- nvmath/fft/__init__.py +7 -0
- nvmath/fft/_configuration.py +208 -0
- nvmath/fft/_exec_utils.py +109 -0
- nvmath/fft/_helpers.py +237 -0
- nvmath/fft/fft.py +2903 -0
- nvmath/internal/__init__.pxd +3 -0
- nvmath/internal/__init__.py +10 -0
- nvmath/internal/_bindings.cp314-win_amd64.pyd +0 -0
- nvmath/internal/_bindings.pxd +12 -0
- nvmath/internal/_device_utils.py +54 -0
- nvmath/internal/enum_utils.py +142 -0
- nvmath/internal/formatters.py +87 -0
- nvmath/internal/mem_limit.py +51 -0
- nvmath/internal/memory.cp314-win_amd64.pyd +0 -0
- nvmath/internal/memory.pxd +10 -0
- nvmath/internal/memory.pyi +46 -0
- nvmath/internal/ndbuffer/__init__.pxd +3 -0
- nvmath/internal/ndbuffer/__init__.py +7 -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 +240 -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/ndbuffer/copy_kernel.cp314-win_amd64.pyd +0 -0
- nvmath/internal/ndbuffer/copy_kernel.pxd +9 -0
- nvmath/internal/ndbuffer/copy_kernel.pyi +9 -0
- nvmath/internal/ndbuffer/data_layout.cp314-win_amd64.pyd +0 -0
- nvmath/internal/ndbuffer/data_layout.pxd +72 -0
- nvmath/internal/ndbuffer/data_layout.pyi +23 -0
- nvmath/internal/ndbuffer/jit.cp314-win_amd64.pyd +0 -0
- nvmath/internal/ndbuffer/jit.pxd +7 -0
- nvmath/internal/ndbuffer/jit.pyi +6 -0
- nvmath/internal/ndbuffer/ndbuffer.cp314-win_amd64.pyd +0 -0
- nvmath/internal/ndbuffer/ndbuffer.pxd +36 -0
- nvmath/internal/ndbuffer/ndbuffer.pyi +41 -0
- nvmath/internal/ndbuffer/package_utils.cp314-win_amd64.pyd +0 -0
- nvmath/internal/ndbuffer/package_utils.pxd +11 -0
- nvmath/internal/ndbuffer/package_utils.pyi +12 -0
- nvmath/internal/package_ifc.py +147 -0
- nvmath/internal/package_ifc_cuda.py +49 -0
- nvmath/internal/package_ifc_cupy.py +49 -0
- nvmath/internal/package_ifc_torch.py +31 -0
- nvmath/internal/package_wrapper.py +14 -0
- nvmath/internal/tensor_ifc.py +194 -0
- nvmath/internal/tensor_ifc_cupy.py +241 -0
- nvmath/internal/tensor_ifc_ndbuffer.py +153 -0
- nvmath/internal/tensor_ifc_numpy.py +183 -0
- nvmath/internal/tensor_ifc_torch.py +176 -0
- nvmath/internal/tensor_wrapper.py +157 -0
- nvmath/internal/typemaps.py +162 -0
- nvmath/internal/utils.py +771 -0
- nvmath/linalg/__init__.py +46 -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 +217 -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/typemaps.py +144 -0
- nvmath/linalg/_internal/utils.py +134 -0
- nvmath/linalg/advanced/__init__.py +8 -0
- nvmath/linalg/advanced/_algorithmmod.py +149 -0
- nvmath/linalg/advanced/_configuration.py +349 -0
- nvmath/linalg/advanced/helpers/__init__.py +5 -0
- nvmath/linalg/advanced/helpers/matmul.py +1348 -0
- nvmath/linalg/advanced/matmulmod.py +3937 -0
- nvmath/linalg/generic/__init__.py +43 -0
- nvmath/linalg/generic/_configuration/__init__.py +37 -0
- nvmath/linalg/generic/_configuration/layout.py +263 -0
- nvmath/linalg/generic/_configuration/match.py +609 -0
- nvmath/linalg/generic/_configuration/qualifiers.py +494 -0
- nvmath/linalg/generic/_configuration/wrap.py +217 -0
- nvmath/linalg/generic/_dtype.py +15 -0
- nvmath/linalg/generic/matmulmod.py +1060 -0
- nvmath/memory.py +282 -0
- nvmath/sparse/__init__.py +38 -0
- nvmath/sparse/_internal/__init__.py +21 -0
- nvmath/sparse/_internal/common_utils.py +144 -0
- nvmath/sparse/_internal/cudss_config_ifc.py +741 -0
- nvmath/sparse/_internal/cudss_data_ifc.py +399 -0
- nvmath/sparse/_internal/cudss_utils.py +445 -0
- nvmath/sparse/_internal/cusparse_utils.py +382 -0
- nvmath/sparse/_internal/sparse_bsc_ifc.py +300 -0
- nvmath/sparse/_internal/sparse_bsr_ifc.py +303 -0
- nvmath/sparse/_internal/sparse_coo_ifc.py +254 -0
- nvmath/sparse/_internal/sparse_csc_ifc.py +266 -0
- nvmath/sparse/_internal/sparse_csr_ifc.py +266 -0
- nvmath/sparse/_internal/sparse_dia_ifc.py +239 -0
- nvmath/sparse/_internal/sparse_format_helpers.py +601 -0
- nvmath/sparse/_internal/sparse_tensor_ifc.py +121 -0
- nvmath/sparse/_internal/sparse_ust_ifc.py +146 -0
- nvmath/sparse/_internal/utils.py +56 -0
- nvmath/sparse/advanced/__init__.py +7 -0
- nvmath/sparse/advanced/_configuration.py +230 -0
- nvmath/sparse/advanced/direct_solver.py +1884 -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 +2426 -0
- nvmath/sparse/ust/__init__.py +7 -0
- nvmath/sparse/ust/_converters.py +378 -0
- nvmath/sparse/ust/_drawer.py +552 -0
- nvmath/sparse/ust/_emitter.py +553 -0
- nvmath/sparse/ust/_jit.py +212 -0
- nvmath/sparse/ust/_kernel.py +346 -0
- nvmath/sparse/ust/_semiring.py +71 -0
- nvmath/sparse/ust/_utils.py +102 -0
- nvmath/sparse/ust/interfaces/__init__.py +0 -0
- nvmath/sparse/ust/interfaces/torch_interface.py +476 -0
- nvmath/sparse/ust/semiring_ops.py +71 -0
- nvmath/sparse/ust/tensor.py +1027 -0
- nvmath/sparse/ust/tensor_drawer.py +552 -0
- nvmath/sparse/ust/tensor_emitter.py +558 -0
- nvmath/sparse/ust/tensor_format.py +935 -0
- nvmath/sparse/ust/tensor_jit.py +212 -0
- nvmath/sparse/ust/tensor_kernel.py +348 -0
- nvmath/sparse/ust/tensor_utils.py +380 -0
- nvmath/tensor/__init__.py +6 -0
- nvmath/tensor/_configuration.py +95 -0
- nvmath/tensor/_internal/__init__.py +3 -0
- nvmath/tensor/_internal/cutensor_config_ifc.py +247 -0
- nvmath/tensor/_internal/cutensor_utils.py +162 -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 +1861 -0
- nvmath_python-0.9.0.dist-info/METADATA +127 -0
- nvmath_python-0.9.0.dist-info/RECORD +309 -0
- nvmath_python-0.9.0.dist-info/WHEEL +5 -0
- nvmath_python-0.9.0.dist-info/licenses/LICENSE +177 -0
- nvmath_python-0.9.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"initialize",
|
|
7
|
+
"finalize",
|
|
8
|
+
"is_initialized",
|
|
9
|
+
"nvshmem_empty_dlpack",
|
|
10
|
+
"free",
|
|
11
|
+
"NvshmemMemoryManager",
|
|
12
|
+
"NvshmemNDBufferAllocator",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
import atexit
|
|
16
|
+
import logging
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from cuda.core import Buffer, Device, MemoryResource
|
|
22
|
+
except ImportError:
|
|
23
|
+
from cuda.core.experimental import Buffer, Device, MemoryResource
|
|
24
|
+
|
|
25
|
+
from nvmath import memory
|
|
26
|
+
from nvmath.bindings import nvshmem # type: ignore
|
|
27
|
+
from nvmath.distributed.process_group import ProcessGroup, ReductionOp
|
|
28
|
+
from nvmath.internal.utils import device_ctx
|
|
29
|
+
|
|
30
|
+
# Indicates if this module has initialized NVSHMEM
|
|
31
|
+
_nvshmem_initialized_here = False
|
|
32
|
+
|
|
33
|
+
_atexit_registered = False
|
|
34
|
+
_exiting = False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def initialize(device_id: int, process_group: ProcessGroup) -> None:
|
|
38
|
+
"""Initialize NVSHMEM runtime if not initialized, otherwise do nothing.
|
|
39
|
+
NOTE: device_id must be set as the current device, and a CUDA context must have
|
|
40
|
+
been created for this device.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
global _nvshmem_initialized_here
|
|
44
|
+
|
|
45
|
+
rank = process_group.rank
|
|
46
|
+
nranks = process_group.nranks
|
|
47
|
+
|
|
48
|
+
status = nvshmem.init_status()
|
|
49
|
+
if status > nvshmem.STATUS_IS_BOOTSTRAPPED:
|
|
50
|
+
# NVSHMEM is already initialized
|
|
51
|
+
# NOTE: We assume that the user has passed the same communicator and device_id
|
|
52
|
+
# on which NVSHMEM was initialized (we have no way of checking here).
|
|
53
|
+
assert nvshmem.n_pes() == nranks
|
|
54
|
+
return
|
|
55
|
+
elif status == nvshmem.STATUS_IS_BOOTSTRAPPED:
|
|
56
|
+
# A value of 0 indicates an initialization that is similar to when nvshmem_init
|
|
57
|
+
# is used.
|
|
58
|
+
nvshmem.hostlib_init_attr(0, 0)
|
|
59
|
+
assert nvshmem.n_pes() == nranks
|
|
60
|
+
# Sanity check, can eventually remove
|
|
61
|
+
assert nvshmem.init_status() > nvshmem.STATUS_IS_BOOTSTRAPPED
|
|
62
|
+
_nvshmem_initialized_here = True
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
attr = nvshmem.InitAttr()
|
|
66
|
+
unique_id = nvshmem.UniqueId()
|
|
67
|
+
# PE 0 queries the unique ID
|
|
68
|
+
if rank == 0:
|
|
69
|
+
nvshmem.get_uniqueid(unique_id.ptr)
|
|
70
|
+
# PE 0 broadcasts the unique ID
|
|
71
|
+
process_group.broadcast_buffer(unique_id._data.view(np.int8), root=0) # type: ignore[attr-defined]
|
|
72
|
+
nvshmem.set_attr_uniqueid_args(rank, nranks, unique_id.ptr, attr.ptr)
|
|
73
|
+
nvshmem.hostlib_init_attr(nvshmem.Flags.INIT_WITH_UNIQUEID, attr.ptr)
|
|
74
|
+
|
|
75
|
+
# sanity check
|
|
76
|
+
assert nvshmem.init_status() > nvshmem.STATUS_IS_BOOTSTRAPPED
|
|
77
|
+
_nvshmem_initialized_here = True
|
|
78
|
+
_register_atexit_maybe()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _register_atexit_maybe() -> None:
|
|
82
|
+
global _atexit_registered
|
|
83
|
+
if not _atexit_registered:
|
|
84
|
+
atexit.register(_detect_exit)
|
|
85
|
+
_atexit_registered = True
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _detect_exit() -> None:
|
|
89
|
+
global _exiting
|
|
90
|
+
_exiting = True
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def finalize(device_id: int) -> None:
|
|
94
|
+
"""Finalize NVSHMEM runtime if initialized"""
|
|
95
|
+
global _nvshmem_initialized_here
|
|
96
|
+
if not _nvshmem_initialized_here or (nvshmem.init_status() < nvshmem.STATUS_IS_INITIALIZED):
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
with device_ctx(device_id):
|
|
100
|
+
nvshmem.hostlib_finalize()
|
|
101
|
+
_nvshmem_initialized_here = False
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def is_initialized() -> bool:
|
|
105
|
+
return nvshmem.init_status() >= nvshmem.STATUS_IS_INITIALIZED
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _check_initialized():
|
|
109
|
+
if nvshmem.init_status() < nvshmem.STATUS_IS_INITIALIZED:
|
|
110
|
+
raise RuntimeError("NVSHMEM is not initialized. Please initialize nvmath.distributed with NVSHMEM backend")
|
|
111
|
+
_register_atexit_maybe()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# Keeps track of memory allocated with nvshmem_empty_dlpack. This is used to report memory
|
|
115
|
+
# leaks and double free errors. This functionality might be handled by nvshmem4py in the
|
|
116
|
+
# future. More precisely, how it works is that when a buffer allocated via
|
|
117
|
+
# nvshmem_empty_dlpack is garbage-collected and found to not have been freed explicitly
|
|
118
|
+
# with the `free` function in this module, we report it.
|
|
119
|
+
_resource_registry = {}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class _NvshmemResource(MemoryResource):
|
|
123
|
+
def __init__(self, device):
|
|
124
|
+
self.device = device
|
|
125
|
+
|
|
126
|
+
def allocate(self, size, stream=None) -> Buffer:
|
|
127
|
+
# NOTE: setting the device is left to the caller
|
|
128
|
+
ptr = nvshmem.malloc(size)
|
|
129
|
+
if ptr == 0 and size != 0:
|
|
130
|
+
raise MemoryError("nvshmem_malloc returned NULL")
|
|
131
|
+
self.freed = False
|
|
132
|
+
return Buffer.from_handle(ptr=ptr, size=size, mr=self)
|
|
133
|
+
|
|
134
|
+
def deallocate(self, ptr, size, stream=None, manual=False):
|
|
135
|
+
# NOTE: setting the device is left to the caller
|
|
136
|
+
if not manual:
|
|
137
|
+
if self.freed:
|
|
138
|
+
return # this is fine
|
|
139
|
+
else:
|
|
140
|
+
# We can't call nvshmem_free when deallocate is triggered by the GC, since
|
|
141
|
+
# the GC has non-deterministic behavior and nvshmem_free is a collective
|
|
142
|
+
# call.
|
|
143
|
+
if not _exiting:
|
|
144
|
+
logging.error("Symmetric heap memory needs to be deallocated explicitly")
|
|
145
|
+
else:
|
|
146
|
+
logging.error(
|
|
147
|
+
"Symmetric heap memory was not deallocated explicitly (you may have "
|
|
148
|
+
"forgotten to clean up before exit, or an unrelated exception "
|
|
149
|
+
"crashed the program)"
|
|
150
|
+
)
|
|
151
|
+
return
|
|
152
|
+
if self.freed:
|
|
153
|
+
raise RuntimeError("This memory resource was already deallocated")
|
|
154
|
+
nvshmem.free(ptr)
|
|
155
|
+
self.freed = True
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def is_device_accessible(self) -> bool:
|
|
159
|
+
return True
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def is_host_accessible(self) -> bool:
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def device_id(self) -> int:
|
|
167
|
+
return self.device.device_id
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def nvshmem_empty_dlpack(size, device_id, process_group, make_symmetric=False, skip_symmetric_check=False, logger=None):
|
|
171
|
+
"""Return uninitialized DLPack buffer of given size in bytes, allocated using
|
|
172
|
+
nvshmem_malloc (which makes this a *collective* call). Note that the DLPack
|
|
173
|
+
buffer currently does not include any shape, dtype, or stride information.
|
|
174
|
+
|
|
175
|
+
IMPORTANT: device_id must be the one with which NVSHMEM was initialized, and
|
|
176
|
+
setting the device is left to the caller.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
_check_initialized()
|
|
180
|
+
|
|
181
|
+
global _resource_registry
|
|
182
|
+
|
|
183
|
+
logger = logger if logger is not None else logging.getLogger()
|
|
184
|
+
|
|
185
|
+
if make_symmetric and skip_symmetric_check:
|
|
186
|
+
raise ValueError("skip_symmetric_check is incompatible with make_symmetric=True")
|
|
187
|
+
|
|
188
|
+
if not skip_symmetric_check:
|
|
189
|
+
max_size = np.array([-size, size], dtype=np.int64)
|
|
190
|
+
process_group.allreduce_buffer(max_size, op=ReductionOp.MAX)
|
|
191
|
+
if -max_size[0] != max_size[1]:
|
|
192
|
+
# The buffer size is not the same on all processes.
|
|
193
|
+
if make_symmetric:
|
|
194
|
+
logger.info(
|
|
195
|
+
"Symmetric memory allocator: the buffer will be padded on some processes to "
|
|
196
|
+
f"satisfy symmetric requirement (make_symmetric=True), size={size} max_size={max_size[1]}."
|
|
197
|
+
)
|
|
198
|
+
else:
|
|
199
|
+
raise ValueError(
|
|
200
|
+
"The buffer size for symmetric memory allocation is not the same on all processes. "
|
|
201
|
+
"Consider using make_symmetric=True if you have uneven data distribution."
|
|
202
|
+
)
|
|
203
|
+
else:
|
|
204
|
+
logger.info(f"Symmetric memory allocator: the requested buffer size ({size}) is the same on all processes.")
|
|
205
|
+
# Sizes are equal or make_symmetric=True.
|
|
206
|
+
size = max_size[1]
|
|
207
|
+
|
|
208
|
+
mem = _NvshmemResource(Device(device_id))
|
|
209
|
+
mem_buffer = mem.allocate(size)
|
|
210
|
+
pointer = int(mem_buffer.handle)
|
|
211
|
+
assert pointer not in _resource_registry
|
|
212
|
+
_resource_registry[pointer] = (mem, size)
|
|
213
|
+
return mem_buffer
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def free(pointer):
|
|
217
|
+
"""This is a *collective* call (invokes nvshmem_free), used to free buffers
|
|
218
|
+
allocated with nvshmem_empty_dlpack.
|
|
219
|
+
Setting the device is left to the caller"""
|
|
220
|
+
|
|
221
|
+
_check_initialized()
|
|
222
|
+
|
|
223
|
+
global _resource_registry
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
resource, size = _resource_registry.pop(pointer)
|
|
227
|
+
except KeyError as e:
|
|
228
|
+
raise RuntimeError(
|
|
229
|
+
"Unknown pointer to free. Possible causes:\n"
|
|
230
|
+
" - This memory was not allocated with nvmath.distributed helpers.\n"
|
|
231
|
+
" - This memory was already freed. Possible causes:\n"
|
|
232
|
+
" - Free was called multiple times on the same tensor.\n"
|
|
233
|
+
" - You have multiple tensors sharing the same symmetric memory buffer,\n"
|
|
234
|
+
" e.g. as a result of inplace operations, or tensor operations that\n"
|
|
235
|
+
" result in views such as slicing."
|
|
236
|
+
) from e
|
|
237
|
+
resource.deallocate(pointer, size, manual=True)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class NvshmemMemoryManager(memory.BaseCUDAMemoryManager):
|
|
241
|
+
"""
|
|
242
|
+
Nvshmem memory allocator.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
device_id: The ID (int) of the device on which memory is to be allocated.
|
|
246
|
+
logger (logging.Logger): Python Logger object.
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
def __init__(self, device_id, logger):
|
|
250
|
+
"""
|
|
251
|
+
__init__(device_id)
|
|
252
|
+
"""
|
|
253
|
+
_check_initialized()
|
|
254
|
+
self.device_id = device_id
|
|
255
|
+
self.logger = logger
|
|
256
|
+
|
|
257
|
+
def memalloc(self, size):
|
|
258
|
+
"""This is a *collective* call (invokes nvshmem_malloc)"""
|
|
259
|
+
mem = _NvshmemResource(Device(self.device_id))
|
|
260
|
+
mem_buffer = mem.allocate(size)
|
|
261
|
+
pointer = int(mem_buffer.handle)
|
|
262
|
+
assert pointer not in _resource_registry
|
|
263
|
+
_resource_registry[pointer] = (mem, size)
|
|
264
|
+
|
|
265
|
+
self.logger.debug(
|
|
266
|
+
f"NvshmemMemoryManager (allocate memory): size = {size}, pointer = {pointer}, device_id = {self.device_id}"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
return SymmetricMemoryPointer(mem_buffer)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class NvshmemNDBufferAllocator:
|
|
273
|
+
__slots__ = ("ctx", "make_symmetric", "skip_symmetric_check")
|
|
274
|
+
|
|
275
|
+
def __init__(self, device_id, ctx, make_symmetric, skip_symmetric_check):
|
|
276
|
+
assert ctx.device_id == device_id, (
|
|
277
|
+
"Internal error: attempting to allocate symmetric memory on a device not used "
|
|
278
|
+
"by the NVSHMEM runtime on this process"
|
|
279
|
+
)
|
|
280
|
+
self.ctx = ctx
|
|
281
|
+
self.make_symmetric = make_symmetric
|
|
282
|
+
self.skip_symmetric_check = skip_symmetric_check
|
|
283
|
+
|
|
284
|
+
def allocate(self, size, stream, logger=None):
|
|
285
|
+
return nvshmem_empty_dlpack(
|
|
286
|
+
size,
|
|
287
|
+
self.ctx.device_id,
|
|
288
|
+
self.ctx.process_group,
|
|
289
|
+
make_symmetric=self.make_symmetric,
|
|
290
|
+
skip_symmetric_check=self.skip_symmetric_check,
|
|
291
|
+
logger=logger,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class SymmetricMemoryPointer(memory.MemoryPointer):
|
|
296
|
+
def __init__(self, mem_buffer):
|
|
297
|
+
super().__init__(int(mem_buffer.handle), mem_buffer.size, finalizer=None)
|
|
298
|
+
self.mem_buffer = mem_buffer
|
|
299
|
+
|
|
300
|
+
def free(self):
|
|
301
|
+
"""This is a *collective* call (invokes nvshmem_free)"""
|
|
302
|
+
free(self.device_ptr)
|
|
@@ -0,0 +1,67 @@
|
|
|
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, device_id: int | Literal["cpu"], stream_holder: StreamHolder | None, symmetric_memory: bool = False
|
|
49
|
+
) -> DistributedTensor:
|
|
50
|
+
"""Copy the TensorHolder to a different device.
|
|
51
|
+
|
|
52
|
+
No copy is performed if the TensorHolder is already on the requested device.
|
|
53
|
+
"""
|
|
54
|
+
raise NotImplementedError
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def reshape(self, shape: Sequence[int], *, copy: bool | None = None) -> DistributedTensor:
|
|
58
|
+
raise NotImplementedError
|
|
59
|
+
|
|
60
|
+
def free_symmetric(self) -> None:
|
|
61
|
+
"""
|
|
62
|
+
Release this tensor's allocation on NVSHMEM symmetric memory heap.
|
|
63
|
+
"""
|
|
64
|
+
if not self._is_symmetric_memory:
|
|
65
|
+
raise TypeError("tensor is not on symmetric memory")
|
|
66
|
+
|
|
67
|
+
free(self.data_ptr)
|
|
@@ -0,0 +1,62 @@
|
|
|
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
|
+
device_tensor_class: type[CupyDistributedTensor] # set once CupyDistributedTensor is defined
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Most methods aren't redefined, because they simply act on the local array
|
|
32
|
+
class CupyDistributedTensor(CudaDistributedTensorMixIn, CupyTensor, DistributedTensor):
|
|
33
|
+
"""
|
|
34
|
+
Tensor wrapper for distributed cupy ndarrays.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
host_tensor_class = HostDistributedTensor
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def wrap_ndbuffer(cls, ndbuffer: ndbuffer.NDBuffer) -> CupyDistributedTensor:
|
|
41
|
+
"""
|
|
42
|
+
Wraps NDBuffer into a cupy.ndarray, the method assumes the
|
|
43
|
+
NDBuffer is backed by CUDA device memory.
|
|
44
|
+
"""
|
|
45
|
+
mem = cupy.cuda.UnownedMemory(
|
|
46
|
+
ndbuffer.data_ptr,
|
|
47
|
+
ndbuffer.size_in_bytes,
|
|
48
|
+
owner=ndbuffer.data,
|
|
49
|
+
device_id=ndbuffer.device_id,
|
|
50
|
+
)
|
|
51
|
+
memptr = cupy.cuda.MemoryPointer(mem, offset=0)
|
|
52
|
+
dtype = cls.name_to_dtype[ndbuffer.dtype_name]
|
|
53
|
+
tensor = cupy.ndarray(
|
|
54
|
+
ndbuffer.shape,
|
|
55
|
+
dtype=dtype,
|
|
56
|
+
strides=ndbuffer.strides_in_bytes,
|
|
57
|
+
memptr=memptr,
|
|
58
|
+
)
|
|
59
|
+
return cls(tensor)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
HostDistributedTensor.device_tensor_class = CupyDistributedTensor
|
|
@@ -0,0 +1,165 @@
|
|
|
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
|
+
|
|
19
|
+
__all__ = ["HostDistributedTensorMixIn", "CudaDistributedTensorMixIn"]
|
|
20
|
+
|
|
21
|
+
import nvmath.distributed
|
|
22
|
+
from nvmath.distributed._internal.nvshmem import NvshmemNDBufferAllocator
|
|
23
|
+
from nvmath.internal.ndbuffer import ndbuffer
|
|
24
|
+
from nvmath.internal.package_ifc import StreamHolder
|
|
25
|
+
from nvmath.internal.tensor_ifc import TensorHolder
|
|
26
|
+
from nvmath.internal.typemaps import NAME_TO_ITEM_SIZE
|
|
27
|
+
from nvmath.internal.utils import device_ctx
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class HostDistributedTensorMixIn(ABC): # noqa: B024
|
|
31
|
+
"""
|
|
32
|
+
Host counterpart for distributed tensor wrapping package that
|
|
33
|
+
does not support host memory space (e.g. cupy). The class is marked
|
|
34
|
+
as abstract, because the mixin is not meant to be instantiated directly.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def to(self, device_id, stream_holder, symmetric_memory: bool = False):
|
|
38
|
+
"""
|
|
39
|
+
In addition to the base class semantics:
|
|
40
|
+
- If symmetric_memory=True, target device must be the one used to initialize
|
|
41
|
+
NVSHMEM on this process.
|
|
42
|
+
- Strides must be dense non-overlapping.
|
|
43
|
+
- Memory layout is preserved (if strides are dense non-overlapping,
|
|
44
|
+
the base class guarantees that)
|
|
45
|
+
"""
|
|
46
|
+
if not symmetric_memory or device_id == "cpu":
|
|
47
|
+
tensor = super().to(device_id, stream_holder) # type: ignore
|
|
48
|
+
elif isinstance(device_id, int):
|
|
49
|
+
device_cls = self.device_tensor_class # type: ignore
|
|
50
|
+
tensor = device_cls.create_from_host(self, device_id, stream_holder, symmetric_memory)
|
|
51
|
+
else:
|
|
52
|
+
raise ValueError(f"The device must be specified as an integer or 'cpu', not '{device_id}'.")
|
|
53
|
+
assert tensor.is_symmetric_memory == symmetric_memory
|
|
54
|
+
return tensor
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CudaDistributedTensorMixIn(ABC):
|
|
58
|
+
@classmethod
|
|
59
|
+
def empty(
|
|
60
|
+
cls,
|
|
61
|
+
shape,
|
|
62
|
+
device_id="cpu",
|
|
63
|
+
*,
|
|
64
|
+
dtype="float32",
|
|
65
|
+
strides=None,
|
|
66
|
+
stream_holder: StreamHolder | None = None,
|
|
67
|
+
**context,
|
|
68
|
+
):
|
|
69
|
+
"""
|
|
70
|
+
Create an empty tensor of the specified shape and data type.
|
|
71
|
+
|
|
72
|
+
Note, that the strides, if specified, MUST correspond to a dense (possibly permuted)
|
|
73
|
+
tensor, otherwise the created tensor may be corrupted.
|
|
74
|
+
"""
|
|
75
|
+
symmetric_memory = context.pop("symmetric_memory", False)
|
|
76
|
+
make_symmetric = context.pop("make_symmetric", False)
|
|
77
|
+
skip_symmetric_check = context.pop("skip_symmetric_check", False)
|
|
78
|
+
|
|
79
|
+
if not symmetric_memory:
|
|
80
|
+
if make_symmetric or skip_symmetric_check:
|
|
81
|
+
raise ValueError("Use of symmetric memory option with symmetric_memory=False")
|
|
82
|
+
return super().empty( # type: ignore
|
|
83
|
+
shape,
|
|
84
|
+
device_id=device_id,
|
|
85
|
+
dtype=dtype,
|
|
86
|
+
strides=strides,
|
|
87
|
+
stream_holder=stream_holder,
|
|
88
|
+
**context,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
logger = context.get("logger")
|
|
92
|
+
with device_ctx(device_id):
|
|
93
|
+
ctx = nvmath.distributed.get_context()
|
|
94
|
+
assert ctx is not None, "nvmath.distributed has not been initialized"
|
|
95
|
+
allocator = NvshmemNDBufferAllocator(
|
|
96
|
+
device_id, ctx, make_symmetric=make_symmetric, skip_symmetric_check=skip_symmetric_check
|
|
97
|
+
)
|
|
98
|
+
nd_dst = ndbuffer.empty(
|
|
99
|
+
shape,
|
|
100
|
+
device_id=device_id,
|
|
101
|
+
dtype_name=dtype,
|
|
102
|
+
itemsize=NAME_TO_ITEM_SIZE[dtype],
|
|
103
|
+
strides=strides,
|
|
104
|
+
stream=stream_holder,
|
|
105
|
+
device_memory_pool=allocator,
|
|
106
|
+
logger=logger,
|
|
107
|
+
)
|
|
108
|
+
if nd_dst.cf_order() == "K":
|
|
109
|
+
raise ValueError("CudaDistributedTensor only supports 'C' or 'F' order")
|
|
110
|
+
return cls.wrap_ndbuffer(nd_dst)
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def create_from_host(
|
|
114
|
+
cls,
|
|
115
|
+
tensor: TensorHolder,
|
|
116
|
+
device_id: int,
|
|
117
|
+
stream_holder: StreamHolder,
|
|
118
|
+
symmetric_memory: bool = False,
|
|
119
|
+
):
|
|
120
|
+
if not symmetric_memory:
|
|
121
|
+
return super().create_from_host(tensor, device_id, stream_holder) # type: ignore
|
|
122
|
+
with device_ctx(device_id):
|
|
123
|
+
ctx = nvmath.distributed.get_context()
|
|
124
|
+
assert ctx is not None, "nvmath.distributed has not been initialized"
|
|
125
|
+
allocator = NvshmemNDBufferAllocator(device_id, ctx, make_symmetric=True, skip_symmetric_check=False)
|
|
126
|
+
src_ndbuffer = tensor.asndbuffer()
|
|
127
|
+
dst_ndbuffer = ndbuffer.empty_like(
|
|
128
|
+
src_ndbuffer,
|
|
129
|
+
device_id=device_id,
|
|
130
|
+
stream=stream_holder,
|
|
131
|
+
device_memory_pool=allocator,
|
|
132
|
+
)
|
|
133
|
+
if dst_ndbuffer.cf_order() == "K":
|
|
134
|
+
raise ValueError("CudaDistributedTensor only supports 'C' or 'F' order")
|
|
135
|
+
ndbuffer.copy_into(dst_ndbuffer, src_ndbuffer, stream_holder)
|
|
136
|
+
return cls.wrap_ndbuffer(dst_ndbuffer)
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
@abstractmethod
|
|
140
|
+
def wrap_ndbuffer(cls, ndbuffer: ndbuffer.NDBuffer):
|
|
141
|
+
"""
|
|
142
|
+
Defines how to wrap NDBuffer into a distributed tensor instance.
|
|
143
|
+
The exact implementation depends on `self.tensor` type
|
|
144
|
+
expected by the TensorHolder implementation to be mixed in.
|
|
145
|
+
E.g. for NDBufferTensor, it suffices to call `cls(ndbuffer)`,
|
|
146
|
+
while for CupyTensor, we need to wrap the NDBuffer into a cupy.ndarray first.
|
|
147
|
+
"""
|
|
148
|
+
raise NotImplementedError
|
|
149
|
+
|
|
150
|
+
def to(self, device_id, stream_holder, symmetric_memory: bool = False):
|
|
151
|
+
"""
|
|
152
|
+
In addition to the base class semantics:
|
|
153
|
+
- Target device must be the one used to initialize NVSHMEM on this process.
|
|
154
|
+
- Strides must be dense non-overlapping.
|
|
155
|
+
- Memory layout is preserved (if strides are dense non-overlapping,
|
|
156
|
+
the base class guarantees that)
|
|
157
|
+
"""
|
|
158
|
+
tensor = super().to(device_id, stream_holder) # type: ignore
|
|
159
|
+
assert tensor.is_symmetric_memory == symmetric_memory
|
|
160
|
+
return tensor
|
|
161
|
+
|
|
162
|
+
def reshape(self, shape: Sequence[int], *, copy: bool | None = None):
|
|
163
|
+
if copy:
|
|
164
|
+
raise NotImplementedError("reshape with copy=True is not supported for CUDA distributed tensor")
|
|
165
|
+
return super().reshape(shape, copy=copy) # type: ignore
|
|
@@ -0,0 +1,41 @@
|
|
|
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 Numpy ndarray objects.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations # allows typehint of class methods to return the self class
|
|
10
|
+
|
|
11
|
+
__all__ = ["NumpyDistributedTensor", "CudaDistributedTensor"]
|
|
12
|
+
|
|
13
|
+
from nvmath.internal.ndbuffer import ndbuffer
|
|
14
|
+
from nvmath.internal.tensor_ifc_numpy import CudaTensor, NumpyTensor
|
|
15
|
+
|
|
16
|
+
from .tensor_ifc import DistributedTensor
|
|
17
|
+
from .tensor_ifc_host_device import CudaDistributedTensorMixIn, HostDistributedTensorMixIn
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CudaDistributedTensor(CudaDistributedTensorMixIn, CudaTensor, DistributedTensor):
|
|
21
|
+
"""
|
|
22
|
+
Tensor wrapper for distributed cuda ndarrays.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
host_tensor_class: type[NumpyDistributedTensor] # set once NumpyDistributedTensor is defined
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def wrap_ndbuffer(cls, ndbuffer: ndbuffer.NDBuffer) -> CudaDistributedTensor:
|
|
29
|
+
return cls(ndbuffer)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Most methods aren't redefined, because they simply act on the local array
|
|
33
|
+
class NumpyDistributedTensor(HostDistributedTensorMixIn, NumpyTensor, DistributedTensor):
|
|
34
|
+
"""
|
|
35
|
+
Tensor wrapper for distributed numpy ndarrays.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
device_tensor_class = CudaDistributedTensor
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
CudaDistributedTensor.host_tensor_class = NumpyDistributedTensor
|