Files
2026-07-13 12:24:33 +08:00

424 lines
17 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"id": "1cd06b97",
"metadata": {},
"source": [
"## Setup vLLM and LMCache server\n",
"Follow below instructions (1-4), then proceed to below Python cell.\n",
"1. Start LMCache server first\n",
"```sh\n",
"lmcache server \\\n",
" --l1-size-gb 8 \\\n",
" --eviction-policy LRU \\\n",
" --chunk-size 256 \\\n",
" --port 6555 \\\n",
" --http-port 8080 \\\n",
" --shm-name lmcache_kvcache_sdk_e2e \\\n",
" --no-l1-use-lazy\n",
"```\n",
"2. Wait until LMCache server is ready\n",
"```sh\n",
"curl -sf http://localhost:8080/healthcheck && echo \" LMCache ready\"\n",
"```\n",
"3. Start vLLM once LMCache server is ready\n",
"```sh\n",
"env -u VLLM_PORT \\\n",
" CUDA_VISIBLE_DEVICES=0 \\\n",
" VLLM_ENABLE_V1_MULTIPROCESSING=0 \\\n",
" VLLM_BATCH_INVARIANT=1 \\\n",
" PYTHONHASHSEED=0 \\\n",
" vllm serve Qwen/Qwen3-8B \\\n",
" --port 8000 \\\n",
" --served-model-name Qwen/Qwen3-8B \\\n",
" --no-enable-prefix-caching \\\n",
" --enforce-eager \\\n",
" --max-model-len 4096 \\\n",
" --gpu-memory-utilization 0.6 \\\n",
" --kv-transfer-config '{\"kv_connector\":\"LMCacheMPConnector\",\"kv_role\":\"kv_both\",\"kv_load_failure_policy\":\"recompute\",\"kv_connector_extra_config\":{\"lmcache.mp.host\":\"tcp://localhost\",\"lmcache.mp.port\":6555,\"lmcache.mp.mq_timeout\":10}}' \\\n",
" --override-generation-config '{\"temperature\": 0}' \\\n",
" --trust-remote-code\n",
"```\n",
"4. Wait until vLLM is ready\n",
"```sh\n",
"curl -sf http://localhost:8000/v1/models && echo \" vLLM ready\"\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "c2d81202",
"metadata": {},
"source": [
"## Run E2E KV Edit Example"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "aa87a653",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/home/rani/LMCache/.venv/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
" from .autonotebook import tqdm as notebook_tqdm\n",
"\u001b[32;20m[2026-06-23 23:26:41,202] LMCache INFO:\u001b[0m torch_dev=<module 'torch.cuda' from '/home/rani/LMCache/.venv/lib/python3.12/site-packages/torch/cuda/__init__.py'>, torch_device_type=cuda \u001b[3m(__init__.py:62:lmcache)\u001b[0m\n",
"\u001b[32;20m[2026-06-23 23:26:41,267] LMCache INFO:\u001b[0m Skipping backend lmcache.v1.platform.musa.ops: predicate returned False \u001b[3m(__init__.py:102:lmcache)\u001b[0m\n",
"\u001b[32;20m[2026-06-23 23:26:41,268] LMCache INFO:\u001b[0m Skipping backend lmcache.xpu_ops: predicate returned False \u001b[3m(__init__.py:102:lmcache)\u001b[0m\n",
"\u001b[32;20m[2026-06-23 23:26:41,269] LMCache INFO:\u001b[0m Using backend: lmcache.c_ops \u001b[3m(__init__.py:120:lmcache)\u001b[0m\n",
"\u001b[32;20m[2026-06-23 23:26:41,604] LMCache INFO:\u001b[0m multi_layer_block_kv_transfer mode: ptr \u001b[3m(base.py:94:lmcache.v1.multiprocess.transfer_context.base)\u001b[0m\n"
]
}
],
"source": [
"# SPDX-License-Identifier: Apache-2.0\n",
"\"\"\"End-to-end KV cache remapping driver for the SDK example.\"\"\"\n",
"\n",
"# Standard\n",
"from dataclasses import dataclass\n",
"from itertools import islice\n",
"import json\n",
"import time\n",
"from typing import cast\n",
"\n",
"# Third Party\n",
"import httpx\n",
"from transformers import AutoTokenizer, PreTrainedTokenizerBase\n",
"\n",
"# First Party\n",
"import lmcache.sdk.kvcache as lmc_sdk"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "8f1065b0",
"metadata": {},
"outputs": [],
"source": [
"model_name = \"Qwen/Qwen3-8B\"\n",
"vllm_url = \"http://localhost:8000\"\n",
"lmcache_url = \"http://localhost:8080\"\n",
"lmcache_mq_url = \"tcp://localhost:6555\"\n",
"chunk_size = 256\n",
"min_prompt_tokens = chunk_size * 2\n",
"fake_prefix_tokens = 32\n",
"max_tokens = 32\n",
"timeout = 60 # seconds\n",
"trust_remote_code = True"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "f894f55b",
"metadata": {},
"outputs": [],
"source": [
"@dataclass(frozen=True)\n",
"class CompletionResult:\n",
" \"\"\"Text and latency returned by one OpenAI-compatible completion call.\"\"\"\n",
"\n",
" text: str\n",
" elapsed_seconds: float\n",
"\n",
"\n",
"def _post_completion(\n",
" *,\n",
" vllm_url: str,\n",
" model_name: str,\n",
" prompt: str | list[int],\n",
" max_tokens: int,\n",
" timeout: float,\n",
") -> CompletionResult:\n",
" \"\"\"Send one non-streaming completion request to vLLM.\"\"\"\n",
" payload = {\n",
" \"model\": model_name,\n",
" \"prompt\": prompt,\n",
" \"max_tokens\": max_tokens,\n",
" \"min_tokens\": max_tokens,\n",
" \"temperature\": 0,\n",
" \"seed\": 0,\n",
" \"ignore_eos\": True,\n",
" }\n",
" start = time.perf_counter()\n",
" response = httpx.post(\n",
" f\"{vllm_url.rstrip('/')}/v1/completions\",\n",
" json=payload,\n",
" timeout=timeout,\n",
" )\n",
" elapsed = time.perf_counter() - start\n",
" response.raise_for_status()\n",
" body = response.json()\n",
" choices = body.get(\"choices\")\n",
" if not isinstance(choices, list) or not choices:\n",
" raise RuntimeError(f\"completion response missing choices: {body}\")\n",
" first_choice = choices[0]\n",
" if not isinstance(first_choice, dict) or not isinstance(\n",
" first_choice.get(\"text\"), str\n",
" ):\n",
" raise RuntimeError(f\"completion response has invalid choice: {body}\")\n",
" return CompletionResult(text=first_choice[\"text\"], elapsed_seconds=elapsed)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4cd588ce",
"metadata": {},
"outputs": [],
"source": [
"SOURCE_PARAGRAPH = (\n",
" \"A systems researcher is studying how an inference cache changes the \"\n",
" \"latency profile of a long language model prompt. The notes discuss \"\n",
" \"attention keys, attention values, memory tiers, token chunks, and the \"\n",
" \"careful measurement of cold and warm requests.\"\n",
")\n",
"\n",
"\n",
"def _build_prompts(\n",
" tokenizer: PreTrainedTokenizerBase,\n",
" *,\n",
" min_prompt_tokens: int,\n",
" chunk_size: int,\n",
" fake_prefix_tokens: int,\n",
") -> tuple[list[int], list[int], int]:\n",
" \"\"\"Build equal-length source/target prompts and the cached-prefix length.\n",
" The prompts differ only in ``fake_prefix_tokens`` synthetic leading token IDs.\"\"\"\n",
" if not 0 < fake_prefix_tokens < chunk_size:\n",
" raise ValueError(\n",
" f\"fake_prefix_tokens must be in (0, {chunk_size}), got {fake_prefix_tokens}\"\n",
" )\n",
" min_cache = max(1, min_prompt_tokens - fake_prefix_tokens)\n",
" cache_tokens = ((min_cache + chunk_size - 1) // chunk_size) * chunk_size\n",
"\n",
" # Two distinct, non-special lead IDs for the source/target prefixes.\n",
" special = {int(t) for t in tokenizer.all_special_ids}\n",
" candidates = (t for t in range(1000, int(tokenizer.vocab_size)) if t not in special)\n",
" leads = list(islice(candidates, 2))\n",
" if len(leads) < 2:\n",
" raise ValueError(\"could not find two usable non-special token IDs\")\n",
" source_lead, target_lead = leads\n",
"\n",
" # Repeat the paragraph until it covers the suffix, then take the trailing slice.\n",
" suffix_len = cache_tokens - fake_prefix_tokens\n",
" text = SOURCE_PARAGRAPH\n",
" suffix = tokenizer.encode(text, add_special_tokens=False)\n",
" while len(suffix) < suffix_len:\n",
" text = f\"{text}\\n\\n{SOURCE_PARAGRAPH}\"\n",
" suffix = tokenizer.encode(text, add_special_tokens=False)\n",
" suffix = [int(t) for t in suffix[-suffix_len:]]\n",
"\n",
" source = [source_lead] * fake_prefix_tokens + suffix\n",
" target = [target_lead] * fake_prefix_tokens + suffix\n",
" return source, target, cache_tokens"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "030145fc",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"\u001b[32;20m[2026-06-23 23:26:52,200] LMCache INFO:\u001b[0m Initialized LMCacheKVCacheContext with instance_id=3403760, model_name=Qwen/Qwen3-8B, chunk_size=256, shm_name=lmcache_l1_pool_lmcache_kvcache_sdk_e2e \u001b[3m(kvcache.py:104:lmcache.sdk.kvcache)\u001b[0m\n",
"\u001b[32;20m[2026-06-23 23:26:52,206] LMCache INFO:\u001b[0m Creating transfer context (device_type=cpu, mode=auto) \u001b[3m(worker_transfer.py:551:lmcache.v1.multiprocess.transfer_context.worker_transfer)\u001b[0m\n",
"\u001b[32;20m[2026-06-23 23:26:52,206] LMCache INFO:\u001b[0m Engine KV Format: EngineKVFormat.NL_X_NB_TWO_NH_BS_HS NL x [NB, 2, NH, BS, HS] \u001b[3m(detection.py:44:lmcache.v1.gpu_connector.kv_format.detection)\u001b[0m\n",
"\u001b[32;20m[2026-06-23 23:26:52,208] LMCache INFO:\u001b[0m Creating EngineDrivenContextShm (shm_name=lmcache_l1_pool_lmcache_kvcache_sdk_e2e, pool_size=150323855360) \u001b[3m(base.py:235:lmcache.v1.multiprocess.transfer_context.base)\u001b[0m\n",
"\u001b[32;20m[2026-06-23 23:26:52,209] LMCache INFO:\u001b[0m Worker non-GPU transfer context registered (instance_id=3403760, mode=SHM) \u001b[3m(worker_transfer.py:420:lmcache.v1.multiprocess.transfer_context.worker_transfer)\u001b[0m\n"
]
}
],
"source": [
"# Only create one LMCacheKVCacheContext, reuse for retrieve() and store()\n",
"# multiple times. Only close() once at the end.\n",
"ctx = lmc_sdk.connect(\n",
" url=lmcache_mq_url,\n",
" http_url=lmcache_url,\n",
" model_name=model_name,\n",
" timeout=timeout,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "2f1c8d9a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"== Step 1: source inference stores KV under source token IDs ==\n",
"== Step 2: retrieve source KV into memory through lmcache.sdk ==\n",
"== Step 3: store source KV under different target token IDs ==\n",
"== Step 4: target inference reuses the remapped token IDs ==\n",
"== Evaluation ==\n",
"{\n",
" \"vllm_model_name\": \"Qwen/Qwen3-8B\",\n",
" \"cache_tokens\": 512,\n",
" \"source_prompt_tokens\": 512,\n",
" \"target_prompt_tokens\": 512,\n",
" \"retrieved_hit_tokens\": 512,\n",
" \"retrieved_hit_chunks\": 2,\n",
" \"source_target_same_length\": true,\n",
" \"source_target_last_hit_tokens_match\": false,\n",
" \"target_prefix_ids_differ_from_source\": true,\n",
" \"outputs_match\": true,\n",
" \"store_result\": true,\n",
" \"source_retrieve_after_inference\": {\n",
" \"shape\": [\n",
" 2,\n",
" 36,\n",
" 512,\n",
" 1024\n",
" ],\n",
" \"dtype\": \"torch.bfloat16\",\n",
" \"hit_tokens\": 512,\n",
" \"hit_chunks\": 2\n",
" },\n",
" \"source_latency_seconds\": 1.2143411422148347,\n",
" \"target_latency_seconds\": 1.1793114291504025,\n",
" \"source_output_preview\": \"Okay, I need to understand what the user is asking for. They provided a series of repeated sentences about a systems researcher studying how an inference cache affects the\",\n",
" \"target_output_preview\": \"Okay, I need to understand what the user is asking for. They provided a series of repeated sentences about a systems researcher studying how an inference cache affects the\"\n",
"}\n"
]
}
],
"source": [
"tokenizer = AutoTokenizer.from_pretrained(\n",
" model_name,\n",
" trust_remote_code=trust_remote_code,\n",
")\n",
"source_tokens, target_tokens, cache_tokens = _build_prompts(\n",
" tokenizer,\n",
" min_prompt_tokens=min_prompt_tokens,\n",
" chunk_size=chunk_size,\n",
" fake_prefix_tokens=fake_prefix_tokens,\n",
")\n",
"\n",
"print(\"== Step 1: source inference stores KV under source token IDs ==\")\n",
"source_completion = _post_completion(\n",
" vllm_url=vllm_url,\n",
" model_name=model_name,\n",
" prompt=source_tokens,\n",
" max_tokens=max_tokens,\n",
" timeout=timeout,\n",
")\n",
"\n",
"print(\"== Step 2: retrieve source KV into memory through lmcache.sdk ==\")\n",
"retrieved_kv = lmc_sdk.retrieve(\n",
" ctx=ctx,\n",
" tokens=source_tokens,\n",
")\n",
"if retrieved_kv is None:\n",
" raise RuntimeError(\"source retrieve missed the expected cached prefix\")\n",
"retrieved_hit_tokens = int(retrieved_kv.shape[2])\n",
"retrieved_hit_chunks = retrieved_hit_tokens // chunk_size\n",
"if retrieved_hit_tokens < cache_tokens:\n",
" raise RuntimeError(\n",
" \"source retrieve did not return the expected cached prefix: \"\n",
" f\"{retrieved_hit_tokens} < {cache_tokens}\"\n",
" )\n",
"\n",
"target_prefix_tokens = target_tokens[:retrieved_hit_tokens]\n",
"source_prefix_tokens = source_tokens[:retrieved_hit_tokens]\n",
"if source_prefix_tokens == target_prefix_tokens:\n",
" raise RuntimeError(\"source and target token prefixes unexpectedly match\")\n",
"\n",
"print(\"== Step 3: store source KV under different target token IDs ==\")\n",
"store_result = lmc_sdk.store(\n",
" ctx=ctx,\n",
" kv=retrieved_kv,\n",
" tokens=target_prefix_tokens,\n",
")\n",
"if not store_result:\n",
" raise RuntimeError(\n",
" f\"Failed to store {len(target_prefix_tokens)} target prefix tokens. \"\n",
" f\"Might be because LMCache already has the KV cache.\"\n",
" )\n",
"\n",
"print(\"== Step 4: target inference reuses the remapped token IDs ==\")\n",
"target_completion = _post_completion(\n",
" vllm_url=vllm_url,\n",
" model_name=model_name,\n",
" prompt=target_tokens,\n",
" max_tokens=max_tokens,\n",
" timeout=timeout,\n",
")\n",
"outputs_match = source_completion.text == target_completion.text\n",
"\n",
"evaluation = {\n",
" \"vllm_model_name\": model_name,\n",
" \"cache_tokens\": cache_tokens,\n",
" \"source_prompt_tokens\": len(source_tokens),\n",
" \"target_prompt_tokens\": len(target_tokens),\n",
" \"retrieved_hit_tokens\": retrieved_hit_tokens,\n",
" \"retrieved_hit_chunks\": retrieved_hit_chunks,\n",
" \"source_target_same_length\": len(source_tokens) == len(target_tokens),\n",
" \"source_target_last_hit_tokens_match\": source_tokens[-retrieved_hit_tokens:]\n",
" == target_tokens[-retrieved_hit_tokens:],\n",
" \"target_prefix_ids_differ_from_source\": source_prefix_tokens\n",
" != target_prefix_tokens,\n",
" \"outputs_match\": outputs_match,\n",
" \"store_result\": store_result,\n",
" \"source_retrieve_after_inference\": {\n",
" \"shape\": tuple(retrieved_kv.shape),\n",
" \"dtype\": str(retrieved_kv.dtype),\n",
" \"hit_tokens\": retrieved_hit_tokens,\n",
" \"hit_chunks\": retrieved_hit_chunks,\n",
" },\n",
" \"source_latency_seconds\": source_completion.elapsed_seconds,\n",
" \"target_latency_seconds\": target_completion.elapsed_seconds,\n",
" \"source_output_preview\": \" \".join(source_completion.text.split()[:max_tokens]),\n",
" \"target_output_preview\": \" \".join(target_completion.text.split()[:max_tokens]),\n",
"}\n",
"print(\"== Evaluation ==\")\n",
"print(json.dumps(cast(dict[str, object], evaluation), indent=2, default=str))\n",
"if not outputs_match:\n",
" raise RuntimeError(\"Target output did not match source output after KV remap.\")"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "f83db925",
"metadata": {},
"outputs": [],
"source": [
"# Only close the context when done.\n",
"lmc_sdk.close(ctx)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "LMCache (3.12.3.final.0)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}