- Hunyuan A13B Instruct is a free model from Tencent that you can run on your own computer. In our tests it's not one we'd recommend right now: 52 out of 100, #29 of 56.
- It solved 11 of 30 coding jobs and scored 67 on reading documents. On our hardest tasks it scored 28.
- Runs on a Mac with 96 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. Hunyuan A13B Instruct got 11 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. Hunyuan A13B Instruct scored 67; the best model scored 100.
| Test | Score | Public questions | Secret questions |
|---|---|---|---|
| Coding | 37 | 57 | 30 |
| Reading documents | 67 | 83 | 63 |
| Decisions | 74 | 66 | 76 |
On the 18 hardest tasks (included in the scores above) it scored 28. 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?
You can download Hunyuan A13B Instruct in several sizes. Smaller versions fit on more computers and lose a little quality, so pick the biggest one that fits.
| Your computer | Runs it? | Version to download |
|---|---|---|
| Graphics card, 8 GB | No | – |
| Graphics card, 12 GB | No | – |
| Graphics card, 16 GB | No | – |
| Graphics card, 24 GB | No | – |
| Graphics card, 32 GB | No | – |
| Mac, 16 GB | No | – |
| Mac, 24 GB | No | – |
| Mac, 32 GB | No | – |
| Mac, 48 GB | No | – |
| Mac, 64 GB | No | – |
| Mac, 96 GB | Yes | Q6 |
| Mac, 128 GB | Yes | Q8 |
For normal-length chats. Long documents need more memory. Q4 or Q8? · How we worked this out
How to try it
Search for "Hunyuan A13B Instruct 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 Hunyuan A13B Instruct wrote for our public questions, word for word.
Coding: INI parser (hard) Didn't work
```python
import re
from typing import Dict
def parse_ini(text: str) -> Dict[str, Dict[str, str]]:
def interpolate(value_part: str, current_section: str, sections: Dict[str, Dict]) -> str:
visited = set()
pattern = re.compile(r'\$\{([^}]+)\}')
def replace(match: re.Match) -> str:
content = match.group(1)
parts = content.split(':', 1)
# Check for cycles
if len(parts) == 1:
key = parts[0]
if key in visited:
raise ValueError(f"Interpolation cycle detected: {visited} -> {key}")
new_visited = visited.copy()
new_visited.add(key)
else:
section_name, key = parts
if section_name in visited:
raise ValueError(f"Interpolation cycle detected: {visited} -> {section_name}:{key}")
new_visited = visited.copy()
new_visited.add(section_name)
# Resolve value
if len(parts) == 1:
# Key lookup in current section or DEFAULT
val = sections[current_section].get(key)
if val is None:
val = sections.get('DEFAULT', {}).get(key)
else:
# Section:key lookup
section = sections.get(section_name)
if section is None:
raise KeyError(f"Section '{section_name}' not found")
val = section.get(key)
if val is None:
val = sections.get('DEFAULT', {}).get(key)
# Handle missing key/section
if val is None:
if len(parts) == 1:
if val is None:
raise KeyError(f"Key '{key}' not found in section '{current_section}' or DEFAULT")
else:
if val is None:
raise KeyError(f"Key '{key}' not found in section '{section_name}' or DEFAULT")
# Recursively resolve if string
if isinstance(val, str):
return replace(pattern, val)
return str(val)
return replace(pattern, value_part)
sections = {'DEFAULT': {}}
result = {}
current_section = None
prev_was_key = False
last_key = None
lines = [line.rstrip('\n') for line in text.splitlines()]
for line in lines:
stripped_line = line.strip()
if not stripped_line or stripped_line.startswith(';') or stripped_line.startswith('#'):
continue # Rule 3: ignore empty/comments
section_match = re.match(r'\s*\[([^\]]+)\s*\]', line)
if section_match is not None:
# Rule 1: process section header
current_section = section_match.group(1)
prev_was_key = False
if current_section != 'DEFAULT' and current_section not in sections:
sections[current_section] = {}
continue
if prev_was_key:
if line.startswith(' '):
# Rule 6: continuation line
continuation_text = line.lstrip()
current_section_dict = sections.get(current_section)
if current_section_dict is None:
raise ValueError("Section not found (internal error)")
current_section_dict[last_key] += '\n' + continuation_text
else:
raise ValueError(f"Malformed line (continuation without key)")
else:
# Rule 10: key line or malformed
eq_pos = line.find('=')
colon_pos = line.find(':')
separator_pos = min(eq_pos, colon_pos) if eq_pos != -1 or colon_pos != -1 else -1
if separator_pos == -1:
raise ValueError("Malformed line: no separator found")
key_part = line[:separator_pos].strip()
value_part = line[separator_pos+1:].lstrip() # Rule 2: value stripped
if not key_part:
raise ValueError("Malformed line: empty key")
key = key_part.lower() # Rule 2: key lowercased
try:
value = interpolate(value_part, current_section, sections)
except (KeyError, ValueError) as e:
raise ValueError(f"Line {text.splitlines().index(line) + 1}: {e}") from e
if current_section == 'DEFAULT':
sections['DEFAULT'][key] = value
else:
sections.setdefault(current_section, {})[key] = value
last_key = key
prev_was_key = True
# Build result by merging sections with DEFAULT (Rule 8)
for section_name in sections:
if section_name == 'DEFAULT':
continue
merged = sections['DEFAULT'].copy()
merged.update(sections[section_name])
result[section_name] = merged
return result
```
Coding: TTL LRU (hard) Didn't work
```python
class TTLCache:
class Node:
def __init__(self, key, value, expiry):
self.key = key
self.value = value
self.expiry = expiry
self.prev = None
self.next = None
def __init__(self, capacity: int, ttl: float, clock):
self.capacity = capacity
self.ttl = ttl
self.clock = clock
self.cache = dict()
self.head = None # Most Recently Used
self.tail = None # Least Recently Used
self.stats = {
"hits": 0,
"misses": 0,
"evictions": 0,
"expirations": 0,
}
def _remove_node(self, node):
if node.prev:
node.prev.next = node.next
else:
self.head = node.next
if node.next:
node.next.prev = node.prev
else:
self.tail = node.prev
def _move_to_front(self, node):
if node == self.head:
return
self._remove_node(node)
node.next = self.head
self.head.prev = node
self.head = node
if node.prev is None:
self.tail = node
def _add_to_front(self, key, value, expiry):
new_node = self.Node(key, value, expiry)
new_node.next = self.head
if self.head:
self.head.prev = new_node
self.head = new_node
if self.tail is None:
self.tail = new_node
def put(self, key, value):
if self.capacity == 0:
return
now = self.clock()
if key in self.cache:
node = self.cache[key]
node.value = value
node.expiry = now + self.ttl
self._move_to_front(node)
else:
expired = []
current = self.head
while current:
next_node = current.next
if current.expiry <= now:
expired.append(current)
self._remove_node(current)
current = next_node
self.stats["expirations"] += len(expired)
current_size = len(self.cache)
if current_size + 1 > self.capacity:
if self.tail:
evicted_node = self.tail
self._remove_node(evicted_node)
del self.cache[evicted_node.key]
self.stats["evictions"] += 1
self._add_to_front(key, value, now + self.ttl)
def get(self, key, default=None):
if self.capacity == 0:
return default
now = self.clock()
if key not in self.cache:
self.stats["misses"] += 1
return default
node = self.cache[key]
if node.expiry <= now:
self._remove_node(node)
del self.cache[key]
self.stats["expirations"] += 1
self.stats["misses"] += 1
return default
self._move_to_front(node)
self.stats["hits"] += 1
return node.value
def __len__(self):
if self.capacity == 0:
return 0
now = self.clock()
count = 0
current = self.head
while current:
if current.expiry > now:
count += 1
current = current.next
return count
def stats(self):
return {
"hits": self.stats["hits"],
"misses": self.stats["misses"],
"evictions": self.stats["evictions"],
"expirations": self.stats["expirations"],
}
```
Coding: Parse duration Worked
```python
def parse_duration(s: str) -> int:
if not s.strip():
raise ValueError("empty string")
i = 0
parts = []
n = len(s)
units_order = {'h', 'm', 's'}
priorities = {'h': 0, 'm': 1, 's': 2}
while i < n:
# Skip leading whitespace
while i < n and s[i].isspace():
i += 1
if i >= n:
break # Shouldn't occur due to initial check but handle defensively
# Extract number part
j = i
while j < n and s[j].isdigit():
j += 1
if j == i:
raise ValueError("number without unit")
num = int(s[i:j])
i = j
# Skip whitespace after number
while i < n and s[i].isspace():
i += 1
if i >= n:
raise ValueError("number without unit")
# Extract unit
unit_char = s[i]
unit = unit_char.lower()
if unit not in units_order:
raise ValueError("unknown unit")
i += 1
# Validate unit order and uniqueness
if parts:
prev_unit = parts[-1][1]
prev_prio = priorities[prev_unit]
curr_prio = priorities[unit]
if curr_prio < prev_prio:
raise ValueError("units out of order")
if curr_prio == prev_prio:
raise ValueError("repeated unit")
parts.append((num, unit))
# Calculate total seconds
total = 0
for num, unit in parts:
if unit == 'h':
total += num * 3600
elif unit == 'm':
total += num * 60
else:
total += num
return total
```
Coding: Summarize ranges Worked
```python
def summarize_ranges(nums: list[int]) -> str:
sorted_unique = sorted(set(nums))
if not sorted_unique:
return ""
groups = []
current_group = [sorted_unique[0]]
for num in sorted_unique[1:]:
if num == current_group[-1] + 1:
current_group.append(num)
else:
groups.append(current_group)
current_group = [num]
groups.append(current_group)
result = []
for group in groups:
if len(group) >= 3:
result.append(f"{group[0]}..{group[-1]}")
else:
result.extend(map(str, group))
return ",".join(result)
```
Coding: Top customers Worked
```python
def top_customers(rows: list[dict], n: int) -> list[tuple[str, float]]:
customer_totals = {} # Key: lowercase trimmed name, Value: (original trimmed name, total)
for row in rows:
# Process customer name
customer = row.get("customer", "").strip()
if not customer:
continue # Skip rows with empty customer after trimming
# Process and parse amount
amount_str = row.get("amount", "").strip()
sign = 1
amount_str_processed = amount_str
# Handle negative amounts
if amount_str_processed.startswith('-'):
sign = -1
amount_str_processed = amount_str_processed[1:]
# Remove dollar sign if present
if amount_str_processed.startswith('$'):
amount_str_processed = amount_str_processed[1:]
# Remove commas (thousand separators)
amount_str_clean = amount_str_processed.replace(',', '')
# Validate and convert to float
try:
amount = sign * float(amount_str_clean)
except ValueError:
continue # Skip rows with unparseable amounts
# Update customer totals
key = customer.lower()
if key not in customer_totals:
customer_totals[key] = (customer, amount)
else:
existing_name, existing_total = customer_totals[key]
customer_totals[key] = (existing_name, existing_total + amount)
# Extract and sort customers
sorted_customers = sorted(
[(name, total) for (_, (name, total)) in customer_totals.items()],
key=lambda x: (-x[1], x[0]) # Descending total, ascending name
)
# Take top n and round totals to 2 decimals
top_n = sorted_customers[:n]
return [(name, round(total, 2)) for name, total in top_n]
```
Coding: Slugify Didn't work
```python
import unicodedata
def slugify(title: str, max_len: int = 50) -> str:
# Step 1 & 2: Transliterate accented characters to ASCII, remove non-ASCII, and lowercase
normalized = unicodedata.normalize('NFKD', title)
transliterated = []
for c in normalized:
if ord(c) < 128: # Keep only ASCII characters
transliterated.append(c.lower())
transliterated_str = ''.join(transliterated)
# Step 3: Replace non-alphanumeric characters with '-', strip leading/trailing dashes
slug = re.sub(r'[^a-z0-9]+', '-', transliterated_str).strip('-')
# Handle empty case immediately
if not slug:
return slug
# Step 4: Truncate to max_len without splitting words
words = slug.split('-')
current_length = 0
result_words = []
for word in words:
# Calculate tentative length if we add this word (including dashes)
tentative_length = current_length + len(word) + len(result_words)
if tentative_length > max_len:
if not result_words:
# First word is too long, cut to max_len (handle max_len=0)
return word[:max_len] if max_len > 0 else ''
else:
# Can't add this word, return current prefix
return '-'.join(result_words)
else:
result_words.append(word)
current_length = tentative_length
# Join the remaining valid words
return '-'.join(result_words)
```
Coding: Token bucket Worked
```python
class TokenBucket:
def __init__(self, capacity: float, refill_per_sec: float, clock):
self.capacity = capacity
self.refill_per_sec = refill_per_sec
self.clock = clock
self._last_refill_time = clock()
self._tokens = capacity # Start with full bucket
def _refill(self) -> float:
"""Refill the bucket based on elapsed time and update state."""
current_time = self.clock()
elapsed = current_time - self._last_refill_time
tokens_added = elapsed * self.refill_per_sec
self._tokens = min(self._tokens + tokens_added, self.capacity)
self._last_refill_time = current_time
return self._tokens
@property
def tokens(self) -> float:
"""Return current token count after refill."""
return self._refill()
def allow(self, cost: float = 1) -> bool:
"""Refill then check/allow consumption of 'cost' tokens."""
tokens = self._refill()
if tokens >= cost:
self._tokens -= cost
return True
return False
```
Decisions: Refund window (hard) 100% right
{"outcome": {"full_refund": 0, "partial_refund": 0, "store_credit": 1, "nothing": 0}, "defective": {"true": 0, "false": 1}}
Decisions: Incident matrix (hard) 50% right
{"severity": {"3": 1.0}, "page": {"false": 1.0}}
Decisions: Tool followup (hard) 0% right
```json
{
"tool": "calendar",
"confirm": "true"
}
```
Decisions: Legit security alert (hard) 0% right
{"phishing": false, "action_needed": false}
Decisions: Meeting slot (hard) 50% right
{"slot": {"A": 0.0, "B": 1.0, "C": 0.0, "D": 0.0}, "raj_last": true}
Decisions: Review mixed (hard) 100% right
{"hardware": {"true": 1.0}, "support": {"true": 1.0}}
Decisions: Support checkout down 67% right
{
"department": {
"billing": 0.9,
"technical": 0.05,
"account": 0.03,
"shipping": 0.01,
"sales": 0.01
},
"urgency": {
"0": 0.0,
"1": 0.0,
"2": 0.0,
"3": 1.0
},
"outage": {
"true": 1.0,
"false": 0.0
}
}
Decisions: Refund wrong plan 100% right
{"department": {"billing": 1.0}, "refund": {"true": 1.0}, "tone": {"calm": 1.0}}
Decisions: Moderation doxxing 100% right
{"policy": {"harassment": 1.0}, "personal_info": {"true": 1.0}}
Decisions: Route calendar 0% right
```json
{
"tool": "calendar",
"confirm": "true"
}
```
Decisions: Doc invoice missing due 50% right
{
"doc_type": {"invoice": 0.99, "resume": 0.0, "contract": 0.01, "bank_statement": 0.0, "other": 0.0},
"missing_due_date": {"true": 0.05, "false": 0.95}
}
Decisions: Phishing paypal 100% right
{"phishing": {"true": 0.95, "false": 0.05}, "risk": {"0": 0.02, "1": 0.02, "2": 0.02, "3": 0.94}}
Decisions: Pii ssn email 100% right
{"data_kind": {"none": 0.05, "contact": 0.3, "financial": 0.1, "government_id": 0.5, "health": 0.05}, "sensitive": {"true": 1.0, "false": 0.0}}
Decisions: Review mixed 100% right
{"sentiment": {"negative": 1.0}, "defect": {"true": 1.0}, "recommend": {"false": 1.0}}
Documents: Saas escalator (hard) 30% right
{"year2_price_per_seat_month": 47.75, "year3_price_per_seat_month": 47.75, "year1_invoice": 4860.0, "year2_invoice": 5157.0, "addon_months_billed": 6, "addon_invoice": 38964.0, "year3_invoice": 11364.5, "year3_discount_percent": 15.0, "total_contract_value": 60345.5, "contract_end_date": "2027-02-28"}
Documents: Expense thread 100% 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.6
},
{
"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": 195.0,
"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 83% 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-15T19: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: 80B parameters. First tested OCT 11.
Comments
Sign in with GitHub to comment. Spam and abuse are hidden automatically.