Small input linear and cubic upsampling should use dense matmuls - #4418
Open
slink wants to merge 3 commits into
Open
Small input linear and cubic upsampling should use dense matmuls#4418slink wants to merge 3 commits into
slink wants to merge 3 commits into
Conversation
Gather-based interpolation backward is a scatter-add, which is slow on Metal. For upscaling with small spatial inputs, build per-axis interpolation matrices and contract them instead. I found this to be 2-4x faster forward and backward for linear with N <= 128 and cubic gated with N <= 32.
adding matmul tests for path-parity, gradient-parity, and routing-boundary.
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.
Gather-based interpolation backward is a scatter-add, which is slow on Metal. For upscaling with small spatial inputs, we can instead build per-axis interpolation matrices and contract them.
I found this to be 2-4x faster forward and backward for linear with N <= 128 and cubic gated with N <= 32.
Benchmark data
Setup:
MLX_ENABLE_TF32=0Times in ms, mean of 10–30 calls after warmup; "fwd+bwd" is
mx.gradof a mean-square loss through the op.crossover sweeps
Crossover sweep — linear, ×2 upscale, input
(4, N, N, 32)Cases the gate excludes (gather wins or matmul loses)
Other measured points
(64, 8, 16, 64)×10, fwd(64, 8, 16, 64)×10, bwd(4, 32, 32, 32)×2, fwdTesting
python/tests/test_upsample.pypasses: 9 tests, 172 subtests (was 6 / 152 before the PR), including torch parity across fractional scales and bothalign_cornerssettings.MLX_ENABLE_TF32=0; see caveats).test_matmul_gather_path_parity— matmul vs gather output equality across cases straddling both thresholds, including a downscale case, a 1D case, and bothalign_cornerssettings.test_matmul_gather_grad_parity— gradient equality at the motivating shape (8×16, ×10), since the backward pass is the point of the change.test_matmul_path_routing— exact threshold boundaries for both modes (tied to the constants, so retuning keeps the test valid), mixed up/down scale rejected, scale=1.0 accepted.main(_interpolate_matmuland the threshold constants do not exist there), demonstrating they exercise only new code.pre-commit(black, isort) clean on both changed files.Caveats
MLX_ENABLE_TF32=1on M5 Metal / CUDA it loses ~1e-3 accuracy relative to the gather path (which never touches GEMM). WithMLX_ENABLE_TF32=0, parity is 2.4e-7. Relates to [BUG] fp32 matmul silently defaults to TF32-class precision (MLX_ENABLE_TF32=1), undocumented on both backends #3860.