chore: import upstream snapshot with attribution
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:38:16 +08:00
commit 94057c3d3e
7152 changed files with 2120455 additions and 0 deletions
@@ -0,0 +1,121 @@
# SiMM as L3 KV Cache
This document describes how to use SiMM as the L3 KV cache for SGLang.
## About SiMM
SiMM(Scalable In-Memory Middleware) is a distributed, high-performance, elastic cache acceleration layer for all AI workloads.
For more details about SiMM, please refer to [SiMM project](https://github.com/scitix/SiMM) and [SiMM documents](https://github.com/scitix/SiMM/tree/main/docs).
### SiMM & SGLang HiCache
SiMM serves as a high-performance L3 storage backend for SGLang HiCache, enabling distributed KV cache storage across multiple servers with RDMA-baed transport. This integration addresses the capacity limitations of traditional GPU-only or GPU+CPU caching by providing virtually unlimited cache storage through a distributed memory pool.
When a cache miss occurs in L1 and L2, HiCache automatically fetches the required KV cache from SiMM's distributed memory pool. The system uses intelligent prefetching strategies to minimize latency, and utilize RDMA technology and zero-copy technique to ensure high-bandwidth, low-latency data transfer between SGLang instances and SiMM data servers.
## Install SiMM
**from source**
Clone SiMM project:
```bash
git clone https://github.com/scitix/SiMM --recursive
```
Install dependencies:
```bash
cd SiMM
bash configure.sh
```
Build and install SiMM:
```bash
bash build.sh --mode=release --clean
```
For more details, please refer to [SiMM official installation guide](https://github.com/scitix/SiMM/blob/main/README.md).
## Deployment
**SiMM**
Before launch `SGLang server` with SiMM, you should launch SiMM `cluster manager service` and `data server service`.
You can visit [SiMM official deploy guide](https://github.com/scitix/SiMM/blob/main/docs/deploy_guide.md) and deploy SiMM on your K8S cluster with RDMA network.
**Start the `SGLang server` with SiMM enabled:**
There are three ways to configure SiMM:
1. Via extra configuration passed through sglang parameters
2. Using JSON configuration files
3. Using environment variables
SiMM loads configuration in the following priority order:
1. If SiMM-specific options are provided in `--hicache-storage-backend-extra-config`, they are used first.
2. If not, SiMM checks whether the environment variable `DEFAULT_SIMM_CONFIG_PATH_ENV` is set, and loads the JSON config file from that path.
3. If neither of the above is provided, SiMM falls back to environment variables.
**HiCache Related Parameters for SGLang Server**
For a comprehensive overview of HiCache-related parameters, please refer to [this document](https://docs.sglang.io/advanced_features/hicache_design.html#related-parameters).
Note that, for `--hicache-mem-layout {layer_first,page_first,page_first_direct}`, which specifies the memory layout for the host memory pool, `page_first` or `page_first_direct` are required if use SiMM backend.
### Distributed Deployment
**Using extra-config of sglang arguments to configure SiMM**
```bash
python -m sglang.launch_server \
--enable-hierarchical-cache \
--hicache-storage-backend simm \
--model-path [model_path] \
--hicache-storage-backend-extra-config '{"manager_address": "127.0.0.1:30001"}'
```
**Using JSON file to configure SiMM**
SGLang server can load SiMM config from `SGLANG_HICACHE_SIMM_CONFIG_PATH`.
```bash
export SGLANG_HICACHE_SIMM_CONFIG_PATH=/sgl-workspace/sglang/benchmark/hicache/simm_config.json
echo '{
"manager_address": "127.0.0.1:30001"
}' > ${SGLANG_HICACHE_SIMM_CONFIG_PATH}
python -m sglang.launch_server \
--enable-hierarchical-cache \
--hicache-storage-backend simm \
--model-path [model_path]
```
**Using env variables to configure SiMM**
```bash
SIMM_CLUSTER_MANAGER="127.0.0.1:30001"
python -m sglang.launch_server \
--enable-hierarchical-cache \
--hicache-storage-backend simm \
--model-path [model_path]
```
## Test SiMM
This test is intended for developers to quickly verify that the SiMM class interfaces are functioning correctly.
First, start the `cluster manager service` and `data server service`. Then run the `test_hicache_simm.py`.
```bash
SIMM_CLUSTER_MANAGER="127.0.0.1:30001" \
python3 [path of test_hicache_simm.py]
```
If all tests pass, the message "✅ All tests passed" will be printed at the end.
@@ -0,0 +1,544 @@
import json
import logging
import os
import re
import time
import uuid
from collections import defaultdict
from dataclasses import dataclass
from datetime import datetime
from typing import Any, Dict, List, Optional
import torch
from sglang.srt.mem_cache.hicache_storage import (
HiCacheStorage,
HiCacheStorageConfig,
HiCacheStorageExtraInfo,
)
from sglang.srt.mem_cache.pool_host import HostKVCache
# Third Party
try:
from simm.kv import BlockView, Store, register_mr, set_flag
except ImportError as e:
raise ImportError(
"Please install simm by following the instructions at https://github.com/scitix/SiMM "
"to run SGLang with SimmConnector."
) from e
SGLANG_HICACHE_SIMM_JSON_ENV_VAR = "SGLANG_HICACHE_SIMM_CONFIG_PATH"
logger = logging.getLogger(__name__)
@dataclass
class SiMMConfig:
manager_address: str
clnt_threadpool_size: int
enable_profile: bool
@staticmethod
def from_file() -> "SiMMConfig":
"""Load the config from a JSON file."""
if os.environ.get(SGLANG_HICACHE_SIMM_JSON_ENV_VAR) is None:
raise RuntimeError(
f"Config file path not set. Please set {SGLANG_HICACHE_SIMM_JSON_ENV_VAR}"
)
file_path = os.environ.get(SGLANG_HICACHE_SIMM_JSON_ENV_VAR)
try:
with open(file_path) as fin:
config = json.load(fin)
except Exception as e:
raise RuntimeError(f"Failed to load config from {file_path}: {str(e)}")
if "manager_address" not in config:
raise ValueError("Manager_address is required in config file")
return SiMMConfig(
manager_address=config.get("manager_address"),
clnt_threadpool_size=config.get("clnt_threadpool_size", 10),
enable_profile=config.get("enable_profile", False),
)
@staticmethod
def load_from_extra_config(extra_config: dict) -> "SiMMConfig":
"""Load config from extra_config dictionary."""
if "manager_address" not in extra_config:
raise ValueError("manager_address is required in extra_config")
return SiMMConfig(
manager_address=extra_config.get("manager_address"),
clnt_threadpool_size=extra_config.get("clnt_threadpool_size", 10),
enable_profile=extra_config.get("enable_profile", False),
)
def get_current_process_numa() -> int:
"""
Return value: numa_node of current process, failed return -1
"""
try:
# get current cpu
with open("/proc/self/stat", "r") as f:
stat_data = f.read()
# the 39th field is processor
fields = stat_data.split()
if len(fields) < 39:
return -1
current_cpu = int(fields[38])
numa_path = f"/sys/devices/system/cpu/cpu{current_cpu}/node0"
if os.path.exists(numa_path) and os.path.islink(numa_path):
link_target = os.readlink(numa_path)
# parse numa node from path
match = re.search(r"node(\d+)$", link_target)
if match:
return int(match.group(1))
return -1
except Exception:
return -1
def get_numa_nic_mapping() -> Dict[int, List[str]]:
"""
Return value: Dict[numa_node, List(rdma_device_name)]
"""
ib_root = "/sys/class/infiniband"
device_map = defaultdict(list)
if not os.path.exists(ib_root):
logger.error(f"SiMM ERROR: {ib_root} not found. Are RDMA drivers loaded?")
return []
for device_name in os.listdir(ib_root):
numa_path = os.path.join(ib_root, device_name, "device", "numa_node")
numa_node = -1 # default value, if system is UMA.
try:
if os.path.exists(numa_path):
with open(numa_path, "r") as f:
content = f.read().strip()
numa_node = int(content)
except (IOError, ValueError):
pass
device_map[numa_node].append(device_name)
return device_map
class HiCacheSiMM(HiCacheStorage):
def __init__(
self, storage_config: HiCacheStorageConfig = None, mem_pool: HostKVCache = None
):
try:
extra_config = (
getattr(storage_config, "extra_config", None)
if storage_config
else None
)
# Load configuration with manager_address prioritized from extra_config if available
if (
extra_config is not None
and extra_config.get("manager_address") is not None
):
# Load from extra_config
self.config = SiMMConfig.load_from_extra_config(extra_config)
logger.info("SiMM Configuration loaded from extra_config successfully.")
else:
# Load from config file
self.config = SiMMConfig.from_file()
logger.info("SiMM Configuration loaded from file successfully.")
# Check if extra_backend_tag should be passed to SiMM data server
self.extra_backend_tag = None
if extra_config and "extra_backend_tag" in extra_config:
self.extra_backend_tag = extra_config["extra_backend_tag"]
logger.info(f"Using extra_backend_tag: {self.extra_backend_tag}")
# Set nic device according to current process numa node
nic_mapping = get_numa_nic_mapping()
logger.info(f"SiMM NUMA-awared allocation: {nic_mapping}")
current_numa = get_current_process_numa()
if current_numa >= 0:
rdma_devices = nic_mapping.get(current_numa)
if rdma_devices is not None and len(rdma_devices) > 0:
rdma_device_str = ",".join(rdma_devices)
os.environ["SICL_NET_DEVICES"] = rdma_device_str
logger.info(f"SiMM using rdma {rdma_device_str}")
# Set simm log path: /var/log/simm/{filename_ts}-{pid}/simm_clnt.log
filename_ts = datetime.now().strftime("%Y%m%d-%H%M%S")
log_file_path: str = (
f"/var/log/simm/{filename_ts}-{os.getpid()}/simm_clnt.log"
)
cm_ip = self.config.manager_address.split(":")[0]
cm_port = self.config.manager_address.split(":")[1]
set_flag("cm_primary_node_ip", cm_ip)
set_flag("cm_primary_node_port", cm_port)
set_flag("clnt_log_file", log_file_path)
set_flag("clnt_thread_pool_size", str(self.config.clnt_threadpool_size))
self.store = Store()
logger.info("SiMM store setup successfully.")
self.mr_ext = None
self.warmup()
logger.info("SiMM store warmup successfully.")
if storage_config is not None:
self.model_name = storage_config.model_name
self.is_mla_backend = storage_config.is_mla_model
self.local_rank = storage_config.tp_rank
self.pp_rank = storage_config.pp_rank
self.pp_size = storage_config.pp_size
else:
self.model_name = ""
self.is_mla_backend = False
self.local_rank = 0
self.pp_rank = 0
self.pp_size = 1
self.enable_pp = self.pp_size > 1
if self.enable_pp:
self.mha_suffix = f"{self.local_rank}_{self.pp_rank}"
self.mla_suffix = f"{self.pp_rank}"
else:
self.mha_suffix = f"{self.local_rank}"
self.mla_suffix = ""
except ValueError as e:
logger.error("Configuration loading failed: %s", e)
raise
except Exception as exc:
logger.error("An error occurred while loading the configuration: %s", exc)
raise
def warmup(self):
"""Dryrun a key to warmup SiMM client"""
logger.info("begin warm up SiMM client")
start_time = time.perf_counter_ns()
warmup_key = "sglang_simm_warmup_key" + uuid.uuid4().hex
warmup_tensor = torch.frombuffer(
bytearray(warmup_key.encode()), dtype=torch.uint8
)
warmup_size = 4 * 1024 # 4 KB
block = self.store.allocate(warmup_size)
block_ = block.as_ref()
block_[: len(warmup_key)] = warmup_tensor
if self.store.put(warmup_key, block.view()) != 0:
logger.warning(f"SiMM client warmup put key {warmup_key} failed")
if not self.store.exists(warmup_key):
logger.warning(f"SiMM client warmup key {warmup_key} not exists")
got_block = self.store.allocate(warmup_size)
if self.store.get(warmup_key, got_block.view()) < 0:
logger.warning(f"SiMM client warmup get key {warmup_key} failed")
if not all(got_block.as_ref()[: len(warmup_key)] == warmup_tensor):
logger.warning(f"SiMM client warmup key {warmup_key} data wrong")
logger.info(
f"finish SiMM client warm up, cost {(time.perf_counter_ns() - start_time)/1000:.2f} us"
)
def register_mem_pool_host(self, mem_pool_host: HostKVCache):
super().register_mem_pool_host(mem_pool_host)
assert self.mem_pool_host.layout in [
"page_first",
"page_first_direct",
], "simm storage backend only support page first or page first direct layout"
buffer = self.mem_pool_host.kv_buffer
try:
self.mr_ext = register_mr(buffer)
if self.mr_ext is None:
logger.error(
f"Failed to register buffer, {buffer=}, please check buffer and RDMA network"
)
raise RuntimeError(f"Failed to register buffer to SiMM")
except TypeError as err:
logger.error("Failed to register buffer to SiMM: %s", err)
raise TypeError("SiMM Register Buffer Error.") from err
def _get_mha_buffer_meta(self, keys, indices):
ptr_list, element_size_list = self.mem_pool_host.get_page_buffer_meta(indices)
key_list = []
for key_ in keys:
key_list.append(f"{key_}_{self.mha_suffix}_k")
key_list.append(f"{key_}_{self.mha_suffix}_v")
if len(key_list) != len(ptr_list):
logger.error(
f"key size {len(key_list)} not equal with incides ptr size {len(ptr_list)}"
)
assert len(key_list) == len(ptr_list)
return key_list, ptr_list, element_size_list
def _get_mla_buffer_meta(self, keys, indices):
ptr_list, element_size_list = self.mem_pool_host.get_page_buffer_meta(indices)
key_list = []
for key_ in keys:
key_list.append(f"{key_}_{self.mla_suffix}_k")
if len(key_list) != len(ptr_list):
logger.error(
f"key size {len(key_list)} not equal with incides ptr size {len(ptr_list)}"
)
assert len(key_list) == len(ptr_list)
return key_list, ptr_list, element_size_list
def _batch_preprocess(self, keys, host_indices):
assert len(keys) > 0
assert len(keys) == len(host_indices) // self.mem_pool_host.page_size
if self.is_mla_backend:
return self._get_mla_buffer_meta(keys, host_indices)
else:
return self._get_mha_buffer_meta(keys, host_indices)
def _batch_postprocess(self, results: List[int], is_set_operate=False):
"""
for batch_get_into, results is Vector of integers,
where each element is the number of bytes read on success, or a negative value on error
for batch_put_from, results is Vector of integers,
where each element is 0 on success, or a negative value on error
"""
if self.is_mla_backend:
return [k_res == 0 if is_set_operate else k_res > 0 for k_res in results]
else:
kv_pairs = zip(results[::2], results[1::2])
return [
(
(k_res == 0 and v_res == 0)
if is_set_operate
else (k_res > 0 and v_res > 0)
)
for k_res, v_res in kv_pairs
]
def batch_get_v1(
self,
keys: List[str],
host_indices: torch.Tensor,
extra_info: Optional[HiCacheStorageExtraInfo] = None,
) -> List[bool]:
# Apply extra_backend_tag prefix if available
if self.extra_backend_tag is not None:
prefix = self.extra_backend_tag
keys = [f"{prefix}_{key}" for key in keys]
t1 = time.perf_counter_ns()
key_strs, buffer_ptrs, buffer_sizes = self._batch_preprocess(keys, host_indices)
get_results = self._get_batch_zero_copy_impl(
key_strs, buffer_ptrs, buffer_sizes
)
t2 = time.perf_counter_ns()
total_size = sum([k_res if k_res > 0 else 0 for k_res in get_results])
if self.config.enable_profile:
logger.info(
f"SiMM batch_get_v1 {len(keys)} keys, total size: {total_size / 1024**2} MiB, \
using {(t2 - t1)/1000} us, Throughput: {total_size / 1024**3 / ((t2 - t1) / 1000**3):.2f} GiB/s"
)
return self._batch_postprocess(get_results, is_set_operate=False)
def batch_set_v1(
self,
keys: List[str],
host_indices: torch.Tensor,
extra_info: Optional[HiCacheStorageExtraInfo] = None,
) -> List[bool]:
# Apply extra_backend_tag prefix if available
if self.extra_backend_tag is not None:
prefix = self.extra_backend_tag
keys = [f"{prefix}_{key}" for key in keys]
t1 = time.perf_counter_ns()
key_strs, buffer_ptrs, buffer_sizes = self._batch_preprocess(keys, host_indices)
exist_result = self._batch_exist_impl(key_strs)
t2 = time.perf_counter_ns()
if self.config.enable_profile:
logger.info(
f"SiMM batch exists {len(keys)} keys, using {(t2 - t1)/1000} us"
)
set_keys = []
set_buffer_ptrs = []
set_buffer_sizes = []
set_indices = []
set_results = [-1] * len(key_strs)
total_size = 0
for i in range(len(key_strs)):
if not exist_result[i]:
set_keys.append(key_strs[i])
set_buffer_ptrs.append(buffer_ptrs[i])
set_buffer_sizes.append(buffer_sizes[i])
set_indices.append(i)
total_size += buffer_sizes[i]
else:
set_results[i] = 0
# Only set non-existing keys to storage
if len(set_keys) > 0:
put_results = self._put_batch_zero_copy_impl(
set_keys, set_buffer_ptrs, set_buffer_sizes
)
for i in range(len(set_indices)):
set_results[set_indices[i]] = put_results[i]
t3 = time.perf_counter_ns()
if self.config.enable_profile:
logger.info(
f"SiMM batch_put_v1 {len(keys)} keys, total size: {total_size / 1024**2} MiB, \
using {(t3 - t2)/1000} us, Throughput: {total_size / 1024**3 / ((t3 - t2) / 1000**3):.2f} GiB/s"
)
return self._batch_postprocess(set_results, is_set_operate=True)
def set(
self,
key,
value: Optional[Any] = None,
target_location: Optional[List[int]] = None,
target_sizes: Optional[List[int]] = None,
) -> bool:
# Only support zero copy set for now
assert target_location is not None and target_sizes is not None
exist_result = self._batch_exist_impl([key])
if exist_result[0]:
return True
put_result = self._put_batch_zero_copy_impl(
[key], [target_location], [target_sizes]
)
return put_result[0] == 0
def batch_set(
self,
keys: List[str],
values: Optional[List[torch.Tensor]] = None,
target_locations: Optional[List[int]] = None,
target_sizes: Optional[List[int]] = None,
) -> bool:
# Only support zero copy set for now
assert target_locations is not None and target_sizes is not None
assert len(keys) == len(target_locations) == len(target_sizes)
if len(keys) == 0:
return False
for i in range(len(keys)):
if (
keys[i] is None
or target_locations[i] is None
or target_sizes[i] is None
):
return False
exist_result = self._batch_exist_impl(keys)
set_keys = []
set_target_locations = []
set_target_sizes = []
set_indices = []
for i in range(len(keys)):
if not exist_result[i]:
set_keys.append(keys[i])
set_target_locations.append(target_locations[i])
set_target_sizes.append(target_sizes[i])
set_indices.append(i)
# Only set non-existing keys to storage
put_result = self._put_batch_zero_copy_impl(
set_keys, set_target_locations, set_target_sizes
)
for i in range(len(set_indices)):
if put_result[i] == 0:
exist_result[set_indices[i]] = 1
# return the number of consecutive successful operations from the start.
success_count = 0
for i in range(len(keys)):
if exist_result[i] == 0:
break
success_count += 1
return success_count == len(keys)
def get(
self,
key,
target_location: Optional[Any] = None,
target_sizes: Optional[Any] = None,
) -> bool:
assert target_location is not None and target_sizes is not None
get_result = self._get_batch_zero_copy_impl(
[key], [target_location], [target_sizes]
)
return get_result[0] >= 0
def batch_get(
self,
keys: List[str],
target_locations: Optional[Any] = None,
target_sizes: Optional[Any] = None,
) -> int:
assert len(keys) == len(target_locations) == len(target_sizes)
if len(keys) == 0:
return 0
get_result = self._get_batch_zero_copy_impl(
keys, target_locations, target_sizes
)
if self.is_mla_backend:
key_multiplier = 1
else:
key_multiplier = 2
for i in range(len(keys)):
if get_result[i] < 0:
return i // key_multiplier
return len(keys) // key_multiplier
def exists(self, key) -> bool:
exist_result = self._batch_exist_impl([key])
return exist_result[0]
def batch_exists(
self, keys, extra_info: Optional[HiCacheStorageExtraInfo] = None
) -> int:
if self.is_mla_backend:
query_keys = [f"{key}_{self.mla_suffix}_k" for key in keys]
key_multiplier = 1
else:
query_keys = []
for key in keys:
query_keys.append(f"{key}_{self.mha_suffix}_k")
query_keys.append(f"{key}_{self.mha_suffix}_v")
key_multiplier = 2
t1 = time.perf_counter_ns()
exist_result = self._batch_exist_impl(query_keys)
t2 = time.perf_counter_ns()
if self.config.enable_profile:
logger.info(
f"SiMM batch exists {len(keys)} keys, using {(t2 - t1)/1000} us"
)
for i in range(len(query_keys)):
if not exist_result[i]:
return i // key_multiplier
return len(query_keys) // key_multiplier
def _put_batch_zero_copy_impl(
self, key_strs: List[str], buffer_ptrs: List[int], buffer_sizes: List[int]
) -> List[int]:
block_views = []
for i in range(len(buffer_ptrs)):
block_view = BlockView.from_buffer(
buffer_ptrs[i], buffer_sizes[i], self.mr_ext
)
block_views.append(block_view)
return self.store.mput(key_strs, block_views)
def _get_batch_zero_copy_impl(
self, key_strs: List[str], buffer_ptrs: List[int], buffer_sizes: List[int]
) -> List[int]:
block_views = []
for i in range(len(buffer_ptrs)):
block_view = BlockView.from_buffer(
buffer_ptrs[i], buffer_sizes[i], self.mr_ext
)
block_views.append(block_view)
return self.store.mget(key_strs, block_views)
def _batch_exist_impl(self, key_strs: List[str]) -> List[bool]:
return self.store.mexists(key_strs)
@@ -0,0 +1,181 @@
import logging
import uuid
import torch
from python.sglang.srt.mem_cache.storage.simm.hicache_simm import HiCacheSiMM
from sglang.srt.mem_cache.hicache_storage import HiCacheStorageConfig
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
def generate_batch_query_keys(kv_num: int, config: HiCacheStorageConfig):
keys = ["test_" + str(uuid.uuid4()) for _ in range(kv_num)]
set_keys = []
for key in keys:
if config.is_mla_model:
set_keys.append(key + "_k")
else:
set_keys.append(key + f"_{config.tp_rank}_k")
set_keys.append(key + f"_{config.tp_rank}_v")
get_keys = set_keys
exist_keys = keys
return set_keys, get_keys, exist_keys
def create_mock_host_kv_cache(buffer_size, dtype=torch.float32):
"""Create a mock HostKVCache-like object for testing."""
buffer = torch.randn(buffer_size, dtype=dtype)
class MockHostKVCache:
def __init__(self, buffer):
self.kv_buffer = buffer
self.layout = "page_first"
self.page_size = 1 # Simple page size for testing
def get_page_buffer_meta(self, indices):
"""Mock implementation of get_page_buffer_meta."""
ptr_list = []
element_size_list = []
for idx in indices:
# Create mock pointers and sizes for each page
ptr_list.append(idx * self.page_size * self.kv_buffer.element_size())
element_size_list.append(self.page_size * self.kv_buffer.element_size())
return ptr_list, element_size_list
return MockHostKVCache(buffer), buffer
def test_single_operation():
"""Test the set API with a single key-value pair."""
print("=" * 100)
print("Testing single operation")
buffer_size = 1024 * 1024 * 16 # 16MB
value_elements = 1024
store = HiCacheSiMM()
mock_host_kv_cache, buffer = create_mock_host_kv_cache(buffer_size)
# Register the memory pool host - this is the proper workflow
store.register_mem_pool_host(mock_host_kv_cache)
value_size = value_elements * buffer.element_size()
key = str(uuid.uuid4())
set_slice = buffer[:value_elements]
get_slice = buffer[value_elements : 2 * value_elements]
set_location = set_slice.data_ptr()
get_location = get_slice.data_ptr()
# Test set operation
result = store.set(key, target_location=set_location, target_sizes=value_size)
assert result is True, f"❌set operation failed for key: {key}"
# Test exists operation
assert store.exists(key), f"❌key {key} should exist after set operation"
# Test get operation
result = store.get(key, target_location=get_location, target_sizes=value_size)
assert result is True, f"❌get operation failed for key: {key}"
# Compare the data using proper tensor indices
assert torch.allclose(
set_slice, get_slice, atol=1e-6
), f"❌get operation failed for key: {key}"
logger.info(f"✅ Single operation passed")
def test_batch_operation(config: HiCacheStorageConfig):
"""Test the batch set/get APIs with multiple key-value pairs."""
print("=" * 100)
print(f"Testing batch operation with config: {config}")
buffer_size = 1024 * 1024 * 16 # 16MB
value_elements = 256
kv_num = 13
store = HiCacheSiMM(config)
mock_host_kv_cache, buffer = create_mock_host_kv_cache(buffer_size)
store.register_mem_pool_host(mock_host_kv_cache)
value_size = value_elements * buffer.element_size()
set_keys, get_keys, exist_keys = generate_batch_query_keys(kv_num, config)
set_slices = [
buffer[i * value_elements : (i + 1) * value_elements]
for i in range(len(set_keys))
]
set_indices = torch.cat(set_slices)
# Test batch set operation
result = store.batch_set_v1(set_keys, set_indices)
assert all(result), f"❌batch set operation failed"
# Test batch exists operation
assert store.batch_exists(
exist_keys
), f"❌keys should exist after batch set operation"
# Test batch get operation
get_slices = [
buffer[
(len(set_keys) + i)
* value_elements : (len(set_keys) + i + 1)
* value_elements
]
for i in range(len(get_keys))
]
get_indices = torch.cat(get_slices)
result = store.batch_get_v1(get_keys, get_indices)
assert all(result), f"❌batch get operation failed"
for i in range(len(get_keys)):
assert torch.allclose(
set_slices[i], get_slices[i], atol=1e-6
), f"❌batch get operation failed for key: {get_keys[i]}"
logger.info(f"✅ Batch operation passed")
if __name__ == "__main__":
test_single_operation()
test_batch_operation(
HiCacheStorageConfig(
is_mla_model=False,
tp_rank=0,
tp_size=1,
model_name=None,
is_page_first_layout=True,
)
)
test_batch_operation(
HiCacheStorageConfig(
is_mla_model=True,
tp_rank=0,
tp_size=1,
model_name=None,
is_page_first_layout=True,
)
)
test_batch_operation(
HiCacheStorageConfig(
is_mla_model=False,
tp_rank=1,
tp_size=4,
model_name=None,
is_page_first_layout=True,
)
)
test_batch_operation(
HiCacheStorageConfig(
is_mla_model=True,
tp_rank=3,
tp_size=8,
model_name=None,
is_page_first_layout=True,
)
)
logger.info(f"✅ All tests passed")