Mixed-precision 4-bit MLX quantization of IFM/K2-Horizon-MoVA-36B-A4B, converted from revision e5c131d. 5.6 bits/weight effective, 25 GB on disk. For Apple silicon.
K2-Horizon-MoVA-36B-A4B is IFM's sparse K2-Horizon model: a 36B-parameter Mixture-of-Experts model with 4B active per token and 512K context. Its attention is Mixture-of-Values (MoVA), where the values come from 4 of 64 routed value experts.
mlx-lm doesn't support the k2_horizon architecture yet. There's an open request: mlx-lm#1876. Until support lands, this repo ships the MLX model code (k2_horizon.py), which mlx-lm loads through the model_file entry in config.json. So the released mlx-lm works as-is:
pip install -U mlx-lm
Pass --trust-remote-code (or trust_remote_code=True). mlx-lm 0.31.3 loads the file without it, but newer versions require it. The file runs on your machine, so read it first if you like.
Once K2-Horizon support lands in a released mlx-lm, this repo will be updated to use it. If something breaks after that, open a discussion here asking for a re-upload.
Mixed precision, chosen from measurements rather than a fixed rule:
4-bit (affine, group size 64): the routed MoE experts and the shared expert. These hold most of the parameters.
8-bit (affine, group size 64): everything else: attention (including the MoVA value experts), the dense MLPs of layers 0–2, the embeddings and lm_head.
bf16: the MoE and MoVA routers.
I compared three 4-bit recipes against bf16 on WikiText-2. Plain 4-bit everywhere: +4.5% perplexity. 4-bit with only the MoVA value experts at 8-bit: +4.4%. This recipe: +1.2%, for about 5 GB more memory than plain 4-bit. Keeping the value experts alone at 8-bit barely helps; what matters is keeping all the non-expert weights at 8-bit.
Peak 26.5 GB for a short prompt: fits a 48 GB Mac under the default Metal working-set cap. A 36 GB Mac needs a raised cap (sudo sysctl iogpu.wired_limit_mb=...). The KV cache adds about 192 KB per token (about 24 GB at 128K tokens).
The MLX implementation was checked against IFM's PyTorch code (modeling_k2_horizon.py) in fp32 on the real weights, one layer at a time. All 48 layers match to a relative error of 1e-6 or better, and the next-token predictions agree at every position.
Smoke-tested after conversion with released mlx-lm 0.31.3 (mlx_lm.generate and mlx_lm.server):
17 * 23 → 391 with correct reasoning
"capital of Australia" → Canberra
A multi-turn server conversation that carries context correctly
On a Mac Studio M4 Max 128GB: 59.6 tok/s generation, peak 26.5 GB (short prompt).
mlx_lm.generate --model mlx-community/K2-Horizon-MoVA-36B-A4B-4bit --trust-remote-code --prompt "Explain mixture-of-experts in two sentences." --max-tokens 2048
from mlx_lm import load, generate
# Newer mlx-lm versions need trust_remote_code=True; on mlx-lm 0.31.3 use load("mlx-community/K2-Horizon-MoVA-36B-A4B-4bit").
model, tokenizer = load("mlx-community/K2-Horizon-MoVA-36B-A4B-4bit", trust_remote_code=True)
messages = [{"role": "user", "content": "Explain mixture-of-experts in two sentences."}]
prompt = tokenizer.apply_chat_template(messages, add_generation_prompt=True)
print(generate(model, tokenizer, prompt, max_tokens=2048))
mlx_lm.chat and mlx_lm.server take the same --trust-remote-code flag.
The model thinks before it answers, inside <ifm|think> … </ifm|think>. Leave room for that in max_tokens. The effort level is set with reasoning_effort in the chat template: "high" (default), "medium" or "low", e.g. apply_chat_template(messages, add_generation_prompt=True, reasoning_effort="low").
Notes:
Server output: mlx-lm doesn't recognize the <ifm|think> tags yet, so mlx_lm.server returns the thinking text inside content, before </ifm|think>, rather than in a separate reasoning field. K2-Horizon's tool-call format isn't parsed yet either.
Chat template change: the original template raises an error when an earlier assistant message has no thinking field, which is what OpenAI-style clients send. The template here renders empty thinking for those messages instead. Nothing else was changed.
Apache-2.0, inherited from the base model. Refer to the original model card for architecture, benchmarks and intended use. All credit for the model belongs to IFM.
Files
14 files · 26.4 GBWeights 26.4 GB (safetensors). Sizes and sha256 from the signed manifest.
import os
os.environ["HF_ENDPOINT"] = "https://freeweights.allternit.com"
from transformers import AutoModelForCausalLM, AutoTokenizer
tok = AutoTokenizer.from_pretrained("mlx-community/K2-Horizon-MoVA-36B-A4B-4bit", revision="923353ec7a3eb0a85576ddb92e1496a753ea8e07")
model = AutoModelForCausalLM.from_pretrained("mlx-community/K2-Horizon-MoVA-36B-A4B-4bit", revision="923353ec7a3eb0a85576ddb92e1496a753ea8e07", device_map="auto")