What do model.train() and model.eval() change in a PyTorch model?
answer
- one boolean, recursively set
- only some layers read it
- dropout stops dropping; batch stats stop updating
- it does not switch off autograd
- running buffers get polluted at validation
basics
~20 sThey flip a boolean flag that certain layers read. Dropout zeroes activations only in train mode; BatchNorm normalizes with batch statistics and updates its running averages in train, and uses the stored averages in eval. Neither call affects gradient tracking.
solid answer
~40 s`train()` and `eval()` set `self.training` on the module and recursively on every submodule — that is all they do. The behaviour change lives inside the layers that read the flag. `nn.Dropout` drops a fraction of activations and rescales the survivors during training, and is a pure identity in eval. `nn.BatchNorm2d` and its siblings normalize using the current batch's mean and variance while training and simultaneously update `running_mean`/`running_var`; in eval they use those stored statistics, which is what makes inference independent of batch composition. Layers without state-dependent behaviour — `nn.Linear`, `nn.Conv2d`, `nn.LayerNorm` — behave identically in both modes. Crucially, `eval()` does **not** disable autograd: you still wrap validation in `torch.no_grad()` or `torch.inference_mode()` to save memory and time. Forgetting `eval()` at validation gives noisy, pessimistic numbers *and* pollutes BatchNorm's running statistics with validation batches.
code
python · 11 linesimport torch
from torch import nn
m = nn.Dropout(p=0.5)
x = torch.ones(6)
m.train()
print(m.training, m(x)) # ~half zeros, survivors scaled by 1/(1-p)
m.eval()
print(m.training, m(x)) # identity: all onesgo deeper
Remember to call model.train() before training batches and model.eval() before validation or inference, and be able to name Dropout and BatchNorm as the layers that care.
Explain the mechanics: a training flag set recursively, inverted dropout scaling, and BatchNorm switching between batch statistics and running buffers. State clearly that eval() and no_grad() are separate concerns.
Diagnose from symptoms — noisy validation, batch-composition-dependent serving results, drifting running statistics — and know the fine-tuning idiom of putting BatchNorm modules in eval while the rest of the model trains.
Own the guardrail: mode handling is the classic train/serve skew, so decide where it is enforced — a shared evaluate() helper, an inference wrapper, or a test that asserts the flag — rather than trusting every call site to remember.
## A flag, not a mode switch `model.train()` and `model.eval()` are thin: they set the boolean attribute `training` on the module and recurse into every registered submodule. `model.eval()` is exactly `model.train(False)`. Nothing is compiled, nothing is frozen, no graph is rebuilt. The visible behaviour change comes entirely from layers that branch on `self.training` inside their own `forward()`. That framing matters because it tells you the failure mode: any layer you write yourself that should behave differently at inference has to read the flag itself, and any third-party layer that ignores it will not change behaviour no matter which call you make. ## Dropout `nn.Dropout(p)` in train mode zeroes each activation independently with probability `p` and divides the survivors by `1 - p` ("inverted dropout"), so the expected value of the output matches the input. In eval mode it is the identity function — no zeroing, no rescaling. This is why evaluating a model that was left in train mode produces a validation loss that is both noisy and worse than the truth: you are measuring a randomly crippled network. It is also why the same input produces a different output on every call in train mode, which people often mistake for nondeterminism in the data pipeline. ## BatchNorm `nn.BatchNorm1d/2d/3d` are the more dangerous half. In train mode the layer normalizes the batch using the mean and variance **of that batch**, and updates its buffers `running_mean` and `running_var` with an exponential moving average controlled by the `momentum` argument. In eval mode it normalizes using those buffers and updates nothing. Two consequences follow. First, in train mode a sample's output depends on the other samples in its batch — feed a batch of size 1 to a `BatchNorm` layer in train mode and the variance is zero or undefined, which is why tiny batches destabilize BatchNorm models. Second, if you run validation without calling `eval()`, the running statistics are updated with validation data. That is a subtle form of contamination that survives into the next training epoch and into the saved checkpoint, because those buffers are part of `state_dict()`. `nn.LayerNorm`, by contrast, normalizes each sample over its own feature dimensions and keeps no running statistics, so it is genuinely mode-independent — one reason transformer stacks are less sensitive to this bug than convolutional ones. ## What the calls do not do The single most common misconception is that `eval()` turns off gradient tracking. It does not. A forward pass in eval mode still builds the autograd graph, still allocates the intermediate tensors needed for a backward pass, and still costs the memory. To actually skip that you need a context manager: `torch.no_grad()`, or `torch.inference_mode()` which is slightly stricter and slightly faster. Validation code therefore needs both: - `model.eval()` for correct layer behaviour, and - `with torch.no_grad():` for memory and speed. Equally, `eval()` does not freeze parameters. `requires_grad` is untouched, and if you call `backward()` and `step()` while in eval mode the weights update perfectly happily. ## Symptoms of getting it wrong - Validation loss noticeably worse than training loss with no other sign of overfitting, and different on every run → `eval()` was never called. - Inference results that shift depending on how requests are batched → the model is serving in train mode with BatchNorm. - Validation slowly degrading over epochs while training improves → running statistics being polluted by validation batches. - Out-of-memory during validation on batches the training loop handles fine → missing `no_grad`, because activations are being retained for a backward pass that never comes. ## Fine-tuning nuance When fine-tuning a pretrained backbone on a small dataset, teams often want BatchNorm to keep its pretrained statistics rather than re-estimate them from small, unrepresentative batches. The idiom is to call `model.train()` for the whole model and then call `.eval()` on the BatchNorm modules specifically — usually by iterating modules and checking `isinstance(m, nn.modules.batchnorm._BatchNorm)`. Note that this only stops the statistics updating; the affine `weight` and `bias` still train unless you also set `requires_grad = False` on them. Getting this distinction right — buffers versus parameters — is a strong senior signal. ## The checkpoint angle Because `running_mean` and `running_var` are buffers rather than parameters, they are saved by `state_dict()` but never seen by the optimizer. A model whose running statistics were corrupted therefore carries that corruption in its checkpoint, and no amount of optimizer-state restoration fixes it.
- How do you keep BatchNorm layers using their pretrained statistics while fine-tuning the rest of the model?Call model.train() as usual, then iterate the modules and call .eval() on each BatchNorm instance so it stops updating running_mean and running_var and normalizes with the pretrained buffers. That alone leaves the affine weight and bias trainable; set requires_grad = False on them too if you want the layer fully frozen.
- Does model.eval() change how nn.LayerNorm behaves?No. LayerNorm normalizes each sample across its own feature dimensions and keeps no running statistics, so its output is identical in both modes. The same is true of Linear and Conv layers. Only layers whose forward branches on self.training — Dropout, BatchNorm, InstanceNorm with tracked statistics — change.
- If eval() does not disable gradients, what is torch.inference_mode() for?It is a stricter, faster relative of torch.no_grad(): besides skipping graph construction it disables version counting and view tracking, so tensors created inside it cannot later be used in autograd. Use it for pure serving paths; use no_grad when a tensor produced inside might still need to re-enter a graph.
saying these in an interview costs you the question
- Saying model.eval() disables gradient computation
- Saying eval() freezes the weights so no update can happen
- Thinking Dropout scales activations at inference rather than during training
- Not knowing BatchNorm updates running statistics during validation in train mode
- Claiming every layer behaves differently between the two modes