- Ternary Bonsai 2 27B is a free model from prism-ml that you can run on your own computer. In our tests it's a solid all-rounder: 82 out of 100, #15 of 65.
- It solved 22 of 30 coding jobs and scored 91 on reading documents. On our hardest tasks it scored 68.
- Runs on a 12 GB graphics card or a Mac with 16 GB.
Coding
Our coding test is 30 programming jobs, from small ones like reading time durations or cleaning up messy data to harder ones like a config-file parser or a double-entry ledger. We run each answer against tests the model never sees, and a job only counts if everything passes. Ternary Bonsai 2 27B got 22 of 30 right. The best local coders solved 29 of 30.
Reading documents
The second test hands the model things like an expense claim thread, a pay stub or an insurance statement, and asks for specific numbers and dates. Many questions need a bit of math, or noticing a correction further down the email. Ternary Bonsai 2 27B scored 91; the best model scored 100.
| Test | Score | Public questions | Secret questions |
|---|---|---|---|
| Coding | 73 | 86 | 70 |
| Reading documents | 91 | 98 | 89 |
| Decisions | 97 | 96 | 98 |
On the 18 hardest tasks (included in the scores above) it scored 68. This number separates the top models.
This model did much better on our public questions than on the secret ones. Sometimes that's luck; it can also mean the model has seen the public questions before.
We tested the full-size version online. The smaller versions you download usually score a little lower.
Can your computer run it?
Ternary Bonsai 2 27B comes in one standard size, which needs about 9.9 GB of memory.
| Your computer | Runs it? | Version to download |
|---|---|---|
| Graphics card, 8 GB | No | – |
| Graphics card, 12 GB | Yes | standard |
| Graphics card, 16 GB | Yes | standard |
| Graphics card, 24 GB | Yes | standard |
| Graphics card, 32 GB | Yes | standard |
| Mac, 16 GB | Yes | standard |
| Mac, 24 GB | Yes | standard |
| Mac, 32 GB | Yes | standard |
| Mac, 48 GB | Yes | standard |
| Mac, 64 GB | Yes | standard |
| Mac, 96 GB | Yes | standard |
| Mac, 128 GB | Yes | standard |
For normal-length chats. Long documents need more memory. Q4 or Q8? · How we worked this out
How to try it
Search for "Ternary Bonsai 2 27B GGUF" in LM Studio or on Hugging Face and pick the version that fits your computer. New to this? Start with our Ollama guide.
Want the strongest all-rounder instead? Qwen3.6 27B scored 97.
Its actual answers
What Ternary Bonsai 2 27B wrote for our public questions, word for word.
Coding: INI parser (hard) Didn't work
Coding: TTL LRU (hard) Worked
```python
from collections import OrderedDict
class TTLCache:
def __init__(self, capacity: int, ttl: float, clock):
self._capacity = max(0, capacity)
self._ttl = ttl
self._clock = clock
self._store = OrderedDict()
self._stats = {
"hits": 0,
"misses": 0,
"evictions": 0,
"expirations": 0,
}
def put(self, key, value) -> None:
now = self._clock()
if self._capacity == 0:
return
if key in self._store:
self._store[key] = (value, now + self._ttl)
self._store.move_to_end(key)
return
if len(self._store) + 1 > self._capacity:
self._purge_expired(now)
if len(self._store) >= self._capacity:
self._store.popitem(last=False)
self._stats["evictions"] += 1
self._store[key] = (value, now + self._ttl)
def get(self, key, default=None):
now = self._clock()
if key not in self._store:
self._stats["misses"] += 1
return default
value, expiry = self._store[key]
if now >= expiry:
del self._store[key]
self._stats["misses"] += 1
self._stats["expirations"] += 1
return default
self._store.move_to_end(key)
self._stats["hits"] += 1
return value
def __len__(self) -> int:
now = self._clock()
return sum(1 for _, expiry in self._store.values() if now < expiry)
def stats(self) -> dict:
return {
"hits": self._stats["hits"],
"misses": self._stats["misses"],
"evictions": self._stats["evictions"],
"expirations": self._stats["expirations"],
}
def _purge_expired(self, now) -> None:
expired = [key for key, (_, expiry) in self._store.items() if now >= expiry]
if expired:
for key in expired:
del self._store[key]
self._stats["expirations"] += len(expired)
``` Coding: Parse duration Worked
```python
import re
_DURATION_RE = re.compile(
r"(?:\s*([0-9]+)h)?(?:\s*([0-9]+)m)?(?:\s*([0-9]+)s)?\s*",
re.IGNORECASE | re.ASCII,
)
def parse_duration(s: str) -> int:
if not s.strip():
raise ValueError("duration string must not be empty")
match = _DURATION_RE.fullmatch(s)
if match is None:
raise ValueError(f"invalid duration string: {s!r}")
hours, minutes, seconds = match.groups()
return (
(int(hours) if hours is not None else 0) * 3600
+ (int(minutes) if minutes is not None else 0) * 60
+ (int(seconds) if seconds is not None else 0)
)
``` Coding: Summarize ranges Worked
```python
def summarize_ranges(nums: list[int]) -> str:
unique = sorted(set(nums))
if not unique:
return ""
parts = []
i = 0
n = len(unique)
while i < n:
start = unique[i]
end = start
j = i + 1
while j < n and unique[j] == end + 1:
end = unique[j]
j += 1
length = end - start + 1
if length >= 3:
parts.append(f"{start}..{end}")
elif length == 2:
parts.append(f"{start},{end}")
else:
parts.append(f"{start}")
i = j
return ",".join(parts)
``` Coding: Top customers Worked
```python
import re
_AMOUNT_RE = re.compile(
r"^\s*(?P<sign1>[+-]?)\s*(?:\$)?\s*(?P<sign2>[+-]?)\s*"
r"(?P<number>\d{1,3}(?:,\d{3})*(?:\.\d*)?|\d+(?:\.\d*)?|\.\d+)\s*$"
)
def _parse_amount(value: object) -> float | None:
if not isinstance(value, str):
return None
match = _AMOUNT_RE.fullmatch(value)
if match is None:
return None
sign1 = match.group("sign1")
sign2 = match.group("sign2")
if sign1 and sign2:
return None
number = match.group("number").replace(",", "")
try:
amount = float(number)
except (ValueError, OverflowError):
return None
sign = sign1 or sign2
if sign == "-":
return -amount
return amount
def top_customers(rows: list[dict], n: int) -> list[tuple[str, float]]:
if n <= 0:
return []
totals: dict[str, float] = {}
display_names: dict[str, str] = {}
for row in rows:
if not isinstance(row, dict):
continue
customer = row.get("customer")
if not isinstance(customer, str):
continue
name = customer.strip()
if not name:
continue
amount = _parse_amount(row.get("amount"))
if amount is None:
continue
key = name.casefold()
if key not in display_names:
display_names[key] = name
totals[key] = totals.get(key, 0.0) + amount
results = []
for key, total in totals.items():
rounded_total = round(total, 2)
if rounded_total == 0:
rounded_total = 0.0
results.append((display_names[key], rounded_total))
results.sort(key=lambda item: (-item[1], item[0]))
return results[:n]
``` Coding: Slugify Worked
```python
import re
import unicodedata
def slugify(title: str, max_len: int = 50) -> str:
normalized = unicodedata.normalize("NFD", title)
ascii_text = "".join(ch for ch in normalized if ch.isascii())
slug = re.sub(r"[^a-z0-9]+", "-", ascii_text.lower()).strip("-")
if not slug or max_len <= 0:
return ""
if len(slug) <= max_len:
return slug
words = slug.split("-")
if len(words[0]) > max_len:
return words[0][:max_len]
kept = [words[0]]
current_len = len(words[0])
for word in words[1:]:
candidate_len = current_len + 1 + len(word)
if candidate_len <= max_len:
kept.append(word)
current_len = candidate_len
else:
break
return "-".join(kept)
``` Coding: Token bucket Worked
```python
from collections.abc import Callable
class TokenBucket:
def __init__(self, capacity: float, refill_per_sec: float, clock: Callable[[], float]) -> None:
self._capacity = float(capacity)
self._refill_per_sec = float(refill_per_sec)
self._clock = clock
self._tokens = self._capacity
self._last_time = self._clock()
def _refill(self) -> float:
now = self._clock()
elapsed = now - self._last_time
if elapsed > 0.0:
self._tokens = min(self._capacity, self._tokens + elapsed * self._refill_per_sec)
self._last_time = now
return self._tokens
def allow(self, cost: float = 1) -> bool:
self._refill()
if cost <= 0.0:
return True
if self._tokens >= cost:
self._tokens -= cost
return True
return False
@property
def tokens(self) -> float:
self._refill()
return self._tokens
``` Decisions: Refund window (hard) 100% right
{
"outcome": {
"full_refund": 0.0,
"partial_refund": 0.0,
"store_credit": 1.0,
"nothing": 0.0
},
"defective": {
"true": 0.0,
"false": 1.0
}
} Decisions: Incident matrix (hard) 100% right
{
"severity": {
"0": 0,
"1": 1,
"2": 0,
"3": 0
},
"page": {
"true": 0,
"false": 1
}
} Decisions: Tool followup (hard) 50% right
{
"tool": {
"web_search": 0.0,
"calculator": 0.02,
"calendar": 0.98,
"email": 0.0,
"none": 0.0
},
"confirm": {
"true": 0.05,
"false": 0.95
}
} Decisions: Legit security alert (hard) 100% right
{
"phishing": {
"true": 0.01,
"false": 0.99
},
"action_needed": {
"true": 0.05,
"false": 0.95
}
} Decisions: Meeting slot (hard) 100% right
{
"slot": {
"A": 0.0,
"B": 1.0,
"C": 0.0,
"D": 0.0
},
"raj_last": {
"true": 1.0,
"false": 0.0
}
} Decisions: Review mixed (hard) 100% right
{
"hardware": {
"true": 0.95,
"false": 0.05
},
"support": {
"true": 0.85,
"false": 0.15
}
} Decisions: Support checkout down 100% right
{
"department": {
"billing": 0.04,
"technical": 0.95,
"account": 0.005,
"shipping": 0.005,
"sales": 0.005
},
"urgency": {
"0": 0,
"1": 0,
"2": 0.03,
"3": 0.97
},
"outage": {
"true": 0.98,
"false": 0.02
}
} Decisions: Refund wrong plan 100% right
{
"department": {
"billing": 0.95,
"technical": 0.01,
"account": 0.01,
"shipping": 0.00,
"sales": 0.03
},
"refund": {
"true": 0.99,
"false": 0.01
},
"tone": {
"frustrated": 0.05,
"calm": 0.95
}
} Decisions: Moderation doxxing 100% right
{"policy":{"none":0.02,"harassment":0.98,"hate":0.00,"spam":0.00,"self_harm":0.00},"personal_info":{"true":0.97,"false":0.03}} Decisions: Route calendar 100% right
{
"tool": {
"web_search": 0.02,
"calculator": 0.0,
"calendar": 0.94,
"email": 0.03,
"none": 0.01
},
"confirm": {
"true": 0.88,
"false": 0.12
}
} Decisions: Doc invoice missing due 100% right
{
"doc_type": {
"invoice": 1.0,
"resume": 0.0,
"contract": 0.0,
"bank_statement": 0.0,
"other": 0.0
},
"missing_due_date": {
"true": 1.0,
"false": 0.0
}
} Decisions: Phishing paypal 100% right
{
"phishing": {
"true": 0.99,
"false": 0.01
},
"risk": {
"0": 0.001,
"1": 0.004,
"2": 0.045,
"3": 0.95
}
} Decisions: Pii ssn email 100% right
{"data_kind":{"none":0.02,"contact":0.1,"financial":0.03,"government_id":0.85,"health":0.0},"sensitive":{"true":0.99,"false":0.01}} Decisions: Review mixed 100% right
{
"sentiment": {
"positive": 0.1,
"neutral": 0.0,
"negative": 0.9
},
"defect": {
"true": 0.99,
"false": 0.01
},
"recommend": {
"true": 0.02,
"false": 0.98
}
} Documents: Saas escalator (hard) 100% right
{
"year2_price_per_seat_month": 47.25,
"year3_price_per_seat_month": 47.25,
"year1_invoice": 58320.00,
"year2_invoice": 61236.00,
"addon_months_billed": 6,
"addon_invoice": 38556.00,
"year3_invoice": 134946.00,
"year3_discount_percent": 15,
"total_contract_value": 293058.00,
"contract_end_date": "2027-02-28"
} Documents: Expense thread 96% right
{
"employee_id": "EMP-20417",
"destination_city": "Lisbon",
"trip_start": "2025-02-24",
"trip_end": "2025-02-27",
"approved_items": [
{
"date": "2025-02-24",
"category": "airfare",
"amount_usd": 1184.60
},
{
"date": "2025-02-24",
"category": "ground_transport",
"amount_usd": 38.88
},
{
"date": "2025-02-25",
"category": "meals",
"amount_usd": 229.39
},
{
"date": "2025-02-26",
"category": "lodging",
"amount_usd": 466.56
},
{
"date": "2025-02-27",
"category": "ground_transport",
"amount_usd": 44.82
}
],
"rejected_item_count": 1,
"per_diem_days": 3,
"per_diem_usd": 65.00,
"total_reimbursable_usd": 2159.25,
"approver_email": "priya.raman@corvane.com"
} Documents: Lease amendment 100% right
{
"tenants": [
"Marcus Lin",
"Sofia Lin"
],
"landlord": "Ridgeline Property Group LLC",
"zip": "97205",
"lease_end": "2025-11-30",
"original_monthly_rent": 2150.00,
"monthly_rent_from_2025_06_01": 2236.00,
"late_fee_from_2025_06_01": 111.80,
"security_deposit": 2150.00,
"total_pet_deposits": 800.00,
"total_monthly_payment_july_2025": 2306.00,
"move_in_payment": 4700.00
} Documents: Ticket SLA 92% right
{
"ticket_id": "48213",
"account_id": "ACC-7731",
"open_issue": "inventory_sync",
"resolved_issues": [
"billing_address",
"invoice_pdf"
],
"affected_orders": [
"SO-99812",
"SO-99820",
"SO-99827"
],
"priority": "P2",
"sla_due_local": "2025-09-15T15:30",
"sla_due_utc": "2025-09-15T20:30:00Z",
"reissued_invoice": "INV-2025-0812"
} Documents: Sales footnotes 100% right
{
"q3_total_usd": 15346000,
"q2_total_usd": 14464000,
"q2_central_originally_reported_usd": 3047000,
"q2_to_q3_change_pct": 6.1,
"top_region_q3": "East",
"fastest_growing_region_q1_to_q3": "International",
"regions_declining_q2_to_q3": ["East"],
"international_q3_organic_usd": 1731000,
"west_excluding_mountain_q3_usd": 4201000
} Size: 28B parameters. First tested OCT 11.
Comments
Sign in with GitHub to comment. Spam and abuse are hidden automatically.