You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Metal] implicit-GEMM conv2d reads past the weight buffer when the output channels are not a multiple of the tile width #4630
In the Metal implicit-GEMM conv2d path, the weight loader reads a full BN-row tile without a bounds check unless BN == 8, and BN can be larger than the number of output channels. The extra rows are never stored, so results are correct, but the kernel reads device memory past the end of the weight buffer.
Where (main @ 264c14f, same in v0.25.1 and v0.32.3)
mlx/backend/metal/conv.cpp, implicit_gemm_conv_2D_gpu (l. 343-344): the tile width depends on M and C, not on O.
$ MTL_SHADER_VALIDATION=1 MTL_DEBUG_LAYER=1 MTL_SHADER_VALIDATION_REPORT_TO_STDERR=1 python repro.py 9601 32
Invalid Metal usage: Invalid load in 'device' or 'constant' address space at offset 66304, executing compute function: "implicit_gemm_conv_2d_float16_bm64_bn64_bk16_wm2_wn2_channel_l_filter_s"
MTLBuffer: <no label>, length:65536, ...
Results with mlx 0.25.2 and 0.32.3 (pip), identical on both:
M
O
shader validation
9601
32
invalid load past a 65,536-byte buffer
9601
48
invalid load past a 98,304-byte buffer
9601
96
invalid load past a 180,224-byte buffer
8192
32
invalid load
8191
32
clean (bn = 32)
9601
16, 64, 128
clean
Impact
Silent on the Mac we tested, since the output is unaffected. We found it while investigating [METAL] Command buffer execution failed: Caused GPU Address Fault Error (0000000b:kIOGPUCommandBufferCallbackErrorPageFault) on A14 iOS devices running a conv of this shape. We have not confirmed on a device that this read is the cause of those faults.
Possible fix (not tested)
Use the checked branch whenever the tile is not fully in range, in both loaders:
- if (BN != 8 || do_read) {+ if (do_read) {
Workaround
Pad the weight with zero filters so that O is a multiple of 64 (or at most 16) and slice the output. With O padded from 32 to 64 the validation report disappears and the original output channels are bit-identical.
Environment
macOS 27.0, Apple M5. mlx 0.25.2 and 0.32.3 from pip. Also reproduced through mlx-swift 0.25.2.
Activity
navthings commentedon Oct 8, 2026
working on a pr now 👍