ZhengyangZhang commited on
Commit
6af4c9b
·
verified ·
1 Parent(s): be40555

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/_higher_order_ops/__init__.py +22 -0
  2. lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/__init__.cpython-312.pyc +0 -0
  3. lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/associative_scan.cpython-312.pyc +0 -0
  4. lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/auto_functionalize.cpython-312.pyc +0 -0
  5. lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/cond.cpython-312.pyc +0 -0
  6. lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/effects.cpython-312.pyc +0 -0
  7. lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/executorch_call_delegate.cpython-312.pyc +0 -0
  8. lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/flex_attention.cpython-312.pyc +0 -0
  9. lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/hints_wrap.cpython-312.pyc +0 -0
  10. lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/invoke_subgraph.cpython-312.pyc +0 -0
  11. lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/map.cpython-312.pyc +0 -0
  12. lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/out_dtype.cpython-312.pyc +0 -0
  13. lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/prim_hop_base.cpython-312.pyc +0 -0
  14. lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/run_const_graph.cpython-312.pyc +0 -0
  15. lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/scan.cpython-312.pyc +0 -0
  16. lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/strict_mode.cpython-312.pyc +0 -0
  17. lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/torchbind.cpython-312.pyc +0 -0
  18. lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/triton_kernel_wrap.cpython-312.pyc +0 -0
  19. lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/utils.cpython-312.pyc +0 -0
  20. lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/while_loop.cpython-312.pyc +0 -0
  21. lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/wrap.cpython-312.pyc +0 -0
  22. lib/python3.12/site-packages/torch/_higher_order_ops/associative_scan.py +441 -0
  23. lib/python3.12/site-packages/torch/_higher_order_ops/auto_functionalize.py +832 -0
  24. lib/python3.12/site-packages/torch/_higher_order_ops/cond.py +559 -0
  25. lib/python3.12/site-packages/torch/_higher_order_ops/effects.py +288 -0
  26. lib/python3.12/site-packages/torch/_higher_order_ops/executorch_call_delegate.py +175 -0
  27. lib/python3.12/site-packages/torch/_higher_order_ops/flex_attention.py +1149 -0
  28. lib/python3.12/site-packages/torch/_higher_order_ops/hints_wrap.py +151 -0
  29. lib/python3.12/site-packages/torch/_higher_order_ops/invoke_subgraph.py +308 -0
  30. lib/python3.12/site-packages/torch/_higher_order_ops/map.py +264 -0
  31. lib/python3.12/site-packages/torch/_higher_order_ops/out_dtype.py +166 -0
  32. lib/python3.12/site-packages/torch/_higher_order_ops/prim_hop_base.py +190 -0
  33. lib/python3.12/site-packages/torch/_higher_order_ops/run_const_graph.py +60 -0
  34. lib/python3.12/site-packages/torch/_higher_order_ops/scan.py +518 -0
  35. lib/python3.12/site-packages/torch/_higher_order_ops/strict_mode.py +108 -0
  36. lib/python3.12/site-packages/torch/_higher_order_ops/torchbind.py +142 -0
  37. lib/python3.12/site-packages/torch/_higher_order_ops/triton_kernel_wrap.py +1417 -0
  38. lib/python3.12/site-packages/torch/_higher_order_ops/utils.py +519 -0
  39. lib/python3.12/site-packages/torch/_higher_order_ops/while_loop.py +340 -0
  40. lib/python3.12/site-packages/torch/_higher_order_ops/wrap.py +240 -0
  41. lib/python3.12/site-packages/torch/_prims/__init__.py +2946 -0
  42. lib/python3.12/site-packages/torch/_prims/context.py +146 -0
  43. lib/python3.12/site-packages/torch/_prims/debug_prims.py +54 -0
  44. lib/python3.12/site-packages/torch/_prims/executor.py +60 -0
  45. lib/python3.12/site-packages/torch/_prims/rng_prims.py +319 -0
  46. lib/python3.12/site-packages/torch/backends/__init__.py +72 -0
  47. lib/python3.12/site-packages/torch/backends/cuda/__init__.py +480 -0
  48. lib/python3.12/site-packages/torch/backends/cuda/__pycache__/__init__.cpython-312.pyc +0 -0
  49. lib/python3.12/site-packages/torch/backends/cudnn/__init__.py +208 -0
  50. lib/python3.12/site-packages/torch/backends/cudnn/__pycache__/__init__.cpython-312.pyc +0 -0
lib/python3.12/site-packages/torch/_higher_order_ops/__init__.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from torch._higher_order_ops.cond import cond
2
+ from torch._higher_order_ops.flex_attention import (
3
+ flex_attention,
4
+ flex_attention_backward,
5
+ )
6
+ from torch._higher_order_ops.hints_wrap import hints_wrapper
7
+ from torch._higher_order_ops.invoke_subgraph import invoke_subgraph
8
+ from torch._higher_order_ops.prim_hop_base import PrimHOPBase
9
+ from torch._higher_order_ops.scan import scan
10
+ from torch._higher_order_ops.while_loop import while_loop
11
+
12
+
13
+ __all__ = [
14
+ "cond",
15
+ "while_loop",
16
+ "invoke_subgraph",
17
+ "scan",
18
+ "flex_attention",
19
+ "flex_attention_backward",
20
+ "hints_wrapper",
21
+ "PrimHOPBase",
22
+ ]
lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (799 Bytes). View file
 
lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/associative_scan.cpython-312.pyc ADDED
Binary file (23.2 kB). View file
 
lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/auto_functionalize.cpython-312.pyc ADDED
Binary file (33.9 kB). View file
 
lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/cond.cpython-312.pyc ADDED
Binary file (24.5 kB). View file
 
lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/effects.cpython-312.pyc ADDED
Binary file (12.7 kB). View file
 
lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/executorch_call_delegate.cpython-312.pyc ADDED
Binary file (9.12 kB). View file
 
lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/flex_attention.cpython-312.pyc ADDED
Binary file (45.5 kB). View file
 
lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/hints_wrap.cpython-312.pyc ADDED
Binary file (7.2 kB). View file
 
lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/invoke_subgraph.cpython-312.pyc ADDED
Binary file (13.4 kB). View file
 
lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/map.cpython-312.pyc ADDED
Binary file (13.2 kB). View file
 
lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/out_dtype.cpython-312.pyc ADDED
Binary file (9.07 kB). View file
 
lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/prim_hop_base.cpython-312.pyc ADDED
Binary file (9.79 kB). View file
 
lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/run_const_graph.cpython-312.pyc ADDED
Binary file (3.72 kB). View file
 
lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/scan.cpython-312.pyc ADDED
Binary file (23.5 kB). View file
 
lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/strict_mode.cpython-312.pyc ADDED
Binary file (5.91 kB). View file
 
lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/torchbind.cpython-312.pyc ADDED
Binary file (7.25 kB). View file
 
lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/triton_kernel_wrap.cpython-312.pyc ADDED
Binary file (54.5 kB). View file
 
lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/utils.cpython-312.pyc ADDED
Binary file (23.1 kB). View file
 
lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/while_loop.cpython-312.pyc ADDED
Binary file (14.3 kB). View file
 
lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/wrap.cpython-312.pyc ADDED
Binary file (10.7 kB). View file
 
lib/python3.12/site-packages/torch/_higher_order_ops/associative_scan.py ADDED
@@ -0,0 +1,441 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ import functools
3
+ import itertools
4
+ from typing import Any, Callable, List
5
+
6
+ import torch
7
+ import torch._prims_common as utils
8
+ import torch._subclasses.functional_tensor
9
+ import torch.utils._pytree as pytree
10
+ from torch._C import DispatchKey
11
+ from torch._higher_order_ops.utils import (
12
+ _maybe_run_with_interpreter,
13
+ _set_compilation_env,
14
+ autograd_not_implemented,
15
+ first_slice_copy,
16
+ reenter_make_fx,
17
+ unique_graph_id,
18
+ )
19
+ from torch._inductor.utils import is_pointwise_use
20
+ from torch._ops import HigherOrderOperator
21
+ from torch._subclasses.fake_tensor import FakeTensorMode
22
+ from torch.fx.experimental.proxy_tensor import (
23
+ disable_proxy_modes_tracing,
24
+ ProxyTorchDispatchMode,
25
+ track_tensor_tree,
26
+ )
27
+
28
+
29
+ aten = torch._ops.ops.aten
30
+
31
+
32
+ def wrap_combine_fn_flat(*args, combine_fn, spec, num_leaves):
33
+ assert len(args) == 2 * num_leaves
34
+ lhs = pytree.tree_unflatten(args[:num_leaves], spec)
35
+ rhs = pytree.tree_unflatten(args[num_leaves:], spec)
36
+ combined = combine_fn(lhs, rhs)
37
+ combined_leaves = pytree.tree_leaves(combined)
38
+ assert num_leaves == len(combined_leaves)
39
+ return combined_leaves
40
+
41
+
42
+ def _interleave(a, b, dim):
43
+ # https://stackoverflow.com/questions/60869537/how-can-i-interleave-5-pytorch-tensors
44
+ if b_trunc := (a.shape[dim] == b.shape[dim] + 1):
45
+ pad = (
46
+ [0] * ((b.ndim - dim - 1) * 2 + 1)
47
+ + [1]
48
+ + [0] * (b.ndim * 2 - ((b.ndim - dim - 1) * 2 + 2))
49
+ )
50
+ b = torch.nn.functional.pad(b, pad)
51
+
52
+ stacked = torch.stack([a, b], dim=dim + 1)
53
+ interleaved = torch.flatten(stacked, start_dim=dim, end_dim=dim + 1)
54
+ if b_trunc:
55
+ # TODO: find torch alternative for slice_along dim for torch.jit.script to work
56
+ interleaved = aten.slice(interleaved, dim, 0, b.shape[dim] + a.shape[dim] - 1)
57
+ return interleaved
58
+
59
+
60
+ def safe_map(f, *args):
61
+ args = list(map(list, args))
62
+ n = len(args[0])
63
+ for arg in args[1:]:
64
+ if len(arg) != n:
65
+ raise ValueError("length mismatch: {list(map(len, args))}")
66
+
67
+ def nf(a):
68
+ return f(*a)
69
+
70
+ return list(map(nf, zip(*args)))
71
+
72
+
73
+ class AssociativeScanOp(HigherOrderOperator):
74
+ def __init__(self):
75
+ super().__init__("associative_scan")
76
+
77
+ def __call__(self, combine_fn, xs, dim):
78
+ return super().__call__(combine_fn, xs, dim)
79
+
80
+
81
+ associative_scan_op = AssociativeScanOp()
82
+
83
+
84
+ def associative_scan(
85
+ combine_fn: Callable[[pytree.PyTree, pytree.PyTree], pytree.PyTree],
86
+ xs: pytree.PyTree,
87
+ dim: int,
88
+ reverse: bool = False,
89
+ combine_mode: str = "pointwise",
90
+ ) -> torch.Tensor:
91
+ r"""
92
+ Performs an inclusive scan with an associative combine function.
93
+
94
+ .. warning::
95
+ `torch.associative_scan` is a prototype feature in PyTorch. It currently
96
+ does not support autograd and you may run into miscompiles.
97
+ Read more about feature classification at:
98
+ https://pytorch.org/blog/pytorch-feature-classification-changes/#prototype
99
+
100
+ This operator requires runtime code generation and so requires support for
101
+ ``torch.compile``. Further, only CUDA device codegen is supported at the moment.
102
+
103
+ Args:
104
+ combine_fn (Callable): A binary callable with type ``(Tensor, Tensor) -> Tensor``,
105
+ or if input is a pytree ``(pytree, pytree) -> pytree``.
106
+ This function must be pure, i.e., no lifted arguments are supported at the moment,
107
+ satisfy the associative property and have no side-effects.
108
+ xs (torch.Tensor): The input tensor, or nested pytree of tensors.
109
+ All inputs are expected to have the same shape.
110
+ dim (int): the dimension to scan over
111
+ reverse (bool): A boolean stating if the scan should be reversed with respect to ``dim``, default ``False``.
112
+ combine_mode (str): A string indicating whether the ``combine_fn`` is ``pointwise`` or ``generic``, default ``pointwise``.
113
+ If ``combine_mode=pointwise``, ``combine_fn`` must be pure, may only contain pointwise operations
114
+ and ``xs`` must be CUDA tensors.
115
+ In all other cases ``combine_mode=generic`` should be used.
116
+ Note: ``combine_mode=pointwise`` is more efficient than ``combine_mode=generic``.
117
+
118
+
119
+ Example::
120
+
121
+ def add(x: torch.Tensor, y: torch.Tensor):
122
+ return x + y
123
+
124
+ cumsum = associative_scan(add, x, dim)
125
+
126
+ """
127
+ if not callable(combine_fn):
128
+ raise ValueError("Combine_fn must be a callable, but got {combine_fn}")
129
+ if not isinstance(dim, int):
130
+ raise ValueError("Dim must be an int, but got " + str(type(dim)))
131
+ if combine_mode not in ["pointwise", "generic"]:
132
+ raise ValueError(
133
+ "Combine_mode must either 'pointwise' or 'generic', but got {combine_mode}"
134
+ )
135
+
136
+ if not torch._dynamo.is_compiling():
137
+ with _set_compilation_env(), torch._dynamo.utils.disable_cache_limit():
138
+ return torch.compile(associative_scan, fullgraph=True)(
139
+ combine_fn, xs, dim, reverse=reverse, combine_mode=combine_mode
140
+ )
141
+
142
+ leaves, spec = pytree.tree_flatten(xs)
143
+
144
+ if combine_mode == "pointwise" and not all(l.device.type == "cuda" for l in leaves):
145
+ raise ValueError(
146
+ "For combine_mode='pointwise', all input tensors need to be on CUDA"
147
+ )
148
+
149
+ if len(leaves) == 0:
150
+ raise ValueError("Expected at least 1 xs leaf")
151
+ if any(not isinstance(x, torch.Tensor) for x in leaves):
152
+ raise ValueError("xs leaves must be a Tensor")
153
+ if any(x.is_sparse for x in leaves):
154
+ raise ValueError("xs leaves must dense Tensors, consider using `to_dense()`")
155
+ if any(x.ndim <= dim for x in leaves):
156
+ raise ValueError(
157
+ "All xs leaves must at least have 'dim' number of dimensions and scan dimension > 0"
158
+ )
159
+ if any(x.shape[dim] == 0 for x in leaves):
160
+ raise ValueError(
161
+ "All xs leaves must at least have 'dim' number of dimensions and scan dimension > 0"
162
+ )
163
+
164
+ if reverse:
165
+ leaves = [torch.flip(elem, [dim]) for elem in leaves]
166
+
167
+ ndim = leaves[0].ndim
168
+ dim = utils.canonicalize_dim(ndim, dim)
169
+
170
+ # Call the combine_fn with only a slice along the scan dim
171
+ # and check whether the output leaves have the same slice dimensions
172
+ sliced_leaves = [first_slice_copy(leaf, dim) for leaf in leaves]
173
+
174
+ out = combine_fn(
175
+ pytree.tree_unflatten(sliced_leaves, spec),
176
+ pytree.tree_unflatten(sliced_leaves, spec),
177
+ )
178
+ out_leaves = pytree.tree_leaves(out)
179
+ if len(leaves) != len(out_leaves):
180
+ raise RuntimeError(
181
+ "The number of leaves of the pytree of the output of the operator needs to match the length of the pytree of the input"
182
+ )
183
+ if any(
184
+ x.shape != x_sliced.shape
185
+ or x.dtype != x_sliced.dtype
186
+ or x.device != x_sliced.device
187
+ or x.stride() != x_sliced.stride()
188
+ for x, x_sliced in zip(out_leaves, sliced_leaves)
189
+ ):
190
+ raise RuntimeError(
191
+ f"The metadata of the output of the operator needs to match the meta data of the xs pytree"
192
+ f"\n xs metadata : {[(x.shape, x.dtype, x.device, x.stride()) for x in sliced_leaves]}"
193
+ f"\n operator output metadata: {[(x.shape, x.dtype, x.device, x.stride()) for x in out_leaves]}"
194
+ )
195
+
196
+ if combine_mode == "generic":
197
+ # The generic_associative_scan implementation calls the combine_fn with a `batch` along the scan dimension
198
+ # For example, consider:
199
+ # def add(x: torch.Tensor, y: torch.Tensor):
200
+ # return x + y
201
+ # leaves = torch.tensor([[0.0, 1.0, 2.0, 3.0]
202
+ # [0.0, 1.0, 2.0, 3.0]])
203
+ # which has shape 2 x 4;
204
+ # dim = 1;
205
+ # In the first iteration of `_scan` the combine_fn gets invoked with
206
+ # combine_fn([torch.tensor([[0.0, 2.0],
207
+ # [0.0, 2.0]])],
208
+ # [torch.tensor([[1.0, 3.0],
209
+ # [1.0, 3.0]])])
210
+ # The arguments are of shape 2 x 2, but can be evaluated in parallel along the scan dimension.
211
+ # TODO: In case of the additional inputs, we the in_dims should be set to None
212
+ combine_fn = functools.partial(
213
+ wrap_combine_fn_flat,
214
+ combine_fn=torch.vmap(
215
+ combine_fn,
216
+ in_dims=(
217
+ pytree.tree_unflatten([dim] * len(leaves), spec),
218
+ pytree.tree_unflatten([dim] * len(leaves), spec),
219
+ ),
220
+ out_dims=dim,
221
+ ),
222
+ spec=spec,
223
+ num_leaves=len(leaves),
224
+ )
225
+ result_flat = generic_associative_scan(combine_fn, leaves, dim)
226
+ else:
227
+ combine_fn = functools.partial(
228
+ wrap_combine_fn_flat,
229
+ combine_fn=combine_fn,
230
+ spec=spec,
231
+ num_leaves=len(leaves),
232
+ )
233
+ result_flat = associative_scan_op(combine_fn, leaves, dim)
234
+
235
+ if reverse:
236
+ result_flat = [torch.flip(elem, [dim]) for elem in result_flat]
237
+
238
+ return pytree.tree_unflatten(result_flat, spec)
239
+
240
+
241
+ def generic_associative_scan(operator, leaves, dim=0):
242
+ r"""
243
+ This function performs the associative_scan operation.
244
+ The algorithm works by recursively collecting neighbours of ``leaves`` and subsequently
245
+ applying the ``operator`` on all pairs in parallel along ``dim``.
246
+ The results of the recursive calls are later combined.
247
+
248
+ Args:
249
+ operator (Callable): A binary callable with type ``(Tensor, Tensor) -> Tensor``,
250
+ or if input is a pytree ``(pytree, pytree) -> pytree``.
251
+ This function must be pure, pointwise, and satisfy the associative property.
252
+ leaves (torch.Tensor): A list of torch.Tensors converted from the pytree of
253
+ ``xs`` provided to ``associative_scan``.
254
+ All inputs are expected to have the same shape.
255
+ dim (int): the dimension to scan over
256
+
257
+
258
+ Example::
259
+
260
+ def add(x: torch.Tensor, y: torch.Tensor):
261
+ return x + y
262
+
263
+ leaves = torch.tensor([0.0, 1.0, 2.0, 3.0])
264
+
265
+ First iteration of _scan ->
266
+ # odd_elems -> apply operator on all neighbours
267
+ # odd_elems = operator([torch.tensor([0.0, 2.0])],
268
+ # [torch.tensor([1.0, 3.0])])
269
+ odd_elems = torch.tensor([1.0, 5.0])
270
+ Second iteration of _scan ->
271
+ # odd_elems = operator([torch.tensor([1.0])],
272
+ # [torch.tensor([5.0])])
273
+ odd_elems = torch.tensor([6.0])
274
+ # even_elems -> apply operator on all odd_elems and
275
+ # every second element of ``elems``, starting from the second element.
276
+ # even_elems is expanded with the first element of ``elems``
277
+ even_elems = [1.0]
278
+ # Merges odd_elems and even_elems
279
+ res = torch.tensor([1.0, 6.0])
280
+ # even_elems -> apply operator on all odd_elems and
281
+ # every second element of ``elems``, starting from the second element.
282
+ # even_elems is expanded with the first element of ``elems``
283
+ even_elems = [0.0, 3.0]
284
+ # Merges odd_elems and even_elems
285
+ res = torch.tensor([0.0, 1.0, 3.0, 6.0])
286
+
287
+ """
288
+
289
+ def _scan(elems):
290
+ """Perform the actual recursive scan on ``elems``."""
291
+ num_elems = elems[0].shape[dim]
292
+
293
+ if num_elems < 2:
294
+ return elems
295
+
296
+ reduced_elems = operator(
297
+ *[aten.slice(elem, dim, 0, -1, 2) for elem in elems],
298
+ *[aten.slice(elem, dim, 1, None, 2) for elem in elems],
299
+ )
300
+
301
+ # Recursively compute scan for partially reduced tensors.
302
+ odd_elems = _scan(reduced_elems)
303
+
304
+ if num_elems % 2 == 0:
305
+ even_elems = operator(
306
+ *[aten.slice(e, dim, 0, -1) for e in odd_elems],
307
+ *[aten.slice(e, dim, 2, None, 2) for e in elems],
308
+ )
309
+ else:
310
+ even_elems = operator(
311
+ *odd_elems,
312
+ *[aten.slice(e, dim, 2, None, 2) for e in elems],
313
+ )
314
+
315
+ # The first element of a scan is the same as the first element
316
+ # of the original `elems`.
317
+ even_elems = [
318
+ torch.cat([aten.slice(elem, dim, 0, 1), result], dim=dim)
319
+ if result.shape.numel() > 0 and elem.shape[dim] > 0
320
+ else result
321
+ if result.shape.numel() > 0
322
+ else aten.slice(
323
+ elem, dim, 0, 1
324
+ ) # Jax allows/ignores concat with 0-dim, Pytorch does not
325
+ for (elem, result) in zip(elems, even_elems)
326
+ ]
327
+
328
+ return list(
329
+ safe_map(functools.partial(_interleave, dim=dim), even_elems, odd_elems)
330
+ )
331
+
332
+ scans = _scan(leaves)
333
+
334
+ return scans
335
+
336
+
337
+ def trace_associative_scan(
338
+ proxy_mode, func_overload, combine_fn: Callable, xs: List[torch.Tensor], dim: int
339
+ ):
340
+ with disable_proxy_modes_tracing():
341
+ sample_xs = [first_slice_copy(x, dim) for x in itertools.chain(xs, xs)]
342
+ combine_graph = reenter_make_fx(combine_fn)(*sample_xs)
343
+
344
+ outputs = None
345
+ for node in combine_graph.graph.nodes:
346
+ if node.op == "output":
347
+ assert outputs is None
348
+ assert len(node.args) == 1
349
+ outputs = node.args[0]
350
+
351
+ if not all(is_pointwise_use(use) or use.op == "output" for use in node.users):
352
+ raise ValueError(
353
+ "For combine_mode='pointwise', the combine_fn needs to be pointwise"
354
+ )
355
+
356
+ assert outputs is not None
357
+ assert len(outputs) == len(
358
+ xs
359
+ ), f"expected combine_fn to return {len(xs)} results but got {len(outputs)}"
360
+
361
+ for i, o in zip(xs, outputs):
362
+ o_meta = o.meta["tensor_meta"]
363
+ assert o_meta.dtype == i.dtype, (
364
+ f"combine_fn output type mismatch, expected {i.dtype} "
365
+ + f"but got {o_meta.dtype}"
366
+ )
367
+
368
+ _, combine_graph_name = unique_graph_id(proxy_mode, prefix="scan_combine_graph")
369
+
370
+ proxy_mode.tracer.root.register_module(combine_graph_name, combine_graph)
371
+
372
+ args = (combine_graph, xs, dim)
373
+ proxy_args = pytree.tree_map(proxy_mode.tracer.unwrap_proxy, args)
374
+ out_proxy = proxy_mode.tracer.create_proxy(
375
+ "call_function", func_overload, proxy_args, {}, name="associative_scan"
376
+ )
377
+
378
+ with disable_proxy_modes_tracing():
379
+ out = [aten.clone(x) for x in xs]
380
+
381
+ return track_tensor_tree(out, out_proxy, constant=None, tracer=proxy_mode.tracer)
382
+
383
+
384
+ @associative_scan_op.py_impl(DispatchKey.CompositeExplicitAutograd)
385
+ def associative_scan_op_dense(combine_fn, xs, dim):
386
+ return generic_associative_scan(combine_fn, xs, dim)
387
+
388
+
389
+ associative_scan_op.py_impl(DispatchKey.Autograd)(
390
+ autograd_not_implemented(associative_scan_op, deferred_error=True)
391
+ )
392
+
393
+
394
+ @associative_scan_op.py_impl(ProxyTorchDispatchMode)
395
+ def associative_scan_proxy_mode(mode, combine_fn, xs, dim):
396
+ return trace_associative_scan(mode, associative_scan_op, combine_fn, xs, dim)
397
+
398
+
399
+ @associative_scan_op.py_impl(FakeTensorMode)
400
+ def assoiciative_scan_fake_tensor_mode(mode, combine_fn, xs, dim):
401
+ with mode:
402
+ return [x.clone() for x in xs]
403
+
404
+
405
+ @associative_scan_op.py_functionalize_impl
406
+ def associative_scan_functionalize(ctx, combine_fn, xs, dim):
407
+ unwrapped_xs = ctx.unwrap_tensors(xs)
408
+ with ctx.redispatch_to_next() as m:
409
+ functional_combine_fn = ctx.functionalize(
410
+ _maybe_run_with_interpreter(combine_fn)
411
+ )
412
+ ret = associative_scan_op(functional_combine_fn, unwrapped_xs, dim)
413
+ return ctx.wrap_tensors(ret)
414
+
415
+
416
+ def _fake_associative_scan(combine_fn, xs, dim, reverse=False): # noqa: F811
417
+ inp_leaves, spec = pytree.tree_flatten(xs)
418
+ result_flat: List[Any] = []
419
+ num_leaves = len(inp_leaves)
420
+ op = reversed if reverse else lambda x: x
421
+
422
+ for ind in op(range(inp_leaves[0].size(dim))):
423
+ r = [
424
+ inp_leaves[leave_ind][(slice(None),) * dim + (ind,)]
425
+ for leave_ind in range(num_leaves)
426
+ ]
427
+ if (ind > 0 and not reverse) or (
428
+ ind < (inp_leaves[0].size(dim) - 1) and reverse
429
+ ):
430
+ r = combine_fn(
431
+ pytree.tree_unflatten(result_flat[-1], spec),
432
+ pytree.tree_unflatten(r, spec),
433
+ )
434
+ r_flat, _ = pytree.tree_flatten(r)
435
+ result_flat.append(r_flat)
436
+
437
+ results = [
438
+ torch.stack([e[leave_ind] for e in op(result_flat)], dim)
439
+ for leave_ind in range(num_leaves)
440
+ ]
441
+ return pytree.tree_unflatten(results, spec)
lib/python3.12/site-packages/torch/_higher_order_ops/auto_functionalize.py ADDED
@@ -0,0 +1,832 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-decorators
2
+ # mypy: allow-untyped-defs
3
+ import warnings
4
+ from abc import ABC, abstractmethod
5
+ from dataclasses import dataclass
6
+ from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
7
+
8
+ import torch
9
+ import torch.utils._pytree as pytree
10
+ from torch import Tensor
11
+ from torch._C import DispatchKey
12
+ from torch._ops import HigherOrderOperator, OperatorBase, OpOverload
13
+ from torch._prims_common import clone_preserve_strides
14
+ from torch._subclasses.fake_tensor import FakeTensorMode
15
+ from torch.fx.experimental.proxy_tensor import (
16
+ disable_proxy_modes_tracing,
17
+ ProxyTorchDispatchMode,
18
+ track_tensor_tree,
19
+ )
20
+
21
+
22
+ def get_base(tensor):
23
+ if torch.is_inference_mode_enabled():
24
+ return tensor._inference_mode_base
25
+ else:
26
+ return tensor._base
27
+
28
+
29
+ class ViewInfo(ABC):
30
+ base_index: int
31
+
32
+ def __init__(self, base_index):
33
+ self.base_index = base_index
34
+
35
+ @abstractmethod
36
+ def regenerate_view(self, bases_list: List[Tensor]):
37
+ pass
38
+
39
+
40
+ @dataclass
41
+ class AsStridedViewInfo(ViewInfo):
42
+ size: Sequence[Union[int, torch.SymInt]]
43
+ stride: Sequence[Union[int, torch.SymInt]]
44
+ storage_offset: int
45
+
46
+ def __init__(self, base_index, size, stride, storage_offset):
47
+ super().__init__(base_index)
48
+ self.size = size
49
+ self.stride = stride
50
+ self.storage_offset = storage_offset
51
+
52
+ def regenerate_view(self, bases_list: List[Tensor]):
53
+ return torch.as_strided(
54
+ bases_list[self.base_index],
55
+ self.size,
56
+ self.stride,
57
+ self.storage_offset,
58
+ )
59
+
60
+
61
+ @dataclass
62
+ class SliceViewInfo(ViewInfo):
63
+ dim: Union[int, torch.SymInt]
64
+ start: Union[int, torch.SymInt]
65
+ end: Union[int, torch.SymInt]
66
+
67
+ def __init__(self, base_index, dim, start, end):
68
+ super().__init__(base_index)
69
+ self.dim = dim
70
+ self.start = start
71
+ self.end = end
72
+
73
+ def regenerate_view(self, bases_list: List[Tensor]):
74
+ return torch.ops.aten.slice.Tensor(
75
+ bases_list[self.base_index], self.dim, self.start, self.end
76
+ )
77
+
78
+
79
+ @dataclass
80
+ class AliasViewInfo(ViewInfo):
81
+ def __init__(self, base_index):
82
+ super().__init__(base_index)
83
+
84
+ def regenerate_view(self, bases_list: List[Tensor]):
85
+ return torch.ops.aten.alias.default(bases_list[self.base_index])
86
+
87
+
88
+ @dataclass
89
+ class NotView(ViewInfo):
90
+ def __init__(self, base_index):
91
+ super().__init__(base_index)
92
+
93
+ def regenerate_view(self, bases_list: List[Tensor]):
94
+ return bases_list[self.base_index]
95
+
96
+
97
+ def is_alias(base, tensor):
98
+ from torch.fx.experimental.symbolic_shapes import statically_known_true, sym_eq
99
+
100
+ return all(
101
+ statically_known_true(a)
102
+ for a in [
103
+ sym_eq(base.storage_offset(), tensor.storage_offset()),
104
+ sym_eq(base.stride(), tensor.stride()),
105
+ sym_eq(base.size(), tensor.size()),
106
+ ]
107
+ )
108
+
109
+
110
+ # return None or (dim, start, end)
111
+ def try_use_slice(base, tensor):
112
+ from torch.fx.experimental.symbolic_shapes import statically_known_true, sym_eq
113
+
114
+ # This condition should never be triggered.
115
+ if is_alias(base, tensor):
116
+ return (0, 0, base.size()[0])
117
+
118
+ # TODO is there cases can we use slice even if stride or len(sizes) are not equal?
119
+ if not statically_known_true(sym_eq(tensor.stride(), base.stride())):
120
+ return None
121
+ if not statically_known_true(sym_eq(len(tensor.size()), len(base.size()))):
122
+ return None
123
+
124
+ dim = None
125
+ count = 0
126
+ for i in range(len(tensor.size())):
127
+ if base.size()[i] != tensor.size()[i]:
128
+ dim = i
129
+ count = count + 1
130
+ if count != 1:
131
+ return None
132
+
133
+ if tensor.storage_offset() % tensor.stride()[dim] != 0:
134
+ return None
135
+ start = tensor.storage_offset() // tensor.stride()[dim]
136
+ end = start + tensor.size()[dim]
137
+ return (dim, start, end)
138
+
139
+
140
+ def write_view_information_to_args(
141
+ mutable_arg_names: List[str],
142
+ mutable_arg_types: List[torch.Type],
143
+ kwargs: Dict[str, Any],
144
+ arg_to_base_index: Dict[str, Any],
145
+ ):
146
+ """
147
+ This function writes the view information into kwargs. It reads mutable_args from kwargs.
148
+ and uses arg_to_base_index and tensor information to write ViewInfo into kwargs.
149
+ mutable_arg_names: mutable custom operator arg names.
150
+ mutable_arg_types: mutable custom operator arg types.
151
+ kwargs: the original custom operator args.
152
+ arg_to_base_index: maps mutable_arg_name to int | [int] that refers to the base tensor that
153
+ corresponds to the input tensor
154
+ """
155
+
156
+ def write_single_view(prefix: str, tensor: Tensor, base_index: int):
157
+ assert f"{prefix}_base_index" not in kwargs
158
+ assert f"{prefix}_size" not in kwargs
159
+ assert f"{prefix}_stride" not in kwargs
160
+ assert f"{prefix}_storage_offset" not in kwargs
161
+
162
+ assert f"{prefix}_slice_dim" not in kwargs
163
+ assert f"{prefix}_slice_start" not in kwargs
164
+ assert f"{prefix}_slice_end" not in kwargs
165
+
166
+ def use_as_strided(tensor):
167
+ kwargs[f"{prefix}_size"] = tensor.size()
168
+ kwargs[f"{prefix}_stride"] = tensor.stride()
169
+ kwargs[f"{prefix}_storage_offset"] = tensor.storage_offset()
170
+
171
+ def use_slice(dim, start, end):
172
+ kwargs[f"{prefix}_slice_dim"] = dim
173
+ kwargs[f"{prefix}_slice_start"] = start
174
+ kwargs[f"{prefix}_slice_end"] = end
175
+
176
+ def use_alias():
177
+ kwargs[f"{prefix}_alias"] = True
178
+
179
+ # The start if the function
180
+ if tensor is None:
181
+ kwargs[f"{prefix}_base_index"] = None
182
+ else:
183
+ base = get_base(tensor)
184
+ kwargs[f"{prefix}_base_index"] = base_index
185
+ if base is None:
186
+ # no need to add anything else other than _base_index
187
+ return
188
+ elif is_alias(base, tensor):
189
+ use_alias()
190
+ elif (slice_info := try_use_slice(base, tensor)) is not None:
191
+ use_slice(*slice_info)
192
+ else:
193
+ use_as_strided(tensor)
194
+
195
+ for arg_name, arg_type in zip(mutable_arg_names, mutable_arg_types):
196
+ arg = kwargs[arg_name]
197
+ if isinstance(arg_type, torch.ListType):
198
+ if arg is None:
199
+ kwargs[f"_{arg_name}_length"] = None
200
+
201
+ kwargs[f"_{arg_name}_length"] = len(arg)
202
+ for i, elem in enumerate(arg):
203
+ write_single_view(
204
+ f"_{arg_name}_{i}", elem, arg_to_base_index[arg_name][i]
205
+ )
206
+
207
+ elif isinstance(arg_type, (torch.TensorType, torch.OptionalType)):
208
+ write_single_view(
209
+ f"_{arg_name}",
210
+ kwargs[arg_name],
211
+ arg_to_base_index.get(arg_name, None),
212
+ )
213
+ else:
214
+ raise RuntimeError(f"Unsupported type {arg_type}")
215
+
216
+
217
+ # Returns a dict of arg_name -> ViewInfo | [ViewInfo]
218
+ def read_view_information_from_args(
219
+ mutable_arg_names: List[str],
220
+ mutable_arg_types: List[torch.Type],
221
+ kwargs: Dict[str, Any],
222
+ all_bases: List[Tensor],
223
+ ):
224
+ """
225
+ This reads the view information added by `write_view_information_to_args` from kwargs, pop them,
226
+ and returns a dict arg_name -> ViewInfo | [ViewInfo](if the input is list). that maps each mutable arg
227
+ to its view information.
228
+ mutable_arg_names: mutable custom operator arg names.
229
+ mutable_arg_types: mutable custom operator arg types.
230
+ kwargs : args of auto_functionalize(custom_op, kwargs)
231
+ """
232
+
233
+ def get_arg(name):
234
+ return kwargs.pop(name)
235
+
236
+ def read_single_view(prefix):
237
+ base_index = get_arg(f"{prefix}_base_index")
238
+ if base_index is None:
239
+ return None
240
+ elif f"{prefix}_alias" in kwargs:
241
+ get_arg(f"{prefix}_alias")
242
+ return AliasViewInfo(base_index)
243
+ elif f"{prefix}_storage_offset" in kwargs:
244
+ # The view is regenerated using as_strided.
245
+ size = get_arg(f"{prefix}_size")
246
+ stride = get_arg(f"{prefix}_stride")
247
+ storage_offset = get_arg(f"{prefix}_storage_offset")
248
+ return AsStridedViewInfo(base_index, size, stride, storage_offset)
249
+ elif f"{prefix}_slice_dim" in kwargs:
250
+ dim = get_arg(f"{prefix}_slice_dim")
251
+ start = get_arg(f"{prefix}_slice_start")
252
+ end = get_arg(f"{prefix}_slice_end")
253
+ return SliceViewInfo(base_index, dim, start, end)
254
+ else:
255
+ # This means that the argument is the base tensor
256
+ return NotView(base_index)
257
+
258
+ args_view_info: Dict[str, Any] = {}
259
+ for arg_name, arg_type in zip(mutable_arg_names, mutable_arg_types):
260
+ if isinstance(arg_type, torch.ListType):
261
+ length = get_arg(f"_{arg_name}_length")
262
+ if length is None:
263
+ # The whole list is None.
264
+ args_view_info[arg_name] = None
265
+ else:
266
+ args_view_info[arg_name] = [
267
+ read_single_view(f"_{arg_name}_{i}") for i in range(length)
268
+ ]
269
+
270
+ elif isinstance(arg_type, (torch.TensorType, torch.OptionalType)):
271
+ args_view_info[arg_name] = read_single_view(f"_{arg_name}")
272
+ else:
273
+ raise RuntimeError(f"Unsupported type {arg_type}")
274
+ return args_view_info
275
+
276
+
277
+ # NOTE: [auto-functionalizing custom ops]
278
+ # Users may wish to torch.compile custom ops that mutate their inputs.
279
+ # torch.compile will automatically support this op without anyone needing
280
+ # to provide a functionalization kernel for it. Here's how.
281
+ #
282
+ # Let's say we have a hypothetical mylib::sin_(Tensor(a!) x) -> ()
283
+ # op. First, when FakeTensor sees this op:
284
+ # - If the schema says it returns nothing, we can generate a trivial
285
+ # FakeTensor rule for it (that returns nothing).
286
+ # - Otherwise, the user needs to provide a FakeTensor impl (fake impl)
287
+ #
288
+ # Next, when Python FunctionalTensor sees the op, it will functionalize
289
+ # it by emitting a call to an auto_functionalize(op, ["x"], {"x": ...})
290
+ # HOP and replacing the mutated inputs with corresponding outputs of this HOP.
291
+ # This HOP effectively runs the functional version of the op when
292
+ # called: it clones inputs that will be mutated, runs the op, and
293
+ # then returns (output, Tensors with the new values)
294
+ #
295
+ # auto_functionalize_v2 is an improved version of auto_functionalize that better handle
296
+ # re-inplacing views.
297
+
298
+
299
+ class AutoFunctionalized(HigherOrderOperator):
300
+ """auto_functionalized(_mutable_op, **kwargs)
301
+
302
+ This HOP runs a "functional" version of _mutable_op.
303
+
304
+ Concretely, it looks at all the arguments that are mutable through
305
+ _mutable_op's operator schema, clones those kwargs, runs
306
+ `out = _mutable_op(**kwargs)` with the cloned values, and then returns the
307
+ operator output concatenated with the cloned values that were mutated.
308
+
309
+ We have some restrictions on `_mutable_op`.
310
+ See `can_auto_functionalize` for the restrictions. We can likely lift
311
+ many of these if users request it.
312
+
313
+ The reason why _mutable_op is prefixed with an
314
+ underscore is to prevent collisions with kwarg names in **kwargs.
315
+ """
316
+
317
+ def __init__(self) -> None:
318
+ super().__init__("auto_functionalized")
319
+
320
+ def __call__(
321
+ self,
322
+ /,
323
+ _mutable_op: OpOverload,
324
+ **kwargs: Any,
325
+ ) -> Tuple[Any, Tuple[Tensor, ...]]:
326
+ assert can_auto_functionalize(_mutable_op)
327
+ assert isinstance(kwargs, dict)
328
+ return super().__call__(_mutable_op, **kwargs)
329
+
330
+
331
+ auto_functionalized = AutoFunctionalized()
332
+ auto_functionalized.__module__ = "torch.ops.higher_order"
333
+
334
+ auto_functionalized.fallthrough(DispatchKey.AutogradCPU)
335
+ auto_functionalized.fallthrough(DispatchKey.AutogradCUDA)
336
+
337
+
338
+ class AutoFunctionalizedV2(HigherOrderOperator):
339
+ """auto_functionalized_v2(_mutable_op, **kwargs)
340
+
341
+ This HOP runs a "functional" version of _mutable_op.
342
+ Unlike AutoFunctionalized, this version is improved to better handle
343
+ view tensors. This version is only used in non export mode.
344
+ """
345
+
346
+ def __init__(self) -> None:
347
+ super().__init__("auto_functionalized_v2")
348
+
349
+ def __call__(
350
+ self,
351
+ /,
352
+ _mutable_op: OpOverload,
353
+ **kwargs: Any,
354
+ ) -> Tuple[Any, Tuple[Tensor, ...]]:
355
+ assert can_auto_functionalize(_mutable_op)
356
+ assert isinstance(kwargs, dict)
357
+ return super().__call__(_mutable_op, **kwargs)
358
+
359
+
360
+ auto_functionalized_v2 = AutoFunctionalizedV2()
361
+ auto_functionalized_v2.__module__ = "torch.ops.higher_order"
362
+
363
+ auto_functionalized_v2.fallthrough(DispatchKey.AutogradCPU)
364
+ auto_functionalized_v2.fallthrough(DispatchKey.AutogradCUDA)
365
+
366
+
367
+ def can_auto_functionalize(op: OperatorBase) -> bool:
368
+ if not isinstance(op, OpOverload):
369
+ return False
370
+
371
+ if torch._library.utils.is_builtin(op):
372
+ # We control the built-ins. These may (in rare cases)
373
+ # do input metadata mutation (which we have banned on custom ops)
374
+ return False
375
+ schema = op._schema
376
+ if not schema.is_mutable:
377
+ return False
378
+ schema = op._schema
379
+
380
+ for arg in schema.arguments:
381
+ if arg.alias_info is None:
382
+ continue
383
+ if not arg.alias_info.is_write:
384
+ continue
385
+ if type(arg.type) is torch.TensorType:
386
+ continue
387
+ if (
388
+ type(arg.type) is torch.OptionalType
389
+ and type(arg.type.getElementType()) is torch.TensorType
390
+ ):
391
+ continue
392
+ if (
393
+ type(arg.type) is torch.ListType
394
+ and type(arg.type.getElementType()) is torch.TensorType
395
+ ):
396
+ continue
397
+ # Not yet supported: other Tensor types. This includes things like
398
+ # Tensor?[], Tensor[]?.
399
+ return False
400
+
401
+ if len(schema.returns) == 1 and isinstance(schema.returns[0].type, torch.NoneType):
402
+ # Skip schema returns -> None
403
+ return True
404
+ # The returns must not alias anything
405
+ for ret in schema.returns:
406
+ if ret.alias_info is None and type(ret.type) is torch.TensorType:
407
+ continue
408
+ # Not yet supported: List[Tensor] return.
409
+ return False
410
+ if torch._C._dispatch_has_kernel_for_dispatch_key(op.name(), "Functionalize"):
411
+ return False
412
+ return True
413
+
414
+
415
+ def get_mutable_args(op: OpOverload) -> Tuple[List[str], List[torch.Type]]:
416
+ """
417
+ Returns the list of argument names that get mutated according to the
418
+ schema and their types.
419
+ """
420
+ mutable_args_names = [
421
+ arg.name
422
+ for arg in op._schema.arguments
423
+ if arg.alias_info is not None and arg.alias_info.is_write
424
+ ]
425
+
426
+ mutable_args_types = [
427
+ arg.type
428
+ for arg in op._schema.arguments
429
+ if arg.alias_info is not None and arg.alias_info.is_write
430
+ ]
431
+ return mutable_args_names, mutable_args_types
432
+
433
+
434
+ def do_auto_functionalize(
435
+ op: OpOverload,
436
+ args: Tuple[Any, ...],
437
+ kwargs: Dict[str, Any],
438
+ ) -> Any:
439
+ """Functionalizes a call to op(*args, **kwargs) by emitting a call to
440
+ `outs = auto_functionalized(op, normalized_kwargs)`
441
+ and replacing the mutated (args, kwargs) with the corresponding outputs.
442
+
443
+ The normalized_kwargs are just the (args, kwargs), but all in kwarg form.
444
+ This makes handling easier for the auto_functionalized HOP.
445
+ """
446
+ from torch._subclasses.functional_tensor import PythonFunctionalizeAPI
447
+
448
+ ctx = PythonFunctionalizeAPI()
449
+
450
+ # All of the (args, kwargs), but all as kwargs. The names for the
451
+ # args come from the schema. This makes it easier for us to work with them.
452
+ normalized_kwargs = {}
453
+ schema = op._schema
454
+ for idx, arg in enumerate(schema.arguments):
455
+ # NB: torch_dispatch kwargs are the args defined as kwarg-only in the schema
456
+ if arg.name in kwargs:
457
+ normalized_kwargs[arg.name] = kwargs[arg.name]
458
+ elif idx < len(args):
459
+ # if its out of bounds we don't need to do anything
460
+ # as it means the the optional arg was passed with its default
461
+ # value
462
+ normalized_kwargs[arg.name] = args[idx]
463
+ else:
464
+ normalized_kwargs[arg.name] = arg.default_value
465
+
466
+ unwrapped_kwargs = ctx.unwrap_tensors(normalized_kwargs) # type: ignore[arg-type]
467
+ if "self" in unwrapped_kwargs or "self_" in unwrapped_kwargs:
468
+ warnings.warn(
469
+ "Using `self` or `self_` as an argument in the definition of custom ops may lead to ambiguous parsing. "
470
+ "Please consider using a different name for this argument to avoid potential issues."
471
+ )
472
+ with ctx.redispatch_to_next():
473
+ unwrapped_outs = auto_functionalized(
474
+ op, **unwrapped_kwargs # type: ignore[arg-type]
475
+ )
476
+
477
+ # List of the name of args that get mutated (according to the schema)
478
+ mutable_args_names, _ = get_mutable_args(op)
479
+
480
+ unwrapped_actual_out: Union[Any, Tuple[Any]] = unwrapped_outs[
481
+ : -len(mutable_args_names)
482
+ ]
483
+ unwrapped_mutable_out = unwrapped_outs[-len(mutable_args_names) :]
484
+
485
+ if len(op._schema.returns) == 0:
486
+ assert unwrapped_actual_out[0] is None
487
+ unwrapped_actual_out = None
488
+ elif len(op._schema.returns) == 1:
489
+ assert len(unwrapped_actual_out) == 1
490
+ unwrapped_actual_out = unwrapped_actual_out[0]
491
+ else:
492
+ assert len(unwrapped_actual_out) == len(op._schema.returns)
493
+
494
+ for name, unwrapped_out in zip(mutable_args_names, unwrapped_mutable_out):
495
+ # Can be None if input was `Tensor(a!)?`
496
+ if unwrapped_out is None:
497
+ continue
498
+
499
+ # We only handle Tensor or List[Tensor] here for now.
500
+ def sync_update(o, orig_arg):
501
+ ctx.replace(orig_arg, o)
502
+ ctx.commit_update(orig_arg)
503
+ ctx.sync(orig_arg)
504
+
505
+ orig_arg = normalized_kwargs[name]
506
+
507
+ if isinstance(unwrapped_out, torch.Tensor):
508
+ sync_update(unwrapped_out, orig_arg)
509
+ elif isinstance(unwrapped_out, list) and all(
510
+ isinstance(o, torch.Tensor) for o in unwrapped_out
511
+ ):
512
+ assert len(orig_arg) == len(unwrapped_out)
513
+ for orig_a, o in zip(orig_arg, unwrapped_out):
514
+ sync_update(o, orig_a)
515
+ else:
516
+ raise RuntimeError(
517
+ f"unsupported type for auto-functionalization: {unwrapped_out}"
518
+ )
519
+
520
+ return ctx.wrap_tensors(unwrapped_actual_out) # type: ignore[arg-type]
521
+
522
+
523
+ def do_auto_functionalize_v2(
524
+ op: OpOverload,
525
+ args: Tuple[Any, ...],
526
+ kwargs: Dict[str, Any],
527
+ ) -> Any:
528
+ from torch._subclasses.functional_tensor import PythonFunctionalizeAPI
529
+
530
+ ctx = PythonFunctionalizeAPI()
531
+
532
+ # All of the (args, kwargs), but all as kwargs. The names for the
533
+ # args come from the schema. This makes it easier for us to work with them.
534
+ normalized_kwargs = {}
535
+
536
+ schema = op._schema
537
+ for idx, arg in enumerate(schema.arguments):
538
+ # NB: torch_dispatch kwargs are the args defined as kwarg-only in the schema
539
+ if arg.name in kwargs:
540
+ normalized_kwargs[arg.name] = kwargs[arg.name]
541
+ elif idx < len(args):
542
+ # if its out of bounds we don't need to do anything
543
+ # as it means the the optional arg was passed with its default
544
+ # value
545
+ normalized_kwargs[arg.name] = args[idx]
546
+ else:
547
+ normalized_kwargs[arg.name] = arg.default_value
548
+
549
+ # List of the name of args that get mutated (according to the schema)
550
+ mutable_args_names, mutable_args_types = get_mutable_args(op)
551
+
552
+ # A list of all bases of mutable args without duplication
553
+ all_bases = []
554
+ all_bases_addresses: list[int] = []
555
+
556
+ # Map arg_name to the index of its base in all_bases.
557
+ arg_to_base_index: Dict[str, Any] = {}
558
+
559
+ def update_dict(tensor, arg_name, index=None):
560
+ base = tensor if get_base(tensor) is None else get_base(tensor)
561
+
562
+ def set_result(base_index):
563
+ if index is None:
564
+ arg_to_base_index[arg_name] = base_index
565
+ else:
566
+ arg_to_base_index[arg_name][index] = base_index
567
+
568
+ if not all_bases_addresses.__contains__(base._cdata):
569
+ all_bases_addresses.append(base._cdata)
570
+ all_bases.append(base)
571
+ set_result(len(all_bases) - 1)
572
+ else:
573
+ set_result(all_bases_addresses.index(base._cdata))
574
+
575
+ for arg_name in mutable_args_names:
576
+ arg = normalized_kwargs[arg_name]
577
+ if arg is None:
578
+ continue
579
+
580
+ if isinstance(arg, list):
581
+ arg_to_base_index[arg_name] = {}
582
+ for i, tensor in enumerate(arg):
583
+ if tensor is None:
584
+ arg_to_base_index[arg_name].append(None)
585
+ continue
586
+
587
+ update_dict(tensor, arg_name, i)
588
+
589
+ else:
590
+ update_dict(arg, arg_name)
591
+
592
+ # add view_meta for each args into unwrapped_kwargs.
593
+ write_view_information_to_args(
594
+ mutable_args_names,
595
+ mutable_args_types,
596
+ normalized_kwargs,
597
+ arg_to_base_index,
598
+ )
599
+
600
+ # remove mutated args from the kwargs (its a function of _all_bases now)
601
+ for arg_name in mutable_args_names:
602
+ del normalized_kwargs[arg_name] # type: ignore[arg-type]
603
+
604
+ unwrapped_kwargs = ctx.unwrap_tensors(normalized_kwargs) # type: ignore[arg-type]
605
+ if "self" in unwrapped_kwargs or "self_" in unwrapped_kwargs:
606
+ warnings.warn(
607
+ "Using `self` or `self_` as an argument in the definition of custom ops may lead to ambiguous parsing. "
608
+ "Please consider using a different name for this argument to avoid potential issues."
609
+ )
610
+ all_basis_unwrapped = ctx.unwrap_tensors(all_bases)
611
+
612
+ with ctx.redispatch_to_next():
613
+ unwrapped_outs = auto_functionalized_v2(
614
+ op, **dict(unwrapped_kwargs, _all_bases=all_basis_unwrapped) # type: ignore[arg-type]
615
+ )
616
+
617
+ unwrapped_actual_out: Union[Any, Tuple[Any]] = (
618
+ unwrapped_outs if len(all_bases) == 0 else unwrapped_outs[: -len(all_bases)]
619
+ )
620
+
621
+ unwrapped_mutable_out = (
622
+ [] if len(all_bases) == 0 else unwrapped_outs[-len(all_bases) :]
623
+ )
624
+
625
+ if len(op._schema.returns) == 0:
626
+ assert unwrapped_actual_out[0] is None
627
+ unwrapped_actual_out = None
628
+ elif len(op._schema.returns) == 1:
629
+ assert len(unwrapped_actual_out) == 1
630
+ unwrapped_actual_out = unwrapped_actual_out[0]
631
+ else:
632
+ assert len(unwrapped_actual_out) == len(op._schema.returns)
633
+
634
+ for orig_arg, unwrapped_out in zip(all_bases, unwrapped_mutable_out):
635
+ # Can be None if input was `Tensor(a!)?`
636
+ if unwrapped_out is None:
637
+ continue
638
+
639
+ # We only handle Tensor or List[Tensor] here for now.
640
+ def sync_update(o, orig_arg):
641
+ ctx.replace(orig_arg, o)
642
+ ctx.commit_update(orig_arg)
643
+ ctx.sync(orig_arg)
644
+
645
+ if isinstance(unwrapped_out, torch.Tensor):
646
+ sync_update(unwrapped_out, orig_arg)
647
+ elif isinstance(unwrapped_out, list) and all(
648
+ isinstance(o, torch.Tensor) for o in unwrapped_out
649
+ ):
650
+ assert len(orig_arg) == len(unwrapped_out)
651
+ for orig_a, o in zip(orig_arg, unwrapped_out):
652
+ sync_update(o, orig_a)
653
+ else:
654
+ raise RuntimeError(
655
+ f"unsupported type for auto-functionalization: {unwrapped_out}"
656
+ )
657
+
658
+ return ctx.wrap_tensors(unwrapped_actual_out) # type: ignore[arg-type]
659
+
660
+
661
+ # auto_functionalize functions
662
+ @auto_functionalized.py_impl(DispatchKey.CompositeExplicitAutograd)
663
+ def auto_functionalized_dense(
664
+ _mutable_op: OpOverload,
665
+ _only_clone_these_tensors: Optional[Tuple[str, ...]] = None,
666
+ **kwargs: Any,
667
+ ) -> Tuple[Any, Tuple[Tensor, ...]]:
668
+ new_kwargs = dict(**kwargs)
669
+ result = []
670
+
671
+ _mutable_args_names, _ = get_mutable_args(_mutable_op)
672
+ for name in _mutable_args_names:
673
+ if (
674
+ _only_clone_these_tensors is not None
675
+ and name not in _only_clone_these_tensors
676
+ ):
677
+ new_kwargs[name] = kwargs[name]
678
+ else:
679
+ new_kwargs[name] = (
680
+ [clone_preserve_strides(x) for x in kwargs[name]]
681
+ if kwargs[name] is not None and isinstance(kwargs[name], list)
682
+ else (
683
+ clone_preserve_strides(kwargs[name])
684
+ if kwargs[name] is not None
685
+ else None
686
+ )
687
+ )
688
+ result.append(new_kwargs[name])
689
+ out = _mutable_op(**new_kwargs)
690
+
691
+ if isinstance(out, tuple):
692
+ return (*out, *result) # type: ignore[return-value]
693
+ else:
694
+ return (out, *result) # type: ignore[return-value]
695
+
696
+
697
+ @auto_functionalized.py_impl(FakeTensorMode)
698
+ def auto_functionalized_fake(
699
+ mode,
700
+ _mutable_op: OpOverload,
701
+ **kwargs: Any,
702
+ ) -> Tuple[Any, Tuple[Tensor, ...]]:
703
+ with mode:
704
+ result = auto_functionalized_dense(
705
+ _mutable_op, _only_clone_these_tensors=None, **kwargs
706
+ )
707
+ return result
708
+
709
+
710
+ @auto_functionalized.py_impl(ProxyTorchDispatchMode)
711
+ def auto_functionalized_proxy(
712
+ mode,
713
+ _mutable_op: OpOverload,
714
+ **kwargs: Any,
715
+ ) -> Tuple[Any, Tuple[Tensor, ...]]:
716
+ with disable_proxy_modes_tracing():
717
+ out = auto_functionalized(_mutable_op, **kwargs)
718
+
719
+ proxy_kwargs = pytree.tree_map(mode.tracer.unwrap_proxy, kwargs)
720
+ out_proxy = mode.tracer.create_proxy(
721
+ "call_function",
722
+ auto_functionalized,
723
+ (_mutable_op,),
724
+ proxy_kwargs,
725
+ )
726
+ result = track_tensor_tree(out, out_proxy, constant=None, tracer=mode.tracer)
727
+ return result
728
+
729
+
730
+ @auto_functionalized.py_functionalize_impl
731
+ def auto_functionalized_func(ctx, _mutable_op, **kwargs):
732
+ unwrapped_kwargs = ctx.unwrap_tensors(kwargs)
733
+ with ctx.redispatch_to_next():
734
+ result = auto_functionalized(_mutable_op, **unwrapped_kwargs)
735
+ return ctx.wrap_tensors(result)
736
+
737
+
738
+ # auto_functionalized_v2 functions
739
+ @auto_functionalized_v2.py_impl(DispatchKey.CompositeExplicitAutograd)
740
+ def auto_functionalized_v2_dense(
741
+ _mutable_op: OpOverload,
742
+ _only_clone_these_bases: Optional[Tuple[int, ...]] = None,
743
+ **kwargs: Any,
744
+ ) -> Tuple[Any, Tuple[Tensor, ...]]:
745
+ all_bases: List[Tensor] = kwargs.pop("_all_bases", [])
746
+ mutable_args_names, mutable_args_types = get_mutable_args(_mutable_op)
747
+ args_view_info = read_view_information_from_args(
748
+ mutable_args_names, mutable_args_types, kwargs, all_bases
749
+ )
750
+
751
+ if _only_clone_these_bases is None:
752
+ _only_clone_these_bases = tuple(range(len(all_bases)))
753
+
754
+ def maybe_copy(i, t):
755
+ if t is None:
756
+ return None
757
+ if i in _only_clone_these_bases:
758
+ return clone_preserve_strides(t)
759
+ else:
760
+ return t
761
+
762
+ all_bases_new = [maybe_copy(i, t) for i, t in enumerate(all_bases)]
763
+
764
+ # create new args
765
+ new_kwargs = dict(**kwargs)
766
+
767
+ # re-generate all inputs from all_bases_new using args_view_info and add them to new_kwargs.
768
+ for arg_name in mutable_args_names:
769
+ if args_view_info[arg_name] is None:
770
+ new_kwargs[arg_name] = None
771
+ elif isinstance(args_view_info[arg_name], list):
772
+ new_kwargs[arg_name] = []
773
+ for i, elem in enumerate(args_view_info[arg_name]):
774
+ if elem is None:
775
+ new_kwargs[arg_name].append(None)
776
+ else:
777
+ view_info = args_view_info[arg_name][i]
778
+ new_kwargs[arg_name].append(
779
+ view_info.regenerate_view(all_bases_new)
780
+ )
781
+ else:
782
+ new_kwargs[arg_name] = args_view_info[arg_name].regenerate_view(
783
+ all_bases_new
784
+ )
785
+
786
+ out = _mutable_op(**new_kwargs)
787
+
788
+ if isinstance(out, tuple):
789
+ return (*out, *all_bases_new) # type: ignore[return-value]
790
+ else:
791
+ return (out, *all_bases_new) # type: ignore[return-value]
792
+
793
+
794
+ @auto_functionalized_v2.py_impl(FakeTensorMode)
795
+ def auto_functionalized_v2_fake(
796
+ mode,
797
+ _mutable_op: OpOverload,
798
+ **kwargs: Dict[str, Any],
799
+ ) -> Tuple[Any, Tuple[Tensor, ...]]:
800
+ with mode:
801
+ result = auto_functionalized_v2_dense(
802
+ _mutable_op, _only_clone_these_bases=None, **kwargs
803
+ )
804
+ return result
805
+
806
+
807
+ @auto_functionalized_v2.py_impl(ProxyTorchDispatchMode)
808
+ def auto_functionalized_v2_proxy(
809
+ mode,
810
+ _mutable_op: OpOverload,
811
+ **kwargs: Dict[str, Any],
812
+ ) -> Tuple[Any, Tuple[Tensor, ...]]:
813
+ with disable_proxy_modes_tracing():
814
+ out = auto_functionalized_v2(_mutable_op, **kwargs)
815
+
816
+ proxy_kwargs = pytree.tree_map(mode.tracer.unwrap_proxy, kwargs)
817
+ out_proxy = mode.tracer.create_proxy(
818
+ "call_function",
819
+ auto_functionalized_v2,
820
+ (_mutable_op,),
821
+ proxy_kwargs,
822
+ )
823
+ result = track_tensor_tree(out, out_proxy, constant=None, tracer=mode.tracer)
824
+ return result
825
+
826
+
827
+ @auto_functionalized_v2.py_functionalize_impl
828
+ def auto_functionalized_v2_func(ctx, _mutable_op, **kwargs):
829
+ unwrapped_kwargs = ctx.unwrap_tensors(kwargs)
830
+ with ctx.redispatch_to_next():
831
+ result = auto_functionalized_v2(_mutable_op, **unwrapped_kwargs)
832
+ return ctx.wrap_tensors(result)
lib/python3.12/site-packages/torch/_higher_order_ops/cond.py ADDED
@@ -0,0 +1,559 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-decorators
2
+ # mypy: allow-untyped-defs
3
+ import contextlib
4
+ import logging
5
+ import warnings
6
+ from typing import Any, Callable, List, Tuple, Union
7
+
8
+ import torch
9
+ import torch._subclasses.functional_tensor
10
+ import torch.utils._pytree as pytree
11
+ from torch._C import DispatchKey
12
+ from torch._C._functorch import (
13
+ _add_batch_dim,
14
+ get_unwrapped,
15
+ is_batchedtensor,
16
+ maybe_get_bdim,
17
+ )
18
+ from torch._dispatch.python import suspend_functionalization
19
+ from torch._functorch.utils import exposed_in
20
+ from torch._guards import detect_fake_mode
21
+ from torch._higher_order_ops.utils import (
22
+ _has_potential_branch_input_alias,
23
+ _has_potential_branch_input_mutation,
24
+ _maybe_run_with_interpreter,
25
+ _set_compilation_env,
26
+ reenter_make_fx,
27
+ save_tensors_and_symints_for_backward,
28
+ saved_tensors_and_symints,
29
+ unique_graph_id,
30
+ UnsupportedAliasMutationException,
31
+ validate_subgraph_args_types,
32
+ )
33
+ from torch._ops import HigherOrderOperator
34
+ from torch._subclasses.fake_tensor import FakeTensorMode
35
+ from torch._subclasses.functional_tensor import disable_functional_mode
36
+ from torch.fx.experimental.proxy_tensor import (
37
+ _temp_remove_metadata_torch_function_mode,
38
+ _temp_remove_pre_dispatch_torch_function_mode,
39
+ disable_proxy_modes_tracing,
40
+ ProxyTorchDispatchMode,
41
+ track_tensor_tree,
42
+ )
43
+ from torch.fx.passes.shape_prop import _extract_tensor_metadata
44
+ from torch.utils._python_dispatch import _get_current_dispatch_mode
45
+
46
+ from .utils import _from_fun, create_fw_bw_graph
47
+
48
+
49
+ log = logging.getLogger(__name__)
50
+
51
+ """
52
+ We're going to define a `cond_op` operation.
53
+ In order to do this, we need implementations for each of the dispatch keys.
54
+ """
55
+
56
+
57
+ class CondOp(HigherOrderOperator):
58
+ def __init__(self):
59
+ super().__init__("cond")
60
+
61
+ def __call__(self, pred, true_fn, false_fn, operands):
62
+ validate_subgraph_args_types(operands)
63
+ return super().__call__(pred, true_fn, false_fn, operands)
64
+
65
+
66
+ cond_op = CondOp()
67
+
68
+
69
+ @exposed_in("torch")
70
+ def cond(
71
+ pred: Union[bool, int, float, torch.Tensor],
72
+ true_fn: Callable,
73
+ false_fn: Callable,
74
+ operands: Union[Tuple, List] = (),
75
+ ) -> Any:
76
+ r"""
77
+ Conditionally applies `true_fn` or `false_fn`.
78
+
79
+ .. warning::
80
+ `torch.cond` is a prototype feature in PyTorch. It has limited support for input and output types and
81
+ doesn't support training currently. Please look forward to a more stable implementation in a future version of PyTorch.
82
+ Read more about feature classification at: https://pytorch.org/blog/pytorch-feature-classification-changes/#prototype
83
+
84
+ `cond` is structured control flow operator. That is, it is like a Python if-statement,
85
+ but has restrictions on `true_fn`, `false_fn`, and `operands` that enable it to be
86
+ capturable using torch.compile and torch.export.
87
+
88
+ Assuming the constraints on `cond`'s arguments are met, `cond` is equivalent to the following::
89
+
90
+ def cond(pred, true_branch, false_branch, operands):
91
+ if pred:
92
+ return true_branch(*operands)
93
+ else:
94
+ return false_branch(*operands)
95
+
96
+ Args:
97
+ pred (Union[bool, torch.Tensor]): A boolean expression or a tensor with one element,
98
+ indicating which branch function to apply.
99
+
100
+ true_fn (Callable): A callable function (a -> b) that is within the
101
+ scope that is being traced.
102
+
103
+ false_fn (Callable): A callable function (a -> b) that is within the
104
+ scope that is being traced. The true branch and false branch must
105
+ have consistent input and outputs, meaning the inputs have to be
106
+ the same, and the outputs have to be the same type and shape.
107
+
108
+ operands (Tuple of possibly nested dict/list/tuple of torch.Tensor): A tuple of inputs to the
109
+ true/false functions. It can be empty if true_fn/false_fn doesn't require input. Defaults to ().
110
+
111
+ Example::
112
+
113
+ def true_fn(x: torch.Tensor):
114
+ return x.cos()
115
+ def false_fn(x: torch.Tensor):
116
+ return x.sin()
117
+ return cond(x.shape[0] > 4, true_fn, false_fn, (x,))
118
+
119
+ Restrictions:
120
+ - The conditional statement (aka `pred`) must meet one of the following constraints:
121
+
122
+ - It's a `torch.Tensor` with only one element, and torch.bool dtype
123
+
124
+ - It's a boolean expression, e.g. `x.shape[0] > 10` or `x.dim() > 1 and x.shape[1] > 10`
125
+
126
+ - The branch function (aka `true_fn`/`false_fn`) must meet all of the following constraints:
127
+
128
+ - The function signature must match with operands.
129
+
130
+ - The function must return a tensor with the same metadata, e.g. shape,
131
+ dtype, etc.
132
+
133
+ - The function cannot have in-place mutations on inputs or global variables.
134
+ (Note: in-place tensor operations such as `add_` for intermediate results
135
+ are allowed in a branch)
136
+
137
+ .. warning::
138
+ Temporal Limitations:
139
+
140
+ - The **output** of branches must be a **single Tensor**. Pytree of tensors will be supported in the future.
141
+
142
+ """
143
+ if torch.compiler.is_dynamo_compiling():
144
+ return cond_op(pred, true_fn, false_fn, operands)
145
+
146
+ from torch._dynamo.backends.debugging import (
147
+ make_eager_backend_with_torch_function_mode,
148
+ )
149
+
150
+ if isinstance(pred, (bool, int, float)):
151
+ # This is the non-strict export case. Strict export and torch.compile are
152
+ # handled above in dynamo.
153
+ if torch.compiler.is_compiling():
154
+ warnings.warn(
155
+ "Pred is a Python constant. When used with torch.cond, it specializes on one of the branches."
156
+ " If you want torch.cond to preserve two branches, please make the predicate a boolean tensor or a SymBool.",
157
+ UserWarning,
158
+ )
159
+ # This is the eager case. We can just run the true or false branch.
160
+ if pred:
161
+ return true_fn(*operands)
162
+ else:
163
+ return false_fn(*operands)
164
+
165
+ def _validate_input(pred, true_fn, false_fn, operands):
166
+ if not isinstance(pred, (bool, torch.Tensor, torch.SymBool)):
167
+ raise RuntimeError(f"Expected pred to be bool or tensor, but got {pred}.")
168
+
169
+ if isinstance(pred, torch.Tensor) and pred.numel() != 1:
170
+ raise RuntimeError(
171
+ f"Expected pred to be bool or single-element tensor, but got {pred}."
172
+ )
173
+
174
+ if not callable(true_fn) or not callable(false_fn):
175
+ raise RuntimeError("Expect both branches to be callable.")
176
+
177
+ if not isinstance(operands, (tuple, list)) or pytree.tree_any(
178
+ lambda t: not isinstance(t, torch.Tensor), operands
179
+ ):
180
+ raise RuntimeError(
181
+ "Expect operands to be a tuple of possibly nested dict/list/tuple that only "
182
+ f"consists of tensor leaves, but got {operands}."
183
+ )
184
+
185
+ _validate_input(pred, true_fn, false_fn, operands)
186
+
187
+ if not torch._dynamo.is_dynamo_supported():
188
+ raise RuntimeError("torch.cond requires dynamo support.")
189
+
190
+ # Dynamo is expecting a callable with "__code__" attribute.
191
+ # We cannot directly pass cond_op to it. So we wrap it in a dummy function.
192
+ def _cond_op_wrapper(*args, **kwargs):
193
+ return cond_op(*args, **kwargs)
194
+
195
+ with _set_compilation_env(), torch._dynamo.utils.disable_cache_limit(), _temp_remove_pre_dispatch_torch_function_mode():
196
+ with _temp_remove_metadata_torch_function_mode() as metadata_mode:
197
+ if metadata_mode:
198
+ backend = make_eager_backend_with_torch_function_mode(metadata_mode)
199
+ else:
200
+ backend = "eager"
201
+ return torch.compile(_cond_op_wrapper, backend=backend, fullgraph=True)(
202
+ pred, true_fn, false_fn, operands
203
+ )
204
+
205
+
206
+ def create_fw_bw_graph_branches(true_fn, false_fn, *operands):
207
+ # See Note [HOP create fw_bw graph] in create_fw_bw_graph in utils.py
208
+
209
+ with suspend_functionalization(), disable_functional_mode():
210
+ with disable_proxy_modes_tracing():
211
+ fw_inputs = pytree.tree_map(_from_fun, operands)
212
+
213
+ fw_outputs_true = pytree.tree_map(_from_fun, true_fn(*fw_inputs))
214
+ if any(
215
+ not isinstance(out, torch.Tensor)
216
+ for out in fw_outputs_true
217
+ if out is not None
218
+ ):
219
+ raise RuntimeError(
220
+ "Expect outputs of true_fn to only contains tensors or None. "
221
+ f"Got types {[type(out) for out in fw_outputs_true]}."
222
+ )
223
+ fw_outputs_false = pytree.tree_map(_from_fun, false_fn(*fw_inputs))
224
+ if any(
225
+ not isinstance(out, torch.Tensor)
226
+ for out in fw_outputs_false
227
+ if out is not None
228
+ ):
229
+ raise RuntimeError(
230
+ "Expect outputs of false_fn to only contains tensors or None. "
231
+ f"Got types {[type(out) for out in fw_outputs_false]}."
232
+ )
233
+
234
+ # TODO: There is a major issue that the create_fw_bw in the higher_order_op is invoked twice:
235
+ # Once in the forward path (as it should) and once in the backward path, where it shouldn't be called
236
+ # If we can get rid of the second invokation, it would simplify this function
237
+ fw_true_graph, joint_true_graph = create_fw_bw_graph(
238
+ true_fn, False, fw_inputs, fw_outputs_true
239
+ )
240
+ fw_false_graph, joint_false_graph = create_fw_bw_graph(
241
+ false_fn, False, fw_inputs, fw_outputs_false
242
+ )
243
+
244
+ return fw_true_graph, fw_false_graph, joint_true_graph, joint_false_graph
245
+
246
+
247
+ def trace_cond(proxy_mode, func_overload, pred, true_fn, false_fn, operands):
248
+ assert isinstance(
249
+ operands, (list, tuple)
250
+ ), f"Cond operands must be a list or tuple of tensors and SymInts {operands}"
251
+
252
+ true_graph = reenter_make_fx(true_fn)(*operands)
253
+ false_graph = reenter_make_fx(false_fn)(*operands)
254
+
255
+ true_outs = []
256
+ false_outs = []
257
+ for node in true_graph.graph.nodes:
258
+ if node.op == "output":
259
+ true_outs.extend(node.args)
260
+
261
+ for node in false_graph.graph.nodes:
262
+ if node.op == "output":
263
+ false_outs.extend(node.args)
264
+
265
+ flat_true_outs = pytree.arg_tree_leaves(*true_outs)
266
+ flat_false_outs = pytree.arg_tree_leaves(*false_outs)
267
+ if len(flat_true_outs) != len(flat_false_outs):
268
+ raise torch._dynamo.exc.CondOpArgsMismatchError(
269
+ f"Expected to return same number of outputs but got:"
270
+ f"\n true branch returns {len(flat_true_outs)} item(s)"
271
+ f"\n false branch returns {len(flat_false_outs)} item(s)"
272
+ )
273
+
274
+ for i in range(0, len(flat_true_outs)):
275
+ true_out = flat_true_outs[i]
276
+ false_out = flat_false_outs[i]
277
+
278
+ # Note that we need skip the check for requires_grad because we're after
279
+ # after autograd key during tracing, so the rquires_grad attribute of the tensors
280
+ # are no longer. See Note [invariants for node meta 'val']
281
+ def _same_meta_except_requires_grad(true_out, false_out):
282
+ if true_out is None and false_out is None:
283
+ return True
284
+ elif true_out is None or false_out is None:
285
+ # Consider the following case:
286
+ # def true_fn(x, y):
287
+ # return x * y
288
+ #
289
+ # def false_fn(x, y):
290
+ # return x.sin()
291
+ #
292
+ # We'll get the following graphs for backward:
293
+ # def backward_true_fn(x, y, grad_out):
294
+ # return grad_out * y, grad_out * x
295
+ #
296
+ # def backward_false_fn(x, y, grad_out):
297
+ # retrun grad_out, None
298
+ #
299
+ # This suggests that when we make_fx into the backward graph,
300
+ # the output graph would produce outputs with metadata, this is undesirable.
301
+ #
302
+ # Ideally, we should provide an optional type to indicate that one of the branches might
303
+ # return None. But we'll just let it pass for now and let downstream/runtime handle.
304
+ #
305
+ # Note that this corner case should **only** happen when user want to trace backward graph because
306
+ # if it's foward, dynamo will error.
307
+ return True
308
+ true_meta = true_out.meta.get("tensor_meta", None)
309
+ false_meta = false_out.meta.get("tensor_meta", None)
310
+ return (
311
+ true_meta.shape == false_meta.shape
312
+ and true_meta.dtype == false_meta.dtype
313
+ and true_meta.stride == false_meta.stride
314
+ )
315
+
316
+ if not _same_meta_except_requires_grad(true_out, false_out):
317
+ raise torch._dynamo.exc.CondOpArgsMismatchError(
318
+ f"Expected each tensor to have same metadata but got:"
319
+ f"\n {true_fn.__name__} returns {true_out.meta['tensor_meta']}"
320
+ f"\n {false_fn.__name__} returns {false_out.meta['tensor_meta']}"
321
+ )
322
+
323
+ i, true_name = unique_graph_id(proxy_mode, prefix="true_graph")
324
+
325
+ false_name = f"false_graph_{i}"
326
+ assert not hasattr(proxy_mode.tracer.root, false_name)
327
+
328
+ proxy_mode.tracer.root.register_module(true_name, true_graph)
329
+ proxy_mode.tracer.root.register_module(false_name, false_graph)
330
+
331
+ args = (pred, true_graph, false_graph, operands)
332
+
333
+ proxy_args = pytree.tree_map(proxy_mode.tracer.unwrap_proxy, args)
334
+
335
+ out_proxy = proxy_mode.tracer.create_proxy(
336
+ "call_function", func_overload, proxy_args, {}
337
+ )
338
+
339
+ # At this point, we're *guaranteed* that whether an output came from the
340
+ # true or false branch is indistinguishable. So, as this is just for tracing
341
+ # purposes, choose the true branch.
342
+
343
+ # TODO: the unbacked symbol allocations MUST NOT leak out, if you want to
344
+ # support this we need to arrange for the reenter_make_fx unbacked SymInts
345
+ # to be used, AND we need to arrange for some sort of unification between
346
+ # the two branches (but not really unification; e.g., if one branch
347
+ # returns [u0] and the other returns [5] this is OK but you MUST NOT
348
+ # conclude the result is 5. Also if one branch returns [3] and another
349
+ # branch returns [5] you can make it work by immediately allocating a new
350
+ # unbacked SymInt here).
351
+ ignore_fresh_unbacked = contextlib.nullcontext()
352
+ if (fake_mode := detect_fake_mode()) and fake_mode.shape_env:
353
+ ignore_fresh_unbacked = fake_mode.shape_env.ignore_fresh_unbacked_symbols()
354
+
355
+ # TODO: Uhh.... it shouldn't matter, but changing this to true_fn results in
356
+ # a FakeTensorMode error :
357
+ # `Current active mode <class 'torch._subclasses.fake_tensor.FakeTensorMode'> not registered`
358
+ # TODO Sometimes the operands are not completely FakeTensor, something seems went wrong in
359
+ # dynamo? Because of that it runs real computation sometimes and re-triggering downstream dispatch keys.
360
+ with ignore_fresh_unbacked:
361
+ out = false_fn(*operands)
362
+
363
+ return track_tensor_tree(out, out_proxy, constant=None, tracer=proxy_mode.tracer)
364
+
365
+
366
+ @cond_op.py_impl(DispatchKey.CompositeExplicitAutograd)
367
+ def cond_op_dense(pred, true_fn, false_fn, operands):
368
+ assert all(
369
+ isinstance(o, (torch.Tensor, int)) for o in operands
370
+ ), f"Dense implementation operands must be a list of tensors and ints {operands}"
371
+ mode = _get_current_dispatch_mode()
372
+ assert mode is None, "Mode should never be enabled for CPU/CUDA key"
373
+ if pred:
374
+ return true_fn(*operands)
375
+ else:
376
+ return false_fn(*operands)
377
+
378
+
379
+ class CondAutogradOp(torch.autograd.Function):
380
+ @staticmethod
381
+ def forward(
382
+ ctx,
383
+ pred,
384
+ fw_true_graph,
385
+ fw_false_graph,
386
+ joint_true_graph,
387
+ joint_false_graph,
388
+ *operands,
389
+ ):
390
+ ctx._pred = pred
391
+ ctx._joint_true_graph = joint_true_graph
392
+ ctx._joint_false_graph = joint_false_graph
393
+ save_tensors_and_symints_for_backward(ctx, operands)
394
+
395
+ with torch._C._AutoDispatchBelowAutograd():
396
+ return cond_op(pred, fw_true_graph, fw_false_graph, operands)
397
+
398
+ @staticmethod
399
+ def backward(ctx, *flat_grads):
400
+ operands = saved_tensors_and_symints(ctx)
401
+
402
+ grads = cond_op(
403
+ ctx._pred,
404
+ ctx._joint_true_graph,
405
+ ctx._joint_false_graph,
406
+ flat_grads + operands,
407
+ )
408
+ return None, None, None, None, None, *grads
409
+
410
+
411
+ @cond_op.py_impl(DispatchKey.Autograd)
412
+ def cond_autograd(pred, true_fn, false_fn, operands):
413
+ # A shortcut for the case where all inputs don't require gradient,
414
+ # we skip tracing the forward and backward graph.
415
+ if pytree.tree_all_only(
416
+ torch.Tensor,
417
+ lambda t: not t.requires_grad, # type: ignore[union-attr]
418
+ (pred, operands),
419
+ ):
420
+ with torch._C._AutoDispatchBelowAutograd():
421
+ return cond_op(pred, true_fn, false_fn, operands)
422
+
423
+ (
424
+ fw_true_graph,
425
+ fw_false_graph,
426
+ joint_true_graph,
427
+ joint_false_graph,
428
+ ) = create_fw_bw_graph_branches(true_fn, false_fn, *operands)
429
+ flat_out = CondAutogradOp.apply(
430
+ pred,
431
+ fw_true_graph,
432
+ fw_false_graph,
433
+ joint_true_graph,
434
+ joint_false_graph,
435
+ *operands,
436
+ )
437
+ return flat_out
438
+
439
+
440
+ @cond_op.py_impl(ProxyTorchDispatchMode)
441
+ def inner(mode, pred, true_fn, false_fn, operands):
442
+ return trace_cond(mode, cond_op, pred, true_fn, false_fn, operands)
443
+
444
+
445
+ @cond_op.py_impl(FakeTensorMode)
446
+ def cond_fake_tensor_mode(mode, pred, true_fn, false_fn, operands):
447
+ # Ignore here, because if you've gotten here but you're not manually
448
+ # tracing the inner graphs, that means that you intend to reuse the graph
449
+ # directly. Which means the old unbacked symbol bindings are appropriate.
450
+ # This strategy will not work if unbacked symbols can escape.
451
+ ignore_fresh_unbacked = contextlib.nullcontext()
452
+ if mode.shape_env:
453
+ ignore_fresh_unbacked = mode.shape_env.ignore_fresh_unbacked_symbols()
454
+
455
+ with mode, ignore_fresh_unbacked:
456
+ true_outs = true_fn(*operands)
457
+ flat_true_outs = pytree.tree_leaves(true_outs)
458
+ flat_false_outs = pytree.tree_leaves(false_fn(*operands))
459
+ if len(flat_true_outs) != len(flat_false_outs):
460
+ raise RuntimeError("Unmatched number of outputs from cond() branches.")
461
+
462
+ for true_out, false_out in zip(flat_true_outs, flat_false_outs):
463
+ if true_out is None or false_out is None:
464
+ if true_out is None and false_out is None:
465
+ continue
466
+ raise torch._dynamo.exc.CondOpArgsMismatchError(
467
+ f"Expected both branches to return None:"
468
+ f"\n {true_fn.__name__} returns {true_out}"
469
+ f"\n {false_fn.__name__} returns {false_out}"
470
+ )
471
+ true_meta = _extract_tensor_metadata(true_out)
472
+ false_meta = _extract_tensor_metadata(false_out)
473
+ if true_meta != false_meta:
474
+ raise torch._dynamo.exc.CondOpArgsMismatchError(
475
+ f"Expected each tensor to have same metadata but got:"
476
+ f"\n {true_fn.__name__} returns {true_meta}"
477
+ f"\n {false_fn.__name__} returns {false_meta}"
478
+ )
479
+ return true_outs
480
+
481
+
482
+ @cond_op.py_functionalize_impl
483
+ def cond_func(ctx, pred, true_fn, false_fn, inputs):
484
+ unwrapped_inputs = ctx.unwrap_tensors(inputs)
485
+ unwrapped_pred = ctx.unwrap_tensors(pred)
486
+ with ctx.redispatch_to_next() as m:
487
+ functional_true = ctx.functionalize(_maybe_run_with_interpreter(true_fn))
488
+ functional_false = ctx.functionalize(_maybe_run_with_interpreter(false_fn))
489
+ pre_dispatch = hasattr(ctx, "mode") and ctx.mode.pre_dispatch
490
+ for branch in [functional_true, functional_false]:
491
+ if _has_potential_branch_input_mutation(
492
+ branch, unwrapped_inputs, pre_dispatch=pre_dispatch
493
+ ):
494
+ raise UnsupportedAliasMutationException(
495
+ "One of torch.cond branch might be modifying the input! "
496
+ "Consider cloning the input before modifying it. "
497
+ )
498
+ for branch in [true_fn, false_fn]:
499
+ if _has_potential_branch_input_alias(
500
+ branch, unwrapped_inputs, pre_dispatch=pre_dispatch
501
+ ):
502
+ raise UnsupportedAliasMutationException(
503
+ "One of torch.cond branch might be aliasing the input! "
504
+ "If you are returning a view of the input, please make sure "
505
+ "to clone it. "
506
+ )
507
+
508
+ cond_return = cond_op(
509
+ unwrapped_pred, functional_true, functional_false, unwrapped_inputs
510
+ )
511
+ return ctx.wrap_tensors(cond_return)
512
+
513
+
514
+ @cond_op.py_impl(torch._C._functorch.TransformType.Vmap)
515
+ def cond_batch_rule(interpreter, pred, true_fn, false_fn, inputs):
516
+ assert isinstance(
517
+ inputs, (list, tuple)
518
+ ), "Cond inputs must be a list or tuple of tensors"
519
+ assert all(
520
+ isinstance(i, torch.Tensor) for i in inputs
521
+ ), "Cond inputs must be a list of tensors"
522
+
523
+ pred_is_batched = isinstance(pred, torch.Tensor) and is_batchedtensor(pred)
524
+ pred_ = get_unwrapped(pred) if pred_is_batched else pred
525
+
526
+ # unbatched tensors are not vmapped
527
+ tensors, in_dims = zip(
528
+ *[
529
+ (get_unwrapped(t), maybe_get_bdim(t)) if is_batchedtensor(t) else (t, None)
530
+ for t in inputs
531
+ ]
532
+ )
533
+
534
+ if pred_is_batched:
535
+ # prepend "pred" and vmap everything
536
+ tensors = (pred_,) + tensors
537
+ in_dims = (0,) + in_dims
538
+
539
+ def fn(p, *args):
540
+ t = true_fn(*args)
541
+ f = false_fn(*args)
542
+ return torch.where(p, t[0], f[0])
543
+
544
+ with interpreter.lower():
545
+ result = torch.vmap(fn, in_dims=in_dims)(*tensors)
546
+
547
+ else:
548
+ # predicate is known at this stage and it is a boolean expression or a
549
+ # tensor with one element.
550
+ true_fn = torch.vmap(true_fn, in_dims=in_dims)
551
+ false_fn = torch.vmap(false_fn, in_dims=in_dims)
552
+
553
+ with interpreter.lower():
554
+ result = cond_op(pred, true_fn, false_fn, tensors)
555
+
556
+ if not isinstance(result, tuple):
557
+ result = (result,)
558
+ lvl = interpreter.level()
559
+ return tuple([_add_batch_dim(r, 0, lvl) for r in result])
lib/python3.12/site-packages/torch/_higher_order_ops/effects.py ADDED
@@ -0,0 +1,288 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-decorators
2
+ # mypy: allow-untyped-defs
3
+ from enum import Enum
4
+ from typing import Any, Dict, Optional, Tuple, Union
5
+ from weakref import WeakKeyDictionary
6
+
7
+ import torch
8
+ import torch.utils._pytree as pytree
9
+ from torch._C import DispatchKey
10
+ from torch._higher_order_ops.torchbind import call_torchbind
11
+ from torch._ops import HigherOrderOperator
12
+ from torch._subclasses.fake_tensor import FakeTensorMode
13
+ from torch.fx.experimental.proxy_tensor import (
14
+ disable_proxy_modes_tracing,
15
+ ProxyTorchDispatchMode,
16
+ track_tensor_tree,
17
+ )
18
+
19
+
20
+ class _EffectType(Enum):
21
+ ORDERED = "Ordered"
22
+
23
+
24
+ OpType = Union[torch._ops.HigherOrderOperator, torch._ops.OpOverload]
25
+
26
+
27
+ SIDE_EFFECTS: "WeakKeyDictionary[OpType, _EffectType]" = WeakKeyDictionary(
28
+ {
29
+ torch.ops.aten._print.default: _EffectType.ORDERED,
30
+ call_torchbind: _EffectType.ORDERED,
31
+ }
32
+ )
33
+
34
+
35
+ def _register_effectful_op(op: OpType, effect: _EffectType):
36
+ assert isinstance(
37
+ op, (torch._ops.OpOverload, torch._ops.HigherOrderOperator)
38
+ ) and not has_aliasing(op)
39
+ if op in SIDE_EFFECTS and SIDE_EFFECTS[op] != effect:
40
+ raise RuntimeError(
41
+ f"Already registered effect type {SIDE_EFFECTS[op]} to op {op}, "
42
+ f"trying to register a different effect type {effect}."
43
+ )
44
+ SIDE_EFFECTS[op] = effect
45
+
46
+
47
+ def _deregister_effectful_op(op: OpType):
48
+ if op not in SIDE_EFFECTS:
49
+ raise RuntimeError(f"Op {op} is not registered as effectful")
50
+
51
+ del SIDE_EFFECTS[op]
52
+
53
+
54
+ class WithEffects(HigherOrderOperator):
55
+ """
56
+ with_effects(token, op, args, kwargs) -> (new_token, op_results)
57
+
58
+ This HOP helps ensure ordering between side effectful ops like prints or ops
59
+ using torchbind objects. This is needed to ensure a traced graph from
60
+ AOTAutograd is functional so that future optimization passes do not reorder
61
+ these operators. This is done through threading "effect tokens" through the
62
+ graph to enforce data dependence between side effectful ops.
63
+
64
+ The tokens are basically dummy values (torch.tensor([])). We create a token
65
+ per "effect type", which are enumerated in the _EffectType enum.
66
+ """
67
+
68
+ def __init__(self) -> None:
69
+ super().__init__("with_effects")
70
+
71
+ def __call__(
72
+ self,
73
+ token,
74
+ op: OpType,
75
+ *args: Tuple[Any, ...],
76
+ **kwargs: Dict[str, Any],
77
+ ) -> Tuple[Any, ...]:
78
+ assert isinstance(op, (torch._ops.HigherOrderOperator, torch._ops.OpOverload))
79
+ assert not has_aliasing(op), "Ops with aliasing is not supported"
80
+ assert has_effects(op, args, kwargs)
81
+ assert isinstance(kwargs, dict)
82
+ return super().__call__(token, op, *args, **kwargs)
83
+
84
+
85
+ with_effects = WithEffects()
86
+
87
+
88
+ def has_aliasing(op: OpType):
89
+ # NOT FOR PUBLIC USE
90
+ if isinstance(op, torch._ops.HigherOrderOperator):
91
+ return op not in SIDE_EFFECTS
92
+
93
+ for arg in op._schema.arguments:
94
+ if arg.alias_info is not None:
95
+ return True
96
+ for arg in op._schema.returns:
97
+ if arg.alias_info is not None:
98
+ return True
99
+ return False
100
+
101
+
102
+ def has_effects(op, args, kwargs) -> bool:
103
+ # Skip over the profiler's RecordFunction as they should not show up in the graph
104
+ _skip_ops = {torch.ops.profiler._record_function_exit._RecordFunction}
105
+ if op in _skip_ops:
106
+ return False
107
+
108
+ return (
109
+ isinstance(op, (torch._ops.HigherOrderOperator, torch._ops.OpOverload))
110
+ and not has_aliasing(op)
111
+ and get_effect_key(op, args, kwargs) is not None
112
+ )
113
+
114
+
115
+ def get_effect_key(op, args, kwargs) -> Optional[_EffectType]:
116
+ if op in SIDE_EFFECTS:
117
+ return SIDE_EFFECTS[op]
118
+
119
+ for arg in args:
120
+ if isinstance(arg, torch.ScriptObject):
121
+ # Add it to the table so that next time we see the same op we don't
122
+ # have to parse through the args again
123
+ SIDE_EFFECTS[op] = _EffectType.ORDERED
124
+ return _EffectType.ORDERED
125
+
126
+ return None
127
+
128
+
129
+ def new_token_tensor() -> torch.Tensor:
130
+ return torch.tensor([])
131
+
132
+
133
+ @with_effects.py_impl(DispatchKey.CompositeExplicitAutograd)
134
+ def with_effects_dense(
135
+ token: torch.Tensor,
136
+ op: torch._ops.OpOverload,
137
+ *args: Tuple[Any, ...],
138
+ **kwargs: Dict[str, Any],
139
+ ) -> Tuple[torch.Tensor, ...]:
140
+ out = op(*args, **kwargs)
141
+ new_token = new_token_tensor()
142
+ if isinstance(out, tuple):
143
+ return (new_token, *out)
144
+ return (new_token, out)
145
+
146
+
147
+ @with_effects.py_impl(FakeTensorMode)
148
+ def with_effects_fake(
149
+ mode,
150
+ token: torch.Tensor,
151
+ op: torch._ops.OpOverload,
152
+ *args: Tuple[Any, ...],
153
+ **kwargs: Dict[str, Any],
154
+ ) -> Tuple[torch.Tensor, ...]:
155
+ with mode:
156
+ result = with_effects_dense(token, op, *args, **kwargs)
157
+ return result
158
+
159
+
160
+ @with_effects.py_impl(ProxyTorchDispatchMode)
161
+ def with_effects_proxy(
162
+ mode,
163
+ token: torch.Tensor,
164
+ op: torch._ops.OpOverload,
165
+ *args: Tuple[Any, ...],
166
+ **kwargs: Dict[str, Any],
167
+ ) -> Tuple[torch.Tensor, ...]:
168
+ with disable_proxy_modes_tracing():
169
+ out = with_effects(token, op, *args, **kwargs)
170
+
171
+ proxy_token = mode.tracer.unwrap_proxy(token)
172
+ proxy_args = pytree.tree_map(mode.tracer.unwrap_proxy, args)
173
+ proxy_kwargs = pytree.tree_map(mode.tracer.unwrap_proxy, kwargs)
174
+
175
+ from torch.fx.node import has_side_effect
176
+
177
+ # To avoid the being DCEed by graph.eliminate_dead_code if they.
178
+ # don't have output or their outputs are not used.
179
+ has_side_effect(op)
180
+
181
+ out_proxy = mode.tracer.create_proxy(
182
+ "call_function",
183
+ with_effects,
184
+ (proxy_token, op, *proxy_args),
185
+ proxy_kwargs,
186
+ )
187
+ result = track_tensor_tree(out, out_proxy, constant=None, tracer=mode.tracer)
188
+ return result
189
+
190
+
191
+ with_effects.fallthrough(DispatchKey.AutogradCPU)
192
+ with_effects.fallthrough(DispatchKey.AutogradCUDA)
193
+
194
+
195
+ def _get_schema(op, args) -> torch.FunctionSchema:
196
+ if isinstance(op, torch._ops.OpOverload):
197
+ return op._schema
198
+ elif op == call_torchbind:
199
+ return getattr(args[0], args[1]).schema
200
+ else:
201
+ raise RuntimeError(f"Unable to get schema for op {op}")
202
+
203
+
204
+ def handle_effects(
205
+ allow_token_discovery: bool,
206
+ tokens: Dict[_EffectType, torch.Tensor],
207
+ op: OpType,
208
+ args: Tuple[Any, ...],
209
+ kwargs: Dict[str, Any],
210
+ ) -> Any:
211
+ """
212
+ Args:
213
+ allow_token_discovery: Whether or not we are discovering tokens. If this
214
+ is true, we will create a token for every side effect type seen that
215
+ does not have a token assigned yet. If this is false, the tokens
216
+ should've all been created ahead of time, so we will error if there is
217
+ no token mapping to every effect type.
218
+
219
+ tokens: Map of effect type to tokens. This is to chain operators of the
220
+ same effects together so that they do not get reordered in later
221
+ optimization passes.
222
+ """
223
+
224
+ # Get a token. We can't do `tokens.get(op, torch.tensor([]))` because
225
+ # this will create an empty tensor during proxy mode tracing if the token
226
+ # doesn't exist. But the tokens should always exist during proxy mode tracing.
227
+ key = get_effect_key(op, args, kwargs)
228
+ assert key is not None
229
+ if key not in tokens:
230
+ assert (
231
+ allow_token_discovery
232
+ ), f"Could not find a token for effect {key} which came from the function {op}"
233
+ proxy_tensor_mode = torch._C._get_dispatch_mode(
234
+ torch._C._TorchDispatchModeKey.PROXY
235
+ )
236
+ if proxy_tensor_mode is not None:
237
+ # If we discovered a new token during tracing, we are in backward.
238
+ # Then we patch the graph, adding additional tangents_token as input to the joint graph.
239
+ tracer = proxy_tensor_mode.tracer
240
+
241
+ from torch.fx.experimental.proxy_tensor import (
242
+ disable_proxy_modes_tracing,
243
+ track_tensor_tree,
244
+ )
245
+
246
+ with disable_proxy_modes_tracing():
247
+ token_tensor = new_token_tensor()
248
+
249
+ token_proxy = proxy_tensor_mode.tracer.create_proxy(
250
+ "placeholder", "tangents_token", (), {}, name="tangents_token"
251
+ )
252
+ track_tensor_tree(token_tensor, token_proxy, constant=None, tracer=tracer)
253
+
254
+ tokens[key] = token_tensor
255
+ else:
256
+ tokens[key] = new_token_tensor()
257
+
258
+ token = tokens[key]
259
+
260
+ from torch._subclasses.functional_tensor import PythonFunctionalizeAPI
261
+
262
+ ctx = PythonFunctionalizeAPI()
263
+
264
+ unwrapped_token = ctx.unwrap_tensors([token])[0]
265
+ unwrapped_args = ctx.unwrap_tensors(args)
266
+ unwrapped_kwargs = ctx.unwrap_tensors(kwargs) # type: ignore[arg-type]
267
+ with ctx.redispatch_to_next():
268
+ (new_token, *unwrapped_outs) = with_effects(
269
+ unwrapped_token, op, *unwrapped_args, **unwrapped_kwargs
270
+ )
271
+
272
+ schema = _get_schema(op, unwrapped_args)
273
+ if len(schema.returns) == 0:
274
+ assert unwrapped_outs[0] is None
275
+ unwrapped_outs = None # type: ignore[assignment]
276
+ elif len(schema.returns) == 1:
277
+ assert len(unwrapped_outs) == 1
278
+ unwrapped_outs = unwrapped_outs[0]
279
+ else:
280
+ assert len(unwrapped_outs) == len(schema.returns)
281
+
282
+ # Add the newly created token into the tokens map for a following call to
283
+ # use this token.
284
+ wrapped_token = ctx.wrap_tensors(new_token)
285
+ assert isinstance(wrapped_token, torch.Tensor)
286
+ tokens[key] = wrapped_token
287
+
288
+ return ctx.wrap_tensors(unwrapped_outs)
lib/python3.12/site-packages/torch/_higher_order_ops/executorch_call_delegate.py ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+
3
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ # All rights reserved.
5
+ #
6
+ # This source code is licensed under the BSD-style license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+
9
+ # pyre-strict
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Any, cast
14
+
15
+ import torch
16
+ import torch.utils._pytree as pytree
17
+ from torch._ops import HigherOrderOperator
18
+ from torch._subclasses.fake_tensor import FakeTensorMode
19
+ from torch.fx.experimental.proxy_tensor import (
20
+ disable_proxy_modes_tracing,
21
+ get_proxy_slot,
22
+ ProxyTorchDispatchMode,
23
+ track_tensor_tree,
24
+ )
25
+ from torch.utils._pytree import tree_flatten
26
+
27
+
28
+ class ExecutorchCallDelegate(HigherOrderOperator):
29
+ def __init__(self):
30
+ super().__init__("executorch_call_delegate")
31
+
32
+ def __call__(self, lowered_module, *args):
33
+ return super().__call__(lowered_module, *args)
34
+
35
+
36
+ executorch_call_delegate = ExecutorchCallDelegate()
37
+ executorch_call_delegate.fallthrough(torch._C.DispatchKey.PythonDispatcher)
38
+ executorch_call_delegate.fallthrough(torch._C.DispatchKey.PythonTLSSnapshot)
39
+ executorch_call_delegate.fallthrough(torch._C.DispatchKey.ADInplaceOrView)
40
+ executorch_call_delegate.fallthrough(torch._C.DispatchKey.AutocastCPU)
41
+
42
+ LOWERED_BACKEND_MODULE_TYPE = "LoweredBackendModule"
43
+
44
+
45
+ # pyre-ignore
46
+ def trace_call_delegate(proxy_mode, func_overload, lowered_module, *args):
47
+ # pyre-ignore
48
+ def _unwrap_proxy(e):
49
+ if not isinstance(e, (torch.Tensor, torch.SymInt, torch.SymFloat)):
50
+ return e
51
+ return get_proxy_slot(
52
+ cast(torch.Tensor, e), proxy_mode.tracer, e, lambda e: e.proxy # type: ignore[attr-defined]
53
+ )
54
+
55
+ if not is_lowered_module(lowered_module):
56
+ raise ValueError(
57
+ "executorch_call_delegate()'s first argument must be a LoweredBackendModule"
58
+ )
59
+
60
+ with disable_proxy_modes_tracing():
61
+ out = call_delegate_cpu(lowered_module, *args)
62
+
63
+ get_lowered_module_name(proxy_mode.tracer.root, lowered_module)
64
+
65
+ node_args = (lowered_module, *args)
66
+ proxy_args = pytree.tree_map(_unwrap_proxy, node_args)
67
+ out_proxy = proxy_mode.tracer.create_proxy(
68
+ "call_function", func_overload, proxy_args, {}, name="executorch_call_delegate"
69
+ )
70
+ return track_tensor_tree(out, out_proxy, constant=None, tracer=proxy_mode.tracer)
71
+
72
+
73
+ @executorch_call_delegate.py_impl(torch._C.DispatchKey.CompositeExplicitAutograd)
74
+ # pyre-ignore
75
+ def call_delegate_cpu(lowered_module, *args):
76
+ # FX creates this immutable_dict/list concept. Get rid of this.
77
+ map_types = {
78
+ torch.fx.immutable_collections.immutable_dict: dict,
79
+ torch.fx.immutable_collections.immutable_list: list,
80
+ }
81
+ new_args = pytree.tree_map_only(
82
+ tuple(map_types.keys()),
83
+ lambda a: map_types[type(a)](a),
84
+ args,
85
+ lambda a: isinstance(a, tuple(map_types.keys())),
86
+ )
87
+ return lowered_module.original_module.module()(*new_args)
88
+
89
+
90
+ @executorch_call_delegate.py_impl(torch._C.DispatchKey.Autograd)
91
+ # pyre-ignore
92
+ def call_delegate_autograd(lowered_module, *args):
93
+ # TODO: support autograd
94
+ flat_operands, _ = tree_flatten([lowered_module, *args])
95
+ requires_grad = any(
96
+ f.requires_grad for f in flat_operands if isinstance(f, torch.Tensor)
97
+ )
98
+
99
+ with torch._C._ExcludeDispatchKeyGuard(
100
+ torch._C.DispatchKeySet(torch._C.DispatchKey.AutogradCPU)
101
+ ):
102
+ res = executorch_call_delegate(lowered_module, *args)
103
+
104
+ if requires_grad:
105
+ # Create aliases of the output that has requires_grad=True. We need
106
+ # at least one of the inputs to err_fn to require grad so that the
107
+ # output will have a grad_fn.
108
+
109
+ # pyre-ignore
110
+ def fake_requires_grad(var):
111
+ if var is not None:
112
+ var = var.detach()
113
+ if torch.is_floating_point(var) or torch.is_complex(var):
114
+ var.requires_grad = True
115
+ return var
116
+
117
+ return pytree.tree_map_only(torch.Tensor, fake_requires_grad, res)
118
+
119
+ return res
120
+
121
+
122
+ @executorch_call_delegate.py_impl(ProxyTorchDispatchMode)
123
+ # pyre-ignore
124
+ def call_delegate_proxy_torch_dispatch_mode(mode, lowered_module, *args):
125
+ res = trace_call_delegate(mode, executorch_call_delegate, lowered_module, *args)
126
+ return res
127
+
128
+
129
+ @executorch_call_delegate.py_impl(FakeTensorMode)
130
+ # pyre-ignore
131
+ def call_delegate_fake_tensor_mode(mode, lowered_module, *args):
132
+ with mode:
133
+ return call_delegate_cpu(lowered_module, *args)
134
+
135
+
136
+ @executorch_call_delegate.py_functionalize_impl
137
+ # pyre-ignore
138
+ def call_delegate_functionalize(ctx, lowered_module, *args):
139
+ unwrapped_args = tuple(ctx.unwrap_tensors(arg) for arg in args)
140
+ with ctx.redispatch_to_next():
141
+ res = executorch_call_delegate(lowered_module, *unwrapped_args)
142
+ return ctx.wrap_tensors(res)
143
+
144
+
145
+ # pyre-ignore: Missing parameter annotation [2]: Parameter `obj` must have a type other than `Any`.Pyre
146
+ def is_lowered_module(obj: Any) -> bool:
147
+ """
148
+ This function is added to avoid using isinstance(obj,
149
+ LoweredBackendModule) as it will import LoweredBackendModule, which may
150
+ cause a circular import.
151
+ """
152
+ return type(obj).__name__ == LOWERED_BACKEND_MODULE_TYPE
153
+
154
+
155
+ def get_lowered_module_name(
156
+ root: torch.nn.Module,
157
+ # pyre-ignore: Undefined or invalid type [11]: Annotation `LoweredBackendModule` is not defined as a type.
158
+ lowered_module: LOWERED_BACKEND_MODULE_TYPE, # type: ignore[valid-type]
159
+ ) -> str:
160
+ """
161
+ Adds the given lowered_module into the given root module and returns the
162
+ name of the module added.
163
+ """
164
+ # Find a qualifying name for the lowered submodule
165
+ qualname = None
166
+ i = 0
167
+ while True:
168
+ qualname = f"lowered_module_{i}"
169
+ if not hasattr(root, qualname):
170
+ break
171
+ i += 1
172
+ assert qualname is not None
173
+
174
+ root.add_module(qualname, lowered_module)
175
+ return qualname
lib/python3.12/site-packages/torch/_higher_order_ops/flex_attention.py ADDED
@@ -0,0 +1,1149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ from typing import Any, Callable, Dict, Optional, Sequence, Tuple, Union
3
+
4
+ import torch
5
+ import torch.utils._pytree as pytree
6
+ from torch import Tensor
7
+ from torch._C import DispatchKey
8
+ from torch._higher_order_ops.utils import (
9
+ _has_potential_branch_input_mutation,
10
+ autograd_not_implemented,
11
+ reenter_make_fx,
12
+ save_tensors_and_symints_for_backward,
13
+ saved_tensors_and_symints,
14
+ UnsupportedAliasMutationException,
15
+ validate_subgraph_args_types,
16
+ )
17
+ from torch._ops import HigherOrderOperator
18
+ from torch._subclasses import FakeTensorMode
19
+ from torch.fx.experimental.proxy_tensor import (
20
+ make_fx,
21
+ ProxyTorchDispatchMode,
22
+ track_tensor_tree,
23
+ )
24
+ from torch.fx.graph_module import GraphModule
25
+
26
+
27
+ # Duplicate of _inductor/kernel/flex_attention.py to avoid circular import
28
+ def _construct_strides(
29
+ sizes: Sequence[int],
30
+ fill_order: Sequence[int],
31
+ ) -> Sequence[int]:
32
+ """From a list of sizes and a fill order, construct the strides of the permuted tensor."""
33
+ # Initialize strides
34
+ assert len(sizes) == len(
35
+ fill_order
36
+ ), "Length of sizes must match the length of the fill order"
37
+ strides = [0] * len(sizes)
38
+
39
+ # Start with stride 1 for the innermost dimension
40
+ current_stride = 1
41
+
42
+ # Iterate through the fill order populating strides
43
+ for dim in fill_order:
44
+ strides[dim] = current_stride
45
+ current_stride *= sizes[dim]
46
+
47
+ return strides
48
+
49
+
50
+ def _permute_strides(out: torch.Tensor, query_strides: Tuple[int, ...]) -> torch.Tensor:
51
+ """
52
+ Create a new tensor with the same data and shape as the input,
53
+ but with strides permuted based on the input tensor's stride order.
54
+
55
+ Args:
56
+ out (torch.Tensor): The output tensor of attention.
57
+ query_strides (List[int]): The stride order of the input query tensor
58
+
59
+ Returns:
60
+ torch.Tensor: A new tensor with same shape and data as the input,
61
+ but with strides permuted based on the query tensor's stride order.
62
+ """
63
+ from torch._inductor.ir import get_fill_order
64
+
65
+ fill_order = get_fill_order(query_strides)
66
+ assert out.storage_offset() == 0, "Only support storage_offset == 0"
67
+ out_strides = _construct_strides(out.shape, fill_order)
68
+ new_out = out.new_empty(out.shape).as_strided(out.shape, out_strides)
69
+ new_out.copy_(out)
70
+ return new_out
71
+
72
+
73
+ class FlexAttentionHOP(HigherOrderOperator):
74
+ def __init__(self) -> None:
75
+ super().__init__("flex_attention", cacheable=True)
76
+
77
+ def __call__(
78
+ self,
79
+ query: torch.Tensor,
80
+ key: torch.Tensor,
81
+ value: torch.Tensor,
82
+ score_mod: Callable,
83
+ block_mask: Tuple,
84
+ scale: float,
85
+ kernel_options: Dict[str, Any],
86
+ score_mod_other_buffers: Tuple = (),
87
+ mask_mod_other_buffers: Tuple = (),
88
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
89
+ validate_subgraph_args_types(score_mod_other_buffers + mask_mod_other_buffers)
90
+ return super().__call__(
91
+ query,
92
+ key,
93
+ value,
94
+ score_mod,
95
+ block_mask,
96
+ scale,
97
+ kernel_options,
98
+ score_mod_other_buffers,
99
+ mask_mod_other_buffers,
100
+ )
101
+
102
+
103
+ flex_attention = FlexAttentionHOP()
104
+
105
+
106
+ class FlexAttentionBackwardHOP(HigherOrderOperator):
107
+ def __init__(self) -> None:
108
+ super().__init__("flex_attention_backward")
109
+
110
+ def __call__(
111
+ self,
112
+ query: torch.Tensor,
113
+ key: torch.Tensor,
114
+ value: torch.Tensor,
115
+ out: torch.Tensor,
116
+ logsumexp: torch.Tensor,
117
+ grad_out: torch.Tensor,
118
+ grad_logsumexp: torch.Tensor,
119
+ fw_graph: Union[Callable, GraphModule],
120
+ joint_graph: GraphModule,
121
+ block_mask: Tuple,
122
+ scale: float,
123
+ kernel_options: Dict[str, Any],
124
+ score_mod_other_buffers: Tuple = (),
125
+ mask_mod_other_buffers: Tuple = (),
126
+ ) -> Tuple[
127
+ torch.Tensor, torch.Tensor, torch.Tensor, Tuple[Optional[torch.Tensor], ...]
128
+ ]:
129
+ validate_subgraph_args_types(score_mod_other_buffers + mask_mod_other_buffers)
130
+ return super().__call__(
131
+ query,
132
+ key,
133
+ value,
134
+ out,
135
+ logsumexp,
136
+ grad_out,
137
+ grad_logsumexp,
138
+ fw_graph,
139
+ joint_graph,
140
+ block_mask,
141
+ scale,
142
+ kernel_options,
143
+ score_mod_other_buffers,
144
+ mask_mod_other_buffers,
145
+ )
146
+
147
+
148
+ flex_attention_backward = FlexAttentionBackwardHOP()
149
+
150
+
151
+ def _math_attention_inner(
152
+ query: torch.Tensor,
153
+ key: torch.Tensor,
154
+ value: torch.Tensor,
155
+ score_mod: Callable,
156
+ block_mask: Tuple,
157
+ scale: float,
158
+ kernel_options: Dict[str, Any],
159
+ score_mod_other_buffers: Tuple = (),
160
+ mask_mod_other_buffers: Tuple = (),
161
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
162
+ from torch._dynamo._trace_wrapped_higher_order_op import TransformGetItemToIndex
163
+
164
+ working_precision = torch.float64 if query.dtype == torch.float64 else torch.float32
165
+
166
+ scores = (query @ key.transpose(-2, -1)).to(dtype=working_precision)
167
+
168
+ b = torch.arange(0, scores.size(0), device=scores.device)
169
+ h = torch.arange(0, scores.size(1), device=scores.device)
170
+ m = torch.arange(0, scores.size(2), device=scores.device)
171
+ n = torch.arange(0, scores.size(3), device=scores.device)
172
+
173
+ captured_buffers_in_dim = (None,) * len(score_mod_other_buffers)
174
+ from torch.nn.attention.flex_attention import _vmap_for_bhqkv
175
+
176
+ # first input is score
177
+ score_mod = _vmap_for_bhqkv(score_mod, prefix=(0,), suffix=captured_buffers_in_dim)
178
+
179
+ mask_mod = block_mask[-1]
180
+ mask_mod_in_dim_buffers = (None,) * len(mask_mod_other_buffers)
181
+ mask_mod = _vmap_for_bhqkv(mask_mod, prefix=(), suffix=mask_mod_in_dim_buffers)
182
+
183
+ with TransformGetItemToIndex():
184
+ scores = (scores * scale).to(working_precision)
185
+ post_mod_scores = torch.where(
186
+ mask_mod(b, h, m, n, *mask_mod_other_buffers),
187
+ score_mod(scores, b, h, m, n, *score_mod_other_buffers),
188
+ torch.tensor(-float("inf"), dtype=working_precision, device=scores.device),
189
+ )
190
+
191
+ return scores, post_mod_scores
192
+
193
+
194
+ def math_attention(
195
+ query: torch.Tensor,
196
+ key: torch.Tensor,
197
+ value: torch.Tensor,
198
+ score_mod: Callable,
199
+ block_mask: Tuple,
200
+ scale: float,
201
+ kernel_options: Dict[str, Any],
202
+ score_mod_other_buffers: Tuple = (),
203
+ mask_mod_other_buffers: Tuple = (),
204
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
205
+ """Eager implementation
206
+
207
+ This implementation uses vmap to vectorize the score_mod function over the batch, head, m, and n dimensions.
208
+ We then apply the vectorized score_mod function to the scores matrix. Each wrap of vmap applies one of the
209
+ batch, head, m, or n dimensions. We need to apply vmap 4 times to vectorized over all 4 dimensions.
210
+
211
+ Args:
212
+ query: The query tensor
213
+ key: The key tensor
214
+ value: The value tensor
215
+ score_mod: The score_mod function
216
+ other_buffers: Other buffers that are passed to the score_mod function
217
+ """
218
+ # broadcast query & key along head dim for GQA
219
+ G = query.size(1) // key.size(1)
220
+ value = torch.repeat_interleave(value, G, dim=1)
221
+ key = torch.repeat_interleave(key, G, dim=1)
222
+
223
+ Bq, Bkv = query.size(0), key.size(0)
224
+ if not ((Bq == Bkv) or (Bq > 1 and Bkv == 1)):
225
+ raise RuntimeError(f"Bq and Bkv must broadcast. Got Bq={Bq} and Bkv={Bkv}")
226
+
227
+ key = key.expand((Bq, *key.size()[1:]))
228
+ value = value.expand((Bq, *value.size()[1:]))
229
+
230
+ _, post_mod_scores = _math_attention_inner(
231
+ query,
232
+ key,
233
+ value,
234
+ score_mod,
235
+ block_mask,
236
+ scale,
237
+ kernel_options,
238
+ score_mod_other_buffers,
239
+ mask_mod_other_buffers,
240
+ )
241
+
242
+ # Set fully masked rows' sumexp to 0.0
243
+ logsumexp = post_mod_scores.logsumexp(dim=-1)
244
+ masked_rows = torch.all(post_mod_scores == -float("inf"), dim=-1)
245
+ logsumexp = torch.where(masked_rows, -float("inf"), logsumexp)
246
+
247
+ post_mod_scores = torch._safe_softmax(post_mod_scores, dim=-1)
248
+
249
+ return post_mod_scores.to(query.dtype) @ value, logsumexp / math.log(2)
250
+
251
+
252
+ @flex_attention.py_impl(DispatchKey.CompositeExplicitAutograd)
253
+ def sdpa_dense(
254
+ query: torch.Tensor,
255
+ key: torch.Tensor,
256
+ value: torch.Tensor,
257
+ score_mod: Callable,
258
+ block_mask: Tuple,
259
+ scale: float,
260
+ kernel_options: Dict[str, Any],
261
+ score_mod_other_buffers: Tuple = (),
262
+ mask_mod_other_buffers: Tuple = (),
263
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
264
+ out, lse = math_attention(
265
+ query,
266
+ key,
267
+ value,
268
+ score_mod,
269
+ block_mask,
270
+ scale,
271
+ kernel_options,
272
+ score_mod_other_buffers,
273
+ mask_mod_other_buffers,
274
+ )
275
+ out = _permute_strides(out, query.stride())
276
+ return out, lse
277
+
278
+
279
+ def trace_flex_attention(
280
+ proxy_mode: ProxyTorchDispatchMode,
281
+ query: torch.Tensor,
282
+ key: torch.Tensor,
283
+ value: torch.Tensor,
284
+ score_mod: Callable,
285
+ block_mask: Tuple,
286
+ scale: float,
287
+ kernel_options: Dict[str, Any],
288
+ score_mod_other_buffers: Tuple = (),
289
+ mask_mod_other_buffers: Tuple = (),
290
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
291
+ """Traces the flex_attention operator with the given score_mod function and other_buffers.
292
+
293
+ Trace SDPA will call make_fx with "fake" example vals and then trace the score_mod function
294
+ This will produce a GraphModule that will be stored on the root tracer as "sdpa_score". We
295
+ access this graph module in inductor to inline the score_mod function to the triton template.
296
+ """
297
+ from torch._dynamo._trace_wrapped_higher_order_op import TransformGetItemToIndex
298
+
299
+ example_out = flex_attention(
300
+ query,
301
+ key,
302
+ value,
303
+ score_mod,
304
+ block_mask,
305
+ scale,
306
+ kernel_options,
307
+ score_mod_other_buffers,
308
+ mask_mod_other_buffers,
309
+ )
310
+ example_vals = [query.new_zeros((), requires_grad=query.requires_grad)] + [
311
+ query.new_zeros((), dtype=torch.int) for _ in range(4)
312
+ ]
313
+ mask_example_vals = [query.new_zeros((), dtype=torch.int) for _ in range(4)]
314
+ mask_mod = block_mask[-1]
315
+ with TransformGetItemToIndex():
316
+ score_graph = reenter_make_fx(score_mod)(
317
+ *example_vals, *score_mod_other_buffers
318
+ )
319
+ mask_graph = reenter_make_fx(mask_mod)(
320
+ *mask_example_vals, *mask_mod_other_buffers
321
+ )
322
+ assert isinstance(proxy_mode.tracer, torch.fx.Tracer)
323
+ block_mask = block_mask[:-1] + (mask_graph,)
324
+ qualname = proxy_mode.tracer.get_fresh_qualname("sdpa_score")
325
+ proxy_mode.tracer.root.register_module(qualname, score_graph)
326
+ mask_qualname = proxy_mode.tracer.get_fresh_qualname("sdpa_mask")
327
+ proxy_mode.tracer.root.register_module(mask_qualname, mask_graph)
328
+ node_args = (
329
+ query,
330
+ key,
331
+ value,
332
+ score_graph,
333
+ block_mask,
334
+ scale,
335
+ kernel_options,
336
+ score_mod_other_buffers,
337
+ mask_mod_other_buffers,
338
+ )
339
+ proxy_args = pytree.tree_map(proxy_mode.tracer.unwrap_proxy, node_args)
340
+ out_proxy = proxy_mode.tracer.create_proxy(
341
+ "call_function", flex_attention, proxy_args, {}
342
+ )
343
+ return track_tensor_tree(
344
+ example_out, out_proxy, constant=None, tracer=proxy_mode.tracer
345
+ )
346
+
347
+
348
+ @flex_attention.py_impl(ProxyTorchDispatchMode)
349
+ def flex_attention_proxy_torch_dispatch_mode(
350
+ mode: ProxyTorchDispatchMode,
351
+ query: torch.Tensor,
352
+ key: torch.Tensor,
353
+ value: torch.Tensor,
354
+ score_mod: Callable,
355
+ block_mask: Tuple,
356
+ scale: float,
357
+ kernel_options: Dict[str, Any],
358
+ score_mod_other_buffers: Tuple = (),
359
+ mask_mod_other_buffers: Tuple = (),
360
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
361
+ assert mode is not None, "Mode should always be enabled for python fallback key"
362
+ return trace_flex_attention(
363
+ mode,
364
+ query,
365
+ key,
366
+ value,
367
+ score_mod,
368
+ block_mask,
369
+ scale,
370
+ kernel_options,
371
+ score_mod_other_buffers,
372
+ mask_mod_other_buffers,
373
+ )
374
+
375
+
376
+ @flex_attention.py_functionalize_impl
377
+ def flex_attention_functionalize(
378
+ ctx: torch._subclasses.functional_tensor.BaseFunctionalizeAPI,
379
+ query: torch.Tensor,
380
+ key: torch.Tensor,
381
+ value: torch.Tensor,
382
+ score_mod: Callable,
383
+ block_mask: Tuple,
384
+ scale: float,
385
+ kernel_options: Dict[str, Any],
386
+ score_mod_other_buffers: Tuple = (),
387
+ mask_mod_other_buffers: Tuple = (),
388
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
389
+ """Defines the functionalization rules for the flex_attention operator.
390
+
391
+ Write now we are unwrapping each tensor and then redispatching to the next, however we want to
392
+ guard against any mutations in the score_mod function, to the other_buffers since those
393
+ are free variables.
394
+ """
395
+ from torch._dynamo._trace_wrapped_higher_order_op import TransformGetItemToIndex
396
+
397
+ query_unwrapped = ctx.unwrap_tensors(query)
398
+ key_unwrapped = ctx.unwrap_tensors(key)
399
+ value_unwrapped = ctx.unwrap_tensors(value)
400
+ block_mask_unwrapped = ctx.unwrap_tensors(block_mask)
401
+ score_mod_other_buffers_unwrapped = ctx.unwrap_tensors(score_mod_other_buffers)
402
+ mask_mod_other_buffers_unwrapped = ctx.unwrap_tensors(mask_mod_other_buffers)
403
+
404
+ # Appease the mypy overlords
405
+ assert isinstance(query_unwrapped, torch.Tensor)
406
+ assert isinstance(key_unwrapped, torch.Tensor)
407
+ assert isinstance(value_unwrapped, torch.Tensor)
408
+ assert isinstance(block_mask_unwrapped, tuple)
409
+ assert isinstance(score_mod_other_buffers_unwrapped, tuple)
410
+ assert isinstance(mask_mod_other_buffers_unwrapped, tuple)
411
+
412
+ example_vals = (
413
+ [query_unwrapped.new_zeros(())]
414
+ + [query_unwrapped.new_zeros((), dtype=torch.int) for _ in range(4)]
415
+ + list(score_mod_other_buffers_unwrapped)
416
+ )
417
+ with ctx.redispatch_to_next() as m:
418
+ functional_score_mod = ctx.functionalize(score_mod)
419
+ pre_dispatch = hasattr(ctx, "mode") and ctx.mode.pre_dispatch
420
+ with TransformGetItemToIndex():
421
+ mutates = _has_potential_branch_input_mutation(
422
+ functional_score_mod, example_vals, pre_dispatch
423
+ )
424
+ # The only care about mutations of existing buffers since we can't replay these.
425
+ # However, we can just error if anything is detected
426
+ if mutates:
427
+ raise UnsupportedAliasMutationException("Mutations detected in score_mod")
428
+
429
+ out = flex_attention(
430
+ query_unwrapped,
431
+ key_unwrapped,
432
+ value_unwrapped,
433
+ functional_score_mod,
434
+ block_mask_unwrapped,
435
+ scale,
436
+ kernel_options,
437
+ score_mod_other_buffers_unwrapped,
438
+ mask_mod_other_buffers_unwrapped,
439
+ )
440
+ return ctx.wrap_tensors(out) # type: ignore[return-value, arg-type]
441
+
442
+
443
+ @flex_attention.py_impl(FakeTensorMode)
444
+ def flex_attention_fake_tensor_mode(
445
+ mode: FakeTensorMode,
446
+ query: torch.Tensor,
447
+ key: torch.Tensor,
448
+ value: torch.Tensor,
449
+ score_mod: Callable,
450
+ block_mask: Tuple,
451
+ scale: float,
452
+ kernel_options: Dict[str, Any],
453
+ score_mod_other_buffers: Tuple = (),
454
+ mask_mod_other_buffers: Tuple = (),
455
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
456
+ with mode:
457
+ v_head_dim = value.size(-1)
458
+ batch_size, num_heads, seq_len_q, q_head_dim = query.shape
459
+ logsumexp = query.new_empty(
460
+ batch_size, num_heads, seq_len_q, dtype=torch.float32
461
+ )
462
+ out_shape = (batch_size, num_heads, seq_len_q, v_head_dim)
463
+ out = query.new_empty(out_shape)
464
+ out = _permute_strides(out, query.stride())
465
+ return out, logsumexp
466
+
467
+
468
+ # ---------------------------- Autograd Implementation ----------------------------
469
+ def create_fw_bw_graph(
470
+ score_mod: Callable,
471
+ index_values: Tuple[Tensor, Tensor, Tensor, Tensor, Tensor],
472
+ other_buffers: Tuple[Tensor, ...],
473
+ ) -> Tuple[Callable, Callable]:
474
+ # See Note:[HOP create fw_bw graph]
475
+
476
+ # All of these imports need to be here in order to avoid circular dependencies
477
+ from torch._dispatch.python import suspend_functionalization
478
+ from torch._functorch.aot_autograd import AOTConfig, create_joint
479
+ from torch._subclasses.fake_tensor import FakeTensor, FakeTensorMode
480
+ from torch._subclasses.functional_tensor import disable_functional_mode
481
+ from torch.fx.experimental.proxy_tensor import disable_proxy_modes_tracing
482
+
483
+ dummy_aot_config = AOTConfig(
484
+ fw_compiler=None, # type: ignore[arg-type]
485
+ bw_compiler=None, # type: ignore[arg-type]
486
+ partition_fn=None, # type: ignore[arg-type]
487
+ decompositions={},
488
+ num_params_buffers=0,
489
+ aot_id=0,
490
+ keep_inference_input_mutations=False,
491
+ )
492
+
493
+ with suspend_functionalization(), disable_functional_mode():
494
+ with disable_proxy_modes_tracing():
495
+
496
+ def _from_fun(
497
+ t: Union[Tensor, torch.SymInt, int]
498
+ ) -> Union[Tensor, torch.SymInt, int]:
499
+ if isinstance(t, torch.Tensor):
500
+ return torch.empty_strided(
501
+ t.size(),
502
+ t.stride(),
503
+ device=t.device,
504
+ dtype=t.dtype,
505
+ requires_grad=t.requires_grad,
506
+ )
507
+ return t
508
+
509
+ # If someone runs this hop under the default compiler backend ("eager")
510
+ # Then this path will be run with the actual user inputs. We convert them
511
+ # to fake tensors in order to not perform any actual compute.
512
+ from torch._guards import detect_fake_mode
513
+
514
+ fake_mode = detect_fake_mode(index_values)
515
+ if fake_mode is None:
516
+ fake_mode = FakeTensorMode(allow_non_fake_inputs=True)
517
+
518
+ with fake_mode:
519
+ unwrapped_score_mod_indexes = pytree.tree_map(_from_fun, index_values)
520
+ unwrapped_other_buffers = pytree.tree_map(_from_fun, other_buffers)
521
+
522
+ assert all(
523
+ isinstance(t, (FakeTensor, int, torch.SymInt))
524
+ for t in unwrapped_score_mod_indexes + unwrapped_other_buffers
525
+ )
526
+
527
+ example_flat_out = pytree.tree_map(
528
+ _from_fun,
529
+ score_mod(*unwrapped_score_mod_indexes, *unwrapped_other_buffers),
530
+ )
531
+ if not isinstance(example_flat_out, torch.Tensor):
532
+ raise RuntimeError(
533
+ "Expected output of score_mod to be a tensor."
534
+ f"Got type {type(example_flat_out)}."
535
+ )
536
+ example_grad = _from_fun(example_flat_out)
537
+
538
+ def joint_f(
539
+ score: Tensor,
540
+ b: Tensor,
541
+ h: Tensor,
542
+ m: Tensor,
543
+ n: Tensor,
544
+ example_grad: Tensor,
545
+ *other_buffers: Tuple[Tensor, ...],
546
+ ) -> Tuple[Tensor, ...]:
547
+ def fw_with_masks(
548
+ *args: Tuple[Tensor, ...]
549
+ ) -> Tuple[Tuple[Tensor], Tuple[bool]]:
550
+ fw_out = score_mod(*args)
551
+ out_requires_grad = fw_out.requires_grad
552
+ return ((fw_out,), (out_requires_grad,))
553
+
554
+ joint = create_joint(fw_with_masks, aot_config=dummy_aot_config)
555
+ args = [score, b, h, m, n] + list(other_buffers)
556
+ optional_grad = [example_grad] if example_grad.requires_grad else []
557
+ _, grads = joint(args, optional_grad)
558
+
559
+ return grads
560
+
561
+ joint_graph = make_fx(joint_f)(
562
+ *unwrapped_score_mod_indexes, example_grad, *unwrapped_other_buffers
563
+ )
564
+ return score_mod, joint_graph
565
+
566
+
567
+ class FlexAttentionAutogradOp(torch.autograd.Function):
568
+ @staticmethod
569
+ def forward(
570
+ ctx: Any,
571
+ query: Tensor,
572
+ key: Tensor,
573
+ value: Tensor,
574
+ fw_graph: Callable,
575
+ joint_graph: Callable,
576
+ block_mask: Tuple[Any, ...],
577
+ scale: float,
578
+ kernel_options: Dict[str, Any],
579
+ mask_mod_other_buffers: Tuple[Any, ...],
580
+ *score_mod_other_buffers: Tuple[Any, ...],
581
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
582
+ any_buffer_requires_grad = any(
583
+ buffer.requires_grad
584
+ for buffer in mask_mod_other_buffers
585
+ if isinstance(buffer, torch.Tensor)
586
+ )
587
+ assert (
588
+ not any_buffer_requires_grad
589
+ ), "Captured buffers from mask mod that require grad are not supported."
590
+ ctx._fw_graph = fw_graph
591
+ ctx._joint_graph = joint_graph
592
+ ctx._mask_graph = block_mask[-1]
593
+ ctx.scale = scale
594
+ ctx.kernel_options = kernel_options
595
+ ctx._score_mod_other_buffers_len = len(score_mod_other_buffers)
596
+ with torch._C._AutoDispatchBelowAutograd():
597
+ out, logsumexp = flex_attention(
598
+ query,
599
+ key,
600
+ value,
601
+ fw_graph,
602
+ block_mask,
603
+ scale,
604
+ kernel_options,
605
+ score_mod_other_buffers,
606
+ mask_mod_other_buffers,
607
+ )
608
+
609
+ save_tensors_and_symints_for_backward(
610
+ ctx,
611
+ (
612
+ query,
613
+ key,
614
+ value,
615
+ out,
616
+ logsumexp,
617
+ *block_mask[:-1],
618
+ *score_mod_other_buffers,
619
+ *mask_mod_other_buffers,
620
+ ),
621
+ )
622
+ return out, logsumexp
623
+
624
+ @staticmethod
625
+ def backward(ctx: Any, grad_out: Tensor, grad_logsumexp: Tensor) -> Tuple[Optional[Tensor], ...]: # type: ignore[override]
626
+ fw_args = saved_tensors_and_symints(ctx)
627
+ (
628
+ query,
629
+ key,
630
+ value,
631
+ out,
632
+ logsumexp,
633
+ query_lengths,
634
+ kv_lengths,
635
+ kv_num_blocks,
636
+ kv_indices,
637
+ full_kv_num_blocks,
638
+ full_kv_indices,
639
+ q_num_blocks,
640
+ q_indices,
641
+ full_q_num_blocks,
642
+ full_q_indices,
643
+ Q_BLOCK_SIZE,
644
+ KV_BLOCK_SIZE,
645
+ *other_buffers,
646
+ ) = fw_args
647
+ fw_graph = ctx._fw_graph
648
+ joint_graph = ctx._joint_graph
649
+ mask_graph = ctx._mask_graph
650
+ scale = ctx.scale
651
+ kernel_options = ctx.kernel_options
652
+ score_mod_other_buffers = tuple(
653
+ other_buffers[: ctx._score_mod_other_buffers_len]
654
+ )
655
+ mask_mod_other_buffers = tuple(
656
+ other_buffers[ctx._score_mod_other_buffers_len :]
657
+ )
658
+ # We have asserted that mask_mod_other_buffers do not require grad,
659
+ # but score_mod_other_buffers can require grad.
660
+ none_grads = [None] * 6
661
+ (
662
+ grad_query,
663
+ grad_key,
664
+ grad_value,
665
+ grad_score_mod_captured,
666
+ ) = flex_attention_backward(
667
+ query,
668
+ key,
669
+ value,
670
+ out,
671
+ logsumexp,
672
+ grad_out,
673
+ grad_logsumexp,
674
+ fw_graph,
675
+ joint_graph,
676
+ (
677
+ query_lengths,
678
+ kv_lengths,
679
+ kv_num_blocks,
680
+ kv_indices,
681
+ full_kv_num_blocks,
682
+ full_kv_indices,
683
+ q_num_blocks,
684
+ q_indices,
685
+ full_q_num_blocks,
686
+ full_q_indices,
687
+ Q_BLOCK_SIZE,
688
+ KV_BLOCK_SIZE,
689
+ mask_graph,
690
+ ),
691
+ scale,
692
+ kernel_options,
693
+ score_mod_other_buffers,
694
+ mask_mod_other_buffers,
695
+ )
696
+ return grad_query, grad_key, grad_value, *none_grads, *grad_score_mod_captured
697
+
698
+
699
+ @flex_attention.py_impl(DispatchKey.Autograd)
700
+ def flex_attention_autograd(
701
+ query: torch.Tensor,
702
+ key: torch.Tensor,
703
+ value: torch.Tensor,
704
+ score_mod: Callable,
705
+ block_mask: Tuple,
706
+ scale: float,
707
+ kernel_options: Dict[str, Any],
708
+ score_mod_other_buffers: Tuple[Tensor, ...] = (),
709
+ mask_mod_other_buffers: Tuple[Tensor, ...] = (),
710
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
711
+ from torch._dynamo._trace_wrapped_higher_order_op import TransformGetItemToIndex
712
+
713
+ with TransformGetItemToIndex():
714
+ input_requires_grad = any(
715
+ isinstance(t, torch.Tensor) and t.requires_grad
716
+ for t in (query, key, value, *score_mod_other_buffers)
717
+ )
718
+ if torch.is_grad_enabled() and input_requires_grad:
719
+ example_vals = (
720
+ query.new_zeros((), requires_grad=input_requires_grad),
721
+ query.new_zeros((), dtype=torch.int),
722
+ query.new_zeros((), dtype=torch.int),
723
+ query.new_zeros((), dtype=torch.int),
724
+ query.new_zeros((), dtype=torch.int),
725
+ )
726
+ fw_graph, bw_graph = create_fw_bw_graph(
727
+ score_mod, example_vals, score_mod_other_buffers
728
+ )
729
+ else:
730
+ fw_graph, bw_graph = score_mod, None
731
+ out, logsumexp = FlexAttentionAutogradOp.apply(
732
+ query,
733
+ key,
734
+ value,
735
+ fw_graph,
736
+ bw_graph,
737
+ block_mask,
738
+ scale,
739
+ kernel_options,
740
+ mask_mod_other_buffers,
741
+ *score_mod_other_buffers,
742
+ )
743
+ return out, logsumexp
744
+
745
+
746
+ # ---------------------------- Backward HOP Implementation ----------------------------
747
+
748
+
749
+ @flex_attention_backward.py_impl(DispatchKey.CompositeExplicitAutograd)
750
+ def sdpa_dense_backward(
751
+ query: torch.Tensor,
752
+ key: torch.Tensor,
753
+ value: torch.Tensor,
754
+ out: torch.Tensor,
755
+ logsumexp: torch.Tensor,
756
+ grad_out: torch.Tensor,
757
+ grad_logsumexp: torch.Tensor,
758
+ fw_graph: Callable, # GraphModule type hint?
759
+ joint_graph: Callable,
760
+ block_mask: Tuple,
761
+ scale: float,
762
+ kernel_options: Dict[str, Any],
763
+ score_mod_other_buffers: Tuple,
764
+ mask_mod_other_buffers: Tuple,
765
+ ) -> Tuple[
766
+ torch.Tensor, torch.Tensor, torch.Tensor, Tuple[Optional[torch.Tensor], ...]
767
+ ]:
768
+ from torch._dynamo._trace_wrapped_higher_order_op import TransformGetItemToIndex
769
+
770
+ # Get outputs before calling repeat interleave
771
+ actual_grad_query = torch.empty_like(query)
772
+ actual_grad_key = torch.empty_like(key)
773
+ actual_grad_value = torch.empty_like(value)
774
+
775
+ def _maybe_new_buffer(
776
+ buffer: Union[torch.Tensor, torch.SymInt, int]
777
+ ) -> Optional[Union[torch.Tensor, torch.SymInt, int]]:
778
+ if isinstance(buffer, torch.Tensor):
779
+ return torch.empty_like(buffer) if buffer.requires_grad else None
780
+ return buffer
781
+
782
+ actual_grad_score_mod_captured = [
783
+ _maybe_new_buffer(buffer) for buffer in score_mod_other_buffers
784
+ ]
785
+
786
+ Bq, Bkv = query.size(0), key.size(0)
787
+ if not ((Bq == Bkv) or (Bq > 1 and Bkv == 1)):
788
+ raise RuntimeError(f"Bq and Bkv must broadcast. Got Bq={Bq} and Bkv={Bkv}")
789
+
790
+ key = key.expand((Bq, *key.size()[1:]))
791
+ value = value.expand((Bq, *value.size()[1:]))
792
+
793
+ G = query.size(1) // key.size(1)
794
+ key = torch.repeat_interleave(key, G, dim=1)
795
+ value = torch.repeat_interleave(value, G, dim=1)
796
+
797
+ # We're undoing the log -> log2 change of base in the forwards
798
+ logsumexp = logsumexp * math.log(2)
799
+ # The backwards formula for the log -> log2 change of base in the forwards
800
+ grad_logsumexp = grad_logsumexp / math.log(2)
801
+ scores, post_mod_scores = _math_attention_inner(
802
+ query,
803
+ key,
804
+ value,
805
+ fw_graph,
806
+ block_mask,
807
+ scale,
808
+ kernel_options,
809
+ score_mod_other_buffers,
810
+ mask_mod_other_buffers,
811
+ )
812
+ masked_out_rows = logsumexp == -float("inf")
813
+ softmax_scores = torch.exp(post_mod_scores - logsumexp.unsqueeze(-1))
814
+ softmax_scores = torch.where(masked_out_rows.unsqueeze(-1), 0, softmax_scores)
815
+
816
+ grad_value = softmax_scores.to(query.dtype).transpose(-2, -1) @ grad_out
817
+
818
+ grad_softmax_scores = grad_out @ value.transpose(-2, -1)
819
+
820
+ sum_scores = torch.sum(out * grad_out, -1, keepdim=True)
821
+ grad_score_mod = softmax_scores * (
822
+ grad_softmax_scores - sum_scores + grad_logsumexp.unsqueeze(-1)
823
+ )
824
+
825
+ b = torch.arange(0, scores.size(0), device=scores.device)
826
+ h = torch.arange(0, scores.size(1), device=scores.device)
827
+ m = torch.arange(0, scores.size(2), device=scores.device)
828
+ n = torch.arange(0, scores.size(3), device=scores.device)
829
+
830
+ mask_graph = block_mask[-1]
831
+ # Gradient of the inline score_mod function, with respect to the scores
832
+ captured_buffers_in_dim = (None,) * len(score_mod_other_buffers)
833
+ out_dims = [0, None, None, None, None] + [None] * len(score_mod_other_buffers)
834
+ from torch.nn.attention.flex_attention import _vmap_for_bhqkv
835
+
836
+ # inputs are [score, b, h, q_idx, kv_idx, gradOut, ...]
837
+ # score and gradOut are "fully" batched
838
+ joint_score_mod = _vmap_for_bhqkv(
839
+ joint_graph,
840
+ prefix=(0,),
841
+ suffix=(0,) + captured_buffers_in_dim,
842
+ out_dims=out_dims,
843
+ )
844
+ with TransformGetItemToIndex():
845
+ grad_scores, _, _, _, _, *grad_score_mod_captured = joint_score_mod(
846
+ scores, b, h, m, n, grad_score_mod, *score_mod_other_buffers
847
+ )
848
+ grad_scores = grad_scores * scale
849
+ grad_scores = grad_scores.to(query.dtype)
850
+
851
+ mask_mod = _vmap_for_bhqkv(
852
+ mask_graph, prefix=(), suffix=(None,) * len(mask_mod_other_buffers)
853
+ )
854
+ with TransformGetItemToIndex():
855
+ mask_scores = mask_mod(b, h, m, n, *mask_mod_other_buffers)
856
+ grad_scores = torch.where(
857
+ mask_scores, grad_scores, torch.tensor(0, dtype=query.dtype)
858
+ )
859
+
860
+ grad_query = grad_scores @ key
861
+ grad_key = grad_scores.transpose(-2, -1) @ query
862
+
863
+ # Reduce DK, DV along broadcasted heads.
864
+ grad_key = grad_key.view(
865
+ grad_key.size(0), -1, G, grad_key.size(-2), grad_key.size(-1)
866
+ )
867
+ grad_value = grad_value.view(
868
+ grad_value.size(0), -1, G, grad_value.size(-2), grad_value.size(-1)
869
+ )
870
+
871
+ grad_key = torch.sum(grad_key, 2, keepdim=False)
872
+ grad_value = torch.sum(grad_value, 2, keepdim=False)
873
+
874
+ if Bq != Bkv:
875
+ assert (
876
+ Bq > 1 and Bkv == 1
877
+ ), f"Bq and Bkv must broadcast. Got Bq={Bq} and Bkv={Bkv}"
878
+
879
+ # Reduce DK, DV along broadcasted batches.
880
+ grad_key = torch.sum(grad_key, 0, keepdim=True)
881
+ grad_value = torch.sum(grad_value, 0, keepdim=True)
882
+
883
+ actual_grad_query.copy_(grad_query)
884
+ actual_grad_key.copy_(grad_key)
885
+ actual_grad_value.copy_(grad_value)
886
+ score_mod_other_buffer_grads = [
887
+ actual_grad.copy_(grad) if isinstance(actual_grad, torch.Tensor) else None
888
+ for actual_grad, grad in zip(
889
+ actual_grad_score_mod_captured, grad_score_mod_captured
890
+ )
891
+ ]
892
+
893
+ return (
894
+ actual_grad_query,
895
+ actual_grad_key,
896
+ actual_grad_value,
897
+ tuple(score_mod_other_buffer_grads),
898
+ )
899
+
900
+
901
+ def trace_flex_attention_backward(
902
+ proxy_mode: ProxyTorchDispatchMode,
903
+ query: torch.Tensor,
904
+ key: torch.Tensor,
905
+ value: torch.Tensor,
906
+ out: torch.Tensor,
907
+ logsumexp: torch.Tensor,
908
+ grad_out: torch.Tensor,
909
+ grad_logsumexp: torch.Tensor,
910
+ fw_graph: Union[Callable, GraphModule],
911
+ joint_graph: GraphModule,
912
+ block_mask: Tuple,
913
+ scale: float,
914
+ kernel_options: Dict[str, Any],
915
+ score_mod_other_buffers: Tuple = (),
916
+ mask_mod_other_buffers: Tuple = (),
917
+ ) -> Tuple[
918
+ torch.Tensor, torch.Tensor, torch.Tensor, Tuple[Optional[torch.Tensor], ...]
919
+ ]:
920
+ """We already have the forward graph and joint graph from the forward pass, so we create a proxy attach both graphs"""
921
+ from torch._dynamo._trace_wrapped_higher_order_op import TransformGetItemToIndex
922
+
923
+ example_out = flex_attention_backward(
924
+ query,
925
+ key,
926
+ value,
927
+ out,
928
+ logsumexp,
929
+ grad_out,
930
+ grad_logsumexp,
931
+ fw_graph,
932
+ joint_graph,
933
+ block_mask,
934
+ scale,
935
+ kernel_options,
936
+ score_mod_other_buffers,
937
+ mask_mod_other_buffers,
938
+ )
939
+
940
+ requires_grad = any(pytree.tree_map(lambda x: x.requires_grad, (query, key)))
941
+ fw_example_vals = [query.new_zeros((), requires_grad=requires_grad)] + [
942
+ query.new_zeros((), dtype=torch.int) for _ in range(4)
943
+ ]
944
+ bw_example_vals = fw_example_vals + [query.new_zeros(())]
945
+ mask_example_vals = [query.new_zeros((), dtype=torch.int) for _ in range(4)]
946
+ mask_graph = block_mask[-1]
947
+ with TransformGetItemToIndex():
948
+ fw_graph = reenter_make_fx(fw_graph)(*fw_example_vals, *score_mod_other_buffers)
949
+ joint_graph = reenter_make_fx(joint_graph)(
950
+ *bw_example_vals, *score_mod_other_buffers
951
+ )
952
+ mask_graph = reenter_make_fx(mask_graph)(
953
+ *mask_example_vals, *mask_mod_other_buffers
954
+ )
955
+ assert isinstance(proxy_mode.tracer, torch.fx.Tracer)
956
+ block_mask = block_mask[:-1] + (mask_graph,)
957
+
958
+ qualname = proxy_mode.tracer.get_fresh_qualname("fw_graph")
959
+ proxy_mode.tracer.root.register_module(qualname, fw_graph) # type: ignore[arg-type]
960
+ qualname = proxy_mode.tracer.get_fresh_qualname("joint_graph")
961
+ proxy_mode.tracer.root.register_module(qualname, joint_graph)
962
+ qualname = proxy_mode.tracer.get_fresh_qualname("mask_graph")
963
+ proxy_mode.tracer.root.register_module(qualname, mask_graph)
964
+
965
+ node_args = (
966
+ query,
967
+ key,
968
+ value,
969
+ out,
970
+ logsumexp,
971
+ grad_out,
972
+ grad_logsumexp,
973
+ fw_graph,
974
+ joint_graph,
975
+ block_mask,
976
+ scale,
977
+ kernel_options,
978
+ score_mod_other_buffers,
979
+ mask_mod_other_buffers,
980
+ )
981
+ proxy_args = pytree.tree_map(proxy_mode.tracer.unwrap_proxy, node_args)
982
+ out_proxy = proxy_mode.tracer.create_proxy(
983
+ "call_function",
984
+ flex_attention_backward,
985
+ proxy_args,
986
+ {},
987
+ name="flex_attention_backward",
988
+ )
989
+ return track_tensor_tree(
990
+ example_out, out_proxy, constant=None, tracer=proxy_mode.tracer
991
+ )
992
+
993
+
994
+ @flex_attention_backward.py_impl(ProxyTorchDispatchMode)
995
+ def flex_attention_backward_proxy_torch_dispatch_mode(
996
+ mode: ProxyTorchDispatchMode,
997
+ query: torch.Tensor,
998
+ key: torch.Tensor,
999
+ value: torch.Tensor,
1000
+ out: torch.Tensor,
1001
+ logsumexp: torch.Tensor,
1002
+ grad_out: torch.Tensor,
1003
+ grad_logsumexp: torch.Tensor,
1004
+ fw_graph: Union[Callable, GraphModule],
1005
+ joint_graph: GraphModule,
1006
+ block_mask: Tuple,
1007
+ scale: float,
1008
+ kernel_options: Dict[str, Any],
1009
+ score_mod_other_buffers: Tuple = (),
1010
+ mask_mod_other_buffers: Tuple = (),
1011
+ ) -> Tuple[
1012
+ torch.Tensor, torch.Tensor, torch.Tensor, Tuple[Optional[torch.Tensor], ...]
1013
+ ]:
1014
+ assert mode is not None, "Mode should always be enabled for python fallback key"
1015
+ return trace_flex_attention_backward(
1016
+ mode,
1017
+ query,
1018
+ key,
1019
+ value,
1020
+ out,
1021
+ logsumexp,
1022
+ grad_out,
1023
+ grad_logsumexp,
1024
+ fw_graph,
1025
+ joint_graph,
1026
+ block_mask,
1027
+ scale,
1028
+ kernel_options,
1029
+ score_mod_other_buffers,
1030
+ mask_mod_other_buffers,
1031
+ )
1032
+
1033
+
1034
+ @flex_attention_backward.py_functionalize_impl
1035
+ def flex_attention_backward_functionalize(
1036
+ ctx: torch._subclasses.functional_tensor.BaseFunctionalizeAPI,
1037
+ query: torch.Tensor,
1038
+ key: torch.Tensor,
1039
+ value: torch.Tensor,
1040
+ out: torch.Tensor,
1041
+ logsumexp: torch.Tensor,
1042
+ grad_out: torch.Tensor,
1043
+ grad_logsumexp: torch.Tensor,
1044
+ fw_graph: Union[Callable, GraphModule],
1045
+ joint_graph: GraphModule,
1046
+ block_mask: Tuple,
1047
+ scale: float,
1048
+ kernel_options: Dict[str, Any],
1049
+ score_mod_other_buffers: Tuple = (),
1050
+ mask_mod_other_buffers: Tuple = (),
1051
+ ) -> Tuple[
1052
+ torch.Tensor, torch.Tensor, torch.Tensor, Tuple[Optional[torch.Tensor], ...]
1053
+ ]:
1054
+ """Defines the functionalization rules for the flex_attention operator.
1055
+
1056
+ Write now we are unwrapping each tensor and then redispatching to the next,
1057
+ since we know that the forward score mod function is assured to be free of mutations
1058
+ to the other_buffers, we skip that mutate check and go straight to redispatching.
1059
+ """
1060
+ query_unwrapped = ctx.unwrap_tensors(query)
1061
+ key_unwrapped = ctx.unwrap_tensors(key)
1062
+ value_unwrapped = ctx.unwrap_tensors(value)
1063
+ out_unwrapped = ctx.unwrap_tensors(out)
1064
+ logsumexp_unwrapped = ctx.unwrap_tensors(logsumexp)
1065
+ grad_out_unwrapped = ctx.unwrap_tensors(grad_out)
1066
+ grad_logsumexp_unwrapped = ctx.unwrap_tensors(grad_logsumexp)
1067
+ block_mask_unwrapped = ctx.unwrap_tensors(block_mask)
1068
+ score_mod_other_buffers_unwrapped = ctx.unwrap_tensors(score_mod_other_buffers)
1069
+ mask_mod_other_buffers_unwrapped = ctx.unwrap_tensors(mask_mod_other_buffers)
1070
+
1071
+ # Appease the mypy overlords
1072
+ assert isinstance(query_unwrapped, torch.Tensor)
1073
+ assert isinstance(key_unwrapped, torch.Tensor)
1074
+ assert isinstance(value_unwrapped, torch.Tensor)
1075
+ assert isinstance(out_unwrapped, torch.Tensor)
1076
+ assert isinstance(logsumexp_unwrapped, torch.Tensor)
1077
+ assert isinstance(grad_out_unwrapped, torch.Tensor)
1078
+ assert isinstance(grad_logsumexp_unwrapped, torch.Tensor)
1079
+ assert isinstance(block_mask_unwrapped, tuple)
1080
+ assert isinstance(score_mod_other_buffers_unwrapped, tuple)
1081
+ assert isinstance(mask_mod_other_buffers_unwrapped, tuple)
1082
+
1083
+ with ctx.redispatch_to_next() as m:
1084
+ functional_fw_graph = ctx.functionalize(fw_graph)
1085
+ functional_joint_graph = ctx.functionalize(joint_graph)
1086
+
1087
+ (
1088
+ grad_query,
1089
+ grad_key,
1090
+ grad_value,
1091
+ grad_score_mod_captured,
1092
+ ) = flex_attention_backward(
1093
+ query_unwrapped,
1094
+ key_unwrapped,
1095
+ value_unwrapped,
1096
+ out_unwrapped,
1097
+ logsumexp_unwrapped,
1098
+ grad_out_unwrapped,
1099
+ grad_logsumexp_unwrapped,
1100
+ functional_fw_graph, # type: ignore[arg-type]
1101
+ functional_joint_graph, # type: ignore[arg-type]
1102
+ block_mask_unwrapped,
1103
+ scale,
1104
+ kernel_options,
1105
+ score_mod_other_buffers_unwrapped,
1106
+ mask_mod_other_buffers_unwrapped,
1107
+ )
1108
+
1109
+ return ctx.wrap_tensors((grad_query, grad_key, grad_value, grad_score_mod_captured)) # type: ignore[return-value,arg-type]
1110
+
1111
+
1112
+ @flex_attention_backward.py_impl(FakeTensorMode)
1113
+ def flex_attention_backward_fake_tensor_mode(
1114
+ mode: FakeTensorMode,
1115
+ query: torch.Tensor,
1116
+ key: torch.Tensor,
1117
+ value: torch.Tensor,
1118
+ out: torch.Tensor,
1119
+ logsumexp: torch.Tensor,
1120
+ grad_out: torch.Tensor,
1121
+ grad_logsumexp: torch.Tensor,
1122
+ fw_graph: Union[Callable, GraphModule],
1123
+ joint_graph: GraphModule,
1124
+ block_mask: Tuple,
1125
+ scale: float,
1126
+ kernel_options: Dict[str, Any],
1127
+ score_mod_other_buffers: Tuple = (),
1128
+ mask_mod_other_buffers: Tuple = (),
1129
+ ) -> Tuple[
1130
+ torch.Tensor, torch.Tensor, torch.Tensor, Tuple[Optional[torch.Tensor], ...]
1131
+ ]:
1132
+ with mode:
1133
+ grad_query = torch.empty_like(query)
1134
+ grad_key = torch.empty_like(key)
1135
+ grad_value = torch.empty_like(value)
1136
+ grad_score_mod_captured = tuple(
1137
+ [
1138
+ torch.empty_like(buffer)
1139
+ if isinstance(buffer, torch.Tensor) and buffer.requires_grad
1140
+ else None
1141
+ for buffer in score_mod_other_buffers
1142
+ ]
1143
+ )
1144
+ return grad_query, grad_key, grad_value, grad_score_mod_captured
1145
+
1146
+
1147
+ flex_attention_backward.py_impl(DispatchKey.Autograd)(
1148
+ autograd_not_implemented(flex_attention_backward, deferred_error=True)
1149
+ )
lib/python3.12/site-packages/torch/_higher_order_ops/hints_wrap.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ import torch
3
+ import torch.utils._pytree as pytree
4
+ from torch._C import DispatchKey
5
+ from torch._higher_order_ops.utils import (
6
+ _has_potential_branch_input_alias,
7
+ _has_potential_branch_input_mutation,
8
+ autograd_not_implemented,
9
+ reenter_make_fx,
10
+ unique_graph_id,
11
+ UnsupportedAliasMutationException,
12
+ )
13
+ from torch._ops import HigherOrderOperator
14
+ from torch._subclasses.fake_tensor import FakeTensorMode
15
+ from torch.fx.experimental.proxy_tensor import ProxyTorchDispatchMode, track_tensor_tree
16
+
17
+
18
+ # used for wrapping a function/op with context hints
19
+ class HintsWrapper(HigherOrderOperator):
20
+ def __init__(self):
21
+ super().__init__("hints_wrapper")
22
+
23
+ def __call__(self, body_fn, args, kwargs, hints):
24
+ r"""
25
+ Call implementation of hints_wrapper
26
+
27
+ Args:
28
+ body_fn (Callable): A callable function that is within the scope
29
+ that is being traced.
30
+
31
+ args (Tuple of torch.Tensor/int/float/bool): A tuple of inputs to
32
+ body_fn.
33
+
34
+ kwargs (dict): Keyword argument to the body_fn.
35
+
36
+ hints (dict): A dict of context hints which could be passed to
37
+ backend compiler.
38
+ """
39
+ if not isinstance(args, tuple):
40
+ raise RuntimeError(f"args must be a tuple, got {type(args)}")
41
+
42
+ if not all(isinstance(t, (torch.Tensor, int, float, bool)) for t in args):
43
+ raise RuntimeError(
44
+ "args must be a tuple of tensors, ints, floats, or bools, got "
45
+ f"{args}"
46
+ )
47
+
48
+ if not isinstance(kwargs, dict):
49
+ raise RuntimeError(f"kwargs must be a dict, got {type(kwargs)}")
50
+
51
+ if len(kwargs) > 0:
52
+ raise RuntimeError(
53
+ f"kwargs except for hints are not supported, got {kwargs}"
54
+ )
55
+
56
+ if not isinstance(hints, dict):
57
+ raise RuntimeError(f"hints must be a dict, got {type(hints)}")
58
+
59
+ for k, v in hints.items():
60
+ if not isinstance(k, str):
61
+ raise RuntimeError(f"hints key must be a str, got {k}.")
62
+
63
+ if not isinstance(v, (int, float, bool, str)):
64
+ raise RuntimeError(
65
+ "hints must be a dict containing int, float, bool or str "
66
+ f"value, got value {v} for key {k}."
67
+ )
68
+
69
+ return super().__call__(body_fn, args, kwargs, hints)
70
+
71
+
72
+ hints_wrapper = HintsWrapper()
73
+
74
+
75
+ @hints_wrapper.py_impl(DispatchKey.CompositeExplicitAutograd)
76
+ def hints_wrapper_dense(body_fn, args, kwargs, hints):
77
+ return body_fn(*args, **kwargs)
78
+
79
+
80
+ hints_wrapper.py_impl(DispatchKey.Autograd)(
81
+ autograd_not_implemented(hints_wrapper, deferred_error=True)
82
+ )
83
+
84
+
85
+ @hints_wrapper.py_impl(FakeTensorMode)
86
+ def hints_wrapper_fake_tensor_mode(mode, body_func, args, kwargs, hints):
87
+ flat_args = pytree.tree_leaves(args)
88
+ with mode:
89
+ return body_func(*flat_args, **kwargs)
90
+
91
+
92
+ @hints_wrapper.py_functionalize_impl
93
+ def hints_wrapper_functionalize(ctx, body_fn, args, kwargs, hints):
94
+ unwrapped_args = ctx.unwrap_tensors(args)
95
+ unwrapped_kwargs = ctx.unwrap_tensors(kwargs)
96
+ unwrapped_hints = ctx.unwrap_tensors(hints)
97
+ with ctx.redispatch_to_next():
98
+ functional_body_fn = ctx.functionalize(body_fn)
99
+ pre_dispatch = hasattr(ctx, "mode") and ctx.mode.pre_dispatch
100
+ if _has_potential_branch_input_mutation(
101
+ functional_body_fn, unwrapped_args, pre_dispatch=pre_dispatch
102
+ ):
103
+ raise UnsupportedAliasMutationException(
104
+ "body_fn of hints_wrapper might be modifying the input!"
105
+ )
106
+ if _has_potential_branch_input_alias(
107
+ functional_body_fn, unwrapped_args, pre_dispatch=pre_dispatch
108
+ ):
109
+ raise UnsupportedAliasMutationException(
110
+ "body_fn of hints_wrapper might be aliasing the input!"
111
+ )
112
+ outputs = hints_wrapper(
113
+ functional_body_fn,
114
+ unwrapped_args,
115
+ unwrapped_kwargs,
116
+ unwrapped_hints,
117
+ )
118
+ return ctx.wrap_tensors(outputs)
119
+
120
+
121
+ def trace_hints_wrapper(proxy_mode, hints_wrapper, body_fn, args, kwargs, hints):
122
+ flat_args = tuple(pytree.tree_leaves(args))
123
+ body_graph = reenter_make_fx(body_fn)(*flat_args, **kwargs)
124
+
125
+ _, body_graph_name = unique_graph_id(proxy_mode, prefix="hints_wrapper_body_graph")
126
+ proxy_mode.tracer.root.register_module(body_graph_name, body_graph)
127
+
128
+ new_args: tuple = (body_graph, flat_args, {})
129
+ # merge hints into kwargs
130
+ new_kwargs = {}
131
+ new_kwargs["hints"] = hints
132
+
133
+ proxy_args = pytree.tree_map(proxy_mode.tracer.unwrap_proxy, new_args)
134
+ proxy_kwargs = pytree.tree_map(proxy_mode.tracer.unwrap_proxy, new_kwargs)
135
+
136
+ out_proxy = proxy_mode.tracer.create_proxy(
137
+ "call_function", hints_wrapper, proxy_args, proxy_kwargs, name="hints_wrapper"
138
+ )
139
+
140
+ out = body_fn(*flat_args, **kwargs)
141
+ return track_tensor_tree(out, out_proxy, constant=None, tracer=proxy_mode.tracer)
142
+
143
+
144
+ @hints_wrapper.py_impl(ProxyTorchDispatchMode)
145
+ def inner(proxy_mode, body_fn, args, kwargs, hints):
146
+ if proxy_mode.enable_tracing:
147
+ return trace_hints_wrapper(
148
+ proxy_mode, hints_wrapper, body_fn, args, kwargs, hints
149
+ )
150
+ else:
151
+ return hints_wrapper(body_fn, args, kwargs, hints)
lib/python3.12/site-packages/torch/_higher_order_ops/invoke_subgraph.py ADDED
@@ -0,0 +1,308 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-decorators
2
+ # mypy: allow-untyped-defs
3
+
4
+
5
+ from typing import List, Optional, Tuple, Union
6
+
7
+ import torch
8
+ import torch.utils._pytree as pytree
9
+ from torch._C import DispatchKey
10
+ from torch._dispatch.python import suspend_functionalization
11
+ from torch._higher_order_ops.utils import (
12
+ _from_fun,
13
+ _maybe_reenter_make_fx,
14
+ clone_outputs_aliasing_inputs,
15
+ get_dummy_aot_autograd_config,
16
+ prepare_fw_with_masks,
17
+ reenter_make_fx,
18
+ save_tensors_and_symints_for_backward,
19
+ saved_tensors_and_symints,
20
+ )
21
+ from torch._ops import HigherOrderOperator
22
+ from torch._subclasses import FakeTensorMode
23
+ from torch._subclasses.functional_tensor import disable_functional_mode
24
+ from torch.fx.experimental.proxy_tensor import (
25
+ disable_proxy_modes_tracing,
26
+ ProxyTorchDispatchMode,
27
+ track_tensor_tree,
28
+ )
29
+ from torch.fx.graph_module import GraphModule
30
+
31
+
32
+ invoke_subgraph_counter = 0
33
+
34
+
35
+ class InvokeSubgraphHOP(HigherOrderOperator):
36
+ def __init__(self) -> None:
37
+ super().__init__("invoke_subgraph")
38
+
39
+ # identifier is setup by upper part of the stack. This helps us in
40
+ # identifying two invoke_subgraph calls have same subgraph.
41
+ def __call__(
42
+ self,
43
+ subgraph: GraphModule,
44
+ identifier: Optional[str],
45
+ operands: Union[
46
+ List[Union[torch.Tensor, int, torch.SymInt]],
47
+ Tuple[Union[torch.Tensor, int, torch.SymInt]],
48
+ ],
49
+ ):
50
+ assert identifier is None or isinstance(
51
+ identifier, str
52
+ ), "identifier must be a None or a string"
53
+
54
+ assert isinstance(
55
+ operands, (list, tuple)
56
+ ), f"invoke_subgraph operands must be a list or tuple of tensors/ints/SymInts {operands}"
57
+ assert all(
58
+ isinstance(o, (torch.Tensor, int, torch.SymInt)) for o in operands
59
+ ), f"invoke_subgraph operands must be a list of tensors/ints/SymInts {operands}"
60
+
61
+ return super().__call__(subgraph, identifier, operands)
62
+
63
+
64
+ invoke_subgraph = InvokeSubgraphHOP()
65
+
66
+
67
+ def invoke_subgraph_placeholder(subgraph, *args, **kwargs):
68
+ # Just a placeholder for Dynamo to replace with invoke_subgraph
69
+ return subgraph(*args, **kwargs)
70
+
71
+
72
+ def mark_compile_region(fn=None):
73
+ """
74
+ This wrapper instructs torch.compile to compile the wrapped region once and
75
+ reuse the compiled artifact, instead of the usual way of aggressively
76
+ inlining the function.
77
+
78
+ Under the hood, it tells TorchDynamo to use InvokeSubgraph HOP for the
79
+ region. For PyTorch eager, this is a no-op.
80
+ """
81
+
82
+ def wrap(func):
83
+ def inner(*args, **kwargs):
84
+ return invoke_subgraph_placeholder(func, *args, **kwargs)
85
+
86
+ return inner
87
+
88
+ if fn:
89
+ return wrap(fn)
90
+ else:
91
+ return wrap
92
+
93
+
94
+ def get_invoke_subgraph_cache():
95
+ cache = None
96
+ if tracing_ctx := torch._guards.TracingContext.try_get():
97
+ cache = tracing_ctx.hop_dispatch_set_cache.get_cache(invoke_subgraph)
98
+ return cache
99
+
100
+
101
+ def trace_joint_graph(fn, fw_inputs, fw_outputs):
102
+ """
103
+ Naively trace out a joint graph. This simplifies the reconstruction of joint
104
+ graph in the min-cut partitioner later on.
105
+ """
106
+ from torch._functorch.aot_autograd import create_joint
107
+
108
+ dummy_aot_config = get_dummy_aot_autograd_config()
109
+
110
+ # This joint_fn is inserted as the backward graph as is. This simplifies the
111
+ # min-cut partitioner work later on.
112
+ # Input signature - (*primals, *tangents)
113
+ # Output signature - (*grads, *fw_outs)
114
+ # The output signature is deliberately kept grads first and fw_outs second.
115
+ # Having grads first makes the min-cut partitioner HOP graph stitching
116
+ # easier.
117
+ def joint_fn(*primals_and_tangents):
118
+ primals = primals_and_tangents[: len(fw_inputs)]
119
+ tangents = primals_and_tangents[len(fw_inputs) :]
120
+
121
+ fw_outs, grads = create_joint(
122
+ prepare_fw_with_masks(fn), aot_config=dummy_aot_config
123
+ )(primals, tangents)
124
+
125
+ maybe_clone = clone_outputs_aliasing_inputs(primals_and_tangents)
126
+
127
+ # return signature is deliberately kept (*grads, *fw_outs). This
128
+ # simplifies partitioning work later on.
129
+ return pytree.tree_map(maybe_clone, grads + list(fw_outs))
130
+
131
+ primals = list(fw_inputs)
132
+ # This assumes that the tangent strides match fw_outputs strides. Check the
133
+ # InvokeSubgraphAutogradOp backward op for the contiguous call.
134
+ tangents = [_from_fun(out) for out in fw_outputs]
135
+
136
+ joint_operands = primals + tangents
137
+
138
+ return _maybe_reenter_make_fx(joint_fn)(*joint_operands)
139
+
140
+
141
+ def create_fw_bw_graph(subgraph, operands, grad_outputs=None):
142
+ with suspend_functionalization(), disable_functional_mode():
143
+ with disable_proxy_modes_tracing():
144
+ # args are functional tensors, generate some example tensors
145
+ fw_inputs = pytree.tree_map(_from_fun, operands)
146
+
147
+ if grad_outputs is None:
148
+ # Infer grad_outputs to be the same properties as the fw_outputs
149
+ # if they're not passed in.
150
+ grad_outputs = pytree.tree_map(_from_fun, subgraph(*fw_inputs))
151
+ if any(
152
+ not isinstance(out, torch.Tensor)
153
+ for out in grad_outputs
154
+ if out is not None
155
+ ):
156
+ raise RuntimeError(
157
+ "Expect outputs of invoke_subgraph to only contains tensors or None. "
158
+ f"Got types {[type(out) for out in grad_outputs]}."
159
+ )
160
+
161
+ # Trace the forward subgraph
162
+ fw_graph = _maybe_reenter_make_fx(subgraph)(*fw_inputs)
163
+
164
+ # Trace the joint graph and assign it to the bwd graph
165
+ bw_graph = trace_joint_graph(
166
+ subgraph,
167
+ fw_inputs,
168
+ grad_outputs,
169
+ )
170
+ return fw_graph, bw_graph, len(grad_outputs)
171
+
172
+
173
+ class InvokeSubgraphAutogradOp(torch.autograd.Function):
174
+ """
175
+ This autograd function op is to stash the backward graph in the ctx while
176
+ running forward.
177
+ """
178
+
179
+ @staticmethod
180
+ def forward(ctx, fw_graph, bw_graph, identifier, num_fw_outs, *operands):
181
+ ctx._fw_graph = fw_graph
182
+ ctx._bw_graph = bw_graph
183
+ ctx._identifier = identifier
184
+ ctx._num_fw_outs = num_fw_outs
185
+
186
+ with torch._C._AutoDispatchBelowAutograd():
187
+ out = invoke_subgraph(
188
+ fw_graph,
189
+ f"___forward_{identifier}",
190
+ operands,
191
+ )
192
+
193
+ save_tensors_and_symints_for_backward(ctx, operands)
194
+ return out
195
+
196
+ @staticmethod
197
+ def backward(ctx, *grad_outs):
198
+ bw_graph = ctx._bw_graph
199
+ identifier = ctx._identifier
200
+ primals = saved_tensors_and_symints(ctx)
201
+ num_fw_outs = ctx._num_fw_outs
202
+
203
+ # While tracing we made the assumption that tangents are contiguous. So,
204
+ # force the grad_outs to be contiguous.
205
+ contiguous_grad_outs = tuple([o.contiguous() for o in grad_outs])
206
+
207
+ # bw_graph is a joint graph with signature (*primals_and_tangents) and
208
+ # returns (*grads_and_fw_outs). To get the grads, we use the num_fw_outs
209
+ # to extract the grads.
210
+ primals_and_tangents = primals + contiguous_grad_outs
211
+ grads = invoke_subgraph(
212
+ bw_graph, f"___backward_{identifier}", primals_and_tangents
213
+ )[:-num_fw_outs]
214
+ return None, None, None, None, *grads
215
+
216
+
217
+ @invoke_subgraph.py_impl(DispatchKey.CompositeExplicitAutograd)
218
+ def _(subgraph, identifier, operands):
219
+ from torch.utils._python_dispatch import _get_current_dispatch_mode
220
+
221
+ mode = _get_current_dispatch_mode()
222
+ assert mode is None, "Mode should never be enabled for CPU/CUDA key"
223
+ return subgraph(*operands)
224
+
225
+
226
+ @invoke_subgraph.py_impl(DispatchKey.Autograd)
227
+ def _(subgraph, identifier, operands):
228
+ if not torch.is_grad_enabled():
229
+ with torch._C._AutoDispatchBelowAutograd():
230
+ return invoke_subgraph(subgraph, identifier, operands)
231
+
232
+ # A shortcut for the case where all inputs don't require gradient,
233
+ # we skip tracing the forward and backward graph.
234
+ if pytree.tree_all_only(
235
+ torch.Tensor,
236
+ lambda t: not t.requires_grad, # type: ignore[union-attr]
237
+ operands,
238
+ ):
239
+ with torch._C._AutoDispatchBelowAutograd():
240
+ return invoke_subgraph(subgraph, identifier, operands)
241
+
242
+ # Check if we have already traced the subgraph.
243
+ invoke_subgraph_cache = get_invoke_subgraph_cache()
244
+ if invoke_subgraph_cache:
245
+ if saved_autograd_fn := invoke_subgraph_cache.get_autograd_key_entry(
246
+ identifier
247
+ ):
248
+ return saved_autograd_fn(*operands)
249
+
250
+ fw_graph, bw_graph, num_fw_outs = create_fw_bw_graph(subgraph, operands)
251
+
252
+ def autograd_fn_callable(*args):
253
+ return InvokeSubgraphAutogradOp.apply(
254
+ fw_graph, bw_graph, identifier, num_fw_outs, *args
255
+ )
256
+
257
+ # Save the autograd_fn_callable in the dispatch set cache.
258
+ if invoke_subgraph_cache:
259
+ invoke_subgraph_cache.add_autograd_key_entry(identifier, autograd_fn_callable)
260
+
261
+ return autograd_fn_callable(*operands)
262
+
263
+
264
+ @invoke_subgraph.py_functionalize_impl
265
+ def _(ctx, subgraph, identifier, operands):
266
+ unwrapped_operands = ctx.unwrap_tensors(operands)
267
+ with ctx.redispatch_to_next() as m:
268
+ # NB: There is an assumption that subgraph does not mutate inputs and
269
+ # there is no aliasing. Its Dynamo responsibility to prevent formation
270
+ # of invoke_subgraph ops if input aliasing/mutation is detected.
271
+ functionalized_subgraph = ctx.functionalize(subgraph)
272
+ out = invoke_subgraph(functionalized_subgraph, identifier, unwrapped_operands)
273
+ return ctx.wrap_tensors(out)
274
+
275
+
276
+ @invoke_subgraph.py_impl(FakeTensorMode)
277
+ def _(mode, subgraph, identifier, operands):
278
+ # TODO(anijain2305) - Implement fake tensor caching.
279
+ with mode:
280
+ return subgraph(*operands)
281
+
282
+
283
+ @invoke_subgraph.py_impl(ProxyTorchDispatchMode)
284
+ def _(proxy_mode: ProxyTorchDispatchMode, subgraph, identifier, operands):
285
+ # Check if we have already traced the subgraph.
286
+ graph = None
287
+ invoke_subgraph_cache = get_invoke_subgraph_cache()
288
+ if invoke_subgraph_cache:
289
+ graph = invoke_subgraph_cache.get_proxy_dispatch_entry(identifier)
290
+
291
+ if graph is None:
292
+ graph = reenter_make_fx(subgraph)(*operands)
293
+ assert isinstance(proxy_mode.tracer, torch.fx.Tracer)
294
+ qualname = proxy_mode.tracer.get_fresh_qualname("repeated_subgraph")
295
+ proxy_mode.tracer.root.register_module(qualname, graph)
296
+ if invoke_subgraph_cache:
297
+ invoke_subgraph_cache.add_proxy_dispatch_entry(identifier, graph)
298
+
299
+ node_args = (graph, identifier, operands)
300
+ proxy_args = pytree.tree_map(proxy_mode.tracer.unwrap_proxy, node_args) # type: ignore[union-attr]
301
+ out_proxy = proxy_mode.tracer.create_proxy(
302
+ "call_function", invoke_subgraph, proxy_args, {}
303
+ )
304
+
305
+ example_out = invoke_subgraph(graph, identifier, operands)
306
+ return track_tensor_tree(
307
+ example_out, out_proxy, constant=None, tracer=proxy_mode.tracer
308
+ )
lib/python3.12/site-packages/torch/_higher_order_ops/map.py ADDED
@@ -0,0 +1,264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ import torch
3
+ import torch.utils._pytree as pytree
4
+ from torch._C import DispatchKey
5
+ from torch._dispatch.python import suspend_functionalization
6
+ from torch._functorch.aot_autograd import AOTConfig, create_joint
7
+ from torch._higher_order_ops.utils import (
8
+ _has_potential_branch_input_alias,
9
+ _has_potential_branch_input_mutation,
10
+ _maybe_run_with_interpreter,
11
+ reenter_make_fx,
12
+ UnsupportedAliasMutationException,
13
+ )
14
+ from torch._ops import HigherOrderOperator
15
+ from torch._subclasses.fake_tensor import FakeTensorMode
16
+ from torch._subclasses.functional_tensor import disable_functional_mode
17
+ from torch.fx.experimental.proxy_tensor import (
18
+ disable_proxy_modes_tracing,
19
+ make_fx,
20
+ ProxyTorchDispatchMode,
21
+ track_tensor_tree,
22
+ )
23
+
24
+ from .utils import (
25
+ _from_fun,
26
+ _stack_pytree,
27
+ _unstack_pytree,
28
+ clone_outputs_aliasing_inputs,
29
+ prepare_fw_with_masks,
30
+ save_tensors_and_symints_for_backward,
31
+ saved_tensors_and_symints,
32
+ )
33
+
34
+
35
+ # TODO: We add this to prevent dymamo from tracing into map_wrapper,
36
+ # remove the wrapper call when it's ready.
37
+ class MapWrapper(HigherOrderOperator):
38
+ def __init__(self):
39
+ super().__init__("map")
40
+
41
+ def __call__(self, xs, *args):
42
+ return map_wrapper(xs, *args)
43
+
44
+
45
+ class MapImpl(HigherOrderOperator):
46
+ def __init__(self):
47
+ super().__init__("map_impl")
48
+
49
+ def __call__(self, *args, **kwargs):
50
+ return super().__call__(*args, **kwargs)
51
+
52
+
53
+ map = MapWrapper()
54
+
55
+ map_impl = MapImpl()
56
+
57
+ dummy_aot_config = AOTConfig(
58
+ fw_compiler=None, # type: ignore[arg-type]
59
+ bw_compiler=None, # type: ignore[arg-type]
60
+ partition_fn=None, # type: ignore[arg-type]
61
+ decompositions={},
62
+ num_params_buffers=0,
63
+ aot_id=0,
64
+ keep_inference_input_mutations=False,
65
+ )
66
+
67
+
68
+ def create_fw_bw_graph(f, num_mapped_args, *args):
69
+ mapped_xs = args[:num_mapped_args]
70
+ pos_args = args[num_mapped_args:]
71
+
72
+ # See Note [HOP create fw_bw graph] in create_fw_bw_graph in utils.py
73
+
74
+ with suspend_functionalization(), disable_functional_mode():
75
+ with disable_proxy_modes_tracing():
76
+ unwrapped_mapped_xs = pytree.tree_map(_from_fun, mapped_xs)
77
+ example_xs = _unstack_pytree(unwrapped_mapped_xs)[0]
78
+
79
+ example_pos_args = [
80
+ _from_fun(arg) if isinstance(arg, torch.Tensor) else arg
81
+ for arg in pos_args
82
+ ]
83
+ example_flat_out = pytree.tree_map(
84
+ _from_fun, f(*example_xs, *example_pos_args)
85
+ )
86
+ if any(
87
+ not isinstance(out, torch.Tensor)
88
+ for out in example_flat_out
89
+ if out is not None
90
+ ):
91
+ raise RuntimeError(
92
+ "Expect outputs of map only contains tensors or None. "
93
+ f"Got types {[type(out) for out in example_flat_out]}."
94
+ )
95
+ example_grad = [_from_fun(out) for out in example_flat_out]
96
+
97
+ fw_graph = make_fx(f)(*example_xs, *example_pos_args)
98
+
99
+ def joint_f(*example_args):
100
+ joint_mapped_args = example_args[:joint_num_mapped]
101
+ args = example_args[joint_num_mapped:]
102
+
103
+ mapped_input = joint_mapped_args[:num_mapped_args]
104
+ mapped_grads = joint_mapped_args[num_mapped_args:]
105
+
106
+ joint = create_joint(prepare_fw_with_masks(f), aot_config=dummy_aot_config)
107
+ _, grads = joint(
108
+ list(mapped_input) + list(args),
109
+ [
110
+ grad
111
+ for grad in mapped_grads
112
+ if grad is not None and grad.requires_grad
113
+ ],
114
+ )
115
+
116
+ # In order to keep map functional for backward graph,
117
+ # we clone outputs that are aliasing inputs
118
+ maybe_clone = clone_outputs_aliasing_inputs(example_args)
119
+
120
+ return pytree.tree_map(maybe_clone, grads)
121
+
122
+ joint_num_mapped = len(example_grad) + len(example_xs)
123
+ joint_graph = make_fx(joint_f)(*example_xs, *example_grad, *example_pos_args)
124
+ return fw_graph, joint_graph
125
+
126
+
127
+ def map_wrapper(f, xs, *args):
128
+ flat_xs, xs_spec = pytree.tree_flatten(xs)
129
+ if not all(isinstance(t, torch.Tensor) for t in flat_xs):
130
+ raise RuntimeError(f"Mapped xs can only consist of tensors. Got xs {flat_xs}.")
131
+
132
+ num_mapped_args = len(flat_xs)
133
+ shapes = [xs.shape for xs in flat_xs]
134
+ leading_dim_size = shapes[0][0]
135
+ if leading_dim_size == 0:
136
+ raise RuntimeError("Leading dimensions of mapped xs cannot be 0.")
137
+
138
+ if any(cur_shape[0] != leading_dim_size for cur_shape in shapes):
139
+ raise RuntimeError(
140
+ f"Leading dimensions of mapped xs must be consistent. Got shapes {shapes}."
141
+ )
142
+
143
+ out_spec = None
144
+
145
+ def flat_fn(*flat_args):
146
+ xs = pytree.tree_unflatten(list(flat_args[:num_mapped_args]), xs_spec)
147
+ unflattened_out = f(xs, *flat_args[num_mapped_args:])
148
+ flat_out, tmp_out_spec = pytree.tree_flatten(unflattened_out)
149
+
150
+ nonlocal out_spec
151
+ out_spec = tmp_out_spec
152
+ return flat_out
153
+
154
+ return pytree.tree_unflatten(
155
+ map_impl(flat_fn, flat_xs, args), out_spec # type: ignore[arg-type]
156
+ )
157
+
158
+
159
+ class MapAutogradOp(torch.autograd.Function):
160
+ @staticmethod
161
+ def forward(ctx, fw_graph, joint_graph, num_mapped_args, *flat_args):
162
+ save_tensors_and_symints_for_backward(ctx, flat_args)
163
+ ctx._joint_graph = joint_graph
164
+ ctx._num_mapped_args = num_mapped_args
165
+ with torch._C._AutoDispatchBelowAutograd():
166
+ return (
167
+ *map_impl(
168
+ fw_graph, flat_args[:num_mapped_args], flat_args[num_mapped_args:]
169
+ ),
170
+ )
171
+
172
+ @staticmethod
173
+ def backward(ctx, *flat_grads):
174
+ fw_args = saved_tensors_and_symints(ctx)
175
+ fw_mapped_args = fw_args[: ctx._num_mapped_args]
176
+ pos_args = fw_args[ctx._num_mapped_args :]
177
+
178
+ grads = map_impl(
179
+ ctx._joint_graph,
180
+ fw_mapped_args + flat_grads,
181
+ pos_args,
182
+ )
183
+ return None, None, None, *grads
184
+
185
+
186
+ def trace_map(proxy_mode, func_overload, f, xs, pos_args):
187
+ leading_dim_size = xs[0].shape[0]
188
+
189
+ example_input = _unstack_pytree(xs)[0]
190
+ body_graph = f
191
+
192
+ body_graph = reenter_make_fx(body_graph)(*example_input, *pos_args)
193
+
194
+ next_name = proxy_mode.tracer.get_fresh_qualname("body_graph_")
195
+
196
+ proxy_mode.tracer.root.register_module(next_name, body_graph)
197
+
198
+ with disable_proxy_modes_tracing():
199
+ example_outs = body_graph(*example_input, *pos_args)
200
+
201
+ def expand_tensor(t):
202
+ if isinstance(t, torch.Tensor):
203
+ return t.expand(leading_dim_size, *t.shape)
204
+ return t
205
+
206
+ expanded_outs = pytree.tree_map(expand_tensor, example_outs)
207
+
208
+ node_args = (body_graph, list(xs), list(pos_args))
209
+ proxy_args = pytree.tree_map(proxy_mode.tracer.unwrap_proxy, node_args)
210
+ out_proxy = proxy_mode.tracer.create_proxy(
211
+ "call_function", func_overload, proxy_args, {}, name="map_impl"
212
+ )
213
+ return track_tensor_tree(
214
+ expanded_outs, out_proxy, constant=None, tracer=proxy_mode.tracer
215
+ )
216
+
217
+
218
+ @map_impl.py_impl(DispatchKey.CompositeExplicitAutograd)
219
+ def map_dense(f, xs, pos_args):
220
+ pytrees = [f(*inp, *pos_args) for inp in _unstack_pytree(xs)]
221
+ return _stack_pytree(pytrees)
222
+
223
+
224
+ @map_impl.py_impl(DispatchKey.Autograd)
225
+ def map_autograd(f, xs, pos_args):
226
+ num_mapped_args = len(xs)
227
+ fw_graph, bw_graph = create_fw_bw_graph(f, num_mapped_args, *xs, *pos_args)
228
+ flat_out = MapAutogradOp.apply(fw_graph, bw_graph, num_mapped_args, *xs, *pos_args)
229
+ return flat_out
230
+
231
+
232
+ @map_impl.py_impl(ProxyTorchDispatchMode)
233
+ def map_proxy_torch_dispatch_mode(mode, f, xs, args):
234
+ return trace_map(mode, map_impl, f, xs, args)
235
+
236
+
237
+ @map_impl.py_impl(FakeTensorMode)
238
+ def map_fake_tensor_mode(mode, f, xs, args):
239
+ with mode:
240
+ return map_dense(f, xs, args)
241
+
242
+
243
+ @map_impl.py_functionalize_impl
244
+ def map_functionalize(ctx, f, xs, pos_args):
245
+ unwrapped_xs = ctx.unwrap_tensors(xs)
246
+ unwrapped_args = ctx.unwrap_tensors(pos_args)
247
+ wrapped_fn = ctx.functionalize(_maybe_run_with_interpreter(f))
248
+
249
+ with ctx.redispatch_to_next():
250
+ with disable_proxy_modes_tracing():
251
+ example_inputs = (*_unstack_pytree(unwrapped_xs)[0], *unwrapped_args)
252
+ pre_dispatch = hasattr(ctx, "mode") and ctx.mode.pre_dispatch
253
+ if _has_potential_branch_input_mutation(
254
+ f, example_inputs, pre_dispatch=pre_dispatch
255
+ ):
256
+ raise UnsupportedAliasMutationException("torch.map is mutating the input!")
257
+
258
+ if _has_potential_branch_input_alias(
259
+ f, example_inputs, pre_dispatch=pre_dispatch
260
+ ):
261
+ raise UnsupportedAliasMutationException("torch.map is aliasing the input!")
262
+
263
+ map_return = map_impl(wrapped_fn, unwrapped_xs, unwrapped_args)
264
+ return ctx.wrap_tensors(map_return)
lib/python3.12/site-packages/torch/_higher_order_ops/out_dtype.py ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-decorators
2
+ # mypy: allow-untyped-defs
3
+
4
+ import torch
5
+ import torch.utils._pytree as pytree
6
+ from torch._C import DispatchKey
7
+ from torch._higher_order_ops.utils import autograd_not_implemented
8
+ from torch._ops import HigherOrderOperator
9
+ from torch._prims_common import elementwise_dtypes, ELEMENTWISE_TYPE_PROMOTION_KIND
10
+ from torch._subclasses.fake_tensor import FakeTensorMode
11
+ from torch.fx.experimental.proxy_tensor import (
12
+ disable_proxy_modes_tracing,
13
+ maybe_handle_decomp,
14
+ ProxyTorchDispatchMode,
15
+ track_tensor_tree,
16
+ )
17
+
18
+
19
+ # TODO to figure out a more generic approach
20
+ ALLOWABLE_OPS = [
21
+ torch.ops.aten.linear.default,
22
+ torch.ops.aten.mm.default,
23
+ torch.ops.aten.conv2d.default,
24
+ torch.ops.aten.convolution.default,
25
+ torch.ops.aten.mul.Tensor,
26
+ torch.ops.aten.mul.Scalar,
27
+ torch.ops.aten.div.Tensor,
28
+ torch.ops.aten.div.Scalar,
29
+ ]
30
+
31
+
32
+ class OutDtypeOperator(HigherOrderOperator):
33
+ """
34
+ The out_dtype operator takes an existing ATen functional operator, an
35
+ `out_dtype` argument, and arguments to the original operator, and executes
36
+ the original operator and returns a Tensor with the `out_dtype` precision.
37
+ This operator does not mandate a compute precision so it allows the
38
+ representation to not be opinionated about the exact implementation.
39
+
40
+ The general implementation for all operators will be the following:
41
+ 1. Promote inputs dtypes based on default PyTorch dtype promotion rules,
42
+ using the dtypes of all input Tensors/Scalars and the `out_dtype`
43
+ arugument.
44
+ 2. Execute the operator
45
+ 3. Cast the output to `out_dtype`
46
+ """
47
+
48
+ def __init__(self) -> None:
49
+ super().__init__("out_dtype")
50
+
51
+ def __call__(self, op, output_dtype, *args):
52
+ if not isinstance(op, torch._ops.OpOverload):
53
+ raise ValueError("out_dtype's first argument must be an OpOverload")
54
+ if op._schema.is_mutable:
55
+ raise ValueError(
56
+ "out_dtype's first argument needs to be a functional operator"
57
+ )
58
+ if not (
59
+ len(op._schema.returns) == 1
60
+ and isinstance(op._schema.returns[0].type, torch.TensorType)
61
+ ):
62
+ raise ValueError(
63
+ "out_dtype's can only apply to ops that return a single tensor"
64
+ f"Instead got {[r.type for r in op._schema.returns]}"
65
+ )
66
+
67
+ if op not in ALLOWABLE_OPS:
68
+ raise ValueError(
69
+ f"out_dtype only allows the following operators: {ALLOWABLE_OPS}."
70
+ )
71
+
72
+ res = super().__call__(op, output_dtype, *args)
73
+
74
+ return res
75
+
76
+
77
+ out_dtype = OutDtypeOperator()
78
+
79
+
80
+ def trace_out_dtype(proxy_mode, func_overload, op, output_dtype, *args):
81
+ # NB: Long-term we should put the decomposition logic into
82
+ # ProxyTorchDispatchMode so that people do not need to call maybe_handle_decomp
83
+ # in all HigherOrderOp proxy implementations.
84
+ r = maybe_handle_decomp(proxy_mode, func_overload, (op, output_dtype, *args), {})
85
+ if r is not NotImplemented:
86
+ return r
87
+
88
+ with disable_proxy_modes_tracing():
89
+ # This is a simplified implementation of this operator just for tracing.
90
+ # Actual implementation may also first promote the arguments
91
+ out = op(*args).to(dtype=output_dtype)
92
+
93
+ node_args = (op, output_dtype, *args)
94
+ proxy_args = pytree.tree_map(proxy_mode.tracer.unwrap_proxy, node_args)
95
+ out_proxy = proxy_mode.tracer.create_proxy(
96
+ "call_function", func_overload, proxy_args, {}, name="out_dtype"
97
+ )
98
+ return track_tensor_tree(out, out_proxy, constant=None, tracer=proxy_mode.tracer)
99
+
100
+
101
+ @out_dtype.py_impl(DispatchKey.CompositeExplicitAutograd)
102
+ def out_dtype_dense(op: torch._ops.OpOverload, output_dtype: torch.dtype, *args):
103
+ if is_int_mm(op, output_dtype, args):
104
+ return torch._int_mm(*args)
105
+ return out_dtype_fallback(op, output_dtype, *args)
106
+
107
+
108
+ def is_int_mm(op, output_dtype, args):
109
+ return (
110
+ op == torch.ops.aten.mm.default
111
+ and output_dtype == torch.int32
112
+ and len(args) == 2
113
+ and args[0].dtype == torch.int8
114
+ and args[1].dtype == torch.int8
115
+ and args[0].is_cuda
116
+ and args[1].is_cuda
117
+ )
118
+
119
+
120
+ def out_dtype_fallback(op, output_dtype, *args):
121
+ flat_inputs = pytree.arg_tree_leaves(*args) + [torch.ones(1, dtype=output_dtype)]
122
+ promote_dtype: torch.dtype = elementwise_dtypes(
123
+ *flat_inputs,
124
+ type_promotion_kind=ELEMENTWISE_TYPE_PROMOTION_KIND.DEFAULT,
125
+ )[0]
126
+
127
+ casted_args = pytree.tree_map_only(
128
+ torch.Tensor, lambda arg: arg.to(dtype=promote_dtype), args
129
+ )
130
+ res = op(*casted_args).to(dtype=output_dtype)
131
+ return res
132
+
133
+
134
+ out_dtype.py_impl(DispatchKey.Autograd)(
135
+ autograd_not_implemented(out_dtype, deferred_error=True)
136
+ )
137
+
138
+
139
+ @out_dtype.py_impl(ProxyTorchDispatchMode)
140
+ def out_dtype_proxy(
141
+ mode: ProxyTorchDispatchMode,
142
+ op: torch._ops.OpOverload,
143
+ output_dtype: torch.dtype,
144
+ *args,
145
+ ):
146
+ return trace_out_dtype(mode, out_dtype, op, output_dtype, *args)
147
+
148
+
149
+ @out_dtype.py_impl(FakeTensorMode)
150
+ def out_dtype_fake_tensor_mode(
151
+ mode: FakeTensorMode,
152
+ op: torch._ops.OpOverload,
153
+ output_dtype: torch.dtype,
154
+ *args,
155
+ ):
156
+ with mode:
157
+ return out_dtype_dense(op, output_dtype, *args)
158
+
159
+
160
+ @out_dtype.py_functionalize_impl
161
+ def out_dtype_func(ctx, op, output_dtype, *args):
162
+ unwrapped_args = tuple(ctx.unwrap_tensors(arg) for arg in args)
163
+
164
+ with ctx.redispatch_to_next():
165
+ res = out_dtype(op, output_dtype, *unwrapped_args)
166
+ return ctx.wrap_tensors(res)
lib/python3.12/site-packages/torch/_higher_order_ops/prim_hop_base.py ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-decorators
2
+ # mypy: allow-untyped-defs
3
+
4
+ import abc
5
+
6
+ import torch
7
+ import torch.utils._pytree as pytree
8
+ from torch._C import DispatchKey
9
+ from torch._dispatch.python import suspend_functionalization
10
+ from torch._higher_order_ops.utils import reenter_make_fx
11
+ from torch._ops import HigherOrderOperator
12
+ from torch._subclasses import FakeTensorMode
13
+ from torch._subclasses.functional_tensor import disable_functional_mode
14
+ from torch.fx.experimental.proxy_tensor import (
15
+ disable_proxy_modes_tracing,
16
+ ProxyTorchDispatchMode,
17
+ track_tensor_tree,
18
+ )
19
+
20
+
21
+ class PrimHOPBase(HigherOrderOperator, abc.ABC):
22
+ """
23
+ This is the "Base" HOP implementation for a HOP that looks like:
24
+
25
+ call_subgraph_hop(subgraph, operands, **kwargs)
26
+
27
+ That is:
28
+ 1) the HOP is a "prim" (it stays alive until Inductor)
29
+ 2) the HOP's semantics are subgraph(*operands)
30
+
31
+ To use this, please subclass this class and override methods as necessary:
32
+ ```
33
+ class InvokeQuant(PrimHOPBase):
34
+ def __init__(self):
35
+ return super().__init__("invoke_quant")
36
+
37
+ invoke_quant = InvokeQuant()
38
+
39
+ def g(x):
40
+ return x.sin().cos()
41
+
42
+ @torch.compile(backend="aot_eager")
43
+ def f(x):
44
+ return invoke_quant(g, (x,), scheme="nf4")
45
+ ```
46
+
47
+ NOTE: don't subclass PrimHOPBase out of tree! That is not allowed. All
48
+ usages must be in tree.
49
+ """
50
+
51
+ def __init__(self, hop_name) -> None:
52
+ super().__init__(hop_name)
53
+
54
+ # Set up the registrations
55
+ # If you want to override any of these, override them in your subclass.
56
+ self.py_impl(DispatchKey.Autograd)(self._call_Autograd)
57
+ self.py_functionalize_impl(self._call_Functionalize)
58
+ self.py_impl(ProxyTorchDispatchMode)(self._call_ProxyTorchDispatchMode)
59
+ self.py_impl(FakeTensorMode)(self._call_FakeTensorMode)
60
+ self.py_impl(DispatchKey.CompositeExplicitAutograd)(
61
+ self._call_CompositeExplicitAutograd
62
+ )
63
+
64
+ def __call__(self, subgraph, operands, *unused, **kwargs):
65
+ # We accept *unused (and *_) to make mypy happy. Otherwise mypy
66
+ # complains that we're violating LSP. We are violating LSP, but it's
67
+ # OK for the purposes of implementation-sharing (end users should never
68
+ # subclass these methods; only in-tree PyTorch developers are allowed to).
69
+ assert len(unused) == 0
70
+ if not isinstance(subgraph, (torch.fx.GraphModule, FunctionWithNoFreeVars)):
71
+ raise RuntimeError(
72
+ f"{self._name}: when calling this API without torch.compile, "
73
+ f"we require that the subgraph be a torch.fx.GraphModule (or "
74
+ f"a function we know doesn't have free variables)."
75
+ )
76
+ return super().__call__(subgraph, operands, **kwargs)
77
+
78
+ def _call_Autograd(self, subgraph, operands, *_, **kwargs):
79
+ if isinstance(subgraph, torch.fx.GraphModule):
80
+ pass
81
+ if not torch.is_grad_enabled() or pytree.tree_all_only(
82
+ torch.Tensor,
83
+ lambda t: not t.requires_grad, # type: ignore[union-attr]
84
+ operands,
85
+ ):
86
+ with torch._C._AutoDispatchBelowAutograd():
87
+ return self(subgraph, operands, **kwargs)
88
+
89
+ # We assume the subgraph doesn't mutate inputs and there is no aliasing.
90
+ # In the PT2 stack, this is Dynamo's responsibility to figure out.
91
+ return PrimHOPBaseFunction.apply(self, subgraph, kwargs, *operands)
92
+
93
+ def _call_CompositeExplicitAutograd(self, subgraph, operands, *_, **kwargs):
94
+ from torch.utils._python_dispatch import _get_current_dispatch_mode
95
+
96
+ mode = _get_current_dispatch_mode()
97
+ assert mode is None, "Mode should never be enabled for CPU/CUDA key"
98
+ return subgraph(*operands)
99
+
100
+ def _call_ProxyTorchDispatchMode(
101
+ self, proxy_mode, subgraph, operands, *_, **kwargs
102
+ ):
103
+ traced_graph = reenter_make_fx(subgraph)(*operands)
104
+ assert isinstance(proxy_mode.tracer, torch.fx.Tracer)
105
+ qualname = proxy_mode.tracer.get_fresh_qualname("subgraph")
106
+ proxy_mode.tracer.root.register_module(qualname, traced_graph)
107
+
108
+ node_args = (traced_graph, operands)
109
+ proxy_args = pytree.tree_map(proxy_mode.tracer.unwrap_proxy, node_args) # type: ignore[attr-defined]
110
+ proxy_kwargs = pytree.tree_map(proxy_mode.tracer.unwrap_proxy, kwargs) # type: ignore[attr-defined]
111
+ out_proxy = proxy_mode.tracer.create_proxy(
112
+ "call_function", self, proxy_args, proxy_kwargs
113
+ )
114
+
115
+ out = self(subgraph, operands, **kwargs)
116
+ return track_tensor_tree(
117
+ out, out_proxy, constant=None, tracer=proxy_mode.tracer # type: ignore[arg-type]
118
+ )
119
+
120
+ def _call_FakeTensorMode(self, mode, subgraph, operands, *_, **kwargs):
121
+ # TODO: this should probably route through FakeTensorMode to reuse caching
122
+ with mode:
123
+ return subgraph(*operands)
124
+
125
+ def _call_Functionalize(self, ctx, subgraph, operands, *_, **kwargs):
126
+ unwrapped_operands = ctx.unwrap_tensors(operands)
127
+ with ctx.redispatch_to_next() as m:
128
+ # We assume the subgraph doesn't mutate inputs and there is no aliasing.
129
+ # In the PT2 stack, this is Dynamo's responsibility to figure out.
130
+ functionalized_subgraph = FunctionWithNoFreeVars(
131
+ ctx.functionalize(subgraph)
132
+ )
133
+ out = self(functionalized_subgraph, unwrapped_operands, **kwargs)
134
+ return ctx.wrap_tensors(out)
135
+
136
+
137
+ class PrimHOPBaseFunction(torch.autograd.Function):
138
+ @staticmethod
139
+ def forward(ctx, hop, subgraph, kwargs, *operands):
140
+ ctx.hop = hop
141
+ ctx.operands = operands
142
+ ctx.subgraph = subgraph
143
+ ctx.kwargs = kwargs
144
+
145
+ with torch._C._AutoDispatchBelowAutograd():
146
+ return hop(subgraph, operands, **kwargs)
147
+
148
+ @staticmethod
149
+ def backward(ctx, *grad_outputs):
150
+ subgraph = ctx.subgraph
151
+ operands = ctx.operands
152
+ kwargs = ctx.kwargs
153
+
154
+ # TODO: Something special needs to happen with min cut partitioner
155
+ with suspend_functionalization(), disable_functional_mode(), torch.enable_grad():
156
+ with disable_proxy_modes_tracing():
157
+ from .invoke_subgraph import create_fw_bw_graph
158
+ from .utils import _from_fun
159
+
160
+ fw_inputs = pytree.tree_map(_from_fun, operands)
161
+ fw_outputs = subgraph(*fw_inputs)
162
+ _, joint_graph, _ = create_fw_bw_graph(
163
+ subgraph, fw_inputs, grad_outputs
164
+ )
165
+
166
+ # The joint graph returns (*grad_inputs, *fwd_outputs).
167
+ # We only need the grad_inputs.
168
+ def bwd_fn(*args):
169
+ operands = args[: -len(grad_outputs)]
170
+ grad_outs = args[-len(grad_outputs) :]
171
+ result = joint_graph(*operands, *grad_outs)
172
+ grad_inputs = result[: -len(grad_outputs)]
173
+ return grad_inputs
174
+
175
+ return (
176
+ None,
177
+ None,
178
+ None,
179
+ *ctx.hop(
180
+ FunctionWithNoFreeVars(bwd_fn), (*operands, *grad_outputs), **kwargs
181
+ ),
182
+ )
183
+
184
+
185
+ class FunctionWithNoFreeVars:
186
+ def __init__(self, fn):
187
+ self.fn = fn
188
+
189
+ def __call__(self, *args, **kwargs):
190
+ return self.fn(*args, **kwargs)
lib/python3.12/site-packages/torch/_higher_order_ops/run_const_graph.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ import torch
3
+ from torch._C import DispatchKey
4
+ from torch._higher_order_ops.utils import autograd_not_implemented
5
+ from torch._ops import HigherOrderOperator
6
+ from torch._subclasses.fake_tensor import FakeTensorMode
7
+ from torch.fx.experimental.proxy_tensor import ProxyTorchDispatchMode, track_tensor_tree
8
+ from torch.utils import _pytree as pytree
9
+
10
+
11
+ class RunConstGraph(HigherOrderOperator):
12
+ def __init__(self):
13
+ super().__init__("run_const_graph")
14
+
15
+ def __call__(self, *args):
16
+ return super().__call__(*args)
17
+
18
+
19
+ run_const_graph = RunConstGraph()
20
+
21
+
22
+ @run_const_graph.py_impl(ProxyTorchDispatchMode)
23
+ def run_const_graph_dispatch_mode(mode, *args):
24
+ const_gm, weights = args
25
+ p_args = pytree.tree_map(mode.tracer.unwrap_proxy, args)
26
+ assert isinstance(const_gm, torch.fx.GraphModule)
27
+ assert not hasattr(mode.tracer.root, "_const_graph")
28
+ mode.tracer.root.register_module("_const_graph", const_gm)
29
+
30
+ proxy = mode.tracer.create_proxy("call_function", run_const_graph, p_args, {})
31
+
32
+ out = const_gm(*weights)
33
+ return track_tensor_tree(out, proxy, constant=None, tracer=mode.tracer)
34
+
35
+
36
+ @run_const_graph.py_functionalize_impl
37
+ def run_const_graph_functional(ctx, *args):
38
+ unwrapped_args = ctx.unwrap_tensors(args)
39
+
40
+ with ctx.redispatch_to_next():
41
+ out = run_const_graph(*unwrapped_args)
42
+ return ctx.wrap_tensors(out)
43
+
44
+
45
+ run_const_graph.py_impl(DispatchKey.Autograd)(
46
+ autograd_not_implemented(run_const_graph, deferred_error=True)
47
+ )
48
+
49
+
50
+ @run_const_graph.py_impl(FakeTensorMode)
51
+ def run_const_graph_fake_tensor_mode(mode, graph, args):
52
+ assert isinstance(graph, torch.fx.GraphModule)
53
+ with mode:
54
+ return graph(*args)
55
+
56
+
57
+ @run_const_graph.py_impl(DispatchKey.CPU)
58
+ def run_const_graph_cpu(graph, args):
59
+ assert isinstance(graph, torch.fx.GraphModule)
60
+ return graph(*args)
lib/python3.12/site-packages/torch/_higher_order_ops/scan.py ADDED
@@ -0,0 +1,518 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ import functools
3
+ import itertools
4
+ from typing import Any, Callable, List, Tuple
5
+
6
+ import torch
7
+ import torch._prims_common as utils
8
+ import torch._subclasses.functional_tensor
9
+ import torch.utils._pytree as pytree
10
+ from torch._C import DispatchKey
11
+ from torch._higher_order_ops.utils import (
12
+ _has_potential_branch_input_alias,
13
+ _has_potential_branch_input_mutation,
14
+ _set_compilation_env,
15
+ autograd_not_implemented,
16
+ reenter_make_fx,
17
+ unique_graph_id,
18
+ UnsupportedAliasMutationException,
19
+ validate_subgraph_args_types,
20
+ )
21
+ from torch._ops import HigherOrderOperator
22
+ from torch._subclasses.fake_tensor import FakeTensorMode
23
+ from torch.fx.experimental.proxy_tensor import (
24
+ _temp_remove_metadata_torch_function_mode,
25
+ disable_proxy_modes_tracing,
26
+ ProxyTorchDispatchMode,
27
+ track_tensor_tree,
28
+ )
29
+ from torch.utils._python_dispatch import _get_current_dispatch_mode
30
+
31
+
32
+ aten = torch._ops.ops.aten
33
+
34
+
35
+ def wrap_combine_fn_flat(
36
+ *args, combine_fn, spec_init, spec_xs, num_init_leaves, num_inp_leaves
37
+ ):
38
+ assert len(args) == (num_init_leaves + num_inp_leaves)
39
+ carry = pytree.tree_unflatten(args[:num_init_leaves], spec_init)
40
+ xs = pytree.tree_unflatten(args[num_init_leaves:], spec_xs)
41
+ carry, combined = combine_fn(carry, xs)
42
+ carry_flat = pytree.tree_leaves(carry)
43
+ combined_flat = pytree.tree_leaves(combined)
44
+ assert num_init_leaves == len(carry_flat)
45
+ return [*carry_flat, *combined_flat]
46
+
47
+
48
+ def _extract_carry_and_out(flat_out: List[Any], num_carry: int):
49
+ return flat_out[:num_carry], flat_out[num_carry:]
50
+
51
+
52
+ def scan(
53
+ combine_fn: Callable[
54
+ [pytree.PyTree, pytree.PyTree], Tuple[pytree.PyTree, pytree.PyTree]
55
+ ],
56
+ init: pytree.PyTree,
57
+ xs: pytree.PyTree,
58
+ *,
59
+ dim: int = 0,
60
+ reverse: bool = False,
61
+ ) -> Tuple[pytree.PyTree, pytree.PyTree]:
62
+ r"""
63
+ Performs an inclusive scan with a combine function.
64
+
65
+ .. warning::
66
+ `torch.scan` is a prototype feature in PyTorch. It currently
67
+ does not support autograd and you may run into miscompiles.
68
+ Read more about feature classification at:
69
+ https://pytorch.org/blog/pytorch-feature-classification-changes/#prototype
70
+
71
+ Args:
72
+ combine_fn (Callable): A binary callable with type ``(Tensor, Tensor) -> (Tensor, Tensor)``,
73
+ or if xs is a pytree ``(pytree, pytree) -> (pytree, pytree)``.
74
+ The first input to ``combine_fn`` is the previous or initial scan carry
75
+ and the second input element to ``combine_fn`` is a slice of the input along dim.
76
+ The first output element of ``combine_fn`` is the next scan carry
77
+ and the second output of ``combine_fn`` represents a slice of the output.
78
+ This function must be pure, i.e., no lifted arguments are supported at the moment
79
+ and may not have any side effects.
80
+ init (torch.Tensor or pytree with tensor leaves): The inital scan carry, a tensor, or nested pytree of tensors.
81
+ The ``init`` is expected to have the same pytree structure as the first output element (i.e. carry)
82
+ of ``combine_fn``.
83
+ xs (torch.Tensor or pytree with tensor leaves): The input tensor, or nested pytree of tensors.
84
+
85
+ Kwargs:
86
+ dim (int): the dimension to scan over, default 0.
87
+ reverse (bool): A boolean stating if the scan should be reversed with respect to ``dim``, default ``False``.
88
+
89
+ Returns:
90
+ final_carry (torch.Tensor or pytree with tensor leaves),
91
+ the final carry of the scan operation with same pytree structure as init.
92
+ out (torch.Tensor or pytree with tensor leaves),
93
+ each tensor leaf is a stacked output along first dim, where each slice is the output of a scan iteration.
94
+
95
+ Example::
96
+
97
+ def add(x: torch.Tensor, y: torch.Tensor):
98
+ next_carry = y = x + y
99
+ return next_carry, y
100
+
101
+ i0 = torch.zeros(1)
102
+ xs = torch.arange(5)
103
+ # returns torch.tensor([10.]), torch.tensor([[0], [1.], [3.], [6.], [10.]])
104
+ last_carry, cumsum = scan(add, init=i0, xs=xs)
105
+
106
+
107
+ """
108
+ if not callable(combine_fn):
109
+ raise RuntimeError("Combine_fn must be a callable, but got {combine_fn}")
110
+ if not isinstance(dim, int):
111
+ raise RuntimeError("Dim must be an int, but got " + str(type(dim)))
112
+ if not isinstance(reverse, bool):
113
+ raise RuntimeError("Reverse must be a bool, but got " + str(type(reverse)))
114
+
115
+ leaves_init, spec_init = pytree.tree_flatten(init)
116
+ leaves_xs, spec_xs = pytree.tree_flatten(xs)
117
+
118
+ if len(leaves_init) == 0:
119
+ raise RuntimeError("Init tensors must be provided")
120
+ for x in leaves_init:
121
+ if not isinstance(x, torch.Tensor):
122
+ raise RuntimeError(f"All init leaves must be a Tensor but got {x}")
123
+ for x in leaves_xs:
124
+ if not isinstance(x, torch.Tensor):
125
+ raise RuntimeError(f"All xs leaves must be a Tensor but got {x}")
126
+ if x.shape[dim] == 0:
127
+ raise RuntimeError(
128
+ f"All xs leaves must have a scan dimension > 0 but got {x}"
129
+ )
130
+
131
+ if len(leaves_xs) == 0:
132
+ return pytree.tree_unflatten(leaves_init, spec_init), xs
133
+
134
+ shape = leaves_xs[0].shape
135
+ ndim = len(shape)
136
+ dim = utils.canonicalize_dim(ndim, dim)
137
+
138
+ out = combine_fn(
139
+ pytree.tree_unflatten(leaves_init, spec_init),
140
+ pytree.tree_unflatten([elem.select(dim, 0) for elem in leaves_xs], spec_xs),
141
+ )
142
+
143
+ # The first output needs to have the same pytree as init
144
+ carry_leaves = pytree.tree_leaves(out[0])
145
+ if len(carry_leaves) != len(leaves_init):
146
+ raise RuntimeError(
147
+ f"The number of leaves of the pytree of the new carry produced by the operator is {len(carry_leaves)}\
148
+ doesn't match the length of the pytree of the init {len(leaves_init)}"
149
+ )
150
+
151
+ def _check_new_carry_match_init(leaves_init, carry_leaves):
152
+ for i, (init, new_carry) in enumerate(zip(leaves_init, carry_leaves)):
153
+ if init.shape != new_carry.shape:
154
+ raise RuntimeError(
155
+ f"The shape of the new_carry[{i}] {new_carry.shape} doesn't match that of the init[{i}] {init.shape}."
156
+ )
157
+ if init.stride() != new_carry.stride():
158
+ raise RuntimeError(
159
+ f"The stride of the new_carry[{i}] {new_carry.stride()} doesn't match that of the init[{i}] {init.stride()}."
160
+ )
161
+ if init.dtype != new_carry.dtype:
162
+ raise RuntimeError(
163
+ f"The dtype of the new_carry[{i}] {new_carry.dtype} doesn't match that of the init[{i}] {init.dtype}."
164
+ )
165
+ if init.requires_grad != new_carry.requires_grad:
166
+ raise RuntimeError(
167
+ f"The requires_grad of the new_carry[{i}] {new_carry.requires_grad} doesn't match that of the init[{i}] {init.requires_grad}." # noqa: B950
168
+ )
169
+
170
+ _check_new_carry_match_init(leaves_init, carry_leaves)
171
+
172
+ # There are no pytree restrictions on the second output of the operator
173
+ out_leaves, tree_out = pytree.tree_flatten(out[1])
174
+
175
+ # TODO: Support closures/nn_modules in order to be able represent RNNs with scan
176
+ # TODO: Support _inductor lowering
177
+ # TODO: Support Autograd
178
+ # TODO: Unify handling of pytrees for control flow ops, such as cond, while_loop, etc.
179
+ # TODO: Unify the list inputs of control flow ops to tuple.
180
+
181
+ combine_fn = functools.partial(
182
+ wrap_combine_fn_flat,
183
+ combine_fn=combine_fn,
184
+ spec_init=spec_init,
185
+ spec_xs=spec_xs,
186
+ num_init_leaves=len(leaves_init),
187
+ num_inp_leaves=len(leaves_xs),
188
+ )
189
+
190
+ def run_flattened_scan(combine_fn, leaves_init, leaves_xs, dim, reverse):
191
+ return scan_op(
192
+ combine_fn, leaves_init, leaves_xs, dim, reverse, additional_inputs=[]
193
+ )
194
+
195
+ if not torch._dynamo.is_compiling():
196
+ from torch._dynamo.backends.debugging import (
197
+ make_eager_backend_with_torch_function_mode,
198
+ )
199
+
200
+ with _set_compilation_env(), torch._dynamo.utils.disable_cache_limit():
201
+ with _temp_remove_metadata_torch_function_mode() as metadata_mode:
202
+ if metadata_mode:
203
+ backend = make_eager_backend_with_torch_function_mode(metadata_mode)
204
+ else:
205
+ backend = "eager"
206
+ result = torch.compile(
207
+ run_flattened_scan, backend=backend, fullgraph=True
208
+ )(
209
+ combine_fn,
210
+ leaves_init,
211
+ leaves_xs,
212
+ dim=dim,
213
+ reverse=reverse,
214
+ )
215
+ else:
216
+ result = run_flattened_scan(combine_fn, leaves_init, leaves_xs, dim, reverse)
217
+
218
+ result_carry, result_flat = _extract_carry_and_out(
219
+ result,
220
+ len(leaves_init),
221
+ )
222
+
223
+ return pytree.tree_unflatten(result_carry, spec_init), pytree.tree_unflatten(
224
+ result_flat, tree_out
225
+ )
226
+
227
+
228
+ class ScanOp(HigherOrderOperator):
229
+ def __init__(self):
230
+ super().__init__("scan")
231
+
232
+ def __call__(self, combine_fn, init, xs, dim, reverse, additional_inputs):
233
+ assert isinstance(additional_inputs, list), "additional_inputs must be a list."
234
+ validate_subgraph_args_types(additional_inputs)
235
+ return super().__call__(combine_fn, init, xs, dim, reverse, additional_inputs)
236
+
237
+
238
+ scan_op = ScanOp()
239
+
240
+
241
+ def generic_scan(operator, init, xs, dim=0, reverse=False, additional_inputs=None):
242
+ additional_inputs = additional_inputs if additional_inputs is not None else []
243
+
244
+ def _scan(init, xs):
245
+ """Perform scan on `elems` using `elems_init."""
246
+ carry = init
247
+ if len(xs) == 0:
248
+ return carry, []
249
+
250
+ num_elems = xs[0].shape[dim]
251
+ if reverse:
252
+ ind = num_elems - 1
253
+ else:
254
+ ind = 0
255
+
256
+ # Compute dummy shapes for the pre-allocation
257
+ num_init_leaves = len(init)
258
+ dummy_carry, dummy_out = _extract_carry_and_out(
259
+ operator(
260
+ *carry,
261
+ *[first_slice_copy(elem, dim) for elem in xs],
262
+ *additional_inputs,
263
+ ),
264
+ num_init_leaves,
265
+ )
266
+
267
+ # Pre-alocate
268
+ # outs -> Output matrix
269
+ # idxs -> Index matrix for scatter_
270
+ # out: (num_elems, M, N, ...)
271
+ # idx: (1, M, N)
272
+ outs, idxs = zip(
273
+ *[
274
+ [
275
+ torch.zeros(
276
+ [num_elems] + list(e.size()),
277
+ dtype=e.dtype,
278
+ device=e.device,
279
+ ),
280
+ torch.ones_like(e, dtype=torch.int64).unsqueeze(0),
281
+ ]
282
+ for i, e in enumerate(dummy_out)
283
+ ]
284
+ )
285
+
286
+ def store_out_in_outs(out, ind):
287
+ # Store the intermediate out in the outs matrix
288
+ for o, x, idx in zip(outs, out, idxs):
289
+ # o: (num_elems, M, N ...)
290
+ # x: (M, N, ...) -> (1, M, N)
291
+ # ind * idx: (1, M, N,) with values to be ind
292
+ # essentially: o[ind][n][k] = x[0][n][k]
293
+ o.scatter_(0, ind * idx, x.unsqueeze(0))
294
+
295
+ for i in range(num_elems):
296
+ ind = i if not reverse else num_elems - i - 1
297
+ carry, out = _extract_carry_and_out(
298
+ operator(
299
+ *carry,
300
+ *[elem.select(dim, ind) for elem in xs],
301
+ *additional_inputs,
302
+ ),
303
+ num_init_leaves,
304
+ )
305
+
306
+ # Store the inits in the outs matrix.
307
+ store_out_in_outs(out, ind)
308
+
309
+ return [*carry, *list(outs)]
310
+
311
+ scans = _scan(init, xs)
312
+ return scans
313
+
314
+
315
+ def first_slice_copy(t: torch.Tensor, dim: int) -> torch.Tensor:
316
+ return torch.select_copy(t, dim, 0)
317
+
318
+
319
+ # We also do a clone with contiguous_format. This is to be consistent with
320
+ # eager semantic of scan, which stacks the outputs. The result is contiguous
321
+ # as a result of the stack operation.
322
+ def stack_y(y: torch.Tensor, scan_length: int) -> torch.Tensor:
323
+ return (
324
+ y.unsqueeze(0)
325
+ .repeat(*([scan_length] + [1] * y.ndim))
326
+ .clone(memory_format=torch.contiguous_format)
327
+ )
328
+
329
+
330
+ def trace_scan(
331
+ proxy_mode,
332
+ func_overload,
333
+ combine_fn: Callable,
334
+ init: List[torch.Tensor],
335
+ xs: List[torch.Tensor],
336
+ dim: int,
337
+ reverse: bool,
338
+ additional_inputs: List[torch.Tensor],
339
+ ):
340
+ from torch._dynamo.utils import clone_input
341
+
342
+ with disable_proxy_modes_tracing():
343
+ sample_inits = [clone_input(x_init) for x_init in init]
344
+ sample_inputs = [first_slice_copy(x, dim) for x in xs]
345
+ sample_additional_inputs = [
346
+ clone_input(x) if isinstance(x, torch.Tensor) else x
347
+ for x in additional_inputs
348
+ ]
349
+ combine_graph = reenter_make_fx(combine_fn)(
350
+ *sample_inits, *sample_inputs, *sample_additional_inputs
351
+ )
352
+
353
+ outputs = None
354
+ for node in combine_graph.graph.nodes:
355
+ if node.op == "output":
356
+ assert outputs is None
357
+ assert len(node.args) == 1
358
+ outputs = node.args[0]
359
+
360
+ assert outputs is not None
361
+
362
+ carry, output = _extract_carry_and_out(outputs, len(init))
363
+
364
+ for ini, ca in zip(init, carry):
365
+ ini_meta = ini
366
+ carry_meta = ca.meta["tensor_meta"]
367
+ carry_val = ca.meta["val"]
368
+ if (
369
+ carry_val.device != ini_meta.device
370
+ or carry_meta.dtype != ini_meta.dtype
371
+ or carry_meta.shape != ini_meta.shape
372
+ ):
373
+ raise RuntimeError(
374
+ f"Expected metadata of the combine_fn result {carry_meta} to be the same as "
375
+ + f"the metadata of init with {ini_meta}"
376
+ )
377
+
378
+ _, combine_graph_name = unique_graph_id(proxy_mode, prefix="scan_combine_graph")
379
+
380
+ proxy_mode.tracer.root.register_module(combine_graph_name, combine_graph)
381
+
382
+ args = (combine_graph, init, xs, dim, reverse, additional_inputs)
383
+ proxy_args = pytree.tree_map(proxy_mode.tracer.unwrap_proxy, args)
384
+ out_proxy = proxy_mode.tracer.create_proxy(
385
+ "call_function", func_overload, proxy_args, {}, name="scan"
386
+ )
387
+
388
+ with disable_proxy_modes_tracing():
389
+ scan_length = xs[0].shape[dim]
390
+ fake_carry, fake_outputs = _extract_carry_and_out(
391
+ [o.meta["val"] for o in outputs], len(init)
392
+ )
393
+ out = (
394
+ *fake_carry,
395
+ *(stack_y(t, scan_length) for t in fake_outputs),
396
+ )
397
+
398
+ return track_tensor_tree(out, out_proxy, constant=None, tracer=proxy_mode.tracer)
399
+
400
+
401
+ @scan_op.py_impl(DispatchKey.CompositeExplicitAutograd)
402
+ def scan_op_dense(combine_fn, init, xs, dim, reverse, additional_inputs):
403
+ mode = _get_current_dispatch_mode()
404
+ assert mode is None, "Mode should never be enabled for CPU/CUDA key"
405
+ return generic_scan(combine_fn, init, xs, dim, reverse, additional_inputs)
406
+
407
+
408
+ scan_op.py_impl(DispatchKey.Autograd)(
409
+ autograd_not_implemented(scan_op, deferred_error=True)
410
+ )
411
+
412
+
413
+ @scan_op.py_impl(ProxyTorchDispatchMode)
414
+ def scan_proxy_mode(mode, combine_fn, init, xs, dim, reverse, additional_inputs):
415
+ return trace_scan(
416
+ mode, scan_op, combine_fn, init, xs, dim, reverse, additional_inputs
417
+ )
418
+
419
+
420
+ @scan_op.py_impl(FakeTensorMode)
421
+ def scan_fake_tensor_mode(mode, combine_fn, init, xs, dim, reverse, additional_inputs):
422
+ with mode:
423
+ scan_length = xs[0].shape[dim]
424
+ carry, outputs = _extract_carry_and_out(
425
+ combine_fn(
426
+ *init,
427
+ *[first_slice_copy(inp, dim) for inp in xs],
428
+ *additional_inputs,
429
+ ),
430
+ len(init),
431
+ )
432
+ out = (
433
+ *carry,
434
+ *(stack_y(t, scan_length) for t in outputs),
435
+ )
436
+ return out
437
+
438
+
439
+ @scan_op.py_functionalize_impl
440
+ def scan_functionalize(ctx, combine_fn, init, xs, dim, reverse, additional_inputs):
441
+ unwrapped_xs = ctx.unwrap_tensors(xs)
442
+ unwrapped_init = ctx.unwrap_tensors(init)
443
+ unwrapped_additional_inputs = ctx.unwrap_tensors(additional_inputs)
444
+ with ctx.redispatch_to_next() as m:
445
+ functional_combine_fn = ctx.functionalize(combine_fn)
446
+ pre_dispatch = hasattr(ctx, "mode") and ctx.mode.pre_dispatch
447
+ sample_unwrapped_xs_sliced = [
448
+ first_slice_copy(inp, dim) for inp in unwrapped_xs
449
+ ]
450
+ sample_inputs = list(
451
+ itertools.chain(
452
+ unwrapped_init,
453
+ sample_unwrapped_xs_sliced,
454
+ unwrapped_additional_inputs,
455
+ )
456
+ )
457
+ if _has_potential_branch_input_mutation(
458
+ functional_combine_fn, sample_inputs, pre_dispatch=pre_dispatch
459
+ ):
460
+ raise UnsupportedAliasMutationException(
461
+ "Combine_fn might be modifying the input!"
462
+ )
463
+ if _has_potential_branch_input_alias(
464
+ functional_combine_fn, sample_inputs, pre_dispatch=pre_dispatch
465
+ ):
466
+ raise UnsupportedAliasMutationException(
467
+ "Combine_fn might be aliasing the input!"
468
+ )
469
+ ret = scan_op(
470
+ functional_combine_fn,
471
+ unwrapped_init,
472
+ unwrapped_xs,
473
+ dim,
474
+ reverse,
475
+ unwrapped_additional_inputs,
476
+ )
477
+ return ctx.wrap_tensors(ret)
478
+
479
+
480
+ # dense implementation for scan. Used for testing only.
481
+ def _fake_scan(combine_fn, init, xs=None, dim=0, reverse=False):
482
+ carry_leaves, carry_spec = pytree.tree_flatten(init)
483
+ inp_leaves, inp_spec = pytree.tree_flatten(xs)
484
+ if xs is None or len(inp_leaves) == 0:
485
+ return init, []
486
+ result_flat = []
487
+ carry = carry_leaves
488
+ op = reversed if reverse else lambda x: x
489
+
490
+ dummy_carry, dummy_out = combine_fn(
491
+ pytree.tree_unflatten(carry, carry_spec),
492
+ pytree.tree_unflatten(
493
+ [first_slice_copy(elem, dim) for elem in inp_leaves],
494
+ inp_spec,
495
+ ),
496
+ )
497
+ dummy_out_leaves, dummy_out_spec = pytree.tree_flatten(dummy_out)
498
+ num_leaves = len(dummy_out_leaves)
499
+
500
+ for ind in op(range(inp_leaves[0].size(dim))):
501
+ xs = [elem.select(dim, ind) for elem in inp_leaves]
502
+
503
+ carry, y = combine_fn(
504
+ pytree.tree_unflatten(carry, carry_spec),
505
+ pytree.tree_unflatten(xs, inp_spec),
506
+ )
507
+ carry, _ = pytree.tree_flatten(carry)
508
+ y, _ = pytree.tree_flatten(y)
509
+ result_flat.append(y)
510
+
511
+ results = [
512
+ torch.stack([e[leave_ind] for e in op(result_flat)])
513
+ for leave_ind in range(num_leaves)
514
+ ]
515
+ return (
516
+ pytree.tree_unflatten(carry, carry_spec),
517
+ pytree.tree_unflatten(results, dummy_out_spec),
518
+ )
lib/python3.12/site-packages/torch/_higher_order_ops/strict_mode.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ import torch
3
+ import torch._subclasses.functional_tensor
4
+ import torch.utils._pytree as pytree
5
+ from torch._C import DispatchKey
6
+ from torch._functorch.utils import exposed_in
7
+ from torch._higher_order_ops.utils import _set_compilation_env, autograd_not_implemented
8
+ from torch._ops import HigherOrderOperator
9
+ from torch._subclasses.fake_tensor import FakeTensorMode
10
+ from torch.fx.experimental.proxy_tensor import (
11
+ _temp_remove_metadata_torch_function_mode,
12
+ _temp_remove_pre_dispatch_torch_function_mode,
13
+ disable_proxy_modes_tracing,
14
+ make_fx,
15
+ ProxyTorchDispatchMode,
16
+ track_tensor_tree,
17
+ )
18
+ from torch.utils._python_dispatch import _get_current_dispatch_mode
19
+
20
+
21
+ @exposed_in("torch")
22
+ def strict_mode(callable, operands):
23
+ from torch._dynamo.backends.debugging import (
24
+ make_eager_backend_with_torch_function_modes,
25
+ )
26
+
27
+ if torch.compiler.is_dynamo_compiling():
28
+ return strict_mode_op(callable, operands)
29
+
30
+ with _set_compilation_env():
31
+ with _temp_remove_metadata_torch_function_mode() as metadata_mode:
32
+ with _temp_remove_pre_dispatch_torch_function_mode() as predispatch_mode:
33
+ modes = [metadata_mode, predispatch_mode]
34
+ modes = [mode for mode in modes if mode is not None]
35
+ if modes:
36
+ backend = make_eager_backend_with_torch_function_modes(modes)
37
+ else:
38
+ backend = "eager"
39
+ with torch._dynamo.utils.disable_cache_limit():
40
+ return torch.compile(
41
+ strict_mode_op, backend=backend, fullgraph=True
42
+ )(callable, operands)
43
+
44
+
45
+ class StrictMode(HigherOrderOperator):
46
+ def __init__(self):
47
+ super().__init__("strict_mode")
48
+
49
+ def __call__(self, callable, operands):
50
+ return super().__call__(callable, operands)
51
+
52
+
53
+ strict_mode_op = StrictMode()
54
+
55
+
56
+ @strict_mode_op.py_impl(DispatchKey.CompositeExplicitAutograd)
57
+ def strict_mode_op_dense(callable, operands):
58
+ mode = _get_current_dispatch_mode()
59
+ assert mode is None, "Mode should never be enabled for CPU/CUDA key"
60
+ return callable(*operands)
61
+
62
+
63
+ strict_mode_op.py_impl(DispatchKey.Autograd)(
64
+ autograd_not_implemented(strict_mode_op, deferred_error=True)
65
+ )
66
+
67
+
68
+ @strict_mode_op.py_impl(ProxyTorchDispatchMode)
69
+ def inner(mode, callable, operands):
70
+ return trace_strict_mode(mode, strict_mode_op, callable, operands)
71
+
72
+
73
+ def trace_strict_mode(mode, strict_mode_op, callable, operands):
74
+ pre_dispatch = getattr(mode, "pre_dispatch", False)
75
+
76
+ with disable_proxy_modes_tracing():
77
+ graph = make_fx(callable, pre_dispatch=pre_dispatch)(*operands)
78
+
79
+ graph_name = mode.tracer.get_fresh_qualname("strict_graph_")
80
+ mode.tracer.root.register_module(graph_name, graph)
81
+
82
+ args = (graph, operands)
83
+
84
+ proxy_args = pytree.tree_map(mode.tracer.unwrap_proxy, args)
85
+
86
+ out_proxy = mode.tracer.create_proxy(
87
+ "call_function", strict_mode_op, proxy_args, {}, name="strict_mode"
88
+ )
89
+
90
+ out = graph(*operands)
91
+ return track_tensor_tree(out, out_proxy, constant=None, tracer=mode.tracer)
92
+
93
+
94
+ @strict_mode_op.py_impl(FakeTensorMode)
95
+ def strict_mode_fake_tensor_mode(mode, callable, operands):
96
+ with mode:
97
+ true_outs = callable(*operands)
98
+ return true_outs
99
+
100
+
101
+ @strict_mode_op.py_functionalize_impl
102
+ def strict_mode_func(ctx, callable, inputs):
103
+ unwrapped_inputs = ctx.unwrap_tensors(inputs)
104
+ with ctx.redispatch_to_next():
105
+ functional_callable = ctx.functionalize(callable)
106
+
107
+ cond_return = strict_mode_op(functional_callable, unwrapped_inputs)
108
+ return ctx.wrap_tensors(cond_return)
lib/python3.12/site-packages/torch/_higher_order_ops/torchbind.py ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ import logging
3
+ from contextlib import contextmanager
4
+
5
+ import torch
6
+ from torch._C import DispatchKey # @manual
7
+ from torch._functorch._aot_autograd.utils import KNOWN_TYPES
8
+ from torch._higher_order_ops.utils import autograd_not_implemented
9
+ from torch._library.fake_class_registry import _ns_and_class_name, FakeScriptObject
10
+ from torch._ops import HigherOrderOperator
11
+ from torch._subclasses.fake_tensor import FakeTensorMode
12
+ from torch.fx.experimental.proxy_tensor import ProxyTorchDispatchMode, track_tensor_tree
13
+ from torch.fx.node import has_side_effect
14
+ from torch.utils import _pytree as pytree
15
+
16
+
17
+ log = logging.getLogger(__name__)
18
+
19
+
20
+ # The call_torchbind operator represents a method invocation on a torchbind
21
+ # object. The calling convention is:
22
+ # call_torchbind(self: ScriptObject, method_name: str, *method_args, **method_kwargs)
23
+ # We do not expect users to write this operator directly. Instead it will be
24
+ # emitted by Dynamo when tracing encounters a torchbind object.
25
+ class CallTorchBind(HigherOrderOperator):
26
+ def __init__(self):
27
+ super().__init__("call_torchbind")
28
+
29
+ def __call__(self, obj, method, *args, **kwargs):
30
+ return super().__call__(obj, method, *args, **kwargs)
31
+
32
+
33
+ call_torchbind = CallTorchBind()
34
+
35
+ # Register this operator as side-effectful with FX.
36
+ # TODO: this is not really sufficient. While passes (hopefully) check
37
+ # Node.is_impure() and make good decisions, we also assume we can execute the
38
+ # graph as many times as we want without changing behavior, which is NOT true of
39
+ # ops that mutate torchbind object state.
40
+ has_side_effect(call_torchbind)
41
+
42
+ _orig_scriptmethod_call = torch.ScriptMethod.__call__
43
+
44
+
45
+ def torchbind_method_redispatch(self, *args, **kwargs):
46
+ if isinstance(self.raw_owner, torch.ScriptObject):
47
+ return call_torchbind(self.raw_owner, self.name, *args, **kwargs)
48
+ return _orig_scriptmethod_call(self, *args, **kwargs)
49
+
50
+
51
+ @contextmanager
52
+ def enable_torchbind_tracing():
53
+ """Context manager that acts as a feature flag to enable torchbind tracing
54
+ behavior. Once torchbind tracing has been stabilized, we can remove this and
55
+ turn it always on.
56
+ """
57
+ try:
58
+ KNOWN_TYPES.append(torch.ScriptObject)
59
+ torch.ScriptMethod.__call__ = torchbind_method_redispatch # type: ignore[method-assign]
60
+ yield
61
+ finally:
62
+ assert (
63
+ KNOWN_TYPES.pop() is torch.ScriptObject
64
+ ), "Someone else messed with KNOWN_TYPES during tracing, exploding."
65
+ torch.ScriptMethod.__call__ = _orig_scriptmethod_call # type: ignore[method-assign]
66
+
67
+
68
+ @call_torchbind.py_impl(DispatchKey.CompositeExplicitAutograd)
69
+ def call_torchbind_impl(obj, method, *args, **kwargs):
70
+ if isinstance(obj, torch.ScriptObject):
71
+ return _orig_scriptmethod_call(getattr(obj, method), *args, **kwargs)
72
+ elif isinstance(obj, FakeScriptObject):
73
+ return getattr(obj.wrapped_obj, method)(*args, **kwargs)
74
+ else:
75
+ raise RuntimeError(f"Unsupported first arg type {type(obj)} for call_torchbind")
76
+
77
+
78
+ @call_torchbind.py_impl(ProxyTorchDispatchMode)
79
+ def inner(mode, *args, **kwargs):
80
+ proxy_args = pytree.tree_map(mode.tracer.unwrap_proxy, args)
81
+ proxy_kwargs = pytree.tree_map(mode.tracer.unwrap_proxy, kwargs)
82
+
83
+ out_proxy = mode.tracer.create_proxy(
84
+ "call_function",
85
+ call_torchbind,
86
+ proxy_args,
87
+ proxy_kwargs,
88
+ )
89
+ out = call_torchbind(*args, **kwargs)
90
+
91
+ obj, method, *rest_args = args
92
+ if isinstance(obj, torch.ScriptObject):
93
+ ns, class_name = _ns_and_class_name(
94
+ obj._type().qualified_name() # type: ignore[attr-defined]
95
+ )
96
+ log.warning(
97
+ "Tracing torchbind method %s.%s with real ScriptObject. This may"
98
+ " cause the original object being mutated. If this is not intended,"
99
+ ' You can register a fake class with torch._library.register_fake_class("%s::%s").',
100
+ class_name,
101
+ method,
102
+ ns,
103
+ class_name,
104
+ )
105
+
106
+ ret = track_tensor_tree(out, out_proxy, constant=None, tracer=mode.tracer)
107
+ if "val" not in out_proxy.node.meta:
108
+ assert out is None or isinstance(
109
+ out, (int, float, bool)
110
+ ), "Currently, only these constant dtypes are supported to be returned from torchbind methods."
111
+ out_proxy.node.meta["val"] = out
112
+ return ret
113
+
114
+
115
+ # When tracing with fake script object, the call_torchbind op will return a fake tensor
116
+ # When tracing with real script object, the call_torchbind op may return a real tensor,
117
+ # we need to convert it to fake tensor mannually. Dynamic shape is surpported.
118
+ @call_torchbind.py_impl(FakeTensorMode)
119
+ def call_torchbind_fake(mode, *args, **kwargs):
120
+ with mode:
121
+ out = call_torchbind_impl(*args, **kwargs)
122
+ return pytree.tree_map_only(
123
+ torch.Tensor,
124
+ lambda x: mode.from_tensor(x, static_shapes=True)
125
+ if not isinstance(x, torch._subclasses.fake_tensor.FakeTensor)
126
+ else x,
127
+ out,
128
+ )
129
+
130
+
131
+ call_torchbind.py_impl(DispatchKey.Autograd)(
132
+ autograd_not_implemented(call_torchbind, deferred_error=True)
133
+ )
134
+
135
+
136
+ @call_torchbind.py_functionalize_impl
137
+ def call_torchbind_func(ctx, *args, **kwargs):
138
+ from torch._higher_order_ops.effects import handle_effects
139
+
140
+ return handle_effects(
141
+ ctx.mode._allow_token_discovery, ctx.mode._tokens, call_torchbind, args, kwargs
142
+ )
lib/python3.12/site-packages/torch/_higher_order_ops/triton_kernel_wrap.py ADDED
@@ -0,0 +1,1417 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import collections
2
+ import copy
3
+ import dataclasses
4
+ import inspect
5
+ import logging
6
+ import threading
7
+ from collections import defaultdict
8
+ from typing import (
9
+ Any,
10
+ Callable,
11
+ Dict,
12
+ List,
13
+ Optional,
14
+ Sequence,
15
+ Tuple,
16
+ TYPE_CHECKING,
17
+ Union,
18
+ )
19
+ from typing_extensions import Never
20
+
21
+ import sympy
22
+
23
+ import torch.fx as fx
24
+ import torch.utils._pytree as pytree
25
+ from torch import SymInt, Tensor
26
+ from torch._C import DispatchKey
27
+ from torch._ops import HigherOrderOperator
28
+ from torch._prims_common import clone_preserve_strides
29
+ from torch._subclasses.fake_tensor import FakeTensorMode
30
+ from torch.fx.experimental.proxy_tensor import (
31
+ disable_proxy_modes_tracing,
32
+ ProxyTorchDispatchMode,
33
+ track_tensor_tree,
34
+ )
35
+ from torch.fx.experimental.symbolic_shapes import guard_scalar
36
+
37
+
38
+ if TYPE_CHECKING:
39
+ from triton._C.libtriton.ir import (
40
+ module as TritonIRModule,
41
+ operation as TritonIROperation,
42
+ )
43
+
44
+ from torch._dynamo.symbolic_convert import InstructionTranslator
45
+ from torch._dynamo.variables.constant import ConstantVariable
46
+ from torch._dynamo.variables.functions import TritonKernelVariable
47
+ from torch._subclasses.functional_tensor import BaseFunctionalizeAPI
48
+ from torch.fx.proxy import Proxy
49
+ from torch.utils._triton import has_triton
50
+
51
+ TritonMetaParamsType = Dict[str, int]
52
+ TritonGridTupleType = Tuple[Union[int, sympy.Expr, SymInt], ...]
53
+ TritonGridCallableType = Callable[[TritonMetaParamsType], Tuple[int, ...]]
54
+ TritonGridType = Union[TritonGridTupleType, TritonGridCallableType]
55
+
56
+ if has_triton():
57
+ from triton.runtime.autotuner import Autotuner
58
+ from triton.runtime.jit import JITFunction
59
+ else:
60
+
61
+ class Autotuner: # type: ignore[no-redef]
62
+ pass
63
+
64
+ class JITFunction: # type: ignore[no-redef]
65
+ pass
66
+
67
+ TritonKernelType = Union[Autotuner, JITFunction]
68
+
69
+
70
+ log = logging.getLogger("torch._dynamo")
71
+
72
+ # TMADescriptorMetadata maps kernel parameter names to the metadata that allows
73
+ # reconstructing TMA descriptors from the underlying tensors (passed as kernel
74
+ # arguments in the fx graph, instead of the TMA descriptors). Namely: a tuple
75
+ # conisting of list of dims, list of block dims, and element size. E.g., for this
76
+ # call in host-side Triton TMA API ``create_2d_tma_descriptor(ptr, 50, 60, 32, 15, 4)``,
77
+ # the metadata will look like ``([50, 60], [32, 15], 4)``. All ints can be SymInts.
78
+ TMADescriptorMetadata = Dict[
79
+ str, # kernel parameter name
80
+ Tuple[
81
+ List[Union[int, SymInt]], # dims
82
+ List[Union[int, SymInt]], # block_dims
83
+ Union[int, SymInt], # element_size
84
+ ],
85
+ ]
86
+
87
+
88
+ ###############################################################################
89
+ # Kernel Side Table
90
+
91
+
92
+ # We cannot put Triton Kernels into the FX graph as the graph nodes
93
+ # do not support arbitrary functions.
94
+ # Use a side table.
95
+ # We use two dicts so that fetching both the kernel and id are O(1)
96
+ class KernelSideTable:
97
+ id_to_kernel: Dict[int, "TritonKernelType"] = {}
98
+ kernel_to_id: Dict["TritonKernelType", int] = {}
99
+ constant_args: Dict[int, Dict[str, Any]] = {}
100
+ lock = threading.Lock()
101
+
102
+ # Returns index on the table
103
+ def add_kernel(self, kernel: "TritonKernelType") -> int:
104
+ with self.lock:
105
+ if kernel in self.kernel_to_id:
106
+ return self.kernel_to_id[kernel]
107
+
108
+ idx = len(self.id_to_kernel)
109
+ self.id_to_kernel[idx] = kernel
110
+ self.kernel_to_id[kernel] = idx
111
+ return idx
112
+
113
+ # Returns the triton kernel at the given index
114
+ def get_kernel(self, idx: int) -> "TritonKernelType":
115
+ # No need to lock here as fetching from dict is atomic
116
+ assert idx in self.id_to_kernel
117
+ return self.id_to_kernel[idx]
118
+
119
+ # Not every constant arg can be added to the graph. Use this side table
120
+ # for constant args.
121
+ def add_constant_args(self, args: Dict[str, Any]) -> int:
122
+ with self.lock:
123
+ idx = len(self.constant_args)
124
+ self.constant_args[idx] = args
125
+ return idx
126
+
127
+ # Returns the constant args
128
+ def get_constant_args(self, idx: int) -> Dict[str, Any]:
129
+ # No need to lock here as fetching from dict is atomic
130
+ assert idx in self.constant_args
131
+ return self.constant_args[idx]
132
+
133
+ # Resets the table (only meant to be used in unit tests)
134
+ # This is only safe assuming single threaded execution
135
+ def reset_table(self) -> None:
136
+ self.id_to_kernel = {}
137
+ self.kernel_to_id = {}
138
+ self.constant_args = {}
139
+
140
+
141
+ kernel_side_table = KernelSideTable()
142
+
143
+
144
+ ###############################################################################
145
+ # Mutation Tracker
146
+
147
+
148
+ @dataclasses.dataclass(frozen=True)
149
+ class Param:
150
+ idx: int
151
+
152
+
153
+ @dataclasses.dataclass(frozen=True)
154
+ class Intermediate:
155
+ idx: int
156
+
157
+ def fake(self) -> bool:
158
+ return self.idx < 0
159
+
160
+
161
+ @dataclasses.dataclass(frozen=True)
162
+ class Op:
163
+ name: str
164
+ fn_call_name: Optional[str]
165
+ args: List[Union[Param, Intermediate]]
166
+ ret: Intermediate = dataclasses.field(repr=False)
167
+
168
+ def __post_init__(self) -> None:
169
+ if self.name == "tt.call":
170
+ assert self.fn_call_name is not None
171
+ else:
172
+ assert self.fn_call_name is None
173
+
174
+
175
+ def generate_ttir(
176
+ kernel: "TritonKernelType", kwargs: Dict[str, Any]
177
+ ) -> Tuple["TritonIRModule", List[str]]:
178
+ """
179
+ Uses Triton's internal code generation to create TTIR
180
+ """
181
+ import sympy
182
+ import triton
183
+ from triton.compiler.compiler import ASTSource
184
+ from triton.runtime.autotuner import Autotuner
185
+ from triton.runtime.jit import JITFunction
186
+
187
+ import torch._inductor.ir
188
+ from torch._subclasses.fake_tensor import FakeTensor
189
+
190
+ if isinstance(kernel, Autotuner):
191
+ if len(kernel.configs) > 0:
192
+ # If we are autotuning, then it doesn't matter which version gets
193
+ # picked for tracing purposes, so lets pick the first one
194
+ kwargs = {**kwargs, **kernel.configs[0].kwargs}
195
+ kernel = kernel.fn
196
+
197
+ assert isinstance(kernel, JITFunction)
198
+
199
+ context = triton._C.libtriton.ir.context()
200
+ target = triton.runtime.driver.active.get_current_target()
201
+ backend = triton.compiler.compiler.make_backend(target)
202
+ options = backend.parse_options({})
203
+
204
+ # ignore backend-specific kwargs same way as in the native Triton code
205
+ # https://github.com/triton-lang/triton/blob/a6bb57d6285e723c58e87dd7cba263db6efff789/python/triton/runtime/jit.py#L594-L596
206
+ # why this is important for user-defined Triton kernels on AMD: https://github.com/pytorch/pytorch/issues/140800
207
+ for name in list(kwargs):
208
+ if name not in kernel.arg_names and name in options.__dict__:
209
+ kwargs.pop(name)
210
+
211
+ if len(kwargs) != len(kernel.arg_names):
212
+ raise ValueError(
213
+ "Incorrect number of arguments passed to kernel: "
214
+ f"passed {list(kwargs.keys())}, expected {kernel.arg_names}."
215
+ )
216
+
217
+ # Replace all SymExprs with a regular value for TTIR generation
218
+ # Replace all FakeTensor/TensorBox with real tensors
219
+ # These replacements are needed for triton's type, key and config functions
220
+ ordered_args: Dict[str, Any] = {}
221
+ for name in kernel.arg_names:
222
+ a = kwargs[name]
223
+ if isinstance(a, (torch.SymInt, torch.SymFloat, torch.SymBool, sympy.Expr)):
224
+ ordered_args[name] = 2
225
+ elif isinstance(a, (FakeTensor, torch._inductor.ir.TensorBox)):
226
+ with torch._C._DisableTorchDispatch():
227
+ ordered_args[name] = torch.empty(2, dtype=a.dtype)
228
+ else:
229
+ ordered_args[name] = a
230
+
231
+ ordered_tensor_names = [
232
+ name for name, arg in ordered_args.items() if isinstance(arg, Tensor)
233
+ ]
234
+
235
+ def _get_specialization(args): # type: ignore[no-untyped-def]
236
+ try:
237
+ from triton.backends.compiler import AttrsDescriptor # noqa: F401
238
+
239
+ target = triton.runtime.driver.active.get_current_target()
240
+ backend = triton.compiler.compiler.make_backend(target)
241
+ return backend.get_attrs_descriptor(args, kernel.params)
242
+ except ImportError:
243
+ return kernel._get_config(*args)
244
+
245
+ specialization = _get_specialization(ordered_args.values())
246
+ constants = {
247
+ name: arg for name, arg in ordered_args.items() if not isinstance(arg, Tensor)
248
+ }
249
+
250
+ # Build kernel signature -- doesn't include constexpr arguments.
251
+ signature = {
252
+ name: kernel._type_of(kernel._key_of(arg))
253
+ for i, (name, arg) in enumerate(ordered_args.items())
254
+ if i not in kernel.constexprs
255
+ }
256
+
257
+ triton._C.libtriton.ir.load_dialects(context)
258
+ backend.load_dialects(context)
259
+
260
+ src = ASTSource(kernel, signature, constants, specialization)
261
+
262
+ # Triton changes ASTSource.make_ir to take 3/4 arguments. Handle
263
+ # backward compatibility here.
264
+ make_ir_sig_params = len(inspect.signature(src.make_ir).parameters)
265
+ if make_ir_sig_params == 2:
266
+ ttir_module = src.make_ir(options, context)
267
+ elif make_ir_sig_params == 3:
268
+ codegen_fns = backend.get_codegen_implementation()
269
+ ttir_module = src.make_ir(options, codegen_fns, context)
270
+ else:
271
+ codegen_fns = backend.get_codegen_implementation()
272
+ module_map = backend.get_module_map()
273
+ ttir_module = src.make_ir(options, codegen_fns, module_map, context)
274
+ if not ttir_module.verify():
275
+ raise RuntimeError("Verification for TTIR module has failed")
276
+
277
+ return ttir_module, ordered_tensor_names
278
+
279
+
280
+ def ttir_to_functions(
281
+ ttir_module: "TritonIRModule",
282
+ ) -> Dict[str, Dict[Intermediate, List[Op]]]:
283
+ """
284
+ Walk the `ttir_module` bottom up to mine the `functions` from
285
+ the structured MLIR entities representing the Triton kernel
286
+ (mlir::Operation, mlir::Block, mlir::Region).
287
+ """
288
+ functions: Dict[str, Dict[Intermediate, List[Op]]] = {}
289
+
290
+ # block id --> op result (Intermediate) --> one or more ops
291
+ op_stack: Dict[int, Dict[Intermediate, List[Op]]] = defaultdict(
292
+ lambda: defaultdict(list)
293
+ )
294
+ region_id_to_block_ids: Dict[int, List[int]] = defaultdict(list)
295
+ block_id_to_block_arg_ids: Dict[int, List[int]] = {}
296
+ replacements: Dict[int, Union[Intermediate, Param]] = {}
297
+ reindex_map: Dict[int, int] = {}
298
+ next_fake_intermediate = 0
299
+
300
+ def reindex(idx: int) -> int:
301
+ if idx not in reindex_map:
302
+ reindex_map[idx] = len(reindex_map)
303
+ return reindex_map[idx]
304
+
305
+ def mlir_to_functions(op: "TritonIROperation") -> None:
306
+ name: str = op.get_name()
307
+ if name == "builtin.module":
308
+ # this wraps all tt.func ops
309
+ return
310
+
311
+ operand_ids: List[int] = [
312
+ reindex(op.get_operand(i).id()) for i in range(op.get_num_operands())
313
+ ]
314
+ result_ids: List[int] = [
315
+ reindex(op.get_result(i).id()) for i in range(op.get_num_results())
316
+ ]
317
+
318
+ child_block_ids: List[int] = []
319
+ for i in [op.get_region(i).id() for i in range(op.get_num_regions())]:
320
+ # as the walk is bottom-up, the region_id_to_block_ids[i]
321
+ # must be populated by the time we process the enclosing op
322
+ child_block_ids.extend(region_id_to_block_ids[i])
323
+
324
+ parent_block_id = -1
325
+ parent_block = op.get_block()
326
+ if parent_block is not None:
327
+ parent_block_id = parent_block.id()
328
+ if parent_block_id not in block_id_to_block_arg_ids:
329
+ block_id_to_block_arg_ids[parent_block_id] = []
330
+ for i in range(parent_block.get_num_arguments()):
331
+ block_id_to_block_arg_ids[parent_block_id].append(
332
+ reindex(parent_block.get_argument(i).id()),
333
+ )
334
+ # the region info is collected via ops' parent blocks to be
335
+ # used later when the region's encloding op is traversed
336
+ parent_region = parent_block.get_parent()
337
+ if parent_region is not None:
338
+ region_id_to_block_ids[parent_region.id()].append(parent_block_id)
339
+
340
+ nonlocal next_fake_intermediate
341
+
342
+ if name == "tt.func":
343
+ # for function ops: gather and inline
344
+ # the ops from all child blocks
345
+ fn_ops = defaultdict(list)
346
+ for child_block_id in child_block_ids:
347
+ for result, block_fn_ops in op_stack.pop(child_block_id).items():
348
+ for block_fn_op in block_fn_ops:
349
+ fn_ops[result].append(block_fn_op)
350
+
351
+ # replace the corresponding Intermediates in the
352
+ # child op args with the function args (Params)
353
+ for i, idx in enumerate(block_id_to_block_arg_ids[child_block_ids[0]]):
354
+ replacements[idx] = Param(i)
355
+
356
+ for fn_op_list in fn_ops.values():
357
+ for fn_op in fn_op_list:
358
+ for i in range(len(fn_op.args)):
359
+ arg = fn_op.args[i]
360
+ seen = set() # to break cycles
361
+ # there can be transitive replacements, but likely
362
+ # no cycles (we keep the `seen` set just in case)
363
+ while (
364
+ isinstance(arg, Intermediate)
365
+ and arg.idx in replacements
366
+ and arg.idx not in seen
367
+ ):
368
+ seen.add(arg.idx)
369
+ arg = fn_op.args[i] = replacements[arg.idx]
370
+
371
+ # next function capture starts
372
+ # with empty replacements
373
+ replacements.clear()
374
+
375
+ fn_name = op.get_str_attr("sym_name")
376
+ functions[fn_name] = fn_ops
377
+ elif child_block_ids:
378
+ if name in {"scf.if", "scf.for", "scf.while", "tt.reduce", "tt.scan"}:
379
+ # for blocked ops: inline the enclosed ops into
380
+ # the parent block + rewire the last op in each
381
+ # child block to return the block result
382
+ return_ops = []
383
+ for block_id in child_block_ids:
384
+ if name == "scf.for":
385
+ # example:
386
+ # %result = scf.for %iv = %lb to %ub step %step iter_args(%arg = %init) -> (i32) ...
387
+ # block args: 2 (%iv, %arg)
388
+ # op operands: 4 (%lb, %ub, %step, %init)
389
+ # `%arg` is mapping to `%init`
390
+ for i, idx in enumerate(block_id_to_block_arg_ids[block_id]):
391
+ if i == 0:
392
+ next_fake_intermediate -= 1
393
+ replacements[idx] = Intermediate(next_fake_intermediate)
394
+ else:
395
+ replacements[idx] = Intermediate(operand_ids[i + 2])
396
+ elif name == "scf.while":
397
+ # example:
398
+ # %3:3 = scf.while (%arg2 = %1, %arg3 = %2, %arg4 = %c0_i32_8) ...
399
+ # block args: 3 (%arg2, %arg3, %arg4)
400
+ # op operands: 3 (%1, %2, %c0_i32_8)
401
+ # `%arg2` is mapping to `%1`, `%arg3` is mapping to `%2`, ...
402
+ for i, idx in enumerate(block_id_to_block_arg_ids[block_id]):
403
+ replacements[idx] = Intermediate(operand_ids[i])
404
+ elif name == "scf.if":
405
+ # the scf block args are ignored by the pass. but, as they
406
+ # may be used as operands of the ops inside the block
407
+ # (and nested blocks inlined in the current block by now),
408
+ # they are replaced by new fake Intermediates to avoid "this
409
+ # operand is not returned by any other op in the fn" error
410
+ # in the downstream analysis
411
+ for idx in block_id_to_block_arg_ids[block_id]:
412
+ next_fake_intermediate -= 1
413
+ replacements[idx] = Intermediate(next_fake_intermediate)
414
+ else:
415
+ assert name in ("tt.reduce", "tt.scan")
416
+ # wire the block arguments to the op arguments
417
+ num_operands = len(operand_ids)
418
+ block_arg_ids = block_id_to_block_arg_ids[block_id]
419
+ assert len(block_arg_ids) == 2 * num_operands, (
420
+ f"{name} is expected to have twice as "
421
+ "many block arguments as op arguments: "
422
+ f"{operand_ids=}, {block_arg_ids=}."
423
+ )
424
+ for i, idx in enumerate(block_arg_ids):
425
+ # for a tt.reduce/tt.scan op with N arguments, the block
426
+ # arguments comprise N reduced values followed by
427
+ # N current values corresponding to the N op args
428
+ replacements[idx] = Intermediate(
429
+ operand_ids[i % num_operands]
430
+ )
431
+
432
+ if block_id in op_stack:
433
+ block_ops = op_stack.pop(block_id)
434
+ if not block_ops:
435
+ continue
436
+ last_ret, last_ops = block_ops.popitem()
437
+ if all(
438
+ op.name
439
+ in ("scf.yield", "tt.reduce.return", "tt.scan.return")
440
+ for op in last_ops
441
+ ):
442
+ # if last_ops are all return ops, treat them separately
443
+ return_ops.extend(last_ops)
444
+ else:
445
+ # otherwise, return last_ops to the block
446
+ block_ops[last_ret] = last_ops
447
+ for op_result, child_ops in block_ops.items():
448
+ op_stack[parent_block_id][op_result].extend(child_ops)
449
+
450
+ scf_results = [Intermediate(idx) for idx in result_ids]
451
+ for scf_result in scf_results:
452
+ for return_op in return_ops:
453
+ op_stack[parent_block_id][scf_result].append(return_op)
454
+ else:
455
+ raise RuntimeError(
456
+ f"Unknown blocked function: {name}. Can't capture the TTIR."
457
+ )
458
+ else:
459
+ callee = None
460
+ if name == "tt.call":
461
+ callee = op.get_flat_symbol_ref_attr("callee")
462
+ args: List[Union[Param, Intermediate]] = [
463
+ Intermediate(operand) for operand in operand_ids
464
+ ]
465
+ block_ops = op_stack[parent_block_id]
466
+ if result_ids:
467
+ for result_id in result_ids:
468
+ res = Intermediate(result_id)
469
+ block_ops[res].append(Op(name, callee, args, res))
470
+ else:
471
+ next_fake_intermediate -= 1
472
+ fake_res = Intermediate(next_fake_intermediate)
473
+ block_ops[fake_res].append(Op(name, callee, args, fake_res))
474
+
475
+ ttir_module.walk(mlir_to_functions)
476
+
477
+ return functions
478
+
479
+
480
+ class MemoizeWithCycleCheck:
481
+ fn: Callable[..., Any]
482
+ cache: Dict[Tuple[str, int], Any]
483
+
484
+ def __init__(self, fn: Callable[..., Any]) -> None:
485
+ self.fn = fn
486
+ self.reset()
487
+
488
+ def __call__(
489
+ self,
490
+ functions: Dict[str, Dict[Intermediate, List[Op]]],
491
+ fn_name: str,
492
+ num_args: int,
493
+ ) -> List[bool]:
494
+ key = (fn_name, num_args)
495
+ if key not in self.cache:
496
+ self.cache[key] = None
497
+ self.cache[key] = self.fn(functions, fn_name, num_args)
498
+ if self.cache[key] is None:
499
+ raise RuntimeError("Recursion is not supported")
500
+ return self.cache[key]
501
+
502
+ def reset(self) -> None:
503
+ self.cache = {}
504
+
505
+
506
+ @MemoizeWithCycleCheck
507
+ def analyze_kernel_mutations(
508
+ functions: Dict[str, Dict[Intermediate, List[Op]]], fn_name: str, num_args: int
509
+ ) -> List[bool]:
510
+ """
511
+ Analyzes the graph to detect all sinks from a predefined list of sinks
512
+ by using triton's MemWrite trait list. NOTE: What if triton exposed this?
513
+ From each sink, it traverses the CFG backwards to identify all the input
514
+ pointers that are mutated.
515
+ """
516
+ # Name of mutation op to mutated parameter indices
517
+ # List from Triton Github include/triton/Dialect/Triton/IR/TritonOps.td
518
+ # All the OPs that have MemWrite trait.
519
+ # What if Triton exposed this?
520
+ MUTATION_OPS = {
521
+ "tt.store": [0],
522
+ "tt.atomic_cas": [0],
523
+ "tt.atomic_rmw": [0],
524
+ "tt.experimental_descriptor_store": [0],
525
+ }
526
+ # Ops that we want to bail out on
527
+ UNKNOWN_OPS = {"tt.elementwise_inline_asm"}
528
+
529
+ stack: List[Union[Param, Intermediate]] = []
530
+ visited = set()
531
+ ops = functions[fn_name]
532
+ for op_list in ops.values():
533
+ for op in op_list:
534
+ if op.name in UNKNOWN_OPS:
535
+ raise RuntimeError(
536
+ f"ttir analysis hit an op we do not know how to analyze: {op.name}"
537
+ )
538
+
539
+ if op.name == "tt.call":
540
+ assert op.fn_call_name in functions
541
+ mutations = analyze_kernel_mutations(
542
+ functions, op.fn_call_name, len(op.args)
543
+ )
544
+ stack.extend(arg for arg, mutated in zip(op.args, mutations) if mutated)
545
+ else:
546
+ stack.extend(op.args[idx] for idx in MUTATION_OPS.get(op.name, []))
547
+
548
+ # The following is an iterative DFS algorithm
549
+ mutated = [False] * num_args
550
+ while stack:
551
+ arg = stack.pop()
552
+ if arg in visited:
553
+ continue
554
+
555
+ visited.add(arg)
556
+
557
+ if isinstance(arg, Param):
558
+ if arg.idx >= num_args:
559
+ # This is an argument defined in the kernel, not passed in
560
+ continue
561
+ mutated[arg.idx] = True
562
+ elif isinstance(arg, Intermediate) and not arg.fake():
563
+ for op in ops[arg]:
564
+ # Skip arguments to load
565
+ if op.name != "tt.load":
566
+ stack.extend(op.args)
567
+ return mutated
568
+
569
+
570
+ def identify_mutated_tensors(
571
+ kernel: "TritonKernelType", kwargs: Dict[str, Any]
572
+ ) -> List[str]:
573
+ """
574
+ Given a triton kernel and the arguments for this kernel, this function
575
+ 1) Retrieves the TTIR converted version of the kernel from Triton's API.
576
+ 2) Parses the TTIR and creates a control flow graph
577
+ 3) Analyzes the graph to detect all input tensor mutations
578
+ """
579
+
580
+ ttir_module = None
581
+ functions = None
582
+ try:
583
+ ttir_module, ordered_tensor_names = generate_ttir(kernel, kwargs)
584
+
585
+ # extract functions from TTIR using MLIR bindings exposed by Triton code
586
+ functions = ttir_to_functions(ttir_module)
587
+
588
+ assert functions is not None
589
+ kernel_name = next(iter(functions.keys()))
590
+ # Triton codegen modifies the name
591
+ assert kernel.fn.__name__ in kernel_name
592
+ # Reset the cache between top level invocations
593
+ # The cache for analyze kernel mutations is mainly used for cycle
594
+ # detection, so each top level invocation needs a clean cache
595
+ analyze_kernel_mutations.reset()
596
+ mutations = analyze_kernel_mutations(
597
+ functions, kernel_name, len(ordered_tensor_names)
598
+ )
599
+
600
+ return [
601
+ ordered_tensor_names[i] for i, mutated in enumerate(mutations) if mutated
602
+ ]
603
+ except Exception as e:
604
+ log.warning(
605
+ "Encountered an exception in identify_mutated_tensors, assuming every input is mutated",
606
+ exc_info=True,
607
+ )
608
+ if ttir_module is not None:
609
+ log.debug("TTIR:\n%s", str(ttir_module))
610
+ if functions is not None:
611
+ log.debug("functions:")
612
+ for name, fn in functions.items():
613
+ log.debug("===\t%s\t===", name)
614
+ for ret, ops in fn.items():
615
+ log.debug("%s\t=>\t%s", ret, ops)
616
+ return [key for key, value in kwargs.items() if isinstance(value, Tensor)]
617
+
618
+
619
+ ###############################################################################
620
+ # Triton Kernel Wrappers
621
+
622
+
623
+ # Used for wrapping a Triton Kernel
624
+ class TritonKernelWrapperMutation(HigherOrderOperator):
625
+ def __init__(self) -> None:
626
+ super().__init__("triton_kernel_wrapper_mutation", cacheable=True)
627
+
628
+ def __call__(
629
+ self,
630
+ kernel_idx: int,
631
+ constant_args_idx: int,
632
+ grid: List["TritonGridType"],
633
+ tma_descriptor_metadata: TMADescriptorMetadata,
634
+ kwargs: Dict[str, Any],
635
+ ) -> Any:
636
+ return super().__call__(
637
+ kernel_idx=kernel_idx,
638
+ constant_args_idx=constant_args_idx,
639
+ grid=grid,
640
+ tma_descriptor_metadata=tma_descriptor_metadata,
641
+ kwargs=kwargs,
642
+ )
643
+
644
+
645
+ triton_kernel_wrapper_mutation = TritonKernelWrapperMutation()
646
+
647
+
648
+ # Used for wrapping a Triton Kernel in a functional manner
649
+ class TritonKernelWrapperFunctional(HigherOrderOperator):
650
+ def __init__(self) -> None:
651
+ super().__init__("triton_kernel_wrapper_functional", cacheable=True)
652
+
653
+ def __call__(
654
+ self,
655
+ kernel_idx: int,
656
+ constant_args_idx: int,
657
+ grid: List["TritonGridType"],
658
+ tma_descriptor_metadata: TMADescriptorMetadata,
659
+ kwargs: Dict[str, Any],
660
+ tensors_to_clone: List[str],
661
+ ) -> Dict[str, Any]:
662
+ return super().__call__(
663
+ kernel_idx=kernel_idx,
664
+ constant_args_idx=constant_args_idx,
665
+ grid=grid,
666
+ tma_descriptor_metadata=tma_descriptor_metadata,
667
+ kwargs=kwargs,
668
+ tensors_to_clone=tensors_to_clone,
669
+ )
670
+
671
+
672
+ triton_kernel_wrapper_functional = TritonKernelWrapperFunctional()
673
+
674
+
675
+ @triton_kernel_wrapper_mutation.py_impl(DispatchKey.CompositeExplicitAutograd)
676
+ def triton_kernel_wrapper_mutation_dense(
677
+ *,
678
+ kernel_idx: int,
679
+ constant_args_idx: int,
680
+ grid: List["TritonGridType"],
681
+ tma_descriptor_metadata: TMADescriptorMetadata,
682
+ kwargs: Dict[str, Any],
683
+ ) -> None:
684
+ from torch._inductor.codegen.wrapper import user_defined_kernel_grid_fn_code
685
+
686
+ kernel = kernel_side_table.get_kernel(kernel_idx)
687
+ constant_args = kernel_side_table.get_constant_args(constant_args_idx)
688
+
689
+ if len(grid) == 1:
690
+ grid_fn = grid[0]
691
+ else:
692
+ fn_name, code = user_defined_kernel_grid_fn_code(
693
+ kernel.fn.__name__, kernel.configs, grid
694
+ )
695
+ namespace: Dict[str, Any] = {}
696
+ exec(code, namespace)
697
+ grid_fn = namespace[fn_name]
698
+
699
+ if tma_descriptor_metadata:
700
+ from triton.tools.experimental_descriptor import ( # noqa: F401
701
+ create_1d_tma_descriptor,
702
+ create_2d_tma_descriptor,
703
+ )
704
+
705
+ # as we need to launch the kernel here, we "unwrap" the
706
+ # tma_descriptor_metadata, create the TMA descriptors
707
+ # from it, and replace the tensors in the kwargs by the
708
+ # correspoinding TMA descriptors before launching
709
+ kwargs = kwargs.copy()
710
+ for k, v in tma_descriptor_metadata.items():
711
+ tensor = kwargs[k]
712
+ dims, block_dims, element_size = v
713
+ create_tma_descriptor = (
714
+ create_1d_tma_descriptor if len(dims) == 1 else create_2d_tma_descriptor
715
+ )
716
+ kwargs[k] = create_tma_descriptor(
717
+ tensor.data_ptr(),
718
+ *dims,
719
+ *block_dims,
720
+ element_size,
721
+ )
722
+
723
+ # move as many positional arguments from dicts to args as we
724
+ # can to circumvent the bug with the kwargs and pre_/post_hook:
725
+ # https://github.com/triton-lang/triton/issues/5082
726
+ # TODO: remove this when the Triton issue above is fixed
727
+ args = []
728
+ # copy kwargs and constant_args here to
729
+ # avoid mutating the original inputs
730
+ kwargs = kwargs.copy()
731
+ constant_args = constant_args.copy()
732
+ for name in kernel.arg_names:
733
+ if name in kwargs:
734
+ args.append(kwargs.pop(name))
735
+ elif name in constant_args:
736
+ args.append(constant_args.pop(name))
737
+ else:
738
+ break
739
+
740
+ kernel[grid_fn](*args, **kwargs, **constant_args)
741
+
742
+
743
+ @triton_kernel_wrapper_mutation.py_impl(FakeTensorMode)
744
+ def triton_kernel_wrapper_mutation_fake_tensor_mode(
745
+ mode: FakeTensorMode,
746
+ *,
747
+ kernel_idx: int,
748
+ constant_args_idx: int,
749
+ grid: List["TritonGridType"],
750
+ tma_descriptor_metadata: TMADescriptorMetadata,
751
+ kwargs: Dict[str, Any],
752
+ ) -> None:
753
+ with mode:
754
+ return None
755
+
756
+
757
+ @triton_kernel_wrapper_mutation.py_impl(DispatchKey.Meta)
758
+ def _(
759
+ *,
760
+ kernel_idx: int,
761
+ constant_args_idx: int,
762
+ grid: List["TritonGridType"],
763
+ tma_descriptor_metadata: TMADescriptorMetadata,
764
+ kwargs: Dict[str, Any],
765
+ ) -> None:
766
+ return None
767
+
768
+
769
+ def trace_triton_kernel_wrapper(
770
+ proxy_mode: ProxyTorchDispatchMode,
771
+ func_overload: Callable[..., Any],
772
+ node_args: Dict[str, Any],
773
+ ) -> Optional[Dict[str, Any]]:
774
+ with disable_proxy_modes_tracing():
775
+ out = func_overload(**node_args)
776
+
777
+ proxy_args = pytree.tree_map(
778
+ proxy_mode.tracer.unwrap_proxy, node_args # type: ignore[union-attr]
779
+ )
780
+ out_proxy = proxy_mode.tracer.create_proxy(
781
+ "call_function",
782
+ func_overload,
783
+ (),
784
+ proxy_args,
785
+ name=func_overload.__name__ + "_proxy",
786
+ )
787
+
788
+ ret = track_tensor_tree(out, out_proxy, constant=None, tracer=proxy_mode.tracer)
789
+ return ret
790
+
791
+
792
+ @triton_kernel_wrapper_mutation.py_impl(ProxyTorchDispatchMode)
793
+ def triton_kernel_wrapper_mutation_proxy_torch_dispatch_mode(
794
+ mode: ProxyTorchDispatchMode,
795
+ *,
796
+ kernel_idx: int,
797
+ constant_args_idx: int,
798
+ grid: List["TritonGridType"],
799
+ tma_descriptor_metadata: TMADescriptorMetadata,
800
+ kwargs: Dict[str, Any],
801
+ ) -> None:
802
+ trace_triton_kernel_wrapper(
803
+ mode,
804
+ triton_kernel_wrapper_mutation,
805
+ {
806
+ "kernel_idx": kernel_idx,
807
+ "constant_args_idx": constant_args_idx,
808
+ "grid": grid,
809
+ "tma_descriptor_metadata": tma_descriptor_metadata,
810
+ "kwargs": kwargs,
811
+ },
812
+ )
813
+
814
+ return None
815
+
816
+
817
+ def get_mutated_tensors(
818
+ kernel_idx: int, constant_args_idx: int, kwargs: Dict[str, Any]
819
+ ) -> List[str]:
820
+ kernel = kernel_side_table.get_kernel(kernel_idx)
821
+ constant_args = kernel_side_table.get_constant_args(constant_args_idx)
822
+ return identify_mutated_tensors(kernel, {**kwargs, **constant_args})
823
+
824
+
825
+ @triton_kernel_wrapper_mutation.py_functionalize_impl
826
+ def triton_kernel_wrapper_mutation_functionalize(
827
+ ctx: "BaseFunctionalizeAPI",
828
+ kernel_idx: int,
829
+ constant_args_idx: int,
830
+ grid: List["TritonGridType"],
831
+ tma_descriptor_metadata: TMADescriptorMetadata,
832
+ kwargs: Dict[str, Any],
833
+ ) -> None:
834
+ unwrapped_kwargs = ctx.unwrap_tensors(kwargs) # type: ignore[arg-type]
835
+ # TODO(oulgen): Preexisting bug, if two kernel inputs are views of each
836
+ # other, and one gets mutated in kernel, and later another gets mutated,
837
+ # they are no longer equal. Fix this by graph breaking on this condition
838
+ # earlier in dynamo.
839
+ tensors_to_clone = get_mutated_tensors(
840
+ kernel_idx, constant_args_idx, unwrapped_kwargs
841
+ )
842
+ with ctx.redispatch_to_next():
843
+ unwrapped_outputs = triton_kernel_wrapper_functional(
844
+ kernel_idx=kernel_idx,
845
+ constant_args_idx=constant_args_idx,
846
+ grid=grid,
847
+ tma_descriptor_metadata=tma_descriptor_metadata,
848
+ kwargs=unwrapped_kwargs,
849
+ tensors_to_clone=tensors_to_clone,
850
+ )
851
+
852
+ assert set(unwrapped_outputs.keys()).issubset(set(kwargs.keys()))
853
+ for key, output_arg in unwrapped_outputs.items():
854
+ if not isinstance(output_arg, Tensor):
855
+ continue
856
+ input_arg = kwargs[key]
857
+ assert isinstance(input_arg, Tensor)
858
+
859
+ ctx.replace(input_arg, output_arg)
860
+ # indicate that above replace is hidden from autograd
861
+ ctx.mark_mutation_hidden_from_autograd(input_arg)
862
+ ctx.commit_update(input_arg)
863
+ ctx.sync(input_arg)
864
+ return None
865
+
866
+
867
+ @triton_kernel_wrapper_functional.py_impl(DispatchKey.CompositeExplicitAutograd)
868
+ def triton_kernel_wrapper_functional_dense(
869
+ *,
870
+ kernel_idx: int,
871
+ constant_args_idx: int,
872
+ grid: List["TritonGridType"],
873
+ tma_descriptor_metadata: TMADescriptorMetadata,
874
+ kwargs: Dict[str, Any],
875
+ tensors_to_clone: List[str],
876
+ ) -> Dict[str, Any]:
877
+ # TODO(oulgen): For performance reasons, we want to ensure that these
878
+ # `clone_preserve_strides` calls are never executed at runtime
879
+ # (inductor should always optimize them away).
880
+ # Requires https://github.com/pytorch/pytorch/issues/109240
881
+ kwargs = {
882
+ key: (clone_preserve_strides(val) if key in tensors_to_clone else val)
883
+ for key, val in kwargs.items()
884
+ }
885
+ triton_kernel_wrapper_mutation(
886
+ kernel_idx=kernel_idx,
887
+ constant_args_idx=constant_args_idx,
888
+ grid=grid,
889
+ tma_descriptor_metadata=tma_descriptor_metadata,
890
+ kwargs=kwargs,
891
+ )
892
+ return {key: val for key, val in kwargs.items() if key in tensors_to_clone}
893
+
894
+
895
+ @triton_kernel_wrapper_functional.py_impl(FakeTensorMode)
896
+ def triton_kernel_wrapper_functional_fake_tensor_mode(
897
+ mode: FakeTensorMode,
898
+ *,
899
+ kernel_idx: int,
900
+ constant_args_idx: int,
901
+ grid: List["TritonGridType"],
902
+ tma_descriptor_metadata: TMADescriptorMetadata,
903
+ kwargs: Dict[str, Any],
904
+ tensors_to_clone: List[str],
905
+ ) -> Dict[str, Any]:
906
+ # TODO(oulgen): For performance reasons, we want to ensure that these
907
+ # `clone_preserve_strides` calls are never executed at runtime
908
+ # (inductor should always optimize them away).
909
+ # Requires https://github.com/pytorch/pytorch/issues/109240
910
+ with mode:
911
+ return {
912
+ key: clone_preserve_strides(val)
913
+ for key, val in kwargs.items()
914
+ if key in tensors_to_clone
915
+ }
916
+
917
+
918
+ @triton_kernel_wrapper_functional.py_impl(ProxyTorchDispatchMode)
919
+ def triton_kernel_wrapper_functional_proxy_torch_dispatch_mode(
920
+ mode: ProxyTorchDispatchMode,
921
+ *,
922
+ kernel_idx: int,
923
+ constant_args_idx: int,
924
+ grid: List["TritonGridType"],
925
+ tma_descriptor_metadata: TMADescriptorMetadata,
926
+ kwargs: Dict[str, Any],
927
+ tensors_to_clone: List[str],
928
+ ) -> Dict[str, Any]:
929
+ ret = trace_triton_kernel_wrapper(
930
+ mode,
931
+ triton_kernel_wrapper_functional,
932
+ {
933
+ "kernel_idx": kernel_idx,
934
+ "constant_args_idx": constant_args_idx,
935
+ "grid": grid,
936
+ "tma_descriptor_metadata": tma_descriptor_metadata,
937
+ "kwargs": kwargs,
938
+ "tensors_to_clone": tensors_to_clone,
939
+ },
940
+ )
941
+ assert ret is not None
942
+ return ret
943
+
944
+
945
+ @triton_kernel_wrapper_functional.py_functionalize_impl
946
+ def triton_kernel_wrapper_functional_functionalize(
947
+ ctx: "BaseFunctionalizeAPI",
948
+ kernel_idx: int,
949
+ constant_args_idx: int,
950
+ grid: List["TritonGridType"],
951
+ tma_descriptor_metadata: TMADescriptorMetadata,
952
+ kwargs: Dict[str, Any],
953
+ tensors_to_clone: List[str],
954
+ ) -> Dict[str, Any]:
955
+ unwrapped_kwargs = ctx.unwrap_tensors(kwargs) # type: ignore[arg-type]
956
+ with ctx.redispatch_to_next():
957
+ outputs = triton_kernel_wrapper_functional(
958
+ kernel_idx=kernel_idx,
959
+ constant_args_idx=constant_args_idx,
960
+ grid=grid,
961
+ tma_descriptor_metadata=tma_descriptor_metadata,
962
+ kwargs=unwrapped_kwargs,
963
+ tensors_to_clone=tensors_to_clone,
964
+ )
965
+ return ctx.wrap_tensors(outputs) # type: ignore[return-value,arg-type]
966
+
967
+
968
+ triton_kernel_wrapper_mutation.fallthrough(DispatchKey.PythonDispatcher) # type: ignore[attr-defined]
969
+ triton_kernel_wrapper_mutation.fallthrough(DispatchKey.PythonTLSSnapshot) # type: ignore[attr-defined]
970
+ triton_kernel_wrapper_mutation.fallthrough(DispatchKey.ADInplaceOrView)
971
+ triton_kernel_wrapper_mutation.fallthrough(DispatchKey.BackendSelect)
972
+ triton_kernel_wrapper_mutation.fallthrough(DispatchKey.AutocastCPU) # type: ignore[attr-defined]
973
+ triton_kernel_wrapper_mutation.fallthrough(DispatchKey.AutocastCUDA) # type: ignore[attr-defined]
974
+ triton_kernel_wrapper_mutation.fallthrough(DispatchKey.AutogradCUDA)
975
+ triton_kernel_wrapper_mutation.fallthrough(DispatchKey.AutogradCPU)
976
+
977
+ triton_kernel_wrapper_functional.fallthrough(DispatchKey.PythonDispatcher) # type: ignore[attr-defined]
978
+ triton_kernel_wrapper_functional.fallthrough(DispatchKey.PythonTLSSnapshot) # type: ignore[attr-defined]
979
+ triton_kernel_wrapper_functional.fallthrough(DispatchKey.ADInplaceOrView)
980
+ triton_kernel_wrapper_functional.fallthrough(DispatchKey.BackendSelect)
981
+ triton_kernel_wrapper_functional.fallthrough(DispatchKey.AutocastCPU) # type: ignore[attr-defined]
982
+ triton_kernel_wrapper_functional.fallthrough(DispatchKey.AutocastCUDA) # type: ignore[attr-defined]
983
+ triton_kernel_wrapper_functional.fallthrough(DispatchKey.AutogradCUDA)
984
+ triton_kernel_wrapper_functional.fallthrough(DispatchKey.AutogradCUDA)
985
+ triton_kernel_wrapper_functional.fallthrough(DispatchKey.AutogradCPU)
986
+
987
+
988
+ ###############################################################################
989
+ # The "TritonHOPifier": a class that transforms a call to a triton kernel into
990
+ # a call to the triton_kernel_wrapper_mutation HOP.
991
+
992
+
993
+ class TritonHOPifier:
994
+ """Orchestrator for converting a user-defined triton kernel into a call
995
+ to the triton_kernel_wrapper_mutation HOP.
996
+
997
+ It has two main use cases.
998
+
999
+ 1. When Dynamo sees a triton kernel, it wraps it into a TritonKernelVariable
1000
+ and uses the TritonHOPifier to convert calls to the TritonKernelVariable
1001
+ into a call to the HOP.
1002
+
1003
+ 2. In order to capture a user-defined triton kernel while performing
1004
+ tracing (via make_fx or non-strict export), a user must annotate their
1005
+ triton kernel with the `wrap_triton` decorator. The decorator uses
1006
+ TritonHOPifier to convert calls to the triton kernel into a call
1007
+ to the HOP (which can then be traced).
1008
+
1009
+ Because Dynamo has its own calling conventions for e.g. invoking a user-defined function
1010
+ TritonHOPifier is an abstract class that can be overriden by its subclasses.
1011
+ """
1012
+
1013
+ def raise_unsupported(self, msg: str) -> Never:
1014
+ raise NotImplementedError("abstract method")
1015
+
1016
+ def is_callable(self, maybe_callable: Any) -> bool:
1017
+ raise NotImplementedError("abstract method")
1018
+
1019
+ def get_value(self, val: Any) -> Any:
1020
+ raise NotImplementedError("abstract method")
1021
+
1022
+ def call_grid( # type: ignore[no-untyped-def]
1023
+ self,
1024
+ grid,
1025
+ meta,
1026
+ tx,
1027
+ ) -> Union[Tuple[Union[int, sympy.Expr, SymInt], ...], Tuple["Proxy", ...]]:
1028
+ raise NotImplementedError("abstract method")
1029
+
1030
+ def call_HOP( # type: ignore[no-untyped-def]
1031
+ self,
1032
+ variable,
1033
+ grids,
1034
+ combined_args: Dict[str, Any],
1035
+ tx,
1036
+ ) -> Optional["ConstantVariable"]:
1037
+ raise NotImplementedError("abstract method")
1038
+
1039
+ def check_grid( # type: ignore[no-untyped-def]
1040
+ self, grid
1041
+ ) -> Union[Tuple[Union[int, sympy.Expr, SymInt], ...], Tuple["Proxy", ...]]:
1042
+ raise NotImplementedError("abstract method")
1043
+
1044
+ def init_variable(
1045
+ self,
1046
+ variable: Union["TraceableTritonKernelWrapper", "TritonKernelVariable"],
1047
+ kernel: "TritonKernelType",
1048
+ kernel_idx: Optional[int],
1049
+ grid: Optional["TritonGridType"],
1050
+ ) -> None:
1051
+ from triton.runtime.autotuner import Autotuner
1052
+
1053
+ assert kernel is not None
1054
+
1055
+ variable.kernel = kernel
1056
+ variable.kernel_idx = kernel_side_table.add_kernel(kernel)
1057
+
1058
+ assert kernel_idx is None or variable.kernel_idx == kernel_idx
1059
+
1060
+ variable.grid = grid
1061
+
1062
+ if isinstance(kernel, Autotuner):
1063
+ import torch
1064
+ import torch._dynamo
1065
+
1066
+ # We only support configs, keys, and restore_value arguments
1067
+ # of triton.autotune. Make sure other arguments are defaulted.
1068
+ defaults = inspect.signature(Autotuner.__init__).parameters
1069
+ # Newer version of triton change attribute name from warmup to num_warmup and rep to num_rep.
1070
+ # The call to get_first_attr is to maintain backward-compatibility.
1071
+ if (
1072
+ not torch._inductor.config.unsafe_ignore_unsupported_triton_autotune_args
1073
+ and (
1074
+ (
1075
+ "warmup" in defaults
1076
+ and defaults["warmup"].default
1077
+ != torch._dynamo.utils.get_first_attr(
1078
+ kernel, "num_warmups", "warmup"
1079
+ )
1080
+ )
1081
+ or (
1082
+ "rep" in defaults
1083
+ and defaults["rep"].default
1084
+ != torch._dynamo.utils.get_first_attr(kernel, "num_reps", "rep")
1085
+ )
1086
+ or (
1087
+ "prune_configs_by" in defaults
1088
+ and defaults["prune_configs_by"].default
1089
+ != kernel.early_config_prune
1090
+ )
1091
+ or (
1092
+ "use_cuda_graph" in defaults
1093
+ and defaults["use_cuda_graph"].default != kernel.use_cuda_graph
1094
+ )
1095
+ )
1096
+ ):
1097
+ self.raise_unsupported(
1098
+ "Only configs, keys, restore_value, and reset_to_zero are supported for triton.autotune"
1099
+ )
1100
+ if (
1101
+ not torch._inductor.config.unsafe_ignore_unsupported_triton_autotune_args
1102
+ and (
1103
+ # pre_hook requires running arbitrary code at runtime, which we cannot handle at this time
1104
+ # https://github.com/pytorch/pytorch/issues/139059
1105
+ # we can't support pre_hook or post_hook in user defined triton kernels at the moment,
1106
+ # as they require the ability to execute code at runtime (AOTI can't support this)
1107
+ (
1108
+ hasattr(kernel, "user_defined_pre_hook")
1109
+ and kernel.user_defined_pre_hook is not False
1110
+ )
1111
+ or (
1112
+ hasattr(kernel, "user_defined_post_hook")
1113
+ and kernel.user_defined_post_hook is not False
1114
+ )
1115
+ or (
1116
+ # Check Config passed to autotuner in configs
1117
+ any(cfg.pre_hook is not None for cfg in kernel.configs)
1118
+ )
1119
+ )
1120
+ ):
1121
+ self.raise_unsupported(
1122
+ "pre_hook and post_hook are not supported in triton.Autotune or triton.Config"
1123
+ )
1124
+
1125
+ def call_getitem(
1126
+ self,
1127
+ variable: Union["TritonKernelVariable", "TraceableTritonKernelWrapper"],
1128
+ args: Sequence[Any],
1129
+ ) -> Union["TritonKernelVariable", "TraceableTritonKernelWrapper"]:
1130
+ # __getitem__ should only be called if we don't already have a grid
1131
+ # Only grid needs to be passed
1132
+ if variable.grid is not None or len(args) != 1:
1133
+ self.raise_unsupported(
1134
+ "Triton kernels should be called with only a single grid"
1135
+ )
1136
+
1137
+ return type(variable)(
1138
+ kernel=variable.kernel,
1139
+ kernel_idx=variable.kernel_idx,
1140
+ grid=args[0],
1141
+ )
1142
+
1143
+ def call_run(
1144
+ self,
1145
+ variable: Union["TritonKernelVariable", "TraceableTritonKernelWrapper"],
1146
+ args: Sequence[Any],
1147
+ kwargs: Dict[str, Any],
1148
+ tx: Optional["InstructionTranslator"],
1149
+ ) -> Optional["ConstantVariable"]:
1150
+ if "grid" not in kwargs:
1151
+ self.raise_unsupported("Triton kernel requires to be called with a grid")
1152
+ grid = kwargs.pop("grid")
1153
+ kwargs.pop("warmup", None)
1154
+ # rewrite kernel.run(*args, grid=grid) to kernel[grid](*args)
1155
+ return self.call_triton_kernel(
1156
+ type(variable)(
1157
+ kernel=variable.kernel, kernel_idx=variable.kernel_idx, grid=grid
1158
+ ),
1159
+ args,
1160
+ kwargs,
1161
+ tx,
1162
+ )
1163
+
1164
+ def call_triton_kernel(
1165
+ self,
1166
+ variable: Union["TritonKernelVariable", "TraceableTritonKernelWrapper"],
1167
+ args: Sequence[Any],
1168
+ kwargs: Dict[str, Any],
1169
+ tx: Optional["InstructionTranslator"],
1170
+ ) -> Optional["ConstantVariable"]:
1171
+ from triton import JITFunction
1172
+ from triton.runtime.autotuner import autotune, Autotuner, Config
1173
+
1174
+ SPECIAL_CONFIG_NAMES = {"num_warps", "num_stages", "num_ctas"}
1175
+
1176
+ if "num_ctas" in kwargs:
1177
+ self.raise_unsupported(
1178
+ "Passing num_ctas directly to the Triton kernel is not supported. "
1179
+ "Please use a Config in @triton.autotune instead."
1180
+ )
1181
+
1182
+ special_kwargs = {}
1183
+ for name in SPECIAL_CONFIG_NAMES:
1184
+ if name in kwargs:
1185
+ # remove special kwargs from `kwargs`
1186
+ val = kwargs.pop(name)
1187
+ special_kwargs[name] = self.get_value(val)
1188
+
1189
+ if special_kwargs:
1190
+ if isinstance(variable.kernel, Autotuner):
1191
+ # if there is Autotuner already, set
1192
+ # special kwargs to each of its configs
1193
+ new_configs = copy.deepcopy(variable.kernel.configs)
1194
+ for config in new_configs:
1195
+ config.__dict__.update(special_kwargs)
1196
+ new_kernel = autotune(configs=new_configs, key=[])(variable.kernel.fn)
1197
+ else:
1198
+ # if there is no Autotuner, wrap the kernel into a
1199
+ # new one with a single config with special kwargs
1200
+ new_config = Config(kwargs={}, **special_kwargs)
1201
+ new_kernel = autotune(configs=[new_config], key=[])(variable.kernel)
1202
+
1203
+ # create a new variable to contain the new (wrapped) kernel;
1204
+ # skip kernel_idx to get a new record in the kernel side table
1205
+ new_var = type(variable)(new_kernel, None, variable.grid)
1206
+ return self.call_triton_kernel(new_var, args, kwargs, tx)
1207
+
1208
+ if isinstance(variable.kernel, Autotuner):
1209
+ special_param_names = []
1210
+ for name in SPECIAL_CONFIG_NAMES:
1211
+ if name in variable.kernel.fn.arg_names:
1212
+ special_param_names.append(name)
1213
+
1214
+ if special_param_names:
1215
+ # If the Triton kernel has SPECIAL_CONFIG_NAMES in parameters, those should
1216
+ # be passed from the kernel configs: the behavior of Triton runtime is that
1217
+ # those values get folded into the kernel arguments iff there are parameters
1218
+ # with the same name. Normally the values of those parameters are defined
1219
+ # outside the `kwargs` part of the autotuning configs. Here we move them to
1220
+ # the `kwargs` part (if they're absent there) to facilitate passing them as
1221
+ # arguments to the kernel downstream.
1222
+ updated = False
1223
+ new_configs = copy.deepcopy(variable.kernel.configs)
1224
+ for config in new_configs:
1225
+ for name in special_param_names:
1226
+ if name not in config.__dict__["kwargs"]:
1227
+ assert (
1228
+ name in config.__dict__
1229
+ ), f"{name} must be in autotuning configs to be used as a kernel parameter"
1230
+ config.__dict__["kwargs"][name] = config.__dict__[name]
1231
+ updated = True
1232
+
1233
+ if updated:
1234
+ new_kernel = autotune(configs=new_configs, key=[])(
1235
+ variable.kernel.fn
1236
+ )
1237
+ new_var = type(variable)(new_kernel, None, variable.grid)
1238
+ return self.call_triton_kernel(new_var, args, kwargs, tx)
1239
+
1240
+ if variable.grid is None:
1241
+ self.raise_unsupported("Triton kernels should always be called with a grid")
1242
+
1243
+ # Both for grid's meta as well as for the kernel, we need combined
1244
+ # args and kwargs combined and normalized
1245
+ combined_args_raw = {**dict(zip(variable.kernel.arg_names, args)), **kwargs}
1246
+
1247
+ configs = (
1248
+ [config.kwargs for config in variable.kernel.configs]
1249
+ if isinstance(variable.kernel, Autotuner)
1250
+ else [{}]
1251
+ )
1252
+ grids = []
1253
+ for config_args in configs:
1254
+ # If the grid is a function, then lets execute it and convert it to
1255
+ # a list
1256
+ grid = variable.grid
1257
+ assert grid is not None
1258
+ if self.is_callable(grid):
1259
+ # Populate the special "meta" argument to call the grid function
1260
+ meta = {**combined_args_raw, **config_args}
1261
+ grid = self.call_grid(grid, meta, tx) # type: ignore[arg-type]
1262
+ grids.append(self.check_grid(grid))
1263
+
1264
+ for i in range(len(grids)):
1265
+ if not isinstance(grids[i], tuple):
1266
+ self.raise_unsupported("Only tuple grids are supported")
1267
+ # inductor expects all grids to be 3-tuple so lets make it
1268
+ if len(grids[i]) == 1:
1269
+ grids[i] = (grids[i][0], 1, 1)
1270
+ elif len(grids[i]) == 2:
1271
+ grids[i] = (grids[i][0], grids[i][1], 1)
1272
+ elif len(grids[i]) > 3:
1273
+ self.raise_unsupported("Grid can have at most rank 3")
1274
+
1275
+ assert len(grids) != 0
1276
+ if isinstance(variable.kernel, JITFunction):
1277
+ constexprs = variable.kernel.constexprs
1278
+ else:
1279
+ assert isinstance(variable.kernel, Autotuner)
1280
+ constexprs = variable.kernel.fn.constexprs
1281
+
1282
+ for idx, arg_name in enumerate(variable.kernel.arg_names):
1283
+ if idx in constexprs:
1284
+ if arg_name in combined_args_raw:
1285
+ # [Note: Specialize tl.constexpr args in user-defined triton kernels]
1286
+ # This arg is marked as tl.constexpr. That means that triton will recompile every time
1287
+ # this value changes.
1288
+ # https://github.com/pytorch/pytorch/issues/136504
1289
+ # One option is to correctly pass the symints in so that the symbolic expressions are defined
1290
+ # when the triton code is being executed.
1291
+ # But since triton will have to recompile either way, we instead just specialize on the value.
1292
+ #
1293
+ # Depending on the type of `variable` we might expect different types for the symbolic args:
1294
+ # either SymNodeVariables (for TritonKernelVariables) or SymInts (TracingTritonKernelWrapper)
1295
+ combined_args_raw[arg_name] = variable.specialize_symbolic(
1296
+ combined_args_raw[arg_name]
1297
+ )
1298
+ return self.call_HOP(variable, grids, combined_args_raw, tx)
1299
+
1300
+
1301
+ ###############################################################################
1302
+ # Helpers for wrap_triton API that makes a user-defined triton kernel traceable into
1303
+ # a graph via make_fx or non-strict export (coming soon)
1304
+
1305
+
1306
+ class TracingTritonHOPifier(TritonHOPifier):
1307
+ def raise_unsupported(self, msg: str) -> Never:
1308
+ raise RuntimeError(msg)
1309
+
1310
+ def is_callable(self, maybe_callable: Any) -> bool:
1311
+ return callable(maybe_callable)
1312
+
1313
+ def get_value(self, val: Any) -> Any:
1314
+ return val
1315
+
1316
+ def call_grid(
1317
+ self,
1318
+ grid: "TritonGridCallableType",
1319
+ meta: "TritonMetaParamsType",
1320
+ tx: None,
1321
+ ) -> Tuple[Union[int, sympy.Expr, SymInt], ...]:
1322
+ assert tx is None
1323
+ assert isinstance(meta, dict)
1324
+ assert callable(grid)
1325
+ return grid(meta)
1326
+
1327
+ def check_grid(
1328
+ self,
1329
+ grid: "TritonGridType",
1330
+ ) -> Tuple[Union[int, sympy.Expr, SymInt], ...]:
1331
+ if not isinstance(grid, collections.abc.Sequence):
1332
+ raise RuntimeError(
1333
+ "wrap_triton can only handle grids that resolve to Sequence[int]."
1334
+ )
1335
+ # normalize to tuple
1336
+ return tuple(grid)
1337
+
1338
+ def call_HOP(
1339
+ self,
1340
+ variable: "TraceableTritonKernelWrapper",
1341
+ grids: List["TritonGridTupleType"],
1342
+ combined_args: Dict[str, Any],
1343
+ tx: None,
1344
+ ) -> None:
1345
+ assert tx is None
1346
+ assert isinstance(variable, TraceableTritonKernelWrapper)
1347
+
1348
+ def is_graphable(val: Any) -> bool:
1349
+ return isinstance(val, fx.node.base_types)
1350
+
1351
+ non_graphable_args = {
1352
+ k: v for k, v in combined_args.items() if not is_graphable(v)
1353
+ }
1354
+ graphable_args = {k: v for k, v in combined_args.items() if is_graphable(v)}
1355
+
1356
+ constant_args_idx = kernel_side_table.add_constant_args(non_graphable_args)
1357
+ assert isinstance(variable.kernel_idx, int)
1358
+ return triton_kernel_wrapper_mutation(
1359
+ kernel_idx=variable.kernel_idx,
1360
+ constant_args_idx=constant_args_idx,
1361
+ grid=grids, # type: ignore[arg-type]
1362
+ # TMA descriptor capturing not yet
1363
+ # supported in non-dynamo tracing
1364
+ tma_descriptor_metadata={},
1365
+ kwargs=graphable_args,
1366
+ )
1367
+
1368
+
1369
+ tracing_triton_hopifier_singleton = TracingTritonHOPifier()
1370
+
1371
+
1372
+ class TraceableTritonKernelWrapper:
1373
+ kernel: "TritonKernelType"
1374
+ kernel_idx: Optional[int]
1375
+ grid: Optional["TritonGridType"]
1376
+
1377
+ def __init__(
1378
+ self,
1379
+ kernel: "TritonKernelType",
1380
+ kernel_idx: Optional[int],
1381
+ grid: Optional["TritonGridType"],
1382
+ ) -> None:
1383
+ self.kernel = None
1384
+ self.grid = None
1385
+ tracing_triton_hopifier_singleton.init_variable(self, kernel, kernel_idx, grid)
1386
+ assert self.kernel is not None
1387
+
1388
+ def __getitem__(self, *args: Sequence[Any]) -> "TraceableTritonKernelWrapper":
1389
+ return tracing_triton_hopifier_singleton.call_getitem(self, args) # type: ignore[return-value]
1390
+
1391
+ def run(self, *args: Sequence[Any], **kwargs: Dict[str, Any]) -> Any:
1392
+ from torch._library.triton import is_wrap_triton_enabled
1393
+
1394
+ if is_wrap_triton_enabled():
1395
+ return tracing_triton_hopifier_singleton.call_run(self, args, kwargs, None)
1396
+ else:
1397
+ assert self.kernel is not None
1398
+ return self.kernel.run(*args, **kwargs)
1399
+
1400
+ def __call__(self, *args: Sequence[Any], **kwargs: Dict[str, Any]) -> Any:
1401
+ from torch._library.triton import is_wrap_triton_enabled
1402
+
1403
+ if is_wrap_triton_enabled():
1404
+ return tracing_triton_hopifier_singleton.call_triton_kernel(
1405
+ self, args, kwargs, None
1406
+ )
1407
+ else:
1408
+ assert self.kernel is not None
1409
+ return self.kernel[self.grid](*args, **kwargs)
1410
+
1411
+ def specialize_symbolic(self, arg: Sequence[Any]) -> Any:
1412
+ import torch
1413
+
1414
+ # See [Note: Specialize tl.constexpr args in user-defined triton kernels]
1415
+ if isinstance(arg, (torch.SymInt, torch.SymBool, torch.SymFloat)):
1416
+ return guard_scalar(arg)
1417
+ return arg
lib/python3.12/site-packages/torch/_higher_order_ops/utils.py ADDED
@@ -0,0 +1,519 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ import functools
3
+ from contextlib import contextmanager
4
+ from dataclasses import dataclass
5
+ from typing import Any, Callable, List, Tuple, Union
6
+
7
+ import torch
8
+ import torch.fx.traceback as fx_traceback
9
+ import torch.utils._pytree as pytree
10
+ from torch._ops import OperatorBase
11
+ from torch.fx.experimental.proxy_tensor import make_fx
12
+ from torch.fx.passes.shape_prop import TensorMetadata
13
+ from torch.multiprocessing.reductions import StorageWeakRef
14
+
15
+
16
+ @dataclass
17
+ class UnsupportedAliasMutationException(RuntimeError):
18
+ reason: str
19
+
20
+
21
+ def autograd_not_implemented_inner(
22
+ operator: OperatorBase, delayed_error: bool, *args: Any, **kwargs: Any
23
+ ) -> Any:
24
+ """If autograd is enabled and any of the arguments require grad this will either
25
+ raise an error or return a DelayedError depending on the value of delayed.
26
+
27
+ Args:
28
+ operator: The Operator to call with the *args and **kwargs with
29
+ op_name: The name of the Operator
30
+ delayed_error: If True, return a DelayedError instead of raising an error
31
+ args: The flattened operands to the Operator
32
+ kwargs: The keyword arguments to the Operator
33
+
34
+ Raises:
35
+ RuntimeError: If autograd is enabled and any of the arguments to the Operator
36
+ """
37
+ with torch._C._AutoDispatchBelowAutograd():
38
+ result = operator(*args, **kwargs)
39
+ flat_operands = pytree.arg_tree_leaves(*args)
40
+ if torch.is_grad_enabled() and any(
41
+ f.requires_grad for f in flat_operands if isinstance(f, torch.Tensor)
42
+ ):
43
+ if delayed_error:
44
+ err_fn = torch._C._functions.DelayedError(
45
+ f"Autograd not implemented for {str(operator)}",
46
+ 1,
47
+ )
48
+
49
+ def fake_requires_grad(tensor):
50
+ if torch.is_floating_point(tensor) or torch.is_complex(tensor):
51
+ tensor = tensor.detach()
52
+ tensor.requires_grad = True
53
+ return tensor
54
+
55
+ return pytree.tree_map_only(
56
+ torch.Tensor, lambda x: err_fn(fake_requires_grad(x)), result
57
+ )
58
+ else:
59
+ raise RuntimeError(f"Autograd not implemented for {str(operator)}")
60
+ return result
61
+
62
+
63
+ def autograd_not_implemented(op: OperatorBase, deferred_error: bool) -> Callable:
64
+ def inner(*args, **kwargs):
65
+ return autograd_not_implemented_inner(op, deferred_error, *args, **kwargs)
66
+
67
+ return inner
68
+
69
+
70
+ def _maybe_run_with_interpreter(fn):
71
+ maybe_interpreted_fn = fn
72
+ if isinstance(fn, torch.fx.GraphModule) and fx_traceback.has_preserved_node_meta():
73
+ # Running graph with interpreter is needed for propagating the stack_trace
74
+ def graph_with_interpreter(*args):
75
+ with fx_traceback.preserve_node_meta():
76
+ return torch.fx.Interpreter(fn).run(*args)
77
+
78
+ maybe_interpreted_fn = graph_with_interpreter
79
+ return maybe_interpreted_fn
80
+
81
+
82
+ def reenter_make_fx(fn):
83
+ from torch.fx.experimental.proxy_tensor import _CURRENT_MAKE_FX_TRACER
84
+
85
+ @functools.wraps(fn)
86
+ def wrapped(*args):
87
+ assert (
88
+ _CURRENT_MAKE_FX_TRACER is not None
89
+ ), "Cannot reenter make_fx when we're not under a make_fx tracing session"
90
+ return _CURRENT_MAKE_FX_TRACER.trace_subgraph(
91
+ _maybe_run_with_interpreter(fn), *args
92
+ )
93
+
94
+ return wrapped
95
+
96
+
97
+ def _maybe_reenter_make_fx(fn):
98
+ from torch.fx.experimental.proxy_tensor import _CURRENT_MAKE_FX_TRACER
99
+
100
+ if _CURRENT_MAKE_FX_TRACER is not None:
101
+ return reenter_make_fx(fn)
102
+ else:
103
+
104
+ def _maybe_make_fx_with_fake_mode(fn):
105
+ @functools.wraps(fn)
106
+ def wrapped(*args):
107
+ from torch._guards import detect_fake_mode
108
+
109
+ fake_mode = detect_fake_mode(args)
110
+ if fake_mode is None:
111
+ # we creaeta a fake_mode here to make sure we could
112
+ # trace the graph with data-dependent calls e.g. .item()
113
+ return make_fx(fn, tracing_mode="fake")(*args)
114
+ # Tracing with real if all inputs have been fakfied
115
+ return make_fx(fn)(*args)
116
+
117
+ return wrapped
118
+
119
+ return _maybe_make_fx_with_fake_mode(fn)
120
+
121
+
122
+ @contextmanager
123
+ def _set_compilation_env():
124
+ _old_is_tracing = torch.fx._symbolic_trace._is_fx_tracing_flag
125
+ try:
126
+ # We need to turn off the is_fx_tracing_flag. Remove this flag check from dyanmo
127
+ # once we are confident fx tracing works with dynamo.
128
+ torch.fx._symbolic_trace._is_fx_tracing_flag = False
129
+ yield
130
+ finally:
131
+ torch.fx._symbolic_trace._is_fx_tracing_flag = _old_is_tracing
132
+
133
+
134
+ def _detect_input_mutation(gm):
135
+ input_nodes = set()
136
+ for node in gm.graph.nodes:
137
+ if node.op == "placeholder":
138
+ input_nodes.add(node)
139
+ if node.op == "call_function":
140
+ target = node.target
141
+ if isinstance(target, torch._ops.OpOverload) and target._schema.is_mutable:
142
+ for arg in node.args:
143
+ if arg in input_nodes:
144
+ return True
145
+
146
+ for _, module in gm.named_children():
147
+ if isinstance(module, torch.fx.GraphModule):
148
+ if _detect_input_mutation(module):
149
+ return True
150
+
151
+ return False
152
+
153
+
154
+ def _detect_input_alias(gm):
155
+ input_storages = set()
156
+ for node in gm.graph.nodes:
157
+ # We need to check existence of "val" because we reuse the logic here
158
+ # for map operator, where num_mapped_args is a scalar
159
+ # and doesn't have a "val" meta.
160
+ if (
161
+ node.op == "placeholder"
162
+ and "val" in node.meta
163
+ and isinstance(node.meta["val"], torch.Tensor)
164
+ ):
165
+ input_storages.add(StorageWeakRef(node.meta["val"]._typed_storage()))
166
+ if node.op == "output":
167
+
168
+ def check_alias(out):
169
+ if (
170
+ out is not None
171
+ and "val" in out.meta
172
+ and isinstance(out.meta["val"], torch.Tensor)
173
+ ):
174
+ out_storage = StorageWeakRef(out.meta["val"]._typed_storage())
175
+ return out_storage in input_storages
176
+ return False
177
+
178
+ if any(pytree.tree_leaves(pytree.tree_map(check_alias, node.args))):
179
+ return True
180
+
181
+ for _, module in gm.named_children():
182
+ if isinstance(module, torch.fx.GraphModule) and _detect_input_alias(module):
183
+ return True
184
+
185
+ return False
186
+
187
+
188
+ def has_potential_input_alias_or_mutation(gm, inputs, pre_dispatch=False):
189
+ try:
190
+ gm = make_fx(gm, pre_dispatch=pre_dispatch)(*inputs)
191
+ except UnsupportedAliasMutationException:
192
+ # this can happen when nested cond_op is
193
+ # functionalized
194
+ return True
195
+ except Exception as e:
196
+ raise e
197
+
198
+ return _detect_input_mutation(gm) or _detect_input_alias(gm)
199
+
200
+
201
+ def _has_potential_branch_input_mutation(branch, inputs, pre_dispatch=False):
202
+ """
203
+ Dispatch-trace the branch with inputs and check if
204
+ producing graph has mutable op on the input. This is
205
+ bit restrictive as the branch must be traceable.
206
+ """
207
+ try:
208
+ gm = make_fx(branch, pre_dispatch=pre_dispatch)(*inputs)
209
+ except UnsupportedAliasMutationException:
210
+ # this can happen when nested cond_op is
211
+ # functionalized
212
+ return True
213
+ except Exception as e:
214
+ raise e
215
+
216
+ return _detect_input_mutation(gm)
217
+
218
+
219
+ def _has_potential_branch_input_alias(branch, inputs, pre_dispatch=False):
220
+ """
221
+ Dispatch-trace the branch with inputs and check if
222
+ producing graph has output aliasing the branch input. This is
223
+ bit restrictive as the branch must be traceable.
224
+ """
225
+ try:
226
+ gm = make_fx(branch, pre_dispatch=pre_dispatch)(*inputs)
227
+ except UnsupportedAliasMutationException:
228
+ # this can happen when nested cond_op is
229
+ # functionalized
230
+ return True
231
+ except Exception as e:
232
+ raise e
233
+
234
+ return _detect_input_alias(gm)
235
+
236
+
237
+ def unique_graph_id(proxy_mode, prefix):
238
+ """Returns a unique name and id for a graph to be added to a proxy_mode tracer"""
239
+ # There are probably better ways - I know that create_arg has some self incrementing name
240
+ # magic to it, but since we explicitly have to get the name for register_module,
241
+ # I was not sure how to do that. This kinda simulates it.
242
+ next_name = None
243
+ i = 0
244
+ while not next_name:
245
+ candidate = f"{prefix}_{i}"
246
+ if hasattr(proxy_mode.tracer.root, candidate):
247
+ i += 1
248
+ else:
249
+ next_name = candidate
250
+ return i, next_name
251
+
252
+
253
+ def _from_fun(t):
254
+ from torch._functorch.aot_autograd import from_fun
255
+ from torch._subclasses.functional_tensor import FunctionalTensor
256
+
257
+ if isinstance(t, torch.Tensor):
258
+ if t.dtype != torch.bool:
259
+ return torch.empty_strided(
260
+ t.size(),
261
+ t.stride(),
262
+ dtype=t.dtype,
263
+ requires_grad=t.requires_grad,
264
+ device=t.device,
265
+ )
266
+ else:
267
+ # clone of a functional tensor produces a functional tensor
268
+ # but we want to avoid it so we clone a non-functional version
269
+ maybe_unfunc_t = t
270
+ if isinstance(t, FunctionalTensor):
271
+ torch._sync(t)
272
+ maybe_unfunc_t = from_fun(t)
273
+ elif torch._is_functional_tensor(t):
274
+ # need to handle both types of functionalization here:
275
+ # these are the tensors that came from the user,
276
+ # which could be either FunctionalTensorWrapper or FunctionalTensor
277
+ torch._sync(t)
278
+ maybe_unfunc_t = torch._from_functional_tensor(t)
279
+ return maybe_unfunc_t.clone()
280
+ return t
281
+
282
+
283
+ def clone_outputs_aliasing_inputs(args):
284
+ input_storage = {
285
+ StorageWeakRef(arg._typed_storage())
286
+ for arg in args
287
+ if isinstance(arg, torch.Tensor)
288
+ }
289
+
290
+ def maybe_clone(t):
291
+ if (
292
+ isinstance(t, torch.Tensor)
293
+ and StorageWeakRef(t._typed_storage()) in input_storage
294
+ ):
295
+ return t.clone()
296
+ return t
297
+
298
+ return maybe_clone
299
+
300
+
301
+ def prepare_fw_with_masks(fn):
302
+ def fw_with_masks(*args):
303
+ fw_out = fn(*args)
304
+ return fw_out, [
305
+ True if isinstance(ret, torch.Tensor) and ret.requires_grad else False
306
+ for ret in fw_out
307
+ ]
308
+
309
+ return fw_with_masks
310
+
311
+
312
+ # TODO: The parameter use_output_and_grad_bw is required because some operations
313
+ # that utilize this function, such as the while_loop, may require (grad, fwd_outputs)
314
+ def create_fw_bw_graph(fn, use_output_and_grad_bw, fw_inputs, fw_outputs):
315
+ from torch._functorch.aot_autograd import AOTConfig, create_joint
316
+
317
+ # Note:[HOP create fw_bw graph] We create "clean" environments for make_fx by suspending all dispatch keys
318
+ # between Autograd and Python key. Currently, we only suspend functionalization but more can be
319
+ # added when required. Will encounter two problems if we don't suspend functionalization:
320
+ #
321
+ # 1. make_fx fails to capture operations on input: the inputs are wrapped as _to_functional_tensor_wrapper,
322
+ # but they will be unwrapped before entering ProxyTorchDispatchMode as part of the dispatching.
323
+ # However, it's the outside wrapper that tracer creates proxies for. This casuses tracer fail to
324
+ # fetch the proxy for the inputs and fail to capture any operations on them.
325
+ #
326
+ # 2. make_fx fails to capture output: the outputs after ProxyTorchDispatchMode are further
327
+ # wrapped as FunctionalTensorWrapper in Functionalize key after return. However, the tracer
328
+ # only associates the inner tensor with proxy in ProxyTorchDispatchMode. Therefore,
329
+ # when creating the output node, it fails to associate the wrapped tensor with its proxy.
330
+ # Instead, it will create _tensor_constant as output.
331
+
332
+ dummy_aot_config = AOTConfig(
333
+ fw_compiler=None, # type: ignore[arg-type]
334
+ bw_compiler=None, # type: ignore[arg-type]
335
+ partition_fn=None, # type: ignore[arg-type]
336
+ decompositions={},
337
+ num_params_buffers=0,
338
+ aot_id=0,
339
+ keep_inference_input_mutations=False,
340
+ )
341
+
342
+ example_grad = [_from_fun(out) for out in fw_outputs]
343
+ num_grads = len(example_grad)
344
+ fw_graph = _maybe_reenter_make_fx(fn)(*fw_inputs)
345
+
346
+ def joint_fn(*joint_operands_grads):
347
+ if use_output_and_grad_bw:
348
+ grads = joint_operands_grads[0]
349
+ inputs = joint_operands_grads[1][-1:]
350
+ else:
351
+ grads = joint_operands_grads[:num_grads]
352
+ inputs = joint_operands_grads[num_grads:]
353
+
354
+ joint = create_joint(prepare_fw_with_masks(fn), aot_config=dummy_aot_config)
355
+ _, grads = joint(
356
+ list(inputs),
357
+ [grad for grad in grads if grad is not None and grad.requires_grad],
358
+ )
359
+
360
+ # In order to keep map functional for backward graph,
361
+ # we clone outputs that are aliasing inputs
362
+ maybe_clone = clone_outputs_aliasing_inputs(joint_operands_grads)
363
+
364
+ return pytree.tree_map(maybe_clone, grads)
365
+
366
+ if use_output_and_grad_bw:
367
+ example_xs_out = list(fw_inputs) + list(fw_outputs)
368
+ joint_graph = _maybe_reenter_make_fx(joint_fn)(
369
+ (list(example_grad), list(example_xs_out))
370
+ )
371
+ else:
372
+ example_xs_out = list(fw_inputs)
373
+ joint_graph = _maybe_reenter_make_fx(joint_fn)(
374
+ *(list(example_grad) + list(example_xs_out))
375
+ )
376
+
377
+ return fw_graph, joint_graph
378
+
379
+
380
+ def _unstack_pytree(xs):
381
+ flat_xs, inspec = pytree.tree_flatten(xs)
382
+ if not all(isinstance(xs, torch.Tensor) for xs in flat_xs):
383
+ raise RuntimeError(f"Leaves of xs must be Tensor {flat_xs}")
384
+
385
+ if not all(xs.shape[0] == flat_xs[0].shape[0] for xs in flat_xs):
386
+ raise RuntimeError(
387
+ f"Leaves of xs must have same leading dimension size {[xs.shape for xs in flat_xs]}"
388
+ )
389
+
390
+ a = zip(*flat_xs)
391
+
392
+ pytrees = [pytree.tree_unflatten(tuple, inspec) for tuple in a]
393
+ return pytrees
394
+
395
+
396
+ def _stack_pytree(pytrees):
397
+ flat_out = []
398
+ out_spec = None
399
+ for pt in pytrees:
400
+ flat_pt, out_spec = pytree.tree_flatten(pt)
401
+ flat_out.append(flat_pt)
402
+ assert out_spec is not None
403
+ b = zip(*flat_out)
404
+ stacked_out = []
405
+ for leaves in b:
406
+ if all(isinstance(leaf, torch.Tensor) for leaf in leaves):
407
+ stacked_out.append(torch.stack(leaves))
408
+ elif all(leaf is None for leaf in leaves):
409
+ # Backward graph can return None output when forward inputs doesn't require grad.
410
+ # When we eagerly execute backward graph, we need to call _stack_pytree on its output,
411
+ # therefore we need to deal with None output.
412
+ stacked_out.append(None) # type: ignore[arg-type]
413
+ else:
414
+ raise RuntimeError(f"Cannot stack {leaves}.")
415
+ return pytree.tree_unflatten(stacked_out, out_spec)
416
+
417
+
418
+ # We cannot call save_for_backward for symints. This helper function
419
+ # can be used to save symints as direct attributes of ctx in autograd.Function.
420
+ #
421
+ # For example, if args = (x, y, s0, z, s1),
422
+ # save_tensors_and_symints_for_backward will partition the args into two lists, and a bookkeeping list pos:
423
+ # partitioned_args[0] = (x, y, z)
424
+ # partitioned_args[1] = (s0, s1)
425
+ # pos = (0, 0, 1, 0, 1)
426
+ # pos list keeps track of which partition the args
427
+ # is partitioned into in order to recover it in saved_tensors_and_symints.
428
+ #
429
+ # In saved_tensors_and_symints, we can recover the original args by:
430
+ # iterating over the pos list and pop one item from the front of paritioned_args[pos[i]].
431
+ # We use t_idx and s_idx to keep track of the next index of the item we are going to pop for the two lists.
432
+ def save_tensors_and_symints_for_backward(ctx, args):
433
+ assert all(
434
+ isinstance(arg, (torch.Tensor, torch.SymInt, int, type(None))) for arg in args
435
+ ), args
436
+ partitioned_args: List[Any] = [[], []]
437
+ pos = []
438
+ for i, arg in enumerate(args):
439
+ idx = 0 if isinstance(arg, torch.Tensor) else 1
440
+ partitioned_args[idx].append(arg)
441
+ pos.append(idx)
442
+
443
+ assert not hasattr(ctx, "sym_int_args"), "ctx already has sym_int_args attribute."
444
+ assert not hasattr(ctx, "pos"), "ctx already has pos attribute."
445
+ ctx.save_for_backward(*partitioned_args[0])
446
+ ctx.sym_int_args = partitioned_args[1]
447
+ ctx.pos = pos
448
+
449
+
450
+ def saved_tensors_and_symints(ctx):
451
+ args = []
452
+ t_idx = 0
453
+ s_idx = 0
454
+ saved_tensors = ctx.saved_tensors
455
+ for p in ctx.pos:
456
+ if p == 0:
457
+ args.append(saved_tensors[t_idx])
458
+ t_idx += 1
459
+ else:
460
+ args.append(ctx.sym_int_args[s_idx])
461
+ s_idx += 1
462
+ assert t_idx + s_idx == len(ctx.pos)
463
+ return tuple(args)
464
+
465
+
466
+ def get_dummy_aot_autograd_config():
467
+ from torch._functorch.aot_autograd import AOTConfig
468
+
469
+ return AOTConfig(
470
+ fw_compiler=None, # type: ignore[arg-type]
471
+ bw_compiler=None, # type: ignore[arg-type]
472
+ partition_fn=None, # type: ignore[arg-type]
473
+ decompositions={},
474
+ num_params_buffers=0,
475
+ aot_id=0,
476
+ keep_inference_input_mutations=False,
477
+ )
478
+
479
+
480
+ # Slices off the first element of a given dimension
481
+ def first_slice_copy(t: torch.Tensor, dim: int = 0) -> torch.Tensor:
482
+ return torch.select_copy(t, dim, 0)
483
+
484
+
485
+ # Reports the difference between meta of two tensors in a string
486
+ def diff_tensor_meta(
487
+ meta1: TensorMetadata, meta2: TensorMetadata, check_grad=True
488
+ ) -> List[str]:
489
+ from torch.fx.experimental.symbolic_shapes import GuardOnDataDependentSymNode
490
+
491
+ pair_diffs = []
492
+ for meta_name in TensorMetadata._fields:
493
+ if not check_grad and meta_name == "requires_grad":
494
+ continue
495
+ val1 = getattr(meta1, meta_name)
496
+ val2 = getattr(meta2, meta_name)
497
+ try:
498
+ if val1 != val2:
499
+ pair_diffs.append(f"'{meta_name}: {val1} vs {val2}'")
500
+ except GuardOnDataDependentSymNode as _:
501
+ pair_diffs.append(f"'{meta_name}: {val1} vs {val2}'")
502
+ continue
503
+ return pair_diffs
504
+
505
+
506
+ # Note [lifted arg types in hop]
507
+ # For dynamoed hops, we automatically lift the free symbols in tensors as arguments.
508
+ # This has implications for the types of lifted args for different dispatch keys:
509
+ # 1. functionalization, FakeTensorMode, ProxyTorchDispatchMode, Autograd need to support torch.Symint
510
+ # lifted args because it's on the path of torch.compile(dynamic=True).
511
+ # 2. functionalization, FakeTensorMode, ProxyTorchDispatchMode, Autograd, CompositeExplicitAutograd need
512
+ # to support int arguments. In the eager run case, we re-trace the subgraph in AutogradKey, so inner
513
+ # hops may receive int inputs from the shape of outer tensor inputs.
514
+ # However, CompositeExplicitAutograd won't receive SymInt inputs because it only accepts real tensor inputs.
515
+ def validate_subgraph_args_types(lifted_args: Union[Tuple[Any, ...], List[Any]]):
516
+ allowed_types = (torch.Tensor, int, torch.SymInt)
517
+ assert all(
518
+ isinstance(arg, (torch.Tensor, int, torch.SymInt)) for arg in lifted_args
519
+ ), f"{lifted_args} can only be of {allowed_types} but got {tuple(type(arg) for arg in lifted_args)}"
lib/python3.12/site-packages/torch/_higher_order_ops/while_loop.py ADDED
@@ -0,0 +1,340 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ from typing import Callable, List, Tuple, Union
3
+
4
+ import torch
5
+ import torch.utils._pytree as pytree
6
+ from torch._C import DispatchKey
7
+ from torch._higher_order_ops.utils import (
8
+ _has_potential_branch_input_alias,
9
+ _has_potential_branch_input_mutation,
10
+ _maybe_run_with_interpreter,
11
+ _set_compilation_env,
12
+ autograd_not_implemented,
13
+ diff_tensor_meta,
14
+ reenter_make_fx,
15
+ UnsupportedAliasMutationException,
16
+ validate_subgraph_args_types,
17
+ )
18
+ from torch._ops import HigherOrderOperator
19
+ from torch._subclasses.fake_tensor import FakeTensorMode
20
+ from torch.fx.experimental.proxy_tensor import (
21
+ _temp_remove_metadata_torch_function_mode,
22
+ ProxyTorchDispatchMode,
23
+ track_tensor_tree,
24
+ )
25
+ from torch.fx.passes.shape_prop import _extract_tensor_metadata
26
+
27
+
28
+ class WhileLoopOp(HigherOrderOperator):
29
+ def __init__(self) -> None:
30
+ super().__init__("while_loop")
31
+
32
+ def __call__(
33
+ self,
34
+ cond_fn: Callable,
35
+ body_fn: Callable,
36
+ carried_inputs: Tuple[Union[torch.Tensor, int, float, bool]],
37
+ additional_inputs: Tuple[Union[torch.Tensor, torch.SymInt, int], ...],
38
+ /,
39
+ ):
40
+ if not isinstance(carried_inputs, tuple):
41
+ raise RuntimeError(
42
+ f"carried_inputs must be a tuple, got {type(carried_inputs)}"
43
+ )
44
+ if not isinstance(additional_inputs, tuple):
45
+ raise RuntimeError(
46
+ f"additional_inputs must be a tuple, got {type(additional_inputs)}"
47
+ )
48
+
49
+ validate_subgraph_args_types(carried_inputs)
50
+ validate_subgraph_args_types(additional_inputs)
51
+ return super().__call__(cond_fn, body_fn, carried_inputs, additional_inputs)
52
+
53
+
54
+ while_loop_op = WhileLoopOp()
55
+
56
+
57
+ def while_loop(cond_fn, body_fn, carried_inputs):
58
+ r"""
59
+ Run body_fn(*carried_inputs) while cond_fn(*carried_inputs) returns a True scalar tensor. Returns the output of body_fn or
60
+ initial carried_inputs.
61
+
62
+ .. warning::
63
+ `torch.while_loop` is a prototype feature in PyTorch. It has limited support for input and output types and
64
+ doesn't support training currently. Please look forward to a more stable implementation in a future version of PyTorch.
65
+ Read more about feature classification at: https://pytorch.org/blog/pytorch-feature-classification-changes/#prototype
66
+
67
+ `while_loop` is a structured control flow operator. It preserves the loop semantic across the torch.compile and torch.export.
68
+
69
+ `while_loop` is equivalent to the following:
70
+
71
+ def while_loop(cond_fn, body_fn, carried_inputs):
72
+ val = carried_inputs
73
+ while cond_fn(*val):
74
+ val = body_fn(*val)
75
+ return val
76
+
77
+ Args:
78
+ cond_fn (Callable): A callable function that returns a boolean Scalar tensor.
79
+
80
+ body_fn (Callable): A callable function that takes the same inputs as `cond_fn` and returns a tuple of tensors
81
+
82
+ carried_inputs (Tuple of possibly nested dict/list/tuple of tensors): A tuple of inputs to cond_fn and body_fn. It's also
83
+ the initial value of states that are carried across iterations.
84
+
85
+ Example:
86
+
87
+ def cond_fn(iter, x):
88
+ return iter.sum() < 10
89
+
90
+ def body_fn(iter, x):
91
+ return iter + 1, x.sin()
92
+
93
+ while_loop(cond_fn, body_fn, (torch.zeros(1), torch.randn(3, 4)))
94
+
95
+ Restrictions:
96
+
97
+ - body_fn must return tensors with the same metadata (e.g.shape, dtype) as inputs.
98
+
99
+ - body_fn and cond_fn must not in-place mutate the carried_inputs. A clone before the mutation is required.
100
+
101
+ - body_fn and cond_fn must not mutate python varialbles (e.g. list/dict) created outside of the body_fn.
102
+
103
+ - body_fn and cond_fn's output cannot aliase any of the inputs. A clone is required.
104
+
105
+ .. warning::
106
+ Temporal Limitations:
107
+
108
+ - 'while_loop' only supports **inference** right now. Autograd will be supported in the future.
109
+
110
+ """
111
+ from torch._dynamo.backends.debugging import (
112
+ make_eager_backend_with_torch_function_mode,
113
+ )
114
+
115
+ # Currently, additional_inputs is not a user-facing input. It will be automatically set in dynamo.
116
+ # parameters and buffers accessed in cond_fn or body_fn or tensor closures will become additional_inputs.
117
+ additional_inputs: Tuple = ()
118
+
119
+ # The reason we flatten the output before calling into dynamo is that
120
+ # we want to create a consistent input ordering for cond_fn and body_fn.
121
+ # and we also want to the input ordering matches the output ordering.
122
+ # Also see NOTE: [why we cannot use "automatic" for while_loop]
123
+ # Construct flat cond_fn and flat_body_fn, which takes flattened inputs
124
+ flat_inputs, in_spec = pytree.tree_flatten((carried_inputs, additional_inputs))
125
+
126
+ def flat_cond_fn(*flat_args):
127
+ carried, additional = pytree.tree_unflatten(flat_args, in_spec)
128
+ return cond_fn(*carried, *additional)
129
+
130
+ def flat_body_fn(*flat_args):
131
+ carried, additional = pytree.tree_unflatten(flat_args, in_spec)
132
+ return body_fn(*carried, *additional)
133
+
134
+ if torch.compiler.is_dynamo_compiling():
135
+ return while_loop_op(flat_cond_fn, flat_body_fn, tuple(flat_inputs), tuple())
136
+
137
+ def _validate_input(cond_fn, body_fn, carried_inputs):
138
+ from torch._higher_order_ops.utils import validate_subgraph_args_types
139
+
140
+ if not callable(cond_fn) or not callable(body_fn):
141
+ raise RuntimeError("Expect cond_fn and body_fn to be callable.")
142
+
143
+ validate_subgraph_args_types(flat_inputs)
144
+
145
+ if not pytree.tree_all(lambda t: isinstance(t, torch.Tensor), carried_inputs):
146
+ raise RuntimeError(
147
+ "Expect carried_inputs to be a tuple of possibly nested dict/list/tuple that only"
148
+ f"consists of tensor leaves, but got {carried_inputs}."
149
+ )
150
+
151
+ _validate_input(cond_fn, body_fn, carried_inputs)
152
+
153
+ # Dynamo is expecting a callable with "__code__" attribute.
154
+ # We cannot directly pass cond_op to it. So we wrap it in a dummy function.
155
+ def _while_loop_op_wrapper(*args, **kwargs):
156
+ return while_loop_op(*args, **kwargs)
157
+
158
+ with _set_compilation_env(), torch._dynamo.utils.disable_cache_limit():
159
+ with _temp_remove_metadata_torch_function_mode() as metadata_mode:
160
+ with _temp_remove_metadata_torch_function_mode() as metadata_mode:
161
+ if metadata_mode:
162
+ backend = make_eager_backend_with_torch_function_mode(metadata_mode)
163
+ else:
164
+ backend = "eager"
165
+ return torch.compile(
166
+ _while_loop_op_wrapper, backend=backend, fullgraph=True
167
+ )(flat_cond_fn, flat_body_fn, tuple(flat_inputs), tuple())
168
+
169
+
170
+ @while_loop_op.py_impl(DispatchKey.CompositeExplicitAutograd)
171
+ def while_loop_dense(cond_fn, body_fn, carried_inputs, additional_inputs):
172
+ carried_vals = carried_inputs
173
+
174
+ def _is_boolean_scalar_tensor(pred):
175
+ return (
176
+ isinstance(pred, torch.Tensor)
177
+ and pred.size() == torch.Size([])
178
+ and pred.dtype == torch.bool
179
+ )
180
+
181
+ if not isinstance(carried_inputs, tuple):
182
+ raise RuntimeError(
183
+ f"carried_inputs must be a tuple but got {type(carried_inputs)}"
184
+ )
185
+
186
+ while pred := cond_fn(*carried_vals, *additional_inputs):
187
+ if not _is_boolean_scalar_tensor(pred):
188
+ raise RuntimeError(
189
+ f"cond_fn must return a boolean scalar tensor but got {pred}"
190
+ )
191
+ out = body_fn(*carried_vals, *additional_inputs)
192
+ assert isinstance(
193
+ out, tuple
194
+ ), f"body_fn should return a tuple but got {type(out)}"
195
+ assert len(out) == len(
196
+ carried_inputs
197
+ ), "body_fn should return the same number of elements as carried_inputs"
198
+ carried_vals = out
199
+ return carried_vals
200
+
201
+
202
+ while_loop_op.py_impl(DispatchKey.Autograd)(
203
+ autograd_not_implemented(while_loop_op, deferred_error=True)
204
+ )
205
+
206
+
207
+ @while_loop_op.py_impl(ProxyTorchDispatchMode)
208
+ def while_loop_tracing(mode, cond_fn, body_fn, carried_inputs, additional_inputs):
209
+ def _trace_while_loop(
210
+ proxy_mode, while_loop_op, cond_fn, body_fn, carried_inputs, additional_inputs
211
+ ):
212
+ cond_graph = reenter_make_fx(cond_fn)(*carried_inputs, *additional_inputs)
213
+ body_graph = reenter_make_fx(body_fn)(*carried_inputs, *additional_inputs)
214
+
215
+ next_name = None
216
+ i = 0
217
+ while not next_name:
218
+ candidate = f"while_loop_cond_graph_{i}"
219
+ if hasattr(proxy_mode.tracer.root, candidate):
220
+ i += 1
221
+ else:
222
+ next_name = candidate
223
+ cond_graph_name = next_name
224
+ body_graph_name = f"while_loop_body_graph_{i}"
225
+ assert not hasattr(proxy_mode.tracer.root, body_graph_name)
226
+
227
+ proxy_mode.tracer.root.register_module(cond_graph_name, cond_graph)
228
+ proxy_mode.tracer.root.register_module(body_graph_name, body_graph)
229
+
230
+ args = (cond_graph, body_graph, carried_inputs, additional_inputs)
231
+
232
+ proxy_args = pytree.tree_map(proxy_mode.tracer.unwrap_proxy, args)
233
+
234
+ out_proxy = proxy_mode.tracer.create_proxy(
235
+ "call_function", while_loop_op, proxy_args, {}, name="while_loop"
236
+ )
237
+
238
+ out = while_loop_op(cond_graph, body_graph, carried_inputs, additional_inputs)
239
+ return track_tensor_tree(
240
+ out, out_proxy, constant=None, tracer=proxy_mode.tracer
241
+ )
242
+
243
+ return _trace_while_loop(
244
+ mode, while_loop_op, cond_fn, body_fn, carried_inputs, additional_inputs
245
+ )
246
+
247
+
248
+ def check_outputs_carry_consistency(
249
+ outs: List[torch.Tensor], carries: List[torch.Tensor]
250
+ ) -> None:
251
+ all_diffs_in_meta = []
252
+ for out, cry in zip(outs, carries):
253
+ if diff := diff_tensor_meta(
254
+ _extract_tensor_metadata(cry), _extract_tensor_metadata(out)
255
+ ):
256
+ all_diffs_in_meta.append(",".join(diff))
257
+ if all_diffs_in_meta:
258
+ diff_str = "\n".join(all_diffs_in_meta)
259
+ raise RuntimeError(
260
+ f"Expected carried_inputs and body outputs return tensors with same metadata but found:\n{diff_str}"
261
+ )
262
+
263
+
264
+ @while_loop_op.py_impl(FakeTensorMode)
265
+ def while_loop_fake_tensor_mode(
266
+ mode, cond_fn, body_fn, carried_inputs, additional_inputs
267
+ ):
268
+ with mode:
269
+ # NOTE: [Handling unback symints created in subgraph of while_loop]
270
+ # The idea is that the scope of unbacked symints are limited to the subgraph.
271
+ #
272
+ # We're implementing the fake tensor mode of while_loop operator.
273
+ # and we run body_fn once to get an fake output.
274
+ # Let's only consider tensor output for now:
275
+ #
276
+ # Case 1:
277
+ # if the unbacked symints is local to the subgraph e.g.
278
+ # def body_fn(it, x):
279
+ # nz = x.nonzero()
280
+ # return it+1. nz.sum()
281
+ # we can just ignore the newly created unbacked symints because it has
282
+ # no effect on the output of while_loop and it's tracked when we tracing.
283
+ # the subgraph.
284
+ #
285
+ # Case 2.1:
286
+ # if the unbacked symints are part of output of while_loop e.g.
287
+ # def body_fn(it, x):
288
+ # nz = x.nonzero()
289
+ # return it+1, nz
290
+ # This will fail the shape check because in each iteration, the carried_input's shape
291
+ # must match the output shape as nz.shape contains newly allocated unbacked symint, this
292
+ # won't match the carried_input's shape.
293
+ #
294
+ # Case 2.2:
295
+ # if the unbacked symints are part of carried_inputs e.g.
296
+ # nz = a.nonzero()
297
+ # body_fn(it, nz):
298
+ # return it+1. nz.sin() + 1,
299
+ # There's no new unbacked symints allocated in subgraph, so we're safe.
300
+ with mode.shape_env.ignore_fresh_unbacked_symbols():
301
+ # body_fn return output with the same pytree and tensor meta data as carried_inputs
302
+ # so we could just return the output after one iteration.
303
+ body_outs = body_fn(*carried_inputs, *additional_inputs)
304
+ check_outputs_carry_consistency(body_outs, carried_inputs)
305
+ return body_outs
306
+
307
+
308
+ @while_loop_op.py_functionalize_impl
309
+ def while_loop_func(ctx, cond_fn, body_fn, carried_inputs, additional_inputs):
310
+ unwrapped_carried_inputs = ctx.unwrap_tensors(carried_inputs)
311
+ unwrapped_additional_inputs = ctx.unwrap_tensors(additional_inputs)
312
+ unwrapped_inputs = unwrapped_carried_inputs + unwrapped_additional_inputs
313
+ with ctx.redispatch_to_next() as m:
314
+ functional_cond_fn = ctx.functionalize(_maybe_run_with_interpreter(cond_fn))
315
+ functional_body_fn = ctx.functionalize(_maybe_run_with_interpreter(body_fn))
316
+ pre_dispatch = hasattr(ctx, "mode") and ctx.mode.pre_dispatch
317
+ for fn, fn_name in [
318
+ (functional_cond_fn, "cond_fn"),
319
+ (functional_body_fn, "body_fn"),
320
+ ]:
321
+ if _has_potential_branch_input_mutation(
322
+ fn, unwrapped_inputs, pre_dispatch=pre_dispatch
323
+ ):
324
+ raise UnsupportedAliasMutationException(
325
+ f"torch.while_loop's {fn_name} might be modifying the input!"
326
+ )
327
+
328
+ if _has_potential_branch_input_alias(
329
+ fn, unwrapped_inputs, pre_dispatch=pre_dispatch
330
+ ):
331
+ raise UnsupportedAliasMutationException(
332
+ f"torch.while_loop's {fn_name} might be aliasing the input!"
333
+ )
334
+ ret = while_loop_op(
335
+ functional_cond_fn,
336
+ functional_body_fn,
337
+ unwrapped_carried_inputs,
338
+ unwrapped_additional_inputs,
339
+ )
340
+ return ctx.wrap_tensors(ret)
lib/python3.12/site-packages/torch/_higher_order_ops/wrap.py ADDED
@@ -0,0 +1,240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ import inspect
3
+ import itertools
4
+ import logging
5
+ from typing import Optional
6
+
7
+ from torch._logging import warning_once
8
+ from torch._ops import HigherOrderOperator
9
+ from torch.types import _dtype
10
+ from torch.utils.checkpoint import checkpoint, CheckpointPolicy
11
+
12
+
13
+ log = logging.getLogger(__name__)
14
+
15
+ uid = itertools.count(1)
16
+
17
+
18
+ # Used for testing the HigherOrderOperator mechanism
19
+ class Wrap(HigherOrderOperator):
20
+ def __init__(self) -> None:
21
+ super().__init__("wrap")
22
+
23
+ def __call__(self, func, *args, **kwargs):
24
+ # Dynamo already traces the body of HigherOrderOp beforehand when it
25
+ # so no need to trace into it.
26
+ import torch._dynamo # noqa: F401
27
+ from torch._dynamo import disable
28
+
29
+ @disable
30
+ def wrapper():
31
+ result = func(*args, **kwargs)
32
+ return result
33
+
34
+ return wrapper()
35
+
36
+
37
+ wrap = Wrap()
38
+
39
+
40
+ class WrapWithSetGradEnabled(HigherOrderOperator):
41
+ def __init__(self) -> None:
42
+ super().__init__("wrap_with_set_grad_enabled")
43
+
44
+ def __call__(self, enable_grad, wrapped_func, *args, **kwargs):
45
+ # Dynamo already traces the body of HigherOrderOp beforehand when it
46
+ # so no need to trace into it.
47
+ import torch._dynamo # noqa: F401
48
+ from torch._dynamo import disable
49
+
50
+ @disable
51
+ def wrapper():
52
+ with torch.set_grad_enabled(enable_grad):
53
+ return wrapped_func(*args, **kwargs)
54
+
55
+ return wrapper()
56
+
57
+
58
+ wrap_with_set_grad_enabled = WrapWithSetGradEnabled()
59
+
60
+
61
+ class WrapWithAutocast(HigherOrderOperator):
62
+ def __init__(self):
63
+ super().__init__("wrap_with_autocast")
64
+
65
+ def __call__(
66
+ self,
67
+ device_type: str,
68
+ dtype: Optional[_dtype],
69
+ enabled: bool,
70
+ cache_enabled: Optional[bool],
71
+ wrapped_func,
72
+ *args,
73
+ **kwargs,
74
+ ):
75
+ # Dynamo already traces the body of HigherOrderOp beforehand when it
76
+ # so no need to trace into it.
77
+ import torch._dynamo # noqa: F401
78
+ from torch._dynamo import disable
79
+
80
+ @disable
81
+ def wrapper():
82
+ with torch.autocast(device_type, dtype, enabled, cache_enabled):
83
+ return wrapped_func(*args, **kwargs)
84
+
85
+ return wrapper()
86
+
87
+
88
+ wrap_with_autocast = WrapWithAutocast()
89
+
90
+
91
+ class WrapActivationCheckpoint(HigherOrderOperator):
92
+ """
93
+ This operator is used to wrap torch.utils.checkpoint. This avoids
94
+ TorchDynamo to look into saved tensor hooks and directly passes the control
95
+ to AOT Autograd, which is ok with tracing saved tensor hooks. As a result of
96
+ AOT tracing torch.utils.checkpoint code, we have a backward graph with
97
+ recomputed forward nodes.
98
+
99
+ However, we might deprecate this operator soon. The difficulty arises in the
100
+ functionalization of rng ops. Today, there are two different
101
+ functionalization of rng ops - one at AOT autograd and other at Inductor.
102
+ And they are difficult to map to each other. The rng states also complicate
103
+ pattern matching in Inductor. Due to the ease of implementation, we are
104
+ currently inclined towards functionalization at Inductor level, which means
105
+ that duplication/recomputation is done as a compiler pass in the
106
+ partitioners. See TagActivationCheckpoint for more information.
107
+ """
108
+
109
+ def __init__(self) -> None:
110
+ super().__init__("wrap_activation_checkpoint", cacheable=False)
111
+
112
+ def __call__(self, function, *args, **kwargs):
113
+ # use_reentrant is set to False because this op is going to be traced.
114
+ # And we ensure that AOT Autograd traces through the non reentrant
115
+ # version of checkpointing.
116
+ import torch.fx.traceback as fx_traceback
117
+ from torch.fx import Interpreter
118
+
119
+ kwargs["use_reentrant"] = False
120
+ kwargs["preserve_rng_state"] = False
121
+ # Using interpreter allows preservation of metadata through torch.compile stack.
122
+ with fx_traceback.preserve_node_meta():
123
+ return checkpoint(Interpreter(function).run, *args, **kwargs)
124
+
125
+
126
+ wrap_activation_checkpoint = WrapActivationCheckpoint()
127
+
128
+
129
+ class TagActivationCheckpoint(HigherOrderOperator):
130
+ """
131
+ This operator is supposed to be used only with torch.compile stack. This
132
+ accepts a Fx graph module which needs to be checkpointed. This operator adds
133
+ "recomputable" tag to the nodes of the Fx graph that should be recomputed.
134
+
135
+ The goal is to:
136
+ 1. Avoid using Dynamo to trace through saved tensor hooks.
137
+ 2. For selective checkpointing case, let AOTAutograd trace through
138
+ saved tensor hooks but has special logic with TorchDispatchMode to override
139
+ the usual saved_tensor_hooks fn logic in order to tag the nodes.
140
+ 3. Rely on the partitioners to actually duplicate the nodes.
141
+ This sits well in the torch.compile stack, because by the time graph
142
+ reaches partitioner, inductor has already run its functionalization of rng
143
+ ops (by setting fixed seed for each random op, see `replace_random_passes`).
144
+ Therefore, the duplication of nodes, by design, respects the rng states in
145
+ the forward and recomputed forward in backward.
146
+ """
147
+
148
+ def __init__(self) -> None:
149
+ super().__init__("tag_activation_checkpoint", cacheable=False)
150
+
151
+ @staticmethod
152
+ def divide_kwargs(kwargs):
153
+ """
154
+ checkpoint fn can have mixed kwargs between checkpointed fn and
155
+ checkpoint fn itself. For example
156
+ >> def gn(x, y, z=None):
157
+ >> a = torch.matmul(x, y)
158
+ >> if z is not None:
159
+ >> return torch.matmul(a, z)
160
+ >> return a
161
+ >> def fn(x, y, z):
162
+ >> return torch.cos(checkpoint(gn, x, y, use_reentrant=False, z=z))
163
+ In the above case, z belongs to checkpointed function gn, but
164
+ use_reentrant belongs to the checkpoint function. This function splits
165
+ the kwargs into checkpoint_kwargs and gmod_kwargs (or
166
+ checkpointed_fn_kwargs).
167
+ We do sorting to ensure same graph from run to run for better
168
+ debuggability. It is not required for correctness.
169
+ """
170
+ ckpt_signature = inspect.signature(checkpoint)
171
+ checkpoint_keys = set()
172
+ for name in ckpt_signature.parameters:
173
+ if name in ("function", "args", "kwargs"):
174
+ continue
175
+ checkpoint_keys.add(name)
176
+
177
+ # `preserve_rng_state` is not a regular kwarg
178
+ checkpoint_keys.add("preserve_rng_state")
179
+
180
+ checkpoint_kwargs = {
181
+ name: kwargs[name] for name in kwargs.keys() if name in checkpoint_keys
182
+ }
183
+ gmod_kwargs = {
184
+ name: kwargs[name] for name in kwargs.keys() if name not in checkpoint_keys
185
+ }
186
+ return checkpoint_kwargs, gmod_kwargs
187
+
188
+ def tag_nodes(self, gmod, is_sac):
189
+ unique_graph_id = next(uid)
190
+ for node in gmod.graph.nodes:
191
+ if node.op in ("call_function", "call_method", "call_module"):
192
+ node.meta["ac_graph_id"] = unique_graph_id
193
+ if is_sac:
194
+ # For selective checkpointing, we will populate this tag later in _CachingTorchDispatchMode.
195
+ node.meta["recompute"] = None
196
+ else:
197
+ # Under vanilla activation checkpointing, all nodes should be recomputed.
198
+ node.meta["recompute"] = CheckpointPolicy.PREFER_RECOMPUTE
199
+ return gmod
200
+
201
+ def __call__(self, gmod, *args, **kwargs):
202
+ import torch.fx.traceback as fx_traceback
203
+ from torch.fx import Interpreter
204
+
205
+ if "_checkpoint_context_fn" in gmod.meta:
206
+ warning_once(
207
+ log,
208
+ """
209
+ Detected that context_fn is passed to torch.utils.checkpoint under torch.compile.
210
+ Please make sure the checkpointed region does not contain in-place ops (e.g. torch.relu_).
211
+ """,
212
+ )
213
+ # use_reentrant is set to False because this op is going to be traced.
214
+ # And we ensure that AOT Autograd traces through the non reentrant
215
+ # version of checkpointing.
216
+ kwargs["use_reentrant"] = False
217
+ # preserve_rng_state is set to False because we want to prevent AOTAutograd from tracing through
218
+ # `torch.random.fork_rng` op (which is not supported yet under CUDA).
219
+ # This doesn't mean that we don't preserve RNG state. Instead, we will always preserve RNG state
220
+ # regardless of this flag (by doing RNG functionalization via `replace_random_passes` in Inductor
221
+ # instead of in AOTAutograd).
222
+ kwargs["preserve_rng_state"] = False
223
+ kwargs["context_fn"] = gmod.meta["_checkpoint_context_fn"]
224
+ # We first tag all nodes as "recompute" in this graph, and then we undo the "recompute" tag
225
+ # for specific nodes in _CachingTorchDispatchMode in torch/utils/checkpoint.py.
226
+ gmod = self.tag_nodes(gmod, is_sac=True)
227
+ # Using interpreter allows preservation of metadata through torch.compile stack.
228
+ with fx_traceback.preserve_node_meta():
229
+ return checkpoint(Interpreter(gmod).run, *args, **kwargs)
230
+ else:
231
+ gmod = self.tag_nodes(gmod, is_sac=False)
232
+ # Using interpreter allows preservation of metadata through torch.compile stack.
233
+ # TODO: We want to use the same `checkpoint(Interpreter(gmod).run, *args, **kwargs)` here
234
+ # as the `context_fn != None` case, but that depends on in-place op support in TorchDispatchMode + torch.compile.
235
+ # (for details on in-place op issue, run `test_compile_selective_checkpoint_inplace_op` unit test)
236
+ with fx_traceback.preserve_node_meta():
237
+ return Interpreter(gmod).run(*args)
238
+
239
+
240
+ tag_activation_checkpoint = TagActivationCheckpoint()
lib/python3.12/site-packages/torch/_prims/__init__.py ADDED
@@ -0,0 +1,2946 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ import operator
3
+ from enum import Enum
4
+ from functools import partial, reduce
5
+ from typing import Callable, List, Optional, Sequence, Tuple, Type, Union
6
+
7
+ import torch
8
+ import torch._prims_common as utils
9
+ import torch.library
10
+ from torch import sym_float, Tensor
11
+ from torch._C import _get_default_device
12
+ from torch._higher_order_ops.effects import new_token_tensor
13
+ from torch._library.utils import is_functional_schema
14
+ from torch._prims.debug_prims import register_debug_prims
15
+ from torch._prims.rng_prims import register_rng_prims
16
+ from torch._prims_common import (
17
+ Dim,
18
+ DimsSequenceType,
19
+ DimsType,
20
+ IntLike,
21
+ Number,
22
+ NumberType,
23
+ RETURN_TYPE,
24
+ ShapeType,
25
+ StrideType,
26
+ TensorLike,
27
+ TensorLikeType,
28
+ type_to_dtype,
29
+ )
30
+ from torch._prims_common.wrappers import backwards_not_supported
31
+ from torch._subclasses.fake_tensor import FakeTensor, FakeTensorMode
32
+ from torch.overrides import handle_torch_function, has_torch_function
33
+ from torch.utils._pytree import tree_flatten, tree_map, tree_unflatten
34
+
35
+
36
+ prim = torch.library.Library("prims", "DEF")
37
+ prim_impl = torch.library.Library("prims", "IMPL", "CompositeExplicitAutograd")
38
+ prim_backend_select_impl = torch.library.Library("prims", "IMPL", "BackendSelect")
39
+ prim_autograd_impl = torch.library.Library("prims", "IMPL", "Autograd")
40
+ prim_meta_impl = torch.library.Library("prims", "IMPL", "Meta")
41
+
42
+ # Experimental module containing prototype "primitive" operations.
43
+
44
+ __all__ = [
45
+ #
46
+ # Common datastructures and helpers
47
+ #
48
+ "RETURN_TYPE",
49
+ #
50
+ # Elementwise unary prims
51
+ #
52
+ "abs",
53
+ "acos",
54
+ "acosh",
55
+ "asin",
56
+ "asinh",
57
+ "atan",
58
+ "atanh",
59
+ "cos",
60
+ "cosh",
61
+ "bessel_i0",
62
+ "bessel_i0e",
63
+ "bessel_i1",
64
+ "bessel_i1e",
65
+ "bessel_j0",
66
+ "bessel_j1",
67
+ "bitwise_not",
68
+ "cbrt",
69
+ "ceil",
70
+ "conj_physical",
71
+ "digamma",
72
+ "erf",
73
+ "erf_inv",
74
+ "erfc",
75
+ "erfcx",
76
+ "exp",
77
+ "expm1",
78
+ "exp2",
79
+ "fill",
80
+ "floor",
81
+ "imag",
82
+ "isfinite",
83
+ "lgamma",
84
+ "log",
85
+ "log1p",
86
+ "log2",
87
+ "log10",
88
+ "ndtri",
89
+ "neg",
90
+ "real",
91
+ "reciprocal",
92
+ "round",
93
+ "sign",
94
+ "signbit",
95
+ "sin",
96
+ "sinh",
97
+ "spherical_bessel_j0",
98
+ "sqrt",
99
+ "tan",
100
+ "tanh",
101
+ "trunc",
102
+ #
103
+ # Elementwise binary prims
104
+ #
105
+ "add",
106
+ "atan2",
107
+ "bitwise_and",
108
+ "bitwise_or",
109
+ "bitwise_xor",
110
+ # 'complex', # needs custom meta
111
+ "div",
112
+ "eq",
113
+ "fmax",
114
+ "fmin",
115
+ "fmod",
116
+ "frexp",
117
+ "gcd",
118
+ "ge",
119
+ "gt",
120
+ "hypot",
121
+ "igamma",
122
+ "igammac",
123
+ "le",
124
+ "lt",
125
+ "maximum",
126
+ "minimum",
127
+ "mul",
128
+ "ne",
129
+ "nextafter",
130
+ "pow",
131
+ "remainder",
132
+ "rsqrt",
133
+ "shift_left",
134
+ "shift_right_arithmetic",
135
+ "shift_right_logical", # not implemented
136
+ "sub",
137
+ "zeta",
138
+ #
139
+ # View prims
140
+ #
141
+ "as_strided",
142
+ "broadcast_in_dim",
143
+ "collapse_view",
144
+ "conj",
145
+ "expand_dims",
146
+ "slice",
147
+ "slice_in_dim", # implemented using slice -- make this a ref?
148
+ "split_dim",
149
+ "squeeze",
150
+ "transpose",
151
+ "view_of",
152
+ "view_element_type",
153
+ #
154
+ # Functionalized view mutations
155
+ #
156
+ "as_strided_scatter",
157
+ #
158
+ # Shape prims
159
+ #
160
+ "collapse",
161
+ "cat",
162
+ "reshape",
163
+ "rev",
164
+ #
165
+ # Conditional prims
166
+ #
167
+ "where",
168
+ #
169
+ # Data conversion and movement prims
170
+ #
171
+ "clone",
172
+ "convert_element_type",
173
+ "device_put",
174
+ "item",
175
+ "maximum_value",
176
+ "minimum_value",
177
+ "copy_strided",
178
+ #
179
+ # Inplace prims
180
+ #
181
+ "copy_to",
182
+ "resize",
183
+ # "_set", # Commented out, see note below
184
+ #
185
+ # Reduction prims
186
+ #
187
+ "amax",
188
+ "amin",
189
+ "prod",
190
+ "sum",
191
+ "xor_sum",
192
+ "var",
193
+ #
194
+ # Tensor Creation Prims
195
+ #
196
+ "empty_strided",
197
+ "empty_permuted",
198
+ "scalar_tensor",
199
+ "iota",
200
+ #
201
+ # Linear algebra (linalg) Prims
202
+ #
203
+ "svd",
204
+ #
205
+ # Randomness Prims
206
+ #
207
+ "normal",
208
+ "_uniform_helper",
209
+ #
210
+ # FFT prims
211
+ #
212
+ "fft_r2c",
213
+ "fft_c2c",
214
+ "fft_c2r",
215
+ #
216
+ # prims for making/sinking tokens
217
+ #
218
+ "_make_token",
219
+ "_sink_tokens",
220
+ ]
221
+
222
+
223
+ def TensorMeta(
224
+ tensorlike: Optional[Union[NumberType, torch.Tensor]] = None,
225
+ *,
226
+ shape: Optional[ShapeType] = None,
227
+ strides: Optional[StrideType] = None,
228
+ dtype: Optional[torch.dtype] = None,
229
+ device: Optional[Union[torch.device, str]] = None,
230
+ ):
231
+ if isinstance(tensorlike, Number):
232
+ assert not shape and (shape is None or isinstance(shape, Sequence))
233
+ assert not strides and (strides is None or isinstance(strides, Sequence))
234
+ inferred_shape: Tuple[int, ...] = ()
235
+ inferred_strides: Tuple[int, ...] = ()
236
+ inferred_dtype = type_to_dtype(type(tensorlike))
237
+ inferred_device = torch.device("cpu")
238
+ # TODO: This looks wrong, a number that is wrapped into a tensor
239
+ # needs to behave differently than a scalar tensor for type
240
+ # promotion purposes
241
+ elif tensorlike is not None:
242
+ assert isinstance(tensorlike, torch.Tensor)
243
+ inferred_shape = tuple(tensorlike.shape)
244
+ inferred_strides = tuple(tensorlike.stride())
245
+ inferred_dtype = tensorlike.dtype
246
+ inferred_device = tensorlike.device
247
+ else:
248
+ # If no tensorlike "example" is given then all metadata
249
+ # must be provided explicitly
250
+ assert shape is not None
251
+ assert strides is not None
252
+ assert dtype is not None
253
+ assert device is not None
254
+
255
+ shape = inferred_shape if shape is None else tuple(shape) # type: ignore[possibly-undefined]
256
+ strides = inferred_strides if strides is None else tuple(strides) # type: ignore[possibly-undefined]
257
+ dtype = inferred_dtype if dtype is None else dtype # type: ignore[possibly-undefined]
258
+ device = inferred_device if device is None else device # type: ignore[possibly-undefined]
259
+
260
+ if isinstance(device, str):
261
+ device = torch.device(device)
262
+
263
+ return torch.empty_strided(shape, strides, dtype=dtype, device=device)
264
+
265
+
266
+ def _make_prim(
267
+ *,
268
+ schema: str,
269
+ return_type: Union[RETURN_TYPE, Tuple[RETURN_TYPE, ...]],
270
+ meta: Callable,
271
+ impl_aten: Callable,
272
+ doc: str,
273
+ tags: Optional[Sequence[torch.Tag]] = None,
274
+ use_old_custom_ops_api: bool = False,
275
+ register_conj_neg_fallthrough: bool = False,
276
+ ):
277
+ """
278
+ Creates a primitive operation.
279
+
280
+ """
281
+
282
+ def _prim_impl(*args, **kwargs):
283
+ # always run the meta function because aten implementation will
284
+ # typically accept more inputs (e.g., it will do promotion and
285
+ # broadcasting) which we want to reject
286
+ meta(*args, **kwargs)
287
+ return impl_aten(*args, **kwargs)
288
+
289
+ # Right now prims don't support autograd (we can and should add an
290
+ # argument that provides an implementation for backward here.) Because we
291
+ # don't have derivative formulas, we must setup a custom autograd function
292
+ # that raises an error if backwards is invoked
293
+ def _autograd_impl(*args, **kwargs):
294
+ return backwards_not_supported(_prim)(*args, **kwargs)
295
+
296
+ def _backend_select_impl(*args, **kwargs):
297
+ if kwargs.get("device") and kwargs["device"].type == "meta":
298
+ return meta(*args, **kwargs)
299
+ if any(isinstance(x, torch.device) and x.type == "meta" for x in args):
300
+ return meta(*args, **kwargs)
301
+ else:
302
+ return _prim_impl(*args, **kwargs)
303
+
304
+ name = schema.split("(")[0]
305
+ schema = schema[len(name) :]
306
+
307
+ # register non-functional ops with old custom ops API
308
+ cpp_schema = torch._C.parse_schema(name + schema)
309
+ if use_old_custom_ops_api or not is_functional_schema(cpp_schema):
310
+ prim.define(name + schema, tags=torch.Tag.pt2_compliant_tag)
311
+ prim_impl.impl(name, _prim_impl)
312
+ prim_autograd_impl.impl(name, _autograd_impl)
313
+ prim_meta_impl.impl(name, meta)
314
+ else:
315
+ mutates_args = [
316
+ arg.name
317
+ for arg in cpp_schema.arguments
318
+ if arg.alias_info is not None and arg.alias_info.is_write
319
+ ]
320
+ prim_def = torch.library.custom_op(
321
+ "prims::" + name,
322
+ _prim_impl,
323
+ mutates_args=tuple(mutates_args),
324
+ schema=schema,
325
+ )
326
+ prim_def.register_fake(meta)
327
+
328
+ # all view ops get conj/neg fallthroughs
329
+ if return_type == RETURN_TYPE.VIEW or register_conj_neg_fallthrough:
330
+ prim_def._lib.impl(name, torch.library.fallthrough_kernel, "Conjugate")
331
+ prim_def._lib.impl(name, torch.library.fallthrough_kernel, "Negative")
332
+
333
+ _prim_packet = getattr(torch._ops.ops.prims, name)
334
+ _prim = _prim_packet.default
335
+ if tags:
336
+ _prim._tags = tags
337
+ elif aten_packet := getattr(torch.ops.aten, name, None):
338
+ overload_tags = [
339
+ getattr(aten_packet, overload).tags for overload in aten_packet.overloads()
340
+ ]
341
+ tags_intersection = set(overload_tags[0])
342
+ tags_intersection.intersection_update(*overload_tags[1:])
343
+
344
+ # dont inadvertently add to prim ops
345
+ tags_intersection.discard(torch.Tag.core)
346
+ # causes errors with python ref executor tests, none of the
347
+ # data dependent pytorch ops actually decompose to prims
348
+ tags_intersection.discard(torch.Tag.data_dependent_output)
349
+
350
+ # iter over first tags for determinism
351
+ _prim._tags = tuple(t for t in overload_tags[0] if t in tags_intersection)
352
+
353
+ from torch._subclasses.fake_tensor import contains_tensor_types
354
+
355
+ if not any(contains_tensor_types(a.type) for a in _prim._schema.arguments) or str(
356
+ _prim
357
+ ) in [
358
+ # See https://github.com/pytorch/pytorch/issues/103532
359
+ "prims.device_put.default"
360
+ ]:
361
+ prim_backend_select_impl.impl(name, _backend_select_impl)
362
+
363
+ for p in (_prim_packet, _prim):
364
+ p.__doc__ = doc
365
+ p.return_type = return_type # type: ignore[attr-defined]
366
+
367
+ p.schema = schema
368
+ p.prim_impl = _prim_impl
369
+ p.prim_meta_impl = meta
370
+ p.impl_aten = impl_aten
371
+
372
+ return _prim
373
+
374
+
375
+ class ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND(Enum):
376
+ DEFAULT = (0,)
377
+ INT_TO_FLOAT = (2,)
378
+ ALWAYS_BOOL = (3,)
379
+ COMPLEX_TO_FLOAT = (4,)
380
+
381
+
382
+ # TODO: implement dtype validation here, too, or on the corresponding refs
383
+ def _prim_elementwise_meta(
384
+ *args,
385
+ type_promotion: ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND,
386
+ args_with_fixed_dtypes: Optional[Tuple[TensorLikeType, ...]] = None,
387
+ ) -> FakeTensor:
388
+ """
389
+ Meta function for elementwise operations that produce outputs in the same dtype
390
+ as their inputs.
391
+
392
+ Stride logic is currently incorrect.
393
+ """
394
+
395
+ assert len(args) > 0
396
+
397
+ utils.check_same_dtype(*args)
398
+
399
+ args_ = list(args)
400
+ if args_with_fixed_dtypes is not None:
401
+ args_ = list(args_with_fixed_dtypes) + args_
402
+
403
+ utils.check_same_device(*args_, allow_cpu_scalar_tensors=True)
404
+ utils.check_same_shape(*args_, allow_cpu_scalar_tensors=True)
405
+
406
+ l2p_perm = utils.compute_elementwise_output_logical_to_physical_perm(*args_)
407
+ shape = utils.extract_shape(*args_, allow_cpu_scalar_tensors=True)
408
+
409
+ # Acquires the dtype
410
+ dtype = None
411
+ scalar_type = None
412
+ for arg in args:
413
+ if isinstance(arg, TensorLike):
414
+ if not utils.is_cpu_scalar_tensor(arg):
415
+ dtype = arg.dtype
416
+ break
417
+ else:
418
+ dtype = arg.dtype
419
+ elif isinstance(arg, Number):
420
+ scalar_type = type(arg)
421
+
422
+ if dtype is None and scalar_type is not None:
423
+ dtype = utils.type_to_dtype(scalar_type)
424
+
425
+ # Acquires the device (if it exists) or number
426
+ device = None
427
+ number = None
428
+ for arg in args_:
429
+ if isinstance(arg, TensorLike):
430
+ if utils.is_cpu_scalar_tensor(arg):
431
+ if device is None:
432
+ device = arg.device
433
+ # keep going, in case there is a cuda tensor later
434
+ else:
435
+ device = arg.device
436
+ break
437
+
438
+ elif isinstance(arg, Number):
439
+ if number is None:
440
+ number = arg
441
+
442
+ # NOTE: type promotion behavior here is mostly hidden from tests because
443
+ # references will typically handle the type promotion properly even if this doesn't
444
+ # (but getting it wrong will cause too many casts to be inserted in traces!)
445
+ if device is not None:
446
+ assert dtype is not None
447
+ if type_promotion == ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT:
448
+ dtype = dtype
449
+ elif type_promotion == ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.ALWAYS_BOOL:
450
+ dtype = torch.bool
451
+ elif type_promotion == ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.INT_TO_FLOAT:
452
+ if utils.is_integer_dtype(dtype) or utils.is_boolean_dtype(dtype):
453
+ dtype = torch.get_default_dtype()
454
+ elif type_promotion == ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.COMPLEX_TO_FLOAT:
455
+ if utils.is_complex_dtype(dtype):
456
+ dtype = utils.corresponding_real_dtype(dtype)
457
+ else:
458
+ dtype = dtype
459
+
460
+ assert shape is not None
461
+ return torch.empty_permuted(shape, l2p_perm, device=device, dtype=dtype) # type: ignore[return-value]
462
+
463
+ # Number case
464
+ # TODO: fix number type promotion (bool, complex->float)
465
+
466
+ # For now for symint/float, just implementing the common / simple cases of (int,float,symint,symfloat)
467
+ seen_float = False
468
+ if isinstance(number, (torch.SymInt, torch.SymFloat)):
469
+ for a in args:
470
+ assert isinstance(a, (int, float, torch.SymInt, torch.SymFloat)), "NYI"
471
+ seen_float = seen_float or isinstance(a, (float, torch.SymFloat))
472
+ if seen_float:
473
+ number = sym_float(number)
474
+
475
+ return TensorMeta(number) # type: ignore[arg-type]
476
+
477
+
478
+ def _complex_only_elementwise_meta(*args, **kwargs):
479
+ torch._check(
480
+ utils.is_complex_dtype(args[0].dtype), lambda: "Only complex dtype is supported"
481
+ )
482
+ return _prim_elementwise_meta(*args, **kwargs)
483
+
484
+
485
+ def _make_elementwise_unary_prim(
486
+ name: str, *, type_promotion: ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND, **kwargs
487
+ ):
488
+ """
489
+ Creates an elementwise unary prim.
490
+ """
491
+
492
+ return _make_prim(
493
+ schema=f"{name}(Tensor self) -> Tensor",
494
+ meta=partial(_prim_elementwise_meta, type_promotion=type_promotion),
495
+ return_type=RETURN_TYPE.NEW,
496
+ **kwargs,
497
+ )
498
+
499
+
500
+ def _make_elementwise_binary_prim(
501
+ name: str, *, type_promotion: ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND, **kwargs
502
+ ):
503
+ """
504
+ Creates an elementwise binary prim.
505
+ """
506
+
507
+ return _make_prim(
508
+ schema=f"{name}(Tensor self, Tensor other) -> Tensor",
509
+ meta=partial(_prim_elementwise_meta, type_promotion=type_promotion),
510
+ return_type=RETURN_TYPE.NEW,
511
+ **kwargs,
512
+ )
513
+
514
+
515
+ def _not_impl(*args, **kwargs):
516
+ raise NotImplementedError
517
+
518
+
519
+ #
520
+ # Elementwise unary operations
521
+ #
522
+
523
+
524
+ abs = _make_elementwise_unary_prim(
525
+ "abs",
526
+ impl_aten=torch.abs,
527
+ doc="",
528
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.COMPLEX_TO_FLOAT,
529
+ )
530
+
531
+ acos = _make_elementwise_unary_prim(
532
+ "acos",
533
+ impl_aten=torch.acos,
534
+ doc="",
535
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
536
+ )
537
+
538
+ acosh = _make_elementwise_unary_prim(
539
+ "acosh",
540
+ impl_aten=torch.acosh,
541
+ doc="",
542
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
543
+ )
544
+
545
+ asin = _make_elementwise_unary_prim(
546
+ "asin",
547
+ impl_aten=torch.asin,
548
+ doc="",
549
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
550
+ )
551
+
552
+ asinh = _make_elementwise_unary_prim(
553
+ "asinh",
554
+ impl_aten=torch.asinh,
555
+ doc="",
556
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
557
+ )
558
+
559
+ atan = _make_elementwise_unary_prim(
560
+ "atan",
561
+ impl_aten=torch.atan,
562
+ doc="",
563
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
564
+ )
565
+
566
+ atanh = _make_elementwise_unary_prim(
567
+ "atanh",
568
+ impl_aten=torch.atanh,
569
+ doc="",
570
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
571
+ )
572
+
573
+ cos = _make_elementwise_unary_prim(
574
+ "cos",
575
+ impl_aten=torch.cos,
576
+ doc="",
577
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
578
+ )
579
+
580
+ cosh = _make_elementwise_unary_prim(
581
+ "cosh",
582
+ impl_aten=torch.cosh,
583
+ doc="",
584
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
585
+ )
586
+
587
+ bessel_j0 = _make_elementwise_unary_prim(
588
+ "bessel_j0",
589
+ impl_aten=torch.special.bessel_j0,
590
+ doc="",
591
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
592
+ )
593
+
594
+ bessel_j1 = _make_elementwise_unary_prim(
595
+ "bessel_j1",
596
+ impl_aten=torch.special.bessel_j1,
597
+ doc="",
598
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
599
+ )
600
+
601
+ bessel_i0 = _make_elementwise_unary_prim(
602
+ "bessel_i0",
603
+ impl_aten=torch.i0,
604
+ doc="",
605
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
606
+ )
607
+
608
+ bessel_i0e = _make_elementwise_unary_prim(
609
+ "bessel_i0e",
610
+ impl_aten=torch.special.i0e,
611
+ doc="",
612
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
613
+ )
614
+
615
+ bessel_i1 = _make_elementwise_unary_prim(
616
+ "bessel_i1",
617
+ impl_aten=torch.special.i1,
618
+ doc="",
619
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
620
+ )
621
+
622
+ bessel_i1e = _make_elementwise_unary_prim(
623
+ "bessel_i1e",
624
+ impl_aten=torch.special.i1e,
625
+ doc="",
626
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
627
+ )
628
+
629
+ bitwise_not = _make_elementwise_unary_prim(
630
+ "bitwise_not",
631
+ impl_aten=torch.bitwise_not,
632
+ doc="",
633
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
634
+ )
635
+
636
+
637
+ def _cbrt_aten(a: torch.Tensor) -> Tensor:
638
+ torch._check(
639
+ not a.is_complex(),
640
+ lambda: "cbrt: Complex inputs not supported. Consider calling torch.pow(a, 1.0/3.0)",
641
+ )
642
+ # Returns the real cubic root of the number.
643
+ # Note that if a < 0, pow(a, (1. / 3.)) returns th complex number
644
+ # exp(1/3 * log(a)) = exp(1/3 * (log(abs(a)) + pi*i)) = cbrt(abs(a)) * e^{pi/3*i}
645
+ # which is a complex number.
646
+ # For more info see the section Note in
647
+ # https://en.cppreference.com/w/cpp/numeric/math/cbrt
648
+ return torch.copysign(torch.pow(a.abs(), 1 / 3), a)
649
+
650
+
651
+ cbrt = _make_elementwise_unary_prim(
652
+ "cbrt",
653
+ impl_aten=_cbrt_aten,
654
+ doc="",
655
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
656
+ )
657
+
658
+ ceil = _make_elementwise_unary_prim(
659
+ "ceil",
660
+ impl_aten=torch.ceil,
661
+ doc="",
662
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
663
+ )
664
+
665
+
666
+ def _conj_physical_meta(input: TensorLikeType) -> TensorLikeType:
667
+ if not input.dtype.is_complex:
668
+ raise RuntimeError("prims.conj_physical is only defined for complex dtypes")
669
+
670
+ strides = utils.compute_elementwise_output_strides(input)
671
+ return TensorMeta(input, strides=strides)
672
+
673
+
674
+ conj_physical = _make_prim(
675
+ schema="conj_physical(Tensor self) -> Tensor",
676
+ meta=_conj_physical_meta,
677
+ impl_aten=torch._conj_physical,
678
+ doc="Returns the physical conjugation of a complex tensor",
679
+ return_type=RETURN_TYPE.NEW,
680
+ )
681
+
682
+
683
+ def _clone_meta(
684
+ input: TensorLikeType, *, memory_format: torch.memory_format = torch.preserve_format
685
+ ) -> TensorLikeType:
686
+ if memory_format != torch.preserve_format:
687
+ return torch.empty(
688
+ input.shape,
689
+ dtype=input.dtype,
690
+ layout=input.layout,
691
+ device=input.device,
692
+ memory_format=memory_format,
693
+ )
694
+
695
+ # memory_format == torch.preserve_format
696
+ strides = utils.compute_elementwise_output_strides(input)
697
+ return torch.empty_strided(
698
+ input.shape,
699
+ strides,
700
+ dtype=input.dtype,
701
+ layout=input.layout,
702
+ device=input.device,
703
+ )
704
+
705
+
706
+ clone = _make_prim(
707
+ schema="clone(Tensor self, *, MemoryFormat? memory_format=None) -> Tensor",
708
+ meta=_clone_meta,
709
+ impl_aten=torch.clone,
710
+ doc="Returns the copy of a tensor",
711
+ return_type=RETURN_TYPE.NEW,
712
+ register_conj_neg_fallthrough=True,
713
+ )
714
+
715
+ digamma = _make_elementwise_unary_prim(
716
+ "digamma",
717
+ impl_aten=torch.digamma,
718
+ doc="",
719
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
720
+ )
721
+
722
+ erf = _make_elementwise_unary_prim(
723
+ "erf",
724
+ impl_aten=torch.erf,
725
+ doc="",
726
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
727
+ )
728
+
729
+ erf_inv = _make_elementwise_unary_prim(
730
+ "erf_inv",
731
+ impl_aten=torch.special.erfinv,
732
+ doc="",
733
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
734
+ )
735
+
736
+ erfc = _make_elementwise_unary_prim(
737
+ "erfc",
738
+ impl_aten=torch.special.erfc,
739
+ doc="",
740
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
741
+ )
742
+
743
+ erfcx = _make_elementwise_unary_prim(
744
+ "erfcx",
745
+ impl_aten=torch.special.erfcx,
746
+ doc="",
747
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
748
+ )
749
+
750
+ exp = _make_elementwise_unary_prim(
751
+ "exp",
752
+ impl_aten=torch.exp,
753
+ doc="",
754
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
755
+ )
756
+
757
+ expm1 = _make_elementwise_unary_prim(
758
+ "expm1",
759
+ impl_aten=torch.special.expm1,
760
+ doc="",
761
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
762
+ )
763
+
764
+ exp2 = _make_elementwise_unary_prim(
765
+ "exp2",
766
+ impl_aten=torch.special.exp2,
767
+ doc="",
768
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
769
+ )
770
+
771
+
772
+ def _fill_meta(a: TensorLikeType, value: NumberType) -> TensorLikeType:
773
+ return _prim_elementwise_meta(
774
+ a, type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT
775
+ )
776
+
777
+
778
+ # NOTE: fill uses _make_prim directly because it has a value parameter
779
+ fill = _make_prim(
780
+ schema="fill(Tensor self, Scalar value) -> Tensor",
781
+ return_type=RETURN_TYPE.NEW,
782
+ meta=_fill_meta,
783
+ impl_aten=torch.fill,
784
+ doc="",
785
+ )
786
+
787
+ floor = _make_elementwise_unary_prim(
788
+ "floor",
789
+ impl_aten=torch.floor,
790
+ doc="",
791
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
792
+ )
793
+
794
+ imag = _make_prim(
795
+ schema="imag(Tensor(a) self) -> Tensor(a)",
796
+ meta=partial(
797
+ _complex_only_elementwise_meta,
798
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.COMPLEX_TO_FLOAT,
799
+ ),
800
+ return_type=RETURN_TYPE.VIEW,
801
+ impl_aten=torch.imag,
802
+ doc="",
803
+ )
804
+
805
+ isfinite = _make_elementwise_unary_prim(
806
+ "isfinite",
807
+ impl_aten=torch.isfinite,
808
+ doc="",
809
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.ALWAYS_BOOL,
810
+ )
811
+
812
+ lgamma = _make_elementwise_unary_prim(
813
+ "lgamma",
814
+ impl_aten=torch.lgamma,
815
+ doc="",
816
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
817
+ )
818
+
819
+ log = _make_elementwise_unary_prim(
820
+ "log",
821
+ impl_aten=torch.log,
822
+ doc="",
823
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
824
+ )
825
+
826
+ log1p = _make_elementwise_unary_prim(
827
+ "log1p",
828
+ impl_aten=torch.log1p,
829
+ doc="",
830
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
831
+ )
832
+
833
+ log2 = _make_elementwise_unary_prim(
834
+ "log2",
835
+ impl_aten=torch.log2,
836
+ doc="",
837
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
838
+ )
839
+
840
+ log10 = _make_elementwise_unary_prim(
841
+ "log10",
842
+ impl_aten=torch.log10,
843
+ doc="",
844
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
845
+ )
846
+
847
+ real = _make_prim(
848
+ schema="real(Tensor(a) self) -> Tensor(a)",
849
+ meta=partial(
850
+ _complex_only_elementwise_meta,
851
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.COMPLEX_TO_FLOAT,
852
+ ),
853
+ return_type=RETURN_TYPE.VIEW,
854
+ impl_aten=torch.real,
855
+ doc="",
856
+ )
857
+
858
+ reciprocal = _make_elementwise_unary_prim(
859
+ "reciprocal",
860
+ impl_aten=torch.reciprocal,
861
+ doc="",
862
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
863
+ )
864
+
865
+ ndtri = _make_elementwise_unary_prim(
866
+ "ndtri",
867
+ impl_aten=torch.special.ndtri,
868
+ doc="",
869
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
870
+ )
871
+
872
+ neg = _make_elementwise_unary_prim(
873
+ "neg",
874
+ impl_aten=torch.neg,
875
+ doc="",
876
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
877
+ )
878
+
879
+ round = _make_elementwise_unary_prim(
880
+ "round",
881
+ impl_aten=torch.round,
882
+ doc="",
883
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
884
+ )
885
+
886
+ rsqrt = _make_elementwise_unary_prim(
887
+ "rsqrt",
888
+ impl_aten=torch.rsqrt,
889
+ doc="",
890
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
891
+ )
892
+
893
+ sign = _make_elementwise_unary_prim(
894
+ "sign",
895
+ impl_aten=torch.sign,
896
+ doc="",
897
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
898
+ )
899
+
900
+ signbit = _make_elementwise_unary_prim(
901
+ "signbit",
902
+ impl_aten=torch.signbit,
903
+ doc="",
904
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
905
+ )
906
+
907
+ sin = _make_elementwise_unary_prim(
908
+ "sin",
909
+ impl_aten=torch.sin,
910
+ doc="",
911
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
912
+ )
913
+
914
+ sinh = _make_elementwise_unary_prim(
915
+ "sinh",
916
+ impl_aten=torch.sinh,
917
+ doc="",
918
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
919
+ )
920
+
921
+ spherical_bessel_j0 = _make_elementwise_unary_prim(
922
+ "spherical_bessel_j0",
923
+ impl_aten=torch.special.spherical_bessel_j0,
924
+ doc="",
925
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
926
+ )
927
+
928
+ sqrt = _make_elementwise_unary_prim(
929
+ "sqrt",
930
+ impl_aten=torch.sqrt,
931
+ doc="",
932
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
933
+ )
934
+
935
+ tan = _make_elementwise_unary_prim(
936
+ "tan",
937
+ impl_aten=torch.tan,
938
+ doc="",
939
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
940
+ )
941
+
942
+ tanh = _make_elementwise_unary_prim(
943
+ "tanh",
944
+ impl_aten=torch.tanh,
945
+ doc="",
946
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
947
+ )
948
+
949
+ trunc = _make_elementwise_unary_prim(
950
+ "trunc",
951
+ impl_aten=torch.trunc,
952
+ doc="",
953
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
954
+ )
955
+
956
+ #
957
+ # Elementwise binary operations
958
+ #
959
+
960
+ add = _make_elementwise_binary_prim(
961
+ name="add",
962
+ impl_aten=torch.add,
963
+ doc="",
964
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
965
+ )
966
+
967
+ atan2 = _make_elementwise_binary_prim(
968
+ name="atan2",
969
+ impl_aten=torch.atan2,
970
+ doc="",
971
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
972
+ )
973
+
974
+ bitwise_and = _make_elementwise_binary_prim(
975
+ "bitwise_and",
976
+ impl_aten=torch.bitwise_and,
977
+ doc="",
978
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
979
+ )
980
+
981
+ bitwise_or = _make_elementwise_binary_prim(
982
+ "bitwise_or",
983
+ impl_aten=torch.bitwise_or,
984
+ doc="",
985
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
986
+ )
987
+
988
+ bitwise_xor = _make_elementwise_binary_prim(
989
+ "bitwise_xor",
990
+ impl_aten=torch.bitwise_xor,
991
+ doc="",
992
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
993
+ )
994
+
995
+ # TODO: complex needs a special meta to account for its float -> complex behavior
996
+ # complex = _make_elementwise_binary_prim(
997
+ # impl_aten=torch.complex,
998
+ # doc="",
999
+ # )
1000
+
1001
+
1002
+ # div prim performs truncation division on integer inputs
1003
+ # and true division for floating and complex inputs
1004
+ def _div_aten(a, b):
1005
+ is_integral = isinstance(a, (bool, int, torch.SymInt)) or (
1006
+ isinstance(a, torch.Tensor) and utils.is_integer_dtype(a.dtype)
1007
+ )
1008
+
1009
+ if is_integral:
1010
+ return torch.div(a, b, rounding_mode="trunc")
1011
+ else:
1012
+ return torch.true_divide(a, b)
1013
+
1014
+
1015
+ div = _make_elementwise_binary_prim(
1016
+ "div",
1017
+ impl_aten=_div_aten,
1018
+ doc="",
1019
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
1020
+ )
1021
+
1022
+ eq = _make_elementwise_binary_prim(
1023
+ "eq",
1024
+ impl_aten=torch.eq,
1025
+ doc="",
1026
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.ALWAYS_BOOL,
1027
+ )
1028
+
1029
+ fmax = _make_elementwise_binary_prim(
1030
+ "fmax",
1031
+ impl_aten=torch.fmax,
1032
+ doc="",
1033
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
1034
+ )
1035
+
1036
+ fmin = _make_elementwise_binary_prim(
1037
+ "fmin",
1038
+ impl_aten=torch.fmin,
1039
+ doc="",
1040
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
1041
+ )
1042
+
1043
+ fmod = _make_elementwise_binary_prim(
1044
+ "fmod",
1045
+ impl_aten=torch.fmod,
1046
+ doc="",
1047
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
1048
+ )
1049
+
1050
+
1051
+ gcd = _make_elementwise_binary_prim(
1052
+ "gcd",
1053
+ impl_aten=torch.gcd,
1054
+ doc="",
1055
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
1056
+ )
1057
+
1058
+
1059
+ ge = _make_elementwise_binary_prim(
1060
+ "ge",
1061
+ impl_aten=torch.ge,
1062
+ doc="",
1063
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.ALWAYS_BOOL,
1064
+ )
1065
+
1066
+ gt = _make_elementwise_binary_prim(
1067
+ "gt",
1068
+ impl_aten=torch.gt,
1069
+ doc="",
1070
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.ALWAYS_BOOL,
1071
+ )
1072
+
1073
+ hypot = _make_elementwise_binary_prim(
1074
+ "hypot",
1075
+ impl_aten=torch.hypot,
1076
+ doc="",
1077
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
1078
+ )
1079
+
1080
+ igamma = _make_elementwise_binary_prim(
1081
+ "igamma",
1082
+ impl_aten=torch.special.gammainc,
1083
+ doc="",
1084
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
1085
+ )
1086
+
1087
+ igammac = _make_elementwise_binary_prim(
1088
+ "igammac",
1089
+ impl_aten=torch.special.gammaincc,
1090
+ doc="",
1091
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
1092
+ )
1093
+
1094
+ le = _make_elementwise_binary_prim(
1095
+ "le",
1096
+ impl_aten=torch.le,
1097
+ doc="",
1098
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.ALWAYS_BOOL,
1099
+ )
1100
+
1101
+ lt = _make_elementwise_binary_prim(
1102
+ "lt",
1103
+ impl_aten=torch.lt,
1104
+ doc="",
1105
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.ALWAYS_BOOL,
1106
+ )
1107
+
1108
+
1109
+ # Note: the following impls are because torch.maximum and torch.minimum do not support scalar inputs
1110
+ def _maximum_aten(
1111
+ a: Union[TensorLikeType, NumberType], b: Union[TensorLikeType, NumberType]
1112
+ ) -> TensorLikeType:
1113
+ if isinstance(a, TensorLike) and isinstance(b, Number):
1114
+ b = scalar_tensor(b, dtype=a.dtype, device=a.device)
1115
+ elif isinstance(b, TensorLike) and isinstance(a, Number):
1116
+ a = scalar_tensor(a, dtype=b.dtype, device=b.device)
1117
+
1118
+ return torch.maximum(a, b) # type: ignore[arg-type]
1119
+
1120
+
1121
+ maximum = _make_elementwise_binary_prim(
1122
+ "maximum",
1123
+ impl_aten=_maximum_aten,
1124
+ doc="",
1125
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
1126
+ )
1127
+
1128
+
1129
+ def _minimum_aten(
1130
+ a: Union[TensorLikeType, NumberType], b: Union[TensorLikeType, NumberType]
1131
+ ) -> TensorLikeType:
1132
+ if isinstance(a, TensorLike) and isinstance(b, Number):
1133
+ b = scalar_tensor(b, dtype=a.dtype, device=a.device)
1134
+ elif isinstance(b, TensorLike) and isinstance(a, Number):
1135
+ a = scalar_tensor(a, dtype=b.dtype, device=b.device)
1136
+
1137
+ return torch.minimum(a, b) # type: ignore[arg-type]
1138
+
1139
+
1140
+ minimum = _make_elementwise_binary_prim(
1141
+ "minimum",
1142
+ impl_aten=_minimum_aten,
1143
+ doc="",
1144
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
1145
+ )
1146
+
1147
+ mul = _make_elementwise_binary_prim(
1148
+ "mul",
1149
+ impl_aten=torch.mul,
1150
+ doc="",
1151
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
1152
+ )
1153
+
1154
+ ne = _make_elementwise_binary_prim(
1155
+ "ne",
1156
+ impl_aten=torch.ne,
1157
+ doc="",
1158
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.ALWAYS_BOOL,
1159
+ )
1160
+
1161
+ nextafter = _make_elementwise_binary_prim(
1162
+ "nextafter",
1163
+ impl_aten=torch.nextafter,
1164
+ doc="",
1165
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
1166
+ )
1167
+
1168
+ pow = _make_elementwise_binary_prim(
1169
+ "pow",
1170
+ impl_aten=torch.pow,
1171
+ doc="",
1172
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
1173
+ )
1174
+
1175
+ remainder = _make_elementwise_binary_prim(
1176
+ "remainder",
1177
+ impl_aten=torch.remainder,
1178
+ doc="",
1179
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
1180
+ )
1181
+
1182
+
1183
+ shift_left = _make_elementwise_binary_prim(
1184
+ "shift_left",
1185
+ impl_aten=torch.bitwise_left_shift,
1186
+ doc="",
1187
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
1188
+ )
1189
+
1190
+ shift_right_arithmetic = _make_elementwise_binary_prim(
1191
+ "shift_right_arithmetic",
1192
+ impl_aten=torch.bitwise_right_shift,
1193
+ doc="",
1194
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
1195
+ )
1196
+
1197
+ shift_right_logical = _not_impl
1198
+
1199
+ sub = _make_elementwise_binary_prim(
1200
+ "sub",
1201
+ impl_aten=torch.sub,
1202
+ doc="",
1203
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
1204
+ )
1205
+
1206
+ zeta = _make_elementwise_binary_prim(
1207
+ "zeta",
1208
+ impl_aten=torch.special.zeta,
1209
+ doc="",
1210
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
1211
+ )
1212
+
1213
+
1214
+ #
1215
+ # View operations
1216
+ def _as_strided_meta(
1217
+ a: TensorLikeType, size: ShapeType, stride: StrideType, storage_offset: int
1218
+ ) -> TensorLikeType:
1219
+ assert len(size) == len(stride)
1220
+ assert storage_offset >= 0
1221
+ utils.validate_strides(stride)
1222
+ utils.validate_shape(size)
1223
+
1224
+ if reduce(operator.mul, size) == 0:
1225
+ # NOTE: This special case is to avoid having to acquire the storage below
1226
+ # as_strided to shapes with no elements are trivially valid, so it's OK
1227
+ pass
1228
+ elif isinstance(a, torch.Tensor):
1229
+ utils.check_in_bounds_for_storage(
1230
+ a._typed_storage(), size, stride, storage_offset
1231
+ )
1232
+
1233
+ return torch.as_strided(a, size, stride, storage_offset)
1234
+
1235
+
1236
+ def _as_strided_aten(
1237
+ a: Tensor, size: ShapeType, stride: StrideType, storage_offset: int
1238
+ ) -> Tensor:
1239
+ return torch.as_strided(a, size, stride, storage_offset)
1240
+
1241
+
1242
+ _as_strided_doc = """
1243
+ Creates a view of the tensor with the given shape (size), strides (stride) and
1244
+ storage offset (storage_offset).
1245
+ """
1246
+
1247
+ as_strided = _make_prim(
1248
+ schema="as_strided(Tensor(a!) a, SymInt[] size, SymInt[] stride, SymInt storage_offset) -> Tensor(a!)",
1249
+ meta=_as_strided_meta,
1250
+ impl_aten=_as_strided_aten,
1251
+ return_type=RETURN_TYPE.VIEW,
1252
+ doc=_as_strided_doc,
1253
+ )
1254
+
1255
+
1256
+ def _broadcast_in_dim_meta(
1257
+ a: TensorLikeType, shape: ShapeType, broadcast_dimensions: Sequence[int]
1258
+ ):
1259
+ from torch.fx.experimental.symbolic_shapes import guard_size_oblivious
1260
+
1261
+ # Type checks
1262
+ assert isinstance(a, TensorLike)
1263
+ assert isinstance(shape, Sequence)
1264
+ assert isinstance(broadcast_dimensions, Sequence)
1265
+
1266
+ # every dimension must be accounted for
1267
+ assert a.ndim == len(broadcast_dimensions)
1268
+
1269
+ # broadcast shape must have weakly more dimensions
1270
+ assert len(shape) >= a.ndim
1271
+
1272
+ # broadcast_dimensions must be an ascending sequence
1273
+ # (no relative reordering of dims) of integers and
1274
+ # each dimension must be within the new shape
1275
+ def _greater_than_reduce(acc, x):
1276
+ assert isinstance(x, Dim)
1277
+ assert x > acc
1278
+ assert x < len(shape)
1279
+
1280
+ return x
1281
+
1282
+ reduce(_greater_than_reduce, broadcast_dimensions, -1)
1283
+
1284
+ # shape must be broadcastable to
1285
+ for idx, new_idx in enumerate(broadcast_dimensions):
1286
+ if not guard_size_oblivious(a.shape[idx] == 1):
1287
+ torch._check(
1288
+ a.shape[idx] == shape[new_idx],
1289
+ lambda: f"{a.shape[idx]} must be broadcastable to {shape[new_idx]}",
1290
+ )
1291
+
1292
+ new_strides = []
1293
+ original_idx = 0
1294
+ for idx in range(len(shape)):
1295
+ if idx in broadcast_dimensions:
1296
+ # Assigns a stride of zero to dimensions
1297
+ # which were actually broadcast
1298
+ if guard_size_oblivious(a.shape[original_idx] != shape[idx]):
1299
+ new_strides.append(0)
1300
+ else:
1301
+ new_strides.append(a.stride()[original_idx])
1302
+ original_idx = original_idx + 1
1303
+ else:
1304
+ if guard_size_oblivious(shape[idx] != 1):
1305
+ new_strides.append(0)
1306
+ elif original_idx == a.ndim:
1307
+ new_strides.append(1)
1308
+ else:
1309
+ new_strides.append(a.stride()[original_idx] * a.size()[original_idx])
1310
+
1311
+ return a.as_strided(shape, new_strides, a.storage_offset())
1312
+
1313
+
1314
+ def _broadcast_in_dim_aten(a, shape, broadcast_dimensions):
1315
+ s = list(shape)
1316
+ for broadcast_dimension in broadcast_dimensions:
1317
+ s[broadcast_dimension] = -1
1318
+
1319
+ v = a
1320
+ for idx, x in enumerate(s):
1321
+ if x != -1:
1322
+ v = v.unsqueeze(idx)
1323
+
1324
+ return v.expand(shape)
1325
+
1326
+
1327
+ _broadcast_in_dim_doc = """
1328
+ Creates a view of a with the specified shape.
1329
+
1330
+ Allows adding dimensions of any length and broadcasting
1331
+ dimensions of length one in a to any length.
1332
+
1333
+ The location of the broadcast dimensions must be specified
1334
+ using the broadcast_dimensions argument. Changing the
1335
+ relative order of dimensions is not supported.
1336
+ """
1337
+
1338
+ broadcast_in_dim = _make_prim(
1339
+ schema="broadcast_in_dim(Tensor(a) a, SymInt[] shape, int[] broadcast_dimensions) -> Tensor(a)",
1340
+ meta=_broadcast_in_dim_meta,
1341
+ impl_aten=_broadcast_in_dim_aten,
1342
+ return_type=RETURN_TYPE.VIEW,
1343
+ doc=_broadcast_in_dim_doc,
1344
+ )
1345
+
1346
+
1347
+ def _validate_collapse_args(a: Tensor, start: int, end: int) -> None:
1348
+ # Special-case for zero dimensional tensors
1349
+ ndim = max(1, a.dim())
1350
+ utils.validate_idx(ndim, start)
1351
+ utils.validate_idx(ndim, end)
1352
+
1353
+ # Verifies end is strictly greater than start
1354
+ # (Collapse requires a non-empty interval)
1355
+ torch._check_value(
1356
+ end >= start,
1357
+ lambda: f"Attempting to collapse but end, {end}, is less than start, {start}!",
1358
+ )
1359
+
1360
+
1361
+ def _collapsed_shape(shape: ShapeType, start: int, end: int) -> Tuple[int, ...]:
1362
+ """
1363
+ Returns the shape of a with dims in [start, end) merged into a single dimension.
1364
+ """
1365
+ # Special-case for zero dimensional tensors
1366
+ shape = (1,) if len(shape) == 0 else tuple(shape)
1367
+
1368
+ dim_length = 1
1369
+ for s in shape[start : end + 1]:
1370
+ dim_length = dim_length * s
1371
+
1372
+ return shape[0:start] + (dim_length,) + shape[end + 1 :]
1373
+
1374
+
1375
+ def _collapse_view_helper(
1376
+ a: TensorLikeType, start: int, end: int
1377
+ ) -> Tuple[Optional[ShapeType], Optional[StrideType]]:
1378
+ assert isinstance(a, TensorLike)
1379
+
1380
+ from torch.fx.experimental.symbolic_shapes import guard_size_oblivious
1381
+
1382
+ _validate_collapse_args(a, start, end)
1383
+
1384
+ # Special-case for zero dimensional tensors
1385
+ if a.ndim == 0:
1386
+ shape = (1,)
1387
+ strides = (1,)
1388
+ else:
1389
+ shape = a.shape # type: ignore[assignment]
1390
+ strides = a.stride() # type: ignore[assignment]
1391
+
1392
+ if a.ndim == 0 or (end == start):
1393
+ return shape, strides
1394
+
1395
+ length = shape[end]
1396
+ stride = strides[end]
1397
+ for idx in range(end - 1, start - 1, -1):
1398
+ if guard_size_oblivious(shape[idx] == 0) or guard_size_oblivious(
1399
+ shape[idx + 1] == 0
1400
+ ):
1401
+ length = 0
1402
+ stride = 0
1403
+ break
1404
+
1405
+ if guard_size_oblivious(shape[idx] == 1):
1406
+ continue
1407
+
1408
+ length = length * shape[idx]
1409
+ if guard_size_oblivious(stride < strides[idx]):
1410
+ stride = stride
1411
+ else:
1412
+ stride = strides[idx]
1413
+
1414
+ if (
1415
+ guard_size_oblivious(a.numel() > 0)
1416
+ and guard_size_oblivious(shape[idx + 1] != 1)
1417
+ and not guard_size_oblivious(
1418
+ strides[idx] == strides[idx + 1] * shape[idx + 1]
1419
+ )
1420
+ ):
1421
+ return None, None
1422
+
1423
+ new_shape = shape[:start] + (length,) + shape[end + 1 :]
1424
+ new_strides = strides[:start] + (stride,) + strides[end + 1 :]
1425
+
1426
+ # NOTE: when the input has no elements it's restrided as if it were contiguous
1427
+ if guard_size_oblivious(a.numel() == 0):
1428
+ new_strides = utils.make_contiguous_strides_for(new_shape)
1429
+
1430
+ return new_shape, new_strides
1431
+
1432
+
1433
+ def _collapse_view_meta(a: TensorLikeType, start: int, end: int) -> TensorLikeType:
1434
+ new_shape, new_strides = _collapse_view_helper(a, start, end)
1435
+
1436
+ if new_shape is None:
1437
+ msg = "Attempting to view a collapsed tensor, but no such view exists!"
1438
+ raise ValueError(msg)
1439
+
1440
+ assert new_strides is not None
1441
+ return a.as_strided(new_shape, new_strides, a.storage_offset())
1442
+
1443
+
1444
+ def _collapse_view_aten(a: Tensor, start: int, end: int) -> Tensor:
1445
+ new_shape = _collapsed_shape(a.shape, start, end)
1446
+ return a.view(new_shape)
1447
+
1448
+
1449
+ _collapse_view_doc = """
1450
+ Creates a view of a with the dimensions between
1451
+ start (inclusive) and end (exclusive) merged into a
1452
+ single dimension.
1453
+
1454
+ If it's not possible to take such a view then an error
1455
+ is thrown. See collapse instead.
1456
+
1457
+ The dimensions can be merged if and only if
1458
+ they are all "nested" with each other. That is, they all
1459
+ have the property that
1460
+
1461
+ stride[i] = stride[i+1] * shape[i+1]
1462
+
1463
+ for all i in [start, end - 1).
1464
+ """
1465
+
1466
+ collapse_view = _make_prim(
1467
+ schema="collapse_view(Tensor(a) a, int start, int end) -> Tensor(a)",
1468
+ meta=_collapse_view_meta,
1469
+ impl_aten=_collapse_view_aten,
1470
+ return_type=RETURN_TYPE.VIEW,
1471
+ doc=_collapse_view_doc,
1472
+ )
1473
+
1474
+
1475
+ def _conj_meta(a: TensorLikeType) -> TensorLikeType:
1476
+ if not a.dtype.is_complex:
1477
+ raise RuntimeError("Expected complex dtype in prims.conj")
1478
+ out = a.as_strided(a.shape, a.stride(), a.storage_offset())
1479
+ torch._C._set_conj(out, not a.is_conj())
1480
+ return out
1481
+
1482
+
1483
+ _conj_doc = """
1484
+ Returns a conjugated view of the original tensor
1485
+ """
1486
+
1487
+ conj = _make_prim(
1488
+ schema="conj(Tensor(a) a) -> Tensor(a)",
1489
+ meta=_conj_meta,
1490
+ impl_aten=torch.conj,
1491
+ return_type=RETURN_TYPE.VIEW,
1492
+ doc=_conj_doc,
1493
+ )
1494
+
1495
+
1496
+ def expand_dims(
1497
+ a: TensorLikeType, dimensions: DimsSequenceType, ndim=None
1498
+ ) -> TensorLikeType:
1499
+ """
1500
+ Creates a view of a with a.ndim + len(dimensions) dimensions, with new
1501
+ dimensions of length one at the dimensions specified by dimensions.
1502
+ """
1503
+ if ndim is not None:
1504
+ # TODO: this is only here to support the unsqueeze ref
1505
+ dims = sorted(utils.canonicalize_dims(ndim, dimensions)) # type: ignore[arg-type]
1506
+ else:
1507
+ dims = sorted(utils.canonicalize_dims(a.ndim, dimensions)) # type: ignore[arg-type]
1508
+ if len(set(dims)) != len(dims):
1509
+ msg = f"Received duplicate dimensions to expand in {str(dimensions)}"
1510
+ raise ValueError(msg)
1511
+
1512
+ new_shape = list(a.shape)
1513
+ for idx in dims:
1514
+ new_shape.insert(idx, 1)
1515
+
1516
+ broadcast_dimensions = [
1517
+ idx for idx in range(len(new_shape)) if idx not in dimensions
1518
+ ]
1519
+ return broadcast_in_dim(a, new_shape, broadcast_dimensions)
1520
+
1521
+
1522
+ def _split_dim_meta(a: TensorLikeType, dim: int, outer_length: int) -> TensorLikeType:
1523
+ assert isinstance(a, TensorLike)
1524
+ utils.validate_idx(a.ndim, dim)
1525
+ utils.validate_dim_length(outer_length)
1526
+
1527
+ # Verifies the dim can be split with the specified lhs_length
1528
+ inner_length = a.shape[dim] // outer_length
1529
+
1530
+ if (a.shape[dim] % outer_length) != 0:
1531
+ msg = (
1532
+ f"Attempting to split dimension of length {a.shape[dim]}, "
1533
+ f"but outer length of {outer_length} divides it with a remainder!"
1534
+ )
1535
+ raise ValueError(msg)
1536
+
1537
+ new_shape: List[int] = []
1538
+ new_strides: List[int] = []
1539
+ for idx in range(a.ndim):
1540
+ if idx == dim:
1541
+ new_shape.extend((outer_length, inner_length))
1542
+ new_strides.extend((a.stride()[idx] * inner_length, a.stride()[idx]))
1543
+ else:
1544
+ new_shape.append(a.shape[idx])
1545
+ new_strides.append(a.stride()[idx])
1546
+
1547
+ return a.as_strided(new_shape, new_strides, a.storage_offset())
1548
+
1549
+
1550
+ def _split_dim_aten(a: Tensor, dim: int, outer_length: int) -> Tensor:
1551
+ inner_length = a.shape[dim] // outer_length
1552
+ new_shape = a.shape[0:dim] + (outer_length, inner_length) + a.shape[dim + 1 :]
1553
+
1554
+ return a.view(new_shape)
1555
+
1556
+
1557
+ _split_dim_doc = """
1558
+ Creates a view of a with the given dimension (of length l) split
1559
+ into two dimensions, with the outer of the two having
1560
+ length outer_length and the inner of the two having computed
1561
+ length inner_length such outer_length * inner_length = l.
1562
+ """
1563
+
1564
+ # TODO: consider renaming split_dim_view
1565
+ split_dim = _make_prim(
1566
+ schema="split_dim(Tensor(a) a, int dim, SymInt outer_length) -> Tensor(a)",
1567
+ meta=_split_dim_meta,
1568
+ impl_aten=_split_dim_aten,
1569
+ return_type=RETURN_TYPE.VIEW,
1570
+ doc=_split_dim_doc,
1571
+ )
1572
+
1573
+
1574
+ # Note: allows dimensions to be specified redundantly
1575
+ def _squeeze_meta(a: TensorLikeType, dimensions: Sequence) -> TensorLikeType:
1576
+ assert isinstance(a, TensorLike)
1577
+
1578
+ for idx in dimensions:
1579
+ utils.validate_idx(a.ndim, idx)
1580
+ assert a.shape[idx] == 1
1581
+
1582
+ new_shape = []
1583
+ new_strides = []
1584
+ for idx in range(len(a.shape)):
1585
+ if idx in dimensions:
1586
+ continue
1587
+
1588
+ new_shape.append(a.shape[idx])
1589
+ new_strides.append(a.stride()[idx])
1590
+
1591
+ return a.as_strided(new_shape, new_strides, a.storage_offset())
1592
+
1593
+
1594
+ _squeeze_doc = """
1595
+ Creates a view of the tensor with the specified dimensions removed.
1596
+
1597
+ The removed dimensions must each have length one.
1598
+ """
1599
+
1600
+ squeeze = _make_prim(
1601
+ schema="squeeze(Tensor(a) a, int[] dimensions) -> Tensor(a)",
1602
+ meta=_squeeze_meta,
1603
+ impl_aten=torch.squeeze,
1604
+ return_type=RETURN_TYPE.VIEW,
1605
+ doc=_squeeze_doc,
1606
+ )
1607
+
1608
+
1609
+ def _transpose_meta(a: TensorLikeType, permutation: DimsSequenceType) -> TensorLikeType:
1610
+ if a.ndim != len(permutation):
1611
+ msg = f"Attempting to permute a tensor of rank {a.ndim}, but received a permutation of length {len(permutation)}!"
1612
+ raise ValueError(msg)
1613
+
1614
+ if not utils.is_valid_permutation(a.ndim, permutation):
1615
+ msg = f"Received an invalid permutation, {permutation}!"
1616
+ raise ValueError(msg)
1617
+
1618
+ new_shape = [0] * a.ndim
1619
+ new_strides = [0] * a.ndim
1620
+ for idx, dim in enumerate(permutation):
1621
+ new_shape[idx] = a.shape[dim]
1622
+ new_strides[idx] = a.stride()[dim]
1623
+
1624
+ return a.as_strided(tuple(new_shape), tuple(new_strides), a.storage_offset())
1625
+
1626
+
1627
+ def _transpose_aten(a: Tensor, permutation: DimsSequenceType) -> Tensor:
1628
+ return torch.permute(a, permutation)
1629
+
1630
+
1631
+ _transpose_doc = """
1632
+ Creates a view of the tensor with its dimensions permuted.
1633
+
1634
+ The length of the permutation must be the rank of the tensor,
1635
+ and each element of the permutation specifies the new order
1636
+ for the corresponding dimension.
1637
+ """
1638
+
1639
+ transpose = _make_prim(
1640
+ schema="transpose(Tensor(a) a, int[] permutation) -> Tensor(a)",
1641
+ meta=_transpose_meta,
1642
+ impl_aten=_transpose_aten,
1643
+ return_type=RETURN_TYPE.VIEW,
1644
+ doc=_transpose_doc,
1645
+ )
1646
+
1647
+
1648
+ def _view_of_meta(a: TensorLikeType) -> TensorLikeType:
1649
+ return a.as_strided(a.shape, a.stride(), a.storage_offset())
1650
+
1651
+
1652
+ def _view_of_aten(a: Tensor) -> Tensor:
1653
+ return a.view(a.shape)
1654
+
1655
+
1656
+ _view_of_doc = """
1657
+ Creates a view of the tensor.
1658
+ """
1659
+
1660
+ view_of = _make_prim(
1661
+ schema="view_of(Tensor(a) a) -> Tensor(a)",
1662
+ meta=_view_of_meta,
1663
+ impl_aten=_view_of_aten,
1664
+ return_type=RETURN_TYPE.VIEW,
1665
+ doc=_view_of_doc,
1666
+ )
1667
+
1668
+
1669
+ def _view_element_type_meta(a: TensorLikeType, dtype: torch.dtype) -> TensorLikeType:
1670
+ return a.view(dtype)
1671
+
1672
+
1673
+ def _view_element_type_aten(a: Tensor, dtype: torch.dtype) -> Tensor:
1674
+ return a.view(dtype)
1675
+
1676
+
1677
+ _view_element_type_doc = """
1678
+ Creates a view of the tensor with a different dtype.
1679
+ """
1680
+
1681
+ view_element_type = _make_prim(
1682
+ schema="view_of_dtype(Tensor(a) a, ScalarType dtype) -> Tensor(a)",
1683
+ meta=_view_element_type_meta,
1684
+ impl_aten=_view_element_type_aten,
1685
+ return_type=RETURN_TYPE.VIEW,
1686
+ doc=_view_element_type_doc,
1687
+ )
1688
+
1689
+ #
1690
+ # Functionalized view mutations
1691
+ #
1692
+
1693
+
1694
+ def _as_strided_scatter_meta(
1695
+ input: TensorLikeType,
1696
+ src: TensorLikeType,
1697
+ size: ShapeType,
1698
+ stride: StrideType,
1699
+ storage_offset: int,
1700
+ ) -> TensorLikeType:
1701
+ utils.validate_shape(size)
1702
+ utils.validate_strides(stride)
1703
+
1704
+ required_size = utils.compute_required_storage_length(size, stride, storage_offset)
1705
+ torch._check(
1706
+ input.numel() >= required_size,
1707
+ lambda: (
1708
+ f"as_strided_scatter: sizes {size}, strides {stride}, storage offset {storage_offset} "
1709
+ f" and itemsize {input.element_size()} requiring a storage size of "
1710
+ f"{required_size * input.element_size()} are out of bounds "
1711
+ f"for storage of size {input.numel() * input.element_size()}"
1712
+ ),
1713
+ )
1714
+ torch._check(
1715
+ utils.is_same_shape(src.shape, size),
1716
+ lambda: f"expected src to have a size equal to the slice of self. src size = {src.shape}, slice size = {size}",
1717
+ )
1718
+
1719
+ return utils.clone_preserve_strides(input)
1720
+
1721
+
1722
+ _as_strided_scatter_doc = """
1723
+ Creates a new tensor equivalent to ``out = input.clone()`` after mutation by
1724
+ ``out.as_strided(size, stride, storage_offset).copy_(src)``.
1725
+ """
1726
+
1727
+ as_strided_scatter = _make_prim(
1728
+ schema="as_strided_scatter(Tensor self, Tensor src, SymInt[] size, SymInt[] stride, SymInt storage_offset) -> Tensor",
1729
+ meta=_as_strided_scatter_meta,
1730
+ impl_aten=torch.as_strided_scatter,
1731
+ return_type=RETURN_TYPE.NEW,
1732
+ doc=_as_strided_scatter_doc,
1733
+ )
1734
+
1735
+
1736
+ #
1737
+ # Shape operations
1738
+ #
1739
+
1740
+
1741
+ def _collapse_meta(a: Tensor, start: int, end: int) -> Tensor:
1742
+ # Special-case for zero dimensional tensors
1743
+ _validate_collapse_args(a, start, end)
1744
+ new_shape = _collapsed_shape(a.shape, start, end)
1745
+ return a.new_empty(new_shape)
1746
+
1747
+
1748
+ def _collapse_aten(a: Tensor, start: int, end: int) -> Tensor:
1749
+ new_shape = _collapsed_shape(a.shape, start, end)
1750
+ out = a.new_empty(new_shape)
1751
+ with torch.no_grad():
1752
+ out.view_as(a).copy_(a)
1753
+ return out
1754
+
1755
+
1756
+ _collapse_doc = """
1757
+ Collapse a span of neighboring dimensions into one.
1758
+
1759
+ See collapse_view for the corresponding view operation.
1760
+ """
1761
+ collapse = _make_prim(
1762
+ schema="collapse(Tensor a, int start, int end) -> Tensor",
1763
+ meta=_collapse_meta,
1764
+ impl_aten=_collapse_aten,
1765
+ return_type=RETURN_TYPE.NEW,
1766
+ doc=_collapse_doc,
1767
+ )
1768
+
1769
+
1770
+ # TODO: review stride logic
1771
+ # NB: unlike torch.cat, this is more strict about empty tensors and dim is
1772
+ # never negative
1773
+ def _cat_meta(tensors: Sequence[TensorLikeType], dim: int) -> TensorLikeType:
1774
+ # Verifies same shape (except in the concat dimension)
1775
+ assert dim >= 0
1776
+ shape = tensors[0].shape
1777
+ sym_sum_args = []
1778
+ for tensor_idx, tensor in enumerate(tensors):
1779
+ assert len(shape) == len(tensor.shape)
1780
+ for idx, (common_length, length) in enumerate(zip(shape, tensor.shape)):
1781
+ if idx == dim:
1782
+ sym_sum_args.append(length)
1783
+ else:
1784
+ torch._check(
1785
+ length == common_length,
1786
+ lambda: f"Sizes of tensors must match except in dimension {dim}. "
1787
+ f"Expected {common_length} in dimension {idx} but got {length} for tensor number "
1788
+ f"{tensor_idx} in the list",
1789
+ )
1790
+
1791
+ new_shape = list(tensors[0].shape).copy()
1792
+ new_shape[dim] = torch.sym_sum(sym_sum_args)
1793
+ return TensorMeta(
1794
+ tensors[0],
1795
+ shape=new_shape,
1796
+ strides=utils.make_contiguous_strides_for(new_shape),
1797
+ )
1798
+
1799
+
1800
+ def _cat_aten(tensors: Union[Tuple[Tensor, ...], List[Tensor]], dim: int) -> Tensor:
1801
+ return torch.cat(tensors, dim)
1802
+
1803
+
1804
+ _cat_doc = """
1805
+ Concatenates tensors along the specified dimension.
1806
+
1807
+ The tensors' shapes must have the same rank and same length for other dimensions.
1808
+ """
1809
+
1810
+ cat = _make_prim(
1811
+ schema="cat(Tensor[] tensors, int dim) -> Tensor",
1812
+ meta=_cat_meta,
1813
+ impl_aten=_cat_aten,
1814
+ return_type=RETURN_TYPE.NEW,
1815
+ doc=_cat_doc,
1816
+ )
1817
+
1818
+
1819
+ def _reshape_meta(a: TensorLikeType, shape: ShapeType):
1820
+ assert isinstance(a, TensorLike)
1821
+ utils.validate_shape(shape)
1822
+
1823
+ # Validates the tensor and the requested shape have the
1824
+ # same number of elements
1825
+ numel = reduce(operator.mul, shape)
1826
+ if numel != a.numel():
1827
+ msg = f"Attempting to reshape a tensor with {a.numel()} elements to a shape with {numel} elements!"
1828
+ raise ValueError(msg)
1829
+
1830
+ return TensorMeta(a, shape=shape, strides=utils.make_contiguous_strides_for(shape))
1831
+
1832
+
1833
+ def _reshape_aten(a: Tensor, shape: ShapeType) -> Tensor:
1834
+ return a.reshape(shape).contiguous().clone()
1835
+
1836
+
1837
+ _reshape_doc = """
1838
+ Creates a contiguous tensor with the specified shape
1839
+ containing a copy of the data in a.
1840
+ """
1841
+ reshape = _make_prim(
1842
+ schema="reshape(Tensor a, SymInt[] shape) -> Tensor",
1843
+ meta=_reshape_meta,
1844
+ impl_aten=_reshape_aten,
1845
+ return_type=RETURN_TYPE.NEW,
1846
+ doc=_reshape_doc,
1847
+ )
1848
+
1849
+
1850
+ def _rev_meta(a: TensorLikeType, dims: DimsSequenceType) -> TensorLikeType:
1851
+ utils.validate_dimension_indices(a.ndim, dims)
1852
+ return torch.empty_like(a, memory_format=torch.preserve_format)
1853
+
1854
+
1855
+ _rev_doc = """
1856
+ Reverses the order of elements along the given dimensions.
1857
+ """
1858
+
1859
+ rev = _make_prim(
1860
+ schema="rev(Tensor a, int[] dims) -> Tensor",
1861
+ meta=_rev_meta,
1862
+ impl_aten=torch.flip,
1863
+ return_type=RETURN_TYPE.NEW,
1864
+ doc=_rev_doc,
1865
+ )
1866
+
1867
+ #
1868
+ # Conditional prims
1869
+ #
1870
+
1871
+
1872
+ def _where_meta(
1873
+ pred: TensorLikeType, a: TensorLikeType, b: TensorLikeType
1874
+ ) -> TensorLikeType:
1875
+ return _prim_elementwise_meta(
1876
+ a,
1877
+ b,
1878
+ type_promotion=ELEMENTWISE_PRIM_TYPE_PROMOTION_KIND.DEFAULT,
1879
+ args_with_fixed_dtypes=(pred,),
1880
+ )
1881
+
1882
+
1883
+ _where_doc = """
1884
+ Selects elements from a and b according to pred.
1885
+
1886
+ Where pred is true the result contains the element from a, and
1887
+ where pred is false the result contains the element from b.
1888
+ """
1889
+
1890
+ where = _make_prim(
1891
+ schema="where(Tensor pred, Tensor a, Tensor b) -> Tensor",
1892
+ meta=_where_meta,
1893
+ impl_aten=torch.where,
1894
+ return_type=RETURN_TYPE.NEW,
1895
+ doc=_where_doc,
1896
+ )
1897
+
1898
+
1899
+ #
1900
+ # Type conversions
1901
+ #
1902
+ def _convert_element_type_meta(a: TensorLikeType, dtype: torch.dtype) -> TensorLikeType:
1903
+ # Type checks
1904
+ assert isinstance(a, TensorLike)
1905
+ assert isinstance(dtype, torch.dtype)
1906
+
1907
+ # dtype conversion preserves dense strides
1908
+ if torch._prims_common.is_non_overlapping_and_dense(a):
1909
+ strides = a.stride()
1910
+ else:
1911
+ strides = utils.compute_elementwise_output_strides(a)
1912
+
1913
+ return TensorMeta(a, strides=strides, dtype=dtype)
1914
+
1915
+
1916
+ def _convert_element_type_aten(a: Tensor, dtype: torch.dtype) -> Tensor:
1917
+ # Propagates requires grad when possible
1918
+ if not utils.is_grad_dtype(dtype):
1919
+ requires_grad = False
1920
+ else:
1921
+ # TODO: update meta objects so this can be acquired directly
1922
+ try:
1923
+ requires_grad = a.requires_grad
1924
+ except Exception as e:
1925
+ requires_grad = False
1926
+
1927
+ result = torch.empty_like(
1928
+ a, device=a.device, dtype=dtype, requires_grad=requires_grad
1929
+ )
1930
+ with torch.no_grad():
1931
+ return copy_to(result, a)
1932
+
1933
+
1934
+ _convert_element_type_doc = """
1935
+ Creates a copy of a tensor with the given dtype.
1936
+ """
1937
+
1938
+ convert_element_type = _make_prim(
1939
+ schema="convert_element_type(Tensor a, ScalarType dtype) -> Tensor",
1940
+ meta=_convert_element_type_meta,
1941
+ impl_aten=_convert_element_type_aten,
1942
+ return_type=RETURN_TYPE.NEW,
1943
+ doc=_convert_element_type_doc,
1944
+ tags=(torch.Tag.pointwise,),
1945
+ )
1946
+
1947
+
1948
+ def _device_put_meta(
1949
+ a: TensorLikeType, device: Union[str, torch.device], non_blocking=False
1950
+ ) -> TensorLikeType:
1951
+ assert isinstance(a, TensorLike)
1952
+ assert isinstance(device, (str, torch.device))
1953
+ assert isinstance(non_blocking, bool)
1954
+
1955
+ return TensorMeta(a, device=utils.canonicalize_device(device))
1956
+
1957
+
1958
+ def _device_put_aten(
1959
+ a: Tensor, device: Union[str, torch.device], non_blocking=False
1960
+ ) -> Tensor:
1961
+ return a.to(device, non_blocking=non_blocking)
1962
+
1963
+
1964
+ _device_put_doc = """
1965
+ Creates a copy of a tensor on the given device.
1966
+ """
1967
+
1968
+ device_put = _make_prim(
1969
+ schema="device_put(Tensor a, Device device, bool non_blocking=False) -> Tensor",
1970
+ meta=_device_put_meta,
1971
+ impl_aten=_device_put_aten,
1972
+ return_type=RETURN_TYPE.NEW,
1973
+ doc=_device_put_doc,
1974
+ )
1975
+
1976
+
1977
+ # NOTE: need to model meta scalars
1978
+ # See https://github.com/pytorch/pytorch/issues/78070
1979
+ def _item_meta(a: TensorLikeType) -> FakeTensor:
1980
+ number_type = utils.dtype_to_type(a.dtype)
1981
+ return TensorMeta(number_type(-1))
1982
+
1983
+
1984
+ _item_doc = """
1985
+ Converts a tensor with one element to a Python number.
1986
+ """
1987
+
1988
+
1989
+ # We can't call into python dispatcher for item again
1990
+ # because the current prim decomp calls into python dispatcher
1991
+ # again. https://github.com/pytorch/pytorch/issues/136050
1992
+ def _item_aten_no_python_dispatcher(*args, **kwargs):
1993
+ with torch._dispatch.python.no_python_dispatcher():
1994
+ return torch.Tensor.item(*args, **kwargs)
1995
+
1996
+
1997
+ # TODO: create a new return type for scalars?
1998
+ # FIXME: currently returns integers for boolean tensors
1999
+ # https://github.com/pytorch/pytorch/issues/78071
2000
+ item = _make_prim(
2001
+ schema="item(Tensor a) -> Scalar",
2002
+ meta=_item_meta,
2003
+ impl_aten=_item_aten_no_python_dispatcher,
2004
+ return_type=RETURN_TYPE.NEW,
2005
+ doc=_item_doc,
2006
+ )
2007
+
2008
+
2009
+ # NOTE: need to model meta scalars
2010
+ # See https://github.com/pytorch/pytorch/issues/78070
2011
+ def _maximum_value_meta(dtype: torch.dtype) -> FakeTensor:
2012
+ number_type = utils.dtype_to_type(dtype)
2013
+ return TensorMeta(number_type(-1))
2014
+
2015
+
2016
+ def _maximum_value_aten(dtype: torch.dtype):
2017
+ if dtype == torch.bool:
2018
+ return True
2019
+ elif dtype.is_complex or dtype.is_floating_point:
2020
+ return torch.finfo(dtype).max
2021
+ else:
2022
+ return torch.iinfo(dtype).max
2023
+
2024
+
2025
+ _maximum_value_doc = """
2026
+ Return the maximum finite value for a dtype.
2027
+ """
2028
+
2029
+ # TODO: create a new return type for scalars?
2030
+ # FIXME: currently returns integers for boolean tensors
2031
+ # https://github.com/pytorch/pytorch/issues/78071
2032
+ maximum_value = _make_prim(
2033
+ schema="maximum_value(ScalarType dtype) -> Scalar",
2034
+ meta=_maximum_value_meta,
2035
+ impl_aten=_maximum_value_aten,
2036
+ return_type=RETURN_TYPE.NEW,
2037
+ doc=_maximum_value_doc,
2038
+ )
2039
+
2040
+
2041
+ # NOTE: need to model meta scalars
2042
+ # See https://github.com/pytorch/pytorch/issues/78070
2043
+ def _minimum_value_meta(dtype: torch.dtype) -> FakeTensor:
2044
+ number_type = utils.dtype_to_type(dtype)
2045
+ return TensorMeta(number_type(-1))
2046
+
2047
+
2048
+ def _minimum_value_aten(dtype: torch.dtype):
2049
+ if dtype == torch.bool:
2050
+ return False
2051
+ elif dtype.is_complex or dtype.is_floating_point:
2052
+ return torch.finfo(dtype).min
2053
+ else:
2054
+ return torch.iinfo(dtype).min
2055
+
2056
+
2057
+ _minimum_value_doc = """
2058
+ Return the minimum finite value for a dtype.
2059
+ """
2060
+
2061
+ # TODO: create a new return type for scalars?
2062
+ # FIXME: currently returns integers for boolean tensors
2063
+ # https://github.com/pytorch/pytorch/issues/78071
2064
+ minimum_value = _make_prim(
2065
+ schema="minimum_value(ScalarType dtype) -> Scalar",
2066
+ meta=_minimum_value_meta,
2067
+ impl_aten=_minimum_value_aten,
2068
+ return_type=RETURN_TYPE.NEW,
2069
+ doc=_minimum_value_doc,
2070
+ )
2071
+
2072
+ #
2073
+ # Inplace operators
2074
+ #
2075
+
2076
+
2077
+ def _copy_to_meta(a: TensorLikeType, b: TensorLikeType):
2078
+ assert isinstance(a, TensorLike)
2079
+ assert isinstance(b, TensorLike)
2080
+
2081
+ # Validates the cast is safe
2082
+ # TODO: move this as an option on the reference
2083
+ # a_typ = utils.dtype_to_type(a.dtype)
2084
+ # b_typ = utils.dtype_to_type(b.dtype)
2085
+ # if a_typ is not utils.get_higher_type(a_typ, b_typ):
2086
+ # raise RuntimeError(str(b.dtype), " can't be cast safely to ", str(a.dtype), "!")
2087
+
2088
+ # Validates the tensors have the same number of elements
2089
+ if a.numel() != b.numel():
2090
+ msg = f"Attempting to copy {b.numel()} elements to a tensor with {a.numel()} elements!"
2091
+ raise RuntimeError(msg)
2092
+
2093
+ return a
2094
+
2095
+
2096
+ def _copy_to_aten(a: Tensor, b: Tensor) -> Tensor:
2097
+ return a.copy_(b)
2098
+
2099
+
2100
+ _copy_to_doc = """
2101
+ Copies the data in b to a and returns the modified a.
2102
+ """
2103
+
2104
+ # TODO: Remove safe casting and implement on reference instead
2105
+ copy_to = _make_prim(
2106
+ schema="copy_to(Tensor(a!) a, Tensor b) -> Tensor(a!)",
2107
+ meta=_copy_to_meta,
2108
+ impl_aten=_copy_to_aten,
2109
+ return_type=RETURN_TYPE.INPLACE,
2110
+ doc=_copy_to_doc,
2111
+ register_conj_neg_fallthrough=True,
2112
+ )
2113
+
2114
+
2115
+ def _copy_strided_meta(a: TensorLikeType, stride: ShapeType):
2116
+ assert isinstance(a, TensorLike)
2117
+ return torch.empty_strided(
2118
+ a.shape,
2119
+ stride,
2120
+ dtype=a.dtype,
2121
+ layout=a.layout,
2122
+ device=a.device,
2123
+ requires_grad=a.requires_grad,
2124
+ )
2125
+
2126
+
2127
+ def _copy_strided_aten(a: Tensor, stride: ShapeType) -> Tensor:
2128
+ out = torch.empty_strided(
2129
+ a.size(),
2130
+ stride=stride,
2131
+ dtype=a.dtype,
2132
+ layout=a.layout,
2133
+ device=a.device,
2134
+ requires_grad=a.requires_grad,
2135
+ )
2136
+ out.copy_(a)
2137
+ return out
2138
+
2139
+
2140
+ _copy_strided_doc = """
2141
+ Copies the data in a to a new tensor, the new tensor has same shape with a size, but has different stride.
2142
+ """
2143
+
2144
+
2145
+ copy_strided = _make_prim(
2146
+ schema="copy_strided(Tensor a, SymInt[] stride) -> Tensor",
2147
+ meta=_copy_strided_meta,
2148
+ impl_aten=_copy_strided_aten,
2149
+ return_type=RETURN_TYPE.NEW,
2150
+ doc=_copy_strided_doc,
2151
+ )
2152
+
2153
+
2154
+ def _resize_meta(a: TensorLikeType, shape: ShapeType):
2155
+ return a.resize_(shape)
2156
+
2157
+
2158
+ def _resize_aten(a: Tensor, shape: ShapeType) -> Tensor:
2159
+ return a.resize_(shape)
2160
+
2161
+
2162
+ _resize_doc = """
2163
+ Gives a tensor with no elements a new shape, returning the modified tensor.
2164
+
2165
+ The tensor's strides are contiguous and its values are unitialized.
2166
+ """
2167
+
2168
+ # TODO: review support arbitrary resizes
2169
+ resize = _make_prim(
2170
+ schema="resize(Tensor(a!) a, SymInt[] shape) -> Tensor(a!)",
2171
+ meta=_resize_meta,
2172
+ impl_aten=_resize_aten,
2173
+ return_type=RETURN_TYPE.INPLACE,
2174
+ doc=_resize_doc,
2175
+ )
2176
+
2177
+
2178
+ def _reduction_meta(inp, dims, *, output_dtype=None):
2179
+ """
2180
+ Meta function for single output reduction operations
2181
+ Stride logic is incorrect
2182
+ """
2183
+ assert isinstance(inp, TensorLike)
2184
+ if output_dtype is None:
2185
+ output_dtype = inp.dtype
2186
+ output_shape = utils.compute_reduction_output_shape(inp.shape, dims)
2187
+ return TensorMeta(
2188
+ shape=output_shape,
2189
+ strides=utils.make_contiguous_strides_for(output_shape),
2190
+ dtype=output_dtype,
2191
+ device=inp.device,
2192
+ )
2193
+
2194
+
2195
+ def _var_reduction_meta(inp, dims, correction):
2196
+ if utils.is_complex_dtype(inp.dtype):
2197
+ output_dtype = utils.corresponding_real_dtype(inp.dtype)
2198
+ else:
2199
+ output_dtype = inp.dtype
2200
+ return _reduction_meta(inp, dims, output_dtype=output_dtype)
2201
+
2202
+
2203
+ _sum_doc = """
2204
+ Computes the sum of elements in the input tensor over the list of dimensions
2205
+ specified in the dim argument
2206
+ """
2207
+ _xor_sum_doc = """
2208
+ Computes the xor sum of elements in the input tensor over the list of dimensions
2209
+ specified in the dim argument
2210
+ """
2211
+ _prod_doc = """
2212
+ Computes the product of elements in the input tensor over the list of dimensions
2213
+ specified in the dim argument
2214
+ """
2215
+ _amax_doc = """
2216
+ Computes the maximum value of elements in the input tensor over the list of dimensions
2217
+ specified in the dim argument
2218
+ """
2219
+ _amin_doc = """
2220
+ Computes the minimum value of elements in the input tensor over the list of dimensions
2221
+ specified in the dim argument
2222
+ """
2223
+ _var_doc = """
2224
+ Computes the biased variance of x over the list of dimensions specified in the dim argument
2225
+ """
2226
+
2227
+
2228
+ def _make_reduction_prim(name: str, impl_aten, doc):
2229
+ """Creates a reduction prim."""
2230
+ return _make_prim(
2231
+ schema=f"{name}(Tensor inp, int[]? dims, *, ScalarType? output_dtype=None) -> Tensor",
2232
+ meta=_reduction_meta,
2233
+ impl_aten=impl_aten,
2234
+ return_type=RETURN_TYPE.NEW,
2235
+ doc=doc,
2236
+ )
2237
+
2238
+
2239
+ def _make_var_reduction_prim(name: str, impl_aten, doc):
2240
+ """Creates a reduction prim."""
2241
+ return _make_prim(
2242
+ schema=f"{name}(Tensor inp, int[]? dims, float? correction=1, *, ScalarType? output_dtype=None) -> Tensor",
2243
+ meta=_var_reduction_meta,
2244
+ impl_aten=impl_aten,
2245
+ return_type=RETURN_TYPE.NEW,
2246
+ doc=doc,
2247
+ )
2248
+
2249
+
2250
+ sum = _make_reduction_prim(
2251
+ name="sum",
2252
+ impl_aten=torch.sum,
2253
+ doc=_sum_doc,
2254
+ )
2255
+
2256
+
2257
+ def _xor_sum_aten(
2258
+ inp: TensorLikeType,
2259
+ dims: Optional[DimsSequenceType],
2260
+ *,
2261
+ dtype: Optional[torch.dtype] = None,
2262
+ ) -> Tensor:
2263
+ raise NotImplementedError("xor_sum only implemented with inductor")
2264
+
2265
+
2266
+ xor_sum = _make_reduction_prim(
2267
+ name="xor_sum",
2268
+ impl_aten=_xor_sum_aten,
2269
+ doc=_xor_sum_doc,
2270
+ )
2271
+
2272
+
2273
+ def _prod_aten(
2274
+ inp: TensorLikeType,
2275
+ dims: Optional[DimsSequenceType],
2276
+ *,
2277
+ dtype: Optional[torch.dtype] = None,
2278
+ ) -> Tensor:
2279
+ if dims is not None:
2280
+ if len(dims) == 0:
2281
+ return inp.clone()
2282
+ for d in sorted(dims, reverse=True):
2283
+ assert d >= 0
2284
+ inp = torch.prod(inp, d, dtype=dtype)
2285
+ return inp
2286
+ else:
2287
+ return torch.prod(inp, dims, dtype=dtype)
2288
+
2289
+
2290
+ prod = _make_reduction_prim(
2291
+ name="prod",
2292
+ impl_aten=_prod_aten,
2293
+ doc=_prod_doc,
2294
+ )
2295
+
2296
+
2297
+ # torch.var, but correction is not kwarg-only
2298
+ def torch_var(input, dim=None, correction=1, **kwargs):
2299
+ return torch.var(input, dim=dim, correction=correction, **kwargs)
2300
+
2301
+
2302
+ var = _make_var_reduction_prim(
2303
+ name="var",
2304
+ impl_aten=torch_var,
2305
+ doc=_var_doc,
2306
+ )
2307
+
2308
+ amax = _make_reduction_prim(
2309
+ name="amax",
2310
+ impl_aten=torch.amax,
2311
+ doc=_amax_doc,
2312
+ )
2313
+
2314
+ amin = _make_reduction_prim(
2315
+ name="amin",
2316
+ impl_aten=torch.amin,
2317
+ doc=_amin_doc,
2318
+ )
2319
+
2320
+
2321
+ _iota_doc = """
2322
+ Constructs a 1-D tensor t where ``t[i] == start + i * step``.
2323
+ """
2324
+
2325
+
2326
+ # TODO: layout, pin_memory, memory_format
2327
+ # TODO: model requires_grad on TensorMeta
2328
+ def _iota_meta(
2329
+ length: int,
2330
+ *,
2331
+ start: int,
2332
+ step: int,
2333
+ dtype: torch.dtype,
2334
+ device: torch.device,
2335
+ requires_grad: bool,
2336
+ ) -> TensorLikeType:
2337
+ torch._check(
2338
+ utils.is_integer_dtype(dtype),
2339
+ lambda: "prims.iota only supports integer dtypes",
2340
+ )
2341
+ torch._check(step != 0, lambda: "step must be nonzero")
2342
+ return torch.empty(
2343
+ length,
2344
+ dtype=dtype,
2345
+ device=device,
2346
+ requires_grad=requires_grad,
2347
+ )
2348
+
2349
+
2350
+ def _iota_aten(
2351
+ length: int,
2352
+ *,
2353
+ start: int,
2354
+ step: int,
2355
+ dtype: torch.dtype,
2356
+ device: torch.device,
2357
+ requires_grad: bool,
2358
+ ) -> TensorLikeType:
2359
+ end = start + length * step
2360
+ return torch.arange(
2361
+ start, end, step, dtype=dtype, device=device, requires_grad=requires_grad
2362
+ )
2363
+
2364
+
2365
+ iota = _make_prim(
2366
+ schema="iota(SymInt length, *, SymInt start, SymInt step, ScalarType dtype, Device device, bool requires_grad) -> Tensor", # noqa: B950
2367
+ return_type=RETURN_TYPE.NEW,
2368
+ meta=_iota_meta,
2369
+ impl_aten=_iota_aten,
2370
+ doc=_iota_doc,
2371
+ )
2372
+
2373
+
2374
+ # TODO: layout, pin_memory, memory_format
2375
+ # TODO: model requires_grad on TensorMeta
2376
+ def _empty_meta(
2377
+ shape: ShapeType, *, dtype: torch.dtype, device: torch.device, requires_grad: bool
2378
+ ) -> TensorLikeType:
2379
+ strides = utils.make_contiguous_strides_for(shape)
2380
+ return TensorMeta(shape=shape, strides=strides, dtype=dtype, device=device)
2381
+
2382
+
2383
+ def _empty_aten(
2384
+ shape: ShapeType, *, dtype: torch.dtype, device: torch.device, requires_grad: bool
2385
+ ) -> Tensor:
2386
+ return torch.empty(shape, dtype=dtype, device=device, requires_grad=requires_grad)
2387
+
2388
+
2389
+ _empty_doc = """
2390
+ Creates a tensor with uninitialized values and the specified shape, dtype, and device.
2391
+ """
2392
+
2393
+ empty = _make_prim(
2394
+ schema="empty(SymInt[] shape, *, ScalarType dtype, Device device, bool requires_grad) -> Tensor",
2395
+ meta=_empty_meta,
2396
+ impl_aten=_empty_aten,
2397
+ return_type=RETURN_TYPE.NEW,
2398
+ doc=_empty_doc,
2399
+ )
2400
+
2401
+
2402
+ def _empty_strided_meta(
2403
+ shape: ShapeType,
2404
+ strides: StrideType,
2405
+ *,
2406
+ dtype: torch.dtype,
2407
+ device: torch.device,
2408
+ requires_grad: bool,
2409
+ ) -> TensorLikeType:
2410
+ return TensorMeta(shape=shape, strides=strides, dtype=dtype, device=device)
2411
+
2412
+
2413
+ _empty_strided_doc = """
2414
+ Creates a tensor with uninitialized values.
2415
+ """
2416
+
2417
+ # TODO: add layout, pin_memory
2418
+ empty_strided = _make_prim(
2419
+ schema="empty_strided(SymInt[] shape, SymInt[] strides, *, ScalarType dtype, Device device, bool requires_grad) -> Tensor",
2420
+ return_type=RETURN_TYPE.NEW,
2421
+ meta=_empty_strided_meta,
2422
+ impl_aten=torch.empty_strided,
2423
+ doc=_empty_strided_doc,
2424
+ )
2425
+
2426
+
2427
+ def _empty_permuted_meta(
2428
+ shape: ShapeType,
2429
+ physical_layout: DimsSequenceType,
2430
+ *,
2431
+ dtype: torch.dtype,
2432
+ device: torch.device,
2433
+ requires_grad: bool,
2434
+ ) -> TensorLikeType:
2435
+ p_strides = utils.make_contiguous_strides_for([shape[l] for l in physical_layout])
2436
+ dim = len(shape)
2437
+ torch._check(
2438
+ len(physical_layout) == dim,
2439
+ lambda: (
2440
+ "Number of dimensions in the tensor input does not match the "
2441
+ f"length of the physical layout; i.e. len(size) = {dim} "
2442
+ f"is not equal to len(physical_layout) = {len(physical_layout)}"
2443
+ ),
2444
+ )
2445
+ strides = [0] * len(shape)
2446
+ seen_dims = set()
2447
+ for p, l in enumerate(physical_layout):
2448
+ torch._check(
2449
+ 0 <= l < dim,
2450
+ lambda: (
2451
+ f"Dimension out of range (expected to be between 0 and {dim - 1}, but got "
2452
+ f"{l} at index {p}). NB: negative dims "
2453
+ "not currently supported; file an issue if you want it."
2454
+ ),
2455
+ )
2456
+ torch._check(l not in seen_dims, lambda: "Duplicate dim not allowed")
2457
+ strides[l] = p_strides[p]
2458
+ seen_dims.add(l)
2459
+ return TensorMeta(
2460
+ shape=shape,
2461
+ strides=strides,
2462
+ dtype=dtype,
2463
+ device=device,
2464
+ )
2465
+
2466
+
2467
+ _empty_permuted_doc = """
2468
+ Creates a tensor with uninitialized values according to some physical layout,
2469
+ that is guaranteed to be non-overlapping and dense.
2470
+ """
2471
+
2472
+ # TODO: add layout, pin_memory
2473
+ empty_permuted = _make_prim(
2474
+ schema="empty_permuted(SymInt[] shape, int[] physical_layout, *, ScalarType dtype, Device device, bool requires_grad) -> Tensor", # noqa: B950
2475
+ return_type=RETURN_TYPE.NEW,
2476
+ meta=_empty_permuted_meta,
2477
+ impl_aten=torch.empty_permuted,
2478
+ doc=_empty_permuted_doc,
2479
+ )
2480
+
2481
+
2482
+ def _full_meta(
2483
+ shape: ShapeType,
2484
+ fill_value: NumberType,
2485
+ *,
2486
+ dtype: torch.dtype,
2487
+ device: torch.device,
2488
+ requires_grad: bool,
2489
+ ) -> TensorLikeType:
2490
+ strides = utils.make_contiguous_strides_for(shape)
2491
+ return TensorMeta(shape=shape, strides=strides, dtype=dtype, device=device)
2492
+
2493
+
2494
+ def _full_aten(
2495
+ shape: ShapeType,
2496
+ fill_value: NumberType,
2497
+ *,
2498
+ dtype: torch.dtype,
2499
+ device: torch.device,
2500
+ requires_grad: bool,
2501
+ ) -> Tensor:
2502
+ # Note that Mypy thinks torch.full can't accept a complex fill_value
2503
+ return torch.full(
2504
+ shape, fill_value, dtype=dtype, device=device, requires_grad=requires_grad # type: ignore[arg-type]
2505
+ )
2506
+
2507
+
2508
+ _full_doc = """
2509
+ Creates a tensor filled with the given fill value, and with the specified shape, dtype, and device.
2510
+ """
2511
+
2512
+ # TODO: add layout
2513
+ full = _make_prim(
2514
+ schema="full(SymInt[] shape, Scalar fill_value, *, ScalarType dtype, Device device, bool requires_grad) -> Tensor",
2515
+ meta=_full_meta,
2516
+ impl_aten=_full_aten,
2517
+ return_type=RETURN_TYPE.NEW,
2518
+ doc=_full_doc,
2519
+ )
2520
+
2521
+
2522
+ def _full_like_meta(
2523
+ a: TensorLikeType,
2524
+ fill_value: NumberType,
2525
+ *,
2526
+ dtype: torch.dtype,
2527
+ device: torch.device,
2528
+ requires_grad: bool,
2529
+ ) -> TensorLikeType:
2530
+ strides = utils.compute_elementwise_output_strides(a)
2531
+ if a.numel() == 0:
2532
+ strides = a.stride()
2533
+
2534
+ return TensorMeta(a, strides=strides, dtype=dtype, device=device)
2535
+
2536
+
2537
+ def _full_like_aten(
2538
+ a: Tensor,
2539
+ fill_value: NumberType,
2540
+ *,
2541
+ dtype: torch.dtype,
2542
+ device: torch.device,
2543
+ requires_grad: bool,
2544
+ ) -> Tensor:
2545
+ # Note that Mypy thinks torch.full can't accept a complex fill_value
2546
+ return torch.full_like(
2547
+ a, fill_value, dtype=dtype, device=device, requires_grad=requires_grad # type: ignore[arg-type]
2548
+ )
2549
+
2550
+
2551
+ _full_like_doc = """
2552
+ Creates a tensor filled with the given fill value, and the same shape, dtype, and device as the
2553
+ given tensor by default. The dtype and device settings can be overridden
2554
+ by specifying them explicitly.
2555
+ """
2556
+
2557
+ full_like = _make_prim(
2558
+ schema="full_like(Tensor a, Scalar fill_value, *, ScalarType dtype, Device device, bool requires_grad) -> Tensor",
2559
+ meta=_full_like_meta,
2560
+ impl_aten=_full_like_aten,
2561
+ return_type=RETURN_TYPE.NEW,
2562
+ doc=_full_like_doc,
2563
+ )
2564
+
2565
+
2566
+ def _scalar_tensor_meta(
2567
+ scalar: NumberType,
2568
+ *,
2569
+ dtype: torch.dtype,
2570
+ device: torch.device,
2571
+ ) -> TensorLikeType:
2572
+ shape: ShapeType = []
2573
+ strides = utils.make_contiguous_strides_for(shape)
2574
+ return TensorMeta(scalar, shape=shape, strides=strides, dtype=dtype, device=device)
2575
+
2576
+
2577
+ def _scalar_tensor_aten(
2578
+ scalar: NumberType,
2579
+ *,
2580
+ dtype: torch.dtype,
2581
+ device: torch.device,
2582
+ ) -> Tensor:
2583
+ if isinstance(scalar, complex) and (
2584
+ dtype is None or not utils.is_complex_dtype(dtype)
2585
+ ):
2586
+ raise TypeError("Complex scalar requires complex tensor dtype.")
2587
+ # Note that Mypy thinks torch.scalar can't accept a complex scalar
2588
+ return torch.scalar_tensor(scalar, dtype=dtype, device=device) # type: ignore[arg-type]
2589
+
2590
+
2591
+ _scalar_tensor_doc = """
2592
+ Wraps a Number into a Tensor with the specified dtype and device.
2593
+ """
2594
+
2595
+ # TODO: add layout and pin_memory support
2596
+ scalar_tensor = _make_prim(
2597
+ schema="scalar_tensor(Scalar s, *, ScalarType? dtype=None, Device? device=None) -> Tensor",
2598
+ meta=_scalar_tensor_meta,
2599
+ impl_aten=_scalar_tensor_aten,
2600
+ return_type=RETURN_TYPE.NEW,
2601
+ doc=_scalar_tensor_doc,
2602
+ )
2603
+
2604
+
2605
+ #
2606
+ # Linear algebra (linalg) prims
2607
+ #
2608
+
2609
+
2610
+ def _svd_meta(
2611
+ A: TensorLikeType, *, full_matrices: bool
2612
+ ) -> Tuple[TensorLikeType, TensorLikeType, TensorLikeType]:
2613
+ utils.check_is_matrix(A, "linalg.svd")
2614
+ utils.check_fp_or_complex(A.dtype, "linalg.svd", allow_low_precision_dtypes=False)
2615
+
2616
+ A_shape = A.shape
2617
+ batch = A_shape[:-2]
2618
+ m, n = A_shape[-2:]
2619
+ k = min(m, n)
2620
+
2621
+ shape_U = batch + (m, m if full_matrices else k)
2622
+ strides_U = utils.make_contiguous_strides_for(shape_U, row_major=False)
2623
+ U = TensorMeta(shape=shape_U, strides=strides_U, dtype=A.dtype, device=A.device)
2624
+
2625
+ shape_S = batch + (k,)
2626
+ strides_S = utils.make_contiguous_strides_for(shape_S)
2627
+ S = TensorMeta(
2628
+ shape=shape_S,
2629
+ strides=strides_S,
2630
+ dtype=utils.corresponding_real_dtype(A.dtype) if A.is_complex() else A.dtype,
2631
+ device=A.device,
2632
+ )
2633
+
2634
+ shape_Vh = batch + (n if full_matrices else k, n)
2635
+ # The CPU backend returns V, but the cuSolver backend returns V^H
2636
+ # TODO The MAGMA backend returns V, so this is wrong if used with the MAGMA backend
2637
+ is_cuda = A.device.type == "cuda"
2638
+ strides_Vh = utils.make_contiguous_strides_for(shape_Vh, row_major=is_cuda)
2639
+ Vh = TensorMeta(shape=shape_Vh, strides=strides_Vh, dtype=A.dtype, device=A.device)
2640
+ # Also makes sure this is CUDA or HIP:
2641
+ # https://pytorch.org/docs/stable/notes/hip.html#checking-for-hip
2642
+ if A.numel() != 0 and Vh.is_complex() and torch.cuda.is_available():
2643
+ Vh = Vh.conj()
2644
+ return U, S, Vh
2645
+
2646
+
2647
+ def _svd_aten(
2648
+ A: TensorLikeType, *, full_matrices: bool
2649
+ ) -> Tuple[Tensor, Tensor, Tensor]:
2650
+ return torch.linalg.svd(A, full_matrices=full_matrices)
2651
+
2652
+
2653
+ _svd_doc = """
2654
+ Returns the SVD of a matrix or batch of matrices.
2655
+
2656
+ The `full_matrices` flag controls whether the full or reduced SVD decomposition is returned.
2657
+ """
2658
+
2659
+ svd = _make_prim(
2660
+ schema="svd(Tensor A, *, bool full_matrices) -> (Tensor U, Tensor S, Tensor Vh)",
2661
+ meta=_svd_meta,
2662
+ impl_aten=_svd_aten,
2663
+ return_type=(RETURN_TYPE.NEW, RETURN_TYPE.NEW, RETURN_TYPE.NEW),
2664
+ doc=_svd_doc,
2665
+ )
2666
+
2667
+
2668
+ #
2669
+ # Randomness Prims
2670
+ #
2671
+
2672
+
2673
+ def _normal_meta(
2674
+ shape: ShapeType,
2675
+ *,
2676
+ mean: Union[float, complex],
2677
+ std: float,
2678
+ dtype: torch.dtype,
2679
+ device: torch.device,
2680
+ requires_grad: bool,
2681
+ generator: Optional[torch.Generator] = None,
2682
+ ) -> TensorLikeType:
2683
+ torch._check(
2684
+ std >= 0.0,
2685
+ lambda: f"expected non-negative standard deviation, but got std={std}",
2686
+ )
2687
+
2688
+ torch._check(
2689
+ utils.is_float_dtype(dtype) or utils.is_complex_dtype(dtype),
2690
+ lambda: f"expected a floating-point or complex dtype, but got dtype={dtype}",
2691
+ )
2692
+
2693
+ strides = utils.make_contiguous_strides_for(shape)
2694
+ return TensorMeta(shape=shape, strides=strides, dtype=dtype, device=device)
2695
+
2696
+
2697
+ def _normal_aten(
2698
+ shape: ShapeType,
2699
+ *,
2700
+ mean: Union[float, complex],
2701
+ std: float,
2702
+ dtype: torch.dtype,
2703
+ device: torch.device,
2704
+ requires_grad: bool,
2705
+ generator: Optional[torch.Generator] = None,
2706
+ ) -> Tensor:
2707
+ a = torch.empty(shape, dtype=dtype, device=device, requires_grad=requires_grad)
2708
+ with torch.no_grad():
2709
+ # NOTE: normal_ is incorrectly annotated to expect mean to be a float
2710
+ a.normal_(mean, std, generator=generator) # type: ignore[arg-type]
2711
+ return a
2712
+
2713
+
2714
+ _normal_doc = """
2715
+ Constructs a tensor filled with values drawn from a normal distribution with the specified mean
2716
+ and standard deviation.
2717
+
2718
+ Only supports floating-point types.
2719
+ """
2720
+
2721
+ normal = _make_prim(
2722
+ schema=(
2723
+ "normal(SymInt[] shape, *, Scalar mean, Scalar std, ScalarType dtype, Device device, bool requires_grad, Generator? generator=None) -> Tensor" # noqa: B950
2724
+ ),
2725
+ return_type=RETURN_TYPE.NEW,
2726
+ meta=_normal_meta,
2727
+ impl_aten=_normal_aten,
2728
+ doc=_normal_doc,
2729
+ )
2730
+
2731
+
2732
+ def _uniform_meta(
2733
+ shape: ShapeType,
2734
+ *,
2735
+ low: float,
2736
+ high: float,
2737
+ dtype: torch.dtype,
2738
+ device: torch.device,
2739
+ generator: Optional[torch.Generator] = None,
2740
+ ) -> TensorLikeType:
2741
+ strides = utils.make_contiguous_strides_for(shape)
2742
+ return TensorMeta(shape=shape, strides=strides, dtype=dtype, device=device)
2743
+
2744
+
2745
+ def _uniform_aten(
2746
+ shape: ShapeType,
2747
+ *,
2748
+ low: float,
2749
+ high: float,
2750
+ dtype: torch.dtype,
2751
+ device: torch.device,
2752
+ generator: Optional[torch.Generator] = None,
2753
+ ) -> Tensor:
2754
+ a = torch.empty(shape, dtype=dtype, device=device)
2755
+ a.uniform_(low, high, generator=generator)
2756
+ return a
2757
+
2758
+
2759
+ _uniform_doc = """
2760
+ Constructs a tensor filled with values drawn uniformly from low to high.
2761
+ """
2762
+
2763
+ # TODO: we should more seriously review randomness modeling and prims
2764
+ _uniform_helper = _make_prim(
2765
+ schema=(
2766
+ "uniform(SymInt[] shape, *, Scalar low, Scalar high, ScalarType dtype, Device device, Generator? generator=None) -> Tensor"
2767
+ ),
2768
+ return_type=RETURN_TYPE.NEW,
2769
+ meta=_uniform_meta,
2770
+ impl_aten=_uniform_aten,
2771
+ doc=_uniform_doc,
2772
+ )
2773
+
2774
+ #
2775
+ # FFT prims
2776
+ #
2777
+
2778
+
2779
+ def _fft_r2c_meta(
2780
+ input: TensorLike,
2781
+ *,
2782
+ dim: DimsSequenceType,
2783
+ onesided: bool,
2784
+ ) -> TensorLikeType:
2785
+ dim = utils.canonicalize_dims(input.ndim, dim)
2786
+ utils.validate_no_repeating_dims(dim)
2787
+
2788
+ shape = list(input.shape)
2789
+ if onesided:
2790
+ last_dim = dim[-1]
2791
+ shape[last_dim] = shape[last_dim] // 2 + 1
2792
+
2793
+ dtype = utils.corresponding_complex_dtype(input.dtype)
2794
+ strides = utils.make_contiguous_strides_for(shape)
2795
+ return TensorMeta(shape=shape, strides=strides, dtype=dtype, device=input.device)
2796
+
2797
+
2798
+ def _fft_r2c_aten(
2799
+ input: TensorLike,
2800
+ *,
2801
+ dim: DimsSequenceType,
2802
+ onesided: bool,
2803
+ ) -> TensorLikeType:
2804
+ normalization = 0 # No normalization
2805
+ return torch._fft_r2c(input, dim, normalization, onesided)
2806
+
2807
+
2808
+ _fft_r2c_doc = """
2809
+ Performs a real to complex Fast Fourier Transform
2810
+ """
2811
+
2812
+
2813
+ fft_r2c = _make_prim(
2814
+ schema="fft_r2c(Tensor self, *, int[] dim, bool onesided) -> Tensor",
2815
+ meta=_fft_r2c_meta,
2816
+ impl_aten=_fft_r2c_aten,
2817
+ return_type=RETURN_TYPE.NEW,
2818
+ doc=_fft_r2c_doc,
2819
+ )
2820
+
2821
+
2822
+ def _fft_c2c_meta(
2823
+ input: TensorLike,
2824
+ *,
2825
+ dim: DimsSequenceType,
2826
+ forward: bool,
2827
+ ) -> TensorLikeType:
2828
+ dim = utils.canonicalize_dims(input.ndim, dim)
2829
+ utils.validate_no_repeating_dims(dim)
2830
+
2831
+ shape = input.shape
2832
+ strides = utils.make_contiguous_strides_for(shape)
2833
+ return TensorMeta(
2834
+ shape=shape, strides=strides, dtype=input.dtype, device=input.device
2835
+ )
2836
+
2837
+
2838
+ def _fft_c2c_aten(
2839
+ input: TensorLike,
2840
+ *,
2841
+ dim: DimsSequenceType,
2842
+ forward: bool,
2843
+ ) -> TensorLikeType:
2844
+ normalization = 0 # No normalization
2845
+ return torch._fft_c2c(input, dim, normalization, forward)
2846
+
2847
+
2848
+ _fft_c2c_doc = """
2849
+ Performs either a Fast Fourier Transform, or its inverse
2850
+ """
2851
+
2852
+
2853
+ fft_c2c = _make_prim(
2854
+ schema="fft_c2c(Tensor self, *, int[] dim, bool forward) -> Tensor",
2855
+ meta=_fft_c2c_meta,
2856
+ impl_aten=_fft_c2c_aten,
2857
+ return_type=RETURN_TYPE.NEW,
2858
+ doc=_fft_c2c_doc,
2859
+ )
2860
+
2861
+
2862
+ def _fft_c2r_meta(
2863
+ input: TensorLike,
2864
+ *,
2865
+ dim: DimsSequenceType,
2866
+ last_dim_size: int,
2867
+ ) -> TensorLikeType:
2868
+ dim = utils.canonicalize_dims(input.ndim, dim)
2869
+ utils.validate_no_repeating_dims(dim)
2870
+
2871
+ shape = list(input.shape)
2872
+ shape[dim[-1]] = last_dim_size
2873
+ dtype = utils.corresponding_real_dtype(input.dtype)
2874
+ strides = utils.make_contiguous_strides_for(shape)
2875
+ return TensorMeta(shape=shape, strides=strides, dtype=dtype, device=input.device)
2876
+
2877
+
2878
+ def _fft_c2r_aten(
2879
+ input: TensorLike,
2880
+ *,
2881
+ dim: DimsSequenceType,
2882
+ last_dim_size: int,
2883
+ ) -> TensorLikeType:
2884
+ normalization = 0 # No normalization
2885
+ return torch._fft_c2r(input, dim, normalization, last_dim_size)
2886
+
2887
+
2888
+ _fft_c2r_doc = """
2889
+ Performs a complex to real Inverse Fast Fourier Transform
2890
+ """
2891
+
2892
+
2893
+ fft_c2r = _make_prim(
2894
+ schema="fft_c2r(Tensor self, *, int[] dim, SymInt last_dim_size) -> Tensor",
2895
+ meta=_fft_c2r_meta,
2896
+ impl_aten=_fft_c2r_aten,
2897
+ return_type=RETURN_TYPE.NEW,
2898
+ doc=_fft_c2r_doc,
2899
+ )
2900
+
2901
+
2902
+ def _frexp_meta(self: TensorLikeType) -> Tuple[TensorLikeType, TensorLikeType]:
2903
+ torch._check(
2904
+ self.dtype.is_floating_point,
2905
+ lambda: "torch.frexp() only supports floating-point dtypes",
2906
+ )
2907
+ return torch.empty_like(self), torch.empty_like(self, dtype=torch.int32)
2908
+
2909
+
2910
+ frexp = _make_prim(
2911
+ schema="frexp(Tensor self) -> (Tensor mantissa, Tensor exponent)",
2912
+ meta=_frexp_meta,
2913
+ return_type=(RETURN_TYPE.NEW, RETURN_TYPE.NEW),
2914
+ impl_aten=torch.frexp,
2915
+ doc="",
2916
+ )
2917
+
2918
+
2919
+ def _make_token_aten() -> TensorLikeType:
2920
+ return new_token_tensor()
2921
+
2922
+
2923
+ _make_token = _make_prim(
2924
+ schema="_make_token() -> Tensor",
2925
+ meta=_make_token_aten,
2926
+ return_type=RETURN_TYPE.NEW,
2927
+ impl_aten=_make_token_aten,
2928
+ doc="Creates a token used for keeping track of side effects.",
2929
+ )
2930
+
2931
+
2932
+ def _sink_tokens_aten(tokens) -> None:
2933
+ pass
2934
+
2935
+
2936
+ _sink_tokens = _make_prim(
2937
+ schema="_sink_tokens(Tensor[] tokens) -> ()",
2938
+ meta=_sink_tokens_aten,
2939
+ return_type=RETURN_TYPE.NONE,
2940
+ impl_aten=_sink_tokens_aten,
2941
+ doc="Sink all of the tokens which were previously used for keeping track of side effects.",
2942
+ )
2943
+
2944
+
2945
+ register_rng_prims()
2946
+ register_debug_prims()
lib/python3.12/site-packages/torch/_prims/context.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ import functools
3
+ from contextlib import nullcontext
4
+ from typing import Any, Callable, Dict, Optional, Sequence
5
+
6
+ import torch
7
+ import torch._decomp
8
+ import torch._prims
9
+ import torch._refs
10
+ import torch._refs.nn
11
+ import torch._refs.nn.functional
12
+ import torch._refs.special
13
+ import torch.overrides
14
+ from torch._prims_common import torch_function_passthrough
15
+
16
+
17
+ @functools.lru_cache(None)
18
+ def torch_to_refs_map():
19
+ """
20
+ Mapping of torch API functions to torch._refs functions.
21
+ E.g. torch_to_refs_map()[torch.add] == torch._refs.add
22
+ """
23
+ modules = [
24
+ (torch, torch._refs),
25
+ (torch.nn, torch._refs.nn),
26
+ (torch.nn.functional, torch._refs.nn.functional),
27
+ (torch.special, torch._refs.special),
28
+ (torch.fft, torch._refs.fft),
29
+ (torch.linalg, torch._refs.linalg),
30
+ ]
31
+ r: Dict[Any, Any] = {
32
+ torch.Tensor.__invert__: torch._refs.bitwise_not,
33
+ torch.Tensor.__xor__: torch._refs.bitwise_xor,
34
+ torch.Tensor.__and__: torch._refs.bitwise_and,
35
+ torch.Tensor.__or__: torch._refs.bitwise_or,
36
+ torch.Tensor.__eq__: torch._refs.eq,
37
+ torch.Tensor.__rsub__: torch._refs.rsub,
38
+ torch.Tensor.__rtruediv__: torch._refs.rtruediv,
39
+ torch.Tensor.__floordiv__: torch._refs.floor_divide,
40
+ torch.Tensor.__rfloordiv__: torch._refs.rfloordiv,
41
+ torch.Tensor.__pow__: torch._refs.pow,
42
+ torch.Tensor.__rpow__: torch._refs.rpow,
43
+ torch.Tensor.new_empty: torch._refs.new_empty,
44
+ torch.Tensor.new_full: torch._refs.new_full,
45
+ torch.Tensor.new_zeros: torch._refs.new_zeros,
46
+ torch.Tensor.new_ones: torch._refs.new_ones,
47
+ torch.Tensor.fill_: torch._refs.fill_,
48
+ torch.Tensor.zero_: torch._refs.zero_,
49
+ torch.Tensor.to: torch._refs.to,
50
+ torch.Tensor.sum_to_size: torch._refs.sum_to_size,
51
+ # TODO: Should these methods be mapped some other way?
52
+ torch.Tensor.copy_: torch._prims.copy_to,
53
+ torch.Tensor.resize: torch._prims.resize,
54
+ }
55
+ for mod_torch, mod_refs in modules:
56
+ for s in mod_refs.__all__: # type: ignore[attr-defined]
57
+ r[mod_torch.__dict__.get(s)] = mod_refs.__dict__.get(s)
58
+
59
+ # Support remapping torch.Tensor.foo to _refs.foo
60
+ for s in dir(torch.Tensor):
61
+ if s in torch._refs.__all__:
62
+ r[getattr(torch.Tensor, s)] = torch._refs.__dict__.get(s)
63
+
64
+ # Support conversions
65
+ for s in torch._refs._conversions.__all__:
66
+ tensor_attr = getattr(torch.Tensor, s, None) or getattr(torch, s)
67
+ r[tensor_attr] = torch._refs._conversions.__dict__.get(s)
68
+
69
+ return r
70
+
71
+
72
+ @functools.lru_cache(None)
73
+ def all_prims():
74
+ """
75
+ Set of all prim functions, e.g., torch._prims.add in all_prims()
76
+ """
77
+ return {torch._prims.__dict__.get(s) for s in torch._prims.__all__}
78
+
79
+
80
+ class TorchRefsMode(torch.overrides.TorchFunctionMode):
81
+ """
82
+ Switches the interpretation of torch.* functions and Tensor methods to
83
+ use PrimTorch refs in torch._refs. (Direct calls to _refs are unaffected.)
84
+
85
+ >>> # xdoctest: +SKIP
86
+ >>> with TorchRefsMode():
87
+ ... torch.add(x, y) # calls torch._refs.add(x, y)
88
+
89
+ By default, this context manager will fall back on the torch.* if the
90
+ ref does not exist; set strict=True to error if this occurs.
91
+ If the ref exists we still would like to fall back on the torch.* sometimes,
92
+ this behavior can be customized by passing a function to should_fallback_fn.
93
+ """
94
+
95
+ def __init__(
96
+ self,
97
+ strict=False,
98
+ should_fallback_fn=lambda *_: False,
99
+ prims_mode_cls=nullcontext,
100
+ ):
101
+ self.strict = strict
102
+ self.should_fallback_fn = should_fallback_fn
103
+ self.prims_mode_cls = prims_mode_cls
104
+
105
+ def __torch_function__(
106
+ self,
107
+ orig_func: Callable,
108
+ types: Sequence,
109
+ args: Sequence[Any] = (),
110
+ kwargs: Optional[Dict] = None,
111
+ ):
112
+ if kwargs is None:
113
+ kwargs = {}
114
+ # For primitive operations, run them as is without interception
115
+ # Unless we are in prims_mode, in which case we want to use nvprims
116
+ if orig_func in torch_function_passthrough or orig_func in all_prims():
117
+ with self.prims_mode_cls():
118
+ return orig_func(*args, **kwargs)
119
+ mapping = torch_to_refs_map()
120
+ func = mapping.get(orig_func, None)
121
+
122
+ # For torch.ops.aten.*, use registered decompositions from torch._decomp
123
+ # torch._decomp.decomposition_table provides a mapping from
124
+ # torch.ops.aten.* to torch._refs or torch._decomp.decompositions
125
+ # implementations.
126
+ # There're other ways to implement this functionality,
127
+ # see https://github.com/pytorch/pytorch/pull/82657#discussion_r939776417
128
+ if func is None and isinstance(orig_func, torch._ops.OpOverload):
129
+ func = torch._decomp.decomposition_table.get(orig_func, None)
130
+ elif func is None and isinstance(orig_func, torch._ops.OpOverloadPacket):
131
+ default = getattr(orig_func, "default", None)
132
+ if default is not None:
133
+ func = torch._decomp.decomposition_table.get(default, None)
134
+
135
+ if func is not None:
136
+ # If the ref exists query whether we should use it or not
137
+ if self.should_fallback_fn(self, orig_func, func, args, kwargs):
138
+ return orig_func(*args, **kwargs)
139
+ # torch calls inside func should be interpreted as refs calls
140
+ with self:
141
+ return func(*args, **kwargs)
142
+ if self.strict:
143
+ raise RuntimeError(
144
+ f"no _refs support for {torch.overrides.resolve_name(orig_func)}"
145
+ )
146
+ return orig_func(*args, **kwargs)
lib/python3.12/site-packages/torch/_prims/debug_prims.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ import contextlib
3
+ from typing import Optional
4
+
5
+ import torch
6
+ from torch.utils._content_store import ContentStoreReader
7
+
8
+
9
+ LOAD_TENSOR_READER: Optional[ContentStoreReader] = None
10
+
11
+
12
+ @contextlib.contextmanager
13
+ def load_tensor_reader(loc):
14
+ global LOAD_TENSOR_READER
15
+ assert LOAD_TENSOR_READER is None
16
+ # load_tensor is an "op", and we will play merry hell on
17
+ # Inductor's memory planning if we return a tensor that
18
+ # aliases another tensor that we previously returned from
19
+ # an operator. So unlike standard ContentStoreReader use,
20
+ # we disable the cache so that you always get fresh storages
21
+ # (no aliasing for you!)
22
+ LOAD_TENSOR_READER = ContentStoreReader(loc, cache=False)
23
+ try:
24
+ yield
25
+ finally:
26
+ LOAD_TENSOR_READER = None
27
+
28
+
29
+ def register_debug_prims():
30
+ torch.library.define(
31
+ "debugprims::load_tensor",
32
+ "(str name, int[] size, int[] stride, *, ScalarType dtype, Device device) -> Tensor",
33
+ )
34
+
35
+ @torch.library.impl("debugprims::load_tensor", "BackendSelect")
36
+ def load_tensor_factory(name, size, stride, dtype, device):
37
+ if LOAD_TENSOR_READER is None:
38
+ from torch._dynamo.testing import rand_strided
39
+
40
+ return rand_strided(size, stride, dtype, device)
41
+ else:
42
+ from torch._dynamo.utils import clone_input
43
+
44
+ # device argument here takes care of coercion
45
+ r = LOAD_TENSOR_READER.read_tensor(name, device=device)
46
+ assert list(r.size()) == size, f"{r.size()} != {size}"
47
+ assert list(r.stride()) == stride, f"{r.stride()} != {stride}"
48
+ assert r.device == device, f"{r.device} != {device}"
49
+
50
+ # Unlike the other properties, we will do coercions for dtype
51
+ # mismatch
52
+ if r.dtype != dtype:
53
+ r = clone_input(r, dtype=dtype)
54
+ return r
lib/python3.12/site-packages/torch/_prims/executor.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ from typing import Callable, Optional
3
+
4
+ from torch._prims.context import TorchRefsMode
5
+ from torch.fx import GraphModule
6
+ from torch.fx.experimental.proxy_tensor import make_fx, wrapper_and_args_for_make_fx
7
+
8
+
9
+ def execute(
10
+ gm: GraphModule,
11
+ *args,
12
+ executor: str = "aten",
13
+ executor_parameters: Optional[dict] = None,
14
+ ):
15
+ """
16
+ Prototype ATen executor.
17
+
18
+ Just executes the context's graph.
19
+ """
20
+
21
+ if executor == "aten":
22
+ return gm.forward(*args)
23
+
24
+ msg = f"Received unexpected value for 'executor': {executor}. Allowed values are: aten."
25
+ raise ValueError(msg)
26
+
27
+
28
+ def make_traced(fn: Callable):
29
+ """
30
+ Returns a function that, when called, will
31
+ trace its torch operations to prims and then
32
+ execute those prims on the requested trace executor
33
+ (possibly lowering them to that trace executor first).
34
+
35
+ Only supports the torch operations defined in _torch_to_reference_map
36
+ in context.py and operations with positional args. All args must
37
+ be tensors.
38
+ In the near future all these restrictions will be lifted.
39
+
40
+ Example usage:
41
+
42
+ def foo(a, b):
43
+ return torch.add(a, b)
44
+
45
+ traced_foo = make_traced(foo)
46
+
47
+ a = torch.randn((1, 2, 3, 4, 5), device='cuda')
48
+ b = torch.randn((1, 2, 3, 4, 5), device='cuda')
49
+ result = traced_foo(a, b, executor='aten')
50
+ """
51
+
52
+ def _traced(*args, executor="aten", **kwargs):
53
+ # TODO: caching
54
+ wrapped, all_args = wrapper_and_args_for_make_fx(fn, args, kwargs)
55
+
56
+ with TorchRefsMode():
57
+ gm = make_fx(wrapped)(all_args)
58
+ return execute(gm, all_args, executor=executor)
59
+
60
+ return _traced
lib/python3.12/site-packages/torch/_prims/rng_prims.py ADDED
@@ -0,0 +1,319 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ from typing import Optional, Tuple
3
+
4
+ import torch
5
+ import torch.utils._pytree as pytree
6
+ from torch import _prims
7
+ from torch._C import DispatchKey
8
+ from torch._higher_order_ops.utils import autograd_not_implemented
9
+ from torch._ops import HigherOrderOperator
10
+ from torch._prims_common import CUDARngStateHelper, make_contiguous_strides_for
11
+ from torch._subclasses.fake_tensor import FakeTensorMode
12
+ from torch.fx.experimental.proxy_tensor import (
13
+ disable_proxy_modes_tracing,
14
+ ProxyTorchDispatchMode,
15
+ track_tensor_tree,
16
+ )
17
+ from torch.types import _device, _dtype
18
+
19
+
20
+ def throw_on_non_cuda(device):
21
+ raise RuntimeError(
22
+ f"You are trying to functionalize a {device.type} RNG operator but {device.type} does not "
23
+ f"use Philox/counter-based RNG. Therefore, functionalizing a {device.type} RNG operator is "
24
+ "not supported. We are discussing the possibility of a Philox-based RNG implementation for CPU."
25
+ )
26
+
27
+
28
+ def register_rng_prim(name, schema, impl_aten, impl_meta, doc, tags=None):
29
+ rngprim_def = torch.library.custom_op(
30
+ "rngprims::" + name, impl_aten, mutates_args=(), schema=schema
31
+ )
32
+ rngprim_def.register_fake(impl_meta)
33
+
34
+ prim_packet = getattr(torch._ops.ops.rngprims, name)
35
+ prim = prim_packet.default
36
+ if tags:
37
+ prim._tags = tags
38
+
39
+ for p in (prim_packet, prim):
40
+ p.__doc__ = doc
41
+ p.return_type = torch._prims_common.RETURN_TYPE.NEW # type: ignore[attr-defined]
42
+
43
+ p.schema = name + schema
44
+ p.impl_aten = impl_aten
45
+ p.prim_meta_impl = impl_meta
46
+
47
+
48
+ # Philox rand offsets could be shared in future with other philox ops, so
49
+ # keeping these functions in global scope.
50
+ def philox_rand_offset_meta(
51
+ shape: torch.Size,
52
+ ):
53
+ return _prims.TensorLike(torch.tensor(0, dtype=torch.int64))
54
+
55
+
56
+ def philox_rand_offset(
57
+ shape: torch.Size,
58
+ ):
59
+ # For impl, look at the function calc_execution_policy in the file
60
+ # aten/src/ATen/native/cuda/DistributionTemplates.h. The impl was copied at
61
+ # commit hash 72aa0667bd16707d50eb8fa337092a1f5d11dfb6
62
+ numel_scalar = 1
63
+ for dim_size in shape:
64
+ numel_scalar *= dim_size
65
+ numel = torch.scalar_tensor(numel_scalar, dtype=torch.int64)
66
+
67
+ block_size = 256
68
+ unroll = 4
69
+ curand4_engine_calls = 4
70
+ device_property = torch.cuda.get_device_properties(torch.cuda.current_device())
71
+ blocks_per_sm = device_property.max_threads_per_multi_processor // block_size
72
+ grid_size = (numel + block_size - 1) // block_size
73
+ grid_size = min(grid_size, device_property.multi_processor_count * blocks_per_sm)
74
+ offset = (
75
+ (numel - 1) // (block_size * grid_size * unroll) + 1
76
+ ) * curand4_engine_calls
77
+ return offset
78
+
79
+
80
+ def register_philox_rand():
81
+ name = "philox_rand"
82
+ schema = "(SymInt[] size, Tensor seed, Tensor offset, int[]? stride, Device? device=None, ScalarType? dtype=None) -> (Tensor, Tensor)" # noqa: B950
83
+
84
+ def _philox_rand_meta(
85
+ shape: torch.Size,
86
+ seed: torch.Tensor,
87
+ offset: torch.Tensor,
88
+ stride: Optional[Tuple[int, ...]],
89
+ device: _device,
90
+ dtype: _dtype,
91
+ ):
92
+ # stride arg will be useful for distributed usecase. Currently, its unused.
93
+ assert stride is None
94
+ stride = make_contiguous_strides_for(shape)
95
+ random_values = _prims.TensorMeta(
96
+ shape=shape, strides=stride, dtype=dtype, device=device
97
+ )
98
+ offset = philox_rand_offset_meta(shape)
99
+ return (random_values, offset)
100
+
101
+ def _philox_rand(
102
+ shape: torch.Size,
103
+ seed: torch.Tensor,
104
+ offset: torch.Tensor,
105
+ stride: Optional[Tuple[int, ...]],
106
+ device: _device,
107
+ dtype: _dtype,
108
+ ):
109
+ # stride arg will be useful for distributed usecase. Currently, its unused.
110
+ assert stride is None
111
+ if device.type == "cpu":
112
+ devices = []
113
+ else:
114
+ devices = [device]
115
+
116
+ if device.type != "cuda":
117
+ raise throw_on_non_cuda(device)
118
+
119
+ with torch.random.fork_rng(devices):
120
+ CUDARngStateHelper.set_torch_state_tensor(seed, offset)
121
+ random_values = torch.rand(shape, device=device, dtype=dtype)
122
+
123
+ return random_values, philox_rand_offset(shape)
124
+
125
+ register_rng_prim(
126
+ name=name,
127
+ schema=schema,
128
+ impl_aten=_philox_rand,
129
+ impl_meta=_philox_rand_meta,
130
+ doc="Philox based stateless rand operator",
131
+ tags=(torch.Tag.nondeterministic_seeded,),
132
+ )
133
+
134
+
135
+ def get_device(args, kwargs):
136
+ if kwargs.get("device"):
137
+ device = kwargs.get("device")
138
+ if isinstance(device, str):
139
+ device = torch.device(device)
140
+ return device.type
141
+
142
+ devices = {arg.device.type for arg in args if isinstance(arg, torch.Tensor)}
143
+ if any(dev == "cuda" for dev in devices):
144
+ return "cuda"
145
+ elif any(dev == "xpu" for dev in devices):
146
+ return "xpu"
147
+ elif any(dev == "hpu" for dev in devices):
148
+ return "hpu"
149
+ elif any(dev == "cpu" for dev in devices):
150
+ return "cpu"
151
+ return None
152
+
153
+
154
+ def register_run_and_save_rng_state_op():
155
+ class RunAndSaveRngState(HigherOrderOperator):
156
+ def __init__(self):
157
+ super().__init__("run_and_save_rng_state")
158
+
159
+ def __call__(self, op, *args, **kwargs):
160
+ return super().__call__(op, *args, **kwargs)
161
+
162
+ run_and_save_rng_state = RunAndSaveRngState()
163
+
164
+ run_and_save_rng_state.py_impl(DispatchKey.Autograd)(
165
+ autograd_not_implemented(run_and_save_rng_state, deferred_error=True)
166
+ )
167
+
168
+ @run_and_save_rng_state.py_impl(DispatchKey.CUDA)
169
+ def impl_cuda(op, *args, **kwargs):
170
+ return torch.cuda.get_rng_state(), op(*args, **kwargs)
171
+
172
+ @run_and_save_rng_state.py_impl(DispatchKey.CPU)
173
+ def impl_cpu(op, *args, **kwargs):
174
+ return torch.get_rng_state(), op(*args, **kwargs)
175
+
176
+ @run_and_save_rng_state.py_impl(DispatchKey.HPU)
177
+ def impl_hpu(op, *args, **kwargs):
178
+ if hasattr(torch, "hpu"):
179
+ return torch.hpu.get_rng_state(), op(*args, **kwargs)
180
+ raise RuntimeError("functionalize a hpu RNG operator is not supported.")
181
+
182
+ @run_and_save_rng_state.py_impl(DispatchKey.XPU)
183
+ def impl_xpu(op, *args, **kwargs):
184
+ return torch.xpu.get_rng_state(), op(*args, **kwargs)
185
+
186
+ @run_and_save_rng_state.py_impl(DispatchKey.BackendSelect)
187
+ def impl_backend_select(op, *args, **kwargs):
188
+ impl_map = {
189
+ "cuda": impl_cuda,
190
+ "cpu": impl_cpu,
191
+ "hpu": impl_hpu,
192
+ "xpu": impl_xpu,
193
+ }
194
+ device = get_device(args, kwargs)
195
+ assert device in impl_map, f"Backend not supported for {device}"
196
+ impl = impl_map[device]
197
+ return impl(op, *args, **kwargs)
198
+
199
+ @run_and_save_rng_state.py_impl(FakeTensorMode)
200
+ def impl_fake_tensor_mode(mode, op, *args, **kwargs):
201
+ # Check device to call the right impl
202
+ with mode:
203
+ return impl_backend_select(op, *args, **kwargs)
204
+
205
+ @run_and_save_rng_state.py_impl(ProxyTorchDispatchMode)
206
+ def impl_proxy_dispatch_mode(mode, op, *args, **kwargs):
207
+ out = impl_backend_select(op, *args, **kwargs)
208
+ proxy_args = pytree.tree_map(mode.tracer.unwrap_proxy, (op, *args))
209
+ proxy_kwargs = pytree.tree_map(mode.tracer.unwrap_proxy, kwargs)
210
+ out_proxy = mode.tracer.create_proxy(
211
+ "call_function", run_and_save_rng_state, proxy_args, proxy_kwargs
212
+ )
213
+ return track_tensor_tree(out, out_proxy, constant=None, tracer=mode.tracer)
214
+
215
+ return run_and_save_rng_state
216
+
217
+
218
+ def register_run_with_rng_state_op():
219
+ class RunWithRngState(HigherOrderOperator):
220
+ def __init__(self):
221
+ super().__init__("run_with_rng_state")
222
+
223
+ def __call__(self, rng_state, op, *args, **kwargs):
224
+ return super().__call__(rng_state, op, *args, **kwargs)
225
+
226
+ run_with_rng_state = RunWithRngState()
227
+
228
+ run_with_rng_state.py_impl(DispatchKey.Autograd)(
229
+ autograd_not_implemented(run_with_rng_state, deferred_error=True)
230
+ )
231
+
232
+ @run_with_rng_state.py_impl(DispatchKey.CUDA)
233
+ def impl_cuda(rng_state, op, *args, **kwargs):
234
+ current_state = torch.cuda.get_rng_state()
235
+ torch.cuda.set_rng_state(rng_state.cpu())
236
+ out = op(*args, **kwargs)
237
+ torch.cuda.set_rng_state(current_state)
238
+ return out
239
+
240
+ @run_with_rng_state.py_impl(DispatchKey.CPU)
241
+ def impl_cpu(rng_state, op, *args, **kwargs):
242
+ current_state = torch.get_rng_state()
243
+ torch.set_rng_state(rng_state)
244
+ out = op(*args, **kwargs)
245
+ torch.set_rng_state(current_state)
246
+ return out
247
+
248
+ @run_with_rng_state.py_impl(DispatchKey.HPU)
249
+ def impl_hpu(rng_state, op, *args, **kwargs):
250
+ if hasattr(torch, "hpu"):
251
+ current_state = torch.hpu.get_rng_state()
252
+ torch.hpu.set_rng_state(rng_state)
253
+ out = op(*args, **kwargs)
254
+ torch.hpu.set_rng_state(current_state)
255
+ return out
256
+ raise RuntimeError("functionalize a hpu RNG operator is not supported.")
257
+
258
+ @run_with_rng_state.py_impl(DispatchKey.XPU)
259
+ def impl_xpu(rng_state, op, *args, **kwargs):
260
+ current_state = torch.xpu.get_rng_state()
261
+ torch.xpu.set_rng_state(rng_state)
262
+ out = op(*args, **kwargs)
263
+ torch.xpu.set_rng_state(current_state)
264
+ return out
265
+
266
+ @run_with_rng_state.py_impl(ProxyTorchDispatchMode)
267
+ def impl_proxy_dispatch_mode(mode, rng_state, op, *args, **kwargs):
268
+ # TODO: you don't need to do this, the dispatch here already disabled
269
+ # it
270
+ with disable_proxy_modes_tracing():
271
+ out = run_with_rng_state(rng_state, op, *args, **kwargs)
272
+ proxy_args = pytree.tree_map(mode.tracer.unwrap_proxy, (rng_state, op, *args))
273
+ proxy_kwargs = pytree.tree_map(mode.tracer.unwrap_proxy, kwargs)
274
+ out_proxy = mode.tracer.create_proxy(
275
+ "call_function", run_with_rng_state, proxy_args, proxy_kwargs
276
+ )
277
+ return track_tensor_tree(out, out_proxy, constant=None, tracer=mode.tracer)
278
+
279
+ @run_with_rng_state.py_impl(DispatchKey.BackendSelect)
280
+ def impl_backend_select(rng_state, op, *args, **kwargs):
281
+ impl_map = {
282
+ "cuda": impl_cuda,
283
+ "cpu": impl_cpu,
284
+ "hpu": impl_hpu,
285
+ "xpu": impl_xpu,
286
+ }
287
+ device = get_device(args, kwargs)
288
+ assert device in impl_map, f"Backend not supported for {device}"
289
+ impl = impl_map[device]
290
+ return impl(rng_state, op, *args, **kwargs)
291
+
292
+ @run_with_rng_state.py_impl(FakeTensorMode)
293
+ def impl_fake_tensor_mode(mode, rng_state, op, *args, **kwargs):
294
+ # Skip setting the set_rng_state as it does not work well with fake tensors.
295
+ # And it does not matter for the fake tensor mode.
296
+ with mode:
297
+ return op(*args, **kwargs)
298
+
299
+ @run_with_rng_state.py_functionalize_impl
300
+ def impl_functional(ctx, rng_state, op, *args, **kwargs):
301
+ unwrapped_rng_state = ctx.unwrap_tensors(rng_state)
302
+ unwrapped_args = ctx.unwrap_tensors(args)
303
+ unwrapped_kwargs = ctx.unwrap_tensors(kwargs)
304
+
305
+ with ctx.redispatch_to_next():
306
+ out = run_with_rng_state(
307
+ unwrapped_rng_state, op, *unwrapped_args, **unwrapped_kwargs
308
+ )
309
+ return ctx.wrap_tensors(out)
310
+
311
+ return run_with_rng_state
312
+
313
+
314
+ run_and_save_rng_state = register_run_and_save_rng_state_op()
315
+ run_with_rng_state = register_run_with_rng_state_op()
316
+
317
+
318
+ def register_rng_prims():
319
+ register_philox_rand()
lib/python3.12/site-packages/torch/backends/__init__.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ import types
3
+ from contextlib import contextmanager
4
+
5
+
6
+ # The idea for this parameter is that we forbid bare assignment
7
+ # to torch.backends.<cudnn|mkldnn>.enabled and friends when running our
8
+ # test suite, where it's very easy to forget to undo the change
9
+ # later.
10
+ __allow_nonbracketed_mutation_flag = True
11
+
12
+
13
+ def disable_global_flags():
14
+ global __allow_nonbracketed_mutation_flag
15
+ __allow_nonbracketed_mutation_flag = False
16
+
17
+
18
+ def flags_frozen():
19
+ return not __allow_nonbracketed_mutation_flag
20
+
21
+
22
+ @contextmanager
23
+ def __allow_nonbracketed_mutation():
24
+ global __allow_nonbracketed_mutation_flag
25
+ old = __allow_nonbracketed_mutation_flag
26
+ __allow_nonbracketed_mutation_flag = True
27
+ try:
28
+ yield
29
+ finally:
30
+ __allow_nonbracketed_mutation_flag = old
31
+
32
+
33
+ class ContextProp:
34
+ def __init__(self, getter, setter):
35
+ self.getter = getter
36
+ self.setter = setter
37
+
38
+ def __get__(self, obj, objtype):
39
+ return self.getter()
40
+
41
+ def __set__(self, obj, val):
42
+ if not flags_frozen():
43
+ self.setter(val)
44
+ else:
45
+ raise RuntimeError(
46
+ f"not allowed to set {obj.__name__} flags "
47
+ "after disable_global_flags; please use flags() context manager instead"
48
+ )
49
+
50
+
51
+ class PropModule(types.ModuleType):
52
+ def __init__(self, m, name):
53
+ super().__init__(name)
54
+ self.m = m
55
+
56
+ def __getattr__(self, attr):
57
+ return self.m.__getattribute__(attr)
58
+
59
+
60
+ from torch.backends import (
61
+ cpu as cpu,
62
+ cuda as cuda,
63
+ cudnn as cudnn,
64
+ cusparselt as cusparselt,
65
+ mha as mha,
66
+ mkl as mkl,
67
+ mkldnn as mkldnn,
68
+ mps as mps,
69
+ nnpack as nnpack,
70
+ openmp as openmp,
71
+ quantized as quantized,
72
+ )
lib/python3.12/site-packages/torch/backends/cuda/__init__.py ADDED
@@ -0,0 +1,480 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ import contextlib
3
+ from typing import Union
4
+ from typing_extensions import deprecated
5
+
6
+ import torch
7
+
8
+
9
+ __all__ = [
10
+ "is_built",
11
+ "cuFFTPlanCacheAttrContextProp",
12
+ "cuFFTPlanCache",
13
+ "cuFFTPlanCacheManager",
14
+ "cuBLASModule",
15
+ "preferred_linalg_library",
16
+ "preferred_blas_library",
17
+ "cufft_plan_cache",
18
+ "matmul",
19
+ "SDPAParams",
20
+ "enable_cudnn_sdp",
21
+ "cudnn_sdp_enabled",
22
+ "enable_flash_sdp",
23
+ "flash_sdp_enabled",
24
+ "enable_mem_efficient_sdp",
25
+ "mem_efficient_sdp_enabled",
26
+ "math_sdp_enabled",
27
+ "enable_math_sdp",
28
+ "allow_fp16_bf16_reduction_math_sdp",
29
+ "fp16_bf16_reduction_math_sdp_allowed",
30
+ "is_flash_attention_available",
31
+ "can_use_flash_attention",
32
+ "can_use_efficient_attention",
33
+ "can_use_cudnn_attention",
34
+ "sdp_kernel",
35
+ ]
36
+
37
+
38
+ def is_built():
39
+ r"""
40
+ Return whether PyTorch is built with CUDA support.
41
+
42
+ Note that this doesn't necessarily mean CUDA is available; just that if this PyTorch
43
+ binary were run on a machine with working CUDA drivers and devices, we would be able to use it.
44
+ """
45
+ return torch._C._has_cuda
46
+
47
+
48
+ class cuFFTPlanCacheAttrContextProp:
49
+ # Like regular ContextProp, but uses the `.device_index` attribute from the
50
+ # calling object as the first argument to the getter and setter.
51
+ def __init__(self, getter, setter):
52
+ self.getter = getter
53
+ self.setter = setter
54
+
55
+ def __get__(self, obj, objtype):
56
+ return self.getter(obj.device_index)
57
+
58
+ def __set__(self, obj, val):
59
+ if isinstance(self.setter, str):
60
+ raise RuntimeError(self.setter)
61
+ self.setter(obj.device_index, val)
62
+
63
+
64
+ class cuFFTPlanCache:
65
+ r"""
66
+ Represent a specific plan cache for a specific `device_index`.
67
+
68
+ The attributes `size` and `max_size`, and method `clear`, can fetch and/ or
69
+ change properties of the C++ cuFFT plan cache.
70
+ """
71
+
72
+ def __init__(self, device_index):
73
+ self.device_index = device_index
74
+
75
+ size = cuFFTPlanCacheAttrContextProp(
76
+ torch._cufft_get_plan_cache_size,
77
+ ".size is a read-only property showing the number of plans currently in the "
78
+ "cache. To change the cache capacity, set cufft_plan_cache.max_size.",
79
+ )
80
+
81
+ max_size = cuFFTPlanCacheAttrContextProp(
82
+ torch._cufft_get_plan_cache_max_size, torch._cufft_set_plan_cache_max_size
83
+ )
84
+
85
+ def clear(self):
86
+ return torch._cufft_clear_plan_cache(self.device_index)
87
+
88
+
89
+ class cuFFTPlanCacheManager:
90
+ r"""
91
+ Represent all cuFFT plan caches, return the cuFFTPlanCache for a given device when indexed.
92
+
93
+ Finally, this object, when used directly as a `cuFFTPlanCache` object (e.g.,
94
+ setting the `.max_size`) attribute, the current device's cuFFT plan cache is
95
+ used.
96
+ """
97
+
98
+ __initialized = False
99
+
100
+ def __init__(self):
101
+ self.caches = []
102
+ self.__initialized = True
103
+
104
+ def __getitem__(self, device):
105
+ index = torch.cuda._utils._get_device_index(device)
106
+ if index < 0 or index >= torch.cuda.device_count():
107
+ raise RuntimeError(
108
+ f"cufft_plan_cache: expected 0 <= device index < {torch.cuda.device_count()}, but got "
109
+ f"device with index {index}"
110
+ )
111
+ if len(self.caches) == 0:
112
+ self.caches.extend(
113
+ cuFFTPlanCache(index) for index in range(torch.cuda.device_count())
114
+ )
115
+ return self.caches[index]
116
+
117
+ def __getattr__(self, name):
118
+ return getattr(self[torch.cuda.current_device()], name)
119
+
120
+ def __setattr__(self, name, value):
121
+ if self.__initialized:
122
+ return setattr(self[torch.cuda.current_device()], name, value)
123
+ else:
124
+ return super().__setattr__(name, value)
125
+
126
+
127
+ class cuBLASModule:
128
+ def __getattr__(self, name):
129
+ if name == "allow_tf32":
130
+ return torch._C._get_cublas_allow_tf32()
131
+ elif name == "allow_fp16_reduced_precision_reduction":
132
+ return torch._C._get_cublas_allow_fp16_reduced_precision_reduction()
133
+ elif name == "allow_bf16_reduced_precision_reduction":
134
+ return torch._C._get_cublas_allow_bf16_reduced_precision_reduction()
135
+ raise AttributeError("Unknown attribute " + name)
136
+
137
+ def __setattr__(self, name, value):
138
+ if name == "allow_tf32":
139
+ return torch._C._set_cublas_allow_tf32(value)
140
+ elif name == "allow_fp16_reduced_precision_reduction":
141
+ return torch._C._set_cublas_allow_fp16_reduced_precision_reduction(value)
142
+ elif name == "allow_bf16_reduced_precision_reduction":
143
+ return torch._C._set_cublas_allow_bf16_reduced_precision_reduction(value)
144
+ raise AttributeError("Unknown attribute " + name)
145
+
146
+
147
+ _LinalgBackends = {
148
+ "default": torch._C._LinalgBackend.Default,
149
+ "cusolver": torch._C._LinalgBackend.Cusolver,
150
+ "magma": torch._C._LinalgBackend.Magma,
151
+ }
152
+ _LinalgBackends_str = ", ".join(_LinalgBackends.keys())
153
+
154
+
155
+ def preferred_linalg_library(
156
+ backend: Union[None, str, torch._C._LinalgBackend] = None
157
+ ) -> torch._C._LinalgBackend:
158
+ r"""
159
+ Override the heuristic PyTorch uses to choose between cuSOLVER and MAGMA for CUDA linear algebra operations.
160
+
161
+ .. warning:: This flag is experimental and subject to change.
162
+
163
+ When PyTorch runs a CUDA linear algebra operation it often uses the cuSOLVER or MAGMA libraries,
164
+ and if both are available it decides which to use with a heuristic.
165
+ This flag (a :class:`str`) allows overriding those heuristics.
166
+
167
+ * If `"cusolver"` is set then cuSOLVER will be used wherever possible.
168
+ * If `"magma"` is set then MAGMA will be used wherever possible.
169
+ * If `"default"` (the default) is set then heuristics will be used to pick between
170
+ cuSOLVER and MAGMA if both are available.
171
+ * When no input is given, this function returns the currently preferred library.
172
+ * User may use the environment variable TORCH_LINALG_PREFER_CUSOLVER=1 to set the preferred library to cuSOLVER
173
+ globally.
174
+ This flag only sets the initial value of the preferred library and the preferred library
175
+ may still be overridden by this function call later in your script.
176
+
177
+ Note: When a library is preferred other libraries may still be used if the preferred library
178
+ doesn't implement the operation(s) called.
179
+ This flag may achieve better performance if PyTorch's heuristic library selection is incorrect
180
+ for your application's inputs.
181
+
182
+ Currently supported linalg operators:
183
+
184
+ * :func:`torch.linalg.inv`
185
+ * :func:`torch.linalg.inv_ex`
186
+ * :func:`torch.linalg.cholesky`
187
+ * :func:`torch.linalg.cholesky_ex`
188
+ * :func:`torch.cholesky_solve`
189
+ * :func:`torch.cholesky_inverse`
190
+ * :func:`torch.linalg.lu_factor`
191
+ * :func:`torch.linalg.lu`
192
+ * :func:`torch.linalg.lu_solve`
193
+ * :func:`torch.linalg.qr`
194
+ * :func:`torch.linalg.eigh`
195
+ * :func:`torch.linalg.eighvals`
196
+ * :func:`torch.linalg.svd`
197
+ * :func:`torch.linalg.svdvals`
198
+ """
199
+ if backend is None:
200
+ pass
201
+ elif isinstance(backend, str):
202
+ if backend not in _LinalgBackends:
203
+ raise RuntimeError(
204
+ "Unknown input value. " f"Choose from: {_LinalgBackends_str}."
205
+ )
206
+ torch._C._set_linalg_preferred_backend(_LinalgBackends[backend])
207
+ elif isinstance(backend, torch._C._LinalgBackend):
208
+ torch._C._set_linalg_preferred_backend(backend)
209
+ else:
210
+ raise RuntimeError("Unknown input value type.")
211
+
212
+ return torch._C._get_linalg_preferred_backend()
213
+
214
+
215
+ _BlasBackends = {
216
+ "cublas": torch._C._BlasBackend.Cublas,
217
+ "cublaslt": torch._C._BlasBackend.Cublaslt,
218
+ "hipblaslt": torch._C._BlasBackend.Cublaslt, # alias
219
+ "ck": torch._C._BlasBackend.Ck,
220
+ }
221
+ _BlasBackends_str = ", ".join(_BlasBackends.keys())
222
+
223
+
224
+ def preferred_blas_library(
225
+ backend: Union[None, str, torch._C._BlasBackend] = None
226
+ ) -> torch._C._BlasBackend:
227
+ r"""
228
+ Override the library PyTorch uses for BLAS operations. Choose between cuBLAS, cuBLASLt, and CK [ROCm-only].
229
+
230
+ .. warning:: This flag is experimental and subject to change.
231
+
232
+ When PyTorch runs a CUDA BLAS operation it defaults to cuBLAS even if both cuBLAS and cuBLASLt are available.
233
+ For PyTorch built for ROCm, hipBLAS, hipBLASLt, and CK may offer different performance.
234
+ This flag (a :class:`str`) allows overriding which BLAS library to use.
235
+
236
+ * If `"cublas"` is set then cuBLAS will be used wherever possible.
237
+ * If `"cublaslt"` is set then cuBLASLt will be used wherever possible.
238
+ * If `"ck"` is set then CK will be used wherever possible.
239
+ * When no input is given, this function returns the currently preferred library.
240
+ * User may use the environment variable TORCH_BLAS_PREFER_CUBLASLT=1 to set the preferred library to cuBLASLt
241
+ globally.
242
+ This flag only sets the initial value of the preferred library and the preferred library
243
+ may still be overridden by this function call later in your script.
244
+
245
+ Note: When a library is preferred other libraries may still be used if the preferred library
246
+ doesn't implement the operation(s) called.
247
+ This flag may achieve better performance if PyTorch's library selection is incorrect
248
+ for your application's inputs.
249
+
250
+ """
251
+ if backend is None:
252
+ pass
253
+ elif isinstance(backend, str):
254
+ if backend not in _BlasBackends:
255
+ raise RuntimeError(
256
+ "Unknown input value. " f"Choose from: {_BlasBackends_str}."
257
+ )
258
+ torch._C._set_blas_preferred_backend(_BlasBackends[backend])
259
+ elif isinstance(backend, torch._C._BlasBackend):
260
+ torch._C._set_blas_preferred_backend(backend)
261
+ else:
262
+ raise RuntimeError("Unknown input value type.")
263
+
264
+ return torch._C._get_blas_preferred_backend()
265
+
266
+
267
+ from torch._C import _SDPAParams as SDPAParams, _SDPBackend as SDPBackend
268
+
269
+
270
+ # Set the __module__ attribute
271
+ SDPAParams.__module__ = "torch.backends.cuda"
272
+ SDPAParams.__name__ = "SDPAParams"
273
+
274
+
275
+ def flash_sdp_enabled():
276
+ r"""
277
+ .. warning:: This flag is beta and subject to change.
278
+
279
+ Returns whether flash scaled dot product attention is enabled or not.
280
+ """
281
+ return torch._C._get_flash_sdp_enabled()
282
+
283
+
284
+ def enable_flash_sdp(enabled: bool):
285
+ r"""
286
+ .. warning:: This flag is beta and subject to change.
287
+
288
+ Enables or disables flash scaled dot product attention.
289
+ """
290
+ torch._C._set_sdp_use_flash(enabled)
291
+
292
+
293
+ def mem_efficient_sdp_enabled():
294
+ r"""
295
+ .. warning:: This flag is beta and subject to change.
296
+
297
+ Returns whether memory efficient scaled dot product attention is enabled or not.
298
+ """
299
+ return torch._C._get_mem_efficient_sdp_enabled()
300
+
301
+
302
+ def enable_mem_efficient_sdp(enabled: bool):
303
+ r"""
304
+ .. warning:: This flag is beta and subject to change.
305
+
306
+ Enables or disables memory efficient scaled dot product attention.
307
+ """
308
+ torch._C._set_sdp_use_mem_efficient(enabled)
309
+
310
+
311
+ def math_sdp_enabled():
312
+ r"""
313
+ .. warning:: This flag is beta and subject to change.
314
+
315
+ Returns whether math scaled dot product attention is enabled or not.
316
+ """
317
+ return torch._C._get_math_sdp_enabled()
318
+
319
+
320
+ def enable_math_sdp(enabled: bool):
321
+ r"""
322
+ .. warning:: This flag is beta and subject to change.
323
+
324
+ Enables or disables math scaled dot product attention.
325
+ """
326
+ torch._C._set_sdp_use_math(enabled)
327
+
328
+
329
+ def allow_fp16_bf16_reduction_math_sdp(enabled: bool):
330
+ r"""
331
+ .. warning:: This flag is beta and subject to change.
332
+
333
+ Enables or disables fp16/bf16 reduction in math scaled dot product attention.
334
+ """
335
+ torch._C._set_math_sdp_allow_fp16_bf16_reduction(enabled)
336
+
337
+
338
+ def fp16_bf16_reduction_math_sdp_allowed():
339
+ r"""
340
+ .. warning:: This flag is beta and subject to change.
341
+
342
+ Returns whether fp16/bf16 reduction in math scaled dot product attention is enabled or not.
343
+ """
344
+ return torch._C._get_math_sdp_allow_fp16_bf16_reduction()
345
+
346
+
347
+ def is_flash_attention_available() -> bool:
348
+ r"""Check if PyTorch was built with FlashAttention for scaled_dot_product_attention.
349
+
350
+ Returns:
351
+ True if FlashAttention is built and available; otherwise, False.
352
+
353
+ Note:
354
+ This function is dependent on a CUDA-enabled build of PyTorch. It will return False
355
+ in non-CUDA environments.
356
+ """
357
+ return torch._C._is_flash_attention_available()
358
+
359
+
360
+ def can_use_flash_attention(params: SDPAParams, debug: bool = False) -> bool:
361
+ r"""Check if FlashAttention can be utilized in scaled_dot_product_attention.
362
+
363
+ Args:
364
+ params: An instance of SDPAParams containing the tensors for query,
365
+ key, value, an optional attention mask, dropout rate, and
366
+ a flag indicating if the attention is causal.
367
+ debug: Whether to logging.warn debug information as to why FlashAttention could not be run.
368
+ Defaults to False.
369
+
370
+ Returns:
371
+ True if FlashAttention can be used with the given parameters; otherwise, False.
372
+
373
+ Note:
374
+ This function is dependent on a CUDA-enabled build of PyTorch. It will return False
375
+ in non-CUDA environments.
376
+ """
377
+ return torch._C._can_use_flash_attention(params, debug)
378
+
379
+
380
+ def can_use_efficient_attention(params: SDPAParams, debug: bool = False) -> bool:
381
+ r"""Check if efficient_attention can be utilized in scaled_dot_product_attention.
382
+
383
+ Args:
384
+ params: An instance of SDPAParams containing the tensors for query,
385
+ key, value, an optional attention mask, dropout rate, and
386
+ a flag indicating if the attention is causal.
387
+ debug: Whether to logging.warn with information as to why efficient_attention could not be run.
388
+ Defaults to False.
389
+
390
+ Returns:
391
+ True if efficient_attention can be used with the given parameters; otherwise, False.
392
+
393
+ Note:
394
+ This function is dependent on a CUDA-enabled build of PyTorch. It will return False
395
+ in non-CUDA environments.
396
+ """
397
+ return torch._C._can_use_mem_efficient_attention(params, debug)
398
+
399
+
400
+ def can_use_cudnn_attention(params: SDPAParams, debug: bool = False) -> bool:
401
+ r"""Check if cudnn_attention can be utilized in scaled_dot_product_attention.
402
+
403
+ Args:
404
+ params: An instance of SDPAParams containing the tensors for query,
405
+ key, value, an optional attention mask, dropout rate, and
406
+ a flag indicating if the attention is causal.
407
+ debug: Whether to logging.warn with information as to why cuDNN attention could not be run.
408
+ Defaults to False.
409
+
410
+ Returns:
411
+ True if cuDNN can be used with the given parameters; otherwise, False.
412
+
413
+ Note:
414
+ This function is dependent on a CUDA-enabled build of PyTorch. It will return False
415
+ in non-CUDA environments.
416
+ """
417
+ return torch._C._can_use_cudnn_attention(params, debug)
418
+
419
+
420
+ def cudnn_sdp_enabled():
421
+ r"""
422
+ .. warning:: This flag is beta and subject to change.
423
+
424
+ Returns whether cuDNN scaled dot product attention is enabled or not.
425
+ """
426
+ return torch._C._get_cudnn_sdp_enabled()
427
+
428
+
429
+ def enable_cudnn_sdp(enabled: bool):
430
+ r"""
431
+ .. warning:: This flag is beta and subject to change.
432
+
433
+ Enables or disables cuDNN scaled dot product attention.
434
+ """
435
+ torch._C._set_sdp_use_cudnn(enabled)
436
+
437
+
438
+ @contextlib.contextmanager
439
+ @deprecated(
440
+ (
441
+ "`torch.backends.cuda.sdp_kernel()` is deprecated. "
442
+ "In the future, this context manager will be removed. "
443
+ "Please see `torch.nn.attention.sdpa_kernel()` for the new context manager, "
444
+ "with updated signature."
445
+ ),
446
+ category=FutureWarning,
447
+ )
448
+ def sdp_kernel(
449
+ enable_flash: bool = True,
450
+ enable_math: bool = True,
451
+ enable_mem_efficient: bool = True,
452
+ enable_cudnn: bool = True,
453
+ ):
454
+ r"""
455
+ .. warning:: This flag is beta and subject to change.
456
+
457
+ This context manager can be used to temporarily enable or disable any of the three backends for scaled dot product attention.
458
+ Upon exiting the context manager, the previous state of the flags will be restored.
459
+ """
460
+ from torch.nn.attention import sdpa_kernel
461
+
462
+ backend_list = []
463
+ if enable_flash:
464
+ backend_list.append(SDPBackend.FLASH_ATTENTION)
465
+ if enable_mem_efficient:
466
+ backend_list.append(SDPBackend.EFFICIENT_ATTENTION)
467
+ if enable_math:
468
+ backend_list.append(SDPBackend.MATH)
469
+ if enable_cudnn:
470
+ backend_list.append(SDPBackend.CUDNN_ATTENTION)
471
+
472
+ with sdpa_kernel(backend_list) as context:
473
+ try:
474
+ yield context
475
+ finally:
476
+ pass
477
+
478
+
479
+ cufft_plan_cache = cuFFTPlanCacheManager()
480
+ matmul = cuBLASModule()
lib/python3.12/site-packages/torch/backends/cuda/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (22.4 kB). View file
 
lib/python3.12/site-packages/torch/backends/cudnn/__init__.py ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ import os
3
+ import sys
4
+ import warnings
5
+ from contextlib import contextmanager
6
+ from typing import Optional
7
+
8
+ import torch
9
+ from torch.backends import __allow_nonbracketed_mutation, ContextProp, PropModule
10
+
11
+
12
+ try:
13
+ from torch._C import _cudnn
14
+ except ImportError:
15
+ _cudnn = None # type: ignore[assignment]
16
+
17
+ # Write:
18
+ #
19
+ # torch.backends.cudnn.enabled = False
20
+ #
21
+ # to globally disable CuDNN/MIOpen
22
+
23
+ __cudnn_version: Optional[int] = None
24
+
25
+ if _cudnn is not None:
26
+
27
+ def _init():
28
+ global __cudnn_version
29
+ if __cudnn_version is None:
30
+ __cudnn_version = _cudnn.getVersionInt()
31
+ runtime_version = _cudnn.getRuntimeVersion()
32
+ compile_version = _cudnn.getCompileVersion()
33
+ runtime_major, runtime_minor, _ = runtime_version
34
+ compile_major, compile_minor, _ = compile_version
35
+ # Different major versions are always incompatible
36
+ # Starting with cuDNN 7, minor versions are backwards-compatible
37
+ # Not sure about MIOpen (ROCm), so always do a strict check
38
+ if runtime_major != compile_major:
39
+ cudnn_compatible = False
40
+ elif runtime_major < 7 or not _cudnn.is_cuda:
41
+ cudnn_compatible = runtime_minor == compile_minor
42
+ else:
43
+ cudnn_compatible = runtime_minor >= compile_minor
44
+ if not cudnn_compatible:
45
+ if os.environ.get("PYTORCH_SKIP_CUDNN_COMPATIBILITY_CHECK", "0") == "1":
46
+ return True
47
+ base_error_msg = (
48
+ f"cuDNN version incompatibility: "
49
+ f"PyTorch was compiled against {compile_version} "
50
+ f"but found runtime version {runtime_version}. "
51
+ f"PyTorch already comes bundled with cuDNN. "
52
+ f"One option to resolving this error is to ensure PyTorch "
53
+ f"can find the bundled cuDNN. "
54
+ )
55
+
56
+ if "LD_LIBRARY_PATH" in os.environ:
57
+ ld_library_path = os.environ.get("LD_LIBRARY_PATH", "")
58
+ if any(
59
+ substring in ld_library_path for substring in ["cuda", "cudnn"]
60
+ ):
61
+ raise RuntimeError(
62
+ f"{base_error_msg}"
63
+ f"Looks like your LD_LIBRARY_PATH contains incompatible version of cudnn. "
64
+ f"Please either remove it from the path or install cudnn {compile_version}"
65
+ )
66
+ else:
67
+ raise RuntimeError(
68
+ f"{base_error_msg}"
69
+ f"one possibility is that there is a "
70
+ f"conflicting cuDNN in LD_LIBRARY_PATH."
71
+ )
72
+ else:
73
+ raise RuntimeError(base_error_msg)
74
+
75
+ return True
76
+
77
+ else:
78
+
79
+ def _init():
80
+ return False
81
+
82
+
83
+ def version():
84
+ """Return the version of cuDNN."""
85
+ if not _init():
86
+ return None
87
+ return __cudnn_version
88
+
89
+
90
+ CUDNN_TENSOR_DTYPES = {
91
+ torch.half,
92
+ torch.float,
93
+ torch.double,
94
+ }
95
+
96
+
97
+ def is_available():
98
+ r"""Return a bool indicating if CUDNN is currently available."""
99
+ return torch._C._has_cudnn
100
+
101
+
102
+ def is_acceptable(tensor):
103
+ if not torch._C._get_cudnn_enabled():
104
+ return False
105
+ if tensor.device.type != "cuda" or tensor.dtype not in CUDNN_TENSOR_DTYPES:
106
+ return False
107
+ if not is_available():
108
+ warnings.warn(
109
+ "PyTorch was compiled without cuDNN/MIOpen support. To use cuDNN/MIOpen, rebuild "
110
+ "PyTorch making sure the library is visible to the build system."
111
+ )
112
+ return False
113
+ if not _init():
114
+ warnings.warn(
115
+ "cuDNN/MIOpen library not found. Check your {libpath}".format(
116
+ libpath={"darwin": "DYLD_LIBRARY_PATH", "win32": "PATH"}.get(
117
+ sys.platform, "LD_LIBRARY_PATH"
118
+ )
119
+ )
120
+ )
121
+ return False
122
+ return True
123
+
124
+
125
+ def set_flags(
126
+ _enabled=None,
127
+ _benchmark=None,
128
+ _benchmark_limit=None,
129
+ _deterministic=None,
130
+ _allow_tf32=None,
131
+ ):
132
+ orig_flags = (
133
+ torch._C._get_cudnn_enabled(),
134
+ torch._C._get_cudnn_benchmark(),
135
+ None if not is_available() else torch._C._cuda_get_cudnn_benchmark_limit(),
136
+ torch._C._get_cudnn_deterministic(),
137
+ torch._C._get_cudnn_allow_tf32(),
138
+ )
139
+ if _enabled is not None:
140
+ torch._C._set_cudnn_enabled(_enabled)
141
+ if _benchmark is not None:
142
+ torch._C._set_cudnn_benchmark(_benchmark)
143
+ if _benchmark_limit is not None and is_available():
144
+ torch._C._cuda_set_cudnn_benchmark_limit(_benchmark_limit)
145
+ if _deterministic is not None:
146
+ torch._C._set_cudnn_deterministic(_deterministic)
147
+ if _allow_tf32 is not None:
148
+ torch._C._set_cudnn_allow_tf32(_allow_tf32)
149
+ return orig_flags
150
+
151
+
152
+ @contextmanager
153
+ def flags(
154
+ enabled=False,
155
+ benchmark=False,
156
+ benchmark_limit=10,
157
+ deterministic=False,
158
+ allow_tf32=True,
159
+ ):
160
+ with __allow_nonbracketed_mutation():
161
+ orig_flags = set_flags(
162
+ enabled, benchmark, benchmark_limit, deterministic, allow_tf32
163
+ )
164
+ try:
165
+ yield
166
+ finally:
167
+ # recover the previous values
168
+ with __allow_nonbracketed_mutation():
169
+ set_flags(*orig_flags)
170
+
171
+
172
+ # The magic here is to allow us to intercept code like this:
173
+ #
174
+ # torch.backends.<cudnn|mkldnn>.enabled = True
175
+
176
+
177
+ class CudnnModule(PropModule):
178
+ def __init__(self, m, name):
179
+ super().__init__(m, name)
180
+
181
+ enabled = ContextProp(torch._C._get_cudnn_enabled, torch._C._set_cudnn_enabled)
182
+ deterministic = ContextProp(
183
+ torch._C._get_cudnn_deterministic, torch._C._set_cudnn_deterministic
184
+ )
185
+ benchmark = ContextProp(
186
+ torch._C._get_cudnn_benchmark, torch._C._set_cudnn_benchmark
187
+ )
188
+ benchmark_limit = None
189
+ if is_available():
190
+ benchmark_limit = ContextProp(
191
+ torch._C._cuda_get_cudnn_benchmark_limit,
192
+ torch._C._cuda_set_cudnn_benchmark_limit,
193
+ )
194
+ allow_tf32 = ContextProp(
195
+ torch._C._get_cudnn_allow_tf32, torch._C._set_cudnn_allow_tf32
196
+ )
197
+
198
+
199
+ # This is the sys.modules replacement trick, see
200
+ # https://stackoverflow.com/questions/2447353/getattr-on-a-module/7668273#7668273
201
+ sys.modules[__name__] = CudnnModule(sys.modules[__name__], __name__)
202
+
203
+ # Add type annotation for the replaced module
204
+ enabled: bool
205
+ deterministic: bool
206
+ benchmark: bool
207
+ allow_tf32: bool
208
+ benchmark_limit: int
lib/python3.12/site-packages/torch/backends/cudnn/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (7.85 kB). View file