Linear
y = x @ Wᵀ + b, matching torch.nn.Linear.
The weight is stored as (out_features, in_features) and transposed in forward, exactly as PyTorch does. That layout is not arbitrary — it means a PyTorch state_dict loads without any transposition, which keeps the validation comparison honest.
Initialisation draws U(−1/√fan_in, +1/√fan_in) for both weight and bias. That closed form is exactly torch's default kaiming_uniform_(w, a=√5): with a=√5 the gain is √(2/6), so the bound reduces to 1/√fan_in. Verified against torch.nn.Linear — both give ±0.0357142857 at fan_in=784.
Weights are drawn from a module-level generator seeded by nn.manual_seed, not from default_rng() per layer. An unseeded generator would give every run different weights, making a training result impossible to reproduce and a divergence impossible to investigate.
manual_seed mirrors torch.manual_seed in spirit, not in stream — the two libraries draw from different generators by design, so equal seeds do not give equal weights. To compare against torch, copy a state_dict rather than seeding both.