Skip to content

Fix/speculative shape invariance - #4279

Open
aleroot wants to merge 3 commits into
ml-explore:mainfrom
aleroot:fix/speculative-shape-invariance
Open

Fix/speculative shape invariance#4279
aleroot wants to merge 3 commits into
ml-explore:mainfrom
aleroot:fix/speculative-shape-invariance

Conversation

@aleroot

@aleroot aleroot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Proposed changes

Related issue: ml-explore/mlx-swift-lm#542

Opt-in batch-invariant execution mode for selected Metal inference kernels. It is intended for workloads such as greedy speculative decoding, where evaluating a token independently and evaluating the same token inside a short verification block must produce identical results.

Problem

Autoregressive decoding normally evaluates one token at a time, while speculative decoding verifies several tokens in one target-model invocation.

Although these computations are mathematically equivalent, their shapes currently select different Metal execution paths:

  • A single matrix row uses GEMV, while a short matrix can use gemv_wide or GEMM.
  • Two-pass SDPA derives its split count from the query length, changing the softmax reduction tree between single-query decoding and multi-query verification.

Floating-point reductions are not associative. Consequently, the different kernels can produce slightly different BF16 results for the same logical token. Across many transformer layers and decoding rounds, these differences can eventually change the ordering of two close logits and cause greedy argmax decoding to diverge.

This is especially visible with quantized models and long contexts, but the underlying problem is shape-dependent floating-point reduction order rather than quantized matrix multiplication itself.

Changes

This PR introduces MLX_METAL_BATCH_INVARIANT_LIMIT, with matching runtime APIs:

mx.metal.set_batch_invariant_limit(limit)
mx.metal.get_batch_invariant_limit()

When the relevant leading/query dimension is within the configured limit:

  • Short matrix rows are executed as a parallel batch of canonical single-row GEMVs. Rows remain parallel, but each row uses the same reduction kernel and order as token-by-token inference.
  • Two-pass SDPA uses the single-query split configuration, preserving the same softmax reduction layout for a query evaluated alone or inside a short causal block.

The setting is process-wide and stored atomically. It defaults to 0, meaning disabled, so existing execution and performance are unchanged unless an application explicitly opts in. Long prefills and dimensions above the configured limit continue using the existing optimized paths.

The mode is intentionally opt-in because selecting canonical reduction kernels can reduce performance. Applications only need to cover their maximum speculative verification width.

Checklist

Put an x in the boxes that apply.

@zcbenz zcbenz added the await verification This pull request is non-trivial and requires a human expert to verify its correctness. label Aug 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

await verification This pull request is non-trivial and requires a human expert to verify its correctness.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants