Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 53 additions & 17 deletions Dockerfile.quick-start-sglang
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,28 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

# QS_MODE selects the SGLang integration in docs/quick-start-sglang.md: hicache or
# linker. They are separate SGLang PR branches, so each mode pins its own SGLang
# and Dynamo revisions.
ARG QS_MODE=hicache
ARG SGLANG_IMAGE=lmsysorg/sglang:v0.5.21-cu130-runtime@sha256:797614a377ead47cf89ccaba810685b56a1bf0d6f0c45f84af2e8f42c198e104

FROM rust:1.96.1-bookworm@sha256:d99f7b31f49909348dc59b51f3c95d1efded1701ffb222f095aaab7de3c4abd8 AS sglang-dynamo-builder
RUN apt-get update && apt-get install -y --no-install-recommends \
cmake git libclang-dev libhwloc-dev libprotobuf-dev libssl-dev libudev-dev \
pkg-config protobuf-compiler python3-dev python3-pip python3-venv \
&& rm -rf /var/lib/apt/lists/*
ARG SGLANG_DYNAMO_REF=83ea37e849e883c27d7ed848e0f329961e1fb173
RUN git init /src/dynamo \
ARG QS_MODE
RUN case "${QS_MODE}" in \
hicache) ref=83ea37e849e883c27d7ed848e0f329961e1fb173 ;; \
linker) ref=fe81cfdccedc23b4eabef3a250b538ff11ba102c ;; \
*) echo "QS_MODE must be hicache or linker" >&2; exit 1 ;; \
esac \
&& git init /src/dynamo \
&& git -C /src/dynamo remote add origin https://github.com/ai-dynamo/dynamo.git \
&& git -C /src/dynamo fetch --depth 1 origin "${SGLANG_DYNAMO_REF}" \
&& git -C /src/dynamo fetch --depth 1 origin "${ref}" \
&& git -C /src/dynamo checkout --detach FETCH_HEAD \
&& test "$(git -C /src/dynamo rev-parse HEAD)" = "${SGLANG_DYNAMO_REF}"
&& test "$(git -C /src/dynamo rev-parse HEAD)" = "${ref}"
RUN python3 -m venv /venv \
&& /venv/bin/pip install --no-cache-dir 'maturin[patchelf]==1.9.6'
ENV CARGO_BUILD_JOBS=4
Expand All @@ -24,16 +33,20 @@ RUN /venv/bin/pip wheel --no-deps /src/dynamo --wheel-dir /wheels

FROM ${SGLANG_IMAGE} AS sglang
USER root
ARG SGLANG_REF=658755604ace9f476b84f98511f76b815606072a
ARG QS_MODE
RUN test "$(uname -m)" = x86_64 \
&& apt-get update && apt-get install -y --no-install-recommends \
git curl libatomic1 libhwloc15 libnuma1 libssl3t64 libudev1 \
&& rm -rf /var/lib/apt/lists/*
RUN git init /opt/sglang-src \
RUN case "${QS_MODE}" in \
hicache) ref=24a3a79ed3ed3ee5811f78ba2c4cbd5ceaac27d1 ;; \
linker) ref=002d6d8d1df91a58695a8868ce2c2db590809f75 ;; \
esac \
&& git init /opt/sglang-src \
&& git -C /opt/sglang-src remote add origin https://github.com/ziqifan617/sglang.git \
&& git -C /opt/sglang-src fetch --depth 1 origin "${SGLANG_REF}" \
&& git -C /opt/sglang-src fetch --depth 1 origin "${ref}" \
&& git -C /opt/sglang-src checkout --detach FETCH_HEAD \
&& test "$(git -C /opt/sglang-src rev-parse HEAD)" = "${SGLANG_REF}"
&& test "$(git -C /opt/sglang-src rev-parse HEAD)" = "${ref}"
# Install KVCR from the checkout used as the Docker build context.
COPY pyproject.toml README.md LICENSE /opt/kvcr-src/
COPY src /opt/kvcr-src/src
Expand Down Expand Up @@ -80,21 +93,18 @@ ENV DYN_REQUEST_PLANE=tcp DYN_EVENT_PLANE=zmq DYN_DISCOVERY_BACKEND=file \
DYN_FILE_KV=/tmp/dynamo-sglang-quick-start DYN_SYSTEM_HOST=127.0.0.1 \
DYN_RUNTIME_NUM_WORKER_THREADS=4 DYN_RUNTIME_MAX_BLOCKING_THREADS=8 \
TOKENIZERS_PARALLELISM=false PYTHONHASHSEED=0 RUST_LOG=info \
UCX_TLS=tcp,sm,self,cuda_copy OMP_NUM_THREADS=4
UCX_TLS=tcp,sm,self,cuda_copy OMP_NUM_THREADS=4 SGLANG_JIT_DEEPGEMM_PRECOMPILE=0
# Import/version canary only; model/peer-transfer acceptance is in the guide.
RUN python3 - <<'PY'
import os
from importlib import metadata
from pathlib import Path
import tomllib
import dynamo.sglang
import torch
from dynamo.common.constants import KV_HINT_TRANSFER_CAPABILITY_KEY
from dynamo.llm import WorkerType
from dynamo.sglang import kv_hints
from dynamo.sglang.state_agent import StateAgentLifecycle
from kvcr import ZmqPeerControlChannel
from sglang.srt.mem_cache.hicache_storage import HiCacheStorage
from sglang.srt.mem_cache.storage.kvcr.kvcr_store import KVCRStore

for line in Path('/opt/sglang-constraints.txt').read_text().splitlines():
name, expected = line.split('==')
Expand All @@ -103,15 +113,41 @@ for line in Path('/opt/sglang-constraints.txt').read_text().splitlines():
print(f'{name}=={actual}')
assert torch.version.cuda == '13.0'
assert KV_HINT_TRANSFER_CAPABILITY_KEY == 'router_hint'
assert callable(KVCRStore.batch_get_v1)
assert callable(HiCacheStorage.get_kv_hint_source)
assert callable(StateAgentLifecycle.start)
assert callable(ZmqPeerControlChannel)
if os.environ['QS_MODE'] == 'hicache':
from dynamo.sglang import kv_hints
from dynamo.sglang.state_agent import StateAgentLifecycle
from sglang.srt.mem_cache.hicache_storage import HiCacheStorage
from sglang.srt.mem_cache.storage.kvcr.kvcr_store import KVCRStore

assert callable(KVCRStore.batch_get_v1)
assert callable(HiCacheStorage.get_kv_hint_source)
assert callable(StateAgentLifecycle.start)
else:
from dynamo.sglang.kv_hints import is_supported_kvcr_linker_topology
from dynamo.sglang.state_agent import state_agent_descriptors
from kvcr.hints.fetch import KV_FETCH
from kvcr.hints.parser import parse_hints
from sglang.srt.managers.kv_hints import decode_kv_hints_envelope
from sglang.srt.mem_cache.storage.kvcr.kvcr_direct_linker import KVCRDirectLinker

assert callable(is_supported_kvcr_linker_topology) and callable(state_agent_descriptors)
assert KVCRDirectLinker.local_storage_ownership == 'kvcr'
hint = decode_kv_hints_envelope({
'protocol_version': '0.1', 'message_id': 'canary',
'actions': [{'action_id': 'fetch', 'action_type': 'kv.fetch',
'action_version': '1.0', 'payload': {
'source_control_endpoint': 'tcp://127.0.0.1:19500',
'block_hashes': [1],
}}],
})
parsed, rejected = parse_hints(hint, {'kv.fetch': KV_FETCH})
assert len(parsed) == 1 and not rejected
assert metadata.version('ai-dynamo') == '1.7.0'
assert metadata.version('ai-dynamo-runtime') == '1.7.0'
kvcr_project = tomllib.loads(Path('/opt/kvcr-src/pyproject.toml').read_text())['project']
assert metadata.version('kvcr') == kvcr_project['version']
print('SGLang HiCache import/version checks passed; GPU verification still required')
print(f"SGLang {os.environ['QS_MODE']} import/version checks passed; GPU verification still required")
PY
RUN python3 -m pip freeze --all > /opt/sglang-installed-packages.txt
WORKDIR /workspace
Expand Down
9 changes: 6 additions & 3 deletions Dockerfile.quick-start-vllm
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

ARG VLLM_IMAGE=vllm/vllm-openai:nightly-edad8b03c5285a1c12e7e4f83c1af709b319a01b@sha256:2e735ffeef26457f8a3ff44a62ae182870b10af37adc06ecbb08baca152365c6
ARG VLLM_IMAGE=vllm/vllm-openai:nightly-40941f2c4ba96fba0a7e282a5355e89cd74d9ac5@sha256:298b8765981d9dabda0da1504487bcdca876d5c18e8533455b3fda65ae964ba2

# Keep the vLLM image, adapter source, and Dynamo revision compatible.
FROM rust:1.93-bookworm AS build-base
Expand All @@ -14,7 +14,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
# Pin the merged vLLM KVCR integration to an exact commit SHA.
FROM build-base AS vllm-kvcr-source
ARG KVCR_VLLM_REPO=https://github.com/vllm-project/vllm.git
ARG KVCR_VLLM_REF=fed7de909f894fbee0c45130376aed2e2c9bf0d3
ARG KVCR_VLLM_REF=a5db4c9ff58a0ec4dc20594ba5cea9f776dcd693
RUN git init /src/vllm-kvcr \
&& git -C /src/vllm-kvcr remote add origin "${KVCR_VLLM_REPO}" \
&& git -C /src/vllm-kvcr fetch --depth 1 origin "${KVCR_VLLM_REF}" \
Expand All @@ -37,7 +37,7 @@ RUN sed -i 's/^from kvcr\.control_channels import ZmqPeerControlChannel$/from kv
# Pin a landed Dynamo revision with the versioned KV hint protocol and
# per-rank KVCR control ports.
FROM build-base AS dynamo-builder
ARG DYNAMO_REF=701da25292ecca8f64d770cc0b17d389200019a1
ARG DYNAMO_REF=318ab0390d8e3e1fa7517ea6a1ebf46a7854c236
RUN git clone --filter=blob:none https://github.com/ai-dynamo/dynamo.git /src/dynamo \
&& git -C /src/dynamo fetch --depth 1 origin "${DYNAMO_REF}" \
&& git -C /src/dynamo checkout --detach "${DYNAMO_REF}"
Expand Down Expand Up @@ -97,6 +97,9 @@ import inspect
from types import SimpleNamespace

import dynamo.vllm
# The worker entry point: fails here, not at `python -m dynamo.vllm`, when the
# pinned Dynamo and vLLM disagree on an internal API.
import dynamo.vllm.main
import kvcr
import vllm
from dynamo.common.constants import KV_HINT_TRANSFER_CAPABILITY_KEY
Expand Down
23 changes: 6 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@
<img src="docs/figures/kvcr-masthead.jpg" alt="KV Cache Runner - Data Plane Architecture" width="100%">
</p>

> [!NOTE]
> **Initial release (experimental).** KVCR supports cross-node DRAM sharing, local disk caching, and resiliency for vLLM. Future releases will add SGLang and TRT-LLM integrations and object storage support.
>
> Feedback and contributions are welcome - see [CONTRIBUTING.md](CONTRIBUTING.md).

# KV Cache Runner

Modern AI workloads require KV cache infrastructure that delivers speed, scale, and resilience.
Expand Down Expand Up @@ -38,7 +33,7 @@ For more details, see the [design document](docs/design_overview.md).

## Framework support

KVCR is vendor and framework agnostic, with no dependency on a specific request router. The list below summarizes framework integrations that are complete or in progress. We plan to extend support to additional frameworks and routers in the future.
KVCR is vendor and framework agnostic and can integrate with any request router that supports the KV Hint Protocol. The list below summarizes framework integrations that are complete or in progress. We plan to extend support to additional frameworks and routers in the future.

### KV Hint Protocol

Expand All @@ -51,7 +46,7 @@ KVCR is vendor and framework agnostic, with no dependency on a specific request
- SGLang - supported
- Additional features tracked in [RFC #36224](https://github.com/sgl-project/sglang/issues/36224).

- Dynamo KV Router - supported
- [Dynamo KV Router](https://docs.nvidia.com/dynamo/dev/knowledge-base/modular-components/router/overview) - supported

A hint envelope may carry several hints. KVCR parses and dispatches each one independently and records a per-hint outcome.

Expand All @@ -63,23 +58,17 @@ A hint envelope may carry several hints. KVCR parses and dispatches each one ind

- vLLM - supported

- SGLang
- [[RFC] KVCR as a HiCacheStorage backend for peer-to-peer KV reuse #32903](https://github.com/sgl-project/sglang/issues/32903)


### Routers

- [Dynamo KV Router](https://docs.nvidia.com/dynamo/dev/knowledge-base/modular-components/router/overview)
- [sgl-router](https://github.com/sgl-project/sglang/tree/main/experimental/sgl-router)
- [llmd-router](https://llm-d.ai/docs/dev/architecture/core/router)
- SGLang - supported
- HiCache storage backend: [#45647](https://github.com/sgl-project/sglang/pull/45647)
- Direct linker (experimental, TP1 for now): [#45650](https://github.com/sgl-project/sglang/pull/45650)

## Using KVCR

### Quick start

The [vLLM quick start](docs/quick-start-vllm.md) is a public preview that builds and runs KVCR with vLLM, Dynamo, and NIXL using pinned source revisions and a compatible base image. See the [developer guide](docs/dev-guide.md#integrate-with-vllm-and-dynamo-optional) for source installation and verification.

The [SGLang HiCache quick start](docs/quick-start-sglang.md) is a preview for building and running KVCR with SGLang and Dynamo.
The [SGLang quick start](docs/quick-start-sglang.md) is a preview for building and running KVCR with SGLang and Dynamo, through either the HiCache storage backend or the experimental direct linker.

### Development

Expand Down
24 changes: 17 additions & 7 deletions docs/design_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,21 +253,31 @@ The router maintains an eventually-consistent global prefix inventory for cache-

Framework-owned memory is reported through the framework's existing router event path. If needed, `tier_enter`/`tier_exit` reporting or `inventory_snapshot()` can be added to the KVCR API later.

KVCR-owned inventory events use the same `BlockKey` values as fetch and `submit_hint` in the normal case. KVCR deliberately reports only the affected keys, tier, and whether the residency was removed. KVCR delivers each event to `inventory_sink`; the framework or local indexer owns any buffering or batching for publication. Each event may contain multiple keys and is tier-specific because one block may be present in several tiers at once.
KVCR-owned inventory events use the same full `BlockKey` values as fetch. Request-scoped hints instead carry hashes interpreted by the configured key adapter. KVCR deliberately reports only the affected keys, tier, and whether the residency was removed. KVCR delivers each event to `inventory_sink`; the framework or local indexer owns any buffering or batching for publication. Each event may contain multiple keys and is tier-specific because one block may be present in several tiers at once.

### Request-scoped hints

For data shared among engines on the same node, the router can use one KVCR as the source in hints sent to the other local KVCRs. A destination may retain a local copy when replication is worthwhile. Otherwise, the router includes `no_retain` in `submit_hint`. The destination still satisfies any committed framework request; `no_retain` only advises it not to keep an additional KVCR-owned copy after the framework no longer needs it.
A `kv.fetch` hint identifies a source control endpoint and advertised block hashes for one request. `submit_hint` stores this advice and may establish the peer connection, but starts no block transfer or remote pinning. The framework supplies full `BlockKey` values through later `fetch()` or `deliver()` calls with the same request ID; the configured key adapter matches those keys against the hint hashes. Both calls copy without requesting source removal.

KVCR-to-KVCR movement is destination-initiated. A router hint gives the destination the source node and KVCR control endpoint. `mode=copy` retains the source copy, while `mode=move` permits source eviction only after successful completion. Timeout or cancellation keeps the source copy. When the hint has no source control endpoint, the destination uses its local storage information or checks object storage when enabled. The router does not need object-store inventory for locality decisions, but may track object presence to issue source hints.
Failed or conflicting advice does not prevent local cache hits. `discard_hint` clears request advice without cancelling already-started operations. A `kv.fetch` without a request ID does not execute an independent operation. Source-less hints and `no_retain` are unsupported.

`submit_hint` may establish the peer connection, but does not start data movement or remote pinning yet. Each hint in an envelope is parsed and handled independently; proactive fetching, staging, or pinning may be added later as new hint types. `discard_hint` normally reports that the relevant request is over, and also permits an integration to discard the request-scoped hint early. A route-time `submit_hint` may carry a whole `BlockKeyList` from one source. In the future, if necessary, multi-source assembly can be added by splitting the list into multiple hinted operations.
We plan to add a request-scoped `kv.migrate` hint to move KV cache for cold starts.

The router may choose not to send a hint based on its internal cost model. Router hints may later be extended to request proactive copy or move for cache rebalancing, which may require periodic utilization reports from KVCR. The router also considers sending hints for matching TP layouts, meaning prefill-to-prefill, decode-to-decode, and aggregated deployments, which cover many use cases. A TP-independent host representation further removes that constraint.
### Independent hints

`kv.prefetch` copies cached KV blocks; `kv.move` also requests removal of the source copy. Each hint names a source and block hashes and runs independently of its carrier request's keys and lifetime. Request advice and independent actions can share an envelope, and independent actions also work without a request ID.

The destination first resolves the hashes into full keys and layouts, validates the reply, then transfers complete blocks from the source's KVCR-managed DRAM (G2). See [hash-addressed operations](hash_management.md) for detailed contracts.

### Move and source invalidation

For moves, the source withdraws eligible blocks from new access before transferring them. After a successful transfer, it removes their G2 copies once existing readers finish. A failed transfer keeps the source copy and reopens access. If the destination already has complete copies, it sends invalidation without copying them again.

Removal is best effort and honors `no_evict`; framework and G3 copies remain. Destination completion reports data readiness, while source cleanup may finish later. Source removal does not wait for destination acknowledgement.

### Peer control and liveness

The router is never on the data path: KVCR instances execute transfers peer-to-peer via NIXL. Source endpoints remain request-scoped rather than becoming part of global block state, so this coordination adds no KVCR-side peer inventory.
The router is never on the data path: KVCR instances execute transfers peer-to-peer via NIXL. Source endpoints belong to request advice or an independent operation rather than global block state, so this coordination adds no KVCR-side peer inventory.

Peer transfers use a separate control channel for connection metadata, acknowledgements, and transfer control; payload bytes move directly through NIXL and never traverse the router or control channel. Peer-protocol versioning and compatibility checks may be added as needed.

Expand Down Expand Up @@ -313,7 +323,7 @@ At initialization, an integration selects one built-in policy or supplies an ext

Eviction scores are finite floats. LRU favors evicting later sequence keys at comparable recency. Custom policies may use ordering position or ignore it.

Policy decides whether to stage, retain, move, or drop data within KVCR-owned DRAM and downstream storage. Fetch and `no_evict` deposit claims are hard mechanism constraints: while they exist, policy cannot evict the entry. `no_retain` is an advisory router retention hint. If it conflicts with a framework `no_evict` claim, `no_evict` wins until the framework calls `release` with the returned handle; policy may then honor or override `no_retain`.
Policy decides whether to stage, retain, move, or drop data within KVCR-owned DRAM and downstream storage. Fetch and `no_evict` deposit claims prevent eviction while held. Move uses the source invalidation rules above rather than a policy eviction decision. `no_retain` is not currently supported.

The policy methods are invoked as follows:

Expand Down
Loading