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,1301 @@
|
|
|
1
|
+
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
__all__ = ("DirectSolver", "direct_solver", "InvalidDirectSolverState", "DirectSolverOptions")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
import itertools
|
|
10
|
+
import logging
|
|
11
|
+
from collections.abc import Callable, Sequence
|
|
12
|
+
from typing import Any, TypeAlias
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
from nvmath import memory
|
|
17
|
+
from nvmath._internal.workspace import Workspace, get_pinned_memory_resource
|
|
18
|
+
from nvmath.bindings import cublas, cusolverDn
|
|
19
|
+
from nvmath.internal import tensor_wrapper, typemaps, utils
|
|
20
|
+
from nvmath.internal.tensor_ifc_numpy import NumpyTensor
|
|
21
|
+
from nvmath.linalg._internal import solver_utils
|
|
22
|
+
from nvmath.linalg._internal.solver_utils import SolverBackend
|
|
23
|
+
from nvmath.linalg._internal.utils import get_handle
|
|
24
|
+
from nvmath.linalg.generic import ExecutionCUDA
|
|
25
|
+
from nvmath.linalg.generic._configuration.solver_configuration import DirectSolverOptions
|
|
26
|
+
|
|
27
|
+
AnyTensor: TypeAlias = tensor_wrapper.AnyTensor
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Shared doc fragments for the dense direct linear solver public API.
|
|
31
|
+
GENERIC_DIRECT_SOLVER_DOCUMENTATION: dict = utils.COMMON_SHARED_DOC_MAP.copy()
|
|
32
|
+
GENERIC_DIRECT_SOLVER_DOCUMENTATION.update(
|
|
33
|
+
{
|
|
34
|
+
"a": r"""\
|
|
35
|
+
The dense operand (or sequence of operands) representing the left-hand side (LHS) of the system
|
|
36
|
+
of equations: a square matrix ``a`` (or batch of square ``(n, n)`` matrices) for ``a @ x = b``.
|
|
37
|
+
The LHS may be a (sequence of) :class:`numpy.ndarray`, :class:`cupy.ndarray`, and
|
|
38
|
+
:class:`torch.Tensor`. For *batched* input, ``a`` can also be a single tensor of shape
|
|
39
|
+
``(*batch_shape, n, n)`` (implicit batching) or a :class:`list` or :class:`tuple` of matrices of
|
|
40
|
+
the *same* shape. Refer to the **Semantics** section for details.\
|
|
41
|
+
""".replace("\n", " "),
|
|
42
|
+
"b": r"""\
|
|
43
|
+
The ndarray/tensor or (sequence of ndarray/tensors) representing the dense right-hand side (RHS) of
|
|
44
|
+
the system of equations. The RHS operand may be a (sequence of) :class:`numpy.ndarray`,
|
|
45
|
+
:class:`cupy.ndarray`, and :class:`torch.Tensor`. Unbatched shapes are ``(n,)`` or ``(n, nrhs)``;
|
|
46
|
+
with batching parallel to ``a``, use a :class:`list` / :class:`tuple` of 1D/2D arrays (explicit) or a
|
|
47
|
+
single tensor ``(*batch_shape, n, nrhs)`` (implicit). The package and device must match ``a``. Refer
|
|
48
|
+
to the **Semantics** section for details.\
|
|
49
|
+
""".replace("\n", " "),
|
|
50
|
+
"options": r"""\
|
|
51
|
+
Specify options for the direct linear solver as a :class:`DirectSolverOptions` object. Alternatively, a
|
|
52
|
+
`dict` containing the parameters for the ``DirectSolverOptions`` constructor can also be
|
|
53
|
+
provided. If not specified, the value will be set to the default-constructed ``DirectSolverOptions``
|
|
54
|
+
object.\
|
|
55
|
+
""".replace("\n", " "),
|
|
56
|
+
"execution": r"""\
|
|
57
|
+
Specify execution space options for the direct solver as an :class:`ExecutionCUDA` object. Alternatively, a
|
|
58
|
+
string (``"cuda"``) or a ``dict`` with a ``"name"`` of ``"cuda"`` and optional
|
|
59
|
+
``device_id`` relevant to the given execution space. The default execution space is ``"cuda"`` and the
|
|
60
|
+
corresponding :class:`ExecutionCUDA` will be default-constructed on device 0. CPU operands are executed
|
|
61
|
+
on the selected CUDA device as needed.\
|
|
62
|
+
""".replace("\n", " "),
|
|
63
|
+
"result": r"""\
|
|
64
|
+
The result of the specified direct linear solve, ``x``, which has the same batching structure
|
|
65
|
+
as the RHS ``b`` (a single array/tensor, or a sequence of ndarray/tensors, each the same shape as
|
|
66
|
+
the corresponding tensor in ``b``), a dtype matching ``a`` and ``b``, and device and package
|
|
67
|
+
placement consistent with the chosen execution and operands.\
|
|
68
|
+
""".replace("\n", " "),
|
|
69
|
+
"semantics": r"""\
|
|
70
|
+
The dense direct linear solver solves :math:`a @ x = b` for ``x`` given the left-hand side
|
|
71
|
+
(LHS) ``a`` and the right-hand side (RHS) ``b`` (square dense :math:`a`, dense ``b``), using
|
|
72
|
+
an LU factorization and triangular solves on the target device.
|
|
73
|
+
|
|
74
|
+
* **Non-batched:** ``a`` is a square ``(n, n)`` matrix; ``b`` is a vector
|
|
75
|
+
``(n,)`` or a matrix ``(n, nrhs)`` (one or more right-hand sides). The
|
|
76
|
+
solve runs on the selected CUDA device using **cuSOLVER**.
|
|
77
|
+
|
|
78
|
+
* **Batched** (explicit or implicit): the solve uses **cuBLAS** batched
|
|
79
|
+
APIs, so in one call every batch entry shares the *same* system
|
|
80
|
+
size :math:`n` (all ``(n, n)`` ``a``). For ``b``, explicit batching
|
|
81
|
+
uses per-item shapes ``(n,)`` or ``(n, m)``; implicit batching
|
|
82
|
+
uses ``(*batch_shape, n, nrhs)``. If ``a`` and ``b`` are implicitly batched, they
|
|
83
|
+
must share the same leading ``batch_shape`` where applicable. The LHS
|
|
84
|
+
and RHS batching forms may differ (for example, a sequence of ``a``
|
|
85
|
+
with an implicitly batched ``b``, or the reverse) as long as the number
|
|
86
|
+
of batch entries is the same for ``a`` and ``b``.
|
|
87
|
+
|
|
88
|
+
.. seealso:: :class:`nvmath.sparse.advanced.DirectSolver` for
|
|
89
|
+
:math:`a` as a **sparse** CSR matrix (a different feature set and
|
|
90
|
+
backend).
|
|
91
|
+
""".strip(),
|
|
92
|
+
"direct_solver_note": r""".. note::
|
|
93
|
+
|
|
94
|
+
- Currently supported dtypes are: ``float32``, ``float64``, ``complex64``,
|
|
95
|
+
``complex128`` for both ``a`` and ``b``.
|
|
96
|
+
- If users provide ``options.handle``, it is used only for non-batched
|
|
97
|
+
execution (cuSOLVER path). In batched execution (cuBLAS path), the
|
|
98
|
+
provided handle is ignored and an internal cached cuBLAS handle is used.
|
|
99
|
+
- When ``inplace_a=True`` or ``inplace_b=True`` for batched execution,
|
|
100
|
+
users are responsible for ensuring that the LHS and RHS batch entries do
|
|
101
|
+
not overlap in memory.
|
|
102
|
+
""",
|
|
103
|
+
"reset_operands_inplace_note": r""".. note::
|
|
104
|
+
|
|
105
|
+
When ``inplace_a=True`` or ``inplace_b=True`` and the input operands
|
|
106
|
+
reside on the GPU, replacement operands must have the same layout as the
|
|
107
|
+
original operands. CPU operands are copied through internal GPU buffers, so
|
|
108
|
+
this layout restriction does not apply to CPU inputs.""",
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
GENERIC_DIRECT_SOLVER_DOCUMENTATION["release_operands"] = utils._release_operand_docstring(
|
|
112
|
+
True, version_added=None, execution_methods=("factorize", "solve")
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class InvalidDirectSolverState(Exception):
|
|
117
|
+
"""
|
|
118
|
+
Raised when a :class:`DirectSolver` instance (or a method on it) is used after
|
|
119
|
+
:meth:`DirectSolver.free` has run and resources are invalid.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@utils.docstring_decorator(GENERIC_DIRECT_SOLVER_DOCUMENTATION, skip_missing=False)
|
|
124
|
+
class DirectSolver:
|
|
125
|
+
r"""
|
|
126
|
+
Create a stateful object that encapsulates the specified dense direct linear solver
|
|
127
|
+
computations and required resources. This object ensures the validity of resources during
|
|
128
|
+
use and releases them when they are no longer needed to prevent misuse.
|
|
129
|
+
|
|
130
|
+
This object encompasses all functionality of the function-form API
|
|
131
|
+
:func:`~nvmath.linalg.direct_solver`, which is a
|
|
132
|
+
convenience wrapper around it. The stateful object also allows amortization of preparatory
|
|
133
|
+
costs (for example :meth:`plan` and an LU :meth:`factorize`) when the same size and layout
|
|
134
|
+
are reused, including after :meth:`reset_operands` to update ``a``/``b``.
|
|
135
|
+
|
|
136
|
+
Using the stateful object typically involves the following steps:
|
|
137
|
+
|
|
138
|
+
1. **Problem specification**: Initialize the object with ``a``, ``b``, and optional
|
|
139
|
+
``options`` / ``execution`` / ``stream``.
|
|
140
|
+
2. **Preparation**: :meth:`plan` in preparation for factorization (see **Semantics**).
|
|
141
|
+
3. **Execution**: :meth:`factorize`, then :meth:`solve` to obtain ``x`` (or :meth:`solve`
|
|
142
|
+
again with the same ``a`` and an updated ``b`` after :meth:`reset_operands` when the
|
|
143
|
+
matrix or RHS values change without recreating the solver).
|
|
144
|
+
4. **Resource management**: Release resources with :meth:`free` or by using a ``with`` block.
|
|
145
|
+
|
|
146
|
+
More detail on what each phase is doing (planning, factorization, and solve) can be
|
|
147
|
+
seen in the logs by passing a :class:`logging.Logger` in :class:`DirectSolverOptions`,
|
|
148
|
+
or by configuring the root logger that :class:`DirectSolverOptions` uses by default, for
|
|
149
|
+
example:
|
|
150
|
+
|
|
151
|
+
>>> import logging
|
|
152
|
+
>>> logging.basicConfig(
|
|
153
|
+
... level=logging.INFO,
|
|
154
|
+
... format="%(asctime)s %(levelname)-8s %(message)s",
|
|
155
|
+
... datefmt="%m-%d %H:%M:%S",
|
|
156
|
+
... )
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
a: {a}
|
|
160
|
+
b: {b}
|
|
161
|
+
options: {options}
|
|
162
|
+
execution: {execution}
|
|
163
|
+
stream: {stream}
|
|
164
|
+
|
|
165
|
+
Semantics:
|
|
166
|
+
{semantics}
|
|
167
|
+
|
|
168
|
+
{direct_solver_note}
|
|
169
|
+
|
|
170
|
+
.. seealso:: :func:`~nvmath.linalg.direct_solver`, :class:`DirectSolverOptions`, :class:`ExecutionCUDA`, and
|
|
171
|
+
:class:`nvmath.sparse.advanced.DirectSolver` (sparse ``a``).
|
|
172
|
+
|
|
173
|
+
Examples:
|
|
174
|
+
|
|
175
|
+
Non-batched :class:`numpy.ndarray` operands: build a well-conditioned
|
|
176
|
+
:math:`n \times n` system and solve with :meth:`plan`, :meth:`factorize`, and
|
|
177
|
+
:meth:`solve` inside a context manager so that :meth:`free` is called
|
|
178
|
+
automatically:
|
|
179
|
+
|
|
180
|
+
>>> import numpy as np
|
|
181
|
+
>>> import nvmath.linalg as la
|
|
182
|
+
>>>
|
|
183
|
+
>>> n = 4
|
|
184
|
+
>>> rng = np.random.default_rng(0)
|
|
185
|
+
>>> a = rng.standard_normal((n, n), dtype=np.float64)
|
|
186
|
+
>>> a = a + n * np.eye(n, dtype=np.float64)
|
|
187
|
+
>>> b = np.ones(n, dtype=np.float64)
|
|
188
|
+
>>>
|
|
189
|
+
>>> with la.DirectSolver(a, b) as solver:
|
|
190
|
+
... solver.plan()
|
|
191
|
+
... solver.factorize()
|
|
192
|
+
... x = solver.solve()
|
|
193
|
+
>>> x.shape
|
|
194
|
+
(4,)
|
|
195
|
+
|
|
196
|
+
The same call pattern applies to :class:`cupy.ndarray` and
|
|
197
|
+
:class:`torch.Tensor` on the default CUDA device when the operands
|
|
198
|
+
already live on the GPU, and to batching; see **Semantics** and the
|
|
199
|
+
example-tree link below. Further worked examples (implicit batching,
|
|
200
|
+
:meth:`reset_operands`, streams) are in
|
|
201
|
+
`generic direct solver examples
|
|
202
|
+
<https://github.com/NVIDIA/nvmath-python/tree/main/examples/linalg/generic/direct_solver>`_
|
|
203
|
+
on GitHub.
|
|
204
|
+
|
|
205
|
+
""" # noqa: W505
|
|
206
|
+
|
|
207
|
+
def __init__(
|
|
208
|
+
self,
|
|
209
|
+
a: AnyTensor,
|
|
210
|
+
b: AnyTensor,
|
|
211
|
+
options: DirectSolverOptions | dict[str, Any] | None = None,
|
|
212
|
+
execution: ExecutionCUDA | str | dict[str, Any] | None = None,
|
|
213
|
+
stream: utils.AnyStream | int | None = None,
|
|
214
|
+
):
|
|
215
|
+
# Process options.
|
|
216
|
+
self.options: DirectSolverOptions = utils.check_or_create_options(
|
|
217
|
+
DirectSolverOptions, options, "direct linear solver options"
|
|
218
|
+
) # type: ignore[assignment]
|
|
219
|
+
self.inplace_a = self.options.inplace_a
|
|
220
|
+
self.inplace_b = self.options.inplace_b
|
|
221
|
+
|
|
222
|
+
# Process execution options. The default execution space is CUDA.
|
|
223
|
+
self.execution_options = utils.check_or_create_one_of_options(
|
|
224
|
+
(ExecutionCUDA,), execution, "execution options", default_name="cuda"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
self.logger = self.options.logger if self.options.logger is not None else logging.getLogger()
|
|
228
|
+
self.logger.info("= SPECIFICATION PHASE =")
|
|
229
|
+
|
|
230
|
+
# Wrap operands, check dtypes/devices, and validate LHS/RHS batching consistency.
|
|
231
|
+
(
|
|
232
|
+
self.lhs_backup,
|
|
233
|
+
self.lhs_batch_info,
|
|
234
|
+
self.rhs_backup,
|
|
235
|
+
self.rhs_batch_info,
|
|
236
|
+
self.n,
|
|
237
|
+
self.nrhs,
|
|
238
|
+
self.package,
|
|
239
|
+
self.input_device_id,
|
|
240
|
+
self.dtype_name,
|
|
241
|
+
lhs_layout,
|
|
242
|
+
rhs_layout,
|
|
243
|
+
) = solver_utils.parse_solver_operands(
|
|
244
|
+
a,
|
|
245
|
+
b,
|
|
246
|
+
check_inplace_a_layout=self.inplace_a,
|
|
247
|
+
check_inplace_b_layout=self.inplace_b,
|
|
248
|
+
)
|
|
249
|
+
# LHS/RHS batching consistency is already validated in parse_solver_operands.
|
|
250
|
+
# Use LHS metadata for batched flag and batch_count.
|
|
251
|
+
self.batched = self.lhs_batch_info.explicitly_batched or self.lhs_batch_info.implicitly_batched
|
|
252
|
+
self.batch_count = self.lhs_batch_info.batch_count
|
|
253
|
+
self.cuda_dtype = typemaps.NAME_TO_DATA_TYPE[self.dtype_name]
|
|
254
|
+
|
|
255
|
+
# Backend:
|
|
256
|
+
# non-batched → cuSOLVER:
|
|
257
|
+
# - lhs/a: (n, n); rhs/b: (n,) or (n, nrhs)
|
|
258
|
+
# batched → cuBLAS *Batched APIs
|
|
259
|
+
# - Explicit batching:
|
|
260
|
+
# - lhs/a: list/tuple of (n, n);
|
|
261
|
+
# rhs/b: list/tuple of (n,) or (n, nrhs)
|
|
262
|
+
# - Implicit batching:
|
|
263
|
+
# - lhs/a: (*batch_shape, n, n);
|
|
264
|
+
# rhs/b: (*batch_shape, n, nrhs)
|
|
265
|
+
if not self.batched:
|
|
266
|
+
self.backend = SolverBackend.CUSOLVER
|
|
267
|
+
self.logger.info("For non-batched input, execution will be performed using cusolver library.")
|
|
268
|
+
else:
|
|
269
|
+
self.backend = SolverBackend.CUBLAS
|
|
270
|
+
self.logger.info("For batched input, execution will be performed using cublas library.")
|
|
271
|
+
|
|
272
|
+
# Determine the stream package for the execution space.
|
|
273
|
+
self.stream_package = "cuda" if self.package == "numpy" else self.package
|
|
274
|
+
|
|
275
|
+
self.memory_space = solver_utils.get_single_or_sequence_attr(self.lhs_backup, "device")
|
|
276
|
+
if self.memory_space == "cuda":
|
|
277
|
+
# When the operands are on device,
|
|
278
|
+
# we respect the device id of the operands over execution options.
|
|
279
|
+
self.execution_device = solver_utils.get_single_or_sequence_attr(self.lhs_backup, "device_id")
|
|
280
|
+
self.copy_across_memspace = False
|
|
281
|
+
else:
|
|
282
|
+
# When the operands are on CPU,
|
|
283
|
+
# we use the device id from execution options.
|
|
284
|
+
self.execution_device = self.execution_options.device_id
|
|
285
|
+
self.copy_across_memspace = True
|
|
286
|
+
self.input_operand_class = solver_utils.get_single_or_sequence_attr(self.lhs_backup, "__class__")
|
|
287
|
+
if self.copy_across_memspace and self.package == "numpy":
|
|
288
|
+
self.internal_operand_class = self.input_operand_class.device_tensor_class
|
|
289
|
+
else:
|
|
290
|
+
self.internal_operand_class = self.input_operand_class
|
|
291
|
+
|
|
292
|
+
# Get the wrapped input shapes:
|
|
293
|
+
# - For explicit batching, this represents the per-batch shapes.
|
|
294
|
+
# - For implicit batching or non-batched, this represents the full tensor shape.
|
|
295
|
+
self.lhs_shape = solver_utils.get_single_or_sequence_attr(self.lhs_backup, "shape") # type: ignore[assignment]
|
|
296
|
+
self.rhs_shape = solver_utils.get_single_or_sequence_attr(self.rhs_backup, "shape") # type: ignore[assignment]
|
|
297
|
+
|
|
298
|
+
if self.logger.isEnabledFor(logging.INFO):
|
|
299
|
+
if self.batched:
|
|
300
|
+
self.logger.info("Batched problem specification:")
|
|
301
|
+
self.logger.info(f" dtype: {self.dtype_name}")
|
|
302
|
+
self.logger.info(f" lhs shape(s): {self.lhs_shape!r}")
|
|
303
|
+
self.logger.info(f" rhs shape(s): {self.rhs_shape!r}")
|
|
304
|
+
self.logger.info(f" lhs batching: {self.lhs_batch_info}")
|
|
305
|
+
self.logger.info(f" rhs batching: {self.rhs_batch_info}")
|
|
306
|
+
else:
|
|
307
|
+
self.logger.info("Non-batched problem specification:")
|
|
308
|
+
self.logger.info(f" dtype: {self.dtype_name}")
|
|
309
|
+
self.logger.info(f" lhs shape(s): {self.lhs_shape!r}")
|
|
310
|
+
self.logger.info(f" rhs shape(s): {self.rhs_shape!r}")
|
|
311
|
+
|
|
312
|
+
# lhs_backup/rhs_backup wrap the user operands and keep them available for
|
|
313
|
+
# reset/retry flows when their inplace option is False.
|
|
314
|
+
# lhs/rhs are internal execution buffers otherwise.
|
|
315
|
+
stream_holder = utils.get_or_create_stream(self.execution_device, stream, self.stream_package)
|
|
316
|
+
|
|
317
|
+
# Keep track of the lhs/rhs allocation streams to ensure
|
|
318
|
+
# proper ordering in free() and release_operands().
|
|
319
|
+
self.input_lhs_layout = lhs_layout
|
|
320
|
+
self.input_rhs_layout = rhs_layout
|
|
321
|
+
|
|
322
|
+
if self.inplace_a and not self.copy_across_memspace:
|
|
323
|
+
if lhs_layout is None:
|
|
324
|
+
raise RuntimeError("Internal error: inplace_a LHS layout was not computed.")
|
|
325
|
+
self.lhs = self.lhs_backup
|
|
326
|
+
self.lhs_backup = None
|
|
327
|
+
self.lhs_mirror_stream = None
|
|
328
|
+
self.lhs_layout = lhs_layout
|
|
329
|
+
else:
|
|
330
|
+
self.lhs = self._create_empty_lhs(stream_holder)
|
|
331
|
+
solver_utils.copy_operands(self.lhs, self.lhs_backup, stream_holder)
|
|
332
|
+
self.lhs_layout = solver_utils.LHSLayout("F", self.n)
|
|
333
|
+
self.logger.debug("lhs/a has been copied to the execution space for the linear solver...")
|
|
334
|
+
# GETRF always factorizes the column-major view of lhs. For C-order input
|
|
335
|
+
# that view is A.T, so GETRS uses op=T to solve against the original A.
|
|
336
|
+
self.getrs_operation = cublas.Operation.N if self.lhs_layout.order == "F" else cublas.Operation.T
|
|
337
|
+
# Whether the execution LHS buffer contains the same values as the source LHS.
|
|
338
|
+
# For device-resident inplace_a, these are the same allocation, so this remains True
|
|
339
|
+
# and freshness is user-managed.
|
|
340
|
+
self.lhs_synced = True
|
|
341
|
+
|
|
342
|
+
if self.inplace_b and not self.copy_across_memspace:
|
|
343
|
+
if rhs_layout is None:
|
|
344
|
+
raise RuntimeError("Internal error: inplace_b RHS layout was not computed.")
|
|
345
|
+
self.rhs = self.rhs_backup
|
|
346
|
+
self.rhs_backup = None
|
|
347
|
+
self.rhs_mirror_stream = None
|
|
348
|
+
self.rhs_layout = rhs_layout
|
|
349
|
+
else:
|
|
350
|
+
self.rhs = self._create_empty_rhs(stream_holder)
|
|
351
|
+
solver_utils.copy_operands(self.rhs, self.rhs_backup, stream_holder)
|
|
352
|
+
self.rhs_layout = solver_utils.get_rhs_layout(self.rhs, self.nrhs, check_inplace_compatible_strides=False)
|
|
353
|
+
# Whether the execution RHS buffer contains the same values as the source RHS.
|
|
354
|
+
# For device-resident inplace_b, these are the same allocation, so this remains True
|
|
355
|
+
# and freshness is user-managed.
|
|
356
|
+
self.rhs_synced = True
|
|
357
|
+
self.logger.debug("rhs/b has been copied to the execution space for the linear solver...")
|
|
358
|
+
|
|
359
|
+
# Solver library resources (non-batched → cuSOLVER; batched → cuBLAS batched).
|
|
360
|
+
# Typical buffer shapes (batch_count is 1 for non-batched):
|
|
361
|
+
#
|
|
362
|
+
# Resource | cuSOLVER | cuBLAS
|
|
363
|
+
# ---------------+--------------------------+----------------------------------
|
|
364
|
+
# handle | yes if not provided | yes (cached) if not provided
|
|
365
|
+
# params | yes | no
|
|
366
|
+
# getrf_info | (1,) int32 | (batch_count,) int32
|
|
367
|
+
# getrs_info | (1,) int32 | no
|
|
368
|
+
# pivot buffer | (n,) int64 | (batch_count, n) int32
|
|
369
|
+
# lhs_pointers | no | (batch_count,) uint64
|
|
370
|
+
# rhs_pointers | no | (batch_count,) uint64
|
|
371
|
+
#
|
|
372
|
+
|
|
373
|
+
# getrf_info_host.tensor must be an ndarray where we can inspect the info values.
|
|
374
|
+
self.getrf_info_host = utils.create_empty_tensor(NumpyTensor, (self.batch_count,), "int32", "cpu", None, False)
|
|
375
|
+
self.getrf_info_device = self.getrf_info_host.to(self.execution_device, stream_holder)
|
|
376
|
+
self.logger.debug("Allocating info in the execution space for the linear solver factorization...")
|
|
377
|
+
|
|
378
|
+
if self.backend == SolverBackend.CUSOLVER:
|
|
379
|
+
if self.options.handle is None:
|
|
380
|
+
with utils.device_ctx(self.execution_device):
|
|
381
|
+
self.handle = cusolverDn.create()
|
|
382
|
+
self.own_handle = True
|
|
383
|
+
self.logger.info("A cusolverDn handle has been created.")
|
|
384
|
+
else:
|
|
385
|
+
self.handle = self.options.handle
|
|
386
|
+
self.own_handle = False
|
|
387
|
+
self.params = cusolverDn.create_params()
|
|
388
|
+
self.logger.info("A cusolverDn params object has been created.")
|
|
389
|
+
|
|
390
|
+
self.pivot = self._create_internal_operand((self.n,), "int64", stream_holder)
|
|
391
|
+
self.lhs_pointers = None
|
|
392
|
+
self.rhs_pointers = None
|
|
393
|
+
else:
|
|
394
|
+
# When execution is dispatched to cuBLAS, the provided handle is ignored
|
|
395
|
+
# and a cuBLAS handle will be created.
|
|
396
|
+
if self.options.handle is not None:
|
|
397
|
+
self.logger.info("Execution will be performed with cuBLAS, the provided handle is ignored.")
|
|
398
|
+
|
|
399
|
+
# get the cached cublas handle
|
|
400
|
+
self.handle = get_handle(self.execution_device, binding="cublas")
|
|
401
|
+
self.logger.info("A cublas handle has been created.")
|
|
402
|
+
|
|
403
|
+
# cublas does not require params
|
|
404
|
+
self.params = None
|
|
405
|
+
self.own_handle = False
|
|
406
|
+
|
|
407
|
+
self.pivot = self._create_internal_operand((self.batch_count, self.n), "int32", stream_holder)
|
|
408
|
+
|
|
409
|
+
# Per-batch device pointers for cuBLAS *Batched: explicit batching gathers
|
|
410
|
+
# data_ptr from each wrapped tensor; implicit batching uses batch_indices on
|
|
411
|
+
# one strided tensor.
|
|
412
|
+
if self.lhs_batch_info.implicitly_batched:
|
|
413
|
+
self.lhs_batch_indices = tuple(itertools.product(*list(map(range, self.lhs_batch_info.batch_shape)))) # type: ignore[assignment]
|
|
414
|
+
else:
|
|
415
|
+
self.lhs_batch_indices = None # type: ignore[assignment]
|
|
416
|
+
self.lhs_pointers = solver_utils.get_operands_ptr_array(self.lhs, self.lhs_batch_indices).to(
|
|
417
|
+
device_id=self.execution_device, stream_holder=stream_holder
|
|
418
|
+
)
|
|
419
|
+
if self.rhs_batch_info.implicitly_batched:
|
|
420
|
+
self.rhs_batch_indices = tuple(itertools.product(*list(map(range, self.rhs_batch_info.batch_shape)))) # type: ignore[assignment]
|
|
421
|
+
else:
|
|
422
|
+
self.rhs_batch_indices = None # type: ignore[assignment]
|
|
423
|
+
self.rhs_pointers = solver_utils.get_operands_ptr_array(self.rhs, self.rhs_batch_indices).to(
|
|
424
|
+
device_id=self.execution_device, stream_holder=stream_holder
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
# Stream used when allocating handles/pointers above; free() may wait on it.
|
|
428
|
+
self.resource_alloc_stream = stream_holder.obj
|
|
429
|
+
|
|
430
|
+
self.solver_planned = False
|
|
431
|
+
self.solver_factorized = False
|
|
432
|
+
# Last GPU event from factorize/solve; free() waits so teardown cannot race kernels.
|
|
433
|
+
self.last_compute_event = None
|
|
434
|
+
|
|
435
|
+
self._operands_released = False
|
|
436
|
+
|
|
437
|
+
# DirectSolver always synchronizes so factorization info can be inspected safely.
|
|
438
|
+
# TODO: enable asynchronous execution
|
|
439
|
+
self.blocking = True
|
|
440
|
+
self.call_prologue = "This call is blocking and will return only after the operation is complete."
|
|
441
|
+
|
|
442
|
+
# Set memory allocator.
|
|
443
|
+
self.allocator = (
|
|
444
|
+
self.options.allocator
|
|
445
|
+
if self.options.allocator is not None
|
|
446
|
+
else memory._MEMORY_MANAGER[self.stream_package](self.execution_device, self.logger)
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
# resolution deferred until plan() is called
|
|
450
|
+
self._getrf_func: Callable | None = None
|
|
451
|
+
self._getrs_func: Callable | None = None
|
|
452
|
+
|
|
453
|
+
# Workspace lifecycle is managed by `Workspace` (allocate-on-demand,
|
|
454
|
+
# reuse, exception cleanup, stream-ordered free).
|
|
455
|
+
self.device_workspace = Workspace(
|
|
456
|
+
self.allocator,
|
|
457
|
+
self.logger,
|
|
458
|
+
label="device workspace",
|
|
459
|
+
device_id=self.execution_device,
|
|
460
|
+
)
|
|
461
|
+
# cuSOLVER getrf may use host workspace during factorization. Use pinned
|
|
462
|
+
# host memory so Workspace can order its lifetime on the compute stream.
|
|
463
|
+
# Prefer PinnedMemoryResource when runtime support is available; otherwise
|
|
464
|
+
# fall back to synchronous LegacyPinnedMemoryResource.
|
|
465
|
+
host_mr = get_pinned_memory_resource(self.execution_device)
|
|
466
|
+
self.host_workspace = Workspace(host_mr, device_id=self.execution_device, logger=self.logger, label="host workspace")
|
|
467
|
+
|
|
468
|
+
self.valid_state = True
|
|
469
|
+
self.logger.info("The DirectSolver operation has been created.")
|
|
470
|
+
|
|
471
|
+
def _check_valid_solver(self, *args, **kwargs):
|
|
472
|
+
"""
|
|
473
|
+
Check if the DirectSolver object is alive and well.
|
|
474
|
+
"""
|
|
475
|
+
if not self.valid_state:
|
|
476
|
+
raise InvalidDirectSolverState("The DirectSolver object cannot be used after resources are free'd")
|
|
477
|
+
|
|
478
|
+
def _check_planned(self, *args, **kwargs):
|
|
479
|
+
what = kwargs["what"]
|
|
480
|
+
if not self.solver_planned:
|
|
481
|
+
raise RuntimeError(f"{what} cannot be performed before plan() has been called.")
|
|
482
|
+
|
|
483
|
+
def _check_factorized(self, *args, **kwargs):
|
|
484
|
+
what = kwargs["what"]
|
|
485
|
+
if not self.solver_factorized:
|
|
486
|
+
raise RuntimeError(f"{what} cannot be performed before factorize() has been called.")
|
|
487
|
+
|
|
488
|
+
def _check_valid_operands(self, *args, **kwargs):
|
|
489
|
+
"""
|
|
490
|
+
Check if the operands are available for the operation.
|
|
491
|
+
"""
|
|
492
|
+
what = kwargs["what"]
|
|
493
|
+
if self._operands_released:
|
|
494
|
+
raise RuntimeError(
|
|
495
|
+
f"{what} cannot be performed after the operands have been released. "
|
|
496
|
+
f"Use reset_operands() to provide new operands before performing the {what.lower()}."
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
def _check_lhs_layout(self, lhs_layout):
|
|
500
|
+
expected_layout = self.input_lhs_layout if self.copy_across_memspace and self.inplace_a else self.lhs_layout
|
|
501
|
+
if lhs_layout != expected_layout:
|
|
502
|
+
raise ValueError(
|
|
503
|
+
f"The layout of 'a' ({lhs_layout}) must match the original layout ({expected_layout}) when inplace_a is True."
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
def _check_rhs_layout(self, rhs_layout):
|
|
507
|
+
expected_layout = self.input_rhs_layout if self.copy_across_memspace and self.inplace_b else self.rhs_layout
|
|
508
|
+
if rhs_layout != expected_layout:
|
|
509
|
+
raise ValueError(
|
|
510
|
+
f"The layout of 'b' ({rhs_layout}) must match the original layout ({expected_layout}) when inplace_b is True."
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
@utils.precondition(_check_valid_solver)
|
|
514
|
+
def release_operands(self):
|
|
515
|
+
"""
|
|
516
|
+
{release_operands}
|
|
517
|
+
"""
|
|
518
|
+
if self._operands_released:
|
|
519
|
+
self.logger.info("Operands have already been released; nothing to do.")
|
|
520
|
+
return
|
|
521
|
+
|
|
522
|
+
if self.lhs_mirror_stream is not None:
|
|
523
|
+
self.lhs_mirror_stream.wait(self.last_compute_event)
|
|
524
|
+
self.lhs_mirror_stream = None
|
|
525
|
+
if self.rhs_mirror_stream is not None:
|
|
526
|
+
self.rhs_mirror_stream.wait(self.last_compute_event)
|
|
527
|
+
self.rhs_mirror_stream = None
|
|
528
|
+
|
|
529
|
+
def release_wrapped_reference(wrapped_ops):
|
|
530
|
+
if isinstance(wrapped_ops, Sequence):
|
|
531
|
+
for op in wrapped_ops:
|
|
532
|
+
op.tensor = None
|
|
533
|
+
else:
|
|
534
|
+
wrapped_ops.tensor = None
|
|
535
|
+
|
|
536
|
+
# When possible, we keep the TensorHolder wrappers alive and only release the
|
|
537
|
+
# internal tensor reference so that reset_operands_unchecked can reuse them
|
|
538
|
+
# without re-wrapping.
|
|
539
|
+
# When inplace_a is True on device operands, the user operand is self.lhs
|
|
540
|
+
# directly. CPU inplace keeps the user operand in self.lhs_backup.
|
|
541
|
+
if self.inplace_a and not self.copy_across_memspace:
|
|
542
|
+
release_wrapped_reference(self.lhs)
|
|
543
|
+
else:
|
|
544
|
+
release_wrapped_reference(self.lhs_backup)
|
|
545
|
+
self.lhs = None
|
|
546
|
+
|
|
547
|
+
# When inplace_b is True on device operands, the user operand is self.rhs
|
|
548
|
+
# directly. CPU inplace keeps the user operand in self.rhs_backup.
|
|
549
|
+
if self.inplace_b and not self.copy_across_memspace:
|
|
550
|
+
release_wrapped_reference(self.rhs)
|
|
551
|
+
elif self.rhs_backup is not None:
|
|
552
|
+
release_wrapped_reference(self.rhs_backup)
|
|
553
|
+
self.rhs = None
|
|
554
|
+
|
|
555
|
+
self._operands_released = True
|
|
556
|
+
self.logger.info("User-provided operands have been released.")
|
|
557
|
+
|
|
558
|
+
@utils.precondition(_check_valid_solver)
|
|
559
|
+
def reset_operands(self, *, a=None, b=None, stream: utils.AnyStream | int | None = None):
|
|
560
|
+
"""
|
|
561
|
+
Replace ``a`` and/or ``b`` with operands that match the *same* problem shape,
|
|
562
|
+
batching, device, package, and dtype as the original.
|
|
563
|
+
|
|
564
|
+
Args:
|
|
565
|
+
a: New left-hand side, or ``None`` to keep the previous left-hand side.
|
|
566
|
+
b: New right-hand side, or ``None`` to keep the previous right-hand side.
|
|
567
|
+
stream: {stream}
|
|
568
|
+
|
|
569
|
+
Semantics:
|
|
570
|
+
- Only the operands explicitly passed are updated. At least one operand
|
|
571
|
+
is required (all of them after :meth:`release_operands`), otherwise
|
|
572
|
+
a :class:`ValueError` is raised.
|
|
573
|
+
|
|
574
|
+
- Supplying a new ``a`` invalidates the current factorization; call
|
|
575
|
+
:meth:`factorize` again before the next :meth:`solve`.
|
|
576
|
+
|
|
577
|
+
{reset_operands_inplace_note}
|
|
578
|
+
"""
|
|
579
|
+
# if the operands have been released, both 'a' and 'b' must be provided
|
|
580
|
+
if self._operands_released:
|
|
581
|
+
if a is None or b is None:
|
|
582
|
+
raise ValueError("After release_operands(), both 'a' and 'b' must be provided to reset_operands().")
|
|
583
|
+
elif a is None and b is None:
|
|
584
|
+
# All arguments are None: there is nothing to update, so reject the call.
|
|
585
|
+
raise ValueError("reset_operands() requires at least one operand to be provided.")
|
|
586
|
+
|
|
587
|
+
stream_holder = utils.get_or_create_stream(self.execution_device, stream, self.stream_package)
|
|
588
|
+
|
|
589
|
+
def _check_operands_space(operand, operand_name):
|
|
590
|
+
package = solver_utils.get_single_or_sequence_attr(operand, "name")
|
|
591
|
+
device_id = solver_utils.get_single_or_sequence_attr(operand, "device_id")
|
|
592
|
+
dtype_name = solver_utils.get_single_or_sequence_attr(operand, "dtype")
|
|
593
|
+
if package != self.package:
|
|
594
|
+
raise ValueError(f"The package for {operand_name} ({package}) must match the original one ({self.package}).")
|
|
595
|
+
if device_id != self.input_device_id:
|
|
596
|
+
raise ValueError(
|
|
597
|
+
f"The device id for {operand_name} ({device_id}) must match the original one ({self.input_device_id})."
|
|
598
|
+
)
|
|
599
|
+
if dtype_name != self.dtype_name:
|
|
600
|
+
raise ValueError(
|
|
601
|
+
f"The dtype for {operand_name} ({dtype_name}) must match the original one ({self.dtype_name})."
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
if a is not None:
|
|
605
|
+
lhs, lhs_batch_info, n, lhs_layout = solver_utils.wrap_check_solver_lhs(a, check_inplace_a_layout=self.inplace_a)
|
|
606
|
+
if lhs_batch_info != self.lhs_batch_info:
|
|
607
|
+
raise ValueError(
|
|
608
|
+
f"The batching metadata for 'a' ({lhs_batch_info!r}) must match the original ({self.lhs_batch_info!r})."
|
|
609
|
+
)
|
|
610
|
+
if n != self.n:
|
|
611
|
+
raise ValueError(f"The number of columns for 'a' ({n}) must match the original one ({self.n}).")
|
|
612
|
+
_check_operands_space(lhs, "a")
|
|
613
|
+
if self.inplace_a and not self.copy_across_memspace:
|
|
614
|
+
self._check_lhs_layout(lhs_layout)
|
|
615
|
+
self.lhs = lhs
|
|
616
|
+
else:
|
|
617
|
+
self.lhs_backup = lhs
|
|
618
|
+
if self.lhs is None:
|
|
619
|
+
self.lhs = self._create_empty_lhs(stream_holder)
|
|
620
|
+
solver_utils.copy_operands(self.lhs, self.lhs_backup, stream_holder)
|
|
621
|
+
if self.backend == SolverBackend.CUBLAS:
|
|
622
|
+
solver_utils.update_operands_ptr_array(
|
|
623
|
+
self.lhs_pointers, # type: ignore[arg-type]
|
|
624
|
+
self.lhs,
|
|
625
|
+
stream_holder,
|
|
626
|
+
self.lhs_batch_indices,
|
|
627
|
+
)
|
|
628
|
+
self.lhs_synced = True
|
|
629
|
+
self.solver_factorized = False
|
|
630
|
+
self.logger.info("User-provided lhs/a has been reset.")
|
|
631
|
+
if b is not None:
|
|
632
|
+
rhs, rhs_batch_info, n, nrhs, rhs_layout = solver_utils.wrap_check_solver_rhs(
|
|
633
|
+
b, check_inplace_b_layout=self.inplace_b
|
|
634
|
+
)
|
|
635
|
+
if rhs_batch_info != self.rhs_batch_info:
|
|
636
|
+
raise ValueError(
|
|
637
|
+
f"The batching metadata for 'b' ({rhs_batch_info!r}) must match the original ({self.rhs_batch_info!r})."
|
|
638
|
+
)
|
|
639
|
+
if n != self.n:
|
|
640
|
+
raise ValueError(f"The number of columns for 'b' ({n}) must match the original one ({self.n}).")
|
|
641
|
+
if nrhs != self.nrhs:
|
|
642
|
+
raise ValueError(f"The number of right-hand sides for 'b' ({nrhs}) must match the original one ({self.nrhs}).")
|
|
643
|
+
_check_operands_space(rhs, "b")
|
|
644
|
+
if self.inplace_b and not self.copy_across_memspace:
|
|
645
|
+
self._check_rhs_layout(rhs_layout)
|
|
646
|
+
self.rhs = rhs
|
|
647
|
+
else:
|
|
648
|
+
self.rhs_backup = rhs
|
|
649
|
+
if self.rhs is None:
|
|
650
|
+
self.rhs = self._create_empty_rhs(stream_holder)
|
|
651
|
+
solver_utils.copy_operands(self.rhs, self.rhs_backup, stream_holder)
|
|
652
|
+
if self.backend == SolverBackend.CUBLAS:
|
|
653
|
+
solver_utils.update_operands_ptr_array(
|
|
654
|
+
self.rhs_pointers, # type: ignore[arg-type]
|
|
655
|
+
self.rhs,
|
|
656
|
+
stream_holder,
|
|
657
|
+
self.rhs_batch_indices,
|
|
658
|
+
)
|
|
659
|
+
self.rhs_synced = True
|
|
660
|
+
self.logger.info("User-provided rhs/b has been reset.")
|
|
661
|
+
|
|
662
|
+
self._operands_released = False
|
|
663
|
+
|
|
664
|
+
@utils.precondition(_check_valid_solver)
|
|
665
|
+
def reset_operands_unchecked(self, *, a=None, b=None, stream: utils.AnyStream | int | None = None):
|
|
666
|
+
"""
|
|
667
|
+
.. experimental:: method
|
|
668
|
+
|
|
669
|
+
This method is a performance-optimized alternative to :meth:`reset_operands` that
|
|
670
|
+
eliminates validation and logging overhead, making it ideal for
|
|
671
|
+
performance-critical loops where ``a``/``b`` compatibility is guaranteed by the
|
|
672
|
+
caller.
|
|
673
|
+
|
|
674
|
+
This method accepts the same parameters as :meth:`reset_operands`.
|
|
675
|
+
|
|
676
|
+
Args:
|
|
677
|
+
a: New left-hand side, or ``None`` to keep the previous one. The caller must
|
|
678
|
+
guarantee the same invariants :meth:`reset_operands` would enforce; this
|
|
679
|
+
method does not validate.
|
|
680
|
+
b: New right-hand side, or ``None`` to keep the previous one. Same invariants
|
|
681
|
+
as ``a`` where applicable.
|
|
682
|
+
stream: {stream}
|
|
683
|
+
|
|
684
|
+
Returns:
|
|
685
|
+
None
|
|
686
|
+
|
|
687
|
+
Semantics:
|
|
688
|
+
The semantics are the same as in :meth:`reset_operands`, except that this
|
|
689
|
+
method does not perform any validation
|
|
690
|
+
(e.g. batching metadata, ``n``, ``n_rhs``, device, and dtype) or logging.
|
|
691
|
+
|
|
692
|
+
When to Use:
|
|
693
|
+
- Performance-critical loops with repeated :meth:`solve` or operand updates
|
|
694
|
+
when compatibility is already guaranteed
|
|
695
|
+
|
|
696
|
+
- After verifying correctness with :meth:`reset_operands` during development
|
|
697
|
+
|
|
698
|
+
- When ``a``/``b`` compatibility is guaranteed by construction or invariant
|
|
699
|
+
|
|
700
|
+
{reset_operands_inplace_note}
|
|
701
|
+
|
|
702
|
+
.. seealso::
|
|
703
|
+
:meth:`reset_operands`, :meth:`release_operands`
|
|
704
|
+
"""
|
|
705
|
+
# If operands have been released, all required operands must be provided
|
|
706
|
+
if self._operands_released and (a is None or b is None):
|
|
707
|
+
raise ValueError("After release_operands(), both 'a' and 'b' must be provided to reset_operands_unchecked().")
|
|
708
|
+
|
|
709
|
+
stream_holder = utils.get_or_create_stream(self.execution_device, stream, self.stream_package)
|
|
710
|
+
if a is not None:
|
|
711
|
+
if self.inplace_a and not self.copy_across_memspace:
|
|
712
|
+
# inplace_a requires operands to be in the execution space,
|
|
713
|
+
# therefore we can directly update self.lhs with the user-provided operands.
|
|
714
|
+
if self.lhs_batch_info.explicitly_batched:
|
|
715
|
+
for lhs_wrapper, lhs_raw in zip(self.lhs, a, strict=True):
|
|
716
|
+
lhs_wrapper.tensor = lhs_raw
|
|
717
|
+
else:
|
|
718
|
+
self.lhs.tensor = a
|
|
719
|
+
else:
|
|
720
|
+
if self.lhs is None:
|
|
721
|
+
self.lhs = self._create_empty_lhs(stream_holder)
|
|
722
|
+
# update the internal tensor references held by the wrappers
|
|
723
|
+
# for the user-provided operands.
|
|
724
|
+
if self.lhs_batch_info.explicitly_batched:
|
|
725
|
+
for lhs_wrapper, lhs_raw in zip(self.lhs_backup, a, strict=True):
|
|
726
|
+
lhs_wrapper.tensor = lhs_raw
|
|
727
|
+
else:
|
|
728
|
+
self.lhs_backup.tensor = a
|
|
729
|
+
solver_utils.copy_operands(self.lhs, self.lhs_backup, stream_holder)
|
|
730
|
+
if self.backend == SolverBackend.CUBLAS:
|
|
731
|
+
solver_utils.update_operands_ptr_array(
|
|
732
|
+
self.lhs_pointers, # type: ignore[arg-type]
|
|
733
|
+
self.lhs,
|
|
734
|
+
stream_holder,
|
|
735
|
+
self.lhs_batch_indices,
|
|
736
|
+
)
|
|
737
|
+
self.lhs_synced = True
|
|
738
|
+
self.solver_factorized = False
|
|
739
|
+
if b is not None:
|
|
740
|
+
if self.inplace_b and not self.copy_across_memspace:
|
|
741
|
+
# inplace_b requires operands to be in the execution space,
|
|
742
|
+
# therefore we can directly update self.rhs with the user-provided operands.
|
|
743
|
+
if self.rhs_batch_info.explicitly_batched:
|
|
744
|
+
for rhs_wrapper, rhs_raw in zip(self.rhs, b, strict=True):
|
|
745
|
+
rhs_wrapper.tensor = rhs_raw
|
|
746
|
+
else:
|
|
747
|
+
self.rhs.tensor = b
|
|
748
|
+
else:
|
|
749
|
+
if self.rhs is None:
|
|
750
|
+
self.rhs = self._create_empty_rhs(stream_holder)
|
|
751
|
+
# update the internal tensor references held by the wrappers
|
|
752
|
+
# for the user-provided operands.
|
|
753
|
+
if self.rhs_batch_info.explicitly_batched:
|
|
754
|
+
for rhs_wrapper, rhs_raw in zip(self.rhs_backup, b, strict=True):
|
|
755
|
+
rhs_wrapper.tensor = rhs_raw
|
|
756
|
+
else:
|
|
757
|
+
self.rhs_backup.tensor = b
|
|
758
|
+
solver_utils.copy_operands(self.rhs, self.rhs_backup, stream_holder)
|
|
759
|
+
if self.backend == SolverBackend.CUBLAS:
|
|
760
|
+
solver_utils.update_operands_ptr_array(
|
|
761
|
+
self.rhs_pointers, # type: ignore[arg-type]
|
|
762
|
+
self.rhs,
|
|
763
|
+
stream_holder,
|
|
764
|
+
self.rhs_batch_indices,
|
|
765
|
+
)
|
|
766
|
+
self.rhs_synced = True
|
|
767
|
+
self._operands_released = False
|
|
768
|
+
|
|
769
|
+
@utils.precondition(_check_valid_solver)
|
|
770
|
+
@utils.precondition(_check_valid_operands, "Planning")
|
|
771
|
+
def plan(self, stream: utils.AnyStream | int | None = None):
|
|
772
|
+
"""
|
|
773
|
+
Plan the dense direct solve. This is the **preparation** step before numerical
|
|
774
|
+
factorization (any sizing or setup required for the current operands on the
|
|
775
|
+
selected device). After planning, the object is ready for :meth:`factorize`.
|
|
776
|
+
|
|
777
|
+
In the class-based workflow, call :meth:`plan` once before calling
|
|
778
|
+
:meth:`factorize` and :meth:`solve`.
|
|
779
|
+
|
|
780
|
+
Args:
|
|
781
|
+
stream: {stream}
|
|
782
|
+
|
|
783
|
+
.. seealso::
|
|
784
|
+
:meth:`factorize`, :meth:`solve`.
|
|
785
|
+
"""
|
|
786
|
+
self.logger.info("= PLANNING PHASE =")
|
|
787
|
+
if self.solver_planned:
|
|
788
|
+
self.logger.info("Skipping planning since it has already been performed in a previous call.")
|
|
789
|
+
return
|
|
790
|
+
|
|
791
|
+
if self.backend == SolverBackend.CUBLAS:
|
|
792
|
+
# cublas does not require workspace memory for cublasXgetrf/getrsBatched
|
|
793
|
+
self.logger.info("For batched input, workspace memory allocation is not required for cublasXgetrf/getrsBatched.")
|
|
794
|
+
self.device_workspace.set_size(0)
|
|
795
|
+
self.host_workspace.set_size(0)
|
|
796
|
+
# resolve the function pointers for the batched getrf/getrs functions
|
|
797
|
+
match self.dtype_name:
|
|
798
|
+
case "float32":
|
|
799
|
+
self._getrf_func = cublas.sgetrf_batched
|
|
800
|
+
self._getrs_func = cublas.sgetrs_batched
|
|
801
|
+
case "float64":
|
|
802
|
+
self._getrf_func = cublas.dgetrf_batched
|
|
803
|
+
self._getrs_func = cublas.dgetrs_batched
|
|
804
|
+
case "complex64":
|
|
805
|
+
self._getrf_func = cublas.cgetrf_batched
|
|
806
|
+
self._getrs_func = cublas.cgetrs_batched
|
|
807
|
+
case "complex128":
|
|
808
|
+
self._getrf_func = cublas.zgetrf_batched
|
|
809
|
+
self._getrs_func = cublas.zgetrs_batched
|
|
810
|
+
case _:
|
|
811
|
+
raise NotImplementedError(f"The dtype {self.dtype_name} is not supported yet.")
|
|
812
|
+
else:
|
|
813
|
+
# resolve the function pointers for the Non-batched getrf/getrs functions
|
|
814
|
+
self._getrf_func = cusolverDn.xgetrf
|
|
815
|
+
self._getrs_func = cusolverDn.xgetrs
|
|
816
|
+
stream_holder = utils.get_or_create_stream(self.execution_device, stream, self.stream_package)
|
|
817
|
+
with utils.cuda_call_ctx(stream_holder, self.blocking) as (self.last_compute_event, elapsed):
|
|
818
|
+
cusolverDn.set_stream(self.handle, stream_holder.ptr)
|
|
819
|
+
workspace_size_device, workspace_size_host = cusolverDn.xgetrf_buffer_size(
|
|
820
|
+
self.handle,
|
|
821
|
+
self.params,
|
|
822
|
+
self.n,
|
|
823
|
+
self.n,
|
|
824
|
+
self.cuda_dtype,
|
|
825
|
+
self.lhs.data_ptr,
|
|
826
|
+
self.lhs_layout.lda,
|
|
827
|
+
self.cuda_dtype,
|
|
828
|
+
) # type: ignore[assignment]
|
|
829
|
+
self.device_workspace.set_size(workspace_size_device)
|
|
830
|
+
self.host_workspace.set_size(workspace_size_host)
|
|
831
|
+
if elapsed.data is not None:
|
|
832
|
+
self.logger.info(f"Planning took {elapsed.data:.3f} ms to complete.")
|
|
833
|
+
else:
|
|
834
|
+
self.logger.info("Planning has been completed.")
|
|
835
|
+
self.solver_planned = True
|
|
836
|
+
|
|
837
|
+
def _create_internal_operand(self, shape, dtype, stream_holder, strides=None):
|
|
838
|
+
return utils.create_empty_tensor(
|
|
839
|
+
self.internal_operand_class,
|
|
840
|
+
shape,
|
|
841
|
+
dtype,
|
|
842
|
+
self.execution_device,
|
|
843
|
+
stream_holder,
|
|
844
|
+
verify_strides=False,
|
|
845
|
+
strides=strides,
|
|
846
|
+
)
|
|
847
|
+
|
|
848
|
+
def _create_empty_lhs(self, stream_holder: utils.StreamHolder):
|
|
849
|
+
# TODO: currently lhs is created with F order, matching the generally
|
|
850
|
+
# expected input for cusolver/cublas. In principle we could optimize it such that:
|
|
851
|
+
# 1. If input is in C order, lhs also gets created in C order and
|
|
852
|
+
# makes use of cublas.Operation.T to perform the equivalent operation.
|
|
853
|
+
# 2. If input is not in C order, lhs also gets created in F order and
|
|
854
|
+
# makes use of cublas.Operation.N to perform the equivalent operation.
|
|
855
|
+
# 3. This would help speedup the d2d copy process when the input is in C order.
|
|
856
|
+
if not self.batched:
|
|
857
|
+
lhs = self._create_internal_operand(self.lhs_shape, self.dtype_name, stream_holder, (1, self.n))
|
|
858
|
+
elif self.lhs_batch_info.explicitly_batched:
|
|
859
|
+
lhs = [
|
|
860
|
+
self._create_internal_operand(self.lhs_shape, self.dtype_name, stream_holder, (1, self.n))
|
|
861
|
+
for _ in range(self.batch_count)
|
|
862
|
+
]
|
|
863
|
+
else:
|
|
864
|
+
assert self.lhs_batch_info.implicitly_batched, "Internal Error."
|
|
865
|
+
# For execution, store lhs as shape (m, m, *batch_shape) in F order.
|
|
866
|
+
opt_shape = (self.n, self.n) + self.lhs_shape[:-2]
|
|
867
|
+
opt_strides = solver_utils.compute_f_strides(opt_shape)
|
|
868
|
+
# revert the the original view to get the correct strides
|
|
869
|
+
shape = self.lhs_shape
|
|
870
|
+
strides = opt_strides[2:] + opt_strides[:2]
|
|
871
|
+
lhs = self._create_internal_operand(shape, self.dtype_name, stream_holder, strides)
|
|
872
|
+
# Update the allocation stream for proper ordering in free() and release_operands()
|
|
873
|
+
self.lhs_mirror_stream = stream_holder.obj
|
|
874
|
+
self.logger.debug("Allocating lhs/a in the execution space for the linear solver...")
|
|
875
|
+
return lhs
|
|
876
|
+
|
|
877
|
+
def _create_empty_rhs(self, stream_holder: utils.StreamHolder):
|
|
878
|
+
if not self.batched:
|
|
879
|
+
rhs = self._create_internal_operand(
|
|
880
|
+
self.rhs_shape, self.dtype_name, stream_holder, solver_utils.compute_f_strides(self.rhs_shape)
|
|
881
|
+
)
|
|
882
|
+
elif self.rhs_batch_info.explicitly_batched:
|
|
883
|
+
strides = solver_utils.compute_f_strides(self.rhs_shape)
|
|
884
|
+
rhs = [
|
|
885
|
+
self._create_internal_operand(self.rhs_shape, self.dtype_name, stream_holder, strides)
|
|
886
|
+
for _ in range(self.batch_count)
|
|
887
|
+
]
|
|
888
|
+
else:
|
|
889
|
+
assert self.rhs_batch_info.implicitly_batched, "Internal Error."
|
|
890
|
+
b_dim = len(self.rhs_shape)
|
|
891
|
+
batch_dim = len(self.rhs_batch_info.batch_shape)
|
|
892
|
+
|
|
893
|
+
if b_dim == batch_dim + 1:
|
|
894
|
+
# m, nrhs
|
|
895
|
+
opt_shape = (self.n,) + self.rhs_batch_info.batch_shape
|
|
896
|
+
opt_strides = solver_utils.compute_f_strides(opt_shape)
|
|
897
|
+
strides = opt_strides[1:] + opt_strides[:1]
|
|
898
|
+
else:
|
|
899
|
+
assert b_dim == batch_dim + 2, "Internal Error."
|
|
900
|
+
opt_shape = (self.n, self.nrhs) + self.rhs_batch_info.batch_shape
|
|
901
|
+
opt_strides = solver_utils.compute_f_strides(opt_shape)
|
|
902
|
+
strides = opt_strides[2:] + opt_strides[:2]
|
|
903
|
+
rhs = self._create_internal_operand(self.rhs_shape, self.dtype_name, stream_holder, strides)
|
|
904
|
+
# Update the allocation stream for proper ordering in free() and release_operands()
|
|
905
|
+
self.rhs_mirror_stream = stream_holder.obj
|
|
906
|
+
self.logger.debug("Allocating rhs/b in the execution space for the linear solver...")
|
|
907
|
+
return rhs
|
|
908
|
+
|
|
909
|
+
@utils.precondition(_check_valid_solver)
|
|
910
|
+
@utils.precondition(_check_planned, "Factorization")
|
|
911
|
+
@utils.precondition(_check_valid_operands, "Factorization")
|
|
912
|
+
def factorize(self, *, stream: utils.AnyStream | int | None = None):
|
|
913
|
+
"""
|
|
914
|
+
Factorize the system of equations. This performs a numerical **LU** decomposition
|
|
915
|
+
of the left-hand side ``a`` for the current problem. Call :meth:`plan` first.
|
|
916
|
+
|
|
917
|
+
Each call factorizes the LHS ``a`` currently held by the solver. To
|
|
918
|
+
change the LHS values between calls, call :meth:`reset_operands` (or
|
|
919
|
+
:meth:`reset_operands_unchecked`) with the new ``a``. Alternatively,
|
|
920
|
+
when ``inplace_a=True`` and ``a`` is accessible from the execution
|
|
921
|
+
space, users may directly modify ``a`` in place, for example with
|
|
922
|
+
``a[:] = a_new``. In that case, users are responsible for ensuring
|
|
923
|
+
that ``a`` contains the intended LHS values before each call, since
|
|
924
|
+
:meth:`factorize` overwrites that operand with its LU factors.
|
|
925
|
+
|
|
926
|
+
Args:
|
|
927
|
+
stream: {stream}
|
|
928
|
+
|
|
929
|
+
.. seealso::
|
|
930
|
+
:meth:`plan`, :meth:`solve`, :meth:`reset_operands`.
|
|
931
|
+
"""
|
|
932
|
+
|
|
933
|
+
self.logger.info("= FACTORIZATION PHASE =")
|
|
934
|
+
self.logger.info(self.call_prologue)
|
|
935
|
+
self.solver_factorized = False
|
|
936
|
+
|
|
937
|
+
stream_holder = utils.get_or_create_stream(self.execution_device, stream, self.stream_package)
|
|
938
|
+
|
|
939
|
+
if not self.lhs_synced:
|
|
940
|
+
solver_utils.copy_operands(self.lhs, self.lhs_backup, stream_holder)
|
|
941
|
+
self.lhs_synced = True
|
|
942
|
+
|
|
943
|
+
elapsed = None
|
|
944
|
+
getrf = self._getrf_func
|
|
945
|
+
assert getrf is not None, "Internal error"
|
|
946
|
+
|
|
947
|
+
if self.backend == SolverBackend.CUBLAS:
|
|
948
|
+
with utils.cuda_call_ctx(stream_holder, self.blocking) as (self.last_compute_event, elapsed):
|
|
949
|
+
cublas.set_stream(self.handle, stream_holder.ptr)
|
|
950
|
+
getrf(
|
|
951
|
+
self.handle,
|
|
952
|
+
self.n,
|
|
953
|
+
self.lhs_pointers.data_ptr, # type: ignore[union-attr]
|
|
954
|
+
self.lhs_layout.lda,
|
|
955
|
+
self.pivot.data_ptr,
|
|
956
|
+
self.getrf_info_device.data_ptr,
|
|
957
|
+
self.batch_count,
|
|
958
|
+
)
|
|
959
|
+
else:
|
|
960
|
+
with (
|
|
961
|
+
self.device_workspace.allocate_perhaps(
|
|
962
|
+
stream_holder, get_last_event=lambda: self.last_compute_event
|
|
963
|
+
) as device_ws,
|
|
964
|
+
self.host_workspace.allocate_perhaps(stream_holder, get_last_event=lambda: self.last_compute_event) as host_ws,
|
|
965
|
+
utils.cuda_call_ctx(stream_holder, self.blocking) as (self.last_compute_event, elapsed),
|
|
966
|
+
):
|
|
967
|
+
cusolverDn.set_stream(self.handle, stream_holder.ptr)
|
|
968
|
+
getrf(
|
|
969
|
+
self.handle,
|
|
970
|
+
self.params,
|
|
971
|
+
self.n,
|
|
972
|
+
self.n,
|
|
973
|
+
self.cuda_dtype,
|
|
974
|
+
self.lhs.data_ptr,
|
|
975
|
+
self.lhs_layout.lda,
|
|
976
|
+
self.pivot.data_ptr,
|
|
977
|
+
self.cuda_dtype, # compute type
|
|
978
|
+
device_ws.raw_ptr,
|
|
979
|
+
device_ws.size,
|
|
980
|
+
host_ws.raw_ptr,
|
|
981
|
+
host_ws.size,
|
|
982
|
+
self.getrf_info_device.data_ptr,
|
|
983
|
+
)
|
|
984
|
+
# When inplace_a=True and the LHS operand lives on the execution space,
|
|
985
|
+
# lhs_synced always remains True. Otherwise, GETRF overwrites the internal
|
|
986
|
+
# execution buffer with LU factors, so it no longer matches the source LHS.
|
|
987
|
+
self.lhs_synced = self.inplace_a and not self.copy_across_memspace
|
|
988
|
+
|
|
989
|
+
if elapsed.data is not None:
|
|
990
|
+
self.logger.info(f"The factorization phase took {elapsed.data:.3f} ms to complete.")
|
|
991
|
+
|
|
992
|
+
self.getrf_info_host.copy_(self.getrf_info_device, stream_holder)
|
|
993
|
+
if self.backend == SolverBackend.CUSOLVER:
|
|
994
|
+
info = self.getrf_info_host.tensor.item()
|
|
995
|
+
if info != 0:
|
|
996
|
+
if info > 0:
|
|
997
|
+
raise RuntimeError(f"Factorization failed with U[{info}, {info}] being 0.")
|
|
998
|
+
else:
|
|
999
|
+
# The (-info)-th parameter to cusolverDnXgetrf is invalid.
|
|
1000
|
+
raise AssertionError(
|
|
1001
|
+
f"Internal error: cusolverDnXgetrf reported an invalid argument at position {-info} (info={info})."
|
|
1002
|
+
)
|
|
1003
|
+
else:
|
|
1004
|
+
info = self.getrf_info_host.tensor
|
|
1005
|
+
if not np.all(info == 0):
|
|
1006
|
+
failed_batch_indices = np.where(info > 0)[0]
|
|
1007
|
+
if len(failed_batch_indices) > 0:
|
|
1008
|
+
raise RuntimeError(f"Factorization failed for the following batch indices: {failed_batch_indices}.")
|
|
1009
|
+
# The (-info)-th parameter to cublasXgetrfBatched is invalid.
|
|
1010
|
+
info = info.item(0)
|
|
1011
|
+
raise AssertionError(
|
|
1012
|
+
f"Internal error: cublasXgetrfBatched reported an invalid argument at position {-info} (info={info})."
|
|
1013
|
+
)
|
|
1014
|
+
|
|
1015
|
+
if self.inplace_a and self.copy_across_memspace:
|
|
1016
|
+
solver_utils.copy_operands(self.lhs_backup, self.lhs, stream_holder)
|
|
1017
|
+
self.logger.info("Factorization has been completed.")
|
|
1018
|
+
self.solver_factorized = True
|
|
1019
|
+
return
|
|
1020
|
+
|
|
1021
|
+
@utils.precondition(_check_valid_solver)
|
|
1022
|
+
@utils.precondition(_check_valid_operands, "Solver Execution")
|
|
1023
|
+
@utils.precondition(_check_planned, "Solver Execution")
|
|
1024
|
+
@utils.precondition(_check_factorized, "Solver Execution")
|
|
1025
|
+
def solve(self, *, stream: utils.AnyStream | int | None = None):
|
|
1026
|
+
"""
|
|
1027
|
+
Solve the factorized system of equations, producing the solution ``x`` for
|
|
1028
|
+
:math:`a @ x = b` using the factor from the most recent :meth:`factorize` and
|
|
1029
|
+
the current right-hand side ``b``.
|
|
1030
|
+
|
|
1031
|
+
Each call uses the RHS ``b`` currently held by the solver. To change
|
|
1032
|
+
the RHS values between calls, call :meth:`reset_operands` (or
|
|
1033
|
+
:meth:`reset_operands_unchecked`) with the new ``b``. Alternatively,
|
|
1034
|
+
when ``inplace_b=True`` and ``b`` is accessible from the execution
|
|
1035
|
+
space, users may directly modify ``b`` in place, for example with
|
|
1036
|
+
``b[:] = b_new``. In that case, users are responsible for ensuring
|
|
1037
|
+
that ``b`` contains the intended RHS values before each call, since
|
|
1038
|
+
:meth:`solve` overwrites that operand with the solution.
|
|
1039
|
+
|
|
1040
|
+
Args:
|
|
1041
|
+
stream: {stream}
|
|
1042
|
+
|
|
1043
|
+
Returns:
|
|
1044
|
+
{result}
|
|
1045
|
+
|
|
1046
|
+
.. seealso::
|
|
1047
|
+
:meth:`plan`, :meth:`factorize`, :meth:`reset_operands`.
|
|
1048
|
+
"""
|
|
1049
|
+
|
|
1050
|
+
self.logger.info("= SOLVER EXECUTION PHASE =")
|
|
1051
|
+
self.logger.info(self.call_prologue)
|
|
1052
|
+
|
|
1053
|
+
stream_holder = utils.get_or_create_stream(self.execution_device, stream, self.stream_package)
|
|
1054
|
+
|
|
1055
|
+
if self.rhs is None:
|
|
1056
|
+
if self.inplace_b:
|
|
1057
|
+
raise RuntimeError("Internal error: inplace_b=True requires an active RHS operand.")
|
|
1058
|
+
self.rhs = self._create_empty_rhs(stream_holder)
|
|
1059
|
+
if self.backend == SolverBackend.CUBLAS:
|
|
1060
|
+
solver_utils.update_operands_ptr_array(
|
|
1061
|
+
self.rhs_pointers, # type: ignore[arg-type]
|
|
1062
|
+
self.rhs,
|
|
1063
|
+
stream_holder,
|
|
1064
|
+
self.rhs_batch_indices,
|
|
1065
|
+
)
|
|
1066
|
+
if not self.rhs_synced:
|
|
1067
|
+
# The execution RHS was overwritten by a previous solve. Refresh it
|
|
1068
|
+
# from the source RHS before the next solve. reset_operands(..., b=...)
|
|
1069
|
+
# also repopulates rhs and sets rhs_synced True.
|
|
1070
|
+
#
|
|
1071
|
+
# Example: same rhs, new lhs — only the lhs must be updated:
|
|
1072
|
+
#
|
|
1073
|
+
# with DirectSolver(a0, b) as solver:
|
|
1074
|
+
# solver.plan()
|
|
1075
|
+
# solver.factorize()
|
|
1076
|
+
# x0 = solver.solve()
|
|
1077
|
+
# solver.reset_operands(a=a1)
|
|
1078
|
+
# solver.factorize()
|
|
1079
|
+
# x1 = solver.solve()
|
|
1080
|
+
solver_utils.copy_operands(self.rhs, self.rhs_backup, stream_holder)
|
|
1081
|
+
self.rhs_synced = True
|
|
1082
|
+
|
|
1083
|
+
getrs = self._getrs_func
|
|
1084
|
+
assert getrs is not None, "Internal error"
|
|
1085
|
+
|
|
1086
|
+
with utils.cuda_call_ctx(stream_holder, self.blocking) as (self.last_compute_event, elapsed):
|
|
1087
|
+
if self.backend == SolverBackend.CUSOLVER:
|
|
1088
|
+
# From cusolverDn, regardless of whether b is a vector or a matrix,
|
|
1089
|
+
# b is viewed as an F ordered matrix with shape (m, nrhs) with ldb = m
|
|
1090
|
+
# When b is a vector, nrhs = 1
|
|
1091
|
+
ldb = self.rhs_layout.ldb
|
|
1092
|
+
cusolverDn.set_stream(self.handle, stream_holder.ptr)
|
|
1093
|
+
# here getrf_info_device is reused for getrs_info
|
|
1094
|
+
# we can skip the check for getrs_info as it only checks for invalid args
|
|
1095
|
+
getrs(
|
|
1096
|
+
self.handle,
|
|
1097
|
+
self.params,
|
|
1098
|
+
self.getrs_operation,
|
|
1099
|
+
self.n,
|
|
1100
|
+
self.nrhs,
|
|
1101
|
+
self.cuda_dtype,
|
|
1102
|
+
self.lhs.data_ptr,
|
|
1103
|
+
self.lhs_layout.lda,
|
|
1104
|
+
self.pivot.data_ptr,
|
|
1105
|
+
self.cuda_dtype,
|
|
1106
|
+
self.rhs.data_ptr,
|
|
1107
|
+
ldb,
|
|
1108
|
+
self.getrf_info_device.data_ptr,
|
|
1109
|
+
)
|
|
1110
|
+
self.getrf_info_host.copy_(self.getrf_info_device, stream_holder)
|
|
1111
|
+
info = self.getrf_info_host.tensor.item()
|
|
1112
|
+
assert info == 0, (
|
|
1113
|
+
f"Internal error: cusolverDnXgetrs reported an invalid argument at position {-info} (info={info})."
|
|
1114
|
+
)
|
|
1115
|
+
else:
|
|
1116
|
+
assert self.backend == SolverBackend.CUBLAS, "Internal Error."
|
|
1117
|
+
ldb = self.rhs_layout.ldb
|
|
1118
|
+
info = np.empty(1, dtype=np.int32)
|
|
1119
|
+
cublas.set_stream(self.handle, stream_holder.ptr)
|
|
1120
|
+
getrs(
|
|
1121
|
+
self.handle,
|
|
1122
|
+
self.getrs_operation,
|
|
1123
|
+
self.n,
|
|
1124
|
+
self.nrhs,
|
|
1125
|
+
self.lhs_pointers.data_ptr, # type: ignore[union-attr]
|
|
1126
|
+
self.lhs_layout.lda,
|
|
1127
|
+
self.pivot.data_ptr,
|
|
1128
|
+
self.rhs_pointers.data_ptr, # type: ignore[union-attr]
|
|
1129
|
+
ldb,
|
|
1130
|
+
info.ctypes.data,
|
|
1131
|
+
self.batch_count,
|
|
1132
|
+
)
|
|
1133
|
+
assert np.all(info == 0), f"Internal error: cublasXgetrsBatched reported an invalid argument (info={info})."
|
|
1134
|
+
|
|
1135
|
+
if elapsed.data is not None:
|
|
1136
|
+
self.logger.info(f"The solver execution took {elapsed.data:.3f} ms to complete.")
|
|
1137
|
+
|
|
1138
|
+
self.logger.info("Solver execution has been completed.")
|
|
1139
|
+
|
|
1140
|
+
# GETRS overwrites rhs with the solution. For device-resident inplace_b,
|
|
1141
|
+
# rhs is also the source allocation, so execution/source still match and
|
|
1142
|
+
# users are responsible for restoring intended RHS values. Otherwise,
|
|
1143
|
+
# the execution RHS no longer matches source until refreshed.
|
|
1144
|
+
self.rhs_synced = self.inplace_b and not self.copy_across_memspace
|
|
1145
|
+
|
|
1146
|
+
if self.inplace_b:
|
|
1147
|
+
if self.copy_across_memspace:
|
|
1148
|
+
solver_utils.copy_operands(self.rhs_backup, self.rhs, stream_holder)
|
|
1149
|
+
rhs_return = self.rhs_backup
|
|
1150
|
+
else:
|
|
1151
|
+
rhs_return = self.rhs
|
|
1152
|
+
if isinstance(rhs_return, Sequence):
|
|
1153
|
+
return [o.tensor for o in rhs_return]
|
|
1154
|
+
return rhs_return.tensor
|
|
1155
|
+
elif self.copy_across_memspace:
|
|
1156
|
+
self.logger.debug("Copying the result to the input device id...")
|
|
1157
|
+
# return the result on the input device id
|
|
1158
|
+
if isinstance(self.rhs, Sequence):
|
|
1159
|
+
return [o.to(self.input_device_id, stream_holder=stream_holder).tensor for o in self.rhs]
|
|
1160
|
+
else:
|
|
1161
|
+
return self.rhs.to(self.input_device_id, stream_holder=stream_holder).tensor
|
|
1162
|
+
else:
|
|
1163
|
+
# return the ownership of the result to the caller
|
|
1164
|
+
if isinstance(self.rhs, Sequence):
|
|
1165
|
+
out = [o.tensor for o in self.rhs]
|
|
1166
|
+
else:
|
|
1167
|
+
out = self.rhs.tensor
|
|
1168
|
+
self.rhs = None
|
|
1169
|
+
return out
|
|
1170
|
+
|
|
1171
|
+
def free(self):
|
|
1172
|
+
"""Free :class:`DirectSolver` resources.
|
|
1173
|
+
|
|
1174
|
+
It is recommended that the object be used as a context manager; if that is
|
|
1175
|
+
not possible, call this method explicitly when the object is no longer needed.
|
|
1176
|
+
"""
|
|
1177
|
+
if not self.valid_state:
|
|
1178
|
+
return
|
|
1179
|
+
|
|
1180
|
+
try:
|
|
1181
|
+
self.device_workspace.release(self.last_compute_event)
|
|
1182
|
+
self.host_workspace.release(self.last_compute_event)
|
|
1183
|
+
if self.last_compute_event is not None:
|
|
1184
|
+
streams = {
|
|
1185
|
+
self.lhs_mirror_stream,
|
|
1186
|
+
self.rhs_mirror_stream,
|
|
1187
|
+
self.resource_alloc_stream,
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
for stream in streams:
|
|
1191
|
+
if stream is not None:
|
|
1192
|
+
stream.wait(self.last_compute_event)
|
|
1193
|
+
|
|
1194
|
+
self.lhs_mirror_stream = None
|
|
1195
|
+
self.rhs_mirror_stream = None
|
|
1196
|
+
self.resource_alloc_stream = None
|
|
1197
|
+
self.last_compute_event = None
|
|
1198
|
+
|
|
1199
|
+
# Free cusolverDn params if it exists.
|
|
1200
|
+
if self.params is not None:
|
|
1201
|
+
cusolverDn.destroy_params(self.params)
|
|
1202
|
+
self.params = None
|
|
1203
|
+
|
|
1204
|
+
# Free handle if we own it.
|
|
1205
|
+
if self.handle is not None and self.own_handle:
|
|
1206
|
+
cusolverDn.destroy(self.handle)
|
|
1207
|
+
self.handle, self.own_handle = None, False
|
|
1208
|
+
|
|
1209
|
+
# Set all attributes to None except for logger and valid_state
|
|
1210
|
+
_keep = {"logger", "valid_state"}
|
|
1211
|
+
for attr in list(vars(self)):
|
|
1212
|
+
if attr not in _keep:
|
|
1213
|
+
setattr(self, attr, None)
|
|
1214
|
+
|
|
1215
|
+
except Exception as e:
|
|
1216
|
+
self.logger.critical("Internal error: only part of the DirectSolver object's resources have been released.")
|
|
1217
|
+
self.logger.critical(str(e))
|
|
1218
|
+
raise e
|
|
1219
|
+
finally:
|
|
1220
|
+
self.valid_state = False
|
|
1221
|
+
|
|
1222
|
+
self.logger.info("The DirectSolver object's resources have been released.")
|
|
1223
|
+
|
|
1224
|
+
def __enter__(self):
|
|
1225
|
+
"""
|
|
1226
|
+
Return ``self`` for a ``with`` block; the matching :meth:`__exit__` calls
|
|
1227
|
+
:meth:`free` so resources are always released.
|
|
1228
|
+
"""
|
|
1229
|
+
return self
|
|
1230
|
+
|
|
1231
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
1232
|
+
"""
|
|
1233
|
+
Call :meth:`free` (context manager ``__exit__`` protocol).
|
|
1234
|
+
"""
|
|
1235
|
+
self.free()
|
|
1236
|
+
|
|
1237
|
+
|
|
1238
|
+
@utils.docstring_decorator(GENERIC_DIRECT_SOLVER_DOCUMENTATION, skip_missing=False)
|
|
1239
|
+
def direct_solver(
|
|
1240
|
+
a: AnyTensor,
|
|
1241
|
+
b: AnyTensor,
|
|
1242
|
+
*,
|
|
1243
|
+
options: DirectSolverOptions | dict[str, Any] | None = None,
|
|
1244
|
+
execution: ExecutionCUDA | str | dict[str, Any] | None = None,
|
|
1245
|
+
stream: utils.AnyStream | int | None = None,
|
|
1246
|
+
):
|
|
1247
|
+
r"""
|
|
1248
|
+
Solve :math:`a @ x = b` for :math:`x`. This function-form API is a wrapper around the
|
|
1249
|
+
stateful :class:`DirectSolver` object APIs and is meant for *single* use (for example,
|
|
1250
|
+
when the user needs to perform just one dense direct linear solve), in which case
|
|
1251
|
+
there is no possibility of amortizing preparatory costs (such as :meth:`DirectSolver.plan`
|
|
1252
|
+
and :meth:`DirectSolver.factorize` before :meth:`DirectSolver.solve`).
|
|
1253
|
+
|
|
1254
|
+
Use :class:`DirectSolver` when reusing a factorization or performing many solves for the
|
|
1255
|
+
same problem size and layout, or when using :class:`DirectSolverOptions` in a long-lived
|
|
1256
|
+
way (for example a logger or a custom handle).
|
|
1257
|
+
|
|
1258
|
+
Args:
|
|
1259
|
+
a: {a}
|
|
1260
|
+
b: {b}
|
|
1261
|
+
options: {options}
|
|
1262
|
+
execution: {execution}
|
|
1263
|
+
stream: {stream}
|
|
1264
|
+
|
|
1265
|
+
Returns:
|
|
1266
|
+
{result}
|
|
1267
|
+
|
|
1268
|
+
Semantics:
|
|
1269
|
+
{semantics}
|
|
1270
|
+
|
|
1271
|
+
{direct_solver_note}
|
|
1272
|
+
|
|
1273
|
+
Examples:
|
|
1274
|
+
|
|
1275
|
+
A single :func:`~nvmath.linalg.direct_solver` call (equivalent to :meth:`DirectSolver.plan` followed
|
|
1276
|
+
by :meth:`DirectSolver.factorize` and :meth:`DirectSolver.solve` on a stateful
|
|
1277
|
+
object) with NumPy CPU operands, which the implementation executes on the
|
|
1278
|
+
default CUDA device:
|
|
1279
|
+
|
|
1280
|
+
>>> import numpy as np
|
|
1281
|
+
>>> import nvmath.linalg as la
|
|
1282
|
+
>>>
|
|
1283
|
+
>>> n = 4
|
|
1284
|
+
>>> rng = np.random.default_rng(0)
|
|
1285
|
+
>>> a = rng.standard_normal((n, n), dtype=np.float64) + n * np.eye(n, dtype=np.float64)
|
|
1286
|
+
>>> b = np.ones(n, dtype=np.float64)
|
|
1287
|
+
>>>
|
|
1288
|
+
>>> x = la.direct_solver(a, b)
|
|
1289
|
+
>>> x.shape
|
|
1290
|
+
(4,)
|
|
1291
|
+
|
|
1292
|
+
For ``options``, ``stream``, and batching, see the **Semantics** section. More
|
|
1293
|
+
examples (batching, :meth:`~DirectSolver.reset_operands`, streams) are in
|
|
1294
|
+
`generic direct solver examples
|
|
1295
|
+
<https://github.com/NVIDIA/nvmath-python/tree/main/examples/linalg/generic/direct_solver>`_
|
|
1296
|
+
on GitHub.
|
|
1297
|
+
""" # noqa: W505
|
|
1298
|
+
with DirectSolver(a, b, options=options, execution=execution, stream=stream) as solver:
|
|
1299
|
+
solver.plan(stream=stream)
|
|
1300
|
+
solver.factorize(stream=stream)
|
|
1301
|
+
return solver.solve(stream=stream)
|