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,966 @@
1
+ # Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ """
6
+ ``DirectSolver`` construction workflow: free functions that build a solver
7
+ instance in place, plus the derived problem spec they produce.
8
+ :func:`initialize_direct_solver` orchestrates the full workflow and is called
9
+ from ``DirectSolver.__init__``; see the "cuSOLVERMp workflow -> code map"
10
+ comment in ``solvermod.py`` for how each step maps onto this module.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import logging
16
+ from collections.abc import Sequence
17
+ from dataclasses import dataclass
18
+ from typing import Any, Literal
19
+
20
+ import numpy as np
21
+
22
+ import nvmath.distributed
23
+ from nvmath import memory
24
+ from nvmath._internal.workspace import NumpyMemoryResource, Workspace
25
+ from nvmath.bindings import cusolverMp # type: ignore[attr-defined]
26
+ from nvmath.distributed._internal import tensor_wrapper
27
+ from nvmath.distributed.distribution import BlockCyclic, BlockNonCyclic, Distribution, ProcessGrid
28
+ from nvmath.internal import typemaps, utils
29
+ from nvmath.internal.tensor_ifc import TensorHolder
30
+
31
+ from ...process_group import ReductionOp
32
+ from ._caching import get_handle
33
+ from ._factorization import _FactorizationState
34
+ from ._problem_spec import _direct_solver_problem_spec_reducer, _DirectSolverProblemSpec, _grid_coords
35
+
36
+ # Minimum supported cuSOLVERMp version (800 == 0.8.0).
37
+ _MIN_CUSOLVERMP_VERSION = 800
38
+
39
+
40
+ def _validate_cusolvermp_version(*, handle, logger: logging.Logger) -> int:
41
+ """
42
+ Validate the local cuSOLVERMp library version against the minimum.
43
+ This is a purely local check.
44
+ """
45
+ assert handle is not None, "_validate_cusolvermp_version requires a cuSOLVERMp handle"
46
+ version = int(cusolverMp.get_version(handle))
47
+
48
+ if version < _MIN_CUSOLVERMP_VERSION:
49
+ raise RuntimeError(f"cuSOLVERMp version {version} is below the required minimum {_MIN_CUSOLVERMP_VERSION}.")
50
+
51
+ logger.info("cuSOLVERMp version %d validated.", version)
52
+ return version
53
+
54
+
55
+ # (dtype(A), dtype(b)) -> cuSOLVERMp ``computeType`` for getrf / getrs.
56
+ #
57
+ # Sourced from the "supported combinations" tables published under
58
+ # https://docs.nvidia.com/cuda/cusolvermp/usage/functions.html#cusolvermpgetrf
59
+ # https://docs.nvidia.com/cuda/cusolvermp/usage/functions.html#cusolvermpgetrs
60
+ # Each documented row is same-type all the way across ("Data Type of A" ==
61
+ # computeType == "Output Data Type"), and "Data Type of B" is not separately
62
+ # enumerated; the only documented-safe path is dtype(B) == dtype(A) ==
63
+ # computeType. The descriptor API itself is looser -- cusolverMpCreateMatrixDesc
64
+ # takes one cudaDataType per descriptor, so descA / descB *could* in principle
65
+ # disagree -- but the supported-combinations tables don't list any such row.
66
+
67
+ _GETRF_GETRS_COMPUTE_TYPE_TABLE: dict[tuple[str, str], str] = {
68
+ ("float32", "float32"): "float32",
69
+ ("float64", "float64"): "float64",
70
+ ("complex64", "complex64"): "complex64",
71
+ ("complex128", "complex128"): "complex128",
72
+ }
73
+
74
+
75
+ def _resolve_compute_dtype(dtype_a: str, dtype_b: str) -> str:
76
+ """
77
+ Map the user's ``(dtype(A), dtype(b))`` pair to the single ``computeType``
78
+ cusolverMpGetrf / cusolverMpGetrs will accept.
79
+ """
80
+ try:
81
+ return _GETRF_GETRS_COMPUTE_TYPE_TABLE[(dtype_a, dtype_b)]
82
+ except KeyError:
83
+ supported = ", ".join(f"({a}, {b})" for (a, b) in _GETRF_GETRS_COMPUTE_TYPE_TABLE)
84
+ raise TypeError(
85
+ f"DirectSolver: cuSOLVERMp's getrf/getrs supported-combinations "
86
+ f"tables do not list a (dtype(A), dtype(b)) = ({dtype_a}, {dtype_b}) "
87
+ f"row; documented combinations are {supported}."
88
+ ) from None
89
+
90
+
91
+ def _local_shape_fits(
92
+ operand: TensorHolder,
93
+ local_rows_expected: int,
94
+ local_cols_expected: int,
95
+ ) -> bool:
96
+ """Return whether the user's local buffer has at least the numroc-prescribed extents.
97
+
98
+ Pure per-rank predicate (no raise): the prescribed extents are
99
+ merged-spec-derived (symmetric) but the operand shape is this rank's local
100
+ shape, so the verdict is per-rank. :meth:`_ResolvedProblemSpec.from_merged_spec`
101
+ records it on the spec and :func:`_check_local_shapes_collective` reduces it
102
+ across ranks so a too-small buffer fails construction on every rank.
103
+ """
104
+ rows = operand.shape[0]
105
+ cols = operand.shape[1] if len(operand.shape) >= 2 else 1
106
+ return rows >= local_rows_expected and cols >= local_cols_expected
107
+
108
+
109
+ def _operand_lld(o: TensorHolder, fallback_local_rows: int) -> int:
110
+ """
111
+ Compute the cuSOLVERMp descriptor ``lld`` (in elements) for ``o``.
112
+ """
113
+ if any(d == 0 for d in o.shape):
114
+ return max(fallback_local_rows, 1)
115
+ if len(o.shape) == 1:
116
+ return int(o.shape[0])
117
+ # for cols <= 1 the column stride is degenerate
118
+ # (often 1 element on cupy/numpy because the size-1 axis allows any
119
+ # stride). The cuSOLVERMp descriptor still requires lld >= local rows;
120
+ # since there's no padding possible with a single column, lld == rows.
121
+ if o.shape[1] <= 1:
122
+ return int(o.shape[0])
123
+ return int(o.strides[1])
124
+
125
+
126
+ def _is_col_major(o: TensorHolder) -> bool:
127
+ """Return True if ``o``'s local layout is column-major (Fortran).
128
+
129
+ Generalised F-order check with padded leading-dim allowance, of any
130
+ ndim. For 2-D this matches cuSOLVERMp's matrix-descriptor constraint:
131
+ unit stride down a column (``strides[0] == 1``) and a
132
+ column-to-column stride at least ``shape[0]`` (the descriptor's
133
+ ``lld``, which lets the user pass a slice of a larger allocation,
134
+ e.g. ``A_buf[:, :n]`` from an ``(m, N)`` buffer with ``N > n``).
135
+
136
+ Empty operands (any zero-sized dim) have degenerate strides on
137
+ NumPy / CuPy -- we short-circuit to True so ranks that legitimately
138
+ own no data (e.g. ``b``'s column slab on ``mypcol > 0``) don't fail
139
+ the check.
140
+ """
141
+ # TODO: maybe there is a better way to do this, revisit later.
142
+
143
+ # NOTE: ``o.strides`` is in elements (not bytes); see TensorHolder.strides.
144
+ if any(d == 0 for d in o.shape):
145
+ return True
146
+ # Innermost meaningful axis must have stride 1; each subsequent
147
+ # meaningful axis must have stride >= shape * stride of the previous
148
+ # one (padded LLD allowance). Axes with extent <= 1 are skipped.
149
+ expected = 1
150
+ first = True
151
+ for k in range(len(o.shape)):
152
+ if o.shape[k] <= 1:
153
+ continue
154
+ if first:
155
+ if o.strides[k] != 1:
156
+ return False
157
+ first = False
158
+ elif o.strides[k] < expected:
159
+ return False
160
+ expected = o.shape[k] * o.strides[k]
161
+ return True
162
+
163
+
164
+ @dataclass(frozen=True, slots=True)
165
+ class _OperandResetInvariants:
166
+ """
167
+ Per-rank operand properties captured at __init__ time that must
168
+ remain invariant across any subsequent :meth:`DirectSolver.reset_operands`
169
+ call.
170
+ """
171
+
172
+ label: str
173
+ dtype_name: str
174
+ local_shape: tuple[int, ...]
175
+ local_strides: tuple[int, ...]
176
+ package: str
177
+ device_id: int | Literal["cpu"]
178
+
179
+ def check(self, wrapped: TensorHolder) -> None:
180
+ if wrapped.name != self.package:
181
+ raise ValueError(f"The package for {self.label} ({wrapped.name}) must match the original one ({self.package}).")
182
+ if wrapped.device_id != self.device_id:
183
+ raise ValueError(
184
+ f"The device id for {self.label} ({wrapped.device_id}) must match the original one "
185
+ f"({self.device_id}), which is also where the distributed runtime is bound on this rank."
186
+ )
187
+ if wrapped.dtype != self.dtype_name:
188
+ raise ValueError(f"The dtype for {self.label} ({wrapped.dtype}) must match the original one ({self.dtype_name}).")
189
+
190
+ utils.check_attribute_match(self.local_shape, tuple(wrapped.shape), f"local shape of operand {self.label}")
191
+ utils.check_attribute_match(self.local_strides, tuple(wrapped.strides), f"local strides of operand {self.label}")
192
+
193
+
194
+ @dataclass(frozen=True, slots=True)
195
+ class _ResolvedProblemSpec:
196
+ """
197
+ Immutable, cross-rank-consistent description of the problem, derived once
198
+ from the reduced :class:`_DirectSolverProblemSpec`.
199
+
200
+ Every field is a pure function of the reduced spec, so it is identical on
201
+ every rank by construction. Holds only the *derived* facts -- the runtime /
202
+ distributed context and all acquired resources stay flat on the solver.
203
+
204
+ ``local_ld_{a,b}`` is intentionally absent: it depends on the *active*
205
+ operand buffer (mirror vs user alias), which isn't known until after operand
206
+ staging, so it's resolved later and kept flat with the other resources.
207
+ """
208
+
209
+ # package / memory spaces
210
+ package: str
211
+ stream_package: str
212
+ operands_memory_space: Literal["cuda", "cpu"]
213
+ # cuSOLVERMp executes on GPU; this field is a constant for now, kept for
214
+ # symmetry with ``operands_memory_space`` and to make the cross-space
215
+ # mirror logic in :meth:`from_merged_spec` self-documenting.
216
+ execution_space: Literal["cuda"]
217
+ # mirror policy
218
+ need_lhs_mirror: bool
219
+ need_rhs_mirror: bool
220
+ # dtypes
221
+ cuda_dtype_a: Any
222
+ cuda_dtype_b: Any
223
+ solver_cuda_dtype: Any
224
+ solver_dtype_name: str
225
+ # global problem dims
226
+ global_m: int
227
+ global_n: int
228
+ nrhs: int
229
+ # cuSOLVERMp (ia, ja) / (ib, jb) Fortran 1-based offsets of the operated-on
230
+ # submatrix within the global A / b.
231
+ ia: int
232
+ ja: int
233
+ ib: int
234
+ jb: int
235
+ # distributions / process grids
236
+ distribution_a: Distribution
237
+ distribution_b: Distribution
238
+ process_grid_a: ProcessGrid
239
+ process_grid_b: ProcessGrid
240
+ nprow_a: int
241
+ npcol_a: int
242
+ nprow_b: int
243
+ npcol_b: int
244
+ myprow_a: int
245
+ mypcol_a: int
246
+ myprow_b: int
247
+ mypcol_b: int
248
+ # blocking factors
249
+ mb_a: int
250
+ nb_a: int
251
+ mb_b: int
252
+ nb_b: int
253
+ # per-rank local row counts (numroc); reused for the lld fallback
254
+ local_rows_a: int
255
+ local_rows_b: int
256
+ # operand-reset invariants snapshotted from the user's pre-mirror operands
257
+ lhs_reset_invariants: _OperandResetInvariants
258
+ rhs_reset_invariants: _OperandResetInvariants
259
+
260
+ @classmethod
261
+ def from_merged_spec(cls, solver, merged_spec: _DirectSolverProblemSpec) -> _ResolvedProblemSpec:
262
+ """
263
+ Derive the resolved spec from the post-reducer ``merged_spec``.
264
+
265
+ Reads runtime facts (``_runtime.rank``, ``_runtime.device_id``) and the
266
+ wrapped *user* operands (``_lhs_user`` / ``_rhs_user``, which hold the
267
+ user's tensors at this point in the workflow) off ``solver``, but does
268
+ not mutate it -- the caller assigns the returned spec to
269
+ ``solver._spec``.
270
+
271
+ After the collective validation of the problem spec, all fields of the
272
+ merged spec are guaranteed identical on all ranks, so every derivation
273
+ below that is a pure function of ``merged_spec`` raises symmetrically
274
+ across ranks (no collective-safety wrapping needed, unlike in
275
+ :func:`_collective_problem_spec`).
276
+ """
277
+ package = merged_spec.packages[0]
278
+ stream_package = "cuda" if package == "numpy" else package
279
+
280
+ # Memory space of the user operands; execution space is always cuda.
281
+ operands_memory_space = merged_spec.memory_spaces[0]
282
+ execution_space: Literal["cuda"] = "cuda"
283
+
284
+ # Per-operand "needs a compute mirror" decision. A mirror is required
285
+ # when the operand's memory space differs from the execution space (data
286
+ # has to be staged across address spaces anyway) or when the user opted
287
+ # out of in-place mutation (cuSOLVERMp's getrf / getrs are destructive
288
+ # in-place on A / b, so a separate buffer is needed to absorb the write).
289
+ cross_space = operands_memory_space != execution_space
290
+ need_lhs_mirror = cross_space or not solver._options.inplace_a
291
+ need_rhs_mirror = cross_space or not solver._options.inplace_b
292
+
293
+ # Per-operand matrix dtypes (used by cusolverMpCreateMatrixDesc, whose
294
+ # cudaDataType parameter describes that matrix's element type).
295
+ cuda_dtype_a = typemaps.NAME_TO_DATA_TYPE[merged_spec.operands_dtypes[0]]
296
+ cuda_dtype_b = typemaps.NAME_TO_DATA_TYPE[merged_spec.operands_dtypes[1]]
297
+
298
+ # Resolve the cuSOLVERMp ``computeType`` (passed to getrf / getrs) from
299
+ # the (dtype(A), dtype(b)) pair via the documented supported-combinations
300
+ # table.
301
+ solver_dtype_name = _resolve_compute_dtype(merged_spec.operands_dtypes[0], merged_spec.operands_dtypes[1])
302
+ solver_cuda_dtype = typemaps.NAME_TO_DATA_TYPE[solver_dtype_name]
303
+
304
+ # The merged spec contains the global shape.
305
+ global_a = tuple(merged_spec.shapes[0])
306
+ global_b = tuple(merged_spec.shapes[1])
307
+ if len(global_a) != 2 or global_a[0] != global_a[1]:
308
+ raise ValueError(f"DirectSolver requires A to be a square matrix; got global shape {global_a}.")
309
+ global_m = int(global_a[0])
310
+ global_n = int(global_a[1])
311
+
312
+ if len(global_b) != 2 or global_b[0] != global_m or global_b[1] < 1:
313
+ raise ValueError(f"Global shape of b must be ({global_m}, NRHS) with NRHS >= 1; got {global_b}.")
314
+ nrhs = int(global_b[1])
315
+
316
+ distribution_a, distribution_b = merged_spec.distributions
317
+ process_grid_a = distribution_a.process_grid
318
+ process_grid_b = distribution_b.process_grid
319
+ nprow_a, npcol_a = process_grid_a.shape
320
+ nprow_b, npcol_b = process_grid_b.shape
321
+ myprow_a, mypcol_a = _grid_coords(process_grid_a, solver._runtime.rank)
322
+ myprow_b, mypcol_b = _grid_coords(process_grid_b, solver._runtime.rank)
323
+
324
+ # Finalize the copied distribution objects with the global shape the
325
+ # reducer just recovered from local-shape sums: if unbound, bind it;
326
+ # otherwise, if their pre-bound global shape disagrees with what the data
327
+ # actually implies, fail.
328
+ for d, expected, name in (
329
+ (distribution_a, (global_m, global_n), "A"),
330
+ (distribution_b, (global_m, nrhs), "b"),
331
+ ):
332
+ if d._bound:
333
+ if tuple(d._data_global_shape) != expected:
334
+ raise ValueError(
335
+ f"distribution for {name} was already bound to global_shape="
336
+ f"{d._data_global_shape}, but the global shape recovered from "
337
+ f"local operand shapes is {expected}. Pass an unbound distribution, "
338
+ f"or bind it to the correct global shape."
339
+ )
340
+ else:
341
+ d._bind(expected)
342
+
343
+ mb_a, nb_a, mb_b, nb_b = _resolve_blocking_factors(distribution_a, distribution_b, global_m, solver._logger)
344
+
345
+ # Per-rank local extents for the diagnostic log below; the row counts
346
+ # are also stored on the spec (reused for the lld fallback). The
347
+ # buffer-fit check is not done here, it is enforced
348
+ # in _check_local_shapes_collective.
349
+ #
350
+ # ``distribution.shape(rank)`` must stay after _resolve_blocking_factors,
351
+ # which may rewrite the distributions' block sizes in place.
352
+ assert solver._lhs_user is not None and solver._rhs_user is not None
353
+ local_rows_a, local_cols_a = distribution_a.shape(solver._runtime.rank)
354
+ local_rows_b, local_cols_b = distribution_b.shape(solver._runtime.rank)
355
+
356
+ solver._logger.info(
357
+ "DirectSolver: rank=%d; A on (%dx%d) grid coord (%d,%d); "
358
+ "b on (%dx%d) grid coord (%d,%d); m=%d n=%d; "
359
+ "local A=(%d,%d); local b=(%d,%d)",
360
+ solver._runtime.rank,
361
+ nprow_a,
362
+ npcol_a,
363
+ myprow_a,
364
+ mypcol_a,
365
+ nprow_b,
366
+ npcol_b,
367
+ myprow_b,
368
+ mypcol_b,
369
+ global_m,
370
+ global_n,
371
+ local_rows_a,
372
+ local_cols_a,
373
+ local_rows_b,
374
+ local_cols_b,
375
+ )
376
+
377
+ # Snapshot user-side operand traits for reset_operands from the user
378
+ # wrappers in ``_lhs_user`` / ``_rhs_user``. Taken here (before
379
+ # _resolve_active_operand_buffers derives the compute buffers) so it
380
+ # captures the user wrappers' traits -- which is what reset_operands will
381
+ # receive again from the user later -- regardless of which operands end
382
+ # up mirrored.
383
+ #
384
+ # User-side device id: "cpu" under cpu operands (NumPy / torch CPU tensors
385
+ # carry .device_id == "cpu"); the runtime GPU id under cuda operands.
386
+ # Matches what TensorHolder.device_id returns for any operand the user
387
+ # later passes to reset_operands.
388
+ user_device_id: int | Literal["cpu"] = "cpu" if operands_memory_space == "cpu" else solver._runtime.device_id
389
+ lhs_reset_invariants = _OperandResetInvariants(
390
+ label="A",
391
+ dtype_name=solver._lhs_user.dtype,
392
+ local_shape=tuple(solver._lhs_user.shape),
393
+ local_strides=tuple(solver._lhs_user.strides),
394
+ package=package,
395
+ device_id=user_device_id,
396
+ )
397
+ rhs_reset_invariants = _OperandResetInvariants(
398
+ label="b",
399
+ dtype_name=solver._rhs_user.dtype,
400
+ local_shape=tuple(solver._rhs_user.shape),
401
+ local_strides=tuple(solver._rhs_user.strides),
402
+ package=package,
403
+ device_id=user_device_id,
404
+ )
405
+
406
+ return cls(
407
+ package=package,
408
+ stream_package=stream_package,
409
+ operands_memory_space=operands_memory_space,
410
+ execution_space=execution_space,
411
+ need_lhs_mirror=need_lhs_mirror,
412
+ need_rhs_mirror=need_rhs_mirror,
413
+ cuda_dtype_a=cuda_dtype_a,
414
+ cuda_dtype_b=cuda_dtype_b,
415
+ solver_cuda_dtype=solver_cuda_dtype,
416
+ solver_dtype_name=solver_dtype_name,
417
+ global_m=global_m,
418
+ global_n=global_n,
419
+ nrhs=nrhs,
420
+ # Pin the submatrix offsets to 1 (Fortran 1-based) because we always operate
421
+ # on the full user-supplied A / b for now.
422
+ ia=1,
423
+ ja=1,
424
+ ib=1,
425
+ jb=1,
426
+ distribution_a=distribution_a,
427
+ distribution_b=distribution_b,
428
+ process_grid_a=process_grid_a,
429
+ process_grid_b=process_grid_b,
430
+ nprow_a=nprow_a,
431
+ npcol_a=npcol_a,
432
+ nprow_b=nprow_b,
433
+ npcol_b=npcol_b,
434
+ myprow_a=myprow_a,
435
+ mypcol_a=mypcol_a,
436
+ myprow_b=myprow_b,
437
+ mypcol_b=mypcol_b,
438
+ mb_a=mb_a,
439
+ nb_a=nb_a,
440
+ mb_b=mb_b,
441
+ nb_b=nb_b,
442
+ local_rows_a=local_rows_a,
443
+ local_rows_b=local_rows_b,
444
+ lhs_reset_invariants=lhs_reset_invariants,
445
+ rhs_reset_invariants=rhs_reset_invariants,
446
+ )
447
+
448
+
449
+ def _resolve_blocking_factors(
450
+ distribution_a: BlockCyclic,
451
+ distribution_b: BlockCyclic,
452
+ global_m: int,
453
+ logger: logging.Logger,
454
+ ) -> tuple[int, int, int, int]:
455
+ """Resolve ``(mb, nb)`` for A and b from their bound distributions.
456
+
457
+ ``nb_b`` is intentionally not constrained against A's ``nb``: it controls how
458
+ b's NRHS columns are dealt across b's process columns (via
459
+ ``numroc(nrhs, nb_b, ...)`` downstream) and is independent of A's column
460
+ blocking.
461
+
462
+ Mirrors :meth:`Matmul._infer_blocking_sizes` in matmulmod.py. The in-place
463
+ row-block rewrite is safe because the distributions are copies
464
+ (:func:`_direct_solver_problem_spec_reducer` converts user-supplied
465
+ distributions with ``copy=True``).
466
+ """
467
+ mb_a, nb_a = distribution_a.block_sizes
468
+ mb_b, nb_b = distribution_b.block_sizes
469
+
470
+ both_safe_to_realign = all(
471
+ isinstance(d, BlockNonCyclic) and d._is_1d_distribution() for d in (distribution_a, distribution_b)
472
+ )
473
+ if both_safe_to_realign and mb_a != mb_b:
474
+ # both_safe_to_realign guarantees both are 1-D BlockNonCyclic, so each
475
+ # mb was inferred by _infer_block_sizes as a uniform partition of
476
+ # global_m and is one of {global_m, global_m // nranks}. They differ
477
+ # only when one operand leaves its row dim un-partitioned (mb ==
478
+ # global_m): there a single block is interchangeable with nranks
479
+ # contiguous blocks of global_m // nranks, so shrinking that mb relabels
480
+ # block boundaries without moving any data.
481
+ # Picking min() lands on the partitioned operand's mb (global_m //
482
+ # nranks). It is still a divisor of global_m -- so the result remains a
483
+ # valid uniform block and needs no divisibility re-check -- and it now
484
+ # matches on both sides, satisfying cusolverMpTrsm's A.MB == X.MB rule.
485
+ aligned = min(mb_a, mb_b)
486
+ logger.info(
487
+ "Aligning BlockNonCyclic row block sizes for cuSOLVERMp getrs: "
488
+ "A.mb=%d, b.mb=%d -> %d (un-partitioned row dim admits any "
489
+ "block size that divides global_m=%d).",
490
+ mb_a,
491
+ mb_b,
492
+ aligned,
493
+ global_m,
494
+ )
495
+ distribution_a._block_sizes = (aligned, nb_a)
496
+ distribution_b._block_sizes = (aligned, nb_b)
497
+ mb_a = mb_b = aligned
498
+
499
+ # mb_b must match mb_a, or cusolverMpTrsm (inside getrs) fails with:
500
+ # [cusolverMpTrsm_bufferSize] A.MB != X.MB: <mb_a> != <mb_b>
501
+ if mb_b != mb_a:
502
+ raise ValueError(
503
+ f"b's row block size must match A's row block size (A's mb={mb_a}, "
504
+ f"got b's mb={mb_b}); enforced by cusolverMpTrsm inside getrs."
505
+ )
506
+ return mb_a, nb_a, mb_b, nb_b
507
+
508
+
509
+ @dataclass(frozen=True, slots=True)
510
+ class _RuntimeContext:
511
+ """The distributed-runtime facts the rest of construction depends on.
512
+
513
+ Unlike :class:`_ResolvedProblemSpec`, these are *per-rank* (``rank`` and
514
+ ``device_id`` differ across processes), so they are grouped on the solver
515
+ as ``solver._runtime`` rather than folded into the cross-rank-consistent
516
+ spec. Resolved before the construction try/rollback scope because the
517
+ rollback path reads ``device_id``.
518
+
519
+ ``distributed_ctx`` is the single source of truth; ``rank``, ``nranks``,
520
+ and ``device_id`` are convenience accessors that delegate to it (kept as
521
+ short names because construction reads them on many lines).
522
+ """
523
+
524
+ distributed_ctx: nvmath.distributed.DistributedContext
525
+
526
+ @property
527
+ def rank(self) -> int:
528
+ return self.distributed_ctx.process_group.rank
529
+
530
+ @property
531
+ def nranks(self) -> int:
532
+ return self.distributed_ctx.process_group.nranks
533
+
534
+ @property
535
+ def device_id(self) -> int:
536
+ return self.distributed_ctx.device_id
537
+
538
+
539
+ def _resolve_runtime_context() -> _RuntimeContext:
540
+ """Verify the NCCL communicator and snapshot the distributed context.
541
+
542
+ Cheap and side-effect-free (no resource acquisition), so ``__init__`` runs
543
+ it before entering the build/rollback scope and assigns the result to
544
+ ``solver._runtime``.
545
+ """
546
+ distributed_ctx = nvmath.distributed.get_context()
547
+ if distributed_ctx is None:
548
+ raise RuntimeError("nvmath.distributed runtime is not initialized; call nvmath.distributed.initialize(...) first.")
549
+ if distributed_ctx.nccl_comm is None:
550
+ raise RuntimeError(
551
+ "DirectSolver requires the NCCL backend to be active on the "
552
+ "distributed runtime; re-initialize nvmath.distributed with NCCL."
553
+ )
554
+
555
+ return _RuntimeContext(distributed_ctx=distributed_ctx)
556
+
557
+
558
+ # ============================================================================
559
+ # Construction workflow -- module-level free functions operating on a solver.
560
+ # ``solver`` is intentionally untyped so these helpers don't depend on the
561
+ # DirectSolver type. Derived facts are read via ``solver._spec``, runtime facts
562
+ # via ``solver._runtime``; resources and operand state stay flat on ``solver``.
563
+ # ============================================================================
564
+
565
+
566
+ def _collective_problem_spec(
567
+ solver,
568
+ a: Any,
569
+ b: Any,
570
+ distributions: Sequence[Distribution],
571
+ ) -> _DirectSolverProblemSpec:
572
+ """
573
+ Build the per-rank spec from user inputs and reduce it across ranks.
574
+ """
575
+
576
+ # This try block is needed to keep the allreduce_object collective
577
+ # below deadlock-free: the failable user-input steps must turn any raise into an
578
+ # Exception rather than an uncaught exception.
579
+ # The reducer accepts Exceptions and propagates them
580
+ # through the reduction. Without the catch, failing ranks would
581
+ # exit before the collective and the surviving ranks would hang
582
+ # in the allreduce. _DirectSolverProblemSpec construction
583
+ # lives in the `else` branch because it's a plain @dataclass holder
584
+ # that only stores fields semantic validation is deferred to
585
+ # the reducer's _leaf_* helpers, which run inside the collective.
586
+ local_spec: _DirectSolverProblemSpec | Exception
587
+ try:
588
+ operands = (
589
+ tensor_wrapper.wrap_operand(a),
590
+ tensor_wrapper.wrap_operand(b),
591
+ )
592
+ distributions = list(distributions)
593
+ except Exception as e:
594
+ local_spec = e
595
+ else:
596
+ # Hold the wrapped user operands (which may be CPU tensors) on the
597
+ # user-side slots. ``_resolve_active_operand_buffers`` later derives the
598
+ # actual compute buffers ``_lhs_compute`` / ``_rhs_compute`` from these.
599
+ # Stored only on the success path; the exception path short-circuits
600
+ # before any code reads these.
601
+ solver._lhs_user, solver._rhs_user = operands
602
+
603
+ local_spec = _DirectSolverProblemSpec(
604
+ shapes=[list(o.shape) for o in operands],
605
+ is_F=[_is_col_major(o) for o in operands],
606
+ operands_dtypes=[o.dtype for o in operands],
607
+ packages=[o.name for o in operands],
608
+ memory_spaces=[o.device for o in operands],
609
+ distributions=distributions,
610
+ options=_DirectSolverProblemSpec.Options(solver._options),
611
+ device_ids=[o.device_id for o in operands],
612
+ runtime_device_id=solver._runtime.device_id,
613
+ nranks=solver._runtime.nranks,
614
+ rank=solver._runtime.rank,
615
+ )
616
+
617
+ if solver._runtime.nranks > 1:
618
+ process_group = solver._runtime.distributed_ctx.process_group
619
+ merged_or_exception = process_group.allreduce_object(local_spec, op=_direct_solver_problem_spec_reducer)
620
+ else:
621
+ # nranks==1: still feed the spec through the reducer once so its
622
+ # leaf-only checks (per-rank preconditions) run -- and so a local
623
+ # Exception is unwrapped uniformly with the multi-rank path.
624
+ merged_or_exception = _direct_solver_problem_spec_reducer(local_spec, local_spec)
625
+
626
+ if isinstance(merged_or_exception, Exception):
627
+ raise merged_or_exception
628
+ return merged_or_exception
629
+
630
+
631
+ def _check_local_shapes_collective(solver) -> None:
632
+ """
633
+ Check the per-rank local buffer-fit verdicts and enforce them collectively.
634
+
635
+ Whether this rank's local A / b buffers meet the numroc-prescribed extents
636
+ is a per-rank verdict (the prescribed extents are merged-spec-derived and
637
+ symmetric, but the actual operand shape is local), so raising on it directly
638
+ would be asymmetric. We compute it here from the bound distributions and the
639
+ user wrappers, then OR the "too small" flags across ranks so every rank
640
+ reaches the same verdict and raises together.
641
+
642
+ Must run before :func:`_resolve_active_operand_buffers`, which may clear the
643
+ ``_lhs_user`` / ``_rhs_user`` wrappers this reads.
644
+
645
+ The reduced message names the offending operand(s) but not the specific
646
+ rank(s); each rank still logs its own local detail below for debugging.
647
+ """
648
+ spec = solver._spec
649
+ rank = solver._runtime.rank
650
+ assert solver._lhs_user is not None and solver._rhs_user is not None
651
+ local_rows_a, local_cols_a = spec.distribution_a.shape(rank)
652
+ local_rows_b, local_cols_b = spec.distribution_b.shape(rank)
653
+ lhs_fits = _local_shape_fits(solver._lhs_user, local_rows_a, local_cols_a)
654
+ rhs_fits = _local_shape_fits(solver._rhs_user, local_rows_b, local_cols_b)
655
+
656
+ # 1 == "this rank's buffer is too small"; reduced with MAX so the flag is
657
+ # set on every rank if *any* rank's buffer is too small.
658
+ flags = np.array(
659
+ [0 if lhs_fits else 1, 0 if rhs_fits else 1],
660
+ dtype=np.int32,
661
+ )
662
+
663
+ if not lhs_fits or not rhs_fits:
664
+ solver._logger.error(
665
+ "DirectSolver: rank=%d local operand buffer too small for the "
666
+ "BlockCyclic distribution (A fits=%s, b fits=%s); cuSOLVERMp would "
667
+ "read past the buffer.",
668
+ rank,
669
+ lhs_fits,
670
+ rhs_fits,
671
+ )
672
+
673
+ if solver._runtime.nranks > 1:
674
+ process_group = solver._runtime.distributed_ctx.process_group
675
+ process_group.allreduce_buffer(flags, op=ReductionOp.MAX)
676
+
677
+ lhs_too_small = bool(flags[0])
678
+ rhs_too_small = bool(flags[1])
679
+ if lhs_too_small or rhs_too_small:
680
+ bad = [name for name, too_small in (("A", lhs_too_small), ("b", rhs_too_small)) if too_small]
681
+ raise ValueError(
682
+ f"DirectSolver: local operand buffer(s) {bad} are smaller than the "
683
+ f"BlockCyclic distribution prescribes on at least one rank. "
684
+ f"Pass local operands sized consistently with the distribution."
685
+ )
686
+
687
+
688
+ def _resolve_active_operand_buffers(solver, stream_holder) -> None:
689
+ """
690
+ Resolve what ``solver._lhs_compute`` / ``solver._rhs_compute`` refer to for
691
+ the actual computation, per-operand.
692
+
693
+ For each of A / b:
694
+
695
+ * If no mirror is needed, the user's tensor *is* the compute buffer:
696
+ move it into ``_lhs_compute`` / ``_rhs_compute`` and clear the user slot.
697
+ * Otherwise allocate a compute-space mirror via
698
+ :meth:`DirectSolver._create_compute_mirror`, keeping the user's wrapper in
699
+ ``_lhs_user`` / ``_rhs_user`` for the copy-back path and for
700
+ ``release_operands``.
701
+
702
+ After this call ``_lhs_compute`` / ``_rhs_compute`` are populated for the
703
+ first time (they don't exist before it).
704
+ """
705
+ # ``_create_compute_mirror`` returns the abstract ``TensorHolder``;
706
+ # at runtime it's the same concrete subclass as ``src`` (via
707
+ # ``src.__class__.empty`` / ``src.to``). (No type: ignore needed here:
708
+ # ``solver`` is untyped, so the assignment isn't narrowed by mypy.)
709
+ assert solver._lhs_user is not None and solver._rhs_user is not None
710
+ if solver._spec.need_lhs_mirror:
711
+ solver._lhs_compute = solver._create_compute_mirror(solver._lhs_user, stream_holder)
712
+ else:
713
+ solver._lhs_compute = solver._lhs_user
714
+ solver._lhs_user = None
715
+ if solver._spec.need_rhs_mirror:
716
+ solver._rhs_compute = solver._create_compute_mirror(solver._rhs_user, stream_holder)
717
+ else:
718
+ solver._rhs_compute = solver._rhs_user
719
+ solver._rhs_user = None
720
+ if solver._spec.need_lhs_mirror or solver._spec.need_rhs_mirror:
721
+ solver._logger.info(
722
+ "DirectSolver: rank=%d; copied operands to a separate buffer on device %d "
723
+ "(A copied=%s, b copied=%s, operand_memspace=%s, execution_space=%s).",
724
+ solver._runtime.rank,
725
+ solver._runtime.device_id,
726
+ solver._spec.need_lhs_mirror,
727
+ solver._spec.need_rhs_mirror,
728
+ solver._spec.operands_memory_space,
729
+ solver._spec.execution_space,
730
+ )
731
+
732
+
733
+ def _resolve_active_local_ld(solver) -> None:
734
+ """
735
+ Set :attr:`_local_ld_a` / :attr:`_local_ld_b` (flat resources) from the
736
+ active :attr:`_lhs_compute` / :attr:`_rhs_compute` strides. Must run after
737
+ :func:`_resolve_active_operand_buffers` (which decides what the active buffer
738
+ is) and before :func:`_create_descriptors` (which bakes the LLD into the
739
+ cuSOLVERMp descriptor).
740
+
741
+ Kept flat (not in ``_spec``) because the LLD reflects the *active* buffer
742
+ -- the mirror under CPU operands, the user wrapper under GPU operands --
743
+ which isn't known until the mirror swap above has run.
744
+ """
745
+ solver._local_ld_a = _operand_lld(solver._lhs_compute, solver._spec.local_rows_a)
746
+ solver._local_ld_b = _operand_lld(solver._rhs_compute, solver._spec.local_rows_b)
747
+ solver._logger.info(
748
+ "DirectSolver: rank=%d; lld(A)=%d lld(b)=%d (operands_memory_space=%s)",
749
+ solver._runtime.rank,
750
+ solver._local_ld_a,
751
+ solver._local_ld_b,
752
+ solver._spec.operands_memory_space,
753
+ )
754
+
755
+
756
+ def _create_device_grids(solver) -> None:
757
+ # Called only from the build workflow, which has already bound the
758
+ # resource-allocation stream to ``solver._cusolvermp_handle``.
759
+ # No ``set_stream`` is needed here.
760
+
761
+ assert solver._cusolvermp_handle is not None, "_create_grids requires the cuSOLVERMp handle"
762
+
763
+ # NCCL comm presence is guaranteed by _resolve_runtime_context
764
+ nccl_comm = solver._runtime.distributed_ctx.nccl_comm
765
+ assert nccl_comm is not None # for mypy; enforced upstream
766
+
767
+ # one device grid per descriptor
768
+ def _grid_mapping(grid: ProcessGrid) -> cusolverMp.GridMapping:
769
+ return (
770
+ cusolverMp.GridMapping.COL_MAJOR
771
+ if grid.layout == ProcessGrid.Layout.COL_MAJOR
772
+ else cusolverMp.GridMapping.ROW_MAJOR
773
+ )
774
+
775
+ solver._cusolvermp_grid_a = cusolverMp.create_device_grid(
776
+ solver._cusolvermp_handle,
777
+ nccl_comm.ptr,
778
+ solver._spec.nprow_a,
779
+ solver._spec.npcol_a,
780
+ _grid_mapping(solver._spec.process_grid_a),
781
+ )
782
+ solver._cusolvermp_grid_b = cusolverMp.create_device_grid(
783
+ solver._cusolvermp_handle,
784
+ nccl_comm.ptr,
785
+ solver._spec.nprow_b,
786
+ solver._spec.npcol_b,
787
+ _grid_mapping(solver._spec.process_grid_b),
788
+ )
789
+ solver._logger.debug(
790
+ "cuSOLVERMp grids created: gridA=%d gridB=%d",
791
+ solver._cusolvermp_grid_a,
792
+ solver._cusolvermp_grid_b,
793
+ )
794
+
795
+
796
+ def _create_descriptors(solver) -> None:
797
+ # Called only from the build workflow, after ``_create_device_grids``. The
798
+ # handle's stream was already bound at ``cusolverMp.create`` time, so
799
+ # no ``set_stream`` is needed.
800
+
801
+ # Each descriptor's cudaDataType is *that matrix's* element type:
802
+ # descA → dtype(A), descB → dtype(b). cuSOLVERMp's getrf/getrs
803
+ # supported-combinations tables (see _GETRF_GETRS_COMPUTE_TYPE_TABLE)
804
+ # force dtype(A) == dtype(b).
805
+ # rsrc and csrc are zero: cuSOLVERMp only supports a (0, 0) first_process,
806
+ # which _check_block_cyclic_requirements already enforced (with a message in
807
+ # nvmath's own vocabulary) before we get here.
808
+ # https://docs.nvidia.com/cuda/cusolvermp/usage/functions.html#cusolvermpcreatematrixdesc
809
+ rsrc = 0
810
+ csrc = 0
811
+
812
+ solver._desc_a = cusolverMp.create_matrix_desc(
813
+ solver._cusolvermp_grid_a,
814
+ solver._spec.cuda_dtype_a,
815
+ solver._spec.global_m,
816
+ solver._spec.global_n,
817
+ solver._spec.mb_a,
818
+ solver._spec.nb_a,
819
+ rsrc,
820
+ csrc,
821
+ solver._local_ld_a,
822
+ )
823
+ solver._desc_b = cusolverMp.create_matrix_desc(
824
+ solver._cusolvermp_grid_b,
825
+ solver._spec.cuda_dtype_b,
826
+ solver._spec.global_m,
827
+ solver._spec.nrhs,
828
+ solver._spec.mb_b,
829
+ solver._spec.nb_b,
830
+ rsrc,
831
+ csrc,
832
+ solver._local_ld_b,
833
+ )
834
+ solver._logger.debug(
835
+ "Matrix descriptors created: descA=%d descB=%d",
836
+ solver._desc_a,
837
+ solver._desc_b,
838
+ )
839
+
840
+
841
+ def _create_factorization_state_and_workspaces(solver, stream_holder) -> None:
842
+ assert solver._cusolvermp_grid_a is not None and solver._cusolvermp_grid_b is not None, (
843
+ "grids must be created before allocating workspaces"
844
+ )
845
+
846
+ # ipiv holds one int64 pivot per local column cuSOLVERMp actually
847
+ # factorises. That count is the distribution's prescribed local column
848
+ # extent (``distribution.shape(rank)`` is numroc under the hood), *not* the
849
+ # user's ``lhs.shape[1]``. The user's shape may carry trailing column
850
+ # padding (see _local_shape_fits) that cuSOLVERMp never reads or writes;
851
+ # sizing ipiv to it would waste memory without producing any extra pivots.
852
+ _, local_cols_a = solver._spec.distribution_a.shape(solver._runtime.rank)
853
+ solver._factorization_state = _FactorizationState(
854
+ local_cols_a=local_cols_a,
855
+ device_id=solver._runtime.device_id,
856
+ stream_holder=stream_holder,
857
+ )
858
+
859
+ # Draw device scratch from the operand package's pool.
860
+ # No user allocator option: this will likely move to NCCL
861
+ # symmetric memory, for which we will own the allocation.
862
+ device_allocator = memory._MEMORY_MANAGER[solver._spec.stream_package](solver._runtime.device_id, solver._logger)
863
+ solver._device_workspace = Workspace(
864
+ device_allocator,
865
+ solver._logger,
866
+ label="cuSOLVERMp device workspace",
867
+ )
868
+
869
+ # Host workspace allocator: pageable host memory for now.
870
+ # We deliberately do not use pinned host memory here:
871
+ #
872
+ # - There's no benefit for now: ``factorize`` and ``solve`` are always
873
+ # host-blocking on the user's compute stream, so there's no async H2D/D2H
874
+ # overlap for pinned memory to accelerate, and the workspace lifetime is
875
+ # bounded by a host-side sync before the next call. ``factorize`` is
876
+ # host-blocking both because we impose it (the unconditional
877
+ # ``sync_and_check_factorize_info`` on the user stream after ``getrf``) and
878
+ # because cuSOLVERMp's ``getrf`` itself currently does an internal stream
879
+ # sync before returning; ``solve`` blocks via its blocking return-path
880
+ # dispatch.
881
+ #
882
+ # - It sidesteps a cuSOLVERMp 0.8.0 bug that produces wrong solver output
883
+ # whenever the host workspace uses pinned memory allocated via
884
+ # ``cudaMallocHost`` / ``cudaHostRegister`` (i.e.
885
+ # ``cudaPointerAttributes::devicePointer != NULL``). Pageable host memory
886
+ # avoids it entirely.
887
+ host_resource = NumpyMemoryResource()
888
+
889
+ solver._host_workspace = Workspace(
890
+ host_resource,
891
+ solver._logger,
892
+ device_id=solver._runtime.device_id,
893
+ label="cuSOLVERMp host workspace",
894
+ )
895
+
896
+
897
+ def initialize_direct_solver(solver, a, b, *, distributions, stream) -> None:
898
+ """
899
+ Run the cuSOLVERMp construction workflow.
900
+ The caller (:meth:`DirectSolver.__init__`) owns the partial-init rollback,
901
+ so this function does not catch construction failures.
902
+
903
+ IMPORTANT: the order below matters; don't reorder without re-reading
904
+ the workflow and understanding the dependencies.
905
+ """
906
+ # Set context once so that every helper below uses it.
907
+ with utils.device_ctx(solver._runtime.device_id):
908
+ # collective validation and normalization of problem spec
909
+ merged_spec = _collective_problem_spec(solver, a, b, distributions)
910
+
911
+ # Derive the immutable, cross-rank-consistent spec and store it once.
912
+ # from_merged_spec also snapshots the operand-reset invariants from the
913
+ # user wrappers (_lhs_user / _rhs_user), which is why it must run BEFORE
914
+ # _resolve_active_operand_buffers, which may clear those user slots (the
915
+ # no-mirror path moves the user tensor into _lhs_compute / _rhs_compute
916
+ # and nulls _lhs_user / _rhs_user).
917
+ solver._spec = _ResolvedProblemSpec.from_merged_spec(solver, merged_spec)
918
+
919
+ # Enforce the per-rank buffer-fit verdicts recorded on the spec. This is
920
+ # a collective: it reduces the verdicts so a too-small local buffer on
921
+ # any rank fails construction on every rank (symmetric raise), avoiding a
922
+ # later deadlock in getrf/getrs. Must run while all ranks are still in
923
+ # lockstep, i.e. before any per-rank-failable resource allocation below.
924
+ _check_local_shapes_collective(solver)
925
+
926
+ # everything below depends on the resolved problem spec
927
+
928
+ # resolve the stream
929
+ stream_holder = utils.get_or_create_stream(solver._runtime.device_id, stream, solver._spec.stream_package)
930
+ solver._logger.info("Resource allocation stream: %s", stream_holder.obj)
931
+
932
+ # resolve the handle
933
+ if solver._options.handle is not None:
934
+ solver._cusolvermp_handle = solver._options.handle
935
+ else:
936
+ solver._cusolvermp_handle = get_handle(solver._runtime.device_id, stream_holder.ptr)
937
+
938
+ # Local cuSOLVERMp version check: each rank
939
+ # validates its own handle's version against the minimum.
940
+ solver._cusolvermp_version = _validate_cusolvermp_version(
941
+ handle=solver._cusolvermp_handle,
942
+ logger=solver._logger,
943
+ )
944
+
945
+ # Per-operand, bind the active compute buffer from the user wrapper:
946
+ # - If :attr:`need_lhs_mirror` / :attr:`need_rhs_mirror` is True,
947
+ # allocate a compute mirror and stage the copy.
948
+ # - Otherwise the user's tensor itself becomes the compute buffer.
949
+ # Must run before any cuSOLVERMp resource creation that depends on the
950
+ # active buffer layout.
951
+ _resolve_active_operand_buffers(solver, stream_holder)
952
+
953
+ # Finalize solver._local_ld_{a,b} from the strides of the
954
+ # currently-active solver._lhs_compute / solver._rhs_compute (either an
955
+ # alias of the user's tensor or the mirror just allocated above). Must
956
+ # run before _create_descriptors so the descriptor's LLD reflects the
957
+ # buffer cuSOLVERMp will read from.
958
+ _resolve_active_local_ld(solver)
959
+
960
+ _create_device_grids(solver)
961
+ _create_descriptors(solver)
962
+
963
+ # allocate the persistent _FactorizationState
964
+ # (ipiv + info flags) and instantiate the device/host Workspace
965
+ # objects *without* allocating the workspace memory.
966
+ _create_factorization_state_and_workspaces(solver, stream_holder)