nvmath-python 1.0.0__cp314-cp314t-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 (332) 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 +130 -0
  7. nvmath/_internal/threadsafe.py +106 -0
  8. nvmath/_internal/utils.py +43 -0
  9. nvmath/_internal/workspace.py +490 -0
  10. nvmath/_utils.py +147 -0
  11. nvmath/bindings/__init__.py +60 -0
  12. nvmath/bindings/_internal/__init__.pxd +0 -0
  13. nvmath/bindings/_internal/__init__.py +0 -0
  14. nvmath/bindings/_internal/common_types.pxd +31 -0
  15. nvmath/bindings/_internal/cublas.cp314t-win_amd64.pyd +0 -0
  16. nvmath/bindings/_internal/cublas.pxd +530 -0
  17. nvmath/bindings/_internal/cublasLt.cp314t-win_amd64.pyd +0 -0
  18. nvmath/bindings/_internal/cublasLt.pxd +59 -0
  19. nvmath/bindings/_internal/cublasMp.pxd +52 -0
  20. nvmath/bindings/_internal/cudss.cp314t-win_amd64.pyd +0 -0
  21. nvmath/bindings/_internal/cudss.pxd +54 -0
  22. nvmath/bindings/_internal/cufft.cp314t-win_amd64.pyd +0 -0
  23. nvmath/bindings/_internal/cufft.pxd +70 -0
  24. nvmath/bindings/_internal/cufftMp.pxd +77 -0
  25. nvmath/bindings/_internal/curand.cp314t-win_amd64.pyd +0 -0
  26. nvmath/bindings/_internal/curand.pxd +42 -0
  27. nvmath/bindings/_internal/cusolver.cp314t-win_amd64.pyd +0 -0
  28. nvmath/bindings/_internal/cusolver.pxd +15 -0
  29. nvmath/bindings/_internal/cusolverDn.cp314t-win_amd64.pyd +0 -0
  30. nvmath/bindings/_internal/cusolverDn.pxd +406 -0
  31. nvmath/bindings/_internal/cusolverMp.pxd +71 -0
  32. nvmath/bindings/_internal/cusolverSp.cp314t-win_amd64.pyd +0 -0
  33. nvmath/bindings/_internal/cusolverSp.pxd +75 -0
  34. nvmath/bindings/_internal/cusparse.cp314t-win_amd64.pyd +0 -0
  35. nvmath/bindings/_internal/cusparse.pxd +471 -0
  36. nvmath/bindings/_internal/cusparseLt.cp314t-win_amd64.pyd +0 -0
  37. nvmath/bindings/_internal/cusparseLt.pxd +48 -0
  38. nvmath/bindings/_internal/cutensor.cp314t-win_amd64.pyd +0 -0
  39. nvmath/bindings/_internal/cutensor.pxd +58 -0
  40. nvmath/bindings/_internal/mathdx.cp314t-win_amd64.pyd +0 -0
  41. nvmath/bindings/_internal/mathdx.pxd +116 -0
  42. nvmath/bindings/_internal/nvshmem.pxd +29 -0
  43. nvmath/bindings/_internal/utils.cp314t-win_amd64.pyd +0 -0
  44. nvmath/bindings/_internal/utils.pxd +174 -0
  45. nvmath/bindings/_internal/utils.pyi +10 -0
  46. nvmath/bindings/cublas.cp314t-win_amd64.pyd +0 -0
  47. nvmath/bindings/cublas.pxd +558 -0
  48. nvmath/bindings/cublas.pyi +812 -0
  49. nvmath/bindings/cublasLt.cp314t-win_amd64.pyd +0 -0
  50. nvmath/bindings/cublasLt.pxd +109 -0
  51. nvmath/bindings/cublasLt.pyi +1461 -0
  52. nvmath/bindings/cublasMp.pxd +85 -0
  53. nvmath/bindings/cublasMp.pyi +267 -0
  54. nvmath/bindings/cudss.cp314t-win_amd64.pyd +0 -0
  55. nvmath/bindings/cudss.pxd +98 -0
  56. nvmath/bindings/cudss.pyi +443 -0
  57. nvmath/bindings/cufft.cp314t-win_amd64.pyd +0 -0
  58. nvmath/bindings/cufft.pxd +118 -0
  59. nvmath/bindings/cufft.pyi +301 -0
  60. nvmath/bindings/cufftMp.pxd +124 -0
  61. nvmath/bindings/cufftMp.pyi +326 -0
  62. nvmath/bindings/curand.cp314t-win_amd64.pyd +0 -0
  63. nvmath/bindings/curand.pxd +71 -0
  64. nvmath/bindings/curand.pyi +189 -0
  65. nvmath/bindings/cusolver.cp314t-win_amd64.pyd +0 -0
  66. nvmath/bindings/cusolver.pxd +62 -0
  67. nvmath/bindings/cusolver.pyi +320 -0
  68. nvmath/bindings/cusolverDn.cp314t-win_amd64.pyd +0 -0
  69. nvmath/bindings/cusolverDn.pxd +430 -0
  70. nvmath/bindings/cusolverDn.pyi +422 -0
  71. nvmath/bindings/cusolverMp.pxd +98 -0
  72. nvmath/bindings/cusolverMp.pyi +114 -0
  73. nvmath/bindings/cusolverSp.cp314t-win_amd64.pyd +0 -0
  74. nvmath/bindings/cusolverSp.pxd +95 -0
  75. nvmath/bindings/cusolverSp.pyi +70 -0
  76. nvmath/bindings/cusparse.cp314t-win_amd64.pyd +0 -0
  77. nvmath/bindings/cusparse.pxd +546 -0
  78. nvmath/bindings/cusparse.pyi +1017 -0
  79. nvmath/bindings/cusparseLt.cp314t-win_amd64.pyd +0 -0
  80. nvmath/bindings/cusparseLt.pxd +99 -0
  81. nvmath/bindings/cusparseLt.pyi +252 -0
  82. nvmath/bindings/cutensor.cp314t-win_amd64.pyd +0 -0
  83. nvmath/bindings/cutensor.pxd +98 -0
  84. nvmath/bindings/cutensor.pyi +324 -0
  85. nvmath/bindings/cycublas.cp314t-win_amd64.pyd +0 -0
  86. nvmath/bindings/cycublas.pxd +664 -0
  87. nvmath/bindings/cycublasLt.cp314t-win_amd64.pyd +0 -0
  88. nvmath/bindings/cycublasLt.pxd +1045 -0
  89. nvmath/bindings/cycublasMp.pxd +171 -0
  90. nvmath/bindings/cycudss.cp314t-win_amd64.pyd +0 -0
  91. nvmath/bindings/cycudss.pxd +277 -0
  92. nvmath/bindings/cycufft.cp314t-win_amd64.pyd +0 -0
  93. nvmath/bindings/cycufft.pxd +333 -0
  94. nvmath/bindings/cycufftMp.pxd +342 -0
  95. nvmath/bindings/cycurand.cp314t-win_amd64.pyd +0 -0
  96. nvmath/bindings/cycurand.pxd +141 -0
  97. nvmath/bindings/cycusolver.cp314t-win_amd64.pyd +0 -0
  98. nvmath/bindings/cycusolver.pxd +137 -0
  99. nvmath/bindings/cycusolverDn.cp314t-win_amd64.pyd +0 -0
  100. nvmath/bindings/cycusolverDn.pxd +443 -0
  101. nvmath/bindings/cycusolverMp.pxd +107 -0
  102. nvmath/bindings/cycusolverSp.cp314t-win_amd64.pyd +0 -0
  103. nvmath/bindings/cycusolverSp.pxd +93 -0
  104. nvmath/bindings/cycusparse.cp314t-win_amd64.pyd +0 -0
  105. nvmath/bindings/cycusparse.pxd +679 -0
  106. nvmath/bindings/cycusparseLt.cp314t-win_amd64.pyd +0 -0
  107. nvmath/bindings/cycusparseLt.pxd +135 -0
  108. nvmath/bindings/cycutensor.cp314t-win_amd64.pyd +0 -0
  109. nvmath/bindings/cycutensor.pxd +189 -0
  110. nvmath/bindings/cymathdx.cp314t-win_amd64.pyd +0 -0
  111. nvmath/bindings/cymathdx.pxd +552 -0
  112. nvmath/bindings/cynvshmem.pxd +118 -0
  113. nvmath/bindings/mathdx.cp314t-win_amd64.pyd +0 -0
  114. nvmath/bindings/mathdx.pxd +182 -0
  115. nvmath/bindings/mathdx.pyi +1562 -0
  116. nvmath/bindings/nvpl/__init__.pxd +0 -0
  117. nvmath/bindings/nvpl/__init__.py +13 -0
  118. nvmath/bindings/nvpl/_internal/__init__.pxd +0 -0
  119. nvmath/bindings/nvpl/_internal/__init__.py +0 -0
  120. nvmath/bindings/nvpl/_internal/blas.cp314t-win_amd64.pyd +0 -0
  121. nvmath/bindings/nvpl/_internal/blas.pxd +237 -0
  122. nvmath/bindings/nvpl/_internal/fft.cp314t-win_amd64.pyd +0 -0
  123. nvmath/bindings/nvpl/_internal/fft.pxd +36 -0
  124. nvmath/bindings/nvpl/blas.cp314t-win_amd64.pyd +0 -0
  125. nvmath/bindings/nvpl/blas.pxd +131 -0
  126. nvmath/bindings/nvpl/blas.pyi +168 -0
  127. nvmath/bindings/nvpl/cyblas.cp314t-win_amd64.pyd +0 -0
  128. nvmath/bindings/nvpl/cyblas.pxd +280 -0
  129. nvmath/bindings/nvpl/cyfft.cp314t-win_amd64.pyd +0 -0
  130. nvmath/bindings/nvpl/cyfft.pxd +93 -0
  131. nvmath/bindings/nvpl/fft.cp314t-win_amd64.pyd +0 -0
  132. nvmath/bindings/nvpl/fft.pxd +100 -0
  133. nvmath/bindings/nvpl/fft.pyi +168 -0
  134. nvmath/bindings/nvshmem.pxd +54 -0
  135. nvmath/bindings/nvshmem.pyi +191 -0
  136. nvmath/device/__init__.py +38 -0
  137. nvmath/device/_deprecated.py +33 -0
  138. nvmath/device/common.py +315 -0
  139. nvmath/device/common_backend.py +131 -0
  140. nvmath/device/common_cuda.py +201 -0
  141. nvmath/device/common_numba.py +300 -0
  142. nvmath/device/common_numba_cuda_mlir.py +202 -0
  143. nvmath/device/common_opaque_tensor.py +201 -0
  144. nvmath/device/cublasdx.py +1606 -0
  145. nvmath/device/cublasdx_backend.py +860 -0
  146. nvmath/device/cublasdx_numba.py +1534 -0
  147. nvmath/device/cublasdx_numba_cuda_mlir.py +208 -0
  148. nvmath/device/cufftdx.py +373 -0
  149. nvmath/device/cufftdx_backend.py +220 -0
  150. nvmath/device/cufftdx_numba.py +140 -0
  151. nvmath/device/cufftdx_numba_cuda_mlir.py +79 -0
  152. nvmath/device/curand_kernel.py +9147 -0
  153. nvmath/device/cusolverdx.py +2708 -0
  154. nvmath/device/cusolverdx_backend.py +440 -0
  155. nvmath/device/cusolverdx_numba.py +567 -0
  156. nvmath/device/cusolverdx_numba_cuda_mlir.py +604 -0
  157. nvmath/device/cusolverdx_overload_backend.py +1029 -0
  158. nvmath/device/llvm_array.py +29 -0
  159. nvmath/device/random.py +441 -0
  160. nvmath/device/random_helpers.py +23 -0
  161. nvmath/device/random_states.py +187 -0
  162. nvmath/device/types.py +138 -0
  163. nvmath/device/vector_types_numba.py +259 -0
  164. nvmath/distributed/__init__.py +200 -0
  165. nvmath/distributed/_internal/__init__.py +0 -0
  166. nvmath/distributed/_internal/nccl.py +86 -0
  167. nvmath/distributed/_internal/nvshmem.py +307 -0
  168. nvmath/distributed/_internal/symmetric_memory.py +35 -0
  169. nvmath/distributed/_internal/tensor_ifc.py +70 -0
  170. nvmath/distributed/_internal/tensor_ifc_cupy.py +68 -0
  171. nvmath/distributed/_internal/tensor_ifc_host_device.py +172 -0
  172. nvmath/distributed/_internal/tensor_ifc_numpy.py +46 -0
  173. nvmath/distributed/_internal/tensor_ifc_torch.py +162 -0
  174. nvmath/distributed/_internal/tensor_wrapper.py +81 -0
  175. nvmath/distributed/_utils.py +167 -0
  176. nvmath/distributed/distribution/__init__.py +30 -0
  177. nvmath/distributed/distribution/_configuration.py +39 -0
  178. nvmath/distributed/distribution/distributions.py +1024 -0
  179. nvmath/distributed/distribution/redistribute.py +1284 -0
  180. nvmath/distributed/fft/__init__.py +7 -0
  181. nvmath/distributed/fft/_configuration.py +82 -0
  182. nvmath/distributed/fft/fft.py +2742 -0
  183. nvmath/distributed/linalg/__init__.py +22 -0
  184. nvmath/distributed/linalg/_internal/__init__.py +3 -0
  185. nvmath/distributed/linalg/_internal/epilog_protocol.py +586 -0
  186. nvmath/distributed/linalg/_internal/matmul_desc_ifc.py +28 -0
  187. nvmath/distributed/linalg/advanced/__init__.py +8 -0
  188. nvmath/distributed/linalg/advanced/_configuration.py +171 -0
  189. nvmath/distributed/linalg/advanced/matmulmod.py +3573 -0
  190. nvmath/distributed/linalg/generic/__init__.py +8 -0
  191. nvmath/distributed/linalg/generic/_caching.py +66 -0
  192. nvmath/distributed/linalg/generic/_configuration.py +61 -0
  193. nvmath/distributed/linalg/generic/_factorization.py +172 -0
  194. nvmath/distributed/linalg/generic/_initialization.py +966 -0
  195. nvmath/distributed/linalg/generic/_problem_spec.py +511 -0
  196. nvmath/distributed/linalg/generic/solvermod.py +1368 -0
  197. nvmath/distributed/process_group.py +408 -0
  198. nvmath/fft/__init__.py +7 -0
  199. nvmath/fft/_configuration.py +189 -0
  200. nvmath/fft/_exec_utils.py +82 -0
  201. nvmath/fft/_helpers.py +237 -0
  202. nvmath/fft/fft.py +3122 -0
  203. nvmath/internal/__init__.pxd +3 -0
  204. nvmath/internal/__init__.py +10 -0
  205. nvmath/internal/_bindings.cp314t-win_amd64.pyd +0 -0
  206. nvmath/internal/_bindings.pxd +18 -0
  207. nvmath/internal/_device_utils.py +45 -0
  208. nvmath/internal/_layout/__init__.pxd +3 -0
  209. nvmath/internal/_layout/__init__.py +7 -0
  210. nvmath/internal/_layout/_layout.cp314t-win_amd64.pyd +0 -0
  211. nvmath/internal/_layout/_layout.pxd +1303 -0
  212. nvmath/internal/_layout/_layout.pyi +1145 -0
  213. nvmath/internal/enum_utils.py +142 -0
  214. nvmath/internal/formatters.py +87 -0
  215. nvmath/internal/mem_limit.py +51 -0
  216. nvmath/internal/memory.cp314t-win_amd64.pyd +0 -0
  217. nvmath/internal/memory.pxd +13 -0
  218. nvmath/internal/memory.pyi +50 -0
  219. nvmath/internal/ndbuffer/__init__.pxd +3 -0
  220. nvmath/internal/ndbuffer/__init__.py +9 -0
  221. nvmath/internal/ndbuffer/_copy_kernel.cp314t-win_amd64.pyd +0 -0
  222. nvmath/internal/ndbuffer/_copy_kernel.pxd +10 -0
  223. nvmath/internal/ndbuffer/_jit.cp314t-win_amd64.pyd +0 -0
  224. nvmath/internal/ndbuffer/_jit.pxd +7 -0
  225. nvmath/internal/ndbuffer/_ndbuffer.cp314t-win_amd64.pyd +0 -0
  226. nvmath/internal/ndbuffer/_ndbuffer.pxd +40 -0
  227. nvmath/internal/ndbuffer/_ndbuffer.pyi +463 -0
  228. nvmath/internal/ndbuffer/copy_kernel/args.h +34 -0
  229. nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/array_view.h +52 -0
  230. nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/elementwise.h +68 -0
  231. nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/grid_indexer.h +69 -0
  232. nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/transposed.h +242 -0
  233. nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/type_utils.h +39 -0
  234. nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/utils.h +132 -0
  235. nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/vec.h +159 -0
  236. nvmath/internal/ndbuffer/copy_kernel/elementwise.h +53 -0
  237. nvmath/internal/ndbuffer/copy_kernel/transposed.h +58 -0
  238. nvmath/internal/package_ifc.py +168 -0
  239. nvmath/internal/package_ifc_cuda.py +57 -0
  240. nvmath/internal/package_ifc_cupy.py +67 -0
  241. nvmath/internal/package_ifc_torch.py +69 -0
  242. nvmath/internal/package_wrapper.py +14 -0
  243. nvmath/internal/tensor_ifc.py +179 -0
  244. nvmath/internal/tensor_ifc_cupy.py +234 -0
  245. nvmath/internal/tensor_ifc_ndbuffer.py +147 -0
  246. nvmath/internal/tensor_ifc_numpy.py +184 -0
  247. nvmath/internal/tensor_ifc_torch.py +178 -0
  248. nvmath/internal/tensor_wrapper.py +160 -0
  249. nvmath/internal/typemaps.py +113 -0
  250. nvmath/internal/utils.py +805 -0
  251. nvmath/linalg/__init__.py +56 -0
  252. nvmath/linalg/_internal/__init__.py +3 -0
  253. nvmath/linalg/_internal/algo_cap_ifc.py +82 -0
  254. nvmath/linalg/_internal/algo_config_ifc.py +43 -0
  255. nvmath/linalg/_internal/batch.py +234 -0
  256. nvmath/linalg/_internal/enum_to_tuples.py +64 -0
  257. nvmath/linalg/_internal/epilog_protocol.py +766 -0
  258. nvmath/linalg/_internal/layout.py +624 -0
  259. nvmath/linalg/_internal/matmul_desc_ifc.py +28 -0
  260. nvmath/linalg/_internal/matmul_pref_ifc.py +27 -0
  261. nvmath/linalg/_internal/matrix_layout_ifc.py +26 -0
  262. nvmath/linalg/_internal/solver_utils.py +432 -0
  263. nvmath/linalg/_internal/typemaps.py +144 -0
  264. nvmath/linalg/_internal/utils.py +157 -0
  265. nvmath/linalg/advanced/__init__.py +8 -0
  266. nvmath/linalg/advanced/_algorithmmod.py +170 -0
  267. nvmath/linalg/advanced/_configuration.py +351 -0
  268. nvmath/linalg/advanced/helpers/__init__.py +5 -0
  269. nvmath/linalg/advanced/helpers/matmul.py +1316 -0
  270. nvmath/linalg/advanced/matmulmod.py +3734 -0
  271. nvmath/linalg/generic/__init__.py +53 -0
  272. nvmath/linalg/generic/_configuration/__init__.py +39 -0
  273. nvmath/linalg/generic/_configuration/layout.py +263 -0
  274. nvmath/linalg/generic/_configuration/match.py +734 -0
  275. nvmath/linalg/generic/_configuration/qualifiers.py +493 -0
  276. nvmath/linalg/generic/_configuration/solver_configuration.py +59 -0
  277. nvmath/linalg/generic/_configuration/wrap.py +217 -0
  278. nvmath/linalg/generic/_dtype.py +15 -0
  279. nvmath/linalg/generic/matmulmod.py +2094 -0
  280. nvmath/linalg/generic/solvermod.py +1301 -0
  281. nvmath/memory.py +279 -0
  282. nvmath/sparse/__init__.py +38 -0
  283. nvmath/sparse/_internal/__init__.py +21 -0
  284. nvmath/sparse/_internal/common_utils.py +147 -0
  285. nvmath/sparse/_internal/cudss_config_ifc.py +702 -0
  286. nvmath/sparse/_internal/cudss_data_ifc.py +399 -0
  287. nvmath/sparse/_internal/cudss_utils.py +506 -0
  288. nvmath/sparse/_internal/cusparse_utils.py +382 -0
  289. nvmath/sparse/_internal/sparse_bsc_ifc.py +303 -0
  290. nvmath/sparse/_internal/sparse_bsr_ifc.py +305 -0
  291. nvmath/sparse/_internal/sparse_coo_ifc.py +256 -0
  292. nvmath/sparse/_internal/sparse_csc_ifc.py +268 -0
  293. nvmath/sparse/_internal/sparse_csr_ifc.py +288 -0
  294. nvmath/sparse/_internal/sparse_dia_ifc.py +242 -0
  295. nvmath/sparse/_internal/sparse_format_helpers.py +601 -0
  296. nvmath/sparse/_internal/sparse_tensor_ifc.py +133 -0
  297. nvmath/sparse/_internal/sparse_ust_ifc.py +141 -0
  298. nvmath/sparse/_internal/utils.py +56 -0
  299. nvmath/sparse/advanced/__init__.py +7 -0
  300. nvmath/sparse/advanced/_configuration.py +227 -0
  301. nvmath/sparse/advanced/direct_solver.py +2069 -0
  302. nvmath/sparse/generic/__init__.py +7 -0
  303. nvmath/sparse/generic/_configuration.py +129 -0
  304. nvmath/sparse/generic/_helpers.py +137 -0
  305. nvmath/sparse/generic/_thunks.py +21 -0
  306. nvmath/sparse/generic/matmulmod.py +2353 -0
  307. nvmath/sparse/ust/__init__.py +7 -0
  308. nvmath/sparse/ust/_converters.py +422 -0
  309. nvmath/sparse/ust/_cpp.py +28 -0
  310. nvmath/sparse/ust/_drawer.py +565 -0
  311. nvmath/sparse/ust/_emitter.py +1033 -0
  312. nvmath/sparse/ust/_jit.py +188 -0
  313. nvmath/sparse/ust/_kernel.py +282 -0
  314. nvmath/sparse/ust/_utils.py +149 -0
  315. nvmath/sparse/ust/interfaces/__init__.py +0 -0
  316. nvmath/sparse/ust/interfaces/torch_interface.py +476 -0
  317. nvmath/sparse/ust/tensor.py +1016 -0
  318. nvmath/sparse/ust/tensor_format.py +957 -0
  319. nvmath/tensor/__init__.py +6 -0
  320. nvmath/tensor/_configuration.py +120 -0
  321. nvmath/tensor/_internal/__init__.py +3 -0
  322. nvmath/tensor/_internal/cutensor_config_ifc.py +279 -0
  323. nvmath/tensor/_internal/cutensor_utils.py +230 -0
  324. nvmath/tensor/_internal/data.py +43 -0
  325. nvmath/tensor/_internal/einsum_parser.py +444 -0
  326. nvmath/tensor/_internal/typemaps.py +96 -0
  327. nvmath/tensor/contract.py +1900 -0
  328. nvmath_python-1.0.0.dist-info/METADATA +134 -0
  329. nvmath_python-1.0.0.dist-info/RECORD +332 -0
  330. nvmath_python-1.0.0.dist-info/WHEEL +5 -0
  331. nvmath_python-1.0.0.dist-info/licenses/LICENSE +177 -0
  332. nvmath_python-1.0.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,43 @@
1
+ # Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ import logging
6
+ import typing
7
+ from collections.abc import Sequence
8
+
9
+ import numpy as np
10
+
11
+ LoggerLike: typing.TypeAlias = logging.Logger | logging.LoggerAdapter
12
+
13
+
14
+ def get_addresses_of_elements(
15
+ shape: Sequence[int],
16
+ stride: Sequence[int],
17
+ address: int,
18
+ itemsize: int,
19
+ ) -> np.ndarray:
20
+ """Return the byte address of each element in a strided memory block.
21
+
22
+ Elements are enumerated in C order (last dimension varies fastest). The traversal
23
+ order is canonical and independent of the tensor's memory layout, so multiple calls
24
+ with different tensors always produce consistently ordered pointer arrays suitable
25
+ for use as batched-GEMM pointer arrays.
26
+
27
+ Args:
28
+ shape: the number of elements along each dimension of the memory block
29
+ stride: the stride along each dimension, in number of elements
30
+ address: the base byte address of the memory block (address of the element at
31
+ the all-zero index)
32
+ itemsize: the number of bytes per element
33
+
34
+ Returns:
35
+ A 1-D numpy array of byte addresses (dtype np.intp), one per element, in C order.
36
+ """
37
+ assert len(stride) == len(shape), f"stride length {len(stride)} must match shape length {len(shape)}"
38
+ assert itemsize >= 0, f"itemsize must be non-negative, got {itemsize}"
39
+ assert address >= 0, f"address must be non-negative, got {address}"
40
+ if not shape:
41
+ return np.array([address], dtype=np.intp)
42
+ offset_vecs = [np.arange(shape[a], dtype=np.intp) * (itemsize * stride[a]) for a in range(len(shape))]
43
+ return address + sum(np.ix_(*offset_vecs)).ravel() # type: ignore[union-attr]
@@ -0,0 +1,490 @@
1
+ # Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ """Reusable workspace buffer manager for stateful nvmath APIs.
6
+
7
+ A *workspace* is a non-operand scratch buffer the underlying library (cuBLASLt,
8
+ cuFFT, cuSparse, ...) requires for its ``execute`` step. ``Workspace`` manages the
9
+ lifecycle — allocation, reuse across calls, stream-ordered release, and exception
10
+ cleanup — so each stateful API doesn't have to.
11
+
12
+ The required size is recorded during ``plan()`` (via :meth:`Workspace.set_size`); the
13
+ buffer is allocated lazily on first use and reused on subsequent calls.
14
+
15
+ The allocator is pluggable. Choose one based on what kind of memory you need:
16
+
17
+ - :class:`BaseCUDAMemoryManager` — sync device pool (e.g. cupy, torch caching)
18
+ - :class:`BaseCUDAMemoryManagerAsync` — stream-aware device pool (e.g. CUDA async)
19
+ - :class:`cuda.core.MemoryResource` — covers both device and host memory
20
+ (e.g. ``cuda.core.DeviceMemoryResource``, ``cuda.core.PinnedMemoryResource``,
21
+ or ``NumpyMemoryResource``)
22
+
23
+ :meth:`Workspace.allocate_perhaps` is a context manager: the ``with`` block defines
24
+ the lifetime of the consumer's use of the buffer. On normal exit the buffer is kept
25
+ for reuse on the next call. On exception, any buffer this call allocated fresh is
26
+ released. The consumer never has to write an exception handler for the workspace.
27
+
28
+ Typical use::
29
+
30
+ class Matmul:
31
+ def __init__(self, ..., allocator, logger):
32
+ self.workspace = Workspace(allocator, logger, label="matmul workspace")
33
+ self.last_compute_event = None
34
+
35
+ def plan(self, ...):
36
+ ...
37
+ self.workspace.set_size(required_bytes)
38
+
39
+ def execute(self, *, release_workspace=False, stream_holder, ...):
40
+ with self.workspace.allocate_perhaps(
41
+ stream_holder, get_last_event=lambda: self.last_compute_event
42
+ ) as ws:
43
+ cublaslt.matmul(..., ws.raw_ptr, ws.size, ...)
44
+ self.last_compute_event = stream_holder.obj.record()
45
+ if release_workspace:
46
+ ws.release(self.last_compute_event)
47
+ # If the event is not managing the lifetime of other objects,
48
+ # null it to prevent a duplicate wait in self.free().
49
+ self.last_compute_event = None
50
+
51
+ def free(self, ...):
52
+ self.workspace.release(self.last_compute_event)
53
+ self.last_compute_event = None
54
+ """
55
+
56
+ __all__ = ["Workspace", "NumpyMemoryResource", "get_pinned_memory_resource"]
57
+
58
+ import contextlib
59
+ from collections.abc import Callable
60
+ from contextlib import nullcontext
61
+
62
+ import numpy as np
63
+ from cuda.core import Buffer, Event, MemoryResource, Stream
64
+
65
+ from nvmath._internal.utils import LoggerLike
66
+ from nvmath.internal import formatters, utils
67
+ from nvmath.internal.memory import get_legacy_pinned_memory_resource, get_pinned_async_memory_resource
68
+ from nvmath.internal.package_ifc import StreamHolder
69
+ from nvmath.memory import (
70
+ BaseCUDAMemoryManager,
71
+ BaseCUDAMemoryManagerAsync,
72
+ MemoryPointer,
73
+ )
74
+
75
+ _PROTOCOL_TYPE_ERROR_MESSAGE = (
76
+ "The method 'memalloc' in the allocator object must conform to the interface in the 'BaseCUDAMemoryManager' protocol."
77
+ )
78
+
79
+
80
+ class Workspace:
81
+ """Manages a CUDA scratch buffer with reuse, stream-ordered release, and
82
+ exception cleanup.
83
+
84
+ Args:
85
+ allocator: A :class:`BaseCUDAMemoryManager`, :class:`BaseCUDAMemoryManagerAsync`,
86
+ or :class:`cuda.core.MemoryResource`. Use a ``MemoryResource``
87
+ (e.g. ``PinnedMemoryResource``) when you need a host buffer or any backing
88
+ the legacy protocols can't express.
89
+ logger: Logger for debug-level allocation/release messages.
90
+ device_id: The CUDA device the workspace lives on. Optional — if omitted,
91
+ it is read from ``allocator.device_id``. ``cuda.core.MemoryResource``
92
+ declares this attribute in its protocol; the legacy
93
+ ``BaseCUDAMemoryManager(Async)`` protocols don't, but every in-tree
94
+ implementation exposes it as a convention. Pass this explicitly when
95
+ the allocator can't supply it: a third-party legacy allocator that
96
+ doesn't expose ``device_id``, or a pinned-only resource (e.g.
97
+ ``LegacyPinnedMemoryResource``) whose ``device_id`` deliberately
98
+ raises because pinned memory isn't bound to a specific GPU.
99
+ label: Short noun for log messages (e.g. ``"matmul workspace"``).
100
+ on_allocated: Optional callback invoked with the raw pointer right after a
101
+ successful allocation, inside the device + stream context. Used by cuFFT
102
+ to register the buffer with its handle via ``cufft.set_work_area``.
103
+ """
104
+
105
+ __slots__ = (
106
+ # Public state (also annotated below for type checkers / docs).
107
+ "ptr",
108
+ "size",
109
+ "allocated_size",
110
+ "allocated_here",
111
+ "stream",
112
+ # Configuration cached from __init__.
113
+ "_allocator",
114
+ "_logger",
115
+ "_label",
116
+ "_on_allocated",
117
+ "_get_ptr",
118
+ "_close_on_release",
119
+ "_is_device_accessible",
120
+ "_device_id",
121
+ # Transient per-call state set by allocate_perhaps, read by __enter__ /
122
+ # __exit__, cleared back to None on __exit__.
123
+ "_stream_holder",
124
+ "_get_last_event",
125
+ )
126
+
127
+ ptr: MemoryPointer | Buffer | None
128
+ """The owning buffer wrapper, or ``None`` when no buffer is held. Use
129
+ :attr:`raw_ptr` to get the integer pointer for C bindings."""
130
+
131
+ size: int
132
+ """Bytes required by the current plan. Set via :meth:`set_size`."""
133
+
134
+ allocated_size: int
135
+ """Bytes currently held by ``ptr``. May exceed ``size`` after a smaller plan
136
+ (the held buffer is never shrunk)."""
137
+
138
+ allocated_here: bool
139
+ """``True`` iff the current ``allocate_perhaps`` ``with`` block allocated this
140
+ buffer (rather than inheriting one from a previous call). Managed structurally by
141
+ the context manager — consumers don't need to set or reset it themselves. Exposed
142
+ for introspection."""
143
+
144
+ stream: Stream | None
145
+ """The stream the buffer was allocated on. Used by :meth:`release` to order
146
+ freeing against the consumer's last compute event."""
147
+
148
+ def __init__(
149
+ self,
150
+ allocator: BaseCUDAMemoryManager | BaseCUDAMemoryManagerAsync | MemoryResource,
151
+ logger: LoggerLike,
152
+ *,
153
+ device_id: int | None = None,
154
+ label: str = "workspace",
155
+ on_allocated: Callable[[int], None] | None = None,
156
+ ) -> None:
157
+ self._allocator = allocator
158
+ self._logger = logger
159
+ self._label = label
160
+ self._on_allocated = on_allocated
161
+
162
+ # MemoryResource returns a Buffer (which needs explicit, stream-ordered
163
+ # close()); legacy allocators return a MemoryPointer (whose finalizer
164
+ # fires on reference-drop). Cache both decisions at construction for the
165
+ # release hot path.
166
+ if isinstance(allocator, MemoryResource):
167
+ self._get_ptr: Callable[..., int] = self._get_ptr_from_buffer
168
+ self._close_on_release = True
169
+ else:
170
+ self._get_ptr = utils.get_ptr_from_memory_pointer
171
+ self._close_on_release = False
172
+
173
+ # Mirrors the allocator's cuda.core.MemoryResource.is_device_accessible
174
+ # property; legacy BaseCUDAMemoryManager(Async) allocators have no such
175
+ # flag and are device-accessible by definition. Drives whether allocate /
176
+ # release enter the device + stream context.
177
+ self._is_device_accessible = not isinstance(allocator, MemoryResource) or allocator.is_device_accessible
178
+
179
+ # AttributeError covers third-party allocators that simply don't expose
180
+ # device_id; RuntimeError covers cuda.core's LegacyPinnedMemoryResource,
181
+ # whose property raises by design (pinned memory isn't bound to a GPU).
182
+ if device_id is None:
183
+ try:
184
+ device_id = allocator.device_id # type: ignore[union-attr]
185
+ except (AttributeError, RuntimeError):
186
+ device_id = None
187
+ if self._is_device_accessible and device_id is None:
188
+ raise TypeError(
189
+ f"Workspace could not infer device_id from {type(allocator).__name__}: "
190
+ "the allocator is device-accessible but has no usable 'device_id' attribute. "
191
+ "Pass device_id=<int> to Workspace explicitly."
192
+ )
193
+ self._device_id: int | None = device_id
194
+
195
+ self.ptr = None
196
+ self.size = 0
197
+ self.allocated_size = 0
198
+ self.allocated_here = False
199
+ self.stream = None
200
+ # Transient per-call state set by allocate_perhaps and read by
201
+ # __enter__ / __exit__. None outside an active `with` block.
202
+ self._stream_holder: StreamHolder | None = None
203
+ self._get_last_event: Callable[[], Event | None] | None = None
204
+
205
+ @staticmethod
206
+ def _get_ptr_from_buffer(buffer: Buffer) -> int:
207
+ """Pointer extractor for ``MemoryResource``-backed buffers. The ``int()``
208
+ cast unwraps cuda.core's ``CUdeviceptr`` (a typed alias of int)."""
209
+ return int(buffer.handle)
210
+
211
+ # ------------------------------------------------------------------ plan
212
+ def set_size(self, size: int) -> None:
213
+ """Record the size required by the current plan."""
214
+ if size < 0:
215
+ raise ValueError(f"Internal Error: Cannot allocate {self._label} of size {size} < 0.")
216
+ self.size = size
217
+
218
+ # ------------------------------------------------------------ allocation
219
+ def allocate_perhaps(
220
+ self,
221
+ stream_holder: StreamHolder | None,
222
+ *,
223
+ get_last_event: Callable[[], Event | None] | None,
224
+ ) -> "Workspace":
225
+ """Acquire the workspace buffer for the duration of a ``with`` block.
226
+
227
+ On entry, the buffer is either reused (if it's already at least ``size`` bytes) or
228
+ freshly allocated. If the held buffer is too small, it is released first — with a
229
+ stream wait against ``get_last_event()`` to order the free against any pending
230
+ compute on the prior buffer — and then a fresh buffer is allocated. Inside the
231
+ block, read ``ws.raw_ptr`` and ``ws.size`` to plumb into a C binding, and optionally
232
+ call ``ws.release(...)`` to free the buffer mid-scope.
233
+
234
+ On normal exit the buffer is kept for reuse on the next call. On exception, if this
235
+ call allocated a fresh buffer it is released — again with a stream wait against
236
+ ``get_last_event()``. The consumer doesn't write an exception handler.
237
+
238
+ Host-only mode (no CUDA driver required): when the allocator is a
239
+ :class:`cuda.core.MemoryResource` with ``is_device_accessible=False`` (e.g.
240
+ :class:`NumpyMemoryResource`), Workspace skips the device + stream context
241
+ entirely. ``stream_holder`` is ignored in this mode and may be ``None``;
242
+ the allocator is invoked with ``stream=None`` and :meth:`release` will pass
243
+ ``None`` to ``Buffer.close``. Pinned memory (``PinnedMemoryResource``) is
244
+ *device-accessible* — it needs the stream for ordered close and goes through
245
+ the device path even though its ``device_id`` is ``-1``.
246
+
247
+ Args:
248
+ stream_holder: The stream the workspace is allocated on. May be ``None``
249
+ only when the allocator is a host-only ``MemoryResource``.
250
+ get_last_event: A callable returning the consumer's current
251
+ ``last_compute_event``. Called at ``__enter__`` time on the grow path
252
+ (when a held buffer is too small) and at ``__exit__`` time on the
253
+ exception path. Late-binding via callable lets the consumer assign a
254
+ fresh event inside the block and have the next ``__enter__`` see it.
255
+ Required whenever the held buffer may have pending compute (both
256
+ device-only and host-accessible workspaces): the released buffer is
257
+ stream-waited against this event before its (stream-ordered) close.
258
+ May be ``None`` for workspaces guaranteed to be quiescent at the
259
+ relevant call sites.
260
+
261
+ Returns:
262
+ self: A reference to this Workspace. The Workspace is its own context
263
+ manager — the ``with`` statement calls ``__enter__`` to perform
264
+ allocation and ``__exit__`` to handle cleanup. Inside the block,
265
+ read ``self.raw_ptr`` / ``self.size``; optionally call
266
+ ``self.release(...)``.
267
+ """
268
+ # NOTE: get_last_event is required so that human developers must think about
269
+ # using this parameter.
270
+ # Stash transient per-call params for __enter__ / __exit__ to read.
271
+ # Workspace is its own context manager (rather than returning a separate
272
+ # scope object) to avoid the per-call Python object alloc on the hot path.
273
+ # Safe because allocate_perhaps is documented as not reentrant — the
274
+ # __enter__ check on allocated_here would catch any nested misuse.
275
+ self._stream_holder = stream_holder
276
+ self._get_last_event = get_last_event
277
+ return self
278
+
279
+ def __enter__(self) -> "Workspace":
280
+ if self.allocated_here:
281
+ raise RuntimeError(
282
+ "Workspace.allocate_perhaps is reusable but not reentrant: allocated_here was "
283
+ "True on entry, meaning a prior `with` block didn't exit through __exit__ "
284
+ "(consumer bypassed the `with` statement or caught and suppressed the exit-path)."
285
+ )
286
+
287
+ gle = self._get_last_event
288
+
289
+ # Grow-path cleanup: a held buffer that's too small must be released before the
290
+ # fresh allocation overwrites self.ptr — otherwise the old Buffer's __del__ runs
291
+ # unordered against any pending compute that was still reading it. release()
292
+ # handles the stream wait, the stream-ordered close(), and clears all
293
+ # post-release state, leaving the fresh-alloc dispatch with a clean slate.
294
+ if self.ptr is not None and self.allocated_size < self.size:
295
+ self.release(gle() if gle is not None else None)
296
+
297
+ # Allocation with rollback: drop any partial state if _allocate_perhaps raises.
298
+ try:
299
+ self._allocate_perhaps(self._stream_holder)
300
+ except BaseException:
301
+ self.ptr = None
302
+ self.allocated_size = 0
303
+ self.allocated_here = False
304
+ self.stream = None
305
+ raise
306
+
307
+ return self
308
+
309
+ def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None:
310
+ if exc_type is not None and self.allocated_here:
311
+ gle = self._get_last_event
312
+ self.release(gle() if gle is not None else None)
313
+ self.allocated_here = False
314
+ # Drop transient per-call refs — they don't outlive the with-block.
315
+ self._stream_holder = None
316
+ self._get_last_event = None
317
+
318
+ def _allocate_perhaps(self, stream_holder: StreamHolder | None) -> None:
319
+ """Internal — perform the allocation (or reuse short-circuit). Called from
320
+ the :meth:`allocate_perhaps` context manager's ``__enter__``.
321
+
322
+ Implementation notes:
323
+
324
+ - The legacy ``BaseCUDAMemoryManager(Async)`` 0-byte fast path produces a
325
+ ``MemoryPointer(0, 0, ...)`` sentinel rather than calling ``memalloc(0)``,
326
+ since concrete implementations don't uniformly support zero-byte
327
+ allocations. ``MemoryResource.allocate(0)`` is well-defined and used
328
+ directly.
329
+ - Dispatch order is ``MemoryResource``, then ``BaseCUDAMemoryManagerAsync``,
330
+ then ``BaseCUDAMemoryManager`` (most-specific first).
331
+ - The ``on_allocated`` hook fires from inside the device+stream context so
332
+ it sees the right active context — and only on real allocations, never on
333
+ reuse or the 0-byte fast path.
334
+ """
335
+ if self.ptr is not None and self.allocated_size >= self.size:
336
+ return
337
+
338
+ # Host-only allocators (NumpyMemoryResource and similar) skip the device
339
+ # + stream context to keep Workspace usable without a CUDA driver/device;
340
+ # stream_holder is ignored in that mode.
341
+ device_cm: contextlib.AbstractContextManager[object]
342
+ stream_cm: contextlib.AbstractContextManager[object]
343
+ stream_obj: Stream | None
344
+ if self._is_device_accessible:
345
+ if stream_holder is None:
346
+ raise TypeError(
347
+ "Workspace.allocate_perhaps requires stream_holder for device-accessible allocators (got None)."
348
+ )
349
+ # __init__ validates this invariant; assertion narrows int | None for mypy.
350
+ assert self._device_id is not None
351
+ device_cm = utils.device_ctx(self._device_id)
352
+ stream_cm = stream_holder.ctx
353
+ stream_obj = stream_holder.obj
354
+ else:
355
+ device_cm = nullcontext()
356
+ stream_cm = nullcontext()
357
+ stream_obj = None
358
+
359
+ self._logger.debug("Allocating %s...", self._label)
360
+ if self.size == 0 and not isinstance(self._allocator, MemoryResource):
361
+ self.ptr = MemoryPointer(0, 0, finalizer=None)
362
+ self.allocated_here = True
363
+ else:
364
+ with device_cm, stream_cm:
365
+ try:
366
+ if isinstance(self._allocator, MemoryResource):
367
+ self.ptr = self._allocator.allocate(self.size, stream=stream_obj)
368
+ elif isinstance(self._allocator, BaseCUDAMemoryManagerAsync):
369
+ self.ptr = self._allocator.memalloc_async(self.size, stream_obj)
370
+ else:
371
+ self.ptr = self._allocator.memalloc(self.size)
372
+ self.allocated_here = True
373
+ except TypeError as e:
374
+ raise TypeError(_PROTOCOL_TYPE_ERROR_MESSAGE) from e
375
+ if self._on_allocated is not None:
376
+ self._on_allocated(self._get_ptr(self.ptr))
377
+
378
+ self.allocated_size = self.size
379
+ self.stream = stream_obj
380
+ # %-formatting defers MemoryStr(...) __str__ until the logger emits.
381
+ self._logger.debug(
382
+ "Finished allocating %s of size %s in the context of stream %s.",
383
+ self._label,
384
+ formatters.MemoryStr(self.size),
385
+ self.stream,
386
+ )
387
+
388
+ # ------------------------------------------------------------- free path
389
+ def release(self, last_compute_event: Event | None) -> None:
390
+ """Wait for pending compute and free the buffer.
391
+
392
+ Call this explicitly inside an :meth:`allocate_perhaps` ``with`` block
393
+ (e.g. when ``release_workspace=True``) to free the buffer mid-scope. On the
394
+ exception path the context manager calls this internally if the buffer is
395
+ still held; consumers don't need to.
396
+
397
+ Safe to call when no buffer is held — it's a no-op, so a redundant second
398
+ release doesn't pay for a stream wait. After a successful release, null
399
+ your ``last_compute_event`` slot so a subsequent release doesn't re-wait
400
+ on a stale event.
401
+
402
+ .. important:: This is a buffer-release operation, not a standalone event-sync
403
+ primitive. If no buffer is held, the method returns immediately and
404
+ ``last_compute_event`` is not waited on. For a standalone event sync, call
405
+ ``stream.wait(event)`` directly.
406
+ """
407
+ # NOTE: last_compute_event is required so that human developers must think about
408
+ # using this parameter.
409
+ if self.ptr is None:
410
+ return
411
+ if self.stream is not None and last_compute_event is not None:
412
+ self.stream.wait(last_compute_event)
413
+ if self._close_on_release:
414
+ # Pass the stream so the dealloc is stream-ordered — pinned-buffer free
415
+ # is deferred until pending stream work completes, no host stall.
416
+ self.ptr.close(self.stream) # type: ignore[union-attr]
417
+ self.ptr = None
418
+ self.allocated_size = 0
419
+ self.allocated_here = False
420
+ self.stream = None
421
+ self._logger.debug("[release] %s released.", self._label)
422
+
423
+ # ------------------------------------------------------- pointer plumbing
424
+ @property
425
+ def raw_ptr(self) -> int:
426
+ """Raw integer pointer for plumbing into C bindings.
427
+
428
+ Raises :class:`AttributeError` if no buffer is held (called outside a ``with``
429
+ block, or after :meth:`release`). For the legacy 0-byte fast path returns
430
+ ``0`` — if a null device pointer is unsuitable downstream, also branch on
431
+ ``self.size == 0``.
432
+ """
433
+ return self._get_ptr(self.ptr)
434
+
435
+
436
+ class NumpyMemoryResource(MemoryResource):
437
+ """Host :class:`cuda.core.MemoryResource` backed by :func:`numpy.empty`.
438
+
439
+ Allocates plain (non-pinned) host memory via NumPy. Unlike
440
+ :class:`PinnedMemoryResource` / :class:`LegacyPinnedMemoryResource`, this
441
+ resource does not call into the CUDA driver, so it is safe to use in
442
+ environments where no CUDA driver/device is available (CPU-only test runs,
443
+ pure-host scratch space for parameter struct staging, etc.).
444
+
445
+ Pair with ``Workspace(NumpyMemoryResource(), logger)`` and pass
446
+ ``stream_holder=None`` to ``allocate_perhaps`` for a fully CUDA-free path
447
+ (``device_id`` is read from the resource and is ``-1``).
448
+
449
+ Not suitable as the host half of a :class:`MirroredWorkspace`: ``cudaMemcpyAsync``
450
+ requires page-locked memory for asynchronous correctness.
451
+ """
452
+
453
+ def __init__(self) -> None:
454
+ # Strong refs keyed by ptr — Buffer carries only the int pointer.
455
+ self._held: dict[int, np.ndarray] = {}
456
+
457
+ def allocate(self, size: int, *, stream: Stream | None = None) -> Buffer:
458
+ arr = np.empty(size, dtype=np.uint8)
459
+ ptr = int(arr.ctypes.data)
460
+ self._held[ptr] = arr
461
+ return Buffer.from_handle(ptr=ptr, size=size, mr=self)
462
+
463
+ def deallocate(self, ptr: int, size: int, stream: Stream | None = None) -> None:
464
+ # `stream` must NOT be keyword-only: cuda.core < 1 calls deallocate with all
465
+ # arguments positionally (Buffer.close -> mr.deallocate(ptr, size, stream)).
466
+ self._held.pop(int(ptr), None)
467
+
468
+ @property
469
+ def is_host_accessible(self) -> bool:
470
+ return True
471
+
472
+ @property
473
+ def is_device_accessible(self) -> bool:
474
+ return False
475
+
476
+ @property
477
+ def device_id(self) -> int:
478
+ return -1
479
+
480
+
481
+ def get_pinned_memory_resource(device_id: int) -> MemoryResource:
482
+ """
483
+ Use host pinned cuda asynchronous memory pool (via cuda.core.PinnedMemoryResource)
484
+ if available, otherwise use legacy pinned memory resource
485
+ (via cuda.core.LegacyPinnedMemoryResource).
486
+ """
487
+ mr = get_pinned_async_memory_resource(device_id)
488
+ if mr is None:
489
+ mr = get_legacy_pinned_memory_resource(device_id)
490
+ return mr
nvmath/_utils.py ADDED
@@ -0,0 +1,147 @@
1
+ # Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ import logging
6
+ import re
7
+ import sys
8
+ from enum import IntEnum
9
+ from functools import cache
10
+
11
+ from cuda import pathfinder
12
+
13
+ logger = logging.getLogger()
14
+
15
+ #
16
+ # Note: This module should not depend on anything from the nvmath namespace!
17
+ #
18
+
19
+
20
+ # The (subset of) compute types below are shared by cuStateVec and cuTensorNet
21
+ class ComputeType(IntEnum):
22
+ """An enumeration of CUDA compute types."""
23
+
24
+ COMPUTE_DEFAULT = 0
25
+ COMPUTE_16F = 1 << 0
26
+ COMPUTE_32F = 1 << 2
27
+ COMPUTE_64F = 1 << 4
28
+ COMPUTE_8U = 1 << 6
29
+ COMPUTE_8I = 1 << 8
30
+ COMPUTE_32U = 1 << 7
31
+ COMPUTE_32I = 1 << 9
32
+ COMPUTE_16BF = 1 << 10
33
+ COMPUTE_TF32 = 1 << 12
34
+
35
+
36
+ # TODO: use those exposed by CUDA Python instead, but before removing these
37
+ # duplicates, check if they are fixed to inherit IntEnum instead of Enum.
38
+ class CudaDataType(IntEnum):
39
+ """An enumeration of `cudaDataType_t`."""
40
+
41
+ CUDA_R_16F = 2
42
+ CUDA_C_16F = 6
43
+ CUDA_R_16BF = 14
44
+ CUDA_C_16BF = 15
45
+ CUDA_R_32F = 0
46
+ CUDA_C_32F = 4
47
+ CUDA_R_64F = 1
48
+ CUDA_C_64F = 5
49
+ CUDA_R_4I = 16
50
+ CUDA_C_4I = 17
51
+ CUDA_R_4U = 18
52
+ CUDA_C_4U = 19
53
+ CUDA_R_8I = 3
54
+ CUDA_C_8I = 7
55
+ CUDA_R_8U = 8
56
+ CUDA_C_8U = 9
57
+ CUDA_R_16I = 20
58
+ CUDA_C_16I = 21
59
+ CUDA_R_16U = 22
60
+ CUDA_C_16U = 23
61
+ CUDA_R_32I = 10
62
+ CUDA_C_32I = 11
63
+ CUDA_R_32U = 12
64
+ CUDA_C_32U = 13
65
+ CUDA_R_64I = 24
66
+ CUDA_C_64I = 25
67
+ CUDA_R_64U = 26
68
+ CUDA_C_64U = 27
69
+ CUDA_R_8F_E4M3 = 28
70
+ CUDA_R_8F_E5M2 = 29
71
+ CUDA_R_4F_E2M1 = 33 # FP4 (supported on SM >= 10.0)
72
+
73
+
74
+ class LibraryPropertyType(IntEnum):
75
+ """An enumeration of library version information."""
76
+
77
+ MAJOR_VERSION = 0
78
+ MINOR_VERSION = 1
79
+ PATCH_LEVEL = 2
80
+
81
+
82
+ del IntEnum
83
+
84
+
85
+ PLATFORM_LINUX = sys.platform.startswith("linux")
86
+ PLATFORM_WIN = sys.platform.startswith("win32")
87
+
88
+
89
+ def module_init_force_cupy_lib_load():
90
+ """
91
+ Attempt to preload libraries at module import time. We want to do it before
92
+ cupy, since it does not know how to properly search for libraries:
93
+ https://github.com/cupy/cupy/issues/9127
94
+ Fail silently if preload fails.
95
+ """
96
+ from nvmath.bindings import _internal
97
+
98
+ libs = ("cublas", "cufft", "curand", "cusolverDn", "cusparse", "cutensor")
99
+ for lib in libs:
100
+ try:
101
+ mod = getattr(_internal, lib)
102
+ mod._inspect_function_pointers()
103
+ except (_internal.utils.NotSupportedError, RuntimeError):
104
+ pass
105
+
106
+ try:
107
+ pathfinder.load_nvidia_dynamic_lib("nvrtc")
108
+ except pathfinder.DynamicLibNotFoundError:
109
+ pass
110
+
111
+
112
+ @cache
113
+ def get_nvrtc_build_id(minimal=True) -> int:
114
+ from cuda.core import ObjectCode, Program, ProgramOptions
115
+
116
+ code = r"""
117
+ extern "C" __global__ void get_build_id(int* build_id) {
118
+
119
+ *build_id = __CUDACC_VER_BUILD__;
120
+ }
121
+ """
122
+
123
+ prog = Program(code, "c++", ProgramOptions(std="c++17", minimal=minimal, arch="compute_75"))
124
+ obj = prog.compile("ptx")
125
+ assert isinstance(obj, ObjectCode)
126
+
127
+ pattern = re.compile(r"mov\.u32\s+%\w+,\s+(\d+)")
128
+ m = pattern.search(obj.code.decode())
129
+ assert m is not None
130
+
131
+ return int(m.group(1))
132
+
133
+
134
+ @cache
135
+ def get_nvrtc_version() -> tuple[int, int, int]:
136
+ """
137
+ Returns the NVRTC version as a tuple of (major, minor, build).
138
+ """
139
+ from cuda.bindings import nvrtc
140
+
141
+ err, major, minor = nvrtc.nvrtcVersion()
142
+ if err != nvrtc.nvrtcResult.NVRTC_SUCCESS:
143
+ raise RuntimeError(f"nvrtcVersion error: {err}")
144
+ # minimal support was added in CUDA 12.4
145
+ # https://docs.nvidia.com/cuda/archive/12.4.0/cuda-toolkit-release-notes/index.html#cuda-compilers
146
+ build = get_nvrtc_build_id(minimal=major >= 12 and minor >= 4)
147
+ return major, minor, build