Review · updated OCT 11

Qwen3.8 27B review: a solid all-rounder

It scored 88 out of 100, #9 of 56. It solved 23 of 30 coding jobs and scored 99 on reading documents. Runs on a 24 GB graphics card or a Mac with 32 GB.

The short version
  • Qwen3.8 27B is a free model from Alibaba's Qwen team that you can run on your own computer. In our tests it's a solid all-rounder: 88 out of 100, #9 of 56.
  • It solved 23 of 30 coding jobs and scored 99 on reading documents. On our hardest tasks it scored 83.
  • 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.8 27B got 23 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.8 27B scored 99; the best model scored 100.

TestScorePublic questionsSecret questions
Coding777178
Reading documents9910099
Decisions9810098

On the 18 hardest tasks (included in the scores above) it scored 83. This number separates the top models.

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.8 27B 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.8 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 Qwen3.8 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) -> None:
        self._capacity = max(0, int(capacity))
        self._ttl = ttl
        self._clock = clock
        self._data = OrderedDict()
        self._hits = 0
        self._misses = 0
        self._evictions = 0
        self._expirations = 0

    def put(self, key, value) -> None:
        if self._capacity <= 0:
            return

        now = self._clock()

        if key in self._data:
            self._data[key] = (value, now + self._ttl)
            self._data.move_to_end(key)
        else:
            if len(self._data) >= self._capacity:
                self._purge_expired(now)
                if len(self._data) >= self._capacity:
                    self._data.popitem(last=False)
                    self._evictions += 1

            self._data[key] = (value, now + self._ttl)

    def get(self, key, default=None):
        entry = self._data.get(key)

        if entry is None:
            self._misses += 1
            return default

        now = self._clock()
        value, expiry = entry

        if expiry <= now:
            del self._data[key]
            self._expirations += 1
            self._misses += 1
            return default

        self._data.move_to_end(key)
        self._hits += 1
        return value

    def __len__(self) -> int:
        if not self._data:
            return 0

        now = self._clock()
        return sum(1 for _, expiry in self._data.values() if expiry > now)

    def stats(self) -> dict[str, int]:
        return {
            "hits": self._hits,
            "misses": self._misses,
            "evictions": self._evictions,
            "expirations": self._expirations,
        }

    def _purge_expired(self, now) -> None:
        expired_keys = [key for key, (_, expiry) in self._data.items() if expiry <= now]

        for key in expired_keys:
            del self._data[key]
            self._expirations += 1
```
Coding: Parse duration Didn't work
import re

_DURATION_RE = re.compile(r"[0-9]+[hmsHMS](\s+[0-9]+[hmsHMS])*")
_PART_RE = re.compile(r"([0-9]+)([hmsHMS])")
_UNIT_SECONDS = {"h": 3600, "m": 60, "s": 1}
_UNIT_ORDER = {"h": 0, "m": 1, "s": 2}


def parse_duration(s: str) -> int:
    value = s.strip()
    if not value:
        raise ValueError("empty duration string")
    if _DURATION_RE.fullmatch(value) is None:
        raise ValueError("invalid duration string")

    total = 0
    last_order = -1

    for match in _PART_RE.finditer(value):
        amount = int(match.group(1))
        unit = match.group(2).lower()
        order = _UNIT_ORDER[unit]

        if order <= last_order:
            raise ValueError("units must be unique and appear in h, m, s order")

        last_order = order
        total += amount * _UNIT_SECONDS[unit]

    return total
Coding: Summarize ranges Worked


```python
def summarize_ranges(nums: list[int]) -> str:
    nums = sorted(set(nums))
    if not nums:
        return ""

    parts = []
    i = 0
    n = len(nums)

    while i < n:
        j = i
        while j + 1 < n and nums[j + 1] == nums[j] + 1:
            j += 1

        run_length = j - i + 1
        if run_length >= 3:
            parts.append(f"{nums[i]}..{nums[j]}")
        else:
            parts.extend(str(nums[k]) for k in range(i, j + 1))

        i = j + 1

    return ",".join(parts)
```
Coding: Top customers Worked


```python
import re
from decimal import Decimal, ROUND_HALF_UP

_AMOUNT_RE = re.compile(
    r"[+-]?(?:(?:[0-9]{1,3}(?:,[0-9]{3})*|[0-9]+)(?:\.[0-9]+)?|\.[0-9]+)"
)


def _parse_amount(value) -> Decimal | None:
    if value is None:
        return None

    s = str(value).strip()
    if not s:
        return None

    s = s.replace("$", "")
    s = "".join(s.split())

    if not _AMOUNT_RE.fullmatch(s):
        return None

    return Decimal(s.replace(",", ""))


def top_customers(rows: list[dict], n: int) -> list[tuple[str, float]]:
    if n <= 0:
        return []

    totals: dict[str, tuple[str, Decimal]] = {}

    for row in rows:
        customer = row.get("customer")
        if customer is None:
            continue

        name = str(customer).strip()
        if not name:
            continue

        amount = _parse_amount(row.get("amount"))
        if amount is None:
            continue

        key = name.casefold()
        if key in totals:
            display_name, current_total = totals[key]
            totals[key] = (display_name, current_total + amount)
        else:
            totals[key] = (name, amount)

    cent = Decimal("0.01")
    rounded_totals: list[tuple[str, Decimal]] = []

    for name, total in totals.values():
        rounded = total.quantize(cent, rounding=ROUND_HALF_UP)
        if rounded == 0:
            rounded = Decimal("0.00")
        rounded_totals.append((name, rounded))

    rounded_totals.sort(key=lambda item: (-item[1], item[0]))

    return [(name, float(total)) for name, total in rounded_totals[:n]]
```
Coding: Slugify Worked


```python
import re
import unicodedata


def slugify(title: str, max_len: int = 50) -> str:
    if max_len <= 0:
        return ""

    text = unicodedata.normalize("NFD", title)
    text = text.encode("ascii", "ignore").decode("ascii")
    text = text.lower()

    slug = re.sub(r"[^a-z0-9]+", "-", text).strip("-")

    if len(slug) <= max_len:
        return slug

    words = slug.split("-")
    if not words:
        return ""

    first_word = words[0]
    if len(first_word) > max_len:
        return first_word[:max_len]

    result = first_word
    for word in words[1:]:
        candidate = f"{result}-{word}"
        if len(candidate) <= max_len:
            result = candidate
        else:
            break

    return result
```
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_refill = float(self._clock())

    def _refill(self) -> None:
        now = float(self._clock())
        if now > self._last_refill:
            elapsed = now - self._last_refill
            self._tokens = min(self._capacity, self._tokens + elapsed * self._refill_per_sec)
            self._last_refill = now

    def allow(self, cost: float = 1) -> bool:
        self._refill()
        if cost <= 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.01,
    "partial_refund": 0.01,
    "store_credit": 0.98,
    "nothing": 0.0
  },
  "defective": {
    "true": 0.01,
    "false": 0.99
  }
}
Decisions: Incident matrix (hard) 100% right


{
  "severity": {
    "0": 0.0,
    "1": 0.98,
    "2": 0.02,
    "3": 0.0
  },
  "page": {
    "true": 0.02,
    "false": 0.98
  }
}
Decisions: Tool followup (hard) 100% right


{
  "tool": {
    "web_search": 0.0,
    "calculator": 0.0,
    "calendar": 0.98,
    "email": 0.0,
    "none": 0.02
  },
  "confirm": {
    "true": 0.98,
    "false": 0.02
  }
}
Decisions: Legit security alert (hard) 100% right


{
  "phishing": {
    "true": 0.0,
    "false": 1.0
  },
  "action_needed": {
    "true": 0.0,
    "false": 1.0
  }
}
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.99,
    "false": 0.01
  },
  "support": {
    "true": 0.96,
    "false": 0.04
  }
}
Decisions: Support checkout down 100% right


{
  "department": {
    "billing": 0.04,
    "technical": 0.95,
    "account": 0.01,
    "shipping": 0,
    "sales": 0
  },
  "urgency": {
    "0": 0,
    "1": 0,
    "2": 0.01,
    "3": 0.99
  },
  "outage": {
    "true": 0.99,
    "false": 0.01
  }
}
Decisions: Refund wrong plan 100% right


{
  "department": {
    "billing": 0.96,
    "technical": 0.005,
    "account": 0.01,
    "shipping": 0.005,
    "sales": 0.02
  },
  "refund": {
    "true": 0.98,
    "false": 0.02
  },
  "tone": {
    "frustrated": 0.02,
    "calm": 0.98
  }
}
Decisions: Moderation doxxing 100% right


{
  "policy": {
    "none": 0.01,
    "harassment": 0.98,
    "hate": 0.005,
    "spam": 0.004,
    "self_harm": 0.001
  },
  "personal_info": {
    "true": 0.99,
    "false": 0.01
  }
}
Decisions: Route calendar 100% right


{
  "tool": {
    "web_search": 0.02,
    "calculator": 0.01,
    "calendar": 0.95,
    "email": 0.01,
    "none": 0.01
  },
  "confirm": {
    "true": 0.98,
    "false": 0.02
  }
}
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.0,
    "1": 0.01,
    "2": 0.04,
    "3": 0.95
  }
}
Decisions: Pii ssn email 100% right


{
  "data_kind": {
    "none": 0.0,
    "contact": 0.01,
    "financial": 0.0,
    "government_id": 0.99,
    "health": 0.0
  },
  "sensitive": {
    "true": 0.99,
    "false": 0.01
  }
}
Decisions: Review mixed 100% right


{
  "sentiment": {
    "positive": 0.01,
    "neutral": 0.01,
    "negative": 0.98
  },
  "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 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.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": 195.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 100% 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-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 10.

Models that scored about the same

Comments

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