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.
- nvmath/__init__.pxd +0 -0
- nvmath/__init__.py +45 -0
- nvmath/_internal/__init__.py +0 -0
- nvmath/_internal/attribute_ifc_factory.py +330 -0
- nvmath/_internal/layout.py +70 -0
- nvmath/_internal/templates.py +130 -0
- nvmath/_internal/threadsafe.py +106 -0
- nvmath/_internal/utils.py +43 -0
- nvmath/_internal/workspace.py +490 -0
- nvmath/_utils.py +147 -0
- nvmath/bindings/__init__.py +60 -0
- nvmath/bindings/_internal/__init__.pxd +0 -0
- nvmath/bindings/_internal/__init__.py +0 -0
- nvmath/bindings/_internal/common_types.pxd +31 -0
- nvmath/bindings/_internal/cublas.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cublas.pxd +530 -0
- nvmath/bindings/_internal/cublasLt.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cublasLt.pxd +59 -0
- nvmath/bindings/_internal/cublasMp.pxd +52 -0
- nvmath/bindings/_internal/cudss.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cudss.pxd +54 -0
- nvmath/bindings/_internal/cufft.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cufft.pxd +70 -0
- nvmath/bindings/_internal/cufftMp.pxd +77 -0
- nvmath/bindings/_internal/curand.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/curand.pxd +42 -0
- nvmath/bindings/_internal/cusolver.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusolver.pxd +15 -0
- nvmath/bindings/_internal/cusolverDn.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusolverDn.pxd +406 -0
- nvmath/bindings/_internal/cusolverMp.pxd +71 -0
- nvmath/bindings/_internal/cusolverSp.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusolverSp.pxd +75 -0
- nvmath/bindings/_internal/cusparse.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusparse.pxd +471 -0
- nvmath/bindings/_internal/cusparseLt.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusparseLt.pxd +48 -0
- nvmath/bindings/_internal/cutensor.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cutensor.pxd +58 -0
- nvmath/bindings/_internal/mathdx.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/mathdx.pxd +116 -0
- nvmath/bindings/_internal/nvshmem.pxd +29 -0
- nvmath/bindings/_internal/utils.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/utils.pxd +174 -0
- nvmath/bindings/_internal/utils.pyi +10 -0
- nvmath/bindings/cublas.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cublas.pxd +558 -0
- nvmath/bindings/cublas.pyi +812 -0
- nvmath/bindings/cublasLt.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cublasLt.pxd +109 -0
- nvmath/bindings/cublasLt.pyi +1461 -0
- nvmath/bindings/cublasMp.pxd +85 -0
- nvmath/bindings/cublasMp.pyi +267 -0
- nvmath/bindings/cudss.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cudss.pxd +98 -0
- nvmath/bindings/cudss.pyi +443 -0
- nvmath/bindings/cufft.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cufft.pxd +118 -0
- nvmath/bindings/cufft.pyi +301 -0
- nvmath/bindings/cufftMp.pxd +124 -0
- nvmath/bindings/cufftMp.pyi +326 -0
- nvmath/bindings/curand.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/curand.pxd +71 -0
- nvmath/bindings/curand.pyi +189 -0
- nvmath/bindings/cusolver.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cusolver.pxd +62 -0
- nvmath/bindings/cusolver.pyi +320 -0
- nvmath/bindings/cusolverDn.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cusolverDn.pxd +430 -0
- nvmath/bindings/cusolverDn.pyi +422 -0
- nvmath/bindings/cusolverMp.pxd +98 -0
- nvmath/bindings/cusolverMp.pyi +114 -0
- nvmath/bindings/cusolverSp.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cusolverSp.pxd +95 -0
- nvmath/bindings/cusolverSp.pyi +70 -0
- nvmath/bindings/cusparse.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cusparse.pxd +546 -0
- nvmath/bindings/cusparse.pyi +1017 -0
- nvmath/bindings/cusparseLt.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cusparseLt.pxd +99 -0
- nvmath/bindings/cusparseLt.pyi +252 -0
- nvmath/bindings/cutensor.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cutensor.pxd +98 -0
- nvmath/bindings/cutensor.pyi +324 -0
- nvmath/bindings/cycublas.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cycublas.pxd +664 -0
- nvmath/bindings/cycublasLt.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cycublasLt.pxd +1045 -0
- nvmath/bindings/cycublasMp.pxd +171 -0
- nvmath/bindings/cycudss.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cycudss.pxd +277 -0
- nvmath/bindings/cycufft.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cycufft.pxd +333 -0
- nvmath/bindings/cycufftMp.pxd +342 -0
- nvmath/bindings/cycurand.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cycurand.pxd +141 -0
- nvmath/bindings/cycusolver.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cycusolver.pxd +137 -0
- nvmath/bindings/cycusolverDn.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cycusolverDn.pxd +443 -0
- nvmath/bindings/cycusolverMp.pxd +107 -0
- nvmath/bindings/cycusolverSp.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cycusolverSp.pxd +93 -0
- nvmath/bindings/cycusparse.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cycusparse.pxd +679 -0
- nvmath/bindings/cycusparseLt.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cycusparseLt.pxd +135 -0
- nvmath/bindings/cycutensor.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cycutensor.pxd +189 -0
- nvmath/bindings/cymathdx.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/cymathdx.pxd +552 -0
- nvmath/bindings/cynvshmem.pxd +118 -0
- nvmath/bindings/mathdx.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/mathdx.pxd +182 -0
- nvmath/bindings/mathdx.pyi +1562 -0
- nvmath/bindings/nvpl/__init__.pxd +0 -0
- nvmath/bindings/nvpl/__init__.py +13 -0
- nvmath/bindings/nvpl/_internal/__init__.pxd +0 -0
- nvmath/bindings/nvpl/_internal/__init__.py +0 -0
- nvmath/bindings/nvpl/_internal/blas.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/_internal/blas.pxd +237 -0
- nvmath/bindings/nvpl/_internal/fft.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/_internal/fft.pxd +36 -0
- nvmath/bindings/nvpl/blas.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/blas.pxd +131 -0
- nvmath/bindings/nvpl/blas.pyi +168 -0
- nvmath/bindings/nvpl/cyblas.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/cyblas.pxd +280 -0
- nvmath/bindings/nvpl/cyfft.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/cyfft.pxd +93 -0
- nvmath/bindings/nvpl/fft.cp314t-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/fft.pxd +100 -0
- nvmath/bindings/nvpl/fft.pyi +168 -0
- nvmath/bindings/nvshmem.pxd +54 -0
- nvmath/bindings/nvshmem.pyi +191 -0
- nvmath/device/__init__.py +38 -0
- nvmath/device/_deprecated.py +33 -0
- nvmath/device/common.py +315 -0
- nvmath/device/common_backend.py +131 -0
- nvmath/device/common_cuda.py +201 -0
- nvmath/device/common_numba.py +300 -0
- nvmath/device/common_numba_cuda_mlir.py +202 -0
- nvmath/device/common_opaque_tensor.py +201 -0
- nvmath/device/cublasdx.py +1606 -0
- nvmath/device/cublasdx_backend.py +860 -0
- nvmath/device/cublasdx_numba.py +1534 -0
- nvmath/device/cublasdx_numba_cuda_mlir.py +208 -0
- nvmath/device/cufftdx.py +373 -0
- nvmath/device/cufftdx_backend.py +220 -0
- nvmath/device/cufftdx_numba.py +140 -0
- nvmath/device/cufftdx_numba_cuda_mlir.py +79 -0
- nvmath/device/curand_kernel.py +9147 -0
- nvmath/device/cusolverdx.py +2708 -0
- nvmath/device/cusolverdx_backend.py +440 -0
- nvmath/device/cusolverdx_numba.py +567 -0
- nvmath/device/cusolverdx_numba_cuda_mlir.py +604 -0
- nvmath/device/cusolverdx_overload_backend.py +1029 -0
- nvmath/device/llvm_array.py +29 -0
- nvmath/device/random.py +441 -0
- nvmath/device/random_helpers.py +23 -0
- nvmath/device/random_states.py +187 -0
- nvmath/device/types.py +138 -0
- nvmath/device/vector_types_numba.py +259 -0
- nvmath/distributed/__init__.py +200 -0
- nvmath/distributed/_internal/__init__.py +0 -0
- nvmath/distributed/_internal/nccl.py +86 -0
- nvmath/distributed/_internal/nvshmem.py +307 -0
- nvmath/distributed/_internal/symmetric_memory.py +35 -0
- nvmath/distributed/_internal/tensor_ifc.py +70 -0
- nvmath/distributed/_internal/tensor_ifc_cupy.py +68 -0
- nvmath/distributed/_internal/tensor_ifc_host_device.py +172 -0
- nvmath/distributed/_internal/tensor_ifc_numpy.py +46 -0
- nvmath/distributed/_internal/tensor_ifc_torch.py +162 -0
- nvmath/distributed/_internal/tensor_wrapper.py +81 -0
- nvmath/distributed/_utils.py +167 -0
- nvmath/distributed/distribution/__init__.py +30 -0
- nvmath/distributed/distribution/_configuration.py +39 -0
- nvmath/distributed/distribution/distributions.py +1024 -0
- nvmath/distributed/distribution/redistribute.py +1284 -0
- nvmath/distributed/fft/__init__.py +7 -0
- nvmath/distributed/fft/_configuration.py +82 -0
- nvmath/distributed/fft/fft.py +2742 -0
- nvmath/distributed/linalg/__init__.py +22 -0
- nvmath/distributed/linalg/_internal/__init__.py +3 -0
- nvmath/distributed/linalg/_internal/epilog_protocol.py +586 -0
- nvmath/distributed/linalg/_internal/matmul_desc_ifc.py +28 -0
- nvmath/distributed/linalg/advanced/__init__.py +8 -0
- nvmath/distributed/linalg/advanced/_configuration.py +171 -0
- nvmath/distributed/linalg/advanced/matmulmod.py +3573 -0
- nvmath/distributed/linalg/generic/__init__.py +8 -0
- nvmath/distributed/linalg/generic/_caching.py +66 -0
- nvmath/distributed/linalg/generic/_configuration.py +61 -0
- nvmath/distributed/linalg/generic/_factorization.py +172 -0
- nvmath/distributed/linalg/generic/_initialization.py +966 -0
- nvmath/distributed/linalg/generic/_problem_spec.py +511 -0
- nvmath/distributed/linalg/generic/solvermod.py +1368 -0
- nvmath/distributed/process_group.py +408 -0
- nvmath/fft/__init__.py +7 -0
- nvmath/fft/_configuration.py +189 -0
- nvmath/fft/_exec_utils.py +82 -0
- nvmath/fft/_helpers.py +237 -0
- nvmath/fft/fft.py +3122 -0
- nvmath/internal/__init__.pxd +3 -0
- nvmath/internal/__init__.py +10 -0
- nvmath/internal/_bindings.cp314t-win_amd64.pyd +0 -0
- nvmath/internal/_bindings.pxd +18 -0
- nvmath/internal/_device_utils.py +45 -0
- nvmath/internal/_layout/__init__.pxd +3 -0
- nvmath/internal/_layout/__init__.py +7 -0
- nvmath/internal/_layout/_layout.cp314t-win_amd64.pyd +0 -0
- nvmath/internal/_layout/_layout.pxd +1303 -0
- nvmath/internal/_layout/_layout.pyi +1145 -0
- nvmath/internal/enum_utils.py +142 -0
- nvmath/internal/formatters.py +87 -0
- nvmath/internal/mem_limit.py +51 -0
- nvmath/internal/memory.cp314t-win_amd64.pyd +0 -0
- nvmath/internal/memory.pxd +13 -0
- nvmath/internal/memory.pyi +50 -0
- nvmath/internal/ndbuffer/__init__.pxd +3 -0
- nvmath/internal/ndbuffer/__init__.py +9 -0
- nvmath/internal/ndbuffer/_copy_kernel.cp314t-win_amd64.pyd +0 -0
- nvmath/internal/ndbuffer/_copy_kernel.pxd +10 -0
- nvmath/internal/ndbuffer/_jit.cp314t-win_amd64.pyd +0 -0
- nvmath/internal/ndbuffer/_jit.pxd +7 -0
- nvmath/internal/ndbuffer/_ndbuffer.cp314t-win_amd64.pyd +0 -0
- nvmath/internal/ndbuffer/_ndbuffer.pxd +40 -0
- nvmath/internal/ndbuffer/_ndbuffer.pyi +463 -0
- nvmath/internal/ndbuffer/copy_kernel/args.h +34 -0
- nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/array_view.h +52 -0
- nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/elementwise.h +68 -0
- nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/grid_indexer.h +69 -0
- nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/transposed.h +242 -0
- nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/type_utils.h +39 -0
- nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/utils.h +132 -0
- nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/vec.h +159 -0
- nvmath/internal/ndbuffer/copy_kernel/elementwise.h +53 -0
- nvmath/internal/ndbuffer/copy_kernel/transposed.h +58 -0
- nvmath/internal/package_ifc.py +168 -0
- nvmath/internal/package_ifc_cuda.py +57 -0
- nvmath/internal/package_ifc_cupy.py +67 -0
- nvmath/internal/package_ifc_torch.py +69 -0
- nvmath/internal/package_wrapper.py +14 -0
- nvmath/internal/tensor_ifc.py +179 -0
- nvmath/internal/tensor_ifc_cupy.py +234 -0
- nvmath/internal/tensor_ifc_ndbuffer.py +147 -0
- nvmath/internal/tensor_ifc_numpy.py +184 -0
- nvmath/internal/tensor_ifc_torch.py +178 -0
- nvmath/internal/tensor_wrapper.py +160 -0
- nvmath/internal/typemaps.py +113 -0
- nvmath/internal/utils.py +805 -0
- nvmath/linalg/__init__.py +56 -0
- nvmath/linalg/_internal/__init__.py +3 -0
- nvmath/linalg/_internal/algo_cap_ifc.py +82 -0
- nvmath/linalg/_internal/algo_config_ifc.py +43 -0
- nvmath/linalg/_internal/batch.py +234 -0
- nvmath/linalg/_internal/enum_to_tuples.py +64 -0
- nvmath/linalg/_internal/epilog_protocol.py +766 -0
- nvmath/linalg/_internal/layout.py +624 -0
- nvmath/linalg/_internal/matmul_desc_ifc.py +28 -0
- nvmath/linalg/_internal/matmul_pref_ifc.py +27 -0
- nvmath/linalg/_internal/matrix_layout_ifc.py +26 -0
- nvmath/linalg/_internal/solver_utils.py +432 -0
- nvmath/linalg/_internal/typemaps.py +144 -0
- nvmath/linalg/_internal/utils.py +157 -0
- nvmath/linalg/advanced/__init__.py +8 -0
- nvmath/linalg/advanced/_algorithmmod.py +170 -0
- nvmath/linalg/advanced/_configuration.py +351 -0
- nvmath/linalg/advanced/helpers/__init__.py +5 -0
- nvmath/linalg/advanced/helpers/matmul.py +1316 -0
- nvmath/linalg/advanced/matmulmod.py +3734 -0
- nvmath/linalg/generic/__init__.py +53 -0
- nvmath/linalg/generic/_configuration/__init__.py +39 -0
- nvmath/linalg/generic/_configuration/layout.py +263 -0
- nvmath/linalg/generic/_configuration/match.py +734 -0
- nvmath/linalg/generic/_configuration/qualifiers.py +493 -0
- nvmath/linalg/generic/_configuration/solver_configuration.py +59 -0
- nvmath/linalg/generic/_configuration/wrap.py +217 -0
- nvmath/linalg/generic/_dtype.py +15 -0
- nvmath/linalg/generic/matmulmod.py +2094 -0
- nvmath/linalg/generic/solvermod.py +1301 -0
- nvmath/memory.py +279 -0
- nvmath/sparse/__init__.py +38 -0
- nvmath/sparse/_internal/__init__.py +21 -0
- nvmath/sparse/_internal/common_utils.py +147 -0
- nvmath/sparse/_internal/cudss_config_ifc.py +702 -0
- nvmath/sparse/_internal/cudss_data_ifc.py +399 -0
- nvmath/sparse/_internal/cudss_utils.py +506 -0
- nvmath/sparse/_internal/cusparse_utils.py +382 -0
- nvmath/sparse/_internal/sparse_bsc_ifc.py +303 -0
- nvmath/sparse/_internal/sparse_bsr_ifc.py +305 -0
- nvmath/sparse/_internal/sparse_coo_ifc.py +256 -0
- nvmath/sparse/_internal/sparse_csc_ifc.py +268 -0
- nvmath/sparse/_internal/sparse_csr_ifc.py +288 -0
- nvmath/sparse/_internal/sparse_dia_ifc.py +242 -0
- nvmath/sparse/_internal/sparse_format_helpers.py +601 -0
- nvmath/sparse/_internal/sparse_tensor_ifc.py +133 -0
- nvmath/sparse/_internal/sparse_ust_ifc.py +141 -0
- nvmath/sparse/_internal/utils.py +56 -0
- nvmath/sparse/advanced/__init__.py +7 -0
- nvmath/sparse/advanced/_configuration.py +227 -0
- nvmath/sparse/advanced/direct_solver.py +2069 -0
- nvmath/sparse/generic/__init__.py +7 -0
- nvmath/sparse/generic/_configuration.py +129 -0
- nvmath/sparse/generic/_helpers.py +137 -0
- nvmath/sparse/generic/_thunks.py +21 -0
- nvmath/sparse/generic/matmulmod.py +2353 -0
- nvmath/sparse/ust/__init__.py +7 -0
- nvmath/sparse/ust/_converters.py +422 -0
- nvmath/sparse/ust/_cpp.py +28 -0
- nvmath/sparse/ust/_drawer.py +565 -0
- nvmath/sparse/ust/_emitter.py +1033 -0
- nvmath/sparse/ust/_jit.py +188 -0
- nvmath/sparse/ust/_kernel.py +282 -0
- nvmath/sparse/ust/_utils.py +149 -0
- nvmath/sparse/ust/interfaces/__init__.py +0 -0
- nvmath/sparse/ust/interfaces/torch_interface.py +476 -0
- nvmath/sparse/ust/tensor.py +1016 -0
- nvmath/sparse/ust/tensor_format.py +957 -0
- nvmath/tensor/__init__.py +6 -0
- nvmath/tensor/_configuration.py +120 -0
- nvmath/tensor/_internal/__init__.py +3 -0
- nvmath/tensor/_internal/cutensor_config_ifc.py +279 -0
- nvmath/tensor/_internal/cutensor_utils.py +230 -0
- nvmath/tensor/_internal/data.py +43 -0
- nvmath/tensor/_internal/einsum_parser.py +444 -0
- nvmath/tensor/_internal/typemaps.py +96 -0
- nvmath/tensor/contract.py +1900 -0
- nvmath_python-1.0.0.dist-info/METADATA +134 -0
- nvmath_python-1.0.0.dist-info/RECORD +332 -0
- nvmath_python-1.0.0.dist-info/WHEEL +5 -0
- nvmath_python-1.0.0.dist-info/licenses/LICENSE +177 -0
- 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)
|