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,408 @@
|
|
|
1
|
+
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import math
|
|
7
|
+
import pickle
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from enum import IntEnum
|
|
11
|
+
from typing import Literal, TypeAlias, TypeVar
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"ReductionOp",
|
|
17
|
+
"ProcessGroup",
|
|
18
|
+
"MPIProcessGroup",
|
|
19
|
+
"TorchProcessGroup",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ReductionOp(IntEnum):
|
|
24
|
+
SUM = 0
|
|
25
|
+
MIN = 1
|
|
26
|
+
MAX = 2
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
T = TypeVar("T")
|
|
30
|
+
Reducer: TypeAlias = Callable[[T, T], T]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ProcessGroup(ABC):
|
|
34
|
+
"""A ProcessGroup represents a set of processes collectively running
|
|
35
|
+
``nvmath.distributed`` operations. A ProcessGroup implements a small set of
|
|
36
|
+
communication collectives used by nvmath-python to initialize the distributed
|
|
37
|
+
runtime and set up distributed math operations. It is not used for compute
|
|
38
|
+
(each math library uses its chosen communication backend(s), like NCCL or NVSHMEM).
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def rank(self) -> int:
|
|
44
|
+
"Index of this process in the group."
|
|
45
|
+
raise NotImplementedError
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def nranks(self) -> int:
|
|
50
|
+
"""Number of processes in the group."""
|
|
51
|
+
raise NotImplementedError
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
def broadcast_buffer(self, array: np.ndarray, *, root: int = 0) -> None:
|
|
55
|
+
"""Broadcast an array from one process to every process.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
array: input (on root) and output of the collective.
|
|
59
|
+
|
|
60
|
+
root: rank of sending process.
|
|
61
|
+
"""
|
|
62
|
+
raise NotImplementedError
|
|
63
|
+
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def allreduce_buffer(self, array: np.ndarray, *, op: ReductionOp) -> None:
|
|
66
|
+
"""Allreduce an array.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
array: Input and output of the collective. The function operates in-place.
|
|
70
|
+
|
|
71
|
+
op: One of the values from :class:`ReduceOp` enum. Specifies an operation for
|
|
72
|
+
element-wise reductions.
|
|
73
|
+
"""
|
|
74
|
+
raise NotImplementedError
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
def allreduce_object(self, obj: T, *, op: Reducer[T]) -> T:
|
|
78
|
+
"""Reduces all Python objects contributed by members of the group. The result
|
|
79
|
+
is a single reduced object which is returned on every process.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
obj: object contributed by this process.
|
|
83
|
+
|
|
84
|
+
op: A Python function that takes two objects and returns a single (reduced)
|
|
85
|
+
object.
|
|
86
|
+
"""
|
|
87
|
+
raise NotImplementedError
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class MPIProcessGroup(ProcessGroup):
|
|
91
|
+
"""ProcessGroup implemented on mpi4py.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
mpi_comm: mpi4py communicator.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
# Map from mpi4py reduction ops to this module's ReductionOp.
|
|
98
|
+
_reduction_op_map = {} # type: ignore
|
|
99
|
+
|
|
100
|
+
def __init__(self, mpi_comm):
|
|
101
|
+
if not MPIProcessGroup._reduction_op_map:
|
|
102
|
+
from mpi4py import MPI
|
|
103
|
+
|
|
104
|
+
MPIProcessGroup._reduction_op_map = {
|
|
105
|
+
ReductionOp.MIN: MPI.MIN,
|
|
106
|
+
ReductionOp.MAX: MPI.MAX,
|
|
107
|
+
ReductionOp.SUM: MPI.SUM,
|
|
108
|
+
}
|
|
109
|
+
self._mpi_comm = mpi_comm
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def rank(self) -> int:
|
|
113
|
+
return self._mpi_comm.Get_rank()
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def nranks(self) -> int:
|
|
117
|
+
return self._mpi_comm.Get_size()
|
|
118
|
+
|
|
119
|
+
def broadcast_buffer(self, array: np.ndarray, *, root: int = 0) -> None:
|
|
120
|
+
self._mpi_comm.Bcast(array, root=root)
|
|
121
|
+
|
|
122
|
+
def allreduce_buffer(self, array: np.ndarray, *, op: ReductionOp) -> None:
|
|
123
|
+
mpi_reduction_op = MPIProcessGroup._reduction_op_map[op]
|
|
124
|
+
from mpi4py import MPI
|
|
125
|
+
|
|
126
|
+
self._mpi_comm.Allreduce(MPI.IN_PLACE, array, mpi_reduction_op)
|
|
127
|
+
|
|
128
|
+
def allreduce_object(self, obj: T, *, op: Reducer[T]) -> T:
|
|
129
|
+
return self._mpi_comm.allreduce(obj, op=op)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _init_binomial_tree(rank: int, nranks: int) -> tuple[int | None, list[int]]:
|
|
133
|
+
"""Return parent and children of process with given rank in a binomial
|
|
134
|
+
tree of nranks processes."""
|
|
135
|
+
max_height = math.ceil(math.log2(nranks))
|
|
136
|
+
max_size = 1 << max_height # max number of nodes in tree (2^max_height)
|
|
137
|
+
|
|
138
|
+
# Compute order/height of this subtree (in a full binomial tree of max_size nodes).
|
|
139
|
+
# Relabel this node for the computation (so 0 is deepest leaf instead of root).
|
|
140
|
+
p = label = max_size - 1 - rank
|
|
141
|
+
order = 0
|
|
142
|
+
while p > 0:
|
|
143
|
+
if p % 2 == 0:
|
|
144
|
+
break
|
|
145
|
+
p //= 2
|
|
146
|
+
order += 1
|
|
147
|
+
|
|
148
|
+
if rank == 0:
|
|
149
|
+
parent = None
|
|
150
|
+
else:
|
|
151
|
+
parent = label + (1 << order) # parent = label + 2^order
|
|
152
|
+
parent = max_size - 1 - parent # convert back to original numbering scheme
|
|
153
|
+
|
|
154
|
+
children = []
|
|
155
|
+
# Max number of children is the order of this subtree.
|
|
156
|
+
for i in range(order):
|
|
157
|
+
child = label - (1 << i) # child = label - 2^i
|
|
158
|
+
child = max_size - 1 - child # convert back to original numbering scheme
|
|
159
|
+
if child <= nranks - 1:
|
|
160
|
+
children.append(child)
|
|
161
|
+
|
|
162
|
+
return parent, children
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class MsgSizeError(Exception):
|
|
166
|
+
def __init__(self, size: int):
|
|
167
|
+
self.size = size
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class TorchProcessGroup(ProcessGroup):
|
|
171
|
+
"""ProcessGroup implemented on ``torch.distributed``.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
device_id: Device used by the ``torch.distributed`` process group backend.
|
|
175
|
+
|
|
176
|
+
torch_process_group: ``torch.distributed`` process group handle (e.g. returned
|
|
177
|
+
by ``torch.distributed.new_group()``), or None to use the default torch
|
|
178
|
+
process group.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
# Map from torch.distributed reduction ops to this module's ReductionOp.
|
|
182
|
+
_reduction_op_map = {} # type: ignore
|
|
183
|
+
# The minimum buffer size (in bytes) for allreduce_object()
|
|
184
|
+
MIN_ALL_REDUCE_OBJ_BUFFER_SIZE = 128
|
|
185
|
+
|
|
186
|
+
def __init__(
|
|
187
|
+
self,
|
|
188
|
+
*,
|
|
189
|
+
device_id: int | Literal["cpu"],
|
|
190
|
+
torch_process_group=None,
|
|
191
|
+
logger=None,
|
|
192
|
+
):
|
|
193
|
+
import torch
|
|
194
|
+
import torch.distributed as dist
|
|
195
|
+
|
|
196
|
+
if not isinstance(device_id, int) and device_id != "cpu":
|
|
197
|
+
raise ValueError("device_id must be int or 'cpu'")
|
|
198
|
+
|
|
199
|
+
if not dist.is_initialized():
|
|
200
|
+
raise RuntimeError("torch.distributed is not initialized")
|
|
201
|
+
|
|
202
|
+
if logger is None:
|
|
203
|
+
logger = logging.getLogger()
|
|
204
|
+
self.logger = logger
|
|
205
|
+
|
|
206
|
+
# Get communication backend used by this torch process group.
|
|
207
|
+
backend = dist.get_backend(torch_process_group)
|
|
208
|
+
if backend in ("nccl", "xccl") and device_id == "cpu":
|
|
209
|
+
raise ValueError(f"This process group is using a GPU-only backend ({backend}), but the provided device_id is 'cpu'")
|
|
210
|
+
if backend == "mpi":
|
|
211
|
+
logger.warning(
|
|
212
|
+
"You're using torch.distributed with MPI backend. In this case it's "
|
|
213
|
+
"recommended to initialize nvmath.distributed with "
|
|
214
|
+
"nvmath.distributed.process_group.MPIProcessGroup instead of "
|
|
215
|
+
"TorchProcessGroup."
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
self._device_id = device_id
|
|
219
|
+
self._device = torch.device(f"cuda:{device_id}") if device_id != "cpu" else torch.device("cpu")
|
|
220
|
+
self._torch_process_group = torch_process_group
|
|
221
|
+
|
|
222
|
+
if not TorchProcessGroup._reduction_op_map:
|
|
223
|
+
TorchProcessGroup._reduction_op_map = {
|
|
224
|
+
ReductionOp.MIN: dist.ReduceOp.MIN,
|
|
225
|
+
ReductionOp.MAX: dist.ReduceOp.MAX,
|
|
226
|
+
ReductionOp.SUM: dist.ReduceOp.SUM,
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if backend == "nccl":
|
|
230
|
+
# Ensure a NCCL communicator is formed for the whole group. Otherwise if a
|
|
231
|
+
# parent NCCL communicator is not yet formed when batched P2P operations are
|
|
232
|
+
# called on a subset of ranks (see allreduce_object() below), they could fail.
|
|
233
|
+
with torch.cuda.device(self._device):
|
|
234
|
+
a = torch.ones(1, device=self._device)
|
|
235
|
+
dist.all_reduce(a, op=dist.ReduceOp.SUM, group=self._torch_process_group)
|
|
236
|
+
if a[0] != self.nranks:
|
|
237
|
+
raise RuntimeError("torch.distributed incorrect all_reduce output")
|
|
238
|
+
|
|
239
|
+
# A serialized MsgSizeError must fit in MIN_ALL_REDUCE_OBJ_BUFFER_SIZE
|
|
240
|
+
assert len(pickle.dumps(MsgSizeError(1 << 30))) < self.MIN_ALL_REDUCE_OBJ_BUFFER_SIZE, "Internal error."
|
|
241
|
+
# Initial buffer size for allreduce_object (in bytes)
|
|
242
|
+
self.allreduce_obj_buffer_size = 2048
|
|
243
|
+
|
|
244
|
+
# Determine my parent and children for allreduce_object() implementation.
|
|
245
|
+
# A binomial tree is good for small message sizes.
|
|
246
|
+
self._parent, self._children = _init_binomial_tree(self.rank, self.nranks)
|
|
247
|
+
|
|
248
|
+
@property
|
|
249
|
+
def device_id(self) -> int | Literal["cpu"]:
|
|
250
|
+
"""Device used by the communication backend of this torch process group."""
|
|
251
|
+
return self._device_id
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
def rank(self) -> int:
|
|
255
|
+
import torch.distributed as dist
|
|
256
|
+
|
|
257
|
+
return dist.get_rank(group=self._torch_process_group)
|
|
258
|
+
|
|
259
|
+
@property
|
|
260
|
+
def nranks(self) -> int:
|
|
261
|
+
import torch.distributed as dist
|
|
262
|
+
|
|
263
|
+
return dist.get_world_size(group=self._torch_process_group)
|
|
264
|
+
|
|
265
|
+
def broadcast_buffer(self, array: np.ndarray, *, root: int = 0) -> None:
|
|
266
|
+
import torch
|
|
267
|
+
|
|
268
|
+
if self._device_id == "cpu":
|
|
269
|
+
torch.distributed.broadcast(torch.from_numpy(array), group=self._torch_process_group, group_src=root)
|
|
270
|
+
else:
|
|
271
|
+
tensor_gpu = torch.from_numpy(array).to(self._device)
|
|
272
|
+
torch.distributed.broadcast(tensor_gpu, group=self._torch_process_group, group_src=root)
|
|
273
|
+
array[:] = tensor_gpu.cpu().numpy()
|
|
274
|
+
|
|
275
|
+
def allreduce_buffer(self, array: np.ndarray, *, op: ReductionOp) -> None:
|
|
276
|
+
import torch
|
|
277
|
+
|
|
278
|
+
torch_reduction_op = TorchProcessGroup._reduction_op_map[op]
|
|
279
|
+
if self._device_id == "cpu":
|
|
280
|
+
torch.distributed.all_reduce(torch.from_numpy(array), op=torch_reduction_op, group=self._torch_process_group)
|
|
281
|
+
else:
|
|
282
|
+
tensor_gpu = torch.from_numpy(array).to(self._device)
|
|
283
|
+
torch.distributed.all_reduce(tensor_gpu, op=torch_reduction_op, group=self._torch_process_group)
|
|
284
|
+
array[:] = tensor_gpu.cpu().numpy()
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def allreduce_obj_buffer_size(self):
|
|
288
|
+
"""Current buffer size for allreduce_object() (in bytes)"""
|
|
289
|
+
return self._allreduce_obj_buffer_size
|
|
290
|
+
|
|
291
|
+
@allreduce_obj_buffer_size.setter
|
|
292
|
+
def allreduce_obj_buffer_size(self, value):
|
|
293
|
+
"""**NOTE: Same size needs to be set on every process**"""
|
|
294
|
+
if not isinstance(value, int) or value < self.MIN_ALL_REDUCE_OBJ_BUFFER_SIZE:
|
|
295
|
+
raise ValueError(f"Buffer size must be >= {self.MIN_ALL_REDUCE_OBJ_BUFFER_SIZE}")
|
|
296
|
+
self._allreduce_obj_buffer_size = value
|
|
297
|
+
|
|
298
|
+
def allreduce_object(self, obj: T, *, op: Reducer[T]) -> T:
|
|
299
|
+
import torch
|
|
300
|
+
|
|
301
|
+
NUM_TRIES = 5
|
|
302
|
+
for _ in range(NUM_TRIES):
|
|
303
|
+
try:
|
|
304
|
+
if self._device_id != "cpu":
|
|
305
|
+
# It's recommended to set torch cuda device for batch_isend_irecv
|
|
306
|
+
with torch.cuda.device(self._device):
|
|
307
|
+
return self._allreduce_object(obj, op)
|
|
308
|
+
else:
|
|
309
|
+
return self._allreduce_object(obj, op)
|
|
310
|
+
except MsgSizeError as e:
|
|
311
|
+
new_size = int(e.size * 1.2)
|
|
312
|
+
self.logger.warning(
|
|
313
|
+
"allreduce_obj_buffer_size is too small for this allreduce: "
|
|
314
|
+
f"current size is {self.allreduce_obj_buffer_size} and the required "
|
|
315
|
+
f"size is {e.size}. Increasing to {new_size}. "
|
|
316
|
+
"You can set the size manually with 'allreduce_obj_buffer_size' property "
|
|
317
|
+
" to avoid dynamic resizing."
|
|
318
|
+
)
|
|
319
|
+
self.allreduce_obj_buffer_size = new_size
|
|
320
|
+
raise RuntimeError(
|
|
321
|
+
f"Could not find buffer size for allreduce_object() after {NUM_TRIES} tries. "
|
|
322
|
+
"Please set the limit manually with 'allreduce_obj_buffer_size' property."
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
def _allreduce_object(self, obj: T, reducer: Reducer[T]) -> T:
|
|
326
|
+
import torch
|
|
327
|
+
import torch.distributed as dist
|
|
328
|
+
|
|
329
|
+
def to_tensor(obj, out_tensor):
|
|
330
|
+
"""Serializes Python object into out_tensor buffer.
|
|
331
|
+
If the tensor buffer is not large enough to accommodate the serialized object,
|
|
332
|
+
insert a serialized MsgSizeError (with required size) instead."""
|
|
333
|
+
pickled_obj = pickle.dumps(obj)
|
|
334
|
+
pickled_obj_arr = np.frombuffer(pickled_obj, dtype=np.uint8).copy()
|
|
335
|
+
if pickled_obj_arr.nbytes > out_tensor.nbytes:
|
|
336
|
+
pickled_obj = pickle.dumps(MsgSizeError(pickled_obj_arr.nbytes))
|
|
337
|
+
pickled_obj_arr = np.frombuffer(pickled_obj, dtype=np.uint8).copy()
|
|
338
|
+
out_tensor[: pickled_obj_arr.nbytes] = torch.from_numpy(pickled_obj_arr)
|
|
339
|
+
return out_tensor
|
|
340
|
+
|
|
341
|
+
def from_tensor(tensor):
|
|
342
|
+
"""Deserializes Python object from tensor buffer."""
|
|
343
|
+
return pickle.loads(tensor.cpu().numpy().tobytes())
|
|
344
|
+
|
|
345
|
+
num_children = len(self._children)
|
|
346
|
+
|
|
347
|
+
if num_children > 0:
|
|
348
|
+
# 1) Get partially reduced objects from children.
|
|
349
|
+
|
|
350
|
+
# Post a recv for each child.
|
|
351
|
+
# Use batch_isend_irecv because, when using the NCCL backend, individual
|
|
352
|
+
# (unbatched) point-to-point (P2P) send or receive operations can trigger
|
|
353
|
+
# the creation of new, separate NCCL communicators for each pair of
|
|
354
|
+
# communicating processes, which can have significant overhead or use
|
|
355
|
+
# large amounts of GPU memory. Instead, we want the p2p operations to use
|
|
356
|
+
# the parent NCCL communicator whenever possible.
|
|
357
|
+
data = [
|
|
358
|
+
torch.empty(self._allreduce_obj_buffer_size, dtype=torch.uint8, device=self._device)
|
|
359
|
+
for _ in range(num_children)
|
|
360
|
+
]
|
|
361
|
+
recv_ops = []
|
|
362
|
+
for i, child in enumerate(self._children):
|
|
363
|
+
# The tag is to match the recv against the remote send (in case the sender
|
|
364
|
+
# is sending multiple different messages).
|
|
365
|
+
recv_op = dist.P2POp(dist.irecv, data[i], group=self._torch_process_group, tag=2355, group_peer=child)
|
|
366
|
+
recv_ops.append(recv_op)
|
|
367
|
+
|
|
368
|
+
reqs = dist.batch_isend_irecv(recv_ops)
|
|
369
|
+
for req in reqs:
|
|
370
|
+
req.wait()
|
|
371
|
+
|
|
372
|
+
# 2) Apply the reducer on my object and objects received from children.
|
|
373
|
+
reduced_obj = obj
|
|
374
|
+
errors = []
|
|
375
|
+
for i in range(num_children):
|
|
376
|
+
child_obj = from_tensor(data[i])
|
|
377
|
+
if isinstance(child_obj, MsgSizeError):
|
|
378
|
+
errors.append(child_obj)
|
|
379
|
+
elif not errors:
|
|
380
|
+
reduced_obj = reducer(reduced_obj, child_obj)
|
|
381
|
+
if errors:
|
|
382
|
+
# Max reduction of all received errors to get the maximum required buffer
|
|
383
|
+
# size for this subtree.
|
|
384
|
+
reduced_obj = MsgSizeError(max(e.size for e in errors)) # type: ignore
|
|
385
|
+
else:
|
|
386
|
+
reduced_obj = obj
|
|
387
|
+
|
|
388
|
+
data = [torch.empty(self._allreduce_obj_buffer_size, dtype=torch.uint8, device=self._device)]
|
|
389
|
+
if self._parent is not None:
|
|
390
|
+
# Send reduced object to parent.
|
|
391
|
+
send_op = dist.P2POp(
|
|
392
|
+
dist.isend, to_tensor(reduced_obj, data[0]), group=self._torch_process_group, tag=2355, group_peer=self._parent
|
|
393
|
+
)
|
|
394
|
+
dist.batch_isend_irecv([send_op])[0].wait()
|
|
395
|
+
|
|
396
|
+
# Wait for broadcast of reduction from root.
|
|
397
|
+
dist.broadcast(data[0], group=self._torch_process_group, group_src=0)
|
|
398
|
+
result = from_tensor(data[0])
|
|
399
|
+
else:
|
|
400
|
+
# This is the root: broadcast the result.
|
|
401
|
+
assert self.rank == 0
|
|
402
|
+
dist.broadcast(to_tensor(reduced_obj, data[0]), group=self._torch_process_group, group_src=0)
|
|
403
|
+
result = reduced_obj
|
|
404
|
+
|
|
405
|
+
if isinstance(result, MsgSizeError):
|
|
406
|
+
raise result
|
|
407
|
+
|
|
408
|
+
return result
|
nvmath/fft/__init__.py
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
__all__ = ["FFTDirection", "FFTOptions", "DeviceCallable", "ExecutionCUDA", "ExecutionCPU"]
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from enum import IntEnum
|
|
9
|
+
from logging import Logger
|
|
10
|
+
from typing import Literal
|
|
11
|
+
|
|
12
|
+
from nvmath._internal import templates
|
|
13
|
+
from nvmath.memory import BaseCUDAMemoryManager
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
17
|
+
class ExecutionCUDA(templates.ExecutionCUDA):
|
|
18
|
+
"""
|
|
19
|
+
A data class for providing GPU execution options to the :class:`FFT` object and the
|
|
20
|
+
family of wrapper functions :func:`fft`, :func:`ifft`, :func:`rfft`, and :func:`irfft`.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
device_id: CUDA device ordinal (only used if the operand resides on the CPU). The
|
|
24
|
+
default value is 0.
|
|
25
|
+
|
|
26
|
+
.. seealso::
|
|
27
|
+
:class:`ExecutionCPU`, :class:`FFT`, :func:`fft`, :func:`ifft`, :func:`rfft`, and
|
|
28
|
+
:func:`irfft`.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
33
|
+
class ExecutionCPU(templates.ExecutionCPU):
|
|
34
|
+
"""
|
|
35
|
+
A data class for providing CPU execution options to the :class:`FFT` object and the
|
|
36
|
+
family of wrapper functions :func:`fft`, :func:`ifft`, :func:`rfft`, and :func:`irfft`.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
num_threads: The number of CPU threads used to execute the plan.
|
|
40
|
+
If not specified, defaults to the number of CPU cores available to the
|
|
41
|
+
process.
|
|
42
|
+
|
|
43
|
+
.. seealso::
|
|
44
|
+
:class:`ExecutionCUDA`, :class:`FFT`, :func:`fft`, :func:`ifft`, :func:`rfft`, and
|
|
45
|
+
:func:`irfft`.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class FFTOptions:
|
|
51
|
+
"""
|
|
52
|
+
A data class for providing options to the :class:`FFT` object and the family of wrapper
|
|
53
|
+
functions :func:`fft`, :func:`ifft`, :func:`rfft`, and :func:`irfft`.
|
|
54
|
+
|
|
55
|
+
Attributes:
|
|
56
|
+
fft_type: The type of FFT to perform, available options include ``'C2C'``,
|
|
57
|
+
``'C2R'``, and ``'R2C'``. The default is ``'C2C'`` for complex input and
|
|
58
|
+
``'R2C'`` for real input.
|
|
59
|
+
|
|
60
|
+
inplace: Specify if the operation is in-place (`True` or `False`). The operand is
|
|
61
|
+
overwritten by the result if ``inplace`` is `True`. The default is `False`.
|
|
62
|
+
|
|
63
|
+
.. experimental:: attribute
|
|
64
|
+
|
|
65
|
+
last_axis_parity: For complex-to-real FFT (corresponding to ``fft_type='C2R'``),
|
|
66
|
+
specify whether the size of the last axis in the result should be even or odd.
|
|
67
|
+
The even size is calculated as :math:`2 * (m - 1)`, where :math:`m` is the
|
|
68
|
+
size of the last axis of the operand, and the odd size is calculated as :math:`2
|
|
69
|
+
* (m - 1) + 1`. The specified value should be either ``'even'`` or ``'odd'``,
|
|
70
|
+
with the default being ``'even'``.
|
|
71
|
+
|
|
72
|
+
result_layout: The layout to use for the result, either ``'natural'`` or
|
|
73
|
+
``'optimized'``. For the ``'natural'`` option, the result layout is the same as
|
|
74
|
+
that of the operand. The default is ``'optimized'``, which generally provides
|
|
75
|
+
much better performance and should be used if the user doesn't care about the
|
|
76
|
+
result layout matching the operand layout. However in rare cases, depending on
|
|
77
|
+
the device type, shape and strides of the operand, and the FFT dimensions, the
|
|
78
|
+
``'natural'`` layout may perform better. This option is ignored if ``inplace``
|
|
79
|
+
is specified to be True.
|
|
80
|
+
|
|
81
|
+
logger (logging.Logger): Python Logger object. The root logger will be used if a
|
|
82
|
+
logger object is not provided.
|
|
83
|
+
|
|
84
|
+
blocking: A flag specifying the behavior of the execution functions and methods,
|
|
85
|
+
such as :func:`fft` and :meth:`FFT.execute`. When ``blocking`` is `True`, the
|
|
86
|
+
execution methods do not return until the operation is complete. When
|
|
87
|
+
``blocking`` is ``"auto"``, the methods return immediately when the input tensor
|
|
88
|
+
is on the GPU and ``execution`` is set to 'CUDA'. The execution methods always
|
|
89
|
+
block when the input tensor is on the CPU or ``execution`` is specified to 'CPU'
|
|
90
|
+
to ensure that the user doesn't inadvertently use the result before it becomes
|
|
91
|
+
available. The default is ``"auto"``.
|
|
92
|
+
|
|
93
|
+
allocator: An object that supports the :class:`BaseCUDAMemoryManager` protocol, used
|
|
94
|
+
to draw device memory. If an allocator is not provided, a memory allocator from
|
|
95
|
+
the library package will be used (:func:`torch.cuda.caching_allocator_alloc` for
|
|
96
|
+
PyTorch operands, :func:`cupy.cuda.alloc` otherwise). Currently, CPU execution
|
|
97
|
+
does not allow specifying a memory pool.
|
|
98
|
+
|
|
99
|
+
.. seealso::
|
|
100
|
+
:class:`ExecutionCUDA`, :class:`ExecutionCPU`, :class:`FFT`, :func:`fft`,
|
|
101
|
+
:func:`ifft`, :func:`rfft`, and :func:`irfft`.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
fft_type: Literal["C2C", "C2R", "R2C"] | None = None
|
|
105
|
+
inplace: bool = False
|
|
106
|
+
last_axis_parity: Literal["even", "odd"] = "even"
|
|
107
|
+
result_layout: Literal["natural", "optimized"] = "optimized"
|
|
108
|
+
logger: Logger | None = None
|
|
109
|
+
blocking: Literal[True, "auto"] = "auto"
|
|
110
|
+
allocator: BaseCUDAMemoryManager | None = None
|
|
111
|
+
|
|
112
|
+
def __post_init__(self):
|
|
113
|
+
valid_fft_types = [None, "C2C", "C2R", "R2C"]
|
|
114
|
+
if self.fft_type not in valid_fft_types:
|
|
115
|
+
raise ValueError(f"The value specified for 'fft_type' must be one of {valid_fft_types}.")
|
|
116
|
+
|
|
117
|
+
if not isinstance(self.inplace, bool):
|
|
118
|
+
raise ValueError("The value specified for 'inplace' must be of type bool (True or False).")
|
|
119
|
+
|
|
120
|
+
valid_last_axis_parity = ["even", "odd"]
|
|
121
|
+
if self.last_axis_parity not in valid_last_axis_parity:
|
|
122
|
+
raise ValueError(f"The value specified for 'last_axis_parity' must be one of {valid_last_axis_parity}.")
|
|
123
|
+
|
|
124
|
+
valid_result_layout_options = ["natural", "optimized"]
|
|
125
|
+
if self.result_layout not in valid_result_layout_options:
|
|
126
|
+
raise ValueError(f"The value specified for 'result_layout' must be one of {valid_result_layout_options}.")
|
|
127
|
+
|
|
128
|
+
if self.blocking not in (True, "auto"):
|
|
129
|
+
raise ValueError("The value specified for 'blocking' must be either True or 'auto'.")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass
|
|
133
|
+
class DeviceCallable:
|
|
134
|
+
"""A data class capturing LTO-IR callables.
|
|
135
|
+
|
|
136
|
+
Attributes:
|
|
137
|
+
ltoir: A device-callable function in LTO-IR format, which can be provided as as
|
|
138
|
+
either as a :class:`bytes` object or as a pointer to the LTO-IR as Python
|
|
139
|
+
:class:`int`.
|
|
140
|
+
|
|
141
|
+
size: The size of the LTO-IR callable. If not specified and a :class:`bytes` object
|
|
142
|
+
is passed for ``ltoir``, the size is calculated from it. If a pointer is
|
|
143
|
+
provided for the LTO-IR, `size` must be specified.
|
|
144
|
+
|
|
145
|
+
data: A device pointer to user data used in the callback. The default is None,
|
|
146
|
+
which means a null pointer will be used in the callback.
|
|
147
|
+
|
|
148
|
+
.. seealso::
|
|
149
|
+
:meth:`FFT.plan`, :func:`fft`, :func:`ifft`, :func:`rfft`, and :func:`irfft`.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
ltoir: int | bytes | None = None
|
|
153
|
+
size: int | None = None
|
|
154
|
+
data: int | None = None
|
|
155
|
+
|
|
156
|
+
def __post_init__(self):
|
|
157
|
+
if self.ltoir is None:
|
|
158
|
+
return
|
|
159
|
+
if not isinstance(self.ltoir, int | bytes):
|
|
160
|
+
raise ValueError(
|
|
161
|
+
"The LTO-IR code must be provided as a bytes object or as a Python int "
|
|
162
|
+
"representing the pointer to the LTO-IR code."
|
|
163
|
+
)
|
|
164
|
+
if isinstance(self.ltoir, int) and self.size is None:
|
|
165
|
+
raise ValueError(
|
|
166
|
+
"The size of the LTO-IR code specified as a pointer must be explicitly provided using the 'size' option."
|
|
167
|
+
)
|
|
168
|
+
if isinstance(self.ltoir, bytes):
|
|
169
|
+
self.size = len(self.ltoir)
|
|
170
|
+
if not isinstance(self.size, int):
|
|
171
|
+
raise ValueError(f"Invalid size value: {self.size}.")
|
|
172
|
+
if self.data is None:
|
|
173
|
+
self.data = 0
|
|
174
|
+
if not isinstance(self.data, int):
|
|
175
|
+
raise ValueError("The 'data' attribute must be a Python int representing a device pointer to user data.")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
KeywordArgType = dict
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class FFTDirection(IntEnum):
|
|
182
|
+
"""An IntEnum class specifying the direction of the transform.
|
|
183
|
+
|
|
184
|
+
.. seealso::
|
|
185
|
+
:meth:`FFT.execute`, :func:`fft`
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
FORWARD = -1
|
|
189
|
+
INVERSE = 1
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from nvmath.bindings import cufft # type: ignore
|
|
8
|
+
from nvmath.bindings._internal import utils as _bindings_utils # type: ignore
|
|
9
|
+
|
|
10
|
+
IS_EXEC_GPU_AVAILABLE = False
|
|
11
|
+
IS_EXEC_CPU_AVAILABLE = False
|
|
12
|
+
NUM_THREADS_DEFAULT = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _get_num_threads_default():
|
|
16
|
+
global NUM_THREADS_DEFAULT
|
|
17
|
+
if NUM_THREADS_DEFAULT is None:
|
|
18
|
+
if os.name == "posix":
|
|
19
|
+
num_threads = len(os.sched_getaffinity(0))
|
|
20
|
+
else:
|
|
21
|
+
# `sched_getaffinity` is not supported on Windows
|
|
22
|
+
num_threads = os.cpu_count() or 1
|
|
23
|
+
num_threads = max(1, num_threads // 2)
|
|
24
|
+
NUM_THREADS_DEFAULT = num_threads
|
|
25
|
+
return NUM_THREADS_DEFAULT
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _check_init_cufft():
|
|
29
|
+
global IS_EXEC_GPU_AVAILABLE
|
|
30
|
+
if not IS_EXEC_GPU_AVAILABLE:
|
|
31
|
+
try:
|
|
32
|
+
cufft.get_version()
|
|
33
|
+
except (
|
|
34
|
+
_bindings_utils.FunctionNotFoundError,
|
|
35
|
+
_bindings_utils.NotSupportedError,
|
|
36
|
+
RuntimeError,
|
|
37
|
+
) as e:
|
|
38
|
+
raise RuntimeError(
|
|
39
|
+
"The FFT CUDA execution is not available. "
|
|
40
|
+
"Please check if CUDA toolkit and cuFFT are installed and visible to nvmath."
|
|
41
|
+
) from e
|
|
42
|
+
|
|
43
|
+
IS_EXEC_GPU_AVAILABLE = True
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _check_init_fftw():
|
|
47
|
+
global IS_EXEC_CPU_AVAILABLE
|
|
48
|
+
if not IS_EXEC_CPU_AVAILABLE:
|
|
49
|
+
from nvmath.bindings.nvpl import fft as _fftw
|
|
50
|
+
from nvmath.bindings.nvpl._internal import fft as _internal_fftw
|
|
51
|
+
|
|
52
|
+
env_lib_name = os.environ.get("NVMATH_FFT_CPU_LIBRARY")
|
|
53
|
+
|
|
54
|
+
if env_lib_name:
|
|
55
|
+
_internal_fftw._set_lib_so_names((env_lib_name,))
|
|
56
|
+
try:
|
|
57
|
+
_internal_fftw._inspect_function_pointers()
|
|
58
|
+
loaded_lib_name = _internal_fftw._get_current_lib_so_name()
|
|
59
|
+
except (
|
|
60
|
+
_bindings_utils.FunctionNotFoundError,
|
|
61
|
+
_bindings_utils.NotSupportedError,
|
|
62
|
+
RuntimeError,
|
|
63
|
+
) as e:
|
|
64
|
+
raise RuntimeError(
|
|
65
|
+
"The FFT CPU execution is not available, because no FFTW-compatible "
|
|
66
|
+
"library was found. Please make sure to install optional dependency required "
|
|
67
|
+
"for FFT execution (NVPL on aarch64 platforms or MKL on x86_64 platforms). "
|
|
68
|
+
"Users can use other FFTW3-compatible libraries. In order to do that, please "
|
|
69
|
+
"specify the library name as "
|
|
70
|
+
"`NVMATH_FFT_CPU_LIBRARY=some_fftw3_compatible_lib.so` and make sure "
|
|
71
|
+
"the library can be found within current `LD_LIBRARY_PATH`"
|
|
72
|
+
) from e
|
|
73
|
+
|
|
74
|
+
if env_lib_name:
|
|
75
|
+
assert env_lib_name == loaded_lib_name
|
|
76
|
+
|
|
77
|
+
lib_nvpl = "libnvpl_fftw.so"
|
|
78
|
+
if not loaded_lib_name.startswith(lib_nvpl):
|
|
79
|
+
_fftw.init_threads_float()
|
|
80
|
+
_fftw.init_threads_double()
|
|
81
|
+
|
|
82
|
+
IS_EXEC_CPU_AVAILABLE = True
|