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,634 @@
1
+ # Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ """
6
+ A collection of (internal use) helper functions.
7
+ """
8
+
9
+ import contextlib
10
+ import functools
11
+ import time
12
+ import typing
13
+ from collections.abc import Callable, Sequence
14
+
15
+ import cuda.bindings.runtime as cbr
16
+ import cuda.core.experimental as ccx
17
+
18
+ from . import formatters
19
+ from . import mem_limit
20
+ from . import package_wrapper
21
+ from .package_ifc import StreamHolder, Stream, AnyStream
22
+ from .tensor_ifc import TensorHolder, Tensor, AnyTensor
23
+ from nvmath._internal.layout import is_contiguous_and_dense
24
+
25
+
26
+ def infer_object_package(obj: object) -> str:
27
+ """
28
+ Infer the package that defines this object.
29
+ """
30
+ module = obj.__class__.__module__
31
+ return module.split(".")[0]
32
+
33
+
34
+ """A generic type for Options classes"""
35
+ Options = typing.TypeVar("Options")
36
+
37
+
38
+ def check_or_create_options(
39
+ cls: type[Options], options: None | dict[str, typing.Any] | Options, options_description: str, *, keep_none=False
40
+ ) -> Options | None:
41
+ """
42
+ Create the specified options dataclass from a dictionary of options or None.
43
+ """
44
+
45
+ if options is None:
46
+ if keep_none:
47
+ return options
48
+ options = cls()
49
+ elif isinstance(options, dict):
50
+ options = cls(**options)
51
+
52
+ if not isinstance(options, cls):
53
+ raise TypeError(
54
+ f"The {options_description} must be provided as an object "
55
+ f"of type {cls.__name__} or as a dict with valid {options_description}. "
56
+ f"The provided object is '{options}'."
57
+ )
58
+
59
+ return options
60
+
61
+
62
+ def check_or_create_one_of_options(clss, options, options_description, *, cls_key="name", default_name=None):
63
+ """
64
+ Create one of the specified options dataclasses by name or from a dictionary of options.
65
+ """
66
+
67
+ assert isinstance(clss, tuple)
68
+ assert all(hasattr(cls, "name") and isinstance(cls.name, str) for cls in clss)
69
+
70
+ if options is None:
71
+ assert default_name is not None, "Internal error in execution options"
72
+ options = _create_one_of_options_from_name(clss, default_name, options_description, cls_key=cls_key)
73
+ elif isinstance(options, str):
74
+ options = _create_one_of_options_from_name(clss, options, options_description, cls_key=cls_key)
75
+ elif isinstance(options, dict):
76
+ options = _create_one_of_options_from_dict(
77
+ clss,
78
+ options,
79
+ options_description,
80
+ cls_key=cls_key,
81
+ default_name=default_name,
82
+ )
83
+ else:
84
+ if not isinstance(options, clss):
85
+ _raise_invalid_one_of_options(clss, options, options_description, cls_key=cls_key)
86
+ return options
87
+
88
+
89
+ def _create_one_of_options_from_name(clss, cls_name, options_description, *, cls_key="name"):
90
+ try:
91
+ _cls_name = cls_name.lower()
92
+ return next(cls for cls in clss if cls.name == _cls_name)()
93
+ except StopIteration:
94
+ raise _raise_invalid_one_of_options(clss, cls_name, options_description, cls_key=cls_key)
95
+
96
+
97
+ def _create_one_of_options_from_dict(
98
+ clss,
99
+ options,
100
+ options_description,
101
+ *,
102
+ cls_key="name",
103
+ default_name=None,
104
+ ):
105
+ cls_name = options.get(cls_key)
106
+ if cls_name is None:
107
+ cls_name = default_name
108
+ if not isinstance(cls_name, str):
109
+ raise _raise_invalid_one_of_options(clss, cls_name, options_description, cls_key=cls_key)
110
+ try:
111
+ _cls_name = cls_name.lower()
112
+ cls = next(cls for cls in clss if cls.name == _cls_name)
113
+ except StopIteration:
114
+ raise _raise_invalid_one_of_options(clss, cls_name, options_description, cls_key=cls_key)
115
+ return cls(**{key: name for key, name in options.items() if key != cls_key})
116
+
117
+
118
+ def _raise_invalid_one_of_options(clss, options, options_description, *, cls_key="name"):
119
+ accepted_names = ", ".join(f"'{cls.name}'" for cls in clss)
120
+ accepted_types = ", ".join(str(cls) for cls in clss)
121
+ raise ValueError(
122
+ f"The {options_description} must be: \n"
123
+ f"1. an object of one of the following types {accepted_types} or\n"
124
+ f"2. a string with the type name ({accepted_names}) or\n"
125
+ f"3. a dict with a '{cls_key}' key and one of the type names {accepted_names}' as a value, "
126
+ f"and optional options valid for that type. \n"
127
+ f"The provided object is '{options}'."
128
+ )
129
+
130
+
131
+ @contextlib.contextmanager
132
+ def device_ctx(new_device_id: int) -> typing.Iterator[ccx.Device]:
133
+ """
134
+ Semantics:
135
+
136
+ 1. The device context manager makes the specified device current from the point of entry
137
+ until the point of exit.
138
+
139
+ 2. When the context manager exits, the current device is reset to what it was when the
140
+ context manager was entered.
141
+
142
+ 3. Any explicit setting of the device within the context manager (using
143
+ cupy.cuda.Device().use(), torch.cuda.set_device(), etc) will overrule the device set
144
+ by the context manager from that point onwards till the context manager exits. In
145
+ other words, the context manager provides a local device scope and the current device
146
+ can be explicitly reset for the remainder of that scope.
147
+
148
+ Corollary: if any library function resets the device globally and this is an undesired
149
+ side-effect, such functions must be called from within the device context manager.
150
+
151
+ Device context managers can be arbitrarily nested.
152
+ """
153
+ assert isinstance(new_device_id, int), "Internal Error. Setting device context for 'cpu' is not allowed."
154
+ old_device = ccx.Device()
155
+ try:
156
+ if old_device.device_id != new_device_id:
157
+ device = ccx.Device(new_device_id)
158
+ device.set_current()
159
+ yield device
160
+ else:
161
+ yield old_device
162
+ finally:
163
+ # We should always restore the old device at exit.
164
+ old_device.set_current()
165
+
166
+
167
+ def is_hashable(obj: object) -> bool:
168
+ try:
169
+ hash(obj)
170
+ except TypeError:
171
+ return False
172
+ return True
173
+
174
+
175
+ @functools.lru_cache(maxsize=128)
176
+ def cached_get_or_create_stream(
177
+ device_id: int,
178
+ stream: Stream | int,
179
+ op_package: str,
180
+ ) -> StreamHolder[Stream]:
181
+ op_package_ifc = package_wrapper.PACKAGE[op_package]
182
+ if isinstance(stream, int):
183
+ ptr = stream
184
+ if op_package == "torch":
185
+ message = "A stream object must be provided for PyTorch operands, not stream pointer."
186
+ raise TypeError(message)
187
+ external = op_package_ifc.create_external_stream(device_id, ptr)
188
+ ctx = op_package_ifc.to_stream_context(external)
189
+ obj = op_package_ifc.create_stream(external)
190
+ return StreamHolder(ctx=ctx, device_id=device_id, external=external, obj=obj, ptr=ptr, package=op_package)
191
+
192
+ stream_package = infer_object_package(stream)
193
+ if stream_package != op_package:
194
+ message = "The stream object must belong to the same package as the tensor network operands."
195
+ raise TypeError(message)
196
+
197
+ ctx = op_package_ifc.to_stream_context(stream)
198
+ ptr = op_package_ifc.to_stream_pointer(stream)
199
+ external = stream
200
+ obj = op_package_ifc.create_stream(stream)
201
+ return StreamHolder(ctx=ctx, device_id=device_id, external=external, obj=obj, ptr=ptr, package=op_package)
202
+
203
+
204
+ def get_or_create_stream(
205
+ device_id: int,
206
+ stream: Stream | int | None,
207
+ op_package: str,
208
+ ) -> StreamHolder[Stream]:
209
+ """
210
+ Create a stream object from a stream pointer or extract the stream pointer from a stream
211
+ object, or use the current stream.
212
+
213
+ Args:
214
+ device_id: The device ID.
215
+
216
+ stream: A stream object, stream pointer, or None.
217
+
218
+ op_package: The package the tensor network operands belong to.
219
+
220
+ Returns:
221
+ StreamHolder: Hold a CuPy stream object, package stream context, stream pointer, ...
222
+ """
223
+ op_package_ifc = package_wrapper.PACKAGE[op_package]
224
+ if stream is None:
225
+ stream = op_package_ifc.get_current_stream(device_id)
226
+ return cached_get_or_create_stream(device_id, stream, op_package)
227
+
228
+
229
+ @functools.lru_cache(maxsize=128)
230
+ def get_memory_limit_from_device_id(memory_limit: int | float | str, device_id: int) -> int:
231
+ with device_ctx(device_id):
232
+ status, _, total_memory = cbr.cudaMemGetInfo()
233
+ return _get_memory_limit(memory_limit, total_memory)
234
+
235
+
236
+ def _get_memory_limit(memory_limit: int | float | str, total_memory: int) -> int:
237
+ """
238
+ Parse user provided memory limit and return the memory limit in bytes.
239
+ """
240
+ if isinstance(memory_limit, int):
241
+ if memory_limit < 0:
242
+ raise ValueError("The specified memory limit must be greater than or equal to 0.")
243
+ return memory_limit
244
+
245
+ if isinstance(memory_limit, float):
246
+ if memory_limit < 0:
247
+ raise ValueError("The specified memory limit must be greater than or equal to 0.")
248
+ if memory_limit <= 1.0:
249
+ memory_limit *= total_memory
250
+ return int(memory_limit)
251
+
252
+ m = mem_limit.MEM_LIMIT_RE_PCT.match(memory_limit)
253
+ if m:
254
+ factor = float(m.group(1))
255
+ if factor <= 0 or factor > 100:
256
+ raise ValueError("The memory limit percentage must be in the range [0, 100].")
257
+ return int(factor * total_memory / 100.0)
258
+
259
+ m = mem_limit.MEM_LIMIT_RE_VAL.match(memory_limit)
260
+ if not m:
261
+ raise ValueError(mem_limit.MEM_LIMIT_DOC.format(kind="memory limit", value=memory_limit))
262
+
263
+ base = 1000
264
+ if m.group("binary"):
265
+ base = 1024
266
+
267
+ powers = {"": 0, "k": 1, "m": 2, "g": 3}
268
+ unit = m.group("units").lower() if m.group("units") else ""
269
+ multiplier = base ** powers[unit]
270
+
271
+ value = float(m.group("value"))
272
+ memory_limit = int(value * multiplier)
273
+
274
+ return memory_limit
275
+
276
+
277
+ def create_empty_tensor(
278
+ cls: type[TensorHolder[Tensor]],
279
+ extents: Sequence[int],
280
+ dtype: str,
281
+ device_id: int | typing.Literal["cpu"],
282
+ stream_holder: StreamHolder | None,
283
+ verify_strides: bool,
284
+ strides: Sequence[int] | None = None,
285
+ **context,
286
+ ) -> TensorHolder[Tensor]:
287
+ """
288
+ Create a wrapped tensor of the same type as (the wrapped) cls on the specified device
289
+ having the specified extents and dtype.
290
+
291
+ The tensor is created within a stream context to allow for asynchronous memory
292
+ allocators like CuPy's MemoryAsyncPool.
293
+
294
+ Note, the function assumes the `strides` are dense (possibly permuted).
295
+ Otherwise, the behaviour is framework specific and tensor creation may fail
296
+ or created tensor may be corrupted. Set `verify_strides` to True to check
297
+ the layout and drop the strides if the layout is not dense.
298
+ """
299
+ assert isinstance(device_id, int) or device_id == "cpu", f"Internal Error: device_id must be int or 'cpu'; not {device_id}"
300
+ # if device id is none the stream holder must be too
301
+ assert device_id != "cpu" or stream_holder is None
302
+ if strides is not None and verify_strides and not is_contiguous_and_dense(extents, strides):
303
+ strides = None
304
+ tensor = cls.empty(extents, dtype=dtype, device_id=device_id, strides=strides, stream_holder=stream_holder, **context)
305
+ return tensor
306
+
307
+
308
+ def get_operands_device_id(operands: Sequence[TensorHolder[AnyTensor]]):
309
+ """
310
+ Return the id (ordinal) of the device the operands are on, or None if it is on the CPU.
311
+ """
312
+ device_id = operands[0].device_id
313
+ if not all(operand.device_id == device_id for operand in operands):
314
+ devices = {operand.device_id for operand in operands}
315
+ raise ValueError(f"All operands are not on the same device. Devices = {devices}.")
316
+
317
+ return device_id
318
+
319
+
320
+ def get_operands_dtype(operands: Sequence[TensorHolder[AnyTensor]]):
321
+ """
322
+ Return the data type name of the tensors.
323
+ """
324
+ dtype = operands[0].dtype
325
+ if not all(operand.dtype == dtype for operand in operands):
326
+ dtypes = {operand.dtype for operand in operands}
327
+ raise ValueError(f"All tensors in the network must have the same data type. Data types found = {dtypes}.")
328
+ return dtype
329
+
330
+
331
+ def get_operands_package(operands: Sequence[TensorHolder[AnyTensor]]) -> str:
332
+ """
333
+ Return the package name of the tensors.
334
+ """
335
+ package = infer_object_package(operands[0].tensor)
336
+ if not all(infer_object_package(operand.tensor) == package for operand in operands):
337
+ packages = {infer_object_package(operand.tensor) for operand in operands}
338
+ raise TypeError(f"All tensors in the network must be from the same library package. Packages found = {packages}.")
339
+ return package
340
+
341
+
342
+ def check_operands_match(
343
+ orig_operands: Sequence[object] | object, new_operands: Sequence[object] | object, attribute: str, description: str
344
+ ):
345
+ """
346
+ Check if the specified attribute matches between the corresponding new and old operands,
347
+ and raise an exception if it doesn't.
348
+ """
349
+ if isinstance(orig_operands, Sequence):
350
+ assert isinstance(new_operands, Sequence), "Internal Error. Cannot compare operand sequence with non-sequence."
351
+ checks: list[bool] = [
352
+ getattr(o, attribute) == getattr(n, attribute) for o, n in zip(orig_operands, new_operands, strict=True)
353
+ ]
354
+
355
+ if not all(checks):
356
+ mismatch = [
357
+ f"{location}: {getattr(orig_operands[location], attribute)} => {getattr(new_operands[location], attribute)}"
358
+ for location, predicate in enumerate(checks)
359
+ if predicate is False
360
+ ]
361
+ mismatch = formatters.array2string(mismatch)
362
+ message = f"""\
363
+ The {description} of each new operand must match the {description} of the corresponding original operand.
364
+
365
+ The mismatch in {description} as a sequence of "position: original {description} => new {description}" is: \n{mismatch}"""
366
+ raise ValueError(message)
367
+ else:
368
+ assert isinstance(new_operands, object)
369
+ check: bool = getattr(orig_operands, attribute) == getattr(new_operands, attribute)
370
+ if not check:
371
+ message = f"""The {description} of the new operand must match the {description} of the original operand."""
372
+ raise ValueError(message)
373
+
374
+
375
+ def check_attribute_match(orig_attribute: object, new_attribute: object, description: str):
376
+ """
377
+ Check if the specified attribute matches between the corresponding new and old operands,
378
+ and raise an exception if it doesn't.
379
+ """
380
+ check = orig_attribute == new_attribute
381
+ if not check:
382
+ message = f"""The {description} of the new operand must match the {description} of the original operand."""
383
+ raise ValueError(message)
384
+
385
+
386
+ def get_ptr_from_memory_pointer(mem_ptr):
387
+ """
388
+ Access the value associated with one of the attributes 'device_ptr', 'device_pointer',
389
+ 'ptr'.
390
+
391
+ This is a compatibility function so that external memory pointer implementations from
392
+ numba and cupy can be used instead of MemoryPointer from nvmath.memory.
393
+ """
394
+ attributes = (
395
+ "device_ptr", # nvmath.memory
396
+ "device_pointer",
397
+ "ptr", # cupy.cuda.MemoryPointer
398
+ "pointer", # numba.cuda.MemoryPointer
399
+ )
400
+ for attr in attributes:
401
+ if hasattr(mem_ptr, attr):
402
+ return getattr(mem_ptr, attr)
403
+
404
+ message = f"Memory pointer objects should have one of the following attributes specifying the device pointer: {attributes}"
405
+ raise AttributeError(message)
406
+
407
+
408
+ class Value:
409
+ """
410
+ A simple value wrapper holding a default value.
411
+ """
412
+
413
+ def __init__(self, default, *, validator: Callable[[object], bool]):
414
+ """
415
+ Args:
416
+ default: The default value to use.
417
+ validator: A callable that validates the provided value.
418
+ """
419
+ self.validator = validator
420
+ self._data = default
421
+
422
+ @property
423
+ def data(self):
424
+ return self._data
425
+
426
+ @data.setter
427
+ def data(self, value):
428
+ self._data = self._validate(value)
429
+
430
+ def _validate(self, value):
431
+ if self.validator(value):
432
+ return value
433
+ raise ValueError(f"Internal Error: value '{value}' is not valid.")
434
+
435
+
436
+ @contextlib.contextmanager
437
+ def cuda_call_ctx(
438
+ stream_holder: StreamHolder[AnyStream], blocking=True, timing=True
439
+ ) -> typing.Iterator[tuple[ccx.Event, Value]]:
440
+ """
441
+ A simple context manager that provides (non-)blocking behavior depending on the
442
+ `blocking` parameter for CUDA calls. The call is timed only for blocking behavior when
443
+ timing is requested.
444
+
445
+ An `end` event is recorded after the CUDA call for use in establishing stream ordering
446
+ for non-blocking calls. This event is returned together with a `Value` object that
447
+ stores the elapsed time if the call is blocking and timing is requested, or None
448
+ otherwise.
449
+
450
+ Inside the context, the current device is the device which owns the provided stream.
451
+ """
452
+ device_id = stream_holder.device_id
453
+ stream = stream_holder.obj
454
+
455
+ with device_ctx(device_id):
456
+ if timing:
457
+ start = stream.record(options=ccx.EventOptions(enable_timing=blocking))
458
+
459
+ end = stream.device.create_event(options=ccx.EventOptions(enable_timing=(timing and blocking)))
460
+
461
+ time = Value(None, validator=lambda v: True)
462
+ yield end, time
463
+
464
+ stream.record(end)
465
+
466
+ if not blocking:
467
+ return
468
+
469
+ end.sync()
470
+
471
+ if timing:
472
+ time.data = end - start
473
+
474
+
475
+ @contextlib.contextmanager
476
+ def host_call_ctx(timing=False):
477
+ elapsed = Value(None, validator=lambda v: True)
478
+
479
+ if timing:
480
+ start_time = time.perf_counter_ns()
481
+
482
+ yield elapsed
483
+
484
+ if timing:
485
+ elapsed.data = (time.perf_counter_ns() - start_time) * 1e-6
486
+
487
+
488
+ # Decorator definitions
489
+
490
+
491
+ def atomic(
492
+ handler: Callable[[typing.Any, Exception | None], bool] | Callable[[Exception | None], bool], method: bool = False
493
+ ) -> Callable:
494
+ """
495
+ A decorator that provides "succeed or roll-back" semantics. A typical use for this is to
496
+ release partial resources if an exception occurs.
497
+
498
+ Args:
499
+ handler: A function to call when an exception occurs. The handler takes a single
500
+ argument, which is the exception object, and returns a boolean stating whether
501
+ the same exception should be reraised. We assume that this function does not
502
+ raise an exception.
503
+
504
+ method: Specify if the wrapped function as well as the exception handler are methods
505
+ bound to the same object (method = True) or they are free functions (method =
506
+ False).
507
+
508
+ Returns:
509
+ Callable: A decorator that creates the wrapping.
510
+ """
511
+
512
+ def outer(wrapped_function):
513
+ """
514
+ A decorator that actually wraps the function for exception handling.
515
+ """
516
+
517
+ @functools.wraps(wrapped_function)
518
+ def inner(*args, **kwargs):
519
+ """
520
+ Call the wrapped function and return the result. If an exception occurs, then
521
+ call the exception handler and reraise the exception.
522
+ """
523
+ try:
524
+ result = wrapped_function(*args, **kwargs)
525
+ except BaseException as e:
526
+ if method:
527
+ flag = handler(args[0], e)
528
+ else:
529
+ flag = handler(e)
530
+
531
+ if flag:
532
+ raise e
533
+
534
+ return result
535
+
536
+ return inner
537
+
538
+ return outer
539
+
540
+
541
+ def precondition(checker: Callable[..., None], what: str = "") -> Callable:
542
+ """
543
+ A decorator that adds checks to ensure any preconditions are met.
544
+
545
+ Args:
546
+ checker: The function to call to check whether the preconditions are met. It has the
547
+ same signature as the wrapped function with the addition of the keyword argument
548
+ `what`.
549
+ what: A string that is passed in to `checker` to provide context information.
550
+
551
+ Returns:
552
+ Callable: A decorator that creates the wrapping.
553
+ """
554
+
555
+ def outer(wrapped_function):
556
+ """
557
+ A decorator that actually wraps the function for checking preconditions.
558
+ """
559
+
560
+ @functools.wraps(wrapped_function)
561
+ def inner(*args, **kwargs):
562
+ """
563
+ Check preconditions and if they are met, call the wrapped function.
564
+ """
565
+ checker(*args, **kwargs, what=what)
566
+ result = wrapped_function(*args, **kwargs)
567
+
568
+ return result
569
+
570
+ return inner
571
+
572
+ return outer
573
+
574
+
575
+ COMMON_SHARED_DOC_MAP = {
576
+ "operand": """\
577
+ A tensor (ndarray-like object). The currently supported types are :class:`numpy.ndarray`,
578
+ :class:`cupy.ndarray`, and :class:`torch.Tensor`.""".replace("\n", " "),
579
+ #
580
+ "stream": """\
581
+ Provide the CUDA stream to use for executing the operation. Acceptable inputs include
582
+ ``cudaStream_t`` (as Python :class:`int`), :class:`cupy.cuda.Stream`, and
583
+ :class:`torch.cuda.Stream`. If a stream is not provided, the current stream from the operand
584
+ package will be used.""".replace("\n", " "),
585
+ #
586
+ "release_workspace": """\
587
+ A value of `True` specifies that the stateful object should release workspace memory back to
588
+ the package memory pool on function return, while a value of `False` specifies that the
589
+ object should retain the memory. This option may be set to `True` if the application
590
+ performs other operations that consume a lot of memory between successive calls to the (same
591
+ or different) :meth:`execute` API, but incurs a small overhead due to obtaining and
592
+ releasing workspace memory from and to the package memory pool on every call. The default is
593
+ `False`.""".replace("\n", " "),
594
+ }
595
+
596
+
597
+ class DefaultDocstring(dict):
598
+ def __missing__(self, key):
599
+ return "{" + key + "}"
600
+
601
+
602
+ def docstring_decorator(doc_map, skip_missing=False):
603
+ assert isinstance(doc_map, dict)
604
+
605
+ def _format_doc(doc):
606
+ if doc is not None:
607
+ if skip_missing:
608
+ # Using a default class to handle missing keys in doc_map
609
+ doc = doc.format_map(DefaultDocstring(**doc_map))
610
+ else:
611
+ doc = doc.format(**doc_map)
612
+ return doc
613
+
614
+ def decorator(func_or_class):
615
+ if isinstance(func_or_class, type): # class decorator
616
+ # update the docstring of all public methods with docstrings
617
+ static_methods = [] # staticmethods appear to require special handling
618
+ for name, method in vars(func_or_class).items():
619
+ if isinstance(method, staticmethod):
620
+ static_methods.append(name)
621
+ continue
622
+ if callable(method) and (not name.startswith("_")) and method.__doc__:
623
+ method.__doc__ = _format_doc(method.__doc__)
624
+ # update the docstring of the constructor
625
+ func_or_class.__doc__ = _format_doc(func_or_class.__doc__)
626
+ for name in static_methods:
627
+ method = getattr(func_or_class, name)
628
+ method.__doc__ = _format_doc(method.__doc__)
629
+ return func_or_class
630
+ else: # function decorator
631
+ func_or_class.__doc__ = _format_doc(func_or_class.__doc__)
632
+ return func_or_class
633
+
634
+ return decorator
@@ -0,0 +1,11 @@
1
+ # Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from . import advanced
6
+ from nvmath.bindings.cublas import ComputeType # type: ignore
7
+
8
+ __all__ = [
9
+ "advanced",
10
+ "ComputeType",
11
+ ]
@@ -0,0 +1,3 @@
1
+ # Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0