metal: speed up Qwen3-VL image encoding on large images by ~11% - #21443
Avidanborisov wants to merge 3 commits into
Conversation
|
Have you tried increasing the |
|
The reported metric ( Re-ran with
|
|
Hi! Just a gentle bump on this PR, would appreciate any feedback when you have time. Thanks! |
fd0c693 to
6489117
Compare
|
Hey @ggerganov, any updates on this PR? Let me know if I need to change anything. Am happy to adjust whatever needed. |
|
Hey llama team (CC @ggerganov), |
|
Thanks for the PR and the thorough benchmarks. On my M4 Max I measure ~2.5% regression for the same workload, opposite to your M2 result — the optimal Q here is hardware-family dependent. Look at #23114 (comment) There's an ongoing effort (with @ggerganov) to add generic per-device FA parameter tuning to the Metal backend, so I'd prefer to bring the optimization in through there rather than merge the hardcoded path now. Your M2 numbers and profiling would be valuable input, and it'd be great to have you involved once the infrastructure lands. |
Overview
This PR reduces the image encoding time of Qwen3-VL for large images on the Metal backend by ~11%. This issue was first reported here: #20704
Additional information
I've profiled the image encoding path of
unsloth/Qwen3.5-9Bon large images and noticed that the majority of the runtime is spent in the FlashAttention kernels.This patch optimizes the FlashAttention runtime in two complementary ways, each of which increases performance independently:
To keep the impact of this patch constrained and avoid regressions in untested scenarios, the optimized path is gated to
f16KV types and head sizes of 72 only (matching the Qwen3-VL family). This also means that the decoding phase is unaffected in the Qwen3.5-9B pipeline as well.The first commit adds proper support for query sizes above 8 in the Metal FlashAttention implementation. The second commit applies the optimizations mentioned above to the Qwen3-VL image encoder path. Each commit was tested independently for correctness with the CI suite, including
test-backend-ops -b MTL0 -o FLASH_ATTN_EXTin particular, and by asserting bit-identical embedding outputs.Benchmark results
The benchmark runs the following command on small and large image inputs and looks for the
image slice encoded in {X} msoutput:env MTMD_DEBUG_EMBEDDINGS=1 /bin/llama-mtmd-cli \ -m models/unsloth/Qwen3.5-9B-GGUF/Qwen3.5-9B-UD-Q4_K_XL.gguf \ --mmproj models/unsloth/Qwen3.5-9B-GGUF/mmproj-F16.gguf \ --image test-inputs/{test-1,test-1-large-4096}.jpeg \ -p "Describe this image." \ --seed 42 \ --temp 0 \ -n 1 \ --ctx-size 16384We run it with
env MTMD_DEBUG_EMBEDDINGS=1to produce logs like this, to verify that the image embeddings are bit-identical.The reported runtimes are the medians of 3 runs per variant. I've attached the full logs of the runs for more information.
1189 ms40129 ms1189 ms(0.0%)40191 ms(+0.2%)1188 ms(-0.1%)35702 ms(-11.0%)Logs
Technical details
unsloth/Qwen3.5-9B-GGUF/Qwen3.5-9B-UD-Q4_K_XL.ggufunsloth/Qwen3.5-9B-GGUF/mmproj-F16.gguftest-inputs/test-1.jpeg(640 x 488)test-inputs/test-1-large-4096.jpeg(the same image from the repo, upscaled to4096 x 3123)CI results
I ran all the relevant CI tests locally, along with
llama-benchandllama-perplexity, and rantest-backend-opsforFLASH_ATTN_EXT.Requirements
I have read and agree with the contributing guidelines: YES
AI usage disclosure: YES:
This is my first PR to llama.cpp, so I hope I got everything right :)