nvmath-python 0.5.0__cp313-cp313-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 +37 -0
- nvmath/_internal/__init__.py +0 -0
- nvmath/_internal/layout.py +58 -0
- nvmath/_utils.py +363 -0
- nvmath/bindings/__init__.pxd +0 -0
- nvmath/bindings/__init__.py +44 -0
- nvmath/bindings/_internal/__init__.pxd +0 -0
- nvmath/bindings/_internal/__init__.py +0 -0
- nvmath/bindings/_internal/cublas.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cublas.pxd +518 -0
- nvmath/bindings/_internal/cublasLt.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cublasLt.pxd +54 -0
- nvmath/bindings/_internal/cudss.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cudss.pxd +44 -0
- nvmath/bindings/_internal/cufft.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cufft.pxd +69 -0
- nvmath/bindings/_internal/cufftMp.pxd +77 -0
- nvmath/bindings/_internal/curand.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/curand.pxd +42 -0
- nvmath/bindings/_internal/cusolver.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusolver.pxd +15 -0
- nvmath/bindings/_internal/cusolverDn.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusolverDn.pxd +386 -0
- nvmath/bindings/_internal/cusparse.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusparse.pxd +270 -0
- nvmath/bindings/_internal/mathdx.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/mathdx.pxd +79 -0
- nvmath/bindings/_internal/nvshmem.pxd +29 -0
- nvmath/bindings/_internal/utils.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/utils.pxd +186 -0
- nvmath/bindings/cublas.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cublas.pxd +545 -0
- nvmath/bindings/cublas.pyi +18 -0
- nvmath/bindings/cublasLt.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cublasLt.pxd +99 -0
- nvmath/bindings/cudss.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cudss.pxd +83 -0
- nvmath/bindings/cudss.pyi +249 -0
- nvmath/bindings/cufft.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cufft.pxd +114 -0
- nvmath/bindings/cufftMp.pxd +124 -0
- nvmath/bindings/curand.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/curand.pxd +71 -0
- nvmath/bindings/cusolver.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cusolver.pxd +60 -0
- nvmath/bindings/cusolverDn.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cusolverDn.pxd +410 -0
- nvmath/bindings/cusparse.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cusparse.pxd +329 -0
- nvmath/bindings/cycublas.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cycublas.pxd +657 -0
- nvmath/bindings/cycublasLt.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cycublasLt.pxd +1010 -0
- nvmath/bindings/cycudss.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cycudss.pxd +191 -0
- nvmath/bindings/cycufft.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cycufft.pxd +320 -0
- nvmath/bindings/cycufftMp.pxd +333 -0
- nvmath/bindings/cycurand.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cycurand.pxd +146 -0
- nvmath/bindings/cycusolver.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cycusolver.pxd +143 -0
- nvmath/bindings/cycusolverDn.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cycusolverDn.pxd +414 -0
- nvmath/bindings/cycusparse.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cycusparse.pxd +476 -0
- nvmath/bindings/cymathdx.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cymathdx.pxd +363 -0
- nvmath/bindings/cynvshmem.pxd +126 -0
- nvmath/bindings/mathdx.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/mathdx.pxd +131 -0
- nvmath/bindings/mathdx.pyi +686 -0
- nvmath/bindings/nvpl/__init__.pxd +0 -0
- nvmath/bindings/nvpl/__init__.py +11 -0
- nvmath/bindings/nvpl/_internal/__init__.pxd +0 -0
- nvmath/bindings/nvpl/_internal/__init__.py +0 -0
- nvmath/bindings/nvpl/_internal/fft.pxd +36 -0
- nvmath/bindings/nvpl/cyfft.pxd +87 -0
- nvmath/bindings/nvpl/fft.pxd +100 -0
- nvmath/bindings/nvshmem.pxd +54 -0
- nvmath/device/__init__.py +23 -0
- nvmath/device/_deprecated.py +33 -0
- nvmath/device/caching.py +51 -0
- nvmath/device/common.py +131 -0
- nvmath/device/common_backend.py +125 -0
- nvmath/device/common_cuda.py +156 -0
- nvmath/device/common_mathdx.py +97 -0
- nvmath/device/common_numba.py +159 -0
- nvmath/device/common_opaque_tensor.py +128 -0
- nvmath/device/cublasdx.py +1085 -0
- nvmath/device/cublasdx_backend.py +552 -0
- nvmath/device/cublasdx_numba.py +528 -0
- nvmath/device/cufftdx.py +551 -0
- nvmath/device/cufftdx_backend.py +194 -0
- nvmath/device/cufftdx_numba.py +123 -0
- nvmath/device/curand_kernel.py +9147 -0
- nvmath/device/nvrtc.py +95 -0
- nvmath/device/patch.py +35 -0
- nvmath/device/random.py +417 -0
- nvmath/device/random_helpers.py +23 -0
- nvmath/device/random_states.py +189 -0
- nvmath/device/types.py +11 -0
- nvmath/device/vector_types_numba.py +203 -0
- nvmath/distributed/__init__.py +117 -0
- nvmath/distributed/_internal/__init__.py +0 -0
- nvmath/distributed/_internal/nvshmem.py +243 -0
- nvmath/distributed/_internal/tensor_ifc_cupy.py +111 -0
- nvmath/distributed/_internal/tensor_ifc_numpy.py +53 -0
- nvmath/distributed/_internal/tensor_ifc_torch.py +108 -0
- nvmath/distributed/_internal/tensor_wrapper.py +59 -0
- nvmath/distributed/_utils.py +152 -0
- nvmath/distributed/fft/__init__.py +6 -0
- nvmath/distributed/fft/_configuration.py +86 -0
- nvmath/distributed/fft/fft.py +1737 -0
- nvmath/distributed/reshape/__init__.py +6 -0
- nvmath/distributed/reshape/_configuration.py +39 -0
- nvmath/distributed/reshape/reshape.py +1249 -0
- nvmath/fft/__init__.py +7 -0
- nvmath/fft/_configuration.py +208 -0
- nvmath/fft/_exec_utils.py +116 -0
- nvmath/fft/_helpers.py +260 -0
- nvmath/fft/fft.py +2346 -0
- nvmath/internal/__init__.py +5 -0
- nvmath/internal/enum_utils.py +142 -0
- nvmath/internal/formatters.py +87 -0
- nvmath/internal/mem_limit.py +51 -0
- nvmath/internal/package_ifc.py +119 -0
- nvmath/internal/package_ifc_cuda.py +43 -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 +143 -0
- nvmath/internal/tensor_ifc_cupy.py +185 -0
- nvmath/internal/tensor_ifc_numpy.py +157 -0
- nvmath/internal/tensor_ifc_torch.py +115 -0
- nvmath/internal/tensor_wrapper.py +160 -0
- nvmath/internal/typemaps.py +154 -0
- nvmath/internal/utils.py +634 -0
- nvmath/linalg/__init__.py +11 -0
- nvmath/linalg/_internal/__init__.py +3 -0
- nvmath/linalg/_internal/algo_cap_ifc.py +86 -0
- nvmath/linalg/_internal/algo_config_ifc.py +87 -0
- nvmath/linalg/_internal/enum_to_tuples.py +64 -0
- nvmath/linalg/_internal/epilog_protocol.py +738 -0
- nvmath/linalg/_internal/matmul_desc_ifc.py +72 -0
- nvmath/linalg/_internal/matmul_pref_ifc.py +65 -0
- nvmath/linalg/_internal/matrix_layout_ifc.py +59 -0
- nvmath/linalg/_internal/typemaps.py +135 -0
- nvmath/linalg/_internal/utils.py +96 -0
- nvmath/linalg/advanced/__init__.py +8 -0
- nvmath/linalg/advanced/_algorithmmod.py +142 -0
- nvmath/linalg/advanced/_configuration.py +320 -0
- nvmath/linalg/advanced/helpers/__init__.py +5 -0
- nvmath/linalg/advanced/helpers/matmul.py +173 -0
- nvmath/linalg/advanced/matmulmod.py +2855 -0
- nvmath/memory.py +266 -0
- nvmath/sparse/__init__.py +18 -0
- nvmath/sparse/_internal/__init__.py +21 -0
- nvmath/sparse/_internal/common_utils.py +81 -0
- nvmath/sparse/_internal/cudss_config_ifc.py +584 -0
- nvmath/sparse/_internal/cudss_data_ifc.py +291 -0
- nvmath/sparse/_internal/cudss_utils.py +398 -0
- nvmath/sparse/_internal/sparse_csr_ifc.py +154 -0
- nvmath/sparse/_internal/sparse_format_helpers.py +88 -0
- nvmath/sparse/_internal/sparse_tensor_ifc.py +80 -0
- nvmath/sparse/advanced/__init__.py +7 -0
- nvmath/sparse/advanced/_configuration.py +150 -0
- nvmath/sparse/advanced/direct_solver.py +1705 -0
- nvmath_python-0.5.0.dist-info/METADATA +122 -0
- nvmath_python-0.5.0.dist-info/RECORD +174 -0
- nvmath_python-0.5.0.dist-info/WHEEL +5 -0
- nvmath_python-0.5.0.dist-info/licenses/LICENSE +177 -0
- nvmath_python-0.5.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
__all__ = ["initialize", "finalize", "is_initialized", "nvshmem_empty_dlpack", "free", "NvshmemMemoryManager"]
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import numpy as np
|
|
9
|
+
import cuda.core.experimental as ccx
|
|
10
|
+
|
|
11
|
+
from nvmath import memory
|
|
12
|
+
from nvmath.bindings import nvshmem # type: ignore
|
|
13
|
+
from nvmath.internal.utils import device_ctx
|
|
14
|
+
|
|
15
|
+
# Indicates if this module has initialized NVSHMEM
|
|
16
|
+
_nvshmem_initialized_here = False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def initialize(device_id: int, mpi_comm) -> None:
|
|
20
|
+
"""Initialize NVSHMEM runtime if not initialized, otherwise do nothing."""
|
|
21
|
+
|
|
22
|
+
global _nvshmem_initialized_here
|
|
23
|
+
|
|
24
|
+
rank = mpi_comm.Get_rank()
|
|
25
|
+
nranks = mpi_comm.Get_size()
|
|
26
|
+
|
|
27
|
+
# Here we set the device for NVSHMEM initialization, but we also need to make sure that
|
|
28
|
+
# a CUDA context has been created before initializing NVSHMEM. We can't rely on
|
|
29
|
+
# `device_ctx` to do it since it's not guaranteed to make a runtime API call.
|
|
30
|
+
old_device = ccx.Device()
|
|
31
|
+
ccx.Device(device_id).set_current()
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
status = nvshmem.init_status()
|
|
35
|
+
if status > nvshmem.STATUS_IS_BOOTSTRAPPED:
|
|
36
|
+
# NVSHMEM is already initialized
|
|
37
|
+
# NOTE: We assume that the user has passed the same communicator and device_id
|
|
38
|
+
# on which NVSHMEM was initialized (we have no way of checking here).
|
|
39
|
+
assert nvshmem.n_pes() == nranks
|
|
40
|
+
return
|
|
41
|
+
elif status == nvshmem.STATUS_IS_BOOTSTRAPPED:
|
|
42
|
+
# A value of 0 indicates an initialization that is similar to when nvshmem_init
|
|
43
|
+
# is used.
|
|
44
|
+
nvshmem.hostlib_init_attr(0, 0)
|
|
45
|
+
assert nvshmem.n_pes() == nranks
|
|
46
|
+
# Sanity check, can eventually remove
|
|
47
|
+
assert nvshmem.init_status() > nvshmem.STATUS_IS_BOOTSTRAPPED
|
|
48
|
+
_nvshmem_initialized_here = True
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
attr = nvshmem.InitAttr()
|
|
52
|
+
unique_id = nvshmem.UniqueId()
|
|
53
|
+
# PE 0 queries the unique ID
|
|
54
|
+
if rank == 0:
|
|
55
|
+
nvshmem.get_uniqueid(unique_id.ptr)
|
|
56
|
+
# PE 0 broadcasts the unique ID
|
|
57
|
+
mpi_comm.Bcast(unique_id._data.view(np.int8), root=0)
|
|
58
|
+
nvshmem.set_attr_uniqueid_args(rank, nranks, unique_id.ptr, attr.ptr)
|
|
59
|
+
nvshmem.hostlib_init_attr(nvshmem.Flags.INIT_WITH_UNIQUEID, attr.ptr)
|
|
60
|
+
|
|
61
|
+
# sanity check
|
|
62
|
+
assert nvshmem.init_status() > nvshmem.STATUS_IS_BOOTSTRAPPED
|
|
63
|
+
_nvshmem_initialized_here = True
|
|
64
|
+
finally:
|
|
65
|
+
old_device.set_current()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def finalize(device_id: int) -> None:
|
|
69
|
+
"""Finalize NVSHMEM runtime if initialized"""
|
|
70
|
+
global _nvshmem_initialized_here
|
|
71
|
+
if not _nvshmem_initialized_here or (nvshmem.init_status() < nvshmem.STATUS_IS_INITIALIZED):
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
with device_ctx(device_id):
|
|
75
|
+
nvshmem.hostlib_finalize()
|
|
76
|
+
_nvshmem_initialized_here = False
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def is_initialized() -> bool:
|
|
80
|
+
return nvshmem.init_status() >= nvshmem.STATUS_IS_INITIALIZED
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _check_initialized():
|
|
84
|
+
if nvshmem.init_status() < nvshmem.STATUS_IS_INITIALIZED:
|
|
85
|
+
raise RuntimeError("NVSHMEM is not initialized. Please initialize nvmath.distributed")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# Keeps track of memory allocated with nvshmem_empty_dlpack. This is used to report memory
|
|
89
|
+
# leaks and double free errors. This functionality might be handled by nvshmem4py in the
|
|
90
|
+
# future. More precisely, how it works is that when a buffer allocated via
|
|
91
|
+
# nvshmem_empty_dlpack is garbage-collected and found to not have been freed explicitly
|
|
92
|
+
# with the `free` function in this module, we report it.
|
|
93
|
+
_resource_registry = {}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class _NvshmemResource(ccx.MemoryResource):
|
|
97
|
+
def __init__(self, device):
|
|
98
|
+
self.device = device
|
|
99
|
+
|
|
100
|
+
def allocate(self, size, stream=None) -> ccx.Buffer:
|
|
101
|
+
# NOTE: setting the device is left to the caller
|
|
102
|
+
ptr = nvshmem.malloc(size)
|
|
103
|
+
if ptr == 0 and size != 0:
|
|
104
|
+
raise MemoryError("nvshmem_malloc returned NULL")
|
|
105
|
+
self.freed = False
|
|
106
|
+
return ccx.Buffer.from_handle(ptr=ptr, size=size, mr=self)
|
|
107
|
+
|
|
108
|
+
def deallocate(self, ptr, size, stream=None, manual=False):
|
|
109
|
+
# NOTE: setting the device is left to the caller
|
|
110
|
+
if not manual:
|
|
111
|
+
if self.freed:
|
|
112
|
+
return # this is fine
|
|
113
|
+
else:
|
|
114
|
+
# We can't call nvshmem_free when deallocate is triggered by the GC, since
|
|
115
|
+
# the GC has non-deterministic behavior and nvshmem_free is a collective
|
|
116
|
+
# call.
|
|
117
|
+
raise RuntimeError("Symmetric heap memory needs to be deallocated explicitly")
|
|
118
|
+
if self.freed:
|
|
119
|
+
raise RuntimeError("This memory resource was already deallocated")
|
|
120
|
+
nvshmem.free(ptr)
|
|
121
|
+
self.freed = True
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def is_device_accessible(self) -> bool:
|
|
125
|
+
return True
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def is_host_accessible(self) -> bool:
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def device_id(self) -> int:
|
|
133
|
+
return self.device.device_id
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def nvshmem_empty_dlpack(size, device_id, comm, make_symmetric=False, logger=None):
|
|
137
|
+
"""Return uninitialized DLPack buffer of given size in bytes, allocated using
|
|
138
|
+
nvshmem_malloc (which makes this a *collective* call). Note that the DLPack
|
|
139
|
+
buffer currently does not include any shape, dtype, or stride information.
|
|
140
|
+
|
|
141
|
+
IMPORTANT: device_id must be the one with which NVSHMEM was initialized, and
|
|
142
|
+
setting the device is left to the caller.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
_check_initialized()
|
|
146
|
+
|
|
147
|
+
global _resource_registry
|
|
148
|
+
|
|
149
|
+
logger = logger if logger is not None else logging.getLogger()
|
|
150
|
+
|
|
151
|
+
from mpi4py import MPI
|
|
152
|
+
|
|
153
|
+
max_size = np.array([-size, size], dtype=np.int64)
|
|
154
|
+
comm.Allreduce(MPI.IN_PLACE, max_size, MPI.MAX)
|
|
155
|
+
if -max_size[0] != max_size[1]:
|
|
156
|
+
# The buffer size is not the same on all processes.
|
|
157
|
+
if not make_symmetric:
|
|
158
|
+
raise ValueError(
|
|
159
|
+
"The buffer size for symmetric memory allocation is not the same on all processes. "
|
|
160
|
+
"Consider using make_symmetric=True if you have uneven data distribution."
|
|
161
|
+
)
|
|
162
|
+
else:
|
|
163
|
+
logger.info(
|
|
164
|
+
"Symmetric memory allocator: the buffer will be padded on some processes to "
|
|
165
|
+
f"satisfy symmetric requirement (make_symmetric=True), size={size} max_size={max_size[1]}."
|
|
166
|
+
)
|
|
167
|
+
else:
|
|
168
|
+
logger.info(f"Symmetric memory allocator: the requested buffer size ({size}) is the same on all processes.")
|
|
169
|
+
# Sizes are equal or make_symmetric=True.
|
|
170
|
+
size = max_size[1]
|
|
171
|
+
|
|
172
|
+
mem = _NvshmemResource(ccx.Device(device_id))
|
|
173
|
+
mem_buffer = mem.allocate(size)
|
|
174
|
+
pointer = mem_buffer._mnff.ptr
|
|
175
|
+
assert pointer not in _resource_registry
|
|
176
|
+
_resource_registry[pointer] = (mem, size)
|
|
177
|
+
return mem_buffer
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def free(pointer):
|
|
181
|
+
"""This is a *collective* call (invokes nvshmem_free), used to free buffers
|
|
182
|
+
allocated with nvshmem_empty_dlpack.
|
|
183
|
+
Setting the device is left to the caller"""
|
|
184
|
+
|
|
185
|
+
_check_initialized()
|
|
186
|
+
|
|
187
|
+
global _resource_registry
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
resource, size = _resource_registry.pop(pointer)
|
|
191
|
+
except KeyError:
|
|
192
|
+
raise RuntimeError(
|
|
193
|
+
"Unknown pointer to free. Possible causes:\n"
|
|
194
|
+
" - This memory was not allocated with nvmath.distributed helpers.\n"
|
|
195
|
+
" - This memory was already freed. Possible causes:\n"
|
|
196
|
+
" - Free was called multiple times on the same tensor.\n"
|
|
197
|
+
" - You have multiple tensors sharing the same symmetric memory buffer,\n"
|
|
198
|
+
" e.g. as a result of inplace operations, or tensor operations that\n"
|
|
199
|
+
" result in views such as slicing."
|
|
200
|
+
)
|
|
201
|
+
resource.deallocate(pointer, size, manual=True)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class NvshmemMemoryManager(memory.BaseCUDAMemoryManager):
|
|
205
|
+
"""
|
|
206
|
+
Nvshmem memory allocator.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
device_id: The ID (int) of the device on which memory is to be allocated.
|
|
210
|
+
logger (logging.Logger): Python Logger object.
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
def __init__(self, device_id, logger):
|
|
214
|
+
"""
|
|
215
|
+
__init__(device_id)
|
|
216
|
+
"""
|
|
217
|
+
_check_initialized()
|
|
218
|
+
self.device_id = device_id
|
|
219
|
+
self.logger = logger
|
|
220
|
+
|
|
221
|
+
def memalloc(self, size):
|
|
222
|
+
"""This is a *collective* call (invokes nvshmem_malloc)"""
|
|
223
|
+
mem = _NvshmemResource(ccx.Device(self.device_id))
|
|
224
|
+
mem_buffer = mem.allocate(size)
|
|
225
|
+
pointer = mem_buffer._mnff.ptr
|
|
226
|
+
assert pointer not in _resource_registry
|
|
227
|
+
_resource_registry[pointer] = (mem, size)
|
|
228
|
+
|
|
229
|
+
self.logger.debug(
|
|
230
|
+
f"NvshmemMemoryManager (allocate memory): size = {size}, pointer = {pointer}, device_id = {self.device_id}"
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
return SymmetricMemoryPointer(mem_buffer)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class SymmetricMemoryPointer(memory.MemoryPointer):
|
|
237
|
+
def __init__(self, mem_buffer):
|
|
238
|
+
super().__init__(mem_buffer._mnff.ptr, mem_buffer.size, finalizer=None)
|
|
239
|
+
self.mem_buffer = mem_buffer
|
|
240
|
+
|
|
241
|
+
def free(self):
|
|
242
|
+
"""This is a *collective* call (invokes nvshmem_free)"""
|
|
243
|
+
free(self.device_ptr)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Copyright (c) 2025, 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"]
|
|
12
|
+
|
|
13
|
+
import math
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
import cupy
|
|
17
|
+
except ImportError:
|
|
18
|
+
cupy = None
|
|
19
|
+
|
|
20
|
+
from collections.abc import Sequence
|
|
21
|
+
|
|
22
|
+
import nvmath.distributed
|
|
23
|
+
from nvmath.internal.tensor_ifc_cupy import CupyTensor
|
|
24
|
+
from nvmath.internal.utils import device_ctx
|
|
25
|
+
from nvmath.bindings import nvshmem # type: ignore
|
|
26
|
+
from nvmath.distributed._internal.nvshmem import nvshmem_empty_dlpack
|
|
27
|
+
|
|
28
|
+
from .tensor_ifc_numpy import NumpyDistributedTensor
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Most methods aren't redefined, because they simply act on the local array
|
|
32
|
+
class CupyDistributedTensor(CupyTensor):
|
|
33
|
+
"""
|
|
34
|
+
Tensor wrapper for distributed cupy ndarrays.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, tensor):
|
|
38
|
+
super().__init__(tensor)
|
|
39
|
+
if nvshmem.ptr(tensor.data.ptr, nvshmem.my_pe()) == 0:
|
|
40
|
+
raise TypeError(
|
|
41
|
+
"Operand must be on the symmetric heap. Consider allocating it "
|
|
42
|
+
"with nvmath.distributed.allocate_symmetric_memory()."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def empty(cls, shape, device_id="cpu", *, dtype="float32", strides=None, **context) -> CupyDistributedTensor:
|
|
47
|
+
"""
|
|
48
|
+
Create an empty tensor of the specified shape and data type.
|
|
49
|
+
|
|
50
|
+
Note, that the strides, if specified, MUST correspond to a dense (possibly permuted)
|
|
51
|
+
tensor, otherwise the created tensor may be corrupted.
|
|
52
|
+
"""
|
|
53
|
+
dtype = CupyTensor.name_to_dtype[dtype]
|
|
54
|
+
|
|
55
|
+
from nvmath.distributed._utils import calculate_strides
|
|
56
|
+
|
|
57
|
+
ctx = nvmath.distributed.get_context()
|
|
58
|
+
assert ctx is not None, "nvmath.distributed has not been initialized"
|
|
59
|
+
|
|
60
|
+
make_symmetric = context.get("make_symmetric", False)
|
|
61
|
+
logger = context.get("logger")
|
|
62
|
+
|
|
63
|
+
order = "C"
|
|
64
|
+
if strides is not None:
|
|
65
|
+
if list(strides) == calculate_strides(shape, reversed(range(len(shape)))):
|
|
66
|
+
order = "C"
|
|
67
|
+
elif list(strides) == calculate_strides(shape, range(len(shape))):
|
|
68
|
+
order = "F"
|
|
69
|
+
else:
|
|
70
|
+
raise ValueError("CupyDistributedTensor.empty() only supports 'C' or 'F' order")
|
|
71
|
+
|
|
72
|
+
with device_ctx(device_id):
|
|
73
|
+
# TODO: ideally strides should be set in DLPack, but cuda.core doesn't support
|
|
74
|
+
# ndarray yet and instead returns a flat buffer.
|
|
75
|
+
size = math.prod(shape, start=dtype.itemsize)
|
|
76
|
+
dlpack_buf = nvshmem_empty_dlpack(size, device_id, ctx.communicator, make_symmetric=make_symmetric, logger=logger)
|
|
77
|
+
tensor = cupy.from_dlpack(dlpack_buf)
|
|
78
|
+
# Buffer may be padded if make_symmetric=True.
|
|
79
|
+
tensor = tensor[:size].view(dtype).reshape(shape, order=order)
|
|
80
|
+
# assert tensor is not a copy
|
|
81
|
+
assert tensor.base is not None
|
|
82
|
+
|
|
83
|
+
return cls(tensor)
|
|
84
|
+
|
|
85
|
+
def to(self, device_id, stream_holder) -> NumpyDistributedTensor | CupyDistributedTensor:
|
|
86
|
+
"""
|
|
87
|
+
In addition to the base class semantics:
|
|
88
|
+
- Source or target device must be the one used to initialize NVSHMEM on this
|
|
89
|
+
process. This implies that copy from one CUDA device to another is not allowed.
|
|
90
|
+
- Memory layout is preserved.
|
|
91
|
+
- Strides must be dense non-overlapping.
|
|
92
|
+
"""
|
|
93
|
+
if not (device_id == "cpu" or isinstance(device_id, int)):
|
|
94
|
+
raise ValueError(f"The device must be specified as an integer or 'cpu', not '{device_id}'.")
|
|
95
|
+
|
|
96
|
+
if device_id == "cpu":
|
|
97
|
+
# NOTE: not using self.numpy() because it doesn't preserve memory layout.
|
|
98
|
+
np_tensor = NumpyDistributedTensor.empty(self.shape, dtype=self.dtype, strides=self.strides)
|
|
99
|
+
np_tensor.copy_(self, stream_holder)
|
|
100
|
+
return np_tensor
|
|
101
|
+
|
|
102
|
+
if device_id != self.device_id:
|
|
103
|
+
raise ValueError("Cannot copy distributed tensor to a different CUDA device")
|
|
104
|
+
|
|
105
|
+
with device_ctx(device_id), stream_holder.ctx:
|
|
106
|
+
return CupyDistributedTensor(cupy.asarray(self.tensor))
|
|
107
|
+
|
|
108
|
+
def reshape(self, shape: Sequence[int], *, copy: bool | None = None) -> CupyDistributedTensor:
|
|
109
|
+
if copy:
|
|
110
|
+
raise NotImplementedError("reshape with copy=True is not supported for CupyDistributedTensor")
|
|
111
|
+
return super().reshape(shape, copy=copy)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Copyright (c) 2025, 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"]
|
|
12
|
+
|
|
13
|
+
from nvmath.internal.tensor_ifc_numpy import NumpyTensor
|
|
14
|
+
from nvmath.internal.utils import device_ctx
|
|
15
|
+
|
|
16
|
+
from typing import TYPE_CHECKING
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
# Can't import CupyDistributedTensor at runtime here due to circular import, but mypy
|
|
20
|
+
# needs it for type checking.
|
|
21
|
+
from .tensor_ifc_cupy import CupyDistributedTensor
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Most methods aren't redefined, because they simply act on the local array
|
|
25
|
+
class NumpyDistributedTensor(NumpyTensor):
|
|
26
|
+
"""
|
|
27
|
+
Tensor wrapper for distributed numpy ndarrays.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, tensor):
|
|
31
|
+
super().__init__(tensor)
|
|
32
|
+
|
|
33
|
+
def to(self, device_id, stream_holder) -> NumpyDistributedTensor | CupyDistributedTensor:
|
|
34
|
+
"""
|
|
35
|
+
In addition to the base class semantics:
|
|
36
|
+
- Target device must be the one used to initialize NVSHMEM on this process.
|
|
37
|
+
- Memory layout is preserved.
|
|
38
|
+
- Strides must be dense non-overlapping.
|
|
39
|
+
"""
|
|
40
|
+
if not (device_id == "cpu" or isinstance(device_id, int)):
|
|
41
|
+
raise ValueError(f"The device must be specified as an integer or 'cpu', not '{device_id}'.")
|
|
42
|
+
|
|
43
|
+
if device_id == "cpu":
|
|
44
|
+
return NumpyDistributedTensor(self.tensor)
|
|
45
|
+
|
|
46
|
+
from .tensor_ifc_cupy import CupyDistributedTensor
|
|
47
|
+
|
|
48
|
+
with device_ctx(device_id), stream_holder.ctx:
|
|
49
|
+
tensor_device = CupyDistributedTensor.empty(
|
|
50
|
+
self.shape, dtype=self.dtype, device_id=device_id, strides=self.strides, make_symmetric=True
|
|
51
|
+
)
|
|
52
|
+
tensor_device.copy_(self, stream_holder)
|
|
53
|
+
return tensor_device
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Interface to seamlessly use distributed Torch tensor objects.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations # allows typehint of class methods to return the self class
|
|
10
|
+
|
|
11
|
+
__all__ = ["TorchDistributedTensor"]
|
|
12
|
+
|
|
13
|
+
import math
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
import torch
|
|
17
|
+
except ImportError:
|
|
18
|
+
torch = None # type: ignore
|
|
19
|
+
|
|
20
|
+
from collections.abc import Sequence
|
|
21
|
+
|
|
22
|
+
import nvmath.distributed
|
|
23
|
+
from nvmath.internal.tensor_ifc_torch import TorchTensor
|
|
24
|
+
from nvmath.internal.utils import device_ctx
|
|
25
|
+
from nvmath.bindings import nvshmem # type: ignore
|
|
26
|
+
from nvmath.distributed._internal.nvshmem import nvshmem_empty_dlpack
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Most methods aren't redefined, because they simply act on the local array
|
|
30
|
+
class TorchDistributedTensor(TorchTensor):
|
|
31
|
+
"""
|
|
32
|
+
TensorHolder for distributed torch tensors.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, tensor):
|
|
36
|
+
super().__init__(tensor)
|
|
37
|
+
if tensor.device.index is not None and nvshmem.ptr(tensor.data_ptr(), nvshmem.my_pe()) == 0:
|
|
38
|
+
raise TypeError(
|
|
39
|
+
"Operand must be on the symmetric heap. Consider allocating it "
|
|
40
|
+
"with nvmath.distributed.allocate_symmetric_memory()."
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def empty(cls, shape, device_id="cpu", *, dtype="float32", strides=None, **context) -> TorchDistributedTensor:
|
|
45
|
+
"""
|
|
46
|
+
Create an empty tensor of the specified shape and data type on the specified device
|
|
47
|
+
(None, 'cpu', or device id).
|
|
48
|
+
|
|
49
|
+
Note, that the strides, if specified, should correspond to a dense
|
|
50
|
+
(possibly permuted) tensor and MUST NOT overlap.
|
|
51
|
+
Otherwise, the behaviour is not defined.
|
|
52
|
+
"""
|
|
53
|
+
if device_id == "cpu":
|
|
54
|
+
return super().empty(shape, device_id, dtype=dtype, strides=strides)
|
|
55
|
+
|
|
56
|
+
dtype = TorchTensor.name_to_dtype[dtype]
|
|
57
|
+
|
|
58
|
+
ctx = nvmath.distributed.get_context()
|
|
59
|
+
assert ctx is not None, "nvmath.distributed has not been initialized"
|
|
60
|
+
|
|
61
|
+
make_symmetric = context.get("make_symmetric", False)
|
|
62
|
+
logger = context.get("logger")
|
|
63
|
+
|
|
64
|
+
with device_ctx(device_id):
|
|
65
|
+
size = math.prod(shape, start=dtype.itemsize)
|
|
66
|
+
# TODO: ideally strides should be set in DLPack, but cuda.core doesn't support
|
|
67
|
+
# ndarray yet and instead returns a flat buffer.
|
|
68
|
+
dlpack_buf = nvshmem_empty_dlpack(size, device_id, ctx.communicator, make_symmetric=make_symmetric, logger=logger)
|
|
69
|
+
tensor = torch.from_dlpack(dlpack_buf)
|
|
70
|
+
# Buffer may be padded if make_symmetric=True.
|
|
71
|
+
tensor = tensor[:size]
|
|
72
|
+
if strides is None:
|
|
73
|
+
tensor = tensor.view(dtype).view(shape)
|
|
74
|
+
else:
|
|
75
|
+
tensor = torch.as_strided(tensor.view(dtype), shape, strides)
|
|
76
|
+
|
|
77
|
+
return cls(tensor)
|
|
78
|
+
|
|
79
|
+
def to(self, device_id, stream_holder) -> TorchDistributedTensor:
|
|
80
|
+
"""
|
|
81
|
+
In addition to the base class semantics:
|
|
82
|
+
- Source or target device must be the one used to initialize NVSHMEM on this
|
|
83
|
+
process. This implies that copy from one CUDA device to another is not allowed.
|
|
84
|
+
- Memory layout is preserved.
|
|
85
|
+
- Strides must be non-overlapping.
|
|
86
|
+
"""
|
|
87
|
+
if not (device_id == "cpu" or isinstance(device_id, int)):
|
|
88
|
+
raise ValueError(f"The device must be specified as an integer or 'cpu', not '{device_id}'.")
|
|
89
|
+
|
|
90
|
+
if device_id == "cpu" or self.device_id == device_id:
|
|
91
|
+
with stream_holder.ctx:
|
|
92
|
+
tensor = self.tensor.to(device=device_id, non_blocking=(device_id != "cpu"))
|
|
93
|
+
return TorchDistributedTensor(tensor)
|
|
94
|
+
|
|
95
|
+
if self.device_id != "cpu" and self.device_id != device_id:
|
|
96
|
+
raise ValueError("Cannot copy distributed tensor to a different CUDA device")
|
|
97
|
+
|
|
98
|
+
with stream_holder.ctx:
|
|
99
|
+
tensor_device = TorchDistributedTensor.empty(
|
|
100
|
+
self.shape, device_id=device_id, dtype=self.dtype, strides=self.strides, make_symmetric=True
|
|
101
|
+
)
|
|
102
|
+
tensor_device.tensor.copy_(self.tensor, non_blocking=True)
|
|
103
|
+
return tensor_device
|
|
104
|
+
|
|
105
|
+
def reshape(self, shape: Sequence[int], *, copy: bool | None = None) -> TorchDistributedTensor:
|
|
106
|
+
if copy and self.device_id != "cpu":
|
|
107
|
+
raise NotImplementedError("reshape with copy=True is not supported for TorchDistributedTensor on GPU")
|
|
108
|
+
return super().reshape(shape, copy=copy)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Entry point to using tensors from different libraries seamlessly.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__all__ = ["wrap_operand", "copy_"]
|
|
10
|
+
|
|
11
|
+
import warnings
|
|
12
|
+
from collections.abc import Sequence
|
|
13
|
+
|
|
14
|
+
from nvmath.internal.tensor_ifc import Tensor, TensorHolder
|
|
15
|
+
from nvmath.internal.tensor_wrapper import infer_tensor_package
|
|
16
|
+
|
|
17
|
+
from .tensor_ifc_numpy import NumpyDistributedTensor
|
|
18
|
+
|
|
19
|
+
_TENSOR_TYPES: dict[str, type[TensorHolder]] = {"numpy": NumpyDistributedTensor}
|
|
20
|
+
|
|
21
|
+
# Optional modules
|
|
22
|
+
try:
|
|
23
|
+
from .tensor_ifc_cupy import CupyDistributedTensor
|
|
24
|
+
|
|
25
|
+
_TENSOR_TYPES["cupy"] = CupyDistributedTensor
|
|
26
|
+
except ImportError:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
from .tensor_ifc_torch import TorchDistributedTensor
|
|
31
|
+
|
|
32
|
+
_TENSOR_TYPES["torch"] = TorchDistributedTensor
|
|
33
|
+
except ImportError:
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def wrap_operand(native_operand: Tensor) -> TensorHolder[Tensor]:
|
|
38
|
+
"""
|
|
39
|
+
Wrap one "native" operand so that package-agnostic API can be used.
|
|
40
|
+
"""
|
|
41
|
+
if isinstance(native_operand, TensorHolder):
|
|
42
|
+
msg = (
|
|
43
|
+
"wrap_operand() is being called unnecessarily because the input is already a TensorHolder. "
|
|
44
|
+
"Only public facing APIs should call wrap_operand(). "
|
|
45
|
+
"Internal APIs should assume the operands are TensorHolder already."
|
|
46
|
+
"Trying to wrap a TensorHolder will become an error in the future."
|
|
47
|
+
)
|
|
48
|
+
warnings.warn(msg, DeprecationWarning)
|
|
49
|
+
return native_operand
|
|
50
|
+
wrapped_operand = _TENSOR_TYPES[infer_tensor_package(native_operand)](native_operand)
|
|
51
|
+
return wrapped_operand
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def copy_(src: Sequence[TensorHolder], dest: Sequence[TensorHolder], stream_holder):
|
|
55
|
+
"""
|
|
56
|
+
Copy the wrapped operands in dest to the corresponding wrapped operands in src.
|
|
57
|
+
"""
|
|
58
|
+
for s, d in zip(src, dest, strict=True):
|
|
59
|
+
d.copy_(s, stream_holder=stream_holder)
|