Skip to content

Fix off_x/off_y typo in steel BaseMMAFrag::load_safe - #3565

Merged
angeloskath merged 1 commit into
ml-explore:mainfrom
mdegans:fix-load-safe-off-y
May 19, 2026
Merged

angeloskath merged 1 commit into
ml-explore:mainfrom
mdegans:fix-load-safe-off-y

Conversation

@mdegans

@mdegans mdegans commented May 19, 2026

Copy link
Copy Markdown
Contributor

Summary

BaseMMAFrag::load_safe in steel/gemm/mma.h uses off_x where off_y belongs in its source-address computation.

On current main:

dst[i * kElemCols + j] =
    static_cast<T>(src[(off_x + i) * str_x + (off_x + j) * str_y]);

The second term should be (off_y + j). This is a typo, not intentional:

  • The bounds check on the line directly above uses (off_y + j) < lim_y.
  • store_safe and store_slice both compute the address as (off_x + i) * str_x + (off_y + j) * str_y — correct.
  • The non-safe load uses src[i * str_x + j * str_y] — correct.

load_safe is the only one of the four that uses off_x twice — and it contradicts its own bounds check, which checks column j against off_y.

Effect

MMATile::load_safe calls MMAFrag::load_safe with off_x = (i * kFragRows) * w_x and off_y = (j * kFragCols) * w_y, which differ for any fragment off the i == j diagonal. The buggy term then folds the row-fragment offset into the column address, so fragments load from the wrong column band — a silent data corruption (no out-of-bounds access).

Concretely, a MMATile<float, 1, 32> loaded via load_safe returns the first column-fragment's two columns replicated across all 32 column-fragments.

I haven't audited MLX's own kernels for exposure — Atile.load_safe call sites (e.g. in quantized.h) route through the device MMATile::load_safe, so flagging for maintainers to check.

Fix

(off_x + j) → (off_y + j).

Credit

The bug was discovered by Claude Opus 4.7 (Anthropic) while building a FlashAttention QK^T GEMM on steel/gemm/mma.h in a downstream project, and traced to this line by static comparison against the three sibling loaders. This PR is opened from a human collaborator's GitHub account as Claude does not have its own.

load_safe computes the source address with off_x in both terms:
src[(off_x + i) * str_x + (off_x + j) * str_y]. The column term
should use off_y. The bounds check immediately above uses
(off_y + j) < lim_y, and the sibling store_safe and store_slice
both compute (off_y + j) in their address arithmetic, so this is
an isolated typo.

Co-Authored-By: Claude Opus 4.7 <[email protected]>

@angeloskath angeloskath left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for finding that!

@angeloskath
angeloskath merged commit 423b514 into ml-explore:main May 19, 2026
1 check passed
@BrewTestBot BrewTestBot mentioned this pull request Jul 7, 2026
1 task done
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.

2 participants