Lightning-AI / Lightning-AI/lightning-thunder

Autodiff transform prunes incorrectly registered operations (non-prims without subsymbols) without a grad_transform when followed by a stateful nn.Module

Open
#2,361 2 comments 0 reactions 1 assignee View on GitHub

@t-vi is already working on this.

Since Jul 29, 2025.

transforms
Dominant language
Python
Stars
1.5k
Forks
121
PR merge metrics
No merged PRs in 30d

Description

## 🐛 Bug

Thunder's `autodiff` transform appears to unexpectedly prune operations that do not have a registered `grad_transform`. This behavior is observed when such an operation is followed in the computation graph by a stateful `nn.Module` (like `nn.Linear`) that triggers parameter lifting.

The introduction of a stateful module causes its parameters to be "lifted" and treated as inputs to the graph. It seems that in this scenario, the dependency analysis within the `AugmentedForwardProcessor` does not fully trace the gradient requirements from the stateful module's backward pass back to the output of the preceding operation (the one without a `grad_transform`).

Consequently, the processor may incorrectly conclude that the operation is not needed for any gradient computation and prune it from the graph, which can lead to a corrupted trace or a runtime error.

### To Reproduce

Run the following script with `--repro` option.

#### Code sample

```python
import argparse

import torch
import torch.nn as nn

import thunder
from thunder.core.symbol import Symbol
from thunder.core.proxies import TensorProxy
from thunder.executors.torchex import ex
from thunder.executors.torchex import _always_executable

# 1. Define a simple Python function and its meta-function
def my_simple_mul(a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
# The actual implementation doesn't matter for demonstrating the bug,
# as it's the graph structure that triggers it.
return a * b

def my_simple_mul_meta(a: TensorProxy, b: TensorProxy) -> TensorProxy:
# Meta functions should return empty tensors with the correct metadata
return TensorProxy(like=a)

# 2. Manually create a Thunder Symbol for the function
# This symbol will be "opaque" to the autodiff transform because it has no
# registered grad_transform.
my_simple_mul_symbol = Symbol(
name="my_simple_mul",
meta=my_simple_mul_meta,
is_prim=False,
)

# 3. Register an implementation for the symbol with an executor.
ex.register_implementation(my_simple_mul_symbol, my_simple_mul, checker=_always_executable)

# 4. Make the symbol available in a thunder namespace to be called from the model
# (This is for convenience in the reproducer script)
setattr(thunder.torch, "my_simple_mul", my_simple_mul_symbol)

# 5. Define the two nn.Modules to compare
# This module should work correctly.
class SinModule(nn.Module):
def forward(self, x, y):
intermediate = thunder.torch.my_simple_mul(x, y)
output = torch.sin(intermediate)
return output

# This module will demonstrate the bug.
class LinearModule(nn.Module):
def __init__(self):
super().__init__()
self.linear = nn.Linear(2, 2, bias=False)

def forward(self, x, y):
intermediate = thunder.torch.my_simple_mul(x, y)
output = self.linear(intermediate)
return output

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Demonstrate a bug in Thunder's autodiff transform.")
parser.add_argument(
"--repro",
action="store_true",
help="Run the LinearModule case to reproduce the bug. Default is to run the working SinModule case.",
)
args = parser.parse_args()

x = torch.randn(4, 2, device="cuda")
y = torch.randn(4, 2, device="cuda")

if args.repro:
print("\n--- Testing LinearModule (This should demonstrate the bug) ---")
model_class = LinearModule
model_name = "LinearModule"
else:
print("--- Testing SinModule (This should work correctly) ---")
model_class = SinModule
model_name = "SinModule"

model = model_class().cuda()
jitted_model = thunder.jit(
model,
disable_torch_autograd=False,
)

jitted_model(x, y)
```

Running this script with `--repro` would result in
```
--- Testing LinearModule (This should demonstrate the bug) ---
Traceback (most recent call last):
File "/home/mkozuki/ghq/github.com/Lightning-AI/lightning-thunder/concise_repro.py", line 92, in
jitted_model(x, y)
File "/home/mkozuki/ghq/github.com/crcrpar/torch-2/torch/nn/modules/module.py", line 1773, in _wrapped_call_impl
return self._call_impl(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/mkozuki/ghq/github.com/crcrpar/torch-2/torch/nn/modules/module.py", line 1784, in _call_impl
return forward_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/mkozuki/ghq/github.com/Lightning-AI/lightning-thunder/thunder/core/module.py", line 80, in forward
res = self._forward_fn(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/mkozuki/ghq/github.com/Lightning-AI/lightning-thunder/thunder/__init__.py", line 839, in wrapped
return fn(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^
File "/home/mkozuki/ghq/github.com/Lightning-AI/lightning-thunder/thunder/__init__.py", line 881, in fn_
result = cache_entry.computation_fn(*inps)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/mkozuki/ghq/github.com/Lightning-AI/lightning-thunder/thunder/__init__.py", line 800, in wrapped
return fn(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^
File "/home/mkozuki/ghq/github.com/crcrpar/torch-2/torch/utils/_contextlib.py", line 120, in decorate_context
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/mkozuki/ghq/github.com/Lightning-AI/lightning-thunder/thunder/executors/torchex.py", line 167, in no_autocast_fn
return fn(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^
File "thunder.computation_3", line 14, in computation
NameError: name 'intermediate' is not defined
```

### Expected behavior

Both path should not behave differently.

### Environment

- PyTorch Version (e.g., 1.0): 2.9.0a0+git476874b
- OS (e.g., Linux): Ubuntu 24.04
- How you installed PyTorch (`conda`, `pip`, source): source
- Build command you used (if compiling from source): N/A
- Python version: 3.12.10
- CUDA/cuDNN version: 12.9/ N/A
- GPU models and configuration: RTX600Ada
- Any other relevant information: N/A

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.