Spark-X2.5 on Apple Silicon
XHToken's Spark-X2.5-4B: a dense 4B trained for conversation, coding, tool use and agent work, with a native 1M-token context and more than 200 languages, released under Apache-2.0. The model thinks by default. Its attention is a 3:1 hybrid, three sliding-window layers (512 tokens) for every full-attention layer, which is what keeps the KV cache small at long context. The mlx-optiq build is one HF repo, a sensitivity-aware mixed-precision quant at 2.8 GB. Spark-X2.5 is a new architecture for MLX, and mlx-optiq 0.5.7 adds support for it, so run import optiq before mlx_lm.load.
The quant
| Model | Size on disk | Capability Score | vs uniform-4 | Best for |
|---|---|---|---|---|
| Spark-X2.5-4B-OptiQ-4bit | 2.8 GB | 64.69 | +3.17 | Long documents and agent loops on a small footprint; tool calling; 200+ languages |
Per-benchmark breakdown: Spark-X2.5-4B
| Benchmark | uniform-4 | OptiQ-4 (mixed) | Δ |
|---|---|---|---|
| MMLU (5-shot, 1000) | 63.1% | 66.8% | +3.7 |
| GSM8K (no thinking) | 84.9% | 87.0% | +2.1 |
| IFEval (strict) | 75.8% | 75.2% | -0.6 |
| BFCL V3 (simple AST) | 70.0% | 75.5% | +5.5 |
| HumanEval (pass@1) | 74.4% | 75.6% | +1.2 |
| HashHop (overall) | 1.0% | 8.0% | +7.0 |
| Capability Score | 61.52 | 64.69 | +3.17 |
Hello world
import optiq # registers the spark2_5 architecture with mlx-lm from mlx_lm import load, generate model, tok = load("mlx-community/Spark-X2.5-4B-OptiQ-4bit") prompt = tok.apply_chat_template( [{"role": "user", "content": "Summarize the plot of The Iliad in three sentences."}], tokenize=False, add_generation_prompt=True, enable_thinking=False, ) print(generate(model, tok, prompt=prompt, max_tokens=300))
Thinking is on by default
The chat template accepts an enable_thinking flag and defaults it to on: the model reasons in a <think> block before answering, and a short max_tokens can be spent entirely on the thinking. Give it room (a few hundred tokens for a direct question, more for math or code) or switch thinking off per request. Upstream evaluates in thinking mode with temperature 1.0 and top_p 0.95; optiq serve applies those from the bundled generation_config.json unless you pass your own.
| Mode | temperature | top_p | Use when |
|---|---|---|---|
| Think (default) | 1.0 | 0.95 | Math, code, tool use, multi-step agent turns |
| No-think | 1.0 | 0.95 | Fast assistant, rewriting, classification |
Pass the flag via chat_template_kwargs at the OpenAI endpoint or as a keyword to apply_chat_template. Tool calls use the Qwen3-style parser; optiq serve returns them as structured tool_calls.
Serving and long context
$ optiq serve --model mlx-community/Spark-X2.5-4B-OptiQ-4bit --kv-config kv_config.json --port 8000 # From any OpenAI-compatible client: $ curl -s http://localhost:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{"model":"mlx-community/Spark-X2.5-4B-OptiQ-4bit", "messages":[{"role":"user","content":"What is 17 * 23?"}], "chat_template_kwargs":{"enable_thinking":false}}'
The bundled kv_config.json is a per-layer mixed-precision KV plan measured on the quant itself: the most sensitive attention layers keep an 8-bit cache, the rest run at 4-bit. Only the nine full-attention layers grow with the prompt; the 27 sliding-window layers hold a fixed 512-token window and are left at full precision. At 4-bit that is about 9 KB of cache per token, so a 36 GB Mac serves a prompt of roughly 200k tokens with the memory-safe default cap, and a 24 GB Mac somewhat less. The model was trained to 1M; the cap is the machine's, not the model's.