nvmath-python 0.9.0__cp314-cp314-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nvmath/__init__.pxd +0 -0
- nvmath/__init__.py +45 -0
- nvmath/_internal/__init__.py +0 -0
- nvmath/_internal/attribute_ifc_factory.py +330 -0
- nvmath/_internal/layout.py +70 -0
- nvmath/_internal/templates.py +383 -0
- nvmath/_utils.py +150 -0
- nvmath/bindings/__init__.py +51 -0
- nvmath/bindings/_internal/__init__.pxd +0 -0
- nvmath/bindings/_internal/__init__.py +0 -0
- nvmath/bindings/_internal/cublas.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cublas.pxd +530 -0
- nvmath/bindings/_internal/cublasLt.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cublasLt.pxd +59 -0
- nvmath/bindings/_internal/cublasMp.pxd +52 -0
- nvmath/bindings/_internal/cudss.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cudss.pxd +47 -0
- nvmath/bindings/_internal/cufft.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cufft.pxd +70 -0
- nvmath/bindings/_internal/cufftMp.pxd +77 -0
- nvmath/bindings/_internal/curand.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/curand.pxd +42 -0
- nvmath/bindings/_internal/cusolver.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusolver.pxd +15 -0
- nvmath/bindings/_internal/cusolverDn.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusolverDn.pxd +406 -0
- nvmath/bindings/_internal/cusparse.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusparse.pxd +277 -0
- nvmath/bindings/_internal/cusparseLt.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusparseLt.pxd +48 -0
- nvmath/bindings/_internal/cutensor.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cutensor.pxd +58 -0
- nvmath/bindings/_internal/mathdx.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/mathdx.pxd +116 -0
- nvmath/bindings/_internal/nvshmem.pxd +29 -0
- nvmath/bindings/_internal/utils.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/utils.pxd +179 -0
- nvmath/bindings/_internal/utils.pyi +10 -0
- nvmath/bindings/cublas.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cublas.pxd +558 -0
- nvmath/bindings/cublas.pyi +746 -0
- nvmath/bindings/cublasLt.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cublasLt.pxd +109 -0
- nvmath/bindings/cublasLt.pyi +1337 -0
- nvmath/bindings/cublasMp.pxd +85 -0
- nvmath/bindings/cublasMp.pyi +219 -0
- nvmath/bindings/cudss.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cudss.pxd +86 -0
- nvmath/bindings/cudss.pyi +282 -0
- nvmath/bindings/cufft.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cufft.pxd +118 -0
- nvmath/bindings/cufft.pyi +241 -0
- nvmath/bindings/cufftMp.pxd +124 -0
- nvmath/bindings/cufftMp.pyi +260 -0
- nvmath/bindings/curand.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/curand.pxd +71 -0
- nvmath/bindings/curand.pyi +159 -0
- nvmath/bindings/cusolver.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cusolver.pxd +62 -0
- nvmath/bindings/cusolver.pyi +242 -0
- nvmath/bindings/cusolverDn.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cusolverDn.pxd +430 -0
- nvmath/bindings/cusolverDn.pyi +416 -0
- nvmath/bindings/cusparse.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cusparse.pxd +338 -0
- nvmath/bindings/cusparse.pyi +659 -0
- nvmath/bindings/cusparseLt.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cusparseLt.pxd +99 -0
- nvmath/bindings/cusparseLt.pyi +198 -0
- nvmath/bindings/cutensor.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cutensor.pxd +98 -0
- nvmath/bindings/cutensor.pyi +294 -0
- nvmath/bindings/cycublas.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycublas.pxd +684 -0
- nvmath/bindings/cycublasLt.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycublasLt.pxd +1055 -0
- nvmath/bindings/cycublasMp.pxd +183 -0
- nvmath/bindings/cycudss.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycudss.pxd +224 -0
- nvmath/bindings/cycufft.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycufft.pxd +326 -0
- nvmath/bindings/cycufftMp.pxd +334 -0
- nvmath/bindings/cycurand.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycurand.pxd +146 -0
- nvmath/bindings/cycusolver.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycusolver.pxd +154 -0
- nvmath/bindings/cycusolverDn.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycusolverDn.pxd +446 -0
- nvmath/bindings/cycusparse.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycusparse.pxd +470 -0
- nvmath/bindings/cycusparseLt.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycusparseLt.pxd +150 -0
- nvmath/bindings/cycutensor.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cycutensor.pxd +192 -0
- nvmath/bindings/cymathdx.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/cymathdx.pxd +552 -0
- nvmath/bindings/cynvshmem.pxd +126 -0
- nvmath/bindings/mathdx.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/mathdx.pxd +182 -0
- nvmath/bindings/mathdx.pyi +1244 -0
- nvmath/bindings/nvpl/__init__.pxd +0 -0
- nvmath/bindings/nvpl/__init__.py +13 -0
- nvmath/bindings/nvpl/_internal/__init__.pxd +0 -0
- nvmath/bindings/nvpl/_internal/__init__.py +0 -0
- nvmath/bindings/nvpl/_internal/blas.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/_internal/blas.pxd +125 -0
- nvmath/bindings/nvpl/_internal/fft.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/_internal/fft.pxd +36 -0
- nvmath/bindings/nvpl/blas.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/blas.pxd +85 -0
- nvmath/bindings/nvpl/blas.pyi +122 -0
- nvmath/bindings/nvpl/cyblas.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/cyblas.pxd +166 -0
- nvmath/bindings/nvpl/cyfft.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/cyfft.pxd +92 -0
- nvmath/bindings/nvpl/fft.cp314-win_amd64.pyd +0 -0
- nvmath/bindings/nvpl/fft.pxd +100 -0
- nvmath/bindings/nvpl/fft.pyi +100 -0
- nvmath/bindings/nvshmem.pxd +54 -0
- nvmath/bindings/nvshmem.pyi +179 -0
- nvmath/device/__init__.py +21 -0
- nvmath/device/_deprecated.py +33 -0
- nvmath/device/common.py +313 -0
- nvmath/device/common_backend.py +131 -0
- nvmath/device/common_cuda.py +201 -0
- nvmath/device/common_numba.py +310 -0
- nvmath/device/common_opaque_tensor.py +201 -0
- nvmath/device/cublasdx.py +1805 -0
- nvmath/device/cublasdx_backend.py +807 -0
- nvmath/device/cublasdx_numba.py +1612 -0
- nvmath/device/cufftdx.py +510 -0
- nvmath/device/cufftdx_backend.py +196 -0
- nvmath/device/cufftdx_numba.py +196 -0
- nvmath/device/curand_kernel.py +9147 -0
- nvmath/device/cusolverdx.py +2690 -0
- nvmath/device/cusolverdx_backend.py +440 -0
- nvmath/device/cusolverdx_numba.py +1624 -0
- nvmath/device/llvm_array.py +29 -0
- nvmath/device/random.py +445 -0
- nvmath/device/random_helpers.py +23 -0
- nvmath/device/random_states.py +187 -0
- nvmath/device/types.py +147 -0
- nvmath/device/vector_types_numba.py +203 -0
- nvmath/distributed/__init__.py +205 -0
- nvmath/distributed/_internal/__init__.py +0 -0
- nvmath/distributed/_internal/nvshmem.py +302 -0
- nvmath/distributed/_internal/tensor_ifc.py +67 -0
- nvmath/distributed/_internal/tensor_ifc_cupy.py +62 -0
- nvmath/distributed/_internal/tensor_ifc_host_device.py +165 -0
- nvmath/distributed/_internal/tensor_ifc_numpy.py +41 -0
- nvmath/distributed/_internal/tensor_ifc_torch.py +141 -0
- nvmath/distributed/_internal/tensor_wrapper.py +78 -0
- nvmath/distributed/_utils.py +167 -0
- nvmath/distributed/distribution.py +770 -0
- nvmath/distributed/fft/__init__.py +7 -0
- nvmath/distributed/fft/_configuration.py +79 -0
- nvmath/distributed/fft/fft.py +2801 -0
- nvmath/distributed/linalg/__init__.py +12 -0
- nvmath/distributed/linalg/_internal/__init__.py +3 -0
- nvmath/distributed/linalg/_internal/epilog_protocol.py +496 -0
- nvmath/distributed/linalg/_internal/matmul_desc_ifc.py +28 -0
- nvmath/distributed/linalg/advanced/__init__.py +8 -0
- nvmath/distributed/linalg/advanced/_configuration.py +166 -0
- nvmath/distributed/linalg/advanced/matmulmod.py +2908 -0
- nvmath/distributed/process_group.py +408 -0
- nvmath/distributed/reshape/__init__.py +6 -0
- nvmath/distributed/reshape/_configuration.py +39 -0
- nvmath/distributed/reshape/reshape.py +1256 -0
- nvmath/fft/__init__.py +7 -0
- nvmath/fft/_configuration.py +208 -0
- nvmath/fft/_exec_utils.py +109 -0
- nvmath/fft/_helpers.py +237 -0
- nvmath/fft/fft.py +2903 -0
- nvmath/internal/__init__.pxd +3 -0
- nvmath/internal/__init__.py +10 -0
- nvmath/internal/_bindings.cp314-win_amd64.pyd +0 -0
- nvmath/internal/_bindings.pxd +12 -0
- nvmath/internal/_device_utils.py +54 -0
- nvmath/internal/enum_utils.py +142 -0
- nvmath/internal/formatters.py +87 -0
- nvmath/internal/mem_limit.py +51 -0
- nvmath/internal/memory.cp314-win_amd64.pyd +0 -0
- nvmath/internal/memory.pxd +10 -0
- nvmath/internal/memory.pyi +46 -0
- nvmath/internal/ndbuffer/__init__.pxd +3 -0
- nvmath/internal/ndbuffer/__init__.py +7 -0
- nvmath/internal/ndbuffer/copy_kernel/args.h +34 -0
- nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/array_view.h +52 -0
- nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/elementwise.h +68 -0
- nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/grid_indexer.h +69 -0
- nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/transposed.h +240 -0
- nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/type_utils.h +39 -0
- nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/utils.h +132 -0
- nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/vec.h +159 -0
- nvmath/internal/ndbuffer/copy_kernel/elementwise.h +53 -0
- nvmath/internal/ndbuffer/copy_kernel/transposed.h +58 -0
- nvmath/internal/ndbuffer/copy_kernel.cp314-win_amd64.pyd +0 -0
- nvmath/internal/ndbuffer/copy_kernel.pxd +9 -0
- nvmath/internal/ndbuffer/copy_kernel.pyi +9 -0
- nvmath/internal/ndbuffer/data_layout.cp314-win_amd64.pyd +0 -0
- nvmath/internal/ndbuffer/data_layout.pxd +72 -0
- nvmath/internal/ndbuffer/data_layout.pyi +23 -0
- nvmath/internal/ndbuffer/jit.cp314-win_amd64.pyd +0 -0
- nvmath/internal/ndbuffer/jit.pxd +7 -0
- nvmath/internal/ndbuffer/jit.pyi +6 -0
- nvmath/internal/ndbuffer/ndbuffer.cp314-win_amd64.pyd +0 -0
- nvmath/internal/ndbuffer/ndbuffer.pxd +36 -0
- nvmath/internal/ndbuffer/ndbuffer.pyi +41 -0
- nvmath/internal/ndbuffer/package_utils.cp314-win_amd64.pyd +0 -0
- nvmath/internal/ndbuffer/package_utils.pxd +11 -0
- nvmath/internal/ndbuffer/package_utils.pyi +12 -0
- nvmath/internal/package_ifc.py +147 -0
- nvmath/internal/package_ifc_cuda.py +49 -0
- nvmath/internal/package_ifc_cupy.py +49 -0
- nvmath/internal/package_ifc_torch.py +31 -0
- nvmath/internal/package_wrapper.py +14 -0
- nvmath/internal/tensor_ifc.py +194 -0
- nvmath/internal/tensor_ifc_cupy.py +241 -0
- nvmath/internal/tensor_ifc_ndbuffer.py +153 -0
- nvmath/internal/tensor_ifc_numpy.py +183 -0
- nvmath/internal/tensor_ifc_torch.py +176 -0
- nvmath/internal/tensor_wrapper.py +157 -0
- nvmath/internal/typemaps.py +162 -0
- nvmath/internal/utils.py +771 -0
- nvmath/linalg/__init__.py +46 -0
- nvmath/linalg/_internal/__init__.py +3 -0
- nvmath/linalg/_internal/algo_cap_ifc.py +82 -0
- nvmath/linalg/_internal/algo_config_ifc.py +43 -0
- nvmath/linalg/_internal/batch.py +217 -0
- nvmath/linalg/_internal/enum_to_tuples.py +64 -0
- nvmath/linalg/_internal/epilog_protocol.py +766 -0
- nvmath/linalg/_internal/layout.py +624 -0
- nvmath/linalg/_internal/matmul_desc_ifc.py +28 -0
- nvmath/linalg/_internal/matmul_pref_ifc.py +27 -0
- nvmath/linalg/_internal/matrix_layout_ifc.py +26 -0
- nvmath/linalg/_internal/typemaps.py +144 -0
- nvmath/linalg/_internal/utils.py +134 -0
- nvmath/linalg/advanced/__init__.py +8 -0
- nvmath/linalg/advanced/_algorithmmod.py +149 -0
- nvmath/linalg/advanced/_configuration.py +349 -0
- nvmath/linalg/advanced/helpers/__init__.py +5 -0
- nvmath/linalg/advanced/helpers/matmul.py +1348 -0
- nvmath/linalg/advanced/matmulmod.py +3937 -0
- nvmath/linalg/generic/__init__.py +43 -0
- nvmath/linalg/generic/_configuration/__init__.py +37 -0
- nvmath/linalg/generic/_configuration/layout.py +263 -0
- nvmath/linalg/generic/_configuration/match.py +609 -0
- nvmath/linalg/generic/_configuration/qualifiers.py +494 -0
- nvmath/linalg/generic/_configuration/wrap.py +217 -0
- nvmath/linalg/generic/_dtype.py +15 -0
- nvmath/linalg/generic/matmulmod.py +1060 -0
- nvmath/memory.py +282 -0
- nvmath/sparse/__init__.py +38 -0
- nvmath/sparse/_internal/__init__.py +21 -0
- nvmath/sparse/_internal/common_utils.py +144 -0
- nvmath/sparse/_internal/cudss_config_ifc.py +741 -0
- nvmath/sparse/_internal/cudss_data_ifc.py +399 -0
- nvmath/sparse/_internal/cudss_utils.py +445 -0
- nvmath/sparse/_internal/cusparse_utils.py +382 -0
- nvmath/sparse/_internal/sparse_bsc_ifc.py +300 -0
- nvmath/sparse/_internal/sparse_bsr_ifc.py +303 -0
- nvmath/sparse/_internal/sparse_coo_ifc.py +254 -0
- nvmath/sparse/_internal/sparse_csc_ifc.py +266 -0
- nvmath/sparse/_internal/sparse_csr_ifc.py +266 -0
- nvmath/sparse/_internal/sparse_dia_ifc.py +239 -0
- nvmath/sparse/_internal/sparse_format_helpers.py +601 -0
- nvmath/sparse/_internal/sparse_tensor_ifc.py +121 -0
- nvmath/sparse/_internal/sparse_ust_ifc.py +146 -0
- nvmath/sparse/_internal/utils.py +56 -0
- nvmath/sparse/advanced/__init__.py +7 -0
- nvmath/sparse/advanced/_configuration.py +230 -0
- nvmath/sparse/advanced/direct_solver.py +1884 -0
- nvmath/sparse/generic/__init__.py +7 -0
- nvmath/sparse/generic/_configuration.py +129 -0
- nvmath/sparse/generic/_helpers.py +137 -0
- nvmath/sparse/generic/_thunks.py +21 -0
- nvmath/sparse/generic/matmulmod.py +2426 -0
- nvmath/sparse/ust/__init__.py +7 -0
- nvmath/sparse/ust/_converters.py +378 -0
- nvmath/sparse/ust/_drawer.py +552 -0
- nvmath/sparse/ust/_emitter.py +553 -0
- nvmath/sparse/ust/_jit.py +212 -0
- nvmath/sparse/ust/_kernel.py +346 -0
- nvmath/sparse/ust/_semiring.py +71 -0
- nvmath/sparse/ust/_utils.py +102 -0
- nvmath/sparse/ust/interfaces/__init__.py +0 -0
- nvmath/sparse/ust/interfaces/torch_interface.py +476 -0
- nvmath/sparse/ust/semiring_ops.py +71 -0
- nvmath/sparse/ust/tensor.py +1027 -0
- nvmath/sparse/ust/tensor_drawer.py +552 -0
- nvmath/sparse/ust/tensor_emitter.py +558 -0
- nvmath/sparse/ust/tensor_format.py +935 -0
- nvmath/sparse/ust/tensor_jit.py +212 -0
- nvmath/sparse/ust/tensor_kernel.py +348 -0
- nvmath/sparse/ust/tensor_utils.py +380 -0
- nvmath/tensor/__init__.py +6 -0
- nvmath/tensor/_configuration.py +95 -0
- nvmath/tensor/_internal/__init__.py +3 -0
- nvmath/tensor/_internal/cutensor_config_ifc.py +247 -0
- nvmath/tensor/_internal/cutensor_utils.py +162 -0
- nvmath/tensor/_internal/data.py +43 -0
- nvmath/tensor/_internal/einsum_parser.py +444 -0
- nvmath/tensor/_internal/typemaps.py +96 -0
- nvmath/tensor/contract.py +1861 -0
- nvmath_python-0.9.0.dist-info/METADATA +127 -0
- nvmath_python-0.9.0.dist-info/RECORD +309 -0
- nvmath_python-0.9.0.dist-info/WHEEL +5 -0
- nvmath_python-0.9.0.dist-info/licenses/LICENSE +177 -0
- nvmath_python-0.9.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,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
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
__all__ = ["ReshapeOptions"]
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from logging import Logger
|
|
9
|
+
from typing import Literal
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class ReshapeOptions:
|
|
14
|
+
"""
|
|
15
|
+
A data class for providing options to the :class:`Reshape` object and the wrapper
|
|
16
|
+
function :func:`reshape`.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
logger (logging.Logger): Python Logger object. The root logger will be used if a
|
|
20
|
+
logger object is not provided.
|
|
21
|
+
|
|
22
|
+
blocking: A flag specifying the behavior of the execution functions and methods,
|
|
23
|
+
such as :func:`reshape` and :meth:`Reshape.execute`. When ``blocking`` is
|
|
24
|
+
`True`, the execution methods do not return until the operation is complete.
|
|
25
|
+
When ``blocking`` is ``"auto"``, the methods return immediately when the input
|
|
26
|
+
tensor is on the GPU. The execution methods always block when the input tensor
|
|
27
|
+
is on the CPU to ensure that the user doesn't inadvertently use the result
|
|
28
|
+
before it becomes available. The default is ``"auto"``.
|
|
29
|
+
|
|
30
|
+
.. seealso::
|
|
31
|
+
:class:`Reshape` and :func:`reshape`.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
logger: Logger | None = None
|
|
35
|
+
blocking: Literal[True, "auto"] = "auto"
|
|
36
|
+
|
|
37
|
+
def __post_init__(self):
|
|
38
|
+
if self.blocking not in (True, "auto"):
|
|
39
|
+
raise ValueError("The value specified for 'blocking' must be either True or 'auto'.")
|