Hey!
PyTorch provides the Einsum function, which allows you to do different tensor operations based on indices. See here 1, here, here for more explanations. I find einsum the easiest way to do such operations.
(Also, I would assume that matrix is actually of shape [batch, hidden_in, hidden_out], as you want to multiply 2D slices of shape [seq_len, hidden_in] with 2D slices of shape [hidden_in, hidden_out], and that’s the only way that you could achieve that multiplication).
batch = 8
seq_len = 25
hidden_in = 16
hidden_out = 32

input = torch.randn((batch, seq_len, hidden_in))
matrix = torch.randn((batch, hidden_in, hidden_out))

output = torch.einsum('bsi, bio -> bso', input, matrix).
output.shape

torch.Size([8, 25, 32]).

The most important element is the formula ‘bsi, bio → bso’, which basically says:
I have two inputs with shape indices (b, s, i), (b, i o) - hopefully you see the meaning of each letter, and an output of shape (b, s, o). I multiply corresponding 2d slices on the dimension b (meaning slices of shape (s, i) with slices of shape (i, o)). These “si, io → so” is basically regular 2D matrix multiplication rule, where you multiply and sum along the dimension i, common to each operand, and get the result of shape (s, o), for each pair in the batch dimension.