Every neural network, no matter how large or how exotic its attention mechanisms or convolutions, is built out of one operation repeated over and over: a linear (fully connected / dense) transformation of its input, usually followed by a nonlinearity. Understanding what a "linear layer" actually computes, down to the exact shapes involved, is the foundation everything else in this Part builds on: [02-layers/02-linear-backward] needs this forward pass to differentiate, and the Sequential container you'll build later in this track needs to chain many of these together correctly.
A linear layer holds two learnable tensors: a weight matrix and a bias vector. Given an input vector, it computes a new vector where each output entry is a weighted sum of every input entry, plus a learned constant offset. Stacked into a batch of many input vectors at once (the normal case during training, for GPU efficiency), this becomes a single matrix multiplication.
Implement linear_forward(x, weight, bias). x has shape (batch_size, in_features). Match PyTorch's own nn.Linear convention exactly: weight has shape (out_features, in_features) (each ROW of weight is the set of weights for one output neuron), not (in_features, out_features). bias has shape (out_features,). The output has shape (batch_size, out_features).
weight is (out_features, in_features), matching torch.nn.Linear.weight's shape exactly, not its transpose.batch_size, including batch_size == 1.Since weight is (out_features, in_features) rather than (in_features, out_features), you need x @ weight.T, not x @ weight, to get shapes that line up: (batch_size, in_features) @ (in_features, out_features) = (batch_size, out_features).
x @ weight.T + bias: NumPy's broadcasting handles adding a (out_features,) vector to a (batch_size, out_features) matrix automatically, adding it to every row.
Click "Run Tests" to test your implementation