Skip to content

Select diag's diagonal out of a broadcast for 8-byte types - #4309

Closed
erwinzhang7 wants to merge 1 commit into
ml-explore:mainfrom
erwinzhang7:diag-8byte-dtypes
Closed

Select diag's diagonal out of a broadcast for 8-byte types#4309
erwinzhang7 wants to merge 1 commit into
ml-explore:mainfrom
erwinzhang7:diag-8byte-dtypes

Conversation

@erwinzhang7

Copy link
Copy Markdown
Contributor

Fixes #4308.

diag places a 1-D input on the diagonal with a scatter, and Scatter::eval_gpu refuses
any 8-byte output that is not a complex64 sum, so int64, uint64 and complex64 fail
on the GPU and work on the CPU.

eye reached the same guard and #4301 fixes it by building in a narrower type and casting.
That works there because the only values eye writes are 0 and 1, exact in every type it
can return. It does not work here: these are the caller's values, and float32 has 24 bits
of mantissa.

in                  9007199254740993   4611686018427387903
via float32         9007199254740992   4611686018427387904

So the 8-byte types select the diagonal out of a broadcast rather than scattering into it,
which never leaves the dtype. Only those three take that path: the mask is an n x n
allocation the scatter does not need, and there is no reason to spend it on the types that
already work.

Verified

M5 Max, cpu and gpu streams. 180 cases over int64, uint64, complex64, int32, float32 and
uint8, offsets -3 to 2, and lengths 1, 3 and 6, values checked against numpy: all pass.
2^53 + 1, 2^62 - 1 and 2^64 - 1 round trip exactly, which is the case a cast would
have changed. The empty input and the 2-D extraction path are unchanged.

test_ops, test_autograd and test_array pass. Fork CI green on all 22 jobs, including
metal and jit on macOS 14, 15 and 26.2, and the cuda 12.6, 12.9 and 13.0 legs.

diag places a 1-D input on the diagonal with a scatter, and the Metal
scatter has no 8-byte output path, so int64, uint64 and complex64 fail on
the GPU and work on the CPU.

eye has the same problem and can build in a narrower type and cast,
because the only values it writes are 0 and 1. That is not available
here: these are the caller's values, and an int64 past float32's exact
range comes back changed. 2^53 + 1 becomes 2^53, and 2^62 - 1 becomes
2^62.

So the diagonal is selected out of a broadcast instead, which never
leaves the dtype. Only the types that currently throw take that path,
since it allocates an n x n mask alongside the n x n output and the
scatter does not.
@zcbenz

zcbenz commented Aug 17, 2026

Copy link
Copy Markdown
Member

Since this is not an important bug I prefer fixing the root issue rather than adding a workaround.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] mx.diag on a 1-D input fails on the GPU for int64, uint64 and complex64

2 participants