Implement Multi-Head Self-Attention (MHA) from scratch in PyTorch, ensuring your implementation supports batching and causal masking.
Accept x with shape [batch, sequence, model_dim] and projection matrices with shape [model_dim, model_dim]. Return the projected attention output as a nested list with the same shape as x; inputs are provided as JSON-compatible lists and must be converted to PyTorch tensors internally.
Use scaled dot-product attention, split the model dimension across num_heads, apply the upper-triangular future-token mask when causal is true, concatenate heads, and apply w_o. Assume model_dim is divisible by num_heads.
def mha(x, w_q, w_k, w_v, w_o, num_heads, causal):