ZhengyangZhang commited on
Commit
116f62a
·
verified ·
1 Parent(s): 4448330

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. lib/python3.12/site-packages/torch/_refs/__pycache__/_conversions.cpython-312.pyc +0 -0
  2. lib/python3.12/site-packages/torch/_refs/__pycache__/fft.cpython-312.pyc +0 -0
  3. lib/python3.12/site-packages/torch/_refs/linalg/__init__.py +309 -0
  4. lib/python3.12/site-packages/torch/_refs/linalg/__pycache__/__init__.cpython-312.pyc +0 -0
  5. lib/python3.12/site-packages/torch/_refs/nn/__init__.py +4 -0
  6. lib/python3.12/site-packages/torch/_refs/nn/__pycache__/__init__.cpython-312.pyc +0 -0
  7. lib/python3.12/site-packages/torch/_refs/nn/functional/__init__.py +1280 -0
  8. lib/python3.12/site-packages/torch/_refs/nn/functional/__pycache__/__init__.cpython-312.pyc +0 -0
  9. lib/python3.12/site-packages/torch/_refs/special/__init__.py +236 -0
  10. lib/python3.12/site-packages/torch/_refs/special/__pycache__/__init__.cpython-312.pyc +0 -0
  11. lib/python3.12/site-packages/torch/_strobelight/__init__.py +0 -0
  12. lib/python3.12/site-packages/torch/_strobelight/__pycache__/__init__.cpython-312.pyc +0 -0
  13. lib/python3.12/site-packages/torch/_strobelight/__pycache__/cli_function_profiler.cpython-312.pyc +0 -0
  14. lib/python3.12/site-packages/torch/_strobelight/__pycache__/compile_time_profiler.cpython-312.pyc +0 -0
  15. lib/python3.12/site-packages/torch/_strobelight/cli_function_profiler.py +311 -0
  16. lib/python3.12/site-packages/torch/_strobelight/compile_time_profiler.py +195 -0
  17. lib/python3.12/site-packages/torch/compiler/__init__.py +404 -0
  18. lib/python3.12/site-packages/torch/compiler/__pycache__/__init__.cpython-312.pyc +0 -0
  19. lib/python3.12/site-packages/torch/compiler/__pycache__/config.cpython-312.pyc +0 -0
  20. lib/python3.12/site-packages/torch/compiler/config.py +67 -0
  21. lib/python3.12/site-packages/torch/distributed/__init__.py +157 -0
  22. lib/python3.12/site-packages/torch/distributed/_checkpointable.py +38 -0
  23. lib/python3.12/site-packages/torch/distributed/_composable_state.py +44 -0
  24. lib/python3.12/site-packages/torch/distributed/_functional_collectives.py +1191 -0
  25. lib/python3.12/site-packages/torch/distributed/_functional_collectives_impl.py +117 -0
  26. lib/python3.12/site-packages/torch/distributed/_shard/__init__.py +1 -0
  27. lib/python3.12/site-packages/torch/distributed/_shard/__pycache__/__init__.cpython-312.pyc +0 -0
  28. lib/python3.12/site-packages/torch/distributed/_shard/__pycache__/_utils.cpython-312.pyc +0 -0
  29. lib/python3.12/site-packages/torch/distributed/_shard/__pycache__/api.cpython-312.pyc +0 -0
  30. lib/python3.12/site-packages/torch/distributed/_shard/__pycache__/common_op_utils.cpython-312.pyc +0 -0
  31. lib/python3.12/site-packages/torch/distributed/_shard/__pycache__/metadata.cpython-312.pyc +0 -0
  32. lib/python3.12/site-packages/torch/distributed/_shard/__pycache__/op_registry_utils.cpython-312.pyc +0 -0
  33. lib/python3.12/site-packages/torch/distributed/_shard/__pycache__/sharder.cpython-312.pyc +0 -0
  34. lib/python3.12/site-packages/torch/distributed/_shard/_utils.py +32 -0
  35. lib/python3.12/site-packages/torch/distributed/_shard/api.py +306 -0
  36. lib/python3.12/site-packages/torch/distributed/_shard/checkpoint/__init__.py +19 -0
  37. lib/python3.12/site-packages/torch/distributed/_shard/checkpoint/__pycache__/__init__.cpython-312.pyc +0 -0
  38. lib/python3.12/site-packages/torch/distributed/_shard/common_op_utils.py +65 -0
  39. lib/python3.12/site-packages/torch/distributed/_shard/metadata.py +64 -0
  40. lib/python3.12/site-packages/torch/distributed/_shard/op_registry_utils.py +41 -0
  41. lib/python3.12/site-packages/torch/distributed/_shard/sharded_optim/__init__.py +52 -0
  42. lib/python3.12/site-packages/torch/distributed/_shard/sharded_optim/__pycache__/__init__.cpython-312.pyc +0 -0
  43. lib/python3.12/site-packages/torch/distributed/_shard/sharded_optim/__pycache__/api.cpython-312.pyc +0 -0
  44. lib/python3.12/site-packages/torch/distributed/_shard/sharded_optim/api.py +101 -0
  45. lib/python3.12/site-packages/torch/distributed/_shard/sharded_tensor/__init__.py +490 -0
  46. lib/python3.12/site-packages/torch/distributed/_shard/sharded_tensor/__pycache__/__init__.cpython-312.pyc +0 -0
  47. lib/python3.12/site-packages/torch/distributed/_shard/sharded_tensor/__pycache__/api.cpython-312.pyc +0 -0
  48. lib/python3.12/site-packages/torch/distributed/_shard/sharded_tensor/__pycache__/logger.cpython-312.pyc +0 -0
  49. lib/python3.12/site-packages/torch/distributed/_shard/sharded_tensor/__pycache__/logging_handlers.cpython-312.pyc +0 -0
  50. lib/python3.12/site-packages/torch/distributed/_shard/sharded_tensor/__pycache__/metadata.cpython-312.pyc +0 -0
lib/python3.12/site-packages/torch/_refs/__pycache__/_conversions.cpython-312.pyc ADDED
Binary file (4.58 kB). View file
 
lib/python3.12/site-packages/torch/_refs/__pycache__/fft.cpython-312.pyc ADDED
Binary file (26.6 kB). View file
 
lib/python3.12/site-packages/torch/_refs/linalg/__init__.py ADDED
@@ -0,0 +1,309 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ from functools import partial
3
+ from typing import Optional, Tuple, Union
4
+
5
+ import torch
6
+ import torch._prims as prims
7
+ import torch._prims_common as utils
8
+ import torch._refs as refs
9
+ import torch._refs.linalg as linalg
10
+ from torch import Tensor
11
+ from torch._prims_common import (
12
+ check_fp_or_complex,
13
+ check_is_matrix,
14
+ Dim,
15
+ DimsType,
16
+ ELEMENTWISE_TYPE_PROMOTION_KIND,
17
+ IntLike,
18
+ TensorLikeType,
19
+ )
20
+ from torch._prims_common.wrappers import (
21
+ _maybe_convert_to_dtype,
22
+ elementwise_type_promotion_wrapper,
23
+ out_wrapper,
24
+ )
25
+
26
+
27
+ __all__ = [
28
+ "diagonal",
29
+ "matrix_norm",
30
+ "norm",
31
+ "svd",
32
+ "svdvals",
33
+ "vector_norm",
34
+ "vecdot",
35
+ "cross",
36
+ ]
37
+
38
+
39
+ def _check_norm_dtype(dtype: Optional[torch.dtype], x_dtype: torch.dtype, fn_name: str):
40
+ """
41
+ Checks related to the dtype kwarg in `linalg.*norm` functions
42
+ """
43
+ if dtype is not None:
44
+ torch._check(
45
+ utils.is_float_dtype(dtype) or utils.is_complex_dtype(dtype),
46
+ lambda: f"{fn_name}: dtype should be floating point or complex. Got {dtype}",
47
+ )
48
+ torch._check(
49
+ utils.is_complex_dtype(dtype) == utils.is_complex_dtype(x_dtype),
50
+ lambda: "{fn_name}: dtype should be {d} for {d} inputs. Got {dtype}".format(
51
+ fn_name=fn_name,
52
+ d="complex" if utils.is_complex_dtype(x_dtype) else "real",
53
+ dtype=dtype,
54
+ ),
55
+ )
56
+ torch._check(
57
+ utils.get_higher_dtype(dtype, x_dtype) == dtype,
58
+ lambda: f"{fn_name}: the dtype of the input ({x_dtype}) should be convertible "
59
+ "without narrowing to the specified dtype ({dtype})",
60
+ )
61
+
62
+
63
+ import operator
64
+
65
+ # Utilities should come BEFORE this import
66
+ from torch._decomp import register_decomposition
67
+ from torch._decomp.decompositions import pw_cast_for_opmath
68
+
69
+
70
+ @register_decomposition(torch._ops.ops.aten.linalg_cross)
71
+ @out_wrapper()
72
+ @pw_cast_for_opmath
73
+ def cross(a: Tensor, b: Tensor, dim: int = -1):
74
+ torch._check(
75
+ a.ndim == b.ndim,
76
+ lambda: "linalg.cross: inputs must have the same number of dimensions.",
77
+ )
78
+ torch._check(
79
+ a.size(dim) == 3 and b.size(dim) == 3,
80
+ lambda: f"linalg.cross: inputs dim {dim} must have length 3, got {a.size(dim)} and {b.size(dim)}",
81
+ )
82
+ a, b = torch.broadcast_tensors(a, b)
83
+ dim = utils.canonicalize_dim(a.ndim, dim)
84
+ idx = torch.arange(3, device=a.device)
85
+ return a.index_select(dim, (idx + 1) % 3) * b.index_select(
86
+ dim, (idx + 2) % 3
87
+ ) - a.index_select(dim, (idx + 2) % 3) * b.index_select(dim, (idx + 1) % 3)
88
+
89
+
90
+ def diagonal(
91
+ input: TensorLikeType,
92
+ *,
93
+ offset: int = 0,
94
+ dim1: int = -2,
95
+ dim2: int = -1,
96
+ ) -> TensorLikeType:
97
+ return torch.diagonal(input, offset=offset, dim1=dim1, dim2=dim2)
98
+
99
+
100
+ @register_decomposition(torch._ops.ops.aten.linalg_vector_norm)
101
+ @out_wrapper(exact_dtype=True)
102
+ def vector_norm(
103
+ x: TensorLikeType,
104
+ ord: Union[float, int] = 2,
105
+ dim: Optional[DimsType] = None,
106
+ keepdim: bool = False,
107
+ *,
108
+ dtype: Optional[torch.dtype] = None,
109
+ ) -> Tensor:
110
+ from torch.fx.experimental.symbolic_shapes import guard_size_oblivious
111
+
112
+ # Checks
113
+ check_fp_or_complex(x.dtype, "linalg.vector_norm")
114
+
115
+ if isinstance(dim, Dim):
116
+ dim = [dim] # type: ignore[assignment]
117
+
118
+ if guard_size_oblivious(x.numel() == 0) and (ord < 0.0 or ord == float("inf")):
119
+ torch._check(
120
+ dim is not None and len(dim) != 0,
121
+ lambda: f"linalg.vector_norm cannot compute the {ord} norm on an empty tensor "
122
+ "because the operation does not have an identity",
123
+ )
124
+ shape = x.shape
125
+ assert dim is not None # mypy does not seem to be able to see through check?
126
+ for d in dim:
127
+ torch._check(
128
+ shape[d] != 0,
129
+ lambda: f"linalg.vector_norm cannot compute the {ord} norm on the "
130
+ f"dimension {d} because this dimension is empty and the "
131
+ "operation does not have an identity",
132
+ )
133
+ _check_norm_dtype(dtype, x.dtype, "linalg.vector_norm")
134
+
135
+ computation_dtype, result_dtype = utils.reduction_dtypes(
136
+ x, utils.REDUCTION_OUTPUT_TYPE_KIND.COMPLEX_TO_FLOAT, dtype
137
+ )
138
+
139
+ to_result_dtype = partial(_maybe_convert_to_dtype, dtype=result_dtype)
140
+
141
+ # Implementation
142
+ if ord == 0.0:
143
+ return torch.sum(torch.ne(x, 0.0), dim=dim, keepdim=keepdim, dtype=result_dtype)
144
+ elif ord == float("inf"):
145
+ return to_result_dtype(torch.amax(torch.abs(x), dim=dim, keepdim=keepdim)) # type: ignore[return-value,arg-type]
146
+ elif ord == float("-inf"):
147
+ return to_result_dtype(torch.amin(torch.abs(x), dim=dim, keepdim=keepdim)) # type: ignore[return-value,arg-type]
148
+ else:
149
+ # From here on the computation dtype is important as the reduction is non-trivial
150
+ x = _maybe_convert_to_dtype(x, computation_dtype) # type: ignore[assignment]
151
+ reduce_sum = partial(torch.sum, dim=dim, keepdim=keepdim)
152
+
153
+ is_ord_even = ord % 2 == 0 if isinstance(ord, IntLike) else ord % 2.0 == 0.0
154
+ if not (is_ord_even and utils.is_float_dtype(x.dtype)):
155
+ x = torch.abs(x)
156
+ return to_result_dtype(torch.pow(reduce_sum(torch.pow(x, ord)), 1.0 / ord)) # type: ignore[return-value]
157
+
158
+
159
+ def _backshift_permutation(dim0, dim1, ndim):
160
+ # Auxiliary function for matrix_norm
161
+ # Computes the permutation that moves the two given dimensions to the back
162
+ ret = [i for i in range(ndim) if i != dim0 and i != dim1]
163
+ ret.extend((dim0, dim1))
164
+ return ret
165
+
166
+
167
+ def _inverse_permutation(perm):
168
+ # Given a permutation, returns its inverse. It's equivalent to argsort on an array
169
+ return [i for i, j in sorted(enumerate(perm), key=operator.itemgetter(1))]
170
+
171
+
172
+ # CompositeImplicitAutograd
173
+ @out_wrapper(exact_dtype=True)
174
+ def matrix_norm(
175
+ A: TensorLikeType,
176
+ ord: Union[float, str] = "fro",
177
+ dim: DimsType = (-2, -1),
178
+ keepdim: bool = False,
179
+ *,
180
+ dtype: Optional[torch.dtype] = None,
181
+ ) -> TensorLikeType:
182
+ # shape
183
+ check_is_matrix(A, "linalg.matrix_norm")
184
+ # dim
185
+ dim = utils.canonicalize_dims(A.ndim, dim)
186
+ if isinstance(dim, Dim):
187
+ dim = (dim,) # type: ignore[assignment]
188
+ torch._check(
189
+ len(dim) == 2, lambda: "linalg.matrix_norm: dim must be a 2-tuple. Got {dim}"
190
+ )
191
+ torch._check(
192
+ dim[0] != dim[1],
193
+ lambda: "linalg.matrix_norm: dims must be different. Got ({dim[0]}, {dim[1]})",
194
+ )
195
+ # dtype arg
196
+ _check_norm_dtype(dtype, A.dtype, "linalg.matrix_norm")
197
+
198
+ if isinstance(ord, str):
199
+ # ord
200
+ torch._check(
201
+ ord in ("fro", "nuc"),
202
+ lambda: "linalg.matrix_norm: Order {ord} not supported.",
203
+ )
204
+ # dtype
205
+ check_fp_or_complex(
206
+ A.dtype, "linalg.matrix_norm", allow_low_precision_dtypes=ord != "nuc"
207
+ )
208
+
209
+ if ord == "fro":
210
+ return vector_norm(A, 2, dim, keepdim, dtype=dtype)
211
+ else: # ord == "nuc"
212
+ if dtype is not None:
213
+ A = _maybe_convert_to_dtype(A, dtype) # type: ignore[assignment]
214
+ perm = _backshift_permutation(dim[0], dim[1], A.ndim)
215
+ result = torch.sum(svdvals(prims.transpose(A, perm)), -1, keepdim)
216
+ if keepdim:
217
+ inv_perm = _inverse_permutation(perm)
218
+ result = prims.transpose(torch.unsqueeze(result, -1), inv_perm)
219
+ return result
220
+ else:
221
+ # ord
222
+ abs_ord = abs(ord)
223
+ torch._check(
224
+ abs_ord in (2, 1, float("inf")),
225
+ lambda: "linalg.matrix_norm: Order {ord} not supported.",
226
+ )
227
+ # dtype
228
+ check_fp_or_complex(
229
+ A.dtype, "linalg.matrix_norm", allow_low_precision_dtypes=ord != 2
230
+ )
231
+
232
+ max_min = partial(torch.amax if ord > 0.0 else torch.amin, keepdim=keepdim)
233
+
234
+ if abs_ord == 2.0:
235
+ if dtype is not None:
236
+ A = _maybe_convert_to_dtype(A, dtype) # type: ignore[assignment]
237
+ perm = _backshift_permutation(dim[0], dim[1], A.ndim)
238
+ result = max_min(svdvals(prims.transpose(A, perm)), dim=-1)
239
+ if keepdim:
240
+ inv_perm = _inverse_permutation(perm)
241
+ result = prims.transpose(torch.unsqueeze(result, -1), inv_perm)
242
+ return result
243
+ else: # 1, -1, inf, -inf
244
+ dim0, dim1 = dim
245
+ if abs_ord == float("inf"):
246
+ dim0, dim1 = dim1, dim0
247
+ if not keepdim and (dim0 < dim1):
248
+ dim1 -= 1
249
+ return max_min(
250
+ vector_norm(A, 1.0, dim=dim0, keepdim=keepdim, dtype=dtype), dim1
251
+ )
252
+
253
+
254
+ # CompositeImplicitAutograd
255
+ @out_wrapper(exact_dtype=True)
256
+ def norm(
257
+ A: TensorLikeType,
258
+ ord: Optional[Union[float, str]] = None,
259
+ dim: Optional[DimsType] = None,
260
+ keepdim: bool = False,
261
+ *,
262
+ dtype: Optional[torch.dtype] = None,
263
+ ) -> TensorLikeType:
264
+ if dim is not None:
265
+ if isinstance(dim, Dim):
266
+ dim = (dim,) # type: ignore[assignment]
267
+ torch._check(
268
+ len(dim) in (1, 2),
269
+ lambda: "linalg.norm: If dim is specified, it must be of length 1 or 2. Got {dim}",
270
+ )
271
+ elif ord is not None:
272
+ torch._check(
273
+ A.ndim in (1, 2),
274
+ lambda: "linalg.norm: If dim is not specified but ord is, the input must be 1D or 2D. Got {A.ndim}D",
275
+ )
276
+
277
+ if ord is not None and (
278
+ (dim is not None and len(dim) == 2) or (dim is None and A.ndim == 2)
279
+ ):
280
+ if dim is None:
281
+ dim = (0, 1)
282
+ return matrix_norm(A, ord, dim, keepdim, dtype=dtype)
283
+ else:
284
+ if ord is None:
285
+ ord = 2.0
286
+ return vector_norm(A, ord, dim, keepdim, dtype=dtype) # type: ignore[arg-type]
287
+
288
+
289
+ # CompositeImplicitAutograd
290
+ @out_wrapper("U", "S", "Vh", exact_dtype=True)
291
+ def svd(A: TensorLikeType, full_matrices: bool = True) -> Tuple[Tensor, Tensor, Tensor]:
292
+ return prims.svd(A, full_matrices=full_matrices)
293
+
294
+
295
+ # CompositeImplicitAutograd
296
+ @out_wrapper(exact_dtype=True)
297
+ def svdvals(A: TensorLikeType) -> Tensor:
298
+ return svd(A, full_matrices=False)[1]
299
+
300
+
301
+ # CompositeImplicitAutograd
302
+ @out_wrapper()
303
+ @elementwise_type_promotion_wrapper(
304
+ type_promoting_args=("x", "y"),
305
+ type_promotion_kind=ELEMENTWISE_TYPE_PROMOTION_KIND.DEFAULT,
306
+ )
307
+ def vecdot(x: Tensor, y: Tensor, dim: int = -1) -> Tensor:
308
+ check_fp_or_complex(x.dtype, "linalg.vecdot")
309
+ return (x.conj() * y).sum(dim=dim)
lib/python3.12/site-packages/torch/_refs/linalg/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (15 kB). View file
 
lib/python3.12/site-packages/torch/_refs/nn/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ from typing import List
2
+
3
+
4
+ __all__: List[str] = []
lib/python3.12/site-packages/torch/_refs/nn/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (305 Bytes). View file
 
lib/python3.12/site-packages/torch/_refs/nn/functional/__init__.py ADDED
@@ -0,0 +1,1280 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-decorators
2
+ # mypy: allow-untyped-defs
3
+ import math
4
+ from functools import wraps
5
+ from typing import Callable, Optional, Union
6
+
7
+ import torch
8
+ import torch._prims as prims
9
+ import torch._prims_common as utils
10
+ import torch._refs as refs
11
+ from torch._decomp import register_decomposition
12
+ from torch._prims_common import (
13
+ ELEMENTWISE_TYPE_PROMOTION_KIND,
14
+ NumberType,
15
+ ShapeType,
16
+ TensorLike,
17
+ TensorLikeType,
18
+ )
19
+ from torch._prims_common.wrappers import (
20
+ elementwise_type_promotion_wrapper,
21
+ elementwise_unary_scalar_wrapper,
22
+ out_wrapper,
23
+ )
24
+ from torch._refs import _make_inplace
25
+
26
+
27
+ __all__ = [
28
+ "alpha_dropout",
29
+ "celu",
30
+ "celu_",
31
+ "channel_shuffle",
32
+ "dropout",
33
+ "elu",
34
+ "elu_",
35
+ "gelu",
36
+ "glu",
37
+ "group_norm",
38
+ "hardshrink",
39
+ "hardtanh",
40
+ "hinge_embedding_loss",
41
+ "huber_loss",
42
+ "l1_loss",
43
+ "layer_norm",
44
+ "leaky_relu",
45
+ "log_softmax",
46
+ "margin_ranking_loss",
47
+ "mish",
48
+ "mish_",
49
+ "mse_loss",
50
+ "nll_loss",
51
+ "pairwise_distance",
52
+ "pdist",
53
+ "poisson_nll_loss",
54
+ "prelu",
55
+ "relu",
56
+ "relu6",
57
+ "selu",
58
+ "selu_",
59
+ "smooth_l1_loss",
60
+ "softmax",
61
+ "softmin",
62
+ "softplus",
63
+ "softshrink",
64
+ "tanhshrink",
65
+ "threshold",
66
+ "threshold_",
67
+ "triplet_margin_loss",
68
+ ]
69
+
70
+ Tensor = torch.Tensor
71
+ aten = torch._ops.ops.aten
72
+ DispatchKey = torch._C.DispatchKey # type: ignore[attr-defined]
73
+
74
+
75
+ def _dropout_helper(
76
+ self: TensorLikeType,
77
+ val: float,
78
+ ) -> TensorLikeType:
79
+ """
80
+ Helper function for all dropout-type operators. During training,
81
+ some of the elements of the input tensor are randomly masked.
82
+
83
+ Returns the masked tensor of the boolean values.
84
+
85
+ """
86
+
87
+ return (
88
+ refs._uniform_helper(
89
+ self.shape, low=0.0, high=1.0, dtype=torch.float32, device=self.device
90
+ )
91
+ < val
92
+ )
93
+
94
+
95
+ @register_decomposition(aten.alpha_dropout)
96
+ def alpha_dropout(
97
+ self: TensorLikeType, p: float = 0.5, training: bool = False, inplace: bool = False
98
+ ) -> TensorLikeType:
99
+ if inplace:
100
+ raise NotImplementedError
101
+
102
+ if not training:
103
+ return self
104
+
105
+ torch._check(
106
+ p <= 1 and p >= 0,
107
+ lambda: f"dropout probability has to be between 0 and 1, but got, {p}",
108
+ )
109
+
110
+ if p == 1:
111
+ return torch.zeros_like(self)
112
+
113
+ if p == 0:
114
+ return self
115
+
116
+ dropout_mask = _dropout_helper(self, 1 - p)
117
+
118
+ # From paper: Self-Normalizing Neural Networks (https://arxiv.org/pdf/1706.02515.pdf)
119
+ # alpha = - SELU.alpha * SELU.scale, here
120
+ # SELU.alpha = 1.6732632423543772848170429916717 and
121
+ # SELU.scale = 1.0507009873554804934193349852946
122
+ alpha = -1.7580993408473766
123
+
124
+ a = 1.0 / math.sqrt((alpha * alpha * p + 1) * (1 - p))
125
+ b = torch.logical_not(dropout_mask)
126
+ b = b * (alpha * a) + alpha * a * p
127
+ dropout_mask = a * dropout_mask
128
+
129
+ return self * dropout_mask + b
130
+
131
+
132
+ def _inplace_wrapper(fn):
133
+ """
134
+ Given a nn.functional non-linearity, implements its `inplace: bool` argument
135
+ """
136
+
137
+ # nb. We use the name of the first argument used in the unary references
138
+ @wraps(fn)
139
+ def _fn(a, *args, inplace=False, **kwargs):
140
+ if inplace:
141
+ torch._check(
142
+ "out" not in kwargs,
143
+ lambda: "Cannot set inplace=True and pass out= at the same time",
144
+ )
145
+ return fn(a, *args, inplace=False, out=a, **kwargs)
146
+ else:
147
+ return fn(a, *args, inplace=False, **kwargs)
148
+
149
+ return _fn
150
+
151
+
152
+ # celu is implemented specially because it has an alpha argument
153
+ # celu is very similar to elu
154
+ @register_decomposition(aten.celu)
155
+ @_inplace_wrapper
156
+ @out_wrapper()
157
+ @elementwise_type_promotion_wrapper(
158
+ type_promoting_args=("a",),
159
+ type_promotion_kind=ELEMENTWISE_TYPE_PROMOTION_KIND.DEFAULT,
160
+ )
161
+ def celu(
162
+ a: TensorLikeType, alpha: Optional[NumberType] = None, inplace: bool = False
163
+ ) -> TensorLikeType:
164
+ """
165
+ Reference implementation of torch.nn.functional.celu
166
+ """
167
+
168
+ if inplace:
169
+ raise NotImplementedError
170
+
171
+ rhs: TensorLikeType
172
+ if alpha is not None:
173
+ python_type = utils.dtype_to_type(a.dtype)
174
+ if not utils.is_weakly_lesser_type(type(alpha), python_type):
175
+ msg = f"alpha argument of type {type(alpha)} cannot be safely cast to type {python_type}!"
176
+ raise ValueError(msg)
177
+ rhs = alpha * torch.expm1(torch.true_divide(a, alpha)) # type: ignore[arg-type]
178
+ else:
179
+ rhs = torch.expm1(a)
180
+
181
+ return torch.where(a > 0, a, rhs)
182
+
183
+
184
+ @_inplace_wrapper
185
+ @out_wrapper()
186
+ def dropout(
187
+ a: TensorLikeType, p: float = 0.5, training: bool = True, inplace: bool = False
188
+ ) -> TensorLikeType:
189
+ if inplace:
190
+ raise NotImplementedError
191
+
192
+ if not training:
193
+ return a
194
+
195
+ torch._check(
196
+ p <= 1 and p >= 0,
197
+ lambda: f"dropout probability has to be between 0 and 1, but got, {p}",
198
+ )
199
+
200
+ if p == 1:
201
+ return torch.zeros_like(a)
202
+
203
+ if p == 0:
204
+ return a
205
+
206
+ scale = 1 / (1 - p)
207
+ dropout_mask = _dropout_helper(a, 1 - p)
208
+
209
+ return a * dropout_mask * scale
210
+
211
+
212
+ @register_decomposition(aten.elu)
213
+ @_inplace_wrapper
214
+ @out_wrapper()
215
+ @elementwise_type_promotion_wrapper(
216
+ type_promoting_args=("a",),
217
+ type_promotion_kind=ELEMENTWISE_TYPE_PROMOTION_KIND.DEFAULT,
218
+ )
219
+ def elu(
220
+ a: TensorLikeType,
221
+ alpha: NumberType = 1.0,
222
+ scale: NumberType = 1.0,
223
+ input_scale: NumberType = 1.0,
224
+ inplace: bool = False,
225
+ ) -> TensorLikeType:
226
+ """
227
+ Reference implementation of torch.nn.functional.elu
228
+ """
229
+ if inplace:
230
+ raise NotImplementedError
231
+
232
+ # nb. This should be factored out into a can_cast aux function
233
+ python_type = utils.dtype_to_type(a.dtype)
234
+ torch._check(
235
+ utils.is_weakly_lesser_type(type(input_scale), python_type),
236
+ lambda: f"input_scale argument of type {type(input_scale)} cannot be safely cast to type {python_type}!",
237
+ )
238
+ torch._check(
239
+ utils.is_weakly_lesser_type(type(scale), python_type),
240
+ lambda: f"scale argument of type {type(scale)} cannot be safely cast to type {python_type}!",
241
+ )
242
+ torch._check(
243
+ utils.is_weakly_lesser_type(type(alpha), python_type),
244
+ lambda: f"alpha argument of type {type(alpha)} cannot be safely cast to type {python_type}!",
245
+ )
246
+
247
+ return torch.where(a > 0, scale * a, (alpha * scale) * torch.expm1(a * input_scale))
248
+
249
+
250
+ @register_decomposition(aten.relu)
251
+ @_inplace_wrapper
252
+ @out_wrapper()
253
+ @elementwise_type_promotion_wrapper(
254
+ type_promoting_args=("a",),
255
+ type_promotion_kind=ELEMENTWISE_TYPE_PROMOTION_KIND.DEFAULT,
256
+ )
257
+ def relu(a: TensorLikeType, inplace: bool = False) -> TensorLikeType:
258
+ """
259
+ Reference implementation of torch.nn.functional.relu
260
+ """
261
+
262
+ if inplace:
263
+ raise NotImplementedError
264
+
265
+ return torch.where(torch.le(a, 0), 0, a)
266
+
267
+
268
+ @register_decomposition(aten.channel_shuffle)
269
+ @out_wrapper()
270
+ def channel_shuffle(input: TensorLikeType, groups: int) -> TensorLikeType:
271
+ """
272
+ Reference implementation of :func:`torch.nn.functional.channel_shuffle`.
273
+ """
274
+ from torch._meta_registrations import device_hint
275
+
276
+ torch._check(
277
+ input.dim() > 2,
278
+ lambda: f"channel_shuffle expects input with > 2 dims, but got input with sizes {list(input.size())}",
279
+ )
280
+ c = input.shape[1]
281
+ torch._check(
282
+ groups > 0,
283
+ lambda: f"Number of groups to divide channels in must be positive. Value of groups:{groups}",
284
+ )
285
+ torch._check(
286
+ (c % groups) == 0,
287
+ lambda: f"Number of channels must be divisible by groups. Got {c} channels and {groups} groups.",
288
+ )
289
+ n = input.shape[0]
290
+ cg = c // groups
291
+ dhw = input.shape[2:]
292
+
293
+ if input.numel() == 0 or (
294
+ device_hint(input) == "cuda" and (groups == 1 or groups == c)
295
+ ):
296
+ return input.view(input.shape)
297
+
298
+ return (
299
+ input.reshape(n, groups, cg, *dhw)
300
+ .transpose(1, 2)
301
+ .reshape(input.shape)
302
+ .contiguous()
303
+ )
304
+
305
+
306
+ def group_norm(
307
+ input: Tensor,
308
+ num_groups: int,
309
+ weight: Optional[Tensor] = None,
310
+ bias: Optional[Tensor] = None,
311
+ eps: float = 1e-5,
312
+ ) -> Tensor:
313
+ """
314
+ Reference implementation of :func:`torch.nn.functional.group_norm`.
315
+ """
316
+ torch._check(
317
+ input.ndim >= 2,
318
+ lambda: f"Expected at least 2 dimensions for input tensor but received {input.ndim}",
319
+ )
320
+
321
+ batch_size = input.shape[0]
322
+ num_channels = input.shape[1]
323
+ torch._check(
324
+ num_channels % num_groups == 0,
325
+ lambda: "Expected number of channels in input to be divisible by num_groups, "
326
+ + f"but got input of shape {input.shape} and num_groups = {num_groups}",
327
+ )
328
+
329
+ # input shape is (N, C, *), so we flatten all inner dimensions except (N, C)
330
+ flattened_inner_size = 1
331
+ for dim_length in input.shape[2:]:
332
+ flattened_inner_size *= dim_length
333
+
334
+ return torch.native_group_norm(
335
+ input,
336
+ weight,
337
+ bias,
338
+ batch_size,
339
+ num_channels,
340
+ flattened_inner_size,
341
+ num_groups,
342
+ eps,
343
+ )[0]
344
+
345
+
346
+ def layer_norm(
347
+ input: Tensor,
348
+ normalized_shape: ShapeType,
349
+ weight: Optional[Tensor] = None,
350
+ bias: Optional[Tensor] = None,
351
+ eps: float = 1e-5,
352
+ ) -> Tensor:
353
+ """
354
+ Reference implementation of :func:`torch.nn.functional.layer_norm`.
355
+ """
356
+ return torch.native_layer_norm(input, normalized_shape, weight, bias, eps)[0]
357
+
358
+
359
+ @register_decomposition(aten.leaky_relu)
360
+ @_inplace_wrapper
361
+ @out_wrapper()
362
+ @elementwise_type_promotion_wrapper(
363
+ type_promoting_args=("a",),
364
+ type_promotion_kind=ELEMENTWISE_TYPE_PROMOTION_KIND.DEFAULT,
365
+ )
366
+ def leaky_relu(
367
+ a: TensorLikeType, negative_slope: float = 0.01, inplace: bool = False
368
+ ) -> TensorLikeType:
369
+ """
370
+ Reference implementation of torch.nn.functional.leaky_relu
371
+ """
372
+
373
+ if inplace:
374
+ raise NotImplementedError
375
+
376
+ python_type = utils.dtype_to_type(a.dtype)
377
+ if not utils.is_weakly_lesser_type(type(negative_slope), python_type):
378
+ msg = f"negative_slope argument of type {type(negative_slope)} cannot be safely cast to type {python_type}!"
379
+ raise ValueError(msg)
380
+ return torch.where(torch.gt(a, 0), a, torch.mul(a, negative_slope))
381
+
382
+
383
+ @register_decomposition(aten.mish)
384
+ @_inplace_wrapper
385
+ @out_wrapper()
386
+ @elementwise_type_promotion_wrapper(
387
+ type_promoting_args=("a",),
388
+ type_promotion_kind=ELEMENTWISE_TYPE_PROMOTION_KIND.DEFAULT,
389
+ )
390
+ def mish(a: TensorLikeType, inplace: bool = False) -> TensorLikeType:
391
+ """
392
+ Reference implementation of torch.nn.functional.mish
393
+ """
394
+
395
+ if inplace:
396
+ raise NotImplementedError
397
+ return a * torch.tanh(torch.nn.functional.softplus(a))
398
+
399
+
400
+ @register_decomposition(aten.selu)
401
+ @_inplace_wrapper
402
+ @out_wrapper()
403
+ @elementwise_type_promotion_wrapper(
404
+ type_promoting_args=("a",),
405
+ type_promotion_kind=ELEMENTWISE_TYPE_PROMOTION_KIND.DEFAULT,
406
+ )
407
+ def selu(a: TensorLikeType, inplace: bool = False) -> TensorLikeType:
408
+ """
409
+ Reference implementation of torch.nn.functional.selu
410
+ """
411
+ if inplace:
412
+ raise NotImplementedError
413
+
414
+ alpha = 1.6732632423543772848170429916717
415
+ scale = 1.0507009873554804934193349852946
416
+
417
+ rhs = alpha * torch.expm1(a)
418
+
419
+ return scale * torch.where(a > 0, a, rhs)
420
+
421
+
422
+ # Forwarding alias: the functional variant doesn't support the out kwarg
423
+ # CompositeImplicitAutograd - don't register decomp
424
+ def softmax(
425
+ a: TensorLikeType,
426
+ dim: Optional[int] = None,
427
+ _stacklevel: int = 3, # for compat when using TorchRefsMode(strict=True)
428
+ dtype: Optional[torch.dtype] = None,
429
+ ) -> TensorLikeType:
430
+ # The error is for compat with regular PyTorch, which has this behavior
431
+ # deprecated. For PrimTorch, it's fine to drop support for deprecated
432
+ # behavior because it requires explicit opt in. This error is to inform
433
+ # users how to update their calls.
434
+ torch._check(dim is not None, lambda: "implicit dim not supported, use dim=X")
435
+ return torch.softmax(a=a, dim=dim, dtype=dtype) # type: ignore[call-overload]
436
+
437
+
438
+ # CompositeImplicitAutograd - don't register decomp
439
+ def softmin(
440
+ a: TensorLikeType,
441
+ dim: Optional[int] = None,
442
+ _stacklevel: int = 3, # for compat when using TorchRefsMode(strict=True)
443
+ dtype: Optional[torch.dtype] = None,
444
+ ) -> TensorLikeType:
445
+ # The error is for compat with regular PyTorch, which has this behavior
446
+ # deprecated. For PrimTorch, it's fine to drop support for deprecated
447
+ # behavior because it requires explicit opt in. This error is to inform
448
+ # users how to update their calls.
449
+ torch._check(dim is not None, lambda: "implicit dim not supported, use dim=X")
450
+ return torch.softmax(a=-a, dim=dim, dtype=dtype) # type: ignore[call-overload]
451
+
452
+
453
+ # softplus is implemented specially because it has beta and threshold arguments
454
+ @register_decomposition(aten.softplus)
455
+ @_inplace_wrapper
456
+ @out_wrapper()
457
+ @elementwise_type_promotion_wrapper(
458
+ type_promoting_args=("a",),
459
+ type_promotion_kind=ELEMENTWISE_TYPE_PROMOTION_KIND.DEFAULT,
460
+ )
461
+ def softplus(
462
+ a: TensorLikeType,
463
+ beta: Optional[NumberType] = None,
464
+ threshold: NumberType = 20,
465
+ inplace: bool = False,
466
+ ) -> TensorLikeType:
467
+ """
468
+ Reference implementation of torch.nn.functional.softplus
469
+ """
470
+
471
+ if inplace:
472
+ raise NotImplementedError
473
+
474
+ rhs: TensorLikeType
475
+ if beta is not None:
476
+ python_type = utils.dtype_to_type(a.dtype)
477
+ if not utils.is_weakly_lesser_type(type(beta), python_type):
478
+ msg = f"beta argument of type {type(beta)} cannot be safely cast to type {python_type}!"
479
+ raise ValueError(msg)
480
+ scaled_input = a * beta
481
+ rhs = torch.true_divide(torch.log1p(torch.exp(scaled_input)), beta) # type: ignore[arg-type]
482
+
483
+ else:
484
+ scaled_input = a
485
+ rhs = torch.log1p(torch.exp(scaled_input))
486
+
487
+ return torch.where(scaled_input > threshold, a, rhs)
488
+
489
+
490
+ @aten.hardshrink.default.py_impl(DispatchKey.Autograd)
491
+ @register_decomposition(aten.hardshrink)
492
+ @out_wrapper()
493
+ def hardshrink(a: TensorLikeType, lambd: float = 0.5):
494
+ # Formula for reference,
495
+ # hardshrink(x) = x if x > lambd
496
+ # = x if x < -lambd
497
+ # = 0 otherwise
498
+ return torch.where(torch.abs(a) <= lambd, 0, a)
499
+
500
+
501
+ @aten.softshrink.default.py_impl(DispatchKey.Autograd)
502
+ @register_decomposition(aten.softshrink)
503
+ @out_wrapper()
504
+ def softshrink(a: TensorLikeType, lambd: float = 0.5):
505
+ # Formula for reference,
506
+ # softshrink(x) = x - lambd if x > lambd
507
+ # = x + lambd if x < -lambd
508
+ # = 0 otherwise
509
+ torch._check(
510
+ lambd >= 0,
511
+ lambda: f"lambda must be greater or equal to 0, but found to be {lambd}",
512
+ )
513
+ # We implement this in one torch.where to generate better code in the backward
514
+ # see https://github.com/pytorch/pytorch/pull/107052#discussion_r1293748211
515
+ # We multiply by 0 for dealing with nans
516
+ return torch.where(torch.abs(a) > lambd, a - torch.sign(a) * lambd, a * 0)
517
+
518
+
519
+ # Losses
520
+ def _reduction_int_to_str(reduction: int) -> str:
521
+ from torch._decomp.decompositions import Reduction
522
+
523
+ if reduction == Reduction.NONE.value:
524
+ return "none"
525
+ elif reduction == Reduction.MEAN.value:
526
+ return "mean"
527
+ elif reduction == Reduction.SUM.value:
528
+ return "sum"
529
+ else:
530
+ raise ValueError(f"{reduction} is not a valid value for reduction")
531
+
532
+
533
+ def _apply_loss_reduction(loss: TensorLikeType, reduction: str) -> TensorLikeType:
534
+ if reduction == "sum":
535
+ return torch.sum(loss)
536
+ elif reduction == "mean":
537
+ return torch.mean(loss)
538
+ else: # reduction == "none"
539
+ return loss
540
+
541
+
542
+ def _check_reduction_value(reduction: str):
543
+ if reduction not in ("mean", "sum", "none"):
544
+ raise ValueError(f"{reduction} is not a valid value for reduction")
545
+
546
+
547
+ # This helper function maps depreciated arguments, "size_average" and "reduce"
548
+ # to their corresponding "reduction" string argument
549
+ def _get_string_reduction_arg(
550
+ *, size_average: Optional[bool], reduce: Optional[bool]
551
+ ) -> str:
552
+ if size_average is None:
553
+ size_average = True
554
+ if reduce is None:
555
+ reduce = True
556
+ if size_average and reduce:
557
+ ret = "mean"
558
+ elif reduce:
559
+ ret = "sum"
560
+ else:
561
+ ret = "none"
562
+ return ret
563
+
564
+
565
+ # CompositeImplicitAutograd - don't register decomp
566
+ @elementwise_type_promotion_wrapper(
567
+ type_promoting_args=("input", "target"),
568
+ type_promotion_kind=ELEMENTWISE_TYPE_PROMOTION_KIND.COMPLEX_TO_FLOAT,
569
+ )
570
+ def l1_loss(
571
+ input: TensorLikeType,
572
+ target: TensorLikeType,
573
+ size_average: Optional[bool] = None,
574
+ reduce: Optional[bool] = None,
575
+ reduction: str = "mean",
576
+ ) -> TensorLikeType:
577
+ """
578
+ Reference implementation of torch.nn.functional.l1_loss
579
+ """
580
+ if size_average is not None or reduce is not None:
581
+ # TODO: Raise exception instead of converting value. This is only for
582
+ # primTorch since it can drop support for deprecated arguments.
583
+ # msg = "size_average and reduce args are deprecated, please use reduction argument."
584
+ reduction = _get_string_reduction_arg(size_average=size_average, reduce=reduce)
585
+ _check_reduction_value(reduction)
586
+ loss = torch.abs(input - target)
587
+ return _apply_loss_reduction(loss, reduction)
588
+
589
+
590
+ @elementwise_type_promotion_wrapper(
591
+ type_promoting_args=("input", "target"),
592
+ type_promotion_kind=ELEMENTWISE_TYPE_PROMOTION_KIND.COMPLEX_TO_FLOAT,
593
+ )
594
+ def smooth_l1_loss(
595
+ input: TensorLikeType,
596
+ target: TensorLikeType,
597
+ size_average: Optional[bool] = None,
598
+ reduce: Optional[bool] = None,
599
+ reduction: str = "mean",
600
+ beta: float = 1.0,
601
+ ) -> TensorLikeType:
602
+ """
603
+ Reference implementation of torch.nn.functional.smooth_l1_loss
604
+ """
605
+ if size_average is not None or reduce is not None:
606
+ # TODO: Raise exception instead of converting value. This is only for
607
+ # primTorch since it can drop support for deprecated arguments.
608
+ # msg = "size_average and reduce args are deprecated, please use reduction argument."
609
+ reduction = _get_string_reduction_arg(size_average=size_average, reduce=reduce)
610
+ _check_reduction_value(reduction)
611
+
612
+ if beta == 0.0:
613
+ return torch.nn.functional.l1_loss(
614
+ input, target, size_average=size_average, reduce=reduce, reduction=reduction
615
+ )
616
+ else:
617
+ loss = torch.abs(input - target)
618
+ loss = torch.where(loss < beta, 0.5 * loss**2 / beta, loss - 0.5 * beta)
619
+ return _apply_loss_reduction(loss, reduction)
620
+
621
+
622
+ # Forwarding alias: the functional variant doesn't support the out kwarg
623
+ # CompositeImplicitAutograd - don't register decomp
624
+ def log_softmax(
625
+ a: TensorLikeType,
626
+ dim: Optional[int] = None,
627
+ _stacklevel: int = 3, # for compat when using TorchRefsMode(strict=True)
628
+ dtype: Optional[torch.dtype] = None,
629
+ ) -> TensorLikeType:
630
+ # The error is for compat with regular PyTorch, which has this behavior
631
+ # deprecated. For PrimTorch, it's fine to drop support for deprecated
632
+ # behavior because it requires explicit opt in. This error is to inform
633
+ # users how to update their calls.
634
+ torch._check(dim is not None, lambda: "implicit dim not supported, use dim=X")
635
+ return torch.log_softmax(a=a, dim=dim, dtype=dtype) # type: ignore[call-overload]
636
+
637
+
638
+ @register_decomposition(aten.margin_ranking_loss)
639
+ def margin_ranking_loss(
640
+ input1: TensorLikeType,
641
+ input2: TensorLikeType,
642
+ target: TensorLikeType,
643
+ margin: float = 0.0,
644
+ reduction: str = "mean",
645
+ ) -> TensorLikeType:
646
+ # loss_without_reduction = max(0, -target * (input1 - input2) + margin)
647
+ if input1.ndim != input2.ndim or input1.ndim != target.ndim:
648
+ raise RuntimeError(
649
+ "margin_ranking_loss : All input tensors should have same dimension but got sizes: "
650
+ f"input1: {input1.shape}, input2: {input2.shape}, target: {target.shape} "
651
+ )
652
+ _check_reduction_value(reduction)
653
+ loss = torch.clamp_min(-target * (input1 - input2) + margin, 0)
654
+ return _apply_loss_reduction(loss, reduction)
655
+
656
+
657
+ @elementwise_type_promotion_wrapper(
658
+ type_promoting_args=("input", "target"),
659
+ type_promotion_kind=ELEMENTWISE_TYPE_PROMOTION_KIND.COMPLEX_TO_FLOAT,
660
+ )
661
+ def mse_loss(
662
+ input: TensorLikeType,
663
+ target: TensorLikeType,
664
+ size_average: Optional[bool] = None,
665
+ reduce: Optional[bool] = None,
666
+ reduction: str = "mean",
667
+ ) -> TensorLikeType:
668
+ if size_average is not None or reduce is not None:
669
+ # TODO: Raise exception instead of converting value. This is only for
670
+ # primTorch since it can drop support for deprecated arguments.
671
+ # msg = "size_average and reduce args are deprecated, please use reduction argument."
672
+ reduction = _get_string_reduction_arg(size_average=size_average, reduce=reduce)
673
+ _check_reduction_value(reduction)
674
+ loss = torch.pow(input - target, 2)
675
+ return _apply_loss_reduction(loss, reduction)
676
+
677
+
678
+ @register_decomposition(aten.hinge_embedding_loss)
679
+ def hinge_embedding_loss(
680
+ input: TensorLikeType,
681
+ target: TensorLikeType,
682
+ margin: float = 1.0,
683
+ reduction: str = "mean",
684
+ ) -> TensorLikeType:
685
+ # loss_without_reduction = input if y == 1
686
+ # = max(0, margin - input) if y == -1
687
+ _check_reduction_value(reduction)
688
+ margin_clamp = torch.clamp_min(margin - input, 0)
689
+ output_margin = torch.where(target != 1, margin_clamp, 0)
690
+ output_self = torch.where(target != -1, input, 0)
691
+ loss = output_margin + output_self
692
+ return _apply_loss_reduction(loss, reduction)
693
+
694
+
695
+ def _nll_loss_nd(
696
+ input: TensorLikeType,
697
+ target: TensorLikeType,
698
+ weight: Optional[TensorLikeType],
699
+ reduction: str,
700
+ ignore_index: int,
701
+ ) -> TensorLikeType:
702
+ torch._check(
703
+ input.ndim > 0 and input.ndim <= 3,
704
+ lambda: f"Expected input dimension to be either [1, 2, 3] but received {input.ndim}.",
705
+ )
706
+
707
+ torch._check(
708
+ (input.ndim == 1) or (input.shape[0] == target.shape[0]),
709
+ lambda: f"Expected input batch size {input.shape[0]} to match target batch size {target.shape[0]}.",
710
+ )
711
+
712
+ _check_reduction_value(reduction)
713
+
714
+ flat_target = torch.flatten(target)
715
+ ignore_classes_mask = torch.eq(flat_target, ignore_index)
716
+
717
+ # TODO: Enable data-dependent checks with debug mode
718
+ # TODO: This check does not work with FakeTensor inputs; See Issue #85834
719
+ # Explicit cast for class_check to bool; See Issue #78071
720
+ """
721
+ from torch._subclasses.fake_tensor import FakeTensor
722
+ num_classes = input.shape[1] if input.ndim > 1 else input.shape[0]
723
+ valid_classes_mask = torch.logical_and(
724
+ (flat_target >= 0), (flat_target < num_classes)
725
+ )
726
+ class_check = torch.all(torch.logical_or(ignore_classes_mask, valid_classes_mask))
727
+ torch._check(
728
+ isinstance(target, FakeTensor) or bool(class_check.item()),
729
+ lambda: "A target class is out-of-bounds and not the ignore index.",
730
+ )
731
+ """
732
+
733
+ ignore_class_weight = torch.scalar_tensor(0, dtype=input.dtype, device=input.device)
734
+ class_weight = (
735
+ torch.scalar_tensor(1, dtype=input.dtype, device=input.device)
736
+ if weight is None
737
+ else weight[flat_target]
738
+ )
739
+ current_weight = torch.where(
740
+ ignore_classes_mask,
741
+ ignore_class_weight,
742
+ class_weight,
743
+ )
744
+
745
+ if input.ndim == 1:
746
+ # implicit batch size = 1
747
+ # input (1 batch size, C classes)
748
+ loss = -input[target] * current_weight
749
+ elif input.ndim == 2:
750
+ # input (N batch size, C classes)
751
+ batch_size = input.shape[0]
752
+ loss = -input[torch.arange(batch_size), target] * current_weight
753
+ else:
754
+ # 3D case (N batch size, C classe, K dimensions)
755
+ # input (N batch size, C classes, K)
756
+ batch_size = input.shape[0]
757
+ extent = input.shape[2]
758
+ numel = batch_size * extent
759
+ indices = torch.arange(numel)
760
+ bdx = indices // extent
761
+ kdx = indices % extent
762
+ loss = -input[bdx, flat_target, kdx] * current_weight
763
+ loss = torch.reshape(loss, target.shape)
764
+
765
+ if reduction == "none":
766
+ return loss
767
+ elif reduction == "sum":
768
+ return torch.sum(loss)
769
+ else:
770
+ # calculate weighted mean of the loss function
771
+ return torch.sum(loss) / torch.sum(current_weight)
772
+
773
+
774
+ @register_decomposition(aten.nll_loss)
775
+ @out_wrapper()
776
+ @elementwise_type_promotion_wrapper(
777
+ type_promoting_args=("input",),
778
+ type_promotion_kind=ELEMENTWISE_TYPE_PROMOTION_KIND.DEFAULT,
779
+ )
780
+ def nll_loss(
781
+ input: TensorLikeType,
782
+ target: TensorLikeType,
783
+ weight: Optional[TensorLikeType] = None,
784
+ size_average: Optional[bool] = None,
785
+ ignore_index: int = -100,
786
+ reduce: Optional[bool] = None,
787
+ reduction: str = "mean",
788
+ ) -> TensorLikeType:
789
+ """
790
+ Reference implementation of torch.nn.functional.nll_loss
791
+ """
792
+ torch._check(
793
+ input.ndim > 0,
794
+ lambda: f"Expected input tensor to have 1 or more dimensions (got {input.ndim})",
795
+ )
796
+
797
+ # TODO: raise exception instead of converting value
798
+ # msg = "size_average and reduce args are deprecated, please use reduction argument."
799
+ # Convert these options for consistency with the eager mode
800
+ if size_average is not None or reduce is not None:
801
+ reduction = _get_string_reduction_arg(size_average=size_average, reduce=reduce)
802
+
803
+ # The expected behavior when the target and input have zero elements:
804
+ # reduction = 'none' --- tensor([])
805
+ # reduction = 'sum' --- tensor(0.)
806
+ # reduction = 'mean' --- tensor(nan)
807
+ # Mean reduction on empty tensors produces NaN. See the discussion in
808
+ # https://github.com/pytorch/pytorch/pull/64572#issuecomment-926504162
809
+ if input.numel() == 0 and target.numel() == 0:
810
+ if reduction == "none":
811
+ return torch.zeros_like(target)
812
+ elif reduction == "sum":
813
+ return torch.empty_like(target)
814
+ else:
815
+ return torch.full_like(target, float("nan"))
816
+
817
+ # The _nll_loss_nd helper function handles the most common cases.
818
+ # ndim == 1 (Single Example)
819
+ # => Batch Size: 1, Input: (C), Target: ()
820
+ # ndim == 2 (k = 1)
821
+ # => Batch Size: N, Input: (N, C), Target: (N)
822
+ # ndim == 3 (k > 1)
823
+ # => Batch Size: N, Input: (N, C, K), Target: (N, K)
824
+ if input.ndim <= 3:
825
+ return _nll_loss_nd(input, target, weight, reduction, ignore_index)
826
+
827
+ # For ndim > 3, we reshape the input and target to 3-D case.
828
+ # Input (N batch-size, C classes, k-dimensions)
829
+ # Target (N batch-size, k-dimensions)
830
+ torch._check(
831
+ input.ndim > 0 and target.ndim > 0 and target.shape[1:] == input.shape[2:],
832
+ lambda: (
833
+ "Expected input and target to both have ndim > 0 and "
834
+ "target.shape[1:] == input.shape[2:], but got "
835
+ f"target.shape {target.shape} and input.shape {input.shape}"
836
+ ),
837
+ )
838
+
839
+ batch_size = input.shape[0]
840
+ num_classes = input.shape[1]
841
+ out_size = [batch_size] + list(target.shape[1:])
842
+
843
+ input = torch.reshape(input, [batch_size, num_classes, -1])
844
+ target = torch.reshape(target, [batch_size, -1])
845
+ if reduction != "none":
846
+ return _nll_loss_nd(input, target, weight, reduction, ignore_index)
847
+ else:
848
+ result = _nll_loss_nd(input, target, weight, reduction, ignore_index)
849
+ # reshape flattened inner-dim to original k-dimensions
850
+ return torch.reshape(result, out_size)
851
+
852
+
853
+ # TODO: This ref supports int reduction and out kwarg to be compatible with ATen:
854
+ # https://github.com/pytorch/pytorch/issues/83931
855
+ # TODO: Could be rewritten to support complex:
856
+ # https://github.com/pytorch/pytorch/pull/85041
857
+ @register_decomposition(aten.huber_loss)
858
+ @out_wrapper()
859
+ @elementwise_type_promotion_wrapper(
860
+ type_promoting_args=("input", "target"),
861
+ type_promotion_kind=ELEMENTWISE_TYPE_PROMOTION_KIND.DEFAULT,
862
+ )
863
+ def huber_loss(
864
+ input: TensorLikeType,
865
+ target: TensorLikeType,
866
+ reduction: Union[str, int] = "mean",
867
+ delta: float = 1.0,
868
+ ) -> TensorLikeType:
869
+ """
870
+ Reference implementation of torch.nn.functional.huber_loss
871
+ """
872
+ if type(reduction) is int:
873
+ reduction = _reduction_int_to_str(reduction)
874
+ _check_reduction_value(reduction) # type: ignore[arg-type]
875
+ torch._check(
876
+ delta > 0,
877
+ lambda: "huber_loss does not support non-positive values for delta.",
878
+ )
879
+ z = (input - target).abs()
880
+ loss = torch.where(z < delta, 0.5 * z * z, delta * (z - 0.5 * delta))
881
+ return _apply_loss_reduction(loss, reduction) # type: ignore[arg-type]
882
+
883
+
884
+ # tanhshrink does not use _make_elementwise_unary_reference because it does not support out
885
+ @elementwise_unary_scalar_wrapper
886
+ @elementwise_type_promotion_wrapper(
887
+ type_promoting_args=("a",),
888
+ type_promotion_kind=utils.ELEMENTWISE_TYPE_PROMOTION_KIND.INT_TO_FLOAT,
889
+ )
890
+ def tanhshrink(a: TensorLikeType) -> TensorLikeType:
891
+ """
892
+ Reference implementation of torch.nn.functional.tanhshrink
893
+ """
894
+ if not isinstance(a, TensorLike):
895
+ raise RuntimeError(
896
+ "Expected a tensor input for an elementwise unary operation!"
897
+ )
898
+ return a - torch.tanh(a)
899
+
900
+
901
+ @register_decomposition(aten.threshold)
902
+ @_inplace_wrapper
903
+ @out_wrapper()
904
+ @elementwise_type_promotion_wrapper(
905
+ type_promoting_args=("a",),
906
+ type_promotion_kind=ELEMENTWISE_TYPE_PROMOTION_KIND.DEFAULT,
907
+ )
908
+ def threshold(
909
+ a: TensorLikeType,
910
+ threshold: NumberType,
911
+ value: Union[bool, int, float],
912
+ inplace: bool = False,
913
+ ) -> TensorLikeType:
914
+ """
915
+ Reference implementation of torch.nn.functional.threshold
916
+ """
917
+
918
+ if inplace:
919
+ raise NotImplementedError
920
+
921
+ return torch.where(a <= threshold, value, a)
922
+
923
+
924
+ # CompositeImplicitAutograd - don't register decomp
925
+ # No elementwise type promotion - core op doesn't explicitly type promote
926
+ def triplet_margin_loss(
927
+ anchor: TensorLikeType,
928
+ positive: TensorLikeType,
929
+ negative: TensorLikeType,
930
+ margin: float = 1.0,
931
+ p: float = 2,
932
+ eps: float = 1e-6,
933
+ swap: bool = False,
934
+ size_average: Optional[bool] = None,
935
+ reduce: Optional[bool] = None,
936
+ reduction: str = "mean",
937
+ ) -> TensorLikeType:
938
+ if size_average is not None or reduce is not None:
939
+ # TODO: Raise exception instead of converting value. This is only for
940
+ # primTorch since it can drop support for deprecated arguments.
941
+ # msg = "size_average and reduce args are deprecated, please use reduction argument."
942
+ reduction = _get_string_reduction_arg(size_average=size_average, reduce=reduce)
943
+
944
+ if margin <= 0:
945
+ raise ValueError(f"margin must be greater than 0, got {margin}")
946
+
947
+ # torch.nn.functional.triplet_margin_with_distance_loss has no ref defined
948
+ # since it's a pure Python implementation. Use this helper instead.
949
+ return _triplet_margin_with_distance_loss(
950
+ anchor=anchor,
951
+ positive=positive,
952
+ negative=negative,
953
+ distance_function=lambda x, y: torch.pairwise_distance(x, y, p, eps),
954
+ margin=margin,
955
+ swap=swap,
956
+ reduction=reduction,
957
+ )
958
+
959
+
960
+ # Pure Python impl - don't register decomp and don't add a ref. Defined as a
961
+ # helper here since triplet_margin_loss can be nicely implemented with it.
962
+ def _triplet_margin_with_distance_loss(
963
+ anchor: TensorLikeType,
964
+ positive: TensorLikeType,
965
+ negative: TensorLikeType,
966
+ *,
967
+ distance_function: Optional[
968
+ Callable[[TensorLikeType, TensorLikeType], TensorLikeType]
969
+ ] = None,
970
+ margin: float = 1.0,
971
+ swap: bool = False,
972
+ reduction: str = "mean",
973
+ ) -> TensorLikeType:
974
+ _check_reduction_value(reduction)
975
+
976
+ a_dim = anchor.ndim
977
+ p_dim = positive.ndim
978
+ n_dim = negative.ndim
979
+ torch._check(
980
+ a_dim == p_dim and p_dim == n_dim,
981
+ lambda: (
982
+ f"The anchor, positive, and negative tensors are expected to have "
983
+ f"the same number of dimensions, but got: anchor {a_dim}D, "
984
+ f"positive {p_dim}D, and negative {n_dim}D inputs"
985
+ ),
986
+ )
987
+
988
+ if distance_function is None:
989
+ distance_function = torch.pairwise_distance
990
+
991
+ dist_pos = distance_function(anchor, positive)
992
+ dist_neg = distance_function(anchor, negative)
993
+ # The distance swap is described in the paper "Learning shallow
994
+ # convolutional feature descriptors with triplet losses" by V. Balntas, E.
995
+ # Riba et al. If True, and if the positive example is closer to the
996
+ # negative example than the anchor is, swaps the positive example and the
997
+ # anchor in the loss computation.
998
+ if swap:
999
+ dist_swap = distance_function(positive, negative)
1000
+ dist_neg = torch.minimum(dist_neg, dist_swap)
1001
+ loss = torch.clamp_min(margin + dist_pos - dist_neg, 0)
1002
+ return _apply_loss_reduction(loss, reduction)
1003
+
1004
+
1005
+ @register_decomposition(aten.hardtanh)
1006
+ @_inplace_wrapper
1007
+ @out_wrapper()
1008
+ @elementwise_unary_scalar_wrapper
1009
+ @elementwise_type_promotion_wrapper(
1010
+ type_promoting_args=("a"),
1011
+ type_promotion_kind=ELEMENTWISE_TYPE_PROMOTION_KIND.DEFAULT,
1012
+ )
1013
+ def hardtanh(
1014
+ a: TensorLikeType,
1015
+ min_val: NumberType = -1,
1016
+ max_val: NumberType = 1,
1017
+ inplace: bool = False,
1018
+ ) -> TensorLikeType:
1019
+ """
1020
+ Reference implementation of torch.nn.functional.hardtanh
1021
+ """
1022
+ if inplace:
1023
+ raise NotImplementedError
1024
+ if utils.is_boolean_dtype(a.dtype):
1025
+ raise RuntimeError("Bool inputs not supported for hardtanh")
1026
+
1027
+ # preserve legacy behavior of boundaries not causing type promotion
1028
+ if utils.is_integer_dtype(a.dtype):
1029
+ min_val = int(min_val) # type: ignore[arg-type]
1030
+ max_val = int(max_val) # type: ignore[arg-type]
1031
+ if not (a.dtype != torch.uint8 or (min_val >= 0 and max_val >= 0)):
1032
+ raise RuntimeError(
1033
+ "Cannot do hardtanh on an unsigned type with negative limits"
1034
+ )
1035
+
1036
+ if min_val > max_val: # type: ignore[operator]
1037
+ raise ValueError("min_val cannot be greater than max_val")
1038
+
1039
+ return torch.clamp(a, min_val, max_val) # type: ignore[arg-type]
1040
+
1041
+
1042
+ @register_decomposition(aten.gelu)
1043
+ @out_wrapper()
1044
+ @elementwise_unary_scalar_wrapper
1045
+ @elementwise_type_promotion_wrapper(
1046
+ type_promoting_args=("a",),
1047
+ type_promotion_kind=utils.ELEMENTWISE_TYPE_PROMOTION_KIND.DEFAULT,
1048
+ )
1049
+ def gelu(a: TensorLikeType, approximate: str = "none") -> TensorLikeType:
1050
+ """
1051
+ Reference implementation of torch.nn.functional.gelu
1052
+ """
1053
+ if not isinstance(a, TensorLike):
1054
+ raise RuntimeError(
1055
+ "Expected a tensor input for an elementwise unary operation!"
1056
+ )
1057
+ M_SQRT2 = 1.41421356237309504880
1058
+ M_SQRT1_2 = 0.70710678118654752440
1059
+ M_2_SQRTPI = 1.12837916709551257390
1060
+ if approximate == "tanh":
1061
+ kBeta = M_SQRT2 * M_2_SQRTPI * 0.5
1062
+ kKappa = 0.044715
1063
+ a_cube = a * a * a
1064
+ inner = kBeta * (a + kKappa * a_cube)
1065
+ return 0.5 * a * (1 + torch.tanh(inner))
1066
+ elif approximate == "none":
1067
+ kAlpha = M_SQRT1_2
1068
+ return a * 0.5 * (1 + torch.erf(a * kAlpha))
1069
+ else:
1070
+ raise RuntimeError("approximate argument must be either none or tanh.")
1071
+
1072
+
1073
+ # CompositeImplicitAutograd - don't register decomp
1074
+ @elementwise_type_promotion_wrapper(
1075
+ type_promoting_args=("input", "target"),
1076
+ type_promotion_kind=ELEMENTWISE_TYPE_PROMOTION_KIND.INT_TO_FLOAT,
1077
+ )
1078
+ def poisson_nll_loss(
1079
+ input: TensorLikeType,
1080
+ target: TensorLikeType,
1081
+ log_input: bool = True,
1082
+ full: bool = False,
1083
+ size_average: Optional[bool] = None,
1084
+ eps: float = 1e-8,
1085
+ reduce: Optional[bool] = None,
1086
+ reduction: str = "mean",
1087
+ ) -> TensorLikeType:
1088
+ """
1089
+ Reference implementation of torch.nn.functional.poisson_nll_loss
1090
+ """
1091
+ if size_average is not None or reduce is not None:
1092
+ # TODO: Raise exception instead of converting value. This is only for
1093
+ # primTorch since it can drop support for deprecated arguments.
1094
+ # msg = "size_average and reduce args are deprecated, please use reduction argument."
1095
+ reduction = _get_string_reduction_arg(size_average=size_average, reduce=reduce)
1096
+ _check_reduction_value(reduction)
1097
+ if log_input:
1098
+ loss = torch.exp(input) - target * input
1099
+ else:
1100
+ loss = input - target * torch.log(input + eps)
1101
+
1102
+ if full:
1103
+ stirling_term = (
1104
+ target * torch.log(target) - target + 0.5 * torch.log(2 * torch.pi * target)
1105
+ )
1106
+ # avoid inplace add
1107
+ loss = loss + stirling_term.masked_fill(target <= 1, 0)
1108
+ return _apply_loss_reduction(loss, reduction)
1109
+
1110
+
1111
+ @register_decomposition(aten.prelu)
1112
+ @elementwise_type_promotion_wrapper(
1113
+ type_promoting_args=("a", "weight"),
1114
+ type_promotion_kind=utils.ELEMENTWISE_TYPE_PROMOTION_KIND.DEFAULT,
1115
+ )
1116
+ def prelu(a: TensorLikeType, weight: TensorLikeType) -> TensorLikeType:
1117
+ """
1118
+ Reference implementation of torch.nn.functional.prelu
1119
+ """
1120
+ torch._check(
1121
+ isinstance(a, TensorLike),
1122
+ lambda: f"prelu: Expected `a` to be tensor, but got: {type(a)}",
1123
+ )
1124
+ torch._check(
1125
+ isinstance(weight, TensorLike),
1126
+ lambda: f"prelu: Expected `weight` to be tensor, but got: {type(weight)}",
1127
+ )
1128
+
1129
+ if weight.numel() != 1:
1130
+ torch._check(a.ndim > 0, lambda: "Not allow zero-dim input tensor.")
1131
+ channel_size = a.shape[1] if a.ndim >= 2 else 1
1132
+ torch._check(
1133
+ weight.numel() == channel_size,
1134
+ lambda: f"Mismatch of parameter numbers and input channel size. Found parameter numbers ="
1135
+ f" {weight.numel()} and channel size = {channel_size}.",
1136
+ )
1137
+
1138
+ torch._check(
1139
+ weight.ndim == 0 or weight.ndim == 1,
1140
+ lambda: f"prelu: Expected `weight` to be a scalar or 1D tensor, but got: "
1141
+ f"ndim = {weight.ndim}",
1142
+ )
1143
+ if a.ndim == 0:
1144
+ weight = weight[0] if weight.ndim == 1 else weight
1145
+ else:
1146
+ weight = prims.broadcast_in_dim(
1147
+ weight, a.shape, () if weight.ndim == 0 else (0 if a.ndim == 1 else 1,)
1148
+ )
1149
+
1150
+ return torch.where(a > 0, a, a * weight)
1151
+
1152
+
1153
+ @register_decomposition(aten.relu6)
1154
+ @_inplace_wrapper
1155
+ @out_wrapper()
1156
+ def relu6(a: TensorLikeType, inplace: bool = False) -> TensorLikeType:
1157
+ """
1158
+ Reference implementation of torch.nn.functional.relu6
1159
+ """
1160
+ if inplace:
1161
+ raise NotImplementedError
1162
+
1163
+ # See https://github.com/pytorch/pytorch/pull/81142#discussion_r918220126
1164
+ # It may be better to use clamp here, but we use hardtanh to replicate
1165
+ # the behavior of the existing implementation
1166
+ return torch.nn.functional.hardtanh(a, 0, 6)
1167
+
1168
+
1169
+ @register_decomposition(aten.glu)
1170
+ @out_wrapper()
1171
+ @elementwise_type_promotion_wrapper(
1172
+ type_promoting_args=("a",),
1173
+ type_promotion_kind=utils.ELEMENTWISE_TYPE_PROMOTION_KIND.DEFAULT,
1174
+ )
1175
+ def glu(a: TensorLikeType, dim: int = -1) -> TensorLikeType:
1176
+ dim = utils.canonicalize_dims(a.ndim, dim)
1177
+ torch._check(
1178
+ a.shape[dim] % 2 == 0,
1179
+ lambda: f"Halving dimension must be even, but dimension {dim} is size {a.shape[dim]}",
1180
+ )
1181
+ b, c = torch.tensor_split(a, 2, dim)
1182
+
1183
+ return b * torch.sigmoid(c)
1184
+
1185
+
1186
+ @register_decomposition(aten.pairwise_distance)
1187
+ @out_wrapper()
1188
+ def pairwise_distance(
1189
+ x1: TensorLikeType,
1190
+ x2: TensorLikeType,
1191
+ p: NumberType = 2.0,
1192
+ eps: NumberType = 1e-6,
1193
+ keepdim=False,
1194
+ ) -> TensorLikeType:
1195
+ return torch.linalg.vector_norm(x1 - x2 + eps, ord=p, dim=-1, keepdim=keepdim)
1196
+
1197
+
1198
+ @register_decomposition(aten.pdist)
1199
+ @out_wrapper()
1200
+ @elementwise_type_promotion_wrapper(
1201
+ type_promoting_args=("a",),
1202
+ type_promotion_kind=utils.ELEMENTWISE_TYPE_PROMOTION_KIND.DEFAULT,
1203
+ )
1204
+ def pdist(a: TensorLikeType, p: float = 2) -> TensorLikeType:
1205
+ torch._check(a.ndim == 2, lambda: f"pdist only supports 2D tensors, got: {a.ndim}D")
1206
+ torch._check(p >= 0, lambda: "pdist only supports non-negative p values")
1207
+ # For p == 2 we can use an efficient implementation, but other values of p
1208
+ # require creating a much bigger tensor for an intermediate step
1209
+ if p == 2:
1210
+ aTa = torch.mm(a, a.T)
1211
+ aTa_diag = torch.diag(aTa)
1212
+ t = torch.sqrt(torch.clamp(aTa_diag + aTa_diag.unsqueeze(-1) - 2 * aTa, min=0))
1213
+ else:
1214
+ t = torch.linalg.vector_norm(a.unsqueeze(1) - a, ord=p, dim=2)
1215
+ i = torch.triu_indices(t.shape[0], t.shape[1], offset=1, device=a.device)
1216
+ return t.flatten().index_select(0, i[0] * t.shape[0] + i[1])
1217
+
1218
+
1219
+ @register_decomposition(aten.pixel_shuffle)
1220
+ @out_wrapper()
1221
+ def pixel_shuffle(self: Tensor, upscale_factor: int):
1222
+ torch._check(
1223
+ self.dim() >= 3,
1224
+ lambda: f"pixel_shuffle expects input to have at least 3 dimensions, but got input with {self.dim} dimension(s)",
1225
+ )
1226
+ batch = self.shape[:-3]
1227
+ C_out = self.shape[-3] // upscale_factor**2
1228
+ HW_out = (self.shape[-2] * upscale_factor, self.shape[-1] * upscale_factor)
1229
+ n = len(batch)
1230
+ B_dims = range(n)
1231
+ C_dim, r1_dim, r2_dim, H_dim, W_dim = range(n, n + 5)
1232
+ return (
1233
+ self.view(
1234
+ *batch,
1235
+ C_out,
1236
+ upscale_factor,
1237
+ upscale_factor,
1238
+ self.shape[-2],
1239
+ self.shape[-1],
1240
+ )
1241
+ .permute(*B_dims, C_dim, H_dim, r1_dim, W_dim, r2_dim)
1242
+ .reshape(*batch, C_out, *HW_out)
1243
+ .clone(memory_format=utils.suggest_memory_format(self))
1244
+ )
1245
+
1246
+
1247
+ @register_decomposition(aten.pixel_unshuffle)
1248
+ @out_wrapper()
1249
+ def pixel_unshuffle(self: Tensor, downscale_factor: int):
1250
+ torch._check(
1251
+ self.dim() >= 3,
1252
+ lambda: f"pixel_unshuffle expects input to have at least 3 dimensions, but got input with {self.dim} dimension(s)",
1253
+ )
1254
+ batch = self.shape[:-3]
1255
+ C_out = self.shape[-3] * downscale_factor**2
1256
+ HW_out = (self.shape[-2] // downscale_factor, self.shape[-1] // downscale_factor)
1257
+ n = len(batch)
1258
+ B_dims = range(n)
1259
+ C_dim, H_dim, r1_dim, W_dim, r2_dim = range(n, n + 5)
1260
+ return (
1261
+ self.view(
1262
+ *batch,
1263
+ self.shape[-3],
1264
+ HW_out[0],
1265
+ downscale_factor,
1266
+ HW_out[1],
1267
+ downscale_factor,
1268
+ )
1269
+ .permute(*B_dims, C_dim, r1_dim, r2_dim, H_dim, W_dim)
1270
+ .reshape(*batch, C_out, *HW_out)
1271
+ .clone(memory_format=utils.suggest_memory_format(self))
1272
+ )
1273
+
1274
+
1275
+ # Needed as aten.{celu_,elu_...} exist (even if they don't have the in-place kwarg)
1276
+ celu_ = _make_inplace(celu)
1277
+ elu_ = _make_inplace(elu)
1278
+ mish_ = _make_inplace(mish)
1279
+ selu_ = _make_inplace(selu)
1280
+ threshold_ = _make_inplace(threshold)
lib/python3.12/site-packages/torch/_refs/nn/functional/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (48.1 kB). View file
 
lib/python3.12/site-packages/torch/_refs/special/__init__.py ADDED
@@ -0,0 +1,236 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ import math
3
+ from typing import Optional, Union
4
+
5
+ import torch
6
+ import torch._prims as prims
7
+ import torch._prims_common as utils
8
+ import torch._refs as refs
9
+ from torch import Tensor
10
+ from torch._decomp import register_decomposition
11
+ from torch._prims_common import (
12
+ ELEMENTWISE_TYPE_PROMOTION_KIND,
13
+ Number,
14
+ NumberType,
15
+ TensorLike,
16
+ TensorLikeType,
17
+ )
18
+ from torch._prims_common.wrappers import elementwise_type_promotion_wrapper, out_wrapper
19
+ from torch._refs import (
20
+ _make_alias,
21
+ _make_elementwise_binary_reference,
22
+ _make_elementwise_unary_reference,
23
+ )
24
+
25
+
26
+ __all__ = [
27
+ "bessel_j0",
28
+ "bessel_j1",
29
+ "entr",
30
+ "erfcx",
31
+ "expit",
32
+ "i0e",
33
+ "i1",
34
+ "i1e",
35
+ "log_ndtr",
36
+ "logit",
37
+ "log_softmax",
38
+ "multigammaln",
39
+ "ndtr",
40
+ "ndtri",
41
+ "softmax",
42
+ "spherical_bessel_j0",
43
+ "xlog1py",
44
+ "zeta",
45
+ ]
46
+ aten = torch._ops.ops.aten
47
+
48
+
49
+ @_make_elementwise_unary_reference(
50
+ ELEMENTWISE_TYPE_PROMOTION_KIND.INT_TO_FLOAT,
51
+ )
52
+ def bessel_j0(a: TensorLikeType) -> TensorLikeType:
53
+ return prims.bessel_j0(a)
54
+
55
+
56
+ @_make_elementwise_unary_reference(
57
+ ELEMENTWISE_TYPE_PROMOTION_KIND.INT_TO_FLOAT,
58
+ )
59
+ def bessel_j1(a: TensorLikeType) -> TensorLikeType:
60
+ return prims.bessel_j1(a)
61
+
62
+
63
+ @register_decomposition(aten.special_entr)
64
+ @out_wrapper()
65
+ @elementwise_type_promotion_wrapper(
66
+ type_promoting_args=("a",),
67
+ type_promotion_kind=utils.ELEMENTWISE_TYPE_PROMOTION_KIND.INT_TO_FLOAT,
68
+ )
69
+ def entr(a: TensorLikeType) -> TensorLikeType:
70
+ return torch.where(
71
+ torch.isnan(a),
72
+ a,
73
+ torch.where(a > 0, -a * torch.log(a), torch.where(a == 0, 0, -torch.inf)),
74
+ )
75
+
76
+
77
+ @register_decomposition(aten.special_erfcx)
78
+ @out_wrapper()
79
+ @elementwise_type_promotion_wrapper(
80
+ type_promoting_args=("a",),
81
+ type_promotion_kind=utils.ELEMENTWISE_TYPE_PROMOTION_KIND.INT_TO_FLOAT,
82
+ )
83
+ def erfcx(a: TensorLikeType) -> TensorLikeType:
84
+ return prims.erfcx(a)
85
+
86
+
87
+ # alias for sigmoid
88
+ expit = _make_alias(torch.sigmoid, "expit")
89
+
90
+
91
+ @_make_elementwise_unary_reference(
92
+ ELEMENTWISE_TYPE_PROMOTION_KIND.INT_TO_FLOAT,
93
+ )
94
+ def i0e(a: TensorLikeType) -> TensorLikeType:
95
+ return prims.bessel_i0e(a)
96
+
97
+
98
+ @_make_elementwise_unary_reference(
99
+ ELEMENTWISE_TYPE_PROMOTION_KIND.INT_TO_FLOAT,
100
+ )
101
+ def i1(a: TensorLikeType) -> TensorLikeType:
102
+ return prims.bessel_i1(a)
103
+
104
+
105
+ @_make_elementwise_unary_reference(
106
+ ELEMENTWISE_TYPE_PROMOTION_KIND.INT_TO_FLOAT,
107
+ )
108
+ def i1e(a: TensorLikeType) -> TensorLikeType:
109
+ return prims.bessel_i1e(a)
110
+
111
+
112
+ @register_decomposition(aten.special_log_ndtr)
113
+ @out_wrapper()
114
+ @elementwise_type_promotion_wrapper(
115
+ type_promoting_args=("a",),
116
+ type_promotion_kind=utils.ELEMENTWISE_TYPE_PROMOTION_KIND.INT_TO_FLOAT,
117
+ )
118
+ def log_ndtr(a: TensorLikeType) -> TensorLikeType:
119
+ # Note: M_SQRT1_2 is the value of 1 / sqrt(2)
120
+ M_SQRT1_2 = 0.707106781186547524400844362104849039
121
+ t = a * M_SQRT1_2
122
+ return torch.where(
123
+ a < 1.0,
124
+ torch.log(torch.special.erfcx(-t) / 2) - t * t,
125
+ torch.log1p(-torch.erfc(t) / 2),
126
+ )
127
+
128
+
129
+ @register_decomposition(aten.logit)
130
+ @out_wrapper()
131
+ @elementwise_type_promotion_wrapper(
132
+ type_promoting_args=("self",),
133
+ type_promotion_kind=utils.ELEMENTWISE_TYPE_PROMOTION_KIND.INT_TO_FLOAT,
134
+ )
135
+ def logit(self: TensorLikeType, eps: Optional[float] = None) -> TensorLikeType:
136
+ if eps is None:
137
+ eps = -1.0
138
+ lo = eps
139
+ hi = 1 - eps
140
+ self = torch.clamp(self, lo, hi)
141
+ return torch.log(torch.true_divide(self, torch.sub(1, self)))
142
+
143
+
144
+ @register_decomposition(aten.special_xlog1py)
145
+ @out_wrapper()
146
+ @elementwise_type_promotion_wrapper(
147
+ type_promoting_args=("a", "b"),
148
+ type_promotion_kind=ELEMENTWISE_TYPE_PROMOTION_KIND.INT_TO_FLOAT,
149
+ )
150
+ def xlog1py(a: Union[TensorLikeType, NumberType], b: Union[TensorLikeType, NumberType]):
151
+ torch._check(
152
+ isinstance(a, TensorLike) or isinstance(b, TensorLike),
153
+ lambda: 'Expected either argument a or b to be a Tensor"',
154
+ )
155
+
156
+ # Operations like eq and log do not handle scalar values, so we convert them to scalar_tensors.
157
+ if isinstance(a, TensorLike) and isinstance(b, Number):
158
+ b = refs.scalar_tensor(b, dtype=a.dtype, device=a.device)
159
+ elif isinstance(b, TensorLike) and isinstance(a, Number):
160
+ a = refs.scalar_tensor(a, dtype=b.dtype, device=b.device)
161
+
162
+ # mypy: expected "Tensor"
163
+ assert isinstance(a, TensorLike)
164
+ assert isinstance(b, TensorLike)
165
+ rhs = torch.where(torch.eq(a, 0), 0, torch.mul(a, torch.log1p(b)))
166
+ return torch.where(torch.isnan(b), float("nan"), rhs)
167
+
168
+
169
+ @register_decomposition(aten.mvlgamma)
170
+ @out_wrapper()
171
+ @elementwise_type_promotion_wrapper(
172
+ type_promoting_args=("a",),
173
+ type_promotion_kind=utils.ELEMENTWISE_TYPE_PROMOTION_KIND.INT_TO_FLOAT,
174
+ )
175
+ def multigammaln(a: TensorLikeType, p: int) -> TensorLikeType:
176
+ c = 0.25 * p * (p - 1) * math.log(math.pi)
177
+ b = 0.5 * torch.arange(start=(1 - p), end=1, step=1, dtype=a.dtype, device=a.device)
178
+ return torch.sum(torch.lgamma(a.unsqueeze(-1) + b), dim=-1) + c
179
+
180
+
181
+ @register_decomposition(aten.special_ndtr)
182
+ @out_wrapper()
183
+ @elementwise_type_promotion_wrapper(
184
+ type_promoting_args=("a",),
185
+ type_promotion_kind=utils.ELEMENTWISE_TYPE_PROMOTION_KIND.INT_TO_FLOAT,
186
+ )
187
+ def ndtr(a: TensorLikeType) -> TensorLikeType:
188
+ # Note: M_SQRT1_2 is the value of 1 / sqrt(2)
189
+ M_SQRT1_2 = 0.707106781186547524400844362104849039
190
+ a_sqrt_2 = a * M_SQRT1_2
191
+ return (1 + torch.erf(a_sqrt_2)) * 0.5
192
+
193
+
194
+ @register_decomposition(aten.special_ndtri)
195
+ @out_wrapper()
196
+ @elementwise_type_promotion_wrapper(
197
+ type_promoting_args=("a",),
198
+ type_promotion_kind=utils.ELEMENTWISE_TYPE_PROMOTION_KIND.INT_TO_FLOAT,
199
+ )
200
+ def ndtri(a: TensorLikeType) -> TensorLikeType:
201
+ return prims.ndtri(a)
202
+
203
+
204
+ # Forwarding alias: the special variant doesn't support the out kwarg
205
+ # CompositeImplicitAutograd - don't register decomp
206
+ def log_softmax(
207
+ a: TensorLikeType,
208
+ dim: int,
209
+ dtype: Optional[torch.dtype] = None,
210
+ ) -> TensorLikeType:
211
+ return torch.log_softmax(a=a, dim=dim, dtype=dtype) # type: ignore[call-overload]
212
+
213
+
214
+ # Forwarding alias: the special variant doesn't support the out kwarg
215
+ # CompositeImplicitAutograd - don't register decomp
216
+ def softmax(
217
+ a: TensorLikeType,
218
+ dim: int,
219
+ dtype: Optional[torch.dtype] = None,
220
+ ) -> TensorLikeType:
221
+ return torch.softmax(a=a, dim=dim, dtype=dtype) # type: ignore[call-overload]
222
+
223
+
224
+ @_make_elementwise_unary_reference(
225
+ ELEMENTWISE_TYPE_PROMOTION_KIND.INT_TO_FLOAT,
226
+ )
227
+ def spherical_bessel_j0(a: TensorLikeType) -> TensorLikeType:
228
+ return prims.spherical_bessel_j0(a)
229
+
230
+
231
+ # TODO: add docstring
232
+ @_make_elementwise_binary_reference(
233
+ type_promotion_kind=utils.ELEMENTWISE_TYPE_PROMOTION_KIND.INT_TO_FLOAT,
234
+ )
235
+ def zeta(a: TensorLikeType, b: TensorLikeType) -> TensorLikeType:
236
+ return prims.zeta(a, b)
lib/python3.12/site-packages/torch/_refs/special/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (9.77 kB). View file
 
lib/python3.12/site-packages/torch/_strobelight/__init__.py ADDED
File without changes
lib/python3.12/site-packages/torch/_strobelight/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (197 Bytes). View file
 
lib/python3.12/site-packages/torch/_strobelight/__pycache__/cli_function_profiler.cpython-312.pyc ADDED
Binary file (14.5 kB). View file
 
lib/python3.12/site-packages/torch/_strobelight/__pycache__/compile_time_profiler.cpython-312.pyc ADDED
Binary file (8.61 kB). View file
 
lib/python3.12/site-packages/torch/_strobelight/cli_function_profiler.py ADDED
@@ -0,0 +1,311 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: disallow-untyped-defs
2
+
3
+ import functools
4
+ import logging
5
+ import os
6
+ import re
7
+ import subprocess
8
+ import time
9
+ from threading import Lock
10
+ from timeit import default_timer as timer
11
+ from typing import Any, List, Optional, Sequence
12
+
13
+
14
+ logger = logging.getLogger("strobelight_function_profiler")
15
+
16
+ console_handler = logging.StreamHandler()
17
+ formatter = logging.Formatter(
18
+ "%(name)s, line %(lineno)d, %(asctime)s, %(levelname)s: %(message)s"
19
+ )
20
+ console_handler.setFormatter(formatter)
21
+
22
+ logger.addHandler(console_handler)
23
+ logger.setLevel(logging.INFO)
24
+ logger.propagate = False
25
+
26
+
27
+ class StrobelightCLIProfilerError(Exception):
28
+ """
29
+ Raised when an error happens during strobelight profiling
30
+ """
31
+
32
+
33
+ def _pid_namespace_link(pid: Optional[int] = None) -> str:
34
+ """Returns the link to the process's namespace, example: pid:[4026531836]"""
35
+ PID_NAMESPACE_PATH = "/proc/{}/ns/pid"
36
+ pid = pid or os.getpid()
37
+ return os.readlink(PID_NAMESPACE_PATH.format(pid))
38
+
39
+
40
+ def _pid_namespace(pid: Optional[int] = None) -> int:
41
+ """Returns the process's namespace id"""
42
+ pid = pid or os.getpid()
43
+ link = _pid_namespace_link(pid)
44
+ return int(link[link.find("[") + 1 : -1])
45
+
46
+
47
+ def _command_to_string(command: Sequence[str]) -> str:
48
+ return " ".join(command)
49
+
50
+
51
+ class StrobelightCLIFunctionProfiler:
52
+ """
53
+ Note: this is a Meta only tool.
54
+
55
+ StrobelightCLIFunctionProfiler can be used to profile a python function and
56
+ generate a strobelight link with the results. It works on meta servers but
57
+ does not requries an fbcode target.
58
+ When stop_at_error is false(default), error during profiling does not prevent
59
+ the work function from running.
60
+
61
+ Check function_profiler_example.py for an example.
62
+ """
63
+
64
+ # This lock is used to make sure only one thread is running the profiler at any point.
65
+ _lock = Lock()
66
+
67
+ def __init__(
68
+ self,
69
+ *,
70
+ stop_at_error: bool = False,
71
+ max_profile_duration_sec: int = 60 * 10,
72
+ sample_each: float = 1e7, # sample each sample_each cycles.
73
+ run_user_name: str = "pytorch-strobelight-ondemand",
74
+ timeout_wait_for_running_sec: int = 60,
75
+ timeout_wait_for_finished_sec: int = 60,
76
+ recorded_env_variables: Optional[List[str]] = None,
77
+ sample_tags: Optional[List[str]] = None,
78
+ stack_max_len: int = 127,
79
+ async_stack_max_len: int = 127,
80
+ ):
81
+ self.stop_at_error = stop_at_error
82
+ self.max_profile_duration_sec = max_profile_duration_sec
83
+ self.sample_each = sample_each
84
+ self.run_user_name = run_user_name
85
+ self.timeout_wait_for_running_sec = timeout_wait_for_running_sec
86
+ self.timeout_wait_for_finished_sec = timeout_wait_for_finished_sec
87
+ # Results of the most recent run.
88
+ # Tracks the strobelight run id of the most recent run
89
+ self.current_run_id: Optional[int] = None
90
+ self.profile_result: Optional[List[str]] = None
91
+ self.sample_tags = sample_tags
92
+
93
+ def _run_async(self) -> None:
94
+ processId = os.getpid()
95
+ namespace = _pid_namespace(processId)
96
+ command = [
97
+ "strobeclient",
98
+ "run",
99
+ "--profiler",
100
+ "pyperf",
101
+ "--event",
102
+ "cycles",
103
+ "--async",
104
+ "--sample-interval",
105
+ f"{int(self.sample_each)}",
106
+ "--duration-ms",
107
+ f"{int(self.max_profile_duration_sec * 1000)}",
108
+ "--pid",
109
+ f"{namespace}:{processId}",
110
+ ]
111
+
112
+ if self.sample_tags:
113
+ command.append("--sample-tags")
114
+ command.append(",".join(self.sample_tags))
115
+
116
+ logger.debug("running command: %s", _command_to_string(command))
117
+ result = subprocess.run(command, capture_output=True)
118
+ output = result.stderr.decode("utf-8")
119
+ logger.debug("output:\n{%s}", output)
120
+
121
+ if result.returncode != 0:
122
+ raise StrobelightCLIProfilerError(
123
+ f"failed to start strobelight profiling, error in run_async:{output}"
124
+ )
125
+
126
+ if match := re.search(r"INFO Run Id: (-?\d+)", output):
127
+ self.current_run_id = int(match.group(1))
128
+ return
129
+
130
+ raise StrobelightCLIProfilerError(
131
+ f"failed to start strobelight profiling, unexpected result {output}"
132
+ )
133
+
134
+ def _wait_for_running(self, counter: int = 0) -> None:
135
+ if counter > 20:
136
+ raise StrobelightCLIProfilerError(
137
+ "wait_for_running called more than 20 times"
138
+ )
139
+
140
+ command = ["strobeclient", "getRunStatus", "--run-id", f"{self.current_run_id}"]
141
+ logger.debug("running command: %s", _command_to_string(command))
142
+ result = subprocess.run(command, capture_output=True)
143
+ output = result.stderr.decode("utf-8")
144
+ logger.debug("output:\n{%s}", output)
145
+
146
+ if result.returncode != 0:
147
+ raise StrobelightCLIProfilerError(
148
+ f"failed to start strobelight profiling, error in wait_for_running:{output}"
149
+ )
150
+
151
+ if match := re.search("Profile run status: (.*)", output):
152
+ current_status = match.group(1)
153
+ if current_status == "RUNNING":
154
+ return
155
+ elif current_status == "PREPARING":
156
+ time.sleep(10)
157
+ self._wait_for_running(counter + 1)
158
+ return
159
+ else:
160
+ raise StrobelightCLIProfilerError(f"unexpected {current_status} phase")
161
+
162
+ raise StrobelightCLIProfilerError(f"unexpected output\n: {output} ")
163
+
164
+ def _stop_run(self) -> None:
165
+ command = ["strobeclient", "stopRun", "--run-id", str(self.current_run_id)]
166
+ logger.debug("running command: %s", _command_to_string(command))
167
+ result = subprocess.run(command, capture_output=True)
168
+ output = result.stderr.decode("utf-8")
169
+ logger.debug("output:\n{%s}", output)
170
+
171
+ if result.returncode != 0:
172
+ raise StrobelightCLIProfilerError(
173
+ f"failed to stop strobelight profiling, return code is not 0 :{output}"
174
+ )
175
+
176
+ if match := re.search("INFO ::1:(.*)", output):
177
+ current_status = match.group(1)
178
+ if current_status.__contains__("Success!"):
179
+ return
180
+ else:
181
+ raise StrobelightCLIProfilerError(
182
+ f"failed to stop strobelight profiling, got {current_status} result"
183
+ )
184
+
185
+ raise StrobelightCLIProfilerError(f"unexpected output\n: {output} ")
186
+
187
+ def _get_results(self) -> None:
188
+ command = ["strobeclient", "getRunStatus", "--run-id", str(self.current_run_id)]
189
+ logger.debug("running command: %s", _command_to_string(command))
190
+ result = subprocess.run(command, capture_output=True)
191
+ output = result.stderr.decode("utf-8")
192
+ logger.debug("output:\n{%s}", output)
193
+
194
+ if result.returncode != 0:
195
+ raise StrobelightCLIProfilerError(
196
+ f"failed to extract profiling results, return code is not 0 : {output}"
197
+ )
198
+
199
+ if match := re.search("INFO ::1:(.*)", output):
200
+ current_status = match.group(1)
201
+ if current_status.__contains__("Profile run status: PROCESSING"):
202
+ time.sleep(10)
203
+ self._get_results()
204
+ return
205
+ elif not current_status.__contains__("Profile run finished with SUCCESS"):
206
+ raise StrobelightCLIProfilerError(
207
+ f"failed to extract profiling results, unexpected response {output}"
208
+ )
209
+
210
+ self.profile_result = []
211
+ for item in re.findall(
212
+ r"(Total samples(.*)|GraphProfiler(.*)|Icicle view \(python stack\)(.*))",
213
+ output,
214
+ ):
215
+ self.profile_result += item[0]
216
+ logger.info(item[0])
217
+
218
+ def _stop_strobelight_no_throw(
219
+ self,
220
+ collect_results: bool,
221
+ ) -> None:
222
+ try:
223
+ # call stop run
224
+ self._stop_run()
225
+ logger.info("strobelight profiling stopped")
226
+
227
+ logger.debug("collection stopped")
228
+
229
+ if not collect_results:
230
+ return
231
+
232
+ self._get_results()
233
+ except Exception as error:
234
+ logger.warning("error during stop_strobelight", exc_info=True)
235
+
236
+ # Return true if strobelight started and is running. Never throw.
237
+ def _start_strobelight(self) -> bool:
238
+ strobelight_started = False
239
+ try:
240
+ self._run_async()
241
+ strobelight_started = True
242
+ logger.info("strobelight run id is: %s", self.current_run_id)
243
+ self._wait_for_running()
244
+ logger.info("strobelight profiling running")
245
+ return True
246
+
247
+ except Exception as error:
248
+ logger.warning("error during start_strobelight:", exc_info=True)
249
+ if strobelight_started:
250
+ self._stop_strobelight_no_throw(collect_results=False)
251
+ return False
252
+
253
+ def profile(self, work_function: Any, *args: Any, **kwargs: Any) -> Any:
254
+ self.current_run_id = None
255
+ self.profile_result = None
256
+
257
+ if locked := StrobelightCLIFunctionProfiler._lock.acquire(False):
258
+ if not locked:
259
+ if self.stop_at_error:
260
+ raise StrobelightCLIProfilerError("concurrent runs not supported")
261
+
262
+ logger.warning("concurrent runs not supported")
263
+ return work_function(*args, **kwargs)
264
+
265
+ started = self._start_strobelight()
266
+ if not started:
267
+ if self.stop_at_error:
268
+ StrobelightCLIFunctionProfiler._lock.release()
269
+ raise StrobelightCLIProfilerError(
270
+ "failed to start strobelight profiling"
271
+ )
272
+ result = work_function(*args, **kwargs)
273
+ StrobelightCLIFunctionProfiler._lock.release()
274
+ return result
275
+
276
+ try:
277
+ logger.debug("collection started")
278
+ start = timer()
279
+ result = work_function(*args, **kwargs)
280
+ end = timer()
281
+ total_time = end - start # Time in seconds, e.g. 5.38091952400282
282
+ logger.info("work function took %s seconds", total_time)
283
+ self._stop_strobelight_no_throw(collect_results=True)
284
+ StrobelightCLIFunctionProfiler._lock.release()
285
+ return result
286
+ except Exception as error:
287
+ logger.warning("work function throw exception", exc_info=True)
288
+ self._stop_strobelight_no_throw(collect_results=False)
289
+ StrobelightCLIFunctionProfiler._lock.release()
290
+ raise error
291
+
292
+
293
+ # A function decorator that wraps profile, if no profiler is provided one with
294
+ # default args is created. A function can be annotated as:
295
+ # @strobelight()
296
+ # @strobelight(profiler = StrobelightFunctionProfiler(stop_at_error=True,..))
297
+ # @strobelight(stop_at_error=True,...)
298
+ def strobelight(
299
+ profiler: Optional[StrobelightCLIFunctionProfiler] = None, **kwargs: Any
300
+ ) -> Any:
301
+ if not profiler:
302
+ profiler = StrobelightCLIFunctionProfiler(**kwargs)
303
+
304
+ def strobelight_inner(work_function: Any) -> Any:
305
+ @functools.wraps(work_function)
306
+ def wrapper_function(*args: Any, **kwargs: Any) -> Any:
307
+ return profiler.profile(work_function, *args, **kwargs)
308
+
309
+ return wrapper_function
310
+
311
+ return strobelight_inner
lib/python3.12/site-packages/torch/_strobelight/compile_time_profiler.py ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: disallow-untyped-defs
2
+
3
+ import json
4
+ import logging
5
+ import os
6
+ import subprocess
7
+ from datetime import datetime
8
+ from socket import gethostname
9
+ from typing import Any, Optional
10
+
11
+ from torch._strobelight.cli_function_profiler import StrobelightCLIFunctionProfiler
12
+
13
+
14
+ logger = logging.getLogger("strobelight_compile_time_profiler")
15
+
16
+ console_handler = logging.StreamHandler()
17
+ formatter = logging.Formatter(
18
+ "%(name)s, line %(lineno)d, %(asctime)s, %(levelname)s: %(message)s"
19
+ )
20
+ console_handler.setFormatter(formatter)
21
+
22
+ logger.addHandler(console_handler)
23
+ logger.setLevel(logging.INFO)
24
+ logger.propagate = False
25
+
26
+
27
+ def get_fburl(url: str) -> str:
28
+ short_url = url
29
+ # Attempt to shorten the URL
30
+ try:
31
+ result = subprocess.run(
32
+ ["fburl", url], capture_output=True, stdin=subprocess.DEVNULL
33
+ )
34
+ if result.returncode == 0:
35
+ short_url = result.stdout.decode("utf-8")
36
+ except Exception as e:
37
+ logger.warning("URL shortening failed: %s, using long URL", repr(e))
38
+ return short_url
39
+
40
+
41
+ def get_strobelight_url(identifier: str) -> str:
42
+ scuba_json = {
43
+ "aggregateList": [],
44
+ "aggregation_field": "async_stack_complete",
45
+ "b_constraints": [[]],
46
+ "c_constraints": [[]],
47
+ "cols": ["namespace_id", "namespace_process_id"],
48
+ "compare": "none",
49
+ "constraints": [
50
+ [{"column": "sample_tags", "op": "all", "value": [f'["{identifier}"]']}]
51
+ ],
52
+ "derivedCols": [],
53
+ "end": "now",
54
+ "enumCols": [],
55
+ "filterMode": "DEFAULT",
56
+ "hideEmptyColumns": "false",
57
+ "ignoreGroupByInComparison": "false",
58
+ "is_timeseries": "false",
59
+ "mappedCols": [],
60
+ "metric": "count",
61
+ "modifiers": [],
62
+ "order": "weight",
63
+ "order_desc": "true",
64
+ "param_dimensions": [
65
+ {"dim": "py_async_stack", "op": "edge", "param": "0", "anchor": "0"}
66
+ ],
67
+ "purposes": [],
68
+ "return_remainder": "false",
69
+ "samplingRatio": "1",
70
+ "should_pivot": "false",
71
+ "start": "-30 days",
72
+ "timezone": "America/Los_Angeles",
73
+ "top": 10000,
74
+ }
75
+ scuba_url_prefix = "https://www.internalfb.com/intern/scuba/query/?dataset=pyperf_experimental/on_demand&drillstate="
76
+ scuba_url_suff = "&view=GraphProfilerView&&normalized=1726332703&pool=uber"
77
+ long_url = scuba_url_prefix + json.dumps(scuba_json) + scuba_url_suff
78
+ return get_fburl(long_url)
79
+
80
+
81
+ class StrobelightCompileTimeProfiler:
82
+ success_profile_count: int = 0
83
+ failed_profile_count: int = 0
84
+ ignored_profile_runs: int = 0
85
+ inside_profile_compile_time: bool = False
86
+ enabled: bool = False
87
+ # A unique identifier that is used as the run_user_name in the strobelight profile to
88
+ # associate all compile time profiles together.
89
+ identifier: Optional[str] = None
90
+
91
+ current_phase: Optional[str] = None
92
+
93
+ profiler: Optional[Any] = None
94
+
95
+ max_stack_length: int = int(
96
+ os.environ.get("COMPILE_STROBELIGHT_MAX_STACK_LENGTH", 500)
97
+ )
98
+ max_profile_time: int = int(
99
+ os.environ.get("COMPILE_STROBELIGHT_MAX_PROFILE_TIME", 60 * 30)
100
+ )
101
+ # Collect sample each x cycles.
102
+ sample_each: int = int(
103
+ float(os.environ.get("COMPILE_STROBELIGHT_SAMPLE_RATE", 1e7))
104
+ )
105
+
106
+ @classmethod
107
+ def enable(cls, profiler_class: Any = StrobelightCLIFunctionProfiler) -> None:
108
+ if cls.enabled:
109
+ logger.info("compile time strobelight profiling already enabled")
110
+ return
111
+
112
+ logger.info("compile time strobelight profiling enabled")
113
+
114
+ if profiler_class is StrobelightCLIFunctionProfiler:
115
+ import shutil
116
+
117
+ if not shutil.which("strobeclient"):
118
+ logger.info(
119
+ "strobeclient not found, cant enable compile time strobelight profiling, seems"
120
+ "like you are not on a FB machine."
121
+ )
122
+ return
123
+
124
+ cls.enabled = True
125
+ cls._cls_init()
126
+ # profiler_class should have public API similar to that of StrobelightCLIFunctionProfiler.
127
+ # we have pass different functionProfilerClass for meta-internal fbcode targets.
128
+ # NB: the actual implementation in Meta is at
129
+ # fbcode/caffe2/fb/strobelight/function_profiler.py
130
+ cls.profiler = profiler_class(
131
+ sample_each=cls.sample_each,
132
+ max_profile_duration_sec=cls.max_profile_time,
133
+ stack_max_len=cls.max_stack_length,
134
+ async_stack_max_len=cls.max_stack_length,
135
+ run_user_name="pt2-profiler/"
136
+ + os.environ.get("USER", os.environ.get("USERNAME", "")),
137
+ sample_tags={cls.identifier},
138
+ )
139
+
140
+ @classmethod
141
+ def _cls_init(cls) -> None:
142
+ cls.identifier = "{date}{pid}{hostname}".format(
143
+ date=datetime.now().strftime("%Y-%m-%d-%H:%M:%S"),
144
+ pid=os.getpid(),
145
+ hostname=gethostname(),
146
+ )
147
+
148
+ logger.info("Unique sample tag for this run is: %s", cls.identifier)
149
+ logger.info(
150
+ "URL to access the strobelight profile at the end of the run: %s",
151
+ get_strobelight_url(cls.identifier),
152
+ )
153
+
154
+ @classmethod
155
+ def _log_stats(cls) -> None:
156
+ logger.info(
157
+ "%s strobelight success runs out of %s non-recursive compilation events.",
158
+ cls.success_profile_count,
159
+ cls.success_profile_count + cls.failed_profile_count,
160
+ )
161
+
162
+ # TODO use threadlevel meta data to tags to record phases.
163
+ @classmethod
164
+ def profile_compile_time(
165
+ cls, func: Any, phase_name: str, *args: Any, **kwargs: Any
166
+ ) -> Any:
167
+ if not cls.enabled:
168
+ return func(*args, **kwargs)
169
+
170
+ if cls.profiler is None:
171
+ logger.error("profiler is not set")
172
+ return
173
+
174
+ if cls.inside_profile_compile_time:
175
+ cls.ignored_profile_runs += 1
176
+ logger.info(
177
+ "profile_compile_time is requested for phase: %s while already in running phase: %s, recursive call ignored",
178
+ phase_name,
179
+ cls.current_phase,
180
+ )
181
+ return func(*args, **kwargs)
182
+
183
+ cls.inside_profile_compile_time = True
184
+ cls.current_phase = phase_name
185
+
186
+ work_result = cls.profiler.profile(func, *args, **kwargs)
187
+
188
+ if cls.profiler.profile_result is not None:
189
+ cls.success_profile_count += 1
190
+ else:
191
+ cls.failed_profile_count += 1
192
+
193
+ cls._log_stats()
194
+ cls.inside_profile_compile_time = False
195
+ return work_result
lib/python3.12/site-packages/torch/compiler/__init__.py ADDED
@@ -0,0 +1,404 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ from typing import Any, Callable, List, TypeVar
3
+
4
+ import torch
5
+
6
+
7
+ __all__ = [
8
+ "compile",
9
+ "assume_constant_result",
10
+ "reset",
11
+ "allow_in_graph",
12
+ "substitute_in_graph",
13
+ "list_backends",
14
+ "disable",
15
+ "set_stance",
16
+ "cudagraph_mark_step_begin",
17
+ "wrap_numpy",
18
+ "is_compiling",
19
+ "is_dynamo_compiling",
20
+ ]
21
+
22
+
23
+ _F = TypeVar("_F", bound=Callable[..., Any])
24
+
25
+
26
+ def compile(*args, **kwargs):
27
+ """
28
+ See :func:`torch.compile` for details on the arguments for this function.
29
+ """
30
+ return torch.compile(*args, **kwargs)
31
+
32
+
33
+ def reset() -> None:
34
+ """
35
+ This function clears all compilation caches and restores the system to its initial state.
36
+ It is recommended to call this function, especially after using operations like `torch.compile(...)`
37
+ to ensure a clean state before another unrelated compilation
38
+ """
39
+ import torch._dynamo
40
+
41
+ torch._dynamo.reset()
42
+
43
+
44
+ def allow_in_graph(fn):
45
+ """
46
+ Tells the compiler frontend (Dynamo) to skip symbolic introspection of the function
47
+ and instead directly write it to the graph when encountered.
48
+
49
+ If you are using :func:`torch.compile` (with backend="inductor" (the default)), or
50
+ :func:`torch.export.export`, and trying to black-box a Python function throughout
51
+ all tracing, do not use this API.
52
+ Instead, please create a custom operator (see `PyTorch Custom Operators Landing Page
53
+ <https://pytorch.org/tutorials/advanced/custom_ops_landing_page.html>`_)
54
+
55
+ .. warning::
56
+
57
+ If you're a typical torch.compile user (e.g. you're applying torch.compile to
58
+ a model to make it run faster), you probably don't want to use this function.
59
+ :func:`allow_in_graph` is a footgun because it skips the compiler frontend
60
+ (Dynamo) that is responsible for doing safety checks (graph breaks, handling
61
+ closures, etc). Incorrect usage will lead to difficult-to-debug silent
62
+ incorrectness issues.
63
+
64
+ Given a Python function with no allow_in_graph decorator, regular execution
65
+ of torch.compile traces through the function. :func:`allow_in_graph` changes
66
+ it so that the frontend does not trace inside the function, but the compiler
67
+ backend still traces through it. Compare this to custom operators, which
68
+ treats a function as a black box throughout the torch.compile stack. The following
69
+ table compares these mechanisms.
70
+
71
+ +------------------------+-----------------------+--------------------------------+
72
+ | Mechanism | Frontend (Dynamo) | Backend (AOTAutograd+Inductor) |
73
+ +========================+=======================+================================+
74
+ | no decorator | trace inside | trace inside |
75
+ +------------------------+-----------------------+--------------------------------+
76
+ | allow_in_graph | opaque callable | trace inside |
77
+ +------------------------+-----------------------+--------------------------------+
78
+ | custom op | opaque callable | opaque callable |
79
+ +------------------------+-----------------------+--------------------------------+
80
+
81
+ One common use case for :func:`allow_in_graph()` is as an escape hatch for the compiler
82
+ frontend: if you know the function works w.r.t. to the downstream components of the
83
+ compilation stack (AOTAutograd and Inductor) but there is a Dynamo bug that prevents it from
84
+ symbolically introspecting the function properly (or if your code is in C/C++ and
85
+ therefore cannot be introspected with Dynamo), then one can decorate said function
86
+ with :func:`allow_in_graph` to bypass Dynamo.
87
+
88
+ We require that ``fn`` adhere to the following restrictions. Failure to adhere
89
+ results in undefined behavior:
90
+
91
+ - The inputs to ``fn`` must be Proxy-able types in the FX graph. Valid types include:
92
+ Tensor/int/bool/float/None/List[Tensor?]/List[int?]/List[float?]
93
+ Tuple[Tensor?, ...]/Tuple[int?, ...]/Tuple[float?, ...]/torch.dtype/torch.device
94
+ - The outputs to ``fn`` must be Proxy-able types in the FX graph (see previous bullet)
95
+ - all Tensors used inside of ``fn`` must be passed directly as inputs to ``fn``
96
+ (as opposed to being captured variables).
97
+
98
+ Args:
99
+ fn: A callable representing the function to be included in the graph.
100
+ If ``fn`` is a list or tuple of callables it recursively applies
101
+ :func:`allow_in_graph()` to each function and returns a new list or
102
+ tuple containing the modified functions.
103
+
104
+ Example::
105
+
106
+ torch.compiler.allow_in_graph(my_custom_function)
107
+
108
+ @torch.compile(...)
109
+ def fn(x):
110
+ x = torch.add(x, 1)
111
+ x = my_custom_function(x)
112
+ x = torch.add(x, 1)
113
+ return x
114
+
115
+ fn(...)
116
+
117
+ Will capture a single graph containing ``my_custom_function()``.
118
+
119
+ """
120
+ import torch._dynamo
121
+
122
+ return torch._dynamo.allow_in_graph(fn)
123
+
124
+
125
+ def substitute_in_graph(
126
+ original_fn: _F,
127
+ *,
128
+ can_constant_fold_through: bool = False,
129
+ skip_signature_check: bool = False,
130
+ ) -> Callable[[_F], _F]:
131
+ """
132
+ Register a polyfill handler for a function, usually a C function from the C extension, to be
133
+ used in place of the original function when inlining the original function in the graph.
134
+
135
+ .. note::
136
+
137
+ The polyfill handler is only used when inlining the original function. It is not used when
138
+ the original function is called directly. In the eager mode, the decorated function calls
139
+ the performant C function rather than the polyfill handler.
140
+
141
+ The polyfill handler is a function that will be called in place of the original function when
142
+ inlining the original function. The polyfill handler should have the same signature and the same
143
+ behavior as the original function.
144
+
145
+ Args:
146
+ original_fn (callable): The original function, usually a C function, to register a polyfill
147
+ handler for.
148
+ can_constant_fold_through (bool, optional): Whether the polyfill handler can be constant
149
+ folded through. That is, if the polyfill handler is a pure function and its arguments
150
+ are constant, the result of the polyfill handler can be constant folded during the
151
+ compilation. Defaults to ``False``.
152
+ skip_signature_check (bool, optional): Whether to skip the signature check between the
153
+ original function and the polyfill handler. Defaults to ``False``.
154
+
155
+ Returns:
156
+ A decorator that registers the polyfill handler for the original function.
157
+
158
+ Example::
159
+
160
+ >>> import operator
161
+ >>> operator.indexOf([1, 2, 3, 4, 5], 3)
162
+ 2
163
+ >>> torch.compile(operator.indexOf, fullgraph=True)([1, 2, 3, 4, 5], 3)
164
+ ... # xdoctest: +SKIP("Long tracebacks")
165
+ Traceback (most recent call last):
166
+ ...
167
+ torch._dynamo.exc.Unsupported: ...
168
+
169
+ >>> @torch.compiler.substitute_in_graph(operator.indexOf)
170
+ ... def indexOf(a, b, /):
171
+ ... for i, item in enumerate(a):
172
+ ... if item is b or item == b:
173
+ ... return i
174
+ ... raise ValueError("sequence.index(x): x not in sequence")
175
+ >>>
176
+ >>> torch.compile(operator.indexOf, fullgraph=True)([1, 2, 3, 4, 5], 3)
177
+ 2
178
+ """
179
+ import torch._dynamo
180
+
181
+ return torch._dynamo.substitute_in_graph(
182
+ original_fn,
183
+ can_constant_fold_through=can_constant_fold_through,
184
+ skip_signature_check=skip_signature_check,
185
+ )
186
+
187
+
188
+ def list_backends(exclude_tags=("debug", "experimental")) -> List[str]:
189
+ """
190
+ Return valid strings that can be passed to `torch.compile(..., backend="name")`.
191
+
192
+ Args:
193
+ exclude_tags(optional): A tuple of strings representing tags to exclude.
194
+ """
195
+ import torch._dynamo
196
+
197
+ return torch._dynamo.list_backends(exclude_tags)
198
+
199
+
200
+ def assume_constant_result(fn):
201
+ """
202
+ This function is used to mark a function `fn` as having a constant result.
203
+ This allows the compiler to optimize away your function
204
+ Returns The same function `fn`
205
+
206
+ Args:
207
+ fn: The function to be marked as having a constant result.
208
+
209
+ .. warning::
210
+ `assume_constant_result` can if invalid cause safety and soundness issues, :func:`torch.compile`
211
+ will not attempt to validate whether the constant assumption is true or not
212
+
213
+ """
214
+ import torch._dynamo
215
+
216
+ return torch._dynamo.assume_constant_result(fn)
217
+
218
+
219
+ def disable(fn=None, recursive=True):
220
+ """
221
+ This function provides a decorator to disable compilation on a function
222
+ It also provides the option of recursively disabling called functions
223
+
224
+ Args:
225
+ fn (optional): The function to disable
226
+ recursive (optional): A boolean value indicating whether the disabling should be recursive.
227
+ """
228
+ import torch._dynamo
229
+
230
+ return torch._dynamo.disable(fn, recursive)
231
+
232
+
233
+ def set_stance(
234
+ stance: str = "default", *, skip_guard_eval_unsafe=False, force_backend=None
235
+ ):
236
+ """
237
+ Set the current stance of the compiler.
238
+ Can be used as a function, context manager, or decorator.
239
+ Do not use this function inside a `torch.compile` region - an error will be raised otherwise.
240
+
241
+ .. code-block:: python
242
+
243
+ @torch.compile
244
+ def foo(x):
245
+ ...
246
+
247
+ @torch.compiler.set_stance("force_eager")
248
+ def bar():
249
+ # will not be compiled
250
+ foo(...)
251
+
252
+ bar()
253
+
254
+ with torch.compiler.set_stance("force_eager"):
255
+ # will also not be compiled
256
+ foo(...)
257
+
258
+ torch.compiler.set_stance("force_eager")
259
+ # will also not be compiled
260
+ foo(...)
261
+ torch.compiler.set_stance("default")
262
+
263
+ # will be compiled
264
+ foo(...)
265
+
266
+ Args:
267
+ stance: The stance to set the compiler to. Valid values are:
268
+
269
+ - "default": The default stance, used for normal compilation.
270
+ - "force_eager": Ignore all `torch.compile` directives.
271
+ - "eager_on_recompile": Run code eagerly when a recompile is necessary.
272
+ If there is cached compiled code valid for the input, it will still be used.
273
+ - "fail_on_recompile": Raise an error when recompiling a function.
274
+
275
+ skip_guard_eval_unsafe: A flag to run only differentiating guards.
276
+ CAUTION - This flag is unsafe and should only be used if your setup
277
+ meets the following conditions.
278
+
279
+ torch.compile uses a guard system to support recompilations and
280
+ choose which compiled artifact to run at runtime. These guards,
281
+ though efficient, add some overhead, which may impact performance in
282
+ scenarios where you need to optimize for minimal guard processing
283
+ time. This API enables you to disable guard evaluation, assuming
284
+ that you have warmed up the compiled model with a sufficient variety
285
+ of inputs. This assumption means that, after the warmup phase, no
286
+ further recompilations will be necessary. If this assumption fails,
287
+ there is a risk of silently producing incorrect results (hence the
288
+ term "unsafe" in the API name).
289
+
290
+ force_backend: If `stance` is "default", this argument can be used to force `torch.compile`
291
+ to use a specific backend. Otherwise, an error is raised.
292
+ """
293
+ import torch._dynamo
294
+
295
+ return torch._dynamo.set_stance(
296
+ stance,
297
+ skip_guard_eval_unsafe=skip_guard_eval_unsafe,
298
+ force_backend=force_backend,
299
+ )
300
+
301
+
302
+ # forbid in graph
303
+ set_stance._dynamo_forbidden = True # type: ignore[attr-defined]
304
+
305
+
306
+ def cudagraph_mark_step_begin():
307
+ """
308
+ Indicates that a new iteration of inference or training is about to begin.
309
+
310
+ CUDA Graphs will free tensors of a prior iteration. A new iteration is started on each invocation of
311
+ torch.compile, so long as there is not a pending backward that has not been called.
312
+
313
+ If that heuristic is wrong, such as in the following example, manually mark it with this api.
314
+
315
+ .. code-block:: python
316
+
317
+ @torch.compile(mode="reduce-overhead")
318
+ def rand_foo():
319
+ return torch.rand([4], device="cuda")
320
+
321
+ for _ in range(5):
322
+ torch.compiler.cudagraph_mark_step_begin()
323
+ rand_foo() + rand_foo()
324
+
325
+ For more details, see `torch.compiler_cudagraph_trees <https://pytorch.org/docs/main/torch.compiler_cudagraph_trees.html>`__
326
+ """
327
+ from torch._inductor import cudagraph_trees
328
+
329
+ cudagraph_trees.mark_step_begin()
330
+
331
+
332
+ def wrap_numpy(fn):
333
+ r"""Decorator that turns a function from ``np.ndarray``s to ``np.ndarray``s into a function
334
+ from ``torch.Tensor``s to ``torch.Tensor``s.
335
+
336
+ It is designed to be used with :func:`torch.compile` with ``fullgraph=True``. It allows to
337
+ compile a NumPy function as if it were a PyTorch function. This allows you to run NumPy code
338
+ on CUDA or compute its gradients.
339
+
340
+ .. note::
341
+
342
+ This decorator does not work without :func:`torch.compile`.
343
+
344
+ Example::
345
+
346
+ >>> # xdoctest: +REQUIRES(env:TORCH_DOCTEST_CUDA)
347
+ >>> # Compile a NumPy function as a Tensor -> Tensor function
348
+ >>> @torch.compile(fullgraph=True)
349
+ >>> @torch.compiler.wrap_numpy
350
+ >>> def fn(a: np.ndarray):
351
+ >>> return np.sum(a * a)
352
+ >>> # Execute the NumPy function using Tensors on CUDA and compute the gradients
353
+ >>> x = torch.arange(6, dtype=torch.float32, device="cuda", requires_grad=True)
354
+ >>> out = fn(x)
355
+ >>> out.backward()
356
+ >>> print(x.grad)
357
+ tensor([ 0., 2., 4., 6., 8., 10.], device='cuda:0')
358
+ """
359
+ from torch._dynamo.external_utils import wrap_numpy as wrap
360
+
361
+ return wrap(fn)
362
+
363
+
364
+ _is_compiling_flag: bool = False
365
+
366
+
367
+ def is_compiling() -> bool:
368
+ """
369
+ Indicates whether a graph is executed/traced as part of torch.compile() or torch.export().
370
+
371
+ Note that there are 2 other related flags that should deprecated eventually:
372
+ * torch._dynamo.external_utils.is_compiling()
373
+ * torch._utils.is_compiling()
374
+
375
+ Example::
376
+
377
+ >>> def forward(self, x):
378
+ >>> if not torch.compiler.is_compiling():
379
+ >>> pass # ...logic that is not needed in a compiled/traced graph...
380
+ >>>
381
+ >>> # ...rest of the function...
382
+ """
383
+ if torch.jit.is_scripting():
384
+ return False
385
+ else:
386
+ return _is_compiling_flag
387
+
388
+
389
+ def is_dynamo_compiling() -> bool:
390
+ """
391
+ Indicates whether a graph is traced via TorchDynamo.
392
+
393
+ It's stricter than is_compiling() flag, as it would only be set to True when
394
+ TorchDynamo is used.
395
+
396
+ Example::
397
+
398
+ >>> def forward(self, x):
399
+ >>> if not torch.compiler.is_dynamo_compiling():
400
+ >>> pass # ...logic that is not needed in a TorchDynamo-traced graph...
401
+ >>>
402
+ >>> # ...rest of the function...
403
+ """
404
+ return False
lib/python3.12/site-packages/torch/compiler/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (16.2 kB). View file
 
lib/python3.12/site-packages/torch/compiler/__pycache__/config.cpython-312.pyc ADDED
Binary file (1.25 kB). View file
 
lib/python3.12/site-packages/torch/compiler/config.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This is the top-level configuration module for the compiler, containing
3
+ cross-cutting configuration options that affect all parts of the compiler
4
+ stack.
5
+
6
+ You may also be interested in the per-component configuration modules, which
7
+ contain configuration options that affect only a specific part of the compiler:
8
+
9
+ * :mod:`torch._dynamo.config`
10
+ * :mod:`torch._inductor.config`
11
+ * :mod:`torch._functorch.config`
12
+ * :mod:`torch.fx.experimental.config`
13
+ """
14
+
15
+ import os
16
+ import sys
17
+ from typing import Optional
18
+
19
+
20
+ __all__ = [
21
+ "job_id",
22
+ ]
23
+
24
+
25
+ # NB: Docblocks go UNDER variable definitions! Use spacing to make the
26
+ # grouping clear.
27
+
28
+ # FB-internal note: you do NOT have to specify this explicitly specify this if
29
+ # you run on MAST, we will automatically default this to
30
+ # mast:MAST_JOB_NAME:MAST_JOB_VERSION.
31
+ job_id: Optional[str] = os.environ.get("TORCH_COMPILE_JOB_ID", None)
32
+ """
33
+ Semantically, this should be an identifier that uniquely identifies, e.g., a
34
+ training job. You might have multiple attempts of the same job, e.g., if it was
35
+ preempted or needed to be restarted, but each attempt should be running
36
+ substantially the same workload with the same distributed topology. You can
37
+ set this by environment variable with :envvar:`TORCH_COMPILE_JOB_ID`.
38
+
39
+ Operationally, this controls the effect of profile-guided optimization related
40
+ persistent state. PGO state can affect how we perform compilation across
41
+ multiple invocations of PyTorch, e.g., the first time you run your program we
42
+ may compile twice as we discover what inputs are dynamic, and then PGO will
43
+ save this state so subsequent invocations only need to compile once, because
44
+ they remember it is dynamic. This profile information, however, is sensitive
45
+ to what workload you are running, so we require you to tell us that two jobs
46
+ are *related* (i.e., are the same workload) before we are willing to reuse
47
+ this information. Notably, PGO does nothing (even if explicitly enabled)
48
+ unless a valid ``job_id`` is available. In some situations, PyTorch can
49
+ configured to automatically compute a ``job_id`` based on the environment it
50
+ is running in.
51
+
52
+ Profiles are always collected on a per rank basis, so different ranks may have
53
+ different profiles. If you know your workload is truly SPMD, you can run with
54
+ :data:`torch._dynamo.config.enable_compiler_collectives` to ensure nodes get
55
+ consistent profiles across all ranks.
56
+ """
57
+
58
+ cache_key_tag: str = os.environ.get("TORCH_COMPILE_CACHE_KEY_TAG", "")
59
+ """
60
+ Tag to be included in the cache key generation for all torch compile caching.
61
+ A common use case for such a tag is to break caches.
62
+ """
63
+
64
+ from torch.utils._config_module import install_config_module
65
+
66
+
67
+ install_config_module(sys.modules[__name__])
lib/python3.12/site-packages/torch/distributed/__init__.py ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ import logging
3
+ import pdb
4
+ import sys
5
+ import traceback
6
+ import typing
7
+
8
+ import torch
9
+
10
+
11
+ log = logging.getLogger(__name__)
12
+
13
+
14
+ def is_available() -> bool:
15
+ """
16
+ Return ``True`` if the distributed package is available.
17
+
18
+ Otherwise,
19
+ ``torch.distributed`` does not expose any other APIs. Currently,
20
+ ``torch.distributed`` is available on Linux, MacOS and Windows. Set
21
+ ``USE_DISTRIBUTED=1`` to enable it when building PyTorch from source.
22
+ Currently, the default value is ``USE_DISTRIBUTED=1`` for Linux and Windows,
23
+ ``USE_DISTRIBUTED=0`` for MacOS.
24
+ """
25
+ return hasattr(torch._C, "_c10d_init")
26
+
27
+
28
+ if is_available() and not torch._C._c10d_init():
29
+ raise RuntimeError("Failed to initialize torch.distributed")
30
+
31
+ # Custom Runtime Errors thrown from the distributed package
32
+ DistError = torch._C._DistError
33
+ DistBackendError = torch._C._DistBackendError
34
+ DistNetworkError = torch._C._DistNetworkError
35
+ DistStoreError = torch._C._DistStoreError
36
+
37
+ if is_available():
38
+ from torch._C._distributed_c10d import (
39
+ _broadcast_coalesced,
40
+ _compute_bucket_assignment_by_size,
41
+ _ControlCollectives,
42
+ _DEFAULT_FIRST_BUCKET_BYTES,
43
+ _make_nccl_premul_sum,
44
+ _register_builtin_comm_hook,
45
+ _register_comm_hook,
46
+ _StoreCollectives,
47
+ _test_python_store,
48
+ _verify_params_across_processes,
49
+ Backend as _Backend,
50
+ BuiltinCommHookType,
51
+ DebugLevel,
52
+ FileStore,
53
+ get_debug_level,
54
+ GradBucket,
55
+ Logger,
56
+ PrefixStore,
57
+ ProcessGroup as ProcessGroup,
58
+ Reducer,
59
+ set_debug_level,
60
+ set_debug_level_from_env,
61
+ Store,
62
+ TCPStore,
63
+ Work as _Work,
64
+ )
65
+
66
+ class _DistributedPdb(pdb.Pdb):
67
+ """
68
+ Supports using PDB from inside a multiprocessing child process.
69
+
70
+ Usage:
71
+ _DistributedPdb().set_trace()
72
+ """
73
+
74
+ def interaction(self, *args, **kwargs):
75
+ _stdin = sys.stdin
76
+ try:
77
+ sys.stdin = open("/dev/stdin")
78
+ pdb.Pdb.interaction(self, *args, **kwargs)
79
+ finally:
80
+ sys.stdin = _stdin
81
+
82
+ _breakpoint_cache: typing.Dict[int, typing.Any] = {}
83
+
84
+ def breakpoint(rank: int = 0, skip: int = 0):
85
+ """
86
+ Set a breakpoint, but only on a single rank. All other ranks will wait for you to be
87
+ done with the breakpoint before continuing.
88
+
89
+ Args:
90
+ rank (int): Which rank to break on. Default: ``0``
91
+ skip (int): Skip the first ``skip`` calls to this breakpoint. Default: ``0``.
92
+ """
93
+ if skip > 0:
94
+ key = hash(str(traceback.format_exc()))
95
+ counter = _breakpoint_cache.get(key, 0) + 1
96
+ _breakpoint_cache[key] = counter
97
+ if counter <= skip:
98
+ log.warning("Skip the breakpoint, counter=%d", counter)
99
+ return
100
+
101
+ if get_rank() == rank:
102
+ pdb = _DistributedPdb()
103
+ pdb.message(
104
+ "\n!!! ATTENTION !!!\n\n"
105
+ f"Type 'up' to get to the frame that called dist.breakpoint(rank={rank})\n"
106
+ )
107
+ pdb.set_trace()
108
+ # If Meta/Python keys are in the TLS, we want to make sure that we ignore them
109
+ # and hit the (default) CPU/CUDA implementation of barrier.
110
+ meta_in_tls = torch._C._meta_in_tls_dispatch_include()
111
+ guard = torch._C._DisableTorchDispatch() # type: ignore[attr-defined]
112
+ torch._C._set_meta_in_tls_dispatch_include(False)
113
+ try:
114
+ barrier()
115
+ finally:
116
+ torch._C._set_meta_in_tls_dispatch_include(meta_in_tls)
117
+ del guard
118
+
119
+ if sys.platform != "win32":
120
+ from torch._C._distributed_c10d import HashStore
121
+
122
+ from .device_mesh import DeviceMesh, init_device_mesh
123
+
124
+ # Variables prefixed with underscore are not auto imported
125
+ # See the comment in `distributed_c10d.py` above `_backend` on why we expose
126
+ # this.
127
+ from .distributed_c10d import * # noqa: F403
128
+ from .distributed_c10d import (
129
+ _all_gather_base,
130
+ _coalescing_manager,
131
+ _CoalescingManager,
132
+ _create_process_group_wrapper,
133
+ _get_process_group_name,
134
+ _rank_not_in_group,
135
+ _reduce_scatter_base,
136
+ get_node_local_rank,
137
+ )
138
+ from .remote_device import _remote_device
139
+ from .rendezvous import (
140
+ _create_store_from_options,
141
+ register_rendezvous_handler,
142
+ rendezvous,
143
+ )
144
+
145
+ set_debug_level_from_env()
146
+
147
+ else:
148
+ # This stub is sufficient to get
149
+ # python test/test_public_bindings.py -k test_correct_module_names
150
+ # working even when USE_DISTRIBUTED=0. Feel free to add more
151
+ # stubs as necessary.
152
+ # We cannot define stubs directly because they confuse pyre
153
+
154
+ class _ProcessGroupStub:
155
+ pass
156
+
157
+ sys.modules["torch.distributed"].ProcessGroup = _ProcessGroupStub # type: ignore[attr-defined]
lib/python3.12/site-packages/torch/distributed/_checkpointable.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ # Copyright (c) Meta Platforms, Inc. and affiliates
3
+ from typing import Any, Protocol, runtime_checkable
4
+
5
+ import torch
6
+
7
+
8
+ @runtime_checkable
9
+ class _Checkpointable(Protocol): # noqa: PYI046
10
+ """
11
+ Interface for checkpointable objects.
12
+ Implemented as a protocol, implicit subtyping is supported so subclasses do not need to inherit this explicitly.
13
+ This is to allow arbitrary objects/tensor subclasses to hook into DCP seamlessly through implementing the interface.
14
+ """
15
+
16
+ def __create_write_items__(self, fqn: str, object: Any):
17
+ """
18
+ Return a list of WriteItems based on object's contents.
19
+ """
20
+ raise NotImplementedError(
21
+ "_Checkpointable._create_write_items is not implemented"
22
+ )
23
+
24
+ def __create_chunk_list__(self):
25
+ """
26
+ Return a list of `ChunkStorageMetadata` based on object's contents.
27
+ """
28
+ raise NotImplementedError(
29
+ "_Checkpointable._create_chunk_list is not implemented"
30
+ )
31
+
32
+ def __get_tensor_shard__(self, index) -> torch.Tensor:
33
+ """
34
+ Return a 'torch.Tensor' shard based on 'MetadataIndex'.
35
+ """
36
+ raise NotImplementedError(
37
+ "_Checkpointable._get_tensor_shard is not implemented"
38
+ )
lib/python3.12/site-packages/torch/distributed/_composable_state.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import weakref
2
+ from typing import cast, Optional
3
+
4
+ import torch.nn as nn
5
+
6
+
7
+ class _State:
8
+ pass
9
+
10
+
11
+ _module_state_mapping: weakref.WeakKeyDictionary[
12
+ nn.Module, weakref.ReferenceType[_State]
13
+ ] = weakref.WeakKeyDictionary()
14
+
15
+
16
+ def _insert_module_state(module: nn.Module, state: _State) -> None:
17
+ global _module_state_mapping
18
+ assert module not in _module_state_mapping, f"Inserting {module} more than once."
19
+ _module_state_mapping[module] = weakref.ref(state)
20
+
21
+
22
+ def _get_module_state(module: nn.Module) -> Optional[_State]:
23
+ """
24
+ Return the ``_State`` in ``model``.
25
+
26
+ Given a ``module``, this API finds out if the module is also a ``_State``
27
+ instance or if the module is managed by a composable API. If the module
28
+ is also a ``_State``, ``module`` will be casted to ``_State` and returned.
29
+ If it is managed by a composable API, the corresponding ``_State`` will
30
+ be returned.
31
+ """
32
+ global _module_state_mapping
33
+ if isinstance(module, _State):
34
+ return cast(_State, module)
35
+ else:
36
+ # https://github.com/pytorch/pytorch/issues/107054
37
+ if module in _module_state_mapping:
38
+ state_ref = _module_state_mapping[module]
39
+ state = state_ref()
40
+ if state is None:
41
+ raise AssertionError("State has already been garbage collected")
42
+ return state
43
+ else:
44
+ return None
lib/python3.12/site-packages/torch/distributed/_functional_collectives.py ADDED
@@ -0,0 +1,1191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ import contextlib
3
+ import sys
4
+ import warnings
5
+ from typing import Any, cast, List, Optional, Tuple, Type, TYPE_CHECKING, Union
6
+
7
+ import torch
8
+ import torch.distributed as dist
9
+ import torch.distributed.distributed_c10d as c10d
10
+ from torch.distributed.device_mesh import DeviceMesh
11
+ from torch.fx.experimental.proxy_tensor import get_proxy_mode
12
+
13
+ from . import _functional_collectives_impl as fun_col_impl
14
+
15
+
16
+ try:
17
+ from torch.utils._cxx_pytree import tree_map_only
18
+ except ImportError:
19
+ from torch.utils._pytree import tree_map_only # type: ignore[no-redef]
20
+
21
+
22
+ if torch._running_with_deploy():
23
+
24
+ def is_torchdynamo_compiling():
25
+ """Can't import torchdynamo in torchdeploy builds currently."""
26
+ return False
27
+
28
+ else:
29
+ try:
30
+ from torch.compiler import is_dynamo_compiling as is_torchdynamo_compiling
31
+ except Exception:
32
+ warnings.warn(
33
+ "Unable to import torchdynamo util `is_torchdynamo_compiling`, so won't support torchdynamo correctly"
34
+ )
35
+
36
+ def is_torchdynamo_compiling():
37
+ return False
38
+
39
+
40
+ """
41
+ New traceable, functional collectives.
42
+ RFC: https://github.com/pytorch/pytorch/issues/93173
43
+
44
+ compiler: trace these ops with plain-old-data schemas, then choose how to lower them.
45
+ eager: execute these 'functional' ops which in eager return AsyncCollectiveTensor subclasses,
46
+ automatically calling .wait() on underlying/hidden async 'work' obj only when fed to
47
+ a downstream op.
48
+
49
+ Issues:
50
+ * Where should these ops live? Couldn't `import torch` if putting these ops in existing torch.distributed files
51
+ * Proper support for eager requires inplace ops. We should explore having it as an option for the API.
52
+ """
53
+
54
+ """
55
+ Functional collectives are asynchronous only and we perform implicit stream synchronization
56
+ on behalf of the user.
57
+
58
+ We use AsyncCollectiveTensor to wrap the result tensor of a collective and it lets us witness
59
+ first usage of the tensor and insert cross stream sync at the right place.
60
+
61
+ The above are the easy bits, the hard one is how we match the Work object returned by
62
+ c10d and the tensor AsyncCollectiveTensor wraps. We alloc the tensor inside the collective
63
+ op implementation (see ``clone()`` call in ``_all_reduce``) and then it's handled by the
64
+ dispatcher which might call other implementations that are allowed to change the returned
65
+ tensor - even return a tensor with a different shape (see ``torch.vmap``).
66
+
67
+ This means the caller of our ops receives a Tensor that is not guaranteed to be the same
68
+ allocated by our implementations and that makes pairing The AsyncTensor to the original
69
+ tensor a lot harder. This pairing is needed so we can lookup the Work object to use.
70
+
71
+ Originally, we tried WeakKeyDictionary to map from Tensor to Work, but because Tensor's
72
+ identity is not stable across dispatch, the op caller would end up with a different Tensor
73
+ instance that would not match any in the dictionary.
74
+
75
+ With Tensor identity out of the question, we decided use the tensor data pointer, which
76
+ should be stable across all the Tensor changes done during dispatch.
77
+
78
+ We have a dictionary of tensor::data_ptr -> Work that we insert right after we call into c10d.
79
+
80
+ We use this dictionary when AsyncCollectiveTensor is used to invoke Work::wait()
81
+
82
+ Finally, we setup a finalizer against the tensor wrapper to observe it getting collected so we
83
+ can clean up stale entries in the dictionary.
84
+
85
+ To eliminate the possibility of races we have a global version counter that is used by the finalizer.
86
+
87
+ As a wise man said once: Don't cross the streams (https://www.youtube.com/watch?v=wyKQe_i9yyo)
88
+
89
+ """
90
+
91
+ """
92
+ Functional collectives can accept any of these types to describe the ranks participating in collectives.
93
+
94
+ The different types will be desugared to a canonical format
95
+ """
96
+ RANK_TYPES = Union[
97
+ List[int],
98
+ List[List[int]],
99
+ dist.ProcessGroup,
100
+ DeviceMesh,
101
+ Tuple["dist.tensor.DeviceMesh", int],
102
+ str,
103
+ ]
104
+
105
+
106
+ """
107
+ User facing APIs for functional collectives
108
+ -------------------------------------------
109
+
110
+ These apis are called by user code and expected to work both in eager execution and compilation,
111
+ but there are significant differences to how the two modes are implemented underneath.
112
+
113
+ Eager execution is 'optimized' using a tensor subclass that schedules the synchronization (via wait_tensor() op)
114
+ just before the tensor is first used. Compiled tracing currently relies on the compiler to perform this optimization,
115
+ and cannot yet correctly trace the AsyncTensor wrapper class. In the future, these paths may be unified
116
+ if sufficient subclass support is added in dynamo.
117
+
118
+ Example: all_reduce is an entrypoint API, and other collectives follow a similar pattern.
119
+
120
+ Here's how it works under torch.compile/dynamo:
121
+ all_reduce(...)
122
+ |--> _expand_group(...) - desugars processgroup into canonical/traceable format
123
+ |--> c10d_functional.all_reduce(...) - dynamo captures this op call, doesn't trace deeper
124
+ |--> _maybe_wrap_tensor(...) - wait_tensor() op is immediately called, no AsyncTensor subclass needed
125
+
126
+ And under eager execution:
127
+ all_reduce(...)
128
+ |--> _expand_group(...) - same as above, but less critical for eager
129
+ |--> c10d_functional.all_reduce(...) - dispatches to real kernel OR records op in trace
130
+ |--> _maybe_wrap_tensor(...) - AsyncTensor wrapper applied to returned tensor,
131
+ which issues wait_tensor() at the time of first use
132
+ """
133
+
134
+
135
+ def wait_tensor(tensor):
136
+ """
137
+ Wait on a tensor returned by the collectives ops.
138
+
139
+ Waiting follows device semantics, which means blocking on CPU and synchronizing streams on CUDA.
140
+ """
141
+ return torch.ops._c10d_functional.wait_tensor(tensor) # type: ignore[attr-defined]
142
+
143
+
144
+ def broadcast(self: torch.Tensor, src: int, group: RANK_TYPES, tag: str = ""):
145
+ """
146
+ Broadcasts the tensor to all processes in the given process group.
147
+
148
+ Args:
149
+ src (int): Source rank
150
+ group (ProcessGroup or List[int]): The process group to work on.
151
+ tag (str, optional): A unique identifier for the collective. Default: empty string
152
+ """
153
+ group_name = _resolve_group_name(group, tag)
154
+ tensor = torch.ops._c10d_functional.broadcast(self, src, group_name)
155
+ return _maybe_wrap_tensor(tensor)
156
+
157
+
158
+ def all_reduce(self: torch.Tensor, reduceOp: str, group: RANK_TYPES, tag: str = ""):
159
+ """
160
+ Reduces the tensor data across all machines in such a way that all get
161
+ the final result.
162
+
163
+ The input tensor is left unmodified.
164
+
165
+ Group can be one of:
166
+ List[int]: ranks participating in the collective.
167
+ List[List[int]]: 2D mesh of ranks taking part of this collective in MPMD.
168
+ ProcessGroup: Will perform a collective using the ranks and tag of the PG.
169
+ DeviceMesh: Do a SPMD collective over all ranks of the mesh
170
+ (DeviceMesh, int): Do a MPMD collective over one dimension of the DeviceMesh
171
+
172
+ :: N.B. If you pass a PG or a 1D list to perform a MPMD collective, the compiler won't be able to recover
173
+ that information and perform collective algebraic optimization. Use other forms of input for that.
174
+ """
175
+ group_name = _resolve_group_name(group, tag)
176
+ tensor = torch.ops._c10d_functional.all_reduce(self, reduceOp.lower(), group_name)
177
+ return _maybe_wrap_tensor(tensor)
178
+
179
+
180
+ def all_gather_tensor(
181
+ self: torch.Tensor,
182
+ gather_dim: int,
183
+ group: RANK_TYPES,
184
+ tag: str = "",
185
+ ):
186
+ """
187
+ Gather tensor data across from all machines and concatenate over ``gather_dim``.
188
+
189
+ Note that it currently only supports gather_dim = 0.
190
+
191
+ The input tensor is left unmodified.
192
+ Group can be one of:
193
+ List[int]: ranks participating in the collective.
194
+ List[List[int]]: 2D mesh of ranks taking part of this collective in MPMD.
195
+ ProcessGroup: Will perform a collective using the ranks and tag of the PG.
196
+ DeviceMesh: Do a SPMD collective over all ranks of the mesh
197
+ (DeviceMesh, int): Do a MPMD collective over one dimension of the DeviceMesh
198
+
199
+ :: N.B. If you pass a PG or a 1D list to perform a MPMD collective, the compiler won't be able to recover
200
+ that information and perform collective algebraic optimization. Use other forms of input for that.
201
+ """
202
+ assert self.is_contiguous()
203
+ group_name = _resolve_group_name(group, tag)
204
+ group_size = c10d._get_group_size_by_name(group_name)
205
+ tensor = torch.ops._c10d_functional.all_gather_into_tensor(
206
+ self, group_size, group_name
207
+ )
208
+ res = _maybe_wrap_tensor(tensor)
209
+ # TODO this should be done inside AsyncCollectiveTensor to delay the wait() call
210
+ if gather_dim != 0:
211
+ # torch.cat access the data so we already need to wait here, first do wait
212
+ # and then chunk + cat avoid us going through ACT dispatching logic again
213
+ if isinstance(res, AsyncCollectiveTensor):
214
+ res = res.wait() # type: ignore[attr-defined]
215
+ res = torch.cat(torch.chunk(res, group_size, dim=0), dim=gather_dim)
216
+ return res
217
+
218
+
219
+ def all_gather_tensor_autograd(
220
+ self: torch.Tensor,
221
+ gather_dim: int,
222
+ group: RANK_TYPES,
223
+ tag: str = "",
224
+ ):
225
+ """
226
+ Gather tensor data across from all machines and concatenate over ``gather_dim``.
227
+
228
+ Note that it currently only supports gather_dim = 0.
229
+
230
+ This function is the same as all_gather_tensor but will propagate the
231
+ backwards gradient across workers.
232
+
233
+ See all_gather_tensor for more details on usage.
234
+ """
235
+ group_name = _resolve_group_name(group, tag)
236
+ group_size = c10d._get_group_size_by_name(group_name)
237
+
238
+ tensor = torch.ops._c10d_functional_autograd.all_gather_into_tensor(
239
+ self, group_size, group_name
240
+ )
241
+ res = _FromTorchTensor.apply(tensor)
242
+ # TODO this should be done inside AsyncCollectiveTensor to delay the wait() call
243
+ if gather_dim != 0:
244
+ # torch.cat access the data so we already need to wait here, first do wait
245
+ # and then chunk + cat avoid us going through ACT dispatching logic again
246
+ if isinstance(res, AsyncCollectiveTensor):
247
+ res = res.wait() # type: ignore[attr-defined]
248
+ res = torch.cat(torch.chunk(res, group_size, dim=0), dim=gather_dim)
249
+ return res
250
+
251
+
252
+ def reduce_scatter_tensor(
253
+ self: torch.Tensor,
254
+ reduceOp: str,
255
+ scatter_dim: int,
256
+ group: RANK_TYPES,
257
+ tag: str = "",
258
+ ):
259
+ """
260
+ Reduces the tensor data across all machines in such a way that all get
261
+ the final result, then scatter the results to corresponding ranks.
262
+
263
+
264
+ The input tensor is left unmodified.
265
+ Group can be one of:
266
+ List[int]: ranks participating in the collective.
267
+ List[List[int]]: 2D mesh of ranks taking part of this collective in MPMD.
268
+ ProcessGroup: Will perform a collective using the ranks and tag of the PG.
269
+ DeviceMesh: Do a SPMD collective over all ranks of the mesh
270
+ (DeviceMesh, int): Do a MPMD collective over one dimension of the DeviceMesh
271
+ :: N.B. If you pass a PG or a 1D list to perform a MPMD collective, the compiler won't be able to recover
272
+ that information and perform collective algebraic optimization. Use other forms of input for that.
273
+ """
274
+ group_name = _resolve_group_name(group, tag)
275
+ group_size = c10d._get_group_size_by_name(group_name)
276
+
277
+ assert (
278
+ self.size(scatter_dim) % group_size == 0
279
+ ), f"input dimension 0 ({self.size(0)} must be a multiple of group_size {group_size}"
280
+ if scatter_dim != 0:
281
+ tensor_list = torch.chunk(self, group_size, dim=scatter_dim)
282
+ self = torch.cat(tensor_list)
283
+
284
+ tensor = torch.ops._c10d_functional.reduce_scatter_tensor(
285
+ self,
286
+ reduceOp.lower(),
287
+ group_size,
288
+ group_name, # type: ignore[possibly-undefined]
289
+ )
290
+ res = _maybe_wrap_tensor(tensor)
291
+ return res
292
+
293
+
294
+ def reduce_scatter_tensor_autograd(
295
+ self: torch.Tensor,
296
+ reduceOp: str,
297
+ scatter_dim: int,
298
+ group: RANK_TYPES,
299
+ tag: str = "",
300
+ ):
301
+ """
302
+ Reduces the tensor data across all machines in such a way that all get
303
+ the final result, then scatter the results to corresponding ranks.
304
+
305
+ This function is the same as reduce_scatter_tensor but will propagate the
306
+ backwards gradient across workers.
307
+
308
+ Currently only the "sum" reduceOp is supported.
309
+
310
+ See reduce_scatter_tensor for more details on usage.
311
+ """
312
+
313
+ group_name = _resolve_group_name(group, tag)
314
+ group_size = c10d._get_group_size_by_name(group_name)
315
+
316
+ assert (
317
+ self.size(scatter_dim) % group_size == 0
318
+ ), f"input dimension 0 ({self.size(0)} must be a multiple of group_size {group_size}"
319
+ if scatter_dim != 0:
320
+ tensor_list = torch.chunk(self, group_size, dim=scatter_dim)
321
+ self = torch.cat(tensor_list)
322
+
323
+ tensor = torch.ops._c10d_functional_autograd.reduce_scatter_tensor(
324
+ self,
325
+ reduceOp.lower(),
326
+ group_size,
327
+ group_name, # type: ignore[possibly-undefined]
328
+ )
329
+ res = _FromTorchTensor.apply(tensor)
330
+ return res
331
+
332
+
333
+ def all_reduce_coalesced(
334
+ self: List[torch.Tensor], reduceOp: str, group: RANK_TYPES, tag: str = ""
335
+ ) -> List[torch.Tensor]:
336
+ """
337
+ Reduces a list of tensors across all machines in such a way that all get
338
+ the final result.
339
+
340
+ The all tensors in the input list are left unmodified.
341
+
342
+ Group can be one of:
343
+ List[int]: ranks participating in the collective.
344
+ List[List[int]]: 2D mesh of ranks taking part of this collective in MPMD.
345
+ ProcessGroup: Will perform a collective using the ranks and tag of the PG.
346
+ DeviceMesh: Do a SPMD collective over all ranks of the mesh
347
+ (DeviceMesh, int): Do a MPMD collective over one dimension of the DeviceMesh
348
+
349
+ :: N.B. If you pass a PG or a 1D list to perform a MPMD collective, the compiler won't be able to recover
350
+ that information and perform collective algebraic optimization. Use other forms of input for that.
351
+ """
352
+ group_name = _resolve_group_name(group, tag)
353
+ tensor_list = torch.ops._c10d_functional.all_reduce_coalesced( # type: ignore[attr-defined]
354
+ self,
355
+ reduceOp.lower(),
356
+ group_name,
357
+ )
358
+ return list(map(_maybe_wrap_tensor, tensor_list))
359
+
360
+
361
+ def all_gather_into_tensor_coalesced(
362
+ self: List[torch.Tensor], group: RANK_TYPES, tag: str = ""
363
+ ) -> List[torch.Tensor]:
364
+ """
365
+ Gather a list of tensors across from all machines.
366
+
367
+ Note that it currently only supports gather_dim = 0.
368
+
369
+ The input tensor is left unmodified.
370
+ Group can be one of:
371
+ List[int]: ranks participating in the collective.
372
+ List[List[int]]: 2D mesh of ranks taking part of this collective in MPMD.
373
+ ProcessGroup: Will perform a collective using the ranks and tag of the PG.
374
+ DeviceMesh: Do a SPMD collective over all ranks of the mesh
375
+ (DeviceMesh, int): Do a MPMD collective over one dimension of the DeviceMesh
376
+
377
+ :: N.B. If you pass a PG or a 1D list to perform a MPMD collective, the compiler won't be able to recover
378
+ that information and perform collective algebraic optimization. Use other forms of input for that.
379
+ """
380
+ group_name = _resolve_group_name(group, tag)
381
+ group_size = c10d._get_group_size_by_name(group_name)
382
+ tensor_list = torch.ops._c10d_functional.all_gather_into_tensor_coalesced( # type: ignore[attr-defined]
383
+ self,
384
+ group_size,
385
+ group_name,
386
+ )
387
+ return list(map(_maybe_wrap_tensor, tensor_list))
388
+
389
+
390
+ def reduce_scatter_tensor_coalesced(
391
+ inputs: List[torch.Tensor],
392
+ reduceOp: str,
393
+ scatter_dim: List[int],
394
+ group: RANK_TYPES,
395
+ tag: str = "",
396
+ ) -> List[torch.Tensor]:
397
+ """
398
+ Reduces a list of tensors across all machines in such a way that all get
399
+ the final result, then scatter the results to corresponding ranks.
400
+
401
+ The input tensors are left unmodified.
402
+ Group can be one of:
403
+ List[int]: ranks participating in the collective.
404
+ List[List[int]]: 2D mesh of ranks taking part of this collective in MPMD.
405
+ ProcessGroup: Will perform a collective using the ranks and tag of the PG.
406
+ DeviceMesh: Do a SPMD collective over all ranks of the mesh
407
+ (DeviceMesh, int): Do a MPMD collective over one dimension of the DeviceMesh
408
+
409
+ :: N.B. If you pass a PG or a 1D list to perform a MPMD collective, the compiler won't be able to recover
410
+ that information and perform collective algebraic optimization. Use other forms of input for that.
411
+ """
412
+ group_name = _resolve_group_name(group, tag)
413
+ group_size = c10d._get_group_size_by_name(group_name)
414
+
415
+ assert len(scatter_dim) == len(inputs)
416
+ for idx, (dim, tensor) in enumerate(zip(scatter_dim, inputs)):
417
+ assert (
418
+ tensor.size(dim) % group_size == 0
419
+ ), f"input dimension {dim} ({tensor.size(dim)} must be a multiple of group_size {group_size} for tensor at index {idx}"
420
+ if dim != 0:
421
+ tensor_list = torch.chunk(tensor, group_size, dim=dim)
422
+ inputs[idx] = torch.cat(tensor_list)
423
+
424
+ tensor_list = torch.ops._c10d_functional.reduce_scatter_tensor_coalesced( # type: ignore[attr-defined]
425
+ inputs,
426
+ reduceOp.lower(),
427
+ group_size,
428
+ group_name, # type: ignore[possibly-undefined]
429
+ )
430
+
431
+ return list(map(_maybe_wrap_tensor, tensor_list))
432
+
433
+
434
+ # This is a bit unsafe: it checks if the first argument in the schema reports as a non-mutable alias.
435
+ # Today, this maps 1:1 with "aten ops that are views".
436
+ def _is_view_op(tgt):
437
+ assert isinstance(tgt, torch._ops.OpOverload)
438
+ schema = tgt._schema
439
+ if len(schema.arguments) > 0:
440
+ first_arg = schema.arguments[0]
441
+ # check if op is a view
442
+ return first_arg.alias_info is not None and not first_arg.alias_info.is_write
443
+
444
+
445
+ def all_to_all_single(
446
+ self: torch.Tensor,
447
+ output_split_sizes: Optional[List[int]],
448
+ input_split_sizes: Optional[List[int]],
449
+ group: RANK_TYPES,
450
+ tag: str = "",
451
+ ) -> torch.Tensor:
452
+ """
453
+ Each process splits input tensor and then scatters the split list
454
+ to all processes in a group. Then concatenate the received tensors from all
455
+ the processes in the group and return single output tensor.
456
+
457
+ Group can be one of:
458
+ List[int]: ranks participating in the collective.
459
+ List[List[int]]: 2D mesh of ranks taking part of this collective in MPMD.
460
+ ProcessGroup: Will perform a collective using the ranks and tag of the PG.
461
+ DeviceMesh: Do a SPMD collective over all ranks of the mesh
462
+ (DeviceMesh, int): Do a MPMD collective over one dimension of the DeviceMesh
463
+
464
+ :: N.B. If you pass a PG or a 1D list to perform a MPMD collective, the compiler won't be able to recover
465
+ that information and perform collective algebraic optimization. Use other forms of input for that.
466
+ """
467
+ if output_split_sizes is not None:
468
+ assert all(
469
+ isinstance(size, (int, torch.SymInt)) for size in output_split_sizes
470
+ ), output_split_sizes
471
+ if input_split_sizes is not None:
472
+ assert all(
473
+ isinstance(size, (int, torch.SymInt)) for size in input_split_sizes
474
+ ), input_split_sizes
475
+ group_name = _resolve_group_name(group, tag)
476
+ group_size = c10d._get_group_size_by_name(group_name)
477
+ if output_split_sizes is None or input_split_sizes is None:
478
+ assert output_split_sizes is None and input_split_sizes is None, (
479
+ "output_split_sizes and input_split_sizes must either be "
480
+ "specified together or both set to None"
481
+ )
482
+ output_split_sizes = [self.shape[0] // group_size] * group_size
483
+ input_split_sizes = output_split_sizes
484
+ tensor = torch.ops._c10d_functional.all_to_all_single( # type: ignore[attr-defined]
485
+ self,
486
+ output_split_sizes,
487
+ input_split_sizes,
488
+ group_name,
489
+ )
490
+ return _maybe_wrap_tensor(tensor)
491
+
492
+
493
+ def all_to_all_single_autograd(
494
+ self: torch.Tensor,
495
+ output_split_sizes: Optional[List[int]],
496
+ input_split_sizes: Optional[List[int]],
497
+ group: RANK_TYPES,
498
+ tag: str = "",
499
+ ) -> torch.Tensor:
500
+ """
501
+ Same as all_to_all_single but supports autograd.
502
+ """
503
+ if output_split_sizes is not None:
504
+ assert all(
505
+ isinstance(size, (int, torch.SymInt)) for size in output_split_sizes
506
+ ), output_split_sizes
507
+ if input_split_sizes is not None:
508
+ assert all(
509
+ isinstance(size, (int, torch.SymInt)) for size in input_split_sizes
510
+ ), input_split_sizes
511
+
512
+ group_name = _resolve_group_name(group, tag)
513
+ group_size = c10d._get_group_size_by_name(group_name)
514
+ if output_split_sizes is None or input_split_sizes is None:
515
+ assert output_split_sizes is None and input_split_sizes is None, (
516
+ "output_split_sizes and input_split_sizes must either be "
517
+ "specified together or both set to None"
518
+ )
519
+ output_split_sizes = [self.shape[0] // group_size] * group_size
520
+ input_split_sizes = output_split_sizes
521
+ tensor = torch.ops._c10d_functional_autograd.all_to_all_single( # type: ignore[attr-defined]
522
+ self,
523
+ output_split_sizes,
524
+ input_split_sizes,
525
+ group_name,
526
+ )
527
+ return _FromTorchTensor.apply(tensor)
528
+
529
+
530
+ def permute_tensor(
531
+ self: torch.Tensor,
532
+ src_dst: List[int],
533
+ group: RANK_TYPES,
534
+ tag: str = "",
535
+ ) -> torch.Tensor:
536
+ """
537
+ Permutes the elements of the tensor according to the given source/destination pairs. `src_dst` should
538
+ be defined such that src_dst[m] == n means m sends to n.
539
+
540
+ Group can be one of:
541
+ List[int]: ranks participating in the collective.
542
+ List[List[int]]: 2D mesh of ranks taking part of this collective in MPMD.
543
+ ProcessGroup: Will perform a collective using the ranks and tag of the PG.
544
+ DeviceMesh: Do a SPMD collective over all ranks of the mesh
545
+ (DeviceMesh, int): Do a MPMD collective over one
546
+ """
547
+ t, rankset, group_size = _expand_group(group, tag)
548
+ local_pg = c10d._find_or_create_pg_by_ranks_and_tag(t, rankset, group_size)
549
+
550
+ output_split_sizes = [0] * group_size
551
+ input_split_sizes = [0] * group_size
552
+ for src, dst in enumerate(src_dst):
553
+ if src == dist.get_rank(local_pg):
554
+ input_split_sizes[dst] = self.numel()
555
+ if dst == dist.get_rank(local_pg):
556
+ output_split_sizes[src] = self.numel()
557
+
558
+ return all_to_all_single(self, output_split_sizes, input_split_sizes, group, tag)
559
+
560
+
561
+ class AsyncCollectiveTensor(torch.Tensor):
562
+ r"""
563
+ A Tensor wrapper subclass that is used to trigger a call to wait
564
+ prior to first use of the underlying tensor.
565
+ Use it inside functional collective pytorch wrappers like the following:
566
+ def functional_collective(self, group, tag):
567
+ tag, rankset, group_size = _expand_group(group, tag)
568
+ tensor = torch.ops.c10d_functional.{collective}(self, tag, rankset, group_size)
569
+ return _maybe_wrap_tensor(tensor)
570
+ """
571
+ elem: torch.Tensor
572
+ completed: bool
573
+
574
+ __slots__ = ["elem", "completed"]
575
+
576
+ @staticmethod
577
+ def __new__(cls, elem: torch.Tensor):
578
+ r = torch.Tensor._make_wrapper_subclass( # type: ignore[attr-defined]
579
+ cls,
580
+ elem.size(),
581
+ strides=elem.stride(),
582
+ storage_offset=elem.storage_offset(),
583
+ dtype=elem.dtype,
584
+ layout=elem.layout,
585
+ device=elem.device,
586
+ requires_grad=elem.requires_grad,
587
+ )
588
+ r.elem = elem
589
+ r.completed = False
590
+ return r
591
+
592
+ def __tensor_flatten__(self):
593
+ return ["elem"], None
594
+
595
+ def tolist(self):
596
+ return self.trigger_wait().tolist()
597
+
598
+ @staticmethod
599
+ def __tensor_unflatten__(inner_tensors, meta, outer_size, outer_stride):
600
+ assert meta is None
601
+ elem = inner_tensors["elem"]
602
+ return AsyncCollectiveTensor(elem)
603
+
604
+ def __coerce_same_metadata_as_tangent__(
605
+ self, expected_metadata: Any, expected_type: Optional[Type] = None
606
+ ):
607
+ if expected_type is not torch.Tensor:
608
+ return None
609
+
610
+ return self.trigger_wait()
611
+
612
+ def __repr__(self) -> str: # type: ignore[override]
613
+ return f"AsyncCollectiveTensor({self.trigger_wait()})"
614
+
615
+ def trigger_wait(self):
616
+ if not self.completed:
617
+ out = wait_tensor(self.elem)
618
+ self.completed = True
619
+ return out
620
+ else:
621
+ return self.elem
622
+
623
+ def wait(self) -> torch.Tensor:
624
+ return wait_tensor(self.elem)
625
+
626
+ def _get_acs_underlying_tensor(self):
627
+ """This method enables _functional_collectives_impl to test if a tensor is an ACS"""
628
+ return self.elem
629
+
630
+ @classmethod
631
+ def __torch_dispatch__(cls, func, types, args=(), kwargs=None):
632
+ if func == torch.ops.aten.view.default:
633
+ # Fast handle aten.view as a lot of view related op goes to aten.view
634
+ # eventually, this avoids pytree slowdown
635
+ res = func(args[0].elem, args[1])
636
+ wrapper_res = AsyncCollectiveTensor(res)
637
+ return wrapper_res
638
+
639
+ is_view_op = _is_view_op(func)
640
+
641
+ def unwrap(e: AsyncCollectiveTensor):
642
+ # wait_tensor is idepotent and will do stream sync only once
643
+ if not is_view_op:
644
+ return e.trigger_wait()
645
+ return e.elem
646
+
647
+ def wrap(e: torch.Tensor):
648
+ # wait_tensor is idepotent and will do stream sync only once
649
+ assert not isinstance(e, AsyncCollectiveTensor)
650
+ res = AsyncCollectiveTensor(e)
651
+ return res
652
+
653
+ unwrapped_args = tree_map_only(AsyncCollectiveTensor, unwrap, args)
654
+ unwrapped_kwargs = tree_map_only(AsyncCollectiveTensor, unwrap, kwargs)
655
+
656
+ # we don't wrap the result as it doesn't need to be waited on.
657
+ out = func(*unwrapped_args, **unwrapped_kwargs)
658
+
659
+ # View ops dont require a sync, so we should re-wrap the outputs.
660
+ if is_view_op:
661
+ out = tree_map_only(torch.Tensor, wrap, out)
662
+
663
+ return out
664
+
665
+ def numpy(self): # type: ignore[override]
666
+ return self.wait().numpy()
667
+
668
+
669
+ """
670
+ Utils and infrastructure for tracing support
671
+ """
672
+
673
+
674
+ def _expand_group(group: RANK_TYPES, tag: str = "") -> Tuple[str, List[int], int]:
675
+ """
676
+ _expand_group desugars the different RANK_TYPES types into a canonical format that is traceable.
677
+
678
+ By having this be part of the explicit eager codepath, we avoid having to specialize behavior inside
679
+ torchdynamo and can still interoperate with processgroup objects or other untraceable forms.
680
+ """
681
+ # had to define this hack _inside_ expand_group to avoid
682
+ # graph_break [('torch.* op returned non-Tensor int
683
+ # caused by 'cast_*` functions being treated as 'torch.*' ops (iiuc)
684
+ if TYPE_CHECKING:
685
+
686
+ def cast_listlistint(x):
687
+ return cast(List[List[int]], x)
688
+
689
+ def cast_listint(x):
690
+ return cast(List[int], x)
691
+
692
+ else:
693
+ # fake cast op for use at runtime since dynamo doesn't support real cast
694
+ # also, dynamo didn't like encountering 'typing' objects ()
695
+ # NotImplementedError: argument of type: <class 'typing._GenericAlias'>
696
+ def cast_listlistint(x):
697
+ return x
698
+
699
+ def cast_listint(x):
700
+ return x
701
+
702
+ rankset: List[int]
703
+ if isinstance(group, list):
704
+ if isinstance(group[0], list):
705
+ nested_list = cast_listlistint(group)
706
+ rankset = []
707
+ group_size = -1
708
+ for rs in nested_list:
709
+ rankset.extend(rs)
710
+ if group_size != -1 and group_size != len(rs):
711
+ raise ValueError(
712
+ f"group sizes must be identical found {group_size} and {len(rs)}"
713
+ )
714
+ group_size = len(rs)
715
+ else:
716
+ rankset = cast_listint(group)
717
+ group_size = len(rankset)
718
+ elif isinstance(group, dist.ProcessGroup):
719
+ rankset = dist.get_process_group_ranks(group)
720
+ group_size = len(rankset)
721
+ tag = tag or c10d._get_group_tag(group)
722
+ elif isinstance(group, DeviceMesh):
723
+ assert (
724
+ group.ndim == 1
725
+ ), "Only 1D mesh is supported, pass in (DeviceMesh, int) together if mesh > 1D"
726
+ # TODO: it should run collective in the whole mesh instead of dim 0
727
+ tag, rankset, _ = group._dim_group_infos[0]
728
+ group_size = len(rankset)
729
+ elif isinstance(group, tuple):
730
+ if (
731
+ len(group) == 2
732
+ and isinstance(group[0], DeviceMesh)
733
+ and isinstance(group[1], int)
734
+ ):
735
+ dmesh = group[0]
736
+ dim = group[1]
737
+ tag, rankset, _ = dmesh._dim_group_infos[dim]
738
+ group_size = len(rankset)
739
+ else:
740
+ raise ValueError("Invalid tuple for group must be (DeviceMesh, int)")
741
+ else:
742
+ raise ValueError(
743
+ "Invalid type for group, must be one of List, Processgroup, DeviceMesh or (DeviceMesh, int)."
744
+ )
745
+
746
+ return (tag, rankset, group_size)
747
+
748
+
749
+ def _resolve_group_name(group: RANK_TYPES, tag: str = "") -> str:
750
+ """
751
+ Given group in RANK_TYPES, return the group name.
752
+ """
753
+ # `tag` will be deprecated. See details in:
754
+ # https://github.com/pytorch/pytorch/issues/93173#issuecomment-1907095208
755
+ if isinstance(group, dist.ProcessGroup):
756
+ return group.group_name
757
+ elif isinstance(group, str):
758
+ return group
759
+ elif isinstance(group, DeviceMesh):
760
+ assert (
761
+ group.ndim == 1
762
+ ), "Only 1D mesh is supported, pass in (DeviceMesh, int) together if mesh > 1D"
763
+ return group._dim_group_infos[0][2]
764
+ elif isinstance(group, tuple):
765
+ if (
766
+ len(group) == 2
767
+ and isinstance(group[0], DeviceMesh)
768
+ and isinstance(group[1], int)
769
+ ):
770
+ dmesh = group[0]
771
+ dim = group[1]
772
+ return dmesh._dim_group_infos[dim][2]
773
+ else:
774
+ raise ValueError("Invalid tuple for group must be (DeviceMesh, int)")
775
+ elif isinstance(group, list):
776
+ if not is_torchdynamo_compiling():
777
+ warnings.warn(
778
+ "The combination of ranks + tag as process group "
779
+ "identifier has been deprecated. Please switch to "
780
+ "using ProcessGroup, DeviceMesh, or group name instead.",
781
+ FutureWarning,
782
+ stacklevel=3,
783
+ )
784
+ return c10d._resolve_group_name_by_ranks_and_tag(cast(List[int], group), tag)
785
+ else:
786
+ raise ValueError(f"Unsupported group type: {type(group)}, {group}")
787
+
788
+
789
+ class _FromTorchTensor(torch.autograd.Function):
790
+ """
791
+ _FromTorchTensor allows autograd to propagate from a normal Tensor to an
792
+ AsyncCollectiveTensor.
793
+ """
794
+
795
+ @staticmethod
796
+ def forward( # type: ignore[override]
797
+ ctx, # pyre-ignore[2]: Parameter must be annotated.
798
+ input: torch.Tensor,
799
+ ) -> torch.Tensor:
800
+ return _maybe_wrap_tensor(input)
801
+
802
+ @staticmethod
803
+ def backward(ctx, grad_output: torch.Tensor) -> torch.Tensor: # type: ignore[override]
804
+ return grad_output
805
+
806
+
807
+ def _are_we_tracing() -> bool:
808
+ if is_torchdynamo_compiling():
809
+ return True
810
+ # If fake mode is turned on, we are almost definitely compiling/tracing.
811
+ if torch._C._get_dispatch_mode(torch._C._TorchDispatchModeKey.FAKE) is not None:
812
+ return True
813
+ return get_proxy_mode() is not None
814
+
815
+
816
+ def _maybe_wrap_tensor(self) -> torch.Tensor:
817
+ if _are_we_tracing():
818
+ return wait_tensor(self)
819
+ res = AsyncCollectiveTensor(self)
820
+ return cast(torch.Tensor, res)
821
+
822
+
823
+ @contextlib.contextmanager
824
+ def allow_inflight_collective_as_graph_input_ctx(value: bool = True):
825
+ """
826
+ Context manager to temporarily set whether inflight collectives are allowed as torch.compile graph inputs.
827
+ Common use case is when the collective is issued in eager (with `async_op=True`) but waited in compiled region:
828
+ ```
829
+ def all_reduce_eager(x):
830
+ y = x * x
831
+ req = dist.all_reduce(y, op=dist.ReduceOp.SUM, async_op=True)
832
+ return y
833
+
834
+ @torch.compile(fullgraph=True)
835
+ def all_reduce_wait_compiled(y):
836
+ torch.ops.c10d_functional.wait_tensor(y)
837
+ return y * y
838
+
839
+ x = torch.ones(1280, 1280, device="cuda") + self.rank
840
+ # the context manager ensures that `wait_tensor(y)` will wait on the correct work object
841
+ with allow_inflight_collective_as_graph_input_ctx():
842
+ y = all_reduce_eager(x)
843
+ z = all_reduce_wait_compiled(y)
844
+ ```
845
+ With this context manager, when a collective is called, under the hood the work object of the collective
846
+ will be registered in the work registry, and the wait_tensor() in compiled region called on
847
+ the output tensor of the collective will wait on the correct work object.
848
+ """
849
+ previous = torch._C._distributed_c10d._allow_inflight_collective_as_graph_input()
850
+
851
+ try:
852
+ torch._C._distributed_c10d._set_allow_inflight_collective_as_graph_input(value)
853
+ yield
854
+ finally:
855
+ torch._C._distributed_c10d._set_allow_inflight_collective_as_graph_input(
856
+ previous
857
+ )
858
+
859
+
860
+ def _all_gather_into_tensor_coalesced_meta(self, tag, rankset, group_size):
861
+ def mk_out_tensor(shard):
862
+ out_size = list(shard.size())
863
+ out_size[0] *= group_size
864
+ out_tensor = shard.new_empty(out_size)
865
+ return out_tensor
866
+
867
+ return [mk_out_tensor(t) for t in self]
868
+
869
+
870
+ # We now register meta kernels to deal with tracing
871
+ def _broadcast_meta(self, *args):
872
+ return torch.empty_like(self)
873
+
874
+
875
+ def _all_reduce_meta(self, *args):
876
+ return torch.empty_like(self)
877
+
878
+
879
+ def _wait_tensor_meta(self, *args):
880
+ return torch.empty_like(self)
881
+
882
+
883
+ def _all_gather_into_tensor_meta(shard, tag, rankset, group_size):
884
+ out_size = list(shard.size())
885
+ out_size[0] *= group_size
886
+ return shard.new_empty(out_size)
887
+
888
+
889
+ def _reduce_scatter_tensor_meta(input, reduce_op, tag, rankset, group_size):
890
+ out_size = list(input.size())
891
+ out_size[0] //= group_size
892
+ return input.new_empty(out_size)
893
+
894
+
895
+ def _all_reduce_coalesced_meta(self, *args):
896
+ return [torch.empty_like(t) for t in self]
897
+
898
+
899
+ def _all_reduce__meta(inp, *args):
900
+ return inp
901
+
902
+
903
+ def _broadcast__meta(inp, *args):
904
+ return inp
905
+
906
+
907
+ def _all_reduce_coalesced__meta(inputs, *args):
908
+ return inputs
909
+
910
+
911
+ def _reduce_scatter_tensor_coalesced_meta(inputs, reduceOp, tag, rankset, group_size):
912
+ def mk_out_tensor(input):
913
+ out_size = list(input.size())
914
+ out_size[0] //= group_size
915
+ out_tensor = input.new_empty(out_size)
916
+ return out_tensor
917
+
918
+ return [mk_out_tensor(t) for t in inputs]
919
+
920
+
921
+ # NB: We often say all_to_all has dynamic output size, but this is not
922
+ # technically true: instead, what typically happens is you manually
923
+ # communicate the output_split_sizes ahead of time (which is dynamic),
924
+ # but then you pass those sizes explicitly, and the all to all itself
925
+ # isn't dynamic, it just follows the specified output splits
926
+ def _all_to_all_single_meta(
927
+ input, output_split_sizes, input_split_sizes, *args, **kwargs
928
+ ):
929
+ if output_split_sizes is None:
930
+ return input.new_empty(input.size())
931
+ else:
932
+ for s in output_split_sizes:
933
+ torch._check_is_size(s)
934
+ out_size = list(input.size())
935
+ out_size[0] = sum(output_split_sizes)
936
+ return input.new_empty(out_size)
937
+
938
+
939
+ def _all_gather_into_tensor_out_native_meta(input, group_size, group_name, *, out):
940
+ shape = list(input.size())
941
+ shape[0] *= group_size
942
+ return input.new_empty(shape)
943
+
944
+
945
+ def _all_gather_into_tensor_native_meta(input, group_size, group_name):
946
+ shape = list(input.size())
947
+ shape[0] *= group_size
948
+ return input.new_empty(shape)
949
+
950
+
951
+ def _all_gather_into_tensor_coalesced_native_meta(inputs, group_size, group_name):
952
+ return [
953
+ _all_gather_into_tensor_native_meta(input, group_size, group_name)
954
+ for input in inputs
955
+ ]
956
+
957
+
958
+ def _reduce_scatter_tensor_native_meta(inp, reduce_op, group_size, group_name):
959
+ shape = list(inp.size())
960
+ shape[0] //= group_size
961
+ return inp.new_empty(shape)
962
+
963
+
964
+ def _reduce_scatter_tensor_coalesced_native_meta(
965
+ inputs, reduce_op, group_size, group_name
966
+ ):
967
+ return [
968
+ _reduce_scatter_tensor_native_meta(inp, reduce_op, group_size, group_name)
969
+ for inp in inputs
970
+ ]
971
+
972
+
973
+ if not torch._running_with_deploy():
974
+ # Library MUST be defined at module scope or it doesn't work
975
+ # Creating a "DEF" Library always crashes torch::deploy so we create our
976
+ # Library instances here guarded against running inside it
977
+ lib_impl = torch.library.Library("_c10d_functional", "IMPL")
978
+ lib_impl.impl("all_reduce", _all_reduce_meta, "Meta")
979
+ lib_impl.impl("all_reduce_", _all_reduce__meta, "Meta")
980
+ lib_impl.impl("all_reduce_coalesced", _all_reduce_coalesced_meta, "Meta")
981
+ lib_impl.impl("all_reduce_coalesced_", _all_reduce_coalesced__meta, "Meta")
982
+ lib_impl.impl("wait_tensor", _wait_tensor_meta, "Meta")
983
+ lib_impl.impl(
984
+ "all_gather_into_tensor_out", _all_gather_into_tensor_out_native_meta, "Meta"
985
+ )
986
+ lib_impl.impl("all_gather_into_tensor", _all_gather_into_tensor_native_meta, "Meta")
987
+ lib_impl.impl(
988
+ "all_gather_into_tensor_coalesced",
989
+ _all_gather_into_tensor_coalesced_native_meta,
990
+ "Meta",
991
+ )
992
+ lib_impl.impl("reduce_scatter_tensor", _reduce_scatter_tensor_native_meta, "Meta")
993
+ lib_impl.impl(
994
+ "reduce_scatter_tensor_coalesced",
995
+ _reduce_scatter_tensor_coalesced_native_meta,
996
+ "Meta",
997
+ )
998
+ lib_impl.impl("all_to_all_single", _all_to_all_single_meta, "Meta")
999
+ lib_impl.impl("broadcast", _broadcast_meta, "Meta")
1000
+ lib_impl.impl("broadcast_", _broadcast__meta, "Meta")
1001
+
1002
+ # mark these ops has side effect so that they won't be removed by DCE
1003
+ torch.fx.node.has_side_effect(torch.ops._c10d_functional.wait_tensor.default)
1004
+ torch.fx.node.has_side_effect(torch.ops._c10d_functional.wait_tensor)
1005
+
1006
+ # Register legacy ops for backward compatibility
1007
+ # TODO(yifu): remove these in functional collective beta release
1008
+ legacy_lib = torch.library.Library("c10d_functional", "DEF")
1009
+ legacy_lib_impl = torch.library.Library("c10d_functional", "IMPL")
1010
+ ops_defs = [
1011
+ "broadcast(Tensor self, int src, str tag, int[] ranks, int group_size) -> Tensor",
1012
+ "all_reduce(Tensor self, str reduceOp, str tag, int[] ranks, int group_size) -> Tensor",
1013
+ "all_reduce_coalesced(Tensor[] self, str reduceOp, str tag, int[] ranks, int group_size) -> Tensor[]",
1014
+ "wait_tensor(Tensor self) -> Tensor",
1015
+ "all_gather_into_tensor(Tensor shard, str tag, int[] ranks, int group_size) -> Tensor",
1016
+ "all_gather_into_tensor_coalesced(Tensor[] input, str tag, int[] ranks, int group_size) -> Tensor[]",
1017
+ "reduce_scatter_tensor(Tensor input, str reduceOp, str tag, int[] ranks, int group_size) -> Tensor",
1018
+ "reduce_scatter_tensor_coalesced(Tensor[] inputs, str reduceOp, str tag, int[] ranks, int group_size) -> Tensor[]",
1019
+ "all_to_all_single(Tensor input, SymInt[]? output_split_sizes, SymInt[]? input_split_sizes, str tag, int[] ranks, int group_size) -> Tensor", # noqa: B950
1020
+ ]
1021
+
1022
+ my_module = sys.modules[__name__]
1023
+ for op_def in ops_defs:
1024
+ op_name = op_def[0 : op_def.index("(")]
1025
+ backend_impl = getattr(fun_col_impl, f"_{op_name}")
1026
+ legacy_lib.define(op_def, tags=torch.Tag.pt2_compliant_tag)
1027
+ legacy_lib_impl.impl(op_name, backend_impl, "CompositeImplicitAutograd")
1028
+
1029
+ else:
1030
+ warnings.warn(
1031
+ "PyTorch Distributed functional collectives do not work with torch::deploy."
1032
+ )
1033
+
1034
+
1035
+ """
1036
+ Dynamo Remappings allow seamless translation from non-functional collectives of supportable form into
1037
+ functional collective calls followed by inplace copy ops, allowing them to be traced into a functional graph.
1038
+
1039
+ We implement this by writing a decomposition and teaching dynamo how to associate it to a corresponding op via
1040
+ the mapping dict below.
1041
+
1042
+ These schemas intentionally match torch.distributed.distributed_c10d.* ops that we are trying to remap from
1043
+ """
1044
+
1045
+
1046
+ def all_gather_tensor_inplace(
1047
+ output_tensor: torch.Tensor,
1048
+ input_tensor: torch.Tensor,
1049
+ group, # TODO add a type,
1050
+ async_op: bool = False,
1051
+ tag: str = "",
1052
+ gather_dim: int = 0,
1053
+ ):
1054
+ assert (
1055
+ not async_op
1056
+ ), "Can't remap async version of inplace op to functional collective"
1057
+
1058
+ group = group or dist.group.WORLD
1059
+ assert group is not None
1060
+
1061
+ return output_tensor.copy_(all_gather_tensor(input_tensor, gather_dim, group, tag))
1062
+
1063
+
1064
+ def reduce_scatter_tensor_inplace(
1065
+ output: torch.Tensor,
1066
+ input: torch.Tensor,
1067
+ op: str = "sum", # TODO type is actually c10d ReduceOp. is this ok?
1068
+ group=None, # TODO add a type
1069
+ async_op: bool = False,
1070
+ scatter_dim: int = 0,
1071
+ tag: str = "",
1072
+ ):
1073
+ assert (
1074
+ not async_op
1075
+ ), "Can't remap async version of inplace op to functional collective"
1076
+
1077
+ group = group or dist.group.WORLD
1078
+ assert group is not None
1079
+
1080
+ return output.copy_(reduce_scatter_tensor(input, op, scatter_dim, group, tag))
1081
+
1082
+
1083
+ REDUCE_OP_TO_STR = {
1084
+ dist.ReduceOp.SUM: "sum",
1085
+ dist.ReduceOp.AVG: "avg",
1086
+ dist.ReduceOp.PRODUCT: "product",
1087
+ dist.ReduceOp.MIN: "min",
1088
+ dist.ReduceOp.MAX: "max",
1089
+ dist.ReduceOp.BAND: "band",
1090
+ dist.ReduceOp.BOR: "bor",
1091
+ dist.ReduceOp.BXOR: "bxor",
1092
+ }
1093
+
1094
+
1095
+ def all_reduce_inplace(
1096
+ tensor: torch.Tensor,
1097
+ op: str = "sum",
1098
+ group=None,
1099
+ async_op: bool = False,
1100
+ tag: str = "",
1101
+ ):
1102
+ assert (
1103
+ not async_op
1104
+ ), "Can't remap async version of inplace op to functional collective"
1105
+
1106
+ group = group or dist.group.WORLD
1107
+ assert group is not None
1108
+
1109
+ return tensor.copy_(all_reduce(tensor, op, group, tag))
1110
+
1111
+
1112
+ def all_to_all_inplace(
1113
+ output: torch.Tensor,
1114
+ input: torch.Tensor,
1115
+ output_split_sizes=None,
1116
+ input_split_sizes=None,
1117
+ group=None,
1118
+ async_op=False,
1119
+ tag: str = "",
1120
+ ):
1121
+ assert (
1122
+ not async_op
1123
+ ), "Can't remap async version of inplace op to functional collective"
1124
+
1125
+ group = group or dist.group.WORLD
1126
+ assert group is not None
1127
+
1128
+ return output.copy_(
1129
+ all_to_all_single(
1130
+ input,
1131
+ output_split_sizes,
1132
+ input_split_sizes,
1133
+ group,
1134
+ tag,
1135
+ )
1136
+ )
1137
+
1138
+
1139
+ def all_gather_inplace(
1140
+ tensor_list: List[torch.Tensor],
1141
+ tensor: torch.Tensor,
1142
+ group=None,
1143
+ async_op=False,
1144
+ tag: str = "",
1145
+ ):
1146
+ assert (
1147
+ not async_op
1148
+ ), "Can't remap async version of inplace op to functional collective"
1149
+ assert all(
1150
+ t.size(0) == tensor.size(0) for t in tensor_list
1151
+ ), "Remapping variable size all_gather is not yet supported"
1152
+
1153
+ group = group or dist.group.WORLD
1154
+ assert group is not None
1155
+
1156
+ output = all_gather_tensor(tensor, 0, group, tag)
1157
+
1158
+ # Use aten.slice instead of aten.split because the latter causes
1159
+ # tensor.shape(0) to be unnecessarily baked in when it's a SymInt.
1160
+ output_splits = []
1161
+ offset = 0
1162
+ for t in tensor_list:
1163
+ output_splits.append(output[offset : offset + t.size(0)])
1164
+ offset += t.size(0)
1165
+ for dst, src in zip(tensor_list, output_splits):
1166
+ dst.copy_(src)
1167
+ return tensor_list
1168
+
1169
+
1170
+ from torch.distributed.distributed_c10d import (
1171
+ _all_gather_base as legacy_all_gather_base,
1172
+ _reduce_scatter_base as legacy_reduce_scatter_base,
1173
+ all_gather as legacy_all_gather,
1174
+ all_gather_into_tensor as legacy_allgather,
1175
+ all_reduce as legacy_allreduce,
1176
+ all_to_all_single as legacy_all_to_all_single,
1177
+ reduce_scatter_tensor as legacy_reducescatter,
1178
+ )
1179
+
1180
+
1181
+ # This dict should contain sets of functions that dynamo is allowed to remap.
1182
+ # Functions in this set should accept the same args/kwargs 1:1 as their mapping.
1183
+ traceable_collective_remaps = {
1184
+ legacy_allgather: all_gather_tensor_inplace,
1185
+ legacy_reducescatter: reduce_scatter_tensor_inplace,
1186
+ legacy_allreduce: all_reduce_inplace,
1187
+ legacy_all_to_all_single: all_to_all_inplace,
1188
+ legacy_all_gather: all_gather_inplace,
1189
+ legacy_reduce_scatter_base: reduce_scatter_tensor_inplace,
1190
+ legacy_all_gather_base: all_gather_tensor_inplace,
1191
+ }
lib/python3.12/site-packages/torch/distributed/_functional_collectives_impl.py ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ from typing import List, Optional
3
+
4
+ import torch
5
+ import torch.distributed.distributed_c10d as c10d
6
+
7
+
8
+ """
9
+ This file contains the op impls for the legacy (c10d_functional) functional collectives.
10
+ These impls simply call into the native (_c10d_functional) functional collectives.
11
+ """
12
+
13
+
14
+ def _broadcast(input, src, tag, ranks, group_size):
15
+ group_name = c10d._resolve_group_name_by_ranks_and_tag(ranks, tag)
16
+ return torch.ops._c10d_functional.broadcast(
17
+ input,
18
+ src,
19
+ group_name,
20
+ )
21
+
22
+
23
+ def _all_reduce(input, reduce_op, tag, ranks, group_size):
24
+ group_name = c10d._resolve_group_name_by_ranks_and_tag(ranks, tag)
25
+ return torch.ops._c10d_functional.all_reduce(
26
+ input,
27
+ reduce_op,
28
+ group_name,
29
+ )
30
+
31
+
32
+ def _all_reduce_coalesced(inputs, reduce_op, tag, ranks, group_size):
33
+ group_name = c10d._resolve_group_name_by_ranks_and_tag(ranks, tag)
34
+ return torch.ops._c10d_functional.all_reduce_coalesced(
35
+ inputs,
36
+ reduce_op,
37
+ group_name,
38
+ )
39
+
40
+
41
+ def _all_gather_into_tensor(input, tag, ranks, group_size):
42
+ group_name = c10d._resolve_group_name_by_ranks_and_tag(ranks, tag)
43
+ return torch.ops._c10d_functional.all_gather_into_tensor(
44
+ input,
45
+ group_size,
46
+ group_name,
47
+ )
48
+
49
+
50
+ def _all_gather_into_tensor_coalesced(input, tag, ranks, group_size):
51
+ group_name = c10d._resolve_group_name_by_ranks_and_tag(ranks, tag)
52
+ return torch.ops._c10d_functional.all_gather_into_tensor_coalesced(
53
+ input,
54
+ group_size,
55
+ group_name,
56
+ )
57
+
58
+
59
+ def _reduce_scatter_tensor(
60
+ input: torch.Tensor,
61
+ reduce_op: str,
62
+ tag: str,
63
+ ranks: List[int],
64
+ group_size: int,
65
+ ):
66
+ group_name = c10d._resolve_group_name_by_ranks_and_tag(ranks, tag)
67
+ return torch.ops._c10d_functional.reduce_scatter_tensor(
68
+ input,
69
+ reduce_op,
70
+ group_size,
71
+ group_name,
72
+ )
73
+
74
+
75
+ def _reduce_scatter_tensor_coalesced(
76
+ inputs: List[torch.Tensor],
77
+ reduce_op: str,
78
+ tag: str,
79
+ ranks: List[int],
80
+ group_size: int,
81
+ ):
82
+ group_name = c10d._resolve_group_name_by_ranks_and_tag(ranks, tag)
83
+ return torch.ops._c10d_functional.reduce_scatter_tensor_coalesced(
84
+ inputs,
85
+ reduce_op,
86
+ group_size,
87
+ group_name,
88
+ )
89
+
90
+
91
+ def _all_to_all_single(
92
+ input: torch.Tensor,
93
+ output_split_sizes: Optional[List[int]],
94
+ input_split_sizes: Optional[List[int]],
95
+ tag: str,
96
+ ranks: List[int],
97
+ group_size: int,
98
+ ):
99
+ if output_split_sizes is None or input_split_sizes is None:
100
+ assert output_split_sizes is None and input_split_sizes is None, (
101
+ "output_split_sizes and input_split_sizes must either be "
102
+ "specified together or both set to None"
103
+ )
104
+ output_split_sizes = [input.shape[0] // group_size] * group_size
105
+ input_split_sizes = output_split_sizes
106
+
107
+ group_name = c10d._resolve_group_name_by_ranks_and_tag(ranks, tag)
108
+ return torch.ops._c10d_functional.all_to_all_single(
109
+ input,
110
+ output_split_sizes,
111
+ input_split_sizes,
112
+ group_name,
113
+ )
114
+
115
+
116
+ def _wait_tensor(tensor: torch.Tensor) -> torch.Tensor:
117
+ return torch.ops._c10d_functional.wait_tensor(tensor)
lib/python3.12/site-packages/torch/distributed/_shard/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .api import _shard_tensor, load_with_process_group, shard_module, shard_parameter
lib/python3.12/site-packages/torch/distributed/_shard/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (333 Bytes). View file
 
lib/python3.12/site-packages/torch/distributed/_shard/__pycache__/_utils.cpython-312.pyc ADDED
Binary file (1.48 kB). View file
 
lib/python3.12/site-packages/torch/distributed/_shard/__pycache__/api.cpython-312.pyc ADDED
Binary file (13 kB). View file
 
lib/python3.12/site-packages/torch/distributed/_shard/__pycache__/common_op_utils.cpython-312.pyc ADDED
Binary file (3.01 kB). View file
 
lib/python3.12/site-packages/torch/distributed/_shard/__pycache__/metadata.cpython-312.pyc ADDED
Binary file (3.18 kB). View file
 
lib/python3.12/site-packages/torch/distributed/_shard/__pycache__/op_registry_utils.cpython-312.pyc ADDED
Binary file (1.53 kB). View file
 
lib/python3.12/site-packages/torch/distributed/_shard/__pycache__/sharder.cpython-312.pyc ADDED
Binary file (1.51 kB). View file
 
lib/python3.12/site-packages/torch/distributed/_shard/_utils.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Sequence
2
+
3
+ import torch
4
+ from torch.distributed._shard.metadata import ShardMetadata
5
+
6
+
7
+ DEPRECATE_MSG = "Please use DTensor instead and we are deprecating ShardedTensor."
8
+
9
+
10
+ def narrow_tensor_by_index(
11
+ tensor: torch.Tensor,
12
+ offsets: Sequence[int],
13
+ sizes: Sequence[int],
14
+ ) -> torch.Tensor:
15
+ """
16
+ Narrow the tensor according to ``offsets`` and ``sizes``.
17
+ """
18
+ narrowed_tensor = tensor
19
+ for idx, (offset, size) in enumerate(zip(offsets, sizes)):
20
+ if size < tensor.size(idx):
21
+ # Reshape to get shard for this rank and we don't want autograd
22
+ # recording here for the narrow op and 'local_shard' should be a
23
+ # leaf variable in the autograd graph.
24
+ narrowed_tensor = narrowed_tensor.narrow(idx, offset, size)
25
+ return narrowed_tensor
26
+
27
+
28
+ def narrow_tensor(tensor: torch.Tensor, metadata: ShardMetadata) -> torch.Tensor:
29
+ """
30
+ Narrow the tensor according to the metadata
31
+ """
32
+ return narrow_tensor_by_index(tensor, metadata.shard_offsets, metadata.shard_sizes)
lib/python3.12/site-packages/torch/distributed/_shard/api.py ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ from contextlib import contextmanager
3
+ from typing import Optional
4
+
5
+ import torch
6
+ import torch.distributed as dist
7
+ import torch.nn as nn
8
+ from torch.distributed import distributed_c10d
9
+ from torch.distributed._shard.sharded_tensor import ShardedTensor
10
+
11
+ from .sharder import Sharder
12
+ from .sharding_plan import ShardingPlan
13
+ from .sharding_spec import ChunkShardingSpec, ShardingSpec
14
+
15
+
16
+ def _shard_tensor(
17
+ tensor: torch.Tensor, sharding_spec: ShardingSpec, src_rank=0, process_group=None
18
+ ) -> ShardedTensor:
19
+ """
20
+ Given a :class:`torch.Tensor`, it shards that tensor according to the provided
21
+ ``sharding_spec``. ``src_rank`` denotes the source rank which would be
22
+ used as the ground truth of the data which would be scattered as shards
23
+ across the rest of the ranks.
24
+
25
+ Args:
26
+ tensor (:class:`torch.Tensor`): Tensor needs to be sharded.
27
+ sharding_spec (:class:`torch.distributed._shard.sharding_spec.ShardingSpec`): The specification
28
+ describing how to shard the Tensor.
29
+
30
+ Keyword args:
31
+ src_rank (int, optional): The source rank which is used as the ground truth of
32
+ the data for the parameter that would be sharded and scattered
33
+ across the rest of the ranks.
34
+ Default: 0.
35
+ process_group (ProcessGroup, optional): The process group to work on. If None,
36
+ the default process group will be used.
37
+
38
+ Returns:
39
+ A :class:`ShardedTensor` sharded from the given tensor.
40
+
41
+ .. warning::
42
+ Only :class:`torch.distributed._shard.sharding_spec.ChunkShardingSpec` is
43
+ currently supported as the ``sharding_spec``.
44
+ """
45
+ if not tensor.is_contiguous():
46
+ raise ValueError("input tensor is not a contiguous Tensor")
47
+
48
+ pg = (
49
+ process_group
50
+ if process_group is not None
51
+ else distributed_c10d._get_default_group()
52
+ )
53
+ world_size = dist.get_world_size(pg)
54
+ current_rank = dist.get_rank(pg)
55
+
56
+ # Validate src_rank and sharding_spec are same across all ranks.
57
+ gathered_list = [None] * world_size
58
+ dist.all_gather_object(gathered_list, (src_rank, sharding_spec), group=pg)
59
+
60
+ for idx, entry in enumerate(gathered_list):
61
+ if src_rank != entry[0]: # type: ignore[index]
62
+ raise ValueError(
63
+ f"src_rank={src_rank} on rank: {current_rank} does not " # type: ignore[index]
64
+ f"match with src_rank={entry[0]} on rank: {idx}" # type: ignore[index]
65
+ )
66
+ if sharding_spec != entry[1]: # type: ignore[index]
67
+ raise ValueError(
68
+ f"sharding_spec={sharding_spec} on rank: {current_rank} does not " # type: ignore[index]
69
+ f"match with sharding_spec={entry[1]} on rank: {idx}" # type: ignore[index]
70
+ )
71
+
72
+ st = sharding_spec.shard(tensor, src_rank=src_rank, process_group=pg)
73
+
74
+ return st
75
+
76
+
77
+ def shard_parameter(
78
+ module: torch.nn.Module,
79
+ param_name: str,
80
+ sharding_spec: ShardingSpec,
81
+ src_rank=0,
82
+ process_group=None,
83
+ ):
84
+ """
85
+ Given a :class:`torch.nn.Module`, a ``param_name`` for a parameter in that
86
+ module, it shards that parameter according to the provided
87
+ ``sharding_spec``. ``src_rank`` denotes the source rank which would be
88
+ used as the ground truth of the data which would be scattered as shards
89
+ across the rest of the ranks.
90
+
91
+ This method replaces ``module.param_name`` with a
92
+ :class:`torch.distributed._sharded_tensor.ShardedTensor`
93
+
94
+ Args:
95
+ module (:class:`torch.nn.Module`): Module whose parameter needs to be sharded.
96
+ param_name (str): Name of the parameter of ``module`` that needs to be sharded.
97
+ sharding_spec (:class:`torch.distributed._shard.sharding_spec.ShardingSpec`): The specification
98
+ describing how to shard the Tensor.
99
+
100
+ Keyword args:
101
+ src_rank (int, optional): The source rank which is used as the ground truth of
102
+ the data for the parameter that would be sharded and scattered
103
+ across the rest of the ranks.
104
+ Default: 0.
105
+ process_group (ProcessGroup, optional): The process group to work on. If None,
106
+ the default process group will be used.
107
+
108
+ .. warning::
109
+ Only :class:`torch.distributed._shard.sharding_spec.ChunkShardingSpec` is
110
+ currently supported as the ``sharding_spec``.
111
+ """
112
+ # Perform some validation first.
113
+ if not hasattr(module, param_name):
114
+ raise AttributeError(f"{module._get_name()} has no attribute `{param_name}`")
115
+
116
+ tensor = getattr(module, param_name)
117
+ if not isinstance(tensor, torch.Tensor):
118
+ raise ValueError(
119
+ f"Expected {type(module).__name__}.{param_name} to be a Tensor, but found {type(tensor).__name__}"
120
+ )
121
+
122
+ if not tensor.is_contiguous():
123
+ raise ValueError(f"param: {param_name} is not a contiguous Tensor")
124
+
125
+ st = _shard_tensor(tensor, sharding_spec, src_rank, process_group)
126
+
127
+ # Replace param with ShardedTensor.
128
+ module.register_parameter(param_name, nn.Parameter(st))
129
+
130
+
131
+ # Tracks the current process group in the load context manager.
132
+ _CURRENT_PROCESS_GROUP: Optional[dist.ProcessGroup] = None
133
+
134
+
135
+ @contextmanager
136
+ def load_with_process_group(process_group):
137
+ """
138
+ Context manager to set the process group with which to load a ShardedTensor.
139
+ """
140
+ global _CURRENT_PROCESS_GROUP
141
+ if _CURRENT_PROCESS_GROUP is not None:
142
+ raise RuntimeError(
143
+ 'ProcessGroup already set by previous "load_with_process_group" '
144
+ "context manager"
145
+ )
146
+ _CURRENT_PROCESS_GROUP = process_group
147
+ try:
148
+ yield process_group
149
+ finally:
150
+ _CURRENT_PROCESS_GROUP = None
151
+
152
+
153
+ def _get_current_process_group():
154
+ """
155
+ Retrieves the current process group set by ``load_with_process_group``.
156
+ If not set, it just returns the default group.
157
+ """
158
+ global _CURRENT_PROCESS_GROUP
159
+ if _CURRENT_PROCESS_GROUP is None:
160
+ return distributed_c10d._get_default_group()
161
+ else:
162
+ return _CURRENT_PROCESS_GROUP
163
+
164
+
165
+ def _reshard_output(
166
+ module: torch.nn.Module, resharding_spec: ShardingSpec
167
+ ) -> torch.nn.Module:
168
+ """
169
+ Hook a module with output resharding in the forward pass according
170
+ to the given ``resharding_spec``.
171
+
172
+ Args:
173
+ module (:class:`torch.nn.Module`): Module whose output needs to be resharded.
174
+ resharding_spec (:class:`torch.distributed._shard.sharding_spec.ShardingSpec`):
175
+ The specification describing how the output of the module will be resharded.
176
+
177
+ Returns:
178
+ A :class:`torch.nn.Module` object with reshard API hooked.
179
+ """
180
+
181
+ def hook_func(_module, _input, output):
182
+ if isinstance(output, ShardedTensor):
183
+ return output.reshard(resharding_spec)
184
+ return output
185
+
186
+ module.register_forward_hook(hook_func)
187
+ return module
188
+
189
+
190
+ def _collect_local_shard(module: torch.nn.Module) -> torch.nn.Module:
191
+ """
192
+ Hook a module with local shards collection in the forward pass.
193
+
194
+ This API is typically used to convert a sharded representation back to data parallel
195
+ representation. In particular, it returns the local tensor for this Shard. If the
196
+ size along the sharding dimension for the local tensor is 1, this dimension is removed
197
+ from the final result. For example a [4, 16] ShardedTensor across 4 ranks is typically
198
+ a local Tensor of size [16] across each rank and not [1, 16] across each rank.
199
+
200
+ Args:
201
+ module (:class:`torch.nn.Module`): Module whose output is ShardedTensor and the
202
+ local tensor value needs to be returned.
203
+
204
+ Returns:
205
+ A :class:`torch.nn.Module` object with collection API hooked.
206
+ """
207
+
208
+ def hook_func(_module, _input, output):
209
+ if isinstance(output, ShardedTensor):
210
+ local_tensor = output.local_tensor()
211
+ # Squeeze the # of dimensions manually, only applicable to ChunkShardingSpec
212
+ sharding_spec = output._sharding_spec
213
+ if (
214
+ isinstance(sharding_spec, ChunkShardingSpec)
215
+ and local_tensor.size(sharding_spec.dim) == 1 # type: ignore[attr-defined, arg-type]
216
+ ):
217
+ local_tensor = local_tensor.squeeze(
218
+ output._sharding_spec.dim # type: ignore[attr-defined]
219
+ )
220
+ return local_tensor
221
+
222
+ module.register_forward_hook(hook_func)
223
+ return module
224
+
225
+
226
+ def shard_module(module: nn.Module, plan: ShardingPlan, src_rank=0, process_group=None):
227
+ """
228
+ Shards a given module according to the provided sharding `plan`. This method
229
+ first shards all the parameters according to the given sharding `plan`. Then if
230
+ `output_plan` and `return_local_tensor` are specified in the sharding `plan`, it
231
+ will tag the output of modules according `output_plan`, convert the module's
232
+ output back to data parallel according to `return_local_tensor`.
233
+
234
+ Needs to be called on all ranks in an SPMD fashion.
235
+
236
+ Args:
237
+ module (:class:`torch.nn.Module`): The module to apply sharding to
238
+ plan (:class:`torch.distributed._shard.sharding_plan.ShardingPlan`):
239
+ The ShardingPlan which specified param name to ShardingSpec to apply to
240
+ each parameter.
241
+
242
+ Keyword args:
243
+ src_rank (int, optional): The source rank which is used as the ground truth of
244
+ the data for the module that would be sharded and scattered across the rest
245
+ of the ranks.
246
+ Default: 0.
247
+ process_group (ProcessGroup, optional): The process group to work on. If None,
248
+ the default process group will be used.
249
+ """
250
+ # record Sharder paths for sanity check on the plan to ensure items in the plan
251
+ # does not conflict with the submodule tree that the Sharder is working with
252
+ sharder_paths = []
253
+ for name, spec in plan.plan.items():
254
+ if isinstance(spec, Sharder):
255
+ sharder_paths.append(name)
256
+
257
+ # shard the parameter according to the ShardingPlan
258
+ for name, spec in plan.plan.items():
259
+ if isinstance(spec, ShardingSpec):
260
+ # if found a sharding spec, try to shard the parameter
261
+ module_path, _, param_name = name.rpartition(".")
262
+
263
+ for sharder_path in sharder_paths:
264
+ if module_path.startswith(sharder_path):
265
+ raise RuntimeError(
266
+ f"ShardingPlan is in-valid, trying to shard a parameter: {name},"
267
+ f" but there's already a Sharder entry for module {sharder_path},"
268
+ f" parameter sharding should not conflict with the submodule tree"
269
+ f" that a Sharder is working with!"
270
+ )
271
+
272
+ mod = module.get_submodule(module_path)
273
+ shard_parameter(
274
+ mod, param_name, spec, src_rank=src_rank, process_group=process_group
275
+ )
276
+ elif isinstance(spec, Sharder):
277
+ parent_mod_path, _, _mod_name = name.rpartition(".")
278
+ if name == "":
279
+ raise KeyError("Module path must not be empty for custom sharder!")
280
+ mod = module.get_submodule(name)
281
+ parent_mod = module.get_submodule(parent_mod_path)
282
+ sharded_mod = spec.shard(mod)
283
+ # swap this submodule with the sharded module
284
+ parent_mod.mod_name = sharded_mod
285
+ else:
286
+ raise TypeError(
287
+ f"Only `ShardingSpec` and `Sharder` are supported to shard '{name}'"
288
+ )
289
+
290
+ # reshard output if there's an entry in `reshard_output` for this module
291
+ if plan.output_plan is not None:
292
+ for module_path, output_spec in plan.output_plan.items():
293
+ if isinstance(output_spec, ShardingSpec):
294
+ mod = module.get_submodule(module_path)
295
+ _reshard_output(mod, output_spec)
296
+ else:
297
+ raise TypeError(
298
+ f"Only `ShardingSpec` is supported as output_plan for '{module_path}'"
299
+ )
300
+ # convert the output back to data parallel for the modules appears in
301
+ # `return_local_tensor` of the plan, we will call `_collect_local_shard`
302
+ # to collect the local tensor for output of modules
303
+ if plan.return_local_tensor is not None:
304
+ for module_path in plan.return_local_tensor:
305
+ mod = module.get_submodule(module_path)
306
+ _collect_local_shard(mod)
lib/python3.12/site-packages/torch/distributed/_shard/checkpoint/__init__.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Keep old package for BC purposes, this file should be removed once
2
+ # everything moves to the `torch.distributed.checkpoint` package.
3
+ import sys
4
+ import warnings
5
+
6
+ import torch
7
+ from torch.distributed.checkpoint import * # noqa: F403
8
+
9
+
10
+ with warnings.catch_warnings():
11
+ warnings.simplefilter("always")
12
+ warnings.warn(
13
+ "`torch.distributed._shard.checkpoint` will be deprecated, "
14
+ "use `torch.distributed.checkpoint` instead",
15
+ DeprecationWarning,
16
+ stacklevel=2,
17
+ )
18
+
19
+ sys.modules["torch.distributed._shard.checkpoint"] = torch.distributed.checkpoint
lib/python3.12/site-packages/torch/distributed/_shard/checkpoint/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (912 Bytes). View file
 
lib/python3.12/site-packages/torch/distributed/_shard/common_op_utils.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ from typing import Optional
3
+
4
+ import torch
5
+ from torch.utils import _pytree as pytree
6
+
7
+
8
+ def _basic_validation(op, args=(), kwargs=None):
9
+ """
10
+ Common validation across all ops go in here.
11
+ """
12
+ from torch.distributed._shard.sharded_tensor import ShardedTensor
13
+
14
+ if len(args) == 0 and (kwargs is None or len(kwargs) == 0):
15
+ raise ValueError(f" No input for '{op.__name__}'!")
16
+
17
+ # Validate types
18
+ has_distributed_tensor = False
19
+
20
+ def is_distributed_tensor(e):
21
+ nonlocal has_distributed_tensor
22
+ if isinstance(e, ShardedTensor):
23
+ has_distributed_tensor = True
24
+
25
+ pytree.tree_map_(is_distributed_tensor, args)
26
+ pytree.tree_map_(is_distributed_tensor, kwargs)
27
+
28
+ if not has_distributed_tensor:
29
+ raise TypeError(
30
+ f"torch function '{op.__name__}', with args: {args} and "
31
+ f"kwargs: {kwargs} are called without any distributed tensor!"
32
+ )
33
+
34
+ # Validate all distributed tensors use the same PG.
35
+ cur_pg: Optional[torch.distributed.ProcessGroup] = None
36
+
37
+ def validate_pg(e):
38
+ nonlocal cur_pg
39
+ if isinstance(e, ShardedTensor):
40
+ if cur_pg is not None and e._process_group is not cur_pg:
41
+ raise RuntimeError(
42
+ "All distributed tensors should use the "
43
+ "same ProcessGroup if used together in an op."
44
+ )
45
+ cur_pg = e._process_group
46
+
47
+ pytree.tree_map_(validate_pg, args)
48
+ pytree.tree_map_(validate_pg, kwargs)
49
+
50
+
51
+ def _register_default_op(op, decorator):
52
+ @decorator(op)
53
+ def tensor_default_op(types, args=(), kwargs=None, pg=None):
54
+ """
55
+ Handles ``__torch_function__`` dispatch for the default tensor ops that
56
+ behave the same as ``torch.Tensor`` such as ``torch.Tensor.shape`` or
57
+ ``torch.Tensor.dtype``. We simply lower to the real op call with
58
+ DisableTorchFunctionSubclass context like ``torch.Tensor.__torch_function__``
59
+ to avoid recursions.
60
+ """
61
+ if kwargs is None:
62
+ kwargs = {}
63
+
64
+ with torch._C.DisableTorchFunctionSubclass():
65
+ return op(*args, **kwargs)
lib/python3.12/site-packages/torch/distributed/_shard/metadata.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ from dataclasses import dataclass
3
+ from functools import reduce
4
+ from typing import List, Optional, Union
5
+
6
+ from torch.distributed.remote_device import _remote_device
7
+
8
+
9
+ @dataclass
10
+ class ShardMetadata:
11
+ """
12
+ Represents a shard of the overall Tensor including its
13
+ offsets, lengths and device placement.
14
+
15
+ Args:
16
+ shard_offsets(List[int]): Offsets in the original tensor indicating
17
+ the start offsets for this shard. Should have the same rank as
18
+ the original tensor.
19
+ shard_sizes(List[int]): Integers indicating the size of each
20
+ dimension for this shard. Should have the same rank as the
21
+ original tensor.
22
+ placement(:class:`torch.distributed._remote_device`):
23
+ Specifies the placement of this shard.
24
+ """
25
+
26
+ __slots__ = ["shard_offsets", "shard_sizes", "placement"]
27
+
28
+ shard_offsets: List[int]
29
+ shard_sizes: List[int]
30
+ placement: Optional[_remote_device]
31
+
32
+ def __init__(
33
+ self,
34
+ shard_offsets: List[int],
35
+ shard_sizes: List[int],
36
+ placement: Optional[Union[str, _remote_device]] = None,
37
+ ):
38
+ self.shard_offsets = shard_offsets
39
+ self.shard_sizes = shard_sizes
40
+ if isinstance(placement, str):
41
+ self.placement = _remote_device(placement)
42
+ else:
43
+ self.placement = placement
44
+ if len(self.shard_offsets) != len(self.shard_sizes):
45
+ raise ValueError(
46
+ f"shard_offsets and shard_sizes should have "
47
+ f"the same number of elements, found {len(self.shard_offsets)} "
48
+ f"and {self.shard_sizes} respectively"
49
+ )
50
+
51
+ for i in range(len(self.shard_offsets)):
52
+ if self.shard_offsets[i] < 0:
53
+ raise ValueError("shard_offsets should be >=0")
54
+ if self.shard_sizes[i] < 0:
55
+ raise ValueError("shard_sizes should be >= 0")
56
+
57
+ def __hash__(self):
58
+ def _hash_reduce(a, b):
59
+ return (a << 8) + hash(b)
60
+
61
+ res = reduce(_hash_reduce, self.shard_offsets, 37)
62
+ res = reduce(_hash_reduce, self.shard_sizes, res)
63
+ res = _hash_reduce(res, self.placement)
64
+ return res
lib/python3.12/site-packages/torch/distributed/_shard/op_registry_utils.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ import functools
3
+ from inspect import signature
4
+
5
+ from .common_op_utils import _basic_validation
6
+
7
+
8
+ """
9
+ Common utilities to register ops on ShardedTensor
10
+ and PartialTensor.
11
+ """
12
+
13
+
14
+ def _register_op(op, func, op_table):
15
+ """
16
+ Performs basic validation and registers the provided op in the given
17
+ op_table.
18
+ """
19
+ if len(signature(func).parameters) != 4:
20
+ raise TypeError(
21
+ f"Custom sharded op function expects signature: "
22
+ f"(types, args, kwargs, process_group), but received "
23
+ f"signature: {signature(func)}"
24
+ )
25
+
26
+ op_table[op] = func
27
+
28
+
29
+ def _decorator_func(wrapped_func, op, op_table):
30
+ """
31
+ Decorator function to register the given ``op`` in the provided
32
+ ``op_table``
33
+ """
34
+
35
+ @functools.wraps(wrapped_func)
36
+ def wrapper(types, args, kwargs, process_group):
37
+ _basic_validation(op, args, kwargs)
38
+ return wrapped_func(types, args, kwargs, process_group)
39
+
40
+ _register_op(op, wrapper, op_table)
41
+ return wrapper
lib/python3.12/site-packages/torch/distributed/_shard/sharded_optim/__init__.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Iterator, Tuple, Union
2
+
3
+ import torch.nn as nn
4
+ from torch.distributed._shard.sharded_tensor import ShardedTensor
5
+
6
+ from .api import ShardedOptimizer
7
+
8
+
9
+ def named_params_with_sharded_tensor(
10
+ module: nn.Module,
11
+ prefix: str = "",
12
+ recurse: bool = True,
13
+ ) -> Iterator[Tuple[str, Union[nn.Parameter, ShardedTensor]]]:
14
+ r"""Returns an iterator over module parameters (together with the
15
+ ShardedTensor parameters), yielding both the name of the parameter
16
+ as well as the parameter itself. This is typically passed to a
17
+ :class:torch.distributed._shard.sharded_optim.ShardedOptimizer
18
+
19
+ Args:
20
+ prefix (str): prefix to prepend to all parameter names.
21
+ recurse (bool): if True, then yields parameters of this module
22
+ and all submodules. Otherwise, yields only parameters that
23
+ are direct members of this module.
24
+
25
+ Yields:
26
+ (str, Union[Tensor, ShardedTensor]): Tuple containing
27
+ the name and parameter (or ShardedTensor parameter)
28
+
29
+ Example::
30
+
31
+ >>> # xdoctest: +SKIP
32
+ >>> model = torch.nn.Linear(*linear_size)
33
+ >>> shard_parameter(model, "weight", spec)
34
+ >>> for name, param in named_params_with_sharded_tensor(model):
35
+ >>> if name in ['weight']:
36
+ >>> print(param.size())
37
+
38
+ """
39
+ modules = module.named_modules(prefix=prefix) if recurse else [(prefix, module)]
40
+
41
+ memo = set()
42
+ for mod_prefix, mod in modules:
43
+ # find all sharded tensor params
44
+ for name, val in vars(mod).items():
45
+ if isinstance(val, ShardedTensor) and val not in memo:
46
+ memo.add(val)
47
+ name = mod_prefix + ("." if mod_prefix else "") + name
48
+ yield name, val
49
+
50
+ # find all nn.Parameters
51
+ for name, val in module.named_parameters():
52
+ yield name, val
lib/python3.12/site-packages/torch/distributed/_shard/sharded_optim/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (2.46 kB). View file
 
lib/python3.12/site-packages/torch/distributed/_shard/sharded_optim/__pycache__/api.cpython-312.pyc ADDED
Binary file (5.53 kB). View file
 
lib/python3.12/site-packages/torch/distributed/_shard/sharded_optim/api.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ from typing import Any, Dict, List, Mapping, Union
3
+
4
+ import torch.optim as optim
5
+ from torch import Tensor
6
+ from torch.distributed._shard.sharded_tensor import ShardedTensor
7
+
8
+
9
+ class ShardedOptimizer(optim.Optimizer):
10
+ def __init__(
11
+ self,
12
+ named_params: Mapping[str, Union[Tensor, ShardedTensor]],
13
+ optimizer_class,
14
+ *optimizer_args,
15
+ **optimizer_kwargs,
16
+ ):
17
+ """
18
+ ShardedOptimizer collects all tensors and local shard tensors of
19
+ ShardedTensor, then use these tensors as ``params`` for optimizers
20
+
21
+ Args:
22
+ named_params (Dict[str, Union[Tensor, ShardedTensor]]) : a Dict
23
+ of parameters, where key is the parameter key, value is either
24
+ Tensor or ShardedTensor parameter.
25
+ optimizer_class (torch.optim.Optimizer): the Optimizer to use
26
+ locally, i.e. torch.optim.SGD, torch.optim.Adagrad, etc.
27
+ *optimizer_args: the arguments to initialize the optimizer.
28
+ **optimizer_kwargs: the key-word arguments to initialize the optimizer.
29
+
30
+ """
31
+ tensors: List[Tensor] = []
32
+ for value in named_params.values():
33
+ if isinstance(value, ShardedTensor):
34
+ tensors.extend(
35
+ local_shard.tensor for local_shard in value.local_shards()
36
+ )
37
+ else:
38
+ tensors.append(value)
39
+
40
+ self.named_params = named_params
41
+ self._optim = optimizer_class(tensors, *optimizer_args, **optimizer_kwargs)
42
+ self.param_groups = self._optim.param_groups
43
+ self.state = self._optim.state
44
+
45
+ def zero_grad(self, set_to_none: bool = True): # type: ignore[override]
46
+ r"""Resets the gradients of all optimized :class:`torch.Tensor` s.
47
+
48
+ Args:
49
+ set_to_none (bool): instead of setting to zero, set the grads to None.
50
+ This will in general have lower memory footprint, and can modestly improve performance.
51
+ However, it changes certain behaviors. For example:
52
+ 1. When the user tries to access a gradient and perform manual ops on it,
53
+ a None attribute or a Tensor full of 0s will behave differently.
54
+ 2. If the user requests ``zero_grad(set_to_none=True)`` followed by a backward pass, ``.grad``\ s
55
+ are guaranteed to be None for params that did not receive a gradient.
56
+ 3. ``torch.optim`` optimizers have a different behavior if the gradient is 0 or None
57
+ (in one case it does the step with a gradient of 0 and in the other it skips
58
+ the step altogether).
59
+ """
60
+ self._optim.zero_grad(set_to_none)
61
+
62
+ def step(self, closure=None):
63
+ r"""Performs a single optimization step (parameter update).
64
+
65
+ Args:
66
+ closure (Callable): A closure that reevaluates the model and
67
+ returns the loss. Optional for most optimizers.
68
+
69
+ .. note::
70
+ Unless otherwise specified, this function should not modify the
71
+ ``.grad`` field of the parameters.
72
+ """
73
+ self._optim.step(closure)
74
+
75
+ def state_dict(self) -> Dict[str, Any]:
76
+ """
77
+ Returned state and param_groups will contain parameter keys
78
+ instead of parameter indices like torch.optim.Optimizer.
79
+ This allows for advanced functionality like optimizer re-sharding to be implemented.
80
+ """
81
+ # TODO: implement state_dict
82
+ raise NotImplementedError("ShardedOptimizer state_dict not implemented yet!")
83
+
84
+ def load_state_dict(self, state_dict: Mapping[str, Any]):
85
+ r"""Loads the ShardedOptimizer state.
86
+
87
+ Args:
88
+ state_dict (dict): ShardedOptimizer state. Should be an object returned
89
+ from a call to :meth:`state_dict`.
90
+ """
91
+ # TODO: implement load_state_dict
92
+ raise NotImplementedError(
93
+ "ShardedOptimizer load_state_dict not implemented yet!"
94
+ )
95
+
96
+ def add_param_group(self, param_group: Any):
97
+ r"""Add a new param group"""
98
+ # TODO: implement add_param_group
99
+ raise NotImplementedError(
100
+ "ShardedOptimizer add_param_group not implemented yet!"
101
+ )
lib/python3.12/site-packages/torch/distributed/_shard/sharded_tensor/__init__.py ADDED
@@ -0,0 +1,490 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ import functools
3
+ from typing import List, TYPE_CHECKING
4
+
5
+ import torch
6
+ from torch.distributed._shard.op_registry_utils import _decorator_func
7
+
8
+ from .api import (
9
+ _CUSTOM_SHARDED_OPS,
10
+ _SHARDED_OPS,
11
+ Shard,
12
+ ShardedTensor,
13
+ ShardedTensorBase,
14
+ ShardedTensorMetadata,
15
+ TensorProperties,
16
+ )
17
+ from .metadata import ShardMetadata # noqa: F401
18
+
19
+
20
+ if TYPE_CHECKING:
21
+ from torch.distributed._shard.sharding_spec import ShardingSpec
22
+ else:
23
+ ShardingSpec = "ShardingSpec"
24
+
25
+
26
+ def empty(
27
+ sharding_spec: ShardingSpec,
28
+ *size,
29
+ dtype=None,
30
+ layout=torch.strided,
31
+ requires_grad=False,
32
+ pin_memory=False,
33
+ memory_format=torch.contiguous_format,
34
+ process_group=None,
35
+ init_rrefs=False,
36
+ ) -> ShardedTensor:
37
+ """
38
+ Returns a :class:`ShardedTensor` filled with uninitialized data.
39
+ Needs to be called on all ranks in an SPMD fashion.
40
+
41
+ Args:
42
+ sharding_spec (:class:`torch.distributed._shard.sharding_spec.ShardingSpec`): The specification
43
+ describing how to shard the Tensor.
44
+ size (int...): a sequence of integers defining the shape of the output
45
+ tensor. Can be a variable number of arguments or a collection like a list or tuple.
46
+
47
+ Keyword args:
48
+ dtype (:class:`torch.dtype`, optional): the desired data type of returned tensor.
49
+ Default: if ``None``, uses a global default (see :func:`torch.set_default_dtype`).
50
+ layout (:class:`torch.layout`, optional): the desired layout of returned Tensor.
51
+ Default: ``torch.strided``.
52
+ requires_grad (bool, optional): If autograd should record operations on the
53
+ returned tensor. Default: ``False``.
54
+ pin_memory (bool, optional): If set, returned tensor would be allocated in
55
+ the pinned memory. Works only for CPU tensors. Default: ``False``.
56
+ memory_format (:class:`torch.memory_format`, optional): the desired memory format of
57
+ returned Tensor. Default: ``torch.contiguous_format``.
58
+ process_group (ProcessGroup, optional): The process group to work on. If None,
59
+ the default process group will be used.
60
+ init_rrefs (bool, optional): Whether or not to initialize
61
+ :class:`torch.distributed.rpc.RRef`s pointing to remote shards.
62
+ Need to initialize the RPC Framework if specified as ``True``.
63
+ Default: ``False``.
64
+
65
+ Returns:
66
+ A :class:`ShardedTensor` object on each rank
67
+ """
68
+ return ShardedTensor(
69
+ sharding_spec,
70
+ *size,
71
+ dtype=dtype,
72
+ layout=layout,
73
+ requires_grad=requires_grad,
74
+ pin_memory=pin_memory,
75
+ memory_format=memory_format,
76
+ process_group=process_group,
77
+ init_rrefs=init_rrefs,
78
+ )
79
+
80
+
81
+ def ones(
82
+ sharding_spec: ShardingSpec,
83
+ *size,
84
+ dtype=None,
85
+ layout=torch.strided,
86
+ requires_grad=False,
87
+ pin_memory=False,
88
+ memory_format=torch.contiguous_format,
89
+ process_group=None,
90
+ init_rrefs=False,
91
+ ) -> ShardedTensor:
92
+ """
93
+ Returns a :class:`ShardedTensor` with the scalar value 1.
94
+ Needs to be called on all ranks in an SPMD fashion.
95
+
96
+ Args:
97
+ sharding_spec (:class:`torch.distributed._shard.sharding_spec.ShardingSpec`): The specification
98
+ describing how to shard the Tensor.
99
+ size (int...): a sequence of integers defining the shape of the output
100
+ tensor. Can be a variable number of arguments or a collection like a list or tuple.
101
+
102
+ Keyword args:
103
+ dtype (:class:`torch.dtype`, optional): the desired data type of returned tensor.
104
+ Default: if ``None``, uses a global default (see :func:`torch.set_default_dtype`).
105
+ layout (:class:`torch.layout`, optional): the desired layout of returned Tensor.
106
+ Default: ``torch.strided``.
107
+ requires_grad (bool, optional): If autograd should record operations on the
108
+ returned tensor. Default: ``False``.
109
+ pin_memory (bool, optional): If set, returned tensor would be allocated in
110
+ the pinned memory. Works only for CPU tensors. Default: ``False``.
111
+ process_group (ProcessGroup, optional): The process group to work on. If None,
112
+ the default process group will be used.
113
+ init_rrefs (bool, optional): Whether or not to initialize
114
+ :class:`torch.distributed.rpc.RRef`s pointing to remote shards.
115
+ Need to initialize the RPC Framework if specified as ``True``.
116
+ Default: ``False``.
117
+
118
+ Returns:
119
+ A :class:`ShardedTensor` object on each rank
120
+ """
121
+ return full(
122
+ sharding_spec,
123
+ size,
124
+ fill_value=1,
125
+ dtype=dtype,
126
+ layout=layout,
127
+ requires_grad=requires_grad,
128
+ pin_memory=pin_memory,
129
+ memory_format=memory_format,
130
+ process_group=process_group,
131
+ init_rrefs=init_rrefs,
132
+ )
133
+
134
+
135
+ def zeros(
136
+ sharding_spec: ShardingSpec,
137
+ *size,
138
+ dtype=None,
139
+ layout=torch.strided,
140
+ requires_grad=False,
141
+ pin_memory=False,
142
+ memory_format=torch.contiguous_format,
143
+ process_group=None,
144
+ init_rrefs=False,
145
+ ) -> ShardedTensor:
146
+ """
147
+ Returns a :class:`ShardedTensor` filled with the scalar value 0.
148
+ Needs to be called on all ranks in an SPMD fashion.
149
+
150
+ Args:
151
+ sharding_spec (:class:`torch.distributed._shard.sharding_spec.ShardingSpec`): The specification
152
+ describing how to shard the Tensor.
153
+ size (int...): a sequence of integers defining the shape of the output
154
+ tensor. Can be a variable number of arguments or a collection like a list or tuple.
155
+
156
+ Keyword args:
157
+ dtype (:class:`torch.dtype`, optional): the desired data type of returned tensor.
158
+ Default: if ``None``, uses a global default (see :func:`torch.set_default_dtype`).
159
+ layout (:class:`torch.layout`, optional): the desired layout of returned Tensor.
160
+ Default: ``torch.strided``.
161
+ requires_grad (bool, optional): If autograd should record operations on the
162
+ returned tensor. Default: ``False``.
163
+ pin_memory (bool, optional): If set, returned tensor would be allocated in
164
+ the pinned memory. Works only for CPU tensors. Default: ``False``.
165
+ process_group (ProcessGroup, optional): The process group to work on. If None,
166
+ the default process group will be used.
167
+ init_rrefs (bool, optional): Whether or not to initialize
168
+ :class:`torch.distributed.rpc.RRef`s pointing to remote shards.
169
+ Need to initialize the RPC Framework if specified as ``True``.
170
+ Default: ``False``.
171
+
172
+ Returns:
173
+ A :class:`ShardedTensor` object on each rank
174
+ """
175
+ return full(
176
+ sharding_spec,
177
+ size,
178
+ fill_value=0,
179
+ dtype=dtype,
180
+ layout=layout,
181
+ requires_grad=requires_grad,
182
+ pin_memory=pin_memory,
183
+ memory_format=memory_format,
184
+ process_group=process_group,
185
+ init_rrefs=init_rrefs,
186
+ )
187
+
188
+
189
+ def full(
190
+ sharding_spec: ShardingSpec,
191
+ size,
192
+ fill_value,
193
+ *,
194
+ dtype=None,
195
+ layout=torch.strided,
196
+ requires_grad=False,
197
+ pin_memory=False,
198
+ memory_format=torch.contiguous_format,
199
+ process_group=None,
200
+ init_rrefs=False,
201
+ ) -> ShardedTensor:
202
+ """
203
+ Creates a :class:`ShardedTensor` filled with fill_value. The tensor's dtype
204
+ is inferred from fill_value. If dtype is specified, it will override the
205
+ inferred type from fill_value. Needs to be called on all ranks in an SPMD fashion.
206
+ Args:
207
+ sharding_spec (:class:`torch.distributed._sharding_spec.ShardingSpec`): The specification
208
+ describing how to shard the Tensor.
209
+ size (int...): a list, tuple, or `torch.Size` of integers defining the shape of the
210
+ output tensor.
211
+ fill_value (Scalar) - the value to fill the output tensor with.
212
+ Keyword args:
213
+ dtype (:class:`torch.dtype`, optional): the desired data type of returned tensor.
214
+ Default: if ``None``, uses a global default (see :func:`torch.set_default_dtype`).
215
+ layout (:class:`torch.layout`, optional): the desired layout of returned Tensor.
216
+ Default: ``torch.strided``.
217
+ requires_grad (bool, optional): If autograd should record operations on the
218
+ returned tensor. Default: ``False``.
219
+ pin_memory (bool, optional): If set, returned tensor would be allocated in
220
+ the pinned memory. Works only for CPU tensors. Default: ``False``.
221
+ process_group (ProcessGroup, optional): The process group to work on. If None,
222
+ the default process group will be used.
223
+ init_rrefs (bool, optional): Whether or not to initialize
224
+ :class:`torch.distributed.rpc.RRef`s pointing to remote shards.
225
+ Need to initialize the RPC Framework if specified as ``True``.
226
+ Default: ``False``.
227
+ Returns:
228
+ A :class:`ShardedTensor` object on each rank
229
+ """
230
+ sharded_tensor = ShardedTensor(
231
+ sharding_spec,
232
+ *size,
233
+ dtype=dtype,
234
+ layout=layout,
235
+ requires_grad=requires_grad,
236
+ pin_memory=pin_memory,
237
+ memory_format=memory_format,
238
+ process_group=process_group,
239
+ init_rrefs=init_rrefs,
240
+ )
241
+ torch.nn.init.constant_(sharded_tensor, fill_value) # type: ignore[arg-type]
242
+ return sharded_tensor
243
+
244
+
245
+ def rand(
246
+ sharding_spec: ShardingSpec,
247
+ *size,
248
+ dtype=None,
249
+ layout=torch.strided,
250
+ requires_grad=False,
251
+ pin_memory=False,
252
+ memory_format=torch.contiguous_format,
253
+ process_group=None,
254
+ init_rrefs=False,
255
+ ) -> ShardedTensor:
256
+ """
257
+ Creates a :class:`ShardedTensor` filled with random numbers from a uniform distribution
258
+ on the interval :math:`[0, 1)`. The shape of the tensor is defined by the
259
+ variable argument `size`. Needs to be called on all ranks in an SPMD fashion.
260
+
261
+ Args:
262
+ sharding_spec (:class:`torch.distributed._shard.sharding_spec.ShardingSpec`): The specification
263
+ describing how to shard the Tensor.
264
+ size (int...): a list, tuple, or `torch.Size` of integers defining the shape of the
265
+ output tensor.
266
+
267
+ Keyword args:
268
+ dtype (:class:`torch.dtype`, optional): the desired data type of returned tensor.
269
+ Default: if ``None``, uses a global default (see :func:`torch.set_default_dtype`).
270
+ layout (:class:`torch.layout`, optional): the desired layout of returned Tensor.
271
+ Default: ``torch.strided``.
272
+ requires_grad (bool, optional): If autograd should record operations on the
273
+ returned tensor. Default: ``False``.
274
+ pin_memory (bool, optional): If set, returned tensor would be allocated in
275
+ the pinned memory. Works only for CPU tensors. Default: ``False``.
276
+ process_group (ProcessGroup, optional): The process group to work on. If None,
277
+ the default process group will be used.
278
+ init_rrefs (bool, optional): Whether or not to initialize
279
+ :class:`torch.distributed.rpc.RRef`s pointing to remote shards.
280
+ Need to initialize the RPC Framework if specified as ``True``.
281
+ Default: ``False``.
282
+
283
+ Returns:
284
+ A :class:`ShardedTensor` object on each rank
285
+ """
286
+ sharded_tensor = ShardedTensor(
287
+ sharding_spec,
288
+ *size,
289
+ dtype=dtype,
290
+ layout=layout,
291
+ requires_grad=requires_grad,
292
+ pin_memory=pin_memory,
293
+ memory_format=memory_format,
294
+ process_group=process_group,
295
+ init_rrefs=init_rrefs,
296
+ )
297
+ torch.nn.init.uniform_(sharded_tensor, 0, 1) # type: ignore[arg-type]
298
+ return sharded_tensor
299
+
300
+
301
+ def randn(
302
+ sharding_spec: ShardingSpec,
303
+ *size,
304
+ dtype=None,
305
+ layout=torch.strided,
306
+ requires_grad=False,
307
+ pin_memory=False,
308
+ memory_format=torch.contiguous_format,
309
+ process_group=None,
310
+ init_rrefs=False,
311
+ ) -> ShardedTensor:
312
+ """
313
+ Creates a :class:`ShardedTensor` filled with random numbers from a uniform distribution
314
+ with mean `0` and variance `1` (also called standard normal distribution). The shape
315
+ of the tensor is defined by the variable argument `size`. Needs to be called on all ranks
316
+ in an SPMD fashion.
317
+
318
+ Args:
319
+ sharding_spec (:class:`torch.distributed._shard.sharding_spec.ShardingSpec`): The specification
320
+ describing how to shard the Tensor.
321
+ size (int...): a list, tuple, or `torch.Size` of integers defining the shape of the
322
+ output tensor.
323
+
324
+ Keyword args:
325
+ dtype (:class:`torch.dtype`, optional): the desired data type of returned tensor.
326
+ Default: if ``None``, uses a global default (see :func:`torch.set_default_dtype`).
327
+ layout (:class:`torch.layout`, optional): the desired layout of returned Tensor.
328
+ Default: ``torch.strided``.
329
+ requires_grad (bool, optional): If autograd should record operations on the
330
+ returned tensor. Default: ``False``.
331
+ pin_memory (bool, optional): If set, returned tensor would be allocated in
332
+ the pinned memory. Works only for CPU tensors. Default: ``False``.
333
+ process_group (ProcessGroup, optional): The process group to work on. If None,
334
+ the default process group will be used.
335
+ init_rrefs (bool, optional): Whether or not to initialize
336
+ :class:`torch.distributed.rpc.RRef`s pointing to remote shards.
337
+ Need to initialize the RPC Framework if specified as ``True``.
338
+ Default: ``False``.
339
+
340
+ Returns:
341
+ A :class:`ShardedTensor` object on each rank
342
+ """
343
+ sharded_tensor = ShardedTensor(
344
+ sharding_spec,
345
+ *size,
346
+ dtype=dtype,
347
+ layout=layout,
348
+ requires_grad=requires_grad,
349
+ pin_memory=pin_memory,
350
+ memory_format=memory_format,
351
+ process_group=process_group,
352
+ init_rrefs=init_rrefs,
353
+ )
354
+ torch.nn.init.normal_(sharded_tensor, 0, 1) # type: ignore[arg-type]
355
+ return sharded_tensor
356
+
357
+
358
+ def init_from_local_shards(
359
+ local_shards: List[Shard], *global_size, process_group=None, init_rrefs=False
360
+ ) -> ShardedTensor:
361
+ """
362
+ Creates an :class:`ShardedTensor` from local shards and the global metadata.
363
+ Needs to be called on all ranks in an SPMD fashion.
364
+
365
+ Args:
366
+ local_shards (List[:class `torch.distributed._shard.sharded_tensor.Shard`]): A list
367
+ of shards that represent the local shards on this rank.
368
+ global_size (int...): a list, tuple, or `torch.Size` of integers defining the
369
+ shape of the overall sharded tensor.
370
+
371
+ Keyword args:
372
+ process_group (ProcessGroup, optional): The process group to work on. If None,
373
+ the default process group will be used.
374
+ init_rrefs (bool, optional): Whether or not to initialize
375
+ :class:`torch.distributed.rpc.RRef`s pointing to remote shards.
376
+ Need to initialize the RPC Framework if specified as ``True``.
377
+ Default: ``False``.
378
+
379
+ Returns:
380
+ A :class:`ShardedTensor` object handle on this rank
381
+
382
+
383
+ Examples:
384
+ Suppose we want construct a sharded tensor on two ranks, global size = (10, 5),
385
+ each shard have a (5, 5) local tensor, we can do it like below:
386
+
387
+ on rank 0:
388
+ >>> # xdoctest: +SKIP("not distributed")
389
+ >>> local_shard_metadata = ShardMetadata(
390
+ >>> shard_offsets=[0, 0],
391
+ >>> shard_lengths=[5, 5],
392
+ >>> placement="rank:0/cuda:0"
393
+ >>> )
394
+ >>> local_shards = [Shard(torch.randn(5, 5), local_shard_metadata)]
395
+ >>> sharded_tensor = init_from_local_shards(local_shards, [10, 5])
396
+
397
+ on rank 1:
398
+ >>> # xdoctest: +SKIP("not distributed")
399
+ >>> local_shard_metadata = ShardMetadata(
400
+ >>> shard_offsets=[5, 0],
401
+ >>> shard_lengths=[5, 5],
402
+ >>> placement="rank:1/cuda:1"
403
+ >>> )
404
+ >>> local_shards = [Shard(torch.randn(5, 5), local_shard_metadata)]
405
+ >>> sharded_tensor = init_from_local_shards(local_shards, [10, 5])
406
+ """
407
+ return ShardedTensor._init_from_local_shards(
408
+ local_shards, *global_size, process_group=process_group, init_rrefs=init_rrefs
409
+ )
410
+
411
+
412
+ def state_dict_hook(module, destination, prefix, local_metadata):
413
+ """
414
+ Hook to add ShardedTensor to Module's ``state_dict``. Needs to be
415
+ registered to the Module using
416
+ :meth:`torch.nn.Module._register_state_dict_hook`.
417
+ """
418
+ for submodule_name, submodule in module.named_modules():
419
+ for attr_name, attr in submodule.__dict__.items():
420
+ if isinstance(attr, ShardedTensor):
421
+ mod_prefix = prefix + submodule_name
422
+ key = mod_prefix + ("." if mod_prefix else "") + attr_name
423
+ destination[key] = attr
424
+
425
+
426
+ def pre_load_state_dict_hook(
427
+ module,
428
+ state_dict,
429
+ prefix,
430
+ local_metadata,
431
+ strict,
432
+ missing_keys,
433
+ unexpected_keys,
434
+ error_msgs,
435
+ ):
436
+ """
437
+ Pre-load state dict hook to add ShardedTensor to the module.
438
+ """
439
+ for submodule_name, submodule in module.named_modules():
440
+ for attr_name in submodule.__dict__.keys():
441
+ mod_prefix = prefix + submodule_name
442
+ key = mod_prefix + ("." if mod_prefix else "") + attr_name
443
+ if key in state_dict:
444
+ if isinstance(state_dict[key], ShardedTensor):
445
+ setattr(submodule, attr_name, state_dict[key])
446
+
447
+
448
+ def custom_sharded_op_impl(func):
449
+ """
450
+ Provides a way for users to write their own custom sharded operator. This
451
+ can be used to override existing ShardedTensor operators or write a new
452
+ one not supported by ShardedTensor. If the operator in question is covered
453
+ by ``__torch_function__`` dispatch and has a ShardedTensor as any of its
454
+ parameters, the function provided will be invoked for that operator.
455
+
456
+ Example::
457
+ >>> # xdoctest: +SKIP
458
+ >>> @custom_sharded_op_impl(torch.nn.functional.linear)
459
+ >>> def my_custom_sharded_linear(types, args, kwargs, process_group):
460
+ >>> ...
461
+ >>> # xdoctest: +SKIP("Undefined variables")
462
+ >>> input = torch.rand(10, 32)
463
+ >>> weight = sharded_tensor.rand(32, 16)
464
+ >>> bias = torch.rand(16)
465
+ >>> # This will call 'my_custom_sharded_linear'
466
+ >>> torch.nn.functional.linear(input, weight, bias)
467
+
468
+ The types, args and kwargs parameters are the same parameters that are
469
+ passed to ``__torch_function__`` dispatch API
470
+ (https://pytorch.org/docs/stable/notes/extending.html#extending-torch).
471
+ There is an additional ``process_group`` parameter which is the
472
+ process_group used for the ShardedTensor and can be used by
473
+ implementations for communications within a sharded implementation.
474
+
475
+ Args:
476
+ func(Callable): Torch function for which we want to provide a sharded
477
+ implementation (ex: torch.nn.functional.linear)
478
+ """
479
+ return functools.partial(_decorator_func, op=func, op_table=_CUSTOM_SHARDED_OPS)
480
+
481
+
482
+ def _sharded_op_impl(func):
483
+ """
484
+ Decorator to register a default sharded op.
485
+ """
486
+ return functools.partial(_decorator_func, op=func, op_table=_SHARDED_OPS)
487
+
488
+
489
+ # Import all builtin sharded ops
490
+ from ._ops import * # noqa: F403
lib/python3.12/site-packages/torch/distributed/_shard/sharded_tensor/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (19 kB). View file
 
lib/python3.12/site-packages/torch/distributed/_shard/sharded_tensor/__pycache__/api.cpython-312.pyc ADDED
Binary file (51.8 kB). View file
 
lib/python3.12/site-packages/torch/distributed/_shard/sharded_tensor/__pycache__/logger.cpython-312.pyc ADDED
Binary file (1.63 kB). View file
 
lib/python3.12/site-packages/torch/distributed/_shard/sharded_tensor/__pycache__/logging_handlers.cpython-312.pyc ADDED
Binary file (551 Bytes). View file
 
lib/python3.12/site-packages/torch/distributed/_shard/sharded_tensor/__pycache__/metadata.cpython-312.pyc ADDED
Binary file (4.21 kB). View file