Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- lib/python3.12/site-packages/torch/_higher_order_ops/__init__.py +22 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/associative_scan.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/auto_functionalize.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/cond.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/effects.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/executorch_call_delegate.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/flex_attention.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/hints_wrap.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/invoke_subgraph.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/map.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/out_dtype.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/prim_hop_base.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/run_const_graph.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/scan.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/strict_mode.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/torchbind.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/triton_kernel_wrap.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/utils.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/while_loop.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/__pycache__/wrap.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/associative_scan.py +441 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/auto_functionalize.py +832 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/cond.py +559 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/effects.py +288 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/executorch_call_delegate.py +175 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/flex_attention.py +1149 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/hints_wrap.py +151 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/invoke_subgraph.py +308 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/map.py +264 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/out_dtype.py +166 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/prim_hop_base.py +190 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/run_const_graph.py +60 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/scan.py +518 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/strict_mode.py +108 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/torchbind.py +142 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/triton_kernel_wrap.py +1417 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/utils.py +519 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/while_loop.py +340 -0
- lib/python3.12/site-packages/torch/_higher_order_ops/wrap.py +240 -0
- lib/python3.12/site-packages/torch/_prims/__init__.py +2946 -0
- lib/python3.12/site-packages/torch/_prims/context.py +146 -0
- lib/python3.12/site-packages/torch/_prims/debug_prims.py +54 -0
- lib/python3.12/site-packages/torch/_prims/executor.py +60 -0
- lib/python3.12/site-packages/torch/_prims/rng_prims.py +319 -0
- lib/python3.12/site-packages/torch/backends/__init__.py +72 -0
- lib/python3.12/site-packages/torch/backends/cuda/__init__.py +480 -0
- lib/python3.12/site-packages/torch/backends/cuda/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torch/backends/cudnn/__init__.py +208 -0
- 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
|
|
|