spmd_types 0.2.2__tar.gz → 0.2.3__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.2/spmd_types.egg-info → spmd_types-0.2.3}/PKG-INFO +1 -2
- {spmd_types-0.2.2 → spmd_types-0.2.3}/pyproject.toml +1 -2
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/__init__.py +3 -1
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/_checker/__init__.py +61 -14
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/_mesh.py +95 -10
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/_state.py +24 -14
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/checker.py +2 -2
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/runtime.py +147 -39
- {spmd_types-0.2.2 → spmd_types-0.2.3/spmd_types.egg-info}/PKG-INFO +1 -2
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types.egg-info/SOURCES.txt +0 -1
- spmd_types-0.2.2/spmd_types.egg-info/requires.txt +0 -1
- {spmd_types-0.2.2 → spmd_types-0.2.3}/LICENSE +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/README.md +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/setup.cfg +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/_backward_hooks.py +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/_collectives.py +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/_dist.py +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/_dtensor.py +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/_dtensor_checker.py +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/_dtype_utils.py +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/_frame.py +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/_local.py +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/_mesh_axis.py +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/_mesh_region.py +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/_raw_dist.py +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/_reinterpret_mesh.py +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/_scalar.py +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/_scalar_sentinel.py +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/_test_utils.py +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/_testing.py +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/_traceback.py +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/_type_attr.py +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/_vmap.py +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types/types.py +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/spmd_types.egg-info/dependency_links.txt +0 -0
- {spmd_types-0.2.2 → spmd_types-0.2.3}/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.3
|
|
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
|
|
@@ -47,7 +47,6 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
|
47
47
|
Requires-Python: >=3.10
|
|
48
48
|
Description-Content-Type: text/markdown
|
|
49
49
|
License-File: LICENSE
|
|
50
|
-
Requires-Dist: torch>=2.10.0
|
|
51
50
|
Dynamic: license-file
|
|
52
51
|
|
|
53
52
|
# `spmd_types`
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "spmd_types"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.3"
|
|
8
8
|
description = "A type system for distributed (SPMD) tensor computations in PyTorch"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {file = "LICENSE"}
|
|
@@ -24,7 +24,6 @@ classifiers = [
|
|
|
24
24
|
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
25
25
|
]
|
|
26
26
|
dependencies = [
|
|
27
|
-
"torch>=2.10.0",
|
|
28
27
|
]
|
|
29
28
|
|
|
30
29
|
[project.urls]
|
|
@@ -41,7 +41,6 @@ from spmd_types._state import ( # noqa: F401
|
|
|
41
41
|
current_mesh_all_names,
|
|
42
42
|
current_mesh_names,
|
|
43
43
|
is_type_checking,
|
|
44
|
-
no_typecheck,
|
|
45
44
|
)
|
|
46
45
|
from spmd_types._traceback import traceback_filtering # noqa: F401
|
|
47
46
|
from spmd_types._type_attr import ( # noqa: F401
|
|
@@ -56,10 +55,13 @@ from spmd_types.runtime import ( # noqa: F401
|
|
|
56
55
|
assert_local_type_like,
|
|
57
56
|
assert_type,
|
|
58
57
|
assert_type_like,
|
|
58
|
+
get_partition_spec,
|
|
59
|
+
has_local_type,
|
|
59
60
|
Infer,
|
|
60
61
|
local,
|
|
61
62
|
local_map,
|
|
62
63
|
mutate_type,
|
|
64
|
+
no_typecheck,
|
|
63
65
|
register_autograd_function,
|
|
64
66
|
register_decomposition,
|
|
65
67
|
register_local_autograd_function,
|
|
@@ -68,6 +68,7 @@ from spmd_types.runtime import ( # noqa: F401
|
|
|
68
68
|
get_partition_spec,
|
|
69
69
|
has_local_type,
|
|
70
70
|
mutate_type,
|
|
71
|
+
no_typecheck,
|
|
71
72
|
register_autograd_function,
|
|
72
73
|
register_decomposition,
|
|
73
74
|
register_local_autograd_function,
|
|
@@ -2531,6 +2532,8 @@ def _get_autograd_function_class(func) -> type | None:
|
|
|
2531
2532
|
def _validate_partition_spec_for_global_spmd(
|
|
2532
2533
|
local_type: LocalSpmdType,
|
|
2533
2534
|
spec: PartitionSpec | None,
|
|
2535
|
+
*,
|
|
2536
|
+
is_global_axis: Callable[[MeshAxis], bool],
|
|
2534
2537
|
) -> None:
|
|
2535
2538
|
"""Validate PartitionSpec <-> local type consistency.
|
|
2536
2539
|
|
|
@@ -2543,10 +2546,12 @@ def _validate_partition_spec_for_global_spmd(
|
|
|
2543
2546
|
Args:
|
|
2544
2547
|
local_type: The tensor's local SPMD type dict.
|
|
2545
2548
|
spec: The tensor's PartitionSpec (None if absent).
|
|
2549
|
+
is_global_axis: Returns whether an axis requires global PartitionSpec
|
|
2550
|
+
validation in the current mesh context.
|
|
2546
2551
|
"""
|
|
2547
2552
|
spec_axes = spec.axes_with_partition_spec() if spec is not None else set()
|
|
2548
2553
|
for axis, typ in local_type.items():
|
|
2549
|
-
if typ is V and axis not in spec_axes:
|
|
2554
|
+
if is_global_axis(axis) and typ is V and axis not in spec_axes:
|
|
2550
2555
|
ax = format_axis(axis)
|
|
2551
2556
|
raise SpmdTypeError(
|
|
2552
2557
|
f"Tensor has Varying type on axis {ax} but no PartitionSpec entry. "
|
|
@@ -2555,7 +2560,7 @@ def _validate_partition_spec_for_global_spmd(
|
|
|
2555
2560
|
)
|
|
2556
2561
|
for axis in spec_axes:
|
|
2557
2562
|
typ = local_type.get(axis)
|
|
2558
|
-
if typ is not V:
|
|
2563
|
+
if is_global_axis(axis) and typ is not V:
|
|
2559
2564
|
ax = format_axis(axis)
|
|
2560
2565
|
raise SpmdTypeError(
|
|
2561
2566
|
f"Tensor has PartitionSpec on axis {ax} but local type is "
|
|
@@ -2623,6 +2628,23 @@ class _SpmdTypeMode(torch.overrides.TorchFunctionMode):
|
|
|
2623
2628
|
|
|
2624
2629
|
self._disabled = False
|
|
2625
2630
|
|
|
2631
|
+
def _is_global_axis(self, axis: MeshAxis) -> bool:
|
|
2632
|
+
if self._local:
|
|
2633
|
+
return False
|
|
2634
|
+
local_axes = _state._current_local_axes()
|
|
2635
|
+
if not local_axes:
|
|
2636
|
+
return True
|
|
2637
|
+
mesh = _state.current_mesh()
|
|
2638
|
+
if mesh is None or axis not in mesh:
|
|
2639
|
+
raise SpmdTypeError(
|
|
2640
|
+
f"Axis {format_axis(axis)} is not in the current mesh, so its "
|
|
2641
|
+
"local/global typechecking policy is undefined"
|
|
2642
|
+
)
|
|
2643
|
+
return axis not in local_axes
|
|
2644
|
+
|
|
2645
|
+
def _global_axes_for(self, axes: Iterable[MeshAxis]) -> set[MeshAxis]:
|
|
2646
|
+
return {axis for axis in axes if self._is_global_axis(axis)}
|
|
2647
|
+
|
|
2626
2648
|
def __torch_function__(self, func, types, args=(), kwargs=None): # noqa: C901
|
|
2627
2649
|
kwargs = kwargs or {}
|
|
2628
2650
|
# Paused via no_typecheck(): run without type checking.
|
|
@@ -2679,7 +2701,11 @@ class _SpmdTypeMode(torch.overrides.TorchFunctionMode):
|
|
|
2679
2701
|
# Global SPMD validation.
|
|
2680
2702
|
if not self._local:
|
|
2681
2703
|
for local_type, spec in zip(info.tensor_types, info.partition_specs):
|
|
2682
|
-
_validate_partition_spec_for_global_spmd(
|
|
2704
|
+
_validate_partition_spec_for_global_spmd(
|
|
2705
|
+
local_type,
|
|
2706
|
+
spec,
|
|
2707
|
+
is_global_axis=self._is_global_axis,
|
|
2708
|
+
)
|
|
2683
2709
|
|
|
2684
2710
|
# Typecheck-registered autograd function: dispatch to typecheck_forward
|
|
2685
2711
|
# INSTEAD of executing func. The mode is popped off the
|
|
@@ -2744,6 +2770,29 @@ class _SpmdTypeMode(torch.overrides.TorchFunctionMode):
|
|
|
2744
2770
|
# Resolve axis to a list of matching axes on the tensor.
|
|
2745
2771
|
# Direct match: [axis]. Flattened decomposition: [sub1, sub2, ...].
|
|
2746
2772
|
resolved_axes = _resolve_collective_axes(axis, local_type)
|
|
2773
|
+
collective_axes = resolved_axes or [axis]
|
|
2774
|
+
global_collective_axes = [
|
|
2775
|
+
ax for ax in collective_axes if self._is_global_axis(ax)
|
|
2776
|
+
]
|
|
2777
|
+
if global_collective_axes and len(global_collective_axes) != len(
|
|
2778
|
+
collective_axes
|
|
2779
|
+
):
|
|
2780
|
+
global_axis_set = set(global_collective_axes)
|
|
2781
|
+
local_collective_axes = [
|
|
2782
|
+
ax for ax in collective_axes if ax not in global_axis_set
|
|
2783
|
+
]
|
|
2784
|
+
local_names = ", ".join(
|
|
2785
|
+
format_axis(ax) for ax in local_collective_axes
|
|
2786
|
+
)
|
|
2787
|
+
global_names = ", ".join(
|
|
2788
|
+
format_axis(ax) for ax in global_collective_axes
|
|
2789
|
+
)
|
|
2790
|
+
raise SpmdTypeError(
|
|
2791
|
+
f"Collective axis {format_axis(axis)} spans mesh axes with "
|
|
2792
|
+
f"mixed typechecking policies: local axes ({local_names}) "
|
|
2793
|
+
f"and global axes ({global_names}). Pass a collective axis "
|
|
2794
|
+
f"whose component axes are all local or all global."
|
|
2795
|
+
)
|
|
2747
2796
|
|
|
2748
2797
|
if resolved_axes is not None:
|
|
2749
2798
|
# Validate input types on all resolved axes
|
|
@@ -2774,7 +2823,7 @@ class _SpmdTypeMode(torch.overrides.TorchFunctionMode):
|
|
|
2774
2823
|
output_type[ax] = local_dst
|
|
2775
2824
|
_set_local_type(result, output_type)
|
|
2776
2825
|
|
|
2777
|
-
if
|
|
2826
|
+
if global_collective_axes:
|
|
2778
2827
|
input_spec = get_partition_spec(x)
|
|
2779
2828
|
# Find S(dim) for this axis in the input spec, validating
|
|
2780
2829
|
# it is innermost in its multi-axis group.
|
|
@@ -2916,15 +2965,16 @@ class _SpmdTypeMode(torch.overrides.TorchFunctionMode):
|
|
|
2916
2965
|
)
|
|
2917
2966
|
|
|
2918
2967
|
# Stage 3: global shard propagation.
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
all_axes.update(typ.keys())
|
|
2968
|
+
all_axes: set[MeshAxis] = set()
|
|
2969
|
+
for typ in input_types_list:
|
|
2970
|
+
all_axes.update(typ.keys())
|
|
2971
|
+
global_axes = self._global_axes_for(all_axes)
|
|
2972
|
+
if global_axes:
|
|
2925
2973
|
global_shard_axes, shard_edges = _collect_shard_axes(
|
|
2926
|
-
partition_specs,
|
|
2974
|
+
partition_specs, global_axes
|
|
2927
2975
|
)
|
|
2976
|
+
else:
|
|
2977
|
+
global_shard_axes, shard_edges = [], {}
|
|
2928
2978
|
|
|
2929
2979
|
# Validate mutation safety for in-place/out operations.
|
|
2930
2980
|
mutated = _get_mutated_tensors(func, args, kwargs, result)
|
|
@@ -3086,9 +3136,6 @@ def typecheck(
|
|
|
3086
3136
|
_remove_autograd_apply_patch()
|
|
3087
3137
|
|
|
3088
3138
|
|
|
3089
|
-
from spmd_types._state import no_typecheck # noqa: F401
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
3139
|
class _SpmdTypeBackwardCompatibleMode:
|
|
3093
3140
|
"""Backwards-compatible wrapper around ``typecheck()``.
|
|
3094
3141
|
|
|
@@ -13,8 +13,8 @@ from contextlib import contextmanager
|
|
|
13
13
|
from typing import TYPE_CHECKING
|
|
14
14
|
|
|
15
15
|
from spmd_types._mesh_axis import MeshAxis
|
|
16
|
-
from spmd_types._state import _axes_to_pgs, _pop_mesh, _push_mesh
|
|
17
|
-
from spmd_types.types import DeviceMeshAxis, normalize_mesh
|
|
16
|
+
from spmd_types._state import _axes_to_pgs, _current_mesh_entry, _pop_mesh, _push_mesh
|
|
17
|
+
from spmd_types.types import DeviceMeshAxis, format_axis, normalize_axis, normalize_mesh
|
|
18
18
|
from torch.distributed.device_mesh import DeviceMesh
|
|
19
19
|
|
|
20
20
|
if TYPE_CHECKING:
|
|
@@ -24,8 +24,14 @@ if TYPE_CHECKING:
|
|
|
24
24
|
@contextmanager
|
|
25
25
|
def set_current_mesh(
|
|
26
26
|
axes: (
|
|
27
|
-
frozenset[MeshAxis]
|
|
28
|
-
|
|
27
|
+
frozenset[MeshAxis]
|
|
28
|
+
| DeviceMesh
|
|
29
|
+
| Sequence[ProcessGroup]
|
|
30
|
+
| dict[str, MeshAxis]
|
|
31
|
+
| None
|
|
32
|
+
) = None,
|
|
33
|
+
*,
|
|
34
|
+
local_axes: tuple[DeviceMeshAxis, ...] = (),
|
|
29
35
|
):
|
|
30
36
|
"""Context manager that pushes a mesh onto the stack.
|
|
31
37
|
|
|
@@ -37,19 +43,99 @@ def set_current_mesh(
|
|
|
37
43
|
- A frozenset of orthogonal MeshAxis objects (no string lookup).
|
|
38
44
|
- A sequence of ProcessGroup objects, each converted via
|
|
39
45
|
``MeshAxis.of()`` (no string lookup).
|
|
46
|
+
When omitted, reuses the current mesh.
|
|
47
|
+
local_axes: An outer prefix of mesh axes that retains local SPMD
|
|
48
|
+
semantics during global type checking. Each coordinate on these
|
|
49
|
+
axes selects an independent local tensor; the remaining inner axes
|
|
50
|
+
describe the global sharding of that tensor. For example, with
|
|
51
|
+
``dp`` local and ``tp`` global, each ``dp`` coordinate has a
|
|
52
|
+
separate tensor globally sharded across ``tp``.
|
|
40
53
|
|
|
41
54
|
Singleton (size-1) axes are dropped from the active axis set via
|
|
42
55
|
``normalize_mesh`` but remain in the name lookup table
|
|
43
56
|
(``current_mesh_all_names``).
|
|
44
57
|
"""
|
|
45
|
-
|
|
46
|
-
|
|
58
|
+
if axes is None:
|
|
59
|
+
entry = _current_mesh_entry()
|
|
60
|
+
if entry is None:
|
|
61
|
+
raise RuntimeError(
|
|
62
|
+
"set_current_mesh() without axes requires a current mesh"
|
|
63
|
+
)
|
|
64
|
+
resolved, names, pgs = entry.axes, entry.all_names, entry.pgs
|
|
65
|
+
else:
|
|
66
|
+
resolved, names = _resolve_axes(axes)
|
|
67
|
+
pgs = _axes_to_pgs(axes)
|
|
68
|
+
resolved_local_axes = _resolve_local_axes(local_axes, resolved, names)
|
|
69
|
+
_push_mesh(
|
|
70
|
+
resolved,
|
|
71
|
+
names,
|
|
72
|
+
pgs,
|
|
73
|
+
local_axes=resolved_local_axes,
|
|
74
|
+
)
|
|
47
75
|
try:
|
|
48
76
|
yield
|
|
49
77
|
finally:
|
|
50
78
|
_pop_mesh()
|
|
51
79
|
|
|
52
80
|
|
|
81
|
+
def _resolve_local_axes(
|
|
82
|
+
local_axes: tuple[DeviceMeshAxis, ...],
|
|
83
|
+
mesh: frozenset[MeshAxis],
|
|
84
|
+
names: dict[str, MeshAxis],
|
|
85
|
+
) -> frozenset[MeshAxis]:
|
|
86
|
+
"""Resolve ``local_axes`` to members of ``mesh``.
|
|
87
|
+
|
|
88
|
+
Rejects unknown or duplicate axes and requires the local axes to be the
|
|
89
|
+
outermost axes and the global axes to be the innermost axes. For a
|
|
90
|
+
``(dp, tp)`` mesh, ``("dp",)`` is valid but ``("tp",)`` is not.
|
|
91
|
+
|
|
92
|
+
Returns the resolved local axes as a ``frozenset[MeshAxis]``.
|
|
93
|
+
"""
|
|
94
|
+
unknown = tuple(
|
|
95
|
+
axis for axis in local_axes if isinstance(axis, str) and axis not in names
|
|
96
|
+
)
|
|
97
|
+
if unknown:
|
|
98
|
+
raise ValueError(
|
|
99
|
+
f"local_axes contains names not present in this mesh: {unknown!r}; "
|
|
100
|
+
f"available names: {tuple(sorted(names))!r}"
|
|
101
|
+
)
|
|
102
|
+
resolved_list = tuple(
|
|
103
|
+
names[axis] if isinstance(axis, str) else normalize_axis(axis)
|
|
104
|
+
for axis in local_axes
|
|
105
|
+
)
|
|
106
|
+
if len(set(resolved_list)) != len(resolved_list):
|
|
107
|
+
duplicates = {axis for axis in resolved_list if resolved_list.count(axis) > 1}
|
|
108
|
+
duplicate_names = ", ".join(format_axis(axis) for axis in duplicates)
|
|
109
|
+
raise ValueError(
|
|
110
|
+
f"local_axes must not contain duplicate axes; got {local_axes!r}; "
|
|
111
|
+
f"duplicate axes: ({duplicate_names})"
|
|
112
|
+
)
|
|
113
|
+
resolved = frozenset(resolved_list)
|
|
114
|
+
if not resolved <= mesh:
|
|
115
|
+
outside = resolved - mesh
|
|
116
|
+
outside_names = ", ".join(format_axis(axis) for axis in outside)
|
|
117
|
+
mesh_names = ", ".join(format_axis(axis) for axis in sorted(mesh, key=repr))
|
|
118
|
+
raise ValueError(
|
|
119
|
+
f"local_axes contains axes outside this mesh: ({outside_names}); "
|
|
120
|
+
f"current mesh axes: ({mesh_names})"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
ordered = sorted(
|
|
124
|
+
mesh,
|
|
125
|
+
key=lambda axis: max(stride for _, stride in axis.layout.sizes_and_strides),
|
|
126
|
+
reverse=True,
|
|
127
|
+
)
|
|
128
|
+
prefix = set(ordered[: len(resolved)])
|
|
129
|
+
if set(resolved) != prefix:
|
|
130
|
+
local_names = ", ".join(format_axis(axis) for axis in resolved)
|
|
131
|
+
mesh_names = ", ".join(format_axis(axis) for axis in ordered)
|
|
132
|
+
raise ValueError(
|
|
133
|
+
"local_axes must come before all global axes in outer-to-inner mesh "
|
|
134
|
+
f"order; got local_axes=({local_names}) for mesh=({mesh_names})"
|
|
135
|
+
)
|
|
136
|
+
return resolved
|
|
137
|
+
|
|
138
|
+
|
|
53
139
|
def _pg_for_axis(axis: DeviceMeshAxis) -> ProcessGroup:
|
|
54
140
|
"""Resolve a mesh axis to its ``ProcessGroup`` using the current mesh.
|
|
55
141
|
|
|
@@ -57,14 +143,13 @@ def _pg_for_axis(axis: DeviceMeshAxis) -> ProcessGroup:
|
|
|
57
143
|
``MeshAxis``. Raises ``RuntimeError`` if no current mesh carries process
|
|
58
144
|
groups, or if the axis is not part of it.
|
|
59
145
|
"""
|
|
60
|
-
from spmd_types._state import _tls, current_mesh_all_names
|
|
61
146
|
from torch.distributed import ProcessGroup
|
|
62
147
|
|
|
63
148
|
if isinstance(axis, ProcessGroup):
|
|
64
149
|
return axis
|
|
65
150
|
|
|
66
|
-
|
|
67
|
-
pgs =
|
|
151
|
+
entry = _current_mesh_entry()
|
|
152
|
+
pgs = entry.pgs if entry is not None else None
|
|
68
153
|
if not pgs:
|
|
69
154
|
raise RuntimeError(
|
|
70
155
|
"Passing an axis name to a redistribution or collective "
|
|
@@ -73,7 +158,7 @@ def _pg_for_axis(axis: DeviceMeshAxis) -> ProcessGroup:
|
|
|
73
158
|
)
|
|
74
159
|
|
|
75
160
|
if isinstance(axis, str):
|
|
76
|
-
resolved =
|
|
161
|
+
resolved = entry.all_names.get(axis) if entry is not None else None
|
|
77
162
|
else:
|
|
78
163
|
resolved = axis
|
|
79
164
|
if resolved is None or resolved not in pgs:
|
|
@@ -30,6 +30,7 @@ if TYPE_CHECKING:
|
|
|
30
30
|
|
|
31
31
|
class MeshEntry(NamedTuple):
|
|
32
32
|
axes: frozenset[MeshAxis]
|
|
33
|
+
local_axes: frozenset[MeshAxis]
|
|
33
34
|
names: dict[str, MeshAxis]
|
|
34
35
|
all_names: dict[str, MeshAxis]
|
|
35
36
|
pgs: dict[MeshAxis, ProcessGroup]
|
|
@@ -45,7 +46,7 @@ def is_type_checking() -> bool:
|
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
@contextmanager
|
|
48
|
-
def
|
|
49
|
+
def _no_typecheck_context():
|
|
49
50
|
"""Temporarily disable type checking on this thread (like ``no_grad``)."""
|
|
50
51
|
mode = _current_mode()
|
|
51
52
|
if mode is not None:
|
|
@@ -75,12 +76,16 @@ def is_strict() -> bool:
|
|
|
75
76
|
return mode is not None and mode._strict
|
|
76
77
|
|
|
77
78
|
|
|
79
|
+
def _current_mesh_entry() -> MeshEntry | None:
|
|
80
|
+
"""Return the active mesh-stack entry, or None if no mesh is set."""
|
|
81
|
+
stack: list[MeshEntry] = getattr(_tls, "mesh_stack", [])
|
|
82
|
+
return stack[-1] if stack else None
|
|
83
|
+
|
|
84
|
+
|
|
78
85
|
def current_mesh() -> frozenset[MeshAxis] | None:
|
|
79
86
|
"""Return the current mesh axes, or None if no mesh is set."""
|
|
80
|
-
|
|
81
|
-
if
|
|
82
|
-
return stack[-1].axes
|
|
83
|
-
return None
|
|
87
|
+
entry = _current_mesh_entry()
|
|
88
|
+
return entry.axes if entry is not None else None
|
|
84
89
|
|
|
85
90
|
|
|
86
91
|
def current_mesh_names() -> dict[str, MeshAxis] | None:
|
|
@@ -89,18 +94,14 @@ def current_mesh_names() -> dict[str, MeshAxis] | None:
|
|
|
89
94
|
Only includes non-singleton axes, consistent with ``current_mesh()``.
|
|
90
95
|
Use ``current_mesh_all_names()`` to include singleton axes.
|
|
91
96
|
"""
|
|
92
|
-
|
|
93
|
-
if
|
|
94
|
-
return stack[-1].names
|
|
95
|
-
return None
|
|
97
|
+
entry = _current_mesh_entry()
|
|
98
|
+
return entry.names if entry is not None else None
|
|
96
99
|
|
|
97
100
|
|
|
98
101
|
def current_mesh_all_names() -> dict[str, MeshAxis] | None:
|
|
99
102
|
"""Return the full name-to-axis mapping including singleton axes, or None."""
|
|
100
|
-
|
|
101
|
-
if
|
|
102
|
-
return stack[-1].all_names
|
|
103
|
-
return None
|
|
103
|
+
entry = _current_mesh_entry()
|
|
104
|
+
return entry.all_names if entry is not None else None
|
|
104
105
|
|
|
105
106
|
|
|
106
107
|
def _axes_to_pgs(
|
|
@@ -133,6 +134,7 @@ def _push_mesh(
|
|
|
133
134
|
| dict[str, MeshAxis],
|
|
134
135
|
names: dict[str, MeshAxis] | None = None,
|
|
135
136
|
pgs: dict[MeshAxis, ProcessGroup] | None = None,
|
|
137
|
+
local_axes: frozenset[MeshAxis] = frozenset(),
|
|
136
138
|
) -> None:
|
|
137
139
|
if isinstance(mesh, frozenset):
|
|
138
140
|
resolved, resolved_names = mesh, names if names is not None else {}
|
|
@@ -149,7 +151,9 @@ def _push_mesh(
|
|
|
149
151
|
filtered_names = {k: v for k, v in resolved_names.items() if v in resolved}
|
|
150
152
|
if not hasattr(_tls, "mesh_stack"):
|
|
151
153
|
_tls.mesh_stack = []
|
|
152
|
-
_tls.mesh_stack.append(
|
|
154
|
+
_tls.mesh_stack.append(
|
|
155
|
+
MeshEntry(resolved, local_axes, filtered_names, all_names, resolved_pgs)
|
|
156
|
+
)
|
|
153
157
|
|
|
154
158
|
|
|
155
159
|
def _pop_mesh() -> MeshEntry:
|
|
@@ -166,6 +170,12 @@ def _clear_mesh_stack() -> None:
|
|
|
166
170
|
_tls.mesh_stack = []
|
|
167
171
|
|
|
168
172
|
|
|
173
|
+
def _current_local_axes() -> frozenset[MeshAxis]:
|
|
174
|
+
"""Return the local axes for the active mesh, or an empty set."""
|
|
175
|
+
entry = _current_mesh_entry()
|
|
176
|
+
return entry.local_axes if entry is not None else frozenset()
|
|
177
|
+
|
|
178
|
+
|
|
169
179
|
def _is_global() -> bool:
|
|
170
180
|
"""Return True if global SPMD mode is active (local=False)."""
|
|
171
181
|
mode = _current_mode()
|
|
@@ -9,5 +9,5 @@ from spmd_types._checker import ( # noqa: F401
|
|
|
9
9
|
_SpmdTypeBackwardCompatibleMode as SpmdTypeMode,
|
|
10
10
|
typecheck,
|
|
11
11
|
)
|
|
12
|
-
from spmd_types._state import is_type_checking
|
|
13
|
-
from spmd_types.runtime import local_map # noqa: F401
|
|
12
|
+
from spmd_types._state import is_type_checking # noqa: F401
|
|
13
|
+
from spmd_types.runtime import local_map, no_typecheck # noqa: F401
|
|
@@ -27,14 +27,14 @@ from __future__ import annotations
|
|
|
27
27
|
import builtins
|
|
28
28
|
import logging
|
|
29
29
|
import os
|
|
30
|
-
from contextlib import contextmanager
|
|
31
|
-
from typing import Any, TypeAlias
|
|
30
|
+
from contextlib import AbstractContextManager, contextmanager
|
|
31
|
+
from typing import Any, Callable, overload, TypeAlias
|
|
32
32
|
|
|
33
33
|
import torch
|
|
34
34
|
from spmd_types._frame import _get_user_frame
|
|
35
35
|
from spmd_types._mesh_axis import MeshAxis
|
|
36
36
|
from spmd_types._scalar_sentinel import _Scalar
|
|
37
|
-
from spmd_types._state import current_mesh, is_type_checking
|
|
37
|
+
from spmd_types._state import _no_typecheck_context, current_mesh, is_type_checking
|
|
38
38
|
from spmd_types._traceback import api_boundary
|
|
39
39
|
from spmd_types._type_attr import (
|
|
40
40
|
_LOCAL_TYPE_ATTR,
|
|
@@ -589,20 +589,49 @@ def assert_local_type_like(
|
|
|
589
589
|
# =============================================================================
|
|
590
590
|
|
|
591
591
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
tensor: torch.Tensor,
|
|
592
|
+
def _mutate_type_single_axis(
|
|
593
|
+
local_type: LocalSpmdType,
|
|
595
594
|
axis: DeviceMeshAxis,
|
|
596
595
|
*,
|
|
597
596
|
src: PerMeshAxisSpmdType,
|
|
598
597
|
dst: PerMeshAxisSpmdType,
|
|
598
|
+
) -> LocalSpmdType:
|
|
599
|
+
axis = normalize_axis(axis)
|
|
600
|
+
if axis.size() == 1:
|
|
601
|
+
return local_type
|
|
602
|
+
if axis not in local_type:
|
|
603
|
+
raise SpmdTypeError(
|
|
604
|
+
f"mutate_type: axis {format_axis(axis)} not found in tensor's "
|
|
605
|
+
f"SPMD type. Tensor has axes: {set(local_type.keys())}"
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
src_local = to_local_type(src)
|
|
609
|
+
current = local_type[axis]
|
|
610
|
+
if current is not src_local:
|
|
611
|
+
raise SpmdTypeError(
|
|
612
|
+
f"mutate_type: expected current type {src_local} on axis "
|
|
613
|
+
f"{format_axis(axis)}, got {current}"
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
new_type = dict(local_type)
|
|
617
|
+
new_type[axis] = to_local_type(dst)
|
|
618
|
+
return new_type
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
@api_boundary
|
|
622
|
+
def mutate_type(
|
|
623
|
+
tensor: torch.Tensor,
|
|
624
|
+
axis: DeviceMeshAxis | None = None,
|
|
625
|
+
*,
|
|
626
|
+
src: PerMeshAxisSpmdType | dict[DeviceMeshAxis, PerMeshAxisSpmdType],
|
|
627
|
+
dst: PerMeshAxisSpmdType | dict[DeviceMeshAxis, PerMeshAxisSpmdType],
|
|
599
628
|
) -> torch.Tensor:
|
|
600
|
-
"""Change the SPMD type of
|
|
629
|
+
"""Change the SPMD type of mesh axes on an already-annotated tensor.
|
|
601
630
|
|
|
602
631
|
Unlike ``assert_type``, this function *overwrites* the existing type on
|
|
603
|
-
|
|
604
|
-
prevent silent corruption; a ``SpmdTypeError`` is raised if
|
|
605
|
-
current type
|
|
632
|
+
the updated axes. The caller must specify the expected current type
|
|
633
|
+
(``src``) to prevent silent corruption; a ``SpmdTypeError`` is raised if
|
|
634
|
+
an updated axis's current type does not match its expected ``src``.
|
|
606
635
|
|
|
607
636
|
This is intended for internal use in low-level parallelism primitives
|
|
608
637
|
where a buffer legitimately changes its distribution semantics in place
|
|
@@ -613,45 +642,88 @@ def mutate_type(
|
|
|
613
642
|
*views* into a buffer after an in-place collective has changed the
|
|
614
643
|
buffer's semantics.
|
|
615
644
|
|
|
645
|
+
There are four supported call shapes:
|
|
646
|
+
|
|
647
|
+
- ``mutate_type(tensor, axis, src=R, dst=I)`` updates a single axis.
|
|
648
|
+
- ``mutate_type(tensor, src=R, dst=I)`` updates all axes annotated on the
|
|
649
|
+
tensor, assuming all axes share ``src`` type.
|
|
650
|
+
- ``mutate_type(tensor, src=R, dst={axis0: I, axis1: P})`` updates every
|
|
651
|
+
axis in ``dst``, after checking all update axes have ``src`` type.
|
|
652
|
+
Axes not listed in ``dst`` are unaffected.
|
|
653
|
+
- ``mutate_type(tensor, src={axis0: R, axis1: I}, dst=P)`` is similarly supported.
|
|
654
|
+
- ``mutate_type(tensor, src={axis0: R, axis1: R}, dst={axis0: I, axis1: I})``
|
|
655
|
+
updates each axis correspondingly. ``src`` and ``dst`` keys must match,
|
|
656
|
+
and other axes are unaffected.
|
|
657
|
+
|
|
658
|
+
``mutate_type`` errors if any specified ``src`` type doesn't match the tensor's
|
|
659
|
+
annotation.
|
|
660
|
+
|
|
616
661
|
Args:
|
|
617
662
|
tensor: The tensor whose type to mutate. Must already have a local
|
|
618
663
|
type annotation.
|
|
619
|
-
axis: The mesh axis (MeshAxis or ProcessGroup) to modify
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
(S(i) is
|
|
664
|
+
axis: The mesh axis (MeshAxis or ProcessGroup) to modify for the
|
|
665
|
+
single-axis form. Omit when ``src`` or ``dst`` is a dict mapping
|
|
666
|
+
axes to types.
|
|
667
|
+
src: The expected current type, or a dict of per-axis expected current
|
|
668
|
+
types. Accepts R, I, V, P, or S(i); S(i) is compared as V.
|
|
669
|
+
dst: The new type, or a dict of per-axis destination types. Accepts R,
|
|
670
|
+
I, V, P, or S(i); S(i) is stored as V.
|
|
624
671
|
|
|
625
672
|
Returns:
|
|
626
673
|
The tensor for chaining.
|
|
627
674
|
|
|
628
675
|
Raises:
|
|
629
|
-
SpmdTypeError: If the tensor has no type,
|
|
630
|
-
|
|
676
|
+
SpmdTypeError: If the tensor has no type, if a non-singleton updated
|
|
677
|
+
axis is missing from the tensor's local type, if a current type
|
|
678
|
+
does not match ``src``, or if per-axis ``src`` and ``dst`` dict keys
|
|
679
|
+
differ.
|
|
631
680
|
"""
|
|
632
|
-
axis = normalize_axis(axis)
|
|
633
|
-
if axis.size() == 1:
|
|
634
|
-
return tensor # singleton axes are not tracked
|
|
635
681
|
local_type = get_local_type(tensor)
|
|
636
|
-
if
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
682
|
+
if isinstance(dst, dict) and isinstance(src, dict):
|
|
683
|
+
if axis is not None:
|
|
684
|
+
raise SpmdTypeError(
|
|
685
|
+
"mutate_type() expected axis to be omitted when src or dst is a dict."
|
|
686
|
+
)
|
|
687
|
+
if set(dst) != set(src):
|
|
688
|
+
axes = ", ".join(format_axis(axis) for axis in set(dst) ^ set(src))
|
|
689
|
+
raise SpmdTypeError(
|
|
690
|
+
f"mutate_type() expected src and dst axes to match: {axes}"
|
|
691
|
+
)
|
|
692
|
+
updates = [
|
|
693
|
+
(update_axis, src[update_axis], dst_type)
|
|
694
|
+
for update_axis, dst_type in dst.items()
|
|
695
|
+
]
|
|
696
|
+
elif isinstance(dst, dict):
|
|
697
|
+
if axis is not None:
|
|
698
|
+
raise SpmdTypeError(
|
|
699
|
+
"mutate_type() expected axis to be omitted when src or dst is a dict."
|
|
700
|
+
)
|
|
701
|
+
updates = [
|
|
702
|
+
(update_axis, src, dst_type) for update_axis, dst_type in dst.items()
|
|
703
|
+
]
|
|
704
|
+
elif isinstance(src, dict):
|
|
705
|
+
if axis is not None:
|
|
706
|
+
raise SpmdTypeError(
|
|
707
|
+
"mutate_type() expected axis to be omitted when src or dst is a dict."
|
|
708
|
+
)
|
|
709
|
+
updates = [
|
|
710
|
+
(update_axis, src_type, dst) for update_axis, src_type in src.items()
|
|
711
|
+
]
|
|
712
|
+
else:
|
|
713
|
+
updates = (
|
|
714
|
+
[(axis, src, dst)]
|
|
715
|
+
if axis is not None
|
|
716
|
+
else [(update_axis, src, dst) for update_axis in local_type.keys()]
|
|
651
717
|
)
|
|
652
718
|
|
|
653
719
|
new_type = dict(local_type)
|
|
654
|
-
|
|
720
|
+
for update_axis, src_type, dst_type in updates:
|
|
721
|
+
new_type = _mutate_type_single_axis(
|
|
722
|
+
new_type,
|
|
723
|
+
update_axis,
|
|
724
|
+
src=src_type,
|
|
725
|
+
dst=dst_type,
|
|
726
|
+
)
|
|
655
727
|
return _set_local_type(tensor, new_type)
|
|
656
728
|
|
|
657
729
|
|
|
@@ -908,7 +980,32 @@ def local():
|
|
|
908
980
|
yield
|
|
909
981
|
|
|
910
982
|
|
|
911
|
-
|
|
983
|
+
@overload
|
|
984
|
+
def no_typecheck() -> AbstractContextManager[None]: ...
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
@overload
|
|
988
|
+
def no_typecheck(
|
|
989
|
+
*, out_types: Any, in_types: Any = ...
|
|
990
|
+
) -> Callable[[Callable[..., Any]], Callable[..., Any]]: ...
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+
def no_typecheck(**kwargs: Any):
|
|
994
|
+
"""Disable checking in a context or across an explicitly typed function."""
|
|
995
|
+
if not kwargs:
|
|
996
|
+
return _no_typecheck_context()
|
|
997
|
+
if "out_types" not in kwargs or kwargs.keys() - {"in_types", "out_types"}:
|
|
998
|
+
raise TypeError("out_types is required for typed no_typecheck")
|
|
999
|
+
|
|
1000
|
+
return _typecheck_boundary(
|
|
1001
|
+
name="no_typecheck",
|
|
1002
|
+
body_context=_no_typecheck_context,
|
|
1003
|
+
in_types=kwargs.get("in_types", Infer),
|
|
1004
|
+
out_types=kwargs["out_types"],
|
|
1005
|
+
)
|
|
1006
|
+
|
|
1007
|
+
|
|
1008
|
+
def local_map(
|
|
912
1009
|
*,
|
|
913
1010
|
in_types: Any = Infer,
|
|
914
1011
|
out_types: Any,
|
|
@@ -980,12 +1077,23 @@ def local_map( # noqa: C901
|
|
|
980
1077
|
def fn(x, weights):
|
|
981
1078
|
return x @ weights[0], x @ weights[1]
|
|
982
1079
|
"""
|
|
1080
|
+
return _typecheck_boundary(
|
|
1081
|
+
name="local_map",
|
|
1082
|
+
body_context=local,
|
|
1083
|
+
in_types=in_types,
|
|
1084
|
+
out_types=out_types,
|
|
1085
|
+
)
|
|
1086
|
+
|
|
1087
|
+
|
|
1088
|
+
def _typecheck_boundary( # noqa: C901
|
|
1089
|
+
*, name: str, body_context: Any, in_types: Any, out_types: Any
|
|
1090
|
+
):
|
|
983
1091
|
import functools
|
|
984
1092
|
|
|
985
1093
|
from torch.utils._pytree import tree_map
|
|
986
1094
|
|
|
987
1095
|
def decorator(fn):
|
|
988
|
-
prefix = f"
|
|
1096
|
+
prefix = f"{name}[{fn.__qualname__}]"
|
|
989
1097
|
|
|
990
1098
|
def _check_arg(arg: object, spec: _LocalMapSpecLeaf, kind: str) -> None:
|
|
991
1099
|
if spec is None:
|
|
@@ -1087,7 +1195,7 @@ def local_map( # noqa: C901
|
|
|
1087
1195
|
if in_types is not Infer:
|
|
1088
1196
|
_walk_boundary(args, in_types, "input")
|
|
1089
1197
|
|
|
1090
|
-
with
|
|
1198
|
+
with body_context():
|
|
1091
1199
|
result = fn(*args, **kwargs)
|
|
1092
1200
|
|
|
1093
1201
|
_walk_boundary(result, out_types, "output")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spmd_types
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
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
|
|
@@ -47,7 +47,6 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
|
47
47
|
Requires-Python: >=3.10
|
|
48
48
|
Description-Content-Type: text/markdown
|
|
49
49
|
License-File: LICENSE
|
|
50
|
-
Requires-Dist: torch>=2.10.0
|
|
51
50
|
Dynamic: license-file
|
|
52
51
|
|
|
53
52
|
# `spmd_types`
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
torch>=2.10.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|