CoolFace
Modelpublic

Jadson/Qwen3.5-4B-Prime-GGUF

sourceHugging Faceapache-2.0updated 16d agoView on Hugging Face
0likes1.8kdownloads
Model Card

Qwen3.5-4B-Prime — GGUF

GGUF quantizations of a Qwen3.5-4B fine-tune trained to drive Emacs through [python-bridge](https://github.com/manateelazycat/python-bridge)/MCP instead of generic write/edit file tools.

Provenance:

Qwen/Qwen3.5-4B  →  empero-ai/Qwen3.8-4B-Distill  →  this fine-tune  →  GGUF (here)

Fine-tuned on `Jadson/ox-alpha-pi-traces-emacs-bridge`, which rewrites the agent traces of `TeichAI/Ox-Alpha-Pi-Traces` so every file creation and edit routes through ox-bridge-write, ox-bridge-edit and ox-bridge-read.

Text-only. The base is an image-text-to-text checkpoint, but this one carries no vision tower and ships no mmproj. Images are not supported.

Architecture

Qwen3.5 is a hybrid: 32 layers where 24 use linear attention (gated deltanet) and 8 use full attention, interleaved every 4th layer. 4.21 B parameters, vocab 248320, tied embeddings, 262144 max positions.

Because the embeddings are tied and the vocabulary is large, token_embd alone is 15% of all weights — which is why every recipe below keeps it at Q4_K instead of letting it fall to the 2-bit default.

Files

FileSizeVerdict
Qwen3.5-4B-Prime-IQ4_NL.gguf2.68 GBUse this one.
Qwen3.5-4B-Prime-UD-IQ1_M.gguf1.61 GBExperimental, degraded
Qwen3.5-4B-Prime-UD-IQ1_S.gguf1.51 GBExperimental, degraded
Qwen3.5-4B-Prime-UD-TQ1_0.gguf1.45 GBNot usable — see below
Qwen3.5-4B-Prime-UD-Q1_0.gguf1.29 GBNot usable — see below

About the sub-2-bit files

They are published for completeness and reproducibility, not because they work.

Unsloth's UD-IQ1 quants earn their reputation on MoE models of 100 B+ parameters, where expert redundancy absorbs the damage. This is a dense 4 B model: 3.7 B of non-embedding weights with nowhere to hide. Expect heavy degradation at IQ1M and IQ1S.

TQ1_0 is stricter still. It is ternarization — designed for models trained ternary, BitNet-style. Applied to ordinary BF16 weights it rounds them to {−1, 0, +1}. The file loads and generates tokens; the output is not worth reading. Q1_0 at 1.125 bpw is in the same territory.

If you want a small usable file, take IQ4_NL.

Quantization recipe

Importance matrix computed with llama-imatrix over 319 chunks at n_ctx=512, directly from the BF16, covering all 248 quantizable trunk tensors (24 linear-attention layers × 5, 8 full-attention layers × 4, 32 layers × 3 FFN).

Two calibration sources, and the second one is the point:

  • —calibration_datav3.txt — generic mixed-register English, for breadth.
  • —The training distribution itself. 110 sessions sampled from `ox-alpha-pi-traces-emacs-bridge` (the ipython split, matching the format the model actually emits), rendered through the chat template with <think> blocks and ``` `ipython `` cells, and tokenized with --parse-special so <|im_start|>` lands as the real control token rather than literal text.

Calibrating on the deployment distribution is not cosmetic. Against a generic-only matrix, 65 of 248 tensors shifted their importance profile by more than 10% (cosine similarity < 0.90; mean 0.9285, minimum 0.4187), and the largest shifts land on ffn_down and ssm_out — precisely the projections the recipes below protect, and the ones that decide quality at low bit-width. Importance also spread out slightly (top-decile share 0.482 → 0.466), as more diverse calibration should.

One caveat for anyone reproducing this: part of that shift comes from simply having more data (70 → 319 chunks reduces noise), not only from the domain match. The two effects are not separated here.

UD-* is not a llama-quantize type — it is Unsloth's naming for a dynamic mixture, per-tensor rather than uniform. Reproduced here with --tensor-type overrides (first matching regex wins and disables the automatic mixture for that tensor):

bash
llama-quantize --imatrix imatrix.gguf \
  --tensor-type "ssm_alpha=f16" \
  --tensor-type "ssm_beta=f16" \
  --tensor-type "ssm_conv1d=f16" \
  --token-embedding-type q4_k --output-tensor-type q6_k \
  --tensor-type "blk\.(0|1|31|32)\.=q4_k" \
  --tensor-type "attn_k=q4_k" --tensor-type "attn_v=q4_k" \
  --tensor-type "attn_output=iq2_xxs" --tensor-type "ssm_out=iq2_xxs" \
  --tensor-type "ffn_down=iq2_xxs" \
  model-BF16.gguf model-UD-IQ1_S.gguf IQ1_S 20

What is protected and why:

TensorsTypeReason
token_embdQ4_K15% of weights, and tied — it also serves as the output projection
blk.0, blk.1, blk.31, blk.32Q4_KFirst/last layers carry outsized error; blk.32 is the MTP head
attn_k, attn_vQ4_KSmall tensors, large quality impact
ssm_alpha, ssm_beta, ssm_conv1dF16Gated-deltanet control paths, tiny and unforgiving
attn_output, ssm_out, ffn_downone tier above baseMost error-sensitive projections

IQ4_NL uses the stock mixture plus the three ssm_* protections.

Multi-token prediction (MTP)

All files carry an MTP block at blk.32 for speculative decoding:

bash
llama-cli -m Qwen3.5-4B-Prime-IQ4_NL.gguf --spec-type draft-mtp -p "..."

Measured on this machine (CPU, 4 threads, IQ4_NL): 6.8 → 10.0 tok/s, a 47% speedup, with no fallback warnings.

Disclosure: the fine-tuned checkpoint shipped without an MTP head, so these 15 tensors were grafted from Qwen/Qwen3.5-4B base. The head therefore predicts against base weights, not the fine-tune. This lowers the acceptance rate; it does not affect correctness, because the target model verifies every drafted token. It also receives no imatrix calibration — a plain forward pass never exercises the MTP graph — which is why blk.32 is pinned to Q4_K.

At 1-bit the MTP head is pointless regardless.

System prompt — required

Use [`SYSTEM_PROMPT.md`](./SYSTEM_PROMPT.md). It ships in this repo and it is not optional: without it the model still emits ipython cells, but falls back to ! bash python3 -c "..." instead of the Emacs bridge.

Same prompt, same seed, IQ4_NL — without the system prompt:

python
result = ! bash python3 -c "
with open('hello.py', 'w') as f:
    f.write('''def greet(name='World'): ...''')
"

With it:

python
result = await mcp.call_tool("tools", "ox-bridge-write", {"path": "hello.py", "content": "..."})
print(result)

Usage

bash
# chat
llama-cli -m Qwen3.5-4B-Prime-IQ4_NL.gguf -c 8192 -sysf SYSTEM_PROMPT.md

# server, with speculative decoding
llama-server -m Qwen3.5-4B-Prime-IQ4_NL.gguf -c 8192 --spec-type draft-mtp

Requires a llama.cpp build with the qwen35 architecture (src/models/qwen35.cpp). Older builds will refuse to load these files.

The model is a reasoning model and opens a <think> block by default. Suppress it with enable_thinking=false in the chat template.

Reproducing

bash
python convert_hf_to_gguf.py ./model-dir --outtype bf16 --outfile BF16.gguf
llama-imatrix -m BF16.gguf -f calib_domain.txt -o imatrix.gguf \
              -c 512 -b 2048 -ub 512 -np 4 -t 20 -ngl 0 --no-ppl --parse-special
# then the llama-quantize invocations above

On a memory-constrained machine add --max-buffer-size 256 to every llama-quantize call — the default caps tensor rows held in RAM at 8192 MiB, which a 248320×2560 embedding will happily exceed.

License and credits

Apache-2.0, inherited from Qwen3.5-4B. The training dataset is MIT.