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,185 @@
1
+ # Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ """
6
+ Interface to seamlessly use Cupy ndarray objects.
7
+ """
8
+
9
+ __all__ = ["CupyTensor"]
10
+
11
+ from collections.abc import Sequence
12
+
13
+ import cupy
14
+ import numpy as np
15
+
16
+ from . import utils
17
+ from .tensor_ifc import TensorHolder
18
+ from .tensor_ifc_numpy import NumpyTensor
19
+ from .package_ifc import StreamHolder
20
+
21
+
22
+ class CupyTensor(TensorHolder[cupy.ndarray]):
23
+ """
24
+ TensorHolder for cupy ndarrays.
25
+ """
26
+
27
+ name = "cupy"
28
+ module = cupy
29
+ name_to_dtype = TensorHolder.create_name_dtype_map(
30
+ conversion_function=lambda name: np.dtype(name), exception_type=TypeError
31
+ )
32
+
33
+ def __init__(self, tensor):
34
+ super().__init__(tensor)
35
+
36
+ @property
37
+ def data_ptr(self):
38
+ return self.tensor.data.ptr
39
+
40
+ @property
41
+ def device(self):
42
+ return "cuda"
43
+
44
+ @property
45
+ def device_id(self):
46
+ return self.tensor.device.id
47
+
48
+ @property
49
+ def dtype(self):
50
+ """Name of the data type"""
51
+ return self.tensor.dtype.name
52
+
53
+ @property
54
+ def shape(self):
55
+ return tuple(self.tensor.shape)
56
+
57
+ @property
58
+ def size(self):
59
+ return self.tensor.size
60
+
61
+ @property
62
+ def strides(self):
63
+ return tuple(stride_in_bytes // self.tensor.itemsize for stride_in_bytes in self.tensor.strides)
64
+
65
+ def numpy(self, stream_holder: StreamHolder):
66
+ stream = stream_holder.external
67
+ with stream:
68
+ out = self.tensor.get(stream=stream)
69
+ # cupy/cupy#7820
70
+ if stream is not None:
71
+ stream.synchronize()
72
+ return NumpyTensor(out)
73
+
74
+ @classmethod
75
+ def empty(
76
+ cls, shape, device_id="cpu", *, dtype="float32", strides=None, stream_holder: StreamHolder | None = None, **context
77
+ ):
78
+ """
79
+ Create an empty tensor of the specified shape and data type.
80
+
81
+ Note, that the strides, if specified, MUST correspond to a dense (possibly permuted)
82
+ tensor, otherwise the created tensor may be corrupted.
83
+ """
84
+ assert isinstance(device_id, int), "Internal Error: Cupy tensors must be allocated with an integer device_id."
85
+ dtype = CupyTensor.name_to_dtype[dtype]
86
+
87
+ # When using the strides, we need an explicit allocation (see below).
88
+ # If the strides are simple enough, we can still avoid this overhead.
89
+ order = "C"
90
+ if strides is not None:
91
+ if len(strides) == 1 and strides[0] == 1:
92
+ strides = None
93
+ elif len(strides) == 2:
94
+ if strides[0] == 1 and strides[1] == shape[0]:
95
+ strides = None
96
+ order = "F"
97
+ elif strides[0] == shape[1] and strides[1] == 1:
98
+ strides = None
99
+
100
+ assert isinstance(stream_holder, StreamHolder), "Internal Error: CupyTensors must be allocated on a stream."
101
+ with utils.device_ctx(device_id), stream_holder.ctx:
102
+ if strides:
103
+ # need an explicit allocation due to cupy/cupy#7818
104
+ size = dtype.itemsize
105
+ for s in shape:
106
+ size = size * s
107
+ ptr = cupy.cuda.alloc(size)
108
+ # when strides is not None, it should be of unit counts not bytes
109
+ strides = tuple(s * dtype.itemsize for s in strides)
110
+ tensor = cupy.ndarray(shape, dtype=dtype, strides=strides, memptr=ptr)
111
+ else:
112
+ tensor = cupy.ndarray(shape, dtype=dtype, order=order)
113
+
114
+ return cls(tensor)
115
+
116
+ def to(self, device_id, stream_holder):
117
+ if not (device_id == "cpu" or isinstance(device_id, int)):
118
+ raise ValueError(f"The device must be specified as an integer or 'cpu', not '{device_id}'.")
119
+
120
+ if device_id == "cpu":
121
+ return self.numpy(stream_holder=stream_holder)
122
+
123
+ with utils.device_ctx(device_id), stream_holder.ctx:
124
+ return CupyTensor(cupy.asarray(self.tensor))
125
+
126
+ def _c2c_copy_(self, src: cupy.ndarray, stream_holder: StreamHolder):
127
+ """
128
+ Inplace copy of src (copy the data from src into self).
129
+ The src must by cupy ndarray
130
+ """
131
+ with stream_holder.ctx:
132
+ cupy.copyto(self.tensor, src)
133
+
134
+ def _n2c_copy_(self, src: np.ndarray, stream_holder: StreamHolder):
135
+ """
136
+ Inplace copy of src (copy the data from src into self).
137
+ The src must by numpy ndarray
138
+ """
139
+ stream = stream_holder.external
140
+ try:
141
+ self.tensor.set(src, stream=stream)
142
+ except RuntimeError as e:
143
+ # If self is a strided tensor (neither c nor f layout)
144
+ # cupy refuses to copy from numpy array
145
+ if "set to non-contiguous array" not in str(e):
146
+ raise
147
+ else:
148
+ with stream_holder.ctx:
149
+ src_gpu = cupy.asarray(src)
150
+ cupy.copyto(self.tensor, src_gpu)
151
+ # cupy/cupy#7820
152
+ if stream is not None:
153
+ stream.synchronize()
154
+
155
+ def copy_(self, src, stream_holder):
156
+ """
157
+ Inplace copy of src (copy the data from src into self).
158
+ """
159
+ match src.name:
160
+ case "cupy":
161
+ self._c2c_copy_(src.tensor, stream_holder)
162
+ case "numpy":
163
+ self._n2c_copy_(src.tensor, stream_holder)
164
+ case _:
165
+ msg = f"CupyTensor does not convert from {src.name}."
166
+ raise NotImplementedError(msg)
167
+
168
+ def istensor(self):
169
+ """
170
+ Check if the object is ndarray-like.
171
+ """
172
+ return isinstance(self.tensor, cupy.ndarray)
173
+
174
+ def reshape(self, shape: Sequence[int], *, copy: bool | None = None):
175
+ if copy:
176
+ raise NotImplementedError("reshape with copy=True not implemented")
177
+ if copy is False:
178
+ try:
179
+ reshaped_tensor = self.tensor.view()
180
+ reshaped_tensor.shape = shape
181
+ except AttributeError:
182
+ raise ValueError(f"Could not reshape cupy array without copy: current shape={self.shape}, new shape={shape}")
183
+ else:
184
+ reshaped_tensor = self.tensor.reshape(shape)
185
+ return self.__class__(reshaped_tensor)
@@ -0,0 +1,157 @@
1
+ # Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ """
6
+ Interface to seamlessly use Numpy ndarray objects.
7
+ """
8
+
9
+ __all__ = ["NumpyTensor"]
10
+
11
+ try:
12
+ import cupy # type: ignore
13
+ except ImportError:
14
+
15
+ class cupy: # type: ignore
16
+ """A placeholder for the cupy module when it is unavailable."""
17
+
18
+ @classmethod
19
+ def asnumpy(cls, *args, **kwargs):
20
+ raise ImportError("Cannot convert cupy to numpy array when cupy is not installed!")
21
+
22
+ @classmethod
23
+ def asarray(cls, *args, **kwargs):
24
+ raise ImportError("Cannot convert numpy to cupy array when cupy is not installed!")
25
+
26
+
27
+ from collections.abc import Sequence
28
+
29
+ import numpy
30
+ import numpy.typing as npt
31
+
32
+ from . import utils
33
+ from .package_ifc import StreamHolder
34
+ from .tensor_ifc import TensorHolder
35
+
36
+
37
+ class NumpyTensor(TensorHolder[npt.NDArray]):
38
+ """
39
+ TensorHolder for numpy ndarrays.
40
+ """
41
+
42
+ name = "numpy"
43
+ module = numpy
44
+ name_to_dtype = TensorHolder.create_name_dtype_map(
45
+ conversion_function=lambda name: numpy.dtype(name), exception_type=TypeError
46
+ )
47
+
48
+ def __init__(self, tensor):
49
+ super().__init__(tensor)
50
+
51
+ @property
52
+ def data_ptr(self):
53
+ return self.tensor.ctypes.data
54
+
55
+ @property
56
+ def device(self):
57
+ return "cpu"
58
+
59
+ @property
60
+ def device_id(self):
61
+ return "cpu"
62
+
63
+ @property
64
+ def dtype(self):
65
+ """Name of the data type"""
66
+ return self.tensor.dtype.name
67
+
68
+ @classmethod
69
+ def empty(cls, shape, device_id="cpu", *, dtype="float32", strides=None, **context):
70
+ """
71
+ Create an empty tensor of the specified shape and data type.
72
+ """
73
+ assert device_id == "cpu", "Internal Error: Numpy tensors must be allocated with device_id='cpu'"
74
+ dtype = NumpyTensor.name_to_dtype[dtype]
75
+ # when strides is not None, it should be of unit counts not bytes
76
+ return cls(
77
+ cls.module.ndarray(shape, dtype=dtype, strides=(tuple(s * dtype.itemsize for s in strides) if strides else None))
78
+ )
79
+
80
+ @property
81
+ def shape(self):
82
+ return tuple(self.tensor.shape)
83
+
84
+ @property
85
+ def size(self):
86
+ return self.tensor.size
87
+
88
+ @property
89
+ def strides(self):
90
+ return tuple(stride_in_bytes // self.tensor.itemsize for stride_in_bytes in self.tensor.strides)
91
+
92
+ def to(self, device_id, stream_holder):
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
+ return NumpyTensor(self.tensor)
98
+
99
+ # FIXME: Replace with native tensor implementation to avoid required dep on CuPy
100
+ from .tensor_ifc_cupy import CupyTensor
101
+
102
+ with utils.device_ctx(device_id), stream_holder.ctx:
103
+ return CupyTensor(cupy.asarray(self.tensor))
104
+
105
+ def _n2n_copy_(self, src: npt.NDArray) -> None:
106
+ """
107
+ Inplace copy of src (copy the data from src into self).
108
+ The src must by numpy ndarray
109
+ """
110
+ numpy.copyto(self.tensor, src)
111
+
112
+ def _c2n_copy_(self, src, stream_holder: StreamHolder) -> None:
113
+ """
114
+ Inplace copy of src (copy the data from src into self).
115
+ The src must by cupy ndarray
116
+ """
117
+ stream = stream_holder.external
118
+ try:
119
+ with stream:
120
+ src.get(stream=stream, out=self.tensor)
121
+ except RuntimeError as e:
122
+ # If self is a strided tensor (neither c nor f layout)
123
+ # cupy refuses to copy to numpy array
124
+ if "copying to non-contiguous ndarray" not in str(e):
125
+ raise
126
+ else:
127
+ # we cannot simply use blocking=True, as it is
128
+ # not supported by older cupy releases (<13)
129
+ src_cpu = cupy.asnumpy(src, stream=stream)
130
+ self._n2n_copy_(src_cpu)
131
+ # cupy/cupy#7820
132
+ if stream is not None:
133
+ stream.synchronize()
134
+
135
+ def copy_(self, src, stream_holder):
136
+ # Handle NumPy <=> CuPy CPU-GPU ndarray asymmetry.
137
+ match src.name:
138
+ case "cupy":
139
+ assert stream_holder is not None
140
+ self._c2n_copy_(src.tensor, stream_holder)
141
+ case "numpy":
142
+ self._n2n_copy_(src.tensor)
143
+ case _:
144
+ msg = f"NumpyTensor does not convert from {src.name}."
145
+ raise NotImplementedError(msg)
146
+
147
+ def istensor(self):
148
+ """
149
+ Check if the object is ndarray-like.
150
+ """
151
+ return isinstance(self.tensor, numpy.ndarray)
152
+
153
+ def reshape(self, shape: Sequence[int], *, copy: bool | None = None):
154
+ if int(numpy.__version__.split(".")[0]) < 2:
155
+ return self.__class__(numpy.reshape(self.tensor, shape))
156
+ else:
157
+ return self.__class__(numpy.reshape(self.tensor, shape, copy=copy))
@@ -0,0 +1,115 @@
1
+ # Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ """
6
+ Interface to seamlessly use Torch tensor objects.
7
+ """
8
+
9
+ __all__ = ["TorchTensor"]
10
+
11
+ import contextlib
12
+ from collections.abc import Sequence
13
+
14
+ import torch
15
+
16
+ from .tensor_ifc import TensorHolder
17
+ from .package_ifc import StreamHolder
18
+
19
+
20
+ class TorchTensor(TensorHolder[torch.Tensor]):
21
+ """
22
+ TensorHolder for Torch Tensors.
23
+ """
24
+
25
+ name = "torch"
26
+ module = torch
27
+ name_to_dtype = TensorHolder.create_name_dtype_map(
28
+ conversion_function=lambda name: getattr(torch, name), exception_type=AttributeError
29
+ )
30
+
31
+ def __init__(self, tensor):
32
+ super().__init__(tensor)
33
+
34
+ @property
35
+ def data_ptr(self):
36
+ return self.tensor.data_ptr()
37
+
38
+ @property
39
+ def device(self):
40
+ return str(self.tensor.device).split(":")[0]
41
+
42
+ @property
43
+ def device_id(self):
44
+ index = self.tensor.device.index
45
+ return "cpu" if index is None else index
46
+
47
+ @property
48
+ def dtype(self):
49
+ """Name of the data type"""
50
+ return str(self.tensor.dtype).split(".")[-1]
51
+
52
+ @property
53
+ def shape(self):
54
+ return tuple(self.tensor.shape)
55
+
56
+ @property
57
+ def size(self):
58
+ return self.tensor.nelement()
59
+
60
+ @property
61
+ def strides(self):
62
+ return self.tensor.stride()
63
+
64
+ @classmethod
65
+ def empty(
66
+ cls, shape, device_id="cpu", *, dtype="float32", strides=None, stream_holder: StreamHolder | None = None, **context
67
+ ):
68
+ """
69
+ Create an empty tensor of the specified shape and data type on the specified device
70
+ (None, 'cpu', or device id).
71
+
72
+ Note, that the strides, if specified, should correspond to a dense
73
+ (possibly permuted) tensor and MUST NOT overlap.
74
+ Otherwise, the behaviour is not defined.
75
+ """
76
+ dtype = TorchTensor.name_to_dtype[dtype]
77
+ stream_ctx = contextlib.nullcontext() if stream_holder is None else stream_holder.ctx
78
+ with stream_ctx:
79
+ if strides:
80
+ # note: torch strides is not scaled by bytes
81
+ tensor = torch.empty_strided(shape, strides, dtype=dtype, device=device_id)
82
+ else:
83
+ tensor = torch.empty(shape, dtype=dtype, device=device_id)
84
+
85
+ return cls(tensor)
86
+
87
+ def to(self, device_id, stream_holder):
88
+ if not (device_id == "cpu" or isinstance(device_id, int)):
89
+ raise ValueError(f"The device must be specified as an integer or 'cpu', not '{device_id}'.")
90
+
91
+ assert stream_holder is not None, "Internal Error: moving TorchTensor requires a stream."
92
+ with stream_holder.ctx:
93
+ tensor_device = self.tensor.to(device=device_id, non_blocking=(device_id != "cpu"))
94
+
95
+ return TorchTensor(tensor_device)
96
+
97
+ def copy_(self, src, stream_holder):
98
+ """
99
+ Inplace copy of src (copy the data from src into self).
100
+ """
101
+ with (stream_holder and stream_holder.ctx) or contextlib.nullcontext():
102
+ self.tensor.copy_(src.tensor)
103
+
104
+ def istensor(self):
105
+ """
106
+ Check if the object is ndarray-like.
107
+ """
108
+ return isinstance(self.tensor, torch.Tensor)
109
+
110
+ def reshape(self, shape: Sequence[int], *, copy: bool | None = None):
111
+ if copy:
112
+ raise NotImplementedError("reshape with copy=True not implemented")
113
+ if copy is False:
114
+ return self.__class__(self.tensor.view(shape))
115
+ return self.__class__(self.tensor.reshape(shape))
@@ -0,0 +1,160 @@
1
+ # Copyright (c) 2024-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__ = ["infer_tensor_package", "wrap_operand", "wrap_operands", "to", "copy_", "TensorHolder", "AnyTensor"]
10
+
11
+ from collections.abc import Sequence
12
+ import warnings
13
+
14
+ import numpy as np
15
+
16
+ from . import formatters
17
+ from .tensor_ifc import TensorHolder, Tensor, AnyTensor
18
+ from .tensor_ifc_numpy import NumpyTensor
19
+
20
+
21
+ _TENSOR_TYPES: dict[str, type[TensorHolder]] = {"numpy": NumpyTensor}
22
+
23
+ _SUPPORTED_PACKAGES = tuple(_TENSOR_TYPES.keys())
24
+
25
+
26
+ def infer_tensor_package(tensor):
27
+ """
28
+ Infer the package that defines this tensor.
29
+ """
30
+ if issubclass(tensor.__class__, np.ndarray):
31
+ return "numpy"
32
+ module = tensor.__class__.__module__
33
+ package = module.split(".")[0]
34
+ maybe_register_package(package)
35
+ return package
36
+
37
+
38
+ def maybe_register_package(package):
39
+ global _SUPPORTED_PACKAGES
40
+ if package not in _SUPPORTED_PACKAGES:
41
+ global _TENSOR_TYPES
42
+ from . import package_wrapper
43
+ from .. import memory
44
+
45
+ if package == "torch":
46
+ from .tensor_ifc_torch import TorchTensor
47
+ from .package_ifc_torch import TorchPackage
48
+
49
+ _TENSOR_TYPES[package] = TorchTensor
50
+ package_wrapper.PACKAGE[package] = TorchPackage
51
+ memory.lazy_load_torch()
52
+ elif package == "cupy":
53
+ from .tensor_ifc_cupy import CupyTensor
54
+ from .package_ifc_cupy import CupyPackage
55
+
56
+ _TENSOR_TYPES[package] = CupyTensor
57
+ package_wrapper.PACKAGE[package] = CupyPackage
58
+ memory.lazy_load_cupy()
59
+ else:
60
+ message = f"""{package} not supported yet. Currently must be one of ['numpy', 'cupy', 'torch']"""
61
+ raise ValueError(message)
62
+ _SUPPORTED_PACKAGES = tuple(_TENSOR_TYPES.keys())
63
+
64
+
65
+ def wrap_operand(native_operand: Tensor) -> TensorHolder[Tensor]:
66
+ """
67
+ Wrap one "native" operand so that package-agnostic API can be used.
68
+ """
69
+ if isinstance(native_operand, TensorHolder):
70
+ msg = (
71
+ "wrap_operand() is being called unnecessarily because the input is already a TensorHolder. "
72
+ "Only public facing APIs should call wrap_operand(). "
73
+ "Internal APIs should assume the operands are TensorHolder already."
74
+ "Trying to wrap a TensorHolder will become an error in the future."
75
+ )
76
+ warnings.warn(msg, DeprecationWarning)
77
+ return native_operand
78
+ wrapped_operand = _TENSOR_TYPES[infer_tensor_package(native_operand)](native_operand)
79
+ return wrapped_operand
80
+
81
+
82
+ def check_valid_package(native_operands):
83
+ """
84
+ Check if the operands belong to one of the supported packages.
85
+ """
86
+ operands_pkg = [infer_tensor_package(o) for o in native_operands]
87
+ checks = [p in _SUPPORTED_PACKAGES for p in operands_pkg]
88
+ if not all(checks):
89
+ unknown = [f"{location}: {operands_pkg[location]}" for location, predicate in enumerate(checks) if predicate is False]
90
+ unknown = formatters.array2string(unknown)
91
+ message = f"""The operands should be ndarray-like objects from one of {_SUPPORTED_PACKAGES} packages.
92
+ The unsupported operands as a sequence of "position: package" is: \n{unknown}"""
93
+ raise ValueError(message)
94
+
95
+ return operands_pkg
96
+
97
+
98
+ def check_valid_operand_type(wrapped_operands):
99
+ """
100
+ Check if the wrapped operands are ndarray-like.
101
+ """
102
+ istensor = [o.istensor() for o in wrapped_operands]
103
+ if not all(istensor):
104
+ unknown = [
105
+ f"{location}: {type(wrapped_operands[location].tensor)}"
106
+ for location, predicate in enumerate(istensor)
107
+ if predicate is False
108
+ ]
109
+ unknown = formatters.array2string(unknown)
110
+ message = f"""The operands should be ndarray-like objects from one of {_SUPPORTED_PACKAGES} packages.
111
+ The unsupported operands as a sequence of "position: type" is: \n{unknown}"""
112
+ raise ValueError(message)
113
+
114
+
115
+ def _wrapper_helper(operand: Tensor | TensorHolder, index: int, packages: list[str]) -> TensorHolder[Tensor]:
116
+ if isinstance(operand, TensorHolder):
117
+ msg = (
118
+ "wrap_operands() is being called unnecessarily because at least one input is already a TensorHolder. "
119
+ "Only public facing APIs should call wrap_operands(). "
120
+ "Internal APIs should assume the operands are TensorHolder already."
121
+ "Trying to wrap a TensorHolder will become an error in the future."
122
+ )
123
+ warnings.warn(msg, DeprecationWarning)
124
+ return operand
125
+ return _TENSOR_TYPES[packages[index]](operand)
126
+
127
+
128
+ def wrap_operands(native_operands: Sequence[Tensor]) -> Sequence[TensorHolder[Tensor]]:
129
+ """
130
+ Wrap the "native" operands so that package-agnostic API can be used.
131
+ """
132
+
133
+ operands_pkg = check_valid_package(native_operands)
134
+
135
+ wrapped_operands = tuple(_wrapper_helper(o, i, operands_pkg) for i, o in enumerate(native_operands))
136
+
137
+ check_valid_operand_type(wrapped_operands)
138
+
139
+ return wrapped_operands
140
+
141
+
142
+ def to(operands: Sequence[TensorHolder], device_id, stream_holder) -> Sequence[TensorHolder]:
143
+ """
144
+ Copy the wrapped operands to the specified device_id (None or int) and return the
145
+ wrapped operands on the device.
146
+ """
147
+ assert isinstance(operands, Sequence) and isinstance(operands[0], TensorHolder)
148
+ return tuple(o.to(device_id, stream_holder) for o in operands)
149
+
150
+
151
+ def copy_(src: Sequence[TensorHolder], dest: Sequence[TensorHolder], stream_holder):
152
+ """
153
+ Copy the wrapped operands in dest to the corresponding wrapped operands in src.
154
+ """
155
+ for s, d in zip(src, dest, strict=True):
156
+ if s.device_id == "cpu":
157
+ # FIXME: This is probably an extra step because it's supported to copy directly
158
+ # from cpu to device?
159
+ s = s.to(d.device_id, stream_holder=stream_holder)
160
+ d.copy_(s, stream_holder=stream_holder)