Skip to content

Quantized token embedding via GatherBlockQuantized (272MB→76MB) - #400

Merged
justinchuby merged 1 commit into
mainfrom
feat/gguf-quantized-embedding
Jul 13, 2026
Merged

Quantized token embedding via GatherBlockQuantized (272MB→76MB)#400
justinchuby merged 1 commit into
mainfrom
feat/gguf-quantized-embedding

Conversation

@justinchuby

Copy link
Copy Markdown
Member

The token-embedding table was emitted as fp16 [151936,896] ≈ 272 MB (a plain Gather) even in Q4 models — larger than WebGPU's 256 MiB buffer limit and a big bandwidth cost. This keeps it quantized:

  • Emits com.microsoft.GatherBlockQuantized with Q4 packed data [151936,448] + fp16 scales.
  • Size: 272.27 MB → 68.07 MB data + 8.51 MB scales.
  • Tied LM head continues to share the table via MatMulNBits.
  • Verified: CPU EP coherent ('Paris'); WebGPU EP places GatherBlockQuantized on WebGpuExecutionProvider (on-device) with coherent output.

Note: a runtime-side WebGPU stability issue (stale KV validation) is tracked separately in onnx-genai. lintrunner clean; gguf pytest 153 passed.

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown

🏗️ Architecture Diff

Comparing 227be2371e65bc

Model Sub-model Changes Status

No architecture changes detected.


Legend: ⚪ No change · 🔵 Minor (attrs/inits) · 🟡 Moderate (nodes added/removed) · 🔴 Major (interface changed)

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown

Performance Comparison

Comparing 227be2371e65bc

Model Metric Baseline Current Delta
bert (feature-extraction) model_size_bytes 359 KB 359 KB +0.0%
bert (feature-extraction) num_nodes 60 60 +0.0%
falcon model_size_bytes 364 KB 364 KB +0.0%
falcon num_nodes 68 68 +0.0%
gemma2 model_size_bytes 428 KB 428 KB +0.0%
gemma2 num_nodes 107 107 +0.0%
gpt2 model_size_bytes 388 KB 388 KB +0.0%
gpt2 num_nodes 54 54 +0.0%
llama model_size_bytes 425 KB 425 KB +0.0%
llama num_nodes 62 62 +0.0%
llama (static-cache) model_size_bytes 425 KB 425 KB +0.0%
llama (static-cache) num_nodes 58 58 +0.0%
mamba (ssm-text-generation) model_size_bytes 296 KB 296 KB +0.0%
mamba (ssm-text-generation) num_nodes 98 98 +0.0%
phi3 model_size_bytes 421 KB 421 KB +0.0%
phi3 num_nodes 60 60 +0.0%
phi3 (static-cache) model_size_bytes 421 KB 421 KB +0.0%
phi3 (static-cache) num_nodes 56 56 +0.0%
qwen2 model_size_bytes 425 KB 425 KB +0.0%
qwen2 num_nodes 62 62 +0.0%
qwen2 (static-cache) model_size_bytes 425 KB 425 KB +0.0%
qwen2 (static-cache) num_nodes 58 58 +0.0%
qwen3_5_moe (hybrid-text-generation) model_size_bytes 506 KB 506 KB +0.0%
qwen3_5_moe (hybrid-text-generation) num_nodes 275 275 +0.0%
qwen3_5_text (hybrid-text-generation) model_size_bytes 458 KB 458 KB +0.0%
qwen3_5_text (hybrid-text-generation) num_nodes 129 129 +0.0%
qwen3_5_vl (hybrid-qwen-vl) model_size_bytes 977 KB 977 KB +0.0%
qwen3_5_vl (hybrid-qwen-vl) num_nodes 413 413 +0.0%
t5 (seq2seq) model_size_bytes 836 KB 836 KB +0.0%
t5 (seq2seq) num_nodes 166 166 +0.0%
whisper (speech-to-text) model_size_bytes 1008 KB 1008 KB +0.0%
whisper (speech-to-text) num_nodes 128 128 +0.0%

No performance regressions.

@codecov

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.25843% with 6 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/mobius/integrations/gguf/_builder.py 80.64% 3 Missing and 3 partials ⚠️

📢 Thoughts on this report? Let us know!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the GGUF “keep quantized” path to keep the token embedding table block-quantized on disk and in the ONNX graph, switching embeddings from a float Gather to com.microsoft.GatherBlockQuantized to reduce size and bandwidth (notably for WebGPU’s 256 MiB buffer constraint).

Changes:

  • Emit QuantizedEmbedding (GatherBlockQuantized) when GGUF import can preserve embedding quantization, and plumb quantize_embeddings/quantize_lm_head/tie_word_embeddings into the GGUF quantization config.
  • Adjust CausalLMModel.preprocess_weights() to avoid applying float tie logic when the model uses a tied quantized embedding/head table.
  • Add GGUF tests covering quantized embeddings and tied quantized embedding+head behavior; update CLI/help text and quantization config docs accordingly.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/mobius/models/base.py Avoids applying float tie logic in preprocess_weights() when embed+lm_head are tied via the quantized-table path.
src/mobius/integrations/gguf/_builder.py Detects whether embeddings can stay quantized; repacks embedding tensors for GatherBlockQuantized; updates GGUF quantization config wiring and logging.
src/mobius/integrations/gguf/_builder_test.py Adds coverage verifying GatherBlockQuantized is present and that tied quantized embedding+head doesn’t create duplicate lm_head initializers.
src/mobius/_configs/_quantization.py Updates docstrings to reflect both Olive RTN and GGUF using quantized embeddings / tied quantized head.
src/mobius/__main__.py Updates GGUF CLI messaging/help to mention GatherBlockQuantized alongside MatMulNBits.
Comments suppressed due to low confidence (1)

src/mobius/integrations/gguf/_builder.py:599

  • _load_quantized_state_dict always emits a *.zero_points tensor when repacked.zero_points is present. For symmetric GGUF types (config.quantization.sym=True), the QuantizedLinear/QuantizedEmbedding modules don’t have a zero_points initializer, so these entries will be skipped with warning spam and unnecessary memory. Gate zero_points emission on the config’s sym flag.
            state_dict[f"{stem}.scales"] = s
            if repacked.zero_points is not None:
                zp = torch.from_numpy(repacked.zero_points)
                if _needs_qk_permute(hf_name, num_heads, num_kv_heads, model_type):
                    zp = _reverse_permute(zp, n_head)
                state_dict[f"{stem}.zero_points"] = zp

Comment thread src/mobius/integrations/gguf/_builder.py Outdated
Emit GatherBlockQuantized for repackable GGUF token embeddings and preserve tied quantized LM heads.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@justinchuby
justinchuby force-pushed the feat/gguf-quantized-embedding branch from 9f5a9f7 to 71e65bc Compare July 13, 2026 05:15
@justinchuby
justinchuby merged commit e8dc837 into main Jul 13, 2026
20 of 23 checks passed
@justinchuby
justinchuby deleted the feat/gguf-quantized-embedding branch July 13, 2026 13:23
justinchuby added a commit that referenced this pull request Jul 13, 2026
Follow-up to #400 (quantized embedding). Profiling showed the CPU model
shipped an untied `lm_head` as a plain fp32 MatMul (~544 MB) run every
token. This emits the output head as Q4 `MatMulNBits` (untied) / shares
the packed embedding table (tied).

- Result: 169 MatMulNBits, 1 GatherBlockQuantized, **0 plain fp32
MatMul**; no 544 MB fp32 tables. Model ~1.2 GB → ~399 MB.
- Coherent output verified ('Paris').
- Note: a quick 6-thread decode check showed ~38 vs ~40 tok/s (no clear
speedup) — a rigorous benchmark is pending; the win is model size +
correct all-quantized graph shape (matches llama.cpp). Stacks on #400's
embedding commit.

lintrunner clean; gguf pytest 157 passed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants