How to Fix "element 0 of tensors does not require grad and does not have a grad_fn"
The tensor you called .backward() on is not attached to an autograd graph. Its requires_grad is False and its grad_fn is None, so PyTorch has nothing to differentiate. On torch 2.8.0 the usual causes are a forward pass under torch.no_grad(), a .detach(), .data, .item() or numpy step in the loss, an argmax in the loss, or a model whose parameters were all frozen. Fix the cut, not the symptom. loss.requires_grad_(True) makes the error go away and trains nothing.
Print loss.grad_fn before backward(). A healthy loss prints something like <NllLossBackward0>. If it prints None, walk back through the code until you find the line where grad_fn first became None. That line is the bug.
Check your own setup
Pick how the loss was produced, whether the parameters require grad, the mode the model is in, and whether you called requires_grad_(True) on the loss afterwards. The tool tells you whether loss.backward() raises, quotes the exact message, and, when it runs, says whether the parameter gradients are actually populated. Every combination was run on torch 2.8.0 CPU with an nn.Linear(2, 3) and F.cross_entropy, and the strings below are copied from the exceptions.
Runs entirely in your browser. Nothing you pick is sent anywhere. The loss is treated as a single scalar; a multi-output torch.autograd.grad call reports the same messages with a different element index.
The error
RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn
Both Tensor.backward() and torch.autograd.grad() take a sequence of outputs. The engine checks each one for a gradient edge before it starts. A single scalar loss is element 0 of that sequence, which is where the number comes from. It has nothing to do with the first sample in your batch.
Two facts from the PyTorch autograd mechanics notes explain every case on this page. First, no-grad mode "computations in no-grad mode behave as if none of the inputs require grad", and they "are never recorded in the backward graph even if there are inputs that have require_grad=True". Second, the default mode "is the only mode in which requires_grad takes effect. requires_grad is always overridden to be False in both the two other modes." So the loss ends up without a grad_fn whenever the forward ran in no-grad or inference mode, whenever a non-differentiable step sat between the parameters and the loss, or whenever no parameter required grad in the first place. The site's Stack Overflow frequency study ranks this message at number 15 and puts it at roughly 15 percent of gradient errors.
Cause 1, the forward pass ran under torch.no_grad or inference_mode
This is the most common cause in code that shares a forward function between training and validation. The context manager stays on for the training call, or a checkpoint is loaded and evaluated under no_grad and the same output is then fed to the loss.
model.load_state_dict(torch.load("ckpt.pt"))
opt = torch.optim.SGD(model.parameters(), lr=0.1)
with torch.no_grad():
out = model(x) # nothing recorded
loss = F.cross_entropy(out, y)
loss.backward()
# RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn
The output has requires_grad=False, so everything derived from it does too. Move the training forward out of the block. torch.load itself is not the problem. A whole model saved and loaded back keeps requires_grad=True on its parameters, and a normal forward after loading populates weight.grad.
torch.inference_mode() behaves the same way with one extra sting. A loss computed inside it cannot be rescued with requires_grad_(True) at all. Torch 2.8.0 raises RuntimeError: Setting requires_grad=True on inference tensor outside InferenceMode is not allowed. The notes say tensors created in inference mode "will not be able to be used in computations to be recorded by autograd after exiting inference mode".
Cause 2, a detach, .data, .item() or numpy step
Each of these produces a new tensor with no history. The graph is cut at that point and the loss downstream is a plain number wrapped in a tensor.
loss = F.cross_entropy(model(x), y).detach() # cut
loss = F.cross_entropy(model(x).data, y) # cut
loss = torch.tensor(F.cross_entropy(model(x), y).item()) # cut
out = torch.from_numpy(model(x).detach().numpy()) # cut
loss.backward()
# RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn
A model whose forward returns self.fc(x).data raises the same message. So does a custom loss that computes with numpy inside. If you skip the detach() and call .numpy() directly on a tensor that requires grad you get an earlier, clearer error instead: RuntimeError: Can't call numpy() on Tensor that requires grad. Use tensor.detach().numpy() instead. And a custom loss that sums .item() values returns a Python float, which has no backward method at all.
The fix is to keep the training path in torch. Call .item() only when logging, and call it after backward(). Reading loss.item() before backward() is fine too, as long as you keep the original tensor for the backward call.
Cause 3, every parameter is frozen
Freezing a backbone with requires_grad_(False) is normal. Freezing all of it, including the head you meant to train, produces this error because the parameters are the only leaves that could carry a gradient.
model.requires_grad_(False) # froze the head too
loss = F.cross_entropy(model(x), y)
loss.backward()
# RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn
model.head.requires_grad_(True) # or model.requires_grad_(True)
The notes describe this mechanism exactly: computations that use frozen parameters "would not be recorded in the forward pass", so those parameters "won't be part of the backward graph in the first place". If the input itself requires grad, as in adversarial example code, the frozen model does not raise. The gradient flows to x.grad and weight.grad stays None, which is what that use case wants.
Cause 4, argmax or a comparison in the loss
argmax returns an int64 tensor with requires_grad=False and grad_fn=None. The same goes for ==, > and friends. Accuracy is a metric, not a loss, and a loss built from it has no gradient path to the weights.
pred = model(x).argmax(dim=1)
loss = (pred.float() - y.float()).abs().mean()
loss.backward()
# RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn
loss = (pred - y).abs().mean() # without .float()
# RuntimeError: mean(): could not infer output dtype. Input dtype must be either a floating point or complex dtype. Got: Long
Replace the discrete step with a differentiable surrogate. F.cross_entropy on the logits is the standard choice, and 1 - softmax(out)[range(N), y].mean() also trains. Trying to force it with requires_grad_(True) on the integer loss raises RuntimeError: only Tensors of floating point dtype can require gradients.
The fix that fixes nothing
The top search result for this error suggests loss.requires_grad_(True). It works in the sense that the exception disappears. It fails in the sense that matters. The loss becomes a leaf tensor that requires grad, backward() writes 1.0 into loss.grad, and every parameter's .grad stays None. The optimizer step then does nothing. On torch 2.8.0 with all parameters frozen, weight changed=False after opt.step().
x = torch.randn(3) # no requires_grad
loss = (x * 2).sum()
loss.requires_grad_(True)
loss.backward() # no error
print(loss.grad, x.grad) # tensor(1.) None
Your training loop will run for hours with a flat loss curve. If the loss already has a grad_fn, the call is a no-op and does no harm, which is why the advice survives. Use the diagnostic prints from the box at the top instead.
What model.eval() does not do
People reach for model.train() when they see this error. It is unrelated. The autograd notes state that "Evaluation mode is not a mechanism to locally disable gradient computation" and that module.eval() is "completely orthogonal to no-grad mode and inference mode". On torch 2.8.0 an eval-mode nn.Linear backpropagates normally, and every row in the table below is identical for train and eval mode. If your validation loop has no error while training does, the difference is a no_grad block or a detach, not the mode flag.
What torch 2.8.0 actually raises
Every row was produced by an nn.Linear(2, 3), a batch of 4, and F.cross_entropy, then calling loss.backward(). The model mode column is omitted because train and eval gave the same result in all 72 combinations that were run.
| Setup | Result |
|---|---|
| Normal forward, params require grad | Valid. weight.grad populated, shape (3, 2) |
| Normal forward, all params frozen | RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn |
Forward inside torch.no_grad() | RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn |
Forward inside torch.inference_mode() | RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn |
loss.detach() or model(x).data | RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn |
torch.tensor(loss.item()) | RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn |
torch.from_numpy(out.detach().numpy()) | RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn |
Loss from argmax(...).float() | RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn |
Loss from (argmax == y).float().mean() | RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn |
Loss from argmax without .float(), then .mean() | RuntimeError: mean(): could not infer output dtype. Input dtype must be either a floating point or complex dtype. Got: Long |
.numpy() on a tensor that requires grad | RuntimeError: Can't call numpy() on Tensor that requires grad. Use tensor.detach().numpy() instead. |
Custom loss summing .item() values | Returns a Python float. AttributeError: 'float' object has no attribute 'backward' |
Any cut above, then loss.requires_grad_(True) | Runs. loss.grad is 1.0, weight.grad is None, weights unchanged after opt.step() |
Inference-mode loss, then loss.requires_grad_(True) | RuntimeError: Setting requires_grad=True on inference tensor outside InferenceMode is not allowed. |
Integer loss, then loss.requires_grad_(True) | RuntimeError: only Tensors of floating point dtype can require gradients |
model.eval(), normal forward | Valid. weight.grad populated, shape (3, 2) |
Whole model via torch.load, normal forward | Valid. Parameters keep requires_grad=True |
Frozen model, input has requires_grad=True | Valid. x.grad populated, weight.grad None |
One detail is easy to miss. The inference-mode row only raises the "not allowed" message when the loss itself was computed inside the block. If only the forward ran in inference mode and the loss was computed outside, requires_grad_(True) succeeds and you land in the silent row above it, with no parameter gradients.
Three prints that find the cut
out = model(x)
loss = criterion(out, y)
print(next(model.parameters()).requires_grad) # True
print(out.grad_fn) # <AddmmBackward0 ...>
print(loss.requires_grad, loss.grad_fn) # True <NllLossBackward0 ...>
On a healthy loop all three lines are truthy. The first one going False means Cause 3. The second going None with the first True means Cause 1, or a .data inside forward. The third going None with the second alive means the cut is in the loss itself, which is Cause 2 or Cause 4. If you are trying to freeze a layer on purpose, the parameter counter reports how many parameters still require grad so you can confirm the head was left trainable.
Built by Michael Lip. Behaviour verified against torch 2.8.0 on 6 September 2026.