Skip to content

Commit f508d6e

Browse files
committed
feat(gms): add vLLM and SGLang host-tier adapters
Signed-off-by: mkhadkevich <mkhadkevich@nvidia.com>
1 parent 5275c65 commit f508d6e

23 files changed

Lines changed: 10228 additions & 0 deletions
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""SGLang adapter for the gms_kv_ring handle.
5+
6+
`install_for_sglang(...)` monkey-patches SGLang's RadixCache so that:
7+
8+
• on eviction, evicted blocks are pushed to the evict ring
9+
• on cache hit, restored block_id pairs are pushed to the restore
10+
ring and the compute stream waits on the per-slot counter
11+
12+
The monkey-patch is intentionally small. Engines pass us their
13+
existing block-table machinery; we only intercept eviction and
14+
restore call-sites.
15+
16+
NOTE: this file imports SGLang lazily (only inside `install_for_sglang`)
17+
so unit tests for the handle don't require SGLang to be importable.
18+
"""
19+
20+
from gms_kv_ring.engines.sglang.install_kv_ring import install_for_sglang
21+
22+
__all__ = ["install_for_sglang"]
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""GDS-direct connector glue for SGLang's RadixCache / HiCache.
5+
6+
The connector logic is engine-agnostic — see
7+
`gms_kv_ring.engines.gds_block_connector.BlockGdsConnector`. This
8+
module re-exports it under an SGLang-flavored name and documents
9+
the SGLang-specific integration recipe.
10+
11+
SGLang specifics:
12+
- RadixCache nodes carry token-index slots into the KV pool.
13+
What vLLM calls "block_id," SGLang calls a per-token slot
14+
index. The "block" we offload to storage is typically a fixed
15+
page (e.g., 64 tokens) that aligns with SGLang's `page_size`.
16+
- For block-granular GDS spill/restore, the caller assembles
17+
page-aligned slot groups and treats each as a "block id."
18+
19+
Integration recipe:
20+
21+
from gms_kv_ring.engines.sglang.install_kv_ring import install_for_sglang
22+
from gms_kv_ring.engines.sglang.gds_connector import SGLangGdsConnector
23+
24+
handle = install_for_sglang(
25+
engine_id=..., daemon_socket=..., layers=...,
26+
)
27+
28+
def block_layout(page_id):
29+
# SGLang specifics: layers laid out separately, each layer
30+
# has shape (num_pages, page_size_bytes). One "block" here
31+
# = one page across all attention layers.
32+
return [(L, page_id * page_size_bytes, page_size_bytes)
33+
for L in range(n_layers)]
34+
35+
gds = SGLangGdsConnector(handle, block_layout)
36+
37+
# In RadixCache.evict(num_tokens):
38+
# - Pick victim pages (existing LRU/freq logic in SGLang).
39+
# - If gds.is_available(), spill them to durable storage
40+
# so they survive engine restart and can be promoted on
41+
# re-access. Otherwise use the standard host_tier path.
42+
def evict(self, num_tokens):
43+
victim_pages = self._select_victims(num_tokens)
44+
if gds.is_available():
45+
gds.evict_blocks_to_storage(victim_pages)
46+
else:
47+
# standard host_tier path via handle.record_evict per
48+
# victim, same as the existing on_evict hook.
49+
...
50+
51+
# On match_prefix cache hit, before serving from HBM:
52+
# - Identify the hit pages.
53+
# - If they're currently in storage (because they were
54+
# previously evicted), promote them back to HBM in-place
55+
# so the attention path can read from the pool as usual.
56+
def restore_prefix(self, hit_pages):
57+
if gds.is_available():
58+
ok = gds.restore_blocks_from_storage(hit_pages)
59+
# ok[page_id]=False → engine must fall to cold compute
60+
# for that page (the slot is "missing" from the cache).
61+
return ok
62+
else:
63+
# standard host_tier promote + restore_ring path
64+
...
65+
"""
66+
67+
from __future__ import annotations
68+
69+
from gms_kv_ring.engines.gds_block_connector import BlockGdsConnector, EvictResult
70+
71+
# Engine-flavored alias. Same class; SGLang-specific recipe lives
72+
# in this module's docstring above.
73+
SGLangGdsConnector = BlockGdsConnector
74+
75+
__all__ = ["SGLangGdsConnector", "EvictResult"]
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""Wire a GMSKvRing handle into SGLang's RadixCache.
5+
6+
Public entry point: `install_for_sglang(engine_id, daemon_socket,
7+
layer_descs, *, on_evict=None, on_hit=None)`.
8+
9+
The function returns a `GMSKvRing` handle. The caller (engine
10+
bootstrap) is responsible for closing it on engine shutdown.
11+
12+
The on_evict / on_hit hooks the caller can pass in are optional —
13+
if omitted, we install a default monkey-patch on
14+
`sglang.srt.mem_cache.radix_cache.RadixCache.evict` and on the
15+
`match_prefix` path that publishes the obvious record sets.
16+
17+
Why callback-injection instead of always monkey-patching: SGLang's
18+
internals shift between releases (we've seen #199 hangs from
19+
cross-stream sync). Giving the engine the option to call
20+
`handle.record_evict(...)` from its own well-tested hook is safer
21+
than auto-patching across SGLang versions.
22+
"""
23+
24+
from __future__ import annotations
25+
26+
import logging
27+
from typing import Callable, Optional
28+
29+
from gms_kv_ring.engines.handle import GMSKvRing
30+
31+
logger = logging.getLogger(__name__)
32+
33+
34+
def install_for_sglang(
35+
*,
36+
engine_id: str,
37+
daemon_socket: str,
38+
layers: list[dict],
39+
on_evict: Optional[Callable[[GMSKvRing], None]] = None,
40+
on_hit: Optional[Callable[[GMSKvRing], None]] = None,
41+
evict_ring_capacity: int = 4096,
42+
restore_ring_capacity: int = 4096,
43+
num_counters: int = 512,
44+
) -> GMSKvRing:
45+
"""Build the handle and (optionally) wire SGLang hooks to it.
46+
47+
Parameters
48+
----------
49+
engine_id : str
50+
Stable per-engine identifier. Use the same value across
51+
restarts so the daemon can reuse host-tier slots.
52+
daemon_socket : str
53+
Unix-socket path the daemon is listening on.
54+
layers : list[dict]
55+
Per-layer descriptors. Each entry: {layer_idx, va, size, stride}.
56+
`va` is the engine-process virtual address of the layer base
57+
(i.e. the engine has already CUDA-mapped the pool). The
58+
daemon's pool attachment will rely on these.
59+
on_evict, on_hit : callable | None
60+
Optional callbacks invoked exactly once with the handle, so
61+
the caller can wire its own engine hooks. If None, this is a
62+
plain-handle install — useful for tests and for callers that
63+
want to wire their hooks explicitly.
64+
"""
65+
handle = GMSKvRing(
66+
engine_id=engine_id,
67+
daemon_socket=daemon_socket,
68+
layers=layers,
69+
evict_ring_capacity=evict_ring_capacity,
70+
restore_ring_capacity=restore_ring_capacity,
71+
num_counters=num_counters,
72+
)
73+
if on_evict is not None:
74+
on_evict(handle)
75+
if on_hit is not None:
76+
on_hit(handle)
77+
logger.info("gms_kv_ring installed for SGLang engine %r", engine_id)
78+
return handle
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""vLLM adapter for the gms_kv_ring handle.
5+
6+
Same shape as the SGLang adapter: callers pass layer descriptors,
7+
get back a `GMSKvRing` handle, and wire `record_evict` / `record_restore`
8+
into the engine's eviction (KVCacheConnector.evict_kv_blocks) and
9+
cache-hit (KVCacheConnector.start_load_kv) call-sites.
10+
"""
11+
12+
from gms_kv_ring.engines.vllm.install_kv_ring import install_for_vllm
13+
14+
__all__ = ["install_for_vllm"]
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""GDS-direct connector glue for vLLM's KVCacheConnectorV1.
5+
6+
The connector logic is engine-agnostic — see
7+
`gms_kv_ring.engines.gds_block_connector.BlockGdsConnector`. This
8+
module re-exports it under a vLLM-flavored name and documents the
9+
specific integration recipe for vLLM's KVCacheConnectorV1.
10+
11+
Integration recipe (inside a vLLM KVCacheConnectorV1 implementation):
12+
13+
from gms_kv_ring.engines.vllm.install_kv_ring import install_for_vllm
14+
from gms_kv_ring.engines.vllm.gds_connector import VllmGdsConnector
15+
16+
handle = install_for_vllm(
17+
engine_id=..., daemon_socket=..., layers=...,
18+
)
19+
20+
def block_layout(block_id):
21+
# vLLM-version-specific. For typical paged-attention with
22+
# contiguous per-layer regions:
23+
# offset = block_id * block_size_bytes
24+
# size = block_size_bytes
25+
return [(L, block_id * block_size_bytes, block_size_bytes)
26+
for L in range(n_layers)]
27+
28+
gds = VllmGdsConnector(handle, block_layout)
29+
30+
# vLLM's evict_kv_blocks hook:
31+
def evict_kv_blocks(self, block_ids, ipc_event_handle=b""):
32+
if gds.is_available():
33+
gds.evict_blocks_to_storage(block_ids)
34+
else:
35+
# standard host_tier path
36+
for b in block_ids:
37+
handle.record_evict(b, block_layout(b), ipc_event_handle)
38+
39+
# vLLM's start_load_kv hook (on cache hit):
40+
def start_load_kv(self, request):
41+
block_ids = request.hit_blocks # vLLM-version-specific
42+
if gds.is_available():
43+
ok = gds.restore_blocks_from_storage(block_ids)
44+
# Caller decides cold-fallback for ok[b]==False entries.
45+
else:
46+
# standard host_tier promote + restore_ring path
47+
...
48+
"""
49+
50+
from __future__ import annotations
51+
52+
from gms_kv_ring.engines.gds_block_connector import BlockGdsConnector, EvictResult
53+
54+
# Engine-flavored alias. Same class; vLLM-specific recipe lives in
55+
# this module's docstring above.
56+
VllmGdsConnector = BlockGdsConnector
57+
58+
__all__ = ["VllmGdsConnector", "EvictResult"]
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""Wire a GMSKvRing handle into vLLM.
5+
6+
vLLM's KVCacheConnectorV1 interface exposes the two hooks we need:
7+
8+
• `evict_kv_blocks(block_ids, ipc_event_handle)` — call
9+
`handle.record_evict(...)` here.
10+
• `start_load_kv(request)` — translate the request's hit prefix
11+
into (src_block, dest_block) pairs and call
12+
`handle.record_restore(...)` then enqueue
13+
`handle.wait_restore(stream, slot, target)` on the compute stream.
14+
15+
This module deliberately does NOT import vLLM at module load —
16+
unit tests run without vLLM installed. The caller imports vLLM in
17+
its connector and uses our returned handle from inside its hook
18+
methods.
19+
"""
20+
21+
from __future__ import annotations
22+
23+
import logging
24+
from typing import Callable, Optional
25+
26+
from gms_kv_ring.engines.handle import GMSKvRing
27+
28+
logger = logging.getLogger(__name__)
29+
30+
31+
def install_for_vllm(
32+
*,
33+
engine_id: str,
34+
daemon_socket: str,
35+
layers: list[dict],
36+
on_evict: Optional[Callable[[GMSKvRing], None]] = None,
37+
on_hit: Optional[Callable[[GMSKvRing], None]] = None,
38+
evict_ring_capacity: int = 4096,
39+
restore_ring_capacity: int = 4096,
40+
num_counters: int = 512,
41+
) -> GMSKvRing:
42+
"""Build the handle and (optionally) invoke wiring callbacks.
43+
44+
See `engines/sglang/install_kv_ring.py` for parameter semantics —
45+
they are identical."""
46+
handle = GMSKvRing(
47+
engine_id=engine_id,
48+
daemon_socket=daemon_socket,
49+
layers=layers,
50+
evict_ring_capacity=evict_ring_capacity,
51+
restore_ring_capacity=restore_ring_capacity,
52+
num_counters=num_counters,
53+
)
54+
if on_evict is not None:
55+
on_evict(handle)
56+
if on_hit is not None:
57+
on_hit(handle)
58+
logger.info("gms_kv_ring installed for vLLM engine %r", engine_id)
59+
return handle

lib/gms_kv_ring/tests/real_engine/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)