Skip to main content
Ternary Bonsai 2 9B is the mid-size model in the family, for laptops and desktops with 8 GB or more of memory. It is derived from Qwen3.5-9B, a hybrid-attention vision-language model, with the architecture unchanged. Every language-model weight is ternary: {−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.
Before you run it: the recommended sampling settings differ between thinking and non-thinking mode, and the 9B wants a presence penalty that the 27B does not. See Best practices.

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:
Or directly with llama.cpp / MLX:
-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

Use the set that matches the mode you’re running in. Thinking and non-thinking mode want different sampling, and changing the thinking mode alone does not change the sampling for you.
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 Python generate() 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-style image_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.
See Tool calling & MCP in the demo repo for MCP server setup and the agentic walkthrough.

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 run llama-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:
On CUDA, a quantized KV cache (--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:
The MLX pack carries the same MTP head under 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.