Forward and backward hooks

PyTorch · Advanced · 10 min

Hooks power feature extraction, activation statistics, gradient debugging and interpretability tools.

A hook is a callback PyTorch runs at a specific point. Registering one returns a handle; call handle.remove() when done, or the hook keeps running (and keeps references alive).

  • module.register_forward_hook(fn) calls fn(module, inputs, output) after forward. Returning a value replaces the output. Use it to capture intermediate activations.
  • module.register_forward_pre_hook(fn) runs before forward and can modify inputs.
  • module.register_full_backward_hook(fn) calls fn(module, grad_input, grad_output) during backward.
  • tensor.register_hook(fn) calls fn(grad) when that tensor's gradient is computed; returning a tensor replaces the gradient. Handy for gradient clipping on one tensor or for inspecting non-leaf gradients.

Typical uses:

  • Extract embeddings from a pretrained network's penultimate layer.
  • Log activation means and standard deviations per layer to diagnose dead ReLUs or exploding values.
  • Build saliency and Grad-CAM style visualisations from activations plus gradients.

Store output.detach() in forward hooks unless you need gradients through it; otherwise you keep the graph alive. Hooks only fire when the module is called via module(x), not module.forward(x). For torchvision models, torchvision.models.feature_extraction.create_feature_extractor is a cleaner alternative for pulling out named layers.

Example

import torch
from torch import nn

model = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 2))
acts = {}

def save_output(name):
    def hook(module, inputs, output):
        acts[name] = output.detach()
    return hook

handle = model[1].register_forward_hook(save_output("relu"))
model(torch.randn(3, 4))
print(acts["relu"].shape)                     # torch.Size([3, 8])
print((acts["relu"] == 0).float().mean())     # fraction of dead units in this batch
handle.remove()                               # stop capturing

x = torch.randn(3, 4, requires_grad=True)
x.register_hook(lambda g: print("input grad shape", g.shape))
model(x).sum().backward()                     # input grad shape torch.Size([3, 4])

Remember

  • Forward hooks see (module, inputs, output); returning a value replaces output
  • tensor.register_hook sees and can rewrite gradients
  • Keep the handle and remove() it

Watch out for

  • Saving output without .detach(), which keeps the whole autograd graph in memory.

Check yourself

  1. What does a forward hook's return value do if it is not None?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: pytorch.org/docs/stable/generated/torch.nn.Module.html#torch.nn.Module.register_forward_hook