Write a PyTorch implementation of multi-head attention including a causal mask for autoregressive decoding.
For grading, represent tensors as nested lists with shape (batch, sequence_length, embedding_dimension). Return a nested list with shape (batch, query_length, embedding_dimension). Support rectangular key and query lengths, where cached keys precede the current query tokens.
def multi_head_attention(query, key, value, num_heads, causal):