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.
Files changed (174) hide show
  1. nvmath/__init__.pxd +0 -0
  2. nvmath/__init__.py +37 -0
  3. nvmath/_internal/__init__.py +0 -0
  4. nvmath/_internal/layout.py +58 -0
  5. nvmath/_utils.py +363 -0
  6. nvmath/bindings/__init__.pxd +0 -0
  7. nvmath/bindings/__init__.py +44 -0
  8. nvmath/bindings/_internal/__init__.pxd +0 -0
  9. nvmath/bindings/_internal/__init__.py +0 -0
  10. nvmath/bindings/_internal/cublas.cp313-win_amd64.pyd +0 -0
  11. nvmath/bindings/_internal/cublas.pxd +518 -0
  12. nvmath/bindings/_internal/cublasLt.cp313-win_amd64.pyd +0 -0
  13. nvmath/bindings/_internal/cublasLt.pxd +54 -0
  14. nvmath/bindings/_internal/cudss.cp313-win_amd64.pyd +0 -0
  15. nvmath/bindings/_internal/cudss.pxd +44 -0
  16. nvmath/bindings/_internal/cufft.cp313-win_amd64.pyd +0 -0
  17. nvmath/bindings/_internal/cufft.pxd +69 -0
  18. nvmath/bindings/_internal/cufftMp.pxd +77 -0
  19. nvmath/bindings/_internal/curand.cp313-win_amd64.pyd +0 -0
  20. nvmath/bindings/_internal/curand.pxd +42 -0
  21. nvmath/bindings/_internal/cusolver.cp313-win_amd64.pyd +0 -0
  22. nvmath/bindings/_internal/cusolver.pxd +15 -0
  23. nvmath/bindings/_internal/cusolverDn.cp313-win_amd64.pyd +0 -0
  24. nvmath/bindings/_internal/cusolverDn.pxd +386 -0
  25. nvmath/bindings/_internal/cusparse.cp313-win_amd64.pyd +0 -0
  26. nvmath/bindings/_internal/cusparse.pxd +270 -0
  27. nvmath/bindings/_internal/mathdx.cp313-win_amd64.pyd +0 -0
  28. nvmath/bindings/_internal/mathdx.pxd +79 -0
  29. nvmath/bindings/_internal/nvshmem.pxd +29 -0
  30. nvmath/bindings/_internal/utils.cp313-win_amd64.pyd +0 -0
  31. nvmath/bindings/_internal/utils.pxd +186 -0
  32. nvmath/bindings/cublas.cp313-win_amd64.pyd +0 -0
  33. nvmath/bindings/cublas.pxd +545 -0
  34. nvmath/bindings/cublas.pyi +18 -0
  35. nvmath/bindings/cublasLt.cp313-win_amd64.pyd +0 -0
  36. nvmath/bindings/cublasLt.pxd +99 -0
  37. nvmath/bindings/cudss.cp313-win_amd64.pyd +0 -0
  38. nvmath/bindings/cudss.pxd +83 -0
  39. nvmath/bindings/cudss.pyi +249 -0
  40. nvmath/bindings/cufft.cp313-win_amd64.pyd +0 -0
  41. nvmath/bindings/cufft.pxd +114 -0
  42. nvmath/bindings/cufftMp.pxd +124 -0
  43. nvmath/bindings/curand.cp313-win_amd64.pyd +0 -0
  44. nvmath/bindings/curand.pxd +71 -0
  45. nvmath/bindings/cusolver.cp313-win_amd64.pyd +0 -0
  46. nvmath/bindings/cusolver.pxd +60 -0
  47. nvmath/bindings/cusolverDn.cp313-win_amd64.pyd +0 -0
  48. nvmath/bindings/cusolverDn.pxd +410 -0
  49. nvmath/bindings/cusparse.cp313-win_amd64.pyd +0 -0
  50. nvmath/bindings/cusparse.pxd +329 -0
  51. nvmath/bindings/cycublas.cp313-win_amd64.pyd +0 -0
  52. nvmath/bindings/cycublas.pxd +657 -0
  53. nvmath/bindings/cycublasLt.cp313-win_amd64.pyd +0 -0
  54. nvmath/bindings/cycublasLt.pxd +1010 -0
  55. nvmath/bindings/cycudss.cp313-win_amd64.pyd +0 -0
  56. nvmath/bindings/cycudss.pxd +191 -0
  57. nvmath/bindings/cycufft.cp313-win_amd64.pyd +0 -0
  58. nvmath/bindings/cycufft.pxd +320 -0
  59. nvmath/bindings/cycufftMp.pxd +333 -0
  60. nvmath/bindings/cycurand.cp313-win_amd64.pyd +0 -0
  61. nvmath/bindings/cycurand.pxd +146 -0
  62. nvmath/bindings/cycusolver.cp313-win_amd64.pyd +0 -0
  63. nvmath/bindings/cycusolver.pxd +143 -0
  64. nvmath/bindings/cycusolverDn.cp313-win_amd64.pyd +0 -0
  65. nvmath/bindings/cycusolverDn.pxd +414 -0
  66. nvmath/bindings/cycusparse.cp313-win_amd64.pyd +0 -0
  67. nvmath/bindings/cycusparse.pxd +476 -0
  68. nvmath/bindings/cymathdx.cp313-win_amd64.pyd +0 -0
  69. nvmath/bindings/cymathdx.pxd +363 -0
  70. nvmath/bindings/cynvshmem.pxd +126 -0
  71. nvmath/bindings/mathdx.cp313-win_amd64.pyd +0 -0
  72. nvmath/bindings/mathdx.pxd +131 -0
  73. nvmath/bindings/mathdx.pyi +686 -0
  74. nvmath/bindings/nvpl/__init__.pxd +0 -0
  75. nvmath/bindings/nvpl/__init__.py +11 -0
  76. nvmath/bindings/nvpl/_internal/__init__.pxd +0 -0
  77. nvmath/bindings/nvpl/_internal/__init__.py +0 -0
  78. nvmath/bindings/nvpl/_internal/fft.pxd +36 -0
  79. nvmath/bindings/nvpl/cyfft.pxd +87 -0
  80. nvmath/bindings/nvpl/fft.pxd +100 -0
  81. nvmath/bindings/nvshmem.pxd +54 -0
  82. nvmath/device/__init__.py +23 -0
  83. nvmath/device/_deprecated.py +33 -0
  84. nvmath/device/caching.py +51 -0
  85. nvmath/device/common.py +131 -0
  86. nvmath/device/common_backend.py +125 -0
  87. nvmath/device/common_cuda.py +156 -0
  88. nvmath/device/common_mathdx.py +97 -0
  89. nvmath/device/common_numba.py +159 -0
  90. nvmath/device/common_opaque_tensor.py +128 -0
  91. nvmath/device/cublasdx.py +1085 -0
  92. nvmath/device/cublasdx_backend.py +552 -0
  93. nvmath/device/cublasdx_numba.py +528 -0
  94. nvmath/device/cufftdx.py +551 -0
  95. nvmath/device/cufftdx_backend.py +194 -0
  96. nvmath/device/cufftdx_numba.py +123 -0
  97. nvmath/device/curand_kernel.py +9147 -0
  98. nvmath/device/nvrtc.py +95 -0
  99. nvmath/device/patch.py +35 -0
  100. nvmath/device/random.py +417 -0
  101. nvmath/device/random_helpers.py +23 -0
  102. nvmath/device/random_states.py +189 -0
  103. nvmath/device/types.py +11 -0
  104. nvmath/device/vector_types_numba.py +203 -0
  105. nvmath/distributed/__init__.py +117 -0
  106. nvmath/distributed/_internal/__init__.py +0 -0
  107. nvmath/distributed/_internal/nvshmem.py +243 -0
  108. nvmath/distributed/_internal/tensor_ifc_cupy.py +111 -0
  109. nvmath/distributed/_internal/tensor_ifc_numpy.py +53 -0
  110. nvmath/distributed/_internal/tensor_ifc_torch.py +108 -0
  111. nvmath/distributed/_internal/tensor_wrapper.py +59 -0
  112. nvmath/distributed/_utils.py +152 -0
  113. nvmath/distributed/fft/__init__.py +6 -0
  114. nvmath/distributed/fft/_configuration.py +86 -0
  115. nvmath/distributed/fft/fft.py +1737 -0
  116. nvmath/distributed/reshape/__init__.py +6 -0
  117. nvmath/distributed/reshape/_configuration.py +39 -0
  118. nvmath/distributed/reshape/reshape.py +1249 -0
  119. nvmath/fft/__init__.py +7 -0
  120. nvmath/fft/_configuration.py +208 -0
  121. nvmath/fft/_exec_utils.py +116 -0
  122. nvmath/fft/_helpers.py +260 -0
  123. nvmath/fft/fft.py +2346 -0
  124. nvmath/internal/__init__.py +5 -0
  125. nvmath/internal/enum_utils.py +142 -0
  126. nvmath/internal/formatters.py +87 -0
  127. nvmath/internal/mem_limit.py +51 -0
  128. nvmath/internal/package_ifc.py +119 -0
  129. nvmath/internal/package_ifc_cuda.py +43 -0
  130. nvmath/internal/package_ifc_cupy.py +49 -0
  131. nvmath/internal/package_ifc_torch.py +31 -0
  132. nvmath/internal/package_wrapper.py +14 -0
  133. nvmath/internal/tensor_ifc.py +143 -0
  134. nvmath/internal/tensor_ifc_cupy.py +185 -0
  135. nvmath/internal/tensor_ifc_numpy.py +157 -0
  136. nvmath/internal/tensor_ifc_torch.py +115 -0
  137. nvmath/internal/tensor_wrapper.py +160 -0
  138. nvmath/internal/typemaps.py +154 -0
  139. nvmath/internal/utils.py +634 -0
  140. nvmath/linalg/__init__.py +11 -0
  141. nvmath/linalg/_internal/__init__.py +3 -0
  142. nvmath/linalg/_internal/algo_cap_ifc.py +86 -0
  143. nvmath/linalg/_internal/algo_config_ifc.py +87 -0
  144. nvmath/linalg/_internal/enum_to_tuples.py +64 -0
  145. nvmath/linalg/_internal/epilog_protocol.py +738 -0
  146. nvmath/linalg/_internal/matmul_desc_ifc.py +72 -0
  147. nvmath/linalg/_internal/matmul_pref_ifc.py +65 -0
  148. nvmath/linalg/_internal/matrix_layout_ifc.py +59 -0
  149. nvmath/linalg/_internal/typemaps.py +135 -0
  150. nvmath/linalg/_internal/utils.py +96 -0
  151. nvmath/linalg/advanced/__init__.py +8 -0
  152. nvmath/linalg/advanced/_algorithmmod.py +142 -0
  153. nvmath/linalg/advanced/_configuration.py +320 -0
  154. nvmath/linalg/advanced/helpers/__init__.py +5 -0
  155. nvmath/linalg/advanced/helpers/matmul.py +173 -0
  156. nvmath/linalg/advanced/matmulmod.py +2855 -0
  157. nvmath/memory.py +266 -0
  158. nvmath/sparse/__init__.py +18 -0
  159. nvmath/sparse/_internal/__init__.py +21 -0
  160. nvmath/sparse/_internal/common_utils.py +81 -0
  161. nvmath/sparse/_internal/cudss_config_ifc.py +584 -0
  162. nvmath/sparse/_internal/cudss_data_ifc.py +291 -0
  163. nvmath/sparse/_internal/cudss_utils.py +398 -0
  164. nvmath/sparse/_internal/sparse_csr_ifc.py +154 -0
  165. nvmath/sparse/_internal/sparse_format_helpers.py +88 -0
  166. nvmath/sparse/_internal/sparse_tensor_ifc.py +80 -0
  167. nvmath/sparse/advanced/__init__.py +7 -0
  168. nvmath/sparse/advanced/_configuration.py +150 -0
  169. nvmath/sparse/advanced/direct_solver.py +1705 -0
  170. nvmath_python-0.5.0.dist-info/METADATA +122 -0
  171. nvmath_python-0.5.0.dist-info/RECORD +174 -0
  172. nvmath_python-0.5.0.dist-info/WHEEL +5 -0
  173. nvmath_python-0.5.0.dist-info/licenses/LICENSE +177 -0
  174. 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)