From a8776b7bbd086bc37dbf9a5a6b72bec7c21a7b94 Mon Sep 17 00:00:00 2001 From: Shantanu Suryawanshi Date: Tue, 7 Apr 2026 12:16:12 -0400 Subject: [PATCH] Fix: Correct cross-attention query routing in Post-LN TransformerDecoderLayer (#3382) --- python/mlx/nn/layers/transformer.py | 2 +- python/tests/test_nn.py | 75 +++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/python/mlx/nn/layers/transformer.py b/python/mlx/nn/layers/transformer.py index d856f255..d7b65ed7 100644 --- a/python/mlx/nn/layers/transformer.py +++ b/python/mlx/nn/layers/transformer.py @@ -228,7 +228,7 @@ class TransformerDecoderLayer(Module): y = self.dropout1(y) x = self.ln1(x + y) - y = self.cross_attention(y, memory, memory, memory_mask) + y = self.cross_attention(x, memory, memory, memory_mask) y = self.dropout2(y) x = self.ln2(x + y) diff --git a/python/tests/test_nn.py b/python/tests/test_nn.py index 5957184b..dfeaa9f3 100644 --- a/python/tests/test_nn.py +++ b/python/tests/test_nn.py @@ -2006,6 +2006,81 @@ class TestLayers(mlx_tests.MLXTestCase): out = attn(x, x, x) self.assertEqual(out.shape, x.shape) + def test_transformer_encoder_layer(self): + # Test norm_first=True (default) + layer = nn.TransformerEncoderLayer(dims=32, num_heads=4) + x = mx.random.normal(shape=(2, 5, 32)) + out = layer(x, mask=None) + self.assertEqual(out.shape, x.shape) + + # Test norm_first=False + layer = nn.TransformerEncoderLayer(dims=32, num_heads=4, norm_first=False) + out = layer(x, mask=None) + self.assertEqual(out.shape, x.shape) + + # Test with causal mask + mask = nn.MultiHeadAttention.create_additive_causal_mask(5) + out = layer(x, mask=mask) + self.assertEqual(out.shape, x.shape) + + # Test with custom mlp_dims + layer = nn.TransformerEncoderLayer(dims=32, num_heads=4, mlp_dims=64) + out = layer(x, mask=None) + self.assertEqual(out.shape, x.shape) + + def test_transformer_decoder_layer(self): + dims = 32 + num_heads = 4 + x = mx.random.normal(shape=(2, 5, dims)) + memory = mx.random.normal(shape=(2, 8, dims)) + + # Test norm_first=True (default) + layer = nn.TransformerDecoderLayer(dims=dims, num_heads=num_heads) + out = layer(x, memory, x_mask=None, memory_mask=None) + self.assertEqual(out.shape, x.shape) + + # Test norm_first=False + layer = nn.TransformerDecoderLayer( + dims=dims, num_heads=num_heads, norm_first=False + ) + out = layer(x, memory, x_mask=None, memory_mask=None) + self.assertEqual(out.shape, x.shape) + + # Test with masks + x_mask = nn.MultiHeadAttention.create_additive_causal_mask(5) + out = layer(x, memory, x_mask=x_mask, memory_mask=None) + self.assertEqual(out.shape, x.shape) + + # Test with custom mlp_dims + layer = nn.TransformerDecoderLayer(dims=dims, num_heads=num_heads, mlp_dims=64) + out = layer(x, memory, x_mask=None, memory_mask=None) + self.assertEqual(out.shape, x.shape) + + def test_transformer_encoder(self): + encoder = nn.TransformerEncoder(num_layers=2, dims=32, num_heads=4) + x = mx.random.normal(shape=(2, 5, 32)) + out = encoder(x, mask=None) + self.assertEqual(out.shape, x.shape) + + def test_transformer_decoder(self): + decoder = nn.TransformerDecoder(num_layers=2, dims=32, num_heads=4) + x = mx.random.normal(shape=(2, 5, 32)) + memory = mx.random.normal(shape=(2, 8, 32)) + out = decoder(x, memory, x_mask=None, memory_mask=None) + self.assertEqual(out.shape, x.shape) + + def test_transformer(self): + model = nn.Transformer( + dims=32, + num_heads=4, + num_encoder_layers=2, + num_decoder_layers=2, + ) + src = mx.random.normal(shape=(2, 8, 32)) + tgt = mx.random.normal(shape=(2, 5, 32)) + out = model(src, tgt, src_mask=None, tgt_mask=None, memory_mask=None) + self.assertEqual(out.shape, tgt.shape) + if __name__ == "__main__": mlx_tests.MLXTestRunner()