Programmable KV cache control
Warply does not reimplement paged attention or local prefix caches inside vLLM/SGLang. It orchestrates where prompts go, what gets reused, and what gets measured across disaggregated prefill/decode pools.
Engines own the substrate. Warply owns the control plane.
The problem
Custom inference workloads diverge on cache behavior, not just model size:
| Workload | Cache pattern |
|---|---|
| RAG | Long shared system prompts + document prefixes |
| Agents | Repeated tool schemas, branching conversations |
| Batch inference | Identical prompt prefixes across thousands of requests |
| Multi-tenant | Hot prefixes per customer or product surface |
vLLM, SGLang, TensorRT-LLM, and Dynamo already ship strong local prefix caching. LMCache and Mooncake extend tiering to CPU, local SSD, and object storage; NIXL moves blocks across tiers.
What's still missing is a programmable layer that:
- Routes requests to the prefill pool that already holds the matching prefix
- Coordinates KV transfer between disaggregated pools without destroying hit rate
- Applies per-workload tiering and eviction policy in Python — not YAML
- Exposes hit rate, bytes cached, and affinity as first-class metrics
Disaggregation makes this harder. Prefill and decode are separate pools; naive routing loses prefix reuse.
What Warply owns vs. what engines own
| Layer | Owner | Warply's role |
|---|---|---|
| Block allocation / attention | Engine | Escape hatch only |
| Local prefix cache on one node | Engine | Configure, don't rewrite |
| KV transfer (prefill → decode, CUDA) | NIXL, engine connectors | Orchestrate paths |
| KV transfer (prefill → decode, ROCm) | [MORI-IO](https://github.com/ROCm/mori), SGLang/vLLM | Orchestrate paths (Phase 1) |
| Cache-aware routing | Control plane | Core primitive |
| Cross-pool prefix affinity | Control plane | Core primitive |
| Tiering policy (GPU → CPU → SSD → object storage) | LMCache, Mooncake, NIXL (CUDA); Mooncake, MORI-UMBP (ROCm) | Policy in Python |
| Per-workload cache strategy | Control plane | `CacheRouting` API |
Draw the line at placement, routing, tiering, and measurement — not rewriting KV block layouts or building a Dynamo KVBM clone.
Memory hierarchy (Phase 1)
Warply does not implement block allocators or tier movers. It exposes tiering policy; backends execute promotions and demotions.
| Tier | Typical backing | Backend |
|---|---|---|
| L1 — GPU (G1) | HBM / VRAM | Engine local prefix cache |
| L2 — CPU (G2) | Pinned host memory | LMCache, NIXL / MORI paths |
| L3 — SSD (G3) | Local NVMe | LMCache, NIXL (CUDA); LMCache (ROCm) |
| L4 — Object storage (G4) | S3, GCS, remote blob store | Mooncake, NIXL object backends |
Use cases: long RAG contexts that exceed GPU memory, warm prefixes across restarts, multi-tenant hot keys on cheaper storage.
Proposed API surface (Phase 1)
### Engine-level routing policy
import warply as wp
engine = wp.DisaggEngine(
model="meta-llama/Llama-3.1-8B",
prefill=wp.Pool("1xH100", replicas=2),
decode=wp.Pool("1xH100", replicas=2),
backend="sglang",
kv_transfer="nixl",
routing=wp.CacheRouting(
strategy="prefix_affinity", # route to pool with longest matching prefix
min_hit_ratio=0.6, # alert / adapt if below threshold
tiering=wp.KVTier(
l1="gpu",
l2="cpu", # pinned host — LMCache
l3="ssd", # local NVMe — LMCache / NIXL
l4="object_store", # S3/GCS — Mooncake / NIXL
),
),
)
engine.up()
client = engine.client()### Per-request overrides
client.chat.completions.create(
model="warply",
messages=[{"role": "user", "content": "Summarize this document."}],
extra_body={
"warply": {
"cache_key": "tenant:acme:rag-v3",
"prefix_reuse": True,
}
},
)### Observability
stats = engine.cache_stats()
# → hit_rate, bytes_cached, bytes_by_tier, evictions_by_pool, affinity_map, ttft_p50Routing strategies (planned)
- `prefix_affinity` — send prefill to the pool with the longest matching cached prefix
- `kv-reuse` — generic cache-reuse score across pools (current landing-page primitive)
- `tenant_isolated` — namespace cache keys per tenant; route within tenant pools only
- `cost_aware` — combine spot pricing with cache affinity (Phase 2)
Why prefix cache is the highest-ROI first feature
- Disagg breaks naive reuse — without cache-aware routing, every request cold-starts prefill
- Measurable — hit rate, TTFT, and cost per token prove value quickly
- Workload-specific — RAG, chat, and agents need different policies
- Thin competition — few control planes expose programmable cache routing across clouds
Phase 1 pitch: *Warply routes to the prefill pool that already has your prefix — and sets tiering policy across GPU, CPU, SSD, and object storage.*
vs. NVIDIA Dynamo (KVBM and Planner)
Dynamo ships several related components. SSD/object-storage offload is not the Planner.
| Dynamo component | What it does | Warply equivalent |
|---|---|---|
| KV-aware router / PrefillRouter | Route to worker with best prefix overlap | `CacheRouting`, `prefix_affinity` (Phase 1) |
| KVBM (KV Block Manager) | Block lifecycle G1→G2→G3→G4 (GPU/CPU/SSD/object store) | Policy only — LMCache, Mooncake, NIXL execute moves |
| Planner (SLO Planner) | Autoscale prefill/decode pools from load and SLOs | Phase 2 — SLO-aware pool autoscaling |
| NIXL | Transfers across memory and storage tiers | Phase 0+ transfer substrate |
Warply overlaps Dynamo on routing policy and tiering policy, not on rebuilding KVBM or running under CRDs. Teams already on Dynamo K8s can use `export_yaml()`; teams on Lambda use Warply directly with the same tier backends.
Roadmap placement
| Phase | KV / cache work |
|---|---|
| Phase 0 | NIXL KV transfer between disagg pools; routing hooks |
| Phase 1 | Prefix-aware routing, `cache_stats()`, tiering GPU → CPU → SSD → object storage (LMCache, Mooncake, NIXL on CUDA; Mooncake + MORI-IO on ROCm) |
| Phase 2 | Per-tenant cache keys, cost-aware routing, SLO-aware pool autoscaling, RSI loops that optimize hit rate |
Risks and constraints
- Engine leakage — SGLang radix cache ≠ vLLM prefix cache. Abstract at routing/policy layer; expose engine-specific knobs via escape hatches.
- Observability first — ship `cache_stats()` before advanced routing; users can't tune what they can't see.
- Cold start — prefix affinity helps at steady state; document when it doesn't (unique prompts, high churn).
- Scope — Warply orchestrates cache tiering policy; LMCache, Mooncake, and NIXL own block moves. Do not replace engine block managers or Dynamo KVBM.
Related primitives
- `route="kv-reuse"` — composable primitive on the landing page
- NIXL — Phase 0 disagg path on CUDA; extends to SSD and object-storage tiers
- MORI-IO — Phase 1 disagg KV transfer on ROCm ([ROCm/mori](https://github.com/ROCm/mori))
- LMCache / Mooncake — tiering backends in the ecosystem marquee
See the [Warply GitHub repo](https://github.com/warply-ai/warply) for implementation progress.