knlp / kv cache routing

KRI

KRI is a family of training-free algorithms for picking which KV blocks each request should attend to. Compute a per-block score offline, write it as a .pt prior, load it at serve time through the routing substrate, take the top K. No model changes; no custom inference; the same model that produced the prior serves the request. Knlp leverages vLLM cartridges as the routing accelerator, so swapping algorithms and sweeping K becomes decode-only iteration, not prefill-per-experiment.

What KRI is

KRI stands for KV Routing Index — a family of training-free per-block scorers. Each variant answers the same question with a different signal: how much does each KV block contribute to the quality of the output, conditional on the rest of the cache? Variants split on whether the score is computed from attention weights, from logit shifts under leave-one-block-out, from gradients, from query-aware geometry, or from spectral structure on the block graph.

Bake the prior once per cartridge. Ship the .pt file alongside the cartridge. Load it through the CartridgeKRIProvider on the routing branch; the connector takes care of the top-K selection per request and the sparse inject into paged cache.

Training
none
The model is not modified. Bake the prior with the same checkpoint you serve.
Cost
offline
Most variants need one prefill plus a per-block ablation. KRI-Q and KRI-T compute at request time and stay cheap.
Production leader
KRI-D-kv-sum
K=16 on LongHealth with xa25 cross-attention refinement: 26.5% vs random middle 21.5%, 5σ at n=200.

Leaderboard

LongHealth · K=16 · xa25 overlay · n=200 · 3 seeds

Where each algorithm lands on the production benchmark. The bar is “beat random middle at 5σ.” KRI-D-kv-sum holds the line; xa25 is the cross-attention repair overlay applied on top of any selector (it implements the CacheBlend algorithm, arxiv 2405.16444, at a fixed 25% HKVD refresh ratio). Numbers are the validated 2026-04 sweep; lower-K is harder, higher-K closes the gap to full attention.

%
The %, in one sentence. Every score on this leaderboard is LongHealth question accuracy — the percentage of multi-document clinical-QA questions the model answers correctly when serving the routed cartridge. n=200 questions, deterministic logprob scoring, 3 seeds. Higher is better. 26.5% means the model got 53 of 200 questions right under that routing configuration. Full attention on the same slice sits near 30%; random middle sits at 21.5%; the gap between them is what every algorithm on this leaderboard is trying to close at 1/8 of the cache budget.
Naming convention
read the structural-pinning IDs

The A<a>R<r>K<k> tag describes how the K-block budget is split across three regions of the prefix. The cartridge prefix is divided into anchor (head), mid, and recent (tail); each tag pins specific counts to each region.

A<a>
Anchor
First a blocks — system prompt, task spec, exemplars. Always kept.
R<r>
Recent
Last r blocks — query, latest turn, tail context. Always kept.
K<k>
KRI-mid
Best k middle blocks ranked by KRI score. Selected per request.
total
a + r + k
Sums to the routing budget K. For A1R2K13 that is 1 + 2 + 13 = 16.
prefix layout under A1R2K13 (K=16)
# 128-block cartridge prefix, K=16 budget, A1R2K13 split:

block:   0   1   2   3   4   5   6   7  ...  120 121 122 123 124 125 126 127
         [A] [ -- pick best 13 by KRI score in the middle 125 blocks -- ] [R][R]
         ^ anchor=1                                                              ^^^ recent=2

Examples: A0R0K16 is pure KRI selection with no structural pinning. A4R4K8 pins half the budget structurally and lets KRI pick the other half. A1R2K13 is the production structural choice — minimal structural anchoring, KRI does most of the work.

🏆 1ST
Production leader

KRI-D-kv-sum

+ A1R2K13 (1 anchor + 2 recent + 13 KRI mid) · + xa25
26.5%
+5.0pp
vs random middle · 5σ at n=200
offline query-agnostic 5σ validated
🥈 2ND
Structural baseline

A1R2K13 hybrid

anchor=1 · recent=2 · KRI-mid=13 · + xa25
~24%
+2.5pp
vs random middle · xa25 lifts +1.5pp
offline query-agnostic absorbs bad probes
🥉 3RD
Policy fallback

Hybrid anchor + recency

no prior baked · pure policy
~22%
+0.5pp
vs random middle · zero bake cost
zero bake policy-only always-available
Reading the table. The score column is LongHealth question accuracy (% of 200 questions answered correctly at K=16 routing budget with xa25 overlay). The vs random column is the gap to the random-middle baseline at 21.5%. Higher is better in both.
# algorithm score vs random signal regime
1 KRI-D-kv-sum + xa25 26.5% +5.0pp / 5σ hidden L2 leave-one-block-out, sum across layers multi-needle long-context
2 A1R2K13 + xa25 ~24% +2.5pp structural (anchor + recent) + KRI-D mid blocks regime-robust; absorbs bad probes
3 Hybrid (anchor + recency) ~22% +0.5pp position-only policy zero-bake fallback
4 attention_prefill prior + xa25 ~22% flat layer-mean prefill attention probability recency-biased on causal attention
5 eigenvector centrality + xa25 pending dominant eigenvector of block-attention graph addresses KRI-A recency bias and KRI-D marginal-MI
Random middle (baseline) 21.5% 0.0pp uniformly sampled mid-prefix blocks baseline; xa25 stays flat (-0.5pp)
Recency-only (last K) lower last K blocks only end-weighted tasks; collapses on beginning-weighted
How to read this. Numbers are LongHealth K=16 accuracy with xa25 cross-attention repair overlay applied. Full attention on this slice is ~30%, so the gap from 21.5% (random) to 26.5% (production) closes about a third of the way to the full-attention ceiling at 1/8 of the cache budget. xa25 lifts the good selectors (KRI-D +3.5pp, A1R2K13 +1.5pp) and stays flat on bad ones (random middle -0.5pp) — it is a quality amplifier, not a free lift.
Where the leaderboard does not apply. Single-needle NIAH-copy: random middle ties KRI on this workload — placement carries the value, not content selection. Use the leaderboard for multi-block or multi-needle long-context where content selection actually moves the needle.

The variants

variant per-block score where computed strength weakness
KRI-D-kv-sum Sum across layers of L2 between full hidden and leave-block-out hidden offline; one prefill per block robust leader on multi-needle long-context marginal-MI redundancy when needles co-occur
KRI-D KL(logits_full || logits_leave-block-out) offline; one prefill per block direct logit-level signal weaker than kv-sum on multi-block ablation
KRI-A Mean per-layer attention probability mass per block during prefill one prefill baseline; cheapest after prefill recency bias on causal attention
KRI-G L2 of gradient of LM-head loss w.r.t. block-mean K one backward tracks loss curvature numerically sensitive on low-precision
KRI-Q Q·K agreement against block-mean K (per query) online query-aware, free at serve time flat on untrained block-mean K
KRI-T (TriAttention) Pre-RoPE Q/K trigonometric factorization: content-stationary centers + position-modulated trig series online query-aware, position-aware, zero bake cost requires multi-needle evaluation under cross-task drift
kmeans_keys K-means cluster centroids over block-mean K; rank by cluster size offline captures structural redundancy k-means hyper-sensitivity to init
attention_prefill Layer-mean prefill attention (post-softmax) per block one prefill functional, prompt-conditioned signal recency-skewed, untrained-LM-head failure mode
eigenvector centrality Dominant eigenvector of the block-level prefill attention graph one prefill + power iteration addresses both KRI-A recency bias and KRI-D marginal-MI redundancy head-pooling weights load-bearing; needs synthetic-Q variant
The production stack. KRI-D-kv-sum for selection; structural pinning A1R2K13 (anchor=1, recent=2, KRI-mid=13) at K=16; xa25 cross-attention refinement as the post-load repair overlay. Run on the routing branch's CartridgeConnector with CartridgeKRIProvider loading the prior.

Why cartridges are a routing R&D accelerator

A cartridge is a pre-computed KV cache shipped alongside the model. The cartridge tokens have already been pushed through the transformer at bake time; the resulting K and V tensors are saved to disk as fixed-size blocks. At serve time vLLM loads those blocks straight into paged cache — no prefill runs over the cartridge prefix. The model sees the cache as if it had just finished a long prompt it never actually processed.

Without cartridges
~800 ms / request
Each routing experiment re-prefills the 8K-token prefix every time. The prefix dominates the request budget.
With cartridges
~71 ms / request
Cache hit: blocks already on disk, injected into paged cache. About 11× faster than full prefill.
Where the time goes
forward only
The query, the K decoded tokens, and the per-block selection. Iteration speed becomes a function of decode, not prefill.

What this unlocks for KRI work

  • Bake once, query thousands. Bake a KRI prior offline once per cartridge (minutes-to-hours for the leave-one-block-out variants). Every subsequent request reuses both the cartridge and the prior; the per-query cost is decode-only.
  • Stable block geometry. A cartridge ships with a fixed number of fixed-size blocks. The block boundaries do not change across requests, which means a per-block score baked once stays valid for every future query against that cartridge.
  • Apples-to-apples A/B. Two routing algorithms evaluated on the same cartridge see exactly the same K and V tensors at exactly the same positions. The only thing that differs is which blocks the connector lets attention see.
  • No prefill noise. Without a cartridge, two runs of the same prompt can produce slightly different KV because of nondeterminism in attention kernels. With a cartridge, the KV is bit-identical across runs — routing comparisons stop competing with prefill jitter.
  • GPU residency keeps cartridges hot. The connector pins a working set of cartridges on-GPU; sweeping 100 routing configurations against one cartridge does not pay the load cost more than once.
No LMCache required. The cartridge accelerator loop runs on the CartridgeConnector alone — a .pt file on local disk, CartridgeStore loading it into paged cache, and the routing-prior plugin doing block selection. That is the entire stack you need to ship and compare a new KRI variant.
Without LMCache (default)

Single-process, single-GPU, single-node. Cartridge lives on local disk; the connector loads it directly. Zero external dependencies beyond vLLM and the routing branch. This is what every result on this page was measured with.

With LMCache (optional)

Add LMCache only when you want cross-process tiering, persistent shared storage, or cross-node serving. The routing branch ships a native adapter with sparse retrieve_chunks on the use_native switch — deployment plumbing, not a research dependency.

!
The scope. Cartridges are an R&D accelerator, not the only deployment target. A routing algorithm validated on cartridges still has to be checked on plain prefix-cached workloads before any production claim. The routing branch substrate works for both — the cartridge path is just the fastest place to rule out bad ideas.
The cartridge accelerator loop — one full KRI iteration
# Once per cartridge (offline, minutes-to-hours):
$ python routing/bake_kri_yours.py \
      --cartridge /data/carts/patient_00.pt \
      --model meta-llama/Llama-3.2-3B-Instruct \
      --out      /data/priors/patient_00_kri_yours.pt

# Then, for every routing config you want to try (~71 ms per request):
$ vllm serve meta-llama/Llama-3.2-3B-Instruct \
      --kv-transfer-config '{
          "kv_connector": "CartridgeConnector",
          "kv_connector_extra_config": {
              "cartridges": {"patient_00": "/data/carts/patient_00.pt"},
              "routing": {
                  "enabled": true,
                  "K": 16,
                  "priors": {"patient_00": "/data/priors/patient_00_kri_yours.pt"}
              }
          },
          "kv_role": "kv_both"
      }'

# Same cartridge, same prior, every request: decode only. Sweep K, sweep priors,
# swap algorithms by pointing routing.priors at a different .pt file.

How a prior moves through the system

Offline (once per cartridge)
cartridge.pt + model
    ↓
KRI baker (one of: bake_kri_d_kv_prior.py, bake_kri_t_prior.py, ...)
    ↓
prior.pt = {
    block_affinities: Tensor[num_layers, num_kv_heads, num_blocks],   # KRI-D-kv-sum
    # or:
    kmeans_blocks_perK: dict[int, list[int]],                          # per-K block lists
    # or:
    kmeans_blocks: list[int],                                          # legacy single list
}
At serve time (per request)
CartridgeConnector.__init__:
    routing.priors[cart_id] = path/to/prior.pt
    CartridgeKRIProvider.register_prior(prefix_hash=cart_id, prior_path=...)

CartridgeConnector.update_state_after_alloc(request, ...):
    manifest = CartridgeKRIProvider.get_manifest(
        prefix_hash=cart_id,
        query_hash=None,         # KRI-G path; query_hash=req for KRI-Q/KRI-T
        K=routing.K,
    )
    _request_selected_block_ids[req_id] = set(manifest.block_indices)

CartridgeConnector.get_block_skip_list(req_id, num_logical_blocks):
    return sorted([b for b in range(num_logical_blocks)
                   if b not in _request_selected_block_ids[req_id]])

# Scheduler turns the skip list into null_block via
# KVCacheManager.null_block_positions.
# Attention backend skips null_block at non-selected positions.

Scope: where KRI lands

Read KRI as content selection. Earlier evaluation on NIAH-copy showed that, with position-matched random blocks of equal count, the measured quality matches KRI — on that workload, what matters is placement (which positions the model can see), not content (which blocks). KRI's placement profile carries most of its value at K small. On multi-needle long-context, KRI's content selection adds measurable signal: LongHealth K=16 + xa25 lifts KRI-D-kv-sum 5pp over random middle at 5σ (n=200).

Where KRI helps
Multi-block needles, long context

LongHealth at K=16 with xa25. Content selection contributes distinctively beyond placement.

Where KRI is neutral
Single-needle NIAH-copy

Position-matched random blocks match KRI. Placement carries the value; content selection adds nothing.

Where KRI breaks
High-redundancy multi-needle

KRI-D's leave-one-block-out signal underweights blocks whose information appears in multiple other blocks (marginal-MI failure). Use kv-sum or eigenvector variants here.

Ship a new KRI in an afternoon

~4 hours ~120 LoC no vLLM patch needed

Your idea becomes a routing algorithm in four steps. Each step has one concrete file to write or one concrete config knob to set. If you can produce a tensor that scores cartridge blocks, you can ship a KRI.

1
bake

Write your scorer

One Python script. Input: a cartridge + a model. Output: a tensor of per-block scores.

file: routing/bake_kri_<you>.py
2
serialize

Save it as a .pt

Pick one of three on-disk schemas. The simplest is block_affinities: a 3-D tensor and you are done.

artifact: <cart_id>_kri_<you>.pt
3
register

Point the connector at it

One line in extra_config. No vLLM code change. CartridgeKRIProvider handles loading and top-K resolution.

config: routing.priors
4
measure

Clear the bar or kill it

Beat the production stack on LongHealth K=16 or document the negative. Both outcomes ship.

bar: +>0pp vs KRI-D-kv-sum
1

Write your scorer

~60 LoC

Compute one score per block. The signal can be anything — attention probabilities, hidden-state distance under leave-one-block-out, query-aware similarity, eigenvector centrality on the block graph, your own idea. The shape is what matters.

routing/bake_kri_yours.py
# Required output shape:
#   block_affinities: Tensor[num_layers, num_kv_heads, num_blocks]
# The connector mean-pools across layers/heads and takes top-K per request.

import torch
from transformers import AutoModelForCausalLM

def bake(cartridge_path: str, model_id: str) -> torch.Tensor:
    cart = torch.load(cartridge_path)         # your cartridge
    model = AutoModelForCausalLM.from_pretrained(model_id).eval().cuda()
    num_layers, num_heads, num_blocks = shapes_from(cart)

    scores = torch.zeros(num_layers, num_heads, num_blocks)
    for b in range(num_blocks):
        scores[:, :, b] = your_signal(model, cart, block_idx=b)
    return scores

if __name__ == "__main__":
    scores = bake("patient_00.pt", "meta-llama/Llama-3.2-3B-Instruct")
    torch.save({"block_affinities": scores}, "patient_00_kri_yours.pt")
Tip. If you cannot tell what a "block" is, run cartridge.num_tokens // block_size. Cartridge tokens divide evenly into fixed-size blocks; that count is your last dimension.
2

Pick a schema

choose one

Three on-disk shapes are accepted. The first is the easiest and covers most variants; only fall back to the others if your algorithm fundamentally produces a different shape.

key shape / type when to use
block_affinities Tensor[L, H, N] default. Continuous per-block scores. Connector picks top-K.
kmeans_blocks_perK dict[int, list[int]] Your algorithm decides differently per K (e.g. k-means with re-clustering per budget).
kmeans_blocks list[int] Legacy single list, ignored beyond first K entries.
3

Plug it into the connector

config-only

No vLLM code change. The connector loads any .pt in the supported schema and the CartridgeKRIProvider handles the rest. Add your file path under extra_config.routing.priors keyed by cartridge id.

--kv-transfer-config (one line per cartridge)
{
  "kv_connector": "CartridgeConnector",
  "kv_connector_extra_config": {
    "cartridges": {"patient_00": "/data/carts/patient_00.pt"},
    "routing": {
      "enabled": true,
      "K": 16,
      "priors": {
        "patient_00": "/data/priors/patient_00_kri_yours.pt"
      }
    }
  }
}
What happens next. On request arrival, the connector calls CartridgeKRIProvider.get_manifest(prefix_hash, query_hash, K=16). That returns a BlockManifest with block_indices. The scheduler turns the complement into null_block; attention skips those positions. Sparse inject writes only the selected blocks into paged cache.
4

Measure against the bar

5 min eval

The production stack is the target. Run your prior through the same eval harness used to validate KRI-D-kv-sum. Two outcomes both ship: a win goes into the routing-prior table on this page; a documented negative goes into the kill log so the next person does not retry the same idea.

Target
LongHealth K=16 + xa25

Production stack: KRI-D-kv-sum + A1R2K13 + xa25 at K=16. 26.5% vs random middle 21.5%, 5σ at n=200.

Bar
Any positive delta ships

The bar for KRI variants is competitive, not crushing. A clean +0.5pp at the same K with no latency regression is a credit-worthy result.

If you do not beat KRI-D-kv-sum. That is fine. Document the workload regime where your prior does win — or the regime where it ties — and add it to the variants table with the actual numbers. Negatives are landed signal; do not bury them.

When you are done

You have one Python file in routing/, one .pt artifact per cartridge, one new row in the variants table on this page, and a serving config that other people can drop into vllm serve. No vLLM patch. No model fine-tuning. No custom inference. Your algorithm just runs.

Adjacency

KRI ↔ routing

KRI is the algorithm; the routing branch is the substrate. The substrate also accepts non-KRI algorithms through the same interface (hybrid, recency, future ScoutAttention and RetrievalAttention plug-ins).

KRI ↔ KRI-FT

KRI is training-free; the KRI-FT PEFT vehicle fine-tunes a model under a routing mask so it tolerates more aggressive K. Use either independently. The two compose in a 2×2 factorial (base / KRI-FT × dense / routed).

KRI ↔ SPF

SPF is a separate scheduler-side prefetch experiment that did not clear its survival gate. KRI and the routing branch are independent of SPF; the substrate that originally hosted KRI under SPF now lives on the routing branch.