A naive convolution has six nested loops. Frameworks instead unfold every receptive field into a row of a matrix (im2col), so the whole convolution becomes a single matrix multiplication.
For x: (N, C, H, W), square kernels of size k, a stride and zero padding:
im2col(x, k, stride=1, pad=0): return the matrix of shape (N * out_h * out_w, C * k * k). Rows are ordered by sample, then output row, then output column. Each row lists the patch values in (C, k, k) order.conv2d(x, W, b, stride=1, pad=0) for weights W: (F, C, k, k) and bias b: (F,): compute it as im2col(x) @ W.reshape(F, -1).T + b, reshaped to (N, F, out_h, out_w)Output size: out = (H + 2 * pad - k) // stride + 1.