nvmath-python 0.9.0__cp314-cp314-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 (309) hide show
  1. nvmath/__init__.pxd +0 -0
  2. nvmath/__init__.py +45 -0
  3. nvmath/_internal/__init__.py +0 -0
  4. nvmath/_internal/attribute_ifc_factory.py +330 -0
  5. nvmath/_internal/layout.py +70 -0
  6. nvmath/_internal/templates.py +383 -0
  7. nvmath/_utils.py +150 -0
  8. nvmath/bindings/__init__.py +51 -0
  9. nvmath/bindings/_internal/__init__.pxd +0 -0
  10. nvmath/bindings/_internal/__init__.py +0 -0
  11. nvmath/bindings/_internal/cublas.cp314-win_amd64.pyd +0 -0
  12. nvmath/bindings/_internal/cublas.pxd +530 -0
  13. nvmath/bindings/_internal/cublasLt.cp314-win_amd64.pyd +0 -0
  14. nvmath/bindings/_internal/cublasLt.pxd +59 -0
  15. nvmath/bindings/_internal/cublasMp.pxd +52 -0
  16. nvmath/bindings/_internal/cudss.cp314-win_amd64.pyd +0 -0
  17. nvmath/bindings/_internal/cudss.pxd +47 -0
  18. nvmath/bindings/_internal/cufft.cp314-win_amd64.pyd +0 -0
  19. nvmath/bindings/_internal/cufft.pxd +70 -0
  20. nvmath/bindings/_internal/cufftMp.pxd +77 -0
  21. nvmath/bindings/_internal/curand.cp314-win_amd64.pyd +0 -0
  22. nvmath/bindings/_internal/curand.pxd +42 -0
  23. nvmath/bindings/_internal/cusolver.cp314-win_amd64.pyd +0 -0
  24. nvmath/bindings/_internal/cusolver.pxd +15 -0
  25. nvmath/bindings/_internal/cusolverDn.cp314-win_amd64.pyd +0 -0
  26. nvmath/bindings/_internal/cusolverDn.pxd +406 -0
  27. nvmath/bindings/_internal/cusparse.cp314-win_amd64.pyd +0 -0
  28. nvmath/bindings/_internal/cusparse.pxd +277 -0
  29. nvmath/bindings/_internal/cusparseLt.cp314-win_amd64.pyd +0 -0
  30. nvmath/bindings/_internal/cusparseLt.pxd +48 -0
  31. nvmath/bindings/_internal/cutensor.cp314-win_amd64.pyd +0 -0
  32. nvmath/bindings/_internal/cutensor.pxd +58 -0
  33. nvmath/bindings/_internal/mathdx.cp314-win_amd64.pyd +0 -0
  34. nvmath/bindings/_internal/mathdx.pxd +116 -0
  35. nvmath/bindings/_internal/nvshmem.pxd +29 -0
  36. nvmath/bindings/_internal/utils.cp314-win_amd64.pyd +0 -0
  37. nvmath/bindings/_internal/utils.pxd +179 -0
  38. nvmath/bindings/_internal/utils.pyi +10 -0
  39. nvmath/bindings/cublas.cp314-win_amd64.pyd +0 -0
  40. nvmath/bindings/cublas.pxd +558 -0
  41. nvmath/bindings/cublas.pyi +746 -0
  42. nvmath/bindings/cublasLt.cp314-win_amd64.pyd +0 -0
  43. nvmath/bindings/cublasLt.pxd +109 -0
  44. nvmath/bindings/cublasLt.pyi +1337 -0
  45. nvmath/bindings/cublasMp.pxd +85 -0
  46. nvmath/bindings/cublasMp.pyi +219 -0
  47. nvmath/bindings/cudss.cp314-win_amd64.pyd +0 -0
  48. nvmath/bindings/cudss.pxd +86 -0
  49. nvmath/bindings/cudss.pyi +282 -0
  50. nvmath/bindings/cufft.cp314-win_amd64.pyd +0 -0
  51. nvmath/bindings/cufft.pxd +118 -0
  52. nvmath/bindings/cufft.pyi +241 -0
  53. nvmath/bindings/cufftMp.pxd +124 -0
  54. nvmath/bindings/cufftMp.pyi +260 -0
  55. nvmath/bindings/curand.cp314-win_amd64.pyd +0 -0
  56. nvmath/bindings/curand.pxd +71 -0
  57. nvmath/bindings/curand.pyi +159 -0
  58. nvmath/bindings/cusolver.cp314-win_amd64.pyd +0 -0
  59. nvmath/bindings/cusolver.pxd +62 -0
  60. nvmath/bindings/cusolver.pyi +242 -0
  61. nvmath/bindings/cusolverDn.cp314-win_amd64.pyd +0 -0
  62. nvmath/bindings/cusolverDn.pxd +430 -0
  63. nvmath/bindings/cusolverDn.pyi +416 -0
  64. nvmath/bindings/cusparse.cp314-win_amd64.pyd +0 -0
  65. nvmath/bindings/cusparse.pxd +338 -0
  66. nvmath/bindings/cusparse.pyi +659 -0
  67. nvmath/bindings/cusparseLt.cp314-win_amd64.pyd +0 -0
  68. nvmath/bindings/cusparseLt.pxd +99 -0
  69. nvmath/bindings/cusparseLt.pyi +198 -0
  70. nvmath/bindings/cutensor.cp314-win_amd64.pyd +0 -0
  71. nvmath/bindings/cutensor.pxd +98 -0
  72. nvmath/bindings/cutensor.pyi +294 -0
  73. nvmath/bindings/cycublas.cp314-win_amd64.pyd +0 -0
  74. nvmath/bindings/cycublas.pxd +684 -0
  75. nvmath/bindings/cycublasLt.cp314-win_amd64.pyd +0 -0
  76. nvmath/bindings/cycublasLt.pxd +1055 -0
  77. nvmath/bindings/cycublasMp.pxd +183 -0
  78. nvmath/bindings/cycudss.cp314-win_amd64.pyd +0 -0
  79. nvmath/bindings/cycudss.pxd +224 -0
  80. nvmath/bindings/cycufft.cp314-win_amd64.pyd +0 -0
  81. nvmath/bindings/cycufft.pxd +326 -0
  82. nvmath/bindings/cycufftMp.pxd +334 -0
  83. nvmath/bindings/cycurand.cp314-win_amd64.pyd +0 -0
  84. nvmath/bindings/cycurand.pxd +146 -0
  85. nvmath/bindings/cycusolver.cp314-win_amd64.pyd +0 -0
  86. nvmath/bindings/cycusolver.pxd +154 -0
  87. nvmath/bindings/cycusolverDn.cp314-win_amd64.pyd +0 -0
  88. nvmath/bindings/cycusolverDn.pxd +446 -0
  89. nvmath/bindings/cycusparse.cp314-win_amd64.pyd +0 -0
  90. nvmath/bindings/cycusparse.pxd +470 -0
  91. nvmath/bindings/cycusparseLt.cp314-win_amd64.pyd +0 -0
  92. nvmath/bindings/cycusparseLt.pxd +150 -0
  93. nvmath/bindings/cycutensor.cp314-win_amd64.pyd +0 -0
  94. nvmath/bindings/cycutensor.pxd +192 -0
  95. nvmath/bindings/cymathdx.cp314-win_amd64.pyd +0 -0
  96. nvmath/bindings/cymathdx.pxd +552 -0
  97. nvmath/bindings/cynvshmem.pxd +126 -0
  98. nvmath/bindings/mathdx.cp314-win_amd64.pyd +0 -0
  99. nvmath/bindings/mathdx.pxd +182 -0
  100. nvmath/bindings/mathdx.pyi +1244 -0
  101. nvmath/bindings/nvpl/__init__.pxd +0 -0
  102. nvmath/bindings/nvpl/__init__.py +13 -0
  103. nvmath/bindings/nvpl/_internal/__init__.pxd +0 -0
  104. nvmath/bindings/nvpl/_internal/__init__.py +0 -0
  105. nvmath/bindings/nvpl/_internal/blas.cp314-win_amd64.pyd +0 -0
  106. nvmath/bindings/nvpl/_internal/blas.pxd +125 -0
  107. nvmath/bindings/nvpl/_internal/fft.cp314-win_amd64.pyd +0 -0
  108. nvmath/bindings/nvpl/_internal/fft.pxd +36 -0
  109. nvmath/bindings/nvpl/blas.cp314-win_amd64.pyd +0 -0
  110. nvmath/bindings/nvpl/blas.pxd +85 -0
  111. nvmath/bindings/nvpl/blas.pyi +122 -0
  112. nvmath/bindings/nvpl/cyblas.cp314-win_amd64.pyd +0 -0
  113. nvmath/bindings/nvpl/cyblas.pxd +166 -0
  114. nvmath/bindings/nvpl/cyfft.cp314-win_amd64.pyd +0 -0
  115. nvmath/bindings/nvpl/cyfft.pxd +92 -0
  116. nvmath/bindings/nvpl/fft.cp314-win_amd64.pyd +0 -0
  117. nvmath/bindings/nvpl/fft.pxd +100 -0
  118. nvmath/bindings/nvpl/fft.pyi +100 -0
  119. nvmath/bindings/nvshmem.pxd +54 -0
  120. nvmath/bindings/nvshmem.pyi +179 -0
  121. nvmath/device/__init__.py +21 -0
  122. nvmath/device/_deprecated.py +33 -0
  123. nvmath/device/common.py +313 -0
  124. nvmath/device/common_backend.py +131 -0
  125. nvmath/device/common_cuda.py +201 -0
  126. nvmath/device/common_numba.py +310 -0
  127. nvmath/device/common_opaque_tensor.py +201 -0
  128. nvmath/device/cublasdx.py +1805 -0
  129. nvmath/device/cublasdx_backend.py +807 -0
  130. nvmath/device/cublasdx_numba.py +1612 -0
  131. nvmath/device/cufftdx.py +510 -0
  132. nvmath/device/cufftdx_backend.py +196 -0
  133. nvmath/device/cufftdx_numba.py +196 -0
  134. nvmath/device/curand_kernel.py +9147 -0
  135. nvmath/device/cusolverdx.py +2690 -0
  136. nvmath/device/cusolverdx_backend.py +440 -0
  137. nvmath/device/cusolverdx_numba.py +1624 -0
  138. nvmath/device/llvm_array.py +29 -0
  139. nvmath/device/random.py +445 -0
  140. nvmath/device/random_helpers.py +23 -0
  141. nvmath/device/random_states.py +187 -0
  142. nvmath/device/types.py +147 -0
  143. nvmath/device/vector_types_numba.py +203 -0
  144. nvmath/distributed/__init__.py +205 -0
  145. nvmath/distributed/_internal/__init__.py +0 -0
  146. nvmath/distributed/_internal/nvshmem.py +302 -0
  147. nvmath/distributed/_internal/tensor_ifc.py +67 -0
  148. nvmath/distributed/_internal/tensor_ifc_cupy.py +62 -0
  149. nvmath/distributed/_internal/tensor_ifc_host_device.py +165 -0
  150. nvmath/distributed/_internal/tensor_ifc_numpy.py +41 -0
  151. nvmath/distributed/_internal/tensor_ifc_torch.py +141 -0
  152. nvmath/distributed/_internal/tensor_wrapper.py +78 -0
  153. nvmath/distributed/_utils.py +167 -0
  154. nvmath/distributed/distribution.py +770 -0
  155. nvmath/distributed/fft/__init__.py +7 -0
  156. nvmath/distributed/fft/_configuration.py +79 -0
  157. nvmath/distributed/fft/fft.py +2801 -0
  158. nvmath/distributed/linalg/__init__.py +12 -0
  159. nvmath/distributed/linalg/_internal/__init__.py +3 -0
  160. nvmath/distributed/linalg/_internal/epilog_protocol.py +496 -0
  161. nvmath/distributed/linalg/_internal/matmul_desc_ifc.py +28 -0
  162. nvmath/distributed/linalg/advanced/__init__.py +8 -0
  163. nvmath/distributed/linalg/advanced/_configuration.py +166 -0
  164. nvmath/distributed/linalg/advanced/matmulmod.py +2908 -0
  165. nvmath/distributed/process_group.py +408 -0
  166. nvmath/distributed/reshape/__init__.py +6 -0
  167. nvmath/distributed/reshape/_configuration.py +39 -0
  168. nvmath/distributed/reshape/reshape.py +1256 -0
  169. nvmath/fft/__init__.py +7 -0
  170. nvmath/fft/_configuration.py +208 -0
  171. nvmath/fft/_exec_utils.py +109 -0
  172. nvmath/fft/_helpers.py +237 -0
  173. nvmath/fft/fft.py +2903 -0
  174. nvmath/internal/__init__.pxd +3 -0
  175. nvmath/internal/__init__.py +10 -0
  176. nvmath/internal/_bindings.cp314-win_amd64.pyd +0 -0
  177. nvmath/internal/_bindings.pxd +12 -0
  178. nvmath/internal/_device_utils.py +54 -0
  179. nvmath/internal/enum_utils.py +142 -0
  180. nvmath/internal/formatters.py +87 -0
  181. nvmath/internal/mem_limit.py +51 -0
  182. nvmath/internal/memory.cp314-win_amd64.pyd +0 -0
  183. nvmath/internal/memory.pxd +10 -0
  184. nvmath/internal/memory.pyi +46 -0
  185. nvmath/internal/ndbuffer/__init__.pxd +3 -0
  186. nvmath/internal/ndbuffer/__init__.py +7 -0
  187. nvmath/internal/ndbuffer/copy_kernel/args.h +34 -0
  188. nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/array_view.h +52 -0
  189. nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/elementwise.h +68 -0
  190. nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/grid_indexer.h +69 -0
  191. nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/transposed.h +240 -0
  192. nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/type_utils.h +39 -0
  193. nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/utils.h +132 -0
  194. nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/vec.h +159 -0
  195. nvmath/internal/ndbuffer/copy_kernel/elementwise.h +53 -0
  196. nvmath/internal/ndbuffer/copy_kernel/transposed.h +58 -0
  197. nvmath/internal/ndbuffer/copy_kernel.cp314-win_amd64.pyd +0 -0
  198. nvmath/internal/ndbuffer/copy_kernel.pxd +9 -0
  199. nvmath/internal/ndbuffer/copy_kernel.pyi +9 -0
  200. nvmath/internal/ndbuffer/data_layout.cp314-win_amd64.pyd +0 -0
  201. nvmath/internal/ndbuffer/data_layout.pxd +72 -0
  202. nvmath/internal/ndbuffer/data_layout.pyi +23 -0
  203. nvmath/internal/ndbuffer/jit.cp314-win_amd64.pyd +0 -0
  204. nvmath/internal/ndbuffer/jit.pxd +7 -0
  205. nvmath/internal/ndbuffer/jit.pyi +6 -0
  206. nvmath/internal/ndbuffer/ndbuffer.cp314-win_amd64.pyd +0 -0
  207. nvmath/internal/ndbuffer/ndbuffer.pxd +36 -0
  208. nvmath/internal/ndbuffer/ndbuffer.pyi +41 -0
  209. nvmath/internal/ndbuffer/package_utils.cp314-win_amd64.pyd +0 -0
  210. nvmath/internal/ndbuffer/package_utils.pxd +11 -0
  211. nvmath/internal/ndbuffer/package_utils.pyi +12 -0
  212. nvmath/internal/package_ifc.py +147 -0
  213. nvmath/internal/package_ifc_cuda.py +49 -0
  214. nvmath/internal/package_ifc_cupy.py +49 -0
  215. nvmath/internal/package_ifc_torch.py +31 -0
  216. nvmath/internal/package_wrapper.py +14 -0
  217. nvmath/internal/tensor_ifc.py +194 -0
  218. nvmath/internal/tensor_ifc_cupy.py +241 -0
  219. nvmath/internal/tensor_ifc_ndbuffer.py +153 -0
  220. nvmath/internal/tensor_ifc_numpy.py +183 -0
  221. nvmath/internal/tensor_ifc_torch.py +176 -0
  222. nvmath/internal/tensor_wrapper.py +157 -0
  223. nvmath/internal/typemaps.py +162 -0
  224. nvmath/internal/utils.py +771 -0
  225. nvmath/linalg/__init__.py +46 -0
  226. nvmath/linalg/_internal/__init__.py +3 -0
  227. nvmath/linalg/_internal/algo_cap_ifc.py +82 -0
  228. nvmath/linalg/_internal/algo_config_ifc.py +43 -0
  229. nvmath/linalg/_internal/batch.py +217 -0
  230. nvmath/linalg/_internal/enum_to_tuples.py +64 -0
  231. nvmath/linalg/_internal/epilog_protocol.py +766 -0
  232. nvmath/linalg/_internal/layout.py +624 -0
  233. nvmath/linalg/_internal/matmul_desc_ifc.py +28 -0
  234. nvmath/linalg/_internal/matmul_pref_ifc.py +27 -0
  235. nvmath/linalg/_internal/matrix_layout_ifc.py +26 -0
  236. nvmath/linalg/_internal/typemaps.py +144 -0
  237. nvmath/linalg/_internal/utils.py +134 -0
  238. nvmath/linalg/advanced/__init__.py +8 -0
  239. nvmath/linalg/advanced/_algorithmmod.py +149 -0
  240. nvmath/linalg/advanced/_configuration.py +349 -0
  241. nvmath/linalg/advanced/helpers/__init__.py +5 -0
  242. nvmath/linalg/advanced/helpers/matmul.py +1348 -0
  243. nvmath/linalg/advanced/matmulmod.py +3937 -0
  244. nvmath/linalg/generic/__init__.py +43 -0
  245. nvmath/linalg/generic/_configuration/__init__.py +37 -0
  246. nvmath/linalg/generic/_configuration/layout.py +263 -0
  247. nvmath/linalg/generic/_configuration/match.py +609 -0
  248. nvmath/linalg/generic/_configuration/qualifiers.py +494 -0
  249. nvmath/linalg/generic/_configuration/wrap.py +217 -0
  250. nvmath/linalg/generic/_dtype.py +15 -0
  251. nvmath/linalg/generic/matmulmod.py +1060 -0
  252. nvmath/memory.py +282 -0
  253. nvmath/sparse/__init__.py +38 -0
  254. nvmath/sparse/_internal/__init__.py +21 -0
  255. nvmath/sparse/_internal/common_utils.py +144 -0
  256. nvmath/sparse/_internal/cudss_config_ifc.py +741 -0
  257. nvmath/sparse/_internal/cudss_data_ifc.py +399 -0
  258. nvmath/sparse/_internal/cudss_utils.py +445 -0
  259. nvmath/sparse/_internal/cusparse_utils.py +382 -0
  260. nvmath/sparse/_internal/sparse_bsc_ifc.py +300 -0
  261. nvmath/sparse/_internal/sparse_bsr_ifc.py +303 -0
  262. nvmath/sparse/_internal/sparse_coo_ifc.py +254 -0
  263. nvmath/sparse/_internal/sparse_csc_ifc.py +266 -0
  264. nvmath/sparse/_internal/sparse_csr_ifc.py +266 -0
  265. nvmath/sparse/_internal/sparse_dia_ifc.py +239 -0
  266. nvmath/sparse/_internal/sparse_format_helpers.py +601 -0
  267. nvmath/sparse/_internal/sparse_tensor_ifc.py +121 -0
  268. nvmath/sparse/_internal/sparse_ust_ifc.py +146 -0
  269. nvmath/sparse/_internal/utils.py +56 -0
  270. nvmath/sparse/advanced/__init__.py +7 -0
  271. nvmath/sparse/advanced/_configuration.py +230 -0
  272. nvmath/sparse/advanced/direct_solver.py +1884 -0
  273. nvmath/sparse/generic/__init__.py +7 -0
  274. nvmath/sparse/generic/_configuration.py +129 -0
  275. nvmath/sparse/generic/_helpers.py +137 -0
  276. nvmath/sparse/generic/_thunks.py +21 -0
  277. nvmath/sparse/generic/matmulmod.py +2426 -0
  278. nvmath/sparse/ust/__init__.py +7 -0
  279. nvmath/sparse/ust/_converters.py +378 -0
  280. nvmath/sparse/ust/_drawer.py +552 -0
  281. nvmath/sparse/ust/_emitter.py +553 -0
  282. nvmath/sparse/ust/_jit.py +212 -0
  283. nvmath/sparse/ust/_kernel.py +346 -0
  284. nvmath/sparse/ust/_semiring.py +71 -0
  285. nvmath/sparse/ust/_utils.py +102 -0
  286. nvmath/sparse/ust/interfaces/__init__.py +0 -0
  287. nvmath/sparse/ust/interfaces/torch_interface.py +476 -0
  288. nvmath/sparse/ust/semiring_ops.py +71 -0
  289. nvmath/sparse/ust/tensor.py +1027 -0
  290. nvmath/sparse/ust/tensor_drawer.py +552 -0
  291. nvmath/sparse/ust/tensor_emitter.py +558 -0
  292. nvmath/sparse/ust/tensor_format.py +935 -0
  293. nvmath/sparse/ust/tensor_jit.py +212 -0
  294. nvmath/sparse/ust/tensor_kernel.py +348 -0
  295. nvmath/sparse/ust/tensor_utils.py +380 -0
  296. nvmath/tensor/__init__.py +6 -0
  297. nvmath/tensor/_configuration.py +95 -0
  298. nvmath/tensor/_internal/__init__.py +3 -0
  299. nvmath/tensor/_internal/cutensor_config_ifc.py +247 -0
  300. nvmath/tensor/_internal/cutensor_utils.py +162 -0
  301. nvmath/tensor/_internal/data.py +43 -0
  302. nvmath/tensor/_internal/einsum_parser.py +444 -0
  303. nvmath/tensor/_internal/typemaps.py +96 -0
  304. nvmath/tensor/contract.py +1861 -0
  305. nvmath_python-0.9.0.dist-info/METADATA +127 -0
  306. nvmath_python-0.9.0.dist-info/RECORD +309 -0
  307. nvmath_python-0.9.0.dist-info/WHEEL +5 -0
  308. nvmath_python-0.9.0.dist-info/licenses/LICENSE +177 -0
  309. nvmath_python-0.9.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,1060 @@
1
+ # Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ __all__ = [
6
+ "Matmul",
7
+ "matmul",
8
+ ]
9
+
10
+ import dataclasses
11
+ import logging
12
+ import math
13
+ from collections.abc import Sequence
14
+ from typing import Final, TypeAlias
15
+
16
+ import numpy as np
17
+
18
+ from nvmath._internal import templates
19
+ from nvmath.bindings import cublas
20
+ from nvmath.internal import formatters, tensor_wrapper, typemaps, utils
21
+ from nvmath.linalg._internal.batch import BatchTraits
22
+ from nvmath.linalg._internal.layout import BLASMatrixTraits, BLASMMTraitsView, InputMMTraits, check_extents, check_strides
23
+ from nvmath.linalg.advanced.matmulmod import SHARED_MM_DOCUMENTATION
24
+ from nvmath.linalg.generic._configuration import (
25
+ CACHED_LAYOUT_CHECKERS,
26
+ GeneralMatrixQualifier,
27
+ MatmulOptions,
28
+ MatrixQualifier,
29
+ matrix_qualifiers_dtype,
30
+ mm_layout_checker_getter,
31
+ select_blas_mm_function,
32
+ vector_to_square,
33
+ )
34
+ from nvmath.linalg.generic._dtype import check_dtype
35
+
36
+ AnyTensor: TypeAlias = tensor_wrapper.AnyTensor
37
+ SideMode: TypeAlias = cublas.SideMode
38
+ FillMode: TypeAlias = cublas.FillMode
39
+ DiagType: TypeAlias = cublas.DiagType
40
+
41
+
42
+ @dataclasses.dataclass(frozen=True, slots=True, kw_only=True)
43
+ class ExecutionCUDA(templates.ExecutionCUDA):
44
+ """
45
+ A data class for providing GPU execution options.
46
+
47
+ Attributes:
48
+ device_id: CUDA device ordinal (only used if the operand resides on the CPU). The
49
+ default value is 0.
50
+
51
+ .. seealso::
52
+ :class:`ExecutionCPU`, :class:`Matmul`, :func:`matmul`
53
+ """
54
+
55
+ pass
56
+
57
+
58
+ @dataclasses.dataclass(frozen=True, slots=True, kw_only=True)
59
+ class ExecutionCPU(templates.ExecutionCPU):
60
+ """
61
+ A data class for providing CPU execution options.
62
+
63
+ Attributes:
64
+ num_threads: The number of CPU threads used to execute the operation.
65
+ If not specified, defaults to the number of CPU cores available to the
66
+ process.
67
+
68
+ .. seealso::
69
+ :class:`ExecutionCUDA`, :class:`Matmul`, :func:`matmul`,
70
+ :func:`~nvmath.bindings.nvpl.blas.set_num_threads_local`
71
+ """
72
+
73
+ pass
74
+
75
+
76
+ class InvalidMatmulState(Exception):
77
+ pass
78
+
79
+
80
+ GENERIC_MM_DOCUMENTATION = SHARED_MM_DOCUMENTATION.copy()
81
+ GENERIC_MM_DOCUMENTATION.update(
82
+ {
83
+ "qualifiers": """\
84
+ If desired, specify the matrix qualifiers as a :class:`numpy.ndarray` of
85
+ :class:`~nvmath.linalg.generic.matrix_qualifiers_dtype` objects of length <= 3 corresponding to the operands `a`, `b`, and
86
+ `c`. By default, :class:`GeneralMatrixQualifier` is assumed for each tensor. See
87
+ :ref:`matrix-tensor-qualifiers` for the motivation behind qualifiers.""".replace("\n", " "),
88
+ #
89
+ "execution": """\
90
+ Specify execution space options for the Matmul as a :class:`ExecutionCUDA` or :class:`ExecutionCPU` object. If not specified,
91
+ the execution space will be selected to match operand's storage (in GPU or host memory), and the corresponding
92
+ :class:`ExecutionCUDA` or :class:`ExecutionCPU` object will be default-constructed.""".replace("\n", " "),
93
+ #
94
+ "options": """\
95
+ Specify options for the matrix multiplication as a :class:`MatmulOptions` object. If not specified, the
96
+ value will be set to the default-constructed ``MatmulOptions`` object.""".replace("\n", " "),
97
+ #
98
+ "result": """\
99
+ The result of the specified matrix multiplication, which remains on the same device and belong to the
100
+ same package as the input operands.""".replace("\n", " "),
101
+ #
102
+ "semantics": """\
103
+ .. _semantics:
104
+
105
+ The semantics of the matrix multiplication follows :external:py:data:`numpy.matmul` semantics, with some restrictions.
106
+
107
+ * Broadcasting `c` is not supported in this API, but may be supported in the future. See the advanced API
108
+ (:func:`nvmath.linalg.advanced.matmul`) for an API that supports broadcasting `c`.
109
+
110
+ In addition, the semantics for the fused matrix addition are described below:
111
+
112
+ * If arguments `a` and `b` are matrices, they are multiplied according to the rules of matrix multiplication.
113
+ * If argument `a` is 1-D, it is promoted to a matrix by prefixing ``1`` to its dimensions. After matrix
114
+ multiplication, the prefixed ``1`` is removed from the result's dimensions.
115
+ * If argument `b` is 1-D, it is promoted to a matrix by appending ``1`` to its dimensions. After matrix
116
+ multiplication, the appended ``1`` is removed from the result's dimensions.
117
+ * The operand for the matrix addition `c` must be the expected shape of the result of the matrix multiplication.
118
+
119
+ """.strip(),
120
+ }
121
+ )
122
+
123
+
124
+ @utils.docstring_decorator(GENERIC_MM_DOCUMENTATION, skip_missing=False)
125
+ class Matmul(templates.StatefulAPI[MatmulOptions]):
126
+ """
127
+ Create a stateful object encapsulating the specified matrix multiplication computation
128
+ :math:`\\alpha a @ b + \\beta c` and the required resources to perform the operation. A
129
+ stateful object can be used to amortize the cost of preparation (planning in the case of
130
+ matrix multiplication) across multiple executions (also see the :ref:`Stateful APIs
131
+ <host api types>` section).
132
+
133
+ The function-form API :func:`matmul` is a convenient alternative to using stateful
134
+ objects for *single* use (the user needs to perform just one matrix multiplication, for
135
+ example), in which case there is no possibility of amortizing preparatory costs. The
136
+ function-form APIs are just convenience wrappers around the stateful object APIs.
137
+
138
+ Using the stateful object typically involves the following steps:
139
+
140
+ 1. **Problem Specification**: Initialize the object with a defined operation and
141
+ options.
142
+ 2. **Preparation**: Use :meth:`plan` to determine the best algorithmic implementation
143
+ for this specific matrix multiplication operation.
144
+ 3. **Execution**: Perform the matrix multiplication computation with :meth:`execute`.
145
+
146
+ Detailed information on what's happening in the various phases described above can be
147
+ obtained by passing in a :class:`logging.Logger` object to :class:`MatmulOptions` or by
148
+ setting the appropriate options in the root logger object, which is used by default:
149
+
150
+ >>> import logging
151
+ >>> logging.basicConfig(
152
+ ... level=logging.INFO,
153
+ ... format="%(asctime)s %(levelname)-8s %(message)s",
154
+ ... datefmt="%m-%d %H:%M:%S",
155
+ ... )
156
+
157
+ A user can select the desired logging level and, in general, take advantage of all of
158
+ the functionality offered by the Python `logging` module.
159
+
160
+ Args:
161
+ a: {a}
162
+
163
+ b: {b}
164
+
165
+ c: {c}
166
+
167
+ alpha: {alpha}
168
+
169
+ beta: {beta}
170
+
171
+ qualifiers: {qualifiers}
172
+
173
+ options: {options}
174
+
175
+ execution: {execution}
176
+
177
+ stream: {stream}
178
+
179
+ Semantics:
180
+ {semantics}
181
+
182
+ .. seealso::
183
+ :meth:`reset_operands`, :meth:`execute`
184
+
185
+ Examples:
186
+
187
+ >>> import numpy as np
188
+ >>> import nvmath
189
+
190
+ Create two 2-D float64 ndarrays on the CPU:
191
+
192
+ >>> M, N, K = 1024, 1024, 1024
193
+ >>> a = np.random.rand(M, K)
194
+ >>> b = np.random.rand(K, N)
195
+
196
+ We will define a matrix multiplication operation using the generic matrix
197
+ multiplication interface.
198
+
199
+ Create a Matmul object encapsulating the problem specification above:
200
+
201
+ >>> mm = nvmath.linalg.Matmul(a, b)
202
+
203
+ Options can be provided above to control the behavior of the operation using the
204
+ `options` argument (see :class:`MatmulOptions`).
205
+
206
+ Next, plan the operation. The operands' layouts, qualifiers, and dtypes will be
207
+ considered to select an appropriate matrix multiplication:
208
+
209
+ >>> mm.plan()
210
+
211
+ Now execute the matrix multiplication, and obtain the result `r1` as a NumPy
212
+ ndarray.
213
+
214
+ >>> r1 = mm.execute()
215
+
216
+ Note that all :class:`Matmul` methods execute on the current stream by default.
217
+ Alternatively, the `stream` argument can be used to run a method on a specified
218
+ stream.
219
+
220
+ Let's now look at the same problem with CuPy ndarrays on the GPU.
221
+
222
+ Create a 3-D complex128 CuPy ndarray on the GPU:
223
+
224
+ >>> import cupy as cp
225
+ >>> a = cp.random.rand(M, K)
226
+ >>> b = cp.random.rand(K, N)
227
+
228
+ Create an Matmul object encapsulating the problem specification described earlier
229
+ and use it as a context manager.
230
+
231
+ >>> with nvmath.linalg.Matmul(a, b) as mm:
232
+ ... # Plan the operation.
233
+ ... mm.plan()
234
+ ...
235
+ ... # Execute the operation to get the first result.
236
+ ... r1 = mm.execute()
237
+ ...
238
+ ... # Update operands A and B in-place (see reset_operands() for an
239
+ ... # alternative).
240
+ ... a[:] = cp.random.rand(M, K)
241
+ ... b[:] = cp.random.rand(K, N)
242
+ ...
243
+ ... # Execute the operation to get the new result.
244
+ ... r2 = mm.execute()
245
+
246
+
247
+ All the resources used by the object are released at the end of the block.
248
+
249
+ Further examples can be found in the `nvmath/examples/linalg/generic/matmul
250
+ <https://github.com/NVIDIA/nvmath-python/tree/main/examples/linalg/generic/matmul>`_
251
+ directory.
252
+ """
253
+
254
+ _input_traits: Final[InputMMTraits]
255
+ _batch_traits: Final[tuple[BatchTraits, BatchTraits, BatchTraits, BatchTraits]]
256
+ _qualifiers: Final[MatrixQualifier]
257
+
258
+ def __init__(
259
+ self,
260
+ a: AnyTensor,
261
+ b: AnyTensor,
262
+ /,
263
+ c: AnyTensor | None = None,
264
+ *,
265
+ alpha: float | complex | None = None,
266
+ beta: float | complex | None = None,
267
+ qualifiers: MatrixQualifier | None = None,
268
+ options: MatmulOptions | None = None,
269
+ execution: ExecutionCPU | ExecutionCUDA | None = None,
270
+ stream: utils.AnyStream | int | None = None,
271
+ ):
272
+ options = utils.check_or_create_options(MatmulOptions, options, "Matrix multiplication options")
273
+ assert options is not None
274
+
275
+ if c is None and options.inplace:
276
+ raise ValueError("Operation cannot be inplace if operand C is not provided.")
277
+
278
+ a = tensor_wrapper.wrap_operand(a)
279
+ check_dtype(a.dtype, "A")
280
+ check_extents(a.shape, "A")
281
+ check_strides(a.strides, "A")
282
+
283
+ b = tensor_wrapper.wrap_operand(b)
284
+ check_dtype(b.dtype, "B")
285
+ check_extents(b.shape, "B")
286
+ check_strides(b.strides, "B")
287
+ operands = [a, b]
288
+
289
+ self.num_operands = 2
290
+ if c is not None:
291
+ self.num_operands = 3
292
+ c = tensor_wrapper.wrap_operand(c)
293
+ check_dtype(c.dtype, "C")
294
+ check_extents(c.shape, "C")
295
+ check_strides(c.strides, "C")
296
+ operands.append(c)
297
+
298
+ super().__init__(operands, options=options, execution=execution, stream=stream)
299
+
300
+ self._logger.info(f"The data type of operand A is '{a.dtype}', and that of operand B is '{b.dtype}'.")
301
+ if c is not None:
302
+ self._logger.info(f"The data type of operand C is '{c.dtype}'.")
303
+
304
+ if self.options.inplace:
305
+ self._logger.info("The operation will be performed inplace with operand C.")
306
+
307
+ if c is not None and beta is None:
308
+ raise ValueError("A value for beta must be provided if operand C is provided.")
309
+
310
+ assert self.num_operands == 2 or self.num_operands == 3, "Internal Error."
311
+
312
+ if a.dtype != b.dtype or (c is not None and a.dtype != c.dtype):
313
+ raise ValueError(
314
+ "Unsupported combination of dtypes. "
315
+ f"A ({a.dtype}), B ({b.dtype}), and C ({getattr(c, 'dtype', None)}) must all have the same dtype."
316
+ )
317
+ # Determine the data types for a and b.
318
+ self.a_dtype = typemaps.NAME_TO_DATA_TYPE[a.dtype]
319
+ self.b_dtype = typemaps.NAME_TO_DATA_TYPE[b.dtype]
320
+ self.a_dtype_name = a.dtype
321
+ self.b_dtype_name = b.dtype
322
+
323
+ self.is_complex = "complex" in self.a_dtype_name or "complex" in self.b_dtype_name
324
+
325
+ # Determine the data types for c.
326
+ if c is None:
327
+ self.c_dtype = self.a_dtype
328
+ else:
329
+ self.c_dtype = typemaps.NAME_TO_DATA_TYPE[c.dtype]
330
+ self.c_dtype_name = typemaps.DATA_TYPE_TO_NAME[self.c_dtype]
331
+
332
+ self._logger.info(f"The data type for the result D is '{self.c_dtype_name}'.")
333
+
334
+ self.scale_type_name = self.a_dtype_name
335
+
336
+ # Set alpha and beta.
337
+ self.alpha = np.zeros((1,), dtype=self.scale_type_name)
338
+ try:
339
+ self.alpha[0] = alpha if alpha is not None else 1
340
+ except (ValueError, TypeError) as e:
341
+ raise ValueError(f"The value provided for alpha {alpha} is not convertible to dtype '{self.alpha.dtype}'.") from e
342
+
343
+ self.beta = np.zeros((1,), dtype=self.scale_type_name)
344
+ if beta is not None and c is None:
345
+ self._logger.warning(f"Matmul: The provided beta value {beta} is ignored since operand C is not specified.")
346
+ try:
347
+ self.beta[0] = beta if beta is not None and c is not None else 0
348
+ except (ValueError, TypeError) as e:
349
+ raise ValueError(f"The value provided for beta {beta} is not convertible to dtype '{self.beta.dtype}'.") from e
350
+
351
+ if qualifiers is None:
352
+ new_qualifiers = np.empty(3, dtype=matrix_qualifiers_dtype)
353
+ new_qualifiers[:] = GeneralMatrixQualifier.create()
354
+ else:
355
+ if not ((len(qualifiers) == 3) or (len(qualifiers) == 2 and c is None)):
356
+ raise ValueError("The number of MatrixQualifiers must match the number of operands.")
357
+ new_qualifiers = np.empty(3, dtype=matrix_qualifiers_dtype)
358
+ new_qualifiers[:2] = qualifiers[:2]
359
+ new_qualifiers[2] = GeneralMatrixQualifier.create() if len(qualifiers) < 3 else qualifiers[2]
360
+ self._qualifiers = new_qualifiers
361
+ self._logger.info(
362
+ f"The matrix multiplication qualifiers are:\n"
363
+ f" A = {GeneralMatrixQualifier.to_string(self._qualifiers[0])}\n"
364
+ f" B = {GeneralMatrixQualifier.to_string(self._qualifiers[1])}\n"
365
+ f" C = {GeneralMatrixQualifier.to_string(self._qualifiers[2])}"
366
+ )
367
+
368
+ # Set qualifiers based on torch lazy conjugation flag if not provided.
369
+ self._qualifiers[0]["conjugate"] = self._qualifiers[0]["conjugate"] ^ self._operands[0].is_conjugate
370
+ self._qualifiers[1]["conjugate"] = self._qualifiers[1]["conjugate"] ^ self._operands[1].is_conjugate
371
+ self.lazy_conjugation = (self._operands[0].is_conjugate, self._operands[1].is_conjugate, False)
372
+ if c is not None:
373
+ self._qualifiers[2]["conjugate"] = self._qualifiers[2]["conjugate"] ^ self._operands[2].is_conjugate
374
+ if self._qualifiers[2]["conjugate"]:
375
+ raise ValueError("The conjugate flag is currently not supported for operand C.")
376
+
377
+ # Capture operand extents and strides for consistency check when resetting operands.
378
+ self.operand_extents = tuple(o.shape for o in self._operands)
379
+ self.operand_strides = tuple(o.strides for o in self._operands)
380
+
381
+ # Create operand layouts.
382
+ a_layout = BLASMatrixTraits(
383
+ self.a_dtype,
384
+ *vector_to_square(
385
+ self._operands[0].shape[-2:],
386
+ self._operands[0].strides[-2:],
387
+ self._qualifiers[0],
388
+ ),
389
+ is_conjugate=bool(self._qualifiers[0]["conjugate"]),
390
+ is_transpose=False,
391
+ is_lower=self._qualifiers[0]["uplo"] == FillMode.LOWER,
392
+ )
393
+ self._logger.info("Operand A has traits of %s", a_layout)
394
+ b_layout = BLASMatrixTraits(
395
+ self.b_dtype,
396
+ *vector_to_square(
397
+ self._operands[1].shape[-2:],
398
+ self._operands[1].strides[-2:],
399
+ self._qualifiers[1],
400
+ ),
401
+ is_conjugate=bool(self._qualifiers[1]["conjugate"]),
402
+ is_transpose=False,
403
+ is_lower=self._qualifiers[1]["uplo"] == FillMode.LOWER,
404
+ )
405
+ self._logger.info("Operand B has traits of %s", b_layout)
406
+ c_layout = (
407
+ None
408
+ if c is None
409
+ else BLASMatrixTraits(
410
+ self.c_dtype,
411
+ *vector_to_square(
412
+ self._operands[2].shape[-2:],
413
+ self._operands[2].strides[-2:],
414
+ self._qualifiers[2],
415
+ ),
416
+ is_conjugate=bool(self._qualifiers[2]["conjugate"]),
417
+ is_transpose=False,
418
+ is_lower=self._qualifiers[2]["uplo"] == FillMode.LOWER,
419
+ )
420
+ )
421
+ self._logger.info("Operand C has traits of %s", c_layout)
422
+
423
+ # Get the operation traits.
424
+ self._input_traits = InputMMTraits.from_layouts(a_layout, b_layout, c_layout, self.options.inplace, self._logger)
425
+
426
+ a_batch = BatchTraits.from_full_shape_and_strides(
427
+ self._operands[0].shape,
428
+ self._operands[0].strides,
429
+ num_trailing_dims=2,
430
+ overlap_allowed=True,
431
+ )
432
+ b_batch = BatchTraits.from_full_shape_and_strides(
433
+ self._operands[1].shape,
434
+ self._operands[1].strides,
435
+ num_trailing_dims=2,
436
+ overlap_allowed=True,
437
+ )
438
+ c_batch = (
439
+ BatchTraits(shape=(), strides=())
440
+ if c is None
441
+ else BatchTraits.from_full_shape_and_strides(
442
+ self._operands[2].shape,
443
+ self._operands[2].strides,
444
+ num_trailing_dims=2,
445
+ overlap_allowed=False,
446
+ )
447
+ )
448
+ # BatchTraits overloads the * operator with a batch combination behavior
449
+ abc_batch, axis_order = a_batch * (b_batch * c_batch)
450
+ d_batch = (
451
+ c_batch
452
+ if self.options.inplace
453
+ else BatchTraits.from_batch_shape_and_size(
454
+ shape=abc_batch,
455
+ axis_order=axis_order,
456
+ batch_stride=math.prod(self._input_traits.d_layout_traits.shape),
457
+ )
458
+ )
459
+ self._logger.info("Operand A has batch traits of %s.", a_batch)
460
+ self._logger.info("Operand B has batch traits of %s.", b_batch)
461
+ self._logger.info("Operand C has batch traits of %s.", c_batch)
462
+ self._logger.info("Result D has batch traits of %s.", d_batch)
463
+ self._batch_traits = (a_batch, b_batch, c_batch, d_batch)
464
+
465
+ # Track whether user-provided operands have been released
466
+ self._operands_released = False
467
+
468
+ self.valid_state = True
469
+ self._logger.info("The Matmul operation has been created.")
470
+
471
+ def _check_valid_matmul(self, *args, **kwargs):
472
+ """
473
+ Check if the Matmul object is alive and well.
474
+ """
475
+ if not self.valid_state:
476
+ raise InvalidMatmulState("The Matmul object cannot be used after resources are free'd")
477
+
478
+ def _check_valid_operands(self, *args, **kwargs):
479
+ """
480
+ Check if operands are available for operations.
481
+ """
482
+ if self._operands_released:
483
+ raise ValueError(
484
+ "Operands have been released. Call reset_operands() to provide new operands before using this method."
485
+ )
486
+
487
+ @utils.precondition(_check_valid_matmul)
488
+ def plan(self, *, stream: utils.AnyStream | int | None = None) -> None:
489
+ """
490
+ Plan the matrix multiplication operation.
491
+
492
+ Unlike :py:meth:`nvmath.linalg.advanced.Matmul.plan`, this method takes no
493
+ tuning parameters. Its primary function is to find the correct matrix multiplication
494
+ implementation based on the operands and options provided to the constructor.
495
+
496
+ Args:
497
+ stream: {stream}
498
+
499
+ Returns:
500
+ Nothing.
501
+ """
502
+ log_info = self._logger.isEnabledFor(logging.INFO)
503
+
504
+ if log_info:
505
+ self._logger.info("= PLANNING PHASE =")
506
+ self._logger.info("Starting matrix multiplication planning...")
507
+
508
+ with utils.host_call_ctx(timing=log_info) as elapsed:
509
+ mm_layout_checker = mm_layout_checker_getter(self._qualifiers)
510
+
511
+ mm_traits = BLASMMTraitsView.from_input_traits(
512
+ self._input_traits,
513
+ mm_layout_checker,
514
+ self._logger,
515
+ lookup_table_table=CACHED_LAYOUT_CHECKERS,
516
+ )
517
+
518
+ self._function, function_name = select_blas_mm_function(
519
+ (*self._batch_traits[:2], self._batch_traits[3]),
520
+ mm_traits,
521
+ self._qualifiers,
522
+ self._logger,
523
+ self.execution,
524
+ )
525
+
526
+ if log_info and elapsed.data is not None:
527
+ self._logger.info(f"The plan found 1 suitable algorithm named {function_name}.")
528
+ self._logger.info(f"The matrix multiplication planning phase took {elapsed.data:.3f} ms to complete.")
529
+
530
+ # Base FLOP count.
531
+ self.flop_count = 2 * mm_traits.M * mm_traits.N * mm_traits.K
532
+ self._logger.info(f"The base matrix multiplication FLOP count is {formatters.FLOPSStr(self.flop_count, 'FLOP')}.")
533
+
534
+ self._has_plan = True
535
+
536
+ return
537
+
538
+ def _check_and_set_operand(
539
+ self,
540
+ new_operand: utils.TensorHolder,
541
+ operand_name: str,
542
+ stream_holder: utils.StreamHolder | None,
543
+ *,
544
+ operand_index: int,
545
+ dtype: str | None = None,
546
+ extents: Sequence[int] | None = None,
547
+ strides: Sequence[int] | None = None,
548
+ ):
549
+ """
550
+ Check to make sure that the provided operand is consistent with the one it's
551
+ updating, and update it.
552
+ """
553
+ # Make sure that the data type and extents match.
554
+ utils.check_attribute_match(dtype, new_operand.dtype, "data type")
555
+ utils.check_attribute_match(extents, new_operand.shape, "extents")
556
+
557
+ # Package must be the same to preserve stream ordering
558
+ if self._operands_package != new_operand.name:
559
+ raise TypeError(
560
+ f"Library package mismatch: The operand {operand_name} must from the same package ({new_operand.name}) "
561
+ f"as the original operand ({self._operands_package})."
562
+ )
563
+
564
+ if self._operands_device_id != new_operand.device_id:
565
+ raise ValueError(
566
+ f"The operand {operand_name} must be on the same device ({new_operand.device_id}) as the original operand "
567
+ f"({self._operands_device_id})."
568
+ )
569
+
570
+ # Conjugate flag of the provided operands must match the original qualifiers
571
+ if self.lazy_conjugation[operand_index] != new_operand.is_conjugate:
572
+ raise ValueError(f"The provided operand {operand_name} has different conjugate flag than the original operand")
573
+
574
+ self._operands[operand_index], self._operands_backup[operand_index] = templates.copy_operand_perhaps(
575
+ internal_operand=self._operands[operand_index],
576
+ operand=new_operand,
577
+ stream_holder=stream_holder,
578
+ execution_device_id=getattr(self.execution, "device_id", "cpu"),
579
+ operands_device_id=new_operand.device_id,
580
+ )
581
+
582
+ # Check strides after copying because copy could affect data layout?
583
+ # FIXME: Could end up with a broken operand state if user catches error raised here?
584
+ # But if we don't use copy_operand_perhaps, we can't do inplace operand reset.
585
+ utils.check_attribute_match(strides, self._operands[operand_index].strides, "strides")
586
+
587
+ self._logger.info(f"Operand '{operand_name}' has been reset to the new value.")
588
+
589
+ return
590
+
591
+ @utils.precondition(_check_valid_matmul)
592
+ def reset_operands(
593
+ self,
594
+ *,
595
+ a=None,
596
+ b=None,
597
+ c=None,
598
+ alpha=None,
599
+ beta=None,
600
+ stream: utils.AnyStream | int | None = None,
601
+ ):
602
+ """
603
+ Reset one or more of the operands held by this :class:`Matmul` instance.
604
+ Only the operands explicitly passed are updated; omitted operands retain
605
+ their current values.
606
+
607
+ This method will perform various checks on the new operands to make sure:
608
+
609
+ - The shapes, strides, datatypes match those of the old ones.
610
+ - The packages that the operands belong to match those of the old ones.
611
+ - If input tensors are on GPU, the device must match.
612
+
613
+ .. versionchanged:: 0.9
614
+ All parameters are now keyword-only.
615
+
616
+ Args:
617
+ a: {a}
618
+
619
+ b: {b}
620
+
621
+ c: {c}
622
+
623
+ alpha: {alpha}
624
+
625
+ beta: {beta}
626
+
627
+ stream: {stream}
628
+
629
+ Examples:
630
+
631
+ >>> import cupy as cp
632
+ >>> import nvmath
633
+
634
+ Create two 3-D float64 ndarrays on the GPU:
635
+
636
+ >>> M, N, K = 128, 128, 256
637
+ >>> a = cp.random.rand(M, K)
638
+ >>> b = cp.random.rand(K, N)
639
+
640
+ Create an matrix multiplication object as a context manager
641
+
642
+ >>> with nvmath.linalg.Matmul(a, b) as mm:
643
+ ... # Plan the operation.
644
+ ... mm.plan()
645
+ ...
646
+ ... # Execute the MM to get the first result.
647
+ ... r1 = mm.execute()
648
+ ...
649
+ ... # Reset the operands to new CuPy ndarrays.
650
+ ... a_new = cp.random.rand(M, K)
651
+ ... b_new = cp.random.rand(K, N)
652
+ ... mm.reset_operands(a=a_new, b=b_new)
653
+ ...
654
+ ... # Execute to get the new result corresponding to the updated operands.
655
+ ... r2 = mm.execute()
656
+
657
+ Note that if only a subset of operands are reset, the operands that are not
658
+ reset hold their original values.
659
+
660
+ With :meth:`reset_operands`, minimal overhead is achieved as problem
661
+ specification and planning are only performed once.
662
+
663
+ For the particular example above, a slower alternative to calling
664
+ :meth:`reset_operands` would be to modify the operands in-place (e.g.,
665
+ ``a[:] = a_new`` and ``b[:] = b_new``), but this approach is less efficient
666
+ as it involves copying data rather than just updating pointers.
667
+
668
+ For more details, please refer to `inplace update example
669
+ <https://github.com/NVIDIA/nvmath-python/tree/main/examples/linalg/advanced/matmul/example05_stateful_inplace.py>`_.
670
+ """
671
+
672
+ if c is not None and self.num_operands == 2:
673
+ raise ValueError(
674
+ "The matrix multiplication problem specification does not include operand C, so it cannot be reset."
675
+ )
676
+
677
+ # If operands have been released, all required operands must be provided
678
+ if self._operands_released:
679
+ all_provided = a is not None and b is not None and (self.num_operands != 3 or c is not None)
680
+ if not all_provided:
681
+ raise ValueError(
682
+ "After release_operands(), all required operands must be provided to reset_operands(). "
683
+ f"Required: a, b{', c' if self.num_operands == 3 else ''}"
684
+ )
685
+
686
+ # Initialize operands to prepare for new values
687
+ self._operands = [None] * self.num_operands # type: ignore[list-item]
688
+ self._operands_backup = [None] * self.num_operands
689
+
690
+ # Update alpha.
691
+ if alpha is not None:
692
+ try:
693
+ self.alpha[0] = alpha
694
+ except (ValueError, TypeError) as e:
695
+ raise ValueError(
696
+ f"The value provided for alpha {alpha} is not convertible to dtype '{self.alpha.dtype}'."
697
+ ) from e
698
+
699
+ # Update beta.
700
+ if beta is not None:
701
+ if self.num_operands == 2:
702
+ self._logger.warning(f"Matmul: The provided beta value {beta} is ignored since operand C is not specified.")
703
+ else:
704
+ try:
705
+ self.beta[0] = beta
706
+ except (ValueError, TypeError) as e:
707
+ raise ValueError(
708
+ f"The value provided for beta {beta} is not convertible to dtype '{self.beta.dtype}'."
709
+ ) from e
710
+
711
+ _, operand_stream_holder = self._get_or_create_stream_maybe(stream)
712
+
713
+ # Reset the provided operands.
714
+ if a is not None:
715
+ a = tensor_wrapper.wrap_operand(a)
716
+ index = 0
717
+ self._check_and_set_operand(
718
+ a,
719
+ "A",
720
+ operand_stream_holder,
721
+ operand_index=index,
722
+ dtype=self.a_dtype_name,
723
+ extents=self.operand_extents[index],
724
+ strides=self.operand_strides[index],
725
+ )
726
+
727
+ if b is not None:
728
+ b = tensor_wrapper.wrap_operand(b)
729
+ index = 1
730
+ self._check_and_set_operand(
731
+ b,
732
+ "B",
733
+ operand_stream_holder,
734
+ operand_index=index,
735
+ dtype=self.b_dtype_name,
736
+ extents=self.operand_extents[index],
737
+ strides=self.operand_strides[index],
738
+ )
739
+
740
+ if c is not None: # If we get here, we know that C is one of the operands in the problem specification.
741
+ c = tensor_wrapper.wrap_operand(c)
742
+ index = 2
743
+ self._check_and_set_operand(
744
+ c,
745
+ "C",
746
+ operand_stream_holder,
747
+ operand_index=index,
748
+ dtype=self.c_dtype_name,
749
+ extents=self.operand_extents[index],
750
+ strides=self.operand_strides[index],
751
+ )
752
+
753
+ # Clear the released flag since we now have valid operands
754
+ self._operands_released = False
755
+
756
+ @utils.precondition(_check_valid_matmul)
757
+ @utils.precondition(templates.StatefulAPI._check_planned, "Execution")
758
+ @utils.precondition(_check_valid_operands)
759
+ def execute(self, *, stream: utils.AnyStream | int | None = None) -> utils.AnyTensor:
760
+ """
761
+ Execute a prepared (planned) matrix multiplication.
762
+
763
+ This method is a wrapper around :py:meth:`_execute`, which takes the same arguments,
764
+ but skips as many correctness and safety checks as possible.
765
+
766
+ Args:
767
+ stream: {stream}
768
+
769
+ Returns:
770
+ {result}
771
+ """
772
+ return self._execute(stream=stream)
773
+
774
+ def _execute(self, *, stream: utils.AnyStream | int | None = None) -> utils.AnyTensor:
775
+ log_info = self._logger.isEnabledFor(logging.INFO)
776
+ log_debug = self._logger.isEnabledFor(logging.DEBUG)
777
+ if log_info:
778
+ self._logger.info("= EXECUTION PHASE =")
779
+ exec_stream_holder, operand_stream_holder = self._get_or_create_stream_maybe(stream)
780
+ if log_info:
781
+ self._logger.info(
782
+ "The specified stream for execute() is "
783
+ f"{getattr(exec_stream_holder or operand_stream_holder, 'obj', 'no stream')}."
784
+ )
785
+
786
+ # We must handle all valid combinations of:
787
+ # - c-provided and c-not-provided
788
+ # - results in-place and out-of-place
789
+
790
+ # Create empty tensor for the result.
791
+ if self.num_operands == 2 or not self.options.inplace:
792
+ if log_debug:
793
+ self._logger.debug("Beginning output (empty) tensor creation...")
794
+ self._logger.debug(
795
+ f"The output tensor shape = {self._input_traits.d_layout_traits.shape} with strides = "
796
+ f"{self._input_traits.d_layout_traits.strides} and data type '{self.c_dtype_name}'."
797
+ )
798
+ self._logger.debug(
799
+ f"The output tensor has batch dimensions with shape {self._batch_traits[3].shape} "
800
+ f"and strides {self._batch_traits[3].strides}."
801
+ )
802
+ result = utils.create_empty_tensor(
803
+ self._result_class,
804
+ (*self._batch_traits[3].shape, *self._input_traits.d_layout_traits.shape),
805
+ self.c_dtype_name,
806
+ getattr(self.execution, "device_id", "cpu"),
807
+ exec_stream_holder,
808
+ # verify_strides=False because we need strides to be exactly what we
809
+ # request; not arbitrary if the strides aren't contiguous and dense.
810
+ # Otherwise, the layout parameters will mismatch what we pass to the matmul
811
+ # implementation.
812
+ verify_strides=False,
813
+ strides=(*self._batch_traits[3].strides, *self._input_traits.d_layout_traits.strides),
814
+ )
815
+ if log_debug:
816
+ self._logger.debug("The output (empty) tensor has been created.")
817
+ else: # num_operands == 3 and self.options.inplace
818
+ result = self._operands[2]
819
+ self._logger.debug("The output tensor is C (in-place execution).")
820
+
821
+ if self.num_operands == 3 and not self.options.inplace:
822
+ result.copy_(self._operands[2], exec_stream_holder)
823
+ self._logger.debug("Operand C copied to result tensor (out-of-place execution).")
824
+
825
+ a, b = self._operands[0], self._operands[1]
826
+ if log_info:
827
+ self._logger.info("Starting matrix multiplication...")
828
+
829
+ if self.execution.name == "cuda":
830
+ assert exec_stream_holder is not None
831
+ with utils.cuda_call_ctx(exec_stream_holder, self._blocking, timing=log_info) as (
832
+ _,
833
+ elapsed,
834
+ ):
835
+ self._function(
836
+ a,
837
+ b,
838
+ result,
839
+ self.alpha,
840
+ self.beta,
841
+ exec_stream_holder,
842
+ )
843
+ else:
844
+ with utils.host_call_ctx(timing=log_info) as elapsed:
845
+ self._function(
846
+ a,
847
+ b,
848
+ result,
849
+ self.alpha,
850
+ self.beta,
851
+ exec_stream_holder,
852
+ )
853
+
854
+ if log_info and elapsed.data is not None:
855
+ self._logger.info(f"The matrix multiplication calculation took {elapsed.data:.3f} ms to complete.")
856
+
857
+ # Return the result and auxiliary outputs, if present.
858
+ if self._operands_device_id != getattr(self.execution, "device_id", "cpu"):
859
+ if self.options.inplace:
860
+ c = self._operands_backup[2]
861
+ assert c is not None, (
862
+ "Internal Error. "
863
+ "Inplace operation was requested, but the execution space was different from the input space, "
864
+ "and we didn't keep a reference to the input tensor."
865
+ )
866
+ c.copy_(result, stream_holder=operand_stream_holder)
867
+ out = c.tensor
868
+ else:
869
+ out = result.to(self._operands_device_id, stream_holder=operand_stream_holder).tensor
870
+ else:
871
+ out = result.tensor
872
+
873
+ return out
874
+
875
+ @utils.precondition(_check_valid_matmul)
876
+ def release_operands(self):
877
+ """
878
+ {release_operands}
879
+ """
880
+ # Same space: _operands hold direct user references.
881
+ # Cross space: _operands hold internal device mirrors,
882
+ # _operands_backup hold direct user references.
883
+ # In both cases, release _operands.
884
+ # Also release _operands_backup in cross-space.
885
+ self._operands = [None] * len(self._operands) # type: ignore[list-item]
886
+ if getattr(self.execution, "device_id", "cpu") != self._operands_device_id:
887
+ self._operands_backup = [None] * len(self._operands_backup)
888
+
889
+ self._operands_released = True
890
+ self._logger.info("User-provided operands have been released.")
891
+
892
+ def free(self):
893
+ """Free Matmul resources.
894
+
895
+ It is recommended that the :class:`Matmul` object be used within a context,
896
+ but if it is not possible then this method must be called explicitly to ensure
897
+ that the Matmul object doesn't hold unnecessary references to operands.
898
+ """
899
+ if not self.valid_state:
900
+ return
901
+
902
+ try:
903
+ # Note that here we don't need to enforce any ordering:
904
+ # - for CPU operands, the execution is blocking so this method
905
+ # will only be called after the execution is complete.
906
+ # - for GPU operands, the execution is non-blocking, but
907
+ # it operates on the user's operands and they are responsible
908
+ # to ensure use-after-free does not happen.
909
+
910
+ # Call parent class free
911
+ super().free()
912
+
913
+ # Set all attributes to None except for logger and valid_state
914
+ _keep = {"_logger", "valid_state"}
915
+ for attr in list(vars(self)):
916
+ if attr not in _keep:
917
+ setattr(self, attr, None)
918
+
919
+ except Exception as e:
920
+ self._logger.critical("Internal error: only part of the Matmul object's resources have been released.")
921
+ self._logger.critical(str(e))
922
+ raise e
923
+ finally:
924
+ self.valid_state = False
925
+
926
+ self._logger.info("The Matmul object's resources have been released.")
927
+
928
+ def __exit__(self, *args, **kwargs):
929
+ self.free()
930
+
931
+
932
+ @utils.docstring_decorator(GENERIC_MM_DOCUMENTATION, skip_missing=False)
933
+ def matmul(
934
+ a: AnyTensor,
935
+ b: AnyTensor,
936
+ /,
937
+ c: AnyTensor | None = None,
938
+ *,
939
+ alpha: float | complex | None = None,
940
+ beta: float | complex | None = None,
941
+ qualifiers: MatrixQualifier | None = None,
942
+ options: MatmulOptions | None = None,
943
+ execution: ExecutionCPU | ExecutionCUDA | None = None,
944
+ stream: utils.AnyStream | int | None = None,
945
+ ):
946
+ """
947
+ Perform the specified matrix multiplication computation :math:`\\alpha a @ b + \\beta
948
+ c`. This function-form is a wrapper around the stateful :class:`Matmul` object APIs and
949
+ is meant for *single* use (the user needs to perform just one matrix multiplication, for
950
+ example), in which case there is no possibility of amortizing preparatory costs.
951
+
952
+ Detailed information on what's happening within this function can be obtained by passing
953
+ in a :class:`logging.Logger` object to :class:`MatmulOptions` or by setting the
954
+ appropriate options in the root logger object, which is used by default:
955
+
956
+ >>> import logging
957
+ >>> logging.basicConfig(
958
+ ... level=logging.INFO,
959
+ ... format="%(asctime)s %(levelname)-8s %(message)s",
960
+ ... datefmt="%m-%d %H:%M:%S",
961
+ ... )
962
+
963
+ A user can select the desired logging level and, in general, take advantage of all of
964
+ the functionality offered by the Python `logging` module.
965
+
966
+ Args:
967
+ a: {a}
968
+
969
+ b: {b}
970
+
971
+ c: {c}
972
+
973
+ alpha: {alpha}
974
+
975
+ beta: {beta}
976
+
977
+ qualifiers: {qualifiers}
978
+
979
+ options: {options}
980
+
981
+ execution: {execution}
982
+
983
+ stream: {stream}
984
+
985
+ Returns:
986
+ {result}
987
+
988
+ Semantics:
989
+ {semantics}
990
+
991
+ .. seealso::
992
+ :class:`Matmul`, :class:`MatmulOptions`, :class:`matrix_qualifiers_dtype`,
993
+ :class:`MatrixQualifier`
994
+
995
+ Examples:
996
+
997
+ >>> import cupy as cp
998
+ >>> import nvmath
999
+
1000
+ Create three float32 ndarrays on the GPU:
1001
+
1002
+ >>> M, N, K = 128, 64, 256
1003
+ >>> a = cp.random.rand(M, K, dtype=cp.float32)
1004
+ >>> b = cp.random.rand(K, N, dtype=cp.float32)
1005
+ >>> c = cp.random.rand(M, N, dtype=cp.float32)
1006
+
1007
+ Perform the operation :math:`\\alpha A @ B + \\beta C` using :func:`matmul`. The
1008
+ result `r` is also a CuPy float64 ndarray:
1009
+
1010
+ >>> r = nvmath.linalg.matmul(a, b, c, alpha=1.23, beta=0.74)
1011
+
1012
+ The package current stream is used by default, but a stream can be explicitly
1013
+ provided to the Matmul operation. This can be done if the operands are computed on a
1014
+ different stream, for example:
1015
+
1016
+ >>> s = cp.cuda.Stream()
1017
+ >>> with s:
1018
+ ... a = cp.random.rand(M, K)
1019
+ ... b = cp.random.rand(K, N)
1020
+ >>> r = nvmath.linalg.matmul(a, b, stream=s)
1021
+
1022
+ The operation above runs on stream `s` and is ordered with respect to the input
1023
+ computation.
1024
+
1025
+ Create NumPy ndarrays on the CPU.
1026
+
1027
+ >>> import numpy as np
1028
+ >>> a = np.random.rand(M, K)
1029
+ >>> b = np.random.rand(K, N)
1030
+
1031
+ Provide the NumPy ndarrays to :func:`matmul`, with the result also being a NumPy
1032
+ ndarray:
1033
+
1034
+ >>> r = nvmath.linalg.matmul(a, b)
1035
+
1036
+ Notes:
1037
+ - This function is a convenience wrapper around :class:`Matmul` and and is
1038
+ specifically meant for *single* use.
1039
+
1040
+ Further examples can be found in the `nvmath/examples/linalg/generic/matmul
1041
+ <https://github.com/NVIDIA/nvmath-python/tree/main/examples/linalg/generic/matmul>`_
1042
+ directory.
1043
+ """
1044
+
1045
+ with Matmul(
1046
+ a,
1047
+ b,
1048
+ c=c,
1049
+ alpha=alpha,
1050
+ beta=beta,
1051
+ qualifiers=qualifiers,
1052
+ options=options,
1053
+ execution=execution,
1054
+ stream=stream,
1055
+ ) as mm:
1056
+ mm.plan(stream=stream)
1057
+
1058
+ r = mm.execute(stream=stream)
1059
+
1060
+ return r