Image-Text-to-Text
MLX
Safetensors
qwen3_5
apple-silicon
quantized
8-bit precision
multimodal
vision-language
hybrid-attention
speculative-decoding
conversational
4-bit precision
Instructions to use avlp12/Qwen3.8-27B-Alis-MLX-8bit with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use avlp12/Qwen3.8-27B-Alis-MLX-8bit with MLX:
# Make sure mlx-vlm is installed # pip install --upgrade mlx-vlm from mlx_vlm import load, generate from mlx_vlm.prompt_utils import apply_chat_template from mlx_vlm.utils import load_config # Load the model model, processor = load("avlp12/Qwen3.8-27B-Alis-MLX-8bit") config = load_config("avlp12/Qwen3.8-27B-Alis-MLX-8bit") # Prepare input image = ["http://images.cocodataset.org/val2017/000000039769.jpg"] prompt = "Describe this image." # Apply chat template formatted_prompt = apply_chat_template( processor, config, prompt, num_images=1 ) # Generate output output = generate(model, processor, formatted_prompt, image) print(output) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Pi
How to use avlp12/Qwen3.8-27B-Alis-MLX-8bit with Pi:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "avlp12/Qwen3.8-27B-Alis-MLX-8bit"
Configure the model in Pi
# Install Pi: npm install -g @earendil-works/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "mlx-lm": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "avlp12/Qwen3.8-27B-Alis-MLX-8bit" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Hermes Agent
How to use avlp12/Qwen3.8-27B-Alis-MLX-8bit with Hermes Agent:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "avlp12/Qwen3.8-27B-Alis-MLX-8bit"
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default avlp12/Qwen3.8-27B-Alis-MLX-8bit
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use avlp12/Qwen3.8-27B-Alis-MLX-8bit with OpenClaw:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "avlp12/Qwen3.8-27B-Alis-MLX-8bit"
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "avlp12/Qwen3.8-27B-Alis-MLX-8bit" \ --custom-provider-id mlx-lm \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
Restate speculative-decoding tables under the EOS-cut protocol; add real-world sampling numbers
Browse files- README.md +30 -21
- assets/builds.png +2 -2
- assets/builds.svg +27 -45
- assets/make_builds_png.py +40 -55
README.md
CHANGED
|
@@ -17,7 +17,7 @@ tags:
|
|
| 17 |
|
| 18 |
# Qwen3.8-27B-Alis-MLX-8bit
|
| 19 |
|
| 20 |
-
 for Apple silicon, with the **vision tower and the MTP head both preserved**. 27.9 GB on disk, 21.8 tok/s decode, and corpus perplexity **statistically indistinguishable from the bf16 original** on English, Korean, and code alike — paired over ≈103K scored tokens. This is the fidelity build of the set.
|
| 23 |
|
|
@@ -150,25 +150,33 @@ mlx_lm.generate --model avlp12/Qwen3.8-27B-Alis-MLX-8bit \
|
|
| 150 |
--max-tokens 512
|
| 151 |
```
|
| 152 |
|
| 153 |
-
**
|
| 154 |
|
| 155 |
-
|
| 156 |
-
|---|---|---|---|---|---|---|
|
| 157 |
-
| plain | 37.63 | 37.64 | 37.7 | 37.6 | 37.6 | 37.6 |
|
| 158 |
-
| MTP k=2 | 50.36 (1.34×) | 55.71 (1.48×) | 50.6 | 54.6 | 62.0 | 34.3 |
|
| 159 |
-
| DSpark drafter | 62.21 (1.65×) | 71.86 (1.91×) | 49.0 | 75.1 | 91.5 | 33.3 |
|
| 160 |
|
| 161 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
|
| 163 |
-
|
| 164 |
|
| 165 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
|
| 167 |
-
|
| 168 |
|
| 169 |
-
|
| 170 |
|
| 171 |
-
|
|
|
|
|
|
|
| 172 |
|
| 173 |
Two things to settle first:
|
| 174 |
|
|
@@ -194,23 +202,24 @@ prompt = mx.array(tokenizer.apply_chat_template(
|
|
| 194 |
add_generation_prompt=True,
|
| 195 |
))
|
| 196 |
|
| 197 |
-
for token, _accepted in dspark_generate_step(prompt, model, draft, max_tokens=512
|
|
|
|
| 198 |
print(tokenizer.decode([token.item()]), end="", flush=True)
|
| 199 |
```
|
| 200 |
|
| 201 |
-
|
| 202 |
|
| 203 |
-
|
| 204 |
|
| 205 |
-
|
| 206 |
|
| 207 |
-
The
|
| 208 |
|
| 209 |
### Measurement protocol, and why single-prompt numbers lie
|
| 210 |
|
| 211 |
-
Every figure
|
| 212 |
|
| 213 |
-
|
| 214 |
|
| 215 |
## KV cache quantization
|
| 216 |
|
|
@@ -278,7 +287,7 @@ Two honest caveats on the table itself:
|
|
| 278 |
- **Vision quality is unquantified.** The tower is preserved bit-exact and verified to work on a shapes probe. That is a functional check, not a VQA evaluation. No image benchmark was run at any bit-width.
|
| 279 |
- **Shape-dependent numerics.** The hybrid stack takes different kernel paths depending on the shape of the input it receives — the batched scoring pass behind the perplexity table is not the same shape as incremental decode, and near-tie positions can resolve differently between those paths. Perplexity is far less sensitive to this than an argmax metric, but do not expect bit-identical numbers from a different harness.
|
| 280 |
- **Long-form generated reasoning is unprobed.** Perplexity scores 2048-token windows of existing text; it does not exercise long generated reasoning chains, where low-bit damage tends to concentrate. Spot-check your own thinking-mode prompts.
|
| 281 |
-
- **Speculative-decoding gains are workload-dependent
|
| 282 |
|
| 283 |
## License and attribution
|
| 284 |
|
|
|
|
| 17 |
|
| 18 |
# Qwen3.8-27B-Alis-MLX-8bit
|
| 19 |
|
| 20 |
+

|
| 21 |
|
| 22 |
An 8-bit MLX quantization of [Qwen/Qwen3.8-27B](https://huggingface.co/Qwen/Qwen3.8-27B) for Apple silicon, with the **vision tower and the MTP head both preserved**. 27.9 GB on disk, 21.8 tok/s decode, and corpus perplexity **statistically indistinguishable from the bf16 original** on English, Korean, and code alike — paired over ≈103K scored tokens. This is the fidelity build of the set.
|
| 23 |
|
|
|
|
| 150 |
--max-tokens 512
|
| 151 |
```
|
| 152 |
|
| 153 |
+
**Correction (2026-08-16).** The speculative table earlier revisions of this card quoted from the 4-bit build — plain 37.63, MTP k=2 50.36, DSpark 62.21 — came from a harness that **did not stop at end-of-sequence**: on prompts whose answer ended inside the fixed window, the remainder measured post-termination behavior, and on the math prompt the model spent that tail copying its own finished answer (acceptance 4.53), which carried most of the headline. Those figures are retracted, as the footnote they carried promised they would be if unreproducible. Everything below is the re-measurement: **the harness stops at EOS, the four prompts (chat / code / math / Korean) are long-form** so every scored token is genuine answer decode, sampled rows are medians of three runs per prompt averaged across prompts, and stop-detection stays out of the timed loop.
|
| 154 |
|
| 155 |
+
The headline tables are still **measured on the 4-bit build of this set** (its earlier uniform weights; the AWQ swap measures within ±1%, paired) — quoted because they are what the fork's two speculative paths do on this checkpoint. This build now also has its own EOS-clean measurement, further below. Greedy first:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
|
| 157 |
+
| Configuration (greedy, 4-bit build) | tok/s | vs plain |
|
| 158 |
+
|---|---|---|
|
| 159 |
+
| plain | 37.6 | 1.00 |
|
| 160 |
+
| DSpark drafter (block 8) | 48.3 | 1.28× |
|
| 161 |
+
| MTP k=2 | 46.8 | 1.24× |
|
| 162 |
+
| **MTP k=4 + `min_draft_p` 0.6** | **52.8** | **1.40×** |
|
| 163 |
|
| 164 |
+
And under the shipped sampling defaults — temperature 1.0, top_p 0.95, top_k 20, what a chat deployment actually runs — using truncated rejection sampling, which passes target and draft distributions through the same temperature/top-p/top-k chain and so **preserves the requested output distribution exactly** (total variation ≤ 0.0014 against a synthetic oracle; 160/160 token match in the greedy limit):
|
| 165 |
|
| 166 |
+
| Configuration (temp 1.0 · top_p 0.95 · top_k 20, 4-bit build) | 240 tok | vs plain | 1024 tok | vs plain |
|
| 167 |
+
|---|---|---|---|---|
|
| 168 |
+
| plain, sampler on | 37.16 | 1.00 | 36.91 | 1.00 |
|
| 169 |
+
| DSpark drafter | 41.94 | 1.13× | 39.95 | 1.08× |
|
| 170 |
+
| MTP k=2 | 47.74 | 1.28× | 44.10 | 1.19× |
|
| 171 |
+
| **MTP k=4 + `min_draft_p` 0.6** | **48.05** | **1.29×** | **45.13** | **1.22×** |
|
| 172 |
|
| 173 |
+
The gated k=4 row is the 4-bit recommendation (`mtp_num_draft_tokens=4, min_draft_p=0.6` via the Python API, plus `mtp_spec_temp=1.0, mtp_spec_top_k=20, mtp_spec_top_p=0.95` under the sampling defaults — the gate is what makes k=4 win; ungated deeper k measured below k=2). The small-M quantized-GEMM kernel behind the 4-bit verification window fires only on **4-bit, group-64** weights ([ml-explore/mlx#4265](https://github.com/ml-explore/mlx/issues/4265); kill switch `MLXLM_NO_FAST_QMM=1`), so it never engages here — which is priced into the next paragraph.
|
| 174 |
|
| 175 |
+
**This build's own numbers, EOS-clean, same four-prompt protocol, greedy:** plain **21.8 tok/s** · MTP k=2 **31.1 (1.42×)** · DSpark at its 8-bit optimum, block 4, **33.6 (1.54×)**. The multiples are larger than the 4-bit build's because plain decode is slower here while drafting costs about the same — but mind the absolute ceiling: 8-bit with DSpark (33.6) is still slower than the 4-bit build running plain (37.6). Take this build for fidelity and speculate on it by all means; do not take it for speed.
|
| 176 |
|
| 177 |
+
## DSpark: an external drafter
|
| 178 |
+
|
| 179 |
+
DSpark is the fork's other speculative path, and on this build it is the faster of the two — 33.6 tok/s (1.54×) at block 4, EOS-clean, against MTP k=2's 31.1. Unlike MTP it is **not** self-speculation: it drives an external drafter, [`RadixArk/Qwen3.8-27B-DSpark`](https://huggingface.co/RadixArk/Qwen3.8-27B-DSpark) — 1.36B parameters, trained against this checkpoint, published by a third party. The drafter carries no embedding and no output head of its own; it borrows the target's, and drafts a whole block of positions in one forward pass instead of chaining them.
|
| 180 |
|
| 181 |
Two things to settle first:
|
| 182 |
|
|
|
|
| 202 |
add_generation_prompt=True,
|
| 203 |
))
|
| 204 |
|
| 205 |
+
for token, _accepted in dspark_generate_step(prompt, model, draft, max_tokens=512,
|
| 206 |
+
block_size=4):
|
| 207 |
print(tokenizer.decode([token.item()]), end="", flush=True)
|
| 208 |
```
|
| 209 |
|
| 210 |
+
**On this build, run block 4, not the block-8 default.** The default block size of 8 is the 4-bit operating point, where the small-M kernel makes wide verification windows nearly free. That kernel never engages at 8 bits, so width costs what it costs here and the measured optimum shrinks to block 4 — 36.3 tok/s against 35.1 at block 8 on the English-prompt block sweep, 33.6 on the full four-prompt set. The call above pins it. An earlier revision of this card warned that a pre-kernel 8-bit measurement ran DSpark below plain decode and that there might be "nothing here to collect" — the EOS-clean re-measurement retires that warning: 1.54× is real. Just weigh the download against the arithmetic above before collecting it.
|
| 211 |
|
| 212 |
+
### Korean: the recommendation reversed
|
| 213 |
|
| 214 |
+
Until this revision the card said **"run Korean workloads plain,"** on the strength of 4-bit Korean cells that read below plain decode. **That was an artifact of the retracted protocol** — short answers plus a harness that measured past their end. Under the corrected protocol the gated MTP configuration on the 4-bit build is **faster on Korean than plain**: +34% greedy, +27–31% under the shipped sampling defaults. And on this build's own EOS-clean measurement both paths clear plain on Korean as well — MTP k=2 23.9 and DSpark 24.6 against 21.8 plain, greedy.
|
| 215 |
|
| 216 |
+
The honest exception sits on the 4-bit tier: **DSpark under the sampling defaults stays at or below plain on Korean there** (well below at 240 tokens, break-even at best by 1024). The reversal is about the gated MTP path and about this build; it is not a blanket license for every path on every tier.
|
| 217 |
|
| 218 |
### Measurement protocol, and why single-prompt numbers lie
|
| 219 |
|
| 220 |
+
Every figure above is the **average over the fixed four-prompt set**, never a single run. Speculative decoding pays off in proportion to how predictable the continuation is, so a benchmark built from one favorable prompt **overstates the gain you will see in real traffic** by a wide margin — treat any single-prompt speculative figure, including mine, as an upper bound.
|
| 221 |
|
| 222 |
+
The correction at the top of this section adds a second rule, learned at the cost of a retraction: **a speculative benchmark that does not stop at end-of-sequence is measuring luck** — what the model happens to do after its answer ends. The retracted 62.21 headline, and the 91.5 tok/s math cell behind it, were mostly that luck. The per-cell records behind the restated tables ship in the [campaign repository](https://github.com/avlp12/qwen38_alis_mlx).
|
| 223 |
|
| 224 |
## KV cache quantization
|
| 225 |
|
|
|
|
| 287 |
- **Vision quality is unquantified.** The tower is preserved bit-exact and verified to work on a shapes probe. That is a functional check, not a VQA evaluation. No image benchmark was run at any bit-width.
|
| 288 |
- **Shape-dependent numerics.** The hybrid stack takes different kernel paths depending on the shape of the input it receives — the batched scoring pass behind the perplexity table is not the same shape as incremental decode, and near-tie positions can resolve differently between those paths. Perplexity is far less sensitive to this than an argmax metric, but do not expect bit-identical numbers from a different harness.
|
| 289 |
- **Long-form generated reasoning is unprobed.** Perplexity scores 2048-token windows of existing text; it does not exercise long generated reasoning chains, where low-bit damage tends to concentrate. Spot-check your own thinking-mode prompts.
|
| 290 |
+
- **Speculative-decoding gains are workload-dependent.** The speculative tables were restated on 2026-08-16 under an EOS-cut protocol after the earlier harness was found to measure past end-of-sequence; the correction in the MTP section is the full account.
|
| 291 |
|
| 292 |
## License and attribution
|
| 293 |
|
assets/builds.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
assets/builds.svg
CHANGED
|
|
|
|
assets/make_builds_png.py
CHANGED
|
@@ -17,7 +17,7 @@ import matplotlib
|
|
| 17 |
|
| 18 |
matplotlib.use("Agg")
|
| 19 |
import matplotlib.pyplot as plt
|
| 20 |
-
from matplotlib.patches import
|
| 21 |
|
| 22 |
plt.rcParams["font.family"] = ["Helvetica Neue", "Helvetica", "Arial", "DejaVu Sans"]
|
| 23 |
|
|
@@ -93,46 +93,28 @@ ax.plot([179.1, 275.0, 372.4, 736.1], [407.5, 478.9, 517.4, 581.8],
|
|
| 93 |
line(100, 581.8, 800, 581.8, CB, 1.4, 0.8, dash=(6, 6), z=2)
|
| 94 |
txt(108, 573.8, "bf16 baseline — 12.6 tok/s", 12.5, CB)
|
| 95 |
|
| 96 |
-
# ============ speculative
|
| 97 |
-
#
|
| 98 |
-
|
| 99 |
-
#
|
| 100 |
-
line(179.1,
|
| 101 |
-
line(
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
txt(200, 284, "55.7 — 3 English prompts", 12.5, CM)
|
| 107 |
-
|
| 108 |
-
# lower spread: plain 4-bit -> Korean-only lows
|
| 109 |
-
line(179.1, 415.5, 179.1, 436.9, GREY, 1.3, 0.35, z=3)
|
| 110 |
-
line(172.1, 429.9, 186.1, 429.9, CM, 2.2, 1.0, z=4)
|
| 111 |
-
line(171.1, 436.9, 187.1, 436.9, CD, 2.4, 1.0, z=4)
|
| 112 |
-
txt(166, 428, "ko 34.3", 12.5, CM, ha="right")
|
| 113 |
-
txt(166, 446, "ko 33.3", 12.5, CD, ha="right")
|
| 114 |
-
|
| 115 |
-
# ============ lift connectors: plain 37.6 -> MTP 50.36 -> DSpark 62.21 ============
|
| 116 |
-
line(179.1, 399.5, 179.1, 329.5, CM, 1.6, 1.0, dash=(5, 5), z=3)
|
| 117 |
-
txt(166, 370, "+34%", 14, CM, "bold", ha="right")
|
| 118 |
-
|
| 119 |
-
line(179.1, 306.5, 179.1, 250.5, CD, 1.6, 1.0, dash=(5, 5), z=3)
|
| 120 |
-
txt(166, 265, "+65%", 14, CD, "bold", ha="right")
|
| 121 |
-
|
| 122 |
-
# ============ 4-bit + DSpark drafter (triangle, ringed) ============
|
| 123 |
-
ax.add_patch(Circle((179.1, 234.5), 15.5, fill=False, edgecolor=CD,
|
| 124 |
-
alpha=0.45, linewidth=1.3 * PT, zorder=5))
|
| 125 |
-
ax.add_patch(Polygon([(179.1, 223.5), (191.1, 245.5), (167.1, 245.5)],
|
| 126 |
-
closed=True, facecolor=CD, linewidth=0, zorder=6))
|
| 127 |
-
txt(200, 229, "4-bit + DSpark drafter", 15.5, CD, "bold")
|
| 128 |
-
txt(200, 248, "62.21 tok/s · 1.65× vs plain 4-bit", 13.5, CD)
|
| 129 |
-
|
| 130 |
-
# ============ 4-bit + MTP k=2 (diamond) ============
|
| 131 |
-
ax.add_patch(Polygon([(179.1, 306.5), (190.1, 317.5), (179.1, 328.5),
|
| 132 |
-
(168.1, 317.5)],
|
| 133 |
closed=True, facecolor=CM, linewidth=0, zorder=6))
|
| 134 |
-
txt(
|
| 135 |
-
txt(200,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
|
| 137 |
# ============ 4-bit ============
|
| 138 |
ax.plot([179.1], [407.5], "o", markersize=16 * PT, color=C4, zorder=6)
|
|
@@ -158,14 +140,15 @@ txt(790, 512, "bf16 reference", 15.5, CB, "bold", ha="right")
|
|
| 158 |
txt(790, 531, "51.8 GB · 12.6 tok/s", 13.5, CB, ha="right")
|
| 159 |
txt(790, 549, "ko PPL 6.095 — reference", 12.5, CB, ha="right")
|
| 160 |
|
| 161 |
-
# ============ in-plot callout: the
|
| 162 |
ax.add_patch(FancyBboxPatch((459.5, 190.5), 0.1, 15,
|
| 163 |
boxstyle="round,pad=1.5,rounding_size=1.5",
|
| 164 |
linewidth=0, facecolor=CD, zorder=4))
|
| 165 |
-
txt(470, 200, "
|
| 166 |
-
txt(470, 222, "
|
| 167 |
-
txt(470, 240, "
|
| 168 |
-
txt(470,
|
|
|
|
| 169 |
|
| 170 |
# ============ side facts ============
|
| 171 |
ax.add_patch(FancyBboxPatch((836, 122), 330, 468,
|
|
@@ -194,7 +177,8 @@ txt(858, 438, "with --kv-bits 4 — top-1 unchanged.", 13, MUT)
|
|
| 194 |
txt(858, 498, "Speculation is opt-in", 15, INK, "bold")
|
| 195 |
txt(858, 520, "Both paths need the avlp12/mlx-lm fork.", 13, MUT)
|
| 196 |
txt(858, 538, "The MTP head ships inside this build; the", 13, MUT)
|
| 197 |
-
txt(858, 556, "DSpark drafter is a separate 0.76 GB
|
|
|
|
| 198 |
|
| 199 |
# ============ footnotes ============
|
| 200 |
for fy, s in [
|
|
@@ -204,15 +188,16 @@ for fy, s in [
|
|
| 204 |
(701, "wiki 35,179, code CPython 32,295 tokens. 8-bit ≈ bf16 on every "
|
| 205 |
"slice, 6-bit ≈ bf16 on Korean; 4-bit AWQ: +1.2% en, +2.7% ko, "
|
| 206 |
"+7.7% code. No standard benchmarks were run."),
|
| 207 |
-
(720, "Speculative decoding
|
| 208 |
-
"
|
| 209 |
-
"
|
| 210 |
-
(739, "
|
| 211 |
-
"
|
| 212 |
-
"
|
| 213 |
-
(758, "
|
| 214 |
-
"
|
| 215 |
-
"
|
|
|
|
| 216 |
txt(40, fy, s, 12, MUT)
|
| 217 |
|
| 218 |
out = "/Users/gesicht/qwen38/cards/assets/builds.png"
|
|
|
|
| 17 |
|
| 18 |
matplotlib.use("Agg")
|
| 19 |
import matplotlib.pyplot as plt
|
| 20 |
+
from matplotlib.patches import FancyBboxPatch, Polygon
|
| 21 |
|
| 22 |
plt.rcParams["font.family"] = ["Helvetica Neue", "Helvetica", "Arial", "DejaVu Sans"]
|
| 23 |
|
|
|
|
| 93 |
line(100, 581.8, 800, 581.8, CB, 1.4, 0.8, dash=(6, 6), z=2)
|
| 94 |
txt(108, 573.8, "bf16 baseline — 12.6 tok/s", 12.5, CB)
|
| 95 |
|
| 96 |
+
# ============ speculative ladder on the 4-bit build (all at 15.2 GB) ============
|
| 97 |
+
# EOS-cut protocol, restated 2026-08-16 · greedy 4-prompt averages
|
| 98 |
+
|
| 99 |
+
# lift connectors: plain 37.6 -> MTP k=2 46.8 -> gated MTP k=4 52.8
|
| 100 |
+
line(179.1, 399.5, 179.1, 355.5, CM, 1.6, 1.0, dash=(5, 5), z=3)
|
| 101 |
+
line(179.1, 330.4, 179.1, 312.4, CM, 1.6, 1.0, dash=(5, 5), z=3)
|
| 102 |
+
|
| 103 |
+
# ============ 4-bit + gated MTP k=4 (filled diamond, top) ============
|
| 104 |
+
ax.add_patch(Polygon([(179.1, 289.4), (190.1, 300.4), (179.1, 311.4),
|
| 105 |
+
(168.1, 300.4)],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
closed=True, facecolor=CM, linewidth=0, zorder=6))
|
| 107 |
+
txt(166, 304, "+40%", 14, CM, "bold", ha="right")
|
| 108 |
+
txt(200, 296, "4-bit + MTP k=4, p-gated", 15.5, CM, "bold")
|
| 109 |
+
txt(200, 315, "52.8 tok/s · 1.40× vs plain 4-bit (greedy)", 13.5, CM)
|
| 110 |
+
|
| 111 |
+
# ============ 4-bit + MTP k=2 (open diamond) ============
|
| 112 |
+
ax.add_patch(Polygon([(179.1, 331.4), (190.1, 342.4), (179.1, 353.4),
|
| 113 |
+
(168.1, 342.4)],
|
| 114 |
+
closed=True, fill=False, edgecolor=CM,
|
| 115 |
+
linewidth=2 * PT, zorder=6))
|
| 116 |
+
txt(166, 346, "+24%", 14, CM, "bold", ha="right")
|
| 117 |
+
txt(200, 347, "MTP k=2 — 46.8 tok/s · 1.24×", 13.5, CM)
|
| 118 |
|
| 119 |
# ============ 4-bit ============
|
| 120 |
ax.plot([179.1], [407.5], "o", markersize=16 * PT, color=C4, zorder=6)
|
|
|
|
| 140 |
txt(790, 531, "51.8 GB · 12.6 tok/s", 13.5, CB, ha="right")
|
| 141 |
txt(790, 549, "ko PPL 6.095 — reference", 12.5, CB, ha="right")
|
| 142 |
|
| 143 |
+
# ============ in-plot callout: the restatement ============
|
| 144 |
ax.add_patch(FancyBboxPatch((459.5, 190.5), 0.1, 15,
|
| 145 |
boxstyle="round,pad=1.5,rounding_size=1.5",
|
| 146 |
linewidth=0, facecolor=CD, zorder=4))
|
| 147 |
+
txt(470, 200, "Restated 2026-08-16 (EOS-cut)", 15, INK, "bold")
|
| 148 |
+
txt(470, 222, "Speculative points are EOS-cut 4-prompt averages,", 13, MUT)
|
| 149 |
+
txt(470, 240, "greedy. The old 62.2 / 50.4 headline decoded past", 13, MUT)
|
| 150 |
+
txt(470, 258, "EOS and is retracted. DSpark restates to 48.3", 13, MUT)
|
| 151 |
+
txt(470, 276, "(1.28×); Korean gains +27–31% with the gated path.", 13, MUT)
|
| 152 |
|
| 153 |
# ============ side facts ============
|
| 154 |
ax.add_patch(FancyBboxPatch((836, 122), 330, 468,
|
|
|
|
| 177 |
txt(858, 498, "Speculation is opt-in", 15, INK, "bold")
|
| 178 |
txt(858, 520, "Both paths need the avlp12/mlx-lm fork.", 13, MUT)
|
| 179 |
txt(858, 538, "The MTP head ships inside this build; the", 13, MUT)
|
| 180 |
+
txt(858, 556, "DSpark drafter is a separate 0.76 GB", 13, MUT)
|
| 181 |
+
txt(858, 574, "download — greedy 48.3 tok/s (1.28×).", 13, MUT)
|
| 182 |
|
| 183 |
# ============ footnotes ============
|
| 184 |
for fy, s in [
|
|
|
|
| 188 |
(701, "wiki 35,179, code CPython 32,295 tokens. 8-bit ≈ bf16 on every "
|
| 189 |
"slice, 6-bit ≈ bf16 on Korean; 4-bit AWQ: +1.2% en, +2.7% ko, "
|
| 190 |
"+7.7% code. No standard benchmarks were run."),
|
| 191 |
+
(720, "Speculative decoding (restated 2026-08-16): EOS-cut, long-form four "
|
| 192 |
+
"prompts (chat/code/math/Korean), medians of 3 for sampled rows, one "
|
| 193 |
+
"process, on the 4-bit repo's earlier uniform weights"),
|
| 194 |
+
(739, "(the AWQ swap measures within ±1%, paired). Greedy: plain 37.6 · "
|
| 195 |
+
"MTP k=2 46.8 (1.24×) · DSpark block-8 48.3 (1.28×) · MTP k=4 + "
|
| 196 |
+
"min_draft_p 0.6 = 52.8 (1.40×). The old 62.2/50.4 figures"),
|
| 197 |
+
(758, "decoded past EOS; retracted. Real-world sampling (temp 1.0 · top-p "
|
| 198 |
+
"0.95 · top-k 20): gated MTP 1.29× @240 tok, 1.22× @1024, "
|
| 199 |
+
"distribution-preserving; Korean +27–31%; plain pays −1.2% for the "
|
| 200 |
+
"sampler.")]:
|
| 201 |
txt(40, fy, s, 12, MUT)
|
| 202 |
|
| 203 |
out = "/Users/gesicht/qwen38/cards/assets/builds.png"
|