blob: f4152d0af68bf39e1d612bf91e5a25cf92d5ffce [file] [log] [blame]
# Owner(s): ["oncall: jit"]
import unittest
from torch._lazy.ts_backend import init as init_ts_backend
init_ts_backend()
from torch._lazy import config
from torch._lazy.extract_compiled_graph import extract_compiled_graph
import torch
from torch import nn
import dis
import inspect
from torch import fx
import re
from contextlib import contextmanager
import copy
class ModuleConstScale(nn.Module):
def __init__(self):
super(ModuleConstScale, self).__init__()
def forward(self, a):
return a * 2
class ModuleSub(nn.Module):
def __init__(self):
super(ModuleSub, self).__init__()
def forward(self, a, b):
return a - b
class ModuleAddcmul(nn.Module):
"""
addcmul function takes a at::Scalar which results in a special TSData containing a Scalar rather than a Tensor.
"""
def __init__(self):
super(ModuleAddcmul, self).__init__()
def forward(self, a, b, c):
return torch.addcmul(a, b, c, value=5)
class ModuleReturnMulti(nn.Module):
def __init__(self):
super(ModuleReturnMulti, self).__init__()
def forward(self, a, b):
return (b + 1, a - 1)
# The default fx tracer will convert torch.randn to a constant.. We may need
# a custom tracer.
# class ModuleEagerTensor(nn.Module):
# def __init__(self):
# super(ModuleEagerTensor, self).__init__()
#
# def forward(self, a):
# b = torch.randn(2, 3, device="cpu") # eager device
# return a + b
# The module was planned to cover the case that a Fx graph return an eager
# tensor on the default device. It's harder than ModuleEagerTensor because
# we can not just override the device argument to Lazy since there is no
# explicit device argument.
#
# Unfortunately, the default fx tracer convert the return value of the forward
# method to a constant.. Comment out for now
# class ModuleReturnEagerTensorOnDefaultDevice(nn.Module):
# def __init__(self):
# super(ModuleReturnEagerTensorOnDefaultDevice, self).__init__()
#
# def forward(self):
# return torch.tensor((2, 3), dtype=torch.float32)
class ModuleReturnDupTensor(nn.Module):
"""
Handle the corner case that the same tensor appears multiple times in the
returned tuple. torchbench like drq will hit this corner case when running
thru torchdynamo..
"""
def __init__(self):
super(ModuleReturnDupTensor, self).__init__()
def forward(self, a, b):
c = a + b
return a - b, c, a + 1, c
class ModuleInplaceUpdate(nn.Module):
def __init__(self):
super(ModuleInplaceUpdate, self).__init__()
def forward(self, a, b):
a.sub_(b)
return b - 1, b + 1
@contextmanager
def force_fallback_ctx_mgr(fallback_op):
oldconfig = config.get_force_fallback()
config.set_force_fallback(fallback_op)
try:
yield None
finally:
config.set_force_fallback(oldconfig)
@contextmanager
def nop_ctx_mgr():
try:
yield None
finally:
pass
def gen_rand_args(mod):
args = []
for _ in range(len(inspect.signature(mod.forward).parameters)):
args.append(torch.randn(2, 3))
return args
def allclose(expected, actual):
def unwrap(cont):
if isinstance(cont, (list, tuple)) and len(cont) == 1:
return cont[0]
return cont
expected = unwrap(expected)
actual = unwrap(actual)
if isinstance(expected, torch.Tensor) and isinstance(actual, torch.Tensor):
return torch.allclose(expected, actual)
elif isinstance(expected, (tuple, list)) and isinstance(actual, (tuple, list)):
return len(expected) == len(actual) and all(torch.allclose(a, b) for a, b in zip(expected, actual))
else:
raise RuntimeError("Unexpected types")
def verify_reusing_compiled_graph(mod, exception_msg_pattern, ncase=10):
args = gen_rand_args(mod)
out = mod(*args)
dis.dis(mod.forward)
try:
optimized_mod = extract_compiled_graph(fx.symbolic_trace(mod), args)
except RuntimeError as e:
if exception_msg_pattern is None:
raise e # reraise the exception
exception_message = str(e)
if not re.search(exception_msg_pattern, exception_message):
raise RuntimeError(f"Expection message does not match the required pattern: {exception_message}")
else:
# We are done for the test case that expects an exception
return
if exception_msg_pattern is not None:
raise RuntimeError(f"Expect an exception matching pattern {exception_msg_pattern}")
print("return value of optimized_mod", optimized_mod(*args))
# check correctness
failed_index = []
for i in range(ncase):
rand_args = gen_rand_args(mod)
rand_args_copy = copy.deepcopy(rand_args)
expected = mod(*rand_args)
actual = optimized_mod(*rand_args_copy)
if not allclose(expected, actual):
print(f"Incorrect results. expected {expected}, actual {actual}")
failed_index.append(i)
continue
# make sure arguments match after calling the model forward method to handle inplace
# updates.
if not allclose(rand_args, rand_args_copy):
print(f"Incorrect updated arguments. expected {rand_args}, actual {rand_args_copy}")
failed_index.append(i)
continue
if len(failed_index) > 0:
raise RuntimeError(f"Failed {len(failed_index)}/{ncase} cases")
def maketest(module_cls, exception_msg_pattern=None, ctxmgr=None):
def wrapper(self):
nonlocal ctxmgr
if not ctxmgr:
ctxmgr = nop_ctx_mgr()
with ctxmgr:
verify_reusing_compiled_graph(module_cls(), exception_msg_pattern)
return wrapper
class OptimizeTest(unittest.TestCase):
test_sub = maketest(ModuleSub)
# Same as test_sub but force aten::sub to fallback
# We expect an exception caught because of LTC fallabck.
test_ltc_fallback = maketest(ModuleSub, exception_msg_pattern="fallback.*aten::sub", ctxmgr=force_fallback_ctx_mgr("aten::sub"))
test_const_scale = maketest(ModuleConstScale)
test_addcmul = maketest(ModuleAddcmul)
test_return_multi = maketest(ModuleReturnMulti)
test_return_dup_tensor = maketest(ModuleReturnDupTensor)
test_inplace_update = maketest(ModuleInplaceUpdate)