Fix einsum not broadcasting batch dimensions in batched tensordot - #4125
Open
Adityaj0 wants to merge 1 commit into
Open
Fix einsum not broadcasting batch dimensions in batched tensordot#4125Adityaj0 wants to merge 1 commit into
Adityaj0 wants to merge 1 commit into
Conversation
batch_tensordot broadcast the contracting dimensions of its two operands
to a common size but left the batch dimensions alone, then built the
output shape from the first operand only:
for (auto ax : a_batch) {
out_shape.push_back(a.shape(ax));
}
When the first operand had a size 1 batch dimension, matmul broadcast it
against the second operand while out_shape kept the 1, and the final
reshape failed:
mx.einsum("...ij,...jk->...ik", mx.zeros((1, 3, 4)), mx.zeros((2, 4, 5)))
ValueError: [reshape] Cannot reshape array of size 30 into shape (1,3,5).
The reverse order worked because the output shape was then taken from the
larger operand, which hid the bug. It reproduces with explicit labels too,
for example "bij,bjk->bik", so it is not specific to ellipses.
Broadcast the batch dimensions the same way the contracting dimensions
are already broadcast.
Also removes an unconditional return in test_broadcasting that made the
rest of that test dead code. Those assertions pass on main, so this is an
unrelated cleanup, but it was hiding coverage next to the bug.
Contributor
|
Will this cause some performance issue ? @Adityaj0 |
Contributor
Author
|
Hi @JasonHonKL no, because the fix only adds lazy batch dimension broadcasting in the existing batched tensordot path, so it should have negligible overhead beyond a small amount of shape bookkeeping. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4124.
Proposed changes
batch_tensordotbroadcast the contracting dimensions of its two operands to a common size but left the batch dimensions alone, then built the output shape from the first operand only:When the first operand had a size 1 batch dimension,
matmulbroadcast it against the second operand whileout_shapekept the 1, so the final reshape got the wrong element count:The reverse operand order worked, because the output shape was then taken from the larger operand. That asymmetry is why it went unnoticed. It is not specific to ellipses either,
"bij,bjk->bik"with the same shapes fails identically.The fix broadcasts the batch dimensions the same way the contracting dimensions already are, in the same block.
a_batchandb_batchare parallel by construction (b_batchis built by looking up eacha_batchlabel inb's subscript), so they can be zipped exactly likea_contract/b_contract.(1,3,4),(2,4,5)(2,3,5)(2,3,5)(2,3,4),(1,4,5)(2,3,5)(2,3,5)(2,3,5)(1,4),(5,4)->...(5,)(5,)(5,4),(1,4)->...(5,)(5,)(5,)Tests
Added
test_ellipses_broadcasttopython/tests/test_einsum.pycovering both operand orders, multiple leading batch dimensions, a reduced (->...) output, and the explicit-label spelling, each checked against NumPy for both shape and values.I also removed an unconditional
returnintest_broadcastingthat made the four lines after it dead code. To be clear about scope: those assertions pass onmainas well, so that is an unrelated cleanup rather than something this fix enables. I checked before touching it. It was simply hiding coverage right next to the bug.pre-commit run --all-filesto format my code and installed pre-commit prior to committing changesVerified with a CPU-only build (
-DMLX_BUILD_METAL=OFF):test_einsum,test_ops,test_autograd,test_vmap,test_linalg,test_blas,test_nn,test_arrayandtest_reducepass (464 tests).