Qwisp is a single-model-specialised local inference engine for Qwen3.6-35B-A3B (MoE) on Apple Silicon (MLX). It streams MoE expert weights from flash and keeps only the active slice resident, extending the reachable model size on RAM-constrained Macs. The decode core is Seedless — a raw-Metal engine (persistent buffers, hand-issued command buffers, int32 readback) that runs outside the MLX op-graph, peer to "MLX" as a backend.
Positioning: fastest practical-accuracy local LLM for power-users + researchers, with bit-exact lossless (strict L1: reproduces the quantised greedy token stream) exposed as an option. See @README.md.
Status: productization. The research phase is closed and the engine is frozen. Work is converging the ~29K-line research PoC into a shippable OpenAI-compatible local server. Volatile campaign state lives in
HANDOFF.mdandnotes/, not here.
swift/ # the product — Swift package
Sources/QwispCore/ # Tell runtime + Seedless engine (raw-Metal forward, arena/streaming, spec-verify) + locked tests
Sources/qwisp/ # OpenAI server + `qwisp chat` CLI + tokenizer (swift-transformers)
Sources/qwisp-poc/ # bench/gate binary (RAWTESTS + bench harness)
scripts/ # shell gate + benchmark scripts
oracle/ # Python reference/bench oracle (bit-compare only; NEVER in the serving path)
notes/ # engine design rationale (referenced by number from source comments)
docs/ # process docs (handoff-protocol, i18n-policy)
refs/ # canonical measurement refs (raw-greedy) — GITIGNORED, regenerate locally
The boundary that matters: Swift = product + engine; Python = reference oracle only. The server is Swift, in-process — the engine holds GBs of resident Metal buffers a language boundary cannot cheaply reach.
- Xcode + Metal Toolchain required (raw-Metal kernels). SourceKit shows "No such module 'MLX'"
on QwispCore files — LSP-only noise;
xcodebuildis the truth. - Model: a Qwen3.6-35B-A3B MTPLX checkpoint; point
QWISP_MODELat its directory. - Python reference oracle needs an MLX-capable python (numpy/safetensors/mlx_lm), not Homebrew
python3 — see
oracle/README.md.
# build (Release; Metal Toolchain required; ~minutes)
# scheme `qwisp` = product (server + CLI) ; scheme `qwisp-poc` = bench/gate binary
cd swift && xcodebuild build -scheme qwisp -configuration Release \
-destination 'platform=macOS' -derivedDataPath ./.xcode-build-rel \
-skipPackagePluginValidation
# correctness gates (must stay green through every commit)
scripts/test_raw.sh # → RAWTESTS 89/89 (engine, GPU, no model)
scripts/test_bench_batch.sh # → BENCHBATCHTEST PASS (fixture, no GPU)
scripts/test_tokenizer.sh # → TOKTEST 3/3 (needs model tokenizer files)
scripts/test_completion.sh # → COMPTEST 4/4 (needs model tokenizer files)refs/*.safetensors are gitignored. A fresh checkout / git clean loses them and makes the
strict fidelity gate false-red on longctx/shortnl — regenerate per HANDOFF.md before trusting a
red strict cell.
- Lossless is defined at L1: bit-exact reproduction of the quantised greedy token stream. The
strict path is the reference;
bolt/near-lossless is an opt-in speed tier. Never weaken the lossless definition to make a number look better. - RAWTESTS 89/89 is the campaign-wide safety gate. It must stay green through every delete/rename/refactor commit. A red gate blocks the commit, not the other way around.
- Predictive levers lose to mechanical levers on the same slack (engine doctrine, measured repeatedly). Don't re-propose prediction/prefetch schemes as speedups without new measurement.
- Measure before implementing. Heavy Metal/kernel changes get a Python physical-plausibility check first (the research phase burned weeks on plausible-but-wrong kernel ideas).
- The Seedless decode core does not use the MLX op-graph — its speed comes from raw command buffers and persistent buffers. MLX compatibility is coarse (load + generate + tier via a backend protocol), never op-level.
- Python stays a reference/bit-compare oracle. It is never on the serving path.
refs/is the canonical raw-greedy measurement set — regenerate from Swift raw greedy only, never from MLX or a bootstrap.
- Do not regenerate
refs/*.safetensorsfrom MLX or any non-raw-greedy source. - Do not weaken, skip, or delete the
WRITE-LOCKEDtests inswift/Sources/QwispCore/SeedlessVerifyTests.swift(guarded by thetotal = Ncounter; extending the suite with new locked tests bumps N — weakening existing ones never does). They are the lossless safety net. - Do not rewrite the shipped forward path (SeedlessEngine / SeedlessMetalForward / SeedlessFusedVerify / Tell / ExpertArena / ExpertSource + model layers). It is frozen — refactor/rename only, never rewrite.
- Do not rewrite
main's history. Work on a topic branch (claude/<topic>, etc.) and open a PR tomain. - Do not run two
qwisp-pocprocesses at once — the GPU is exclusive; heavy runs must be standalone.
- Conventional Commits with a scope:
feat(seedless):,feat(server):,fix(arena):,docs:,refactor:,test:,chore:. Research scopes likefeat(ghost):are historical. - When an agent authors a commit, append a
Co-Authored-By:trailer for the agent. - Push after every commit (working convention: don't leave commits unpushed).
Cross-session continuity uses HANDOFF.md at the repo root (overwritten each session; the
SessionStart hook re-injects it) plus the file-based memory index. HANDOFF.md is local-only
and gitignored — never commit it (the hook reads the local file; sessions 2026-07-19..22
drifted into committing it, re-agreed untracked 2026-07-22). The GitHub-issue protocol in
docs/handoff-protocol.md is the canonical spec for the issue-based variant if/when a workstream
moves to issues; the session-handoff label and .github/ISSUE_TEMPLATE/handoff.md support it.
qwisp ships a Japanese-facing README. Follow docs/i18n-policy.md:
- Translations are suffix files (
README.ja.mdnext toREADME.md); no language directories. - Only
README.mdand the user-facing introduction tier ofdocs/are in scope. Engineering docs and notes stay English-only (or the author's working language). - Each translated file carries a
> Source: <name>.md @ <sha>header. PRs are never blocked on translation parity.
All implementation work proceeds in this cycle:
- Red: write a failing test that captures the intended behaviour.
- Green: write the minimum code that makes the test pass.
- Refactor: tidy up while keeping tests green.
When a test fails, fix the production code — do not delete, skip, or weaken the test.
Base decisions on observed data, not assumptions. Before optimising, claiming a bottleneck, or asserting that something is slow or broken, measure it — profile, benchmark, log, or reproduce. When you report a cause, cite the measurement that supports it.
- Conventional Commits:
feat:fix:docs:refactor:test:ci:chore:. Project-specific prefixes (e.g.data:,experiments:) live in the project'sAGENTS.md. - Branch naming: use a short prefix for the agent or author followed by a topic, e.g.
claude/<topic>,codex/<topic>, orhuman/<topic>. - Trailer: when an AI agent authors the commit, append a trailer crediting the agent. Do not embed model name or session info in the trailer; put those in the commit body if needed.
- Always ready for review. Open PRs in the "ready" state, never as drafts. Draft PRs do not fire review-requested events and slow the loop.
- Auto-subscribe after creating a PR. Immediately after the PR is created, subscribe to its activity without asking the user. Rationale: the user explicitly opted into the "agent opens and watches its own PRs" workflow at the template level, so the per-PR confirmation is noise. Unsubscribe only when the user says to stop, when the PR merges, or when it is closed unmerged.
- One PR per workstream, matching the handoff issue. Reference the issue with
Closes #Nper.github/PULL_REQUEST_TEMPLATE.md.
Cloud agent sessions occasionally fail with Stream idle timeout - partial response received on long output. To reduce risk:
- Stage long writes. For long documents or source files, write the skeleton (headings, function signatures, trait stubs) first, then fill each section in follow-up edits. Avoid single blocks larger than ~200 lines.
- Watch out after large reads. Reading a big file (e.g.
Cargo.lock, large generated modules) and then immediately producing long output is a common trigger. Split into separate turns or excerpt only the relevant portion. - Recover carefully. A timeout can still leave the file write completed. Run
git statusbefore retrying so the same content is not written twice.
- Do not delete, skip, or comment out existing tests.
- Do not modify CI configuration without explicit instruction.
- Do not weaken production code merely to make tests pass.
- Do not commit credentials, API keys, signed URLs, or anything in
.env*.