- Python 93%
- Shell 4%
- Docker 1%
- Nix <1%
- C <1%
elpis-lossless #
Lossless speculative decoding for Qwen3.8-27B on one RTX 3090. Bend proves it.
- Model: Qwen3.8-27B, EXL3 4.00 bpw weights, 3-bit KV cache.
- Context: 262,144 tokens (native).
- Speculation: DFlash2 draft, 8-row token tree, greedy.
- GPU: one RTX 3090, 350 W cap.
- Proof:
bend PROOF.bend --verdict(Bend 2.0.35, Lean 4.34.0 kernel): ALL PROOFS CHECK.
elpis-lossless is the research build. elpis is the speed build. It uses the same model and benchmarks. It uses int8 prefill attention and makes no lossless claim.
| elpis-lossless | elpis | |
|---|---|---|
| Rule | Keep a speedup only if claims 1-3 stay proven | Keep the fastest build with measured quality |
| Draft can change the output | No: proven and tested | No: proven and tested |
| Prefill attention | fp32 sums; error bound ≤ stock (proven) | int8 Q·Kᵀ; error 3.6-33× stock (measured) |
| Cold prefill, geomean | 1,014.7 tok/s | 1,164.9 tok/s |
Lossless #
Lossless = claims 1, 2 and 3. Each claim has a Bend proof and a GPU test.
| # | Claim | Proof | Test |
|---|---|---|---|
| 1 | The draft never changes the output. elpis-lossless emits the tokens that its target emits at one token per round. This is true for every prompt and every draft, also an adversarial draft. | rinv_laws over spec_inv / spec_inv_tree: chain and 8-row tree, with the engine's own commits. The row hypothesis ~rinv is derived from the kernel laws, not assumed. |
15 prompts × tree / chain × normal / capped / all-rejected draft: 90/90 token-identical (#77, #78). |
| 2 | Speculation adds zero error. Acceptance compares token ids exactly. Nothing is approximated, sampled or thresholded. | Follows from 1. | Follows from 1. |
| 3 | No changed op is less accurate than stock. For each op that elpis-lossless changed, decode and prefill, the worst-case rounding-error bound is ≤ the bound of stock ExLlamaV3 355c6ee (no speculation, one token per step). This is true for every input. |
err_*_laws: the rounding steps from each input term to each output, for elpis-lossless and for stock, copied from source (file:line, checked by bend/err_*_diff.py). A dominated step count has a bound that is not larger (err_bound_laws.dom_sigma; Higham 2002, Lemma 3.1). |
Error vs an fp64 reference, per op (benchmarks §10). |
Lossless does not mean:
- Lossless against BF16: the 4.00 bpw weights and the 3-bit KV cache cost quality. The lane measures this cost. No proof covers it.
- Bit-identical to stock: elpis-lossless and stock round differently. elpis-lossless without the draft also differs: 4 of 15 long generations diverge, each at a near-tie (top-2 logits 0.016-0.17 apart).
- More accurate on each input: the proof bounds the worst case. On one input, either kernel can be nearer to the exact value.
Trust base (tested, not proven):
H_conform: the CUDA and host code agree with their Bend models (bend/*_diff.pysource links, GPU bitwise differentials).H_det,H_mma: the kernels are deterministic.mma.m16n8k16computes each row from that row only.- Rounding follows the standard model, with per-step bounds in the class order of
bend/err_bound.bend. H_mem: the GPU memory model holds at the split-K barrier.H_deploy,H_route,H_stock: the persisted autotune records, the default environment, and the default dispatch of stock.- Other hypotheses: each law module names them in its header. Example:
H_parkinbend/err_pfix_laws.bend(one prefill CTA per SM; the host checks it before the first launch).
Speed #
Target: most tok/s at 262,144 context, one RTX 3090, 350 W, lossless.
Status: p3031b (#78, sha256:6f597b13…) = p9502 (#77) + attention fixes 3030 (decode) and 3031 (prefill). The fixes make claim 3 true. Cost vs a fresh #77 run: decode +0.6-0.8 % ms per round, time to first token (TTFT) +0.4-0.8 %.
| 350 W | elpis-lossless p3031b (#78) |
elpis pfast1 |
|---|---|---|
| Cold prefill, geomean over 8K / 32K / 128K / 262K | 1,014.7 tok/s | 1,164.9 tok/s |
| TTFT, 8K / 32K / 128K / 262K | 5.60 / 24.42 / 148.75 / 431.47 s | 5.58 / 22.67 / 123.66 / 322.98 s |
| Decode, median ms per verify round, 1K / 8K / 32K context | 25.75 / 26.40 / 28.60 | 25.50 / 26.24 / 28.39 |
| Lane scores: AIME 2025 · MMLU-Pro · I3 Logic · LiveCodeBench v6 | 3/3 · 8/10 · 2/4 · 1/3 = 14/20 | 3/3 · 8/10 · 1/4 · 1/3 = 13/20 (p3021p, #73) |
| Attention error vs fp64, relative to stock | decode 0.9998×, prefill 0.890× (mean) | prefill 25× (median) |
| Workload, 350 W | tok/s | tok/J | Run |
|---|---|---|---|
| Lane: 20 calls, thinking on, whole request | 158.74 | not recorded | #78 |
| GSM8K: 40 questions, 512 tokens, median of 5 runs | 202.9 | 0.617 | #68 (tree3s) |
| C1: 1K / 8K / 32K prompt, 1,024 tokens out, whole request | 185.3 / 73.3 / 29.0 | 0.578 / 0.217 / 0.085 | #68 (tree3s) |
- Tokens per round change with the text. Compare tok/s only on the same text.
- #78 data: plan
g6f597b13, 2026-10-03/04, windows interleaved with #77 (benchmarks §10). elpis data: 2026-09-30.
Compared #
Figures of other projects come from their repositories. elpis-lossless did not run them again. No other result uses the same prompts, power cap and metric. This is not a ranking.
| One RTX 3090 | Weights | Speculation | Context / KV | Power | Reported tok/s |
|---|---|---|---|---|---|
| elpis-lossless (measured) | 4.00 bpw | DFlash2 + 8-row tree | 262,144 / 3-bit | 350 W cap | 158.7 lane (#78); 202.9 GSM8K (#68); whole request |
| trellis-serve README (MTP) | 3.00 bpw | MTP, 3 steps / 4 tokens | 212,992 / fp8 | not published | 96.2 prose, 141.1 code (thinking off); 141.3 prose, 129.3 code (thinking on); decode only (sweep) |
| trellis-serve fastest recipe (DFlash2, status "candidate") | 3.00 bpw | DFlash2, 5.0 bpw draft, block 8 | 131,072 / fp8 | not published | 98.2 prose, 225.1 code (thinking off); 227.0 prose, 195.4 code (thinking on); decode only |
| r0b0tlab | 4.00 bpw | DFlash2 | 8,192 / FP16 | 350 W cap | 162.9 GSM8K |
| One RTX 3090 | 32K prompt: TTFT | Longest prompt shown |
|---|---|---|
| elpis-lossless (#78) | 24.4 s (1,344 tok/s) | 262,052 tokens: 431.5 s |
elpis (pfast1) |
22.7 s (1,448 tok/s) | 262,052 tokens: 323.0 s |
| trellis-serve MTP | 21.9 s (1,497 tok/s) | 208,858 tokens: 294 s |
| trellis-serve DFlash2 | 35.9 s (914 tok/s) | 126,782 tokens: 187 s |
| r0b0tlab | not published (150K prompt: 594 tok/s) | 262,080 tokens (needle test) |
- Same test: GSM8K, 350 W cap, same 40 questions: elpis-lossless 202.9, r0b0tlab 162.9 tok/s (+24.6 %).
bench/gsm8k_compare.pyruns the workload of r0b0tlab'sacceptance_check.py. - Metric: trellis-serve reports decode only. elpis-lossless divides by the full request time: prefill and HTTP included.
- Bits: 3.00 bpw reads 25 % fewer weight bits per token than 4.00 bpw (computed), at a larger quantization error.
- Clocks: trellis-serve publishes no power limit. Its DFlash2 soak held the SMs at 1.74 GHz. elpis-lossless: 1.51 GHz median per call (#68 lane).
Proofs and tests #
| Claim | Evidence | Result |
|---|---|---|
| The draft never changes the output | Bend: rinv_laws over spec_inv / spec_inv_tree; ~rinv derived (rowinv_link, rowinv_served, lmhead) |
proven |
| Same, on the GPU | 15 prompts × normal / capped / all-rejected draft × tree and chain | 90/90 on #76, #77, #78 |
| No changed op is less accurate than stock | Bend: err_bound_laws, err_gemm, err_gemm_runs, err_gdn, err_elem, err_attn, err_attn_dec (3030), err_prefill, err_pfix (3031) |
proven |
| Same, measured against fp64 | Each op, decode and prefill; attention on captured q and 3-bit K/V at 10 context lengths | decode ops 0.05-0.84× stock; attention mean error 0.9998× (decode), 0.890× (prefill) stock |
| Acceptance logic | exl3_accept, exl3_tree_accept = list references; emitted to C; checked by table at build |
proven |
| Full contract | bend PROOF.bend --verdict: 58 law modules, 62 proof modules |
ALL PROOFS CHECK |
| Kernel changes | GDN state hashes, 1-8 steps (5108); 64 layers × rows 1-8 × 30 graph replays (2113); all 5,040 tree shapes vs the chain kernel (3012) | bit-exact |
Decode speedups cs10 → tree3s keep the text |
lane 20 + C1 15 answers | byte-identical |
| Power and clocks keep the text | 250 W vs 350 W; memory offsets 0 … −2000 MHz | byte-identical |
LAWS.bend= contract.PROOF.bend= proofs. Order for each engine change: law → proof → measurement.- Bend proves facts about Bend models of the kernels. Source links and GPU tests check the link from model to CUDA. No proof covers this link. See the trust base in Lossless.
err_attn_findinganderr_prefill_findingprove that the attention before 3030 / 3031 did not meet claim 3.
How #
| Lever | Measured |
|---|---|
| DFlash2 draft: one pass proposes 7 tokens | 1.00 → 3.39 tokens per round at 1K; round time unchanged (≈34 ms, 250 W) |
| 8-row token tree: 7 nodes, best first; commit the longest matching path + 1 | 3.39 → 3.89 tokens per round at 1K; lane 111.85 → 122.33 tok/s (250 W) |
52 engine patches: 5 patches/exl3 + 47 patches/exl3-ext |
each changed op: worst-case error bound ≤ stock (claim 3) |
| 350 W cap, quiet fans (≤ 80 % to 84 °C) | lane +32.3 % tok/s vs 250 W at equal tok/J (0.497 vs 0.495) |
| Stock memory clock at 350 W | +5.4 % tok/s, +6.4 % tok/J vs −1500 MHz (sweep) |
Run #
- Make a private
QWEN_STATE_ROOTwithmodels/,cache/,prefix-cache/(mode 0700),api-keyandqwen-inference-launch.lock(docs/docker.md). - Install Docker with Compose and Buildx, the NVIDIA Container Toolkit (CDI
nvidia.com/gpu=0) and Nix with flakes. - Download the pinned weights and check every byte:
export QWEN_STATE_ROOT=/absolute/path/to/state
hf download r0b0tlab/Qwen3.8-27B-EXL3-4.00bpw --revision 3f1771b8c21f83cbb8e82169559ced9f38ca04e5 --local-dir "$QWEN_STATE_ROOT/models/qwen38-27b-exl3"
hf download r0b0tlab/Qwen3.8-27B-DFlash2-EXL3-4.00bpw --revision 265b5240592907d2d55ff0dc4d5f66569692604d --local-dir "$QWEN_STATE_ROOT/models/dflash2-exl3"
python3 -B prepare/verify-models.py --target "$QWEN_STATE_ROOT/models/qwen38-27b-exl3" --draft "$QWEN_STATE_ROOT/models/dflash2-exl3" --representation exl3
- Build from source and start:
nix develop --no-write-lock-file -c true # get the pinned Nix toolchain once
bash docker/fetch-base.sh # the only network step: download and sha256-check every base input
bash docker/build-base.sh # no network; tags qwen-elpis:exl3-base only if its content manifest matches the pin
bash docker/build-exl3.sh candidate-ext qwen-inference:exl3
export QWEN_IMAGE=qwen-inference:exl3
export QWEN_IMAGE_ID="$(docker image inspect -f '{{.Id}}' "$QWEN_IMAGE")" # sha256:<64 hex>
export QWEN_ALLOW_UNQUALIFIED=1
docker compose --project-name qwen-inference up --no-build --pull never --detach --wait
- API:
http://127.0.0.1:18020/v1, modelqwen3.8-27b. Do not run another inference service on the same GPU. - Prefix cache: kept across restarts, bound to
QWEN_IMAGE_ID.QWEN_PREFIX_PERSIST=0turns it off. - Docker owns runtime and restarts. Nix pins the tools, the
.#bendtoolchain and a Compose adapter (nix/STANDALONE.md).
Measure #
bash autoresearch.sh # the lane; protocol: docs/benchmarks.md; scoring: Prime Envs + Verifiers (eval/README.md)
curl -o gsm8k-test.jsonl https://raw.githubusercontent.com/openai/grade-school-math/3101c7d5072418e28b9008a6636bde82a006892c/grade_school_math/data/test.jsonl
python3 -m bench.gsm8k_compare --api-key-file /path/to/api-key --data gsm8k-test.jsonl --out gsm8k.json
Prove #
nix run .#bend -- PROOF.bend # every law, TypeScript checker (about 10 min)
nix run .#bend-verdict -- PROOF.bend --verdict # the same, checked again by the Lean-proven kernel (about 1.7 h)
nix develop --no-write-lock-file -c python3 -I -B bend/engine_trees.py OUT # stock + patched ExLlamaV3 from the pinned tarball
nix develop --no-write-lock-file -c python3 -I -B bend/err_gemm_diff.py --stock OUT/stock # one source link: Bend model text vs patched source
Every source link, its engine tree and its inputs: docs/development.md.
Setup #
| GPU | RTX 3090 24 GiB (GA102, SM86), VBIOS 94.02.42.80.1F, PCIe 4.0 ×16, driver 595.71.05 |
| Power, clocks | 350 W cap since 2026-09-28 (250 W before); core and memory offsets 0; the lane checks all three via NVML |
| Host | Ryzen 7 5800X (8 cores / 16 threads), 125.7 GiB, NixOS 26.05, Linux 6.18.50 |
| Runtime | rootless Docker 29.7.2, CDI, read-only root; Ubuntu 24.04 CUDA base; Python 3.13.10, PyTorch 2.10.0+cu130, CUDA 13.0.96, cuBLAS 13.1.0.3, Triton 3.6.0 |
| Engine | ExLlamaV3 1.5.0 355c6ee (r0b0tlab community, native DFlash2) + 5 patches/exl3 + 47 patches/exl3-ext, SHA256-pinned |
| Server | serve/exl3_server.py: authenticated OpenAI-compatible /v1 chat/completions + tool calls, greedy, one sequence |
| Target | r0b0tlab/Qwen3.8-27B-EXL3-4.00bpw @ 3f1771b8 (Qwen/Qwen3.8-27B): 48 Gated DeltaNet + 16 full-attention layers, hidden 5,120, vocab 248,320; 4.00 bpw, 6 bpw head; 16.5 GB |
| Draft | r0b0tlab/Qwen3.8-27B-DFlash2-EXL3-4.00bpw @ 265b5240 (incoai/Qwen3.8-27B-DFlash2): 5 sliding-attention layers, block 8, reads target layers 5/19/33/47/61, top-16 selector; 1.25 GB |
| Recipe | serve/exl3-entrypoint.sh: context 262,144, cache 270,336, 3-bit KV (12 KiB per token: 16 of 64 layers keep KV); 8 verify rows per round; each file hashed again against prepare/exl3-manifest.json at start |
Limitations #
- Greedy only. One sequence at a time.
- Chat stream=true sends buffered SSE. The first event is not the first token.
- Exact logit ties can depend on max_tokens (cs12: token 39457 at 8192 vs 54185 at 256,
p = 0.28775 each). Compare outputs only at equal request parameters.
References #
- Protocol, every run, every change, every dropped attempt: docs/benchmarks.md
- Architecture and the Bend proof boundary: docs/architecture.md
- Deployment: docs/docker.md
- Evaluator: eval/README.md
- All docs: docs/README.md
- Security reports: SECURITY.md
- License: MIT (LICENSE)