spmd_types 0.2.1__tar.gz → 0.2.2__tar.gz
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.
- {spmd_types-0.2.1/spmd_types.egg-info → spmd_types-0.2.2}/PKG-INFO +34 -16
- spmd_types-0.2.2/README.md +69 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/pyproject.toml +1 -1
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/__init__.py +2 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_checker/__init__.py +87 -85
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_collectives.py +38 -27
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_local.py +35 -28
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_mesh.py +34 -3
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_mesh_axis.py +44 -16
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_state.py +28 -1
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/runtime.py +31 -3
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/types.py +77 -20
- {spmd_types-0.2.1 → spmd_types-0.2.2/spmd_types.egg-info}/PKG-INFO +34 -16
- spmd_types-0.2.1/README.md +0 -51
- {spmd_types-0.2.1 → spmd_types-0.2.2}/LICENSE +0 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/setup.cfg +0 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_backward_hooks.py +0 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_dist.py +0 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_dtensor.py +0 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_dtensor_checker.py +0 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_dtype_utils.py +0 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_frame.py +0 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_mesh_region.py +0 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_raw_dist.py +0 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_reinterpret_mesh.py +0 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_scalar.py +0 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_scalar_sentinel.py +0 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_test_utils.py +0 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_testing.py +0 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_traceback.py +0 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_type_attr.py +0 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_vmap.py +0 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/checker.py +0 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types.egg-info/SOURCES.txt +0 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types.egg-info/dependency_links.txt +0 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types.egg-info/requires.txt +0 -0
- {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spmd_types
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: A type system for distributed (SPMD) tensor computations in PyTorch
|
|
5
5
|
Author: Meta Platforms, Inc.
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -50,14 +50,29 @@ License-File: LICENSE
|
|
|
50
50
|
Requires-Dist: torch>=2.10.0
|
|
51
51
|
Dynamic: license-file
|
|
52
52
|
|
|
53
|
-
# spmd_types
|
|
53
|
+
# `spmd_types`
|
|
54
54
|
|
|
55
|
-
A type system for distributed (SPMD) tensor computations in PyTorch.
|
|
55
|
+
A type system for distributed (SPMD) tensor computations in PyTorch. This
|
|
56
|
+
package provides two type systems:
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
* **Local SPMD types**, which allow you to use Megatron-style differentiable
|
|
59
|
+
collectives in a safe way by tracking whether or not your backward gradients
|
|
60
|
+
are pending reduction or not.
|
|
61
|
+
|
|
62
|
+
* **Global SPMD types**, a DTensor-like abstraction for writing code that has
|
|
63
|
+
the same semantics whether run on a single device or in a distributed
|
|
64
|
+
fashion, but with explicit communication operations so you are never
|
|
65
|
+
guessing when a redistribute occurs.
|
|
66
|
+
|
|
67
|
+
In both cases, the SPMD types makes it possible for you to check that your
|
|
68
|
+
code computes correct gradients (local SPMD) or gives equivalent results
|
|
69
|
+
across different parallelizations (global SPMD), without having to actually
|
|
70
|
+
run a full E2E distributed training run to check for loss matching.
|
|
71
|
+
|
|
72
|
+
The goal of this package is to provide a flexible type system that can
|
|
73
|
+
typecheck realistic training code. We have used local SPMD types to typecheck
|
|
74
|
+
a realistic pretraining codebase, and global SPMD types is actively under
|
|
75
|
+
construction!
|
|
61
76
|
|
|
62
77
|
## Installation
|
|
63
78
|
|
|
@@ -71,6 +86,7 @@ pip install spmd_types
|
|
|
71
86
|
import torch
|
|
72
87
|
import torch.distributed as dist
|
|
73
88
|
import spmd_types as spmd
|
|
89
|
+
import spmd_types.checker
|
|
74
90
|
from torch.distributed.device_mesh import init_device_mesh
|
|
75
91
|
|
|
76
92
|
# Set up a fake process group (no GPUs needed)
|
|
@@ -79,12 +95,12 @@ mesh = init_device_mesh("cpu", (2, 4), mesh_dim_names=("dp", "tp"))
|
|
|
79
95
|
dp = mesh.get_group("dp")
|
|
80
96
|
tp = mesh.get_group("tp")
|
|
81
97
|
|
|
82
|
-
with spmd.set_current_mesh(mesh), spmd.typecheck():
|
|
98
|
+
with spmd.set_current_mesh(mesh), spmd.checker.typecheck():
|
|
83
99
|
x = torch.randn(4)
|
|
84
|
-
spmd.assert_type(x, {dp: spmd.R, tp: spmd.P})
|
|
100
|
+
spmd.assert_type(x, {dp: spmd.R, tp: spmd.P}) # R on dp, partial on tp
|
|
85
101
|
y = spmd.all_reduce(x, tp, src=spmd.P, dst=spmd.R) # sum across tp ranks
|
|
86
|
-
spmd.assert_type(y, {dp: spmd.R, tp: spmd.R})
|
|
87
|
-
z = torch.mul(y, y)
|
|
102
|
+
spmd.assert_type(y, {dp: spmd.R, tp: spmd.R}) # now replicated everywhere
|
|
103
|
+
z = torch.mul(y, y) # type inference: R * R -> R
|
|
88
104
|
spmd.assert_type(z, {dp: spmd.R, tp: spmd.R})
|
|
89
105
|
|
|
90
106
|
dist.destroy_process_group()
|
|
@@ -92,12 +108,14 @@ dist.destroy_process_group()
|
|
|
92
108
|
|
|
93
109
|
## Documentation
|
|
94
110
|
|
|
95
|
-
See [
|
|
96
|
-
Megatron-derived training frameworks
|
|
111
|
+
See [Local SPMD types](docs/local_spmd_types.md) for a hands-on guide on
|
|
112
|
+
porting Megatron-derived training frameworks, including the Megatron-to-spmd_types
|
|
113
|
+
function mapping table and advice on Invariant vs Replicate.
|
|
97
114
|
|
|
98
|
-
See [
|
|
99
|
-
|
|
115
|
+
See [Design](docs/design.md) for the full type system specification, including
|
|
116
|
+
local vs global SPMD modes, collective signatures with diagrams, forward-backward
|
|
117
|
+
pairs, expert mode, cross-mesh compatibility, and partition spec redistribute.
|
|
100
118
|
|
|
101
119
|
## License
|
|
102
120
|
|
|
103
|
-
BSD 3-Clause License. See [
|
|
121
|
+
BSD 3-Clause License. See [CONTRIBUTING.md](CONTRIBUTING.md) for how to contribute.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# `spmd_types`
|
|
2
|
+
|
|
3
|
+
A type system for distributed (SPMD) tensor computations in PyTorch. This
|
|
4
|
+
package provides two type systems:
|
|
5
|
+
|
|
6
|
+
* **Local SPMD types**, which allow you to use Megatron-style differentiable
|
|
7
|
+
collectives in a safe way by tracking whether or not your backward gradients
|
|
8
|
+
are pending reduction or not.
|
|
9
|
+
|
|
10
|
+
* **Global SPMD types**, a DTensor-like abstraction for writing code that has
|
|
11
|
+
the same semantics whether run on a single device or in a distributed
|
|
12
|
+
fashion, but with explicit communication operations so you are never
|
|
13
|
+
guessing when a redistribute occurs.
|
|
14
|
+
|
|
15
|
+
In both cases, the SPMD types makes it possible for you to check that your
|
|
16
|
+
code computes correct gradients (local SPMD) or gives equivalent results
|
|
17
|
+
across different parallelizations (global SPMD), without having to actually
|
|
18
|
+
run a full E2E distributed training run to check for loss matching.
|
|
19
|
+
|
|
20
|
+
The goal of this package is to provide a flexible type system that can
|
|
21
|
+
typecheck realistic training code. We have used local SPMD types to typecheck
|
|
22
|
+
a realistic pretraining codebase, and global SPMD types is actively under
|
|
23
|
+
construction!
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install spmd_types
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import torch
|
|
35
|
+
import torch.distributed as dist
|
|
36
|
+
import spmd_types as spmd
|
|
37
|
+
import spmd_types.checker
|
|
38
|
+
from torch.distributed.device_mesh import init_device_mesh
|
|
39
|
+
|
|
40
|
+
# Set up a fake process group (no GPUs needed)
|
|
41
|
+
dist.init_process_group(backend="fake", rank=0, world_size=8)
|
|
42
|
+
mesh = init_device_mesh("cpu", (2, 4), mesh_dim_names=("dp", "tp"))
|
|
43
|
+
dp = mesh.get_group("dp")
|
|
44
|
+
tp = mesh.get_group("tp")
|
|
45
|
+
|
|
46
|
+
with spmd.set_current_mesh(mesh), spmd.checker.typecheck():
|
|
47
|
+
x = torch.randn(4)
|
|
48
|
+
spmd.assert_type(x, {dp: spmd.R, tp: spmd.P}) # R on dp, partial on tp
|
|
49
|
+
y = spmd.all_reduce(x, tp, src=spmd.P, dst=spmd.R) # sum across tp ranks
|
|
50
|
+
spmd.assert_type(y, {dp: spmd.R, tp: spmd.R}) # now replicated everywhere
|
|
51
|
+
z = torch.mul(y, y) # type inference: R * R -> R
|
|
52
|
+
spmd.assert_type(z, {dp: spmd.R, tp: spmd.R})
|
|
53
|
+
|
|
54
|
+
dist.destroy_process_group()
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Documentation
|
|
58
|
+
|
|
59
|
+
See [Local SPMD types](docs/local_spmd_types.md) for a hands-on guide on
|
|
60
|
+
porting Megatron-derived training frameworks, including the Megatron-to-spmd_types
|
|
61
|
+
function mapping table and advice on Invariant vs Replicate.
|
|
62
|
+
|
|
63
|
+
See [Design](docs/design.md) for the full type system specification, including
|
|
64
|
+
local vs global SPMD modes, collective signatures with diagrams, forward-backward
|
|
65
|
+
pairs, expert mode, cross-mesh compatibility, and partition spec redistribute.
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
BSD 3-Clause License. See [CONTRIBUTING.md](CONTRIBUTING.md) for how to contribute.
|
|
@@ -53,6 +53,7 @@ from spmd_types._type_attr import ( # noqa: F401
|
|
|
53
53
|
# Collectives and operations -- runtime API (no _checker dependency)
|
|
54
54
|
from spmd_types.runtime import ( # noqa: F401
|
|
55
55
|
assert_local_type,
|
|
56
|
+
assert_local_type_like,
|
|
56
57
|
assert_type,
|
|
57
58
|
assert_type_like,
|
|
58
59
|
Infer,
|
|
@@ -84,6 +85,7 @@ from spmd_types.types import ( # noqa: F401
|
|
|
84
85
|
Replicate,
|
|
85
86
|
S,
|
|
86
87
|
Shard,
|
|
88
|
+
SpmdType,
|
|
87
89
|
SpmdTypeError,
|
|
88
90
|
TensorSharding,
|
|
89
91
|
V,
|
|
@@ -74,8 +74,6 @@ from spmd_types.runtime import ( # noqa: F401
|
|
|
74
74
|
trace,
|
|
75
75
|
)
|
|
76
76
|
from spmd_types.types import (
|
|
77
|
-
_canonicalize_shard,
|
|
78
|
-
_check_orthogonality,
|
|
79
77
|
DeviceMeshAxis,
|
|
80
78
|
DTensorPropagationError,
|
|
81
79
|
format_axis,
|
|
@@ -885,13 +883,15 @@ class _OpSpec:
|
|
|
885
883
|
tensor_kwargs: Kwarg names that are tensor inputs.
|
|
886
884
|
tensor_varargs_from: If set, all positional args from this index onward
|
|
887
885
|
are tensor inputs (for ops with *args like einsum).
|
|
886
|
+
nonlinear_args: Positional arg indices whose operand enters the op
|
|
887
|
+
nonlinearly and therefore must not be P.
|
|
888
888
|
"""
|
|
889
889
|
|
|
890
890
|
linearity: OpLinearity
|
|
891
891
|
tensor_args: tuple[int, ...] = ()
|
|
892
892
|
tensor_kwargs: tuple[str, ...] = ()
|
|
893
893
|
tensor_varargs_from: int | None = None
|
|
894
|
-
|
|
894
|
+
nonlinear_args: tuple[int, ...] = ()
|
|
895
895
|
|
|
896
896
|
|
|
897
897
|
_OP_REGISTRY: dict[Callable, _OpSpec] = {
|
|
@@ -941,6 +941,8 @@ _OP_REGISTRY: dict[Callable, _OpSpec] = {
|
|
|
941
941
|
torch.Tensor.mean: _OpSpec(OpLinearity.LINEAR, (0,)),
|
|
942
942
|
torch.nansum: _OpSpec(OpLinearity.LINEAR, (0,)),
|
|
943
943
|
torch.nanmean: _OpSpec(OpLinearity.LINEAR, (0,)),
|
|
944
|
+
torch.cumsum: _OpSpec(OpLinearity.LINEAR, (0,)),
|
|
945
|
+
torch.Tensor.cumsum: _OpSpec(OpLinearity.LINEAR, (0,)),
|
|
944
946
|
# =================================================================
|
|
945
947
|
# LINEAR -- concat / stack (tensor list at pos 0)
|
|
946
948
|
# =================================================================
|
|
@@ -1028,31 +1030,68 @@ _OP_REGISTRY: dict[Callable, _OpSpec] = {
|
|
|
1028
1030
|
torch.Tensor.repeat_interleave: _OpSpec(OpLinearity.LINEAR, (0,)),
|
|
1029
1031
|
torch.Tensor.unfold: _OpSpec(OpLinearity.LINEAR, (0,)),
|
|
1030
1032
|
# =================================================================
|
|
1031
|
-
#
|
|
1033
|
+
# MULTILINEAR with nonlinear_args -- division (linear in numerator only)
|
|
1034
|
+
# Division is multiplicative, not additive: a lone P numerator scaled by an
|
|
1035
|
+
# R/scalar divisor stays P, while LINEAR would reject this as affine.
|
|
1036
|
+
# nonlinear_args additionally forbids P in the divisor because 1/y is
|
|
1037
|
+
# nonlinear in y.
|
|
1032
1038
|
# =================================================================
|
|
1033
|
-
torch.div: _OpSpec(
|
|
1039
|
+
torch.div: _OpSpec(
|
|
1040
|
+
OpLinearity.MULTILINEAR,
|
|
1041
|
+
(0, 1),
|
|
1042
|
+
("input", "other"),
|
|
1043
|
+
nonlinear_args=(1,),
|
|
1044
|
+
),
|
|
1034
1045
|
torch.divide: _OpSpec(
|
|
1035
|
-
OpLinearity.
|
|
1046
|
+
OpLinearity.MULTILINEAR,
|
|
1047
|
+
(0, 1),
|
|
1048
|
+
("input", "other"),
|
|
1049
|
+
nonlinear_args=(1,),
|
|
1036
1050
|
),
|
|
1037
1051
|
torch.true_divide: _OpSpec(
|
|
1038
|
-
OpLinearity.
|
|
1052
|
+
OpLinearity.MULTILINEAR,
|
|
1053
|
+
(0, 1),
|
|
1054
|
+
("input", "other"),
|
|
1055
|
+
nonlinear_args=(1,),
|
|
1056
|
+
),
|
|
1057
|
+
torch.Tensor.div: _OpSpec(
|
|
1058
|
+
OpLinearity.MULTILINEAR,
|
|
1059
|
+
(0, 1),
|
|
1060
|
+
("other",),
|
|
1061
|
+
nonlinear_args=(1,),
|
|
1039
1062
|
),
|
|
1040
|
-
torch.Tensor.div: _OpSpec(OpLinearity.LINEAR, (0, 1), ("other",), fixed_args=(1,)),
|
|
1041
1063
|
torch.Tensor.divide: _OpSpec(
|
|
1042
|
-
OpLinearity.
|
|
1064
|
+
OpLinearity.MULTILINEAR,
|
|
1065
|
+
(0, 1),
|
|
1066
|
+
("other",),
|
|
1067
|
+
nonlinear_args=(1,),
|
|
1043
1068
|
),
|
|
1044
1069
|
torch.Tensor.true_divide: _OpSpec(
|
|
1045
|
-
OpLinearity.
|
|
1070
|
+
OpLinearity.MULTILINEAR,
|
|
1071
|
+
(0, 1),
|
|
1072
|
+
("other",),
|
|
1073
|
+
nonlinear_args=(1,),
|
|
1074
|
+
),
|
|
1075
|
+
torch.Tensor.div_: _OpSpec(
|
|
1076
|
+
OpLinearity.MULTILINEAR,
|
|
1077
|
+
(0, 1),
|
|
1078
|
+
("other",),
|
|
1079
|
+
nonlinear_args=(1,),
|
|
1046
1080
|
),
|
|
1047
|
-
torch.Tensor.div_: _OpSpec(OpLinearity.LINEAR, (0, 1), ("other",), fixed_args=(1,)),
|
|
1048
1081
|
torch.Tensor.__truediv__: _OpSpec(
|
|
1049
|
-
OpLinearity.
|
|
1082
|
+
OpLinearity.MULTILINEAR,
|
|
1083
|
+
(0, 1),
|
|
1084
|
+
("other",),
|
|
1085
|
+
nonlinear_args=(1,),
|
|
1050
1086
|
),
|
|
1051
1087
|
torch.Tensor.__rtruediv__: _OpSpec(
|
|
1052
|
-
OpLinearity.
|
|
1088
|
+
OpLinearity.MULTILINEAR, (0, 1), ("other",), nonlinear_args=(0,)
|
|
1053
1089
|
),
|
|
1054
1090
|
torch.Tensor.__itruediv__: _OpSpec(
|
|
1055
|
-
OpLinearity.
|
|
1091
|
+
OpLinearity.MULTILINEAR,
|
|
1092
|
+
(0, 1),
|
|
1093
|
+
("other",),
|
|
1094
|
+
nonlinear_args=(1,),
|
|
1056
1095
|
),
|
|
1057
1096
|
# =================================================================
|
|
1058
1097
|
# MULTILINEAR -- multiplication
|
|
@@ -1539,80 +1578,40 @@ def _set_result_type(result: object, output_type: LocalSpmdType) -> None:
|
|
|
1539
1578
|
_apply(item)
|
|
1540
1579
|
|
|
1541
1580
|
|
|
1542
|
-
def
|
|
1581
|
+
def _validate_nonlinear_args(
|
|
1543
1582
|
func: Callable,
|
|
1544
1583
|
args: tuple,
|
|
1545
|
-
kwargs: dict,
|
|
1546
1584
|
spec: _OpSpec,
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
"""Filter fixed_args for LINEAR ops when Partial is present.
|
|
1550
|
-
|
|
1551
|
-
``fixed_args`` lists positional arg indices that must be held fixed (not P)
|
|
1552
|
-
for the op to be linear in the remaining args. For example, ``div(a, b)``
|
|
1553
|
-
is linear in ``a`` when ``b`` is fixed, so ``fixed_args=(1,)``.
|
|
1554
|
-
|
|
1555
|
-
When P is present among the non-fixed tensor args:
|
|
1556
|
-
1. Validate that tensor args at fixed_args positions don't have P on any axis.
|
|
1557
|
-
2. Exclude their types from the returned list so LINEAR inference sees only
|
|
1558
|
-
the "free" args (and doesn't reject P + R mixing).
|
|
1559
|
-
|
|
1560
|
-
When no P is present in the free args, return the original list unchanged
|
|
1561
|
-
so normal inference rules apply (e.g., R + V -> V).
|
|
1585
|
+
) -> None:
|
|
1586
|
+
"""Validate that operands entering the op nonlinearly are not Partial.
|
|
1562
1587
|
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
kwargs: Keyword arguments to the torch operation.
|
|
1567
|
-
spec: The op specification (must have non-empty fixed_args).
|
|
1568
|
-
input_types_list: The already-collected input types list.
|
|
1588
|
+
Division is the current user: ``x / y`` is linear (scaling) in ``x`` only,
|
|
1589
|
+
since ``1/y`` is nonlinear in ``y``. Nonlinear operands may be untyped
|
|
1590
|
+
Python scalars or typed non-Partial values, but they cannot be P.
|
|
1569
1591
|
"""
|
|
1570
|
-
# Identify which input_types_list entries came from fixed_args positions.
|
|
1571
|
-
# We re-walk args to figure out which types are "fixed" vs "free".
|
|
1572
|
-
fixed_positions = set(spec.fixed_args)
|
|
1573
|
-
free_types: list[LocalSpmdType] = []
|
|
1574
|
-
fixed_types: list[LocalSpmdType] = []
|
|
1575
1592
|
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
if isinstance(val, torch.Tensor):
|
|
1580
|
-
if i in fixed_positions:
|
|
1581
|
-
fixed_types.append(get_local_type(val))
|
|
1582
|
-
else:
|
|
1583
|
-
free_types.append(get_local_type(val))
|
|
1584
|
-
elif isinstance(val, (list, tuple)):
|
|
1585
|
-
for item in val:
|
|
1586
|
-
if isinstance(item, torch.Tensor):
|
|
1587
|
-
if i in fixed_positions:
|
|
1588
|
-
fixed_types.append(get_local_type(item))
|
|
1589
|
-
else:
|
|
1590
|
-
free_types.append(get_local_type(item))
|
|
1591
|
-
|
|
1592
|
-
# Check if P is present in any free arg on any axis
|
|
1593
|
-
has_p_in_free = any(P in typ.values() for typ in free_types)
|
|
1594
|
-
# Also check default V (missing from dict means V, not P) -- V != P, so fine.
|
|
1595
|
-
|
|
1596
|
-
if not has_p_in_free:
|
|
1597
|
-
# No P in free args -- normal inference, include all types.
|
|
1598
|
-
return input_types_list
|
|
1599
|
-
|
|
1600
|
-
# P is present in free args -- validate fixed args don't have P
|
|
1601
|
-
for fixed_type in fixed_types:
|
|
1602
|
-
for axis, typ in fixed_type.items():
|
|
1603
|
-
if typ is P:
|
|
1593
|
+
def _check_type(typ: LocalSpmdType) -> None:
|
|
1594
|
+
for axis, axis_type in typ.items():
|
|
1595
|
+
if axis_type is P:
|
|
1604
1596
|
raise SpmdTypeError(
|
|
1605
|
-
f"{func.__name__}: Partial type in
|
|
1606
|
-
f"
|
|
1607
|
-
f"
|
|
1597
|
+
f"{func.__name__}: Partial type in nonlinear argument "
|
|
1598
|
+
f"on axis {format_axis(axis)} is not allowed. Reduce or "
|
|
1599
|
+
f"convert the Partial operand before using it in a "
|
|
1600
|
+
f"nonlinear position."
|
|
1608
1601
|
)
|
|
1609
1602
|
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1603
|
+
def _check_value(val: object) -> None:
|
|
1604
|
+
if isinstance(val, torch.Tensor):
|
|
1605
|
+
_check_type(get_local_type(val))
|
|
1606
|
+
elif isinstance(val, Scalar):
|
|
1607
|
+
_check_type(get_local_type(val))
|
|
1608
|
+
elif isinstance(val, (list, tuple)):
|
|
1609
|
+
for item in val:
|
|
1610
|
+
_check_value(item)
|
|
1611
|
+
|
|
1612
|
+
for i in spec.nonlinear_args:
|
|
1613
|
+
if i < len(args):
|
|
1614
|
+
_check_value(args[i])
|
|
1616
1615
|
|
|
1617
1616
|
|
|
1618
1617
|
# Every function in this registry must accept (x: Tensor, axis, *, src=..., dst=...)
|
|
@@ -1680,7 +1679,7 @@ def _set_result_partition_spec(
|
|
|
1680
1679
|
_set_partition_spec(item, spec)
|
|
1681
1680
|
|
|
1682
1681
|
|
|
1683
|
-
def _collect_shard_axes(
|
|
1682
|
+
def _collect_shard_axes( # noqa: C901
|
|
1684
1683
|
partition_specs: list[PartitionSpec | None],
|
|
1685
1684
|
global_axes: set[MeshAxis],
|
|
1686
1685
|
) -> tuple[list[MeshAxis], dict[MeshAxis, set[MeshAxis]]]:
|
|
@@ -2086,11 +2085,16 @@ class _ShardPropagator:
|
|
|
2086
2085
|
"""
|
|
2087
2086
|
mesh = self._get_mesh(axis.size())
|
|
2088
2087
|
|
|
2088
|
+
from torch.utils._python_dispatch import _disable_current_modes
|
|
2089
|
+
|
|
2090
|
+
# Keep checker-internal fake DTensor propagation invisible to active
|
|
2091
|
+
# TorchDispatchModes such as selective activation checkpointing's
|
|
2092
|
+
# caching mode.
|
|
2089
2093
|
# Disable LocalTensorMode during meta DTensor propagation.
|
|
2090
2094
|
# DTensor.from_local calls mesh.get_coordinate() which, under
|
|
2091
2095
|
# LocalTensorMode(N), tries to map all N simulated ranks into
|
|
2092
2096
|
# the propagator's fake 1-D mesh.
|
|
2093
|
-
with maybe_disable_local_tensor_mode():
|
|
2097
|
+
with maybe_disable_local_tensor_mode(), _disable_current_modes():
|
|
2094
2098
|
meta_args = torch.utils._pytree.tree_map(
|
|
2095
2099
|
lambda x: self._to_meta_dtensor(x, axis, mesh), args
|
|
2096
2100
|
)
|
|
@@ -2902,10 +2906,8 @@ class _SpmdTypeMode(torch.overrides.TorchFunctionMode):
|
|
|
2902
2906
|
linearity = (
|
|
2903
2907
|
spec.linearity if spec is not None else OpLinearity.NONLINEAR
|
|
2904
2908
|
)
|
|
2905
|
-
if spec is not None and spec.
|
|
2906
|
-
|
|
2907
|
-
func, args, kwargs, spec, input_types_list
|
|
2908
|
-
)
|
|
2909
|
+
if spec is not None and spec.nonlinear_args:
|
|
2910
|
+
_validate_nonlinear_args(func, original_args, spec)
|
|
2909
2911
|
# NB: output_type may be further updated because global
|
|
2910
2912
|
# shard propagation below may promote local type V to P.
|
|
2911
2913
|
output_type = infer_output_type(
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
-
from typing import List, Optional
|
|
11
|
+
from typing import List, Optional
|
|
12
12
|
|
|
13
13
|
import torch
|
|
14
14
|
from spmd_types import _dist
|
|
@@ -18,13 +18,20 @@ from spmd_types._dtype_utils import (
|
|
|
18
18
|
_process_dtype_options,
|
|
19
19
|
)
|
|
20
20
|
from spmd_types._local import convert, reinterpret
|
|
21
|
+
from spmd_types._mesh import _pg_for_axis
|
|
21
22
|
from spmd_types._traceback import api_boundary
|
|
22
|
-
from spmd_types.types import
|
|
23
|
+
from spmd_types.types import (
|
|
24
|
+
_canonicalize_shard,
|
|
25
|
+
DeviceMeshAxis,
|
|
26
|
+
I,
|
|
27
|
+
P,
|
|
28
|
+
PerMeshAxisSpmdType,
|
|
29
|
+
R,
|
|
30
|
+
Shard,
|
|
31
|
+
V,
|
|
32
|
+
)
|
|
23
33
|
from torch.overrides import handle_torch_function, has_torch_function_unary
|
|
24
34
|
|
|
25
|
-
if TYPE_CHECKING:
|
|
26
|
-
from torch.distributed import ProcessGroup
|
|
27
|
-
|
|
28
35
|
# =============================================================================
|
|
29
36
|
# all_reduce: P -> R | I
|
|
30
37
|
# =============================================================================
|
|
@@ -93,7 +100,7 @@ class _AllReduce(torch.autograd.Function):
|
|
|
93
100
|
@api_boundary
|
|
94
101
|
def all_reduce(
|
|
95
102
|
x,
|
|
96
|
-
axis:
|
|
103
|
+
axis: DeviceMeshAxis,
|
|
97
104
|
*,
|
|
98
105
|
src: PerMeshAxisSpmdType = P,
|
|
99
106
|
dst: PerMeshAxisSpmdType,
|
|
@@ -169,12 +176,13 @@ def all_reduce(
|
|
|
169
176
|
common case), this role is filled by ``reduce_scatter(): P->V``
|
|
170
177
|
instead.
|
|
171
178
|
"""
|
|
179
|
+
pg = _pg_for_axis(axis)
|
|
172
180
|
if has_torch_function_unary(x):
|
|
173
181
|
return handle_torch_function(
|
|
174
182
|
all_reduce,
|
|
175
183
|
(x,),
|
|
176
184
|
x,
|
|
177
|
-
|
|
185
|
+
pg,
|
|
178
186
|
src=src,
|
|
179
187
|
dst=dst,
|
|
180
188
|
inplace=inplace,
|
|
@@ -184,7 +192,7 @@ def all_reduce(
|
|
|
184
192
|
)
|
|
185
193
|
if src is not P:
|
|
186
194
|
if src is V:
|
|
187
|
-
x = reinterpret(x,
|
|
195
|
+
x = reinterpret(x, pg, src=V, dst=P)
|
|
188
196
|
elif src is R or src is I:
|
|
189
197
|
raise ValueError(
|
|
190
198
|
f"all_reduce src must be P, got {src}. "
|
|
@@ -204,7 +212,7 @@ def all_reduce(
|
|
|
204
212
|
requires_grad=x.requires_grad,
|
|
205
213
|
inplace=inplace,
|
|
206
214
|
)
|
|
207
|
-
return _AllReduce.apply(x,
|
|
215
|
+
return _AllReduce.apply(x, pg, dst, inplace, dtype_options)
|
|
208
216
|
else:
|
|
209
217
|
raise ValueError(f"all_reduce dst must be R or I, got {dst}")
|
|
210
218
|
|
|
@@ -387,7 +395,7 @@ class _AllGatherUneven(torch.autograd.Function):
|
|
|
387
395
|
@api_boundary
|
|
388
396
|
def all_gather(
|
|
389
397
|
x,
|
|
390
|
-
axis:
|
|
398
|
+
axis: DeviceMeshAxis,
|
|
391
399
|
*,
|
|
392
400
|
src: PerMeshAxisSpmdType = V,
|
|
393
401
|
dst: PerMeshAxisSpmdType,
|
|
@@ -510,12 +518,13 @@ def all_gather(
|
|
|
510
518
|
welcome to write your own custom collective and give it the same type as
|
|
511
519
|
``spmd.all_gather``.
|
|
512
520
|
"""
|
|
521
|
+
pg = _pg_for_axis(axis)
|
|
513
522
|
if has_torch_function_unary(x):
|
|
514
523
|
return handle_torch_function(
|
|
515
524
|
all_gather,
|
|
516
525
|
(x,),
|
|
517
526
|
x,
|
|
518
|
-
|
|
527
|
+
pg,
|
|
519
528
|
src=src,
|
|
520
529
|
dst=dst,
|
|
521
530
|
gather_dim=gather_dim,
|
|
@@ -560,16 +569,16 @@ def all_gather(
|
|
|
560
569
|
if split_sizes is not None:
|
|
561
570
|
return _AllGatherUneven.apply(
|
|
562
571
|
x,
|
|
563
|
-
|
|
572
|
+
pg,
|
|
564
573
|
dst,
|
|
565
574
|
gather_dim,
|
|
566
575
|
split_sizes,
|
|
567
576
|
dtype_options,
|
|
568
577
|
)
|
|
569
578
|
elif stack:
|
|
570
|
-
return _AllGatherStack.apply(x,
|
|
579
|
+
return _AllGatherStack.apply(x, pg, dst, gather_dim, dtype_options)
|
|
571
580
|
else:
|
|
572
|
-
return _AllGatherShard.apply(x,
|
|
581
|
+
return _AllGatherShard.apply(x, pg, dst, gather_dim, dtype_options)
|
|
573
582
|
else:
|
|
574
583
|
raise ValueError(f"all_gather dst must be R or I, got {dst}")
|
|
575
584
|
|
|
@@ -698,7 +707,7 @@ class _ReduceScatterUneven(torch.autograd.Function):
|
|
|
698
707
|
@api_boundary
|
|
699
708
|
def reduce_scatter(
|
|
700
709
|
x,
|
|
701
|
-
axis:
|
|
710
|
+
axis: DeviceMeshAxis,
|
|
702
711
|
*,
|
|
703
712
|
src: PerMeshAxisSpmdType = P,
|
|
704
713
|
dst: PerMeshAxisSpmdType = V,
|
|
@@ -774,12 +783,13 @@ def reduce_scatter(
|
|
|
774
783
|
It is common to want to reduce-scatter on varying data; just
|
|
775
784
|
``reinterpret(V,P)`` the data as partial before calling ``reduce_scatter``.
|
|
776
785
|
"""
|
|
786
|
+
pg = _pg_for_axis(axis)
|
|
777
787
|
if has_torch_function_unary(x):
|
|
778
788
|
return handle_torch_function(
|
|
779
789
|
reduce_scatter,
|
|
780
790
|
(x,),
|
|
781
791
|
x,
|
|
782
|
-
|
|
792
|
+
pg,
|
|
783
793
|
src=src,
|
|
784
794
|
dst=dst,
|
|
785
795
|
scatter_dim=scatter_dim,
|
|
@@ -793,7 +803,7 @@ def reduce_scatter(
|
|
|
793
803
|
|
|
794
804
|
if src is not P:
|
|
795
805
|
if src is V:
|
|
796
|
-
x = reinterpret(x,
|
|
806
|
+
x = reinterpret(x, pg, src=V, dst=P)
|
|
797
807
|
elif src is R or src is I:
|
|
798
808
|
raise ValueError(
|
|
799
809
|
f"reduce_scatter src must be P, got {src}. "
|
|
@@ -832,17 +842,17 @@ def reduce_scatter(
|
|
|
832
842
|
)
|
|
833
843
|
|
|
834
844
|
if dst is V:
|
|
835
|
-
return _ReduceScatterStack.apply(x,
|
|
845
|
+
return _ReduceScatterStack.apply(x, pg, scatter_dim, dtype_options)
|
|
836
846
|
elif split_sizes is not None:
|
|
837
847
|
return _ReduceScatterUneven.apply(
|
|
838
848
|
x,
|
|
839
|
-
|
|
849
|
+
pg,
|
|
840
850
|
scatter_dim,
|
|
841
851
|
split_sizes,
|
|
842
852
|
dtype_options,
|
|
843
853
|
)
|
|
844
854
|
else:
|
|
845
|
-
return _ReduceScatterShard.apply(x,
|
|
855
|
+
return _ReduceScatterShard.apply(x, pg, scatter_dim, dtype_options)
|
|
846
856
|
|
|
847
857
|
|
|
848
858
|
# =============================================================================
|
|
@@ -971,7 +981,7 @@ class _AllToAllShard(torch.autograd.Function):
|
|
|
971
981
|
@api_boundary
|
|
972
982
|
def all_to_all(
|
|
973
983
|
x,
|
|
974
|
-
axis:
|
|
984
|
+
axis: DeviceMeshAxis,
|
|
975
985
|
*,
|
|
976
986
|
src: PerMeshAxisSpmdType = V,
|
|
977
987
|
dst: PerMeshAxisSpmdType = V,
|
|
@@ -1059,12 +1069,13 @@ def all_to_all(
|
|
|
1059
1069
|
``all_to_all(S(i),S(j)): S(i) -> S(j)``, the backwards is
|
|
1060
1070
|
``all_to_all(S(j),S(i)): S(j) -> S(i)``.
|
|
1061
1071
|
"""
|
|
1072
|
+
pg = _pg_for_axis(axis)
|
|
1062
1073
|
if has_torch_function_unary(x):
|
|
1063
1074
|
return handle_torch_function(
|
|
1064
1075
|
all_to_all,
|
|
1065
1076
|
(x,),
|
|
1066
1077
|
x,
|
|
1067
|
-
|
|
1078
|
+
pg,
|
|
1068
1079
|
src=src,
|
|
1069
1080
|
dst=dst,
|
|
1070
1081
|
split_dim=split_dim,
|
|
@@ -1142,15 +1153,15 @@ def all_to_all(
|
|
|
1142
1153
|
)
|
|
1143
1154
|
return _AllToAllUneven.apply(
|
|
1144
1155
|
x,
|
|
1145
|
-
|
|
1156
|
+
pg,
|
|
1146
1157
|
output_split_sizes,
|
|
1147
1158
|
input_split_sizes,
|
|
1148
1159
|
dtype_options,
|
|
1149
1160
|
)
|
|
1150
1161
|
elif src is V and dst is V:
|
|
1151
|
-
return _AllToAllStack.apply(x,
|
|
1162
|
+
return _AllToAllStack.apply(x, pg, split_dim, concat_dim, dtype_options)
|
|
1152
1163
|
else:
|
|
1153
|
-
return _AllToAllShard.apply(x,
|
|
1164
|
+
return _AllToAllShard.apply(x, pg, split_dim, concat_dim, dtype_options)
|
|
1154
1165
|
|
|
1155
1166
|
|
|
1156
1167
|
# =============================================================================
|
|
@@ -1161,7 +1172,7 @@ def all_to_all(
|
|
|
1161
1172
|
@api_boundary
|
|
1162
1173
|
def redistribute( # noqa: C901
|
|
1163
1174
|
x,
|
|
1164
|
-
axis:
|
|
1175
|
+
axis: DeviceMeshAxis,
|
|
1165
1176
|
*,
|
|
1166
1177
|
src: PerMeshAxisSpmdType,
|
|
1167
1178
|
dst: PerMeshAxisSpmdType,
|
|
@@ -1383,7 +1394,7 @@ def redistribute( # noqa: C901
|
|
|
1383
1394
|
|
|
1384
1395
|
def unshard(
|
|
1385
1396
|
x,
|
|
1386
|
-
axis:
|
|
1397
|
+
axis: DeviceMeshAxis,
|
|
1387
1398
|
*,
|
|
1388
1399
|
src: PerMeshAxisSpmdType,
|
|
1389
1400
|
dst: PerMeshAxisSpmdType,
|