chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:24:33 +08:00
commit f213ec8976
2101 changed files with 494002 additions and 0 deletions
+132
View File
@@ -0,0 +1,132 @@
Async Loading
=============
.. warning::
This page documents the behavior of LMCache's in-process mode (deprecated). Please consider using :doc:`LMCache MP mode </mp/index>` for better feature support and performance.
.. contents:: Table of Contents
:local:
:depth: 2
Overview
--------
This document explains the principle, benefits, differences from vLLM PR `19330 <https://github.com/vllm-project/vllm/pull/19330>`_, and limitations of the LMCache ``async_loading`` feature.
It focuses on LMCache v1 integration with vLLM and the internal storage pipeline.
Key change of components in this feature include:
- LMCache async lookup client/server (ZMQ-based)
- Storage manager orchestrating backends and concurrency
- Cache engine async API entrypoints
- vLLM adapter integration points
Principle and Theory
--------------------
At a high level, ``async_loading`` decouples scheduler-side lookup from worker-side prefetch/retrieval, allowing overlap between I/O and computation while preserving prefix-based correctness.
- The scheduler sends lookup requests with token chunk hashes and offsets.
- Worker-side servers perform tiered ``batched_async_contains`` over available backends and eagerly launch non-blocking batched get operations for hit prefixes.
- Completion is tracked via an ``EventManager`` to safely deliver loaded memory objects back to the requesting path.
- A weighted semaphore with an ``AsyncSerializer`` prevents allocator deadlocks by shaping concurrency according to chunk budget.
The following Mermaid sequence diagram illustrates the end-to-end flow:
.. mermaid::
sequenceDiagram
autonumber
participant S as Scheduler (vLLM)
participant LC as LMCacheAsyncLookupClient
participant WS as LMCacheAsyncLookupServer (Worker)
participant SM as StorageManager
participant BE as Backends (LocalCPU/LocalDisk/FSConnector)
participant EM as EventManager
S->>LC: lookup(token_ids, lookup_id, request_configs)
note right of LC: Hashes + offsets via TokenDatabase
LC->>WS: ZMQ PUSH multipart [lookup_id, hashes, offsets, configs]
WS->>SM: async_lookup_and_prefetch(lookup_id, keys, cum_chunk_lengths)
SM->>BE: batched_async_contains(lookup_id, keys, pin=True)
alt prefix hit across tiers
BE-->>SM: num_hit_chunks (per tier)
SM->>BE: batched_get_non_blocking(lookup_id, hit_prefix)
BE-->>SM: Future[List[MemoryObj]]
SM->>EM: add_event(EventType.LOADING, lookup_id, gather_all)
SM-->>WS: send_response_to_scheduler(lookup_id, retrieved_length)
WS-->>LC: ZMQ PUSH [lookup_id, num_hit_tokens]
else cache miss
SM-->>WS: send_response_to_scheduler(lookup_id, 0)
WS-->>LC: ZMQ PUSH [lookup_id, 0]
end
Architecture (Worker Side)
--------------------------
.. mermaid::
:align: center
flowchart LR
subgraph Worker
direction TB
A["LMCacheAsyncLookupServer<br/>ZMQ PULL/PUSH"]
B["StorageManager<br/>Async loop (thread)"]
C["AsyncSerializer<br/>WeightedSemaphore"]
D["EventManager<br/>EventType.LOADING"]
end
subgraph Backends
E["LocalCPUBackend<br/>contains/get"]
F["LocalDiskBackend<br/>async contains/get"]
G["FSConnector<br/>remote FS"]
end
A --> B
B --> C
B --> D
B -.contains/get.-> E
B -.contains/get.-> F
B -.contains/get.-> G
style E fill:#dff,stroke:#333,stroke-width:1px
style F fill:#ffd,stroke:#333,stroke-width:1px
style G fill:#dfd,stroke:#333,stroke-width:1px
Benefits
--------
- Performance overlap
- **I/OCompute Overlap**: Decoupling lookup/prefetch from loading enables fetching KV chunks while vLLM continues scheduling/computation.
- Robustness and error handling
- **Event-driven Synchronization**: ``EventManager`` ensures safe hand-off of futures and avoids race conditions between threads and the async loop.
- **Backpressure & Deadlock Avoidance**: ``AsyncSerializer`` with a weighted semaphore caps concurrent chunk retrievals based on allocator budget, preventing starvation or allocator lockups.
- **Graceful Miss Path**: Immediate response with ``None`` hit tokens when nothing is retrievable; worker returns quickly without stalling the scheduler.
Comparison with vLLM Load Failure Recovery feature
---------------------------------------------------
The `VLLM_PR_19330 <https://github.com/vllm-project/vllm/pull/19330>`_ introduces a fault recovery mechanism for vLLM's KV connector infrastructure that enables graceful handling of KV cache load failures by automatically detecting failed block loads and rescheduling only affected requests for recomputation from a valid prefix.
By contrast, LMCaches ``async_loading`` is an externalized caching layer with its own client/server, storage backends, and concurrency control.
Limitations
-----------
- Only works with vllm merged `VLLM_PR_23620 <https://github.com/vllm-project/vllm/pull/23620>`_
- Backend support constraint: This feature currently requires backends that implement ``batched_async_contains``; limited to a few backends, e.g.:
- ``LocalCpuBackend``
- ``LocalDiskBackend``
- ``S3Connector``
- ``FSConnector``
- ``RedisConnector/RedisClusterConnector``
Future Work
-----------
- Introduce a default ``batched_async_contains`` implementation, so all backends can support ``async_loading``.
- Add metrics and observability to track the number of asynchronous lookup requests and the number of occupied ``MemoryObj`` instances.
- Improve the lookup framework by passing vLLM prefix cache hit tokens so that async lookup can skip loading parts already hit in vLLM.
+13
View File
@@ -0,0 +1,13 @@
Using Different Caching Policies
===================================
.. warning::
This page documents the behavior of LMCache's in-process mode (deprecated). Please consider using :doc:`LMCache MP mode </mp/index>` for better feature support and performance.
LMCache supports multiple caching policies.
For example, to use LRU, you can set the environment variable ``LMCACHE_CACHE_POLICY=LRU`` or set it in the configuration file with ``cache_policy="LRU"``.
Currently, LMCache supports "LRU" (Least Recently Used), "MRU" (Most Recently Used), "LFU" (Least Frequently Used) and "FIFO" (First-In-First-Out) caching policies.
@@ -0,0 +1,12 @@
:orphan:
Multiprocess Mode (Redirect)
=============================
.. note::
This page has moved. The multiprocess mode documentation is now at
:doc:`/mp/index`.
Please see the new :doc:`/mp/index` section for complete documentation
including quick start, configuration reference, L2 storage, deployment,
observability, and architecture guides.
+237
View File
@@ -0,0 +1,237 @@
.. _p2p_sharing:
P2P KV Cache Sharing
====================
.. warning::
This page documents the behavior of LMCache's in-process mode (deprecated). Please consider using :doc:`LMCache MP mode </mp/index>` for better feature support and performance. For the MP mode equivalent of this page, see :doc:`/mp/p2p`.
P2P (Peer-to-Peer) KV cache sharing enables direct cache transfer between multiple serving engine instances without requiring a centralized cache server. This approach provides high-performance cache sharing with reduced latency and improved scalability, especially beneficial in distributed inference scenarios.
LMCache supports P2P sharing through a controller-based architecture using NIXL (NVIDIA Inference Xfer Library) for optimized data transfer between instances.
Prerequisites
-------------
- **Multi-GPU Setup**: Your server should have at least 2 GPUs
- **NIC**: RDMA is recommended for more performance.
- **NIXL**: Install from `NIXL <https://github.com/ai-dynamo/nixl>`_
- **vLLM**: v1 version is required, refer to :ref:`installation_guide` for details.
- **LMCache**: Install from :ref:`installation_guide`
Configuration
-------------
Create two configuration files for the P2P sharing setup.
**Instance 1 Configuration (example1.yaml)**:
.. code-block:: yaml
chunk_size: 256
local_cpu: True
max_local_cpu_size: 100
enable_async_loading: True
# P2P configurations
enable_p2p: True
p2p_host: "localhost"
p2p_init_ports: 8200
p2p_lookup_ports: 8201
transfer_channel: "nixl"
# Controller configurations
enable_controller: True
lmcache_instance_id: "lmcache_instance_1"
controller_pull_url: "localhost:8300"
controller_reply_url: "localhost:8400"
lmcache_worker_ports: 8500
extra_config:
lookup_backoff_time: 0.001
**Instance 2 Configuration (example2.yaml)**:
.. code-block:: yaml
chunk_size: 256
local_cpu: True
max_local_cpu_size: 100
enable_async_loading: True
# P2P configurations
enable_p2p: True
p2p_host: "localhost"
p2p_init_ports: 8202
p2p_lookup_ports: 8203
transfer_channel: "nixl"
# Controller configurations
enable_controller: True
lmcache_instance_id: "lmcache_instance_2"
controller_pull_url: "localhost:8300"
controller_reply_url: "localhost:8400"
lmcache_worker_ports: 8501
extra_config:
lookup_backoff_time: 0.001
Setup and Usage
---------------
**Step 1: Start the LMCache Controller**
.. code-block:: bash
PYTHONHASHSEED=123 lmcache_controller --host localhost --port 9000 --monitor-ports '{"pull": 8300, "reply": 8400, "heartbeat": 8082}'
Make sure that the 8300 and 8400 ports are set up in **controller_pull_url** and **controller_reply_url** in the configuration files.
Port 9000 is the controller main port, which is arbitrary and can be changed.
After starting the controller, access the WebUI at:
http://localhost:9000/
**Step 2: Start vLLM Engines with LMCache Workers**
If the NIC supports RDMA:
.. code-block:: bash
export UCX_TLS=rc
If the NIC does not support RDMA:
.. code-block:: bash
export UCX_TLS=tcp
Start vLLM engine 1 at port 8010:
.. code-block:: bash
PYTHONHASHSEED=123 CUDA_VISIBLE_DEVICES=0 LMCACHE_CONFIG_FILE=/path/to/example1.yaml \
vllm serve meta-llama/Meta-Llama-3.1-8B-Instruct \
--gpu-memory-utilization 0.8 \
--port 8010 \
--kv-transfer-config '{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}'
Start vLLM engine 2 at port 8011:
.. code-block:: bash
PYTHONHASHSEED=123 CUDA_VISIBLE_DEVICES=1 LMCACHE_CONFIG_FILE=/path/to/example2.yaml \
vllm serve meta-llama/Meta-Llama-3.1-8B-Instruct \
--gpu-memory-utilization 0.8 \
--port 8011 \
--kv-transfer-config '{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}'
**Step 3: Test P2P Cache Sharing**
Send a request to vLLM engine 1 to populate the cache:
.. code-block:: bash
curl -X POST http://localhost:8010/v1/completions \
-H "Content-Type: application/json" \
-d "{
\"model\": \"meta-llama/Meta-Llama-3.1-8B-Instruct\",
\"prompt\": \"$(printf 'Explain the significance of KV cache in language models.%.0s' {1..100})\",
\"max_tokens\": 10
}"
Send the same request to vLLM engine 2 to demonstrate cache retrieval from **engine 1**:
.. code-block:: bash
curl -X POST http://localhost:8011/v1/completions \
-H "Content-Type: application/json" \
-d "{
\"model\": \"meta-llama/Meta-Llama-3.1-8B-Instruct\",
\"prompt\": \"$(printf 'Explain the significance of KV cache in language models.%.0s' {1..100})\",
\"max_tokens\": 10
}"
Expected Output
---------------
When the second request successfully retrieves cache from the first instance, you should see logs similar to:
.. code-block:: bash
(EngineCore_DP0 pid=2577584)[2025-09-21 00:00:11,706] LMCache INFO:[0m Established connection to peer_init_url localhost:8200. The peer_lookup_url: localhost:8201 (p2p_backend.py:278:lmcache.v1.storage_backend.p2p_backend)
(EngineCore_DP0 pid=2577584)[2025-09-21 00:00:11,792] LMCache INFO: Retrieved 1002 out of total 1002 out of total 1002 tokens. size: 0.1223 gb, cost 60.3595 ms, throughput: 2.0264 GB/s; (cache_engine.py:496:lmcache.v1.cache_engine)
These logs indicate successful P2P connection establishment and high-throughput cache retrieval.
**Step 4: Benchmarking P2P Cache Sharing**
Send a request workload to instance 1 to populate the cache:
.. code-block:: bash
python benchmarks/long_doc_qa/long_doc_qa.py \
--model meta-llama/Meta-Llama-3.1-8B-Instruct \
--num-documents 50 \
--document-length 10000 \
--output-len 100 \
--repeat-count 1 \
--repeat-mode tile \
--port 8010 \
--max-inflight-requests 4
Send the same request workload to instance 2 to demonstrate cache retrieval from **instance 1**:
.. code-block:: bash
python benchmarks/long_doc_qa/long_doc_qa.py \
--model meta-llama/Meta-Llama-3.1-8B-Instruct \
--num-documents 50 \
--document-length 10000 \
--output-len 100 \
--repeat-count 1 \
--repeat-mode tile \
--port 8011 \
--max-inflight-requests 4
Benchmark Results
-----------------
First instance metrics:
.. code-block:: text
Warmup round mean TTFT: 2.286s
Warmup round time: 37.957s
Warmup round prompt count: 50
Warmup round successful prompt count: 50
=== BENCHMARK RESULTS ===
Query round mean TTFT: 2.028s
Query round time: 38.323s
Query round prompt count: 50
Query round successful prompt count: 50
Second instance metrics:
.. code-block:: text
Warmup round mean TTFT: 1.036s
Warmup round time: 13.814s
Warmup round prompt count: 50
Warmup round successful prompt count: 50
=== BENCHMARK RESULTS ===
Query round mean TTFT: 0.490s
Query round time: 7.964s
Query round prompt count: 50
Query round successful prompt count: 50
In this example, the warm-up round metric in long_doc_qa is used because no existing KV cache is reused within an instance to benefit solely from P2P sharing. With LMCache P2P sharing enabled, the time to first token (TTFT) is reduced by 54.7%, from 2.286 s to 1.036 s, with a 63.6% reduction in total inference time (37.957 s → 13.814 s).
@@ -0,0 +1,174 @@
3FS
====
.. warning::
This page documents the behavior of LMCache's in-process mode (deprecated). Please consider using :doc:`LMCache MP mode </mp/index>` for better feature support and performance. For the MP mode equivalent of this page, see :doc:`/mp/l2_storage/index`.
.. _3FS-overview:
Overview
--------
3FS (Fire-Flyer File System) is an AI native distributed file-system which provides high performance and
low latency. It is a supported option for KV Cache offloading in LMCache. Even though the FSConnector
backend can work with 3FS storage cluster, but it access data in 3FS storage cluster by FUSE interfaces.
It can't leverage 3FS high performance. This particular backend uses 3FS native USRBIO(User Space Ring
Based IO) interfaces to access 3FS storage cluster which get high performance.
Configure LMCache 3FS Offloading
-----------------------------------------
Passed in through ``LMCACHE_CONFIG_FILE=your-lmcache-config.yaml``
Example ``config.yaml``:
.. code-block:: yaml
# 256 Tokens per KV Chunk
chunk_size: 256
local_cpu: False
# Plugin name mode, obtain base_path in extra_config session
remote_storage_plugins: ["hf3fs.primary"]
# URL mode, obtain base_path from the URL
#remote_url: "hf3fs:///3fs/stage/hello, /3fs/stage/world"
extra_config:
# base_path, for a plugin instance
remote_storage_plugin.hf3fs.primary.base_path: "/3fs/stage/dir1,/3fs/stage/dir2"
# base_path, for all plugin instances
#hf3fs_base_path: "/3fs/stage/dir1,/3fs/stage/dir2"
# Mount point of 3FS
hf3fs_mount_point: "/3fs/stage"
# Shared memory size for Iov in hf3fs client,
# range in [104857600(100MB), 2147483648(2GB)], default:209715200
hf3fs_iov_size: 209715200 #200MB
# Max num of concurrent requests that can be submitted in Ior
# range in [128,1024], default: 256
hf3fs_ior_entries: 256
# Control with I/O depth. 0, no control
# >0, only when io_depth requests are in queue, and issue them in one batch
# <0, wait for at most -io_depth requests are in queue and issue them in one batch
# range in [-128, 128], default: 0
hf3fs_io_depth: 0
# NUMA ID for Ior shared memory, -1 for current process NUMA ID.
hf3fs_numa_id: -1
# Number of io thread
# range in [2,16], default: 4
hf3fs_io_thread_num: 4
There are 2 methods to config hf3fs remote backend:
1. Plugin name mode, uses the parameter remote_storage_plugins(Recommend)
2. URL mode, uses the parameter remote_url(Deprecated, will be removed in a future)
For URL mode, the base_path is contained in the url. For plugin name mode, there are 2 methods to set base_path:
1. remote_storage_plugin.{plugin name}.base_path, it sets the base_path for a plugin instance.
e.g.:remote_storage_plugin.hf3fs.primary.base_path
2. hf3fs_base_path, it set the base_path for all plugin instances
Installation
-------------
.. _3FS-prerequisites:
**Prerequisites:**
- A Machine with at least one GPU. You can adjust the max model length of your vllm instance depending on your GPU memory.
- vllm and lmcache installed
**Step 1. Install 3FS hf3fs_py_usrbio package**
The inference server need install 3FS hf3fs_py_usrbio package, recommend to build the package from source:
.. code-block:: bash
git clone https://github.com/deepseek-ai/3fs
cd 3fs
git submodule update --init --recursive
./patches/apply.sh
Install dependencies
pip install -e .
`3FS Build <https://github.com/deepseek-ai/3FS/blob/main/README.md#build-3fs>`_
**Step 2. Setup 3FS storage cluster**
`3FS Setup <https://github.com/deepseek-ai/3FS/blob/main/deploy/README.md>`_
**Step 3. Deploy 3FS FUSE client in inference server**
The inference server must deploy 3FS FUSE client(a fuse daemon process provided by 3FS),
otherwise, it can't access 3FS storage cluster
`Setup 3FS FUSE Client <https://github.com/deepseek-ai/3FS/blob/main/deploy/README.md#step-8-fuse-client>`_
**Step 4. Start a vLLM server with 3FS offloading enabled**
Create a lmcache configuration file called: ``3fs-offload.yaml``
.. code-block:: yaml
# 256 Tokens per KV Chunk
chunk_size: 256
local_cpu: False
# support multiple paths
remote_storage_plugins: ["hf3fs.primary"]
extra_config:
# base_path
remote_storage_plugin.hf3fs.primary.base_path: "/3fs/stage/dir1,/3fs/stage/dir2"
# Mount point of 3FS
hf3fs_mount_point: "/3fs/stage"
# Shared memory size for Iov in hf3fs client,
# range in [104857600(100MB), 2147483648(2GB)], default:209715200 (200MB)
hf3fs_iov_size: 209715200
# Max num of concurrent requests that can be submitted in Ior
# range in [128,1024], default: 256
hf3fs_ior_entries: 256
# Control with I/O depth. 0, no control
# >0, only when io_depth requests are in queue, and issue them in one batch
# <0, wait for at most -io_depth requests are in queue and issue them in one batch
# range in [-128, 128], default: 0
hf3fs_io_depth: 0
# NUMA ID for Ior shared memory, -1 for current process NUMA ID.
hf3fs_numa_id: -1
# Number of io thread
# range in [2,16], default: 4
hf3fs_io_thread_num: 4
Start vllm:
.. code-block:: bash
export VLLM_USE_V1=0
export LMCACHE_USE_EXPERIMENTAL=True
export LMCACHE_LOG_LEVEL=INFO
export VLLM_WORKER_MULTIPROC_METHOD=spawn
export VLLM_ENABLE_V1_MULTIPROCESSING=1
LMCACHE_CONFIG_FILE="3fs-offload.yaml" \
vllm serve \
meta-llama/Llama-3.1-8B-Instruct \
--max-model-len 65536 \
--kv-transfer-config \
'{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}'
@@ -0,0 +1,78 @@
Azure Blob Storage Backend
==========================
LMCache can offload KV cache to Azure Blob Storage via an async-native
connector (parity with the S3 backend, no NIXL dependency). The connector is
selected by an ``azure://`` ``remote_url``.
Optional Packages
-----------------
The Azure SDKs are optional dependencies and are imported lazily, so install
them only when you use this backend:
.. code-block:: bash
pip install azure-storage-blob
# Only needed for the DefaultAzureCredential fallback (see Authentication):
pip install azure-identity
URL Format
----------
.. code-block:: text
azure://<container_name>
The container name is the only value carried in the URL. The storage account
and credentials are supplied via ``extra_config``.
Example Configuration
---------------------
.. code-block:: yaml
chunk_size: 256
local_cpu: False
save_unfull_chunk: False
remote_url: "azure://my-container"
remote_serde: "naive"
blocking_timeout_secs: 10
extra_config:
azure_account_url: "https://myaccount.blob.core.windows.net"
azure_account_key: "your-account-key"
Configuration Parameters (in extra_config)
-----------------------------------------
* **azure_account_url**: Storage account URL, e.g.
``https://<account>.blob.core.windows.net``. Required unless
``azure_connection_string`` is provided.
* **azure_connection_string**: Full Azure Storage connection string. When set,
it is used on its own and the other fields are ignored.
* **azure_account_key**: Storage account shared key (paired with
``azure_account_url``).
* **azure_sas_token**: Shared Access Signature token (paired with
``azure_account_url``).
Authentication
--------------
Exactly one credential path is resolved, most-explicit-first:
1. **azure_connection_string** — if set, it wins.
2. **azure_account_url + azure_account_key** — shared-key auth.
3. **azure_account_url + azure_sas_token** — SAS-token auth.
4. **azure_account_url** only — falls back to
``DefaultAzureCredential`` (managed identity, environment variables, Azure
CLI login, etc.), which requires the ``azure-identity`` package.
``azure_account_url`` is required for every path except the connection-string
case.
Notes
-----
* Only full chunks are stored; set ``save_unfull_chunk: False``.
* Keys are flattened into one blob per chunk (same flat layout as the S3
backend).
@@ -0,0 +1,152 @@
Google Cloud Bigtable
=====================
.. warning::
This page documents the behavior of LMCache's in-process mode (deprecated). Please consider using :doc:`LMCache MP mode </mp/index>` for better feature support and performance. For the MP mode equivalent of this page, see :doc:`/mp/l2_storage/index`.
.. _bigtable-overview:
Overview
--------
Google Cloud Bigtable is a petabyte-scale, fully managed NoSQL database. Integrating Cloud Bigtable as a built-in remote storage connector inside LMCache bridges volatile high-cost in-memory tiers (Redis) and low-cost, high-latency archival object stores (S3).
For more information, see the `Cloud Bigtable Overview <https://cloud.google.com/bigtable/docs/overview>`_ and `Cloud Bigtable Pricing <https://cloud.google.com/bigtable/pricing>`_.
Architecture & Payload Limits
-----------------------------
- **Chunk Size Optimization**: Set LMCache's logical ``chunk_size`` to **256 tokens**. This groups payloads to minimize sequential Point-Read gRPC calls, preventing Python event-loop (GIL) bottlenecks.
- **MutateRow Limit**: Enforces a strict **90.0 MB request limit** for a single ``MutateRow`` gRPC request.
- **Storage Tier Row Limits**: The **SSD Tier** ceiling is **100 MiB per cell/row**. The **Enterprise Plus In-Memory Tier** is limited to **1.0 MiB per row**.
- **TTLCache Shielding**: Embeds a thread-safe ``TTLCache`` (10-second TTL default) to shield Bigtable nodes from concurrent prefetch lookup spikes.
Infrastructure Setup
--------------------
**1. Enable GCP APIs**
.. code-block:: bash
gcloud services enable bigtable.googleapis.com bigtableadmin.googleapis.com --project=your-gcp-project-id
**2. Provision Bigtable Instance**
Refer to the `gcloud beta bigtable Reference <https://cloud.google.com/sdk/gcloud/reference/beta/bigtable>`_ for additional parameter details.
.. code-block:: bash
gcloud beta bigtable instances create your-bigtable-instance-id \
--display-name="LMCache SSD Instance" \
--edition=ENTERPRISE \
--cluster-storage-type=ssd \
--cluster-config=id=your-cluster-id,zone=us-central1-a,nodes=1 \
--project=your-gcp-project-id
**3. Create Database Table & Column Family**
.. code-block:: bash
gcloud bigtable instances tables create lmcache-benchmark-v1 \
--instance=your-bigtable-instance-id \
--column-families=cf \
--project=your-gcp-project-id
**4. Install LMCache & Bigtable SDK**
.. code-block:: bash
export NO_NATIVE_EXT=1
pip install --no-cache-dir lmcache google-cloud-bigtable
Configuration
-------------
**Example A: Standard Bigtable SSD Integration (L2 Only)**
.. code-block:: yaml
chunk_size: 256
local_cpu: true
max_local_cpu_size: 10.0
remote_url: "bigtable://your-gcp-project-id/your-bigtable-instance-id"
remote_serde: "naive"
extra_config:
bigtable_project_id: "your-gcp-project-id"
bigtable_instance_id: "your-bigtable-instance-id"
bigtable_table_name: "lmcache-benchmark-v1"
.. note::
Alternatively, you can set the environment variables ``BT_PROJECT_ID``, ``BT_INSTANCE_ID``, and ``BT_TABLE_NAME`` instead of using ``extra_config``.
**Example B: 3-Tier Multi-Connector Hybrid (Local CPU -> Redis L2 -> Bigtable SSD L3)**
Deploy Redis for hot-cache loopbacks while offloading long-tail persistent storage to Bigtable SSD, using LMCache's dynamic OrderedDict routing.
.. code-block:: yaml
chunk_size: 256
local_cpu: true
max_local_cpu_size: 15.0
remote_storage_plugins:
- "redis"
- "bigtable"
extra_config:
remote_storage_plugin.redis.redis_url: "redis://your-redis-host:6379"
remote_storage_plugin.bigtable.bigtable_project_id: "your-gcp-project-id"
remote_storage_plugin.bigtable.bigtable_instance_id: "your-bigtable-instance-id"
remote_storage_plugin.bigtable.bigtable_table_name: "lmcache-benchmark-v1"
remote_storage_plugin.bigtable.bigtable_family_name: "cf"
remote_storage_plugin.bigtable.bigtable_column_name: "data"
remote_storage_plugin.bigtable.credentials_path: "/etc/gcp/key.json"
remote_storage_plugin.bigtable.bigtable_max_chunk_size_mb: 90.0
remote_storage_plugin.bigtable.exists_cache_ttl_seconds: 10.0
remote_storage_plugin.bigtable.exists_cache_size: 10000
remote_storage_plugin.bigtable.bigtable_write_timeout_ms: 10000.0
remote_storage_plugin.bigtable.bigtable_read_timeout_ms: 5000.0
Authentication
--------------
- **Application Default Credentials (ADC)**: If ``credentials_path`` is omitted or ``null``, the connector natively invokes ADC. Compatible with local development via ``gcloud auth application-default login`` or GKE Workload Identity Federation.
- **Explicit Keys**: Pass the absolute filesystem path containing a mounted GCP Service Account JSON secret to ``credentials_path``.
Verification
------------
Ensure you have installed the required dependencies:
.. code-block:: bash
pip install cachetools google-cloud-bigtable
Run the unit tests:
.. code-block:: bash
pytest tests/v1/storage_backend/test_bigtable_connector.py
Troubleshooting Large Payload Warnings
--------------------------------------
If you see a warning in the logs indicating that a chunk size exceeds the limit and is skipped (e.g. ``Bigtable chunk size ... MB exceeds threshold ... MB. Skipping write to prevent hard failures``), choose one of the following approaches:
1. **Reduce the LMCache Chunk Size (Recommended)**:
The serialized chunk size depends on LMCache's logical ``chunk_size`` (number of tokens per chunk) and model shape. You can reduce ``chunk_size`` (e.g., from ``256`` to ``128``) in your configuration file to shrink individual chunk payloads.
2. **Increase the Max Chunk Size**:
If your Bigtable instance uses the SSD storage tier (which supports up to 100 MB per cell/row), you can raise the maximum allowed write threshold in the configuration up to ``99.0`` MB using the ``bigtable_max_chunk_size_mb`` config key (or the ``BT_MAX_CHUNK_SIZE_MB`` environment variable).
.. warning::
Do not set ``bigtable_max_chunk_size_mb`` higher than ``100.0`` MB. While Cloud Bigtable supports up to ``256.0`` MB for a single row, a single cell value (which LMCache uses to store the chunk payload) has a hard limit of ``100.0`` MB. Exceeding this will trigger hard gRPC exceptions.
@@ -0,0 +1,330 @@
CPU RAM
=======
.. warning::
This page documents the behavior of LMCache's in-process mode (deprecated). Please consider using :doc:`LMCache MP mode </mp/index>` for better feature support and performance. For the MP mode equivalent of this page, see :doc:`/mp/l2_storage/index`.
.. _cpu_ram-overview:
Overview
--------
CPU RAM and Local Storage are the two ways of offloading KV cache onto non-GPU
memory of the same machine that is running inference.
Two ways to configure LMCache CPU Offloading:
---------------------------------------------
**1. Environment Variables:**
.. code-block:: bash
# 256 Tokens per KV Chunk
export LMCACHE_CHUNK_SIZE=256
# Enable CPU memory backend
export LMCACHE_LOCAL_CPU=True # default
# 5GB of Pinned CPU memory
export LMCACHE_MAX_LOCAL_CPU_SIZE=5.0 # default
**2. Configuration File**:
Passed in through ``LMCACHE_CONFIG_FILE=your-lmcache-config.yaml``
Example ``config.yaml``:
.. code-block:: yaml
# 256 Tokens per KV Chunk
chunk_size: 256
# Enable CPU memory backend
local_cpu: true # default
# 5GB of Pinned CPU memory
max_local_cpu_size: 5.0 # default
CPU RAM Explanation:
---------------------
The ``LMCACHE_MAX_LOCAL_CPU_SIZE`` is the amount of page-locked (for fast GPU transfer)
CPU memory that LMCache will reserve and must be set to a number greater than 0 since
local and remote backends also use CPU RAM as an intermediate buffer when transferring KV caches
with the GPU. This means it is possible to set ``LMCACHE_LOCAL_CPU=False`` even
though ``LMCACHE_MAX_LOCAL_CPU_SIZE`` is set to a non-zero number.
However, it is recommended to *always* set ``LMCACHE_LOCAL_CPU=True`` (the default is ``True`` so if you
don't specify, CPU offloading will automatically be enabled) since this allows all currently unused pinned CPU RAM that
LMCache has reserved to hold KV caches. When the pinned CPU RAM is required for any disk or remote transfers, the CPU KV caches will be LRU evicted to make
space so there is no danger of running out of pinned CPU RAM.
When ``LMCACHE_LOCAL_CPU=True`` is used in conjunction with the disk backend or
a remote backend (:doc:`Redis <./redis>`, :doc:`Mooncake <./mooncake>`, :doc:`Valkey <./valkey>`,
or :doc:`Infinistore <./infinistore>`), we can think of the CPU RAM as a "hot cache" that
will contain the "hottest" (most recently accessed)subset of KV caches from Disk and Remote storage.
Thus, the cache engine also has a **prefetch** mechanism to preload the KV caches for specified
tokens into the pinned CPU RAM from the disk or remote storage (*if* the KV caches for these
tokens are already stored there). This can preemptively avoid the latency of the disk and
remote KV transfer if we predict these tokens will be requested soon (e.g. structured or agentic workflows).
.. _cpu_ram-hugepage-support:
Hugepage Support
-----------------
By default LMCache allocates CPU-pinned memory using regular 4 KiB pages.
For large KV cache buffers (multiple gigabytes), enabling **Linux hugepages**
(2 MiB pages) can reduce TLB (Translation Lookaside Buffer) pressure and
improve memory access performance.
**System prerequisite**
Hugepages must be pre-allocated at the OS level before LMCache starts.
TO find the number of pages needed, divide the desired buffer size by 2 MiB and round up.
For example, 5 GB requires at least 2560 pages:
.. code-block:: bash
# Allocate 2560 hugepages (5 GB)
sudo sysctl -w vm.nr_hugepages=2560
# Make persistent across reboots
echo 'vm.nr_hugepages=2560' | sudo tee -a /etc/sysctl.conf
Verify that pages are available:
.. code-block:: bash
grep HugePages /proc/meminfo
# HugePages_Total: 2560
# HugePages_Free: 2560
**Configuration**
.. code-block:: yaml
local_cpu_use_hugepages: true
Or via environment variable:
.. code-block:: bash
export LMCACHE_LOCAL_CPU_USE_HUGEPAGES=true
**Restrictions**
- Hugepages are **not compatible with P2P mode** (``enable_p2p: true``).
- Hugepages are **not compatible with shared memory** (``shm_name`` is set).
- On non-CUDA platforms, hugepages are not supported. Regular allocation will be used as fallback.
.. _cpu_ram-online-inference-example:
Online Inference Example
------------------------
Let's feel the TTFT (time to first token) differential!
.. _cpu_ram-prerequisites:
**Prerequisites:**
- A Machine with at least one GPU. Adjust the max model length of your vllm instance depending on your GPU memory and the long context you want to use.
- vllm and lmcache installed (:doc:`Installation Guide <../../getting_started/installation>`)
- Hugging Face access to ``meta-llama/Meta-Llama-3.1-8B-Instruct``
.. code-block:: bash
export HF_TOKEN=your_hugging_face_token
- A few packages:
.. code-block:: bash
pip install openai transformers
**Step 0. Set up a directory for this example:**
.. code-block:: bash
mkdir lmcache-cpu-ram-example
cd lmcache-cpu-ram-example
**Step 1. Prepare a long context!**
We want a context long enough that vllm's prefix caching will not be able to hold the KV caches in
GPU memory and LMCache is necessary to keep KV caches in non-GPU memory:
.. code-block:: bash
# 382757 bytes
man bash > man-bash.txt
**Step 2. Start a vLLM server with CPU offloading enabled:**
Create a an lmcache configuration file called: ``cpu-offload.yaml``
.. code-block:: yaml
chunk_size: 256
local_cpu: true
max_local_cpu_size: 5.0
If you don't want to use a config file, uncomment the first three environment variables
and then comment out the ``LMCACHE_CONFIG_FILE`` below:
.. code-block:: bash
# LMCACHE_CHUNK_SIZE=256 \
# LMCACHE_LOCAL_CPU=True \
# LMCACHE_MAX_LOCAL_CPU_SIZE=5.0 \
LMCACHE_CONFIG_FILE="cpu-offload.yaml" \
vllm serve \
meta-llama/Llama-3.1-8B-Instruct \
--max-model-len 16384 \
--kv-transfer-config \
'{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}'
- ``--kv-transfer-config``: This is the parameter that actually tells vLLM to use LMCache for KV cache offloading.
- ``kv_connector``: Specifies the LMCache connector for vLLM V1
- ``kv_role``: Set to "kv_both" for both storing and loading KV cache (important because we will run two queries and the first will produce/store a KV cache while the second will consume/load that KV cache)
**Step 3. Query TTFT improvements with LMCache:**
Once the Open AI compatible server is running on default vllm port 8000, let's query it twice with the same long context!
Create a script called ``query-twice.py`` and paste the following code:
.. code-block:: python
import time
from openai import OpenAI
from transformers import AutoTokenizer
client = OpenAI(
api_key="dummy-key", # required by OpenAI client even for local servers
base_url="http://localhost:8000/v1"
)
models = client.models.list()
model = models.data[0].id
# 119512 characters total
# 26054 tokens total
long_context = ""
with open("man-bash.txt", "r") as f:
long_context = f.read()
# a truncation of the long context for the --max-model-len 16384
# if you increase the --max-model-len, you can decrease the truncation i.e.
# use more of the long context
long_context = long_context[:70000]
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Meta-Llama-3.1-8B-Instruct")
question = "Summarize bash in 2 sentences."
prompt = f"{long_context}\n\n{question}"
print(f"Number of tokens in prompt: {len(tokenizer.encode(prompt))}")
def query_and_measure_ttft():
start = time.perf_counter()
ttft = None
chat_completion = client.chat.completions.create(
messages=[{"role": "user", "content": prompt}],
model=model,
temperature=0.7,
stream=True,
)
for chunk in chat_completion:
chunk_message = chunk.choices[0].delta.content
if chunk_message is not None:
if ttft is None:
ttft = time.perf_counter()
print(chunk_message, end="", flush=True)
print("\n") # New line after streaming
return ttft - start
print("Querying vLLM server with cold LMCache CPU Offload")
cold_ttft = query_and_measure_ttft()
print(f"Cold TTFT: {cold_ttft:.3f} seconds")
print("\nQuerying vLLM server with warm LMCache CPU Offload")
warm_ttft = query_and_measure_ttft()
print(f"Warm TTFT: {warm_ttft:.3f} seconds")
print(f"\nTTFT Improvement: {(cold_ttft - warm_ttft):.3f} seconds \
({(cold_ttft/warm_ttft):.1f}x faster)")
Then run:
.. code-block:: bash
python query-twice.py
Since we're in streaming mode, you'll be able to feel the TTFT differential in
real time!
**Example Output:**
.. code-block:: text
Number of tokens in prompt: 15376
Querying vLLM server with cold LMCache
Bash is a Unix shell and command-line interpreter that executes commands read
from the standard input or from a file, incorporating features from the Korn
and C shells. It is an sh-compatible command language interpreter that can be
configured to be POSIX-conformant by default and is intended to be a conformant
implementation of the Shell and Utilities portion of the IEEE POSIX specification.
Cold TTFT: 6.537 seconds
Querying vLLM server with warm LMCache
Bash is a Unix shell and command-line interpreter that eead from the standard
input or from a file, incorporatinhe Korn and C shells. It is intended to be a
conformant tation of the IEEE POSIX specification and can be configured to be
POSIX-conformant by default, with options for setting the shell's behavior and
interacting with the user.
Warm TTFT: 0.147 seconds
TTFT Improvement: 6.390 seconds (44.5x faster)
If you look at the logs of your vLLM server, you should see (the logs are truncated for cleanliness):
.. code-block:: text
# Cold LMCache Miss and then Store
LMCache INFO: Reqid: chatcmpl-8676f9b9ebf04c79a5d47b9ada7b65fd, Total tokens 15410,
LMCache hit tokens: 0, need to load: 0
# you should see 8 of these storing logs total
# 2048 tokens is a multiple of the chunk size
LMCache INFO: Storing KV cache for 2048 out of 12288 tokens for request
chatcmpl-8676f9b9ebf04c79a5d47b9ada7b65fd
LMCache INFO: Storing KV cache for 2048 out of 14336 tokens for request
chatcmpl-8676f9b9ebf04c79a5d47b9ada7b65fd
LMCache INFO: Storing KV cache for 1074 out of 15410 tokens for request
chatcmpl-8676f9b9ebf04c79a5d47b9ada7b65fd
# Warm LMCache Hit!!
LMCache INFO: Reqid: chatcmpl-136d9dac1ba94bd4b4ae85007e8ad437, Total tokens 15410,
LMCache hit tokens: 15409, need to load: 1
.. _cpu_ram-tips:
Tips:
-----
- If you want to run the ``query-twice.py`` script multiple times, you'll need to either restart the vLLM LMCache server or change the prefix of the context you pass in since you've already warmed LMCache.
- The max model length here was decided by running an L4 with only 23GB of GPU memory. If you have more memory, you can increase the max model length and modify ``query-twice.py`` to use more of the long context. LMCache TTFT improvement becomes more pronounced as the context length increases!
@@ -0,0 +1,6 @@
Custom Storage Backends
=======================
LMCache supports integrating custom storage backends through dynamic loading or plug and play capability. This allows extending cache storage capabilities without modifying core code.
See :doc:`Storage Plugins <../../developer_guide/extending_lmcache/storage_plugins>` for more details.
@@ -0,0 +1,254 @@
# SPDX-License-Identifier: Apache-2.0
Device-DAX (/dev/dax)
=====================
Overview
--------
The DAX storage plugin maps a ``/dev/dax`` device using ``mmap(MAP_SHARED)``
and uses the mapped region as a fixed-size arena for KV cache chunks.
Typical ``/dev/dax`` devices include persistent memory,
CXL-attached memory, and other byte-addressable memory devices.
Data stored on the DAX device may survive process restarts,
but is not guaranteed to be durable.
KV cache data is stored in the DAX region as part of the backend's storage flow.
Reads copy data back into CPU-backed memory objects.
Configuration
-------------
.. code-block:: yaml
local_cpu: true
max_local_cpu_size: 80
storage_plugins: ["dax"]
extra_config:
storage_plugin.dax.module_path: lmcache.v1.storage_backend.plugins.dax_backend
storage_plugin.dax.class_name: DaxBackend
dax.device_path: "/dev/dax1.0"
dax.max_dax_size: 100
dax.restore_workers: 8
dax.restore_max_regions: 8
dax.retrieve_staging_slab_bytes: 268435456
Multiprocess Mode
-----------------
In LMCache multiprocess mode, Device-DAX is configured as a built-in L2
adapter named ``dax``. The MP adapter uses the normal L2 adapter
``submit -> event fd -> query`` contract; no vLLM connector protocol changes
are required.
.. code-block:: bash
lmcache server \
--l1-size-gb 80 \
--eviction-policy LRU \
--l2-adapter '{
"type": "dax",
"device_path": "/dev/dax1.0",
"max_dax_size_gb": 100,
"slot_bytes": 268435456,
"num_store_workers": 1,
"num_lookup_workers": 1,
"num_load_workers": 4
}'
The legacy single-device ``--l2-adapter`` JSON accepts these fields:
- ``device_path``: required path to a readable and writable DAX device.
- ``max_dax_size_gb``: required mapped size in GiB. The value must fit within
the device capacity when capacity can be determined with ``fstat``.
- ``slot_bytes``: required fixed slot size in bytes. It must be large enough
for one full LMCache chunk.
- ``num_store_workers``: optional store worker count, default ``1``.
- ``num_lookup_workers``: optional lookup worker count, default ``1``.
- ``num_load_workers``: optional load worker count, default
``min(4, os.cpu_count())``.
- ``persist_enabled``: accepted by common MP L2 parsing but ignored by
``dax`` in this release.
Runtime hotplug uses the multi-device form. The ``devices`` list may also be
empty when ``hotplug_enabled`` is ``true``.
.. code-block:: bash
lmcache server \
--l1-size-gb 80 \
--eviction-policy LRU \
--l2-adapter '{
"type": "dax",
"devices": [
{"device_path": "/dev/daxX.X", "max_dax_size_gb": 100},
{"device_path": "/dev/daxY.Y", "max_dax_size_gb": 100}
],
"slot_bytes": 268435456,
"hotplug_enabled": true,
"num_store_workers": 1,
"num_lookup_workers": 1,
"num_load_workers": 4
}'
MP DAX stores opaque ``ObjectKey`` values in memory and is volatile-only in
this release. Closing and reopening the server on the same DAX path starts
with an empty index, so previously written bytes are not discoverable after
restart.
MP DAX uses one stable adapter facade per LMCache server. The facade owns
stable event fds and worker pools, and runtime add/remove/resize only changes
the mapped DAX cores behind that facade. It does not add kernel-level CXL or
DAX reconfiguration, per-TP DAX partitions, on-device metadata, or restart
recovery. Capacity accounting and eviction are slot-based: a stored object
occupies one slot even if its payload is smaller than ``slot_bytes``.
Runtime Hotplug API
-------------------
Runtime hotplug is disabled unless ``hotplug_enabled`` is ``true``. The API
changes only LMCache runtime mappings and metadata; the ``/dev/dax*`` device
must already exist and be readable and writable by the LMCache server process.
The runtime endpoints are implemented through StorageManager's generic L2
adapter reconfiguration interface, which routes backend, operation name, and
adapter-specific payload to the selected adapter. DAX owns the path, mode,
migration, and resize semantics; the generic interface is reusable by other
adapters such as P2P.
Use JSON bodies because DAX paths contain slashes:
.. code-block:: bash
curl http://127.0.0.1:9000/reconfigure/dax/status
curl -X POST http://127.0.0.1:9000/reconfigure/dax/add \
-H 'Content-Type: application/json' \
-d '{"device_path": "/dev/daxX.X", "size": "100GiB"}'
curl -X POST http://127.0.0.1:9000/reconfigure/dax/remove \
-H 'Content-Type: application/json' \
-d '{"device_path": "/dev/daxX.X", "mode": "migrate"}'
curl -X POST http://127.0.0.1:9000/reconfigure/dax/resize \
-H 'Content-Type: application/json' \
-d '{"device_path": "/dev/daxX.X", "size": "200GiB"}'
``size`` is required for add and resize. Use an integer byte count or a string
such as ``"100GiB"``. ``remove`` supports these modes:
- ``migrate``: move DAX-resident KV to other active DAX devices before closing
the source device.
- ``evict``: delete DAX-resident KV on the source device. This is destructive
for the DAX tier.
- ``drain``: stop new writes to the source device and leave existing KV
readable until it is evicted or the server closes.
``resize`` supports ``migrate`` and ``evict`` modes. It does not support
``drain`` because resize completes synchronously.
Hotplug operations are lock-safe by default. A remove or shrink that would
delete externally locked or borrowed slots returns ``409 Conflict`` unless
``force`` is set. A migration that has no active destination capacity returns
``507 Insufficient Storage``. Resize grow preserves the in-memory key index and
does not move KV payloads. Resize shrink never silently drops keys; entries
outside the new slot range must migrate first, or the request fails.
Hardware Validation Flow
------------------------
Use the same Qwen 8B or 14B long-context workload before and after a runtime
capacity change. Without hotplug support, ``/reconfigure/dax/status`` and
``/reconfigure/dax/add`` are not available; changing the DAX device set
requires restarting LMCache with a new ``--l2-adapter`` value, which drops the
volatile DAX key index.
.. code-block:: bash
export MODEL=Qwen/Qwen3-8B # or a local Qwen 8B/14B checkpoint
curl http://127.0.0.1:9000/reconfigure/dax/status
python benchmarks/long_doc_qa/long_doc_qa.py \
--model "$MODEL" --num-documents 1 --document-length 1024 \
--output-len 16 --repeat-count 2 --repeat-mode tile \
--completions --host 127.0.0.1 --port 8000 --json-output
curl -X POST http://127.0.0.1:9000/reconfigure/dax/add \
-H 'Content-Type: application/json' \
-d '{"device_path": "/dev/daxX.X", "size": "100GiB"}'
curl http://127.0.0.1:9000/reconfigure/dax/status
Record these fields for the comparison:
- ``total_capacity_bytes`` before and after ``/reconfigure/dax/add``.
- ``total_used_bytes`` while the Qwen workload is running.
- Whether an LMCache restart was required.
- Whether the same cached prompt remains retrievable after the capacity change.
Using The Batched Restore Path
------------------------------
The current DAX optimization is a staged batched restore path for retrieval.
It is enabled automatically whenever the DAX backend is configured. No extra
feature flag is required.
The retrieve flow is:
1. Reserve a batched set of readable DAX chunks.
2. Allocate CPU restore buffers from ``LocalCPUBackend``.
3. Copy DAX data into a backend-owned pinned staging slab in coalesced regions.
4. Copy from the staging slab into the final CPU ``MemoryObj`` outputs.
5. Upload those CPU outputs through the normal GPU connector path.
The store flow is unchanged: KV data is still staged through CPU memory before
being written into the DAX arena.
The new DAX tuning knobs control the batched restore path:
- ``dax.restore_workers``: number of persistent worker threads used to execute
restore regions in parallel.
- ``dax.restore_max_regions``: maximum number of restore regions in one wave.
Larger values increase parallelism but also increase slab space requirements.
- ``dax.retrieve_staging_slab_bytes``: total size in bytes of the reusable
pinned retrieve slab. This must be large enough to hold one full chunk per
configured restore region.
For a first pass, start with:
- ``dax.restore_workers`` equal to the number of CPU workers you want devoted
to DAX restores
- ``dax.restore_max_regions`` equal to ``dax.restore_workers``
- ``dax.retrieve_staging_slab_bytes`` at least
``dax.restore_max_regions * full_chunk_size``, then scale upward if larger
batched restores are common
If retrieve throughput is low, increase the slab size first, then increase
worker and region counts together. If CPU pressure is high, reduce
``dax.restore_workers`` and ``dax.restore_max_regions``.
Runtime Requirements
--------------------
- ``extra_config['dax.device_path']`` is required and must point to a readable
and writable DAX device.
- The process must have read-write access to the DAX device
(e.g., via appropriate permissions or group membership).
- ``LocalCPUBackend`` must be enabled because DAX reads return CPU-backed
memory objects.
Validation and Current Limits
-----------------------------
- Tensor parallelism is currently limited to TP=1
(``metadata.world_size == 1``).
- Only single-tensor chunk layouts are supported. Multi-tensor put
requests are rejected.
- Batched restore uses a backend-owned retrieve staging slab and persistent
restore executors. The slab and region count can be tuned with
``dax.restore_workers``, ``dax.restore_max_regions``, and
``dax.retrieve_staging_slab_bytes``.
- Blocking batched restore preserves positional output semantics, while
asynchronous batched restore returns only the consecutive hit prefix.
@@ -0,0 +1,45 @@
EIC
===
.. warning::
This page documents the behavior of LMCache's in-process mode (deprecated). Please consider using :doc:`LMCache MP mode </mp/index>` for better feature support and performance. For the MP mode equivalent of this page, see :doc:`/mp/l2_storage/index`.
EIC(Elastic Instant Cache) is a distributed database designed for LLM KV Cache. It supports RDMA, GDR and has the capabilities of distributed disaster tolerance and expansion.
You can understand the principles and architecture of EIC through these articles:
* https://mp.weixin.qq.com/s/tasDqXf0Gxr3o_WCJ2IJUQ
* https://mp.weixin.qq.com/s/b_4YhTa96Zeklh23lv8qBw
Deploy EIC
----------
You can visit the official link https://console.volcengine.com/eic and deploy EIC KVCache on your compute cluster with web UI. In addition, we provide particular image in volcano engine, which integrates various optimizations based on the official image.
You may use tests/v1/storage_backend/test_eic.py to detect the connectivity of EIC.
Deploy Model With EIC
---------------------
You can enable EIC KVCache offload with the official interface, such as
.. code-block:: bash
export LMCACHE_CONFIG_FILE=/workspace/config/remote-eic.yaml
export VLLM_USE_V1=1
python3 -m vllm.entrypoints.openai.api_server \
... \
--kv-transfer-config '{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}'
Example ``config.yaml``:
.. code-block:: yaml
chunk_size: 256
remote_url: "eic://your-eic-endpoint"
eic_instance_id: "your-eic-instance-id"
eic_flag_file: "your-eic-config-path"
For more details, you can see https://www.volcengine.com/docs/85848/1749188.
@@ -0,0 +1,194 @@
Filesystem Backend
==================
.. warning::
This page documents the behavior of LMCache's in-process mode (deprecated). Please consider using :doc:`LMCache MP mode </mp/index>` for better feature support and performance. For the MP mode equivalent of this page, see :doc:`/mp/l2_storage/fs`.
The filesystem backend uses ``FSConnector`` to store LMCache remote chunks as
files under one or more POSIX filesystem directories. It is useful when you want
a simple persistent remote backend, or when multiple inference workers can see
the same mounted directory through local disk, NFS, a parallel filesystem, or a
container volume.
This backend is different from :doc:`local_storage`. Local disk offloading is a
per-process local tier configured through ``local_disk``. ``FSConnector`` is a
remote backend configured through ``remote_storage_plugins`` or the legacy
``remote_url`` field, so it participates in the same remote backend path as
Redis, S3, Mooncake, and other remote connectors.
When to use it
--------------
Use ``FSConnector`` when:
* You need a lightweight persistent remote backend for development, examples,
or benchmark runs.
* Multiple LMCache or vLLM processes share a mounted cache directory.
* Your storage is already exposed as a filesystem and does not need a separate
object-store or key-value service.
* You want to test remote-backend behavior before moving to a production
backend such as Redis, Valkey, S3, Mooncake, or InfiniStore.
Avoid using it when:
* The filesystem is not shared by every process that must read the cache.
* You need object-store semantics, cross-region persistence, or service-level
access control.
* The storage path is on a slow network filesystem and sits on the hot request
path.
Recommended configuration
-------------------------
The recommended form is the built-in remote storage plugin configuration. The
plugin name ``fs`` selects ``FSConnector`` and the base path is configured in
``extra_config``.
.. code-block:: yaml
chunk_size: 256
local_cpu: false
max_local_cpu_size: 1
save_unfull_chunk: false
remote_serde: "naive"
blocking_timeout_secs: 10
remote_storage_plugins: ["fs"]
extra_config:
remote_storage_plugin.fs.base_path: "/tmp/lmcache-fs"
save_chunk_meta: false
``FSConnector`` creates the base directory if it does not already exist. Each
cache chunk is written as a ``.data`` file whose name is derived from the
LMCache cache key.
Multiple filesystem instances
-----------------------------
You can configure multiple named ``fs`` instances by appending an instance name
after the connector type. The part before the first dot is still the connector
type; the full plugin name becomes the ``extra_config`` prefix.
.. code-block:: yaml
remote_storage_plugins: ["fs.primary", "fs.backup"]
extra_config:
remote_storage_plugin.fs.primary.base_path: "/mnt/cache-primary/lmcache"
remote_storage_plugin.fs.backup.base_path: "/mnt/cache-backup/lmcache"
save_chunk_meta: false
This is useful when a deployment wants separate filesystem-backed remote stores
for different cache policies, traffic classes, or experiments.
Multiple base paths
-------------------
``remote_storage_plugin.<name>.base_path`` may contain a comma-separated list of
directories. The connector chooses a directory by hashing the cache chunk key,
which spreads files across the configured paths.
.. code-block:: yaml
remote_storage_plugins: ["fs"]
extra_config:
remote_storage_plugin.fs.base_path: "/mnt/nvme0/lmcache,/mnt/nvme1/lmcache"
save_chunk_meta: false
Use multiple paths when each path maps to an independent storage device or mount
point. For best results, keep every path visible to the LMCache processes that
need to retrieve the same chunks.
Legacy ``remote_url`` configuration
-----------------------------------
The legacy ``remote_url`` form is still supported. The host and port are parsed
for compatibility with other remote URL formats; ``FSConnector`` uses the path.
.. code-block:: yaml
chunk_size: 256
local_cpu: false
max_local_cpu_size: 1
save_unfull_chunk: false
remote_url: "fs://localhost:0/tmp/lmcache-fs"
remote_serde: "naive"
blocking_timeout_secs: 10
extra_config:
save_chunk_meta: false
Prefer ``remote_storage_plugins`` for new deployments because it also supports
named instances and keeps connector-specific settings grouped by plugin name.
Optional settings
-----------------
The connector reads the following optional settings from ``extra_config``.
``fs_connector_relative_tmp_dir``
Relative directory used for temporary files before an atomic rename into the
final chunk path. The value must be relative, not absolute. When omitted,
temporary files are created next to the final file with a ``.tmp`` suffix.
``fs_connector_read_ahead_size``
Number of bytes to read first when loading a chunk. If the read fills that
window, the connector reads the remaining bytes. This can trigger filesystem
readahead on filesystems that support it.
``fs_connector_use_odirect``
Enables ``O_DIRECT`` for aligned reads and writes on platforms that expose
it. The connector falls back to normal I/O when a chunk size is not aligned
to the filesystem block size. ``O_DIRECT`` is disabled automatically when
``save_chunk_meta`` is enabled because the metadata prefix is not block
aligned.
``fs_base_path``
Compatibility fallback for plugin mode. Prefer
``remote_storage_plugin.<name>.base_path`` so the setting remains scoped to a
specific plugin instance.
Example with optional settings:
.. code-block:: yaml
remote_storage_plugins: ["fs"]
extra_config:
remote_storage_plugin.fs.base_path: "/data/lmcache-fs"
fs_connector_relative_tmp_dir: ".tmp"
fs_connector_read_ahead_size: 1048576
fs_connector_use_odirect: true
save_chunk_meta: false
Operational notes
-----------------
* Ensure the LMCache process has permission to create directories and write
files under every configured base path.
* Put the path on durable storage if cache reuse must survive process restarts.
Temporary directories such as ``/tmp`` are convenient for tests but may be
cleaned by the operating system.
* Use the same mounted path for every process that should share cache chunks.
If one process writes to a private container path, other processes will miss
those chunks even if they use the same configuration text.
* Leave ``save_chunk_meta`` enabled when workers may infer different metadata
for the same chunk. Disable it only when you need the lower overhead path and
the workers share compatible cache metadata.
* For MP mode L2 storage, see :doc:`../../mp/l2_storage/index`, which documents the
``fs`` and ``fs_native`` L2 adapters configured through ``--l2-adapter``.
Minimal vLLM usage
------------------
After writing ``fs.yaml`` with one of the configurations above, start vLLM with
LMCache enabled:
.. code-block:: bash
LMCACHE_CONFIG_FILE=fs.yaml vllm serve meta-llama/Llama-3.1-8B-Instruct \
--kv-transfer-config '{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}' \
--disable-log-requests
Then send the same long-prefix request twice. The first request stores chunks in
the filesystem backend. The second request should report LMCache hit tokens and
load matching chunks from the configured filesystem path.
@@ -0,0 +1,275 @@
GDS Backend
==================
.. warning::
This page documents the behavior of LMCache's in-process mode (deprecated). Please consider using :doc:`LMCache MP mode </mp/index>` for better feature support and performance. For the MP mode equivalent of this page, see :doc:`/mp/l2_storage/index`.
.. _gds-overview:
Overview
--------
This backend will work with any file system, whether local, remote, and remote
with GDS-based optimizations. Remote file systems allow for multiple LMCache
instances to share data seamlessly. The GDS (GPU-Direct Storage) optimizations
are used for zero-copy I/O from GPU memory to storage systems. Supports both
NVIDIA cuFile and AMD hipFile for GPU-direct storage.
Ways to configure LMCache GDS Backend
-----------------------------------------
**1. Environment Variables:**
.. code-block:: bash
# 256 Tokens per KV Chunk
export LMCACHE_CHUNK_SIZE=256
# Path to store files
export LMCACHE_GDS_PATH="/mnt/gds/cache"
# GDS Buffer Size in MiB
export LMCACHE_GDS_BUFFER_SIZE="8192"
# Disabling CPU RAM offload is sometimes recommended as the
# CPU can get in the way of GPUDirect operations
export LMCACHE_LOCAL_CPU=False
**2. Configuration File**:
Passed in through ``LMCACHE_CONFIG_FILE=your-lmcache-config.yaml``
Example ``config.yaml``:
.. code-block:: yaml
# 256 Tokens per KV Chunk
chunk_size: 256
# Disable local CPU
local_cpu: false
# Path to file system, local, remote or GDS-enabled mount
gds_path: "/mnt/gds/cache"
# GDS Buffer Size in MiB
gds_buffer_size: 8192
Multi-Path (Multi-Device) Support
---------------------------------
When a system has multiple NVMe drives, you can distribute GDS I/O across them
by specifying a comma-separated list of paths in ``gds_path``. The
``gds_path_sharding`` option controls how each GPU worker selects its path.
Currently only ``"by_gpu"`` is supported (the default), which selects a path
based on the device index (``device_id % num_paths``), so traffic is spread
evenly across the drives without any manual pinning.
**Why this helps:** a single PCIe Gen 4 x4 NVMe tops out at ~7 GB/s. With four
drives the aggregate bandwidth can reach ~28 GB/s, matching what multi-GPU
systems need for KV cache eviction and prefetch.
**Environment variables:**
.. code-block:: bash
export LMCACHE_GDS_PATH="/mnt/nvme0/cache,/mnt/nvme1/cache,/mnt/nvme2/cache,/mnt/nvme3/cache"
export LMCACHE_GDS_PATH_SHARDING="by_gpu"
**YAML config:**
.. code-block:: yaml
gds_path: "/mnt/nvme0/cache,/mnt/nvme1/cache,/mnt/nvme2/cache,/mnt/nvme3/cache"
gds_path_sharding: "by_gpu"
With the above configuration on a 4-GPU node:
- ``cuda:0`` writes to ``/mnt/nvme0/cache``
- ``cuda:1`` writes to ``/mnt/nvme1/cache``
- ``cuda:2`` writes to ``/mnt/nvme2/cache``
- ``cuda:3`` writes to ``/mnt/nvme3/cache``
If there are more GPUs than paths, the assignment wraps around (e.g. ``cuda:4``
maps back to ``/mnt/nvme0/cache``). A single path (no commas) works exactly as
before.
All directories are created automatically at startup. Every path in the list
must reside on a filesystem that the rest of the GDS configuration expects
(e.g., all paths on GDS-capable mounts when using cuFile).
**Read behavior:** on startup the backend scans **all** configured paths for
previously-stored KV cache entries, regardless of GPU affinity. This means a
``cuda:0`` worker whose write affinity is ``/mnt/nvme0/cache`` will still
discover entries that were written to ``/mnt/nvme1/cache`` by ``cuda:1`` in a
prior run. Writes, however, always go to the single affinity-selected path.
.. code-block:: text
Startup scan (read): iterate ALL gds_paths → populate hot_cache
Runtime writes: only the affinity path (device_id % num_paths)
Runtime reads: look up hot_cache first; on miss, check ALL
gds_paths on disk → load from whichever path
the entry lives on
GDS Buffer Size Explanation
---------------------------
The backend currently pre-registers buffer space to speed up GDS operations. This buffer space
is registered in VRAM so options like ``--gpu-memory-utilization`` from ``vllm`` should be considered
when setting it. For example, a good rule of thumb for H100 which generally has 80GiBs of VRAM would
be to start with 8GiB and set ``--gpu-memory-utilization 0.85`` and depending on your workflow fine-tune
it from there.
Using AMD hipFile
-----------------
.. note::
hipFile is alpha software and has been tested on limited hardware.
For full installation details, see the
`hipFile install guide <https://github.com/ROCm/rocm-systems/blob/develop/projects/hipfile/INSTALL.md>`__.
**Prerequisites:**
- **ROCm >= 7.2** with ``amdgpu-dkms >= 30.20.1``
(see the `ROCm quick start installation guide <https://rocm.docs.amd.com/projects/install-on-linux/en/latest/install/quick-start.html>`__)
- **Supported storage:** local NVMe drives only
- **Supported filesystems:** ext4 (mounted with ``data=ordered``) and xfs
- **Kernel:** ``CONFIG_PCI_P2PDMA`` must be enabled
**Quick install (Ubuntu 24.04):**
.. code-block:: bash
sudo apt install libmount-dev wget
# Install nightly hipFile packages
wget https://github.com/ROCm/hipFile/releases/download/nightly/hipfile_0.2.0.70200-nightly.9999.24.04_amd64.deb
wget https://github.com/ROCm/hipFile/releases/download/nightly/hipfile-dev_0.2.0.70200-nightly.9999.24.04_amd64.deb
sudo dpkg -i hipfile-dev_0.2.0.70200-nightly.9999.24.04_amd64.deb hipfile_0.2.0.70200-nightly.9999.24.04_amd64.deb
You can verify that the HIP libraries and kernel support AIS (AMD Infinity Storage) by running:
.. code-block:: bash
/opt/rocm/bin/ais-check
Successful output will show ``True`` for ``Kernel P2PDMA support``, ``HIP runtime``, and ``amdgpu``.
**LMCache configuration:**
To use AMD hipFile instead of NVIDIA cuFile, set the GDS backend:
**Environment Variables:**
.. code-block:: bash
export LMCACHE_GDS_BACKEND=hipfile
**Configuration File:**
.. code-block:: yaml
gds_backend: "hipfile"
Note: The ``gds_buffer_size`` configuration is used for both cuFile and hipFile buffers.
Setup Example
-------------
.. _gds-prerequisites:
**Prerequisites:**
- A Machine with at least one GPU. You can adjust the max model length of your vllm instance depending on your GPU memory.
- A mounted file system. A file system supportings GDS will work best.
- vllm and lmcache installed (:doc:`Installation Guide <../../getting_started/installation>`)
- Hugging Face access to ``meta-llama/Llama-3.1-8B-Instruct``
.. code-block:: bash
export HF_TOKEN=your_hugging_face_token
**Step 1. Create cache directory under your file system mount:**
To find all the types of file systems supporting GDS in your system, use `gdscheck` from NVIDIA:
.. code-block:: bash
sudo /usr/local/cuda-*/gds/tools/gdscheck -p
Check with your storage vendor on how to mount the remote file system.
(For example, if you want to use a GDS-enabled NFS driver, try the modified [NFS
stack](https://vastnfs.vastdata.com/), which is an open source driver that
works with any standard [NFS
RDMA](https://datatracker.ietf.org/doc/html/rfc5532) server. More
vendor-specific instructions will be added here in the future).
Create a directory under the file systew mount (the name here is arbitrary):
.. code-block:: bash
mkdir /mnt/gds/cache
**Step 2. Start a vLLM server with file backend enabled:**
Create a an lmcache configuration file called: ``gds-backend.yaml``
.. code-block:: yaml
local_cpu: false
chunk_size: 256
gds_path: "/mnt/gds/cache"
gds_buffer_size: 8192
If you don't want to use a config file, uncomment the first three environment variables
and then comment out the ``LMCACHE_CONFIG_FILE`` below:
.. code-block:: bash
# LMCACHE_LOCAL_CPU=False \
# LMCACHE_CHUNK_SIZE=256 \
# LMCACHE_GDS_PATH="/mnt/gds/cache" \
# LMCACHE_GDS_BUFFER_SIZE=8192 \
LMCACHE_CONFIG_FILE="gds-backend.yaml" \
vllm serve \
meta-llama/Llama-3.1-8B-Instruct \
--max-model-len 65536 \
--kv-transfer-config \
'{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}'
POSIX fallback
--------------
In some cases, libcufile implements its own internal POSIX fallback without `GdsBackend` being aware.
In others, an error such as `RuntimeError: cuFileHandleRegister failed (cuFile err=5030, cuda_err=0)` may be throwned.
Thus, backend can be configured to fallback to its own POSIX implementation when the usage of the GDS APIs is not successful.
To force `GdsBackend` not use GDS APIs for any reason, you can override its behavior via configuration:
.. code-block:: yaml
use_gds: false
Or via environment variable:
.. code-block:: bash
LMCACHE_USE_GDS=False
The ``gds_backend`` field (default: ``cufile``) selects which GDS library to use. Supported
backends are ``cufile`` (NVIDIA cuFile) and ``hipfile`` (AMD hipFile):
.. code-block:: yaml
use_gds: true
gds_backend: "cufile" # or "hipfile"
Note that under this mode it would still use CUDA APIs to map and do operations the pre-registered GPU memory.
@@ -0,0 +1,183 @@
Hugging Face Buckets Backend
============================
The Hugging Face Buckets backend stores LMCache chunks in a Hugging Face Bucket
using LMCache's built-in remote storage plugin framework. This is a persistent
remote backend that fits warm and cold KV cache persistence better than the
hottest local tiers.
When to use it
--------------
Use the HFBucket backend when you want:
* A Hub-native persistent store for KV cache data.
* A remote backend that can be configured through ``remote_storage_plugins``.
* Multiple named bucket instances in one LMCache deployment.
Avoid using it as the primary hot path for the lowest-latency cache lookups.
Local CPU, local disk, and other lower-latency backends are a better fit for
the hottest cache tier.
Requirements and limitations
----------------------------
* LMCache uses ``huggingface_hub`` bucket APIs for uploads, downloads, listing,
and deletes.
* The first built-in release is intentionally conservative:
* Only full chunks are supported.
* Partial chunk uploads are rejected.
* Downloads are rejected when the stored object size does not match the
expected full LMCache chunk size.
* Chunk metadata is not stored in the bucket objects.
Minimal configuration
---------------------
.. code-block:: yaml
chunk_size: 256
local_cpu: false
save_unfull_chunk: false
remote_serde: "naive"
blocking_timeout_secs: 10
remote_storage_plugins: ["hfbucket"]
extra_config:
remote_storage_plugin.hfbucket.bucket_handle: "hf://buckets/my-org/lmcache-kv/prod"
remote_storage_plugin.hfbucket.token_env: "HF_TOKEN"
remote_storage_plugin.hfbucket.create_bucket_if_missing: false
remote_storage_plugin.hfbucket.download_tmp_dir: "/tmp/lmcache-hfbucket"
remote_storage_plugin.hfbucket.metadata_cache_ttl_secs: 30
Multiple instances
------------------
Use instance-qualified plugin names to configure more than one bucket-backed
remote store in the same LMCache config.
.. code-block:: yaml
remote_storage_plugins: ["hfbucket.us", "hfbucket.eu"]
extra_config:
remote_storage_plugin.hfbucket.us.bucket_handle: "hf://buckets/my-org/lmcache-kv/us"
remote_storage_plugin.hfbucket.us.token_env: "HF_US_TOKEN"
remote_storage_plugin.hfbucket.eu.bucket_handle: "hf://buckets/my-org/lmcache-kv/eu"
remote_storage_plugin.hfbucket.eu.token_env: "HF_EU_TOKEN"
Configuration reference
-----------------------
All configuration keys live under
``extra_config.remote_storage_plugin.<plugin_name>.*`` where ``plugin_name`` is
either ``hfbucket`` or an instance-qualified name such as ``hfbucket.prod``.
* ``bucket_handle`` (required): Hugging Face Bucket handle in
``hf://buckets/<namespace>/<bucket>[/<prefix>]`` format.
* ``token_env`` (optional, default ``HF_TOKEN``): Environment variable used to
resolve the Hugging Face access token.
* ``token`` (optional): Direct token override. ``token_env`` takes precedence
when both are set.
* ``create_bucket_if_missing`` (optional, default ``false``): Lazily create the
bucket on the first write path.
* ``download_tmp_dir`` (optional): Root directory for connector-local download
scratch space. On Linux, pointing this at a tmpfs mount such as
``/dev/shm/lmcache-hfbucket`` avoids the disk write on the download path.
* ``metadata_cache_ttl_secs`` (optional, default ``30``): TTL for cached exact
existence and size metadata.
MP Mode Configuration
---------------------
In multi-process (MP) mode, Hugging Face Buckets are configured as an L2
adapter through a JSON spec passed to the LMCache server. This is separate from
the non-MP ``remote_storage_plugins`` configuration above. Each
``--l2-adapter`` argument takes a JSON object whose ``"type": "hfbucket"``
field selects the HFBucket adapter.
.. code-block:: json
{
"type": "hfbucket",
"bucket_handle": "hf://buckets/my-org/lmcache-kv/prod",
"token_env": "HF_TOKEN",
"create_bucket_if_missing": false,
"download_tmp_dir": "/tmp/lmcache-hfbucket-mp",
"metadata_cache_ttl_secs": 30,
"num_workers": 4,
"max_capacity_gb": 500,
"eviction": {
"eviction_policy": "LRU",
"trigger_watermark": 0.85,
"eviction_ratio": 0.2
}
}
HFBucket L2 Adapter Fields
~~~~~~~~~~~~~~~~~~~~~~~~~~
* **type** (required): must be ``"hfbucket"``.
* **bucket_handle** (required): Hugging Face Bucket handle in
``hf://buckets/<namespace>/<bucket>[/<prefix>]`` format.
* **token_env**: environment variable used to resolve the Hugging Face access
token (default ``"HF_TOKEN"``).
* **token**: optional direct token fallback. ``token_env`` takes precedence
when the environment variable is set. Prefer ``token_env`` for production
deployments so secrets do not live in adapter JSON.
* **create_bucket_if_missing**: lazily create the bucket on the first store
operation (default ``false``). This only helps when the bucket is missing and
the token has permission to create it; it does not fix invalid credentials,
invalid handles, or network failures.
* **download_tmp_dir**: root directory for temporary load downloads (default
``/tmp/lmcache-hfbucket-mp``). The MP adapter downloads bucket files into
per-task temporary files and then copies their bytes into the destination
``MemoryObj`` buffers supplied by the MP controller.
* **metadata_cache_ttl_secs**: TTL for cached exact path-size metadata (default
``30``). Set this lower when another process may modify the same bucket
prefix outside LMCache and fresher metadata is more important than reducing
Hugging Face metadata calls.
* **num_workers**: number of worker threads used for blocking Hugging Face Hub
bucket API calls (default ``4``). The HFBucket Python APIs are synchronous,
so MP mode runs upload, lookup, load, and delete work on a bounded thread
pool behind the adapter's eventfd-based completion interface.
* **max_capacity_gb**: capacity used by ``get_usage()`` for watermark-based L2
eviction. Set to ``0`` (default) to disable aggregate capacity tracking;
``get_usage()`` then reports the adapter as not providing an eviction signal.
* **eviction**: optional sub-dict enabling the L2 eviction controller for this
adapter. When present, keys that are currently being loaded are protected by
the lookup-and-lock path and skipped by ``delete()`` until they are unlocked.
Differences vs Non-MP HFBucket
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Hugging Face bucket operations are synchronous but the adapter makes submission
non-blocking by running the blocking calls on worker threads.
* MP loads do not allocate and return new memory. The MP controller provides
destination ``MemoryObj`` buffers, and the adapter copies downloaded bytes
into those buffers.
* Keys are identified by ``ObjectKey`` (``model_name`` + ``kv_rank`` +
``chunk_hash`` + optional ``cache_salt``) rather than ``CacheEngineKey``.
The serialized MP object name is
``<model>@<kv_rank_hex>@<chunk_hash_hex>[@<cache_salt>]`` and is then
encoded for the bucket path. This naming is not compatible with the non-MP
HFBucket connector's ``CacheEngineKey`` object names, so a bucket prefix
populated by non-MP LMCache cannot be read directly by MP LMCache and vice
versa.
* Full object writes are batch based. Hugging Face batch writes are not
transactional, so a failed store task may still leave some objects in the
bucket. The MP adapter reconciles backend metadata after such failures so
any objects that actually landed are counted for usage and later deletion
(submitted store task is still reported as failed).
Notes
-----
* The backend stores objects under the configured bucket prefix using a
reversible encoding of LMCache keys, so ``list()`` returns LMCache key strings
instead of raw bucket object paths.
@@ -0,0 +1,41 @@
Using Different Storage Backends
================================
.. warning::
This page documents the behavior of LMCache's in-process mode (deprecated). Please consider using :doc:`LMCache MP mode </mp/index>` for better feature support and performance. For the MP mode equivalent of this page, see :doc:`/mp/l2_storage/index`.
LMCache supports various storage backends to offload and share KV cache data.
Supported Backends
-------------------------
.. toctree::
:maxdepth: 1
azure
cpu_ram
custom_backend
dax
eic
fs
gds
hfbucket
infinistore
local_storage
maru
mock
mooncake
nixl
redis
bigtable
resp
s3
sagemaker_hyperpod
valkey
weka
3fs
@@ -0,0 +1,208 @@
InfiniStore
===========
.. warning::
This page documents the behavior of LMCache's in-process mode (deprecated). Please consider using :doc:`LMCache MP mode </mp/index>` for better feature support and performance. For the MP mode equivalent of this page, see :doc:`/mp/l2_storage/index`.
.. _infinistore-overview:
Overview
--------
`InfiniStore <https://github.com/bytedance/InfiniStore>`_ is an open-source high-performance KV store. It's designed to support LLM Inference clusters, whether the cluster is in prefill-decoding disaggregation mode or not. InfiniStore provides high-performance and low-latency KV cache transfer and KV cache reuse among inference nodes in the cluster.
There are two major scenarios how InfiniStore supports:
* Prefill-Decoding disaggregation clusters: in such mode inference workloads are separated into two node pools: prefill nodes and decoding nodes. InfiniStore enables KV cache transfer among these two types of nodes, and also KV cache reuse.
* Non-disaggregated clusters: in such mode prefill and decoding workloads are mixed on every node. InfiniStore serves as an extra large KV cache pool in addition to GPU cache and local CPU cache, and also enables cross-node KV cache reuse.
.. image:: ../../assets/InfiniStore-usage.png
:alt: InfiniStore Usage Diagram
For more details, please refer to the `InfiniStore Documentation <https://bytedance.github.io/InfiniStore/index.html>`_.
InfiniStore supports both RDMA and TCP for transport. LMCaches InfiniStore connector only uses the RDMA transport.
Quick Start
-----------
Install InfiniStore via pip:
.. code-block:: bash
pip install infinistore
This package includes the InfiniStore server and the Python bindings.
To build InfiniStore from source, follow the instructions in the `GitHub repository <https://github.com/bytedance/InfiniStore>`_.
Setup and Deployment
~~~~~~~~~~~~~~~~~~~~
**Prerequisites:**
- Machine with at least one GPU for vLLM inference
- RDMA-capable network hardware and drivers
- Python 3.8+ with pip
- vLLM and LMCache installed
**Step 1: Start InfiniStore Server**
For InfiniBand based RDMA:
.. code-block:: bash
infinistore --service-port 12345 --dev-name mlx5_0 --link-type IB
For RoCE based RDMA:
.. code-block:: bash
infinistore --service-port 12345 --dev-name mlx5_0 --link-type Ethernet
You can also specify the ``--hint-gid-index`` option to set the GID index for the InfiniStore server. This is useful when you are in a k8s managed environment.
**Step 2: Create Configuration File**
Create your ``infinistore-config.yaml``:
.. code-block:: yaml
chunk_size: 256
remote_url: "infinistore://127.0.0.1:12345/?device=mlx5_1"
remote_serde: "naive"
local_cpu: False
max_local_cpu_size: 5
**Step 3: Start vLLM with InfiniStore**
.. code-block:: bash
LMCACHE_CONFIG_FILE="infinistore-config.yaml" \
vllm serve \
Qwen/Qwen2.5-7B-Instruct \
--seed 42 \
--max-model-len 16384 \
--gpu-memory-utilization 0.8 \
--kv-transfer-config \
'{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}'
**Step 4: Verify the Setup**
Test the integration with a sample request:
.. code-block:: bash
curl -X POST "http://localhost:8000/v1/completions" \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen2.5-7B-Instruct",
"prompt": "The future of AI is",
"max_tokens": 100,
"temperature": 0.7
}'
**Debugging Tips:**
1. **Enable verbose logging:**
.. code-block:: bash
infinistore --log-level=debug
2. **Check server status:**
.. code-block:: bash
# Check if the server is running
ps aux | grep infinistore
netstat -tlnp | grep -E "12345"
Query TTFT Improvement
~~~~~~~~~~~~~~~~~~~~~~~
Once the OpenAI compatible server is running, let's query it twice and see the TTFT improvement.
Run vLLM's serving benchmark twice with the following parameters:
.. code-block:: bash
vllm bench serve \
--backend vllm \
--model Qwen/Qwen2.5-7B-Instruct \
--num-prompts 50 \
--port 8000 \
--host 127.0.0.1 \
--dataset-name random \
--random-input-len 8192 \
--random-output-len 128 \
--seed 42
**Example Output:**
For the first run, you might see:
.. code-block:: text
============ Serving Benchmark Result ============
Successful requests: 50
Benchmark duration (s): 80.97
Total input tokens: 409544
Total generated tokens: 6273
Request throughput (req/s): 0.62
Output token throughput (tok/s): 77.48
Total Token throughput (tok/s): 5135.74
---------------Time to First Token----------------
Mean TTFT (ms): 36203.54
Median TTFT (ms): 34598.91
P99 TTFT (ms): 76010.91
-----Time per Output Token (excl. 1st token)------
Mean TPOT (ms): 290.30
Median TPOT (ms): 346.25
P99 TPOT (ms): 412.24
---------------Inter-token Latency----------------
Mean ITL (ms): 290.30
Median ITL (ms): 386.78
P99 ITL (ms): 449.83
For the second run, you should see a significant reduction in TTFT:
.. code-block:: text
============ Serving Benchmark Result ============
Successful requests: 50
Benchmark duration (s): 15.14
Total input tokens: 409544
Total generated tokens: 6273
Request throughput (req/s): 3.30
Output token throughput (tok/s): 414.22
Total Token throughput (tok/s): 27457.55
---------------Time to First Token----------------
Mean TTFT (ms): 2880.53
Median TTFT (ms): 3118.50
P99 TTFT (ms): 12027.24
-----Time per Output Token (excl. 1st token)------
Mean TPOT (ms): 73.81
Median TPOT (ms): 71.12
P99 TPOT (ms): 91.24
---------------Inter-token Latency----------------
Mean ITL (ms): 73.81
Median ITL (ms): 63.86
P99 ITL (ms): 565.44
TTFT Improvement: 33.323 seconds (12.6x faster).
**Tips:**
- If you want to run vLLM's serving benchmark multiple times, you'll need to either restart the vLLM LMCache server and the InfiniStore server, or change the ``--seed`` parameter to a different value each time, since you've already warmed up LMCache.
- The benchmark result here was produced by running an L40 with 48GB of GPU memory with ``--gpu-memory-utilization 0.8``. You can adjust the GPU memory utilization and increase the max model length to use more of the long context. LMCache TTFT improvement becomes more pronounced as the context length increases!
Additional Resources
--------------------
- `InfiniStore Documentation <https://bytedance.github.io/InfiniStore/index.html>`_
- `GitHub Repository <https://github.com/bytedance/InfiniStore>`_
@@ -0,0 +1,464 @@
Local storage
=============
.. warning::
This page documents the behavior of LMCache's in-process mode (deprecated). Please consider using :doc:`LMCache MP mode </mp/index>` for better feature support and performance. For the MP mode equivalent of this page, see :doc:`/mp/l2_storage/index`.
.. _local-storage-overview:
Overview
--------
CPU RAM and Local Storage are the two ways of offloading KV cache onto non-GPU
memory of the same machine that is running inference.
Two ways to configure LMCache Disk Offloading:
----------------------------------------------
**1. Environment Variables:**
.. code-block:: bash
# 256 Tokens per KV Chunk
export LMCACHE_CHUNK_SIZE=256
# None if disabled
# Otherwise, enable by setting the directory where LMCache will
# create files for each KV cache chunks
# (this directory does NOT need to exist beforehand)
export LMCACHE_LOCAL_DISK="file://$HOME/local/disk_test/local_disk/"
# 5GB of Disk
export LMCACHE_MAX_LOCAL_DISK_SIZE=5.0
# Disable page cache
# This should be turned on for better performance if most local CPU memory is used
# Optionally tune the number of I/O worker threads (default: 4)
export LMCACHE_EXTRA_CONFIG='{"use_odirect": true, "disk_io_threads": 8}'
**2. Configuration File**:
Passed in through ``LMCACHE_CONFIG_FILE=your-lmcache-config.yaml``
.. code-block:: yaml
# 256 Tokens per KV Chunk
chunk_size: 256
# Enable Disk backend
local_disk: "file:///local/disk_test/local_disk/"
# 5GB of Disk memory
max_local_disk_size: 5.0
# Disable page cache
# This should be turned on for better performance if most local CPU memory is used
# Optionally tune the number of I/O worker threads (default: 4)
extra_config:
use_odirect: true
disk_io_threads: 8
Multi-Path (Multi-Device) Disk Offloading
-----------------------------------------
If you have **multiple NVMe devices** (or any independent mount points), you can
assign each GPU its own disk path so that each device writes to a dedicated drive.
Specify a **comma-separated list** of paths in ``local_disk``.
Each path can optionally use the ``file://`` prefix. The
``local_disk_path_sharding`` option controls how each GPU worker selects its
path. Currently only ``"by_gpu"`` is supported (the default), which selects a
path based on the device index (``device_id % num_paths``), so all KV cache
files from a given GPU land on the same NVMe. This is especially useful when
GPUs and NVMe devices share a PCIe switch or NUMA node.
For example, with two GPUs and two paths:
- ``cuda:0````/mnt/nvme0/kvcache/``
- ``cuda:1````/mnt/nvme1/kvcache/``
``max_local_disk_size`` is the **total budget** shared across all paths.
**Environment variable example:**
.. code-block:: bash
export LMCACHE_LOCAL_DISK="file:///mnt/nvme0/kvcache/,file:///mnt/nvme1/kvcache/"
export LMCACHE_LOCAL_DISK_PATH_SHARDING="by_gpu"
export LMCACHE_MAX_LOCAL_DISK_SIZE=20.0 # combined budget (GB)
**YAML example:**
.. code-block:: yaml
local_disk: "/mnt/nvme0/kvcache/,/mnt/nvme1/kvcache/"
local_disk_path_sharding: "by_gpu"
max_local_disk_size: 20.0
.. note::
Each GPU worker uses only its assigned path, so O_DIRECT alignment
is determined by that path's filesystem block size. Different
devices may have different block sizes without issue.
.. tip::
If you are able to use kernel-level RAID 0 (e.g. ``mdadm --level=0``)
you will get true block-level striping (even a single large file can
use bandwidth from both devices simultaneously). The multi-path
feature is most useful when you cannot or do not want to reconfigure
the block devices — for example, when they already have other data.
Local Storage Explanation:
--------------------------
Unlike CPU RAM offloading, disk offloading is *disabled* by default (``local_disk`` is set to ``None``) and the
max local disk size is set to 0GB instead of 5GB like the default max local cpu size
since the disk space is not strictly necessary for LMCache to function.
Furthermore, instead of greedily allocating the max space up front like the pinned CPU RAM, the disk backend will
create one file per KV cache chunk as they are stored, evicting if capacity is exceeded (LRU currently).
The disk and remote (see :doc:`Redis <./redis>`, :doc:`Mooncake <./mooncake>`, :doc:`Valkey <./valkey>`, :doc:`InfiniStore <./infinistore>`)
backends have asynchronous put() operations so that the IO latency will not slow down inference in addition to blocking get() operations.
The local disk backend also has a prefetch() operation that will preemptively move KV caches from the disk to CPU RAM offloading storage
(i.e. ``LMCACHE_LOCAL_CPU=True`` should be set, see :doc:`CPU RAM <./cpu_ram>`) for specified tokens (these KV caches are also still kept in the disk).
Architecture Overview
---------------------
The following diagram shows the overall architecture of the Local Disk Backend:
.. mermaid::
%%{init: {'theme': 'base', 'themeVariables': { 'fontSize': '18px', 'fontFamily': 'arial', 'primaryColor': '#e3f2fd', 'primaryTextColor': '#000', 'primaryBorderColor': '#1976d2', 'lineColor': '#424242', 'secondaryColor': '#f5f5f5', 'tertiaryColor': '#ffffff', 'background': '#ffffff', 'clusterBkg': '#f8f9fa', 'clusterBorder': '#495057' }}}%%
flowchart TB
subgraph Engine["<b>LMCache Engine</b>"]
E["<b>Request Save/Load Operations</b>"]
end
subgraph LDB["<b>LocalDiskBackend</b>"]
subgraph Meta["<b>Metadata Dictionary</b>"]
Dict["<b>self.dict: CacheEngineKey → DiskCacheMetadata</b>
(path, size, shape, dtype, pinned, positions)"]
end
subgraph Policy["<b>Cache Policy</b>"]
CP["<b>Configurable Policy</b>
(LRU, LFU, FIFO, MRU)
Decides what to evict"]
end
subgraph Worker["<b>LocalDiskWorker</b>"]
PQ["<b>Priority Queue Executor (configurable workers, default 4)</b>"]
P0["<b>Priority 0: PREFETCH</b>"]
P1["<b>Priority 1: DELETE</b>"]
P2["<b>Priority 2: PUT</b>"]
end
CPU["<b>LocalCPUBackend</b>
(memory allocator)"]
end
subgraph Disk["<b>Local Filesystem</b>"]
Files["/cache/vllm@model@...@abc.pt
/cache/vllm@model@...@def.pt
/cache/vllm@model@...@ghi.pt"]
end
E --> Dict
Dict --> CP
CP --> PQ
PQ --> P0
PQ --> P1
PQ --> P2
Worker --> Files
CPU -.-> Worker
**Key Components:**
- **Metadata Dictionary**: Maps each ``CacheEngineKey`` to its disk metadata (file path, size, shape, dtype, pin status)
- **Cache Policy**: Configurable eviction policy (LRU, LFU, FIFO, or MRU) that tracks access patterns and decides which entries to evict when space is needed
- **LocalDiskWorker**: Async task executor with priority queue - prefetch tasks run first (priority 0), then deletes (priority 1), then saves (priority 2). The number of I/O worker threads is configurable via ``extra_config.disk_io_threads`` (default: 4).
- **Local Disk**: Filesystem where KV cache chunks are stored as individual ``.pt`` files
Save Flow (PUT)
~~~~~~~~~~~~~~~
.. mermaid::
%%{init: {'theme': 'base', 'flowchart': {'useMaxWidth': false, 'htmlLabels': true, 'nodeSpacing': 30, 'rankSpacing': 30}, 'themeVariables': { 'fontSize': '18px', 'fontFamily': 'arial', 'primaryColor': '#e3f2fd', 'primaryTextColor': '#000', 'primaryBorderColor': '#1976d2', 'lineColor': '#424242', 'secondaryColor': '#f5f5f5', 'tertiaryColor': '#ffffff', 'background': '#ffffff', 'clusterBkg': '#f8f9fa', 'clusterBorder': '#495057' }}}%%
flowchart LR
A["<b>MemoryObj</b><br/>(KV cache in CPU memory)"] --> B{<b>Disk space<br/>available?</b>}
B -->|"No"| C["<b>Evict via policy</b><br/>Delete .pt files"]
C --> B
B -->|"Yes"| D["<b>Track in put_tasks</b><br/>Queue async write<br/>(Priority 2 - lowest)"]
D --> E["<b>LocalDiskWorker</b><br/>write_file()"]
E --> F[("<b>Disk</b><br/>.pt file")]
F --> G["<b>Add to metadata dict</b>"]
style A fill:#e1f5fe
style F fill:#c8e6c9
style C fill:#ffcdd2
Load Flow (GET)
~~~~~~~~~~~~~~~
.. mermaid::
%%{init: {'theme': 'base', 'flowchart': {'useMaxWidth': false, 'htmlLabels': true, 'nodeSpacing': 30, 'rankSpacing': 30}, 'themeVariables': { 'fontSize': '18px', 'fontFamily': 'arial', 'primaryColor': '#e3f2fd', 'primaryTextColor': '#000', 'primaryBorderColor': '#1976d2', 'lineColor': '#424242', 'secondaryColor': '#f5f5f5', 'tertiaryColor': '#ffffff', 'background': '#ffffff', 'clusterBkg': '#f8f9fa', 'clusterBorder': '#495057' }}}%%
flowchart LR
A["<b>Request</b><br/>(CacheEngineKey)"] --> B{<b>Key exists<br/>in dict?</b>}
B -->|"No"| C["<b>Return None</b><br/>(cache miss)"]
B -->|"Yes"| D["<b>Update policy</b><br/>Mark as accessed"]
D --> E["<b>Allocate buffer</b><br/>via LocalCPUBackend"]
E --> F["<b>Read from disk</b><br/>read_file()"]
F --> G[("<b>Disk</b><br/>.pt file")]
G --> F
F --> H["<b>MemoryObj</b><br/>(KV cache ready)"]
style A fill:#e1f5fe
style H fill:#c8e6c9
style C fill:#ffcdd2
.. _local-storage-online-inference-example:
Online Inference Example
------------------------
This example is almost identical to the :doc:`CPU RAM <./cpu_ram>` example.
Let's feel the TTFT (time to first token) differential!
.. _local-storage-prerequisites:
**Prerequisites:**
- A Machine with at least one GPU. Adjust the max model length of your vllm instance depending on your GPU memory and the long context you want to use.
- vllm and lmcache installed (:doc:`Installation Guide <../../getting_started/installation>`)
- Hugging Face access to ``meta-llama/Meta-Llama-3.1-8B-Instruct``
.. code-block:: bash
export HF_TOKEN=your_hugging_face_token
- A few packages:
.. code-block:: bash
pip install openai transformers
**Step 0. Set up a directory for this example:**
.. code-block:: bash
mkdir lmcache-local-disk-example
cd lmcache-local-disk-example
**Step 1. Prepare a long context!**
We want a context long enough that vllm's prefix caching will not be able to hold the KV caches in
GPU memory and LMCache is necessary to keep KV caches in non-GPU memory:
.. code-block:: bash
# 382757 bytes
man bash > man-bash.txt
**Step 2. Start a vLLM server with Disk offloading enabled:**
*Generally, it is not recommended but we will disable CPU offloading to feel just the disk offloading latency.*
Create a an lmcache configuration file called: ``disk-offload.yaml``
Example ``config.yaml``:
.. code-block:: yaml
chunk_size: 256
local_cpu: false
max_local_cpu_size: 5.0
local_disk: "file:///local/disk_test/local_disk/"
max_local_disk_size: 5.0
If you don't want to use a config file, uncomment the first five environment variables
and then comment out the ``LMCACHE_CONFIG_FILE`` below:
.. code-block:: bash
# LMCACHE_CHUNK_SIZE=256 \
# LMCACHE_LOCAL_CPU=False \
# LMCACHE_MAX_LOCAL_CPU_SIZE=5.0 \
# LMCACHE_LOCAL_DISK="file:///local/disk_test/local_disk/" \
# LMCACHE_MAX_LOCAL_DISK_SIZE=5.0 \
LMCACHE_CONFIG_FILE="disk-offload.yaml" \
vllm serve \
meta-llama/Llama-3.1-8B-Instruct \
--max-model-len 16384 \
--kv-transfer-config \
'{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}'
- ``--kv-transfer-config``: This is the parameter that actually tells vLLM to use LMCache for KV cache offloading.
- ``kv_connector``: Specifies the LMCache connector for vLLM V1
- ``kv_role``: Set to "kv_both" for both storing and loading KV cache (important because we will run two queries and the first will produce/store a KV cache while the second will consume/load that KV cache)
**Step 3. Query TTFT improvements with LMCache:**
Once the Open AI compatible server is running on default vllm port 8000, let's query it twice with the same long context!
Create a script called ``query-twice.py`` and paste the following code:
.. code-block:: python
import time
from openai import OpenAI
from transformers import AutoTokenizer
client = OpenAI(
api_key="dummy-key", # required by OpenAI client even for local servers
base_url="http://localhost:8000/v1"
)
models = client.models.list()
model = models.data[0].id
# 119512 characters total
# 26054 tokens total
long_context = ""
with open("man-bash.txt", "r") as f:
long_context = f.read()
# a truncation of the long context for the --max-model-len 16384
# if you increase the --max-model-len, you can decrease the truncation i.e.
# use more of the long context
long_context = long_context[:70000]
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Meta-Llama-3.1-8B-Instruct")
question = "Summarize bash in 2 sentences."
prompt = f"{long_context}\n\n{question}"
print(f"Number of tokens in prompt: {len(tokenizer.encode(prompt))}")
def query_and_measure_ttft():
start = time.perf_counter()
ttft = None
chat_completion = client.chat.completions.create(
messages=[{"role": "user", "content": prompt}],
model=model,
temperature=0.7,
stream=True,
)
for chunk in chat_completion:
chunk_message = chunk.choices[0].delta.content
if chunk_message is not None:
if ttft is None:
ttft = time.perf_counter()
print(chunk_message, end="", flush=True)
print("\n") # New line after streaming
return ttft - start
print("Querying vLLM server with cold LMCache Disk Offload")
cold_ttft = query_and_measure_ttft()
print(f"Cold TTFT: {cold_ttft:.3f} seconds")
print("\nQuerying vLLM server with warm LMCache Disk Offload")
warm_ttft = query_and_measure_ttft()
print(f"Warm TTFT: {warm_ttft:.3f} seconds")
print(f"\nTTFT Improvement: {(cold_ttft - warm_ttft):.3f} seconds \
({(cold_ttft/warm_ttft):.1f}x faster)")
Then run:
.. code-block:: bash
python query-twice.py
Since we're in streaming mode, you'll be able to feel the TTFT differential in
real time!
Note that if we were to enable ``LMCACHE_LOCAL_CPU=True``, we would just be using
the same example from :doc:`CPU RAM <./cpu_ram>` since the CPU RAM is checked before
the disk by LMCache. In practice, the disk will be capable of storing a larger
quantity of KV caches so the CPU RAM offloading will only be able to store a
subset of the disk's KV caches.
**Example Output:**
.. code-block:: text
Number of tokens in prompt: 15376
Querying vLLM server with cold LMCache Disk Offload
Bash is a Unix shell and command-line interpreter that reads and executes
commands from standard input or a file, incorporating features from the
Korn and C shells. It is a conformant implementation of the IEEE POSIX
specification and can be configure to be POSIX-conformant by default,
supporting a wide range of options, built-in commands,
and features for scripting, job control, and interactive use.
Cold TTFT: 6.314 seconds
Querying vLLM server with warm LMCache Disk Offload
Bash is a Unix shell and command-line interpreter that reads and
executes commands from the standard input or a file, and is designed
to be a conformant implementation of the IEEE POSIX specification. It
is a powerful tool for automating tasks, managing files and directories,
and interacting with other programs and services, with features such as
scripting, conditional statements, loops, and functions.
Warm TTFT: 0.148 seconds
TTFT Improvement: 6.166 seconds (42.6x faster)
If you look at the logs of your vLLM server, you should see (the logs are truncated for cleanliness):
.. code-block:: text
# Cold LMCache Miss and then Store
LMCache INFO: Reqid: chatcmpl-8676f9b9ebf04c79a5d47b9ada7b65fd, Total tokens 15410,
LMCache hit tokens: 0, need to load: 0
# you should see 8 of these storing logs total
# 2048 tokens is a multiple of the chunk size
LMCache INFO: Storing KV cache for 2048 out of 12288 tokens for request
chatcmpl-8676f9b9ebf04c79a5d47b9ada7b65fd
LMCache INFO: Storing KV cache for 2048 out of 14336 tokens for request
chatcmpl-8676f9b9ebf04c79a5d47b9ada7b65fd
LMCache INFO: Storing KV cache for 1074 out of 15410 tokens for request
chatcmpl-8676f9b9ebf04c79a5d47b9ada7b65fd
# Warm LMCache Hit!!
LMCache INFO: Reqid: chatcmpl-136d9dac1ba94bd4b4ae85007e8ad437, Total tokens 15410,
LMCache hit tokens: 15409, need to load: 1
Check out your KV Cache in your SSD:
.. code-block:: bash
ls "$HOME/local/disk_test/local_disk/"
.. _local-storage-tips:
Tips:
-----
- If you want to run the ``query-twice.py`` script multiple times, you'll need to either restart the vLLM LMCache server or change the prefix of the context you pass in since you've already warmed LMCache.
- The max model length here was decided by running an L4 with only 23GB of GPU memory. If you have more memory, you can increase the max model length and modify ``query-twice.py`` to use more of the long context. LMCache TTFT improvement becomes more pronounced as the context length increases!
@@ -0,0 +1,118 @@
Maru
====
.. warning::
This page documents the behavior of LMCache's in-process mode (deprecated). Please consider using :doc:`LMCache MP mode </mp/index>` for better feature support and performance. For the MP mode equivalent of this page, see :doc:`/mp/l2_storage/index`.
.. _maru-overview:
Overview
--------
`Maru <https://github.com/xcena-dev/maru>`_ is a high-performance KV cache storage engine built on CXL shared memory,
designed for LLM inference scenarios where multiple instances need to share a KV cache with minimal latency.
.. image:: ../../assets/maru-kvcache.png
:alt: KV Cache Sharing: Without vs With Maru
For architecture details, see the `Maru documentation <https://xcena-dev.github.io/maru/>`_.
Quick Start
-----------
Install Maru:
.. code-block:: bash
git clone https://github.com/xcena-dev/maru.git
cd maru
./install.sh
This installs ``maru-server``, ``maru-resourced``, and the ``maru`` Python package.
Deploy Model With Maru
~~~~~~~~~~~~~~~~~~~~~~
**Prerequisites:** CXL device (``/dev/dax*``), Python 3.12+, vLLM and LMCache installed.
**1. Start the Maru Server**
.. code-block:: bash
maru-server
**2. Create configuration file** (``maru-config.yaml``):
.. code-block:: yaml
chunk_size: 256
local_cpu: False
max_local_cpu_size: 0
save_unfull_chunk: True
# Maru backend
maru_path: "maru://localhost:5555"
maru_pool_size: 4
**3. Start vLLM with Maru**
.. code-block:: bash
LMCACHE_CONFIG_FILE="maru-config.yaml" \
vllm serve \
meta-llama/Llama-3.1-8B-Instruct \
--max-model-len 65536 \
--kv-transfer-config \
'{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}'
Configuration
-------------
**LMCache Parameters:**
.. list-table::
:header-rows: 1
:widths: 25 15 60
* - Parameter
- Default
- Description
* - ``maru_path``
- Required
- Maru server URL (format: ``maru://host:port``)
* - ``maru_pool_size``
- ``4.0``
- CXL memory pool size per instance in GB (e.g., ``4``, ``0.5``)
**Advanced Parameters (via extra_config):**
.. list-table::
:header-rows: 1
:widths: 25 15 60
* - Parameter
- Default
- Description
* - ``maru_instance_id``
- auto UUID
- Unique client instance identifier
* - ``maru_timeout_ms``
- 5000
- ZMQ RPC socket timeout in milliseconds
* - ``maru_use_async_rpc``
- true
- Async DEALER-ROUTER RPC (``false`` for synchronous REQ-REP)
* - ``maru_max_inflight``
- 64
- Max concurrent async RPC requests
* - ``maru_eager_map``
- true
- Pre-map all shared regions on connect
Additional Resources
--------------------
- `Maru GitHub Repository <https://github.com/xcena-dev/maru>`_
- `Maru Documentation <https://xcena-dev.github.io/maru/>`_
@@ -0,0 +1,70 @@
Mock
====
.. warning::
This page documents the behavior of LMCache's in-process mode (deprecated). Please consider using :doc:`LMCache MP mode </mp/index>` for better feature support and performance. For the MP mode equivalent of this page, see :doc:`/mp/l2_storage/index`.
LMCache provides a mock remote connector that allows you to manually set the peeking latency, read throughput, and write throughput inside of the remote url. It will create copies of your KV cache in unmanaged local RAM.
Configuration
-------------
Create a configuration file (e.g., ``mock.yaml``) with the following content:
.. code-block:: yaml
chunk_size: 256
local_cpu: False
max_local_cpu_size: 10
remote_url: "mock://100/?peeking_latency=1&read_throughput=2&write_throughput=2"
The ``remote_url`` format is ``mock://SIZE/?peeking_latency=LATENCY&read_throughput=READ_GBPS&write_throughput=WRITE_GBPS`` where:
- ``SIZE``: Maximum storage size
- ``peeking_latency``: Latency for peeking operations (in milliseconds)
- ``read_throughput``: Read throughput (in GB/s)
- ``write_throughput``: Write throughput (in GB/s)
Usage
-----
Deploy a serving engine with the mock remote backend:
.. code-block:: bash
LMCACHE_CONFIG_FILE=mock.yaml vllm serve meta-llama/Llama-3.1-8B-Instruct \
--kv-transfer-config '{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}' \
--disable-log-requests \
--no-enable-prefix-caching
Check the retrieval (storing is async so the throughput there is meaningless) logs on the second query to confirm that the throughput is slightly lower than 2 GB/s (the CPU <-> GPU allocation/transfer also has overhead).
Example Query
-------------
Send a test request:
.. code-block:: bash
curl -X POST http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{
"model": "meta-llama/Llama-3.1-8B-Instruct",
"prompt": "'"$(printf 'Elaborate the significance of KV cache in language models. %.0s' {1..1000})"'",
"max_tokens": 10
}'
Expected Logs
-------------
You should see logs similar to the following:
.. code-block:: text
(EngineCore_0 pid=586318) [2025-09-03 05:06:41,751] LMCache INFO: Reqid: cmpl-b34e7c5b2f3e46a592722db2c27f6fc0-0, Total tokens 12002, LMCache hit tokens: 12002, need to load: 12001 (vllm_v1_adapter.py:1049:lmcache.integration.vllm.vllm_v1_adapter)
(EngineCore_0 pid=586318) [2025-09-03 05:06:42,736] LMCache INFO: Retrieved 12002 out of total 12002 out of total 12002 tokens. size: 1.651 gb, cost 980.6983 ms, throughput: 1.8939 GB/s; (cache_engine.py:503:lmcache.v1.cache_engine)
The logs confirm that the throughput is slightly lower than 2 GB/s due to CPU <-> GPU allocation/transfer overhead.
@@ -0,0 +1,255 @@
Mooncake
========
.. warning::
This page documents the behavior of LMCache's in-process mode (deprecated). Please consider using :doc:`LMCache MP mode </mp/index>` for better feature support and performance. For the MP mode equivalent of this page, see :doc:`/mp/l2_storage/mooncake_store`.
.. _mooncake-overview:
Overview
--------
`Mooncake <https://github.com/kvcache-ai/Mooncake>`_ is an open-source distributed KV cache storage system designed specifically for LLM inference scenarios.
The system creates a distributed memory pool by aggregating memory space contributed by various client nodes, enabling efficient resource utilization across clusters.
By pooling underutilized DRAM and SSD resources from multiple nodes, the system forms a unified distributed storage service that maximizes resource efficiency.
.. image:: ../../assets/mooncake-store-preview.png
:alt: Mooncake Architecture Diagram
Key Features
~~~~~~~~~~~~
- **Distributed memory pooling**: Aggregates memory contributions from multiple client nodes into a unified storage pool
- **High bandwidth utilization**: Supports striping and parallel I/O transfer of large objects, fully utilizing multi-NIC aggregated bandwidth
- **RDMA optimization**: Built on Transfer Engine with support for TCP, RDMA (InfiniBand/RoCEv2/eRDMA/NVIDIA GPUDirect)
- **Dynamic resource scaling**: Supports dynamically adding and removing nodes for elastic resource management
For detailed architecture information, see the `Mooncake Architecture Guide <https://kvcache-ai.github.io/Mooncake/design/mooncake-store.html>`_.
Quick Start
-----------
Install Mooncake via pip:
.. code-block:: bash
pip install mooncake-transfer-engine
This package includes all necessary components:
- ``mooncake_master``: Master service that manages cluster metadata and coordinates distributed storage operations
- ``mooncake_http_metadata_server``: HTTP-based metadata server used by the underlying transfer engine for connection establishment
- Mooncake Python bindings
For production deployments or custom builds, see the `Build Instructions <https://kvcache-ai.github.io/Mooncake/getting_started/build.html>`_.
Setup and Deployment
~~~~~~~~~~~~~~~~~~~~
**Prerequisites:**
- Machine with at least one GPU for vLLM inference
- RDMA-capable network hardware and drivers (recommended) or TCP network
- Python 3.8+ with pip
- vLLM and LMCache installed
**Step 1: Start Infrastructure Services**
Start the Mooncake master service (with builtin HTTP metadata server):
.. code-block:: bash
# Master service (use -v=1 for verbose logging)
# The flag enables the integrated HTTP metadata server
mooncake_master --enable_http_metadata_server=1
Expected output:
.. code-block:: text
Master service started on port 50051
HTTP metrics server started on port 9003
Master Metrics: Storage: 0.00 B / 0.00 B | Keys: 0 | ...
**Step 2: Create Configuration File**
Create your ``mooncake-config.yaml``:
.. code-block:: yaml
# LMCache Configuration
local_cpu: False
remote_url: "mooncakestore://localhost:50051/"
max_local_cpu_size: 2 # small local buffer
numa_mode: "auto" # reduce tail latency with multi-NUMA/multi-NIC
pre_caching_hash_algorithm: sha256_cbor_64bit
# Mooncake Configuration (via extra_config)
extra_config:
use_exists_sync: true
save_chunk_meta: False # Enable chunk metadata optimization
local_hostname: "localhost"
metadata_server: "http://localhost:8080/metadata"
protocol: "rdma"
device_name: "" # leave empty; autodetect device(s)
global_segment_size: 21474836480 # 20 GiB per worker
master_server_address: "localhost:50051"
local_buffer_size: 0 # rely on LMCache local_cpu as the buffer
mooncake_prefer_local_alloc: true # prefer local segment if available
**Step 3: Start vLLM with Mooncake**
.. code-block:: bash
# If you see persistent misses (no Mooncake hits), make sure
# PYTHONHASHSEED is fixed across processes (e.g., export PYTHONHASHSEED=0).
LMCACHE_CONFIG_FILE="mooncake-config.yaml" \
vllm serve \
meta-llama/Llama-3.1-8B-Instruct \
--max-model-len 65536 \
--kv-transfer-config \
'{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}'
**Step 4: Verify the Setup**
Test the integration with a sample request:
.. code-block:: bash
curl -X POST "http://localhost:8000/v1/completions" \
-H "Content-Type: application/json" \
-d '{
"model": "meta-llama/Llama-3.1-8B-Instruct",
"prompt": "The future of AI is",
"max_tokens": 100,
"temperature": 0.7
}'
**Debugging Tips:**
1. **Enable verbose logging:**
.. code-block:: bash
mooncake_master -v=1
2. **Check service status:**
.. code-block:: bash
# Check if services are running
ps aux | grep mooncake
netstat -tlnp | grep -E "(8080|50051)"
3. **Monitor metrics:**
Access metrics at ``http://localhost:9003`` when master service is running.
Configuration
-------------
**LMCache Parameters:**
.. list-table::
:header-rows: 1
:widths: 25 15 60
* - Parameter
- Default
- Description
* - ``chunk_size``
- 256
- Number of tokens per KV chunk
* - ``remote_url``
- Required
- Mooncake store connection URL (format: ``mooncakestore://host:port/``).
* - ``remote_serde``
- "naive"
- Serialization method for remote storage
* - ``local_cpu``
- False
- Enable/disable local CPU caching (set to False for pure Mooncake evaluation)
* - ``max_local_cpu_size``
- Required
- Maximum local CPU cache size in GB (required even when local_cpu is False)
* - ``numa_mode``
- "auto"
- NUMA binding mode. "auto" is recommended on multiNIC/multiNUMA systems to reduce tail latency.
* - ``pre_caching_hash_algorithm``
- "sha256_cbor_64bit"
- Hash used for pre-caching keying. For crossprocess consistency, fix ``PYTHONHASHSEED`` (e.g., export ``PYTHONHASHSEED=0``).
**Mooncake Parameters (via extra_config):**
.. list-table::
:header-rows: 1
:widths: 25 15 60
* - Parameter
- Default
- Description
* - ``local_hostname``
- Required
- Hostname/IP of the local node for Mooncake client identification
* - ``metadata_server``
- Required
- HTTP metadata server address. When starting master with ``--enable_http_metadata_server=1``, it exposes this endpoint.
* - ``master_server_address``
- Required
- Mooncake master service address (host:port format)
* - ``protocol``
- "rdma"
- Communication protocol ("rdma" for high performance; "tcp" for compatibility)
* - ``device_name``
- ""
- RDMA device specification (e.g., "erdma_0,erdma_1" or "mlx5_0,mlx5_1"). Leave empty for autodetection in most setups.
* - ``global_segment_size``
- 21474836480
- **Memory size contributed by each vLLM worker** in bytes (e.g., 20 GiB recommended)
* - ``local_buffer_size``
- 0
- Local buffer size in bytes used by Mooncake. Behavior depends on ``save_chunk_meta``:
- When ``save_chunk_meta: False`` (recommended), LMCache uses its local CPU backend for zerocopy RDMA, so Mooncake's ``local_buffer_size`` can be ``0``.
- When ``save_chunk_meta: True``, Mooncake uses its own local buffer; set this to a proper value (e.g., several GiB).
- Note: Some RDMA NICs have memory registration limits; registering LMCache's large CPU buffer can fail on constrained devices. In those cases, consider enabling ``save_chunk_meta: True`` and sizing ``local_buffer_size`` instead.
* - ``transfer_timeout``
- 1
- Timeout for transfer operations in seconds
* - ``storage_root_dir``
- ""
- The root directory for persistence (e.g., "/mnt/mooncake")
* - ``save_chunk_meta``
- False
- Whether to save chunk metadata alongside data. Set to ``False`` to enable the optimized zerocopy path in LMCache.
* - ``use_exists_sync``
- False
- Use synchronous existence checks to avoid async scheduling overhead in hot paths.
* - ``mooncake_prefer_local_alloc``
- False
- Prefer allocating on the local segment when possible.
.. important::
**Understanding global_segment_size**: This parameter defines the amount of memory each vLLM worker contributes to the distributed memory pool.
The total cluster memory available for KV cache storage will be: ``number_of_vllm_workers × global_segment_size``.
Adjust this value based on your available system memory and expected cache requirements.
.. tip::
If you consistently get misses (no Mooncake hits), ensure all processes use the same hashing seed: ``export PYTHONHASHSEED=0``. This keeps precaching keys consistent across processes.
.. note::
RDMA device(s) usually do not need to be specified; leaving ``device_name`` empty works for most deployments.
Additional Resources
--------------------
- `Mooncake Store Architecture <https://kvcache-ai.github.io/Mooncake/design/mooncake-store.html>`_
- `Mooncake Store Deployment Guide <https://kvcache-ai.github.io/Mooncake/deployment/mooncake-store-deployment-guide.html>`_
- `Mooncake Store Python API Reference <https://kvcache-ai.github.io/Mooncake/python-api-reference/mooncake-store.html>`_
- `Transfer Engine Documentation <https://kvcache-ai.github.io/Mooncake/design/transfer-engine/index.html>`_
- `Build Instructions <https://kvcache-ai.github.io/Mooncake/getting_started/build.html>`_
- `GitHub Repository <https://github.com/kvcache-ai/Mooncake>`_
- `LMCache Integration Guide <https://kvcache-ai.github.io/Mooncake/getting_started/examples/lmcache-integration.html>`_
@@ -0,0 +1,275 @@
Nixl
====
.. warning::
This page documents the behavior of LMCache's in-process mode (deprecated). Please consider using :doc:`LMCache MP mode </mp/index>` for better feature support and performance. For the MP mode equivalent of this page, see :doc:`/mp/l2_storage/nixl`.
.. _nixl-overview:
Overview
--------
NIXL (NVIDIA Inference Xfer Library) is a high-performance library designed for accelerating point to point communications in AI inference frameworks. It provides an abstraction over various types of memory (CPU and GPU) and storage through a modular plug-in architecture, enabling efficient data transfer and coordination between different components of the inference pipeline.
LMCache supports using NIXL as a storage backend, allowing using NIXL to save either GPU or CPU memory into storage.
Prerequisites
~~~~~~~~~~~~~
- **LMCache**: Install with ``pip install lmcache``
- **NIXL**: Install from `NIXL GitHub repository <https://github.com/ai-dynamo/nixl>`_
- **Model Access**: Valid Hugging Face token (HF_TOKEN) for Llama 3.1 8B Instruct
Ways to configure LMCache NIXL Offloading
-----------------------------------------
**Configuration File**:
Passed in through ``LMCACHE_CONFIG_FILE=lmcache-config.yaml``
Example ``lmcache-config.yaml`` for POSIX backend:
.. code-block:: yaml
chunk_size: 256
nixl_buffer_device: cpu
local_cpu_use_hugepages: true # optional, requires pre-allocated hugepages
extra_config:
enable_nixl_storage: true
nixl_backend: POSIX
nixl_pool_size: 64
nixl_path: /mnt/nixl/cache/
use_direct_io: true
Key settings:
- ``nixl_buffer_size``: buffer size for NIXL transfers. **GPU mode only** (``nixl_buffer_device: cuda``). Setting this with ``nixl_buffer_device: cpu`` is a configuration error and will be rejected — in CPU mode NIXL shares ``LocalCPUBackend``'s pinned pool, which is sized by ``max_local_cpu_size``.
- ``max_local_cpu_size``: size of ``LocalCPUBackend``'s pinned pool in GiB. In CPU mode, this pool is shared with NIXL and must accommodate both the hot cache and concurrent NIXL I/O in flight. Must be > 0 when ``nixl_buffer_device: cpu``. Default: ``5.0``.
- ``nixl_pool_size``: number of descriptors opened at init time for nixl backend. Set to 0 for dynamic mode.
- ``nixl_path``: directory (or list of directories) under which the storage files will be saved (e.g. /mnt/nixl/). Needed for NIXL backends that store to file. When using a list of paths with ``path_sharding``, paths will be selected based on the sharding strategy.
- ``nixl_buffer_device``: dictates where the memory managed by NIXL should be on. "cpu" or "cuda" is supported for "GDS", "GDS_MT", and "OBJ" backends - for "POSIX", "HF3FS", "AZURE_BLOB" & "DOCA_MEMOS", must be "cpu". In CPU mode, NIXL shares ``LocalCPUBackend``'s pinned buffer; ``LocalCPUBackend`` is always created when ``nixl_buffer_device: cpu``, regardless of the ``local_cpu`` setting. ``local_cpu: false`` still suppresses hot-cache promotions — the backend acts as a staging buffer only, mirroring how ``local_disk`` already uses ``LocalCPUBackend``.
- ``nixl_backend``: configuration of which nixl backend to use for storage.
- ``nixl_path_sharding``: strategy for selecting path when multiple paths are provided. Currently only "by_gpu" is supported, which selects paths based on GPU device ID.
- ``local_cpu_use_hugepages``: whether to use Linux hugepages (2 MiB) for ``LocalCPUBackend``'s pinned pool (which NIXL shares in CPU mode). Requires pre-allocated hugepages (``sysctl vm.nr_hugepages``). Default: ``false``. **Deprecated alias:** ``extra_config.nixl_use_hugepages`` — accepted with a warning and copied into this field; will be removed in a future release.
.. note::
In CPU mode, the shared paged allocator consumes one full page per object. With ``save_unfull_chunk: true`` (only valid in static mode — dynamic mode rejects it; see "Dynamic Mode" → "Restrictions" below), partial chunks still occupy a full page each, so effective capacity degrades proportionally to the fraction of unfull last chunks across active sequences.
.. note::
``enable_p2p: true`` is rejected together with ``nixl_buffer_device: cpu``. The combination is structurally supported — both backends share ``LocalCPUBackend``'s pinned pool, each runs its own NIXL agent over it, and allocations route through ``LocalCPUBackend.allocate()`` — but it has not been exercised end-to-end and has no CI coverage. Use ``enable_p2p: true`` with ``nixl_buffer_device: cuda`` instead, or disable ``enable_p2p`` when running the NIXL CPU shared pool.
- ``nixl_presence_cache``: whether to keep an in-DRAM presence cache of keys known to exist, so repeated existence checks for the same key are answered locally instead of via a NIXL ``query_memory`` call. Applies to the dynamic backend (``nixl_pool_size: 0``). Default: ``false``.
- ``nixl_presence_cache_only``: when ``true``, the dynamic NIXL backend treats the local presence cache (and in-progress put set) as authoritative for existence checks. If a key is not known locally, lookup reports a miss **without** issuing a NIXL ``query_memory`` check. This requires ``nixl_presence_cache: true`` and can intentionally produce **false negatives** for objects that exist in the underlying storage but are absent from local presence metadata — giving "DRAM-only metadata" semantics where a process restart always yields a logically empty cache. Default: ``false``.
.. note::
This is a **lookup/existence-check** mode, not a "never touch the underlying storage" policy. It gates ``contains`` / ``batched_contains`` (and the async variant); direct retrieval still reads from the underlying storage. In normal operation a retrieval is only issued for a key that lookup already reported as present, so locally-unknown keys are not fetched. The option is only consulted by the dynamic backend (``nixl_pool_size: 0``); it is accepted but unused for static configurations.
.. note::
Supported backends are: ["GDS", "GDS_MT", "POSIX", "HF3FS", "OBJ", "AZURE_BLOB", "DOCA_MEMOS"].
Backend specific params should be provided via ``extra_config.nixl_backend_params``. Please refer to NIXL documentation for specifics.
Example ``lmcache-config.yaml`` for POSIX backend with multipath support:
.. code-block:: yaml
chunk_size: 256
nixl_buffer_size: 1073741824 # 1GB
nixl_buffer_device: cpu
extra_config:
enable_nixl_storage: true
nixl_backend: POSIX
nixl_pool_size: 64
nixl_path:
- /mnt/nixl/cache0/
- /mnt/nixl/cache1/
- /mnt/nixl/cache2/
nixl_path_sharding: by_gpu
use_direct_io: True
Example ``lmcache-config.yaml`` for OBJ backend using S3 API:
.. code-block:: yaml
chunk_size: 256
nixl_buffer_device: cpu
max_local_cpu_size: 1 # GiB
extra_config:
enable_nixl_storage: true
nixl_backend: OBJ
nixl_pool_size: 64
nixl_path: /mnt/nixl/cache/
nixl_backend_params:
access_key: <your_access_key>
secret_key: <your_secret_key>
bucket: <your_bucket>
region: <your_region>
Example ``lmcache-config.yaml`` for POSIX backend using liburing:
.. note::
using POSIX backend with liburing requires NIXL to be built with liburing support.
.. code-block:: yaml
chunk_size: 256
nixl_buffer_device: cpu
max_local_cpu_size: 1 # GiB
extra_config:
enable_nixl_storage: true
nixl_backend: POSIX
nixl_pool_size: 64
nixl_path: /mnt/nixl/cache/
use_direct_io: True
nixl_backend_params:
use_uring: "true"
Example ``lmcache-config.yaml`` for AZURE_BLOB backend to offload using Azure Blob Storage API:
.. code-block:: yaml
chunk_size: 256
nixl_buffer_device: cpu
max_local_cpu_size: 1 # GiB
extra_config:
enable_nixl_storage: true
nixl_backend: AZURE_BLOB
nixl_pool_size: 64
nixl_path: /mnt/nixl/cache/
nixl_backend_params:
account_url: https://<your_azure_storage_account_name>.blob.core.windows.net
container_name: <your_container_name>
Per-Worker Endpoint Distribution
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When using the OBJ backend with multiple tensor-parallel (TP) workers, you can
distribute workers across multiple object-storage endpoints by providing a list of
endpoints via ``nixl_endpoint_list``. Each worker selects an endpoint in
round-robin order based on its ``local_worker_id`` (the worker ID within its host).
.. code-block:: yaml
extra_config:
enable_nixl_storage: true
nixl_backend: OBJ
nixl_pool_size: 64
nixl_path: /mnt/nixl/cache/
nixl_endpoint_list:
- https://node-0.object-storage:9021
- https://node-1.object-storage:9021
- https://node-2.object-storage:9021
nixl_backend_params:
access_key: <your_access_key>
secret_key: <your_secret_key>
bucket: <your_bucket>
region: <your_region>
.. note::
When ``nixl_endpoint_list`` is set, any ``endpoint_override`` value in
``nixl_backend_params`` is ignored (a warning is logged).
``nixl_endpoint_list`` is only honored for the OBJ backend; it is ignored
for all other backends (including DOCA_MEMOS, AZURE_BLOB, and the file
backends).
Dynamic Mode
~~~~~~~~~~~~~
Nixl Storage Backend also supports a dynamic mode, which creates nixl storage descriptors on demand instead of at init time.
In order to use dynamic mode, extra_config.nixl_pool_size should be set to 0.
Restrictions
^^^^^^^^^^^^
- Dynamic mode is supported for object backends ("OBJ", "AZURE_BLOB", "DOCA_MEMOS") and file backends ("POSIX", "GDS", "GDS_MT", "HF3FS").
- save_unfull_chunk must be set to False.
Example ``lmcache-config.yaml`` for OBJ backend with dynamic mode:
.. code-block:: yaml
chunk_size: 256
local_cpu: False
save_unfull_chunk: False
enable_async_loading: False # set to True to test async loading
nixl_buffer_device: cpu
max_local_cpu_size: 3 # GiB
extra_config:
enable_nixl_storage: true
nixl_backend: OBJ
nixl_pool_size: 0
nixl_presence_cache: False
nixl_async_put: False
nixl_backend_params:
access_key: <your_access_key>
secret_key: <your_secret_key>
bucket: <your_bucket>
region: <your_region>
endpoint_override: https://url-to-object-storage
ca_bundle: path to self-signed certificate # remove this line if not using self-signed certificate
Example ``lmcache-config.yaml`` for AZURE_BLOB backend with dynamic mode:
.. code-block:: yaml
chunk_size: 256
local_cpu: False
save_unfull_chunk: False
enable_async_loading: False # set to True to test async loading
nixl_buffer_device: cpu
max_local_cpu_size: 3 # GiB
extra_config:
enable_nixl_storage: true
nixl_backend: AZURE_BLOB
nixl_pool_size: 0
nixl_presence_cache: False
nixl_async_put: False
nixl_backend_params:
account_url: https://<your_azure_storage_account_name>.blob.core.windows.net
container_name: <your_container_name>
DOCA_MEMOS Backend (NVIDIA CMX)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``DOCA_MEMOS`` stores KV cache on NVIDIA CMX (Context Memory Storage), a
BlueField-4 context-memory tier accessed through NIXL. It is an object-style
backend (like ``OBJ``), supported in both static (``nixl_pool_size`` > 0) and
dynamic (``nixl_pool_size`` = 0) mode. ``nixl_buffer_device`` must be ``cpu``.
``nixl_endpoint_list`` is not supported for DOCA_MEMOS.
Object names are 128-bit lowercase-hex strings: the NIXL DOCA_MEMOS plugin
passes object names as strings and hex-decodes them on the device side, so
each name is exactly 32 hex characters. In dynamic mode this name is a
truncated SHA-256 of the cache key, so names are opaque (they carry no
model/chunk debug information) and uniqueness is probabilistic at 128 bits.
.. code-block:: yaml
chunk_size: 256
nixl_buffer_device: cpu
max_local_cpu_size: 1 # GiB
extra_config:
enable_nixl_storage: true
nixl_backend: DOCA_MEMOS
nixl_pool_size: 64
nixl_backend_params:
# refer to NIXL DOCA_MEMOS plugin docs for connection params
@@ -0,0 +1,455 @@
Redis
=====
.. warning::
This page documents the behavior of LMCache's in-process mode (deprecated). Please consider using :doc:`LMCache MP mode </mp/index>` for better feature support and performance. For the MP mode equivalent of this page, see :doc:`/mp/l2_storage/resp`.
.. _redis-overview:
Overview
--------
Redis is an in-memory key-value store and is a supported option for remote KV Cache offloading in LMCache.
Some other remote backends are :doc:`Mooncake <./mooncake>`, :doc:`Valkey <./valkey>`, and :doc:`InfiniStore <./infinistore>`.
This guide will mainly focus on single-node Redis but also shows you how to set up Redis Sentinels and an LMCache Server.
Two ways to configure LMCache Redis Offloading:
-----------------------------------------------
**1. Environment Variables:**
.. code-block:: bash
# 256 Tokens per KV Chunk
export LMCACHE_CHUNK_SIZE=256
# Redis host
export LMCACHE_REMOTE_URL="redis://your-redis-host:6379"
# Redis Sentinel hosts (for high availability)
# export LMCACHE_REMOTE_URL="redis-sentinel://localhost:26379,localhost:26380,localhost:26381"
# LMCache Server host
# export LMCACHE_REMOTE_URL="lm://localhost:65432"
# How to serialize and deserialize KV cache on remote transmission
export LMCACHE_REMOTE_SERDE="naive" # "naive" (default) or "cachegen"
**2. Configuration File**:
Passed in through ``LMCACHE_CONFIG_FILE=your-lmcache-config.yaml``
Example ``config.yaml``:
.. code-block:: yaml
# 256 Tokens per KV Chunk
chunk_size: 256
# Redis host
remote_url: "redis://your-redis-host:6379"
# Redis Sentinel hosts (for high availability)
# remote_url: "redis-sentinel://localhost:26379,localhost:26380,localhost:26381"
# LMCache Server host
# remote_url: "lm://localhost:65432"
# How to serialize and deserialize KV cache on remote transmission
remote_serde: "naive" # "naive" (default) or "cachegen"
Dynamic Plugin Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: yaml
# 256 Tokens per KV Chunk
chunk_size: 256
local_cpu: true
remote_storage_plugins:
- "redis"
extra_config:
remote_storage_plugin.redis.redis_url: "redis://your-redis-host:6379"
Remote Storage Explanation:
----------------------------
LMCache's backend is obeys the natural memory hierarchy of prioritizing CPU RAM offloading, then Local Storage
offloading, and finally remote offloading.
For LMCache to know how to create a connector to a remote backend, you must specify in
``remote_url`` a connector type followed by one or most host:port pairs (depending on what connector type is used).
If ``remote_url`` is set to ``None``, LMCache will not use any remote storage.
Examples of ``remote_url``'s:
.. code-block:: yaml
remote_url: "redis://your-redis-host:6379"
remote_url: "redis-sentinel://localhost:26379,localhost:26380,localhost:26381"
remote_url: "lm://localhost:65432"
remote_url: "infinistore://127.0.0.1:12345"
remote_url: "mooncakestore://127.0.0.1:50051"
Remote Storage Example
-----------------------
.. _redis-prerequisites:
**Prerequisites:**
- A Machine with at least one GPU. You can adjust the max model length of your vllm instance depending on your GPU memory.
- vllm and lmcache installed (:doc:`Installation Guide <../../getting_started/installation>`)
- Hugging Face access to ``meta-llama/Meta-Llama-3.1-8B-Instruct``
.. code-block:: bash
export HF_TOKEN=your_hugging_face_token
**Step 0. Set up a directory for this example:**
.. code-block:: bash
mkdir lmcache-redis-offload-example
cd lmcache-redis-offload-example
**Step 1. Start a Redis server:**
.. code-block:: bash
# Ubuntu / Debian Installation
sudo apt-get install redis
redis-server # starts the server on default port 6379
Check if Redis is running:
.. code-block:: bash
redis-cli ping
Expected Response:
.. code-block:: text
PONG
**Optional: Setting up Sentinels:**
To enable high availability with Redis, you can configure Redis sentinels to
monitor the master and automatically fail over to a replica if needed.
**Step 1a. Start a Redis replica:**
.. code-block:: bash
redis-server --port 6380 --replicaof 127.0.0.1 6379
**Step 1b. Create Sentinel configuration files:**
Create three files: ``sentinel-26379.conf``, ``sentinel-26380.conf``, and ``sentinel-26381.conf``, with contents like this:
.. code-block:: ini
port 26379 # Use 26380 and 26381 in other files respectively
sentinel monitor mymaster 127.0.0.1 6379 1
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 10000
sentinel parallel-syncs mymaster 1
**Step 1c. Start each Sentinel:**
.. code-block:: bash
redis-server sentinel-26379.conf --sentinel
redis-server sentinel-26380.conf --sentinel
redis-server sentinel-26381.conf --sentinel
**Step 1d. Make sure the Sentinels are tracking the master:**
.. code-block:: bash
redis-cli -p 26379 sentinel master mymaster
redis-cli -p 26380 sentinel master mymaster
redis-cli -p 26381 sentinel master mymaster
**Step 1e. Verify everything is running:**
.. code-block:: bash
ps aux | grep redis
You should see something like this (without the comments):
.. code-block:: text
# Master (read-write)
user 60816 0.1 0.0 69804 11132 ? Sl 04:11 0:00 redis-server *:6379
# Replica (read-only mirror of 6379)
user 60903 0.1 0.0 80048 10928 ? Sl 04:12 0:00 redis-server *:6380
# Sentinels (monitor the master and hold quorums to decide when to failover)
user 61301 0.1 0.0 67244 10944 ? Sl 04:14 0:00 redis-server *:26379 [sentinel]
user 61382 0.1 0.0 67244 10944 ? Sl 04:14 0:00 redis-server *:26380 [sentinel]
user 61462 0.1 0.0 67244 10944 ? Sl 04:15 0:00 redis-server *:26381 [sentinel]
**Alternative: Starting an LMCache Server:**
The ``lmcache_server`` CLI entrypoint starts a remote LMCache server and comes with
the ``lmcache`` package.
.. code-block:: bash
lmcache_server <host> <port> <device>
lmcache_server localhost 65432
Currently, the only supported device is "cpu" (which is the default, so you don't need to specify it).
**Step 2. Start a vLLM server with remote offloading enabled:**
Create a an lmcache configuration file called: ``redis-offload.yaml``
.. code-block:: yaml
# disabling CPU RAM offload not recommended (on by default) but
# if you want to confirm that the remote backend works by itself
# local_cpu: false
chunk_size: 256
remote_url: "redis://localhost:6379"
remote_serde: "naive"
If you don't want to use a config file, uncomment the first three environment variables
and then comment out the ``LMCACHE_CONFIG_FILE`` below:
.. code-block:: bash
# disabling CPU RAM offload not recommended (on by default) but
# if you want to confirm that the remote backend works by itself
# LMCACHE_LOCAL_CPU=False \
# LMCACHE_CHUNK_SIZE=256 \
# LMCACHE_REMOTE_URL="redis://localhost:6379" \
# LMCACHE_REMOTE_SERDE="naive"
LMCACHE_CONFIG_FILE="redis-offload.yaml" \
vllm serve \
meta-llama/Llama-3.1-8B-Instruct \
--max-model-len 16384 \
--kv-transfer-config \
'{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}'
**Optional: Sentinels**
Create a an lmcache configuration file called: ``redis-sentinel-offload.yaml``
.. code-block:: yaml
chunk_size: 256
remote_url: "redis-sentinel://localhost:26379,localhost:26380,localhost:26381"
remote_serde: "naive"
If you don't want to use a config file, uncomment the first three environment variables
and then comment out the ``LMCACHE_CONFIG_FILE`` below:
.. code-block:: bash
# LMCACHE_CHUNK_SIZE=256 \
# LMCACHE_REMOTE_URL="redis-sentinel://localhost:26379,localhost:26380,localhost:26381" \
# LMCACHE_REMOTE_SERDE="naive"
LMCACHE_CONFIG_FILE="redis-sentinel-offload.yaml" \
vllm serve \
meta-llama/Llama-3.1-8B-Instruct \
--max-model-len 16384 \
--kv-transfer-config \
'{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}'
**Alternative: LMCache Server**
Create a an lmcache configuration file called: ``lmcache-server-offload.yaml``
.. code-block:: yaml
chunk_size: 256
remote_url: "lm://localhost:65432"
remote_serde: "naive"
If you don't want to use a config file, uncomment the first three environment variables
and then comment out the ``LMCACHE_CONFIG_FILE`` below:
.. code-block:: bash
# LMCACHE_CHUNK_SIZE=256 \
# LMCACHE_REMOTE_URL="lm://localhost:65432" \
# LMCACHE_REMOTE_SERDE="naive"
LMCACHE_CONFIG_FILE="lmcache-server-offload.yaml" \
vllm serve \
meta-llama/Llama-3.1-8B-Instruct \
--max-model-len 16384 \
--kv-transfer-config \
'{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}'
**Step 3. Viewing and Managing LMCache Entries in Redis:**
If you would like to feel the TTFT speed up with offloading and KV Cache reuse, feel free to use the same
``query-twice.py`` script and ``man-bash.txt`` long context as in :doc:`CPU RAM <./cpu_ram>` and :doc:`Local Storage <./local_storage>`.
Here, we are instead going to demonstrate how to search for and modify LMCache KV Chunk entries in Redis.
Please note that the official LMCache way to achieve this redis-specific functionality of viewing and modifying LMCache KV Chunks is available in :doc:`LMCache Controller <../../kv_cache_management/index>`.
Let's warm/populate LMCache first with ``curl`` this time:
.. code-block:: bash
curl -X 'POST' \
'http://127.0.0.1:8000/v1/chat/completions' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"model": "meta-llama/Llama-3.1-8B-Instruct",
"messages": [
{"role": "system", "content": "You are a helpful AI coding assistant."},
{"role": "user", "content": "Write a segment tree implementation in python"}
],
"max_tokens": 150
}'
LMCache stores data in Redis using a structured key format. Each key contains the following information in a delimited format:
.. code-block:: text
model_name@world_size@worker_id@chunk_hash
- `model_name`: Name of the language model
- `world_size`: Total number of workers in distributed deployment
- `worker_id`: ID of the worker that created this cache entry, in the range of [0, world_size - 1]
- `chunk_hash`: Hash of the token chunk (SHA-256 based)
For example, a typical key might look like:
.. code-block:: text
vllm@meta-llama/Llama-3.1-8B-Instruct@1@0@a1b2c3d4e5f6...
**Using redis-cli to View LMCache Data**
To inspect and manage LMCache entries in Redis:
.. code-block:: bash
redis-cli -h localhost -p 6379
**Optional: If you are using sentinels, first find the master port:**
.. code-block:: bash
redis-cli -p 26379 sentinel get-master-addr-by-name mymaster
redis-cli -h localhost -p <master-port>
**List LMCache keys:**
Notice (from the suffixes of the keys) that each LMCache KV Chunk has two entries: ``kv_bytes`` and ``metadata``
.. code-block:: bash
# Show all keys
localhost:6379> KEYS *
1) "vllm@meta-llama/Llama-3.1-8B-Instruct@1@0@02783dafec...kv_bytes"
2) "vllm@meta-llama/Llama-3.1-8B-Instruct@1@0@02783dafec...metadata"
# Show keys for a specific model
localhost:6379> KEYS *Llama-3.1-8B-Instruct*
1) "vllm@meta-llama/Llama-3.1-8B-Instruct@1@0@02783dafec...kv_bytes"
2) "vllm@meta-llama/Llama-3.1-8B-Instruct@1@0@02783dafec...metadata"
**Delete LMCache entries:**
.. code-block:: bash
localhost:6379> DEL *
Delete a specific LMCache entry:
.. code-block:: bash
localhost:6379> DEL "vllm@meta-llama/Llama-3.1-8B-Instruct@1@0@02783dafec...kv_bytes"
localhost:6379> KEYS *
1) "vllm@meta-llama/Llama-3.1-8B-Instruct@1@0@02783dafec...metadata"
**Check if a key exists:**
.. code-block:: bash
localhost:6379> EXISTS "vllm@meta-llama/Llama-3.1-8B-Instruct@1@0@02783dafec...kv_bytes"
**View memory usage for a key:**
Notice that the ``kv_bytes`` entry is what is exactly holding the KV Chunk and is much
larger than the ``metadata`` entry.
.. code-block:: bash
localhost:6379> MEMORY USAGE "vllm@meta-llama/Llama-3.1-8B-Instruct@1@0@02783dafec...metadata"
(integer) 198
localhost:6379> MEMORY USAGE "vllm@meta-llama/Llama-3.1-8B-Instruct@1@0@02783dafec...kv_bytes"
(integer) 7340200
**Delete specific keys:**
.. code-block:: bash
# Delete a single key
localhost:6379> DEL "vllm@meta-llama/Llama-3.1-8B-Instruct@1@0@02783dafec...kv_bytes"
.. code-block:: bash
# Delete all keys matching a pattern
redis-cli -h localhost -p 6379 --scan --pattern "vllm@meta-llama/Llama-3.1-8B-Instruct*" \
| xargs redis-cli -h localhost -p 6379 DEL
**Monitor Redis in real-time:**
.. code-block:: bash
localhost:6379> MONITOR
**Get Redis stats for LMCache:**
.. code-block:: bash
# Get memory stats
localhost:6379> INFO memory
# Get statistics about operations
localhost:6379> INFO stats
This tutorial utilized the ``redis-cli`` to directly peak into a remote backend and manipualte
KV Chunks.
Once again, please refer to the :doc:`LMCache Controller <../../kv_cache_management/index>`
for the official LMCache way of controlling and routing your KV Caches in your LMCache instances.
**Step 4. Clean up:**
.. code-block:: bash
redis-cli shutdown
# Optional:
# Shut down the Redis replica (if started)
redis-cli -p 6380 shutdown
# Shut down all Redis Sentinels (if started)
redis-cli -p 26379 shutdown
redis-cli -p 26380 shutdown
redis-cli -p 26381 shutdown
# (Optional) Remove temporary files or configs
rm -f sentinel-26379.conf sentinel-26380.conf sentinel-26381.conf
# Confirm no Redis processes are still running
ps aux | grep redis
@@ -0,0 +1,456 @@
RESP (Native Redis/Valkey)
==========================
.. _resp-overview:
Overview
--------
The RESP backend is a high-performance native C++ storage connector for Redis and Valkey servers,
using the RESP2 wire protocol over TCP. It is designed for maximum throughput on KV cache
store and retrieval operations, achieving **6+ GB/s** on reads with optimal configuration.
Key advantages over the standard Redis connector:
- **Multi-threaded C++ I/O**: Worker threads operate in parallel with zero-copy buffer passing and full GIL release
- **Batched tiling**: Large batch operations are automatically split across worker threads for maximum parallelism
- **eventfd-based completions**: The kernel wakes Python on completion -- no polling overhead
- **Dual-mode support**: The same C++ connector works in both non-MP mode (via ``ConnectorClientBase``) and MP mode (via ``NativeConnectorL2Adapter`` as an L2 adapter)
The native C++ source lives in ``csrc/storage_backends/redis/``. See :doc:`Adding Native Connectors <../../developer_guide/extending_lmcache/native_connectors>` for the full architecture.
Prerequisites
-------------
- LMCache installed from source (``pip install -e .``) to compile the C++ extension
- A Redis 8.2+ or Valkey server (Redis 8.2 recommended for IO threads support)
- A machine with at least one GPU for vLLM inference
Redis Server Setup
------------------
.. important::
Redis version and server configuration have a **major** impact on throughput.
Using Redis 8.2 with IO threads yields ~6 GB/s reads vs ~1.5 GB/s with Redis 6.0 defaults.
**Build Redis 8.2 from source (recommended):**
.. code-block:: bash
git clone https://github.com/redis/redis.git
cd redis
git checkout 8.2
make -j
**Start the server with IO threads enabled:**
.. code-block:: bash
./src/redis-server \
--protected-mode no \
--save '' \
--appendonly no \
--io-threads 4 \
--port 6379
.. list-table:: Recommended Server Flags
:header-rows: 1
:widths: 30 70
* - Flag
- Why
* - ``--protected-mode no``
- Allow connections from other hosts (use auth in production)
* - ``--save '' --appendonly no``
- Disable persistence -- KV cache is ephemeral, persistence wastes bandwidth
* - ``--io-threads 4``
- Enable multi-threaded I/O for parallel read/write handling
* - ``--port 6379``
- Default port (adjust if running multiple instances)
.. tip::
The number of ``--io-threads`` should roughly match the number of physical cores
available to the Redis process. 4 is a good starting point; benchmark with your
hardware to find the optimum.
Chunk Size Selection and Throughput Tuning
------------------------------------------
The chunk size (in tokens) determines how many bytes each Redis key-value pair holds.
This is the **single most important parameter** for throughput.
**The sweet spot is ~4 MB per chunk.** Both smaller and larger chunks degrade throughput:
.. list-table:: Chunk Size vs Throughput (Redis 8.2, 8 workers)
:header-rows: 1
:widths: 20 20 20 20
* - Chunk Size
- Total Data
- SET Throughput
- GET Throughput
* - 1 MB (500 keys)
- 500 MB
- ~3.5 GB/s
- ~5.2 GB/s
* - **4 MB (500 keys)**
- **2 GB**
- **~4.4 GB/s**
- **~5.9 GB/s**
* - 8 MB (200 keys)
- 1.6 GB
- ~4.2 GB/s
- ~1.4 GB/s
**Why 4 MB?**
- Below ~2 MB, per-key overhead (RESP framing, TCP round-trips) dominates
- Above ~4 MB, Redis server-side memory allocation and TCP window sizes become bottlenecks
- At 4 MB, the balance between amortized overhead and memory pressure is optimal
**Calculating chunk size in tokens:**
The chunk size in bytes depends on the model's hidden dimension, number of KV heads,
number of layers, and dtype:
.. code-block:: text
bytes_per_token = 2 * num_kv_heads * head_dim * num_layers * dtype_bytes
For ``meta-llama/Llama-3.1-8B-Instruct`` with BFloat16:
.. code-block:: text
bytes_per_token = 2 * 8 * 128 * 32 * 2 = 131,072 bytes (~128 KB)
chunk_size_tokens = 4 MB / 128 KB = 32 tokens
# But typically chunk_size is set as token count in config:
chunk_size: 16 # ~2 MB per chunk (conservative)
chunk_size: 32 # ~4 MB per chunk (optimal for throughput)
.. note::
The bytes-per-token calculation varies by model architecture. Larger models
(e.g., 70B) have more layers and larger hidden dimensions, so fewer tokens
are needed per chunk to reach the 4 MB sweet spot.
Throughput Sweep
~~~~~~~~~~~~~~~~
To find the optimal configuration for your hardware, use the included benchmark:
.. code-block:: bash
cd examples/kv_cache_reuse/remote_backends/resp
# Sweep chunk sizes
for mb in 0.5 1 2 4 8; do
echo "=== Chunk: ${mb} MB ==="
python benchmark_resp_client.py \
--host 127.0.0.1 --port 6379 \
--chunk-mb $mb --num-workers 8 --num-keys 500
done
# Sweep worker counts
for w in 1 2 4 8 16; do
echo "=== Workers: $w ==="
python benchmark_resp_client.py \
--host 127.0.0.1 --port 6379 \
--chunk-mb 4 --num-workers $w --num-keys 500
done
Expected output:
.. code-block:: text
Redis RESP Client Benchmark
Server: 127.0.0.1:6379, Workers: 8
Chunk size: 4096KB, Keys: 500
------------------------------------------------------------
Batch SET: 4.36 GB/s (1.95 GB written)
Batch GET: 5.91 GB/s (1.95 GB read)
Batch EXISTS: 143528 ops/s (500/500 hits)
------------------------------------------------------------
All tests passed
Environment Variable Configuration
------------------------------------
Sensitive credentials (and optionally host/port) can be provided via environment
variables instead of config files or CLI arguments. This prevents secrets from
appearing in logged configuration at startup.
.. list-table::
:header-rows: 1
:widths: 35 65
* - Variable
- Description
* - ``LMCACHE_RESP_USERNAME``
- Redis ACL username. Used as default when ``username`` is not set in config/JSON.
* - ``LMCACHE_RESP_PASSWORD``
- Redis AUTH password. Used as default when ``password`` is not set in config/JSON.
* - ``LMCACHE_RESP_HOST``
- Redis hostname or IP. Used as default when ``host`` is not set in config/JSON/URL.
* - ``LMCACHE_RESP_PORT``
- Redis port. Used as default when ``port`` is not set in config/JSON/URL.
Config files (non-MP) and ``--l2-adapter`` JSON (MP) take precedence over
environment variables. Environment variables serve as defaults — they are used
when the corresponding config value is empty or unset. They are read at adapter
creation time inside the adapter itself, so they are **never stored in the
config object** and **never printed in startup logs**.
**Example — MP mode with env vars:**
.. code-block:: bash
export LMCACHE_RESP_USERNAME="default"
export LMCACHE_RESP_PASSWORD="secret"
lmcache server \
--l1-size-gb 10 \
--eviction-policy LRU \
--chunk-size 16 \
--l2-adapter '{"type": "resp", "host": "localhost", "port": 6379, "num_workers": 8}' \
--port 6555
**Example — Non-MP mode with env vars:**
.. code-block:: bash
export LMCACHE_RESP_USERNAME="default"
export LMCACHE_RESP_PASSWORD="secret"
LMCACHE_CONFIG_FILE=resp-config.yaml \
vllm serve meta-llama/Llama-3.1-8B-Instruct \
--kv-transfer-config '{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}' \
--no-enable-prefix-caching \
--load-format dummy
.. tip::
For production deployments, always use environment variables for credentials
rather than embedding them in config files or CLI arguments.
Non-MP Mode (Single Process)
-----------------------------
In non-MP mode, the RESP connector is used directly as a remote storage backend
via the ``RESPClient`` asyncio wrapper.
**Configuration file** (``resp-config.yaml``):
.. code-block:: yaml
chunk_size: 16
remote_url: "resp://localhost:6379"
remote_serde: "naive"
Credentials can be set via environment variables (recommended) or in the config file
under ``extra_config`` (see `Environment Variable Configuration`_ above).
**Launch vLLM:**
.. code-block:: bash
LMCACHE_CONFIG_FILE=resp-config.yaml \
vllm serve meta-llama/Llama-3.1-8B-Instruct \
--kv-transfer-config '{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}' \
--no-enable-prefix-caching \
--load-format dummy
.. note::
``save_unfull_chunk`` must be off (default) and chunk metadata saving must be
disabled for optimal throughput with the native RESP connector.
MP Mode (Multiprocess)
-----------------------
In MP mode, LMCache runs as a separate server process communicating with vLLM over
ZMQ. The RESP connector serves as an L2 adapter with variable-size chunk support.
**Step 1: Start Redis** (see `Redis Server Setup`_ above)
**Step 2: Launch LMCache MP Server:**
.. code-block:: bash
lmcache server \
--l1-size-gb 10 \
--eviction-policy LRU \
--chunk-size 16 \
--l2-adapter '{"type": "resp", "host": "localhost", "port": 6379, "num_workers": 8}' \
--port 6555
**Step 3: Launch vLLM with LMCache MP Connector:**
.. code-block:: bash
PORT=8000
vllm serve meta-llama/Llama-3.1-8B-Instruct \
--kv-transfer-config '{
"kv_connector": "LMCacheMPConnector",
"kv_role": "kv_both",
"kv_connector_extra_config": {
"lmcache.mp.host": "tcp://localhost",
"lmcache.mp.port": 6555
}
}' \
--no-enable-prefix-caching \
--port $PORT \
--load-format dummy
L2 Adapter Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~
The ``--l2-adapter`` JSON accepts these fields:
.. list-table::
:header-rows: 1
:widths: 20 10 10 60
* - Field
- Type
- Default
- Description
* - ``type``
- str
- (required)
- Must be ``"resp"``
* - ``host``
- str
- (required)
- Redis/Valkey hostname or IP
* - ``port``
- int
- (required)
- Redis/Valkey port
* - ``num_workers``
- int
- 8
- C++ worker threads for parallel I/O
* - ``username``
- str
- ``""``
- Redis ACL username (leave empty for no auth). Falls back to ``LMCACHE_RESP_USERNAME`` env var if empty.
* - ``password``
- str
- ``""``
- Redis AUTH password (leave empty for no auth). Falls back to ``LMCACHE_RESP_PASSWORD`` env var if empty.
* - ``max_capacity_gb``
- float
- 0
- Maximum L2 storage capacity in GB for client-side usage tracking. Required for L2 eviction. Set to 0 (default) to disable usage tracking.
L2 Eviction
~~~~~~~~~~~~
To enable automatic eviction of least-recently-used keys when the Redis backend fills up,
set ``max_capacity_gb`` and add an ``"eviction"`` block:
.. code-block:: bash
lmcache server \
--l1-size-gb 10 \
--eviction-policy LRU \
--chunk-size 16 \
--l2-adapter '{
"type": "resp",
"host": "localhost",
"port": 6379,
"num_workers": 8,
"max_capacity_gb": 10,
"eviction": {
"eviction_policy": "LRU",
"trigger_watermark": 0.8,
"eviction_ratio": 0.2
}
}' \
--port 6555
This configures a 10 GB capacity limit. When usage exceeds 80% (``trigger_watermark``),
the eviction controller will delete the least-recently-used ~20% of stored keys
(``eviction_ratio``) using the Redis ``DEL`` command.
.. note::
``max_capacity_gb`` enables **client-side** size tracking. It does not configure
the Redis server's ``maxmemory`` setting. You should set ``max_capacity_gb`` to
match or be slightly below your Redis server's available memory.
Testing the Setup
------------------
Send the same prompt twice. The first request stores KV cache to Redis; the second retrieves it.
.. code-block:: bash
PORT=8000
PROMPT="$(printf 'Elaborate the significance of KV cache in language models. %.0s' {1..1000})"
# First request: store
curl -s -X POST http://localhost:${PORT}/v1/completions \
-H "Content-Type: application/json" \
-d '{"model":"meta-llama/Llama-3.1-8B-Instruct","prompt":"'"$PROMPT"'","max_tokens":10}'
# Second request with same prefix: retrieve from Redis
curl -s -X POST http://localhost:${PORT}/v1/completions \
-H "Content-Type: application/json" \
-d '{"model":"meta-llama/Llama-3.1-8B-Instruct","prompt":"'"$PROMPT"'","max_tokens":10}'
Verify data was stored:
.. code-block:: bash
redis-cli -p 6379 DBSIZE
Clear state between runs:
.. code-block:: bash
redis-cli -p 6379 FLUSHALL
Best Practices
--------------
**Server deployment:**
- Use Redis 8.2+ with ``--io-threads 4`` (or more, matching available cores)
- Disable persistence (``--save '' --appendonly no``) for KV cache workloads
- Pin Redis to its own NUMA node if running on multi-socket systems
- For production, enable authentication with ``--requirepass`` and supply credentials via ``LMCACHE_RESP_USERNAME`` / ``LMCACHE_RESP_PASSWORD`` environment variables to keep them out of logs
**Client tuning:**
- Start with ``num_workers: 8`` and increase if the server has spare CPU and you're not saturating the network
- More workers help when chunk sizes are smaller (more keys per batch = more parallelism needed)
- On NUMA systems, ensure the LMCache process runs on the same NUMA node as the NIC
**Chunk size:**
- Target ~4 MB per chunk for maximum throughput
- Calculate the token count using your model's per-token byte size (see formula above)
- If unsure, run the benchmark sweep to find the optimum for your specific hardware
**Network:**
- Use localhost or loopback for single-machine deployments
- For cross-machine setups, ensure low-latency networking (ideally <100 us RTT)
- The RESP connector uses TCP; RDMA is not currently supported (consider :doc:`Mooncake <./mooncake>` for RDMA)
Additional Resources
--------------------
- Benchmark script: ``examples/kv_cache_reuse/remote_backends/resp/benchmark_resp_client.py``
- C++ source: ``csrc/storage_backends/redis/``
- Native connector architecture: ``csrc/storage_backends/README.md``
- Developer guide for adding new native connectors: :doc:`Adding Native Connectors <../../developer_guide/extending_lmcache/native_connectors>`
@@ -0,0 +1,155 @@
S3 Backend
==========
Example Configurations
----------------------
Basic S3 Configuration
~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: yaml
chunk_size: 256
local_cpu: False
save_unfull_chunk: False
remote_url: "s3://your-bucket-name"
remote_serde: "naive"
blocking_timeout_secs: 10
extra_config:
s3_region: "us-east-1"
s3_num_io_threads: 64
save_chunk_meta: False
S3 Express One Zone
~~~~~~~~~~~~~~~~~~~
.. code-block:: yaml
chunk_size: 256
local_cpu: False
save_unfull_chunk: False
remote_url: "s3://{BUCKET_NAME}.s3express-{AZ_ID}.{REGION}.amazonaws.com"
remote_serde: "naive"
blocking_timeout_secs: 10
extra_config:
save_chunk_meta: False
s3_num_io_threads: 64
s3_prefer_http2: True
s3_region: "{REGION}"
s3_enable_s3express: True
CoreWeave (S3-compatible)
~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: yaml
chunk_size: 256
local_cpu: False
max_local_cpu_size: 50
save_unfull_chunk: False
enable_async_loading: True
remote_url: "s3://test-127.cwlota.com"
remote_serde: "naive"
blocking_timeout_secs: 10
extra_config:
s3_num_io_threads: 320
s3_prefer_http2: False
s3_region: "US-WEST-04A"
s3_enable_s3express: False
save_chunk_meta: False
disable_tls: True
aws_access_key_id: "your-access-key-id"
aws_secret_access_key: "your-secret-access-key"
**Note**: `cwlota.com` is CoreWeave's S3-compatible Cloud Storage that caches for GPU locality. You can set `disable_tls: True` for non-AWS services.
Check out the blog post between LMCache, Cohere, and CoreWeave: https://blog.lmcache.ai/en/2025/10/29/breaking-the-memory-barrier-how-lmcache-and-coreweave-power-efficient-llm-inference-for-cohere/
Configuration Parameters
------------------------
* **remote_url**: S3 bucket URL (`s3://bucket-name`)
* **save_unfull_chunk**: Save partial chunks (default: False, **must be False for S3**)
* **enable_async_loading**: Async loading (default: False)
* **blocking_timeout_secs**: Timeout seconds (default: 10)
S3-Specific (in extra_config)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* **s3_region**: AWS region for S3 client (required)
* **s3_num_io_threads**: Number of IO threads for the AWS CRT client to spawn. Benefits taper out after exceeding the number of CPU cores. This is also a way to restrict the number of outgoing requests in case your S3-compatible object store has a rate-limiting gateway.
* **s3_prefer_http2**: Enable HTTP/2 with ALPN negotiation (["h2", "http/1.1"])
* **s3_enable_s3express**: Enable S3 Express One Zone support in AWS CRT client
* **save_chunk_meta**: Whether to save chunk metadata in the object store along with your data (False required for S3)
* **aws_access_key_id**: AWS access key ID (or log in with `aws configure` in your environment)
* **aws_secret_access_key**: AWS secret access key (or log in with `aws configure` in your environment)
**Tips:**::
- Use same region for compute and S3
- Consider S3 Express One Zone for less redundancy but better performance
MP Mode Configuration
---------------------
In multi-process (MP) mode, S3 is configured as an L2 adapter via a JSON
spec passed to the LMCache server, rather than through ``remote_url`` +
``extra_config``. Each ``--l2-adapter`` argument takes a JSON object
whose ``"type": "s3"`` field selects the S3 adapter.
.. code-block:: json
{
"type": "s3",
"s3_endpoint": "s3://my-bucket",
"s3_region": "us-east-1",
"s3_num_io_threads": 64,
"s3_prefer_http2": true,
"s3_enable_s3express": false,
"disable_tls": false,
"max_capacity_gb": 500,
"eviction": {
"eviction_policy": "LRU",
"trigger_watermark": 0.85,
"eviction_ratio": 0.2
}
}
S3 L2 Adapter Fields
~~~~~~~~~~~~~~~~~~~~
* **type** (required): must be ``"s3"``.
* **s3_endpoint** (required): bucket URL. Accepts either ``s3://bucket`` or
the bare host form (e.g. ``bucket.s3.us-east-1.amazonaws.com``).
* **s3_region** (required): AWS region for the S3 client.
* **s3_num_io_threads**: number of CRT IO threads (default ``64``).
* **s3_prefer_http2**: attempt HTTP/2 via ALPN negotiation (default ``true``).
* **s3_enable_s3express**: enable S3 Express One Zone signing (default ``false``).
* **disable_tls**: bypass TLS, for non-AWS HTTP endpoints (default ``false``).
* **aws_access_key_id**, **aws_secret_access_key**: optional static credentials.
When omitted the adapter uses the AWS default credentials chain
(``aws configure``, environment variables, IRSA, etc.).
* **max_capacity_gb**: capacity used by ``get_usage()`` for watermark-based
L2 eviction. Set to ``0`` (default) to disable usage tracking — ``get_usage()``
then returns ``(-1.0, -1.0)`` and no automatic eviction is triggered.
* **eviction**: optional sub-dict enabling the L2 eviction controller for
this adapter. See :class:`L2AdapterConfigBase` ``_parse_eviction_config``
for the full schema. When present, keys that are currently being loaded
(reference-counted by the lookup-and-lock path) are skipped by
``delete()``.
Differences vs Non-MP S3
~~~~~~~~~~~~~~~~~~~~~~~~
* The MP adapter honors first-class eviction: it implements ``delete()``
(real S3 ``DeleteObject``), refcounted ``submit_unlock``, and
``get_usage()`` driven by ``max_capacity_gb``.
* Keys are identified by ``ObjectKey`` (``model_name`` + ``kv_rank`` +
``chunk_hash``) rather than ``CacheEngineKey``. The wire-format object
name is ``<model>@<kv_rank_hex>@<chunk_hash_hex>``, which is **not**
compatible with the non-MP naming. A bucket populated by non-MP LMCache
cannot be read directly by MP LMCache and vice versa.
* Unfull chunks are rejected (same constraint as non-MP).
@@ -0,0 +1,76 @@
SageMaker Hyperpod
==================
.. warning::
This page documents the behavior of LMCache's in-process mode (deprecated). Please consider using :doc:`LMCache MP mode </mp/index>` for better feature support and performance. For the MP mode equivalent of this page, see :doc:`/mp/l2_storage/index`.
Prerequisites
-------------
Create an Amazon SageMaker HyperPod cluster with tiered storage enabled by following the instructions at:
https://docs.aws.amazon.com/sagemaker/latest/dg/managed-tier-checkpointing-setup.html
This enables the ai-toolkit daemon that provides shared memory access for LMCache.
Example Configuration
---------------------
.. code-block:: yaml
chunk_size: 256
local_cpu: True
max_local_cpu_size: 5
remote_url: "sagemaker-hyperpod://$NODE_IP:9200"
Configuration Parameters
------------------------
SageMaker Hyperpod-Specific (in extra_config)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* **sagemaker_hyperpod_bucket**: Bucket name for KV storage namespace (default: "lmcache")
* **sagemaker_hyperpod_shared_memory_name**: Name of shared memory segment (default: "shared_memory"). Set to None to disable shared memory.
* **sagemaker_hyperpod_max_concurrent_requests**: Maximum concurrent HTTP requests allowed in-flight at any moment (application-level throttling, default: 100, minimum: 1). This limit is per LMCache engine instance. With multiple workers (e.g., high TP), each worker creates its own engine with separate limits.
* **sagemaker_hyperpod_max_connections**: Maximum total TCP connections in the connection pool per LMCache engine across all daemons (default: 256, minimum: 1). For typical single-daemon setups, this effectively limits connections from one engine to one daemon. With N workers per node, total connections to the daemon = N × this value.
* **sagemaker_hyperpod_max_connections_per_host**: Maximum TCP connections per LMCache engine to a single daemon address (IP:port) (default: 128, minimum: 1). "Host" refers to the daemon's network address, not the client machine. For today's typical single-daemon setup, this has similar effect as max_connections. This parameter enables future multi-daemon configurations where one engine connects to multiple daemons for load balancing. With N workers per node connecting to the same daemon, total connections = N × this value. Reduce proportionally for high TP setups (e.g., set to 16 for 8 workers to achieve ~128 total connections).
* **sagemaker_hyperpod_timeout_ms**: Timeout for lease acquisition requests in milliseconds (default: 5000, minimum: 100)
* **sagemaker_hyperpod_lease_ttl_s**: Server-side lease timeout in seconds (default: 30.0)
* **sagemaker_hyperpod_put_stream_chunk_bytes**: Chunk size for streaming PUT requests in bytes (default: 65536, minimum: 1024)
* **sagemaker_hyperpod_use_https**: Enable HTTPS instead of HTTP (default: False). **Note**: Ignored if ``remote_url`` already contains ``http://`` or ``https://`` protocol.
* **save_chunk_meta**: Whether to save chunk metadata with data (set False for performance)
Kubernetes Deployment Requirements
-----------------------------------
Environment Variable for Node IP
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Add the ``NODE_IP`` environment variable to resolve the local node's IP address:
.. code-block:: yaml
env:
- name: NODE_IP
valueFrom:
fieldRef:
fieldPath: status.hostIP
/dev/shm Volume Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SageMaker Hyperpod requires /dev/shm for high-performance shared memory operations:
.. code-block:: yaml
volumeMounts:
- name: dshm
mountPath: /dev/shm/shared_memory
subPath: shared_memory
volumes:
- name: dshm
hostPath:
path: /dev/shm
@@ -0,0 +1,205 @@
Valkey
======
.. warning::
This page documents the behavior of LMCache's in-process mode (deprecated). Please consider using :doc:`LMCache MP mode </mp/index>` for better feature support and performance. For the MP mode equivalent of this page, see :doc:`/mp/l2_storage/resp`.
Overview
--------
Valkey is an open source (BSD) high-performance key/value datastore and is a supported option for remote KV Cache offloading in LMCache.
Some other remote backends are :doc:`Mooncake <./mooncake>`, :doc:`Redis <./redis>`, and :doc:`InfiniStore <./infinistore>`.
Prerequisites
-------------
To use this connector, you need valkey-glide 2.0 or higher.
.. code-block:: shell
# Install Valkey-GLIDE (Minimum 2.0.0 or higher)
$ pip install valkey-glide
Configuration Reference
-----------------------
The following ``extra_config`` keys are supported:
.. list-table::
:header-rows: 1
:widths: 30 15 55
* - Key
- Default
- Description
* - ``valkey_num_workers``
- ``8``
- Number of worker threads, each with its own GLIDE client connection.
* - ``valkey_mode``
- ``"standalone"``
- ``"standalone"`` or ``"cluster"``. Cluster mode auto-discovers topology from a seed node.
* - ``tls_enable``
- ``false``
- Enable TLS. Required for ElastiCache Serverless.
* - ``valkey_username``
- ``""``
- Authentication username.
* - ``valkey_password``
- ``""``
- Authentication password.
* - ``valkey_database``
- None
- Database ID (standalone mode only, ignored in cluster mode).
* - ``valkey_enable_ttl``
- ``false``
- Feature flag. When ``true``, every key is written with an expiry (see ``valkey_ttl_sec``) so Valkey/Redis ``volatile-*`` eviction policies can reclaim L2 cache keys once the node reaches ``maxmemory``. When ``false`` (default), keys are persisted without a TTL.
* - ``valkey_ttl_sec``
- ``86400``
- Key TTL in seconds, applied only when ``valkey_enable_ttl`` is ``true``. Must be a positive integer. If the flag is enabled but this key is omitted, it defaults to ``86400`` (24 hours).
* - ``request_timeout``
- ``5.0``
- GLIDE request timeout in seconds. Also used as the Python-side Future timeout.
* - ``connection_timeout``
- ``10.0``
- GLIDE connection timeout in seconds for initial client connections.
Example Configurations
----------------------
Standalone-mode
~~~~~~~~~~~~~~~~
Basic Valkey Configuration (Standalone-mode)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: yaml
chunk_size: 256
remote_url: "valkey://<your host>:6379"
remote_serde: "naive"
extra_config:
valkey_username: "Your username"
valkey_password: "Your password"
Standalone-mode Valkey Configuration with database
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: yaml
chunk_size: 256
remote_url: "valkey://<your host>:6379"
remote_serde: "naive"
extra_config:
valkey_username: "Your username"
valkey_password: "Your password"
valkey_database: 0
Standalone-mode Valkey Configuration with key TTL (volatile eviction)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Enable a per-key TTL so a node configured with a ``volatile-lru`` /
``volatile-lfu`` eviction policy can reclaim L2 cache keys once it reaches
``maxmemory``. Without a TTL such policies never evict the KV cache keys,
which can choke the remote cache.
.. code-block:: yaml
chunk_size: 256
remote_url: "valkey://<your host>:6379"
remote_serde: "naive"
extra_config:
valkey_username: "Your username"
valkey_password: "Your password"
valkey_enable_ttl: true
valkey_ttl_sec: 3600 # keys expire after 1 hour
Cluster-mode
~~~~~~~~~~~~~~~~
Cluster-mode Valkey Configuration (Endpoint)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For example, the configuration endpoint in ElastiCache is as follows:
<cache-name>.<identifier>.clustercfg.<region>.cache.amazonaws.com
You need to add this DNS name in the <your host>.
.. code-block:: yaml
chunk_size: 256
remote_url: "valkey://<your host>:6379"
remote_serde: "naive"
extra_config:
valkey_mode: "cluster"
valkey_username: "Your username"
valkey_password: "Your password"
Cluster-mode Valkey Configuration (Nodes)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Nodes are deployed directly and configured in cluster mode without connecting them via DNS names (CNAME).
In this scenario, you simply input multiple IP hosts and ports.
Example: 172.0.0.1:7001, 172.0.0.2:7002 ... 172.0.0.6:7006
.. code-block:: yaml
chunk_size: 256
remote_url: "valkey://<your host 1>:<your port 1>, <your host 2>:<your port 2>, ... <your host N>:<your port N>"
remote_serde: "naive"
extra_config:
valkey_mode: "cluster"
valkey_username: "Your username"
valkey_password: "Your password"
Cluster-mode Valkey Configuration with numbered databases (Valkey 9.0+ and Valkey-GLIDE 2.1+)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Valkey connector supports numbered databases in both the Endpoint using DNS and the Nodes method using IP and port pairs.
.. code-block:: yaml
chunk_size: 256
remote_url: "valkey://<your host>:6379"
remote_serde: "naive"
extra_config:
valkey_mode: "cluster"
valkey_username: "Your username"
valkey_password: "Your password"
valkey_database: 1
TLS / ElastiCache Serverless
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ElastiCache Serverless requires TLS. Set ``tls_enable: true``:
.. code-block:: yaml
chunk_size: 256
remote_url: "valkey://<serverless-endpoint>:6379"
remote_serde: "naive"
extra_config:
valkey_mode: "cluster"
tls_enable: true
valkey_num_workers: 32
Performance Tuning
~~~~~~~~~~~~~~~~~~
For large models (e.g., 70B with TP=8), increase the worker count for higher throughput:
.. code-block:: yaml
chunk_size: 256
remote_url: "valkey://<your host>:6379"
remote_serde: "naive"
pre_caching_hash_algorithm: sha256_cbor_64bit # Required for TP>1
extra_config:
valkey_mode: "cluster"
valkey_num_workers: 32
@@ -0,0 +1,137 @@
Weka
====
.. warning::
This page documents the behavior of LMCache's in-process mode (deprecated). Please consider using :doc:`LMCache MP mode </mp/index>` for better feature support and performance. For the MP mode equivalent of this page, see :doc:`/mp/l2_storage/index`.
.. _weka-overview:
Overview
--------
WekaFS is a high-performance, distributed filesystem and is a supported option for KV Cache offloading in
LMCache. Even though the local filesystem backend can work with a WekaFS mount, this particular backend is
optimized for Weka's characteristics. It leverages GPUDirect Storage for I/O and it allows data-sharing
between multiple LMCache instances.
Ways to configure LMCache WEKA Offloading
-----------------------------------------
**1. Environment Variables:**
.. code-block:: bash
# 256 Tokens per KV Chunk
export LMCACHE_CHUNK_SIZE=256
# Path to Weka Mount
export LMCACHE_GDS_PATH="/mnt/weka/cache"
# GDS Buffer Size in MiB
export LMCACHE_GDS_BUFFER_SIZE="8192"
# Disabling CPU RAM offload is sometimes recommended as the
# CPU can get in the way of GPUDirect operations
export LMCACHE_LOCAL_CPU=False
# GDS I/O Threads
export LMCACHE_EXTRA_CONFIG='{"disk_io_threads": 32}'
**2. Configuration File**:
Passed in through ``LMCACHE_CONFIG_FILE=your-lmcache-config.yaml``
Example ``config.yaml``:
.. code-block:: yaml
# 256 Tokens per KV Chunk
chunk_size: 256
# Disable local CPU
local_cpu: false
# Path to Weka Mount
gds_path: "/mnt/weka/cache"
# GDS Buffer Size in MiB
gds_buffer_size: 8192
# GDS I/O Threads
extra_config:
disk_io_threads: 32
GDS Buffer Size Explanation
---------------------------
The backend currently pre-registers buffer space to speed up GDS operations. This buffer space
is registered in VRAM so options like ``--gpu-memory-utilization`` from ``vllm`` should be considered
when setting it. For example, a good rule of thumb for H100 which generally has 80GiBs of VRAM would
be to start with 8GiB and set ``--gpu-memory-utilization 0.85`` and depending on your workflow fine-tune
it from there.
Setup Example
-------------
.. _weka-prerequisites:
**Prerequisites:**
- A Machine with at least one GPU. You can adjust the max model length of your vllm instance depending on your GPU memory.
- Weka already installed and mounted.
- vllm and lmcache installed (:doc:`Installation Guide <../../getting_started/installation>`)
- Hugging Face access to ``meta-llama/Llama-3.1-8B-Instruct``
.. code-block:: bash
export HF_TOKEN=your_hugging_face_token
**Step 1. Create cache directory under your Weka mount:**
To find all your WekaFS mounts run:
.. code-block:: bash
mount -t wekafs
For the sake of this example let's say that the above returns:
.. code-block:: text
10.27.1.1/default on /mnt/weka type wekafs (rw,relatime,writecache,inode_bits=auto,readahead_kb=32768,dentry_max_age_positive=1000,dentry_max_age_negative=0,container_name=client)
Then create a directory under it (the name here is arbitrary):
.. code-block:: bash
mkdir /mnt/weka/cache
**Step 2. Start a vLLM server with Weka offloading enabled:**
Create a an lmcache configuration file called: ``weka-offload.yaml``
.. code-block:: yaml
local_cpu: false
chunk_size: 256
gds_path: "/mnt/weka/cache"
gds_buffer_size: 8192
extra_config:
disk_io_threads: 32
If you don't want to use a config file, uncomment the first three environment variables
and then comment out the ``LMCACHE_CONFIG_FILE`` below:
.. code-block:: bash
# LMCACHE_LOCAL_CPU=False \
# LMCACHE_CHUNK_SIZE=256 \
# LMCACHE_GDS_PATH="/mnt/weka/cache" \
# LMCACHE_GDS_BUFFER_SIZE=8192 \
# LMCACHE_EXTRA_CONFIG='{"disk_io_threads": 32}' \
LMCACHE_CONFIG_FILE="weka-offload.yaml" \
vllm serve \
meta-llama/Llama-3.1-8B-Instruct \
--max-model-len 65536 \
--kv-transfer-config \
'{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}'