01-hypothesis-function's input @ weight.T was one specific case of matrix multiplication: two 2D arrays combining into a 2D result. Real networks don't stop there — sometimes you multiply a single vector against a matrix, sometimes you multiply a whole batch of matrices against one shared matrix in a single call, and each of those situations has a slightly different rule for what shape comes out. torch.matmul is the one function that has to make all of these cases behave sensibly, and predicting its output shape correctly is what this question is really testing.
Implement matmul(a, b), mirroring torch.matmul. Theory lays out the dimension-dependent rules (1D-1D, 2D-1D, 1D-2D, 2D-2D, batched); the function itself doesn't need to branch on any of them explicitly, because 03-broadcasting-rules's broadcasting logic and NumPy's own @ operator already implement every one of those cases identically to torch.matmul.
a and b can each be 1D, 2D, or higher-dimensional (batched).(m,n)@(n,p)->(m,p) matrix multiplication.03-broadcasting-rules.a.ndim/b.ndim.Open one at a time. Each gives away a little more than the last.
You don't need to write five different code paths for five different dimensionality cases. Is there a single NumPy operator that already implements all of torch.matmul's dimension-dependent rules?
a @ b is it — @ calls np.matmul under the hood, and np.matmul follows the exact same 1D/2D/batched convention torch.matmul documents. The work in this question is recognizing which rule applies to which test case, not writing more code.
Click "Run Tests" to test your implementation