spmd_types 0.2.1__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.
Files changed (38) hide show
  1. {spmd_types-0.2.1/spmd_types.egg-info → spmd_types-0.2.3}/PKG-INFO +34 -17
  2. spmd_types-0.2.3/README.md +69 -0
  3. {spmd_types-0.2.1 → spmd_types-0.2.3}/pyproject.toml +1 -2
  4. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/__init__.py +5 -1
  5. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/_checker/__init__.py +148 -99
  6. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/_collectives.py +38 -27
  7. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/_local.py +35 -28
  8. spmd_types-0.2.3/spmd_types/_mesh.py +211 -0
  9. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/_mesh_axis.py +44 -16
  10. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/_state.py +51 -14
  11. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/checker.py +2 -2
  12. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/runtime.py +178 -42
  13. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/types.py +77 -20
  14. {spmd_types-0.2.1 → spmd_types-0.2.3/spmd_types.egg-info}/PKG-INFO +34 -17
  15. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types.egg-info/SOURCES.txt +0 -1
  16. spmd_types-0.2.1/README.md +0 -51
  17. spmd_types-0.2.1/spmd_types/_mesh.py +0 -95
  18. spmd_types-0.2.1/spmd_types.egg-info/requires.txt +0 -1
  19. {spmd_types-0.2.1 → spmd_types-0.2.3}/LICENSE +0 -0
  20. {spmd_types-0.2.1 → spmd_types-0.2.3}/setup.cfg +0 -0
  21. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/_backward_hooks.py +0 -0
  22. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/_dist.py +0 -0
  23. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/_dtensor.py +0 -0
  24. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/_dtensor_checker.py +0 -0
  25. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/_dtype_utils.py +0 -0
  26. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/_frame.py +0 -0
  27. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/_mesh_region.py +0 -0
  28. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/_raw_dist.py +0 -0
  29. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/_reinterpret_mesh.py +0 -0
  30. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/_scalar.py +0 -0
  31. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/_scalar_sentinel.py +0 -0
  32. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/_test_utils.py +0 -0
  33. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/_testing.py +0 -0
  34. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/_traceback.py +0 -0
  35. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/_type_attr.py +0 -0
  36. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types/_vmap.py +0 -0
  37. {spmd_types-0.2.1 → spmd_types-0.2.3}/spmd_types.egg-info/dependency_links.txt +0 -0
  38. {spmd_types-0.2.1 → 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.1
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,17 +47,31 @@ 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
- # spmd_types
52
+ # `spmd_types`
54
53
 
55
- A type system for distributed (SPMD) tensor computations in PyTorch.
54
+ A type system for distributed (SPMD) tensor computations in PyTorch. This
55
+ package provides two type systems:
56
56
 
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.
57
+ * **Local SPMD types**, which allow you to use Megatron-style differentiable
58
+ collectives in a safe way by tracking whether or not your backward gradients
59
+ are pending reduction or not.
60
+
61
+ * **Global SPMD types**, a DTensor-like abstraction for writing code that has
62
+ the same semantics whether run on a single device or in a distributed
63
+ fashion, but with explicit communication operations so you are never
64
+ guessing when a redistribute occurs.
65
+
66
+ In both cases, the SPMD types makes it possible for you to check that your
67
+ code computes correct gradients (local SPMD) or gives equivalent results
68
+ across different parallelizations (global SPMD), without having to actually
69
+ run a full E2E distributed training run to check for loss matching.
70
+
71
+ The goal of this package is to provide a flexible type system that can
72
+ typecheck realistic training code. We have used local SPMD types to typecheck
73
+ a realistic pretraining codebase, and global SPMD types is actively under
74
+ construction!
61
75
 
62
76
  ## Installation
63
77
 
@@ -71,6 +85,7 @@ pip install spmd_types
71
85
  import torch
72
86
  import torch.distributed as dist
73
87
  import spmd_types as spmd
88
+ import spmd_types.checker
74
89
  from torch.distributed.device_mesh import init_device_mesh
75
90
 
76
91
  # Set up a fake process group (no GPUs needed)
@@ -79,12 +94,12 @@ mesh = init_device_mesh("cpu", (2, 4), mesh_dim_names=("dp", "tp"))
79
94
  dp = mesh.get_group("dp")
80
95
  tp = mesh.get_group("tp")
81
96
 
82
- with spmd.set_current_mesh(mesh), spmd.typecheck():
97
+ with spmd.set_current_mesh(mesh), spmd.checker.typecheck():
83
98
  x = torch.randn(4)
84
- spmd.assert_type(x, {dp: spmd.R, tp: spmd.P}) # R on dp, partial on tp
99
+ spmd.assert_type(x, {dp: spmd.R, tp: spmd.P}) # R on dp, partial on tp
85
100
  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
101
+ spmd.assert_type(y, {dp: spmd.R, tp: spmd.R}) # now replicated everywhere
102
+ z = torch.mul(y, y) # type inference: R * R -> R
88
103
  spmd.assert_type(z, {dp: spmd.R, tp: spmd.R})
89
104
 
90
105
  dist.destroy_process_group()
@@ -92,12 +107,14 @@ dist.destroy_process_group()
92
107
 
93
108
  ## Documentation
94
109
 
95
- See [MEGATRON_QUICKSTART.md](MEGATRON_QUICKSTART.md) for a guide on porting
96
- Megatron-derived training frameworks to use spmd_types.
110
+ See [Local SPMD types](docs/local_spmd_types.md) for a hands-on guide on
111
+ porting Megatron-derived training frameworks, including the Megatron-to-spmd_types
112
+ function mapping table and advice on Invariant vs Replicate.
97
113
 
98
- See [DESIGN.md](DESIGN.md) for the full type system specification, including
99
- type inference rules, collective signatures, and forward-backward pairs.
114
+ See [Design](docs/design.md) for the full type system specification, including
115
+ local vs global SPMD modes, collective signatures with diagrams, forward-backward
116
+ pairs, expert mode, cross-mesh compatibility, and partition spec redistribute.
100
117
 
101
118
  ## License
102
119
 
103
- BSD 3-Clause License. See [LICENSE](LICENSE) for details.
120
+ 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.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
@@ -53,12 +52,16 @@ from spmd_types._type_attr import ( # noqa: F401
53
52
  # Collectives and operations -- runtime API (no _checker dependency)
54
53
  from spmd_types.runtime import ( # noqa: F401
55
54
  assert_local_type,
55
+ assert_local_type_like,
56
56
  assert_type,
57
57
  assert_type_like,
58
+ get_partition_spec,
59
+ has_local_type,
58
60
  Infer,
59
61
  local,
60
62
  local_map,
61
63
  mutate_type,
64
+ no_typecheck,
62
65
  register_autograd_function,
63
66
  register_decomposition,
64
67
  register_local_autograd_function,
@@ -84,6 +87,7 @@ from spmd_types.types import ( # noqa: F401
84
87
  Replicate,
85
88
  S,
86
89
  Shard,
90
+ SpmdType,
87
91
  SpmdTypeError,
88
92
  TensorSharding,
89
93
  V,
@@ -68,14 +68,13 @@ 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,
74
75
  trace,
75
76
  )
76
77
  from spmd_types.types import (
77
- _canonicalize_shard,
78
- _check_orthogonality,
79
78
  DeviceMeshAxis,
80
79
  DTensorPropagationError,
81
80
  format_axis,
@@ -885,13 +884,15 @@ class _OpSpec:
885
884
  tensor_kwargs: Kwarg names that are tensor inputs.
886
885
  tensor_varargs_from: If set, all positional args from this index onward
887
886
  are tensor inputs (for ops with *args like einsum).
887
+ nonlinear_args: Positional arg indices whose operand enters the op
888
+ nonlinearly and therefore must not be P.
888
889
  """
889
890
 
890
891
  linearity: OpLinearity
891
892
  tensor_args: tuple[int, ...] = ()
892
893
  tensor_kwargs: tuple[str, ...] = ()
893
894
  tensor_varargs_from: int | None = None
894
- fixed_args: tuple[int, ...] = ()
895
+ nonlinear_args: tuple[int, ...] = ()
895
896
 
896
897
 
897
898
  _OP_REGISTRY: dict[Callable, _OpSpec] = {
@@ -941,6 +942,8 @@ _OP_REGISTRY: dict[Callable, _OpSpec] = {
941
942
  torch.Tensor.mean: _OpSpec(OpLinearity.LINEAR, (0,)),
942
943
  torch.nansum: _OpSpec(OpLinearity.LINEAR, (0,)),
943
944
  torch.nanmean: _OpSpec(OpLinearity.LINEAR, (0,)),
945
+ torch.cumsum: _OpSpec(OpLinearity.LINEAR, (0,)),
946
+ torch.Tensor.cumsum: _OpSpec(OpLinearity.LINEAR, (0,)),
944
947
  # =================================================================
945
948
  # LINEAR -- concat / stack (tensor list at pos 0)
946
949
  # =================================================================
@@ -1028,31 +1031,68 @@ _OP_REGISTRY: dict[Callable, _OpSpec] = {
1028
1031
  torch.Tensor.repeat_interleave: _OpSpec(OpLinearity.LINEAR, (0,)),
1029
1032
  torch.Tensor.unfold: _OpSpec(OpLinearity.LINEAR, (0,)),
1030
1033
  # =================================================================
1031
- # LINEAR with fixed_args -- division (linear in numerator only)
1034
+ # MULTILINEAR with nonlinear_args -- division (linear in numerator only)
1035
+ # Division is multiplicative, not additive: a lone P numerator scaled by an
1036
+ # R/scalar divisor stays P, while LINEAR would reject this as affine.
1037
+ # nonlinear_args additionally forbids P in the divisor because 1/y is
1038
+ # nonlinear in y.
1032
1039
  # =================================================================
1033
- torch.div: _OpSpec(OpLinearity.LINEAR, (0, 1), ("input", "other"), fixed_args=(1,)),
1040
+ torch.div: _OpSpec(
1041
+ OpLinearity.MULTILINEAR,
1042
+ (0, 1),
1043
+ ("input", "other"),
1044
+ nonlinear_args=(1,),
1045
+ ),
1034
1046
  torch.divide: _OpSpec(
1035
- OpLinearity.LINEAR, (0, 1), ("input", "other"), fixed_args=(1,)
1047
+ OpLinearity.MULTILINEAR,
1048
+ (0, 1),
1049
+ ("input", "other"),
1050
+ nonlinear_args=(1,),
1036
1051
  ),
1037
1052
  torch.true_divide: _OpSpec(
1038
- OpLinearity.LINEAR, (0, 1), ("input", "other"), fixed_args=(1,)
1053
+ OpLinearity.MULTILINEAR,
1054
+ (0, 1),
1055
+ ("input", "other"),
1056
+ nonlinear_args=(1,),
1057
+ ),
1058
+ torch.Tensor.div: _OpSpec(
1059
+ OpLinearity.MULTILINEAR,
1060
+ (0, 1),
1061
+ ("other",),
1062
+ nonlinear_args=(1,),
1039
1063
  ),
1040
- torch.Tensor.div: _OpSpec(OpLinearity.LINEAR, (0, 1), ("other",), fixed_args=(1,)),
1041
1064
  torch.Tensor.divide: _OpSpec(
1042
- OpLinearity.LINEAR, (0, 1), ("other",), fixed_args=(1,)
1065
+ OpLinearity.MULTILINEAR,
1066
+ (0, 1),
1067
+ ("other",),
1068
+ nonlinear_args=(1,),
1043
1069
  ),
1044
1070
  torch.Tensor.true_divide: _OpSpec(
1045
- OpLinearity.LINEAR, (0, 1), ("other",), fixed_args=(1,)
1071
+ OpLinearity.MULTILINEAR,
1072
+ (0, 1),
1073
+ ("other",),
1074
+ nonlinear_args=(1,),
1075
+ ),
1076
+ torch.Tensor.div_: _OpSpec(
1077
+ OpLinearity.MULTILINEAR,
1078
+ (0, 1),
1079
+ ("other",),
1080
+ nonlinear_args=(1,),
1046
1081
  ),
1047
- torch.Tensor.div_: _OpSpec(OpLinearity.LINEAR, (0, 1), ("other",), fixed_args=(1,)),
1048
1082
  torch.Tensor.__truediv__: _OpSpec(
1049
- OpLinearity.LINEAR, (0, 1), ("other",), fixed_args=(1,)
1083
+ OpLinearity.MULTILINEAR,
1084
+ (0, 1),
1085
+ ("other",),
1086
+ nonlinear_args=(1,),
1050
1087
  ),
1051
1088
  torch.Tensor.__rtruediv__: _OpSpec(
1052
- OpLinearity.LINEAR, (0, 1), ("other",), fixed_args=(0,)
1089
+ OpLinearity.MULTILINEAR, (0, 1), ("other",), nonlinear_args=(0,)
1053
1090
  ),
1054
1091
  torch.Tensor.__itruediv__: _OpSpec(
1055
- OpLinearity.LINEAR, (0, 1), ("other",), fixed_args=(1,)
1092
+ OpLinearity.MULTILINEAR,
1093
+ (0, 1),
1094
+ ("other",),
1095
+ nonlinear_args=(1,),
1056
1096
  ),
1057
1097
  # =================================================================
1058
1098
  # MULTILINEAR -- multiplication
@@ -1539,80 +1579,40 @@ def _set_result_type(result: object, output_type: LocalSpmdType) -> None:
1539
1579
  _apply(item)
1540
1580
 
1541
1581
 
1542
- def _apply_fixed_args( # noqa: C901
1582
+ def _validate_nonlinear_args(
1543
1583
  func: Callable,
1544
1584
  args: tuple,
1545
- kwargs: dict,
1546
1585
  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).
1586
+ ) -> None:
1587
+ """Validate that operands entering the op nonlinearly are not Partial.
1562
1588
 
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.
1589
+ Division is the current user: ``x / y`` is linear (scaling) in ``x`` only,
1590
+ since ``1/y`` is nonlinear in ``y``. Nonlinear operands may be untyped
1591
+ Python scalars or typed non-Partial values, but they cannot be P.
1569
1592
  """
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
1593
 
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:
1594
+ def _check_type(typ: LocalSpmdType) -> None:
1595
+ for axis, axis_type in typ.items():
1596
+ if axis_type is P:
1604
1597
  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."
1598
+ f"{func.__name__}: Partial type in nonlinear argument "
1599
+ f"on axis {format_axis(axis)} is not allowed. Reduce or "
1600
+ f"convert the Partial operand before using it in a "
1601
+ f"nonlinear position."
1608
1602
  )
1609
1603
 
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
1604
+ def _check_value(val: object) -> None:
1605
+ if isinstance(val, torch.Tensor):
1606
+ _check_type(get_local_type(val))
1607
+ elif isinstance(val, Scalar):
1608
+ _check_type(get_local_type(val))
1609
+ elif isinstance(val, (list, tuple)):
1610
+ for item in val:
1611
+ _check_value(item)
1612
+
1613
+ for i in spec.nonlinear_args:
1614
+ if i < len(args):
1615
+ _check_value(args[i])
1616
1616
 
1617
1617
 
1618
1618
  # Every function in this registry must accept (x: Tensor, axis, *, src=..., dst=...)
@@ -1680,7 +1680,7 @@ def _set_result_partition_spec(
1680
1680
  _set_partition_spec(item, spec)
1681
1681
 
1682
1682
 
1683
- def _collect_shard_axes(
1683
+ def _collect_shard_axes( # noqa: C901
1684
1684
  partition_specs: list[PartitionSpec | None],
1685
1685
  global_axes: set[MeshAxis],
1686
1686
  ) -> tuple[list[MeshAxis], dict[MeshAxis, set[MeshAxis]]]:
@@ -2086,11 +2086,16 @@ class _ShardPropagator:
2086
2086
  """
2087
2087
  mesh = self._get_mesh(axis.size())
2088
2088
 
2089
+ from torch.utils._python_dispatch import _disable_current_modes
2090
+
2091
+ # Keep checker-internal fake DTensor propagation invisible to active
2092
+ # TorchDispatchModes such as selective activation checkpointing's
2093
+ # caching mode.
2089
2094
  # Disable LocalTensorMode during meta DTensor propagation.
2090
2095
  # DTensor.from_local calls mesh.get_coordinate() which, under
2091
2096
  # LocalTensorMode(N), tries to map all N simulated ranks into
2092
2097
  # the propagator's fake 1-D mesh.
2093
- with maybe_disable_local_tensor_mode():
2098
+ with maybe_disable_local_tensor_mode(), _disable_current_modes():
2094
2099
  meta_args = torch.utils._pytree.tree_map(
2095
2100
  lambda x: self._to_meta_dtensor(x, axis, mesh), args
2096
2101
  )
@@ -2527,6 +2532,8 @@ def _get_autograd_function_class(func) -> type | None:
2527
2532
  def _validate_partition_spec_for_global_spmd(
2528
2533
  local_type: LocalSpmdType,
2529
2534
  spec: PartitionSpec | None,
2535
+ *,
2536
+ is_global_axis: Callable[[MeshAxis], bool],
2530
2537
  ) -> None:
2531
2538
  """Validate PartitionSpec <-> local type consistency.
2532
2539
 
@@ -2539,10 +2546,12 @@ def _validate_partition_spec_for_global_spmd(
2539
2546
  Args:
2540
2547
  local_type: The tensor's local SPMD type dict.
2541
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.
2542
2551
  """
2543
2552
  spec_axes = spec.axes_with_partition_spec() if spec is not None else set()
2544
2553
  for axis, typ in local_type.items():
2545
- 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:
2546
2555
  ax = format_axis(axis)
2547
2556
  raise SpmdTypeError(
2548
2557
  f"Tensor has Varying type on axis {ax} but no PartitionSpec entry. "
@@ -2551,7 +2560,7 @@ def _validate_partition_spec_for_global_spmd(
2551
2560
  )
2552
2561
  for axis in spec_axes:
2553
2562
  typ = local_type.get(axis)
2554
- if typ is not V:
2563
+ if is_global_axis(axis) and typ is not V:
2555
2564
  ax = format_axis(axis)
2556
2565
  raise SpmdTypeError(
2557
2566
  f"Tensor has PartitionSpec on axis {ax} but local type is "
@@ -2619,6 +2628,23 @@ class _SpmdTypeMode(torch.overrides.TorchFunctionMode):
2619
2628
 
2620
2629
  self._disabled = False
2621
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
+
2622
2648
  def __torch_function__(self, func, types, args=(), kwargs=None): # noqa: C901
2623
2649
  kwargs = kwargs or {}
2624
2650
  # Paused via no_typecheck(): run without type checking.
@@ -2675,7 +2701,11 @@ class _SpmdTypeMode(torch.overrides.TorchFunctionMode):
2675
2701
  # Global SPMD validation.
2676
2702
  if not self._local:
2677
2703
  for local_type, spec in zip(info.tensor_types, info.partition_specs):
2678
- _validate_partition_spec_for_global_spmd(local_type, spec)
2704
+ _validate_partition_spec_for_global_spmd(
2705
+ local_type,
2706
+ spec,
2707
+ is_global_axis=self._is_global_axis,
2708
+ )
2679
2709
 
2680
2710
  # Typecheck-registered autograd function: dispatch to typecheck_forward
2681
2711
  # INSTEAD of executing func. The mode is popped off the
@@ -2740,6 +2770,29 @@ class _SpmdTypeMode(torch.overrides.TorchFunctionMode):
2740
2770
  # Resolve axis to a list of matching axes on the tensor.
2741
2771
  # Direct match: [axis]. Flattened decomposition: [sub1, sub2, ...].
2742
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
+ )
2743
2796
 
2744
2797
  if resolved_axes is not None:
2745
2798
  # Validate input types on all resolved axes
@@ -2770,7 +2823,7 @@ class _SpmdTypeMode(torch.overrides.TorchFunctionMode):
2770
2823
  output_type[ax] = local_dst
2771
2824
  _set_local_type(result, output_type)
2772
2825
 
2773
- if _state._is_global():
2826
+ if global_collective_axes:
2774
2827
  input_spec = get_partition_spec(x)
2775
2828
  # Find S(dim) for this axis in the input spec, validating
2776
2829
  # it is innermost in its multi-axis group.
@@ -2902,10 +2955,8 @@ class _SpmdTypeMode(torch.overrides.TorchFunctionMode):
2902
2955
  linearity = (
2903
2956
  spec.linearity if spec is not None else OpLinearity.NONLINEAR
2904
2957
  )
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
- )
2958
+ if spec is not None and spec.nonlinear_args:
2959
+ _validate_nonlinear_args(func, original_args, spec)
2909
2960
  # NB: output_type may be further updated because global
2910
2961
  # shard propagation below may promote local type V to P.
2911
2962
  output_type = infer_output_type(
@@ -2914,15 +2965,16 @@ class _SpmdTypeMode(torch.overrides.TorchFunctionMode):
2914
2965
  )
2915
2966
 
2916
2967
  # Stage 3: global shard propagation.
2917
- if self._local:
2918
- global_shard_axes, shard_edges = [], {}
2919
- else:
2920
- all_axes: set[DeviceMeshAxis] = set()
2921
- for typ in input_types_list:
2922
- 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:
2923
2973
  global_shard_axes, shard_edges = _collect_shard_axes(
2924
- partition_specs, all_axes
2974
+ partition_specs, global_axes
2925
2975
  )
2976
+ else:
2977
+ global_shard_axes, shard_edges = [], {}
2926
2978
 
2927
2979
  # Validate mutation safety for in-place/out operations.
2928
2980
  mutated = _get_mutated_tensors(func, args, kwargs, result)
@@ -3084,9 +3136,6 @@ def typecheck(
3084
3136
  _remove_autograd_apply_patch()
3085
3137
 
3086
3138
 
3087
- from spmd_types._state import no_typecheck # noqa: F401
3088
-
3089
-
3090
3139
  class _SpmdTypeBackwardCompatibleMode:
3091
3140
  """Backwards-compatible wrapper around ``typecheck()``.
3092
3141