Prism ML Website |
Demo & Examples |
Discord
Bonsai 2 Ternary 2B: GGUF
A 2B-class vision-language model with ternary transformer weights, for llama.cpp (CUDA, Metal, CPU).
~0.52 GB language model (PQ2_0), down from ~3.8 GB at FP16
Highlights
- Ternary language weights across embeddings, attention projections, MLP projections and the (tied) LM head, stored with a group-wise FP16 scale.
- Three GGUF packings of the same weights, consumed directly by custom kernels and never expanded back to FP16: PTQ1_0 (dense trits, 0.43 GB), PQ2_0 (2-bit slots, 0.52 GB) and Q2_0 (0.55 GB).
- Vision input through a separate mmproj pack (Q8_0, or BF16 for reference precision), loaded only when an image arrives.
- 262K-token context, inherited from the Qwen3.5-2B hybrid-attention backbone (18 linear-attention and 6 full-attention layers).
Model Overview
Files
Bits/weight is file size over the ~1.88B language-model parameters, so it includes the F32 tensors and metadata. The three language-model packs hold the same ternary weights; they differ only in storage layout and kernel speed.
Quickstart
PrismML-Eng/Bonsai-demo is the source of truth for running these models. It has the tested setup for every backend and pins a known-good binary release.
Use the PrismML llama.cpp build
The ternary kernels live in the PrismML-Eng/llama.cpp fork (branch prism). It runs every file here. Upstream llama.cpp runs only Q2_0 (recent master, which includes the Hadamard activation runtime); it rejects PQ2_0 and PTQ1_0 as unknown types.
Text
Image + text
Vision projector: use the Q8_0 mmproj. With the current fork build, the BF16 mmproj can crash llama-server when several image requests run at once; Q8_0 is not affected and quality is nearly the same.
Small text in images: for small or dense text, raising the image token budget with --image-min-tokens 1024 may help.
OpenAI-compatible server
Best Practices
Generation Parameters
We recommend these sampling settings. They are the same for every packing and for MLX:
- Thinking mode (default):
temperature=1.0, top_p=0.95, top_k=20, min_p=0.0, presence_penalty=1.0, repetition_penalty=1.0
- Non-thinking mode:
temperature=0.7, top_p=0.80, top_k=20, min_p=0.0, presence_penalty=1.5, repetition_penalty=1.0
Apply the penalties over the last 64 tokens (llama.cpp's default window). Presence penalty has no GGUF metadata key, so pass it on the command line or per request.
Avoid greedy decoding (temperature=0) with thinking on; it can get stuck reasoning on some prompts.
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
You can use a simple system prompt such as:
MLX
On Apple Silicon you can also use the MLX build of the same weights, Ternary-Bonsai-2-2B-mlx-2bit. It loads natively in mlx-vlm 0.7.8 and newer (the released mlx-lm cannot load it yet). Pass the values above explicitly: mlx-vlm's Python generate() is greedy by default. Its model card has the commands.
Choosing a Packing
- PQ2_0: the default; cheaper to unpack, and the pack measured on Apple Silicon.
- PTQ1_0: the smallest file; use it where memory is tightest.
- Q2_0: an alternative layout, and the one format upstream llama.cpp can also run.
Speculative Decoding
The 2B ships without a draft model. At this size a decode step is already cheap, and in our tests speculative decoding with a draft head was slower than plain decoding on everyday prompts, so plain decoding is the recommended setup.
License
Apache 2.0, following the base model.