Qwen3.5-0.8B Chat — MLX (Apple Silicon)
Text-only extraction of Qwen3.5-0.8B, quantized with MLX for on-device chat on Apple Silicon. Three bit widths are published so the quality/size tradeoff can be made with measured numbers rather than assumed.
INT8 is the recommended default. INT4 is kept for size-constrained use but is no longer recommended for anything that parses the reply as JSON — see Measured quality.
Model
| Parameters | 752 M (text model; the tied embedding / LM head is 254 M of that) |
| Architecture | Qwen3.5 hybrid — 24 layers, 18× DeltaNet linear attention + 6× GatedAttention, pattern [linear, linear, linear, full] × 6 |
| Positional encoding | Partial RoPE, 25 % of head_dim (64 of 256 rotary dims) |
| Output head | Tied to the token embedding (lm_head = embed_tokens) |
| Format | MLX safetensors |
| Quantization | mx.quantize affine round-to-nearest, group size 64, at 4 / 5 / 8 bits |
| Declared context | 2048 (max_seq_len); the MLX path does not enforce it — DeltaNet is recurrent and the KV cache grows on demand |
| Vocabulary | 248 320 |
Norm weights, A_log, dt_bias and the causal conv1d kernels are not
quantized; they stay float. Quantized tensors are stored as MLX's
weight / scales / biases triple with fp16 scales and biases.
| Variant | File size | Quantized | Use it when |
|---|---|---|---|
| INT8 | 800 MB | 8-bit, group 64 | Default. Effectively indistinguishable from bf16. |
| INT5 | 518 MB | 5-bit, group 64 | Memory matters more than the last 2 % of quality. |
| INT4 | 424 MB | 4-bit, group 64 | Smallest download, free-form text only. Not for JSON. |
Files
| File | Size | Description |
|---|---|---|
int4/model.safetensors |
424 MB | INT4 weights |
int4/config.json |
1.3 kB | Architecture + quantization_bits: 4, quantization_group_size: 64 |
int4/tokenizer.json |
12.8 MB | Tokenizer |
int4/tokenizer_config.json |
16.7 kB | Chat template and special tokens |
int5/model.safetensors |
518 MB | INT5 weights |
int5/config.json |
1.3 kB | Same, quantization_bits: 5 |
int5/tokenizer.json |
12.8 MB | Tokenizer |
int5/tokenizer_config.json |
16.7 kB | Chat template and special tokens |
int8/model.safetensors |
800 MB | INT8 weights |
int8/config.json |
1.3 kB | Same, quantization_bits: 8 |
int8/tokenizer.json |
12.8 MB | Tokenizer |
int8/tokenizer_config.json |
16.7 kB | Chat template and special tokens |
Each variant is self-contained: one directory is everything needed to run it.
The vision tower and the multi-token-prediction draft head (mtp.*) are not
included — no runtime here reads them. Dropping the draft head alone took 14 MB
off the INT4 download compared with the previous revision.
Measured quality
Every number below is measured, not estimated. Method: the exporter's exact quantize/dequantize step is reproduced on the bf16 checkpoint — same kernel, same bit width, same group size — and the result is compared against the unmodified bf16 model on identical inputs. 32 768 teacher-forced tokens per corpus. A control run with quantization disabled reproduced bf16 exactly (0.000 % perplexity change, 100 % agreement, 0 KL), so the deltas below are the quantization and nothing else.
Perplexity is relative to the bf16 checkpoint; lower is better and 0 % is the target. Top-1 agreement is how often the quantized model's most likely next token is the same as bf16's.
| Variant | Perplexity vs bf16 (wikitext) | Perplexity vs bf16 (dialogsum) | Top-1 agreement | Mean KL from bf16 |
|---|---|---|---|---|
| bf16 reference | 22.798 | 4.846 | — | — |
| INT8 | +0.19 % | −0.002 % | 97.9 % / 98.8 % | 0.0013 / 0.0008 nats |
| INT5 | +2.2 % | +1.6 % | 89.6 % / 94.7 % | 0.032 / 0.016 nats |
| INT4 | +19.9 % | +9.5 % | 78.3 % / 88.5 % | 0.149 / 0.079 nats |
Structured replies
24 tasks that ask for a single JSON object and are scored the way a strict parser would score them: greedy decoding, no repair pass, no leniency for markdown fences or prose. bf16 passes 24 of 24.
| Variant | Strict JSON valid | Byte-identical to bf16 |
|---|---|---|
| INT8 | 24 / 24 | 18 / 24 |
| INT5 | 23 / 24 | 12 / 24 |
| INT4 | 18 / 24 | 5 / 24 |
The INT4 failures are not near-misses. Five of the six are unbalanced braces —
replies such as {"action_items": [{"owner": "Chen", "task": "..."]} that drop
the object's closing brace, which no parser recovers from. The sixth invents a
different key name. This is why INT4 is no longer recommended for any consumer
that parses the reply: it degrades exactly where a downstream pipeline is least
able to cope. INT5's single failure is the same kind of dropped closing brace.
Raising only the embedding to 8 bits while keeping the body at 4 was measured and does not rescue INT4: +15.4 % perplexity, 23 of 24 JSON. If structured output matters, use INT5 or INT8 rather than a mixed INT4.
Usage
Swift (speech-swift)
import Qwen3Chat
let model = try await Qwen35MLXChat.fromPretrained(quantization: .int8)
let response = try model.generate(
messages: [ChatMessage(role: .user, content: "Hello!")],
sampling: ChatSamplingConfig(temperature: 0.3, maxTokens: 100)
)
INT5 and INT8 need a runtime that reads the bit width from
config.json. Older speech-swift versions build everyQuantizedLinearandPreQuantizedEmbeddingwithbits = 4hardcoded, which fits the INT4 file only; they will not read an INT5 or INT8 file correctly. Use a speech-swift version that takesquantization_bitsandquantization_group_sizefromconfig.json, or stay on INT4.
Python
import json
import mlx.core as mx
from huggingface_hub import snapshot_download
path = snapshot_download("aufklarer/Qwen3.5-0.8B-Chat-MLX",
allow_patterns=["int8/*"])
cfg = json.load(open(f"{path}/int8/config.json"))
weights = mx.load(f"{path}/int8/model.safetensors")
# Quantized tensors are stored as a weight/scales/biases triple. Always take the
# bit width and group size from config.json rather than assuming them.
up = mx.dequantize(
weights["layers.0.mlp.up_proj.weight"],
weights["layers.0.mlp.up_proj.scales"],
weights["layers.0.mlp.up_proj.biases"],
group_size=cfg["quantization_group_size"],
bits=cfg["quantization_bits"],
)
print(cfg["quantization"], up.shape) # int8 (3584, 1024)
The layout is the custom key set the Swift runtime loads (layers.N.linear_attn.*
for DeltaNet, layers.N.self_attn.* for the attention layers), not the
mlx-lm naming, so mlx_lm.load will not read it directly.
CLI
# One variant only — do not pull all three
hf download aufklarer/Qwen3.5-0.8B-Chat-MLX --include "int8/*" \
--local-dir ./qwen35-0.8b-int8
A note on earlier downloads
These files replace an earlier export in which the model's final RMSNorm
weight was written without the +1 offset that Qwen3.5 stores its norm weights
with. That mis-scaled the hidden state feeding the LM head and cost about
+19 % perplexity on its own, on top of any quantization error. If you downloaded
this repository before this revision, re-download it; the numbers in this card
apply only to the current files.
Source
Converted from Qwen/Qwen3.5-0.8B
(Apache-2.0). The vision tower and the multi-token-prediction head are dropped;
the text model is quantized with mx.quantize at group size 64, the
zero-centered RMSNorm weights are folded to 1 + w, and the DeltaNet conv1d
kernels are transposed to MLX's channels-last layout.
Links
- speech-swift — Apple SDK
- Docs — install and CLI docs
- soniqo.audio — website
- blog — blog