metal: implicit-GEMM convolution via a ported MLX kernel (9-66x) - #2549
Open
czoli1976 wants to merge 1 commit into
Open
metal: implicit-GEMM convolution via a ported MLX kernel (9-66x)#2549czoli1976 wants to merge 1 commit into
czoli1976 wants to merge 1 commit into
Conversation
The Metal convolution was a direct kernel computing one output position per thread, which leaves most of the GPU idle: on this M1 Pro it runs a 56x56x64 -> 128 3x3 layer at about 20 GFLOP/s. Port mlx's tiled implicit-GEMM conv as owned .metal source and route NHWC f16/f32 single-group 2D convolutions to it, leaving every other shape on the direct kernel. The shared rewrite puts kernels in OIHW, which the direct kernel indexes, so a metal-local rule reorders eligible ones into the OHWI layout the ported kernel wants - from whichever layout the exporter used, and into a constant, since the metal transform does not declutter afterwards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
czoli1976
force-pushed
the
feat/metal-steel-conv
branch
from
August 1, 2026 21:00
f3fc89a to
f023389
Compare
Contributor
Author
|
Updated with end-to-end numbers and a fix. The first version of this reordered the conv kernel assuming OIHW, which is right for ONNX and wrong for TF — the resulting kernel had channel counts where its spatial dims should be. No test failed; the model just ran 470× slower. Inception v3 now goes 477.7 → 44.8 ms on an M1 Pro and 158.4 → 35.9 ms on an M4, and there are end-to-end tests for all three kernel layouts. |
Contributor
Author
|
Depthwise is stacked on this as #2550 — it covers the case this PR declines (MobileNet v2 is 17/18 depthwise), 2–14× at the kernel and 1.34× end to end on MobileNet. |
This was referenced Aug 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The Metal convolution is a direct kernel computing one output position per
thread. On an M1 Pro that runs a 56×56×64 → 128 3×3 layer at roughly
20 GFLOP/s; the same layer through mlx's tiled implicit-GEMM conv runs at about
1.2 TFLOP/s. You called convolution the remaining Metal gap on #2320 — this is
the mlx kernel for it, vendored the same way as
MlxGemm/MlxGemv.NHWC, f16/f32, single group, 2D convolutions route to the ported kernel; every
other shape stays on the direct kernel, so nothing regresses by construction.
The shared rewrite puts every conv kernel in
OIHW, which the direct kernelindexes, while the ported kernel wants
OHWI. A metal-local rule moves eligibleconvs to
OHWIand leaves the rest to the shared rule — the kernel is normallya constant, so the reorder folds away.
Numbers
bench_conv(added here), ms per dispatch, best of 5×10, SameUpper padding.Both kernels are checked against the CPU op first, each with the weight layout
it expects, so this compares two correct implementations.
M1 Pro, 14-core GPU:
M4, 8-core GPU (macOS 26.6) — its direct kernel is much less bad, so the ratios
are smaller, but the ported kernel is still 9–15×:
f16 tracks f32 within a few percent on both machines. The first-layer case
(3 input channels) gains least, as expected — there is little to tile.
End to end
Inception v3 (TF, NHWC, 299×299) on
--metal, M1 Pro. All 54 convolutions areregular and route to the ported kernel; MobileNet v2, for contrast, is 17/18
depthwise and gets almost nothing from this — worth knowing which models benefit.
Stable across three interleaved runs on each — M1 Pro 477.7/477.2/477.1 against
44.8/44.9/45.7; M4 165.2/158.4/159.3 against 60.8/35.9/36.6, where the first
PR run pays pipeline specialisation. Same predicted class as the CPU path,
output finite.
Validation
cargo test -p tract-metal --release: 86 passed on the M1 Pro and on the M4,with one failure on both —
test_mfa_attention_causal_const_is_noop, which ispre-existing on main and unrelated (#2546). New tests cover 1×1, 3×3 valid and
same, strided, dilated, f16 and unaligned channels against the CPU op, plus
end-to-end cases that run a conv through
MetalTransformfrom each of the threekernel layouts —
OHWI,OIHWandHWIO— and check the result against CPU.That last one matters: the first version of this assumed
OIHW, which is rightfor ONNX and wrong for TF, and the reorder produced a kernel whose spatial dims
were channel counts. It did not crash or fail a unit test — it just ran the
model 470× slower, which only the end-to-end run surfaced. fmt and clippy clean.
Not covered, left on the direct kernel: NCHW, grouped and depthwise convs, 3D,
and the specialised non-general implicit-GEMM and Winograd variants mlx also
has. Those are follow-ups if this shape of thing is welcome.
mlx (ml-explore/mlx) is MIT, Copyright (c) 2023-2025 Apple Inc.; attributed in
the source header, flattened from a pinned commit.
🍍