Documentation menu
Overview

Architecture

The parts of the ADP KV cache and how a KV page goes to the card and back. How the keys keep the cache after a restart, and how to place and share cards.

Overview

The parts of the ADP KV cache
from the client to the card · the recommended path
ClientsOpenAI-compatible APIShared-memory ringKV pages in /dev/shmvLLM or SGLangscheduler · GPU workersADP integrationOffloadingSpec backendSDK clientGPU ↔ ring copiesStorage gatewayZeroMQ · port 5557LMCache L2 pluginalternative pathCluster serviceetcd · card set, healthStorage backendkeys · fragmentsADP cardkey-value store · SSDsNVMe-oF targetone service per cardNVMe-oFlocal card: PCIeSolid: the data path. Dashed: an alternative path and the control of a card cluster.

Components

ComponentWhere it runsWhat it does
ADP integrationinside the engine, in the scheduler process and in every tensor-parallel workerFinds the blocks of a prompt that are on the card, moves pages in and out, and fences the transfers against the forward pass. For vLLM this is the OffloadingSpec backend (recommended) or the vLLM KV connector. For SGLang it is the HiCache backend.
SDK clientinside the engineMakes the storage keys, sends requests in batches, and copies pages between GPU memory and the shared-memory ring.
Shared-memory ring/dev/shm of the hostThe transfer window between the engine and the gateway. A slot of the ring holds one transfer. The ring is not a cache.
Storage gatewaya separate process (container) on the GPU server, one for each engineCollects the requests of all ranks of one engine and schedules reads and writes. Cuts large objects into fragments, spreads them over the databases of the card, and talks to the card.
ADP carda PCIe slot of the GPU server, or a storage server reached over NVMe-oFKeeps the objects as key-value pairs on the NVMe SSDs behind it.
Cluster servicestorage servers and witness hosts, only in a card clusterKeeps the card set and the health of each card in etcd for the gateways.

Control messages between the SDK and the gateway go over ZeroMQ (port 5557 by default). Data never goes over ZeroMQ. It goes through the ring.

A restore with the OffloadingSpec backend

  1. Lookup. The scheduler of vLLM computes the block hashes of a new request. The backend asks the card which chunks it holds. The card answers, so hits are also found after a restart. vLLM counts the found tokens as computed and reserves GPU blocks for them.
  2. Load. Before the step computes, the workers read the whole stored prefix in waves. The gateway reads the objects into the ring. Each rank copies its share into a staging slot in GPU memory, and from there into the KV blocks.
  3. Sharded reads. For a page that all ranks hold the same, the card is read once for the group. One rank in each NVLink group copies each share from the ring, and the other ranks get it over NVLink.
  4. Save. vLLM gives each full chunk to the backend after the step. Rank 0 writes a replicated page. The gateway writes it to the card. The step does not wait for the card.

A restore with the vLLM KV connector, step by step

Restore and save
vLLM KV connector · one request · one engine step
vLLM scheduler Connector · worker Storage gateway ADP card LOOKUP EXIST · which chunks of the prompt are stored? chunks 1 … k found reserves GPU blocks for them, schedules the step LOAD load chunks 1 … k GET · one request per layer read fragments objects pages land in the ring ring → GPU on its own CUDA stream, layer i waits for its own pages SAVE PUT · pages after the layer write, asynchronously the step does not wait for the card
  1. Lookup. When vLLM schedules a request, the scheduler side of the connector asks the gateway which chunks of the prompt are on the card (EXIST). vLLM counts them as computed tokens and reserves GPU blocks for them.
  2. Load. In the forward pass, every rank sends one read request for each layer (GET). The gateway reads the objects into the ring. The SDK copies them into the reserved GPU blocks on its own CUDA stream.
  3. Fence for each layer. Before a layer computes, it waits only for its own pages (wait_for_layer_load). So the reads of later layers overlap with the computation of earlier layers. The engine reads some caches before any layer hook: the indexer of DeepSeek Sparse Attention and the recurrent state of Mamba-type layers. The connector restores these caches before the pass.
  4. Save. After a layer computes, the SDK copies its new pages into the ring, and the gateway writes them asynchronously (PUT). On decode steps under full CUDA graphs, the writes of a step go out as one request (deferred write). The engine then keeps its fastest graphs.

The gateway gives the ranks of one engine one shared view. It waits until every rank sent a request, then runs it once. A restore of a long prompt is one request for each layer of each rank, not one for each page. The gateway checks the ring capacity for each request, so the ring does not limit the context length.

Keys and persistence

A stored object is one chunk of tokens. Its key comes from:

  • the block hash of the chunk in the engine. This hash covers the tokens of the chunk and of every chunk before it. It already carries the cache_salt of the request, the LoRA adapter and the multimodal inputs.
  • the layout. The OffloadingSpec backend adds the model, the KV data type, the rank layout and the byte layout of the page. The vLLM KV connector adds the layer.
  • a deployment namespace, optionally (xkv.namespace, key_namespace).

No part of a key depends on the process that wrote it. So a model server after a restart finds everything that it stored before. This needs a fixed PYTHONHASHSEED. Without it, Python salts the block hashes for each process, and nothing is found again. Two requests with the same prefix share the stored chunks. Two tenants with different cache_salt values never share them. Tenant isolation tells how to set it up.

The chunk is the unit of a stored object and of a hit. A prompt of N tokens can hit at most floor(N / chunk_size) × chunk_size tokens. The engine computes the tail again.

WARNING

The layout of the stored data comes from the configuration. The card does not record it. After a change of the chunk size, the namespace, the fragment settings, the tensor-parallel size or the model, clean the cache. The old objects are not found any more. With a different fragment layout, the gateway can read them back incorrectly. Settings that require a clean cache.

Fragments

The card accepts objects up to 256 KiB. Over NVMe-oF, one command carries at most the maximum transfer size of the device (MDTS, usually 128 KiB). A KV page of a hybrid model is 2–3 MiB. So the integration or the gateway cuts a large object into fragments under several keys and puts it together again on read. The fragments of one object go to different databases, so the card reads them in parallel. The fragment size comes from the placement: 131072 for a remote card, 261120 for a local card. The OffloadingSpec backend and the SGLang backend cut the objects themselves (xkv.fragment_bytes, max_io_bytes). The vLLM KV connector uses the gateway’s fragment_size. See ADP card setup.

The shared-memory ring

The ring is a file in /dev/shm. The gateway makes it, and every rank maps it. Its size (shared_memory_size, 6 GiB by default, 10 GiB for 8-GPU servers) sets how many transfers can be in flight:

max_parallel_ios = (shared_memory_size / 2) / io_size

io_size is one object, and the gateway writes it to its log at start. When a request needs more slots than are free, it runs in batches. The gateway gives a free slot to a transfer. It takes the slot back when the page is in GPU memory. A larger ring helps only when one request needs more slots than the ring has. An example is one layer of a long prompt with a small chunk. The ring uses host memory while the gateway runs. Both containers must see the same /dev/shm.

Choosing a mode

Your situationModeDetails
One deployment on one GPU serverOne card for the pod, local or remoteInstallation
One deployment that must keep its cache when a card fails, or needs more capacitySeveral cards behind one gateway, key_placement: dialogPlacement
One long restore must read as fast as possibleSeveral cards, key_placement: stripePlacement
Replicas or several models on several GPU servers share one cache; the set of cards changes rarelyShared cache (remote-xdp, the same list of cards in every gateway)Shared cache
Cards and storage servers join and leave while the models serve; one place for the card set and its healthCard cluster (kv-spdk, etcd on three or more hosts, up to 8 cards)Card cluster

Deployment topologies

TopologyNotes
One engine, one gateway, one cardThe basic unit. One gateway serves the ranks of exactly one engine. A second engine on the same gateway gets a refusal.
Several engines on one serverOne gateway for each engine, each with its own port and ring.
Several deployments on one or more cardsEach pod has its own gateway on the same cards. Each model configuration has its own namespace. See Shared cache.
Cards managed by a cluster serviceThe gateways read the card set from etcd and change it at run time. See Card cluster.
Card in a storage serverThe GPU server connects the NVMe-oF namespace of the card over RDMA. The gateway uses it as a local device.

Several cards: failover and placement

A gateway can use several cards. When one card fails, the gateway takes it out of service and keeps the cache on the others; when the card comes back, the gateway uses it again, with no restart. The gateway setting key_placement selects how the blocks of a conversation go to the cards:

  • dialog (default): all blocks of a conversation on one card. A failed card costs only its conversations, which are computed again in full.
  • stripe: the blocks of a conversation over all cards. One long restore reads from every card at once.

Use dialog unless one long restore must be faster than one card can read: then use stripe, and accept that a failed card cuts most long conversations short until they are computed again.

dialog and stripe
three conversations A, B, C on three cards
dialog · fault tolerance gateway card 1 A A A card 2 · down B B B card 3 C C C all blocks of a conversation on one card: a failed card costs only its conversations (B) stripe · speed gateway card 1 A B C card 2 A B C card 3 A B C the blocks of a conversation on all cards: one restore reads from every card at once

Shared cache → placement tells how to choose and set it.

Card cluster

In cluster mode the gateways do not have a list of cards. The cluster service awide-cluster runs on the storage servers and on witness hosts. The hosts keep an etcd quorum, and the leader writes the card registry: the cards, their NVMe-oF addresses and their health. Each gateway reads the registry about once a second, connects to new cards, and stops to use a card that is sick or gone, while the models serve.

A card cluster
the card registry in etcd · the data over NVMe-oF
STORAGE SERVER 1 awide-cluster · leader etcd member writes the card registry ADP card ADP card STORAGE SERVER 2 awide-cluster etcd member reports its cards ADP card ADP card WITNESS HOST awide-cluster etcd member no cards one etcd quorum on the cluster hosts: three members tolerate one failure GPU SERVER · POD vLLM or SGLang gateway in cluster mode GPU SERVER · POD vLLM or SGLang gateway in cluster mode KV data over NVMe-oF the gateway reads the card registry from etcd

Card cluster tells how to set it up.

When the card is unavailable

The external cache never stops the generation:

  • OffloadingSpec backend. When the gateway stops to answer, the backend pauses the external cache. Lookups are misses and requests continue. When the gateway answers again, every rank opens a new session at the same step, with no restart. A load that fails is computed again.
  • vLLM KV connector. When the gateway stops to answer, the connector turns the external cache off and writes External KV cache turned off on this rank once. A model with attention only then serves from GPU memory. A hybrid model (Mamba-type layers) stops cleanly, because it must not serve a recurrent state that is restored only in part. To turn the external cache on again, restart the gateway and vLLM together (on Kubernetes, the whole pod).
  • SGLang HiCache backend. A failed request is a miss. The backend stops to use the card for a cooldown time, then tries again.

The gateway takes a card that comes back into service by itself, with no restart:

BackendWhen the card goesWhen it comes back
local-xdp (local card)After three answers in a row that the card or its storage service is gone, or a failed probe while no I/O comes (every 5 s), the card leaves service. A lookup is a miss, a write fails at once.A background thread tries again after 1 s, then with the pause doubled up to 30 s. It takes the card back only when it is the same card: the same serial number and the storage identity that the gateway wrote on the card.
remote-xdp (remote card)The health check (every second) takes out a card that does not answer Identify. Without io_timeout_ms, a failed command fails only its key. With io_timeout_ms set, io_timeout_strikes commands in a row that pass the deadline or fail in the transport take the card out; below that, only their keys fail.The gateway looks for the card by its serial number every 5 s, also under a new device name after a restart of the target or a reconnect. It takes the namespace only when it answers with the same serial number and model.
file-system (a directory, for tests)A directory that cannot be read or written leaves service.A directory that comes back under its path with the label .xkv_storage_id of the gateway is used again. A directory with another label, or with files and no label, is another storage and stays out.
kv-spdk (card cluster)The cluster reports the card sick for cluster_sick_polls polls, its listener is gone for cluster_absent_polls polls, or io_timeout_strikes commands in a row pass the io_timeout_ms deadline.At the next poll that reports it healthy (Card cluster).

The container of a local-xdp gateway must see /etc/adp and /sys for the check of the serial number. The container of a remote-xdp gateway must see the host’s /dev and /sys/class/nvme, not a list of devices fixed at start. Without them the card stays out of service until the gateway restarts.

On MLA models with tensor parallelism, every rank needs the same latent page. Both vLLM integrations use NVLink to copy this page from host memory only once for each NVLink group:

  • OffloadingSpec backend: sharded reads (xkv.shard_reads: auto). Each rank copies a share of each load wave from the ring. It gets the rest over NVLink from the staging slots of the other ranks. At start the ranks ask the driver and NVML which peers have NVLink, and they make the groups from that. On a server with two NVLink islands of four GPUs, the ring is copied twice instead of eight times.
  • vLLM KV connector: NVLink fan-out (nvlink_fanout). Each rank of an NVLink group copies its share of a layer from host memory. The ranks exchange the rest over NVLink just before the layer computes. Settings: vLLM KV connector.

Planning a deployment?

Talk to our engineers about your models, context lengths, concurrency and where the ADP card should sit.

Talk to Awide Labs