Review · updated OCT 12

Qwen3 Next 80B A3B Instruct review: not one we'd recommend right now

It scored 57 out of 100, #31 of 63. It solved 18 of 30 coding jobs and scored 54 on reading documents. Runs on a Mac with 96 GB.

The short version
  • Qwen3 Next 80B A3B Instruct is a free model from Alibaba's Qwen team that you can run on your own computer. In our tests it's not one we'd recommend right now: 57 out of 100, #31 of 63.
  • It solved 18 of 30 coding jobs and scored 54 on reading documents. On our hardest tasks it scored 35.
  • 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. Qwen3 Next 80B A3B Instruct got 18 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. Qwen3 Next 80B A3B Instruct scored 54; the best model scored 100.

TestScorePublic questionsSecret questions
Coding605761
Reading documents546750
Decisions848285

On the 18 hardest tasks (included in the scores above) it scored 35. 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 Qwen3 Next 80B A3B Instruct in several sizes. Smaller versions fit on more computers and lose a little quality, so pick the biggest one that fits.

Your computerRuns it?Version to download
Graphics card, 8 GBNo–
Graphics card, 12 GBNo–
Graphics card, 16 GBNo–
Graphics card, 24 GBNo–
Graphics card, 32 GBNo–
Mac, 16 GBNo–
Mac, 24 GBNo–
Mac, 32 GBNo–
Mac, 48 GBNo–
Mac, 64 GBNo–
Mac, 96 GBYesQ6
Mac, 128 GBYesQ8

For normal-length chats. Long documents need more memory. Q4 or Q8? · How we worked this out

How to try it

Search for "Qwen3 Next 80B A3B 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 Qwen3 Next 80B A3B Instruct wrote for our public questions, word for word.

Coding: INI parser (hard) Didn't work
```python
import re
from typing import Dict, Any

def parse_ini(text: str) -> Dict[str, Dict[str, str]]:
    lines = text.splitlines()
    result: Dict[str, Dict[str, str]] = {}
    current_section = None
    last_key = None
    default_section: Dict[str, str] = {}
    
    # Track interpolation references to detect cycles
    interpolation_stack = set()
    
    def interpolate_value(value: str, section: str, seen: set) -> str:
        if not value:
            return value
            
        # Detect recursion
        if (section, value) in seen:
            raise ValueError("Interpolation cycle detected")
        seen.add((section, value))
        
        # Find all ${key} and ${section:key} patterns
        pattern = r'\$\{([^}]+)\}'
        matches = list(re.finditer(pattern, value))
        
        if not matches:
            return value
            
        # Process from right to left to avoid index shifting issues
        result_str = value
        for match in reversed(matches):
            full_match = match.group(0)
            key_ref = match.group(1)
            
            # Parse section:key or just key
            if ':' in key_ref:
                ref_section, ref_key = key_ref.split(':', 1)
                ref_section = ref_section.strip()
                ref_key = ref_key.strip()
            else:
                ref_section = section
                ref_key = key_ref.strip()
            
            # Check if key exists in current section or inherited from DEFAULT
            if ref_section not in result and ref_section != "DEFAULT":
                raise KeyError(f"Section '{ref_section}' not found")
            
            # Look for the key in the specified section
            target_section = result.get(ref_section, {})
            if ref_section == "DEFAULT":
                target_section = default_section
            
            if ref_key.lower() not in target_section:
                raise KeyError(f"Key '{ref_key}' not found in section '{ref_section}'")
            
            # Get the value and recursively interpolate it
            target_value = target_section[ref_key.lower()]
            interpolated_value = interpolate_value(target_value, ref_section, seen.copy())
            
            # Replace the reference
            result_str = result_str[:match.start()] + interpolated_value + result_str[match.end():]
        
        return result_str
    
    for line_num, line in enumerate(lines, 1):
        # Skip empty lines and comments
        stripped_line = line.strip()
        if not stripped_line or stripped_line.startswith((';', '#')):
            continue
            
        # Check for section header
        if stripped_line.startswith('[') and stripped_line.endswith(']'):
            section_name = stripped_line[1:-1].strip()
            if section_name == "DEFAULT":
                current_section = "DEFAULT"
                continue
            else:
                current_section = section_name
                result[section_name] = {}
                last_key = None
                continue
                
        # Check for key-value pair
        if '=' in line or ':' in line:
            # Find the first occurrence of = or :
            eq_pos = line.find('=')
            colon_pos = line.find(':')
            
            if eq_pos == -1:
                sep_pos = colon_pos
                sep_char = ':'
            elif colon_pos == -1:
                sep_pos = eq_pos
                sep_char = '='
            else:
                sep_pos = min(eq_pos, colon_pos)
                sep_char = line[sep_pos]
                
            # Check if separator is inside quotes
            quote_count = 0
            in_quotes = False
            for i, char in enumerate(line):
                if char == '"' and (i == 0 or line[i-1] != '\\'):
                    in_quotes = not in_quotes
                if i == sep_pos and in_quotes:
                    # Separator is inside quotes, continue searching
                    if sep_char == '=':
                        eq_pos = line.find('=', i + 1)
                        if eq_pos != -1:
                            sep_pos = eq_pos
                            sep_char = '='
                    else:
                        colon_pos = line.find(':', i + 1)
                        if colon_pos != -1:
                            sep_pos = colon_pos
                            sep_char = ':'
                    break
            
            # Split on the separator
            key_part = line[:sep_pos]
            value_part = line[sep_pos + 1:]
            
            # Extract key and value
            key = key_part.strip().lower()
            value = value_part.rstrip()
            
            # Check for inline comments outside quotes
            in_quotes = False
            for i, char in enumerate(value):
                if char == '"' and (i == 0 or value[i-1] != '\\'):
                    in_quotes = not in_quotes
                elif not in_quotes and char in ';#' and (i == 0 or value[i-1] == ' '):
                    value = value[:i]
                    break
                    
            # Handle quoted values
            if value.startswith('"') and value.endswith('"') and len(value) >= 2:
                # Remove quotes but preserve content exactly
                value = value[1:-1]
            else:
                # Strip whitespace for non-quoted values
                value = value.strip()
            
            # Add to current section
            if current_section is None:
                raise ValueError("Key-value pair found before any section header")
                
            if current_section == "DEFAULT":
                default_section[key] = value
            else:
                result[current_section][key] = value
            last_key = key
            continue
            
        # Handle continuation lines (starting with whitespace)
        if line.startswith((' ', '\t')) and last_key is not None:
            # Remove leading whitespace and handle inline comments
            stripped_line = line.lstrip()
            in_quotes = False
            for i, char in enumerate(stripped_line):
                if char == '"' and (i == 0 or stripped_line[i-1] != '\\'):
                    in_quotes = not in_quotes
                elif not in_quotes and char in ';#' and (i == 0 or stripped_line[i-1] == ' '):
                    stripped_line = stripped_line[:i]
                    break
                    
            # Append to previous value with newline
            if current_section == "DEFAULT":
                default_section[last_key] += "\n" + stripped_line
            else:
                result[current_section][last_key] += "\n" + stripped_line
            continue
            
        # Malformed line
        raise ValueError(f"Malformed line at line {line_num}: {line}")
    
    # Apply DEFAULT section inheritance
    for section_name, section_data in result.items():
        for key, value in default_section.items():
            if key not in section_data:
                section_data[key] = value
    
    # Perform interpolation on all values
    for section_name, section_data in result.items():
        for key, value in list(section_data.items()):
            try:
                interpolation_stack.clear()
                interpolated_value = interpolate_value(value, section_name, interpolation_stack)
                section_data[key] = interpolated_value
            except ValueError as e:
                raise ValueError(f"Interpolation error in section '{section_name}', key '{key}': {str(e)}")
            except KeyError as e:
                raise KeyError(f"Interpolation error in section '{section_name}', key '{key}': {str(e)}")
    
    return result
```
Coding: TTL LRU (hard) Didn't work
```python
import time
from collections import OrderedDict

class TTLCache:
    def __init__(self, capacity: int, ttl: float, clock):
        self.capacity = capacity
        self.ttl = ttl
        self.clock = clock
        self.cache = OrderedDict()
        self.stats = {"hits": 0, "misses": 0, "evictions": 0, "expirations": 0}
    
    def put(self, key, value):
        if self.capacity == 0:
            return
        
        now = self.clock()
        
        # If key exists, update it (refreshes recency and expiry)
        if key in self.cache:
            self.cache.move_to_end(key)
            self.cache[key] = (value, now + self.ttl)
            return
        
        # For new key, first remove expired entries
        self._remove_expired(now)
        
        # If still full, evict LRU
        if len(self.cache) >= self.capacity:
            oldest_key = next(iter(self.cache))
            del self.cache[oldest_key]
            self.stats["evictions"] += 1
        
        # Insert new entry
        self.cache[key] = (value, now + self.ttl)
    
    def get(self, key, default=None):
        if self.capacity == 0:
            self.stats["misses"] += 1
            return default
        
        now = self.clock()
        
        # Remove expired entries if present
        if key in self.cache:
            value, expiry = self.cache[key]
            if now >= expiry:
                del self.cache[key]
                self.stats["expirations"] += 1
                self.stats["misses"] += 1
                return default
            else:
                # Mark as most recently used
                self.cache.move_to_end(key)
                self.stats["hits"] += 1
                return value
        
        # Key not found or expired (already handled above)
        self.stats["misses"] += 1
        return default
    
    def __len__(self):
        now = self.clock()
        self._remove_expired(now)
        return len(self.cache)
    
    def _remove_expired(self, now):
        # Remove all expired entries
        expired_keys = [k for k, (_, expiry) in self.cache.items() if now >= expiry]
        for key in expired_keys:
            del self.cache[key]
            self.stats["expirations"] += 1
```
Coding: Parse duration Didn't work
```python
import re

def parse_duration(s: str) -> int:
    if not s or s.strip() == "":
        raise ValueError("empty string")
    
    # Normalize whitespace: replace multiple spaces with single space and strip
    s = ' '.join(s.split())
    
    # Split by whitespace to get individual parts
    parts = s.split()
    
    # Define valid units and their multipliers
    unit_multipliers = {'h': 3600, 'm': 60, 's': 1}
    unit_names = {'h': 'h', 'm': 'm', 's': 's', 'H': 'h', 'M': 'm', 'S': 's'}
    
    # Track which units we've seen
    seen_units = set()
    total_seconds = 0
    
    for part in parts:
        # Match a number followed by a unit (case-insensitive)
        match = re.match(r'^(\d+)([hmsHMS])$', part)
        if not match:
            raise ValueError("invalid format")
        
        num_str, unit_char = match.groups()
        unit = unit_names[unit_char]
        
        # Check for repeated units
        if unit in seen_units:
            raise ValueError("repeated unit")
        
        # Check unit order: must be h, then m, then s
        unit_order = ['h', 'm', 's']
        if seen_units:
            # The current unit must come after all previously seen units in order
            last_seen_unit_index = -1
            for u in seen_units:
                last_seen_unit_index = max(last_seen_unit_index, unit_order.index(u))
            if unit_order.index(unit) <= last_seen_unit_index:
                raise ValueError("units out of order")
        
        # Convert to integer
        num = int(num_str)
        if num < 0:
            raise ValueError("negative number")
        
        total_seconds += num * unit_multipliers[unit]
        seen_units.add(unit)
    
    # If no valid parts were found
    if not seen_units:
        raise ValueError("no valid units found")
    
    return total_seconds
```
Coding: Summarize ranges Worked
```python
def summarize_ranges(nums: list[int]) -> str:
    if not nums:
        return ""
    
    # Sort and remove duplicates
    nums = sorted(set(nums))
    
    if not nums:
        return ""
    
    result = []
    start = nums[0]
    end = nums[0]
    
    for i in range(1, len(nums)):
        if nums[i] == end + 1:
            # Continue the run
            end = nums[i]
        else:
            # End of current run
            if end - start >= 2:  # 3 or more consecutive numbers
                result.append(f"{start}..{end}")
            elif end - start == 1:  # 2 consecutive numbers
                result.append(str(start))
                result.append(str(end))
            else:  # Single number
                result.append(str(start))
            start = nums[i]
            end = nums[i]
    
    # Handle the last run
    if end - start >= 2:
        result.append(f"{start}..{end}")
    elif end - start == 1:
        result.append(str(start))
        result.append(str(end))
    else:
        result.append(str(start))
    
    return ",".join(result)
```
Coding: Top customers Worked
```python
import re

def top_customers(rows: list[dict], n: int) -> list[tuple[str, float]]:
    customer_totals = {}
    customer_names = {}
    
    for row in rows:
        customer = row.get("customer", "").strip()
        amount_str = row.get("amount", "").strip()
        
        if not customer:
            continue
            
        # Clean and parse amount
        cleaned_amount = amount_str.replace('$', '').replace(',', '')
        try:
            amount = float(cleaned_amount)
        except ValueError:
            continue
            
        # Use first valid occurrence's spelling (trimmed)
        customer_lower = customer.lower()
        if customer_lower not in customer_names:
            customer_names[customer_lower] = customer
        if customer_lower not in customer_totals:
            customer_totals[customer_lower] = 0.0
        customer_totals[customer_lower] += amount
    
    # Convert to list of tuples with original trimmed names
    result = [
        (customer_names[lower_name], round(total, 2))
        for lower_name, total in customer_totals.items()
    ]
    
    # Sort by total descending, then by name ascending
    result.sort(key=lambda x: (-x[1], x[0]))
    
    return result[:n]
```
Coding: Slugify Worked
```python
import re
import unicodedata

def slugify(title: str, max_len: int = 50) -> str:
    # Step 1: Transliterate accented characters to ASCII and drop other non-ASCII
    normalized = unicodedata.normalize('NFKD', title)
    ascii_only = ''.join(c for c in normalized if ord(c) < 128)
    
    # Step 2: Lowercase
    lowercased = ascii_only.lower()
    
    # Step 3: Replace runs of non-alphanumeric characters with single "-", strip leading/trailing "-"
    cleaned = re.sub(r'[^a-z0-9]+', '-', lowercased)
    cleaned = cleaned.strip('-')
    
    # Step 4: If longer than max_len, shorten without cutting a word
    if len(cleaned) <= max_len:
        return cleaned
    
    # Split by '-' to get words
    words = cleaned.split('-')
    
    # If first word is longer than max_len, hard-cut it
    if len(words[0]) > max_len:
        return words[0][:max_len]
    
    # Otherwise, build up the slug by adding words until we exceed max_len
    result = []
    current_length = 0
    
    for word in words:
        # Calculate length if we add this word (with a '-' if not first)
        new_length = current_length + len(word) + (1 if result else 0)
        
        if new_length <= max_len:
            result.append(word)
            current_length = new_length
        else:
            break
    
    return '-'.join(result)
```
Coding: Token bucket Worked
```python
import time

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._tokens = capacity
        self._last_refill_time = clock()

    @property
    def tokens(self):
        current_time = self._clock()
        elapsed = current_time - self._last_refill_time
        self._tokens = min(self._capacity, self._tokens + elapsed * self._refill_per_sec)
        self._last_refill_time = current_time
        return self._tokens

    def allow(self, cost: float = 1) -> bool:
        current_time = self._clock()
        elapsed = current_time - self._last_refill_time
        self._tokens = min(self._capacity, self._tokens + elapsed * self._refill_per_sec)
        self._last_refill_time = current_time
        
        if self._tokens >= cost:
            self._tokens -= cost
            return True
        return False
```
Decisions: Refund window (hard) 50% right
{
  "outcome": {
    "full_refund": 0.0,
    "partial_refund": 1.0,
    "store_credit": 0.0,
    "nothing": 0.0
  },
  "defective": {
    "true": 0.0,
    "false": 1.0
  }
}
Decisions: Incident matrix (hard) 0% right
{
  "severity": {
    "0": 0.0,
    "1": 0.1,
    "2": 0.8,
    "3": 0.1
  },
  "page": {
    "true": 0.9,
    "false": 0.1
  }
}
Decisions: Tool followup (hard) 100% right
{
  "tool": {
    "web_search": 0.0,
    "calculator": 0.0,
    "calendar": 1.0,
    "email": 0.0,
    "none": 0.0
  },
  "confirm": {
    "true": 1.0,
    "false": 0.0
  }
}
Decisions: Legit security alert (hard) 100% right
{
  "phishing": {
    "true": 0.05,
    "false": 0.95
  },
  "action_needed": {
    "true": 0.1,
    "false": 0.9
  }
}
Decisions: Meeting slot (hard) 0% right
{
  "slot": {
    "A": 0.0,
    "B": 0.0,
    "C": 1.0,
    "D": 0.0
  },
  "raj_last": {
    "true": false,
    "false": 1.0
  }
}
Decisions: Review mixed (hard) 100% right
{
  "hardware": {
    "true": 0.95,
    "false": 0.05
  },
  "support": {
    "true": 0.98,
    "false": 0.02
  }
}
Decisions: Support checkout down 100% right
{
  "department": {
    "billing": 0.0,
    "technical": 1.0,
    "account": 0.0,
    "shipping": 0.0,
    "sales": 0.0
  },
  "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,
    "technical": 0.0,
    "account": 0.0,
    "shipping": 0.0,
    "sales": 0.0
  },
  "refund": {
    "true": 1.0,
    "false": 0.0
  },
  "tone": {
    "frustrated": 0.0,
    "calm": 1.0
  }
}
Decisions: Moderation doxxing 100% right
{
  "policy": {
    "none": 0.0,
    "harassment": 1.0,
    "hate": 0.0,
    "spam": 0.0,
    "self_harm": 0.0
  },
  "personal_info": {
    "true": 1.0,
    "false": 0.0
  }
}
Decisions: Route calendar 100% right
{
  "tool": {
    "web_search": 0.0,
    "calculator": 0.0,
    "calendar": 1.0,
    "email": 0.0,
    "none": 0.0
  },
  "confirm": {
    "true": 1.0,
    "false": 0.0
  }
}
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": 1.0,
    "false": 0.0
  },
  "risk": {
    "0": 0.0,
    "1": 0.0,
    "2": 0.0,
    "3": 1.0
  }
}
Decisions: Pii ssn email 100% right
{
  "data_kind": {
    "none": 0.0,
    "contact": 0.0,
    "financial": 0.0,
    "government_id": 1.0,
    "health": 0.0
  },
  "sensitive": {
    "true": 1.0,
    "false": 0.0
  }
}
Decisions: Review mixed 100% right
{
  "sentiment": {
    "positive": 0.1,
    "neutral": 0.1,
    "negative": 0.8
  },
  "defect": {
    "true": 0.95,
    "false": 0.05
  },
  "recommend": {
    "true": 0.05,
    "false": 0.95
  }
}
Documents: Saas escalator (hard) 40% right
{
  "year2_price_per_seat_month": 47.25,
  "year3_price_per_seat_month": 47.25,
  "year1_invoice": 48600,
  "year2_invoice": 50940,
  "addon_months_billed": 5,
  "addon_invoice": 3780,
  "year3_invoice": 50940,
  "year3_discount_percent": 15,
  "total_contract_value": 141360,
  "contract_end_date": "2027-02-28"
}
Documents: Expense thread 79% 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": 477.36
    },
    {
      "date": "2025-02-27",
      "category": "ground_transport",
      "amount_usd": 44.82
    }
  ],
  "rejected_item_count": 2,
  "per_diem_days": 2,
  "per_diem_usd": 130,
  "total_reimbursable_usd": 2104.85,
  "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,
  "monthly_rent_from_2025_06_01": 2236,
  "late_fee_from_2025_06_01": 111.8,
  "security_deposit": 2150,
  "total_pet_deposits": 800,
  "total_monthly_payment_july_2025": 2306,
  "move_in_payment": 4700
}
Documents: Ticket SLA 82% right
{
  "ticket_id": "48213",
  "account_id": "ACC-7731",
  "open_issue": "inventory_sync",
  "resolved_issues": ["billing_address"],
  "affected_orders": ["SO-99812", "SO-99820", "SO-99827"],
  "priority": "P2",
  "sla_due_local": "2025-09-12T23:30",
  "sla_due_utc": "2025-09-13T04:30:00Z",
  "reissued_invoice": "INV-2025-0812"
}
Documents: Sales footnotes 33% right
{
  "q3_total_usd": 15346,
  "q2_total_usd": 14464,
  "q2_central_originally_reported_usd": 3047,
  "q2_to_q3_change_pct": 6.0,
  "top_region_q3": "East",
  "fastest_growing_region_q1_to_q3": "International",
  "regions_declining_q2_to_q3": [
    "East"
  ],
  "international_q3_organic_usd": 1731,
  "west_excluding_mountain_q3_usd": 4201
}

Size: 81B parameters. First tested OCT 11.

Models that scored about the same

Comments

Sign in with GitHub to comment. Spam and abuse are hidden automatically.