Skip to content

Commit c6caa17

Browse files
authored
PyTorch autograd updates (#2808)
1 parent bc399d4 commit c6caa17

5 files changed

Lines changed: 194 additions & 69 deletions

File tree

thunder/core/jit_ext.py

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -940,27 +940,40 @@ def _generate_random_str_id() -> str:
940940
length = 5
941941
return "".join(secrets.choice(string.ascii_lowercase) for _ in range(length))
942942

943-
args_tensor_mask = unwrap(fwd_kwargs["args_tensor_mask"])
943+
# Support both stable PyTorch (with args_tensor_mask) and nightly (without it)
944+
if "args_tensor_mask" in fwd_kwargs:
945+
args_tensor_mask = unwrap(fwd_kwargs["args_tensor_mask"])
946+
else:
947+
args_tensor_mask = None
948+
944949
# TODO(crcrpar): Think about making use of `non_differentiable_idx`
945950
# note that this key is quite new: https://github.com/pytorch/pytorch/pull/134087
946951
# non_differentiable_idx = fwd_kwargs.get("non_differentiable_idx")
947-
length_of_tensor_args = sum(args_tensor_mask)
948-
949-
# N.B.(crcrpar) When `torch.compile(..., dynamic=True)`,
950-
# GraphModules' forward seem to take `SymInt` and other values
951-
# as its argument with some probability. Though that piece of information unfortunately
952-
# does not seem to be indicated in ``args_tensor_mask`` nor ``non_differentiable_idx``.
953-
# Thus we optimistically iterate over ``fwd_args`` and gather non-tensor values whose index is >= `length_of_tensor_args` to ``fwd_args``.
954-
new_fwd_args = []
955-
for i, v in enumerate(fwd_args):
956-
if i < length_of_tensor_args:
957-
new_fwd_args.append(v)
958-
else:
959-
# note(crcrpar): we might want to include `FutureTensorProxy` and
960-
# a proxy of tensor subclass in the near future.
961-
if not isinstance(unwrap(v), TensorProxy):
952+
953+
if args_tensor_mask is not None:
954+
length_of_tensor_args = sum(args_tensor_mask)
955+
956+
# N.B.(crcrpar) When `torch.compile(..., dynamic=True)`,
957+
# GraphModules' forward seem to take `SymInt` and other values
958+
# as its argument with some probability. Though that piece of information unfortunately
959+
# does not seem to be indicated in ``args_tensor_mask`` nor ``non_differentiable_idx``.
960+
# Thus we optimistically iterate over ``fwd_args`` and gather non-tensor values whose index is >= `length_of_tensor_args` to ``fwd_args``.
961+
new_fwd_args = []
962+
for i, v in enumerate(fwd_args):
963+
if i < length_of_tensor_args:
962964
new_fwd_args.append(v)
963-
new_fwd_args = (wrap_const(None),) + tuple(new_fwd_args)
965+
else:
966+
# note(crcrpar): we might want to include `FutureTensorProxy` and
967+
# a proxy of tensor subclass in the near future.
968+
if not isinstance(unwrap(v), TensorProxy):
969+
new_fwd_args.append(v)
970+
# With args_tensor_mask, the fwd_body expects ctx as first argument
971+
new_fwd_args = (wrap_const(None),) + tuple(new_fwd_args)
972+
else:
973+
# For nightly PyTorch without args_tensor_mask, the fwd_body
974+
# GraphModule does NOT expect a ctx argument.
975+
# We pass all args as-is without prepending None.
976+
new_fwd_args = tuple(fwd_args)
964977
unwrapped_fwd_args = tree_map(lambda t: unwrap(t), new_fwd_args)
965978

966979
tmp_name = _generate_random_str_id()
@@ -998,7 +1011,12 @@ def forward(*args, **kwargs):
9981011

9991012
grads = sequencify(tree_map(lambda t: TensorProxy(like=t), sequencify(output)))
10001013
bwd_tensor_args = grads + tuple(saved_values)
1001-
bwd_args = (None,) + bwd_tensor_args
1014+
1015+
# Support both stable PyTorch (with args_tensor_mask) and nightly (without it)
1016+
if args_tensor_mask is not None:
1017+
bwd_args = (None,) + bwd_tensor_args
1018+
else:
1019+
bwd_args = bwd_tensor_args
10021020
wrapped_bwd_args = tree_map(lambda t: wrap(t, provenance=aug_fwd_provenance), bwd_args)
10031021
bwd_trace, bwd_trace_provenance = _convert_pytorchfunc_to_thundertrace(
10041022
bwd,
@@ -1026,9 +1044,17 @@ def grad_transform(*args, **kwargs):
10261044

10271045
primal, residuals = interpret_trace(aliased_aug_fwd_trace, *args, **kwargs)
10281046
grads = tree_map(lambda t: get_grad(t), sequencify(primal))
1029-
bwd_args = (None,) + tuple(grads) + tuple(sequencify(residuals))
1047+
# Support both stable PyTorch (with args_tensor_mask) and nightly (without it)
1048+
if args_tensor_mask is not None:
1049+
bwd_args = (None,) + tuple(grads) + tuple(sequencify(residuals))
1050+
# Stable PT: first arg is ctx, skip it for put_grads
1051+
grad_inputs = args[1:]
1052+
else:
1053+
bwd_args = tuple(grads) + tuple(sequencify(residuals))
1054+
# Nightly PT: no ctx, use all args
1055+
grad_inputs = args
10301056
result = interpret_trace(aliased_bwd_trace, *bwd_args)
1031-
put_grads(args[1:], result)
1057+
put_grads(grad_inputs, result)
10321058

10331059
return primal
10341060

thunder/tests/test_dynamo.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1633,7 +1633,6 @@ def compile(self, fn, **kwargs):
16331633

16341634

16351635
@requiresCUDA
1636-
@xfail_if_args_tensor_mask_removed
16371636
def test_autograd_function_fx_report(tmp_path):
16381637
class Sin(torch.autograd.Function):
16391638
@staticmethod

thunder/tests/test_jit_general.py

Lines changed: 124 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,48 @@
1010
from torch.testing import assert_close
1111

1212
from lightning_utilities import compare_version
13+
import inspect
1314

1415
import thunder
1516

16-
from thunder.tests.framework import requiresCUDA, IS_WINDOWS, xfail_if_args_tensor_mask_removed
17+
from thunder.tests.framework import requiresCUDA, IS_WINDOWS
1718
from thunder.core.options import CACHE_OPTIONS
1819
import thunder.core.prims as prims
1920
from thunder import pytorch_executor, nvfuser_executor
2021
from thunder.executors.sdpaex import sdpa_ex
2122
from thunder.core.transforms import Transform
2223

2324

25+
# Detect once at module load time whether PyTorch uses args_tensor_mask.
26+
# This must be done outside the JIT-traced function to avoid interpreter issues.
27+
def _detect_has_args_tensor_mask():
28+
"""Check if autograd_function_apply uses args_tensor_mask.
29+
30+
Stable PyTorch requires args_tensor_mask, nightly PyTorch has removed it.
31+
"""
32+
try:
33+
from torch._functorch.autograd_function import AutogradFunctionApply
34+
35+
source = inspect.getsource(AutogradFunctionApply.__call__)
36+
return "args_tensor_mask" in source
37+
except (ImportError, AttributeError, OSError):
38+
# Fallback: assume stable PyTorch with args_tensor_mask
39+
return True
40+
41+
42+
_HAS_ARGS_TENSOR_MASK = _detect_has_args_tensor_mask()
43+
44+
45+
def _autograd_function_apply_kwargs(args_tensor_mask, non_differentiable_idx=None):
46+
"""Create kwargs for autograd_function_apply that work with both stable and nightly PyTorch."""
47+
kwargs = {}
48+
if _HAS_ARGS_TENSOR_MASK:
49+
kwargs["args_tensor_mask"] = args_tensor_mask
50+
if non_differentiable_idx is not None:
51+
kwargs["non_differentiable_idx"] = non_differentiable_idx
52+
return kwargs
53+
54+
2455
thunder_jit = partial(thunder.jit, debug_options=thunder.DebugOptions(check_traces=2))
2556

2657
#
@@ -1252,35 +1283,48 @@ def f(x):
12521283

12531284

12541285
@pytest.mark.filterwarnings("ignore:Please use torch.vmap")
1255-
@xfail_if_args_tensor_mask_removed
12561286
def test_autograd_function_apply():
12571287
# see https://github.com/Lightning-AI/lightning-thunder/issues/1248#issuecomment-2388655917
12581288
# for why `torch.foo` instead of `torch.Tensor.foo`
12591289

12601290
# since https://github.com/pytorch/pytorch/pull/169528 `torch.ops.higher_order.autograd_function_apply`
12611291
# no longer accepts simple callables, but rather `torch.fx.GraphModule`s.
12621292

1263-
class FwdModule(torch.nn.Module):
1264-
def forward(self, ctx, x):
1265-
saved_for_backward = (x,)
1266-
return torch.sin(x), saved_for_backward
1293+
# TODO: Remove this once this autograd API becomes stable.
1294+
# On stable PyTorch (with args_tensor_mask), forward/backward expect ctx as first arg.
1295+
# On nightly PyTorch (without args_tensor_mask), ctx is not an argument.
1296+
if _HAS_ARGS_TENSOR_MASK:
12671297

1268-
fwd = torch.fx.symbolic_trace(FwdModule())
1298+
class FwdModule(torch.nn.Module):
1299+
def forward(self, ctx, x):
1300+
saved_for_backward = (x,)
1301+
return torch.sin(x), saved_for_backward
12691302

1270-
class BwdModule(torch.nn.Module):
1271-
def forward(self, ctx, grad_output, *saved_tensors):
1272-
(x,) = saved_tensors
1273-
return grad_output * torch.cos(x)
1303+
class BwdModule(torch.nn.Module):
1304+
def forward(self, ctx, grad_output, *saved_tensors):
1305+
(x,) = saved_tensors
1306+
return grad_output * torch.cos(x)
1307+
else:
12741308

1309+
class FwdModule(torch.nn.Module):
1310+
def forward(self, x):
1311+
saved_for_backward = (x,)
1312+
return torch.sin(x), saved_for_backward
1313+
1314+
class BwdModule(torch.nn.Module):
1315+
def forward(self, grad_output, *saved_tensors):
1316+
(x,) = saved_tensors
1317+
return grad_output * torch.cos(x)
1318+
1319+
fwd = torch.fx.symbolic_trace(FwdModule())
12751320
bwd = torch.fx.symbolic_trace(BwdModule())
12761321

12771322
def my_sin(x):
12781323
return torch.ops.higher_order.autograd_function_apply(
12791324
fwd,
12801325
bwd,
12811326
x,
1282-
args_tensor_mask=[True],
1283-
non_differentiable_idx=[],
1327+
**_autograd_function_apply_kwargs([True], non_differentiable_idx=[]),
12841328
)
12851329

12861330
jitted = thunder_jit(my_sin)
@@ -1296,10 +1340,21 @@ def my_sin(x):
12961340
expect_grad = torch.autograd.grad(y_ref, x_ref, grad)
12971341
torch.testing.assert_close(actual_grad, expect_grad)
12981342

1299-
class WrongBwdModule(torch.nn.Module):
1300-
def forward(self, ctx, grad_output, *saved_tensors):
1301-
(x,) = saved_tensors
1302-
return grad_output * torch.cos(x)
1343+
# TODO: Remove this once this autograd API becomes stable.
1344+
# On stable PyTorch (with args_tensor_mask), forward/backward expect ctx as first arg.
1345+
# On nightly PyTorch (without args_tensor_mask), ctx is not an argument.
1346+
if _HAS_ARGS_TENSOR_MASK:
1347+
1348+
class WrongBwdModule(torch.nn.Module):
1349+
def forward(self, ctx, grad_output, *saved_tensors):
1350+
(x,) = saved_tensors
1351+
return grad_output * torch.cos(x)
1352+
else:
1353+
1354+
class WrongBwdModule(torch.nn.Module):
1355+
def forward(self, grad_output, *saved_tensors):
1356+
(x,) = saved_tensors
1357+
return grad_output * torch.cos(x)
13031358

13041359
wrong_bwd = torch.fx.symbolic_trace(WrongBwdModule())
13051360

@@ -1308,8 +1363,7 @@ def my_sin_with_wrong_backward(x):
13081363
fwd,
13091364
wrong_bwd,
13101365
x,
1311-
args_tensor_mask=[True],
1312-
non_differentiable_idx=[],
1366+
**_autograd_function_apply_kwargs([True], non_differentiable_idx=[]),
13131367
)
13141368

13151369
jitted = thunder_jit(my_sin_with_wrong_backward)
@@ -1329,26 +1383,40 @@ def my_sin_with_wrong_backward(x):
13291383
gradcheck(jitted, (x,))
13301384

13311385

1332-
@xfail_if_args_tensor_mask_removed
13331386
def test_autograd_function_apply_with_no_grad():
13341387
# This case is using `torch` operations
1335-
def forward(_, x):
1336-
saved_for_backward = (x,)
1388+
# TODO: Remove this once this autograd API becomes stable.
1389+
# On stable PyTorch (with args_tensor_mask), forward/backward expect ctx as first arg.
1390+
# On nightly PyTorch (without args_tensor_mask), ctx is not an argument.
1391+
if _HAS_ARGS_TENSOR_MASK:
1392+
1393+
def forward(_, x):
1394+
saved_for_backward = (x,)
13371395

1338-
with torch.no_grad():
1339-
sin = torch.sin(x)
1340-
return sin, saved_for_backward
1396+
with torch.no_grad():
1397+
sin = torch.sin(x)
1398+
return sin, saved_for_backward
1399+
1400+
def backward(_, grad_output, *saved_tensors):
1401+
return grad_output * 2
1402+
else:
1403+
1404+
def forward(x):
1405+
saved_for_backward = (x,)
13411406

1342-
def backward(_, grad_output, *saved_tensors):
1343-
return grad_output * 2
1407+
with torch.no_grad():
1408+
sin = torch.sin(x)
1409+
return sin, saved_for_backward
1410+
1411+
def backward(grad_output, *saved_tensors):
1412+
return grad_output * 2
13441413

13451414
def my_sin(x):
13461415
res = torch.ops.higher_order.autograd_function_apply(
13471416
forward,
13481417
backward,
13491418
x,
1350-
args_tensor_mask=[True],
1351-
non_differentiable_idx=[],
1419+
**_autograd_function_apply_kwargs([True], non_differentiable_idx=[]),
13521420
)
13531421
return res
13541422

@@ -1364,24 +1432,40 @@ def my_sin(x):
13641432

13651433
# This is using `thunder` operations
13661434
# NOTE - This takes a different codepath compared to above.
1367-
def forward(_, x): # noqa: F811
1368-
saved_for_backward = (x,)
1369-
thunder.torch._set_grad_enabled_with_warning(False)
1370-
sin = thunder.torch.sin(x)
1371-
thunder.torch._set_grad_enabled_with_warning(True)
1372-
return sin, saved_for_backward
1435+
# TODO: Remove this once this autograd API becomes stable.
1436+
# On stable PyTorch (with args_tensor_mask), forward/backward expect ctx as first arg.
1437+
# On nightly PyTorch (without args_tensor_mask), ctx is not an argument.
1438+
if _HAS_ARGS_TENSOR_MASK:
1439+
1440+
def forward(_, x):
1441+
saved_for_backward = (x,)
1442+
thunder.torch._set_grad_enabled_with_warning(False)
1443+
sin = thunder.torch.sin(x)
1444+
thunder.torch._set_grad_enabled_with_warning(True)
1445+
return sin, saved_for_backward
1446+
1447+
def backward(_, grad_output, *saved_tensors):
1448+
# NOTE - This is incorrect on purpose
1449+
return grad_output * 2
1450+
else:
1451+
1452+
def forward(x):
1453+
saved_for_backward = (x,)
1454+
thunder.torch._set_grad_enabled_with_warning(False)
1455+
sin = thunder.torch.sin(x)
1456+
thunder.torch._set_grad_enabled_with_warning(True)
1457+
return sin, saved_for_backward
13731458

1374-
def backward(_, grad_output, *saved_tensors): # noqa: F811
1375-
# NOTE - This is incorrect on purpose
1376-
return grad_output * 2
1459+
def backward(grad_output, *saved_tensors):
1460+
# NOTE - This is incorrect on purpose
1461+
return grad_output * 2
13771462

13781463
def fn(x):
13791464
res = thunder.torch.autograd_function_apply(
13801465
forward,
13811466
backward,
13821467
x,
1383-
args_tensor_mask=[True],
1384-
non_differentiable_idx=[],
1468+
**_autograd_function_apply_kwargs([True], non_differentiable_idx=[]),
13851469
)
13861470
return res
13871471

thunder/tests/test_update_aliases.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
TorchCompileExecutor,
2222
nvFuserExecutor,
2323
requiresCUDA,
24-
xfail_if_args_tensor_mask_removed,
2524
)
2625
from thunder.torch import _torch_to_thunder_function_map, _inplace_to_out_of_place
2726

@@ -478,7 +477,6 @@ def f(a):
478477

479478
@instantiate(
480479
dtypes=(dtypes.float32,),
481-
decorators=(xfail_if_args_tensor_mask_removed,),
482480
)
483481
def test_higher_order_inplace_alias_update(executor, device, dtype):
484482
torch_dtype = dtypes.to_torch_dtype(dtype)

0 commit comments

Comments
 (0)