Skip to content

KV Cache Offloading

Model implementation support

This feature runs on the vllm model implementation only. See the Feature Matrix for feature support by model implementation.

Overview

Prefix caching reuses a KV cache only while it stays in NPU device memory. Device memory is the smallest tier available, so a prefix that is evicted must be recomputed when a later request contains the same prefix.

KV cache offloading stores reusable KV chunks in a larger, slower tier while requests are processed. After the NPU cache evicts a prefix, a later request can restore its KV from that tier instead of recomputing it. RBLN vLLM supports this through LMCache and its RBLN extension, lmcache-rbln.

Architecture

lmcache-rbln is a hardware-specific extension of LMCache for RBLN NPUs. It depends on the upstream lmcache package for cache management (chunking, lookup, and eviction policy) and replaces the device-specific components: the KV cache transfer between NPU memory and the offload tier, and the storage backend for NVMe direct storage.

RBLN vLLM registers the extension's connector, RBLNLMCacheConnectorV1, with its KV connector factory, so it is selected by name through --kv-transfer-config like any other vLLM KV connector.

Storage tiers

LMCache's configuration selects the offload tier. This page covers two:

  • CPU memory: a host memory pool, and LMCache's default backend.
  • Rebellions Direct Storage (RDS): writes KV chunks from NPU memory directly to NVMe, bypassing host memory.

Rebellions Direct Storage (RDS)

RDS is a data path along which an RBLN NPU reads from and writes to an NVMe SSD directly. It connects NVMe and NPU memory over PCIe peer-to-peer DMA and removes the host bounce buffer.

The RDS backend has two constraints:

  • The RDS backend holds its index in memory, so offloaded chunks do not survive a restart.
  • The RDS backend caps the number of stored chunks at rds_max_chunks and evicts the least recently used chunk to make room. If NVMe fills before the cap, the failed write also evicts the least recently used chunk and retries.

Prerequisites

Install lmcache-rbln in the server environment

KV cache offloading requires lmcache-rbln; it is not included in the default vllm-rbln installation. Install the kv_connectors extra in the same Python environment that runs vllm serve. It also installs the required upstream lmcache dependency. See Optional dependencies.

Check the packages in that environment:

python -m pip show vllm-rbln lmcache-rbln lmcache torch-rbln rebel-compiler
python -m pip check

The connector runs on the vllm model implementation, so the server is launched with --model-impl vllm. That is also what puts the KV cache in device tensors, which is the only mode the connector supports: it transfers the cache as torch tensors on device='rbln'.

  • Unless the run resolves to the vllm model implementation, RBLN vLLM never imports the module that registers RBLNLMCacheConnectorV1, so engine startup fails while validating the connector name with Unsupported connector type: RBLNLMCacheConnectorV1. Set --model-impl vllm explicitly, since auto picks transformers for the architectures optimum-rbln supports.
  • PYTHONHASHSEED has to be set to a fixed value. Without it, vLLM derives the base of its block hash chain from a per-process random value, the chain diverges between the scheduler and the workers, and every cache lookup misses. The connector refuses to construct with RuntimeError instead of failing silently. vLLM hashes the variable's string to seed the chain, so any fixed value works.

RDS device availability

For RDS offloading, the host must have an NVMe device bound for RDS. On a systemd host, inspect the binding service and its boot logs:

systemctl status rebellions-rds-binding.service --no-pager
journalctl -u rebellions-rds-binding.service -b --no-pager

The binding service is a oneshot service, so active (exited) can be normal. In a container, check the service on the host. Then check runtime visibility in the Python environment where the server runs:

python -c 'from rebel import rds; print("RDS available:", rds.is_available()); print("RDS devices:", rds.query_available_device_count())'

Expect RDS available: True and a nonzero device count. These checks establish runtime visibility; verify writes and restores after startup using Verify offloading.

Configuration

The cache is configured through LMCache's LMCACHE_* environment variables, and the connector is selected through --kv-transfer-config. Choose a storage tier, review its settings below, then copy the complete block in Server launch.

CPU offloading

CPU offloading keeps KV chunks in host memory through LMCache's default backend, LocalCPUBackend. It needs no additional hardware and follows upstream LMCache usage.

Variable Description Default
LMCACHE_LOCAL_CPU Enables the host memory backend. True
LMCACHE_MAX_LOCAL_CPU_SIZE Host memory pool size in GB. 5.0
LMCACHE_CHUNK_SIZE Tokens per KV chunk. With partial-chunk saving disabled, a prefix must fill a chunk before it can be stored; short prefixes produce no hits. 256

Tip

Set LMCACHE_CHUNK_SIZE equal to --block-size. One chunk then maps to one KV cache block, which gives the best KV copy performance.

For the full set of LMCache configuration keys, see the LMCache documentation.

SSD offloading (RDS)

RDS offloading keeps KV chunks on NVMe, written from NPU memory without passing through host memory. Confirm that the server works with CPU offloading before switching to this tier.

Info

RDS requires a bound NVMe RDS device on the host. There is no fallback path: without the device, the tier is unavailable.

The backend is registered through LMCache's storage-plugin launcher. To make RDS the primary allocator, turn the CPU tier off explicitly. Keeping the CPU tier enabled creates a hybrid configuration in which host memory is the primary tier and chunks reach NVMe through host memory.

Key Value Description
storage_plugin.RDSBackend.module_path lmcache_rbln.storage_plugin.rds_backend Module that holds the backend class.
storage_plugin.RDSBackend.class_name RDSBackend Backend class name.
rds_buffer_size 2048 Device memory staging budget in MiB, not NVMe capacity. Defaults to 2048; values <= 0 disable staging sub-batching.
rds_failure_policy ignore ignore keeps best-effort operation; fail propagates terminal RDS failures to the serving worker. Defaults to ignore.
rds_max_chunks 1000 Maximum number of chunks stored on NVMe. Each stored chunk takes 2 MiB of NPU device memory per NVMe drive while it stays cached, so with the default, up to 2 GiB per drive can be taken dynamically as chunks are stored. This memory is not reserved when the KV cache is sized. 0 removes the cap. Defaults to 1000.

Warning

Staging memory is allocated on demand. A configured budget does not reserve NPU memory or guarantee a store succeeds. With the default rds_failure_policy=ignore, a failed write batch is dropped and serving can continue; the backend is not automatically disabled. With fail, non-capacity write failures and read failures are propagated to the worker. Recognized capacity errors on writes can still evict cached chunks and retry. Check logs after the first requests.

Device memory headroom for RDS

RDS uses NPU device memory outside the KV cache: the staging buffer (rds_buffer_size) and 2 MiB per NVMe drive for each stored chunk (rds_max_chunks). When device memory is tight, lower rds_max_chunks or --gpu-memory-utilization. On Rebel100™, keep rds_max_chunks and rds_buffer_size at their defaults and set --gpu-memory-utilization 0.8 when using RDS.

Server launch

Run one block below in a fresh shell with the server environment activated and no other LMCACHE_* settings, which can otherwise affect the run. LMCACHE_USE_LAYERWISE=True is unsupported and rejected at startup.

export PYTHONHASHSEED=0
export LMCACHE_LOCAL_CPU=True
export LMCACHE_MAX_LOCAL_CPU_SIZE=5.0
export LMCACHE_CHUNK_SIZE=1024
export LMCACHE_USE_LAYERWISE=False

vllm serve Qwen/Qwen3-1.7B \
  --model-impl vllm \
  --max-model-len 8192 \
  --block-size 1024 \
  --enable-prefix-caching \
  --max-num-batched-tokens 128 \
  --max-num-seqs 4 \
  --kv-transfer-config \
  '{"kv_connector":"RBLNLMCacheConnectorV1", "kv_role":"kv_both"}'
export PYTHONHASHSEED=0
export LMCACHE_LOCAL_CPU=False
export LMCACHE_MAX_LOCAL_CPU_SIZE=0
export LMCACHE_CHUNK_SIZE=1024
export LMCACHE_USE_LAYERWISE=False
export LMCACHE_STORAGE_PLUGINS=RDSBackend
export LMCACHE_EXTRA_CONFIG='{
    "storage_plugin.RDSBackend.module_path": "lmcache_rbln.storage_plugin.rds_backend",
    "storage_plugin.RDSBackend.class_name": "RDSBackend",
    "rds_buffer_size": 2048,
    "rds_failure_policy": "ignore"
}'

vllm serve Qwen/Qwen3-1.7B \
  --model-impl vllm \
  --max-model-len 8192 \
  --block-size 1024 \
  --enable-prefix-caching \
  --max-num-batched-tokens 128 \
  --max-num-seqs 4 \
  --kv-transfer-config \
  '{"kv_connector":"RBLNLMCacheConnectorV1", "kv_role":"kv_both"}'
Key Value Description
kv_connector RBLNLMCacheConnectorV1 Connector name, registered by RBLN vLLM.
kv_role kv_both The instance both stores and restores KV cache.

Verify offloading

First check startup, store, and restore logs:

Log Meaning
RDS staging buffer: 2048 MiB The staging budget was resolved. This does not confirm memory allocation or I/O success.
RDS _sync_batched_write: issuing N offloads A write batch started. Check subsequent errors before treating it as successful.
RDS batched_get_blocking: requested=N, hot_cache hits=M The in-memory index contains M requested NVMe entries. hot_cache is an index, not a host KV-data tier.
RDS batched_get_blocking: issuing N restores Restore calls are about to run. A later error can still prevent retrieval.

To check a restore, start the server with --no-enable-prefix-caching and send the same prompt, longer than one chunk, twice. If vllm:external_prefix_cache_hits_total in /metrics is higher after the second request than before the first, the second request reused KV from the offload tier. For RDS, also check that the log shows Retrieved N out of N required tokens with no RDS batched write failed error. Turn prefix caching back on afterward. If no tokens are retrieved, see Troubleshooting.

Troubleshooting

RDS batched write failed

RDS batched write failed for 1 keys

This message reports a batch write failure. Check the following traceback for the cause. Failed writes are not published as new entries in the RDS index.

Symptom What to check
NVMe not enabled or RDS unavailable Check the host binding service, its journal, and runtime device visibility in Prerequisites. Restart the server after correcting host setup.
Staging allocation fails or staging pool exhausted Check available NPU memory, KV pool size, chunk size, and the staging budget. Increasing the budget does not create physical memory; reduce memory pressure first.
KV pool allocation fails during startup Size the pool for the workload. --num-gpu-blocks-override can set an explicit block count when automatic sizing fails; RDS tests use 32 blocks with Qwen3-1.7B and block size 1024, not a universal production value.
Repeated offload failures with LRU eviction Inspect the original exception. In ignore mode, non-capacity errors can also cause eviction and retries; this does not by itself prove NVMe is full. rds_failure_policy=fail avoids eviction retries for non-capacity write errors.

No cache stores or restores

Confirm that lmcache-rbln is installed in the server environment, --model-impl vllm is selected, and the intended backend loaded. Check the effective chunk size and prompt token count. For RDS, keep LMCACHE_LOCAL_CPU=False and LMCACHE_MAX_LOCAL_CPU_SIZE=0 to use device staging as the primary allocator.

A repeated request may be served entirely by NPU prefix caching or sub-block caching. To isolate external retrieval, follow Verify offloading. RDS entries are lost on server restart, so the first request after a restart must populate the cache again.

Limitations

  • Sub-block prefix caching and a KV connector both extend a full-block prefix cache hit, and the scheduler runs only one of them per request, taking whichever offers more tokens, with sub-block winning a tie. See Prefix Caching.