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,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)
|