Two kinds of tensors live inside a Module
- Parameters (
nn.Parameter) — learnable. Show up inmodel.parameters(). The optimizer updates them. - Buffers (registered via
self.register_buffer(name, tensor)) — non-learnable but stateful. Examples: BatchNorm's running mean/var, position encodings, attention masks, EMA weights of a teacher model.
Both are saved in state_dict() and both move with .to(device). The difference is just whether the optimizer touches them.
state_dict — the universal serialization format
model.state_dict() returns an OrderedDict mapping parameter / buffer names to their tensors. load_state_dict(d) restores them. This is the recommended format for saving models because it's framework-version-tolerant: even if the model class evolves, you can load the weights into a new architecture as long as the names and shapes match (or use strict=False to allow mismatches).
Freezing
To freeze a parameter (so the optimizer doesn't update it), set p.requires_grad = False. This is how transfer learning starts — freeze the pretrained backbone, train only the new head. The optimizer still sees the parameter (it's in model.parameters()) but with no gradient there's nothing to update.