nn.Module: defining models

PyTorch · Essentials · 12 min

nn.Module is how every PyTorch model, layer and loss is structured, from a two-layer MLP to an LLM.

A model is a class that inherits from torch.nn.Module. You create sub-layers in __init__ and assign them to attributes; you describe how input flows through them in forward.

Assigning a module or nn.Parameter to self registers it. That registration is what makes these work automatically:

  • model.parameters() and model.named_parameters() iterate all trainable weights (pass them to the optimizer).
  • model.to(device) moves every parameter and buffer.
  • model.state_dict() collects weights for saving.
  • model.train() / model.eval() switch the mode of every sub-module.

Always call super().__init__() first, and call the model as model(x), not model.forward(x), so that hooks run.

Layers held in a plain Python list are not registered; use nn.ModuleList or nn.ModuleDict instead. For a simple stack of layers, nn.Sequential(layer1, layer2, ...) saves writing a class at all, and its items can be indexed like a list.

Because forward is ordinary Python, you can use loops, conditionals and print statements inside it, which makes custom architectures straightforward to write and debug.

Example

import torch
from torch import nn

class MLP(nn.Module):
    def __init__(self, n_in, n_hidden, n_out):
        super().__init__()
        self.fc1 = nn.Linear(n_in, n_hidden)    # registered automatically
        self.fc2 = nn.Linear(n_hidden, n_out)

    def forward(self, x):
        return self.fc2(torch.relu(self.fc1(x)))

model = MLP(4, 16, 3)
out = model(torch.randn(8, 4))                   # call the model, not .forward
print(out.shape)                                 # torch.Size([8, 3])
print(sum(p.numel() for p in model.parameters()))  # 131
for name, p in model.named_parameters():
    print(name, tuple(p.shape))   # fc1.weight (16, 4), fc1.bias (16,), ...

seq = nn.Sequential(nn.Linear(4, 16), nn.ReLU(), nn.Linear(16, 3))
print(seq(torch.randn(8, 4)).shape)              # torch.Size([8, 3])

Remember

  • Create layers in __init__, compute in forward, call model(x)
  • Attribute assignment registers parameters; plain lists do not
  • nn.Sequential covers simple layer stacks

Watch out for

  • Storing layers in a Python list, so the optimizer never sees their parameters; use nn.ModuleList.
  • Forgetting super().__init__(), which fails as soon as you assign a layer.

Check yourself

  1. A model keeps its blocks in self.blocks = [nn.Linear(8, 8) for _ in range(3)]. What goes wrong?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: pytorch.org/docs/stable/generated/torch.nn.Module.html