{−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-2B-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 ~1.88B language-model parameters, so it includes the F32 tensors and metadata. The same model is ~3.8 GB at FP16.
MLX:
prism-ml/Ternary-Bonsai-2-2B-mlx-2bit, 1.34 GB on disk. model.safetensors carries the language model in MLX’s 2-bit grouped format (group 128) plus the vision tower in FP16. 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 2B 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.