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,43 @@
|
|
|
1
|
+
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import typing
|
|
7
|
+
from collections.abc import Sequence
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
LoggerLike: typing.TypeAlias = logging.Logger | logging.LoggerAdapter
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_addresses_of_elements(
|
|
15
|
+
shape: Sequence[int],
|
|
16
|
+
stride: Sequence[int],
|
|
17
|
+
address: int,
|
|
18
|
+
itemsize: int,
|
|
19
|
+
) -> np.ndarray:
|
|
20
|
+
"""Return the byte address of each element in a strided memory block.
|
|
21
|
+
|
|
22
|
+
Elements are enumerated in C order (last dimension varies fastest). The traversal
|
|
23
|
+
order is canonical and independent of the tensor's memory layout, so multiple calls
|
|
24
|
+
with different tensors always produce consistently ordered pointer arrays suitable
|
|
25
|
+
for use as batched-GEMM pointer arrays.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
shape: the number of elements along each dimension of the memory block
|
|
29
|
+
stride: the stride along each dimension, in number of elements
|
|
30
|
+
address: the base byte address of the memory block (address of the element at
|
|
31
|
+
the all-zero index)
|
|
32
|
+
itemsize: the number of bytes per element
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
A 1-D numpy array of byte addresses (dtype np.intp), one per element, in C order.
|
|
36
|
+
"""
|
|
37
|
+
assert len(stride) == len(shape), f"stride length {len(stride)} must match shape length {len(shape)}"
|
|
38
|
+
assert itemsize >= 0, f"itemsize must be non-negative, got {itemsize}"
|
|
39
|
+
assert address >= 0, f"address must be non-negative, got {address}"
|
|
40
|
+
if not shape:
|
|
41
|
+
return np.array([address], dtype=np.intp)
|
|
42
|
+
offset_vecs = [np.arange(shape[a], dtype=np.intp) * (itemsize * stride[a]) for a in range(len(shape))]
|
|
43
|
+
return address + sum(np.ix_(*offset_vecs)).ravel() # type: ignore[union-attr]
|
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""Reusable workspace buffer manager for stateful nvmath APIs.
|
|
6
|
+
|
|
7
|
+
A *workspace* is a non-operand scratch buffer the underlying library (cuBLASLt,
|
|
8
|
+
cuFFT, cuSparse, ...) requires for its ``execute`` step. ``Workspace`` manages the
|
|
9
|
+
lifecycle — allocation, reuse across calls, stream-ordered release, and exception
|
|
10
|
+
cleanup — so each stateful API doesn't have to.
|
|
11
|
+
|
|
12
|
+
The required size is recorded during ``plan()`` (via :meth:`Workspace.set_size`); the
|
|
13
|
+
buffer is allocated lazily on first use and reused on subsequent calls.
|
|
14
|
+
|
|
15
|
+
The allocator is pluggable. Choose one based on what kind of memory you need:
|
|
16
|
+
|
|
17
|
+
- :class:`BaseCUDAMemoryManager` — sync device pool (e.g. cupy, torch caching)
|
|
18
|
+
- :class:`BaseCUDAMemoryManagerAsync` — stream-aware device pool (e.g. CUDA async)
|
|
19
|
+
- :class:`cuda.core.MemoryResource` — covers both device and host memory
|
|
20
|
+
(e.g. ``cuda.core.DeviceMemoryResource``, ``cuda.core.PinnedMemoryResource``,
|
|
21
|
+
or ``NumpyMemoryResource``)
|
|
22
|
+
|
|
23
|
+
:meth:`Workspace.allocate_perhaps` is a context manager: the ``with`` block defines
|
|
24
|
+
the lifetime of the consumer's use of the buffer. On normal exit the buffer is kept
|
|
25
|
+
for reuse on the next call. On exception, any buffer this call allocated fresh is
|
|
26
|
+
released. The consumer never has to write an exception handler for the workspace.
|
|
27
|
+
|
|
28
|
+
Typical use::
|
|
29
|
+
|
|
30
|
+
class Matmul:
|
|
31
|
+
def __init__(self, ..., allocator, logger):
|
|
32
|
+
self.workspace = Workspace(allocator, logger, label="matmul workspace")
|
|
33
|
+
self.last_compute_event = None
|
|
34
|
+
|
|
35
|
+
def plan(self, ...):
|
|
36
|
+
...
|
|
37
|
+
self.workspace.set_size(required_bytes)
|
|
38
|
+
|
|
39
|
+
def execute(self, *, release_workspace=False, stream_holder, ...):
|
|
40
|
+
with self.workspace.allocate_perhaps(
|
|
41
|
+
stream_holder, get_last_event=lambda: self.last_compute_event
|
|
42
|
+
) as ws:
|
|
43
|
+
cublaslt.matmul(..., ws.raw_ptr, ws.size, ...)
|
|
44
|
+
self.last_compute_event = stream_holder.obj.record()
|
|
45
|
+
if release_workspace:
|
|
46
|
+
ws.release(self.last_compute_event)
|
|
47
|
+
# If the event is not managing the lifetime of other objects,
|
|
48
|
+
# null it to prevent a duplicate wait in self.free().
|
|
49
|
+
self.last_compute_event = None
|
|
50
|
+
|
|
51
|
+
def free(self, ...):
|
|
52
|
+
self.workspace.release(self.last_compute_event)
|
|
53
|
+
self.last_compute_event = None
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
__all__ = ["Workspace", "NumpyMemoryResource", "get_pinned_memory_resource"]
|
|
57
|
+
|
|
58
|
+
import contextlib
|
|
59
|
+
from collections.abc import Callable
|
|
60
|
+
from contextlib import nullcontext
|
|
61
|
+
|
|
62
|
+
import numpy as np
|
|
63
|
+
from cuda.core import Buffer, Event, MemoryResource, Stream
|
|
64
|
+
|
|
65
|
+
from nvmath._internal.utils import LoggerLike
|
|
66
|
+
from nvmath.internal import formatters, utils
|
|
67
|
+
from nvmath.internal.memory import get_legacy_pinned_memory_resource, get_pinned_async_memory_resource
|
|
68
|
+
from nvmath.internal.package_ifc import StreamHolder
|
|
69
|
+
from nvmath.memory import (
|
|
70
|
+
BaseCUDAMemoryManager,
|
|
71
|
+
BaseCUDAMemoryManagerAsync,
|
|
72
|
+
MemoryPointer,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
_PROTOCOL_TYPE_ERROR_MESSAGE = (
|
|
76
|
+
"The method 'memalloc' in the allocator object must conform to the interface in the 'BaseCUDAMemoryManager' protocol."
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class Workspace:
|
|
81
|
+
"""Manages a CUDA scratch buffer with reuse, stream-ordered release, and
|
|
82
|
+
exception cleanup.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
allocator: A :class:`BaseCUDAMemoryManager`, :class:`BaseCUDAMemoryManagerAsync`,
|
|
86
|
+
or :class:`cuda.core.MemoryResource`. Use a ``MemoryResource``
|
|
87
|
+
(e.g. ``PinnedMemoryResource``) when you need a host buffer or any backing
|
|
88
|
+
the legacy protocols can't express.
|
|
89
|
+
logger: Logger for debug-level allocation/release messages.
|
|
90
|
+
device_id: The CUDA device the workspace lives on. Optional — if omitted,
|
|
91
|
+
it is read from ``allocator.device_id``. ``cuda.core.MemoryResource``
|
|
92
|
+
declares this attribute in its protocol; the legacy
|
|
93
|
+
``BaseCUDAMemoryManager(Async)`` protocols don't, but every in-tree
|
|
94
|
+
implementation exposes it as a convention. Pass this explicitly when
|
|
95
|
+
the allocator can't supply it: a third-party legacy allocator that
|
|
96
|
+
doesn't expose ``device_id``, or a pinned-only resource (e.g.
|
|
97
|
+
``LegacyPinnedMemoryResource``) whose ``device_id`` deliberately
|
|
98
|
+
raises because pinned memory isn't bound to a specific GPU.
|
|
99
|
+
label: Short noun for log messages (e.g. ``"matmul workspace"``).
|
|
100
|
+
on_allocated: Optional callback invoked with the raw pointer right after a
|
|
101
|
+
successful allocation, inside the device + stream context. Used by cuFFT
|
|
102
|
+
to register the buffer with its handle via ``cufft.set_work_area``.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
__slots__ = (
|
|
106
|
+
# Public state (also annotated below for type checkers / docs).
|
|
107
|
+
"ptr",
|
|
108
|
+
"size",
|
|
109
|
+
"allocated_size",
|
|
110
|
+
"allocated_here",
|
|
111
|
+
"stream",
|
|
112
|
+
# Configuration cached from __init__.
|
|
113
|
+
"_allocator",
|
|
114
|
+
"_logger",
|
|
115
|
+
"_label",
|
|
116
|
+
"_on_allocated",
|
|
117
|
+
"_get_ptr",
|
|
118
|
+
"_close_on_release",
|
|
119
|
+
"_is_device_accessible",
|
|
120
|
+
"_device_id",
|
|
121
|
+
# Transient per-call state set by allocate_perhaps, read by __enter__ /
|
|
122
|
+
# __exit__, cleared back to None on __exit__.
|
|
123
|
+
"_stream_holder",
|
|
124
|
+
"_get_last_event",
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
ptr: MemoryPointer | Buffer | None
|
|
128
|
+
"""The owning buffer wrapper, or ``None`` when no buffer is held. Use
|
|
129
|
+
:attr:`raw_ptr` to get the integer pointer for C bindings."""
|
|
130
|
+
|
|
131
|
+
size: int
|
|
132
|
+
"""Bytes required by the current plan. Set via :meth:`set_size`."""
|
|
133
|
+
|
|
134
|
+
allocated_size: int
|
|
135
|
+
"""Bytes currently held by ``ptr``. May exceed ``size`` after a smaller plan
|
|
136
|
+
(the held buffer is never shrunk)."""
|
|
137
|
+
|
|
138
|
+
allocated_here: bool
|
|
139
|
+
"""``True`` iff the current ``allocate_perhaps`` ``with`` block allocated this
|
|
140
|
+
buffer (rather than inheriting one from a previous call). Managed structurally by
|
|
141
|
+
the context manager — consumers don't need to set or reset it themselves. Exposed
|
|
142
|
+
for introspection."""
|
|
143
|
+
|
|
144
|
+
stream: Stream | None
|
|
145
|
+
"""The stream the buffer was allocated on. Used by :meth:`release` to order
|
|
146
|
+
freeing against the consumer's last compute event."""
|
|
147
|
+
|
|
148
|
+
def __init__(
|
|
149
|
+
self,
|
|
150
|
+
allocator: BaseCUDAMemoryManager | BaseCUDAMemoryManagerAsync | MemoryResource,
|
|
151
|
+
logger: LoggerLike,
|
|
152
|
+
*,
|
|
153
|
+
device_id: int | None = None,
|
|
154
|
+
label: str = "workspace",
|
|
155
|
+
on_allocated: Callable[[int], None] | None = None,
|
|
156
|
+
) -> None:
|
|
157
|
+
self._allocator = allocator
|
|
158
|
+
self._logger = logger
|
|
159
|
+
self._label = label
|
|
160
|
+
self._on_allocated = on_allocated
|
|
161
|
+
|
|
162
|
+
# MemoryResource returns a Buffer (which needs explicit, stream-ordered
|
|
163
|
+
# close()); legacy allocators return a MemoryPointer (whose finalizer
|
|
164
|
+
# fires on reference-drop). Cache both decisions at construction for the
|
|
165
|
+
# release hot path.
|
|
166
|
+
if isinstance(allocator, MemoryResource):
|
|
167
|
+
self._get_ptr: Callable[..., int] = self._get_ptr_from_buffer
|
|
168
|
+
self._close_on_release = True
|
|
169
|
+
else:
|
|
170
|
+
self._get_ptr = utils.get_ptr_from_memory_pointer
|
|
171
|
+
self._close_on_release = False
|
|
172
|
+
|
|
173
|
+
# Mirrors the allocator's cuda.core.MemoryResource.is_device_accessible
|
|
174
|
+
# property; legacy BaseCUDAMemoryManager(Async) allocators have no such
|
|
175
|
+
# flag and are device-accessible by definition. Drives whether allocate /
|
|
176
|
+
# release enter the device + stream context.
|
|
177
|
+
self._is_device_accessible = not isinstance(allocator, MemoryResource) or allocator.is_device_accessible
|
|
178
|
+
|
|
179
|
+
# AttributeError covers third-party allocators that simply don't expose
|
|
180
|
+
# device_id; RuntimeError covers cuda.core's LegacyPinnedMemoryResource,
|
|
181
|
+
# whose property raises by design (pinned memory isn't bound to a GPU).
|
|
182
|
+
if device_id is None:
|
|
183
|
+
try:
|
|
184
|
+
device_id = allocator.device_id # type: ignore[union-attr]
|
|
185
|
+
except (AttributeError, RuntimeError):
|
|
186
|
+
device_id = None
|
|
187
|
+
if self._is_device_accessible and device_id is None:
|
|
188
|
+
raise TypeError(
|
|
189
|
+
f"Workspace could not infer device_id from {type(allocator).__name__}: "
|
|
190
|
+
"the allocator is device-accessible but has no usable 'device_id' attribute. "
|
|
191
|
+
"Pass device_id=<int> to Workspace explicitly."
|
|
192
|
+
)
|
|
193
|
+
self._device_id: int | None = device_id
|
|
194
|
+
|
|
195
|
+
self.ptr = None
|
|
196
|
+
self.size = 0
|
|
197
|
+
self.allocated_size = 0
|
|
198
|
+
self.allocated_here = False
|
|
199
|
+
self.stream = None
|
|
200
|
+
# Transient per-call state set by allocate_perhaps and read by
|
|
201
|
+
# __enter__ / __exit__. None outside an active `with` block.
|
|
202
|
+
self._stream_holder: StreamHolder | None = None
|
|
203
|
+
self._get_last_event: Callable[[], Event | None] | None = None
|
|
204
|
+
|
|
205
|
+
@staticmethod
|
|
206
|
+
def _get_ptr_from_buffer(buffer: Buffer) -> int:
|
|
207
|
+
"""Pointer extractor for ``MemoryResource``-backed buffers. The ``int()``
|
|
208
|
+
cast unwraps cuda.core's ``CUdeviceptr`` (a typed alias of int)."""
|
|
209
|
+
return int(buffer.handle)
|
|
210
|
+
|
|
211
|
+
# ------------------------------------------------------------------ plan
|
|
212
|
+
def set_size(self, size: int) -> None:
|
|
213
|
+
"""Record the size required by the current plan."""
|
|
214
|
+
if size < 0:
|
|
215
|
+
raise ValueError(f"Internal Error: Cannot allocate {self._label} of size {size} < 0.")
|
|
216
|
+
self.size = size
|
|
217
|
+
|
|
218
|
+
# ------------------------------------------------------------ allocation
|
|
219
|
+
def allocate_perhaps(
|
|
220
|
+
self,
|
|
221
|
+
stream_holder: StreamHolder | None,
|
|
222
|
+
*,
|
|
223
|
+
get_last_event: Callable[[], Event | None] | None,
|
|
224
|
+
) -> "Workspace":
|
|
225
|
+
"""Acquire the workspace buffer for the duration of a ``with`` block.
|
|
226
|
+
|
|
227
|
+
On entry, the buffer is either reused (if it's already at least ``size`` bytes) or
|
|
228
|
+
freshly allocated. If the held buffer is too small, it is released first — with a
|
|
229
|
+
stream wait against ``get_last_event()`` to order the free against any pending
|
|
230
|
+
compute on the prior buffer — and then a fresh buffer is allocated. Inside the
|
|
231
|
+
block, read ``ws.raw_ptr`` and ``ws.size`` to plumb into a C binding, and optionally
|
|
232
|
+
call ``ws.release(...)`` to free the buffer mid-scope.
|
|
233
|
+
|
|
234
|
+
On normal exit the buffer is kept for reuse on the next call. On exception, if this
|
|
235
|
+
call allocated a fresh buffer it is released — again with a stream wait against
|
|
236
|
+
``get_last_event()``. The consumer doesn't write an exception handler.
|
|
237
|
+
|
|
238
|
+
Host-only mode (no CUDA driver required): when the allocator is a
|
|
239
|
+
:class:`cuda.core.MemoryResource` with ``is_device_accessible=False`` (e.g.
|
|
240
|
+
:class:`NumpyMemoryResource`), Workspace skips the device + stream context
|
|
241
|
+
entirely. ``stream_holder`` is ignored in this mode and may be ``None``;
|
|
242
|
+
the allocator is invoked with ``stream=None`` and :meth:`release` will pass
|
|
243
|
+
``None`` to ``Buffer.close``. Pinned memory (``PinnedMemoryResource``) is
|
|
244
|
+
*device-accessible* — it needs the stream for ordered close and goes through
|
|
245
|
+
the device path even though its ``device_id`` is ``-1``.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
stream_holder: The stream the workspace is allocated on. May be ``None``
|
|
249
|
+
only when the allocator is a host-only ``MemoryResource``.
|
|
250
|
+
get_last_event: A callable returning the consumer's current
|
|
251
|
+
``last_compute_event``. Called at ``__enter__`` time on the grow path
|
|
252
|
+
(when a held buffer is too small) and at ``__exit__`` time on the
|
|
253
|
+
exception path. Late-binding via callable lets the consumer assign a
|
|
254
|
+
fresh event inside the block and have the next ``__enter__`` see it.
|
|
255
|
+
Required whenever the held buffer may have pending compute (both
|
|
256
|
+
device-only and host-accessible workspaces): the released buffer is
|
|
257
|
+
stream-waited against this event before its (stream-ordered) close.
|
|
258
|
+
May be ``None`` for workspaces guaranteed to be quiescent at the
|
|
259
|
+
relevant call sites.
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
self: A reference to this Workspace. The Workspace is its own context
|
|
263
|
+
manager — the ``with`` statement calls ``__enter__`` to perform
|
|
264
|
+
allocation and ``__exit__`` to handle cleanup. Inside the block,
|
|
265
|
+
read ``self.raw_ptr`` / ``self.size``; optionally call
|
|
266
|
+
``self.release(...)``.
|
|
267
|
+
"""
|
|
268
|
+
# NOTE: get_last_event is required so that human developers must think about
|
|
269
|
+
# using this parameter.
|
|
270
|
+
# Stash transient per-call params for __enter__ / __exit__ to read.
|
|
271
|
+
# Workspace is its own context manager (rather than returning a separate
|
|
272
|
+
# scope object) to avoid the per-call Python object alloc on the hot path.
|
|
273
|
+
# Safe because allocate_perhaps is documented as not reentrant — the
|
|
274
|
+
# __enter__ check on allocated_here would catch any nested misuse.
|
|
275
|
+
self._stream_holder = stream_holder
|
|
276
|
+
self._get_last_event = get_last_event
|
|
277
|
+
return self
|
|
278
|
+
|
|
279
|
+
def __enter__(self) -> "Workspace":
|
|
280
|
+
if self.allocated_here:
|
|
281
|
+
raise RuntimeError(
|
|
282
|
+
"Workspace.allocate_perhaps is reusable but not reentrant: allocated_here was "
|
|
283
|
+
"True on entry, meaning a prior `with` block didn't exit through __exit__ "
|
|
284
|
+
"(consumer bypassed the `with` statement or caught and suppressed the exit-path)."
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
gle = self._get_last_event
|
|
288
|
+
|
|
289
|
+
# Grow-path cleanup: a held buffer that's too small must be released before the
|
|
290
|
+
# fresh allocation overwrites self.ptr — otherwise the old Buffer's __del__ runs
|
|
291
|
+
# unordered against any pending compute that was still reading it. release()
|
|
292
|
+
# handles the stream wait, the stream-ordered close(), and clears all
|
|
293
|
+
# post-release state, leaving the fresh-alloc dispatch with a clean slate.
|
|
294
|
+
if self.ptr is not None and self.allocated_size < self.size:
|
|
295
|
+
self.release(gle() if gle is not None else None)
|
|
296
|
+
|
|
297
|
+
# Allocation with rollback: drop any partial state if _allocate_perhaps raises.
|
|
298
|
+
try:
|
|
299
|
+
self._allocate_perhaps(self._stream_holder)
|
|
300
|
+
except BaseException:
|
|
301
|
+
self.ptr = None
|
|
302
|
+
self.allocated_size = 0
|
|
303
|
+
self.allocated_here = False
|
|
304
|
+
self.stream = None
|
|
305
|
+
raise
|
|
306
|
+
|
|
307
|
+
return self
|
|
308
|
+
|
|
309
|
+
def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None:
|
|
310
|
+
if exc_type is not None and self.allocated_here:
|
|
311
|
+
gle = self._get_last_event
|
|
312
|
+
self.release(gle() if gle is not None else None)
|
|
313
|
+
self.allocated_here = False
|
|
314
|
+
# Drop transient per-call refs — they don't outlive the with-block.
|
|
315
|
+
self._stream_holder = None
|
|
316
|
+
self._get_last_event = None
|
|
317
|
+
|
|
318
|
+
def _allocate_perhaps(self, stream_holder: StreamHolder | None) -> None:
|
|
319
|
+
"""Internal — perform the allocation (or reuse short-circuit). Called from
|
|
320
|
+
the :meth:`allocate_perhaps` context manager's ``__enter__``.
|
|
321
|
+
|
|
322
|
+
Implementation notes:
|
|
323
|
+
|
|
324
|
+
- The legacy ``BaseCUDAMemoryManager(Async)`` 0-byte fast path produces a
|
|
325
|
+
``MemoryPointer(0, 0, ...)`` sentinel rather than calling ``memalloc(0)``,
|
|
326
|
+
since concrete implementations don't uniformly support zero-byte
|
|
327
|
+
allocations. ``MemoryResource.allocate(0)`` is well-defined and used
|
|
328
|
+
directly.
|
|
329
|
+
- Dispatch order is ``MemoryResource``, then ``BaseCUDAMemoryManagerAsync``,
|
|
330
|
+
then ``BaseCUDAMemoryManager`` (most-specific first).
|
|
331
|
+
- The ``on_allocated`` hook fires from inside the device+stream context so
|
|
332
|
+
it sees the right active context — and only on real allocations, never on
|
|
333
|
+
reuse or the 0-byte fast path.
|
|
334
|
+
"""
|
|
335
|
+
if self.ptr is not None and self.allocated_size >= self.size:
|
|
336
|
+
return
|
|
337
|
+
|
|
338
|
+
# Host-only allocators (NumpyMemoryResource and similar) skip the device
|
|
339
|
+
# + stream context to keep Workspace usable without a CUDA driver/device;
|
|
340
|
+
# stream_holder is ignored in that mode.
|
|
341
|
+
device_cm: contextlib.AbstractContextManager[object]
|
|
342
|
+
stream_cm: contextlib.AbstractContextManager[object]
|
|
343
|
+
stream_obj: Stream | None
|
|
344
|
+
if self._is_device_accessible:
|
|
345
|
+
if stream_holder is None:
|
|
346
|
+
raise TypeError(
|
|
347
|
+
"Workspace.allocate_perhaps requires stream_holder for device-accessible allocators (got None)."
|
|
348
|
+
)
|
|
349
|
+
# __init__ validates this invariant; assertion narrows int | None for mypy.
|
|
350
|
+
assert self._device_id is not None
|
|
351
|
+
device_cm = utils.device_ctx(self._device_id)
|
|
352
|
+
stream_cm = stream_holder.ctx
|
|
353
|
+
stream_obj = stream_holder.obj
|
|
354
|
+
else:
|
|
355
|
+
device_cm = nullcontext()
|
|
356
|
+
stream_cm = nullcontext()
|
|
357
|
+
stream_obj = None
|
|
358
|
+
|
|
359
|
+
self._logger.debug("Allocating %s...", self._label)
|
|
360
|
+
if self.size == 0 and not isinstance(self._allocator, MemoryResource):
|
|
361
|
+
self.ptr = MemoryPointer(0, 0, finalizer=None)
|
|
362
|
+
self.allocated_here = True
|
|
363
|
+
else:
|
|
364
|
+
with device_cm, stream_cm:
|
|
365
|
+
try:
|
|
366
|
+
if isinstance(self._allocator, MemoryResource):
|
|
367
|
+
self.ptr = self._allocator.allocate(self.size, stream=stream_obj)
|
|
368
|
+
elif isinstance(self._allocator, BaseCUDAMemoryManagerAsync):
|
|
369
|
+
self.ptr = self._allocator.memalloc_async(self.size, stream_obj)
|
|
370
|
+
else:
|
|
371
|
+
self.ptr = self._allocator.memalloc(self.size)
|
|
372
|
+
self.allocated_here = True
|
|
373
|
+
except TypeError as e:
|
|
374
|
+
raise TypeError(_PROTOCOL_TYPE_ERROR_MESSAGE) from e
|
|
375
|
+
if self._on_allocated is not None:
|
|
376
|
+
self._on_allocated(self._get_ptr(self.ptr))
|
|
377
|
+
|
|
378
|
+
self.allocated_size = self.size
|
|
379
|
+
self.stream = stream_obj
|
|
380
|
+
# %-formatting defers MemoryStr(...) __str__ until the logger emits.
|
|
381
|
+
self._logger.debug(
|
|
382
|
+
"Finished allocating %s of size %s in the context of stream %s.",
|
|
383
|
+
self._label,
|
|
384
|
+
formatters.MemoryStr(self.size),
|
|
385
|
+
self.stream,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
# ------------------------------------------------------------- free path
|
|
389
|
+
def release(self, last_compute_event: Event | None) -> None:
|
|
390
|
+
"""Wait for pending compute and free the buffer.
|
|
391
|
+
|
|
392
|
+
Call this explicitly inside an :meth:`allocate_perhaps` ``with`` block
|
|
393
|
+
(e.g. when ``release_workspace=True``) to free the buffer mid-scope. On the
|
|
394
|
+
exception path the context manager calls this internally if the buffer is
|
|
395
|
+
still held; consumers don't need to.
|
|
396
|
+
|
|
397
|
+
Safe to call when no buffer is held — it's a no-op, so a redundant second
|
|
398
|
+
release doesn't pay for a stream wait. After a successful release, null
|
|
399
|
+
your ``last_compute_event`` slot so a subsequent release doesn't re-wait
|
|
400
|
+
on a stale event.
|
|
401
|
+
|
|
402
|
+
.. important:: This is a buffer-release operation, not a standalone event-sync
|
|
403
|
+
primitive. If no buffer is held, the method returns immediately and
|
|
404
|
+
``last_compute_event`` is not waited on. For a standalone event sync, call
|
|
405
|
+
``stream.wait(event)`` directly.
|
|
406
|
+
"""
|
|
407
|
+
# NOTE: last_compute_event is required so that human developers must think about
|
|
408
|
+
# using this parameter.
|
|
409
|
+
if self.ptr is None:
|
|
410
|
+
return
|
|
411
|
+
if self.stream is not None and last_compute_event is not None:
|
|
412
|
+
self.stream.wait(last_compute_event)
|
|
413
|
+
if self._close_on_release:
|
|
414
|
+
# Pass the stream so the dealloc is stream-ordered — pinned-buffer free
|
|
415
|
+
# is deferred until pending stream work completes, no host stall.
|
|
416
|
+
self.ptr.close(self.stream) # type: ignore[union-attr]
|
|
417
|
+
self.ptr = None
|
|
418
|
+
self.allocated_size = 0
|
|
419
|
+
self.allocated_here = False
|
|
420
|
+
self.stream = None
|
|
421
|
+
self._logger.debug("[release] %s released.", self._label)
|
|
422
|
+
|
|
423
|
+
# ------------------------------------------------------- pointer plumbing
|
|
424
|
+
@property
|
|
425
|
+
def raw_ptr(self) -> int:
|
|
426
|
+
"""Raw integer pointer for plumbing into C bindings.
|
|
427
|
+
|
|
428
|
+
Raises :class:`AttributeError` if no buffer is held (called outside a ``with``
|
|
429
|
+
block, or after :meth:`release`). For the legacy 0-byte fast path returns
|
|
430
|
+
``0`` — if a null device pointer is unsuitable downstream, also branch on
|
|
431
|
+
``self.size == 0``.
|
|
432
|
+
"""
|
|
433
|
+
return self._get_ptr(self.ptr)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
class NumpyMemoryResource(MemoryResource):
|
|
437
|
+
"""Host :class:`cuda.core.MemoryResource` backed by :func:`numpy.empty`.
|
|
438
|
+
|
|
439
|
+
Allocates plain (non-pinned) host memory via NumPy. Unlike
|
|
440
|
+
:class:`PinnedMemoryResource` / :class:`LegacyPinnedMemoryResource`, this
|
|
441
|
+
resource does not call into the CUDA driver, so it is safe to use in
|
|
442
|
+
environments where no CUDA driver/device is available (CPU-only test runs,
|
|
443
|
+
pure-host scratch space for parameter struct staging, etc.).
|
|
444
|
+
|
|
445
|
+
Pair with ``Workspace(NumpyMemoryResource(), logger)`` and pass
|
|
446
|
+
``stream_holder=None`` to ``allocate_perhaps`` for a fully CUDA-free path
|
|
447
|
+
(``device_id`` is read from the resource and is ``-1``).
|
|
448
|
+
|
|
449
|
+
Not suitable as the host half of a :class:`MirroredWorkspace`: ``cudaMemcpyAsync``
|
|
450
|
+
requires page-locked memory for asynchronous correctness.
|
|
451
|
+
"""
|
|
452
|
+
|
|
453
|
+
def __init__(self) -> None:
|
|
454
|
+
# Strong refs keyed by ptr — Buffer carries only the int pointer.
|
|
455
|
+
self._held: dict[int, np.ndarray] = {}
|
|
456
|
+
|
|
457
|
+
def allocate(self, size: int, *, stream: Stream | None = None) -> Buffer:
|
|
458
|
+
arr = np.empty(size, dtype=np.uint8)
|
|
459
|
+
ptr = int(arr.ctypes.data)
|
|
460
|
+
self._held[ptr] = arr
|
|
461
|
+
return Buffer.from_handle(ptr=ptr, size=size, mr=self)
|
|
462
|
+
|
|
463
|
+
def deallocate(self, ptr: int, size: int, stream: Stream | None = None) -> None:
|
|
464
|
+
# `stream` must NOT be keyword-only: cuda.core < 1 calls deallocate with all
|
|
465
|
+
# arguments positionally (Buffer.close -> mr.deallocate(ptr, size, stream)).
|
|
466
|
+
self._held.pop(int(ptr), None)
|
|
467
|
+
|
|
468
|
+
@property
|
|
469
|
+
def is_host_accessible(self) -> bool:
|
|
470
|
+
return True
|
|
471
|
+
|
|
472
|
+
@property
|
|
473
|
+
def is_device_accessible(self) -> bool:
|
|
474
|
+
return False
|
|
475
|
+
|
|
476
|
+
@property
|
|
477
|
+
def device_id(self) -> int:
|
|
478
|
+
return -1
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def get_pinned_memory_resource(device_id: int) -> MemoryResource:
|
|
482
|
+
"""
|
|
483
|
+
Use host pinned cuda asynchronous memory pool (via cuda.core.PinnedMemoryResource)
|
|
484
|
+
if available, otherwise use legacy pinned memory resource
|
|
485
|
+
(via cuda.core.LegacyPinnedMemoryResource).
|
|
486
|
+
"""
|
|
487
|
+
mr = get_pinned_async_memory_resource(device_id)
|
|
488
|
+
if mr is None:
|
|
489
|
+
mr = get_legacy_pinned_memory_resource(device_id)
|
|
490
|
+
return mr
|
nvmath/_utils.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import re
|
|
7
|
+
import sys
|
|
8
|
+
from enum import IntEnum
|
|
9
|
+
from functools import cache
|
|
10
|
+
|
|
11
|
+
from cuda import pathfinder
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger()
|
|
14
|
+
|
|
15
|
+
#
|
|
16
|
+
# Note: This module should not depend on anything from the nvmath namespace!
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# The (subset of) compute types below are shared by cuStateVec and cuTensorNet
|
|
21
|
+
class ComputeType(IntEnum):
|
|
22
|
+
"""An enumeration of CUDA compute types."""
|
|
23
|
+
|
|
24
|
+
COMPUTE_DEFAULT = 0
|
|
25
|
+
COMPUTE_16F = 1 << 0
|
|
26
|
+
COMPUTE_32F = 1 << 2
|
|
27
|
+
COMPUTE_64F = 1 << 4
|
|
28
|
+
COMPUTE_8U = 1 << 6
|
|
29
|
+
COMPUTE_8I = 1 << 8
|
|
30
|
+
COMPUTE_32U = 1 << 7
|
|
31
|
+
COMPUTE_32I = 1 << 9
|
|
32
|
+
COMPUTE_16BF = 1 << 10
|
|
33
|
+
COMPUTE_TF32 = 1 << 12
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# TODO: use those exposed by CUDA Python instead, but before removing these
|
|
37
|
+
# duplicates, check if they are fixed to inherit IntEnum instead of Enum.
|
|
38
|
+
class CudaDataType(IntEnum):
|
|
39
|
+
"""An enumeration of `cudaDataType_t`."""
|
|
40
|
+
|
|
41
|
+
CUDA_R_16F = 2
|
|
42
|
+
CUDA_C_16F = 6
|
|
43
|
+
CUDA_R_16BF = 14
|
|
44
|
+
CUDA_C_16BF = 15
|
|
45
|
+
CUDA_R_32F = 0
|
|
46
|
+
CUDA_C_32F = 4
|
|
47
|
+
CUDA_R_64F = 1
|
|
48
|
+
CUDA_C_64F = 5
|
|
49
|
+
CUDA_R_4I = 16
|
|
50
|
+
CUDA_C_4I = 17
|
|
51
|
+
CUDA_R_4U = 18
|
|
52
|
+
CUDA_C_4U = 19
|
|
53
|
+
CUDA_R_8I = 3
|
|
54
|
+
CUDA_C_8I = 7
|
|
55
|
+
CUDA_R_8U = 8
|
|
56
|
+
CUDA_C_8U = 9
|
|
57
|
+
CUDA_R_16I = 20
|
|
58
|
+
CUDA_C_16I = 21
|
|
59
|
+
CUDA_R_16U = 22
|
|
60
|
+
CUDA_C_16U = 23
|
|
61
|
+
CUDA_R_32I = 10
|
|
62
|
+
CUDA_C_32I = 11
|
|
63
|
+
CUDA_R_32U = 12
|
|
64
|
+
CUDA_C_32U = 13
|
|
65
|
+
CUDA_R_64I = 24
|
|
66
|
+
CUDA_C_64I = 25
|
|
67
|
+
CUDA_R_64U = 26
|
|
68
|
+
CUDA_C_64U = 27
|
|
69
|
+
CUDA_R_8F_E4M3 = 28
|
|
70
|
+
CUDA_R_8F_E5M2 = 29
|
|
71
|
+
CUDA_R_4F_E2M1 = 33 # FP4 (supported on SM >= 10.0)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class LibraryPropertyType(IntEnum):
|
|
75
|
+
"""An enumeration of library version information."""
|
|
76
|
+
|
|
77
|
+
MAJOR_VERSION = 0
|
|
78
|
+
MINOR_VERSION = 1
|
|
79
|
+
PATCH_LEVEL = 2
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
del IntEnum
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
PLATFORM_LINUX = sys.platform.startswith("linux")
|
|
86
|
+
PLATFORM_WIN = sys.platform.startswith("win32")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def module_init_force_cupy_lib_load():
|
|
90
|
+
"""
|
|
91
|
+
Attempt to preload libraries at module import time. We want to do it before
|
|
92
|
+
cupy, since it does not know how to properly search for libraries:
|
|
93
|
+
https://github.com/cupy/cupy/issues/9127
|
|
94
|
+
Fail silently if preload fails.
|
|
95
|
+
"""
|
|
96
|
+
from nvmath.bindings import _internal
|
|
97
|
+
|
|
98
|
+
libs = ("cublas", "cufft", "curand", "cusolverDn", "cusparse", "cutensor")
|
|
99
|
+
for lib in libs:
|
|
100
|
+
try:
|
|
101
|
+
mod = getattr(_internal, lib)
|
|
102
|
+
mod._inspect_function_pointers()
|
|
103
|
+
except (_internal.utils.NotSupportedError, RuntimeError):
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
pathfinder.load_nvidia_dynamic_lib("nvrtc")
|
|
108
|
+
except pathfinder.DynamicLibNotFoundError:
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@cache
|
|
113
|
+
def get_nvrtc_build_id(minimal=True) -> int:
|
|
114
|
+
from cuda.core import ObjectCode, Program, ProgramOptions
|
|
115
|
+
|
|
116
|
+
code = r"""
|
|
117
|
+
extern "C" __global__ void get_build_id(int* build_id) {
|
|
118
|
+
|
|
119
|
+
*build_id = __CUDACC_VER_BUILD__;
|
|
120
|
+
}
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
prog = Program(code, "c++", ProgramOptions(std="c++17", minimal=minimal, arch="compute_75"))
|
|
124
|
+
obj = prog.compile("ptx")
|
|
125
|
+
assert isinstance(obj, ObjectCode)
|
|
126
|
+
|
|
127
|
+
pattern = re.compile(r"mov\.u32\s+%\w+,\s+(\d+)")
|
|
128
|
+
m = pattern.search(obj.code.decode())
|
|
129
|
+
assert m is not None
|
|
130
|
+
|
|
131
|
+
return int(m.group(1))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@cache
|
|
135
|
+
def get_nvrtc_version() -> tuple[int, int, int]:
|
|
136
|
+
"""
|
|
137
|
+
Returns the NVRTC version as a tuple of (major, minor, build).
|
|
138
|
+
"""
|
|
139
|
+
from cuda.bindings import nvrtc
|
|
140
|
+
|
|
141
|
+
err, major, minor = nvrtc.nvrtcVersion()
|
|
142
|
+
if err != nvrtc.nvrtcResult.NVRTC_SUCCESS:
|
|
143
|
+
raise RuntimeError(f"nvrtcVersion error: {err}")
|
|
144
|
+
# minimal support was added in CUDA 12.4
|
|
145
|
+
# https://docs.nvidia.com/cuda/archive/12.4.0/cuda-toolkit-release-notes/index.html#cuda-compilers
|
|
146
|
+
build = get_nvrtc_build_id(minimal=major >= 12 and minor >= 4)
|
|
147
|
+
return major, minor, build
|