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,2353 @@
|
|
|
1
|
+
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
__all__ = ["Matmul", "matmul"]
|
|
6
|
+
|
|
7
|
+
import functools
|
|
8
|
+
import logging
|
|
9
|
+
import operator
|
|
10
|
+
import os
|
|
11
|
+
import re
|
|
12
|
+
from collections.abc import Mapping, Sequence
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from enum import IntEnum
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
import cuda.core as cc
|
|
18
|
+
import numpy as np
|
|
19
|
+
|
|
20
|
+
from nvmath import memory
|
|
21
|
+
from nvmath._internal.layout import check_monotonic_strides, is_contiguous_in_memory
|
|
22
|
+
from nvmath._internal.workspace import Workspace
|
|
23
|
+
from nvmath.bindings import cusparse
|
|
24
|
+
from nvmath.internal import formatters, tensor_wrapper, typemaps, utils
|
|
25
|
+
from nvmath.sparse._internal import common_utils as sp_utils
|
|
26
|
+
from nvmath.sparse._internal import cusparse_utils
|
|
27
|
+
from nvmath.sparse._internal.sparse_ust_ifc import USTensorHolder
|
|
28
|
+
from nvmath.sparse._internal.utils import calculate_strides
|
|
29
|
+
from nvmath.sparse.ust._kernel import KernelGen
|
|
30
|
+
from nvmath.sparse.ust.tensor import Tensor as UST
|
|
31
|
+
|
|
32
|
+
from ._configuration import ComputeType, ExecutionCUDA, MatmulOptions, matrix_qualifiers_dtype
|
|
33
|
+
from ._helpers import compile_prolog
|
|
34
|
+
from ._thunks import default_prolog
|
|
35
|
+
|
|
36
|
+
# TODO: make sure KernelGen is thread-safe.
|
|
37
|
+
KERNEL_CACHE = KernelGen(False)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
CUSPARSE_FORMATS = ["CSR", "CSC", "BSR", "BSC", "COO"]
|
|
41
|
+
|
|
42
|
+
CODEGEN_FORMATS = ["UST", "DIA"] + CUSPARSE_FORMATS
|
|
43
|
+
|
|
44
|
+
# Supported index and data types.
|
|
45
|
+
|
|
46
|
+
CUSPARSE_INDEX_TYPES = {"int32", "int64"}
|
|
47
|
+
|
|
48
|
+
CODEGEN_INDEX_TYPES = {"int32", "int64"}
|
|
49
|
+
|
|
50
|
+
VALID_INDEX_TYPES = CUSPARSE_INDEX_TYPES | CODEGEN_INDEX_TYPES
|
|
51
|
+
|
|
52
|
+
CUSPARSE_DTYPES = {"float16", "bfloat16", "float32", "float64", "complex64", "complex128"}
|
|
53
|
+
|
|
54
|
+
CODEGEN_DTYPES = {"float16", "bfloat16", "float32", "float64", "complex64", "complex128"}
|
|
55
|
+
|
|
56
|
+
VALID_DTYPES = CUSPARSE_DTYPES | CODEGEN_DTYPES
|
|
57
|
+
|
|
58
|
+
CUSPARSE_COMPUTE_TYPES = {"float32", "float64", "complex64", "complex128"}
|
|
59
|
+
|
|
60
|
+
CODEGEN_COMPUTE_TYPES = {"float32", "float64", "complex64", "complex128"}
|
|
61
|
+
|
|
62
|
+
VALID_COMPUTE_TYPES = CUSPARSE_COMPUTE_TYPES | CODEGEN_COMPUTE_TYPES
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# TODO:
|
|
66
|
+
# For the dense tensors forming the sparse tensor representation, check that they are
|
|
67
|
+
# dense and contiguous in C-order.
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class BackendSupport(IntEnum):
|
|
71
|
+
"""An IntEnum class for capturing dispatch and codegen support for the MM."""
|
|
72
|
+
|
|
73
|
+
NOT_SUPPORTED = 0
|
|
74
|
+
PROVISIONALLY_SUPPORTED = 1
|
|
75
|
+
SUPPORTED = 2
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class Api(IntEnum):
|
|
79
|
+
"""An IntEnum class for capturing the codegen or dispatch API."""
|
|
80
|
+
|
|
81
|
+
NONE = 0
|
|
82
|
+
CODEGEN = 1
|
|
83
|
+
MM = 2
|
|
84
|
+
MM_OP = 3
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class InvalidMatmulState(Exception):
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# TODO: generic (semantic) compatibility check.
|
|
92
|
+
# dispatch or codegen specific support checks (dtype, compute type, batching,
|
|
93
|
+
# num dense dim) etc.
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _get_cusparse_op_code(qualifier):
|
|
97
|
+
if qualifier["is_transpose"] and qualifier["is_conjugate"]:
|
|
98
|
+
return cusparse.Operation.CONJUGATE_TRANSPOSE
|
|
99
|
+
|
|
100
|
+
if qualifier["is_transpose"]:
|
|
101
|
+
return cusparse.Operation.TRANSPOSE
|
|
102
|
+
|
|
103
|
+
assert not qualifier["is_conjugate"], "Internal error."
|
|
104
|
+
|
|
105
|
+
return cusparse.Operation.NON_TRANSPOSE
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _is_regular_precision(dtype_name):
|
|
109
|
+
e = r"(complex|b?float)(\d+)"
|
|
110
|
+
m = re.search(e, dtype_name)
|
|
111
|
+
assert m is not None, "Internal error."
|
|
112
|
+
size = int(m.group(2))
|
|
113
|
+
is_complex = m.group(1) == "complex"
|
|
114
|
+
if is_complex:
|
|
115
|
+
size //= 2
|
|
116
|
+
return size >= 32
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# Require that conjugate and transpose be specified together.
|
|
120
|
+
@dataclass
|
|
121
|
+
class DenseLayoutTraits:
|
|
122
|
+
"""An internal data class for capturing dense matrix traits."""
|
|
123
|
+
|
|
124
|
+
order: cusparse.Order
|
|
125
|
+
ld: int
|
|
126
|
+
mm_shape: Sequence[int]
|
|
127
|
+
batch_shape: Sequence[int]
|
|
128
|
+
batch_count: int
|
|
129
|
+
batch_offset: int # Based on strides
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass
|
|
133
|
+
class SparseLayoutTraits:
|
|
134
|
+
"""An internal data class for capturing dense matrix traits."""
|
|
135
|
+
|
|
136
|
+
sparse_format: str
|
|
137
|
+
mm_shape: Sequence[int]
|
|
138
|
+
batch_count: int
|
|
139
|
+
batch_broadcast: bool = False
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@dataclass
|
|
143
|
+
class SpMMTraits:
|
|
144
|
+
"""An internal data class for capturing the matrix multiplication traits."""
|
|
145
|
+
|
|
146
|
+
M: int
|
|
147
|
+
N: int
|
|
148
|
+
K: int
|
|
149
|
+
a_layout_traits: SparseLayoutTraits
|
|
150
|
+
b_layout_traits: DenseLayoutTraits
|
|
151
|
+
c_layout_traits: DenseLayoutTraits
|
|
152
|
+
batch_count: int
|
|
153
|
+
batch_shape: Sequence[int]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def get_dense_matrix_layout_traits(
|
|
157
|
+
mm_shape: Sequence[int],
|
|
158
|
+
mm_strides: Sequence[int],
|
|
159
|
+
batch_strides: Sequence[int],
|
|
160
|
+
ordering: cusparse.Order | None = None,
|
|
161
|
+
orientation: cusparse.Order | None = None,
|
|
162
|
+
) -> tuple[cusparse.Order, int, int]:
|
|
163
|
+
"""
|
|
164
|
+
The 'ordering' option specifies the layout order, if it's not None, as in the case of
|
|
165
|
+
the D matrix whose layout is determined by the other operands' layout. It is required if
|
|
166
|
+
the matrix is degenerate (a vector or scalar: len(mm_shape) < 2).
|
|
167
|
+
|
|
168
|
+
The 'orientation' option (ROW or COL) is required to infer the correct leading dimension
|
|
169
|
+
for degenerate matrices (a vector or scalar: len(mm_shape) < 2).
|
|
170
|
+
"""
|
|
171
|
+
if len(mm_shape) < 2: # The result D can be a scalar or vector.
|
|
172
|
+
assert ordering is not None, "Internal Error: 'ordering' must be specified for degenerate matrices."
|
|
173
|
+
assert orientation is not None, "Internal Error: 'orientation' must be specified for degenerate matrices."
|
|
174
|
+
batch_offset = min(batch_strides) if batch_strides else 0
|
|
175
|
+
order = ordering
|
|
176
|
+
if len(mm_shape) < 1:
|
|
177
|
+
ld = 1
|
|
178
|
+
elif order != orientation:
|
|
179
|
+
# For a ROW vector in COL order, the LD should be 1 since we promote the row
|
|
180
|
+
# vector to a matrix as (1, M). Similarly, for a COL vector in ROW order, the LD
|
|
181
|
+
# should be 1 as well since we promote the column vector to a matrix as (M, 1).
|
|
182
|
+
ld = 1
|
|
183
|
+
else:
|
|
184
|
+
ld = max(mm_strides[0], mm_shape[0])
|
|
185
|
+
return order, ld, batch_offset
|
|
186
|
+
|
|
187
|
+
M, N = mm_shape
|
|
188
|
+
|
|
189
|
+
if ordering is not None:
|
|
190
|
+
order = ordering
|
|
191
|
+
message = f"Internal Error: incompatible ordering '{ordering}' and strides {mm_strides}"
|
|
192
|
+
if order == cusparse.Order.ROW:
|
|
193
|
+
assert mm_strides[0] >= mm_strides[1] and mm_strides[1] == 1, message
|
|
194
|
+
else:
|
|
195
|
+
assert mm_strides[1] >= mm_strides[0] and mm_strides[0] == 1, message
|
|
196
|
+
else:
|
|
197
|
+
# Important: start with the first dimension so that cases like (M, 1) : (1, 1) or
|
|
198
|
+
# (1, M) : (1, 1) in CuTe notation map to COL.
|
|
199
|
+
if mm_strides[0] == 1:
|
|
200
|
+
order = cusparse.Order.COL
|
|
201
|
+
elif mm_strides[1] == 1:
|
|
202
|
+
order = cusparse.Order.ROW
|
|
203
|
+
else:
|
|
204
|
+
if M == 1:
|
|
205
|
+
order = cusparse.Order.COL
|
|
206
|
+
elif N == 1:
|
|
207
|
+
order = cusparse.Order.ROW
|
|
208
|
+
else:
|
|
209
|
+
raise ValueError("Unsupported layout.")
|
|
210
|
+
|
|
211
|
+
ld = max(M, mm_strides[1]) if order == cusparse.Order.COL else max(N, mm_strides[0])
|
|
212
|
+
|
|
213
|
+
# Batch dimensions should be contiguous in memory, which we have already checked. The
|
|
214
|
+
# batch_offset should be based on the lowest stride in the batch dimension to account
|
|
215
|
+
# for embedded matrices.
|
|
216
|
+
batch_offset = min(batch_strides) if batch_strides else 0
|
|
217
|
+
|
|
218
|
+
return order, ld, batch_offset
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def get_spmm_traits(a, b, c, *, qualifiers, inplace, logger):
|
|
222
|
+
"""
|
|
223
|
+
First check A and B compatibility:
|
|
224
|
+
|
|
225
|
+
1. The sparse operand is always 2D or higher. Gives M, K.
|
|
226
|
+
2. The dense operand B can be a vector or matrix. If it's
|
|
227
|
+
a vector, an implicit singleton extent is added (but it
|
|
228
|
+
doesn't appear in the result). Implicitly N=1 in this case.
|
|
229
|
+
3. C can be a vector or matrix compatible with (M,N).
|
|
230
|
+
4. The dense matrix batch dimensions must be in C order.
|
|
231
|
+
5. M, K, N must be determined considering the transpose flag.
|
|
232
|
+
|
|
233
|
+
1. Check MM compatibility (K):
|
|
234
|
+
a. First pad A and/or B MM dimensions if 1-D according to NumPy convention.
|
|
235
|
+
b. The padding is used to determine M, N, and K but should not appear in the output
|
|
236
|
+
dimensions.
|
|
237
|
+
c. If both A and B are N-D, the dimensions must match.
|
|
238
|
+
2. Check batch dimensions:
|
|
239
|
+
a. One of A or B can have missing batch extents, in which case it is broadcast,
|
|
240
|
+
otherwise
|
|
241
|
+
b. A and B must have the same batch ordering.
|
|
242
|
+
c. In addition, the batch dimensions must be tileable (contiguous in memory).
|
|
243
|
+
|
|
244
|
+
Then check C:
|
|
245
|
+
|
|
246
|
+
C can be None. If C is passed in, it must be matrix. Batching rule is the
|
|
247
|
+
same as above.
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
# TODO: remove this guard once we add the number of sparse/dense dimension attributes
|
|
251
|
+
# to UST.
|
|
252
|
+
if not isinstance(a, USTensorHolder):
|
|
253
|
+
a_num_dense_dim = a.attr_name_map["num_dense_dim"](a.tensor)
|
|
254
|
+
if a_num_dense_dim > 0:
|
|
255
|
+
raise ValueError(
|
|
256
|
+
"The sparse matrix contains tensor elements, which is not supported for sparse matrix multiplication."
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
a_shape = list(a.shape)
|
|
260
|
+
a_batch_shape, a_mm_shape = a_shape[:-2], a_shape[-2:]
|
|
261
|
+
|
|
262
|
+
b_shape, b_strides = list(b.shape), list(b.strides)
|
|
263
|
+
b_batch_shape, b_mm_shape = b_shape[:-2], b_shape[-2:]
|
|
264
|
+
b_batch_strides, b_mm_strides = b_strides[:-2], b_strides[-2:]
|
|
265
|
+
|
|
266
|
+
# Handle 1D `a` and `b` according to our semantics.
|
|
267
|
+
d_mm_shape = []
|
|
268
|
+
a_vector = False
|
|
269
|
+
if len(a_mm_shape) == 1:
|
|
270
|
+
a_mm_shape = [1] + a_mm_shape
|
|
271
|
+
a_vector = True
|
|
272
|
+
else:
|
|
273
|
+
d_mm_shape.append(a_mm_shape[0]) # The first mode for d applies only when a is not a vector.
|
|
274
|
+
|
|
275
|
+
if len(b_mm_shape) == 1:
|
|
276
|
+
s, d = b_mm_shape[0], b_mm_strides[0]
|
|
277
|
+
b_mm_shape = b_mm_shape + [1]
|
|
278
|
+
b_mm_strides = b_mm_strides + [s * d]
|
|
279
|
+
else:
|
|
280
|
+
d_mm_shape.append(b_mm_shape[1]) # The second mode for d applies only when b is not a vector.
|
|
281
|
+
|
|
282
|
+
logger.debug(f"The MM shape for operand A is {a_mm_shape}.")
|
|
283
|
+
logger.debug(f"The MM shape for operand B is {b_mm_shape} with strides {b_mm_strides}.")
|
|
284
|
+
logger.debug(f"The MM shape for operand D is {d_mm_shape}.")
|
|
285
|
+
|
|
286
|
+
a_qualifiers, b_qualifiers, c_qualifiers = qualifiers
|
|
287
|
+
M0, K0 = a_mm_shape
|
|
288
|
+
if a_qualifiers["is_transpose"]:
|
|
289
|
+
M0, K0 = K0, M0
|
|
290
|
+
|
|
291
|
+
K1, N0 = b_mm_shape
|
|
292
|
+
if b_qualifiers["is_transpose"]:
|
|
293
|
+
K1, N0 = N0, K1
|
|
294
|
+
|
|
295
|
+
if K0 != K1:
|
|
296
|
+
raise ValueError(
|
|
297
|
+
f"The 'K' extent must match for the operands: K={K0} in operand A is not equal to K={K1} in operand B. \
|
|
298
|
+
The transpose qualifiers for A and B are {a_qualifiers['is_transpose'].item(), b_qualifiers['is_transpose'].item()}."
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
# The batch dimensions of all component dense arrays of the sparse tensor `a` should
|
|
302
|
+
# be tileable in C-order.
|
|
303
|
+
batch_shape = []
|
|
304
|
+
if len(a_batch_shape) > 0:
|
|
305
|
+
batch_shape = a_batch_shape
|
|
306
|
+
|
|
307
|
+
# Check if the dense operand `b` batch dimensions are tileable as well as compatible
|
|
308
|
+
# with `a`.
|
|
309
|
+
if len(b_batch_shape) > 0:
|
|
310
|
+
if not (
|
|
311
|
+
is_contiguous_in_memory(b_batch_shape, b_batch_strides) and check_monotonic_strides(b_batch_strides, reverse=True)
|
|
312
|
+
):
|
|
313
|
+
message = (
|
|
314
|
+
f"The batch layout for B corresponding to shape = {b_batch_shape} and strides = {b_batch_strides} "
|
|
315
|
+
"is currently not supported because it is not tileable and in C-order."
|
|
316
|
+
)
|
|
317
|
+
raise ValueError(message)
|
|
318
|
+
logger.debug(
|
|
319
|
+
f"The batch layout for B corresponding to shape = {b_batch_shape} and strides = {b_batch_strides} \
|
|
320
|
+
IS tileable in C-order."
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
if not batch_shape:
|
|
324
|
+
batch_shape = b_batch_shape
|
|
325
|
+
|
|
326
|
+
if len(b_batch_shape) > 0 and b_batch_shape != batch_shape:
|
|
327
|
+
raise ValueError(f"The batch dimensions of operands A {batch_shape} and B {b_batch_shape} must match.")
|
|
328
|
+
|
|
329
|
+
num_batch_dim = len(batch_shape)
|
|
330
|
+
logger.debug(f"The batch shape is {batch_shape} with batch axis in C-order.")
|
|
331
|
+
|
|
332
|
+
batch_count = functools.reduce(operator.mul, batch_shape, 1)
|
|
333
|
+
|
|
334
|
+
# Create sparse matrix layout traits for `a`.
|
|
335
|
+
a_layout_traits = SparseLayoutTraits(
|
|
336
|
+
sparse_format=a.format_name, mm_shape=a_mm_shape, batch_count=batch_count, batch_broadcast=len(a_batch_shape) == 0
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
# Create dense matrix layout traits for `b`.
|
|
340
|
+
b_order, b_ld, b_batch_offset = get_dense_matrix_layout_traits(b_mm_shape, b_mm_strides, b_batch_strides)
|
|
341
|
+
b_layout_traits = DenseLayoutTraits(
|
|
342
|
+
order=b_order,
|
|
343
|
+
ld=b_ld,
|
|
344
|
+
mm_shape=b_mm_shape,
|
|
345
|
+
batch_shape=b_batch_shape,
|
|
346
|
+
batch_offset=b_batch_offset,
|
|
347
|
+
batch_count=batch_count,
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
# Process matrix c, if provided.
|
|
351
|
+
c_layout_traits = None
|
|
352
|
+
if c is not None:
|
|
353
|
+
# 1. C can be a vector, as long as it's columns don't need to be broadcast.
|
|
354
|
+
# 2. C can be a matrix of dimension (M, N).
|
|
355
|
+
# 3. C can be batched matrices of dimension (..., M, N).
|
|
356
|
+
c_shape, c_strides = list(c.shape), list(c.strides)
|
|
357
|
+
|
|
358
|
+
c_batch_shape, c_mm_shape = c_shape[:-2], c_shape[-2:]
|
|
359
|
+
c_batch_strides, c_mm_strides = c_strides[:-2], c_strides[-2:]
|
|
360
|
+
if len(c_mm_shape) == 0:
|
|
361
|
+
c_mm_shape = [1, 1]
|
|
362
|
+
c_mm_strides = [1, 1]
|
|
363
|
+
elif len(c_mm_shape) == 1:
|
|
364
|
+
# raise ValueError(f"C cannot be a vector. C shape: {c_mm_shape}")
|
|
365
|
+
s, d = c_mm_shape[0], c_mm_strides[0]
|
|
366
|
+
c_mm_shape = [1] + c_mm_shape if a_vector else c_mm_shape + [1]
|
|
367
|
+
c_mm_strides = c_mm_strides + [s * d]
|
|
368
|
+
logger.debug(f"The MM shape for operand C is {c_mm_shape} with strides {c_mm_strides}.")
|
|
369
|
+
|
|
370
|
+
Mc, Nc = c_mm_shape
|
|
371
|
+
if Mc != M0:
|
|
372
|
+
raise ValueError(
|
|
373
|
+
f"The M dimension of the C matrix ({Mc}) must match the M dimension of A ({M0}). \
|
|
374
|
+
The transpose qualifier for A is {a_qualifiers['is_transpose']}."
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
if Nc != N0:
|
|
378
|
+
raise ValueError(
|
|
379
|
+
f"The N dimension of the C matrix ({Nc}) must match the N dimension of B ({N0}). \
|
|
380
|
+
The transpose qualifier for B is {b_qualifiers['is_transpose']}."
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
# For the inplace option, C must be batched if an operand is batched.
|
|
384
|
+
if inplace or len(c_batch_shape) > 0:
|
|
385
|
+
if c_batch_shape != batch_shape:
|
|
386
|
+
raise ValueError(
|
|
387
|
+
f"The batch dimension of operand C {c_batch_shape} must match with that of the other operands "
|
|
388
|
+
f"{batch_shape}."
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
if c_batch_shape and not (
|
|
392
|
+
is_contiguous_in_memory(c_batch_shape, c_batch_strides)
|
|
393
|
+
and check_monotonic_strides(c_batch_strides, reverse=True)
|
|
394
|
+
):
|
|
395
|
+
raise ValueError("The batch axes of operand C must be in C-order.")
|
|
396
|
+
|
|
397
|
+
c_order, c_ld, c_batch_offset = get_dense_matrix_layout_traits(c_mm_shape, c_mm_strides, c_batch_strides)
|
|
398
|
+
else:
|
|
399
|
+
# Compute the shape and strides for the result `c`.
|
|
400
|
+
c_mm_shape = d_mm_shape
|
|
401
|
+
c_shape = batch_shape + c_mm_shape
|
|
402
|
+
|
|
403
|
+
c_axis_order = list(range(0, num_batch_dim))
|
|
404
|
+
# Use the result_ordering from b's ordering.
|
|
405
|
+
if b_order == cusparse.Order.ROW:
|
|
406
|
+
c_axis_order += [num_batch_dim + 1, num_batch_dim]
|
|
407
|
+
elif b_order == cusparse.Order.COL:
|
|
408
|
+
c_axis_order += [num_batch_dim, num_batch_dim + 1]
|
|
409
|
+
|
|
410
|
+
c_strides = calculate_strides(c_shape, c_axis_order)
|
|
411
|
+
|
|
412
|
+
c_batch_shape, _ = c_shape[:-2], c_shape[-2:]
|
|
413
|
+
c_batch_strides, c_mm_strides = c_strides[:-2], c_strides[-2:]
|
|
414
|
+
|
|
415
|
+
# For degenerate matrices, we need to specify the result orientation.
|
|
416
|
+
result_orientation = None
|
|
417
|
+
if len(c_mm_shape) < 2:
|
|
418
|
+
if M0 == 1:
|
|
419
|
+
result_orientation = cusparse.Order.ROW
|
|
420
|
+
elif N0 == 1:
|
|
421
|
+
result_orientation = cusparse.Order.COL
|
|
422
|
+
|
|
423
|
+
c_order, c_ld, c_batch_offset = get_dense_matrix_layout_traits(
|
|
424
|
+
c_mm_shape, c_mm_strides, c_batch_strides, ordering=cusparse.Order.ROW, orientation=result_orientation
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
logger.debug(f"The layout order for operand C is {c_order.name}, with LD {c_ld}, and batch offset {c_batch_offset}.")
|
|
428
|
+
# Create dense matrix layout traits for `c`.
|
|
429
|
+
c_layout_traits = DenseLayoutTraits(
|
|
430
|
+
order=c_order,
|
|
431
|
+
ld=c_ld,
|
|
432
|
+
mm_shape=c_mm_shape,
|
|
433
|
+
batch_shape=c_batch_shape,
|
|
434
|
+
batch_offset=c_batch_offset,
|
|
435
|
+
batch_count=batch_count,
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
M, N, K = M0, N0, K0
|
|
439
|
+
logger.debug(f"The SpMM operand dimensions are M={M}, N={N}, K={K}.")
|
|
440
|
+
|
|
441
|
+
# Create the SpMM traits.
|
|
442
|
+
spmm_traits = SpMMTraits(
|
|
443
|
+
M=M,
|
|
444
|
+
N=N,
|
|
445
|
+
K=K,
|
|
446
|
+
a_layout_traits=a_layout_traits,
|
|
447
|
+
b_layout_traits=b_layout_traits,
|
|
448
|
+
c_layout_traits=c_layout_traits,
|
|
449
|
+
batch_count=batch_count,
|
|
450
|
+
batch_shape=batch_shape,
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
return spmm_traits
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
SPARSE_MM_DOCUMENTATION = utils.COMMON_SHARED_DOC_MAP.copy()
|
|
457
|
+
SPARSE_MM_DOCUMENTATION.update(
|
|
458
|
+
{
|
|
459
|
+
"a": """\
|
|
460
|
+
A *sparse* tensor representing the first operand ``a`` in the sparse matrix multiplication
|
|
461
|
+
(SpMM) from one of the supported sparse packages: SciPy, CuPy, PyTorch, or a
|
|
462
|
+
:class:`universal sparse tensor (UST) <nvmath.sparse.ust.Tensor>` object
|
|
463
|
+
(see :ref:`semantics <spmm_semantics>`). The sparse representation may be in any of the formats supported by
|
|
464
|
+
the sparse package (CSR, BSC, COO, ...), including novel formats defined using the UST DSL.
|
|
465
|
+
""".replace("\n", " "),
|
|
466
|
+
#
|
|
467
|
+
"b": """\
|
|
468
|
+
A *dense* tensor representing the second operand ``b`` in the SpMM (see :ref:`semantics <spmm_semantics>`).
|
|
469
|
+
The currently supported types are :class:`numpy.ndarray`, :class:`cupy.ndarray`,
|
|
470
|
+
:class:`torch.Tensor`, and :class:`nvmath.sparse.ust.Tensor`.""".replace("\n", " "),
|
|
471
|
+
"c": """\
|
|
472
|
+
A *dense* tensor representing the addend ``c`` in the SpMM (see :ref:`semantics <spmm_semantics>`). The currently
|
|
473
|
+
supported types are :class:`numpy.ndarray`, :class:`cupy.ndarray`, :class:`torch.Tensor`,
|
|
474
|
+
and :class:`nvmath.sparse.ust.Tensor`.""".replace("\n", " "),
|
|
475
|
+
#
|
|
476
|
+
"alpha": """\
|
|
477
|
+
The scale factor for the matrix multiplication term as a real or complex number. The default is
|
|
478
|
+
:math:`1.0`.""".replace("\n", " "),
|
|
479
|
+
#
|
|
480
|
+
"beta": """\
|
|
481
|
+
The scale factor for the addend term in the matrix multiplication as a real or complex number.
|
|
482
|
+
The default is :math:`1.0`.""".replace("\n", " "),
|
|
483
|
+
#
|
|
484
|
+
"qualifiers": """\
|
|
485
|
+
If desired, specify the matrix qualifiers as a :class:`numpy.ndarray` of
|
|
486
|
+
:data:`~nvmath.sparse.matmul_matrix_qualifiers_dtype` objects of length 3 corresponding to
|
|
487
|
+
the operands ``a``, ``b``, and ``c``. See :ref:`matrix-tensor-qualifiers` for the motivation behind
|
|
488
|
+
qualifiers.""".replace("\n", " "),
|
|
489
|
+
#
|
|
490
|
+
"options": """\
|
|
491
|
+
Specify options for the sparse matrix multiplication as a :class:`~nvmath.sparse.MatmulOptions`
|
|
492
|
+
object. Alternatively, a `dict` containing the parameters for the ``MatmulOptions`` constructor
|
|
493
|
+
can also be provided. If not specified, the value will be set to the default-constructed
|
|
494
|
+
``MatmulOptions`` object.""".replace("\n", " "),
|
|
495
|
+
#
|
|
496
|
+
"execution": """\
|
|
497
|
+
Specify execution space options for the SpMM as a :class:`ExecutionCUDA` object (the only
|
|
498
|
+
execution space currently supported). If not specified, a :class:`ExecutionCUDA` object will
|
|
499
|
+
be default-constructed.""".replace("\n", " "),
|
|
500
|
+
#
|
|
501
|
+
"prologs": """\
|
|
502
|
+
A dict mapping an operand label (``"a"``, ``"b"``) to its prolog operation in LTO-IR
|
|
503
|
+
format (as a :class:`bytes` object). The prolog is a user-written unary function in Python
|
|
504
|
+
that returns the transformed value, which has the data type of the operand to which it is
|
|
505
|
+
applied. This function can be compiled to LTO-IR using the helper
|
|
506
|
+
:func:`~nvmath.sparse.compile_matmul_prolog` or your own compiler of choice. If not
|
|
507
|
+
specified, no prolog will be applied to the operands.""".replace("\n", " "),
|
|
508
|
+
#
|
|
509
|
+
"compute_capability": """\
|
|
510
|
+
The target compute capability, specified as a string (``'80'``, ``'89'``, ...). The
|
|
511
|
+
default is the compute capability of the current device.""".replace("\n", " "),
|
|
512
|
+
#
|
|
513
|
+
"result": """\
|
|
514
|
+
The result of the sparse matrix multiplication. Currently only in-place SpMM is
|
|
515
|
+
supported (the result of the computation is written into the addend ``c``).
|
|
516
|
+
""".replace("\n", " "),
|
|
517
|
+
#
|
|
518
|
+
"reset_operands_unchecked": utils._reset_operand_unchecked_docstring(True, experimental=False),
|
|
519
|
+
#
|
|
520
|
+
"stream": """\
|
|
521
|
+
Provide the CUDA stream to use for executing the operation. Acceptable inputs include
|
|
522
|
+
``cudaStream_t`` (as Python :class:`int`), :class:`cupy.cuda.Stream`, and
|
|
523
|
+
:class:`torch.cuda.Stream`. If a stream is not provided, the current stream for the
|
|
524
|
+
operand device will be queried from the dense operand ``b`` (and ``c``) package.
|
|
525
|
+
""".replace("\n", " "),
|
|
526
|
+
#
|
|
527
|
+
"semantics": """\
|
|
528
|
+
.. _spmm_semantics:
|
|
529
|
+
|
|
530
|
+
The semantics of the matrix multiplication follows :external:py:data:`numpy.matmul` semantics, with some restrictions on
|
|
531
|
+
broadcasting. In addition, the semantics for the fused matrix addition are described below.
|
|
532
|
+
|
|
533
|
+
* For in-place matrix multiplication (where the result is written into ``c``) the result has the same shape as ``c``.
|
|
534
|
+
* The operand ``a`` must be a sparse matrix or batched sparse matrix. Popular named formats like BSC, BSR, COO,
|
|
535
|
+
CSR, ... are supported in addition to custom formats defined using the UST DSL.
|
|
536
|
+
* The operands ``b`` and ``c`` must be "dense" matrices (that is, their layout is strided).
|
|
537
|
+
* If the operands ``a`` and ``b`` are matrices, they are multiplied according to the rules of matrix
|
|
538
|
+
multiplication.
|
|
539
|
+
* If argument ``b`` is 1-D, it is promoted to a matrix by appending ``1`` to its dimensions. After matrix
|
|
540
|
+
multiplication, the appended ``1`` is removed from the result's dimensions if the operation is not in-place.
|
|
541
|
+
* If ``a`` or ``b`` is N-D (N > 2), then the operand is treated as a batch of matrices. If both ``a`` and ``b``
|
|
542
|
+
are N-D, their batch dimensions must match. If exactly one of ``a`` or ``b`` is N-D, the other operand is
|
|
543
|
+
broadcast.
|
|
544
|
+
* The operand for the matrix addition ``c`` must be a matrix of shape (M, N), or the batched equivalent
|
|
545
|
+
(..., M, N). Here M and N are the dimensions of the result of the matrix multiplication. If batch dimensions
|
|
546
|
+
are not present, ``c`` is broadcast across batches as needed. If the operation is in-place, ``c`` cannot be
|
|
547
|
+
broadcast since it must be large enough to hold the result.
|
|
548
|
+
""".strip(),
|
|
549
|
+
}
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
@utils.docstring_decorator(SPARSE_MM_DOCUMENTATION, skip_missing=False)
|
|
554
|
+
class Matmul:
|
|
555
|
+
"""
|
|
556
|
+
Create a stateful object encapsulating the specified matrix multiplication computation, which is
|
|
557
|
+
one of :math:`\\alpha \\, \\operatorname{{op}}_h(a) \\, @ \\, \\operatorname{{op}}_h(b) + \\beta \\, c`
|
|
558
|
+
or :math:`\\operatorname{{prolog}}_a(\\operatorname{{op}}_t(a)) \\, @ \\,
|
|
559
|
+
\\operatorname{{prolog}}_b(\\operatorname{{op}}_t(b)) + c`, along with
|
|
560
|
+
the required resources to perform the operation.
|
|
561
|
+
The :math:`\\operatorname{{op}}_h` and :math:`\\operatorname{{op}}_t`
|
|
562
|
+
operators optionally specify transpose/hermitian or transpose operations respectively
|
|
563
|
+
via the ``qualifiers`` argument.
|
|
564
|
+
|
|
565
|
+
.. note::
|
|
566
|
+
The complex conjugate operation is mutually exclusive with prolog since it can be
|
|
567
|
+
absorbed into the prolog.
|
|
568
|
+
|
|
569
|
+
.. note::
|
|
570
|
+
Currently only in-place sparse matrix multiplication is supported, so operand ``c``
|
|
571
|
+
must be provided. This restriction will be removed in a future release.
|
|
572
|
+
|
|
573
|
+
A stateful object can be used to amortize the cost of preparation (planning in the
|
|
574
|
+
case of matrix multiplication) across multiple executions (also see the
|
|
575
|
+
:ref:`Stateful APIs <host api types>` section). Prolog operations can be specified
|
|
576
|
+
in :meth:`plan`.
|
|
577
|
+
|
|
578
|
+
The function-form API :func:`matmul` is a convenient alternative to using stateful
|
|
579
|
+
objects for *single* use (the user needs to perform just one matrix multiplication, for
|
|
580
|
+
example), in which case there is no possibility of amortizing preparatory costs. The
|
|
581
|
+
function-form APIs are just convenience wrappers around the stateful object APIs.
|
|
582
|
+
|
|
583
|
+
Using the stateful object typically involves the following steps:
|
|
584
|
+
|
|
585
|
+
1. **Problem Specification**: Initialize the object with a defined operation and
|
|
586
|
+
options.
|
|
587
|
+
2. **Preparation**: Use :meth:`plan` to determine the best algorithmic implementation
|
|
588
|
+
for this specific matrix multiplication operation.
|
|
589
|
+
3. **Execution**: Perform the matrix multiplication computation with :meth:`execute`.
|
|
590
|
+
4. **Resource Management**: Ensure all resources are released either by explicitly
|
|
591
|
+
calling :meth:`free` or by managing the stateful object within a context manager.
|
|
592
|
+
|
|
593
|
+
Detailed information on what's happening in the various phases described above can be
|
|
594
|
+
obtained by passing in a :class:`logging.Logger` object to :class:`MatmulOptions` or by
|
|
595
|
+
setting the appropriate options in the root logger object, which is used by default:
|
|
596
|
+
|
|
597
|
+
>>> import logging
|
|
598
|
+
>>> logging.basicConfig(
|
|
599
|
+
... level=logging.INFO,
|
|
600
|
+
... format="%(asctime)s %(levelname)-8s %(message)s",
|
|
601
|
+
... datefmt="%m-%d %H:%M:%S",
|
|
602
|
+
... )
|
|
603
|
+
|
|
604
|
+
A user can select the desired logging level and, in general, take advantage of all of
|
|
605
|
+
the functionality offered by the Python `logging` module.
|
|
606
|
+
|
|
607
|
+
Args:
|
|
608
|
+
a: {a}
|
|
609
|
+
|
|
610
|
+
b: {b}
|
|
611
|
+
|
|
612
|
+
c: {c}
|
|
613
|
+
|
|
614
|
+
alpha: {alpha}
|
|
615
|
+
|
|
616
|
+
beta: {beta}
|
|
617
|
+
|
|
618
|
+
qualifiers: {qualifiers}
|
|
619
|
+
|
|
620
|
+
options: {options}
|
|
621
|
+
|
|
622
|
+
execution: {execution}
|
|
623
|
+
|
|
624
|
+
stream: {stream}
|
|
625
|
+
|
|
626
|
+
Semantics:
|
|
627
|
+
{semantics}
|
|
628
|
+
|
|
629
|
+
.. seealso::
|
|
630
|
+
:class:`MatmulOptions`, :class:`ExecutionCUDA`, :meth:`plan`,
|
|
631
|
+
:meth:`release_operands`, :meth:`reset_operands`,
|
|
632
|
+
:meth:`reset_operands_unchecked`, :meth:`execute`, :func:`matmul`.
|
|
633
|
+
|
|
634
|
+
Examples:
|
|
635
|
+
|
|
636
|
+
>>> import cupy as cp
|
|
637
|
+
>>> import cupyx.scipy.sparse as sp
|
|
638
|
+
>>> import nvmath
|
|
639
|
+
|
|
640
|
+
The problem parameters.
|
|
641
|
+
|
|
642
|
+
>>> m, n, k = 4, 2, 4
|
|
643
|
+
>>> index_type, dtype = "int32", "float64"
|
|
644
|
+
|
|
645
|
+
Create a sparse float64 ndarray in CSR format on the GPU.
|
|
646
|
+
|
|
647
|
+
>>> crow_indices = cp.array([0, 2, 4, 6, 8], dtype=index_type)
|
|
648
|
+
>>> col_indices = cp.array([0, 1, 0, 1, 2, 3, 2, 3], dtype=index_type)
|
|
649
|
+
>>> values = cp.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=dtype)
|
|
650
|
+
|
|
651
|
+
>>> a = sp.csr_matrix((values, col_indices, crow_indices), shape=(m, k))
|
|
652
|
+
|
|
653
|
+
Create the dense operands ``b`` and ``c``.
|
|
654
|
+
|
|
655
|
+
>>> b = cp.ones((k, n), dtype=dtype)
|
|
656
|
+
>>> c = cp.zeros((m, n), dtype=dtype)
|
|
657
|
+
|
|
658
|
+
We will define a sparse matrix multiplication (SpMM) operation
|
|
659
|
+
:math:`c := \\alpha \\, a \\, @ \\, b + \\beta \\, c` using the generic sparse matrix
|
|
660
|
+
multiplication interface.
|
|
661
|
+
|
|
662
|
+
>>> mm = nvmath.sparse.Matmul(a, b, c=c, alpha=1.2, beta=1.0)
|
|
663
|
+
|
|
664
|
+
Options can be provided above to control the behavior of the operation using the
|
|
665
|
+
`options` argument (see :class:`MatmulOptions`).
|
|
666
|
+
|
|
667
|
+
Next, plan the operation. Optionally, user-defined prologs can be provided.
|
|
668
|
+
|
|
669
|
+
>>> mm.plan()
|
|
670
|
+
|
|
671
|
+
Now execute the matrix multiplication. The result ``r`` is also a CuPy ndarray, and
|
|
672
|
+
specifically in this example, it is the same as operand ``c`` since the operation
|
|
673
|
+
is in-place.
|
|
674
|
+
|
|
675
|
+
>>> r = mm.execute()
|
|
676
|
+
>>> assert r is c, "Error: the operation is not in-place."
|
|
677
|
+
|
|
678
|
+
Finally, free the object's resources. To avoid having to explicitly make this
|
|
679
|
+
call, it's recommended to use the Matmul object as a context manager as shown below,
|
|
680
|
+
if possible.
|
|
681
|
+
|
|
682
|
+
>>> mm.free()
|
|
683
|
+
|
|
684
|
+
.. note:: All :class:`Matmul` methods execute on the package current stream by default.
|
|
685
|
+
Alternatively, the ``stream`` argument can be used to run a method on a specified
|
|
686
|
+
stream.
|
|
687
|
+
|
|
688
|
+
Let's now look at the SpMM :math:`c := a.T \\, @ \\, b.H + c` with PyTorch sparse/dense
|
|
689
|
+
tensors on the CPU.
|
|
690
|
+
|
|
691
|
+
>>> import numpy as np
|
|
692
|
+
>>> import torch
|
|
693
|
+
|
|
694
|
+
>>> m, n, k = 2, 2, 2
|
|
695
|
+
>>> index_type, dtype = torch.int32, torch.float32
|
|
696
|
+
|
|
697
|
+
Create and coalesce a sparse COO tensor on the CPU.
|
|
698
|
+
|
|
699
|
+
>>> indices = torch.tensor([[0, 1], [0, 1]], dtype=index_type)
|
|
700
|
+
>>> values = torch.tensor([2.0, 4.0], dtype=dtype) + 1.0j
|
|
701
|
+
>>> a = torch.sparse_coo_tensor(indices, values, (k, m))
|
|
702
|
+
>>> a = a.coalesce()
|
|
703
|
+
|
|
704
|
+
Create the dense operands ``b`` and ``c``.
|
|
705
|
+
|
|
706
|
+
>>> b = torch.ones(n, k, dtype=dtype) + 1.0j
|
|
707
|
+
>>> c = torch.zeros(m, n, dtype=dtype) + 0.0j
|
|
708
|
+
|
|
709
|
+
The transpose/hermitian operations on ``a`` and ``b`` will be specified using
|
|
710
|
+
:ref:`qualifiers <matrix-tensor-qualifiers>`.
|
|
711
|
+
|
|
712
|
+
>>> qualifiers = np.zeros((3,), dtype=nvmath.sparse.matmul_matrix_qualifiers_dtype)
|
|
713
|
+
>>> qualifiers[0]["is_transpose"] = 1
|
|
714
|
+
>>> qualifiers[1]["is_transpose"] = qualifiers[1]["is_conjugate"] = 1
|
|
715
|
+
|
|
716
|
+
Create the SpMM operation and use it as a context manager.
|
|
717
|
+
|
|
718
|
+
>>> with nvmath.sparse.Matmul(a, b, c=c, qualifiers=qualifiers) as mm:
|
|
719
|
+
... # Plan the operation.
|
|
720
|
+
... mm.plan()
|
|
721
|
+
...
|
|
722
|
+
... # Execute it.
|
|
723
|
+
... r = mm.execute()
|
|
724
|
+
|
|
725
|
+
All the resources used by the object are released at the end of the block.
|
|
726
|
+
|
|
727
|
+
Finally, let's see how to perform a matrix multiplication on a novel format
|
|
728
|
+
using UST operands.
|
|
729
|
+
|
|
730
|
+
>>> device_id = 0
|
|
731
|
+
>>> dtype = torch.float64
|
|
732
|
+
>>> m, n, k = 3, 2, 8
|
|
733
|
+
|
|
734
|
+
Create a dense torch tensor and view it as UST.
|
|
735
|
+
|
|
736
|
+
>>> a = torch.tensor(
|
|
737
|
+
... [[1, 0, 0, 0, 0, 0, 0, 2], [0, 0, 3, 4, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 5]],
|
|
738
|
+
... dtype=dtype,
|
|
739
|
+
... device=device_id,
|
|
740
|
+
... )
|
|
741
|
+
>>> a = nvmath.sparse.ust.Tensor.from_package(a)
|
|
742
|
+
|
|
743
|
+
Create a delta-compression format, using the predefined type or directly with
|
|
744
|
+
the UST DSL.
|
|
745
|
+
|
|
746
|
+
>>> delta = nvmath.sparse.ust.NamedFormats.DELTA(2)
|
|
747
|
+
|
|
748
|
+
Convert the torch tensor to the delta-compressed format.
|
|
749
|
+
|
|
750
|
+
>>> a = a.convert(tensor_format=delta)
|
|
751
|
+
|
|
752
|
+
Create dense operands ``b`` and ``c`` for the SpMM.
|
|
753
|
+
|
|
754
|
+
>>> b = torch.ones(k, n, dtype=dtype, device=device_id)
|
|
755
|
+
>>> b = nvmath.sparse.ust.Tensor.from_package(b)
|
|
756
|
+
>>> c = torch.zeros(m, n, dtype=dtype, device=device_id)
|
|
757
|
+
>>> c = nvmath.sparse.ust.Tensor.from_package(c)
|
|
758
|
+
|
|
759
|
+
Create, plan, and execute the SpMM operation.
|
|
760
|
+
|
|
761
|
+
>>> with nvmath.sparse.Matmul(a, b, c=c) as mm:
|
|
762
|
+
... # Plan the SpMM.
|
|
763
|
+
... mm.plan()
|
|
764
|
+
...
|
|
765
|
+
... # Execute it.
|
|
766
|
+
... r = mm.execute()
|
|
767
|
+
|
|
768
|
+
View the UST result as a torch tensor.
|
|
769
|
+
|
|
770
|
+
>>> r = r.to_package()
|
|
771
|
+
|
|
772
|
+
Examples sampling the vast space of possibilities can be found in the
|
|
773
|
+
`nvmath/examples/sparse/generic/matmul
|
|
774
|
+
<https://github.com/NVIDIA/nvmath-python/tree/main/examples/sparse/generic/matmul>`_
|
|
775
|
+
directory.
|
|
776
|
+
""" # noqa: W505
|
|
777
|
+
|
|
778
|
+
def __init__(
|
|
779
|
+
self,
|
|
780
|
+
a,
|
|
781
|
+
b,
|
|
782
|
+
/,
|
|
783
|
+
c=None,
|
|
784
|
+
*,
|
|
785
|
+
alpha=None,
|
|
786
|
+
beta=None,
|
|
787
|
+
qualifiers=None,
|
|
788
|
+
options: MatmulOptions | dict[str, Any] | None = None,
|
|
789
|
+
execution: ExecutionCUDA | None = None,
|
|
790
|
+
stream: utils.AnyStream | int | None = None,
|
|
791
|
+
):
|
|
792
|
+
options = utils.check_or_create_options(MatmulOptions, options, "matrix multiplication options")
|
|
793
|
+
assert options is not None
|
|
794
|
+
self.options = options
|
|
795
|
+
|
|
796
|
+
self.execution = execution = utils.check_or_create_options(
|
|
797
|
+
ExecutionCUDA, execution, "matrix multiplication execution options"
|
|
798
|
+
)
|
|
799
|
+
|
|
800
|
+
if c is None:
|
|
801
|
+
raise NotImplementedError("The operand C is currently required since only inplace SpMM is supported.")
|
|
802
|
+
|
|
803
|
+
self.logger = options.logger if options.logger is not None else logging.getLogger()
|
|
804
|
+
|
|
805
|
+
# Wrap operand 'a'.
|
|
806
|
+
try:
|
|
807
|
+
self.a = a = sp_utils.wrap_sparse_operands(a)
|
|
808
|
+
except Exception as e:
|
|
809
|
+
raise TypeError(
|
|
810
|
+
"""The operand 'a' must be an N-D sparse array/tensor from one of the supported packages: nvmath.sparse.ust,
|
|
811
|
+
CuPy, PyTorch, or SciPy."""
|
|
812
|
+
) from e
|
|
813
|
+
|
|
814
|
+
self.logger.info(f"The sparse format for operand A is '{a.format_name}'.")
|
|
815
|
+
|
|
816
|
+
# Note the sparse wrapper type for the fast path in reset_operands_unchecked().
|
|
817
|
+
self.a_wrapper_type = self.a.__class__
|
|
818
|
+
self.a_attr_name_map = self.a.attr_name_map
|
|
819
|
+
self.logger.debug(f"The sparse wrapper type for operand A is '{self.a_wrapper_type}'.")
|
|
820
|
+
|
|
821
|
+
if sp_utils.sparse_or_dense(b) != "dense":
|
|
822
|
+
raise ValueError(f"The operand `b` {type(b)} must be dense.")
|
|
823
|
+
|
|
824
|
+
self.ust_operands = []
|
|
825
|
+
# TODO: create unified sparse-dense abstraction for UST later. For now we'll
|
|
826
|
+
# extract the dense tensor from the dense UST for `b` and `c`.
|
|
827
|
+
if (b_package := utils.infer_object_package(b)) == "nvmath":
|
|
828
|
+
self.ust_operands.append(b)
|
|
829
|
+
# Though b._wrapped_operand is the TensorHolder
|
|
830
|
+
# we look for, create a new one, so that
|
|
831
|
+
# matmul is free to modify it in place if needed.
|
|
832
|
+
self.b = b = tensor_wrapper.wrap_operand(b._wrapped_operand.tensor)
|
|
833
|
+
else:
|
|
834
|
+
# Wrap operand 'b' (currently limit to dense operand).
|
|
835
|
+
self.b = b = tensor_wrapper.wrap_operand(b) # type:ignore
|
|
836
|
+
|
|
837
|
+
self.operands = operands = [a, b]
|
|
838
|
+
|
|
839
|
+
# For now, assume 'c' is provided.
|
|
840
|
+
if sp_utils.sparse_or_dense(c) != "dense":
|
|
841
|
+
raise ValueError(f"The operand `c` {type(c)} must be dense.")
|
|
842
|
+
|
|
843
|
+
if (c_package := utils.infer_object_package(c)) == "nvmath":
|
|
844
|
+
self.ust_operands.append(c)
|
|
845
|
+
# Though c._wrapped_operand is the TensorHolder
|
|
846
|
+
# we look for, create a new one, so that
|
|
847
|
+
# matmul is free to modify it in place if needed.
|
|
848
|
+
self.c = c = tensor_wrapper.wrap_operand(c._wrapped_operand.tensor)
|
|
849
|
+
else:
|
|
850
|
+
self.c = c = tensor_wrapper.wrap_operand(c)
|
|
851
|
+
operands.append(c)
|
|
852
|
+
|
|
853
|
+
# The number of operands, since `c` is optional. If `c` is not provided,
|
|
854
|
+
# set Beta = 0.
|
|
855
|
+
self.num_operands = len(operands)
|
|
856
|
+
|
|
857
|
+
if self.num_operands == 3 and b_package != c_package:
|
|
858
|
+
raise TypeError(f"The operands 'b' ({b_package}) and 'c' ({c_package}) do NOT belong to the same package.")
|
|
859
|
+
|
|
860
|
+
self.logger.info(f"The data type of operand A is '{a.dtype}', and that of operand B is '{b.dtype}'.")
|
|
861
|
+
if c is not None:
|
|
862
|
+
self.logger.info(f"The data type of operand C is '{c.dtype}'.")
|
|
863
|
+
self.logger.info(f"The index type of operand A is '{a.index_type}'.")
|
|
864
|
+
|
|
865
|
+
# Currently, only SpMM is supported and so the index type is obtained from `a`.
|
|
866
|
+
self.index_type_name = a.index_type
|
|
867
|
+
self.index_type = typemaps.NAME_TO_DATA_TYPE[a.index_type]
|
|
868
|
+
if self.index_type_name not in VALID_INDEX_TYPES:
|
|
869
|
+
raise TypeError(
|
|
870
|
+
f"The index type {self.index_type_name} is not supported. The supported index types are {VALID_INDEX_TYPES}."
|
|
871
|
+
)
|
|
872
|
+
|
|
873
|
+
# Currently we require `c` and only inplace update of `c` is supported.
|
|
874
|
+
assert self.num_operands == 3, "Internal Error."
|
|
875
|
+
self.inplace = True # Currently only inplace update of `c` is supported.
|
|
876
|
+
if self.inplace:
|
|
877
|
+
self.logger.info("The operation will be performed inplace with operand C.")
|
|
878
|
+
|
|
879
|
+
# Determine the data types for a and b.
|
|
880
|
+
self.a_dtype = typemaps.NAME_TO_DATA_TYPE[a.dtype]
|
|
881
|
+
self.b_dtype = typemaps.NAME_TO_DATA_TYPE[b.dtype]
|
|
882
|
+
self.a_dtype_name = a.dtype
|
|
883
|
+
self.b_dtype_name = b.dtype
|
|
884
|
+
|
|
885
|
+
self.is_complex = "complex" in self.a_dtype_name or "complex" in self.b_dtype_name
|
|
886
|
+
|
|
887
|
+
# Determine the data type for c.
|
|
888
|
+
if self.num_operands == 3:
|
|
889
|
+
self.c_dtype = typemaps.NAME_TO_DATA_TYPE[c.dtype]
|
|
890
|
+
elif self.num_operands == 2:
|
|
891
|
+
self.c_dtype = self.a_dtype
|
|
892
|
+
self.c_dtype_name = typemaps.DATA_TYPE_TO_NAME[self.c_dtype]
|
|
893
|
+
|
|
894
|
+
# The common (semantic) checks for the problem such as compatibility,
|
|
895
|
+
# tileability etc.
|
|
896
|
+
value_type_names = {self.a_dtype_name, self.b_dtype_name, self.c_dtype_name}
|
|
897
|
+
if len(value_type_names) != 1:
|
|
898
|
+
raise NotImplementedError(
|
|
899
|
+
f"The dtype for the operands {self.a_dtype_name}, {self.b_dtype_name}, and \
|
|
900
|
+
{self.c_dtype_name} don't match. Mixing operands of different precisions is currently not supported."
|
|
901
|
+
)
|
|
902
|
+
self.value_type_name = value_type_names.pop()
|
|
903
|
+
|
|
904
|
+
if self.value_type_name not in VALID_DTYPES:
|
|
905
|
+
raise TypeError(
|
|
906
|
+
f"The dtype (value type) {self.value_type_name} is not supported. The supported dtypes are {VALID_DTYPES}."
|
|
907
|
+
)
|
|
908
|
+
self.value_type = typemaps.NAME_TO_DATA_TYPE[self.a_dtype_name]
|
|
909
|
+
self.regular_precision = _is_regular_precision(self.value_type_name)
|
|
910
|
+
|
|
911
|
+
# Set compute type.
|
|
912
|
+
if self.options.compute_type is None:
|
|
913
|
+
default_compute_type = ComputeType.CUDA_C_32F if self.is_complex else ComputeType.CUDA_R_32F
|
|
914
|
+
self.compute_type = self.a_dtype if self.regular_precision else default_compute_type
|
|
915
|
+
else:
|
|
916
|
+
try:
|
|
917
|
+
self.compute_type = ComputeType(self.options.compute_type)
|
|
918
|
+
except Exception as e:
|
|
919
|
+
message = f"The specified compute type {self.options.compute_type} is not a valid compute type."
|
|
920
|
+
raise TypeError(message) from e
|
|
921
|
+
self.compute_type_name = typemaps.DATA_TYPE_TO_NAME[self.compute_type]
|
|
922
|
+
|
|
923
|
+
if self.is_complex and "complex" not in self.compute_type_name:
|
|
924
|
+
raise ValueError(f"The specified compute type {self.compute_type_name} is complex.")
|
|
925
|
+
if not self.is_complex and "complex" in self.compute_type_name:
|
|
926
|
+
raise ValueError(
|
|
927
|
+
f"The specified compute type {self.compute_type_name} is complex, while the \
|
|
928
|
+
operands' dtype {self.value_type} is real."
|
|
929
|
+
)
|
|
930
|
+
|
|
931
|
+
self.logger.info(f"The compute type for the matrix multiplication is {self.compute_type_name}.")
|
|
932
|
+
|
|
933
|
+
# Set alpha and beta: note that the currently-supported compute types are valid
|
|
934
|
+
# NumPy dtypes.
|
|
935
|
+
self.alpha = np.zeros((1,), dtype=self.compute_type_name)
|
|
936
|
+
try:
|
|
937
|
+
self.alpha[0] = alpha if alpha is not None else 1
|
|
938
|
+
except (ValueError, TypeError) as e:
|
|
939
|
+
raise ValueError(f"The value provided for alpha {alpha} is not convertible to dtype '{self.alpha.dtype}'.") from e
|
|
940
|
+
|
|
941
|
+
self.beta = np.zeros((1,), dtype=self.compute_type_name)
|
|
942
|
+
if beta is not None and self.num_operands == 2:
|
|
943
|
+
self.logger.warning(f"Matmul: The provided beta value {beta} is ignored since operand C is not specified.")
|
|
944
|
+
try:
|
|
945
|
+
default_beta = 1.0 if self.inplace else 0.0
|
|
946
|
+
self.beta[0] = beta if beta is not None and self.num_operands == 3 else default_beta
|
|
947
|
+
except (ValueError, TypeError) as e:
|
|
948
|
+
raise ValueError(f"The value provided for beta {beta} is not convertible to dtype '{self.beta.dtype}'.") from e
|
|
949
|
+
|
|
950
|
+
# Check for compatible operand packages.
|
|
951
|
+
self.sparse_package = utils.infer_object_package(self.a.tensor)
|
|
952
|
+
|
|
953
|
+
self.dense_package: str
|
|
954
|
+
if self.ust_operands:
|
|
955
|
+
orig_dense_package = {utils.infer_object_package(o) for o in self.ust_operands}
|
|
956
|
+
if len(orig_dense_package) != 1:
|
|
957
|
+
raise TypeError(f"The dense operands `b` and `c` don't belong to the same package: {orig_dense_package}.")
|
|
958
|
+
orig_dense_package = orig_dense_package.pop() # type: ignore[assignment]
|
|
959
|
+
# Use the wrapped operands to get the dense package for dense UST.
|
|
960
|
+
self.dense_package = utils.get_operands_package(operands[1:])
|
|
961
|
+
else:
|
|
962
|
+
self.dense_package = orig_dense_package = utils.get_operands_package(operands[1:]) # type: ignore
|
|
963
|
+
|
|
964
|
+
if (self.sparse_package, orig_dense_package) not in cusparse_utils.COMPATIBLE_SP_DN_PACKAGES:
|
|
965
|
+
raise TypeError(
|
|
966
|
+
f"""The sparse operand package {self.sparse_package} and dense operand(s) package {orig_dense_package} are
|
|
967
|
+
not part of the compatible choices: {cusparse_utils.COMPATIBLE_SP_DN_PACKAGES}."""
|
|
968
|
+
)
|
|
969
|
+
|
|
970
|
+
a_dense_package = self.a.dense_tensorholder_type.name
|
|
971
|
+
addendum = ""
|
|
972
|
+
if self.sparse_package == "nvmath":
|
|
973
|
+
addendum = f", with the dense representation package {a_dense_package}"
|
|
974
|
+
self.logger.info(f"The sparse operand package is {self.sparse_package}{addendum}.")
|
|
975
|
+
self.logger.info(f"The dense operand(s) package is {orig_dense_package}{addendum}.")
|
|
976
|
+
|
|
977
|
+
# The dense package must match also match the dense package used to represent the
|
|
978
|
+
# sparse tensor to correctly handle stream ordering etc.
|
|
979
|
+
if self.sparse_package == "nvmath" and a_dense_package != self.dense_package:
|
|
980
|
+
raise TypeError(
|
|
981
|
+
f"The UST operands `a` uses a different representation package ({a_dense_package}) from that \
|
|
982
|
+
of the other operand(s) ({self.dense_package})."
|
|
983
|
+
)
|
|
984
|
+
|
|
985
|
+
# Memory space.
|
|
986
|
+
self.memory_space = "cuda"
|
|
987
|
+
self.device_id = utils.get_operands_device_id(operands)
|
|
988
|
+
if self.device_id == "cpu":
|
|
989
|
+
if self.dense_package == "numpy":
|
|
990
|
+
self.dense_package = "cuda"
|
|
991
|
+
self.memory_space = "cpu"
|
|
992
|
+
self.device_id = self.execution.device_id # type: ignore[union-attr]
|
|
993
|
+
|
|
994
|
+
# Execution space.
|
|
995
|
+
self.execution_space = "cuda"
|
|
996
|
+
self.logger.info(
|
|
997
|
+
f"The input operands' memory space is {self.memory_space}, and the execution space is on device {self.device_id}."
|
|
998
|
+
)
|
|
999
|
+
|
|
1000
|
+
# Set stream.
|
|
1001
|
+
stream_holder = utils.get_or_create_stream(self.device_id, stream, self.dense_package)
|
|
1002
|
+
self.logger.info(f"The specified stream for the Matmul ctor is {stream_holder.obj}.")
|
|
1003
|
+
|
|
1004
|
+
# Copy operands to device if needed.
|
|
1005
|
+
self.cpu_c_ref = None
|
|
1006
|
+
if self.memory_space == "cpu":
|
|
1007
|
+
if self.inplace:
|
|
1008
|
+
self.cpu_c_ref = self.ust_operands[1] if self.ust_operands else self.operands[2]
|
|
1009
|
+
|
|
1010
|
+
# Note that UST *code generation* is currently not supported for CPU memory
|
|
1011
|
+
# space while dispatch is, it's checked later in plan().
|
|
1012
|
+
|
|
1013
|
+
self.operands = [o.to(self.device_id, stream_holder) for o in self.operands]
|
|
1014
|
+
|
|
1015
|
+
# Update the operand aliases to point to the mirrors.
|
|
1016
|
+
if self.num_operands == 2:
|
|
1017
|
+
self.a, self.b = self.operands
|
|
1018
|
+
else:
|
|
1019
|
+
self.a, self.b, self.c = self.operands
|
|
1020
|
+
|
|
1021
|
+
# Replace the CPU UST operands with new device UST by *creating* them from the
|
|
1022
|
+
# dense operands.
|
|
1023
|
+
if self.ust_operands:
|
|
1024
|
+
self.ust_operands[0] = UST.from_package(self.b.tensor, stream_holder.external)
|
|
1025
|
+
if self.num_operands == 3:
|
|
1026
|
+
self.ust_operands[1] = UST.from_package(self.c.tensor, stream_holder.external)
|
|
1027
|
+
|
|
1028
|
+
# Set qualifiers.
|
|
1029
|
+
self.qualifiers = qualifiers if qualifiers is not None else np.zeros((3,), dtype=matrix_qualifiers_dtype)
|
|
1030
|
+
if self.qualifiers.dtype != matrix_qualifiers_dtype:
|
|
1031
|
+
raise ValueError(
|
|
1032
|
+
"The qualifiers must be specified as a NumPy array of length 3 corresponding to the operands A, B, and "
|
|
1033
|
+
"C of type 'matrix_qualifiers_dtype'."
|
|
1034
|
+
)
|
|
1035
|
+
|
|
1036
|
+
# Set qualifiers based on torch lazy conjugation flag if not provided.
|
|
1037
|
+
self.qualifiers[1]["is_conjugate"] = self.qualifiers[1]["is_conjugate"] ^ self.operands[1].is_conjugate
|
|
1038
|
+
if self.num_operands == 3:
|
|
1039
|
+
self.qualifiers[2]["is_conjugate"] = self.qualifiers[2]["is_conjugate"] ^ self.operands[2].is_conjugate
|
|
1040
|
+
if self.qualifiers[2]["is_conjugate"]:
|
|
1041
|
+
raise NotImplementedError("The conjugate flag is currently not supported for operand C.")
|
|
1042
|
+
if self.qualifiers[2]["is_transpose"]:
|
|
1043
|
+
raise NotImplementedError("The transpose flag is currently not supported for operand C.")
|
|
1044
|
+
|
|
1045
|
+
# The SpMM traits.
|
|
1046
|
+
self.spmm_traits = get_spmm_traits(a, b, c, qualifiers=self.qualifiers, inplace=self.inplace, logger=self.logger)
|
|
1047
|
+
|
|
1048
|
+
# Set blocking or non-blocking behavior.
|
|
1049
|
+
self.blocking = self.options.blocking is True or self.memory_space == "cpu"
|
|
1050
|
+
if self.blocking:
|
|
1051
|
+
self.call_prologue = "This call is blocking and will return only after the operation is complete."
|
|
1052
|
+
else:
|
|
1053
|
+
self.call_prologue = (
|
|
1054
|
+
"This call is non-blocking and will return immediately after the operation is launched on the device."
|
|
1055
|
+
)
|
|
1056
|
+
|
|
1057
|
+
# The result class is that of the wrapped dense device operand 'b'.
|
|
1058
|
+
self.result_class = self.operands[1].__class__
|
|
1059
|
+
|
|
1060
|
+
# Set memory allocator.
|
|
1061
|
+
self.allocator = (
|
|
1062
|
+
options.allocator
|
|
1063
|
+
if options.allocator is not None
|
|
1064
|
+
else memory._MEMORY_MANAGER[self.dense_package](self.device_id, self.logger)
|
|
1065
|
+
)
|
|
1066
|
+
|
|
1067
|
+
# Set memory limit.
|
|
1068
|
+
self.memory_limit = utils.get_memory_limit_from_device_id(self.options.memory_limit, self.device_id)
|
|
1069
|
+
self.logger.info(f"The memory limit is {formatters.MemoryStr(self.memory_limit)}.")
|
|
1070
|
+
|
|
1071
|
+
# Determine provisional support for dispatch and codegen here, finalize later
|
|
1072
|
+
# in plan.
|
|
1073
|
+
if self.options.codegen:
|
|
1074
|
+
self.logger.info(
|
|
1075
|
+
"The Matmul operation will use a custom kernel if possible or raise an \
|
|
1076
|
+
error since the codegen option is True."
|
|
1077
|
+
)
|
|
1078
|
+
|
|
1079
|
+
if (
|
|
1080
|
+
sp_utils.sparse_or_dense(a.tensor) == "sparse"
|
|
1081
|
+
and self.index_type_name in CUSPARSE_INDEX_TYPES
|
|
1082
|
+
and self.value_type_name in CUSPARSE_DTYPES
|
|
1083
|
+
and a.format_name in cusparse_utils.SUPPORTED_NAMED_FORMATS
|
|
1084
|
+
):
|
|
1085
|
+
self.dispatch_init = BackendSupport.PROVISIONALLY_SUPPORTED
|
|
1086
|
+
else:
|
|
1087
|
+
self.dispatch_init = BackendSupport.NOT_SUPPORTED
|
|
1088
|
+
self.logger.debug(f"The dispatch viability of the Matmul operation is {self.dispatch_init.name}.")
|
|
1089
|
+
|
|
1090
|
+
if self.options.codegen and self.sparse_package != "nvmath":
|
|
1091
|
+
raise NotImplementedError("The code generation path is only available for UST operands.")
|
|
1092
|
+
|
|
1093
|
+
if (
|
|
1094
|
+
self.index_type_name in CODEGEN_INDEX_TYPES
|
|
1095
|
+
and self.value_type_name in CODEGEN_DTYPES
|
|
1096
|
+
and self.sparse_package == "nvmath"
|
|
1097
|
+
and a.format_name in CODEGEN_FORMATS
|
|
1098
|
+
and not (self.spmm_traits.a_layout_traits.batch_broadcast and len(self.spmm_traits.b_layout_traits.batch_shape) > 0)
|
|
1099
|
+
and a.num_dimensions <= 4
|
|
1100
|
+
):
|
|
1101
|
+
self.codegen_init = BackendSupport.PROVISIONALLY_SUPPORTED
|
|
1102
|
+
else:
|
|
1103
|
+
self.codegen_init = BackendSupport.NOT_SUPPORTED
|
|
1104
|
+
self.logger.debug(f"The codegen viability of the Matmul operation is {self.codegen_init.name}.")
|
|
1105
|
+
|
|
1106
|
+
if self.dispatch_init == BackendSupport.NOT_SUPPORTED and self.codegen_init == BackendSupport.NOT_SUPPORTED:
|
|
1107
|
+
raise NotImplementedError("The matrix multiplication is not currently supported for the specified operands.")
|
|
1108
|
+
|
|
1109
|
+
# The finalization of dispatch vs codegen, which we'll determine later in plan().
|
|
1110
|
+
self.dispatch: BackendSupport | None = None
|
|
1111
|
+
self.codegen: BackendSupport | None = None
|
|
1112
|
+
|
|
1113
|
+
# The actual API to use, which we'll determine later in plan().
|
|
1114
|
+
self.api = Api.NONE
|
|
1115
|
+
|
|
1116
|
+
# Capture operand extents, strides, and lazy conjugation for consistency check
|
|
1117
|
+
# when resetting operands.
|
|
1118
|
+
self.operand_extents = tuple(o.shape for o in self.operands)
|
|
1119
|
+
self.operand_strides = (None,) + tuple(o.strides for o in self.operands[1:])
|
|
1120
|
+
|
|
1121
|
+
# PyTorch currently doesn't support lazy conjugation for sparse tensors.
|
|
1122
|
+
assert not self.operands[0].values.is_conjugate, "Internal error."
|
|
1123
|
+
self.lazy_conjugation = (None, self.operands[1].is_conjugate, False)
|
|
1124
|
+
|
|
1125
|
+
# Library attributes.
|
|
1126
|
+
self.handle = None
|
|
1127
|
+
self.own_handle: bool | None = None
|
|
1128
|
+
|
|
1129
|
+
# Plan attributes.
|
|
1130
|
+
self.a_ifc = None # type: ignore
|
|
1131
|
+
self.b_ifc = None # type: ignore
|
|
1132
|
+
self.c_ifc = None # type: ignore
|
|
1133
|
+
# For dispatch to the SpMMOp API.
|
|
1134
|
+
self.mm_op_plan = None
|
|
1135
|
+
|
|
1136
|
+
self.mm_planned = False
|
|
1137
|
+
|
|
1138
|
+
# Workspace lifecycle is managed by `Workspace` (allocate-on-demand,
|
|
1139
|
+
# reuse, mid-call release, exception cleanup, stream-ordered free).
|
|
1140
|
+
# Only the cuSparse dispatch paths (Api.MM, Api.MM_OP) use it; the
|
|
1141
|
+
# codegen path (Api.CODEGEN) constructs the instance but never plans
|
|
1142
|
+
# a size or enters the allocate_perhaps context.
|
|
1143
|
+
self.workspace = Workspace(
|
|
1144
|
+
self.allocator,
|
|
1145
|
+
self.logger,
|
|
1146
|
+
label="cusparse workspace",
|
|
1147
|
+
device_id=self.device_id,
|
|
1148
|
+
)
|
|
1149
|
+
|
|
1150
|
+
# Last event recorded by cuda_call_ctx; consumed on workspace release
|
|
1151
|
+
# so the free is ordered after the compute that touched the buffer.
|
|
1152
|
+
self.last_compute_event: cc.Event | None = None
|
|
1153
|
+
|
|
1154
|
+
# Track whether the operands have been released.
|
|
1155
|
+
self.operands_released = False
|
|
1156
|
+
|
|
1157
|
+
self.valid_state = True
|
|
1158
|
+
self.logger.info("The Matmul operation has been created.")
|
|
1159
|
+
|
|
1160
|
+
def __enter__(self):
|
|
1161
|
+
return self
|
|
1162
|
+
|
|
1163
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
1164
|
+
self.free()
|
|
1165
|
+
|
|
1166
|
+
def _check_valid_matmul(self, *args, **kwargs):
|
|
1167
|
+
"""
|
|
1168
|
+
Check if the Matmul object is alive and well.
|
|
1169
|
+
"""
|
|
1170
|
+
if not self.valid_state:
|
|
1171
|
+
raise InvalidMatmulState("The Matmul object cannot be used after resources are free'd")
|
|
1172
|
+
|
|
1173
|
+
def _check_valid_operands(self, *args, **kwargs):
|
|
1174
|
+
"""
|
|
1175
|
+
Check if the operands are available for the operation.
|
|
1176
|
+
"""
|
|
1177
|
+
what = kwargs["what"]
|
|
1178
|
+
if self.operands_released:
|
|
1179
|
+
raise RuntimeError(
|
|
1180
|
+
f"{what} cannot be performed if the operands have been set to None. Use reset_operands() to set the "
|
|
1181
|
+
f"desired input before performing the {what.lower()}."
|
|
1182
|
+
)
|
|
1183
|
+
|
|
1184
|
+
def _free_plan_resources(self, exception: Exception | None = None) -> bool:
|
|
1185
|
+
"""
|
|
1186
|
+
Free resources allocated in planning, including the cuSPARSE handle that plan()
|
|
1187
|
+
creates and rebuilds during replanning.
|
|
1188
|
+
"""
|
|
1189
|
+
|
|
1190
|
+
# Destroy plan.
|
|
1191
|
+
if self.mm_op_plan is not None:
|
|
1192
|
+
cusparse.sp_mm_op_destroy_plan(self.mm_op_plan)
|
|
1193
|
+
self.mm_op_plan = None
|
|
1194
|
+
|
|
1195
|
+
# Destroy matrix layouts.
|
|
1196
|
+
if self.a_ifc is not None and self.a_ifc.descriptor is not None:
|
|
1197
|
+
cusparse.destroy_sp_mat(self.a_ifc.descriptor)
|
|
1198
|
+
self.a_ifc.descriptor = None
|
|
1199
|
+
if self.b_ifc is not None and self.b_ifc.descriptor is not None:
|
|
1200
|
+
cusparse.destroy_dn_mat(self.b_ifc.descriptor)
|
|
1201
|
+
self.b_ifc.descriptor = None
|
|
1202
|
+
if self.c_ifc is not None and self.c_ifc.descriptor is not None:
|
|
1203
|
+
cusparse.destroy_dn_mat(self.c_ifc.descriptor)
|
|
1204
|
+
self.c_ifc.descriptor = None
|
|
1205
|
+
|
|
1206
|
+
# Destroy the cuSPARSE handle owned by this plan before it can be
|
|
1207
|
+
# overwritten by a subsequent planning run.
|
|
1208
|
+
if self.handle is not None and self.own_handle:
|
|
1209
|
+
cusparse.destroy(self.handle)
|
|
1210
|
+
self.handle, self.own_handle = None, None
|
|
1211
|
+
|
|
1212
|
+
self.mm_planned = False
|
|
1213
|
+
return True
|
|
1214
|
+
|
|
1215
|
+
def _check_planned(self, *args, **kwargs):
|
|
1216
|
+
what = kwargs["what"]
|
|
1217
|
+
if not self.mm_planned:
|
|
1218
|
+
raise RuntimeError(f"{what} cannot be performed before plan() has been called.")
|
|
1219
|
+
|
|
1220
|
+
@utils.precondition(_check_valid_matmul)
|
|
1221
|
+
@utils.precondition(_check_valid_operands, "Planning")
|
|
1222
|
+
@utils.atomic(_free_plan_resources, method=True)
|
|
1223
|
+
def plan(self, *, prologs=None, compute_capability=None, stream: utils.AnyStream | int | None = None):
|
|
1224
|
+
r"""
|
|
1225
|
+
Plan the sparse matrix multiplication operation.
|
|
1226
|
+
|
|
1227
|
+
Args:
|
|
1228
|
+
prologs: {prologs}
|
|
1229
|
+
|
|
1230
|
+
compute_capability: {compute_capability}
|
|
1231
|
+
|
|
1232
|
+
stream: {stream}
|
|
1233
|
+
|
|
1234
|
+
Examples:
|
|
1235
|
+
|
|
1236
|
+
We'll see how to use prologs specify the SpMM
|
|
1237
|
+
:math:`c := 3.14 \, \sin(a) \, @ \, b + c`, where the elementwise transformations
|
|
1238
|
+
are fully-fused into the matrix multiplication.
|
|
1239
|
+
|
|
1240
|
+
>>> import math
|
|
1241
|
+
>>> import cupy as cp
|
|
1242
|
+
>>> import cupyx.scipy.sparse as sp
|
|
1243
|
+
>>> import nvmath
|
|
1244
|
+
|
|
1245
|
+
|
|
1246
|
+
Create a sparse operand in DIA format and view it as UST.
|
|
1247
|
+
|
|
1248
|
+
>>> n = 4
|
|
1249
|
+
>>> values = cp.array(
|
|
1250
|
+
... [[0.0, 2.0, 3.0, 4.0], [10.0, 20.0, 30.0, 40.0], [-1.0, -2.0, -3.0, 0.0]]
|
|
1251
|
+
... )
|
|
1252
|
+
>>> offsets = cp.array([1, 0, -1], dtype=cp.int32)
|
|
1253
|
+
>>> a = sp.dia_matrix((values, offsets), shape=(n, n))
|
|
1254
|
+
>>> a = nvmath.sparse.ust.Tensor.from_package(a)
|
|
1255
|
+
|
|
1256
|
+
Dense ``b`` and ``c``, also viewed as UST.
|
|
1257
|
+
|
|
1258
|
+
>>> b = cp.ones((n, n))
|
|
1259
|
+
>>> b = nvmath.sparse.ust.Tensor.from_package(b)
|
|
1260
|
+
>>> c = cp.zeros((n, n))
|
|
1261
|
+
>>> c = nvmath.sparse.ust.Tensor.from_package(c)
|
|
1262
|
+
|
|
1263
|
+
Define the prolog for ``a``, and compile to LTO-IR using the helper (or
|
|
1264
|
+
your own compiler).
|
|
1265
|
+
|
|
1266
|
+
>>> def transform_a(a):
|
|
1267
|
+
... return 3.14 * math.sin(a)
|
|
1268
|
+
>>>
|
|
1269
|
+
>>> prolog_a = nvmath.sparse.compile_matmul_prolog(
|
|
1270
|
+
... transform_a, operand_label="a", dtype="float64"
|
|
1271
|
+
... )
|
|
1272
|
+
|
|
1273
|
+
Create, plan, and execute the SpMM.
|
|
1274
|
+
|
|
1275
|
+
>>> with nvmath.sparse.Matmul(a, b, c, beta=1.0) as mm:
|
|
1276
|
+
... # Plan the SpMM operation with the prologs.
|
|
1277
|
+
... mm.plan(prologs={{"a": prolog_a}})
|
|
1278
|
+
...
|
|
1279
|
+
... # Execute the SpMM.
|
|
1280
|
+
... r = mm.execute()
|
|
1281
|
+
|
|
1282
|
+
Further examples can be found in the `nvmath/examples/sparse/generic/matmul
|
|
1283
|
+
<https://github.com/NVIDIA/nvmath-python/tree/main/examples/sparse/generic/matmul>`_
|
|
1284
|
+
directory.
|
|
1285
|
+
""" # noqa: W505
|
|
1286
|
+
|
|
1287
|
+
# TODO: add support for this later
|
|
1288
|
+
epilog, semiring = None, None
|
|
1289
|
+
|
|
1290
|
+
# TODO: replace by heuristic to pick best OR include in API
|
|
1291
|
+
algo_env, kernel_env = os.getenv("NVMATH_CUSPARSE_ALGORITHM"), os.getenv("UST_CODEGEN_KERNEL")
|
|
1292
|
+
self.algorithm = cusparse.SpMMAlg.DEFAULT if algo_env is None else cusparse.SpMMAlg(int(algo_env))
|
|
1293
|
+
kernel = 0 if kernel_env is None else int(kernel_env)
|
|
1294
|
+
|
|
1295
|
+
# Set self.dispatch and self.codegen from the corresponding initial values. This
|
|
1296
|
+
# is to enable replanning.
|
|
1297
|
+
self.dispatch = self.dispatch_init
|
|
1298
|
+
self.codegen = self.codegen_init
|
|
1299
|
+
|
|
1300
|
+
self.logger.info("= PLANNING PHASE =")
|
|
1301
|
+
|
|
1302
|
+
# Create stream holder.
|
|
1303
|
+
stream_holder = utils.get_or_create_stream(self.device_id, stream, self.dense_package)
|
|
1304
|
+
self.logger.info(f"The specified stream for the Matmul plan is {stream_holder.obj}.")
|
|
1305
|
+
|
|
1306
|
+
if prologs is not None:
|
|
1307
|
+
if not isinstance(prologs, Mapping):
|
|
1308
|
+
raise TypeError(
|
|
1309
|
+
f"The prologs argument must be a mapping from operand name ('a', 'b', 'c') \
|
|
1310
|
+
to the LTO-IR buffers generated by the compile helper functions. The specified type is {type(prologs)}."
|
|
1311
|
+
)
|
|
1312
|
+
elif not prologs:
|
|
1313
|
+
# Treat empty prologs as if it were None.
|
|
1314
|
+
prologs = None
|
|
1315
|
+
self.logger.warning(f"Matmul plan(): The specified prologs {prologs} is ignored since it is an empty mapping.")
|
|
1316
|
+
|
|
1317
|
+
if semiring is not None:
|
|
1318
|
+
if not isinstance(semiring, Mapping):
|
|
1319
|
+
raise TypeError(
|
|
1320
|
+
f"The semiring argument must be a mapping from operation name ('add', 'mul') \
|
|
1321
|
+
to the LTO-IR buffers generated by the compile helper functions. The specified type is {type(semiring)}."
|
|
1322
|
+
)
|
|
1323
|
+
|
|
1324
|
+
if "add" not in semiring or "mul" not in semiring:
|
|
1325
|
+
raise ValueError(
|
|
1326
|
+
f"Both 'add' and 'mul' operations should be specified to define the semiring \
|
|
1327
|
+
structure: specified operations = {semiring.keys()}."
|
|
1328
|
+
)
|
|
1329
|
+
self.logger.info("The user-specified semiring operations will be used instead of the default.")
|
|
1330
|
+
|
|
1331
|
+
# Check if it still can be codegen'd. Provisional code generation becomes final.
|
|
1332
|
+
if self.sparse_package == "nvmath" and (
|
|
1333
|
+
(
|
|
1334
|
+
self.alpha[0] != 1.0
|
|
1335
|
+
or self.beta[0] != 1.0
|
|
1336
|
+
or self.qualifiers[0]["is_conjugate"]
|
|
1337
|
+
or self.qualifiers[1]["is_conjugate"]
|
|
1338
|
+
or self.compute_type_name not in CODEGEN_COMPUTE_TYPES
|
|
1339
|
+
)
|
|
1340
|
+
and prologs is not None
|
|
1341
|
+
):
|
|
1342
|
+
self.codegen = BackendSupport.NOT_SUPPORTED
|
|
1343
|
+
self.logger.debug(
|
|
1344
|
+
f"The Matmul operation is currently not supported for the code generation path: \
|
|
1345
|
+
alpha = {self.alpha[0]}, beta = {self.beta[0]}, a_conjugate = {bool(self.qualifiers[0]['is_conjugate'])}, b_conjugate = \
|
|
1346
|
+
{bool(self.qualifiers[1]['is_conjugate'])}, compute type = {self.compute_type_name}"
|
|
1347
|
+
)
|
|
1348
|
+
|
|
1349
|
+
# Check if it still can be dispatched and if it can which function to dispatch
|
|
1350
|
+
# it to. Provisional dispatch becomes final.
|
|
1351
|
+
if (
|
|
1352
|
+
(self.qualifiers[0]["is_conjugate"] and not self.qualifiers[0]["is_transpose"])
|
|
1353
|
+
or (self.qualifiers[1]["is_conjugate"] and not self.qualifiers[1]["is_transpose"])
|
|
1354
|
+
or self.compute_type_name not in CUSPARSE_COMPUTE_TYPES
|
|
1355
|
+
or prologs is not None
|
|
1356
|
+
):
|
|
1357
|
+
self.dispatch = BackendSupport.NOT_SUPPORTED
|
|
1358
|
+
self.logger.debug(
|
|
1359
|
+
f"The Matmul operation is currently not supported for dispatch: a_transpose = \
|
|
1360
|
+
{bool(self.qualifiers[0]['is_transpose'])}, a_conjugate = {bool(self.qualifiers[0]['is_conjugate'])}, \
|
|
1361
|
+
b_transpose = {bool(self.qualifiers[1]['is_transpose'])}, b_conjugate = {bool(self.qualifiers[1]['is_conjugate'])}, \
|
|
1362
|
+
compute type = {self.compute_type_name}, prologs specified = {prologs is not None}."
|
|
1363
|
+
)
|
|
1364
|
+
|
|
1365
|
+
# User-specified code generation, if true, takes precedence.
|
|
1366
|
+
if self.codegen == BackendSupport.NOT_SUPPORTED and self.options.codegen:
|
|
1367
|
+
message = ""
|
|
1368
|
+
if self.qualifiers[0]["is_conjugate"] or self.qualifiers[1]["is_conjugate"]:
|
|
1369
|
+
message += "The conjugate operation cannot be specified if prologs are provided \
|
|
1370
|
+
for the code generation path."
|
|
1371
|
+
if self.alpha[0] != 1.0 or self.beta[0] != 1.0:
|
|
1372
|
+
message += "The scalars alpha and/or beta cannot be specified if prologs are provided \
|
|
1373
|
+
for the code generation path."
|
|
1374
|
+
raise NotImplementedError(
|
|
1375
|
+
f"The code generation option is requested (options.codegen=True) but the code generation path is \
|
|
1376
|
+
currently not supported.\n{message}"
|
|
1377
|
+
)
|
|
1378
|
+
|
|
1379
|
+
# User-specified codegen option > dispatch > codegen.
|
|
1380
|
+
if self.options.codegen:
|
|
1381
|
+
if self.codegen != BackendSupport.PROVISIONALLY_SUPPORTED:
|
|
1382
|
+
raise NotImplementedError(
|
|
1383
|
+
"The matrix multiplication is not currently supported for the code generation \
|
|
1384
|
+
path, and options.codegen is set to True ."
|
|
1385
|
+
)
|
|
1386
|
+
self.codegen = BackendSupport.SUPPORTED
|
|
1387
|
+
self.api = Api.CODEGEN
|
|
1388
|
+
|
|
1389
|
+
if not self.options.codegen and self.dispatch == BackendSupport.PROVISIONALLY_SUPPORTED:
|
|
1390
|
+
if semiring is None and epilog is None:
|
|
1391
|
+
self.logger.debug("MM plan (dispatch): neither semiring or epilog is specified.")
|
|
1392
|
+
self.api = Api.MM
|
|
1393
|
+
self.dispatch = BackendSupport.SUPPORTED
|
|
1394
|
+
elif semiring is not None and epilog is not None:
|
|
1395
|
+
self.logger.debug(
|
|
1396
|
+
"MM plan (dispatch): both semiring and epilog are specified. No support due \
|
|
1397
|
+
to 2-argument epilog for now."
|
|
1398
|
+
)
|
|
1399
|
+
self.api = Api.MM_OP
|
|
1400
|
+
self.dispatch = BackendSupport.NOT_SUPPORTED
|
|
1401
|
+
else:
|
|
1402
|
+
self.logger.debug(
|
|
1403
|
+
f"MM plan (dispatch): exactly one of semiring {semiring is not None} or epilog \
|
|
1404
|
+
{epilog is not None} is specified."
|
|
1405
|
+
)
|
|
1406
|
+
self.dispatch = BackendSupport.NOT_SUPPORTED
|
|
1407
|
+
|
|
1408
|
+
if self.dispatch == BackendSupport.NOT_SUPPORTED and self.codegen == BackendSupport.PROVISIONALLY_SUPPORTED:
|
|
1409
|
+
self.codegen = BackendSupport.SUPPORTED
|
|
1410
|
+
self.api = Api.CODEGEN
|
|
1411
|
+
|
|
1412
|
+
if self.dispatch == BackendSupport.NOT_SUPPORTED and self.codegen == BackendSupport.NOT_SUPPORTED:
|
|
1413
|
+
raise NotImplementedError("The matrix multiplication is not currently supported for the specified operands.")
|
|
1414
|
+
|
|
1415
|
+
if self.api == Api.CODEGEN:
|
|
1416
|
+
assert self.codegen == BackendSupport.SUPPORTED, "Internal error."
|
|
1417
|
+
if semiring is not None or epilog is not None:
|
|
1418
|
+
raise NotImplementedError("Code generation currently only supports prologs.")
|
|
1419
|
+
self.logger.info("The Matmul kernel will be generated by the UST library and compiled JIT.")
|
|
1420
|
+
elif self.api in [Api.MM, Api.MM_OP]:
|
|
1421
|
+
assert self.dispatch == BackendSupport.SUPPORTED, "Internal error."
|
|
1422
|
+
self.logger.info("The Matmul operation will be dispatched to the cuSPARSE library.")
|
|
1423
|
+
else:
|
|
1424
|
+
raise AssertionError("Internal error: API not available.")
|
|
1425
|
+
|
|
1426
|
+
if self.api == Api.CODEGEN:
|
|
1427
|
+
from nvmath.sparse.ust._emitter import populate_matmul_parameters
|
|
1428
|
+
|
|
1429
|
+
if self.beta[0] != 1.0:
|
|
1430
|
+
raise NotImplementedError("The code generation path is not supported for beta != 1.")
|
|
1431
|
+
|
|
1432
|
+
# Create prolog LTO-IR for alpha, beta, and conjugate operation.
|
|
1433
|
+
if prologs is None:
|
|
1434
|
+
prologs = {}
|
|
1435
|
+
|
|
1436
|
+
if self.alpha[0] != 1.0 or self.qualifiers[0]["is_conjugate"]:
|
|
1437
|
+
prolog_a = default_prolog(self.alpha[0], is_conjugate=self.qualifiers[0]["is_conjugate"])
|
|
1438
|
+
prolog_a = compile_prolog(
|
|
1439
|
+
prolog_a, operand_label="a", dtype=self.compute_type_name, compute_capability=compute_capability
|
|
1440
|
+
)
|
|
1441
|
+
prologs["a"] = prolog_a
|
|
1442
|
+
|
|
1443
|
+
if self.qualifiers[1]["is_conjugate"]:
|
|
1444
|
+
prolog_b = default_prolog(1.0, is_conjugate=self.qualifiers[1]["is_conjugate"])
|
|
1445
|
+
prolog_b = compile_prolog(
|
|
1446
|
+
prolog_b, operand_label="b", dtype=self.compute_type_name, compute_capability=compute_capability
|
|
1447
|
+
)
|
|
1448
|
+
prologs["b"] = prolog_b
|
|
1449
|
+
|
|
1450
|
+
a, b, c = self.a.tensor, *self.ust_operands
|
|
1451
|
+
with utils.device_ctx(self.device_id):
|
|
1452
|
+
self.kernel, self.grid_iter = KERNEL_CACHE.generate_matmul(
|
|
1453
|
+
a,
|
|
1454
|
+
b,
|
|
1455
|
+
c,
|
|
1456
|
+
compute_type=self.compute_type_name,
|
|
1457
|
+
prologs=prologs,
|
|
1458
|
+
transpose_a=self.qualifiers[0]["is_transpose"] > 0,
|
|
1459
|
+
transpose_b=self.qualifiers[1]["is_transpose"] > 0,
|
|
1460
|
+
kernel=kernel,
|
|
1461
|
+
)
|
|
1462
|
+
|
|
1463
|
+
(self.kernel_grid, self.kernel_block), self.kernel_parameters = populate_matmul_parameters(
|
|
1464
|
+
a,
|
|
1465
|
+
b,
|
|
1466
|
+
c,
|
|
1467
|
+
self.grid_iter,
|
|
1468
|
+
stream_holder=stream_holder,
|
|
1469
|
+
transpose_a=self.qualifiers[0]["is_transpose"] > 0,
|
|
1470
|
+
transpose_b=self.qualifiers[1]["is_transpose"] > 0,
|
|
1471
|
+
)
|
|
1472
|
+
|
|
1473
|
+
self.mm_planned = True
|
|
1474
|
+
self.logger.info("The Matmul planning is complete for the code generation path.")
|
|
1475
|
+
return
|
|
1476
|
+
|
|
1477
|
+
# Capture epilog and semiring LTO-IR if specified.
|
|
1478
|
+
assert (semiring is None and epilog is None) ^ (semiring is not None and epilog is not None), "Internal error."
|
|
1479
|
+
if semiring is not None and epilog is not None:
|
|
1480
|
+
if "add" not in semiring or "mul" not in semiring:
|
|
1481
|
+
raise ValueError("The semiring option must provide LTO-IR code for both 'add' and 'mul' operations.")
|
|
1482
|
+
|
|
1483
|
+
if not isinstance(semiring["add"], bytes) or not isinstance(semiring["mul"], bytes):
|
|
1484
|
+
raise ValueError("The LTO-IR code for 'add' and 'mul' semiring operation must be bytestring objects.")
|
|
1485
|
+
|
|
1486
|
+
# Create handle if needed.
|
|
1487
|
+
# NOTE: currently sparse Matmul does not accept a user-provided handle
|
|
1488
|
+
# and the cusparse library handle is always created internally
|
|
1489
|
+
# during planning (first call) and owned by this Matmul object.
|
|
1490
|
+
# Here we add a guard to make sure to catch this if it changes in the future.
|
|
1491
|
+
if self.handle is None:
|
|
1492
|
+
assert self.own_handle is None, "Internal error."
|
|
1493
|
+
with utils.device_ctx(self.device_id):
|
|
1494
|
+
self.own_handle = True
|
|
1495
|
+
self.handle = cusparse.create()
|
|
1496
|
+
self.logger.info(f"The library handle has been created: {self.handle}.")
|
|
1497
|
+
else:
|
|
1498
|
+
# The handle is already created in a previous plan call.
|
|
1499
|
+
assert self.own_handle is True, "Internal error."
|
|
1500
|
+
|
|
1501
|
+
# Set stream.
|
|
1502
|
+
cusparse.set_stream(self.handle, stream_holder.ptr)
|
|
1503
|
+
|
|
1504
|
+
if (v := cusparse.get_version(self.handle)) < 12501 and self.a.format_name == "CSC" and self.spmm_traits.N == 1:
|
|
1505
|
+
raise NotImplementedError(f"This version ({v}) of cuSPARSE does not support CSC with n == 1.")
|
|
1506
|
+
|
|
1507
|
+
# Set the pointer mode.
|
|
1508
|
+
cusparse.set_pointer_mode(self.handle, cusparse.PointerMode.HOST)
|
|
1509
|
+
|
|
1510
|
+
# Create matrix descriptors if not already created in a previous plan call.
|
|
1511
|
+
if self.a_ifc is None:
|
|
1512
|
+
self.a_ifc = getattr(cusparse_utils, self.a.format_name + "Ifc")(self.a, self.spmm_traits.a_layout_traits) # type: ignore[assignment]
|
|
1513
|
+
self.a_ifc.create() # type: ignore[attr-defined]
|
|
1514
|
+
if self.b_ifc is None:
|
|
1515
|
+
self.b_ifc = cusparse_utils.DenseMatrixIfc(self.b, self.spmm_traits.b_layout_traits) # type: ignore[assignment]
|
|
1516
|
+
self.b_ifc.create() # type: ignore[attr-defined]
|
|
1517
|
+
if self.c_ifc is None:
|
|
1518
|
+
self.c_ifc = cusparse_utils.DenseMatrixIfc(self.c, self.spmm_traits.c_layout_traits) # type: ignore[assignment]
|
|
1519
|
+
self.c_ifc.create() # type: ignore[attr-defined]
|
|
1520
|
+
|
|
1521
|
+
# Transpose
|
|
1522
|
+
self.op_a = _get_cusparse_op_code(self.qualifiers[0])
|
|
1523
|
+
self.op_b = _get_cusparse_op_code(self.qualifiers[1])
|
|
1524
|
+
|
|
1525
|
+
if self.api == Api.MM:
|
|
1526
|
+
# Compute workspace.
|
|
1527
|
+
self.workspace.set_size(
|
|
1528
|
+
cusparse.sp_mm_buffer_size(
|
|
1529
|
+
self.handle,
|
|
1530
|
+
self.op_a,
|
|
1531
|
+
self.op_b,
|
|
1532
|
+
self.alpha.ctypes.data,
|
|
1533
|
+
self.a_ifc.descriptor, # type: ignore[attr-defined]
|
|
1534
|
+
self.b_ifc.descriptor, # type: ignore[attr-defined]
|
|
1535
|
+
self.beta.ctypes.data,
|
|
1536
|
+
self.c_ifc.descriptor, # type: ignore[attr-defined]
|
|
1537
|
+
self.compute_type,
|
|
1538
|
+
self.algorithm,
|
|
1539
|
+
)
|
|
1540
|
+
)
|
|
1541
|
+
elif self.api == Api.MM_OP:
|
|
1542
|
+
assert semiring is not None and epilog is not None
|
|
1543
|
+
|
|
1544
|
+
# TODO: Fix `sp_mm_op_create_plan` to accept bytes objects.
|
|
1545
|
+
add_buffer = np.frombuffer(semiring["add"], dtype=np.int8)
|
|
1546
|
+
mul_buffer = np.frombuffer(semiring["mul"], dtype=np.int8)
|
|
1547
|
+
epilog_buffer = np.frombuffer(epilog, dtype=np.int8)
|
|
1548
|
+
|
|
1549
|
+
# Plan and compute workspace.
|
|
1550
|
+
with utils.device_ctx(self.device_id):
|
|
1551
|
+
self.mm_op_plan, workspace_size = cusparse.sp_mm_op_create_plan(
|
|
1552
|
+
self.handle,
|
|
1553
|
+
self.op_a,
|
|
1554
|
+
self.op_b,
|
|
1555
|
+
self.a_ifc.descriptor, # type: ignore[attr-defined]
|
|
1556
|
+
self.b_ifc.descriptor, # type: ignore[attr-defined]
|
|
1557
|
+
self.c_ifc.descriptor, # type: ignore[attr-defined]
|
|
1558
|
+
self.compute_type,
|
|
1559
|
+
self.algorithm,
|
|
1560
|
+
add_buffer.ctypes.data,
|
|
1561
|
+
len(semiring["add"]),
|
|
1562
|
+
mul_buffer.ctypes.data,
|
|
1563
|
+
len(semiring["mul"]),
|
|
1564
|
+
epilog_buffer.ctypes.data,
|
|
1565
|
+
len(epilog),
|
|
1566
|
+
)
|
|
1567
|
+
self.workspace.set_size(workspace_size)
|
|
1568
|
+
else:
|
|
1569
|
+
raise AssertionError("Internal error.")
|
|
1570
|
+
|
|
1571
|
+
self.logger.info(f"The memory limit is {formatters.MemoryStr(self.memory_limit)}.")
|
|
1572
|
+
if self.workspace.size > self.memory_limit:
|
|
1573
|
+
raise RuntimeError(
|
|
1574
|
+
f"The memory required for the computation is {self.workspace.size} \
|
|
1575
|
+
({formatters.MemoryStr(self.workspace.size)}), while the specified memory limit is {self.memory_limit} \
|
|
1576
|
+
({formatters.MemoryStr(self.memory_limit)})."
|
|
1577
|
+
)
|
|
1578
|
+
|
|
1579
|
+
# TODO: preprocessing based on format and algorithm.
|
|
1580
|
+
|
|
1581
|
+
self.mm_planned = True
|
|
1582
|
+
self.logger.info("The Matmul planning is complete for the library dispatch path.")
|
|
1583
|
+
|
|
1584
|
+
@utils.precondition(_check_valid_matmul)
|
|
1585
|
+
def _check_and_set_dense_operand(self, operand, operand_name, operand_index, operand_ifc, stream_holder):
|
|
1586
|
+
assert self.operands is not None, "Internal Error."
|
|
1587
|
+
assert 0 < operand_index < 3, "Internal Error."
|
|
1588
|
+
|
|
1589
|
+
package = utils.infer_object_package(operand.tensor)
|
|
1590
|
+
|
|
1591
|
+
# Conjugate flag of the provided operands must match the original qualifiers
|
|
1592
|
+
if self.lazy_conjugation[operand_index] != operand.is_conjugate:
|
|
1593
|
+
raise ValueError(f"The provided operand {operand_name} has different conjugate flag than the original operand")
|
|
1594
|
+
|
|
1595
|
+
memory_space = operand.device
|
|
1596
|
+
if memory_space != self.memory_space:
|
|
1597
|
+
raise TypeError(
|
|
1598
|
+
f"The memory space for '{operand_name}' ({memory_space}) doesn't match the original one ({self.memory_space})."
|
|
1599
|
+
)
|
|
1600
|
+
|
|
1601
|
+
device_id = operand.device_id
|
|
1602
|
+
if device_id != "cpu" and device_id != self.device_id:
|
|
1603
|
+
raise TypeError(
|
|
1604
|
+
f"The device id for '{operand_name}' ({device_id}) doesn't match the original one ({self.device_id})."
|
|
1605
|
+
)
|
|
1606
|
+
|
|
1607
|
+
value_type = operand.dtype
|
|
1608
|
+
shape = operand.shape
|
|
1609
|
+
strides = operand.strides
|
|
1610
|
+
|
|
1611
|
+
# Handle cupy <> numpy asymmetry.
|
|
1612
|
+
if package == "numpy":
|
|
1613
|
+
package = "cuda"
|
|
1614
|
+
|
|
1615
|
+
# Check package, device ID, shape, strides, and dtype.
|
|
1616
|
+
if package != self.dense_package:
|
|
1617
|
+
message = f"The package for '{operand_name}' ({package}) doesn't match the original one ({self.dense_package})."
|
|
1618
|
+
raise TypeError(message)
|
|
1619
|
+
|
|
1620
|
+
if value_type != self.value_type_name:
|
|
1621
|
+
message = f"The dtype for '{operand_name}' ({value_type}) doesn't match the original one ({self.value_type_name})."
|
|
1622
|
+
raise TypeError(message)
|
|
1623
|
+
|
|
1624
|
+
required_shape = self.operand_extents[operand_index]
|
|
1625
|
+
if shape != required_shape:
|
|
1626
|
+
message = f"The shape of '{operand_name}' ({shape}) doesn't match the original one ({required_shape})."
|
|
1627
|
+
raise TypeError(message)
|
|
1628
|
+
|
|
1629
|
+
required_strides = self.operand_strides[operand_index]
|
|
1630
|
+
if strides != required_strides:
|
|
1631
|
+
message = f"The strides of '{operand_name}' ({strides}) don't match the original one ({required_strides})."
|
|
1632
|
+
raise TypeError(message)
|
|
1633
|
+
|
|
1634
|
+
if device_id == "cpu":
|
|
1635
|
+
# Copy operand in the original buffer if it exists or create a new one
|
|
1636
|
+
# otherwise.
|
|
1637
|
+
o = getattr(self, operand_name)
|
|
1638
|
+
if o is None:
|
|
1639
|
+
o = operand.to(self.device_id, stream_holder)
|
|
1640
|
+
else:
|
|
1641
|
+
o.copy_(operand, stream_holder)
|
|
1642
|
+
else:
|
|
1643
|
+
o = operand
|
|
1644
|
+
setattr(self, operand_name, o)
|
|
1645
|
+
|
|
1646
|
+
# Update alias.
|
|
1647
|
+
self.operands[operand_index] = o
|
|
1648
|
+
|
|
1649
|
+
# Update the pointer values for dispatch. For codegen update it in
|
|
1650
|
+
# release_operands since individual operand pointers can't be updated.
|
|
1651
|
+
if self.api in [Api.MM, Api.MM_OP]:
|
|
1652
|
+
operand_ifc.update(o)
|
|
1653
|
+
elif self.api != Api.CODEGEN:
|
|
1654
|
+
raise AssertionError(f"Internal error: unsupported backend {self.api}.")
|
|
1655
|
+
|
|
1656
|
+
@utils.precondition(_check_valid_matmul)
|
|
1657
|
+
def reset_operands(
|
|
1658
|
+
self,
|
|
1659
|
+
*,
|
|
1660
|
+
a=None,
|
|
1661
|
+
b=None,
|
|
1662
|
+
c=None,
|
|
1663
|
+
alpha=None,
|
|
1664
|
+
beta=None,
|
|
1665
|
+
stream: utils.AnyStream | int | None = None,
|
|
1666
|
+
):
|
|
1667
|
+
"""
|
|
1668
|
+
Reset one or more operands held by this :class:`Matmul` instance.
|
|
1669
|
+
|
|
1670
|
+
Args:
|
|
1671
|
+
a: {a}
|
|
1672
|
+
|
|
1673
|
+
b: {b}
|
|
1674
|
+
|
|
1675
|
+
c: {c}
|
|
1676
|
+
|
|
1677
|
+
alpha: {alpha}
|
|
1678
|
+
|
|
1679
|
+
beta: {beta}
|
|
1680
|
+
|
|
1681
|
+
stream: {stream}
|
|
1682
|
+
|
|
1683
|
+
Semantics:
|
|
1684
|
+
- Only the operands explicitly passed are updated. At least one operand
|
|
1685
|
+
is required (all of them after :meth:`release_operands`), otherwise
|
|
1686
|
+
a :class:`ValueError` is raised.
|
|
1687
|
+
|
|
1688
|
+
- This method will perform various checks on the new operands to make sure:
|
|
1689
|
+
|
|
1690
|
+
- The shapes, index and data types match those of the old ones.
|
|
1691
|
+
- The packages that the operands belong to match those of the old ones.
|
|
1692
|
+
- If input tensors are on GPU, the device must match.
|
|
1693
|
+
|
|
1694
|
+
Examples:
|
|
1695
|
+
|
|
1696
|
+
|
|
1697
|
+
>>> import torch
|
|
1698
|
+
>>> import nvmath
|
|
1699
|
+
|
|
1700
|
+
Prepare sample input data.
|
|
1701
|
+
|
|
1702
|
+
>>> device_id = 0
|
|
1703
|
+
>>> dtype = torch.float64
|
|
1704
|
+
>>> m, n, k = 4, 2, 8
|
|
1705
|
+
|
|
1706
|
+
Create a torch CSR tensor.
|
|
1707
|
+
|
|
1708
|
+
>>> a = torch.ones(m, k, dtype=dtype, device=device_id)
|
|
1709
|
+
>>> a = a.to_sparse_csr()
|
|
1710
|
+
|
|
1711
|
+
Dense ``b`` and ``c``.
|
|
1712
|
+
|
|
1713
|
+
>>> b = torch.ones(k, n, dtype=dtype, device=device_id)
|
|
1714
|
+
>>> c = torch.ones(m, n, dtype=dtype, device=device_id)
|
|
1715
|
+
|
|
1716
|
+
Create a stateful object that specifies the operation
|
|
1717
|
+
:math:`c := \\alpha \\, a \\, @ \\, b + \\beta \\, c`.
|
|
1718
|
+
|
|
1719
|
+
>>> alpha, beta = 1.2, 2.4
|
|
1720
|
+
>>> with nvmath.sparse.Matmul(a, b, c, alpha=alpha, beta=beta) as mm:
|
|
1721
|
+
... # Plan the operation.
|
|
1722
|
+
... mm.plan()
|
|
1723
|
+
...
|
|
1724
|
+
... # The first execution.
|
|
1725
|
+
... r = mm.execute()
|
|
1726
|
+
...
|
|
1727
|
+
... # The operands can be reset in-place using the array library.
|
|
1728
|
+
... # Note that since `c` has been updated in-place, it also needs to
|
|
1729
|
+
... # be reset unless accumulating into it is desired.
|
|
1730
|
+
... b *= 3.14
|
|
1731
|
+
... c[:] = 1.0
|
|
1732
|
+
...
|
|
1733
|
+
... # Execute the operation with updated `b`.
|
|
1734
|
+
... r = mm.execute()
|
|
1735
|
+
...
|
|
1736
|
+
... # The operands can also be reset using the reset_operand[_unchecked]
|
|
1737
|
+
... # methods. This is needed when the memory space doesn't match the
|
|
1738
|
+
... # execution space or if the operands have not been updated in-place.
|
|
1739
|
+
... # Any operands that are not reset retain their prior values.
|
|
1740
|
+
...
|
|
1741
|
+
... # The sparse matrix needs to be compatible (have the same sparse
|
|
1742
|
+
... # format, shape, dtype, NNZ etc.).
|
|
1743
|
+
... a = 2.718 * torch.ones(m, k, dtype=dtype, device=device_id)
|
|
1744
|
+
... a = a.to_sparse_csr()
|
|
1745
|
+
...
|
|
1746
|
+
... c = 6.28 * torch.ones(m, n, dtype=dtype, device=device_id)
|
|
1747
|
+
...
|
|
1748
|
+
... mm.reset_operands(a=a, c=c)
|
|
1749
|
+
...
|
|
1750
|
+
... # Execute the operation with the reset `a` and `c`, `b` retains
|
|
1751
|
+
... # its previous value.
|
|
1752
|
+
... r = mm.execute()
|
|
1753
|
+
|
|
1754
|
+
For more details, please refer to `the reset operand examples here
|
|
1755
|
+
<https://github.com/NVIDIA/nvmath-python/tree/main/examples/sparse/generic/matmul/>`_.
|
|
1756
|
+
""" # noqa: W505
|
|
1757
|
+
|
|
1758
|
+
self.logger.info("Resetting operands...")
|
|
1759
|
+
|
|
1760
|
+
# If operands have been released, all required operands must be provided.
|
|
1761
|
+
if self.operands_released:
|
|
1762
|
+
if a is None or b is None or c is None:
|
|
1763
|
+
raise ValueError("After release_operands(), 'a', 'b', and 'c' must be provided to reset_operands().")
|
|
1764
|
+
elif all(arg is None for arg in (a, b, c, alpha, beta)):
|
|
1765
|
+
# All arguments are None: there is nothing to update, so reject the call.
|
|
1766
|
+
raise ValueError("reset_operands() requires at least one operand to be provided.")
|
|
1767
|
+
|
|
1768
|
+
# Update alpha.
|
|
1769
|
+
if alpha is not None:
|
|
1770
|
+
if self.api == Api.CODEGEN:
|
|
1771
|
+
raise NotImplementedError("The value of `alpha` cannot be currently reset for the code generation path.")
|
|
1772
|
+
|
|
1773
|
+
try:
|
|
1774
|
+
self.alpha[0] = alpha
|
|
1775
|
+
except (ValueError, TypeError) as e:
|
|
1776
|
+
raise ValueError(
|
|
1777
|
+
f"The value provided for alpha {alpha} is not convertible to dtype '{self.alpha.dtype}'."
|
|
1778
|
+
) from e
|
|
1779
|
+
self.logger.info("The factor `alpha` has been reset to %s.", alpha)
|
|
1780
|
+
|
|
1781
|
+
# Update beta.
|
|
1782
|
+
if beta is not None:
|
|
1783
|
+
if self.api == Api.CODEGEN:
|
|
1784
|
+
raise NotImplementedError("The value of `beta` cannot be currently reset for the code generation path.")
|
|
1785
|
+
|
|
1786
|
+
if self.num_operands == 2:
|
|
1787
|
+
self.logger.warning(f"Matmul: The provided beta value {beta} is ignored since operand C is not specified.")
|
|
1788
|
+
else:
|
|
1789
|
+
try:
|
|
1790
|
+
self.beta[0] = beta
|
|
1791
|
+
except (ValueError, TypeError) as e:
|
|
1792
|
+
raise ValueError(
|
|
1793
|
+
f"The value provided for beta {beta} is not convertible to dtype '{self.beta.dtype}'."
|
|
1794
|
+
) from e
|
|
1795
|
+
self.logger.info("The factor `beta` has been reset to %s.", beta)
|
|
1796
|
+
|
|
1797
|
+
stream_holder = utils.get_or_create_stream(self.device_id, stream, self.dense_package)
|
|
1798
|
+
|
|
1799
|
+
# Update a.
|
|
1800
|
+
if a is not None:
|
|
1801
|
+
# Wrap A.
|
|
1802
|
+
try:
|
|
1803
|
+
a = sp_utils.wrap_sparse_operands(a)
|
|
1804
|
+
except Exception as e:
|
|
1805
|
+
raise TypeError(
|
|
1806
|
+
"The operand 'a' must be an N-D sparse array/tensor tensor from one of the supported \
|
|
1807
|
+
packages: nvmath, CuPy, PyTorch, or SciPy."
|
|
1808
|
+
) from e
|
|
1809
|
+
|
|
1810
|
+
sparse_package = utils.infer_object_package(a.tensor)
|
|
1811
|
+
device_id = a.device_id
|
|
1812
|
+
memory_space = a.device
|
|
1813
|
+
value_type = a.dtype
|
|
1814
|
+
index_type = a.index_type
|
|
1815
|
+
|
|
1816
|
+
shape = a.shape
|
|
1817
|
+
a_shape = self.operand_extents[0]
|
|
1818
|
+
|
|
1819
|
+
# Check package, device ID, dtype, and index type.
|
|
1820
|
+
if sparse_package != self.sparse_package:
|
|
1821
|
+
raise TypeError(
|
|
1822
|
+
f"The package for 'a' ({sparse_package}) doesn't match the original one ({self.sparse_package})."
|
|
1823
|
+
)
|
|
1824
|
+
|
|
1825
|
+
if sparse_package == "nvmath":
|
|
1826
|
+
dense_package = self.dense_package
|
|
1827
|
+
if dense_package == "cuda":
|
|
1828
|
+
dense_package = "numpy"
|
|
1829
|
+
if a.dense_tensorholder_type.name != dense_package:
|
|
1830
|
+
raise TypeError(
|
|
1831
|
+
f"The UST operand 'a' representation package ({a.dense_tensorholder_type.name}) doesn't match the "
|
|
1832
|
+
f"original one ({self.dense_package})."
|
|
1833
|
+
)
|
|
1834
|
+
|
|
1835
|
+
if memory_space != self.memory_space:
|
|
1836
|
+
raise TypeError(
|
|
1837
|
+
f"The memory space for 'a' ({memory_space}) doesn't match the original one ({self.memory_space})."
|
|
1838
|
+
)
|
|
1839
|
+
|
|
1840
|
+
if device_id != "cpu" and device_id != self.device_id:
|
|
1841
|
+
raise TypeError(f"The device id for 'a' ({device_id}) doesn't match the original one ({self.device_id}).")
|
|
1842
|
+
|
|
1843
|
+
if value_type != self.value_type_name:
|
|
1844
|
+
raise TypeError(f"The dtype for 'a' ({value_type}) doesn't match the original one ({self.value_type_name}).")
|
|
1845
|
+
|
|
1846
|
+
if index_type != self.index_type_name:
|
|
1847
|
+
raise TypeError(
|
|
1848
|
+
f"The index type for 'a' ({index_type}) doesn't match the original one ({self.index_type_name})."
|
|
1849
|
+
)
|
|
1850
|
+
|
|
1851
|
+
original_format_name = self.spmm_traits.a_layout_traits.sparse_format
|
|
1852
|
+
if a.format_name != original_format_name:
|
|
1853
|
+
raise TypeError(
|
|
1854
|
+
f"The sparse format for 'a' ({a.format_name}) doesn't match the original one ({original_format_name})."
|
|
1855
|
+
)
|
|
1856
|
+
|
|
1857
|
+
if shape != a_shape:
|
|
1858
|
+
raise TypeError(f"The shape of 'a' ({shape}) doesn't match the original one ({a_shape}).")
|
|
1859
|
+
|
|
1860
|
+
# Copy operand in the original buffer if it exists or create a new one.
|
|
1861
|
+
if device_id == "cpu":
|
|
1862
|
+
if self.a is not None:
|
|
1863
|
+
self.a.copy_(a, stream_holder)
|
|
1864
|
+
else:
|
|
1865
|
+
self.a = a.to(self.device_id, stream_holder)
|
|
1866
|
+
else:
|
|
1867
|
+
self.a = a
|
|
1868
|
+
|
|
1869
|
+
# Update alias.
|
|
1870
|
+
self.operands[0] = self.a
|
|
1871
|
+
|
|
1872
|
+
# Update the pointer values in the existing device buffers.
|
|
1873
|
+
if self.api in [Api.MM, Api.MM_OP]:
|
|
1874
|
+
self.a_ifc.update(self.a) # type: ignore[attr-defined]
|
|
1875
|
+
elif self.api != Api.CODEGEN:
|
|
1876
|
+
raise AssertionError(f"Internal error: unsupported backend {self.api}.")
|
|
1877
|
+
|
|
1878
|
+
self.logger.info("The operand `a` has been reset.")
|
|
1879
|
+
|
|
1880
|
+
# TODO: unify treatment of TensorHolder and UST for dense operands.
|
|
1881
|
+
# Update `b`.
|
|
1882
|
+
if b is not None:
|
|
1883
|
+
if sp_utils.sparse_or_dense(b) != "dense":
|
|
1884
|
+
raise ValueError(f"The operand `b` {type(b)} must be dense.")
|
|
1885
|
+
package = utils.infer_object_package(b)
|
|
1886
|
+
if (package == "nvmath") != (self.sparse_package == "nvmath"):
|
|
1887
|
+
raise TypeError(f"The package for 'b' ({package}) doesn't match the original one ({self.sparse_package}).")
|
|
1888
|
+
b_wrapped = tensor_wrapper.wrap_operand(b._wrapped_operand.tensor if package == "nvmath" else b)
|
|
1889
|
+
# Check and update self.operands and the alias.
|
|
1890
|
+
self._check_and_set_dense_operand(b_wrapped, "b", 1, self.b_ifc, stream_holder)
|
|
1891
|
+
if package == "nvmath":
|
|
1892
|
+
if self.memory_space == "cuda":
|
|
1893
|
+
self.ust_operands[0] = b
|
|
1894
|
+
else:
|
|
1895
|
+
self.ust_operands[0] = UST.from_package(self.b.tensor, stream_holder.external)
|
|
1896
|
+
self.logger.info("The operand `b` has been reset.")
|
|
1897
|
+
|
|
1898
|
+
# Update `c`.
|
|
1899
|
+
if c is not None:
|
|
1900
|
+
if sp_utils.sparse_or_dense(c) != "dense":
|
|
1901
|
+
raise ValueError(f"The operand `c` {type(c)} must be dense.")
|
|
1902
|
+
package = utils.infer_object_package(c)
|
|
1903
|
+
if (package == "nvmath") != (self.sparse_package == "nvmath"):
|
|
1904
|
+
raise TypeError(f"The package for 'c' ({package}) doesn't match the original one ({self.sparse_package}).")
|
|
1905
|
+
c_wrapped = tensor_wrapper.wrap_operand(c._wrapped_operand.tensor if package == "nvmath" else c)
|
|
1906
|
+
# Check and update self.operands and the alias.
|
|
1907
|
+
self._check_and_set_dense_operand(c_wrapped, "c", 2, self.c_ifc, stream_holder)
|
|
1908
|
+
if package == "nvmath":
|
|
1909
|
+
if self.memory_space == "cuda":
|
|
1910
|
+
self.ust_operands[1] = c
|
|
1911
|
+
else:
|
|
1912
|
+
self.ust_operands[1] = UST.from_package(self.c.tensor, stream_holder.external)
|
|
1913
|
+
if self.memory_space == "cpu" and self.inplace:
|
|
1914
|
+
self.cpu_c_ref = c if package == "nvmath" else c_wrapped
|
|
1915
|
+
self.logger.info("The operand `c` has been reset.")
|
|
1916
|
+
|
|
1917
|
+
# For codegen, all pointers will have to be reset since individual pointers
|
|
1918
|
+
# can't be updated.
|
|
1919
|
+
if (a is not None or b is not None or c is not None) and self.api == Api.CODEGEN:
|
|
1920
|
+
from nvmath.sparse.ust._emitter import populate_matmul_parameters
|
|
1921
|
+
|
|
1922
|
+
with utils.device_ctx(self.device_id):
|
|
1923
|
+
a, b, c = self.a.tensor, *self.ust_operands
|
|
1924
|
+
(self.kernel_grid, self.kernel_block), self.kernel_parameters = populate_matmul_parameters(
|
|
1925
|
+
a,
|
|
1926
|
+
b,
|
|
1927
|
+
c,
|
|
1928
|
+
self.grid_iter,
|
|
1929
|
+
stream_holder,
|
|
1930
|
+
transpose_a=self.qualifiers[0]["is_transpose"] > 0,
|
|
1931
|
+
transpose_b=self.qualifiers[1]["is_transpose"] > 0,
|
|
1932
|
+
)
|
|
1933
|
+
|
|
1934
|
+
# Update release operands state.
|
|
1935
|
+
self.operands_released = False
|
|
1936
|
+
|
|
1937
|
+
def reset_operands_unchecked(
|
|
1938
|
+
self,
|
|
1939
|
+
*,
|
|
1940
|
+
a=None,
|
|
1941
|
+
b=None,
|
|
1942
|
+
c=None,
|
|
1943
|
+
alpha=None,
|
|
1944
|
+
beta=None,
|
|
1945
|
+
stream: utils.AnyStream | int | None = None,
|
|
1946
|
+
):
|
|
1947
|
+
"""
|
|
1948
|
+
{reset_operands_unchecked}
|
|
1949
|
+
"""
|
|
1950
|
+
|
|
1951
|
+
if alpha is not None:
|
|
1952
|
+
self.alpha[0] = alpha
|
|
1953
|
+
|
|
1954
|
+
if beta is not None:
|
|
1955
|
+
self.beta[0] = beta
|
|
1956
|
+
|
|
1957
|
+
if self.memory_space == "cuda":
|
|
1958
|
+
if a is not None:
|
|
1959
|
+
self.operands[0].reset_unchecked(a)
|
|
1960
|
+
self.a = self.operands[0]
|
|
1961
|
+
|
|
1962
|
+
if self.api in [Api.MM, Api.MM_OP]:
|
|
1963
|
+
self.a_ifc.update(self.a) # type: ignore[attr-defined]
|
|
1964
|
+
|
|
1965
|
+
if b is not None:
|
|
1966
|
+
if self.ust_operands:
|
|
1967
|
+
self.ust_operands[0] = b
|
|
1968
|
+
# Recall `b` is the wrapped dense non-UST operand.
|
|
1969
|
+
self.operands[1].tensor = b._wrapped_operand.tensor
|
|
1970
|
+
else:
|
|
1971
|
+
self.operands[1].tensor = b
|
|
1972
|
+
|
|
1973
|
+
# Update the alias.
|
|
1974
|
+
self.b = self.operands[1]
|
|
1975
|
+
|
|
1976
|
+
if self.api in [Api.MM, Api.MM_OP]:
|
|
1977
|
+
self.b_ifc.update(self.b) # type: ignore[attr-defined]
|
|
1978
|
+
|
|
1979
|
+
if c is not None:
|
|
1980
|
+
if self.ust_operands:
|
|
1981
|
+
self.ust_operands[1] = c
|
|
1982
|
+
# Recall `c` is the wrapped dense non-UST operand.
|
|
1983
|
+
self.operands[2].tensor = c._wrapped_operand.tensor
|
|
1984
|
+
else:
|
|
1985
|
+
self.operands[2].tensor = c
|
|
1986
|
+
|
|
1987
|
+
# Update the alias.
|
|
1988
|
+
self.c = self.operands[2]
|
|
1989
|
+
|
|
1990
|
+
if self.api in [Api.MM, Api.MM_OP]:
|
|
1991
|
+
self.c_ifc.update(self.c) # type: ignore[attr-defined]
|
|
1992
|
+
|
|
1993
|
+
else:
|
|
1994
|
+
# Handle CPU memory space.
|
|
1995
|
+
|
|
1996
|
+
stream_holder = utils.get_or_create_stream(self.device_id, stream, self.dense_package)
|
|
1997
|
+
|
|
1998
|
+
if a is not None:
|
|
1999
|
+
a_wrapped = self.a_wrapper_type.create_from_tensor(a, attr_name_map=self.a_attr_name_map)
|
|
2000
|
+
self.a = self.operands[0] = a_wrapped.to(self.device_id, stream_holder)
|
|
2001
|
+
|
|
2002
|
+
if self.api in [Api.MM, Api.MM_OP]:
|
|
2003
|
+
self.a_ifc.update(self.a) # type: ignore[attr-defined]
|
|
2004
|
+
|
|
2005
|
+
# We can't use the dense wrapper type `self.[b,c].__class__` when going across
|
|
2006
|
+
# memory spaces due to asymmetries (NumPyTensor <> NDBufferTensor, etc).
|
|
2007
|
+
if b is not None:
|
|
2008
|
+
b_wrapped = tensor_wrapper.wrap_operand(b._wrapped_operand.tensor if self.sparse_package == "nvmath" else b)
|
|
2009
|
+
self.b = self.operands[1] = b_wrapped.to(self.device_id, stream_holder)
|
|
2010
|
+
if self.ust_operands:
|
|
2011
|
+
self.ust_operands[0] = UST.from_package(self.b.tensor, stream_holder.external)
|
|
2012
|
+
|
|
2013
|
+
if self.api in [Api.MM, Api.MM_OP]:
|
|
2014
|
+
self.b_ifc.update(self.b) # type: ignore[attr-defined]
|
|
2015
|
+
|
|
2016
|
+
if c is not None:
|
|
2017
|
+
c_wrapped = tensor_wrapper.wrap_operand(c._wrapped_operand.tensor if self.sparse_package == "nvmath" else c)
|
|
2018
|
+
self.c = self.operands[2] = c_wrapped.to(self.device_id, stream_holder)
|
|
2019
|
+
if self.ust_operands:
|
|
2020
|
+
self.ust_operands[1] = UST.from_package(self.c.tensor, stream_holder.external)
|
|
2021
|
+
|
|
2022
|
+
if self.api in [Api.MM, Api.MM_OP]:
|
|
2023
|
+
self.c_ifc.update(self.c) # type: ignore[attr-defined]
|
|
2024
|
+
|
|
2025
|
+
if self.inplace:
|
|
2026
|
+
self.cpu_c_ref = c if self.sparse_package == "nvmath" else c_wrapped
|
|
2027
|
+
|
|
2028
|
+
if self.api == Api.CODEGEN:
|
|
2029
|
+
from nvmath.sparse.ust._emitter import populate_matmul_parameters
|
|
2030
|
+
|
|
2031
|
+
with utils.device_ctx(self.device_id):
|
|
2032
|
+
stream_holder = utils.get_or_create_stream(self.device_id, stream, self.dense_package)
|
|
2033
|
+
a, b, c = self.a.tensor, *self.ust_operands
|
|
2034
|
+
(self.kernel_grid, self.kernel_block), self.kernel_parameters = populate_matmul_parameters(
|
|
2035
|
+
a,
|
|
2036
|
+
b,
|
|
2037
|
+
c,
|
|
2038
|
+
self.grid_iter,
|
|
2039
|
+
stream_holder,
|
|
2040
|
+
transpose_a=self.qualifiers[0]["is_transpose"] > 0,
|
|
2041
|
+
transpose_b=self.qualifiers[1]["is_transpose"] > 0,
|
|
2042
|
+
)
|
|
2043
|
+
|
|
2044
|
+
# Update release operands state.
|
|
2045
|
+
self.operands_released = False
|
|
2046
|
+
|
|
2047
|
+
@utils.precondition(_check_valid_matmul)
|
|
2048
|
+
def release_operands(self):
|
|
2049
|
+
"""
|
|
2050
|
+
{release_operands}
|
|
2051
|
+
"""
|
|
2052
|
+
|
|
2053
|
+
# Fast exit.
|
|
2054
|
+
if self.operands_released:
|
|
2055
|
+
self.logger.info("Operands have already been released; nothing to do.")
|
|
2056
|
+
return
|
|
2057
|
+
|
|
2058
|
+
# Set the sparse operand to None.
|
|
2059
|
+
self.operands[0].release()
|
|
2060
|
+
|
|
2061
|
+
# For the dense operands, keep the wrapper but set the internal references to None.
|
|
2062
|
+
self.operands[1].tensor = None
|
|
2063
|
+
if self.ust_operands:
|
|
2064
|
+
self.ust_operands[0] = None
|
|
2065
|
+
|
|
2066
|
+
# Set the aliases to None as well.
|
|
2067
|
+
self.a = self.b = None
|
|
2068
|
+
|
|
2069
|
+
if self.num_operands == 3:
|
|
2070
|
+
self.operands[2].tensor = self.c = None
|
|
2071
|
+
if self.ust_operands:
|
|
2072
|
+
self.ust_operands[1] = None
|
|
2073
|
+
|
|
2074
|
+
if self.inplace:
|
|
2075
|
+
self.cpu_c_ref = None
|
|
2076
|
+
|
|
2077
|
+
self.operands_released = True
|
|
2078
|
+
self.logger.info("The user-provided operands have been released.")
|
|
2079
|
+
|
|
2080
|
+
@utils.precondition(_check_valid_matmul)
|
|
2081
|
+
@utils.precondition(_check_planned, "Execution")
|
|
2082
|
+
@utils.precondition(_check_valid_operands, "Execution")
|
|
2083
|
+
def execute(self, *, release_workspace=False, stream: utils.AnyStream | int | None = None):
|
|
2084
|
+
"""
|
|
2085
|
+
Execute a prepared (planned) sparse matrix multiplication.
|
|
2086
|
+
|
|
2087
|
+
Args:
|
|
2088
|
+
release_workspace: {release_workspace}
|
|
2089
|
+
|
|
2090
|
+
stream: {stream}
|
|
2091
|
+
|
|
2092
|
+
Returns:
|
|
2093
|
+
{result}
|
|
2094
|
+
"""
|
|
2095
|
+
log_info = self.logger.isEnabledFor(logging.INFO)
|
|
2096
|
+
|
|
2097
|
+
if log_info:
|
|
2098
|
+
self.logger.info("= EXECUTION PHASE =")
|
|
2099
|
+
stream_holder = utils.get_or_create_stream(self.device_id, stream, self.dense_package)
|
|
2100
|
+
if log_info:
|
|
2101
|
+
self.logger.info(f"The specified stream for execute() is {stream_holder.obj}.")
|
|
2102
|
+
|
|
2103
|
+
# TODO: create empty tensor for the result.
|
|
2104
|
+
|
|
2105
|
+
if log_info:
|
|
2106
|
+
message = ""
|
|
2107
|
+
if self.api == Api.CODEGEN:
|
|
2108
|
+
message = "(codegen path)"
|
|
2109
|
+
elif self.api == Api.MM:
|
|
2110
|
+
message = "(library dispatch path to SpMM)"
|
|
2111
|
+
elif self.api == Api.MM_OP:
|
|
2112
|
+
message = "(library dispatch path to SpMMOp)"
|
|
2113
|
+
else:
|
|
2114
|
+
raise AssertionError("Internal error.")
|
|
2115
|
+
self.logger.info(f"Starting matrix multiplication {message}...")
|
|
2116
|
+
self.logger.info(f"{self.call_prologue}")
|
|
2117
|
+
# TODO: clean this up.
|
|
2118
|
+
if self.api == Api.CODEGEN:
|
|
2119
|
+
from nvmath.sparse.ust._jit import launch_kernel
|
|
2120
|
+
|
|
2121
|
+
launch_kernel(
|
|
2122
|
+
self.kernel,
|
|
2123
|
+
self.kernel_grid,
|
|
2124
|
+
self.kernel_block,
|
|
2125
|
+
self.kernel_parameters,
|
|
2126
|
+
device_id=self.device_id,
|
|
2127
|
+
stream_holder=stream_holder,
|
|
2128
|
+
blocking=True,
|
|
2129
|
+
)
|
|
2130
|
+
self.last_compute_event = None
|
|
2131
|
+
else:
|
|
2132
|
+
# Set stream for library execution.
|
|
2133
|
+
cusparse.set_stream(self.handle, stream_holder.ptr)
|
|
2134
|
+
|
|
2135
|
+
with self.workspace.allocate_perhaps(
|
|
2136
|
+
stream_holder,
|
|
2137
|
+
get_last_event=lambda: self.last_compute_event,
|
|
2138
|
+
) as ws:
|
|
2139
|
+
with utils.cuda_call_ctx(stream_holder, self.blocking, timing=log_info) as (
|
|
2140
|
+
self.last_compute_event,
|
|
2141
|
+
elapsed,
|
|
2142
|
+
):
|
|
2143
|
+
if self.api == Api.MM:
|
|
2144
|
+
cusparse.sp_mm(
|
|
2145
|
+
self.handle,
|
|
2146
|
+
self.op_a,
|
|
2147
|
+
self.op_b,
|
|
2148
|
+
self.alpha.ctypes.data,
|
|
2149
|
+
self.a_ifc.descriptor, # type: ignore[attr-defined]
|
|
2150
|
+
self.b_ifc.descriptor, # type: ignore[attr-defined]
|
|
2151
|
+
self.beta.ctypes.data,
|
|
2152
|
+
self.c_ifc.descriptor, # type: ignore[attr-defined]
|
|
2153
|
+
self.compute_type,
|
|
2154
|
+
self.algorithm,
|
|
2155
|
+
ws.raw_ptr,
|
|
2156
|
+
)
|
|
2157
|
+
elif self.api == Api.MM_OP:
|
|
2158
|
+
cusparse.sp_mm_op(self.mm_op_plan, ws.raw_ptr)
|
|
2159
|
+
else:
|
|
2160
|
+
raise AssertionError("Internal error.")
|
|
2161
|
+
|
|
2162
|
+
if log_info and elapsed.data is not None:
|
|
2163
|
+
self.logger.info(f"The matrix multiplication calculation took {elapsed.data:.3f} ms to complete.")
|
|
2164
|
+
|
|
2165
|
+
if release_workspace:
|
|
2166
|
+
ws.release(self.last_compute_event)
|
|
2167
|
+
self.last_compute_event = None
|
|
2168
|
+
|
|
2169
|
+
# Return the result.
|
|
2170
|
+
if self.memory_space == "cpu":
|
|
2171
|
+
# Currently only inplace is supported.
|
|
2172
|
+
if self.ust_operands:
|
|
2173
|
+
self.cpu_c_ref.copy_(self.ust_operands[1], stream_holder) # type: ignore
|
|
2174
|
+
out = self.cpu_c_ref
|
|
2175
|
+
else:
|
|
2176
|
+
self.cpu_c_ref.copy_(self.c, stream_holder) # type: ignore
|
|
2177
|
+
out = self.cpu_c_ref.tensor # type: ignore
|
|
2178
|
+
else:
|
|
2179
|
+
if self.ust_operands:
|
|
2180
|
+
out = self.ust_operands[1]
|
|
2181
|
+
else:
|
|
2182
|
+
out = self.c.tensor
|
|
2183
|
+
|
|
2184
|
+
return out
|
|
2185
|
+
|
|
2186
|
+
def free(self):
|
|
2187
|
+
"""Free Matmul resources.
|
|
2188
|
+
|
|
2189
|
+
It is recommended that the :class:`Matmul` object be used within a context, but if
|
|
2190
|
+
it is not possible then this method must be called explicitly to ensure that the
|
|
2191
|
+
matrix multiplication resources (especially internal library objects) are properly
|
|
2192
|
+
cleaned up.
|
|
2193
|
+
"""
|
|
2194
|
+
|
|
2195
|
+
if not self.valid_state:
|
|
2196
|
+
return
|
|
2197
|
+
|
|
2198
|
+
try:
|
|
2199
|
+
# Establish ordering with respect to the last computation
|
|
2200
|
+
# before releasing internal resources.
|
|
2201
|
+
self.workspace.release(self.last_compute_event)
|
|
2202
|
+
self.last_compute_event = None
|
|
2203
|
+
|
|
2204
|
+
self._free_plan_resources()
|
|
2205
|
+
|
|
2206
|
+
# Set all attributes to None except for logger and valid_state.
|
|
2207
|
+
for attr in list(vars(self)):
|
|
2208
|
+
if attr not in {"logger", "valid_state"}:
|
|
2209
|
+
setattr(self, attr, None)
|
|
2210
|
+
|
|
2211
|
+
except Exception as e:
|
|
2212
|
+
self.logger.critical("Internal error: only part of the Matmul object's resources have been released.")
|
|
2213
|
+
self.logger.critical(str(e))
|
|
2214
|
+
raise e
|
|
2215
|
+
finally:
|
|
2216
|
+
self.valid_state = False
|
|
2217
|
+
|
|
2218
|
+
self.logger.info("The Matmul object's resources have been released.")
|
|
2219
|
+
|
|
2220
|
+
|
|
2221
|
+
@utils.docstring_decorator(SPARSE_MM_DOCUMENTATION, skip_missing=False)
|
|
2222
|
+
def matmul(
|
|
2223
|
+
a,
|
|
2224
|
+
b,
|
|
2225
|
+
/,
|
|
2226
|
+
c=None,
|
|
2227
|
+
*,
|
|
2228
|
+
alpha=None,
|
|
2229
|
+
beta=None,
|
|
2230
|
+
qualifiers=None,
|
|
2231
|
+
prologs=None,
|
|
2232
|
+
compute_capability=None,
|
|
2233
|
+
options: MatmulOptions | dict[str, Any] | None = None,
|
|
2234
|
+
execution: ExecutionCUDA | None = None,
|
|
2235
|
+
stream: utils.AnyStream | int | None = None,
|
|
2236
|
+
):
|
|
2237
|
+
"""
|
|
2238
|
+
Perform the specified sparse matrix multiplication computation, which is one of
|
|
2239
|
+
:math:`\\alpha \\, \\operatorname{{op}}_h(a) \\, @ \\, \\operatorname{{op}}_h(b)
|
|
2240
|
+
+ \\beta \\, c` or
|
|
2241
|
+
:math:`\\operatorname{{prolog}}_a(\\operatorname{{op}}_t(a)) \\, @ \\,
|
|
2242
|
+
\\operatorname{{prolog}}_b(\\operatorname{{op}}_t(b)) + c`. The
|
|
2243
|
+
:math:`\\operatorname{{op}}_h` and :math:`\\operatorname{{op}}_t` operators optionally
|
|
2244
|
+
specify transpose/hermitian or transpose operations respectively via
|
|
2245
|
+
the ``qualifiers`` argument.
|
|
2246
|
+
|
|
2247
|
+
.. note::
|
|
2248
|
+
The complex conjugate operation is mutually exclusive with prolog since it can be
|
|
2249
|
+
absorbed into the prolog.
|
|
2250
|
+
|
|
2251
|
+
.. note::
|
|
2252
|
+
Currently only in-place sparse matrix multiplication is supported, so operand ``c``
|
|
2253
|
+
must be provided. This restriction will be removed in a future release.
|
|
2254
|
+
|
|
2255
|
+
This function-form is a wrapper around the stateful :class:`Matmul` object APIs and is
|
|
2256
|
+
meant for *single* use (the user needs to perform just one sparse matrix multiplication,
|
|
2257
|
+
for example), in which case there is no possibility of amortizing preparatory costs.
|
|
2258
|
+
|
|
2259
|
+
Detailed information on what's happening within this function can be obtained by passing
|
|
2260
|
+
in a :class:`logging.Logger` object to :class:`MatmulOptions` or by setting the
|
|
2261
|
+
appropriate options in the root logger object, which is used by default:
|
|
2262
|
+
|
|
2263
|
+
>>> import logging
|
|
2264
|
+
>>> logging.basicConfig(
|
|
2265
|
+
... level=logging.INFO,
|
|
2266
|
+
... format="%(asctime)s %(levelname)-8s %(message)s",
|
|
2267
|
+
... datefmt="%m-%d %H:%M:%S",
|
|
2268
|
+
... )
|
|
2269
|
+
|
|
2270
|
+
A user can select the desired logging level and, in general, take advantage of all of
|
|
2271
|
+
the functionality offered by the Python `logging` module.
|
|
2272
|
+
|
|
2273
|
+
Args:
|
|
2274
|
+
a: {a}
|
|
2275
|
+
|
|
2276
|
+
b: {b}
|
|
2277
|
+
|
|
2278
|
+
c: {c}
|
|
2279
|
+
|
|
2280
|
+
alpha: {alpha}
|
|
2281
|
+
|
|
2282
|
+
beta: {beta}
|
|
2283
|
+
|
|
2284
|
+
qualifiers: {qualifiers}
|
|
2285
|
+
|
|
2286
|
+
prologs: {prologs}
|
|
2287
|
+
|
|
2288
|
+
compute_capability: {compute_capability}
|
|
2289
|
+
|
|
2290
|
+
options: {options}
|
|
2291
|
+
|
|
2292
|
+
execution: {execution}
|
|
2293
|
+
|
|
2294
|
+
stream: {stream}
|
|
2295
|
+
|
|
2296
|
+
Returns:
|
|
2297
|
+
{result}
|
|
2298
|
+
|
|
2299
|
+
Semantics:
|
|
2300
|
+
{semantics}
|
|
2301
|
+
|
|
2302
|
+
.. seealso::
|
|
2303
|
+
:class:`Matmul`, :class:`MatmulOptions`, :class:`ExecutionCUDA`
|
|
2304
|
+
|
|
2305
|
+
Examples:
|
|
2306
|
+
|
|
2307
|
+
>>> import torch
|
|
2308
|
+
>>> import nvmath
|
|
2309
|
+
|
|
2310
|
+
Prepare sample data.
|
|
2311
|
+
|
|
2312
|
+
>>> index_type, dtype = torch.int32, torch.float32
|
|
2313
|
+
>>> device_id = 0
|
|
2314
|
+
>>> shape = 2, 2
|
|
2315
|
+
|
|
2316
|
+
Create a torch COO tensor, and view it as UST.
|
|
2317
|
+
|
|
2318
|
+
>>> indices = torch.tensor([[0, 1], [0, 1]], dtype=index_type)
|
|
2319
|
+
>>> values = torch.tensor([2.0, 4.0], dtype=dtype)
|
|
2320
|
+
>>> a = torch.sparse_coo_tensor(indices, values, shape, device=device_id)
|
|
2321
|
+
>>> a = a.coalesce()
|
|
2322
|
+
>>> a = nvmath.sparse.ust.Tensor.from_package(a)
|
|
2323
|
+
|
|
2324
|
+
Dense 'b' and 'c', also viewed as UST objects.
|
|
2325
|
+
|
|
2326
|
+
>>> b = torch.ones(*shape, dtype=dtype, device=device_id)
|
|
2327
|
+
>>> b = nvmath.sparse.ust.Tensor.from_package(b)
|
|
2328
|
+
>>> c = torch.zeros(*shape, dtype=dtype, device=device_id)
|
|
2329
|
+
>>> c = nvmath.sparse.ust.Tensor.from_package(c)
|
|
2330
|
+
|
|
2331
|
+
Solve :math:`c := a @ b + c`.
|
|
2332
|
+
|
|
2333
|
+
>>> r = nvmath.sparse.matmul(a, b, c, beta=1.0)
|
|
2334
|
+
|
|
2335
|
+
The result can also be viewed as a torch tensor.
|
|
2336
|
+
|
|
2337
|
+
>>> r = nvmath.sparse.ust.Tensor.to_package(r)
|
|
2338
|
+
|
|
2339
|
+
.. note:: This function is a convenience wrapper around :class:`Matmul` and is
|
|
2340
|
+
specifically meant for *single* use.
|
|
2341
|
+
|
|
2342
|
+
Further examples can be found in the `nvmath/examples/sparse/generic/matmul
|
|
2343
|
+
<https://github.com/NVIDIA/nvmath-python/tree/main/examples/sparse/generic/matmul>`_
|
|
2344
|
+
directory.
|
|
2345
|
+
"""
|
|
2346
|
+
|
|
2347
|
+
with Matmul(
|
|
2348
|
+
a, b, c=c, alpha=alpha, beta=beta, qualifiers=qualifiers, options=options, execution=execution, stream=stream
|
|
2349
|
+
) as mm:
|
|
2350
|
+
mm.plan(prologs=prologs, compute_capability=compute_capability, stream=stream)
|
|
2351
|
+
r = mm.execute(stream=stream)
|
|
2352
|
+
|
|
2353
|
+
return r
|