Skip to content

[testing] standalone qwen3vl encoder + CPU repack (do not merge)#178

Draft
olyasir wants to merge 2 commits into
temp-9341from
feat/qwen3vl-standalone-encoder
Draft

[testing] standalone qwen3vl encoder + CPU repack (do not merge)#178
olyasir wants to merge 2 commits into
temp-9341from
feat/qwen3vl-standalone-encoder

Conversation

@olyasir

@olyasir olyasir commented Jul 1, 2026

Copy link
Copy Markdown

Summary

Speeds up the CPU vision encoder (mtmd/clip) and adds a standalone
encoder benchmark tool
used to find and validate the win.

On a Pixel 9 Pro, the Qwen3.5-0.8B vision encode (q8_0 mmproj, elephant.jpg,
4 threads) drops from ~1807 ms → ~1114 ms (1.6×), with bit-identical
output.

The production arm64-android build already runs with GGML_CPU_ALL_VARIANTS=ON,
so it ships an armv8.6_1 (i8mm/dotprod) CPU variant that is dlopen-dispatched
at runtime — i.e. the fast i8mm GEMM path is already active in production. This
change is the incremental win on top of that; no build-config change is
required
. (A CPU build with no i8mm/dotprod variant runs ~2689 ms and this is
a safe no-op there.)

1. Repack quantized CLIP weights into the CPU i8mm buffer

clip.cpp allocated all weights in the default CPU buffer, so the q8_0/q4_0
matmul matrices used the generic GEMM instead of the interleaved i8mm/AVX2
kernels. This change allocates the repackable weights in ggml's CPU "repack"
extra buffer type, so they are repacked into the interleaved layout at load.

  • Per-tensor selection via a ggml_backend_dev_supports_op probe (mirrors
    llama's buft_supported): only tensors the backend can actually repack+matmul
    go to the repack buffer. Conv weights / biases / norms stay in the default
    buffer (otherwise their non-matmul ops become unsupported and the scheduler
    aborts). On arches/variants with no repack kernel the probe rejects everything
    and this is a no-op.
  • Weight loading is made per-tensor host-aware, since weights now span the host
    default buffer and the non-host repack buffer.
  • Bit-identical output (verified via cosine-similarity against a non-repack
    golden). Opt out with MTMD_CLIP_NO_REPACK=1.

2. Standalone qwen3vl-encoder benchmark tool

tools/mtmd/qwen3vl-encoder: runs only the vision encoder (ViT + patch-merge
projection) through ggml with no libllama dependency (it compiles the clip
sources directly). Loads an mmproj gguf, preprocesses an image, and times
clip_image_batch_encode across cpu | vulkan | opencl, with a
cosine-similarity check (--dump / --ref) so kernel changes stay lossless.

This gives a seconds-scale edit → build → run loop for optimizing the shared
vision-encoder kernels without the heavy llama build. standalone_loop.md
documents the local + Android + Firebase workflow.

qwen3vl-encoder --mmproj <mmproj.gguf> --image <img> \
                --backend cpu|vulkan|opencl|all [--threads N] [--iters N] \
                [--ref golden.bin] [--cos-min 0.999]

Testing

  • Built for x86-64 (Vulkan) and arm64-android (CPU / Vulkan / OpenCL).
  • CPU + Vulkan cross-checked against a golden embedding (cos-sim); repack path
    verified bit-identical to the non-repack path.
  • On-device on Pixel 9 Pro (Mali, CPU path) and Galaxy S25 (Adreno) via Firebase
    Test Lab game-loop.

@olyasir olyasir requested a review from a team as a code owner July 1, 2026 17:52
@github-actions github-actions Bot added documentation Improvements or additions to documentation mtmd labels Jul 1, 2026
olyasir added 2 commits July 2, 2026 09:48
Vision-encoder weights (q8_0/q4_0 2D matmul matrices) were allocated in the
default CPU buffer, so their mul_mat used the generic path. Allocate the
repackable ones in ggml's CPU "repack" extra buffer type instead, so they get
repacked into the interleaved layout at load and hit the fast i8mm/AVX2 GEMM.

Selection is per-tensor via a ggml_backend_dev_supports_op probe (mirrors
llama's buft_supported): only tensors the backend can actually repack+matmul go
there, so conv weights / biases / norms stay in the default buffer, and on
arches with no repack kernel it is a no-op. Weight loading is made per-tensor
host-aware since weights now span the host default buffer and the non-host
repack buffer. Output is bit-identical. Opt out with MTMD_CLIP_NO_REPACK=1.

On Pixel 9 Pro (Qwen3.5-0.8B mmproj, q8_0, arm64 i8mm build) this cuts the CPU
vision-encode from ~1807 ms to ~1114 ms.
tools/mtmd/qwen3vl-encoder: a small executable that runs only the Qwen3-VL
vision encoder (ViT + patch-merge projection) through ggml, with no libllama
dependency (it compiles the clip sources directly). It loads an mmproj gguf,
preprocesses an image, and times clip_image_batch_encode across CPU / Vulkan /
OpenCL, with a cosine-similarity check (--dump / --ref) to keep changes
lossless. This gives a fast edit -> build -> run loop for optimizing the shared
Vulkan/OpenCL/CPU kernels of the vision encoder without the heavy llama build.

See tools/mtmd/qwen3vl-encoder/standalone_loop.md for the local + Android +
Firebase workflow.
@olyasir olyasir force-pushed the feat/qwen3vl-standalone-encoder branch from 3791bdf to a3bc3d8 Compare July 2, 2026 06:49
@olyasir olyasir changed the base branch from feat/ggml-backend-dl-9341 to temp-9341 July 2, 2026 06:49
@olyasir olyasir changed the title feat: faster CPU vision encoder (q8_0 weight repack) + standalone encoder bench tool [testing] standalone qwen3vl encoder + CPU repack (do not merge) Jul 2, 2026
@olyasir olyasir marked this pull request as draft July 2, 2026 07:49
@olyasir

olyasir commented Jul 2, 2026

Copy link
Copy Markdown
Author

Split: the production change (q8_0 CPU repack, clip.cpp only) now lives in #179 for review/merge. This branch keeps the repack plus the standalone qwen3vl-encoder bench tool for local/CI testing (it's what the monorepo overlay tetherto/qvac#3012 pins). Not for merge.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Status

Current Status: ❌ PENDING
Approvals so far: none

Pending reviews: Needs 1 Management or Team Lead, and 1 more from Management, Team Lead, or Member.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation mtmd

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant