nvmath-python 0.9.0__cp314-cp314-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 +383 -0
- nvmath/_utils.py +150 -0
- nvmath/bindings/__init__.py +51 -0
- nvmath/bindings/_internal/__init__.pxd +0 -0
- nvmath/bindings/_internal/__init__.py +0 -0
- nvmath/bindings/_internal/cublas.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cublas.pxd +530 -0
- nvmath/bindings/_internal/cublasLt.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cublasLt.pxd +59 -0
- nvmath/bindings/_internal/cublasMp.pxd +52 -0
- nvmath/bindings/_internal/cudss.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cudss.pxd +47 -0
- nvmath/bindings/_internal/cufft.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cufft.pxd +70 -0
- nvmath/bindings/_internal/cufftMp.pxd +77 -0
- nvmath/bindings/_internal/curand.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/curand.pxd +42 -0
- nvmath/bindings/_internal/cusolver.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusolver.pxd +15 -0
- nvmath/bindings/_internal/cusolverDn.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusolverDn.pxd +406 -0
- nvmath/bindings/_internal/cusparse.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusparse.pxd +277 -0
- nvmath/bindings/_internal/cusparseLt.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusparseLt.pxd +48 -0
- nvmath/bindings/_internal/cutensor.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cutensor.pxd +58 -0
- nvmath/bindings/_internal/mathdx.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/mathdx.pxd +116 -0
- nvmath/bindings/_internal/nvshmem.pxd +29 -0
- nvmath/bindings/_internal/utils.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/utils.pxd +179 -0
- nvmath/bindings/_internal/utils.pyi +10 -0
- nvmath/bindings/cublas.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cublas.pxd +558 -0
- nvmath/bindings/cublas.pyi +746 -0
- nvmath/bindings/cublasLt.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cublasLt.pxd +109 -0
- nvmath/bindings/cublasLt.pyi +1337 -0
- nvmath/bindings/cublasMp.pxd +85 -0
- nvmath/bindings/cublasMp.pyi +219 -0
- nvmath/bindings/cudss.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cudss.pxd +86 -0
- nvmath/bindings/cudss.pyi +282 -0
- nvmath/bindings/cufft.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cufft.pxd +118 -0
- nvmath/bindings/cufft.pyi +241 -0
- nvmath/bindings/cufftMp.pxd +124 -0
- nvmath/bindings/cufftMp.pyi +260 -0
- nvmath/bindings/curand.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/curand.pxd +71 -0
- nvmath/bindings/curand.pyi +159 -0
- nvmath/bindings/cusolver.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cusolver.pxd +62 -0
- nvmath/bindings/cusolver.pyi +242 -0
- nvmath/bindings/cusolverDn.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cusolverDn.pxd +430 -0
- nvmath/bindings/cusolverDn.pyi +416 -0
- nvmath/bindings/cusparse.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cusparse.pxd +338 -0
- nvmath/bindings/cusparse.pyi +659 -0
- nvmath/bindings/cusparseLt.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cusparseLt.pxd +99 -0
- nvmath/bindings/cusparseLt.pyi +198 -0
- nvmath/bindings/cutensor.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cutensor.pxd +98 -0
- nvmath/bindings/cutensor.pyi +294 -0
- nvmath/bindings/cycublas.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycublas.pxd +684 -0
- nvmath/bindings/cycublasLt.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycublasLt.pxd +1055 -0
- nvmath/bindings/cycublasMp.pxd +183 -0
- nvmath/bindings/cycudss.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycudss.pxd +224 -0
- nvmath/bindings/cycufft.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycufft.pxd +326 -0
- nvmath/bindings/cycufftMp.pxd +334 -0
- nvmath/bindings/cycurand.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycurand.pxd +146 -0
- nvmath/bindings/cycusolver.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycusolver.pxd +154 -0
- nvmath/bindings/cycusolverDn.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycusolverDn.pxd +446 -0
- nvmath/bindings/cycusparse.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycusparse.pxd +470 -0
- nvmath/bindings/cycusparseLt.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycusparseLt.pxd +150 -0
- nvmath/bindings/cycutensor.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycutensor.pxd +192 -0
- nvmath/bindings/cymathdx.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cymathdx.pxd +552 -0
- nvmath/bindings/cynvshmem.pxd +126 -0
- nvmath/bindings/mathdx.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/mathdx.pxd +182 -0
- nvmath/bindings/mathdx.pyi +1244 -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.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/_internal/blas.pxd +125 -0
- nvmath/bindings/nvpl/_internal/fft.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/_internal/fft.pxd +36 -0
- nvmath/bindings/nvpl/blas.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/blas.pxd +85 -0
- nvmath/bindings/nvpl/blas.pyi +122 -0
- nvmath/bindings/nvpl/cyblas.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/cyblas.pxd +166 -0
- nvmath/bindings/nvpl/cyfft.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/cyfft.pxd +92 -0
- nvmath/bindings/nvpl/fft.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/fft.pxd +100 -0
- nvmath/bindings/nvpl/fft.pyi +100 -0
- nvmath/bindings/nvshmem.pxd +54 -0
- nvmath/bindings/nvshmem.pyi +179 -0
- nvmath/device/__init__.py +21 -0
- nvmath/device/_deprecated.py +33 -0
- nvmath/device/common.py +313 -0
- nvmath/device/common_backend.py +131 -0
- nvmath/device/common_cuda.py +201 -0
- nvmath/device/common_numba.py +310 -0
- nvmath/device/common_opaque_tensor.py +201 -0
- nvmath/device/cublasdx.py +1805 -0
- nvmath/device/cublasdx_backend.py +807 -0
- nvmath/device/cublasdx_numba.py +1612 -0
- nvmath/device/cufftdx.py +510 -0
- nvmath/device/cufftdx_backend.py +196 -0
- nvmath/device/cufftdx_numba.py +196 -0
- nvmath/device/curand_kernel.py +9147 -0
- nvmath/device/cusolverdx.py +2690 -0
- nvmath/device/cusolverdx_backend.py +440 -0
- nvmath/device/cusolverdx_numba.py +1624 -0
- nvmath/device/llvm_array.py +29 -0
- nvmath/device/random.py +445 -0
- nvmath/device/random_helpers.py +23 -0
- nvmath/device/random_states.py +187 -0
- nvmath/device/types.py +147 -0
- nvmath/device/vector_types_numba.py +203 -0
- nvmath/distributed/__init__.py +205 -0
- nvmath/distributed/_internal/__init__.py +0 -0
- nvmath/distributed/_internal/nvshmem.py +302 -0
- nvmath/distributed/_internal/tensor_ifc.py +67 -0
- nvmath/distributed/_internal/tensor_ifc_cupy.py +62 -0
- nvmath/distributed/_internal/tensor_ifc_host_device.py +165 -0
- nvmath/distributed/_internal/tensor_ifc_numpy.py +41 -0
- nvmath/distributed/_internal/tensor_ifc_torch.py +141 -0
- nvmath/distributed/_internal/tensor_wrapper.py +78 -0
- nvmath/distributed/_utils.py +167 -0
- nvmath/distributed/distribution.py +770 -0
- nvmath/distributed/fft/__init__.py +7 -0
- nvmath/distributed/fft/_configuration.py +79 -0
- nvmath/distributed/fft/fft.py +2801 -0
- nvmath/distributed/linalg/__init__.py +12 -0
- nvmath/distributed/linalg/_internal/__init__.py +3 -0
- nvmath/distributed/linalg/_internal/epilog_protocol.py +496 -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 +166 -0
- nvmath/distributed/linalg/advanced/matmulmod.py +2908 -0
- nvmath/distributed/process_group.py +408 -0
- nvmath/distributed/reshape/__init__.py +6 -0
- nvmath/distributed/reshape/_configuration.py +39 -0
- nvmath/distributed/reshape/reshape.py +1256 -0
- nvmath/fft/__init__.py +7 -0
- nvmath/fft/_configuration.py +208 -0
- nvmath/fft/_exec_utils.py +109 -0
- nvmath/fft/_helpers.py +237 -0
- nvmath/fft/fft.py +2903 -0
- nvmath/internal/__init__.pxd +3 -0
- nvmath/internal/__init__.py +10 -0
- nvmath/internal/_bindings.cp314-win_amd64.pyd +0 -0
- nvmath/internal/_bindings.pxd +12 -0
- nvmath/internal/_device_utils.py +54 -0
- nvmath/internal/enum_utils.py +142 -0
- nvmath/internal/formatters.py +87 -0
- nvmath/internal/mem_limit.py +51 -0
- nvmath/internal/memory.cp314-win_amd64.pyd +0 -0
- nvmath/internal/memory.pxd +10 -0
- nvmath/internal/memory.pyi +46 -0
- nvmath/internal/ndbuffer/__init__.pxd +3 -0
- nvmath/internal/ndbuffer/__init__.py +7 -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 +240 -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/ndbuffer/copy_kernel.cp314-win_amd64.pyd +0 -0
- nvmath/internal/ndbuffer/copy_kernel.pxd +9 -0
- nvmath/internal/ndbuffer/copy_kernel.pyi +9 -0
- nvmath/internal/ndbuffer/data_layout.cp314-win_amd64.pyd +0 -0
- nvmath/internal/ndbuffer/data_layout.pxd +72 -0
- nvmath/internal/ndbuffer/data_layout.pyi +23 -0
- nvmath/internal/ndbuffer/jit.cp314-win_amd64.pyd +0 -0
- nvmath/internal/ndbuffer/jit.pxd +7 -0
- nvmath/internal/ndbuffer/jit.pyi +6 -0
- nvmath/internal/ndbuffer/ndbuffer.cp314-win_amd64.pyd +0 -0
- nvmath/internal/ndbuffer/ndbuffer.pxd +36 -0
- nvmath/internal/ndbuffer/ndbuffer.pyi +41 -0
- nvmath/internal/ndbuffer/package_utils.cp314-win_amd64.pyd +0 -0
- nvmath/internal/ndbuffer/package_utils.pxd +11 -0
- nvmath/internal/ndbuffer/package_utils.pyi +12 -0
- nvmath/internal/package_ifc.py +147 -0
- nvmath/internal/package_ifc_cuda.py +49 -0
- nvmath/internal/package_ifc_cupy.py +49 -0
- nvmath/internal/package_ifc_torch.py +31 -0
- nvmath/internal/package_wrapper.py +14 -0
- nvmath/internal/tensor_ifc.py +194 -0
- nvmath/internal/tensor_ifc_cupy.py +241 -0
- nvmath/internal/tensor_ifc_ndbuffer.py +153 -0
- nvmath/internal/tensor_ifc_numpy.py +183 -0
- nvmath/internal/tensor_ifc_torch.py +176 -0
- nvmath/internal/tensor_wrapper.py +157 -0
- nvmath/internal/typemaps.py +162 -0
- nvmath/internal/utils.py +771 -0
- nvmath/linalg/__init__.py +46 -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 +217 -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/typemaps.py +144 -0
- nvmath/linalg/_internal/utils.py +134 -0
- nvmath/linalg/advanced/__init__.py +8 -0
- nvmath/linalg/advanced/_algorithmmod.py +149 -0
- nvmath/linalg/advanced/_configuration.py +349 -0
- nvmath/linalg/advanced/helpers/__init__.py +5 -0
- nvmath/linalg/advanced/helpers/matmul.py +1348 -0
- nvmath/linalg/advanced/matmulmod.py +3937 -0
- nvmath/linalg/generic/__init__.py +43 -0
- nvmath/linalg/generic/_configuration/__init__.py +37 -0
- nvmath/linalg/generic/_configuration/layout.py +263 -0
- nvmath/linalg/generic/_configuration/match.py +609 -0
- nvmath/linalg/generic/_configuration/qualifiers.py +494 -0
- nvmath/linalg/generic/_configuration/wrap.py +217 -0
- nvmath/linalg/generic/_dtype.py +15 -0
- nvmath/linalg/generic/matmulmod.py +1060 -0
- nvmath/memory.py +282 -0
- nvmath/sparse/__init__.py +38 -0
- nvmath/sparse/_internal/__init__.py +21 -0
- nvmath/sparse/_internal/common_utils.py +144 -0
- nvmath/sparse/_internal/cudss_config_ifc.py +741 -0
- nvmath/sparse/_internal/cudss_data_ifc.py +399 -0
- nvmath/sparse/_internal/cudss_utils.py +445 -0
- nvmath/sparse/_internal/cusparse_utils.py +382 -0
- nvmath/sparse/_internal/sparse_bsc_ifc.py +300 -0
- nvmath/sparse/_internal/sparse_bsr_ifc.py +303 -0
- nvmath/sparse/_internal/sparse_coo_ifc.py +254 -0
- nvmath/sparse/_internal/sparse_csc_ifc.py +266 -0
- nvmath/sparse/_internal/sparse_csr_ifc.py +266 -0
- nvmath/sparse/_internal/sparse_dia_ifc.py +239 -0
- nvmath/sparse/_internal/sparse_format_helpers.py +601 -0
- nvmath/sparse/_internal/sparse_tensor_ifc.py +121 -0
- nvmath/sparse/_internal/sparse_ust_ifc.py +146 -0
- nvmath/sparse/_internal/utils.py +56 -0
- nvmath/sparse/advanced/__init__.py +7 -0
- nvmath/sparse/advanced/_configuration.py +230 -0
- nvmath/sparse/advanced/direct_solver.py +1884 -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 +2426 -0
- nvmath/sparse/ust/__init__.py +7 -0
- nvmath/sparse/ust/_converters.py +378 -0
- nvmath/sparse/ust/_drawer.py +552 -0
- nvmath/sparse/ust/_emitter.py +553 -0
- nvmath/sparse/ust/_jit.py +212 -0
- nvmath/sparse/ust/_kernel.py +346 -0
- nvmath/sparse/ust/_semiring.py +71 -0
- nvmath/sparse/ust/_utils.py +102 -0
- nvmath/sparse/ust/interfaces/__init__.py +0 -0
- nvmath/sparse/ust/interfaces/torch_interface.py +476 -0
- nvmath/sparse/ust/semiring_ops.py +71 -0
- nvmath/sparse/ust/tensor.py +1027 -0
- nvmath/sparse/ust/tensor_drawer.py +552 -0
- nvmath/sparse/ust/tensor_emitter.py +558 -0
- nvmath/sparse/ust/tensor_format.py +935 -0
- nvmath/sparse/ust/tensor_jit.py +212 -0
- nvmath/sparse/ust/tensor_kernel.py +348 -0
- nvmath/sparse/ust/tensor_utils.py +380 -0
- nvmath/tensor/__init__.py +6 -0
- nvmath/tensor/_configuration.py +95 -0
- nvmath/tensor/_internal/__init__.py +3 -0
- nvmath/tensor/_internal/cutensor_config_ifc.py +247 -0
- nvmath/tensor/_internal/cutensor_utils.py +162 -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 +1861 -0
- nvmath_python-0.9.0.dist-info/METADATA +127 -0
- nvmath_python-0.9.0.dist-info/RECORD +309 -0
- nvmath_python-0.9.0.dist-info/WHEEL +5 -0
- nvmath_python-0.9.0.dist-info/licenses/LICENSE +177 -0
- nvmath_python-0.9.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,1060 @@
|
|
|
1
|
+
# Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"Matmul",
|
|
7
|
+
"matmul",
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
import dataclasses
|
|
11
|
+
import logging
|
|
12
|
+
import math
|
|
13
|
+
from collections.abc import Sequence
|
|
14
|
+
from typing import Final, TypeAlias
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
|
|
18
|
+
from nvmath._internal import templates
|
|
19
|
+
from nvmath.bindings import cublas
|
|
20
|
+
from nvmath.internal import formatters, tensor_wrapper, typemaps, utils
|
|
21
|
+
from nvmath.linalg._internal.batch import BatchTraits
|
|
22
|
+
from nvmath.linalg._internal.layout import BLASMatrixTraits, BLASMMTraitsView, InputMMTraits, check_extents, check_strides
|
|
23
|
+
from nvmath.linalg.advanced.matmulmod import SHARED_MM_DOCUMENTATION
|
|
24
|
+
from nvmath.linalg.generic._configuration import (
|
|
25
|
+
CACHED_LAYOUT_CHECKERS,
|
|
26
|
+
GeneralMatrixQualifier,
|
|
27
|
+
MatmulOptions,
|
|
28
|
+
MatrixQualifier,
|
|
29
|
+
matrix_qualifiers_dtype,
|
|
30
|
+
mm_layout_checker_getter,
|
|
31
|
+
select_blas_mm_function,
|
|
32
|
+
vector_to_square,
|
|
33
|
+
)
|
|
34
|
+
from nvmath.linalg.generic._dtype import check_dtype
|
|
35
|
+
|
|
36
|
+
AnyTensor: TypeAlias = tensor_wrapper.AnyTensor
|
|
37
|
+
SideMode: TypeAlias = cublas.SideMode
|
|
38
|
+
FillMode: TypeAlias = cublas.FillMode
|
|
39
|
+
DiagType: TypeAlias = cublas.DiagType
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclasses.dataclass(frozen=True, slots=True, kw_only=True)
|
|
43
|
+
class ExecutionCUDA(templates.ExecutionCUDA):
|
|
44
|
+
"""
|
|
45
|
+
A data class for providing GPU execution options.
|
|
46
|
+
|
|
47
|
+
Attributes:
|
|
48
|
+
device_id: CUDA device ordinal (only used if the operand resides on the CPU). The
|
|
49
|
+
default value is 0.
|
|
50
|
+
|
|
51
|
+
.. seealso::
|
|
52
|
+
:class:`ExecutionCPU`, :class:`Matmul`, :func:`matmul`
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclasses.dataclass(frozen=True, slots=True, kw_only=True)
|
|
59
|
+
class ExecutionCPU(templates.ExecutionCPU):
|
|
60
|
+
"""
|
|
61
|
+
A data class for providing CPU execution options.
|
|
62
|
+
|
|
63
|
+
Attributes:
|
|
64
|
+
num_threads: The number of CPU threads used to execute the operation.
|
|
65
|
+
If not specified, defaults to the number of CPU cores available to the
|
|
66
|
+
process.
|
|
67
|
+
|
|
68
|
+
.. seealso::
|
|
69
|
+
:class:`ExecutionCUDA`, :class:`Matmul`, :func:`matmul`,
|
|
70
|
+
:func:`~nvmath.bindings.nvpl.blas.set_num_threads_local`
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class InvalidMatmulState(Exception):
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
GENERIC_MM_DOCUMENTATION = SHARED_MM_DOCUMENTATION.copy()
|
|
81
|
+
GENERIC_MM_DOCUMENTATION.update(
|
|
82
|
+
{
|
|
83
|
+
"qualifiers": """\
|
|
84
|
+
If desired, specify the matrix qualifiers as a :class:`numpy.ndarray` of
|
|
85
|
+
:class:`~nvmath.linalg.generic.matrix_qualifiers_dtype` objects of length <= 3 corresponding to the operands `a`, `b`, and
|
|
86
|
+
`c`. By default, :class:`GeneralMatrixQualifier` is assumed for each tensor. See
|
|
87
|
+
:ref:`matrix-tensor-qualifiers` for the motivation behind qualifiers.""".replace("\n", " "),
|
|
88
|
+
#
|
|
89
|
+
"execution": """\
|
|
90
|
+
Specify execution space options for the Matmul as a :class:`ExecutionCUDA` or :class:`ExecutionCPU` object. If not specified,
|
|
91
|
+
the execution space will be selected to match operand's storage (in GPU or host memory), and the corresponding
|
|
92
|
+
:class:`ExecutionCUDA` or :class:`ExecutionCPU` object will be default-constructed.""".replace("\n", " "),
|
|
93
|
+
#
|
|
94
|
+
"options": """\
|
|
95
|
+
Specify options for the matrix multiplication as a :class:`MatmulOptions` object. If not specified, the
|
|
96
|
+
value will be set to the default-constructed ``MatmulOptions`` object.""".replace("\n", " "),
|
|
97
|
+
#
|
|
98
|
+
"result": """\
|
|
99
|
+
The result of the specified matrix multiplication, which remains on the same device and belong to the
|
|
100
|
+
same package as the input operands.""".replace("\n", " "),
|
|
101
|
+
#
|
|
102
|
+
"semantics": """\
|
|
103
|
+
.. _semantics:
|
|
104
|
+
|
|
105
|
+
The semantics of the matrix multiplication follows :external:py:data:`numpy.matmul` semantics, with some restrictions.
|
|
106
|
+
|
|
107
|
+
* Broadcasting `c` is not supported in this API, but may be supported in the future. See the advanced API
|
|
108
|
+
(:func:`nvmath.linalg.advanced.matmul`) for an API that supports broadcasting `c`.
|
|
109
|
+
|
|
110
|
+
In addition, the semantics for the fused matrix addition are described below:
|
|
111
|
+
|
|
112
|
+
* If arguments `a` and `b` are matrices, they are multiplied according to the rules of matrix multiplication.
|
|
113
|
+
* If argument `a` is 1-D, it is promoted to a matrix by prefixing ``1`` to its dimensions. After matrix
|
|
114
|
+
multiplication, the prefixed ``1`` is removed from the result's dimensions.
|
|
115
|
+
* If argument `b` is 1-D, it is promoted to a matrix by appending ``1`` to its dimensions. After matrix
|
|
116
|
+
multiplication, the appended ``1`` is removed from the result's dimensions.
|
|
117
|
+
* The operand for the matrix addition `c` must be the expected shape of the result of the matrix multiplication.
|
|
118
|
+
|
|
119
|
+
""".strip(),
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@utils.docstring_decorator(GENERIC_MM_DOCUMENTATION, skip_missing=False)
|
|
125
|
+
class Matmul(templates.StatefulAPI[MatmulOptions]):
|
|
126
|
+
"""
|
|
127
|
+
Create a stateful object encapsulating the specified matrix multiplication computation
|
|
128
|
+
:math:`\\alpha a @ b + \\beta c` and the required resources to perform the operation. A
|
|
129
|
+
stateful object can be used to amortize the cost of preparation (planning in the case of
|
|
130
|
+
matrix multiplication) across multiple executions (also see the :ref:`Stateful APIs
|
|
131
|
+
<host api types>` section).
|
|
132
|
+
|
|
133
|
+
The function-form API :func:`matmul` is a convenient alternative to using stateful
|
|
134
|
+
objects for *single* use (the user needs to perform just one matrix multiplication, for
|
|
135
|
+
example), in which case there is no possibility of amortizing preparatory costs. The
|
|
136
|
+
function-form APIs are just convenience wrappers around the stateful object APIs.
|
|
137
|
+
|
|
138
|
+
Using the stateful object typically involves the following steps:
|
|
139
|
+
|
|
140
|
+
1. **Problem Specification**: Initialize the object with a defined operation and
|
|
141
|
+
options.
|
|
142
|
+
2. **Preparation**: Use :meth:`plan` to determine the best algorithmic implementation
|
|
143
|
+
for this specific matrix multiplication operation.
|
|
144
|
+
3. **Execution**: Perform the matrix multiplication computation with :meth:`execute`.
|
|
145
|
+
|
|
146
|
+
Detailed information on what's happening in the various phases described above can be
|
|
147
|
+
obtained by passing in a :class:`logging.Logger` object to :class:`MatmulOptions` or by
|
|
148
|
+
setting the appropriate options in the root logger object, which is used by default:
|
|
149
|
+
|
|
150
|
+
>>> import logging
|
|
151
|
+
>>> logging.basicConfig(
|
|
152
|
+
... level=logging.INFO,
|
|
153
|
+
... format="%(asctime)s %(levelname)-8s %(message)s",
|
|
154
|
+
... datefmt="%m-%d %H:%M:%S",
|
|
155
|
+
... )
|
|
156
|
+
|
|
157
|
+
A user can select the desired logging level and, in general, take advantage of all of
|
|
158
|
+
the functionality offered by the Python `logging` module.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
a: {a}
|
|
162
|
+
|
|
163
|
+
b: {b}
|
|
164
|
+
|
|
165
|
+
c: {c}
|
|
166
|
+
|
|
167
|
+
alpha: {alpha}
|
|
168
|
+
|
|
169
|
+
beta: {beta}
|
|
170
|
+
|
|
171
|
+
qualifiers: {qualifiers}
|
|
172
|
+
|
|
173
|
+
options: {options}
|
|
174
|
+
|
|
175
|
+
execution: {execution}
|
|
176
|
+
|
|
177
|
+
stream: {stream}
|
|
178
|
+
|
|
179
|
+
Semantics:
|
|
180
|
+
{semantics}
|
|
181
|
+
|
|
182
|
+
.. seealso::
|
|
183
|
+
:meth:`reset_operands`, :meth:`execute`
|
|
184
|
+
|
|
185
|
+
Examples:
|
|
186
|
+
|
|
187
|
+
>>> import numpy as np
|
|
188
|
+
>>> import nvmath
|
|
189
|
+
|
|
190
|
+
Create two 2-D float64 ndarrays on the CPU:
|
|
191
|
+
|
|
192
|
+
>>> M, N, K = 1024, 1024, 1024
|
|
193
|
+
>>> a = np.random.rand(M, K)
|
|
194
|
+
>>> b = np.random.rand(K, N)
|
|
195
|
+
|
|
196
|
+
We will define a matrix multiplication operation using the generic matrix
|
|
197
|
+
multiplication interface.
|
|
198
|
+
|
|
199
|
+
Create a Matmul object encapsulating the problem specification above:
|
|
200
|
+
|
|
201
|
+
>>> mm = nvmath.linalg.Matmul(a, b)
|
|
202
|
+
|
|
203
|
+
Options can be provided above to control the behavior of the operation using the
|
|
204
|
+
`options` argument (see :class:`MatmulOptions`).
|
|
205
|
+
|
|
206
|
+
Next, plan the operation. The operands' layouts, qualifiers, and dtypes will be
|
|
207
|
+
considered to select an appropriate matrix multiplication:
|
|
208
|
+
|
|
209
|
+
>>> mm.plan()
|
|
210
|
+
|
|
211
|
+
Now execute the matrix multiplication, and obtain the result `r1` as a NumPy
|
|
212
|
+
ndarray.
|
|
213
|
+
|
|
214
|
+
>>> r1 = mm.execute()
|
|
215
|
+
|
|
216
|
+
Note that all :class:`Matmul` methods execute on the current stream by default.
|
|
217
|
+
Alternatively, the `stream` argument can be used to run a method on a specified
|
|
218
|
+
stream.
|
|
219
|
+
|
|
220
|
+
Let's now look at the same problem with CuPy ndarrays on the GPU.
|
|
221
|
+
|
|
222
|
+
Create a 3-D complex128 CuPy ndarray on the GPU:
|
|
223
|
+
|
|
224
|
+
>>> import cupy as cp
|
|
225
|
+
>>> a = cp.random.rand(M, K)
|
|
226
|
+
>>> b = cp.random.rand(K, N)
|
|
227
|
+
|
|
228
|
+
Create an Matmul object encapsulating the problem specification described earlier
|
|
229
|
+
and use it as a context manager.
|
|
230
|
+
|
|
231
|
+
>>> with nvmath.linalg.Matmul(a, b) as mm:
|
|
232
|
+
... # Plan the operation.
|
|
233
|
+
... mm.plan()
|
|
234
|
+
...
|
|
235
|
+
... # Execute the operation to get the first result.
|
|
236
|
+
... r1 = mm.execute()
|
|
237
|
+
...
|
|
238
|
+
... # Update operands A and B in-place (see reset_operands() for an
|
|
239
|
+
... # alternative).
|
|
240
|
+
... a[:] = cp.random.rand(M, K)
|
|
241
|
+
... b[:] = cp.random.rand(K, N)
|
|
242
|
+
...
|
|
243
|
+
... # Execute the operation to get the new result.
|
|
244
|
+
... r2 = mm.execute()
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
All the resources used by the object are released at the end of the block.
|
|
248
|
+
|
|
249
|
+
Further examples can be found in the `nvmath/examples/linalg/generic/matmul
|
|
250
|
+
<https://github.com/NVIDIA/nvmath-python/tree/main/examples/linalg/generic/matmul>`_
|
|
251
|
+
directory.
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
_input_traits: Final[InputMMTraits]
|
|
255
|
+
_batch_traits: Final[tuple[BatchTraits, BatchTraits, BatchTraits, BatchTraits]]
|
|
256
|
+
_qualifiers: Final[MatrixQualifier]
|
|
257
|
+
|
|
258
|
+
def __init__(
|
|
259
|
+
self,
|
|
260
|
+
a: AnyTensor,
|
|
261
|
+
b: AnyTensor,
|
|
262
|
+
/,
|
|
263
|
+
c: AnyTensor | None = None,
|
|
264
|
+
*,
|
|
265
|
+
alpha: float | complex | None = None,
|
|
266
|
+
beta: float | complex | None = None,
|
|
267
|
+
qualifiers: MatrixQualifier | None = None,
|
|
268
|
+
options: MatmulOptions | None = None,
|
|
269
|
+
execution: ExecutionCPU | ExecutionCUDA | None = None,
|
|
270
|
+
stream: utils.AnyStream | int | None = None,
|
|
271
|
+
):
|
|
272
|
+
options = utils.check_or_create_options(MatmulOptions, options, "Matrix multiplication options")
|
|
273
|
+
assert options is not None
|
|
274
|
+
|
|
275
|
+
if c is None and options.inplace:
|
|
276
|
+
raise ValueError("Operation cannot be inplace if operand C is not provided.")
|
|
277
|
+
|
|
278
|
+
a = tensor_wrapper.wrap_operand(a)
|
|
279
|
+
check_dtype(a.dtype, "A")
|
|
280
|
+
check_extents(a.shape, "A")
|
|
281
|
+
check_strides(a.strides, "A")
|
|
282
|
+
|
|
283
|
+
b = tensor_wrapper.wrap_operand(b)
|
|
284
|
+
check_dtype(b.dtype, "B")
|
|
285
|
+
check_extents(b.shape, "B")
|
|
286
|
+
check_strides(b.strides, "B")
|
|
287
|
+
operands = [a, b]
|
|
288
|
+
|
|
289
|
+
self.num_operands = 2
|
|
290
|
+
if c is not None:
|
|
291
|
+
self.num_operands = 3
|
|
292
|
+
c = tensor_wrapper.wrap_operand(c)
|
|
293
|
+
check_dtype(c.dtype, "C")
|
|
294
|
+
check_extents(c.shape, "C")
|
|
295
|
+
check_strides(c.strides, "C")
|
|
296
|
+
operands.append(c)
|
|
297
|
+
|
|
298
|
+
super().__init__(operands, options=options, execution=execution, stream=stream)
|
|
299
|
+
|
|
300
|
+
self._logger.info(f"The data type of operand A is '{a.dtype}', and that of operand B is '{b.dtype}'.")
|
|
301
|
+
if c is not None:
|
|
302
|
+
self._logger.info(f"The data type of operand C is '{c.dtype}'.")
|
|
303
|
+
|
|
304
|
+
if self.options.inplace:
|
|
305
|
+
self._logger.info("The operation will be performed inplace with operand C.")
|
|
306
|
+
|
|
307
|
+
if c is not None and beta is None:
|
|
308
|
+
raise ValueError("A value for beta must be provided if operand C is provided.")
|
|
309
|
+
|
|
310
|
+
assert self.num_operands == 2 or self.num_operands == 3, "Internal Error."
|
|
311
|
+
|
|
312
|
+
if a.dtype != b.dtype or (c is not None and a.dtype != c.dtype):
|
|
313
|
+
raise ValueError(
|
|
314
|
+
"Unsupported combination of dtypes. "
|
|
315
|
+
f"A ({a.dtype}), B ({b.dtype}), and C ({getattr(c, 'dtype', None)}) must all have the same dtype."
|
|
316
|
+
)
|
|
317
|
+
# Determine the data types for a and b.
|
|
318
|
+
self.a_dtype = typemaps.NAME_TO_DATA_TYPE[a.dtype]
|
|
319
|
+
self.b_dtype = typemaps.NAME_TO_DATA_TYPE[b.dtype]
|
|
320
|
+
self.a_dtype_name = a.dtype
|
|
321
|
+
self.b_dtype_name = b.dtype
|
|
322
|
+
|
|
323
|
+
self.is_complex = "complex" in self.a_dtype_name or "complex" in self.b_dtype_name
|
|
324
|
+
|
|
325
|
+
# Determine the data types for c.
|
|
326
|
+
if c is None:
|
|
327
|
+
self.c_dtype = self.a_dtype
|
|
328
|
+
else:
|
|
329
|
+
self.c_dtype = typemaps.NAME_TO_DATA_TYPE[c.dtype]
|
|
330
|
+
self.c_dtype_name = typemaps.DATA_TYPE_TO_NAME[self.c_dtype]
|
|
331
|
+
|
|
332
|
+
self._logger.info(f"The data type for the result D is '{self.c_dtype_name}'.")
|
|
333
|
+
|
|
334
|
+
self.scale_type_name = self.a_dtype_name
|
|
335
|
+
|
|
336
|
+
# Set alpha and beta.
|
|
337
|
+
self.alpha = np.zeros((1,), dtype=self.scale_type_name)
|
|
338
|
+
try:
|
|
339
|
+
self.alpha[0] = alpha if alpha is not None else 1
|
|
340
|
+
except (ValueError, TypeError) as e:
|
|
341
|
+
raise ValueError(f"The value provided for alpha {alpha} is not convertible to dtype '{self.alpha.dtype}'.") from e
|
|
342
|
+
|
|
343
|
+
self.beta = np.zeros((1,), dtype=self.scale_type_name)
|
|
344
|
+
if beta is not None and c is None:
|
|
345
|
+
self._logger.warning(f"Matmul: The provided beta value {beta} is ignored since operand C is not specified.")
|
|
346
|
+
try:
|
|
347
|
+
self.beta[0] = beta if beta is not None and c is not None else 0
|
|
348
|
+
except (ValueError, TypeError) as e:
|
|
349
|
+
raise ValueError(f"The value provided for beta {beta} is not convertible to dtype '{self.beta.dtype}'.") from e
|
|
350
|
+
|
|
351
|
+
if qualifiers is None:
|
|
352
|
+
new_qualifiers = np.empty(3, dtype=matrix_qualifiers_dtype)
|
|
353
|
+
new_qualifiers[:] = GeneralMatrixQualifier.create()
|
|
354
|
+
else:
|
|
355
|
+
if not ((len(qualifiers) == 3) or (len(qualifiers) == 2 and c is None)):
|
|
356
|
+
raise ValueError("The number of MatrixQualifiers must match the number of operands.")
|
|
357
|
+
new_qualifiers = np.empty(3, dtype=matrix_qualifiers_dtype)
|
|
358
|
+
new_qualifiers[:2] = qualifiers[:2]
|
|
359
|
+
new_qualifiers[2] = GeneralMatrixQualifier.create() if len(qualifiers) < 3 else qualifiers[2]
|
|
360
|
+
self._qualifiers = new_qualifiers
|
|
361
|
+
self._logger.info(
|
|
362
|
+
f"The matrix multiplication qualifiers are:\n"
|
|
363
|
+
f" A = {GeneralMatrixQualifier.to_string(self._qualifiers[0])}\n"
|
|
364
|
+
f" B = {GeneralMatrixQualifier.to_string(self._qualifiers[1])}\n"
|
|
365
|
+
f" C = {GeneralMatrixQualifier.to_string(self._qualifiers[2])}"
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
# Set qualifiers based on torch lazy conjugation flag if not provided.
|
|
369
|
+
self._qualifiers[0]["conjugate"] = self._qualifiers[0]["conjugate"] ^ self._operands[0].is_conjugate
|
|
370
|
+
self._qualifiers[1]["conjugate"] = self._qualifiers[1]["conjugate"] ^ self._operands[1].is_conjugate
|
|
371
|
+
self.lazy_conjugation = (self._operands[0].is_conjugate, self._operands[1].is_conjugate, False)
|
|
372
|
+
if c is not None:
|
|
373
|
+
self._qualifiers[2]["conjugate"] = self._qualifiers[2]["conjugate"] ^ self._operands[2].is_conjugate
|
|
374
|
+
if self._qualifiers[2]["conjugate"]:
|
|
375
|
+
raise ValueError("The conjugate flag is currently not supported for operand C.")
|
|
376
|
+
|
|
377
|
+
# Capture operand extents and strides for consistency check when resetting operands.
|
|
378
|
+
self.operand_extents = tuple(o.shape for o in self._operands)
|
|
379
|
+
self.operand_strides = tuple(o.strides for o in self._operands)
|
|
380
|
+
|
|
381
|
+
# Create operand layouts.
|
|
382
|
+
a_layout = BLASMatrixTraits(
|
|
383
|
+
self.a_dtype,
|
|
384
|
+
*vector_to_square(
|
|
385
|
+
self._operands[0].shape[-2:],
|
|
386
|
+
self._operands[0].strides[-2:],
|
|
387
|
+
self._qualifiers[0],
|
|
388
|
+
),
|
|
389
|
+
is_conjugate=bool(self._qualifiers[0]["conjugate"]),
|
|
390
|
+
is_transpose=False,
|
|
391
|
+
is_lower=self._qualifiers[0]["uplo"] == FillMode.LOWER,
|
|
392
|
+
)
|
|
393
|
+
self._logger.info("Operand A has traits of %s", a_layout)
|
|
394
|
+
b_layout = BLASMatrixTraits(
|
|
395
|
+
self.b_dtype,
|
|
396
|
+
*vector_to_square(
|
|
397
|
+
self._operands[1].shape[-2:],
|
|
398
|
+
self._operands[1].strides[-2:],
|
|
399
|
+
self._qualifiers[1],
|
|
400
|
+
),
|
|
401
|
+
is_conjugate=bool(self._qualifiers[1]["conjugate"]),
|
|
402
|
+
is_transpose=False,
|
|
403
|
+
is_lower=self._qualifiers[1]["uplo"] == FillMode.LOWER,
|
|
404
|
+
)
|
|
405
|
+
self._logger.info("Operand B has traits of %s", b_layout)
|
|
406
|
+
c_layout = (
|
|
407
|
+
None
|
|
408
|
+
if c is None
|
|
409
|
+
else BLASMatrixTraits(
|
|
410
|
+
self.c_dtype,
|
|
411
|
+
*vector_to_square(
|
|
412
|
+
self._operands[2].shape[-2:],
|
|
413
|
+
self._operands[2].strides[-2:],
|
|
414
|
+
self._qualifiers[2],
|
|
415
|
+
),
|
|
416
|
+
is_conjugate=bool(self._qualifiers[2]["conjugate"]),
|
|
417
|
+
is_transpose=False,
|
|
418
|
+
is_lower=self._qualifiers[2]["uplo"] == FillMode.LOWER,
|
|
419
|
+
)
|
|
420
|
+
)
|
|
421
|
+
self._logger.info("Operand C has traits of %s", c_layout)
|
|
422
|
+
|
|
423
|
+
# Get the operation traits.
|
|
424
|
+
self._input_traits = InputMMTraits.from_layouts(a_layout, b_layout, c_layout, self.options.inplace, self._logger)
|
|
425
|
+
|
|
426
|
+
a_batch = BatchTraits.from_full_shape_and_strides(
|
|
427
|
+
self._operands[0].shape,
|
|
428
|
+
self._operands[0].strides,
|
|
429
|
+
num_trailing_dims=2,
|
|
430
|
+
overlap_allowed=True,
|
|
431
|
+
)
|
|
432
|
+
b_batch = BatchTraits.from_full_shape_and_strides(
|
|
433
|
+
self._operands[1].shape,
|
|
434
|
+
self._operands[1].strides,
|
|
435
|
+
num_trailing_dims=2,
|
|
436
|
+
overlap_allowed=True,
|
|
437
|
+
)
|
|
438
|
+
c_batch = (
|
|
439
|
+
BatchTraits(shape=(), strides=())
|
|
440
|
+
if c is None
|
|
441
|
+
else BatchTraits.from_full_shape_and_strides(
|
|
442
|
+
self._operands[2].shape,
|
|
443
|
+
self._operands[2].strides,
|
|
444
|
+
num_trailing_dims=2,
|
|
445
|
+
overlap_allowed=False,
|
|
446
|
+
)
|
|
447
|
+
)
|
|
448
|
+
# BatchTraits overloads the * operator with a batch combination behavior
|
|
449
|
+
abc_batch, axis_order = a_batch * (b_batch * c_batch)
|
|
450
|
+
d_batch = (
|
|
451
|
+
c_batch
|
|
452
|
+
if self.options.inplace
|
|
453
|
+
else BatchTraits.from_batch_shape_and_size(
|
|
454
|
+
shape=abc_batch,
|
|
455
|
+
axis_order=axis_order,
|
|
456
|
+
batch_stride=math.prod(self._input_traits.d_layout_traits.shape),
|
|
457
|
+
)
|
|
458
|
+
)
|
|
459
|
+
self._logger.info("Operand A has batch traits of %s.", a_batch)
|
|
460
|
+
self._logger.info("Operand B has batch traits of %s.", b_batch)
|
|
461
|
+
self._logger.info("Operand C has batch traits of %s.", c_batch)
|
|
462
|
+
self._logger.info("Result D has batch traits of %s.", d_batch)
|
|
463
|
+
self._batch_traits = (a_batch, b_batch, c_batch, d_batch)
|
|
464
|
+
|
|
465
|
+
# Track whether user-provided operands have been released
|
|
466
|
+
self._operands_released = False
|
|
467
|
+
|
|
468
|
+
self.valid_state = True
|
|
469
|
+
self._logger.info("The Matmul operation has been created.")
|
|
470
|
+
|
|
471
|
+
def _check_valid_matmul(self, *args, **kwargs):
|
|
472
|
+
"""
|
|
473
|
+
Check if the Matmul object is alive and well.
|
|
474
|
+
"""
|
|
475
|
+
if not self.valid_state:
|
|
476
|
+
raise InvalidMatmulState("The Matmul object cannot be used after resources are free'd")
|
|
477
|
+
|
|
478
|
+
def _check_valid_operands(self, *args, **kwargs):
|
|
479
|
+
"""
|
|
480
|
+
Check if operands are available for operations.
|
|
481
|
+
"""
|
|
482
|
+
if self._operands_released:
|
|
483
|
+
raise ValueError(
|
|
484
|
+
"Operands have been released. Call reset_operands() to provide new operands before using this method."
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
@utils.precondition(_check_valid_matmul)
|
|
488
|
+
def plan(self, *, stream: utils.AnyStream | int | None = None) -> None:
|
|
489
|
+
"""
|
|
490
|
+
Plan the matrix multiplication operation.
|
|
491
|
+
|
|
492
|
+
Unlike :py:meth:`nvmath.linalg.advanced.Matmul.plan`, this method takes no
|
|
493
|
+
tuning parameters. Its primary function is to find the correct matrix multiplication
|
|
494
|
+
implementation based on the operands and options provided to the constructor.
|
|
495
|
+
|
|
496
|
+
Args:
|
|
497
|
+
stream: {stream}
|
|
498
|
+
|
|
499
|
+
Returns:
|
|
500
|
+
Nothing.
|
|
501
|
+
"""
|
|
502
|
+
log_info = self._logger.isEnabledFor(logging.INFO)
|
|
503
|
+
|
|
504
|
+
if log_info:
|
|
505
|
+
self._logger.info("= PLANNING PHASE =")
|
|
506
|
+
self._logger.info("Starting matrix multiplication planning...")
|
|
507
|
+
|
|
508
|
+
with utils.host_call_ctx(timing=log_info) as elapsed:
|
|
509
|
+
mm_layout_checker = mm_layout_checker_getter(self._qualifiers)
|
|
510
|
+
|
|
511
|
+
mm_traits = BLASMMTraitsView.from_input_traits(
|
|
512
|
+
self._input_traits,
|
|
513
|
+
mm_layout_checker,
|
|
514
|
+
self._logger,
|
|
515
|
+
lookup_table_table=CACHED_LAYOUT_CHECKERS,
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
self._function, function_name = select_blas_mm_function(
|
|
519
|
+
(*self._batch_traits[:2], self._batch_traits[3]),
|
|
520
|
+
mm_traits,
|
|
521
|
+
self._qualifiers,
|
|
522
|
+
self._logger,
|
|
523
|
+
self.execution,
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
if log_info and elapsed.data is not None:
|
|
527
|
+
self._logger.info(f"The plan found 1 suitable algorithm named {function_name}.")
|
|
528
|
+
self._logger.info(f"The matrix multiplication planning phase took {elapsed.data:.3f} ms to complete.")
|
|
529
|
+
|
|
530
|
+
# Base FLOP count.
|
|
531
|
+
self.flop_count = 2 * mm_traits.M * mm_traits.N * mm_traits.K
|
|
532
|
+
self._logger.info(f"The base matrix multiplication FLOP count is {formatters.FLOPSStr(self.flop_count, 'FLOP')}.")
|
|
533
|
+
|
|
534
|
+
self._has_plan = True
|
|
535
|
+
|
|
536
|
+
return
|
|
537
|
+
|
|
538
|
+
def _check_and_set_operand(
|
|
539
|
+
self,
|
|
540
|
+
new_operand: utils.TensorHolder,
|
|
541
|
+
operand_name: str,
|
|
542
|
+
stream_holder: utils.StreamHolder | None,
|
|
543
|
+
*,
|
|
544
|
+
operand_index: int,
|
|
545
|
+
dtype: str | None = None,
|
|
546
|
+
extents: Sequence[int] | None = None,
|
|
547
|
+
strides: Sequence[int] | None = None,
|
|
548
|
+
):
|
|
549
|
+
"""
|
|
550
|
+
Check to make sure that the provided operand is consistent with the one it's
|
|
551
|
+
updating, and update it.
|
|
552
|
+
"""
|
|
553
|
+
# Make sure that the data type and extents match.
|
|
554
|
+
utils.check_attribute_match(dtype, new_operand.dtype, "data type")
|
|
555
|
+
utils.check_attribute_match(extents, new_operand.shape, "extents")
|
|
556
|
+
|
|
557
|
+
# Package must be the same to preserve stream ordering
|
|
558
|
+
if self._operands_package != new_operand.name:
|
|
559
|
+
raise TypeError(
|
|
560
|
+
f"Library package mismatch: The operand {operand_name} must from the same package ({new_operand.name}) "
|
|
561
|
+
f"as the original operand ({self._operands_package})."
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
if self._operands_device_id != new_operand.device_id:
|
|
565
|
+
raise ValueError(
|
|
566
|
+
f"The operand {operand_name} must be on the same device ({new_operand.device_id}) as the original operand "
|
|
567
|
+
f"({self._operands_device_id})."
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
# Conjugate flag of the provided operands must match the original qualifiers
|
|
571
|
+
if self.lazy_conjugation[operand_index] != new_operand.is_conjugate:
|
|
572
|
+
raise ValueError(f"The provided operand {operand_name} has different conjugate flag than the original operand")
|
|
573
|
+
|
|
574
|
+
self._operands[operand_index], self._operands_backup[operand_index] = templates.copy_operand_perhaps(
|
|
575
|
+
internal_operand=self._operands[operand_index],
|
|
576
|
+
operand=new_operand,
|
|
577
|
+
stream_holder=stream_holder,
|
|
578
|
+
execution_device_id=getattr(self.execution, "device_id", "cpu"),
|
|
579
|
+
operands_device_id=new_operand.device_id,
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
# Check strides after copying because copy could affect data layout?
|
|
583
|
+
# FIXME: Could end up with a broken operand state if user catches error raised here?
|
|
584
|
+
# But if we don't use copy_operand_perhaps, we can't do inplace operand reset.
|
|
585
|
+
utils.check_attribute_match(strides, self._operands[operand_index].strides, "strides")
|
|
586
|
+
|
|
587
|
+
self._logger.info(f"Operand '{operand_name}' has been reset to the new value.")
|
|
588
|
+
|
|
589
|
+
return
|
|
590
|
+
|
|
591
|
+
@utils.precondition(_check_valid_matmul)
|
|
592
|
+
def reset_operands(
|
|
593
|
+
self,
|
|
594
|
+
*,
|
|
595
|
+
a=None,
|
|
596
|
+
b=None,
|
|
597
|
+
c=None,
|
|
598
|
+
alpha=None,
|
|
599
|
+
beta=None,
|
|
600
|
+
stream: utils.AnyStream | int | None = None,
|
|
601
|
+
):
|
|
602
|
+
"""
|
|
603
|
+
Reset one or more of the operands held by this :class:`Matmul` instance.
|
|
604
|
+
Only the operands explicitly passed are updated; omitted operands retain
|
|
605
|
+
their current values.
|
|
606
|
+
|
|
607
|
+
This method will perform various checks on the new operands to make sure:
|
|
608
|
+
|
|
609
|
+
- The shapes, strides, datatypes match those of the old ones.
|
|
610
|
+
- The packages that the operands belong to match those of the old ones.
|
|
611
|
+
- If input tensors are on GPU, the device must match.
|
|
612
|
+
|
|
613
|
+
.. versionchanged:: 0.9
|
|
614
|
+
All parameters are now keyword-only.
|
|
615
|
+
|
|
616
|
+
Args:
|
|
617
|
+
a: {a}
|
|
618
|
+
|
|
619
|
+
b: {b}
|
|
620
|
+
|
|
621
|
+
c: {c}
|
|
622
|
+
|
|
623
|
+
alpha: {alpha}
|
|
624
|
+
|
|
625
|
+
beta: {beta}
|
|
626
|
+
|
|
627
|
+
stream: {stream}
|
|
628
|
+
|
|
629
|
+
Examples:
|
|
630
|
+
|
|
631
|
+
>>> import cupy as cp
|
|
632
|
+
>>> import nvmath
|
|
633
|
+
|
|
634
|
+
Create two 3-D float64 ndarrays on the GPU:
|
|
635
|
+
|
|
636
|
+
>>> M, N, K = 128, 128, 256
|
|
637
|
+
>>> a = cp.random.rand(M, K)
|
|
638
|
+
>>> b = cp.random.rand(K, N)
|
|
639
|
+
|
|
640
|
+
Create an matrix multiplication object as a context manager
|
|
641
|
+
|
|
642
|
+
>>> with nvmath.linalg.Matmul(a, b) as mm:
|
|
643
|
+
... # Plan the operation.
|
|
644
|
+
... mm.plan()
|
|
645
|
+
...
|
|
646
|
+
... # Execute the MM to get the first result.
|
|
647
|
+
... r1 = mm.execute()
|
|
648
|
+
...
|
|
649
|
+
... # Reset the operands to new CuPy ndarrays.
|
|
650
|
+
... a_new = cp.random.rand(M, K)
|
|
651
|
+
... b_new = cp.random.rand(K, N)
|
|
652
|
+
... mm.reset_operands(a=a_new, b=b_new)
|
|
653
|
+
...
|
|
654
|
+
... # Execute to get the new result corresponding to the updated operands.
|
|
655
|
+
... r2 = mm.execute()
|
|
656
|
+
|
|
657
|
+
Note that if only a subset of operands are reset, the operands that are not
|
|
658
|
+
reset hold their original values.
|
|
659
|
+
|
|
660
|
+
With :meth:`reset_operands`, minimal overhead is achieved as problem
|
|
661
|
+
specification and planning are only performed once.
|
|
662
|
+
|
|
663
|
+
For the particular example above, a slower alternative to calling
|
|
664
|
+
:meth:`reset_operands` would be to modify the operands in-place (e.g.,
|
|
665
|
+
``a[:] = a_new`` and ``b[:] = b_new``), but this approach is less efficient
|
|
666
|
+
as it involves copying data rather than just updating pointers.
|
|
667
|
+
|
|
668
|
+
For more details, please refer to `inplace update example
|
|
669
|
+
<https://github.com/NVIDIA/nvmath-python/tree/main/examples/linalg/advanced/matmul/example05_stateful_inplace.py>`_.
|
|
670
|
+
"""
|
|
671
|
+
|
|
672
|
+
if c is not None and self.num_operands == 2:
|
|
673
|
+
raise ValueError(
|
|
674
|
+
"The matrix multiplication problem specification does not include operand C, so it cannot be reset."
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
# If operands have been released, all required operands must be provided
|
|
678
|
+
if self._operands_released:
|
|
679
|
+
all_provided = a is not None and b is not None and (self.num_operands != 3 or c is not None)
|
|
680
|
+
if not all_provided:
|
|
681
|
+
raise ValueError(
|
|
682
|
+
"After release_operands(), all required operands must be provided to reset_operands(). "
|
|
683
|
+
f"Required: a, b{', c' if self.num_operands == 3 else ''}"
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
# Initialize operands to prepare for new values
|
|
687
|
+
self._operands = [None] * self.num_operands # type: ignore[list-item]
|
|
688
|
+
self._operands_backup = [None] * self.num_operands
|
|
689
|
+
|
|
690
|
+
# Update alpha.
|
|
691
|
+
if alpha is not None:
|
|
692
|
+
try:
|
|
693
|
+
self.alpha[0] = alpha
|
|
694
|
+
except (ValueError, TypeError) as e:
|
|
695
|
+
raise ValueError(
|
|
696
|
+
f"The value provided for alpha {alpha} is not convertible to dtype '{self.alpha.dtype}'."
|
|
697
|
+
) from e
|
|
698
|
+
|
|
699
|
+
# Update beta.
|
|
700
|
+
if beta is not None:
|
|
701
|
+
if self.num_operands == 2:
|
|
702
|
+
self._logger.warning(f"Matmul: The provided beta value {beta} is ignored since operand C is not specified.")
|
|
703
|
+
else:
|
|
704
|
+
try:
|
|
705
|
+
self.beta[0] = beta
|
|
706
|
+
except (ValueError, TypeError) as e:
|
|
707
|
+
raise ValueError(
|
|
708
|
+
f"The value provided for beta {beta} is not convertible to dtype '{self.beta.dtype}'."
|
|
709
|
+
) from e
|
|
710
|
+
|
|
711
|
+
_, operand_stream_holder = self._get_or_create_stream_maybe(stream)
|
|
712
|
+
|
|
713
|
+
# Reset the provided operands.
|
|
714
|
+
if a is not None:
|
|
715
|
+
a = tensor_wrapper.wrap_operand(a)
|
|
716
|
+
index = 0
|
|
717
|
+
self._check_and_set_operand(
|
|
718
|
+
a,
|
|
719
|
+
"A",
|
|
720
|
+
operand_stream_holder,
|
|
721
|
+
operand_index=index,
|
|
722
|
+
dtype=self.a_dtype_name,
|
|
723
|
+
extents=self.operand_extents[index],
|
|
724
|
+
strides=self.operand_strides[index],
|
|
725
|
+
)
|
|
726
|
+
|
|
727
|
+
if b is not None:
|
|
728
|
+
b = tensor_wrapper.wrap_operand(b)
|
|
729
|
+
index = 1
|
|
730
|
+
self._check_and_set_operand(
|
|
731
|
+
b,
|
|
732
|
+
"B",
|
|
733
|
+
operand_stream_holder,
|
|
734
|
+
operand_index=index,
|
|
735
|
+
dtype=self.b_dtype_name,
|
|
736
|
+
extents=self.operand_extents[index],
|
|
737
|
+
strides=self.operand_strides[index],
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
if c is not None: # If we get here, we know that C is one of the operands in the problem specification.
|
|
741
|
+
c = tensor_wrapper.wrap_operand(c)
|
|
742
|
+
index = 2
|
|
743
|
+
self._check_and_set_operand(
|
|
744
|
+
c,
|
|
745
|
+
"C",
|
|
746
|
+
operand_stream_holder,
|
|
747
|
+
operand_index=index,
|
|
748
|
+
dtype=self.c_dtype_name,
|
|
749
|
+
extents=self.operand_extents[index],
|
|
750
|
+
strides=self.operand_strides[index],
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
# Clear the released flag since we now have valid operands
|
|
754
|
+
self._operands_released = False
|
|
755
|
+
|
|
756
|
+
@utils.precondition(_check_valid_matmul)
|
|
757
|
+
@utils.precondition(templates.StatefulAPI._check_planned, "Execution")
|
|
758
|
+
@utils.precondition(_check_valid_operands)
|
|
759
|
+
def execute(self, *, stream: utils.AnyStream | int | None = None) -> utils.AnyTensor:
|
|
760
|
+
"""
|
|
761
|
+
Execute a prepared (planned) matrix multiplication.
|
|
762
|
+
|
|
763
|
+
This method is a wrapper around :py:meth:`_execute`, which takes the same arguments,
|
|
764
|
+
but skips as many correctness and safety checks as possible.
|
|
765
|
+
|
|
766
|
+
Args:
|
|
767
|
+
stream: {stream}
|
|
768
|
+
|
|
769
|
+
Returns:
|
|
770
|
+
{result}
|
|
771
|
+
"""
|
|
772
|
+
return self._execute(stream=stream)
|
|
773
|
+
|
|
774
|
+
def _execute(self, *, stream: utils.AnyStream | int | None = None) -> utils.AnyTensor:
|
|
775
|
+
log_info = self._logger.isEnabledFor(logging.INFO)
|
|
776
|
+
log_debug = self._logger.isEnabledFor(logging.DEBUG)
|
|
777
|
+
if log_info:
|
|
778
|
+
self._logger.info("= EXECUTION PHASE =")
|
|
779
|
+
exec_stream_holder, operand_stream_holder = self._get_or_create_stream_maybe(stream)
|
|
780
|
+
if log_info:
|
|
781
|
+
self._logger.info(
|
|
782
|
+
"The specified stream for execute() is "
|
|
783
|
+
f"{getattr(exec_stream_holder or operand_stream_holder, 'obj', 'no stream')}."
|
|
784
|
+
)
|
|
785
|
+
|
|
786
|
+
# We must handle all valid combinations of:
|
|
787
|
+
# - c-provided and c-not-provided
|
|
788
|
+
# - results in-place and out-of-place
|
|
789
|
+
|
|
790
|
+
# Create empty tensor for the result.
|
|
791
|
+
if self.num_operands == 2 or not self.options.inplace:
|
|
792
|
+
if log_debug:
|
|
793
|
+
self._logger.debug("Beginning output (empty) tensor creation...")
|
|
794
|
+
self._logger.debug(
|
|
795
|
+
f"The output tensor shape = {self._input_traits.d_layout_traits.shape} with strides = "
|
|
796
|
+
f"{self._input_traits.d_layout_traits.strides} and data type '{self.c_dtype_name}'."
|
|
797
|
+
)
|
|
798
|
+
self._logger.debug(
|
|
799
|
+
f"The output tensor has batch dimensions with shape {self._batch_traits[3].shape} "
|
|
800
|
+
f"and strides {self._batch_traits[3].strides}."
|
|
801
|
+
)
|
|
802
|
+
result = utils.create_empty_tensor(
|
|
803
|
+
self._result_class,
|
|
804
|
+
(*self._batch_traits[3].shape, *self._input_traits.d_layout_traits.shape),
|
|
805
|
+
self.c_dtype_name,
|
|
806
|
+
getattr(self.execution, "device_id", "cpu"),
|
|
807
|
+
exec_stream_holder,
|
|
808
|
+
# verify_strides=False because we need strides to be exactly what we
|
|
809
|
+
# request; not arbitrary if the strides aren't contiguous and dense.
|
|
810
|
+
# Otherwise, the layout parameters will mismatch what we pass to the matmul
|
|
811
|
+
# implementation.
|
|
812
|
+
verify_strides=False,
|
|
813
|
+
strides=(*self._batch_traits[3].strides, *self._input_traits.d_layout_traits.strides),
|
|
814
|
+
)
|
|
815
|
+
if log_debug:
|
|
816
|
+
self._logger.debug("The output (empty) tensor has been created.")
|
|
817
|
+
else: # num_operands == 3 and self.options.inplace
|
|
818
|
+
result = self._operands[2]
|
|
819
|
+
self._logger.debug("The output tensor is C (in-place execution).")
|
|
820
|
+
|
|
821
|
+
if self.num_operands == 3 and not self.options.inplace:
|
|
822
|
+
result.copy_(self._operands[2], exec_stream_holder)
|
|
823
|
+
self._logger.debug("Operand C copied to result tensor (out-of-place execution).")
|
|
824
|
+
|
|
825
|
+
a, b = self._operands[0], self._operands[1]
|
|
826
|
+
if log_info:
|
|
827
|
+
self._logger.info("Starting matrix multiplication...")
|
|
828
|
+
|
|
829
|
+
if self.execution.name == "cuda":
|
|
830
|
+
assert exec_stream_holder is not None
|
|
831
|
+
with utils.cuda_call_ctx(exec_stream_holder, self._blocking, timing=log_info) as (
|
|
832
|
+
_,
|
|
833
|
+
elapsed,
|
|
834
|
+
):
|
|
835
|
+
self._function(
|
|
836
|
+
a,
|
|
837
|
+
b,
|
|
838
|
+
result,
|
|
839
|
+
self.alpha,
|
|
840
|
+
self.beta,
|
|
841
|
+
exec_stream_holder,
|
|
842
|
+
)
|
|
843
|
+
else:
|
|
844
|
+
with utils.host_call_ctx(timing=log_info) as elapsed:
|
|
845
|
+
self._function(
|
|
846
|
+
a,
|
|
847
|
+
b,
|
|
848
|
+
result,
|
|
849
|
+
self.alpha,
|
|
850
|
+
self.beta,
|
|
851
|
+
exec_stream_holder,
|
|
852
|
+
)
|
|
853
|
+
|
|
854
|
+
if log_info and elapsed.data is not None:
|
|
855
|
+
self._logger.info(f"The matrix multiplication calculation took {elapsed.data:.3f} ms to complete.")
|
|
856
|
+
|
|
857
|
+
# Return the result and auxiliary outputs, if present.
|
|
858
|
+
if self._operands_device_id != getattr(self.execution, "device_id", "cpu"):
|
|
859
|
+
if self.options.inplace:
|
|
860
|
+
c = self._operands_backup[2]
|
|
861
|
+
assert c is not None, (
|
|
862
|
+
"Internal Error. "
|
|
863
|
+
"Inplace operation was requested, but the execution space was different from the input space, "
|
|
864
|
+
"and we didn't keep a reference to the input tensor."
|
|
865
|
+
)
|
|
866
|
+
c.copy_(result, stream_holder=operand_stream_holder)
|
|
867
|
+
out = c.tensor
|
|
868
|
+
else:
|
|
869
|
+
out = result.to(self._operands_device_id, stream_holder=operand_stream_holder).tensor
|
|
870
|
+
else:
|
|
871
|
+
out = result.tensor
|
|
872
|
+
|
|
873
|
+
return out
|
|
874
|
+
|
|
875
|
+
@utils.precondition(_check_valid_matmul)
|
|
876
|
+
def release_operands(self):
|
|
877
|
+
"""
|
|
878
|
+
{release_operands}
|
|
879
|
+
"""
|
|
880
|
+
# Same space: _operands hold direct user references.
|
|
881
|
+
# Cross space: _operands hold internal device mirrors,
|
|
882
|
+
# _operands_backup hold direct user references.
|
|
883
|
+
# In both cases, release _operands.
|
|
884
|
+
# Also release _operands_backup in cross-space.
|
|
885
|
+
self._operands = [None] * len(self._operands) # type: ignore[list-item]
|
|
886
|
+
if getattr(self.execution, "device_id", "cpu") != self._operands_device_id:
|
|
887
|
+
self._operands_backup = [None] * len(self._operands_backup)
|
|
888
|
+
|
|
889
|
+
self._operands_released = True
|
|
890
|
+
self._logger.info("User-provided operands have been released.")
|
|
891
|
+
|
|
892
|
+
def free(self):
|
|
893
|
+
"""Free Matmul resources.
|
|
894
|
+
|
|
895
|
+
It is recommended that the :class:`Matmul` object be used within a context,
|
|
896
|
+
but if it is not possible then this method must be called explicitly to ensure
|
|
897
|
+
that the Matmul object doesn't hold unnecessary references to operands.
|
|
898
|
+
"""
|
|
899
|
+
if not self.valid_state:
|
|
900
|
+
return
|
|
901
|
+
|
|
902
|
+
try:
|
|
903
|
+
# Note that here we don't need to enforce any ordering:
|
|
904
|
+
# - for CPU operands, the execution is blocking so this method
|
|
905
|
+
# will only be called after the execution is complete.
|
|
906
|
+
# - for GPU operands, the execution is non-blocking, but
|
|
907
|
+
# it operates on the user's operands and they are responsible
|
|
908
|
+
# to ensure use-after-free does not happen.
|
|
909
|
+
|
|
910
|
+
# Call parent class free
|
|
911
|
+
super().free()
|
|
912
|
+
|
|
913
|
+
# Set all attributes to None except for logger and valid_state
|
|
914
|
+
_keep = {"_logger", "valid_state"}
|
|
915
|
+
for attr in list(vars(self)):
|
|
916
|
+
if attr not in _keep:
|
|
917
|
+
setattr(self, attr, None)
|
|
918
|
+
|
|
919
|
+
except Exception as e:
|
|
920
|
+
self._logger.critical("Internal error: only part of the Matmul object's resources have been released.")
|
|
921
|
+
self._logger.critical(str(e))
|
|
922
|
+
raise e
|
|
923
|
+
finally:
|
|
924
|
+
self.valid_state = False
|
|
925
|
+
|
|
926
|
+
self._logger.info("The Matmul object's resources have been released.")
|
|
927
|
+
|
|
928
|
+
def __exit__(self, *args, **kwargs):
|
|
929
|
+
self.free()
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
@utils.docstring_decorator(GENERIC_MM_DOCUMENTATION, skip_missing=False)
|
|
933
|
+
def matmul(
|
|
934
|
+
a: AnyTensor,
|
|
935
|
+
b: AnyTensor,
|
|
936
|
+
/,
|
|
937
|
+
c: AnyTensor | None = None,
|
|
938
|
+
*,
|
|
939
|
+
alpha: float | complex | None = None,
|
|
940
|
+
beta: float | complex | None = None,
|
|
941
|
+
qualifiers: MatrixQualifier | None = None,
|
|
942
|
+
options: MatmulOptions | None = None,
|
|
943
|
+
execution: ExecutionCPU | ExecutionCUDA | None = None,
|
|
944
|
+
stream: utils.AnyStream | int | None = None,
|
|
945
|
+
):
|
|
946
|
+
"""
|
|
947
|
+
Perform the specified matrix multiplication computation :math:`\\alpha a @ b + \\beta
|
|
948
|
+
c`. This function-form is a wrapper around the stateful :class:`Matmul` object APIs and
|
|
949
|
+
is meant for *single* use (the user needs to perform just one matrix multiplication, for
|
|
950
|
+
example), in which case there is no possibility of amortizing preparatory costs.
|
|
951
|
+
|
|
952
|
+
Detailed information on what's happening within this function can be obtained by passing
|
|
953
|
+
in a :class:`logging.Logger` object to :class:`MatmulOptions` or by setting the
|
|
954
|
+
appropriate options in the root logger object, which is used by default:
|
|
955
|
+
|
|
956
|
+
>>> import logging
|
|
957
|
+
>>> logging.basicConfig(
|
|
958
|
+
... level=logging.INFO,
|
|
959
|
+
... format="%(asctime)s %(levelname)-8s %(message)s",
|
|
960
|
+
... datefmt="%m-%d %H:%M:%S",
|
|
961
|
+
... )
|
|
962
|
+
|
|
963
|
+
A user can select the desired logging level and, in general, take advantage of all of
|
|
964
|
+
the functionality offered by the Python `logging` module.
|
|
965
|
+
|
|
966
|
+
Args:
|
|
967
|
+
a: {a}
|
|
968
|
+
|
|
969
|
+
b: {b}
|
|
970
|
+
|
|
971
|
+
c: {c}
|
|
972
|
+
|
|
973
|
+
alpha: {alpha}
|
|
974
|
+
|
|
975
|
+
beta: {beta}
|
|
976
|
+
|
|
977
|
+
qualifiers: {qualifiers}
|
|
978
|
+
|
|
979
|
+
options: {options}
|
|
980
|
+
|
|
981
|
+
execution: {execution}
|
|
982
|
+
|
|
983
|
+
stream: {stream}
|
|
984
|
+
|
|
985
|
+
Returns:
|
|
986
|
+
{result}
|
|
987
|
+
|
|
988
|
+
Semantics:
|
|
989
|
+
{semantics}
|
|
990
|
+
|
|
991
|
+
.. seealso::
|
|
992
|
+
:class:`Matmul`, :class:`MatmulOptions`, :class:`matrix_qualifiers_dtype`,
|
|
993
|
+
:class:`MatrixQualifier`
|
|
994
|
+
|
|
995
|
+
Examples:
|
|
996
|
+
|
|
997
|
+
>>> import cupy as cp
|
|
998
|
+
>>> import nvmath
|
|
999
|
+
|
|
1000
|
+
Create three float32 ndarrays on the GPU:
|
|
1001
|
+
|
|
1002
|
+
>>> M, N, K = 128, 64, 256
|
|
1003
|
+
>>> a = cp.random.rand(M, K, dtype=cp.float32)
|
|
1004
|
+
>>> b = cp.random.rand(K, N, dtype=cp.float32)
|
|
1005
|
+
>>> c = cp.random.rand(M, N, dtype=cp.float32)
|
|
1006
|
+
|
|
1007
|
+
Perform the operation :math:`\\alpha A @ B + \\beta C` using :func:`matmul`. The
|
|
1008
|
+
result `r` is also a CuPy float64 ndarray:
|
|
1009
|
+
|
|
1010
|
+
>>> r = nvmath.linalg.matmul(a, b, c, alpha=1.23, beta=0.74)
|
|
1011
|
+
|
|
1012
|
+
The package current stream is used by default, but a stream can be explicitly
|
|
1013
|
+
provided to the Matmul operation. This can be done if the operands are computed on a
|
|
1014
|
+
different stream, for example:
|
|
1015
|
+
|
|
1016
|
+
>>> s = cp.cuda.Stream()
|
|
1017
|
+
>>> with s:
|
|
1018
|
+
... a = cp.random.rand(M, K)
|
|
1019
|
+
... b = cp.random.rand(K, N)
|
|
1020
|
+
>>> r = nvmath.linalg.matmul(a, b, stream=s)
|
|
1021
|
+
|
|
1022
|
+
The operation above runs on stream `s` and is ordered with respect to the input
|
|
1023
|
+
computation.
|
|
1024
|
+
|
|
1025
|
+
Create NumPy ndarrays on the CPU.
|
|
1026
|
+
|
|
1027
|
+
>>> import numpy as np
|
|
1028
|
+
>>> a = np.random.rand(M, K)
|
|
1029
|
+
>>> b = np.random.rand(K, N)
|
|
1030
|
+
|
|
1031
|
+
Provide the NumPy ndarrays to :func:`matmul`, with the result also being a NumPy
|
|
1032
|
+
ndarray:
|
|
1033
|
+
|
|
1034
|
+
>>> r = nvmath.linalg.matmul(a, b)
|
|
1035
|
+
|
|
1036
|
+
Notes:
|
|
1037
|
+
- This function is a convenience wrapper around :class:`Matmul` and and is
|
|
1038
|
+
specifically meant for *single* use.
|
|
1039
|
+
|
|
1040
|
+
Further examples can be found in the `nvmath/examples/linalg/generic/matmul
|
|
1041
|
+
<https://github.com/NVIDIA/nvmath-python/tree/main/examples/linalg/generic/matmul>`_
|
|
1042
|
+
directory.
|
|
1043
|
+
"""
|
|
1044
|
+
|
|
1045
|
+
with Matmul(
|
|
1046
|
+
a,
|
|
1047
|
+
b,
|
|
1048
|
+
c=c,
|
|
1049
|
+
alpha=alpha,
|
|
1050
|
+
beta=beta,
|
|
1051
|
+
qualifiers=qualifiers,
|
|
1052
|
+
options=options,
|
|
1053
|
+
execution=execution,
|
|
1054
|
+
stream=stream,
|
|
1055
|
+
) as mm:
|
|
1056
|
+
mm.plan(stream=stream)
|
|
1057
|
+
|
|
1058
|
+
r = mm.execute(stream=stream)
|
|
1059
|
+
|
|
1060
|
+
return r
|