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.
Files changed (37) hide show
  1. {spmd_types-0.2.1/spmd_types.egg-info → spmd_types-0.2.2}/PKG-INFO +34 -16
  2. spmd_types-0.2.2/README.md +69 -0
  3. {spmd_types-0.2.1 → spmd_types-0.2.2}/pyproject.toml +1 -1
  4. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/__init__.py +2 -0
  5. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_checker/__init__.py +87 -85
  6. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_collectives.py +38 -27
  7. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_local.py +35 -28
  8. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_mesh.py +34 -3
  9. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_mesh_axis.py +44 -16
  10. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_state.py +28 -1
  11. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/runtime.py +31 -3
  12. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/types.py +77 -20
  13. {spmd_types-0.2.1 → spmd_types-0.2.2/spmd_types.egg-info}/PKG-INFO +34 -16
  14. spmd_types-0.2.1/README.md +0 -51
  15. {spmd_types-0.2.1 → spmd_types-0.2.2}/LICENSE +0 -0
  16. {spmd_types-0.2.1 → spmd_types-0.2.2}/setup.cfg +0 -0
  17. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_backward_hooks.py +0 -0
  18. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_dist.py +0 -0
  19. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_dtensor.py +0 -0
  20. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_dtensor_checker.py +0 -0
  21. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_dtype_utils.py +0 -0
  22. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_frame.py +0 -0
  23. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_mesh_region.py +0 -0
  24. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_raw_dist.py +0 -0
  25. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_reinterpret_mesh.py +0 -0
  26. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_scalar.py +0 -0
  27. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_scalar_sentinel.py +0 -0
  28. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_test_utils.py +0 -0
  29. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_testing.py +0 -0
  30. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_traceback.py +0 -0
  31. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_type_attr.py +0 -0
  32. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/_vmap.py +0 -0
  33. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types/checker.py +0 -0
  34. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types.egg-info/SOURCES.txt +0 -0
  35. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types.egg-info/dependency_links.txt +0 -0
  36. {spmd_types-0.2.1 → spmd_types-0.2.2}/spmd_types.egg-info/requires.txt +0 -0
  37. {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.1
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
- spmd_types tracks per-mesh-axis types on tensors -- Replicate (R), Invariant (I),
58
- Varying (V), Partial (P), and Shard (S) -- and enforces type-correct transitions
59
- through collective operations and local rewrites. It catches distributed
60
- programming errors at development time without requiring a GPU cluster.
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}) # R on dp, partial on tp
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}) # now replicated everywhere
87
- z = torch.mul(y, y) # type inference: R * R -> R
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 [MEGATRON_QUICKSTART.md](MEGATRON_QUICKSTART.md) for a guide on porting
96
- Megatron-derived training frameworks to use spmd_types.
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 [DESIGN.md](DESIGN.md) for the full type system specification, including
99
- type inference rules, collective signatures, and forward-backward pairs.
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 [LICENSE](LICENSE) for details.
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.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "spmd_types"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "A type system for distributed (SPMD) tensor computations in PyTorch"
9
9
  readme = "README.md"
10
10
  license = {file = "LICENSE"}
@@ -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
- fixed_args: tuple[int, ...] = ()
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
- # LINEAR with fixed_args -- division (linear in numerator only)
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(OpLinearity.LINEAR, (0, 1), ("input", "other"), fixed_args=(1,)),
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.LINEAR, (0, 1), ("input", "other"), fixed_args=(1,)
1046
+ OpLinearity.MULTILINEAR,
1047
+ (0, 1),
1048
+ ("input", "other"),
1049
+ nonlinear_args=(1,),
1036
1050
  ),
1037
1051
  torch.true_divide: _OpSpec(
1038
- OpLinearity.LINEAR, (0, 1), ("input", "other"), fixed_args=(1,)
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.LINEAR, (0, 1), ("other",), fixed_args=(1,)
1064
+ OpLinearity.MULTILINEAR,
1065
+ (0, 1),
1066
+ ("other",),
1067
+ nonlinear_args=(1,),
1043
1068
  ),
1044
1069
  torch.Tensor.true_divide: _OpSpec(
1045
- OpLinearity.LINEAR, (0, 1), ("other",), fixed_args=(1,)
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.LINEAR, (0, 1), ("other",), fixed_args=(1,)
1082
+ OpLinearity.MULTILINEAR,
1083
+ (0, 1),
1084
+ ("other",),
1085
+ nonlinear_args=(1,),
1050
1086
  ),
1051
1087
  torch.Tensor.__rtruediv__: _OpSpec(
1052
- OpLinearity.LINEAR, (0, 1), ("other",), fixed_args=(0,)
1088
+ OpLinearity.MULTILINEAR, (0, 1), ("other",), nonlinear_args=(0,)
1053
1089
  ),
1054
1090
  torch.Tensor.__itruediv__: _OpSpec(
1055
- OpLinearity.LINEAR, (0, 1), ("other",), fixed_args=(1,)
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 _apply_fixed_args( # noqa: C901
1581
+ def _validate_nonlinear_args(
1543
1582
  func: Callable,
1544
1583
  args: tuple,
1545
- kwargs: dict,
1546
1584
  spec: _OpSpec,
1547
- input_types_list: list[LocalSpmdType],
1548
- ) -> list[LocalSpmdType]:
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
- Args:
1564
- func: The torch function being called.
1565
- args: Positional arguments to the torch operation.
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
- for i in spec.tensor_args:
1577
- if i < len(args):
1578
- val = args[i]
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 fixed argument "
1606
- f"(denominator/divisor) on axis {format_axis(axis)} is not allowed. "
1607
- f"Division is only linear in the numerator."
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
- # Exclude fixed_types from input_types_list: return only free_types
1611
- # plus any scalar types that were appended by _collect_scalar_types.
1612
- # scalar_types count = len(input_types_list) - len(free_types) - len(fixed_types)
1613
- n_tensor_types = len(free_types) + len(fixed_types)
1614
- scalar_types = input_types_list[n_tensor_types:]
1615
- return free_types + scalar_types
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.fixed_args:
2906
- input_types_list = _apply_fixed_args(
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, TYPE_CHECKING
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 _canonicalize_shard, I, P, PerMeshAxisSpmdType, R, Shard, V
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: ProcessGroup,
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
- axis,
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, axis, src=V, dst=P)
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, axis, dst, inplace, dtype_options)
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: ProcessGroup,
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
- axis,
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
- axis,
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, axis, dst, gather_dim, dtype_options)
579
+ return _AllGatherStack.apply(x, pg, dst, gather_dim, dtype_options)
571
580
  else:
572
- return _AllGatherShard.apply(x, axis, dst, gather_dim, dtype_options)
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: ProcessGroup,
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
- axis,
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, axis, src=V, dst=P)
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, axis, scatter_dim, dtype_options)
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
- axis,
849
+ pg,
840
850
  scatter_dim,
841
851
  split_sizes,
842
852
  dtype_options,
843
853
  )
844
854
  else:
845
- return _ReduceScatterShard.apply(x, axis, scatter_dim, dtype_options)
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: ProcessGroup,
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
- axis,
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
- axis,
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, axis, split_dim, concat_dim, dtype_options)
1162
+ return _AllToAllStack.apply(x, pg, split_dim, concat_dim, dtype_options)
1152
1163
  else:
1153
- return _AllToAllShard.apply(x, axis, split_dim, concat_dim, dtype_options)
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: ProcessGroup,
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: ProcessGroup,
1397
+ axis: DeviceMeshAxis,
1387
1398
  *,
1388
1399
  src: PerMeshAxisSpmdType,
1389
1400
  dst: PerMeshAxisSpmdType,