Skip to content

gemma4 GGUF: embedding quant type hardcoded to Q6_K, so a Q4_0-embedding GGUF fails to load with a bare AssertionError #188

Description

@salekseev

Summary

convert_gemma4_to_gguf (python/freetoken/models/gemma4/gguf.py) hardcodes GGML_Q6_K
for the token embedding table and the tied LM head:

embed = GGUFEmbedding(
    num_embeddings=config.vocab_size,
    embedding_dim=config.hidden_size,
    quant_type=GGML_Q6_K,          # <-- assumed, not read from the file
    embed_scale=config.embedding_scale,
)
...
if config.tie_word_embeddings:
    model.lm_head = GGUFTiedLMHead(embed, GGML_Q6_K)

token_embd.weight is not always Q6_K. google's gemma-4-26B-A4B-it-qat-q4_0-gguf keeps it
at Q6_K, but unsloth's gemma-4-26B-A4B-it-qat-GGUF quantizes it to Q4_0, and row stride
differs by block type — 2310 bytes/row for Q6_K (2816/256 * 210) against 1584 for Q4_0
(2816/32 * 18) at hidden_size = 2816. GGUFEmbedding.__init__ sizes qweight with
row_bytes(embedding_dim, quant_type), so the buffer is allocated 2310 wide and the file
delivers 1584.

The load then dies in the shared loader on an assert with no message:

File "freetoken/layers/base.py", line 45, in load_state_dict
    assert param.shape == item.shape and param.dtype == item.dtype
AssertionError

layers/base.py:45 names neither the parameter nor the shapes, so the failure gives no
indication of which tensor or why. Instrumenting that traversal shows it:

MISMATCH model.embed_tokens.qweight: model wants (262144, 2310) torch.uint8 | file has (262144, 1584) torch.uint8

Nothing else in the checkpoint is affected — the projections and routed experts are Q4_0
and load fine, so the failure is purely this one assumption.

Environment

  • FreeToken 0.1.2 (PyPI wheel + freetoken_kernel_cache-0.1.2+cu130)
  • RTX 4080 SUPER (sm_89, 16 GB), driver 610.57.04, CUDA UMD 13.3
  • Model: unsloth/gemma-4-26B-A4B-it-qat-GGUF / gemma-4-26B-A4B-it-qat-UD-Q4_K_XL.gguf
    (despite the filename this file is general.file_type = 2 (MOSTLY_Q4_0):
    266 Q4_0 + 392 F32 tensors, general.name = "Gemma-4 26B-A4B IT (smart Q4_0, QAT-lossless)")
  • ft serve --model .../gemma-4-26B-A4B-it-qat-UD-Q4_K_XL.gguf

Reproduction

Any gemma4 GGUF whose token_embd.weight is not Q6_K. Reading the type straight out of the
file is enough to show the mismatch without a load:

from freetoken.models.gguf.reader import iter_gguf_tensors
from freetoken.models.gguf.dequant import GGML_NAME, GGML_Q6_K, row_bytes

path = ".../gemma-4-26B-A4B-it-qat-UD-Q4_K_XL.gguf"
for t in iter_gguf_tensors(path):
    if t.name == "token_embd.weight":
        print(f"file: {GGML_NAME[t.ggml_type]}, {row_bytes(2816, t.ggml_type)} bytes/row")
        print(f"code: {GGML_NAME[GGML_Q6_K]}, {row_bytes(2816, GGML_Q6_K)} bytes/row")
file: Q4_0, 1584 bytes/row
code: Q6_K, 2310 bytes/row

Suggested fix

GGUFEmbedding and GGUFTiedLMHead are already type-generic — they thread quant_type
into row_bytes() and ggml_dequantize(), and Q4_0 is in _MMVQ / _MMQ / _DEQUANT in
layers/gguf.py — so only the plumbing is missing. Reading the type from the tensor table
in parse_gguf_config and using it in convert_gemma4_to_gguf is sufficient; a
metadata-only GGUF (an FTW dir's source_metadata.gguf, which lists no tensors) has no
entry to read, so the Q6_K default still applies there.

With that change the model loads and serves correctly on this checkpoint: 179 tok/s decode
at 1200 tokens, --moe-backend offload, sane output, tool calls and reasoning parsed.

Happy to open a PR with the patch and a test if the approach looks right.

One adjacent thing, possibly worth its own issue: layers/base.py:45 should name the
tensor.
Every shape/dtype mismatch in every model surfaces as a bare AssertionError,
and the traversal has the key and both shapes in hand at that point. A one-line message
there would have turned this from an afternoon of instrumenting the loader into a minute.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions