Skip to content

← nn.Module Mechanics step 2 of 7

Easy Primitives

Two modes, one flag

model.train()      # sets self.training = True on the module and every child
model.eval()       # sets it to False

That is the whole mechanism. It is a boolean, propagated down the module tree, and layers read it in their own forward.

The two layers that care, and how

Dropout zeroes a random fraction of its input in training and is the identity in evaluation. Leaving a model in train() during evaluation therefore adds noise to every prediction, which shows up as validation metrics that are worse than they should be and that change between runs.

BatchNorm normalises by the current batch’s statistics in training, while updating a running estimate; in evaluation it uses the running estimate. Evaluating in train() mode means predictions depend on which other examples happen to share the batch, so batch size and ordering change the answer. It also keeps updating the running statistics with your validation data, which quietly contaminates the model.

It has nothing to do with gradients

model.eval()               # changes layer behaviour
with torch.no_grad():      # changes whether a graph is built

Two independent things, and an evaluation loop wants both. eval() alone still builds a graph, and no_grad() alone still runs dropout. Neither implies the other, and forgetting either is its own distinct bug.

The habit

model.train()
for batch in train_loader: ...

model.eval()
with torch.no_grad():
    for batch in val_loader: ...
model.train()      # back, before the next epoch

That last line is the one people forget after an early-stopping check.

Your task

def both_modes(n: int) -> dict

Build a module containing nn.Dropout(p=1.0), run a tensor of ones through it in training mode and again in evaluation mode, and return both outputs plus the module’s training flag in each case.

A dropout probability of 1 makes the difference exact rather than random.