The convention: trailing underscore = mutate
Any PyTorch op with a trailing underscore mutates its tensor in place: add_, mul_, zero_, fill_, uniform_, normal_, clamp_. The non-underscore version returns a new tensor and leaves the input unchanged.
Why care? Two reasons:
- Memory. In-place avoids allocating a new tensor. For a 1B-parameter model, that matters.
- Autograd safety. In-place ops can corrupt the autograd graph. PyTorch tries to detect this and raises a clear error, but you should know the rule: don't mutate a tensor that's needed for backward.
Where in-place ops are routine
optimizer.zero_grad()callsp.grad.zero_()on every parameter (or setsp.grad = Noneif you passset_to_none=True, which is now the default in modern PyTorch and slightly faster).- Custom weight initialization typically uses
.uniform_()/.normal_()inside atorch.no_grad()block. - EMA (exponential moving average) updates of teacher / momentum networks are textbook in-place territory.
Outside those patterns, lean on the non-mutating versions. The memory savings of an extra in-place is rarely worth the autograd risk in normal model code.