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,770 @@
|
|
|
1
|
+
# Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations # allows typehint of class methods to return the self class
|
|
6
|
+
|
|
7
|
+
import copy
|
|
8
|
+
import math
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
from collections.abc import Sequence
|
|
11
|
+
from typing import TypeAlias, cast
|
|
12
|
+
|
|
13
|
+
import nvmath.distributed as dist
|
|
14
|
+
from nvmath.bindings import (
|
|
15
|
+
cublasMp, # type: ignore
|
|
16
|
+
cufftMp, # type: ignore
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = ["ProcessGrid", "Distribution", "Slab", "Box", "BlockCyclic", "BlockNonCyclic"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ProcessGrid:
|
|
23
|
+
"""
|
|
24
|
+
N-dimensional grid of processes used by some distributions like the PBLAS block-cyclic
|
|
25
|
+
distribution.
|
|
26
|
+
|
|
27
|
+
Example 2D process grid for 4 processes, with processes arranged in column-major order::
|
|
28
|
+
|
|
29
|
+
---------
|
|
30
|
+
| 0 | 2 |
|
|
31
|
+
---------
|
|
32
|
+
| 1 | 3 |
|
|
33
|
+
---------
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
Layout: TypeAlias = cublasMp.GridLayout
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
*,
|
|
41
|
+
shape: Sequence[int] | None = None,
|
|
42
|
+
layout: ProcessGrid.Layout | None = None,
|
|
43
|
+
process_array=None,
|
|
44
|
+
):
|
|
45
|
+
"""
|
|
46
|
+
Create a new ProcessGrid object.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
shape: Shape of the process grid.
|
|
50
|
+
|
|
51
|
+
layout: Layout of the process grid (column-major or row-major). This is optional
|
|
52
|
+
for 1D grid or when a custom grid is provided.
|
|
53
|
+
|
|
54
|
+
process_array: optional ndarray specifying custom arrangement of processes.
|
|
55
|
+
"""
|
|
56
|
+
self._nranks = _get_process_group().nranks
|
|
57
|
+
|
|
58
|
+
if process_array is None:
|
|
59
|
+
if shape is None:
|
|
60
|
+
raise ValueError("shape must be provided when process_array=None")
|
|
61
|
+
self._shape = tuple(shape)
|
|
62
|
+
if layout is None:
|
|
63
|
+
if self._is_1d_distribution():
|
|
64
|
+
layout = ProcessGrid.Layout.ROW_MAJOR # layout doesn't matter in this case.
|
|
65
|
+
else:
|
|
66
|
+
raise ValueError("layout must be provided when process_array=None and partitioning on multiple dimensions")
|
|
67
|
+
if not isinstance(layout, ProcessGrid.Layout):
|
|
68
|
+
raise TypeError(f"layout must be of type ProcessGrid.Layout, got {layout}")
|
|
69
|
+
self._layout = layout
|
|
70
|
+
self._process_array = None
|
|
71
|
+
else:
|
|
72
|
+
self._shape = tuple(process_array.shape)
|
|
73
|
+
if shape is not None and self._shape != tuple(shape):
|
|
74
|
+
raise ValueError(f"shape {shape} and process_array.shape ({process_array.shape}) don't match")
|
|
75
|
+
# TODO: Can set layout to COL_MAJOR or ROW_MAJOR automatically if the
|
|
76
|
+
# process_array matches.
|
|
77
|
+
if layout is not None:
|
|
78
|
+
raise NotImplementedError
|
|
79
|
+
self._layout = None
|
|
80
|
+
self._process_array = process_array
|
|
81
|
+
|
|
82
|
+
if math.prod(self._shape) != self._nranks:
|
|
83
|
+
raise ValueError(
|
|
84
|
+
f"Number of grid elements ({math.prod(self._shape)}) must equal the number of processes ({self._nranks})"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def shape(self) -> tuple[int, ...]:
|
|
89
|
+
"""Shape of process grid."""
|
|
90
|
+
return self._shape
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def layout(self) -> ProcessGrid.Layout | None:
|
|
94
|
+
"""Layout of process grid if row-major or column-major, otherwise None."""
|
|
95
|
+
return self._layout
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def process_array(self):
|
|
99
|
+
return self._process_array
|
|
100
|
+
|
|
101
|
+
def __str__(self):
|
|
102
|
+
return f"ProcessGrid(shape={self._shape}, layout={self._layout.name}, process_array={self._process_array})"
|
|
103
|
+
|
|
104
|
+
def __hash__(self):
|
|
105
|
+
# NOTE: The layout isn't considered for the hash, to allow process grids
|
|
106
|
+
# partitioned on a single dimension with the same shape but different layout
|
|
107
|
+
# to be the same dictionary key.
|
|
108
|
+
return hash(self._shape)
|
|
109
|
+
|
|
110
|
+
def __eq__(self, other):
|
|
111
|
+
if self._process_array is None and other._process_array is None:
|
|
112
|
+
if self._shape == other._shape:
|
|
113
|
+
return self._is_1d_distribution() or self._layout == other._layout
|
|
114
|
+
return False
|
|
115
|
+
raise NotImplementedError
|
|
116
|
+
|
|
117
|
+
def _is_1d_distribution(self) -> bool:
|
|
118
|
+
"""True if process grid partitions on a single dimension."""
|
|
119
|
+
return self._nranks in self._shape
|
|
120
|
+
|
|
121
|
+
def _is_row_wise(self) -> bool:
|
|
122
|
+
"""True if 2D process grid partitioned only on rows."""
|
|
123
|
+
return self._shape == (self._nranks, 1)
|
|
124
|
+
|
|
125
|
+
def _is_col_wise(self) -> bool:
|
|
126
|
+
"""True if 2D process grid partitioned only on columns."""
|
|
127
|
+
return self._shape == (1, self._nranks)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class BindDistributionError(Exception):
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class ConvertDistributionError(Exception):
|
|
135
|
+
"""Errors converting a distribution instance to another distribution type"""
|
|
136
|
+
|
|
137
|
+
pass
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class Distribution(ABC):
|
|
141
|
+
"""Specifies how a tensor is distributed across processes."""
|
|
142
|
+
|
|
143
|
+
def __init__(self):
|
|
144
|
+
self._bound = False
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
@abstractmethod
|
|
148
|
+
def ndim(self) -> int | None:
|
|
149
|
+
"""The number of dimensions of a distributed tensor for which this distribution
|
|
150
|
+
applies; None if it doesn't apply to any specific number of dimensions."""
|
|
151
|
+
raise NotImplementedError
|
|
152
|
+
|
|
153
|
+
@abstractmethod
|
|
154
|
+
def shape(self, rank: int, global_shape: Sequence[int] | None = None) -> tuple[int, ...]:
|
|
155
|
+
"""Get the local shape of data on the given rank according to this distribution.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
rank: the process rank for which to calculate the local shape.
|
|
159
|
+
|
|
160
|
+
global_shape: Global shape of data. Required if the distribution is
|
|
161
|
+
not bound to a global shape, otherwise not required.
|
|
162
|
+
"""
|
|
163
|
+
raise NotImplementedError
|
|
164
|
+
|
|
165
|
+
@abstractmethod
|
|
166
|
+
def to(
|
|
167
|
+
self,
|
|
168
|
+
cls: type[Distribution],
|
|
169
|
+
/,
|
|
170
|
+
*,
|
|
171
|
+
ndim: int | None = None,
|
|
172
|
+
copy: bool = False,
|
|
173
|
+
) -> Distribution:
|
|
174
|
+
"""Convert this distribution object to an equivalent distribution of the given type.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
cls: the target distribution type.
|
|
178
|
+
|
|
179
|
+
ndim: dimensionality of the target distribution. Must be compatible with the
|
|
180
|
+
dimensionality of the source distribution. This may be required if the
|
|
181
|
+
source distribution doesn't have associated dimensionality.
|
|
182
|
+
|
|
183
|
+
copy: Returns a copy if the source and target type are the same.
|
|
184
|
+
|
|
185
|
+
Raises:
|
|
186
|
+
ConvertDistributionError: if the conversion is not possible.
|
|
187
|
+
"""
|
|
188
|
+
raise NotImplementedError
|
|
189
|
+
|
|
190
|
+
def _to_checks(self, cls: type[Distribution], ndim: int | None) -> None:
|
|
191
|
+
if cls is Distribution or not issubclass(cls, Distribution):
|
|
192
|
+
raise ValueError(f"{cls} is not a valid distribution")
|
|
193
|
+
if ndim is not None and self.ndim is not None and ndim != self.ndim:
|
|
194
|
+
raise ValueError(f"ndim argument ({ndim}) doesn't match this distribution's dimensionality ({self.ndim})")
|
|
195
|
+
|
|
196
|
+
@abstractmethod
|
|
197
|
+
def _bind(
|
|
198
|
+
self,
|
|
199
|
+
global_shape: Sequence[int],
|
|
200
|
+
*,
|
|
201
|
+
shape: Sequence[int] | None = None,
|
|
202
|
+
) -> Distribution:
|
|
203
|
+
"""Binds this distribution object to a global shape, which determines how a
|
|
204
|
+
distributed tensor with that shape must be partitioned among processes (the local
|
|
205
|
+
shape on each process). You can also provide the local shape on this process to
|
|
206
|
+
check if it fits the distribution (the function will raise an exception if not).
|
|
207
|
+
**The exception may be raised on some ranks but not others (it's up to the caller
|
|
208
|
+
to handle this)**.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
global_shape: global shape of the data.
|
|
212
|
+
|
|
213
|
+
shape: shape of the data on this process.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
self
|
|
217
|
+
|
|
218
|
+
Raises:
|
|
219
|
+
BindDistributionError: if distribution is already bound or local shape doesn't
|
|
220
|
+
fit the distribution.
|
|
221
|
+
"""
|
|
222
|
+
raise NotImplementedError
|
|
223
|
+
|
|
224
|
+
def _binding_str(self):
|
|
225
|
+
return f"[bound: global_shape={self._data_global_shape}, shape={self._data_shape}]" if self._bound else ""
|
|
226
|
+
|
|
227
|
+
def copy(self) -> Distribution:
|
|
228
|
+
"""This is a common implementation for those distributions that only require
|
|
229
|
+
a shallow copy."""
|
|
230
|
+
return copy.copy(self)
|
|
231
|
+
|
|
232
|
+
def _local_shape_checks(self, rank: int, nranks: int, global_shape: Sequence[int] | None = None):
|
|
233
|
+
if not isinstance(rank, int):
|
|
234
|
+
raise ValueError(f"rank must be an integer, got {rank}")
|
|
235
|
+
|
|
236
|
+
if rank < 0 or rank > nranks - 1:
|
|
237
|
+
raise ValueError(f"This is not a valid process rank: got rank={rank} with nranks={nranks}")
|
|
238
|
+
|
|
239
|
+
if global_shape is None and not self._bound:
|
|
240
|
+
raise RuntimeError("This distribution is unbound: please specify a global shape")
|
|
241
|
+
|
|
242
|
+
if global_shape is not None and self._bound and tuple(global_shape) != self._data_global_shape: # type: ignore
|
|
243
|
+
raise ValueError(
|
|
244
|
+
"This distribution is already bound to a different global shape: provided "
|
|
245
|
+
f"{global_shape}, bound to {self._data_global_shape}" # type: ignore
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class Slab(Distribution):
|
|
250
|
+
"""
|
|
251
|
+
Slab distribution
|
|
252
|
+
|
|
253
|
+
Data is partitioned across processes on a single axis, such that:
|
|
254
|
+
|
|
255
|
+
- The shape of the slab on the first s_p % P processes is
|
|
256
|
+
(s_0, ..., s_p // P + 1, ..., s_{n-1})
|
|
257
|
+
- The shape of the slab on the remaining processes is (s_0, ..., s_p // P, ..., s_{n-1})
|
|
258
|
+
- Process 0 owns the first slab according to the global index order, process 1 owns
|
|
259
|
+
the second slab and so on.
|
|
260
|
+
|
|
261
|
+
where:
|
|
262
|
+
|
|
263
|
+
- s_i is the size of dimension i of the global array
|
|
264
|
+
- p is the partition dimension
|
|
265
|
+
- n is the number of dimensions of the array
|
|
266
|
+
- P is the number of processes
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
X: Slab
|
|
270
|
+
"""Slab distribution on axis 0."""
|
|
271
|
+
|
|
272
|
+
Y: Slab
|
|
273
|
+
"""Slab distribution on axis 1."""
|
|
274
|
+
|
|
275
|
+
Z: Slab
|
|
276
|
+
"""Slab distribution on axis 2."""
|
|
277
|
+
|
|
278
|
+
def __init__(
|
|
279
|
+
self,
|
|
280
|
+
partition_dim: int,
|
|
281
|
+
ndim: int | None = None,
|
|
282
|
+
):
|
|
283
|
+
super().__init__()
|
|
284
|
+
if not isinstance(partition_dim, int) or partition_dim < 0:
|
|
285
|
+
raise ValueError(f"partition_dim must be integer >= 0, got {partition_dim}")
|
|
286
|
+
|
|
287
|
+
if ndim is not None:
|
|
288
|
+
if not isinstance(ndim, int) or ndim < 1:
|
|
289
|
+
raise ValueError(f"ndim must be integer >= 1, got {ndim}")
|
|
290
|
+
if partition_dim >= ndim:
|
|
291
|
+
raise ValueError("partition_dim must be < ndim")
|
|
292
|
+
|
|
293
|
+
self._partition_dim = partition_dim
|
|
294
|
+
self._ndim = ndim
|
|
295
|
+
|
|
296
|
+
def __eq__(self, other):
|
|
297
|
+
if not isinstance(other, Slab):
|
|
298
|
+
return False
|
|
299
|
+
if self._partition_dim != other._partition_dim:
|
|
300
|
+
return False
|
|
301
|
+
if self._ndim is None or other._ndim is None:
|
|
302
|
+
return True
|
|
303
|
+
return self._ndim == other._ndim
|
|
304
|
+
|
|
305
|
+
def __hash__(self):
|
|
306
|
+
return self._partition_dim
|
|
307
|
+
|
|
308
|
+
def __str__(self):
|
|
309
|
+
return f"Slab(partition_dim={self._partition_dim}, ndim={self._ndim})" + self._binding_str()
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def name(self) -> str:
|
|
313
|
+
match self._partition_dim:
|
|
314
|
+
case 0:
|
|
315
|
+
return "Slab.X"
|
|
316
|
+
case 1:
|
|
317
|
+
return "Slab.Y"
|
|
318
|
+
case 2:
|
|
319
|
+
return "Slab.Z"
|
|
320
|
+
case _:
|
|
321
|
+
return "Slab"
|
|
322
|
+
|
|
323
|
+
@property
|
|
324
|
+
def partition_dim(self) -> int:
|
|
325
|
+
"""Slab partition dimension"""
|
|
326
|
+
return self._partition_dim
|
|
327
|
+
|
|
328
|
+
@property
|
|
329
|
+
def ndim(self) -> int | None:
|
|
330
|
+
return self._ndim
|
|
331
|
+
|
|
332
|
+
def shape(self, rank: int, global_shape: Sequence[int] | None = None) -> tuple[int, ...]:
|
|
333
|
+
n = _get_process_group().nranks
|
|
334
|
+
self._local_shape_checks(rank, n, global_shape)
|
|
335
|
+
if global_shape is None:
|
|
336
|
+
global_shape = self._data_global_shape
|
|
337
|
+
|
|
338
|
+
S = global_shape[self._partition_dim]
|
|
339
|
+
partition_dim_local_size = S // n + bool(rank < S % n)
|
|
340
|
+
slab_shape = list(global_shape)
|
|
341
|
+
slab_shape[self._partition_dim] = partition_dim_local_size
|
|
342
|
+
return tuple(slab_shape)
|
|
343
|
+
|
|
344
|
+
@property
|
|
345
|
+
def _cufftmp_value(self):
|
|
346
|
+
if self._partition_dim not in (0, 1):
|
|
347
|
+
raise TypeError(f"Unsupported distribution {self} for cuFFTMp: partition dimension must be X or Y")
|
|
348
|
+
return cufftMp.XtSubFormat.FORMAT_INPLACE if self._partition_dim == 0 else cufftMp.XtSubFormat.FORMAT_INPLACE_SHUFFLED
|
|
349
|
+
|
|
350
|
+
def _bind(self, global_shape, *, shape=None) -> Slab:
|
|
351
|
+
if self._bound:
|
|
352
|
+
raise BindDistributionError(f"{self} is already bound")
|
|
353
|
+
|
|
354
|
+
if self._ndim is not None and len(global_shape) != self._ndim:
|
|
355
|
+
raise ValueError(f"The given shape doesn't have the same dimensionality as this {self} distribution")
|
|
356
|
+
|
|
357
|
+
if self._partition_dim >= len(global_shape):
|
|
358
|
+
raise ValueError("partition_dim must be < ndim")
|
|
359
|
+
|
|
360
|
+
rank = _get_process_group().rank
|
|
361
|
+
slab_shape = self.shape(rank, global_shape)
|
|
362
|
+
|
|
363
|
+
if shape is not None and tuple(shape) != slab_shape:
|
|
364
|
+
raise BindDistributionError(
|
|
365
|
+
f"The given shapes (global_shape={global_shape}, shape={shape}) don't fit distribution {str(self)}"
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
self._ndim = len(global_shape)
|
|
369
|
+
self._data_global_shape = tuple(global_shape)
|
|
370
|
+
self._data_shape = slab_shape
|
|
371
|
+
self._bound = True
|
|
372
|
+
return self
|
|
373
|
+
|
|
374
|
+
def to(self, cls, /, *, ndim=None, copy=False):
|
|
375
|
+
super()._to_checks(cls, ndim)
|
|
376
|
+
nranks = _get_process_group().nranks
|
|
377
|
+
|
|
378
|
+
if ndim is None:
|
|
379
|
+
ndim = self._ndim
|
|
380
|
+
elif self._partition_dim >= ndim:
|
|
381
|
+
raise ValueError(f"ndim ({ndim}) must be greater than the partition dimension ({self._partition_dim})")
|
|
382
|
+
|
|
383
|
+
if cls is Slab:
|
|
384
|
+
if copy or (ndim is not None and self._ndim is None):
|
|
385
|
+
d = cast(Slab, self.copy())
|
|
386
|
+
d._ndim = ndim
|
|
387
|
+
return d
|
|
388
|
+
return self
|
|
389
|
+
elif issubclass(cls, BlockCyclic):
|
|
390
|
+
if ndim is None:
|
|
391
|
+
raise ConvertDistributionError("Can't convert Slab distribution to BlockCyclic: unknown dimensionality")
|
|
392
|
+
|
|
393
|
+
process_grid_shape = tuple(1 if x != self._partition_dim else nranks for x in range(ndim))
|
|
394
|
+
# layout doesn't matter when partitioning on a single axis.
|
|
395
|
+
process_grid = ProcessGrid(shape=process_grid_shape, layout=ProcessGrid.Layout.ROW_MAJOR)
|
|
396
|
+
if not self._bound:
|
|
397
|
+
return BlockNonCyclic(process_grid)
|
|
398
|
+
else:
|
|
399
|
+
b = BlockNonCyclic(process_grid)
|
|
400
|
+
return b._bind(self._data_global_shape, shape=self._data_shape)
|
|
401
|
+
elif cls is Box:
|
|
402
|
+
raise NotImplementedError
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
# Define alternate forms for the user to specify Slab on X, Y or Z.
|
|
406
|
+
# NOTE: dimensionality is left unspecified when using these (but will be set when the
|
|
407
|
+
# distribution is bound to data).
|
|
408
|
+
Slab.X = Slab(0)
|
|
409
|
+
Slab.Y = Slab(1)
|
|
410
|
+
Slab.Z = Slab(2)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
class Box(Distribution):
|
|
414
|
+
"""Box distribution"""
|
|
415
|
+
|
|
416
|
+
def __init__(
|
|
417
|
+
self,
|
|
418
|
+
lower: Sequence[int],
|
|
419
|
+
upper: Sequence[int],
|
|
420
|
+
):
|
|
421
|
+
super().__init__()
|
|
422
|
+
if len(lower) != len(upper):
|
|
423
|
+
raise ValueError("lower and upper coordinates must have the same dimensionality")
|
|
424
|
+
for coords in (lower, upper):
|
|
425
|
+
if not all(isinstance(x, int) for x in coords):
|
|
426
|
+
raise ValueError("lower and upper coordinates must be integer")
|
|
427
|
+
if not all(upper[i] > lower[i] for i in range(len(upper))):
|
|
428
|
+
raise ValueError(
|
|
429
|
+
f"The upper coordinates must be larger than the lower coordinates, but got lower={lower} upper={upper}"
|
|
430
|
+
)
|
|
431
|
+
self._lower = tuple(lower)
|
|
432
|
+
self._upper = tuple(upper)
|
|
433
|
+
|
|
434
|
+
@property
|
|
435
|
+
def lower(self) -> tuple[int, ...]:
|
|
436
|
+
"""Box lower coordinates"""
|
|
437
|
+
return self._lower
|
|
438
|
+
|
|
439
|
+
@property
|
|
440
|
+
def upper(self) -> tuple[int, ...]:
|
|
441
|
+
"""Box upper coordinates"""
|
|
442
|
+
return self._upper
|
|
443
|
+
|
|
444
|
+
@property
|
|
445
|
+
def ndim(self) -> int:
|
|
446
|
+
return len(self._lower)
|
|
447
|
+
|
|
448
|
+
def shape(self, rank: int, global_shape: Sequence[int] | None = None) -> tuple[int, ...]:
|
|
449
|
+
process_group = _get_process_group()
|
|
450
|
+
if rank != process_group.rank:
|
|
451
|
+
raise RuntimeError("Can't calculate local shape of peer process with Box distribution")
|
|
452
|
+
nranks = process_group.nranks
|
|
453
|
+
self._local_shape_checks(rank, nranks, global_shape)
|
|
454
|
+
return tuple(self._upper[i] - self._lower[i] for i in range(self.ndim))
|
|
455
|
+
|
|
456
|
+
def __str__(self):
|
|
457
|
+
return f"Box(lower={self._lower}, upper={self._upper})" + self._binding_str()
|
|
458
|
+
|
|
459
|
+
def __eq__(self, other):
|
|
460
|
+
if not isinstance(other, Box):
|
|
461
|
+
return False
|
|
462
|
+
return self._lower == other._lower and self._upper == other._upper
|
|
463
|
+
|
|
464
|
+
def __hash__(self):
|
|
465
|
+
return hash((self._lower, self._upper))
|
|
466
|
+
|
|
467
|
+
def __iter__(self):
|
|
468
|
+
# To allow unpacking
|
|
469
|
+
yield self._lower
|
|
470
|
+
yield self._upper
|
|
471
|
+
|
|
472
|
+
def __getitem__(self, index):
|
|
473
|
+
if index not in (0, 1):
|
|
474
|
+
return IndexError(f"Index must be 0 or 1, got {index}")
|
|
475
|
+
return self._lower if index == 0 else self._upper
|
|
476
|
+
|
|
477
|
+
def _bind(self, global_shape, *, shape=None) -> Distribution:
|
|
478
|
+
if self._bound:
|
|
479
|
+
raise BindDistributionError(f"{self} is already bound")
|
|
480
|
+
my_shape = tuple(self._upper[i] - self._lower[i] for i in range(self.ndim))
|
|
481
|
+
if shape is not None and tuple(shape) != my_shape:
|
|
482
|
+
raise BindDistributionError(
|
|
483
|
+
f"The given shapes don't fit this Box distribution: {global_shape} "
|
|
484
|
+
f"and {shape}, lower={self._lower} upper={self._upper}"
|
|
485
|
+
)
|
|
486
|
+
self._data_global_shape = tuple(global_shape)
|
|
487
|
+
self._data_shape = my_shape
|
|
488
|
+
self._bound = True
|
|
489
|
+
return self
|
|
490
|
+
|
|
491
|
+
def to(self, cls, /, *, ndim=None, copy=False):
|
|
492
|
+
super()._to_checks(cls, ndim)
|
|
493
|
+
if cls is Box:
|
|
494
|
+
return self.copy() if copy else self
|
|
495
|
+
raise NotImplementedError
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
class BlockCyclic(Distribution):
|
|
499
|
+
"""Block-cyclic distribution"""
|
|
500
|
+
|
|
501
|
+
def __init__(
|
|
502
|
+
self,
|
|
503
|
+
process_grid: ProcessGrid,
|
|
504
|
+
block_sizes: Sequence[int],
|
|
505
|
+
*,
|
|
506
|
+
first_process: Sequence[int] | None = None,
|
|
507
|
+
):
|
|
508
|
+
super().__init__()
|
|
509
|
+
if block_sizes is None or not all(isinstance(x, int) for x in block_sizes):
|
|
510
|
+
raise ValueError(f"Must provide a sequence of integer block sizes, got {block_sizes}")
|
|
511
|
+
if len(block_sizes) != len(process_grid.shape):
|
|
512
|
+
raise ValueError(
|
|
513
|
+
f"Number of block sizes ({len(block_sizes)}) doesn't match dimensionality ({len(process_grid.shape)})"
|
|
514
|
+
)
|
|
515
|
+
self._process_grid = process_grid
|
|
516
|
+
self._block_sizes = tuple(block_sizes)
|
|
517
|
+
if first_process is None:
|
|
518
|
+
self._first_process = (0,) * self.ndim
|
|
519
|
+
else:
|
|
520
|
+
if not all(isinstance(x, int) for x in first_process):
|
|
521
|
+
raise ValueError(f"first_process must be a sequence of integer coordinates, got {first_process}")
|
|
522
|
+
for i, x in enumerate(first_process):
|
|
523
|
+
if x < 0 or x >= process_grid.shape[i]:
|
|
524
|
+
raise ValueError(
|
|
525
|
+
f"first_process {first_process} is not a valid index into the process grid of "
|
|
526
|
+
f"shape {process_grid.shape}"
|
|
527
|
+
)
|
|
528
|
+
self._first_process = tuple(first_process)
|
|
529
|
+
|
|
530
|
+
@property
|
|
531
|
+
def process_grid(self) -> ProcessGrid:
|
|
532
|
+
"""The process grid of this BlockCyclic distribution"""
|
|
533
|
+
return self._process_grid
|
|
534
|
+
|
|
535
|
+
@property
|
|
536
|
+
def ndim(self) -> int:
|
|
537
|
+
return len(self._process_grid.shape)
|
|
538
|
+
|
|
539
|
+
@property
|
|
540
|
+
def block_sizes(self) -> tuple[int, ...]:
|
|
541
|
+
"""The block sizes of this BlockCyclic distribution"""
|
|
542
|
+
return self._block_sizes
|
|
543
|
+
|
|
544
|
+
@property
|
|
545
|
+
def first_process(self) -> tuple[int, ...]:
|
|
546
|
+
"""Index in the process grid of the process who owns the first block of the
|
|
547
|
+
distributed tensor."""
|
|
548
|
+
return self._first_process
|
|
549
|
+
|
|
550
|
+
def __str__(self):
|
|
551
|
+
return (
|
|
552
|
+
f"{self.__class__.__name__}(process_grid={self._process_grid}, block_sizes={self._block_sizes})"
|
|
553
|
+
+ self._binding_str()
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
def __eq__(self, other):
|
|
557
|
+
if not isinstance(other, BlockCyclic):
|
|
558
|
+
return False
|
|
559
|
+
return (
|
|
560
|
+
self._process_grid == other._process_grid
|
|
561
|
+
and self._block_sizes == other._block_sizes
|
|
562
|
+
and self._first_process == other._first_process
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
def _is_1d_distribution(self) -> bool:
|
|
566
|
+
"""True if process grid partitions on a single dimension."""
|
|
567
|
+
return self._process_grid._is_1d_distribution()
|
|
568
|
+
|
|
569
|
+
def _is_row_wise(self) -> bool:
|
|
570
|
+
"""True if 2D process grid partitioned only on rows."""
|
|
571
|
+
return self._process_grid._is_row_wise()
|
|
572
|
+
|
|
573
|
+
def _is_col_wise(self) -> bool:
|
|
574
|
+
"""True if 2D process grid partitioned only on columns."""
|
|
575
|
+
return self._process_grid._is_col_wise()
|
|
576
|
+
|
|
577
|
+
def shape(self, rank: int, global_shape: Sequence[int] | None = None) -> tuple[int, ...]:
|
|
578
|
+
self._local_shape_checks(rank, self._process_grid._nranks, global_shape)
|
|
579
|
+
if global_shape is None:
|
|
580
|
+
global_shape = self._data_global_shape
|
|
581
|
+
return self._calc_local_shape(rank, self._block_sizes, global_shape)
|
|
582
|
+
|
|
583
|
+
def _calc_local_shape(self, rank, block_sizes, global_shape):
|
|
584
|
+
nprow, npcol = self._process_grid._shape
|
|
585
|
+
layout = self._process_grid._layout
|
|
586
|
+
if layout is not None:
|
|
587
|
+
myprow = rank % nprow if layout == ProcessGrid.Layout.COL_MAJOR else rank // npcol
|
|
588
|
+
mypcol = rank // nprow if layout == ProcessGrid.Layout.COL_MAJOR else rank % npcol
|
|
589
|
+
index = (myprow, mypcol)
|
|
590
|
+
else:
|
|
591
|
+
raise NotImplementedError
|
|
592
|
+
nrows = cublasMp.numroc(global_shape[0], block_sizes[0], index[0], self._first_process[0], nprow)
|
|
593
|
+
ncols = cublasMp.numroc(global_shape[1], block_sizes[1], index[1], self._first_process[1], npcol)
|
|
594
|
+
return (nrows, ncols)
|
|
595
|
+
|
|
596
|
+
def _bind(self, global_shape, *, shape=None) -> Distribution:
|
|
597
|
+
if self._bound:
|
|
598
|
+
raise BindDistributionError(f"{self} is already bound")
|
|
599
|
+
|
|
600
|
+
if self.ndim != 2:
|
|
601
|
+
raise NotImplementedError
|
|
602
|
+
|
|
603
|
+
if len(global_shape) != self.ndim:
|
|
604
|
+
raise ValueError(
|
|
605
|
+
f"Dimensionality of shapes ({len(global_shape)}) doesn't match dimensionality "
|
|
606
|
+
f"of this distribution ({self.ndim})"
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
rank = _get_process_group().rank
|
|
610
|
+
nrows, ncols = self._calc_local_shape(rank, self._block_sizes, global_shape)
|
|
611
|
+
if shape is not None and tuple(shape) != (nrows, ncols):
|
|
612
|
+
raise BindDistributionError(
|
|
613
|
+
f"The local shape {shape} on process {rank} is not the expected one based "
|
|
614
|
+
f"on the global shape {global_shape}, process grid {self._process_grid} and "
|
|
615
|
+
f"block sizes {self._block_sizes}: expected shape is {(nrows, ncols)}"
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
self._data_global_shape = global_shape
|
|
619
|
+
self._data_shape = (nrows, ncols)
|
|
620
|
+
self._bound = True
|
|
621
|
+
return self
|
|
622
|
+
|
|
623
|
+
def to(self, cls, /, *, ndim=None, copy=False):
|
|
624
|
+
super()._to_checks(cls, ndim)
|
|
625
|
+
nranks = _get_process_group().nranks
|
|
626
|
+
if issubclass(cls, BlockCyclic):
|
|
627
|
+
return self.copy() if copy else self
|
|
628
|
+
elif cls is Slab:
|
|
629
|
+
if not self._bound:
|
|
630
|
+
# Without binding, it's a stretch to assume that this is compatible with
|
|
631
|
+
# Slab (the "cyclic" nature means that it's much more likely that it isn't).
|
|
632
|
+
raise ConvertDistributionError(
|
|
633
|
+
"Unbound BlockCyclic distribution can't be converted to Slab. "
|
|
634
|
+
"Consider using BlockNonCyclic if there is no cyclic distribution of blocks."
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
if not self._is_1d_distribution():
|
|
638
|
+
raise ConvertDistributionError(
|
|
639
|
+
"Can't convert this block distribution to Slab: partitioning must be on a single dimension"
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
# Data must be divisible on partition_dim and the block size must correspond
|
|
643
|
+
# to the slab size.
|
|
644
|
+
partition_dim = self._process_grid.shape.index(nranks)
|
|
645
|
+
|
|
646
|
+
if self._data_global_shape[partition_dim] % nranks != 0:
|
|
647
|
+
raise ConvertDistributionError(
|
|
648
|
+
"Can't convert this distribution to Slab: data doesn't divide evenly on partition dimension."
|
|
649
|
+
f" Global shape is {self._data_global_shape}, partition dimension is "
|
|
650
|
+
f"{partition_dim} and number of processes is {nranks}."
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
if self._block_sizes[partition_dim] != self._data_global_shape[partition_dim] // nranks:
|
|
654
|
+
raise ConvertDistributionError(
|
|
655
|
+
"This distribution can't be converted to Slab, because the block size in the "
|
|
656
|
+
f"partition dimension {partition_dim} is not a factor of the global extent in "
|
|
657
|
+
f"that dimension {self._data_global_shape[partition_dim]}"
|
|
658
|
+
)
|
|
659
|
+
d = Slab(partition_dim, ndim=self.ndim)
|
|
660
|
+
return d._bind(self._data_global_shape, self._data_shape)
|
|
661
|
+
elif cls is Box:
|
|
662
|
+
raise NotImplementedError
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
class BlockNonCyclic(BlockCyclic):
|
|
666
|
+
"""Block distribution without cycles"""
|
|
667
|
+
|
|
668
|
+
def __init__(
|
|
669
|
+
self,
|
|
670
|
+
process_grid: ProcessGrid,
|
|
671
|
+
*,
|
|
672
|
+
first_process: Sequence[int] | None = None,
|
|
673
|
+
):
|
|
674
|
+
super().__init__(process_grid, (-1,) * len(process_grid.shape), first_process=first_process)
|
|
675
|
+
|
|
676
|
+
def __eq__(self, other):
|
|
677
|
+
if isinstance(other, BlockNonCyclic) and (not other._bound or not self._bound):
|
|
678
|
+
# Don't compare block sizes since one of them is not bound.
|
|
679
|
+
return self._process_grid == other._process_grid and self._first_process == other._first_process
|
|
680
|
+
return super().__eq__(other)
|
|
681
|
+
|
|
682
|
+
def shape(self, rank: int, global_shape: Sequence[int] | None = None) -> tuple[int, ...]:
|
|
683
|
+
self._local_shape_checks(rank, self._process_grid._nranks, global_shape)
|
|
684
|
+
if global_shape is None:
|
|
685
|
+
global_shape = self._data_global_shape
|
|
686
|
+
if self._bound:
|
|
687
|
+
block_sizes = self._block_sizes
|
|
688
|
+
else:
|
|
689
|
+
block_sizes = self._infer_block_sizes(global_shape)
|
|
690
|
+
return self._calc_local_shape(rank, block_sizes, global_shape)
|
|
691
|
+
|
|
692
|
+
def _infer_block_sizes(self, global_shape):
|
|
693
|
+
if all(x % self._process_grid._shape[i] == 0 for i, x in enumerate(global_shape)):
|
|
694
|
+
block_sizes = tuple(x // self._process_grid._shape[i] for i, x in enumerate(global_shape))
|
|
695
|
+
else:
|
|
696
|
+
# The logic to bind this global shape to this distribution isn't implemented yet
|
|
697
|
+
# (which doesn't necessarily mean that it isn't possible to fit the data to this
|
|
698
|
+
# distribution).
|
|
699
|
+
raise NotImplementedError(
|
|
700
|
+
"BlockNonCyclic is currently only implemented for uniform partition sizes. "
|
|
701
|
+
"Use BlockCyclic with explicit block sizes instead."
|
|
702
|
+
)
|
|
703
|
+
return block_sizes
|
|
704
|
+
|
|
705
|
+
def _bind(self, global_shape, *, shape=None) -> Distribution:
|
|
706
|
+
if self._bound:
|
|
707
|
+
raise BindDistributionError(f"{self} is already bound")
|
|
708
|
+
|
|
709
|
+
assert all(x == -1 for x in self._block_sizes)
|
|
710
|
+
|
|
711
|
+
if len(global_shape) != self.ndim:
|
|
712
|
+
raise ValueError(
|
|
713
|
+
f"Dimensionality of shapes ({len(global_shape)}) doesn't match dimensionality "
|
|
714
|
+
f"of this distribution ({self.ndim})"
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
# Now we assign block sizes to this distribution based on the shape of the data.
|
|
718
|
+
# NOTE: For dimensions that aren't partitioned, there are multiple blocks sizes that
|
|
719
|
+
# are valid and still fit the Block[Non]Cyclic model. This is because, in addition
|
|
720
|
+
# to block size being the full length L of the dimension, any L//N is also a valid
|
|
721
|
+
# block size (so a single block in that dimension is equivalent to N contiguous
|
|
722
|
+
# blocks in that dimension). The importance of this is that cuBLASMp actually
|
|
723
|
+
# requires configurations with these block sizes, since block sizes have to match
|
|
724
|
+
# across matrices A, B and C but they might be distributed differently.
|
|
725
|
+
# In other words, for distributed matmul with BlockNonCyclic, block sizes have to be
|
|
726
|
+
# inferred jointly with matrices A, B and C, but that is outside the scope of this
|
|
727
|
+
# method.
|
|
728
|
+
|
|
729
|
+
block_sizes = self._infer_block_sizes(global_shape)
|
|
730
|
+
if shape is not None and block_sizes != tuple(shape):
|
|
731
|
+
raise BindDistributionError("Data doesn't fit BlockNonCyclic distribution")
|
|
732
|
+
|
|
733
|
+
self._block_sizes = block_sizes
|
|
734
|
+
return super()._bind(global_shape, shape=shape)
|
|
735
|
+
|
|
736
|
+
def to(self, cls, /, *, ndim=None, copy=False):
|
|
737
|
+
super()._to_checks(cls, ndim)
|
|
738
|
+
nranks = _get_process_group().nranks
|
|
739
|
+
if issubclass(cls, BlockCyclic):
|
|
740
|
+
return self.copy() if copy else self
|
|
741
|
+
elif cls is Slab:
|
|
742
|
+
if not self._is_1d_distribution():
|
|
743
|
+
raise ConvertDistributionError(
|
|
744
|
+
"Can't convert this block distribution to Slab: partitioning must be on a single dimension"
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
partition_dim = self._process_grid.shape.index(nranks)
|
|
748
|
+
d = Slab(partition_dim, ndim=self.ndim)
|
|
749
|
+
# For bound=False, we can allow the conversion and let Slab._bind() catch
|
|
750
|
+
# any potential errors later.
|
|
751
|
+
if self._bound:
|
|
752
|
+
if self._data_global_shape[partition_dim] % nranks != 0:
|
|
753
|
+
raise ConvertDistributionError(
|
|
754
|
+
"Can't convert this distribution to Slab: data doesn't divide evenly on partition dimension"
|
|
755
|
+
)
|
|
756
|
+
d._bind(self._data_global_shape, shape=self._data_shape)
|
|
757
|
+
return d
|
|
758
|
+
elif cls is Box:
|
|
759
|
+
raise NotImplementedError
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
def _get_process_group():
|
|
763
|
+
distributed_ctx = dist.get_context()
|
|
764
|
+
if distributed_ctx is None:
|
|
765
|
+
raise RuntimeError(
|
|
766
|
+
"nvmath.distributed has not been initialized. Refer to "
|
|
767
|
+
"https://docs.nvidia.com/cuda/nvmath-python/latest/distributed-apis/runtime.html"
|
|
768
|
+
" for more information."
|
|
769
|
+
)
|
|
770
|
+
return distributed_ctx.process_group
|