Whisper.cpp Vulkan on Adreno 840 (Snapdragon 8 Elite Gen 5) in Termux — 4.3× faster than CPU, with a shaderc coopmat2 build fix
#3943
xidoc
started this conversation in
Show and tell
Replies: 1 comment 1 reply
The test just checks whether your glslc compiler supports it, not your device. That is a runtime check. You probably just need to update your shader compiler. |
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Whisper.cpp Vulkan on Adreno 840 (Snapdragon 8 Elite Gen 5) in Termux — 4.3× faster than CPU, with a shaderc
coopmat2build fixTL;DR
I got
whisper.cpprunning on the GPU (Adreno 840) of an Honor Magic 8 Pro (Snapdragon 8 Elite Gen 5) inside Termux, no root, using the Turnip/Mesa Vulkan driver.Result: the GPU build transcribes ~4.3× faster than the (already very fast) CPU build.
smallmodelrealtimewhisper-cli -m smallwhisper-cli -m small -t 6 -p 2To get there I had to work around a shaderc build failure on the cooperative-matrix-2 (
cm2) shaders. That failure — and my workaround — is the main reason I'm posting: I suspect the root cause is a false-positive feature-detection in the Vulkan CMake, and a maintainer may want to fix it properly.I'm not sure whether the "correct" home for this is whisper.cpp, ggml, or the shaderc packaging in Termux, so I'm posting here and happy to move it.
1. Hardware / software
libvulkan.so1.4.356freedreno) — the stock Qualcomm Vulkan driver does not work for these shaders (details below)080bbbe8GPU as reported by the working build:
Note
matrix cores: none— the Adreno has no usable cooperative-matrix hardware exposed through Vulkan. This is central: thecm2shaders should arguably never be generated for this device, but the build tries to compile them anyway and crashes.2. The use case (context for why I was building this)
End goal: a one-command pipeline on the phone that:
yt-dlp),whisper.cpp),deep-translator),ffmpeg).Everything 100% on-device. Whisper transcription was by far the slowest stage, which is why GPU offload mattered.
3. The build problem:
shaderc internal error: Invalid capability operand: 5447A straight
-DGGML_VULKAN=ONbuild fails while generating SPIR-V shaders. The failing shader is always a cooperative-matrix-2 one, e.g.flash_attn_cm2/matmul_*_cm2:5447is the SPIR-V capability operand forCooperativeMatrixKHR. Theglslc/shadercbuild shipped in Termux compiles the shader but then fails during the optimizer pass on that capability.This looks like the same class of problem as ggml-org/llama.cpp#15344 ("shaderc v2025.2 causes vulkan compilation failure"), where a newer shaderc + a false-positive
GL_EXT_bfloat16/ coopmat feature test leads to generating shaders that then can't be optimized. In that issue the nixpkgs folks worked around it by patching out the bfloat16 shader feature test.On my machine the CMake feature test
test_shader_extension_support(... "GL_NV_cooperative_matrix2" ...)returns success (a false positive) — soGGML_VULKAN_COOPMAT2_GLSLC_SUPPORTgets defined, the generator emitscm2shaders, and thenglslcdies optimizing them. The hardware reportsmatrix cores: none, so the test arguably should fail here.4. What did NOT work (for the record, so others don't waste time)
-DGGML_VULKAN_COOPMAT2_GLSLC_SUPPORT=OFFon the CMake command line → ignored; the in-treetest_shader_extension_support()re-enables it.set(GGML_VULKAN_COOPMAT2_GLSLC_SUPPORT OFF CACHE BOOL "" FORCE)after the feature tests → too late; the test has already emitted the compile definition, andvulkan-shaders-gen(a separately-built helper) still generatescm2shaders.cm2but the C++ still referencesmatmul_*_cm2_len/_data, you geterror: 'matmul_bf16_cm2_len' was not declared in this scope.Compute pipeline creation failed for adreno_mul_mat_vec_q4_k_q8_1_f32), which is why Turnip is required.SIGKILL(signal 9) a long compile. Keep Termux in the foreground, acquire the wake-lock, and build with-j2to keep RAM pressure down.5. What DID work — the workaround
The reliable fix is to make sure
COOPMAT2is disabled consistently on both sides (shader generator and the C++ consumer), so nothing references shaders that were never generated. I did this by neutralizing the twocoopmat2feature tests in the VulkanCMakeLists.txt, plus hard-disabling the#ifblocks in the generator as a belt-and-braces measure.5a.
ggml/src/ggml-vulkan/CMakeLists.txtReplace the two
coopmat2feature-test calls:with hard-off settings (so the compile definition is never emitted):
5b.
ggml/src/ggml-vulkan/vulkan-shaders/vulkan-shaders-gen.cppForce every
cm2generation block off, so the generator can't emit them even if the macro leaks in from somewhere:sed -i 's/#if defined(GGML_VULKAN_COOPMAT2_GLSLC_SUPPORT)/#if defined(GGML_VULKAN_COOPMAT2_GLSLC_SUPPORT) \&\& 0/g' \ ggml/src/ggml-vulkan/vulkan-shaders/vulkan-shaders-gen.cpp5c. Clean rebuild (important)
vulkan-shaders-genis compiled early and cached; a partial rebuild will reuse the old generator. Always wipebuild/:With
cm2gone, the build completes.cm1(cooperative-matrix v1) and the scalar/fp16 paths are still generated and used.6. Full reproducible setup (from scratch)
6a. Toolchain + Turnip driver
pkg update && pkg upgrade -y pkg install -y git cmake clang make ninja pkg install -y x11-repo tur-repo pkg install -y vulkan-loader-generic vulkan-headers shaderc glslang \ spirv-headers spirv-tools vulkan-tools \ mesa-vulkan-icd-freedreno-dri36b. Clone + patch + build (see section 5)
6c. Run on GPU
The stock Qualcomm ICD must be overridden with Turnip at runtime:
Success line to look for:
7. Benchmark detail
Same 120 s WAV (16 kHz mono),
ggml-small.bin, warm cache:~4.3× faster on GPU. For context, the CPU here is not weak — it's one of the fastest mobile CPUs available, and I'd already tuned it (
-t 6 -p 2beat-t 8, likely memory-bus contention). The GPU still wins comfortably.Interesting because a lot of Adreno +
llama.cppreports show the opposite (GPU slower than CPU on mobile, due to shared-memory bandwidth). That seems to be workload-dependent: Whisper's encoder is compute-heavy matmul that maps well to the Adreno, whereas autoregressive LLM decoding is memory-bound and suffers. So "Adreno GPU is pointless for on-device inference" is not a safe generalization — for Whisper on this SoC it's a big win.8. Questions for maintainers
test_shader_extension_support()forcoopmat2be treated as a hard capability check against the device (which reportsmatrix cores: none) rather than a glslc-only check? The glslc test passing while the device lacks the capability is what leads to generating unusable shaders.5447known/tracked upstream (mirrors llama.cpp#15344 for bf16/coopmat)? A guard that skips optimization or the shader when the target lacks the capability would remove the need for downstream patches.-DGGML_VULKAN_DISABLE_COOPMAT2=ONthat reliably propagates to bothvulkan-shaders-genandggml-vulkan.cppbe acceptable? Right now disabling it consistently requires editing two files.Happy to test patches on real Adreno 840 hardware — glad to help validate a proper fix.
Everything above was done on a stock, non-rooted Honor Magic 8 Pro. Thanks for whisper.cpp — the Vulkan backend genuinely turns a flagship phone into a fast local transcription box.
Disclosure: I'm not a Vulkan/GPU expert. I worked through this debugging session with the help of an AI assistant (Claude), but every command was run on my own physical Honor Magic 8 Pro, and all logs, error messages, and benchmark numbers above are real output captured from that device — nothing here is synthetic or untested. I'm posting the full trace so the result is verifiable and reproducible, and I'm happy to re-run anything or test proposed patches on real Adreno 840 hardware.
Appendix — full pipeline script (
ytdl)All reactions