pytorch - 💡(How to fix) Fix `torch.jit.ScriptModule.register_full_backward_hook` raises RuntimeError due to `_is_full_backward_hook` attribute type cast failure [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
pytorch/pytorch#181473Fetched 2026-04-25 06:02:28
View on GitHub
Comments
0
Participants
1
Timeline
10
Reactions
0
Author
Participants
Timeline (top)
mentioned ×4subscribed ×4labeled ×2

torch.jit.ScriptModule.register_full_backward_hook() raises RuntimeError: Could not cast attribute '_is_full_backward_hook' to type NoneType: Cannot cast True to None when called on a ScriptModule subclass, while the same API works correctly on a plain nn.Module.

Error Message

import torch import torch.nn as nn

class MyModel(torch.jit.ScriptModule): def init(self): super().init() self.linear = nn.Linear(2, 2)

def forward(self, x):
    return self.linear(x)

model = MyModel() model.register_full_backward_hook(lambda module, grad_input, grad_output: grad_input)

RuntimeError: Could not cast attribute '_is_full_backward_hook' to type NoneType: Cannot cast True to None

Root Cause

ScriptModule uses a two-layer structure:

  • Outer layer: the user's class (e.g. MyModel), which inherits from ScriptModule -> nn.Module, with Python-side methods available
  • Inner layer: _actual_script_module, a RecursiveScriptModule instance created during init_then_script, which wraps the compiled C++ TorchScript module

The call chain is:

  1. register_full_backward_hook() (defined in nn.Module) executes self._is_full_backward_hook = True
  2. This triggers ScriptModule.__setattr__, which unconditionally delegates to setattr(self._actual_script_module, attr, value) since _actual_script_module already exists in __dict__
  3. This triggers RecursiveScriptModule.__setattr__, which finds _c.hasattr('_is_full_backward_hook') is True (the attribute was registered as None during nn.Module.__init__ before compilation)
  4. self._c.setattr('_is_full_backward_hook', True) is called on the C++ side, which expects the attribute type to remain NoneType, causing the cast failure

The core issue is that ScriptModule.__setattr__ delegates all attribute assignments to the inner RecursiveScriptModule without distinguishing which attributes should stay on the Python side. _is_full_backward_hook is a Python-side runtime flag (used to enforce mutual exclusion with the deprecated register_backward_hook), it should be set on the Python __dict__ rather than forwarded to the C++ module.

In contrast, register_full_backward_pre_hook works correctly because it does not set _is_full_backward_hook internally.

Code Example

import torch
import torch.nn as nn


class MyModel(torch.jit.ScriptModule):
    def __init__(self):
        super().__init__()
        self.linear = nn.Linear(2, 2)

    def forward(self, x):
        return self.linear(x)


model = MyModel()
model.register_full_backward_hook(lambda module, grad_input, grad_output: grad_input)
# RuntimeError: Could not cast attribute '_is_full_backward_hook' to type NoneType: Cannot cast True to None
RAW_BUFFERClick to expand / collapse

🐛 Describe the bug

Summary

torch.jit.ScriptModule.register_full_backward_hook() raises RuntimeError: Could not cast attribute '_is_full_backward_hook' to type NoneType: Cannot cast True to None when called on a ScriptModule subclass, while the same API works correctly on a plain nn.Module.

Reproduction

import torch
import torch.nn as nn


class MyModel(torch.jit.ScriptModule):
    def __init__(self):
        super().__init__()
        self.linear = nn.Linear(2, 2)

    def forward(self, x):
        return self.linear(x)


model = MyModel()
model.register_full_backward_hook(lambda module, grad_input, grad_output: grad_input)
# RuntimeError: Could not cast attribute '_is_full_backward_hook' to type NoneType: Cannot cast True to None

Expected Behavior

Should work the same as on nn.Module — register the hook and return a RemovableHandle.

Root Cause Analysis

ScriptModule uses a two-layer structure:

  • Outer layer: the user's class (e.g. MyModel), which inherits from ScriptModule -> nn.Module, with Python-side methods available
  • Inner layer: _actual_script_module, a RecursiveScriptModule instance created during init_then_script, which wraps the compiled C++ TorchScript module

The call chain is:

  1. register_full_backward_hook() (defined in nn.Module) executes self._is_full_backward_hook = True
  2. This triggers ScriptModule.__setattr__, which unconditionally delegates to setattr(self._actual_script_module, attr, value) since _actual_script_module already exists in __dict__
  3. This triggers RecursiveScriptModule.__setattr__, which finds _c.hasattr('_is_full_backward_hook') is True (the attribute was registered as None during nn.Module.__init__ before compilation)
  4. self._c.setattr('_is_full_backward_hook', True) is called on the C++ side, which expects the attribute type to remain NoneType, causing the cast failure

The core issue is that ScriptModule.__setattr__ delegates all attribute assignments to the inner RecursiveScriptModule without distinguishing which attributes should stay on the Python side. _is_full_backward_hook is a Python-side runtime flag (used to enforce mutual exclusion with the deprecated register_backward_hook), it should be set on the Python __dict__ rather than forwarded to the C++ module.

In contrast, register_full_backward_pre_hook works correctly because it does not set _is_full_backward_hook internally.

Suggested Fix

ScriptModule.__setattr__ should keep Python-side runtime attributes (like _is_full_backward_hook, _backward_hooks, _backward_pre_hooks, etc.) in the Python __dict__ instead of delegating them to the C++ RecursiveScriptModule. One approach is to check whether the attribute is a Python-only hook-related attribute before delegating.

Versions

  • PyTorch version: 2.11
  • Python version: 3.12

cc @EikanWang @jgong5 @wenzhe-nrv @sanchitintel

extent analysis

TL;DR

Modify ScriptModule.__setattr__ to keep Python-side runtime attributes in the Python __dict__ instead of delegating them to the C++ RecursiveScriptModule.

Guidance

  • Identify Python-side runtime attributes like _is_full_backward_hook, _backward_hooks, _backward_pre_hooks that should not be delegated to the C++ module.
  • Modify ScriptModule.__setattr__ to check if the attribute is a Python-only hook-related attribute before delegating to RecursiveScriptModule.
  • Consider adding a whitelist or blacklist of attributes that should be kept on the Python side to ensure correct behavior.
  • Verify the fix by testing register_full_backward_hook on a ScriptModule subclass and checking that it works as expected.

Example

class ScriptModule(torch.jit.ScriptModule):
    def __setattr__(self, attr, value):
        if attr in ['_is_full_backward_hook', '_backward_hooks', '_backward_pre_hooks']:
            super().__setattr__(attr, value)
        else:
            super().__setattr__(attr, value)  # delegate to RecursiveScriptModule

Notes

This fix assumes that the issue is specific to ScriptModule and its interaction with RecursiveScriptModule. Further testing may be needed to ensure that this fix does not introduce other issues.

Recommendation

Apply workaround: modify ScriptModule.__setattr__ to keep Python-side runtime attributes in the Python __dict__ to fix the RuntimeError issue with register_full_backward_hook.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

pytorch - 💡(How to fix) Fix `torch.jit.ScriptModule.register_full_backward_hook` raises RuntimeError due to `_is_full_backward_hook` attribute type cast failure [1 participants]