Skip to content

Commit efba6c5

Browse files
authored
Merge pull request #135 from FluffyAIcode/AgentMemory/mlx-pad-decode-workaround-2815
Kakeya Attention (algorithm + substrate roadmap), Kakeya-vs-vLLM (ctx1238 + long-context), MLX serial-only
2 parents 8e8e841 + 86cd19f commit efba6c5

35 files changed

Lines changed: 2491 additions & 42 deletions

README.md

Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ patterns — see [`docs/quickstart.md`](docs/quickstart.md).
9898
| **Restored verifier** | Gemma-4 26B-A4B (AR) + DFlash dLLM proposer + trained **f_θ**; **S5** keeps 5 full-attention layers exact, restores sliding layers → bounded resident KV, recall preserved. | `inference_engine.v04` (CUDA), `inference_engine.backends.mlx` (Apple Silicon) |
9999
| `SinkWindowVerifier` | Lightweight path: Qwen3 (0.6B / 1.7B / 4-bit MLX), sink+window K/V trim (ADR 0001 / 0002). | `kv_cache_proposer.verifier` (CPU), `inference_engine.backends.mlx.verifier` |
100100
| **Per-session binding** | `PerSessionVerifierRegistry` + coordinator resolver: each session owns isolated KV (shared weights) — true multi-tenant serving (PR-A3c, `--multi-tenant`). | `inference_engine.session.verifier_registry` |
101-
| **Batched scheduler** | Fuses a cohort's decode steps into one batched forward — **8.45× served throughput** at 8 sessions, recall 1.0. | `inference_engine.session.batch_scheduler` |
101+
| **Batched scheduler** | Fuses a cohort's decode steps into one batched forward — **8.45× served throughput** at 8 sessions, recall 1.0. **CUDA-only**: on Apple-Silicon MLX, `v0.4-mac` multi-tenant is **serial-only** (batched `B>1` decode is unsupported — upstream MLX `B>1, L=1` quantized-kernel bug, [ADR 0014](docs/adr/0014-agent-connection-capacity-and-cross-host-topology-tests.md)). | `inference_engine.session.batch_scheduler` |
102102
| `AppendTokens` / `Generation` coordinators | Drive prefill / incremental forward / greedy decode; route per-session (multi-tenant) or single. | `inference_engine.session.{coordinator,generator}` |
103103
| Python / TypeScript SDKs | `kakeya.Client` / `Session` (sync gRPC); `@kakeya/runtime` (Node 20+). | [`sdks/`](sdks/) |
104104
| HTTP shim (deprecated) | OpenAI-compatible `/v1/chat/completions`; `Deprecation` + `Sunset` headers. | `inference_engine.server.app` |
@@ -147,29 +147,57 @@ KV restoration buys *bounded memory at full fidelity*. See
147147
the **memory axis** all-platform + **throughput** on CUDA) and
148148
[ADR 0013](docs/adr/0013-distributed-inference-topology.md).
149149

150-
### How this differs — Kakeya Attention vs PagedAttention / RadixAttention
150+
### Kakeya Attention — the attention algorithm
151151

152-
Other engines optimise *how* the KV cache is **stored/laid out**; they still
153-
store the **whole** history, so memory **grows with the conversation** and the
154-
node must provision for the *total* footprint. Kakeya optimises *how much* is
155-
stored: a sliding-window bound + a global-attention **restoration** mechanism,
156-
so the resident footprint is **bounded** and the node provisions only for the
157-
**peak window** — not the whole history.
152+
**Kakeya Attention** is an LLM attention compute + KV-management algorithm:
153+
**sliding-window bound (sink + window) + f_θ KV-projection + dLLM-proposer
154+
restoration, taken as one primitive.** It is a peer of — and drop-in replacement
155+
for — the attention layer in today's engines: eager attention, **FlashAttention**,
156+
vLLM **PagedAttention**, and SGLang **RadixAttention**. Where those keep the
157+
**whole** KV history (memory grows with the conversation) and differ only in
158+
*how* the full cache is computed or laid out, Kakeya Attention bounds *how much*
159+
is resident: evicted context is **reconstructed on demand** by the proposer+f_θ,
160+
so the resident footprint does not grow with the session.
158161

159-
| Engine | Mechanism | What it manages | Memory vs conversation length |
162+
| Algorithm | Layer it replaces | Mechanism | Memory vs conversation length |
160163
| --- | --- | --- | --- |
161-
| **vLLM** — PagedAttention | OS-style **paged** KV blocks (virtual→physical page tables) | layout: non-contiguous block allocation; great for whole-block access | **grows** — still stores full KV; needs large-capacity store |
162-
| **SGLang** — RadixAttention | **radix-tree** over KV, dynamic insert/evict | layout + **prefix reuse**: fast, precise prefix lookup/sharing | **grows** — still stores full KV; needs large-capacity store |
163-
| **Kakeya** — Kakeya Attention | sliding-window bound + **global-attention restoration** (dLLM proposer + f_θ/S5) | *length*: dynamically bounds resident KV; evicted context **reconstructed on demand** | **bounded** — footprint does **not** grow with the session; provision only for the **peak window** |
164-
| **CXL / Ollama** | reuse PagedAttention | layout (offload tier / local serving) | **grows** — inherits PagedAttention's full-KV storage |
165-
166-
The orthogonality matters: PagedAttention and RadixAttention make the *same
167-
total* KV cheaper to allocate or share; **Kakeya Attention makes the total
168-
itself bounded** (and is composable — a paged/radix store could hold Kakeya's
169-
bounded window). The cost is the restoration compute (a proposer forward), which
170-
the rest of this section quantifies (recall 1.0; ~AR-parity / 1.79–2.06× on
171-
CUDA; ~4× more concurrent agents per GB, §3.4 of
172-
[ADR 0014](docs/adr/0014-agent-connection-capacity-and-cross-host-topology-tests.md)).
164+
| eager attention | compute | materialise full `QKᵀ` scores | grows (O(T²) compute, full KV) |
165+
| **FlashAttention** | compute | tiled/online-softmax, no score materialisation | grows — still full KV |
166+
| **vLLM** PagedAttention | storage | OS-style **paged** KV blocks | grows — still full KV |
167+
| **SGLang** RadixAttention | storage | **radix-tree** KV, prefix reuse | grows — still full KV |
168+
| **Kakeya Attention** | **compute + storage** | sink+window bound + **f_θ + dLLM-proposer restoration** | **bounded** — provision for the **peak window**, not the history |
169+
170+
The orthogonality matters: FlashAttention makes the compute cheaper, Paged/Radix
171+
make the *same total* KV cheaper to allocate or share — **Kakeya Attention makes
172+
the total itself bounded**, and is **composable** with all of them (a flash
173+
kernel computes a Kakeya window; a paged/radix store holds it). The cost is the
174+
restoration compute (a proposer forward), quantified below (recall 1.0;
175+
~AR-parity / 1.79–2.06× on CUDA; ~4× more concurrent agents per GB,
176+
[ADR 0014 §3.4](docs/adr/0014-agent-connection-capacity-and-cross-host-topology-tests.md)).
177+
178+
**North star — a product-grade engine that replaces vLLM.** Kakeya Attention is
179+
the native algorithm of a **product-grade inference engine whose goal is to
180+
replace vLLM** — not a technique bolted onto HuggingFace transformers, and not
181+
"vLLM with a different cache". The engine is designed **bounded-KV-native**: the
182+
full history is never resident, admission/scheduling sizes sessions by their
183+
**peak window** (not total tokens), and restoration is fused into prefill/decode.
184+
Graph-captured decode, fused-MoE and efficient masking are table stakes built *in
185+
service of* that design, not a port of vLLM's full-KV pipeline
186+
([ADR 0015](docs/adr/0015-kakeya-attention-and-engine-substrate.md)). The
187+
eager-transformers numbers in the comparison reports are **feasibility probes**,
188+
not "Kakeya performance"; the vLLM-beating demonstration runs on a
189+
**full-attention** verifier, where restoration is load-bearing (on gemma-4 its
190+
native sliding window already bounds 25/30 layers, so it is not the showcase).
191+
192+
**Where the bounded-KV win is large (and where it isn't).** The advantage scales
193+
with the model's **full-attention fraction**. On **gemma-4-26B-A4B** only 5 of 30
194+
layers are full-attention (25 are natively sliding-window) — so vLLM already
195+
bounds 25/30 layers, the 5 full layers dominate long-context KV in **both**
196+
engines, and Kakeya's resident-KV edge is only **~7 % at 62k**. On a
197+
**full-attention** model (no native sliding, e.g. Qwen/Llama) vLLM keeps all
198+
layers full while Kakeya bounds all-but-exact → a **~** resident-KV edge. The
199+
long-context concurrency "sweet spot" is therefore **architecture-dependent**
200+
see [the long-context report](docs/reports/kakeya-vs-vllm-longcontext-h200.md).
173201

174202
### Beta scorecards — Kakeya vs the standalone model (`main` @ `9d5e6b4`)
175203

@@ -289,6 +317,13 @@ owns isolated KV (shared weights) — making serving truly multi-tenant, and a
289317
at 8 sessions with **per-session recall 1.0** (see the multi-tenant results
290318
below / [ADR 0014 §3.4–3.7](docs/adr/0014-agent-connection-capacity-and-cross-host-topology-tests.md)
291319
and the [detailed report](docs/reports/pr-a3c-multitenant-serving-test-report.md)).
320+
**Platform scope:** the batched/parallel cohort path is **CUDA-only**. On
321+
Apple-Silicon **MLX, `v0.4-mac` multi-tenant is serial-only** — per-session
322+
binding still gives isolated, recall-preserving sessions, but they are served
323+
**one at a time**; batched `B>1` decode is blocked by an upstream MLX
324+
quantized-kernel bug (`B>1, L=1` → per-session recall collapses to 0.125, while
325+
serialized stays 1.0; confirmed on the latest published `mlx 0.31.2 / mlx-lm
326+
0.31.3` — [ADR 0014 §3.4](docs/adr/0014-agent-connection-capacity-and-cross-host-topology-tests.md)).
292327
Pushing the connection sweep further (preset `agent-capacity-stress`, the
293328
open-file-descriptor limit `RLIMIT_NOFILE` raised to 100k / hard unlimited on
294329
the Mac — each connection uses one descriptor) shows the true ceilings: **the
@@ -584,9 +619,9 @@ scripts/
584619
| Milestone | Status | Description |
585620
| --- | --- | --- |
586621
| Session-bound gRPC runtime | ✅ shipped | Long-running gRPC `RuntimeService`, Python + TS SDKs, bounded memory + prefill (4-h Mac M4 evidence), Mac M4 self-hosted integration gate |
587-
| **v0.4 for Mac (`v0.4-mac`)** | ✅ shipped | MLX restored Gemma-4 26B engine: bounded KV (~90% saved), recall 1.0, ≈AR-parity spec-decode |
622+
| **v0.4 for Mac (`v0.4-mac`)** | ✅ shipped | MLX restored Gemma-4 26B engine: bounded KV (~90% saved), recall 1.0, ≈AR-parity spec-decode. Multi-tenant is **serial-only** (no batched `B>1` decode — upstream MLX kernel bug, [ADR 0014](docs/adr/0014-agent-connection-capacity-and-cross-host-topology-tests.md)) |
588623
| **v0.4 for CUDA (`v0.4-cuda`)** | ✅ shipped | Restored Gemma-4 26B engine on NVIDIA: fused DFlash spec-decode **1.79–2.06× AR**, 44–87× KV saving, recall 1.0 |
589-
| **v0.4 multi-tenant (PR-A3c)** | ✅ shipped | Per-session binding (isolated KV, shared weights) + batched scheduler **8.45× served throughput**, per-session recall 1.0 |
624+
| **v0.4 multi-tenant (PR-A3c)** | ✅ shipped | Per-session binding (isolated KV, shared weights) on both platforms. **CUDA**: batched scheduler **8.45× served throughput**, per-session recall 1.0. **MLX (Mac): serial-only** (sessions served one at a time; batched parallel decode unsupported upstream) |
590625
| Async continuous batching | designing | Dynamic mid-flight arrival + ragged-length cohorts under the async gRPC `Generate` handlers (current batcher is fixed-cohort) |
591626
| Deployment polish | queued | PyPI + npm publishing, GHCR Docker image, `kakeya prewarm` CLI, `kakeya chat` REPL |
592627
| Cross-request KV reuse | designing | Sessions survive across requests on gRPC; turns intra-session drift into 0 ms inter-request drift |

docs/adr/0014-agent-connection-capacity-and-cross-host-topology-tests.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,71 @@ characterized for an upstream report. Recall-safe Mac multi-tenant remains
318318
projections) is a possible future probe. Evidence:
319319
`results/research/k3_mlx_batched_manual_sdpa_mac.json` + the layer-diff logs.
320320

321+
**L≥2 padded decode workaround — hypothesis CONFIRMED, recall recovered, but
322+
no throughput win (Mac, 8 sessions, modal prompt 1149).** The `--pad-decode`
323+
mode (preset `mlx-batched-pad-decode`) duplicates the new token each step so
324+
every decode forward is **length-2**, routing through mlx's matrix-matrix
325+
(`qmm`) quantized kernel instead of the single-token (`qmv`) kernel; query
326+
position 0 is the real prediction (attends to cache+self only, == the L=1
327+
result) and the position-1 duplicate is trimmed from the (Kakeya S5, trimmable)
328+
cache so RoPE offsets stay exact. The batch dimension is untouched (stays
329+
parallel over sessions, Python-only).
330+
331+
| metric | batched **L=1** (bug) | batched **L≥2 padded** | serialized (truth) |
332+
| --- | --- | --- | --- |
333+
| per-session recall | **0.125**| **1.0**| 1.0 |
334+
| per-row tok0 vs serialized | row 1+ diverge | **all 8 match** ||
335+
| aggregate decode tok/s | 57.1 (recall void) | 9.945 | 14.927 |
336+
| speedup vs serialized || **0.67×** | 1.0× |
337+
338+
This **confirms** the root cause: forcing `L≥2` (avoiding the `B>1, L=1` `qmv`
339+
path) restores batched per-session recall to **1.0**, matching serialized
340+
bit-for-bit on the first decoded token across all rows. But the 2× per-step
341+
padding tax exceeds the batching gain at this scale (**0.67×**), so it is a
342+
**correctness-recovery probe, not a shippable throughput path**: a Mac batched
343+
*win* still needs the upstream `L=1, B>1` quantized-kernel fix (no padding tax)
344+
or a much larger cohort / cheaper verify. Evidence:
345+
`results/research/k3_mac_bridge_mlx_batched_pad_decode.json`.
346+
347+
**mlx/mlx-lm upgrade re-test — bug PERSISTS on the latest published release.**
348+
We attempted `pip install --upgrade mlx mlx-lm` on the Mac runner (preset
349+
`mlx-upgrade`): it was a **no-op** — the runner was already at the newest
350+
versions on PyPI (`mlx=0.31.2`, `mlx_lm=0.31.3`, `mlx-metal=0.31.2`; no newer
351+
stable or pre-release exists on the index). A self-contained probe (preset
352+
`mlx-upstream-batch-probe`, zero `inference_engine` imports, native
353+
`model.make_cache()`, plain `L=1` batched decode) then re-ran the parallel
354+
test on that latest build:
355+
356+
| metric | batched (native `L=1`) | serialized (truth) |
357+
| --- | --- | --- |
358+
| per-session recall | **0.125**| 1.0 |
359+
| per-row tok0 vs serialized | **all 8 match** (tok0 is from prefill) ||
360+
| aggregate decode tok/s | 29.6 (recall void) | 21.7 |
361+
| `upstream_l1_batch_bug_fixed` | **false** ||
362+
363+
The first decoded token (computed from the `L>1` prefill logits) matches on
364+
**all 8 rows**, and the divergence appears only in the subsequent `L=1` decode
365+
steps (rows 1–7 fail) — exactly the `B>1, L=1` signature. **Conclusion:** the
366+
latest PyPI mlx/mlx-lm still ships the bug; a pip upgrade cannot fix it because
367+
nothing newer is published. The only further "upgrade" is a from-source
368+
`mlx` git-`main` build (compiles Metal kernels; invasive on the pinned
369+
runner env) or an upstream patch/issue. Recall-safe Mac parallelism therefore
370+
remains: **serialized**, or the `L≥2` padding probe (recall-safe but 0.67×).
371+
Evidence: `results/research/k3_mac_bridge_mlx_upstream_batch_probe.json` +
372+
`.mac-bridge/logs/mlx-upgrade-{0,1,2}.log`.
373+
374+
**DECISION — MLX `v0.4-mac` multi-tenant is SERIAL-ONLY.** Given the upstream
375+
bug is present on the latest published mlx/mlx-lm and is not Python-patchable,
376+
the shipped Mac multi-tenant path is **per-session binding served serially**
377+
(one session decoded at a time): isolated, recall-preserving (1.0) sessions on
378+
shared weights, with bounded resident KV. **Batched/parallel cohort decode
379+
(`B>1`) is NOT supported on MLX** and remains a **CUDA-only** capability (§3.5 /
380+
§3.7: 8.04–8.45× near-linear, recall 1.0). Re-evaluate the Mac batched path
381+
only if (a) a future mlx release fixes the `B>1, L=1` quantized-decode kernel,
382+
or (b) a from-source mlx `main` build / upstream patch lands; the `L≥2` padding
383+
workaround stays available as a recall-safe (but sub-parity, 0.67×) escape
384+
hatch in the meantime.
385+
321386
## 4. Case 2 — cross-host proposer/verifier (FEASIBILITY VERDICT)
322387

323388
### 4.1 Verdict: the requested topology is not implementable today, and is architecturally bounded out

0 commit comments

Comments
 (0)