Review · updated OCT 11

Qwen3 30B A3B Instruct 2507 review: not one we'd recommend right now

It scored 40 out of 100, #39 of 56. It solved 10 of 30 coding jobs and scored 46 on reading documents. Runs on a 24 GB graphics card or a Mac with 32 GB.

The short version
  • Qwen3 30B A3B Instruct 2507 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: 40 out of 100, #39 of 56.
  • It solved 10 of 30 coding jobs and scored 46 on reading documents. On our hardest tasks it scored 25.
  • Runs on a 24 GB graphics card or a Mac with 32 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 30B A3B Instruct 2507 got 10 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 30B A3B Instruct 2507 scored 46; the best model scored 100.

TestScorePublic questionsSecret questions
Coding335726
Reading documents465643
Decisions848683

On the 18 hardest tasks (included in the scores above) it scored 25. 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 30B A3B Instruct 2507 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 GBYesQ5
Graphics card, 32 GBYesQ6
Mac, 16 GBNo–
Mac, 24 GBNo–
Mac, 32 GBYesQ4
Mac, 48 GBYesQ8
Mac, 64 GBYesQ8
Mac, 96 GBYesQ8
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 30B A3B Instruct 2507 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 30B A3B Instruct 2507 wrote for our public questions, word for word.

Coding: INI parser (hard) Didn't work
```python
import re
from typing import Dict, Tuple, List, Optional, Iterator

def parse_ini(text: str) -> Dict[str, Dict[str, str]]:
    # Parse the INI text into sections and keys
    sections: Dict[str, Dict[str, str]] = {}
    default_section: Dict[str, str] = {}
    current_section = None
    prev_line = None
    key = None
    continuation_lines: List[str] = []
    referenced_keys: List[str] = []
    interpolation_stack: List[str] = []

    # Compile regex patterns
    section_pattern = re.compile(r'^\s*\[(.*?)\]\s*$')
    key_value_pattern = re.compile(r'^\s*([^=:#]+)[=:]\s*(.+)$')
    inline_comment_pattern = re.compile(r'(\s+[#;].*)$')
    interpolation_pattern = re.compile(r'\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}')
    section_key_pattern = re.compile(r'\$\{([a-zA-Z_][a-zA-Z0-9_]*)\:([a-zA-Z_][a-zA-Z0-9_]*)\}')

    def get_value(section_name: str, key_name: str) -> str:
        """Get a value with interpolation, handling cycles and missing keys."""
        if not section_name and not key_name:
            raise ValueError("Empty interpolation reference")
        
        # Prevent cycles
        ref = f"{section_name}:{key_name}" if section_name else key_name
        if ref in interpolation_stack:
            raise ValueError("Interpolation cycle detected")
        
        # Check if the section exists
        if section_name and section_name not in sections:
            raise KeyError(f"Section '{section_name}' not found for interpolation")
        
        # If we're in the DEFAULT section or it's inherited
        target_section = sections.get(section_name, default_section)
        
        # Check if key exists in the target section
        value = target_section.get(key_name)
        if value is None:
            raise KeyError(f"Key '{key_name}' not found in section '{section_name}'")

        # Process interpolation recursively
        if '${' in value:
            interpolation_stack.append(ref)
            try:
                # Handle ${key} and ${section:key} patterns
                def replace_interpolation(match):
                    inner = match.group(1)
                    if ':' in inner:
                        sub_section, sub_key = inner.split(':', 1)
                        return get_value(sub_section, sub_key)
                    else:
                        return get_value(section_name, inner)
                
                value = re.sub(r'\$\{[^}]+\}', replace_interpolation, value)
            finally:
                interpolation_stack.pop()
        return value

    lines = text.splitlines()
    in_section = False

    for line_num, line in enumerate(lines):
        stripped_line = line.strip()
        
        # Skip empty lines and comment lines
        if not stripped_line or stripped_line[0] in ';#':
            if prev_line is None or prev_line.startswith(' '):
                continue
            prev_line = None
            continue

        # Check for section header
        section_match = section_pattern.match(line)
        if section_match:
            section_name = section_match.group(1).strip()
            if not section_name:
                raise ValueError(f"Invalid section header at line {line_num + 1}")
            if section_name == "DEFAULT":
                current_section = None
                in_section = False
                continue
            
            sections[section_name] = {}
            current_section = section_name
            in_section = True
            prev_line = None
            continue
        
        # Handle lines that start with whitespace and follow a key line (continuation)
        if line.startswith(' ') and prev_line and key is not None:
            continuation_lines.append(line.strip())
            prev_line = line
            continue
        
        # If we're not in any section yet, and we haven't seen a section header, then it's invalid
        if not in_section:
            raise ValueError(f"Key line before section header at line {line_num + 1}")
        
        # Try to parse key value pair
        key_match = key_value_pattern.match(line)
        if not key_match:
            raise ValueError(f"Malformed key-value line at line {line_num + 1}")
        
        # Extract key and value
        raw_key = key_match.group(1).strip().lower()
        raw_value = key_match.group(2).strip()
        
        # Process inline comments (outside quotes)
        comment_match = inline_comment_pattern.search(line)
        if comment_match:
            # If value is quoted, comment is inside the value, so we don't split
            first_quote_idx = raw_value.find('"')
            if first_quote_idx == -1 or raw_value.find('"', first_quote_idx + 1) == -1:
                # Not a properly quoted value, so split at comment
                raw_value = raw_value[:comment_match.start(1)].strip()
        
        # Process quoted values
        if raw_value.startswith('"') and raw_value.endswith('"'):
            # Remove quotes and keep everything inside
            value = raw_value[1:-1]
        else:
            value = raw_value
        
        # If there are continuation lines, append with newline
        if continuation_lines:
            value = "\n".join(continuation_lines) + "\n" + value
            continuation_lines = []
        
        # Apply interpolation
        if '${' in value:
            try:
                value = get_value(current_section, value)
            except (KeyError, ValueError) as e:
                raise e
            except Exception as e:
                raise ValueError(f"Error during interpolation at line {line_num + 1}: {e}")
        
        # Store key-value in current section
        sections[current_section][raw_key] = value
        key = raw_key
        prev_line = line

    # Now merge DEFAULT values into other sections
    for section_name, section_data in sections.items():
        for default_key, default_value in default_section.items():
            if default_key not in section_data:
                section_data[default_key] = default_value
    
    return sections
```
Coding: TTL LRU (hard) Worked
```python
from collections import OrderedDict
from typing import Any, Callable, Dict, Optional, Tuple

class TTLCache:
    def __init__(self, capacity: int, ttl: float, clock: Callable[[], float]):
        self.capacity = capacity
        self.ttl = ttl
        self.clock = clock
        self._cache = OrderedDict()
        self._stats = {"hits": 0, "misses": 0, "evictions": 0, "expirations": 0}
    
    def _expire_old(self):
        """Remove all expired entries from the cache."""
        now = self.clock()
        keys_to_remove = [key for key, (value, expiry) in self._cache.items() if now >= expiry]
        for key in keys_to_remove:
            del self._cache[key]
            self._stats["expirations"] += 1
    
    def put(self, key: Any, value: Any) -> None:
        if self.capacity == 0:
            return
        
        now = self.clock()
        # Remove expired entries
        self._expire_old()
        
        # If key already exists, update it
        if key in self._cache:
            self._cache[key] = (value, now + self.ttl)
            # Move to end to mark as recently used
            self._cache.move_to_end(key)
            return
        
        # If inserting new entry and would exceed capacity after removing expired
        # remove LRU entry only if still at capacity (after expiring old ones)
        if len(self._cache) >= self.capacity:
            # Evict LRU entry
            self._cache.popitem(last=False)
            self._stats["evictions"] += 1
        
        # Insert new entry
        self._cache[key] = (value, now + self.ttl)
    
    def get(self, key: Any, default: Any = None) -> Any:
        if self.capacity == 0:
            self._stats["misses"] += 1
            return default
        
        now = self.clock()
        # Remove expired entries
        self._expire_old()
        
        # Try to get the entry
        if key not in self._cache:
            self._stats["misses"] += 1
            return default
        
        value, expiry = self._cache[key]
        
        # Check if expired after removal of old ones
        if now >= expiry:
            # Expired - remove and count
            del self._cache[key]
            self._stats["expirations"] += 1
            self._stats["misses"] += 1
            return default
        
        # Valid, not expired - mark as recently used
        self._cache.move_to_end(key)
        self._stats["hits"] += 1
        return value
    
    def __len__(self) -> int:
        if self.capacity == 0:
            return 0
        
        now = self.clock()
        count = 0
        for key, (value, expiry) in self._cache.items():
            if now < expiry:
                count += 1
        return count
    
    def stats(self) -> Dict[str, int]:
        return self._stats.copy()
```
Coding: Parse duration Didn't work
```python
import re

def parse_duration(s: str) -> int:
    # Strip whitespace and check for empty string
    s = s.strip()
    if not s:
        raise ValueError("Empty or blank string")
    
    # Pattern to match valid duration parts: optional spaces, number, unit (h/m/s), optional spaces
    pattern = r'^\s*(\d+)(h|m|s)\s*'
    parts = []
    remaining = s
    
    # Expected order of units
    expected_units = ['h', 'm', 's']
    seen_units = set()
    
    # Parse each part in order
    for unit in expected_units:
        match = re.match(pattern + r'(\s*,?\s*|)', remaining, re.IGNORECASE)
        if match:
            value = int(match.group(1))
            found_unit = match.group(2).lower()
            
            # Check if unit is correct and not repeated
            if found_unit != unit:
                raise ValueError(f"Unit '{found_unit}' is not in expected order or is invalid")
            if found_unit in seen_units:
                raise ValueError(f"Unit '{found_unit}' repeated")
            
            parts.append((unit, value))
            seen_units.add(found_unit)
            remaining = remaining[match.end():].strip()
        else:
            # If we expect a unit but don't find it, it's missing or out of order
            if unit in ['h', 'm'] and len(parts) == 0:
                # Must start with 'h'
                raise ValueError("Duration must start with 'h'")
            # If we're past the expected unit, it's out of order
            if len(parts) > 0 and (unit == 'm' and 'h' not in [p[0] for p in parts]) or (unit == 's' and ('h' not in [p[0] for p in parts] or 'm' not in [p[0] for p in parts])):
                raise ValueError("Units must be in order: h, then m, then s")
    
    # Check if there's anything left after parsing
    if remaining:
        # If remaining string still has digits, it means no unit was provided
        if re.match(r'^\s*\d+', remaining):
            raise ValueError("Number without a unit")
        # If remaining has unknown characters or units, it's invalid
        if re.search(r'\d', remaining) and not re.search(r'[hms]', remaining):
            raise ValueError("Unknown units or malformed input")
        raise ValueError("Invalid duration format")
    
    # Check that we have all required parts in order or just partial parts
    # But we must have units in correct order
    for i, (unit, value) in enumerate(parts):
        if unit != expected_units[i]:
            raise ValueError(f"Unit {unit} out of order")
    
    # Convert to seconds
    total_seconds = 0
    for unit, value in parts:
        if unit == 'h':
            total_seconds += value * 3600
        elif unit == 'm':
            total_seconds += value * 60
        elif unit == 's':
            total_seconds += value
    
    return total_seconds
```
Coding: Summarize ranges Worked
```python
def summarize_ranges(nums: list[int]) -> str:
    if not nums:
        return ""
    
    # Remove duplicates and sort
    unique_nums = sorted(set(nums))
    
    result = []
    i = 0
    n = len(unique_nums)
    
    while i < n:
        start = unique_nums[i]
        # Find the end of the current consecutive sequence
        j = i
        while j + 1 < n and unique_nums[j + 1] == unique_nums[j] + 1:
            j += 1
        
        # Check if the run is 3 or more numbers
        if j - i + 1 >= 3:
            result.append(f"{start}..{unique_nums[j]}")
        else:
            # Add individual numbers
            for k in range(i, j + 1):
                result.append(str(unique_nums[k]))
        
        i = j + 1
    
    return ",".join(result)
```
Coding: Top customers Didn't work
```python
from typing import List, Dict, Tuple
import re

def top_customers(rows: List[Dict], n: int) -> List[Tuple[str, float]]:
    customer_totals = {}
    customer_names = {}
    
    # Regular expression to match valid amount strings
    amount_pattern = re.compile(r'^([-+]?)\$(\d{1,3}(,\d{3})*|\d+)(\.\d{1,2})?$')
    
    for row in rows:
        customer = row.get("customer", "").strip()
        amount_str = row.get("amount", "").strip()
        
        # Skip empty customer name or unparsable amount
        if not customer or not amount_str:
            continue
            
        # Check if amount string matches the pattern
        if not amount_pattern.match(amount_str):
            continue
            
        # Parse the amount
        try:
            # Remove $ and commas, convert to float
            clean_amount = amount_str.replace("$", "").replace(",", "")
            amount = float(clean_amount)
        except ValueError:
            continue
            
        # Normalize customer name (case-insensitive) and store first occurrence
        customer_lower = customer.lower()
        if customer_lower not in customer_names:
            customer_names[customer_lower] = customer  # Use first valid spelling
        
        # Add to total
        if customer_lower not in customer_totals:
            customer_totals[customer_lower] = 0.0
        customer_totals[customer_lower] += amount
    
    # Create result list with (name, total) tuples
    result = []
    for customer_lower, total in customer_totals.items():
        name = customer_names[customer_lower]
        # Round to 2 decimals
        rounded_total = round(total, 2)
        result.append((name, rounded_total))
    
    # Sort by total descending, then by name ascending
    result.sort(key=lambda x: (-x[1], x[0]))
    
    # Return top n customers
    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 non-ASCII
    normalized = unicodedata.normalize('NFKD', title)
    ascii_chars = ''.join(c for c in normalized if unicodedata.category(c) != 'Mn')
    ascii_only = ''.join(c for c in ascii_chars if ord(c) < 128)
    
    # Step 2: Lowercase
    lowercased = ascii_only.lower()
    
    # Step 3: Replace non-alphanumeric chars with single "-" and strip leading/trailing "-"
    slug = re.sub(r'[^a-z0-9]+', '-', lowercased).strip('-')
    
    # Step 4: Shorten if longer than max_len without cutting words
    if len(slug) <= max_len:
        return slug
    
    # Split into words and build prefix
    words = slug.split('-')
    prefix = ""
    for word in words:
        candidate = prefix + (word if not prefix else f"-{word}")
        if len(candidate) <= max_len:
            prefix = candidate
        else:
            # If adding this word would exceed max_len, check if first word is longer
            if len(words[0]) > max_len:
                return words[0][:max_len]
            break
    
    return prefix
```
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._tokens = capacity
        self._last_refill_time = clock()

    @property
    def tokens(self) -> float:
        self._refill()
        return self._tokens

    def allow(self, cost: float = 1) -> bool:
        self._refill()
        if self._tokens >= cost:
            self._tokens -= cost
            return True
        return False

    def _refill(self):
        now = self.clock()
        elapsed = now - self._last_refill_time
        refill_amount = elapsed * self.refill_per_sec
        self._tokens = min(self.capacity, self._tokens + refill_amount)
        self._last_refill_time = now
```
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) 0% right
{"severity": {"0": 0.05, "1": 0.3, "2": 0.6, "3": 0.05}, "page": {"true": 0.7, "false": 0.3}}
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": 0.8, "false": 0.2}}
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": 0.0, "false": 1.0}}
Decisions: Review mixed (hard) 100% right
{"hardware": {"true": 0.95, "false": 0.05}, "support": {"true": 0.9, "false": 0.1}}
Decisions: Support checkout down 100% right
{"department": {"billing": 0.1, "technical": 0.8, "account": 0.05, "shipping": 0.02, "sales": 0.03}, "urgency": {"0": 0.01, "1": 0.05, "2": 0.1, "3": 0.84}, "outage": {"true": 0.95, "false": 0.05}}
Decisions: Refund wrong plan 100% right
{"department": {"billing": 0.95, "technical": 0.01, "account": 0.02, "shipping": 0.01, "sales": 0.01}, "refund": {"true": 0.9, "false": 0.1}, "tone": {"frustrated": 0.1, "calm": 0.9}}
Decisions: Moderation doxxing 100% right
{"policy": {"none": 0.1, "harassment": 0.9, "hate": 0.0, "spam": 0.0, "self_harm": 0.0}, "personal_info": {"true": 0.95, "false": 0.05}}
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": 0.95, "resume": 0.01, "contract": 0.02, "bank_statement": 0.01, "other": 0.01}, "missing_due_date": {"true": 0.8, "false": 0.2}}
Decisions: Phishing paypal 100% right
{"phishing": {"true": 0.99, "false": 0.01}, "risk": {"0": 0.01, "1": 0.02, "2": 0.05, "3": 0.92}}
Decisions: Pii ssn email 100% right
{"data_kind": {"none": 0.0, "contact": 0.1, "financial": 0.05, "government_id": 0.85, "health": 0.0}, "sensitive": {"true": 0.95, "false": 0.05}}
Decisions: Review mixed 100% right
{"sentiment": {"positive": 0.1, "neutral": 0.2, "negative": 0.7}, "defect": {"true": 0.9, "false": 0.1}, "recommend": {"true": 0.1, "false": 0.9}}
Documents: Saas escalator (hard) 20% right
{
  "year2_price_per_seat_month": 46.59,
  "year3_price_per_seat_month": 49.44,
  "year1_invoice": 64800.00,
  "year2_invoice": 67478.40,
  "addon_months_billed": 4,
  "addon_invoice": 31641.60,
  "year3_invoice": 73051.20,
  "year3_discount_percent": 15,
  "total_contract_value": 246971.20,
  "contract_end_date": "2027-02-28"
}
Documents: Expense thread 71% 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":35.28},{"date":"2025-02-25","category":"meals","amount_usd":196.67},{"date":"2025-02-26","category":"lodging","amount_usd":400.0},{"date":"2025-02-27","category":"ground_transport","amount_usd":38.43}],"rejected_item_count":1,"per_diem_days":1,"per_diem_usd":65.0,"total_reimbursable_usd":1929.98,"approver_email":"priya.raman@corvane.com"}
Documents: Lease amendment 83% 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":2586,"move_in_payment":6450}
Documents: Ticket SLA 50% right
{
  "ticket_id": "48213",
  "account_id": "ACC-7731",
  "open_issue": "inventory_sync",
  "resolved_issues": [
    "billing_address",
    "invoice_pdf"
  ],
  "affected_orders": [
    "SO-99827"
  ],
  "priority": "P2",
  "sla_due_local": "2025-09-15T17:00",
  "sla_due_utc": "2025-09-15T21:00:00Z",
  "reissued_invoice": "INV-2025-0812"
}
Documents: Sales footnotes 56% right
{"q3_total_usd":4535100,"q2_total_usd":2205000,"q2_central_originally_reported_usd":3047000,"q2_to_q3_change_pct":7.4,"top_region_q3":"West","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: 31B parameters. First tested OCT 10.

Models that scored about the same

Comments

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