Trace one sample through the source’s model. Shapes are written as (features,); a batch of 16 samples would carry a (16, features) prefix through every step — the batch dimension is never touched by any layer.
input x (2,) raw sample
Linear(2,16) W₁ (16×2) + b₁ (16,) 2×16 + 16 = 48 params
ReLU mask (16,) (16,) 0 params
Linear(16,16) W₂ (16×16) + b₂ (16,) 16×16 + 16 = 272 params
ReLU mask (16,) (16,) 0 params
Linear(16,8) W₃ (8×16) + b₃ (8,) 16×8 + 8 = 136 params
ReLU mask (8,) (8,) 0 params
Linear(8,1) W₄ (1×8) + b₄ (1,) 8×1 + 1 = 9 params
Sigmoid p (1,) (1,) 0 params
-----------------------
465 trainable scalars
the rule, twice over: a Linear layer with fan_in inputs and fan_out
outputs owns fan_in × fan_out + fan_out numbers. Activations own none.
A second, larger check — a digit classifier for 28×28 images, with a 784-wide input, one hidden layer of 128 and 10 output logits:
784 → 128: 784×128 + 128 = 100,352 + 128 = 100,480
128 → 10: 128×10 + 10 = 1,280 + 10 = 1,290
---------
101,770 parameters
memory at fp32 (4 bytes per scalar)
weights alone 101,770 × 4 = 407,080 B ≈ 0.39 MiB
training copies weights + gradients + Adam m + Adam v
= 4 × 0.39 MiB ≈ 1.55 MiB
activations a batch of 64 samples caches 64 × 128 = 8,192
floats ≈ 32 KiB per hidden layer — small here,
which is exactly why this lesson's framework can
afford to cache everything.
That last line is the hidden cost of the Module contract. Caching is what makes backward possible without re-running forward, and it is why a 7-billion-parameter model training on a GPU needs many times the weights’ memory for the gradients, optimizer moments and cached activations. Framework design is memory design.