mirror of
https://github.com/zebrajr/pytorch.git
synced 2025-12-07 00:21:07 +01:00
original PR: https://github.com/pytorch/pytorch/pull/128599 (re-created after revert + poisoned diff train)
Summary:
This PR adds deduplication and CSE for runtime asserts. Existing size computation in the graph is CSE'd along with added runtime asserts, and redundant asserts are removed. Shape calls on intermediate tensors are also turned into compute on input sizes if possible, allowing intermediate tensors to be freed earlier. For example:
```
z = torch.cat([x, x], dim=0) # 2*s0
w = z.repeat(y.shape[0]) # 2*s0*s1
_w = w.shape[0]
s0 = x.shape[0]
s1 = y.shape[0]
_w0 = 2 * s0
_w = _w0 * s1
```
Additionally, constrain_range calls are deduplicated. Single-symbol bound checks for unbacked symbols (e.g. u0 >= 0, u0 <= 5) and sym_constrain_range.default calls are also removed, since they accumulate range info in the ShapeEnv, and are replaced with two _assert_scalar.default calls that check the min/max bounds. For example:
```
torch.sym_constrain_range_for_size(n, min=2, max=16)
torch.sym_constrain_range(n, min=4, max=20)
torch._check(n >= 0)
torch._check(n >= 3)
torch._check(n <= 14)
torch.sym_constrain_range_for_size(n)
torch._check(n >= 4)
torch._check(n <= 14)
```
Test Plan:
contbuild & OSS CI, see 940e4477ab
Original Phabricator Test Plan:
Imported from GitHub, without a `Test Plan:` line.
Differential Revision: D59543603
Pull Request resolved: https://github.com/pytorch/pytorch/pull/130380
Approved by: https://github.com/izaitsevfb
283 lines
5.8 KiB
Python
283 lines
5.8 KiB
Python
# mypy: allow-untyped-defs
|
|
import math
|
|
|
|
import operator
|
|
|
|
import sympy
|
|
|
|
import torch
|
|
from torch.utils._sympy.functions import (
|
|
_keep_float,
|
|
FloatPow,
|
|
FloatTrueDiv,
|
|
FloorDiv,
|
|
IntTrueDiv,
|
|
Mod,
|
|
OpaqueUnaryFn_exp,
|
|
OpaqueUnaryFn_log,
|
|
OpaqueUnaryFn_sqrt,
|
|
PowByNatural,
|
|
RoundDecimal,
|
|
RoundToInt,
|
|
ToFloat,
|
|
TruncToInt,
|
|
)
|
|
|
|
|
|
# The sympy interpretation of operators. It will also sometimes work with
|
|
# plain int/float, but if you do certain operations you will get out a
|
|
# sympy.Basic in the end. If you want the Python/FX traceable interpretation,
|
|
# check PythonReferenceAnalysis.
|
|
# NB: For magic methods this needs to use normal magic methods
|
|
# so that test_magic_methods works
|
|
class ReferenceAnalysis:
|
|
@staticmethod
|
|
def constant(c, dtype):
|
|
return sympy.sympify(c)
|
|
|
|
@staticmethod
|
|
def or_(a, b):
|
|
return a | b
|
|
|
|
@staticmethod
|
|
def and_(a, b):
|
|
return a & b
|
|
|
|
@staticmethod
|
|
def eq(a, b):
|
|
if isinstance(a, sympy.Expr) or isinstance(b, sympy.Expr):
|
|
return sympy.Eq(a, b)
|
|
return a == b
|
|
|
|
@classmethod
|
|
def ne(cls, a, b):
|
|
return cls.not_(cls.eq(a, b))
|
|
|
|
@staticmethod
|
|
def lt(a, b):
|
|
return a < b
|
|
|
|
@staticmethod
|
|
def gt(a, b):
|
|
return a > b
|
|
|
|
@staticmethod
|
|
def le(a, b):
|
|
return a <= b
|
|
|
|
@staticmethod
|
|
def ge(a, b):
|
|
return a >= b
|
|
|
|
@staticmethod
|
|
def not_(a):
|
|
assert not isinstance(a, bool)
|
|
return ~a
|
|
|
|
@staticmethod
|
|
def reciprocal(x):
|
|
return FloatTrueDiv(1.0, x)
|
|
|
|
@staticmethod
|
|
def square(x):
|
|
return PowByNatural(x, 2)
|
|
|
|
@staticmethod
|
|
def trunc_to_int(x, dtype):
|
|
return TruncToInt(x)
|
|
|
|
@staticmethod
|
|
def ceil_to_int(x, dtype):
|
|
return sympy.ceiling(x)
|
|
|
|
@staticmethod
|
|
def floor_to_int(x, dtype):
|
|
return sympy.floor(x)
|
|
|
|
@staticmethod
|
|
def floor(x):
|
|
return _keep_float(sympy.floor)(x)
|
|
|
|
@staticmethod
|
|
def ceil(x):
|
|
return _keep_float(sympy.ceiling)(x)
|
|
|
|
@staticmethod
|
|
def to_dtype(x, dtype):
|
|
if dtype == torch.float64:
|
|
return ToFloat(x)
|
|
raise NotImplementedError(f"to_dtype {dtype} NYI")
|
|
|
|
@staticmethod
|
|
def mod(x, y):
|
|
return Mod(x, y)
|
|
|
|
@staticmethod
|
|
def abs(x):
|
|
return abs(x)
|
|
|
|
@staticmethod
|
|
def neg(x):
|
|
return -x
|
|
|
|
@staticmethod
|
|
def truediv(a, b):
|
|
return FloatTrueDiv(a, b)
|
|
|
|
@staticmethod
|
|
def int_truediv(a, b):
|
|
return IntTrueDiv(a, b)
|
|
|
|
@staticmethod
|
|
def floordiv(a, b):
|
|
return FloorDiv(a, b)
|
|
|
|
@staticmethod
|
|
def truncdiv(a, b):
|
|
raise NotImplementedError("TODO: truncdiv")
|
|
|
|
@staticmethod
|
|
def add(a, b):
|
|
return _keep_float(operator.add)(a, b)
|
|
|
|
@staticmethod
|
|
def mul(a, b):
|
|
return _keep_float(operator.mul)(a, b)
|
|
|
|
@staticmethod
|
|
def sub(a, b):
|
|
return _keep_float(operator.sub)(a, b)
|
|
|
|
@staticmethod
|
|
def exp(x):
|
|
return OpaqueUnaryFn_exp(x)
|
|
|
|
@staticmethod
|
|
def log(x):
|
|
return OpaqueUnaryFn_log(x)
|
|
|
|
@staticmethod
|
|
def sqrt(x):
|
|
return OpaqueUnaryFn_sqrt(x)
|
|
|
|
@staticmethod
|
|
def pow(a, b):
|
|
return _keep_float(FloatPow)(a, b)
|
|
|
|
@staticmethod
|
|
def pow_by_natural(a, b):
|
|
return PowByNatural(a, b)
|
|
|
|
@staticmethod
|
|
def minimum(a, b):
|
|
return sympy.Min(a, b)
|
|
|
|
@staticmethod
|
|
def maximum(a, b):
|
|
return sympy.Max(a, b)
|
|
|
|
@staticmethod
|
|
def round_to_int(a, dtype):
|
|
return RoundToInt(a)
|
|
|
|
@staticmethod
|
|
def round_decimal(a, b):
|
|
return RoundDecimal(a, b)
|
|
|
|
|
|
# Unlike ReferenceAnalysis, does NOT sympyify, instead, works with plain
|
|
# Python types and is FX traceable. Inheritance here is purely for code
|
|
# sharing (TODO: considering splitting out a BaseReferenceAnalysis).
|
|
class PythonReferenceAnalysis(ReferenceAnalysis):
|
|
@staticmethod
|
|
def constant(c, dtype):
|
|
if dtype is torch.int64:
|
|
return int(c)
|
|
elif dtype is torch.double:
|
|
return float(c)
|
|
elif dtype is torch.bool:
|
|
return bool(c)
|
|
else:
|
|
raise AssertionError(f"unrecognized dtype {dtype}")
|
|
|
|
@staticmethod
|
|
def not_(a):
|
|
return torch.sym_not(a)
|
|
|
|
@staticmethod
|
|
def floordiv(a, b):
|
|
return a // b
|
|
|
|
@staticmethod
|
|
def mod(x, y):
|
|
return x % y
|
|
|
|
@staticmethod
|
|
def truncdiv(a, b):
|
|
return a / b
|
|
|
|
@staticmethod
|
|
def to_dtype(x, dtype):
|
|
if dtype == torch.float64:
|
|
return torch.sym_float(x)
|
|
raise NotImplementedError(f"to_dtype {dtype} NYI")
|
|
|
|
@staticmethod
|
|
def exp(x):
|
|
raise AssertionError("exp is not valid shape sympy expr")
|
|
|
|
@staticmethod
|
|
def log(x):
|
|
raise AssertionError("log is not valid shape sympy expr")
|
|
|
|
@staticmethod
|
|
def sqrt(x):
|
|
return torch._sym_sqrt(x) # type: ignore[attr-defined]
|
|
|
|
@staticmethod
|
|
def minimum(a, b):
|
|
return torch.sym_min(a, b)
|
|
|
|
@staticmethod
|
|
def maximum(a, b):
|
|
return torch.sym_max(a, b)
|
|
|
|
@staticmethod
|
|
def floor_to_int(x, dtype):
|
|
return math.floor(x)
|
|
|
|
@staticmethod
|
|
def ceil_to_int(x, dtype):
|
|
return math.ceil(x)
|
|
|
|
@staticmethod
|
|
def floor(x):
|
|
return float(math.floor(x))
|
|
|
|
@staticmethod
|
|
def ceil(x):
|
|
return float(math.ceil(x))
|
|
|
|
@staticmethod
|
|
def truediv(a, b):
|
|
return a / b
|
|
|
|
@staticmethod
|
|
def pow(a, b):
|
|
return a**b
|
|
|
|
@staticmethod
|
|
def pow_by_natural(a, b):
|
|
# Pray that safe_pow is not needed here lol. In particular, this
|
|
# never participates in VR low/high ranges, so overflow should be
|
|
# unlikely
|
|
return a**b
|
|
|
|
@staticmethod
|
|
def round_to_int(a, dtype):
|
|
return round(a)
|
|
|
|
@staticmethod
|
|
def round_decimal(a, b):
|
|
return round(a, ndigits=b)
|