RuntimeError: Trying to backward through the graph a second time
ID: cuda/backward-through-graph-twice
Version Compatibility
| Version | Status | Introduced | Deprecated | Notes |
|---|---|---|---|---|
| 12 | active | — | — | — |
Root Cause
PyTorch frees the computation graph after the first backward() call. Calling backward() again on the same graph without retain_graph=True, or reusing a non-detached tensor across multiple losses, triggers this error.
genericWorkarounds
-
94% success Restructure code to compute all losses before calling a single backward() on the combined loss
loss_total = loss_a + loss_b loss_total.backward() optimizer.step()
Sources: https://pytorch.org/docs/stable/notes/autograd.html
-
90% success Use .detach() on tensors shared between independent computation paths
For GAN training: fake_images = generator(z) d_loss = discriminator(fake_images.detach()) # detach so D backward doesn't traverse G graph g_loss = discriminator(fake_images) # separate forward for G backward
Sources: https://pytorch.org/tutorials/beginner/dcgan_faces_tutorial.html
-
85% success Use retain_graph=True only on the first backward() and let the second backward() free the graph
loss_d.backward(retain_graph=True) # keep graph for generator update loss_g.backward() # final backward frees the graph # Ensure optimizer.step() and zero_grad() are called appropriately for each network
Sources: https://pytorch.org/docs/stable/generated/torch.Tensor.backward.html
Dead Ends
Common approaches that don't work:
-
Always set retain_graph=True on every backward() call
70% fail
retain_graph=True prevents graph memory from being freed, causing massive GPU memory leaks that eventually lead to OOM; it is a workaround, not a fix, and masks the real architectural problem
-
Call optimizer.zero_grad() between the two backward passes hoping to reset the graph
92% fail
zero_grad() only zeroes parameter gradients, it does not recreate the freed computation graph
-
Wrap the second backward call in torch.no_grad()
95% fail
torch.no_grad() disables gradient tracking for new operations but cannot reconstruct an already-freed computation graph