Qwen3.5-0.8B Chat β Core ML
On-device chat generation for Apple Silicon. Stateful Core ML export of Qwen/Qwen3.5-0.8B, a hybrid model that interleaves DeltaNet linear attention with full gated attention. Requires iOS 18+ / macOS 15+.
Ships precompiled .mlmodelc bundles only. On-device MLModel.compileModel() has
been observed to produce different results per runtime β Mac, simulator and
device β so compilation happens ahead of time and the compiled bundle is what is
verified and published.
Earlier revisions of this repository were defective and should not be used. Two separate faults, both since fixed and both measured:
- Final RMSNorm offset. Qwen3.5's last
norm.weightis a zero-centred norm applied asx * (1.0 + weight). The exporter folded that+1into the per-layer norms but not the final one, so the hidden state feeding the LM head was scaled by roughly zero-centred weights. Worth +19.0% perplexity on its own.- Core ML state write. Every
MLStatebuffer was replaced withmul_(0)thenadd_(new), which emits twocoreml_update_stateops and zeroes the buffer mid-graph while a value read from it is still live. The CPU backend tolerates this; the GPU backend stores a different tensor than the graph computed, corrupting the DeltaNet recurrent state so that agreement with the reference decayed as generation went on.If you have a cached copy from before this revision, delete it and re-download.
Model
| Detail | Value |
|---|---|
| Architecture | Qwen3.5 hybrid β 18 DeltaNet + 6 gated-attention layers, 24 total |
| Layer pattern | 3 Γ DeltaNet then 1 Γ gated attention, repeated 6 times |
| Hidden size | 1024 |
| Vocabulary | 248,320 (tied embedding / LM head) |
| Temporal state | DeltaNet conv1d + recurrent state and the attention KV cache, all carried as Core ML MLState |
| KV cache bound | 2048 positions (fixed at export) |
| Weights | 8-bit, per-block linear symmetric, block size 64 |
| Precision | float16 compute |
| Minimum target | iOS 18 / macOS 15 |
Two Core ML models. embedding.mlmodelc is a stateless token-ID lookup;
decoder.mlmodelc is a single-step decoder that owns all recurrent state, so the
caller advances one token per prediction and never manages a cache.
Files
| File | Size | Description |
|---|---|---|
int8/decoder.mlmodelc |
777.8 MB | Single-step decoder, 24 layers + LM head, all MLState |
int8/embedding.mlmodelc |
262.2 MB | Token-ID β embedding lookup |
int8/tokenizer.json |
20.0 MB | Tokenizer |
int8/chat_template.jinja |
7.8 kB | Chat template |
int8/chat_config.json |
1.1 kB | Architecture, quantization mode and special-token IDs |
int8/tokenizer_config.json |
1.1 kB | Tokenizer configuration |
config.json |
β | Repository-level metadata |
Total download for the one variant: 1.06 GB. Earlier revisions were 2.04 GB
because they shipped an uncompiled .mlpackage copy alongside the .mlmodelc;
that copy is gone, and nothing downloaded it.
The directory is named int8/ because that is the path the Swift loader resolves
and because the weights are 8-bit. The exact mode is per-block-64 linear
quantization rather than per-tensor palettization; int8/chat_config.json records
it as "quantization": "int8-linear".
Fidelity
These numbers say how closely the Core ML export reproduces the PyTorch reference it was converted from. They are an agreement measurement, not a quality claim β they say nothing about how good the model is, only that converting it did not change it.
Both models are fed the same five tokens rather than their own predictions, so a disagreement is a conversion fault and never sampling drift.
| Check | Measured | Gate |
|---|---|---|
| Embedding, max (1 β cosine) | 0.000017 | β€ 0.001 |
| Decoder, min per-step logit cosine | 0.998305 | β₯ 0.99 |
| Decoder, cosine change across steps | β0.000676 (improves) | must not fall > 0.02 |
| Decoder, argmax agreement | 5/5 | β₯ 4/5 |
Per step, logit cosine against the reference: 0.998305, 0.999888, 0.999711, 0.999856, 0.999689, with all five argmax predictions matching.
The compiled .mlmodelc published here was verified separately from the
intermediate package it was compiled from, and agreed with it to every digit. That
second pass exists because compilation is precisely the step that motivates
shipping precompiled in the first place.
Measured with coremltools 9.0 and torch 2.13. The exporter's pinned pair, coremltools 8.3.0 and torch 2.3.1, reproduces the same result.
Why there is no 4-bit variant
4-bit was measured and rejected, not omitted for space. It scored 0/5 on the same check, with a minimum logit cosine of 0.448 and an embedding error 270Γ worse than 8-bit. It fails on the very first token, before any recurrent state has accumulated, so this is plain weight-compression loss on a model whose 248,320 Γ 1024 tied embedding dominates its parameter count.
There is no 5-bit variant either, for a different reason: Core ML has no 5-bit path. Linear quantization accepts int4/int8 and their unsigned variants, and palettization accepts 1, 2, 3, 4, 6 or 8 bits. The MLX build of this model does default to 5-bit, which is why the two differ.
Usage
import Qwen3Chat
let chat = try await Qwen35CoreMLChat.fromPretrained()
let reply = try chat.generate(messages: [
ChatMessage(role: .user, content: "Explain linear attention in two sentences.")
])
print(reply)
fromPretrained() downloads only the .mlmodelc bundles and the tokenizer, then
holds the decoder's MLState across the generation loop.
Source
Converted from Qwen/Qwen3.5-0.8B. Weights are Apache-2.0, as is this export.
Links
- speech-swift β Apple SDK
- Docs β install and CLI docs
- soniqo.audio β website
- blog β blog
- Downloads last month
- 1,659