Skip to content

feat: repack quantized CLIP weights into the CPU i8mm buffer#179

Merged
gianni-cor merged 1 commit into
temp-9341from
feat/qwen3vl-cpu-repack
Jul 6, 2026
Merged

feat: repack quantized CLIP weights into the CPU i8mm buffer#179
gianni-cor merged 1 commit into
temp-9341from
feat/qwen3vl-cpu-repack

Conversation

@olyasir

@olyasir olyasir commented Jul 2, 2026

Copy link
Copy Markdown

Summary

Speeds up the CPU vision encoder (mtmd/clip) by repacking quantized weights
into ggml's CPU i8mm buffer. On a Pixel 9 Pro (Qwen3.5-0.8B mmproj, q8_0,
4 threads) the vision encode drops from ~1807 ms → ~1114 ms (1.6×), with
bit-identical output.

Production change only — one file (tools/mtmd/clip.cpp, +72/-2). The standalone
qwen3vl-encoder benchmark tool used to find/validate this lives separately on
branch feat/qwen3vl-standalone-encoder (PR #178) for testing.

What it does

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 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.

Production android already ships an i8mm variant (GGML_CPU_ALL_VARIANTS
armv8.6_1), so this repack is the incremental win on top with no build-config
change needed.

Testing

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.
@olyasir

olyasir commented Jul 3, 2026

Copy link
Copy Markdown
Author

CPU vision-encode benchmark — repack ON vs OFF (same binary)

Measured this PR's repack in the qvac VLM benchmark (several-sources, cognitive preset, Qwen3.5-0.8B · Q8 mmproj), toggling the repack at runtime via MTMD_CLIP_NO_REPACK on a single build — so it's a clean same-binary A/B with no build/thermal confound.

mmproj vision-encode time (lower = faster):

Platform (CPU) repack OFF repack ON speedup
Pixel 9 (Android ARM) 6117 ms 4380 ms 1.40× (−1737 ms)
S25 (Android ARM) 2532 ms 1812 ms 1.40× (−720 ms)
mac-mini M4 (ARM) 2400 ms 1431 ms 1.68×
  • Lossless — VQA quality identical (46.7% on every leg, repack on == off).
  • TTFT drops correspondingly (Pixel 10328 → 8089 ms) since it includes the encode.
  • x86 legs are a no-op by design (q8_0 repack is ARM NEON + i8mm only).

Benchmark results: https://github.com/tetherto/qvac/actions/runs/28658081618/job/84996939580 (run 28658081618, matrix-combine → Summary)

Mobile encode times are captured via the visionEncodeMs runtime stat added in qvac#3028; the A/B used a monorepo overlay pinning this branch (a3bc3d8d).

@gianni-cor
gianni-cor merged commit 6796d62 into temp-9341 Jul 6, 2026
35 of 53 checks passed
@gianni-cor
gianni-cor deleted the feat/qwen3vl-cpu-repack branch July 15, 2026 18:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants