Skip to content

Fix Metal strided-input handling: recip/sign, index_select, gather#3706

Open
v-code01 wants to merge 3 commits into
huggingface:mainfrom
v-code01:metal-strided-recip-sign
Open

Fix Metal strided-input handling: recip/sign, index_select, gather#3706
v-code01 wants to merge 3 commits into
huggingface:mainfrom
v-code01:metal-strided-recip-sign

Conversation

@v-code01

@v-code01 v-code01 commented Jul 5, 2026

Copy link
Copy Markdown

What

Found via a Metal-vs-CPU differential sweep over ops on non-contiguous (strided) inputs. Three related issues in the Metal backend's strided-input handling, each with a regression test vs the CPU reference:

Op On a strided input Was Now
recip / sign (unary) errored "strided unary not implemented" (the strided kernels exist but weren't dispatched) wired up for F32/F16/BF16
index_select strided source silently wrong (~8 max-abs vs CPU): the kernel got the selected-dim size instead of the rank for get_strided_index correct (passes num_dims)
gather strided source silently corrupt rows (the kernel is contiguous-only) rejected with RequiresContiguous, matching the CPU-side contract

The two silent-correctness ones (index_select, gather) are the important fixes — they returned wrong results with no error.

Why one PR

All three are the same class (Metal op mishandles a strided/non-contiguous input) found by the same differential-testing pass; grouping keeps the review as one coherent correctness sweep.

Tests

metal_strided_unary_tests, metal_index_select_strided, metal_gather_contiguous — each compares Metal vs CPU on strided inputs. Verified on Apple M4 (Metal). cargo fmt --all clean; no new clippy warnings.

cc @LaurentMazare @ivarflakstad @EricLBuehler

@v-code01

v-code01 commented Jul 5, 2026

Copy link
Copy Markdown
Author

cc @ivarflakstad @EricLBuehler — small dispatch gap: the strided recip/sign Metal kernels already exist (in the unary ops! list, with contiguous dispatch entries) but weren't wired into the strided path, so .recip()/.sign() on a transposed tensor errored while every other strided unary op works. Found it via a Metal-vs-CPU differential sweep; regression test compares strided recip/sign to CPU across F32/F16/BF16.

v-code01 added 3 commits July 10, 2026 13:37
candle-metal-kernels generates strided `recip` and `sign` kernels (both are in
the unary `ops!` list and both already have contiguous dispatch entries), but the
strided unary dispatch never mapped them. As a result `.recip()` and `.sign()` on
a non-contiguous tensor (e.g. a transpose) errored with "Metal strided unary
u{recip,sign} ... not implemented" while every other unary op and the CPU backend
handled it fine.

Map `urecip`/`usign` to the existing strided kernels for F32/F16/BF16, matching
their contiguous coverage. Adds a regression test comparing Metal strided recip
and sign against the CPU reference across the three dtypes.
The Metal index_select strided path passed `src_dim_size` (the size of the
selected dimension) to `get_strided_index` where the source rank (`num_dims`)
is expected. The strided-index loop therefore ran `src_dim_size` iterations
instead of `rank`, reading `src_dims`/`src_strides` out of bounds and computing
the wrong source offset -- so index_select on a non-contiguous source (e.g. a
transpose) silently returned wrong values on Metal, while matching the CPU
backend only when the rank happened to equal the selected-dim size (as in the
existing 2x2 `index_select_strided` test).

Thread the source rank through the kernel and pass it to `get_strided_index`.
Adds a regression test comparing strided-source index_select against the CPU
reference for shapes where rank != selected-dim size, across dim 0 and dim 1.
The Metal gather kernel indexes the source assuming a contiguous layout, but the
dispatch only checked the ids for contiguity, not the source. A strided source
(e.g. a transpose) therefore produced silently-wrong results, while the CPU
backend errors with RequiresContiguous.

Require a contiguous source, matching the CPU backend and the existing ids
check. Adds a regression test that a strided-source gather now errors and that a
contiguous gather still matches the CPU result.
@v-code01 v-code01 force-pushed the metal-strided-recip-sign branch from 7bb5015 to 02b6896 Compare July 10, 2026 18:39
@v-code01 v-code01 changed the title Metal: wire up strided recip and sign unary kernels Fix Metal strided-input handling: recip/sign, index_select, gather Jul 10, 2026
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.

1 participant