antirez's ds4 ("DwarfStar") is a dependency-free C engine built for exactly one model: DeepSeek V4 Flash, squeezed into an 80.8 GiB 2-bit GGUF. We ran it on the oldest machine in its supported class — a Mac Studio M1 Ultra, 128 GB — swept the speed curves to 262,144 tokens of context, then pointed an independent eval harness it was never tuned for at it. It cleared 92.7% on HumanEval, went 10/13 on AIME 2025, kept ~92% of the official GPQA Diamond score at 2 bits, and handed us one honest negative result. Then we A/B'd it against its 90.9 GiB hybrid sibling. Here is exactly what we measured.
Until recently, models in this class lived exclusively in data centers: you rented them through a browser, your conversations traveled through someone else's servers, and the service could change or vanish overnight. What changed is a clever piece of compression — and it fits inside a well-equipped Mac that also handles my email.
Think of the model as a library of 284 billion tiny numbers. Most of them — the vast store of specialized knowledge — tolerate being stored very roughly, as long as the machinery that reasons with them stays high-quality. Compress the library, protect the librarian.
The second insight is about conversation memory. Normally, the longer an AI's conversation gets, the more memory it burns — often catastrophically. This model's "notes" stay astonishingly compact: a quarter-million words of conversation costs it under 4 GB. That's why a home machine can offer a context window most cloud services would charge real money for.
The genuinely surprising part wasn't the numbers — it was the absence of ceremony. This corner of computing usually greets you with version conflicts and cryptic errors. This was: compile one program (a few seconds), download one file (thirteen minutes), start typing. It even speaks the same "language" as the commercial AI services, so tools built for those plug into the local model by changing a single address.
You need a Mac with 96 GB of memory or more and a comfortable relationship with the Terminal — or a friend who has one. With less memory the project can stream the model from disk at reduced speed; worth a look, but a different experience. The takeaway I keep returning to: a model that would have been a state secret three years ago now runs on hardware you can buy secondhand — completely privately, fast enough that using it feels like a conversation rather than correspondence.
You're reading the plain-English case. The full evidence — the rig, the speed curves, the eval harness, the negative result, and every command to reproduce it — is in the deep dive →
DwarfStar is deliberately narrow: it runs exactly one model family, with GGUFs crafted for the engine's assumptions and validated against logits from the official implementation. The build is make -j8 — five binaries in ~40 seconds, plain C99, Metal backend. No Python environment, no CUDA roulette, no config file.
"2-bit model" undersells what's happening. The quantization is asymmetric: only the routed MoE experts — the overwhelming bulk of a fine-grained MoE's weight — are squeezed to ~2 bits (IQ2_XXS up/gate, Q2_K down). The parts that do the thinking — attention projections, shared experts, routing, output — all stay at 8 bits, imatrix-tuned. Compress the library, protect the librarian.
| Variant (HF: antirez/deepseek-v4-gguf) | Size | Fits 128 GB resident? |
|---|---|---|
| Flash q2-imatrix · used here | 80.8 GiB | yes — with ~1M ctx headroom |
| Flash q2 + last-6-layers-Q4 hybrid | 90.9 GiB | yes (tight) — A/B'd at Exhibit G |
| Flash full Q4 imatrix | 153.3 GiB | no — SSD streaming only |
| Flash MTP sidecar (spec. decoding) | 3.5 GiB | add-on — see Exhibit F |
| PRO q2-imatrix | 432.7 GiB | no — experimental streaming |
The 80.8 GiB quant looks reverse-engineered from a constraint pair: "128 GB Mac, 1M-token context."
ds4-bench loads the model once, snapshots KV state, and measures interval prefill plus 128 greedy decode tokens at each context frontier. Linear sweep to 64k (32 monotonic rows, no cliffs), exponential sweep to 262k.
Full per-frontier tables: m1_ultra.csv m1_ultra_longctx.csv
13.4 KiB per token, dead linear. A quarter-million tokens of context costs 3.6 GB — DeepSeek V4's attention design (a 128-token raw sliding window plus a compressed/indexed path with top-k retrieval) is why a 1M window is even conceivable on consumer hardware. A conventional full KV cache for a model this size would measure in hundreds of gigabytes at that depth.
−30% from 2k to 131k is remarkably flat; the drop to 8.1 t/s at 262k is where retrieval cost stops hiding behind the weight-streaming cost. Still: 262k tokens in, on a 2021 desktop, at reading speed.
81 GiB of weights + 4.5 GiB of context buffers, no Metal working-set errors. Extrapolating the linear KV cost to the full 1M window lands at ~97–98 GiB total — right at the edge of Metal's default limit on a 128 GB Mac. Extrapolation, clearly labeled: the 1M prefill itself is a multi-hour commitment at the measured decay rate.
The repo publishes reference numbers for newer machines on the same engine and quant. The M1 Ultra's story is in the split: prefill is compute-bound and shows the full generation gap; decode is bound by streaming ~81 GiB of experts per token — and a 2021-vintage 800 GB/s memory bus is still competitive.
| Machine | Prefill @ ~12k | Generation |
|---|---|---|
| MacBook Pro M3 Max · 128 GB | 250 t/s | 21.5 t/s |
| MacBook Pro M5 Max · 128 GB | 463 t/s | 25.9 t/s |
| Mac Studio M3 Ultra · 512 GB | 468 t/s | 27.4 t/s |
| DGX Spark GB10 · 128 GB | 344 t/s | 13.8 t/s |
| Mac Studio M1 Ultra · 128 GB (ours) | ~194 t/s | ~17.8 t/s |
Repo-published reference numbers, same engine + quant; ours measured at the same ~12k frontier.
The M1 Ultra generates at 69% of an M5 Max's rate and beats the DGX Spark — while prefilling 2.4× slower. If your workload is chat-shaped rather than ingest-shaped, a five-year-old Mac Studio is still a perfectly serious host for a 284B model. Memory bandwidth ages far better than compute.
ds4 ships its own 92-question eval, but a project that curates its own test set invites an obvious objection. So we built an independent harness instead: ~250 lines of stdlib-only Python driving ds4-server's OpenAI-compatible endpoint, with unit-tested graders — code executed against official tests for HumanEval, final-boxed-integer grading for AIME, per-question shuffled A–D letter grading for GPQA Diamond.
The quant doesn't seem to break the reasoning — not one extracted AIME answer was wrong. It just doesn't think faster than ~12–20 t/s, so the budget you grant is the score you get.
GPQA Diamond is the one place we can put the quant side-by-side with an official number: 130/198 (65.7%) non-thinking against the model card's 71.2 for full-precision Flash in the same mode. The 2-bit expert store keeps ~92% of it. Thinking mode (official: 87.4) was not run — at ~20k thinking tokens per hard science question, that's a multi-day job on this machine.
▸ HumanEval and AIME are absolute, single-run, uncompared numbers — no full-precision baseline. GPQA is the one direct comparison, and even there prompt + extraction methodology differ from the official run, so the −5.5-point gap bounds quant loss and methodology delta together. What all three establish is real: the 2-bit quant clears the "quasi-frontier" bar on benchmarks its packager never saw.
DeepSeek V4 ships an MTP (multi-token prediction) head; ds4 supports it as a 3.5 GiB sidecar GGUF for greedy speculative decoding, gated by a verifier-confidence margin. The README calls it "experimental… at most a slight speedup." Measured A/B on this machine, same prompts, same seeds:
| Prompt | Baseline gen | MTP draft-2 gen | Δ |
|---|---|---|---|
| short | 20.92 t/s | 19.44 t/s | −7.1% |
| ~8k tokens | 17.39 t/s | 16.65 t/s | −4.3% |
A clean negative: speculative decoding buys speed when verification is cheap relative to decoding — but when decode is already saturating the memory bus with 81 GiB of experts, there is no idle compute to hide the draft in. On an M5-class chip with the tensor API and more headroom, the economics presumably flip; that's a follow-up.
The 90.9 GiB hybrid keeps the last six layers at Q4 instead of ~2 bits — the most precision this machine can still hold resident. Same engine, same harness, same greedy non-thinking HumanEval, run A/B against the 80.8 GiB q2.
| ctx | q2 prefill / gen | hybrid prefill / gen |
|---|---|---|
| 4,096 | 208.4 / 17.8 t/s | 200.3 / 18.0 t/s |
| 8,192 | 202.5 / 17.9 t/s | 198.9 / 17.5 t/s |
| 16,384 | 187.3 / 17.4 t/s | 182.4 / 17.4 t/s |
Speed cost of the extra 10.1 GiB: ~0–2% — noise level. Cold-load residency 37 s vs 31 s. Decode streams only the ~13B active parameters per token, so a fatter expert store barely shows.
Five of q2's seven genuine logic slips — the tolerance miss, the operator-precedence miss — repaired by extra precision in the last six layers.
Four tasks q2 passed now fail: under greedy decoding, changing the quant also changes the trajectories, so the gains are partly offset by new misses.
HumanEval moves 152/164 → 153/164 — but 9 tasks flipped to get there. Quantization noise doesn't subtract capability uniformly; it moves the error surface around. The aggregate score barely moves; the composition of errors moves a lot.
▸ The real price of the hybrid isn't speed — it's context headroom: ~350k tokens within Metal's default working set, vs ~1M for pure q2.
10/13 is not a full-exam score. The run was stopped early by design — competition math burns 20k+ thinking tokens per hard problem, and the remaining 17 would have added 4–6 hours. It covers the easier front half of the exam.
We did not run full-precision DeepSeek V4 Flash ourselves. The GPQA comparison borrows the model card's official 71.2 — and prompt + extraction methodology differ, so the −5.5-point gap bounds quant loss and methodology delta together. HumanEval and AIME have no baseline at all.
Every eval figure is single-run, greedy or fixed sampling — honest absolute numbers, not averaged estimates. Speed sweeps are single passes too; warm-cache variance between sweeps ran ~5–8% on prefill.
Full Q4 (153.3 GiB) exceeds this machine's RAM — SSD-streaming only. GPQA thinking mode (official: 87.4) would be a multi-day run at local speeds. 1M context is KV-affordable but the prefill is a lunch break, not an experiment.
Requirements: a 96 GB+ Mac and ~85 GB of free disk. Point any OpenAI SDK at base_url="http://127.0.0.1:8000/v1" — or an Anthropic client at /v1/messages — and local DeepSeek is a drop-in backend.
git clone https://github.com/antirez/ds4 && cd ds4
make -j8 # macOS Metal build, no dependencies
./download_model.sh q2-imatrix # 80.8 GiB from HF, resumable
# interactive chat / one-shot prompts
./ds4 # multi-turn CLI, /nothink /think /ctx N
./ds4 -p "why is the sky blue?" --nothink
# OpenAI- & Anthropic-compatible API server (default 127.0.0.1:8000)
./ds4-server --host 127.0.0.1 --port 8000 --ctx 65536
curl http://127.0.0.1:8000/v1/chat/completions -H 'Content-Type: application/json' \
-d '{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"hello"}],"max_tokens":128}'
# the speed sweep from Exhibit C
./ds4-bench -m ds4flash.gguf --prompt-file speed-bench/promessi_sposi.txt \
--ctx-start 2048 --ctx-max 65536 --step-incr 2048 --gen-tokens 128 ▸ --ctx sets the context budget — 65536 costs under 1 GB of KV on top of the model. Add --host 0.0.0.0 only if other machines on your LAN should reach it.
Stdlib Python only — no third-party packages anywhere in the stack. The runner is resumable, the graders are unit-tested, and every per-item record carries tokens, wall time, and finish_reason so truncation can never masquerade as a wrong answer. Read the code below, or take the bundle and re-score it yourself.
"""Independent eval runner for DeepSeek V4 Flash q2 served by ds4-server.
Usage:
python3 harness.py aime # 30 AIME 2025 problems, thinking mode
python3 harness.py humaneval # 164 HumanEval tasks, non-thinking, greedy
Results append to results/<suite>.jsonl (resumable: done ids are skipped).
"""
import csv
import json
import random
import sys
import time
import urllib.request
from pathlib import Path
from graders import extract_code, extract_mcq_letter, grade_aime, run_humaneval_check
BASE = Path(__file__).parent
SERVER = "http://127.0.0.1:8000/v1/chat/completions"
AIME_INSTRUCTION = (
"Please reason step by step, and put your final answer within \\boxed{}. "
"The answer is an integer between 0 and 999."
)
HUMANEVAL_INSTRUCTION = (
"Complete the following Python function. Reply with the complete, "
"self-contained implementation (including the function signature and any "
"imports it needs) in a single ```python code block. Do not include tests "
"or example usage.\n\n```python\n{prompt}```"
)
def chat(payload, timeout=3600):
req = urllib.request.Request(
SERVER,
data=json.dumps(payload).encode(),
headers={"Content-Type": "application/json"},
)
start = time.monotonic()
with urllib.request.urlopen(req, timeout=timeout) as resp:
body = json.load(resp)
elapsed = time.monotonic() - start
msg = body["choices"][0]["message"]
return {
"content": msg.get("content") or "",
"reasoning": msg.get("reasoning_content") or "",
"finish_reason": body["choices"][0].get("finish_reason"),
"usage": body.get("usage", {}),
"wall_s": round(elapsed, 2),
}
def load_done(path):
if not path.exists():
return set()
return {json.loads(line)["id"] for line in path.open() if line.strip()}
def run_aime(out_path, done):
problems = json.load((BASE / "data" / "aime25.json").open())
for p in problems:
pid = f"aime25-{p['id']}"
if pid in done:
continue
r = chat(
{
"model": "deepseek-v4-flash",
"messages": [
{"role": "user", "content": p["problem"] + "\n\n" + AIME_INSTRUCTION}
],
"max_tokens": 24576,
}
)
graded_text = r["content"] if r["content"].strip() else r["reasoning"]
passed = grade_aime(graded_text, p["answer"])
yield out_path, {
"id": pid,
"passed": passed,
"expected": p["answer"],
"finish_reason": r["finish_reason"],
"usage": r["usage"],
"wall_s": r["wall_s"],
"content": r["content"],
"reasoning_chars": len(r["reasoning"]),
}
def run_humaneval(out_path, done):
tasks = [
json.loads(line)
for line in (BASE / "data" / "HumanEval.jsonl").open()
if line.strip()
]
for t in tasks:
tid = t["task_id"]
if tid in done:
continue
r = chat(
{
"model": "deepseek-v4-flash",
"messages": [
{
"role": "user",
"content": HUMANEVAL_INSTRUCTION.format(prompt=t["prompt"]),
}
],
"think": False,
"temperature": 0,
"max_tokens": 4096,
}
)
code = extract_code(r["content"])
passed, detail = run_humaneval_check(code, t["test"], t["entry_point"])
yield out_path, {
"id": tid,
"passed": passed,
"detail": detail if not passed else "ok",
"finish_reason": r["finish_reason"],
"usage": r["usage"],
"wall_s": r["wall_s"],
"content": r["content"],
}
GPQA_INSTRUCTION = (
"Answer the following multiple-choice question. Think briefly if needed, then "
"give your final answer on the last line in the exact form 'Answer: X' where X "
"is one of A, B, C, D."
)
def run_gpqa(out_path, done):
with (BASE / "data" / "gpqa_diamond.csv").open(newline="") as f:
rows = list(csv.DictReader(f))
for i, row in enumerate(rows):
qid = f"gpqa-diamond-{i}"
if qid in done:
continue
choices = [
("correct", row["Correct Answer"].strip()),
("wrong", row["Incorrect Answer 1"].strip()),
("wrong", row["Incorrect Answer 2"].strip()),
("wrong", row["Incorrect Answer 3"].strip()),
]
random.Random(i).shuffle(choices)
letters = "ABCD"
expected = letters[[k for k, _ in choices].index("correct")]
options = "\n".join(f"{letters[j]}) {text}" for j, (_, text) in enumerate(choices))
prompt = f"{GPQA_INSTRUCTION}\n\n{row['Question'].strip()}\n\n{options}"
r = chat(
{
"model": "deepseek-v4-flash",
"messages": [{"role": "user", "content": prompt}],
"think": False,
"temperature": 0,
"max_tokens": 3072,
}
)
got = extract_mcq_letter(r["content"])
yield out_path, {
"id": qid,
"passed": got == expected,
"expected": expected,
"given": got,
"finish_reason": r["finish_reason"],
"usage": r["usage"],
"wall_s": r["wall_s"],
"content": r["content"],
}
def main():
suite = sys.argv[1] if len(sys.argv) > 1 else ""
runners = {"aime": run_aime, "humaneval": run_humaneval, "gpqa": run_gpqa}
if suite not in runners:
sys.exit(f"usage: harness.py [{'|'.join(runners)}] [result-suffix]")
suffix = f"-{sys.argv[2]}" if len(sys.argv) > 2 else ""
out_path = BASE / "results" / f"{suite}{suffix}.jsonl"
out_path.parent.mkdir(exist_ok=True)
done = load_done(out_path)
if done:
print(f"resuming: {len(done)} items already done", flush=True)
n_pass = n_run = 0
for path, record in runners[suite](out_path, done):
with path.open("a") as f:
f.write(json.dumps(record) + "\n")
n_run += 1
n_pass += bool(record["passed"])
usage = record["usage"]
gen = usage.get("completion_tokens", 0)
tps = round(gen / record["wall_s"], 2) if record["wall_s"] else 0
print(
f"[{record['id']}] {'PASS' if record['passed'] else 'FAIL'} "
f"gen={gen}tok wall={record['wall_s']}s ({tps} t/s) "
f"running: {n_pass}/{n_run}",
flush=True,
)
print(f"suite={suite} new_pass={n_pass}/{n_run}", flush=True)
if __name__ == "__main__":
main()
"""Grading logic for the independent AIME 2025 / HumanEval eval."""
import re
import subprocess
import sys
import tempfile
BOXED_RE = re.compile(r"\\boxed\{([^{}]*)\}")
FENCE_RE = re.compile(r"```(?:python)?\s*\n(.*?)```", re.DOTALL)
INT_RE = re.compile(r"-?\d[\d,]*")
def extract_aime_answer(text):
"""Return the model's integer answer: last \\boxed{} if present, else last integer."""
if not text:
return None
boxed = BOXED_RE.findall(text)
candidates = boxed if boxed else INT_RE.findall(text)
for cand in reversed(candidates):
digits = cand.strip().replace(",", "")
try:
return int(digits)
except ValueError:
continue
return None
def grade_aime(text, expected):
got = extract_aime_answer(text)
return got is not None and got == int(expected)
ANSWER_LETTER_RE = re.compile(
r"(?:answer|correct option|choice)\b[^A-D]{0,20}\b([A-D])\b", re.IGNORECASE
)
STANDALONE_LETTER_RE = re.compile(r"\(?\b([A-D])\b\)?")
def extract_mcq_letter(text):
"""Return the model's A-D choice: prefer an 'Answer: X' style match, else the
last standalone capital letter A-D."""
if not text:
return None
answered = ANSWER_LETTER_RE.findall(text)
if answered:
return answered[-1].upper()
standalone = [m for m in STANDALONE_LETTER_RE.findall(text) if m.isupper()]
return standalone[-1] if standalone else None
def extract_code(text):
"""Return the last fenced code block, or the raw text if no fence exists."""
if not text:
return ""
blocks = FENCE_RE.findall(text)
return blocks[-1].strip() if blocks else text.strip()
def run_humaneval_check(code, test_src, entry_point, timeout=15):
"""Execute candidate code against the HumanEval test in a subprocess.
Returns (passed: bool, detail: str).
"""
program = "\n".join(
[
"from typing import *",
"import math",
"",
code,
"",
test_src,
f"check({entry_point})",
"print('HUMANEVAL_OK')",
]
)
with tempfile.TemporaryDirectory() as sandbox:
try:
proc = subprocess.run(
[sys.executable, "-c", program],
cwd=sandbox,
capture_output=True,
text=True,
timeout=timeout,
)
except subprocess.TimeoutExpired:
return False, "timeout"
if proc.returncode == 0 and "HUMANEVAL_OK" in proc.stdout:
return True, "ok"
return False, (proc.stderr or proc.stdout).strip()[-500:]
import unittest
from graders import extract_aime_answer, grade_aime, extract_code, run_humaneval_check
class TestAimeExtraction(unittest.TestCase):
def test_boxed_answer(self):
self.assertEqual(extract_aime_answer(r"The answer is \boxed{70}."), 70)
def test_last_boxed_wins(self):
text = r"First we get \boxed{12}, but correcting: \boxed{588}"
self.assertEqual(extract_aime_answer(text), 588)
def test_boxed_with_spaces_and_commas(self):
self.assertEqual(extract_aime_answer(r"\boxed{ 1,024 }"), 1024)
def test_no_boxed_falls_back_to_last_integer(self):
self.assertEqual(extract_aime_answer("So the final answer is 204."), 204)
def test_no_answer(self):
self.assertIsNone(extract_aime_answer("I could not solve this."))
def test_grade(self):
self.assertTrue(grade_aime(r"\boxed{70}", "70"))
self.assertFalse(grade_aime(r"\boxed{71}", "70"))
self.assertFalse(grade_aime("no idea", "70"))
class TestCodeExtraction(unittest.TestCase):
def test_python_fence(self):
text = "Here you go:\n```python\ndef f():\n return 1\n```\nDone."
self.assertEqual(extract_code(text), "def f():\n return 1")
def test_last_fence_wins(self):
text = "```python\ndef f():\n return 1\n```\nOops, fix:\n```python\ndef f():\n return 2\n```"
self.assertEqual(extract_code(text), "def f():\n return 2")
def test_bare_fence(self):
text = "```\ndef f():\n return 3\n```"
self.assertEqual(extract_code(text), "def f():\n return 3")
def test_no_fence_returns_raw(self):
text = "def f():\n return 4"
self.assertEqual(extract_code(text), "def f():\n return 4")
class TestHumanEvalCheck(unittest.TestCase):
TEST_SRC = (
"def check(candidate):\n"
" assert candidate(2) == 4\n"
" assert candidate(3) == 9\n"
)
def test_pass(self):
ok, detail = run_humaneval_check("def sq(x):\n return x * x", self.TEST_SRC, "sq")
self.assertTrue(ok, detail)
def test_wrong_answer_fails(self):
ok, _ = run_humaneval_check("def sq(x):\n return x + x", self.TEST_SRC, "sq")
self.assertFalse(ok)
def test_exception_fails(self):
ok, _ = run_humaneval_check("def sq(x):\n raise ValueError", self.TEST_SRC, "sq")
self.assertFalse(ok)
def test_infinite_loop_times_out(self):
ok, detail = run_humaneval_check(
"def sq(x):\n while True:\n pass", self.TEST_SRC, "sq", timeout=3
)
self.assertFalse(ok)
self.assertIn("timeout", detail)
def test_typing_names_available(self):
code = "def ident(x: List[int]) -> List[int]:\n return x"
test = "def check(candidate):\n assert candidate([1]) == [1]\n"
ok, detail = run_humaneval_check(code, test, "ident")
self.assertTrue(ok, detail)
if __name__ == "__main__":
unittest.main()
class TestMcqExtraction(unittest.TestCase):
def test_answer_prefix(self):
from graders import extract_mcq_letter
self.assertEqual(extract_mcq_letter("Some reasoning.\n\nAnswer: C"), "C")
def test_answer_bold(self):
from graders import extract_mcq_letter
self.assertEqual(extract_mcq_letter("The answer is **B**."), "B")
def test_parenthesized(self):
from graders import extract_mcq_letter
self.assertEqual(extract_mcq_letter("Therefore (D) is correct."), "D")
def test_last_standalone_letter_fallback(self):
from graders import extract_mcq_letter
self.assertEqual(extract_mcq_letter("Options A and B fail, so C."), "C")
def test_no_letter(self):
from graders import extract_mcq_letter
self.assertIsNone(extract_mcq_letter("I am not sure."))
def test_ignores_lowercase_article_a(self):
from graders import extract_mcq_letter
self.assertEqual(extract_mcq_letter("This is a tricky one. Answer: B"), "B")
▸ GPQA Diamond is a gated dataset (its access terms exist to keep the questions out of training data), so neither the questions nor the model's response text are republished here: gpqa.jsonl carries per-question verdict, expected/given letter, tokens, and wall time — enough to re-verify the score. Fetch the dataset itself from HF with access and the harness reproduces the run, shuffle included.