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