{−1, 0, +1} in a fixed rotated basis, with an FP16 scale per group of 128 weights. It takes images as well as text, carries the base model’s 262K-token context, and thinks before it answers by default.
Specifications
Artifacts
Both repositories are in the Bonsai 2 collection on Hugging Face. GGUF:prism-ml/Ternary-Bonsai-2-9B-gguf
The three language-model files hold the same ternary weights and differ only in storage layout and kernel speed. Bits/weight is file size over the ~8.95B language-model parameters, so it includes the F32 tensors and metadata. The same model is ~17.9 GB at FP16.
MLX:
prism-ml/Ternary-Bonsai-2-9B-mlx-2bit, 3.46 GB on disk. model.safetensors carries the language model in MLX’s 2-bit grouped format (group 128) plus the vision tower in FP16. mtp/ holds the optional multi-token-prediction draft head (0.14 GB, 4-bit). MLX stores a scale and a bias per group, so the container costs a little more than the GGUF packings; the weights decode to exactly the same ternary values.
Which runtime runs which file. Upstream llama.cpp (build
b11555 or newer) runs the Q2_0 file. PQ2_0 and PTQ1_0 need the PrismML llama.cpp binaries, which the demo’s setup.sh installs. The MLX pack loads natively in mlx-vlm 0.7.8 and newer. See Formats & runtime support.How to run it
Through the demo repo, which applies the right sampling profile, loads the vision projector, and enables tool calling:-n 16384 (or max_tokens) leaves room for the thinking trace; a small cap ends generation mid-thought, before any answer. The demo repo is the source of truth for running these models; tested setups and current implementation details live there.
Best practices
These apply to every GGUF packing and to the MLX pack.Generation parameters
Apply the penalties over the last 64 tokens:
--repeat-last-n 64 in llama.cpp (its default window), presence_context_size=64 in MLX (whose default is 20). The presence penalty has no GGUF metadata key, so pass it on the command line or in each request. Avoid greedy decoding (temperature=0) with thinking on; it can get stuck reasoning on some prompts. The demo’s launchers apply the thinking-mode profile, and BONSAI_THINKING=0 switches both the sampling and the chat template to non-thinking.
Output length
Give thinking mode room to finish. Even simple prompts can use 3,000+ thinking tokens, so allow at least 8,192 output tokens with thinking on. For short answers, turn thinking off.System prompt
A plain system message works well:MLX sampler
mlx-vlm’s Pythongenerate() decodes greedily unless you pass the sampler, and mlx_vlm.server reads temperature, top_p, and top_k from generation_config.json but not min_p or the presence penalty. Send the full set with every request. Matching these values does not make output identical across runtimes: sampler ordering, kernels, and RNG differ.
Choosing a packing
PQ2_0: the default. Cheaper to unpack, faster prompt processing, and the pack measured on Apple Silicon.PTQ1_0: the smallest file. Use it where memory is tightest.Q2_0: the one packing upstream llama.cpp runs. Pick it for tools that bundle a stock llama.cpp.
Vision and tool calling
Send an image as an OpenAI-styleimage_url content part, and tools as a tools array; both come back through the standard API. Use the Q8_0 projector: quality is nearly identical to BF16, and the BF16 file can crash llama-server under concurrent image requests on current builds. For small or dense text in images, raise the image token budget with --image-min-tokens 1024.
Thinking mode
The 9B reasons by default. Over the API, turn it off per request with"chat_template_kwargs": {"enable_thinking": false} on llama-server, or a top-level "enable_thinking": false on the mlx-vlm server, and switch to the non-thinking sampling at the same time. Cap it server-wide with --reasoning-budget N, or start the demo’s launchers with BONSAI_THINKING=0. The built-in chat UI has a Reasoning effort picker that overrides the server default per conversation.
Speculative decoding
Two optional drafters ship in the GGUF repo. Both need the PrismML binaries, and the demo’s launchers don’t select them automatically, so runllama-server yourself.
MTP (recommended). Ternary-Bonsai-2-9B-PQ2_0-MTP-Q8_0.gguf is the PQ2_0 model with its multi-token-prediction head built in: load it instead of the plain PQ2_0 file, not alongside it. Draft depth 2 works best:
--cache-type-k q8_0 --cache-type-v q8_0) works with MTP from release prism-b10776-d00e252 onward; on older CUDA builds keep the default F16 cache, or quantize only K. Metal is not affected.
DFlash2 (optional). Ternary-Bonsai-2-9B-DFlash2-Q8_0.gguf is a block drafter for the PQ2_0 file. It helps on copy-heavy work such as code edits and rewrites and is slower than plain decoding on general chat, so prefer MTP by default:
mtp/ as an mlx-vlm drafter; MTP.md in that repo has the code. Speculative output matches plain decoding up to near-ties, so exact-match comparisons against non-speculative runs can show small differences.