|
| 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"] |
0 commit comments