chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:17:40 +08:00
commit f1825c8ceb
10096 changed files with 2364182 additions and 0 deletions
View File
@@ -0,0 +1,221 @@
"""
Example: Resetting KV Cache in Ray Serve LLM via Control Plane Messages.
This example demonstrates two approaches to reset the KV cache on all replicas
of a Ray Serve LLM deployment using DevIngress:
1. **HTTP Endpoint Path** (`--use-http`):
Calls the built-in `/reset_prefix_cache` HTTP endpoint provided by
DevIngress via CacheManagerIngressMixin. Useful for external clients.
2. **In-Cluster Serve Handle Path** (default):
Uses Ray Serve's deployment handles and the broadcast API to send control
plane messages directly to all replicas. This keeps cache reset logic
within the cluster, avoiding HTTP overhead.
Both approaches use the same DevIngress server which provides control plane
endpoints (/sleep, /wakeup, /is_sleeping, /reset_prefix_cache).
The example:
1. Starts a Serve application with DevIngress and 2 replicas
2. Populates the KV cache on both replicas by sending multiple requests
3. Measures request time for a cached request (control)
4. Resets the KV cache using the selected method
5. Measures request time after cache reset (test)
6. Verifies that the cache was cleared by comparing request times
Usage:
# In-cluster path (using serve handles directly)
python reset_kv_cache_example.py
# HTTP endpoint path
python reset_kv_cache_example.py --use-http
"""
import argparse
import asyncio
import time
import httpx
from ray import serve
from ray.llm._internal.serve.core.ingress.dev_ingress import build_dev_openai_app
from ray.llm._internal.serve.utils.broadcast import broadcast
from ray.serve.llm import LLMConfig
# =============================================================================
# Server Startup
# =============================================================================
def create_llm_config(model: str) -> LLMConfig:
"""Create the LLM configuration."""
return LLMConfig(
model_loading_config=dict(model_id=model),
deployment_config=dict(num_replicas=2, name="llm"),
engine_kwargs=dict(
enable_prefix_caching=True,
enforce_eager=True,
max_num_batched_tokens=128,
),
)
def start_server(llm_config: LLMConfig):
"""Start the server with DevIngress for control plane endpoints.
DevIngress provides built-in control plane endpoints:
- /reset_prefix_cache (via CacheManagerIngressMixin)
- /sleep, /wakeup, /is_sleeping (via SleepableIngressMixin)
"""
app = build_dev_openai_app({"llm_configs": [llm_config]})
print("Starting server with DevIngress...")
serve.run(app)
print("Server started. Control plane endpoints available.")
# =============================================================================
# Cache Reset Functions
# =============================================================================
async def reset_cache_via_http(model: str):
"""Reset KV cache via HTTP endpoint.
This calls the /reset_prefix_cache endpoint provided by DevIngress
via CacheManagerIngressMixin.
"""
url = "http://localhost:8000/reset_prefix_cache"
async with httpx.AsyncClient(timeout=60.0) as client:
response = await client.post(url, json={"model": model})
response.raise_for_status()
def reset_cache_via_handle(model: str):
"""Reset KV cache via in-cluster serve handle.
This uses the broadcast API to send control plane messages directly to
all replicas without exposing functionality over HTTP.
"""
llm_handle = serve.get_deployment_handle("LLMServer:llm", app_name="default")
broadcast(llm_handle, "reset_prefix_cache")
# =============================================================================
# Test Utilities
# =============================================================================
async def send_request(prompt: str, model: str, measure_time: bool = False):
"""Send a completion request and optionally measure response time."""
url = "http://localhost:8000/v1/completions"
data = {
"model": model,
"prompt": prompt,
"max_tokens": 1,
}
start_time = time.time() if measure_time else None
async with httpx.AsyncClient(timeout=60.0) as client:
response = await client.post(url, json=data)
response.raise_for_status()
if measure_time:
return time.time() - start_time
async def populate_cache(prompts: list[str], model: str, repeat: int = 20):
"""Send requests multiple times to populate cache on all replicas."""
print(
f"Populating cache with {len(prompts)} prompt(s), repeating {repeat} times..."
)
tasks = []
for _ in range(repeat):
for prompt in prompts:
tasks.append(send_request(prompt, model, measure_time=False))
await asyncio.gather(*tasks)
print("Cache populated.")
# =============================================================================
# Main
# =============================================================================
async def main(use_http: bool):
model = "Qwen/Qwen2.5-0.5B-Instruct"
# Create LLM config and start server
llm_config = create_llm_config(model)
start_server(llm_config)
# Determine reset method
reset_method = (
"HTTP endpoint (/reset_prefix_cache)"
if use_http
else "in-cluster serve handle (broadcast API)"
)
print(f"\nUsing {reset_method} for cache reset.\n")
# Use long prompts to ensure prefill time is significant
TEST_PROMPT = "The quick brown fox jumps over the lazy dog." * 3000
# 2. Populate cache on all replicas
print("Step 1: Populating cache on all replicas...")
await populate_cache([TEST_PROMPT], model, repeat=20)
# 3. Measure request time for cached request (control)
print("\nStep 2: Measuring request time for cached request (control)...")
control_time = await send_request(TEST_PROMPT, model, measure_time=True)
print(f"Request time (cached): {control_time:.4f}s")
# 4. Reset the KV cache
print(f"\nStep 3: Resetting KV cache via {reset_method}...")
if use_http:
await reset_cache_via_http(model)
else:
reset_cache_via_handle(model)
print("KV cache reset complete.")
# 5. Measure request time after cache reset (test)
print("\nStep 4: Measuring request time after cache reset (test)...")
test_time = await send_request(TEST_PROMPT, model, measure_time=True)
print(f"Request time (after reset): {test_time:.4f}s")
# 6. Verify the results
print("\nStep 5: Verifying results...")
print(f"Control (cached) time: {control_time:.4f}s")
print(f"Test (after reset) time: {test_time:.4f}s")
print(f"Slowdown factor: {test_time / control_time:.2f}x slower after reset")
if test_time > control_time * 10: # At least 10x slower on L4 instances
print(
"✓ SUCCESS: Request time increased after cache reset, "
"indicating cache was cleared."
)
else:
print(
"✗ WARNING: Request time did not increase significantly. "
"Cache may not have been reset properly."
)
print("\nDone. Shutting down...")
time.sleep(2)
serve.shutdown()
print("Shutdown complete.")
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Demonstrate KV cache reset in Ray Serve LLM.",
)
parser.add_argument(
"--use-http",
action="store_true",
help="Reset cache via HTTP /reset_prefix_cache endpoint instead of "
"in-cluster serve handles. Both use the same DevIngress server.",
)
args = parser.parse_args()
asyncio.run(main(use_http=args.use_http))
@@ -0,0 +1,49 @@
"""Batch inference with SGLang using Ray Data.
Usage:
RAY_EXPERIMENTAL_NOSET_CUDA_VISIBLE_DEVICES=0 python batch_sglang_example.py
"""
import ray
from ray.data.llm import SGLangEngineProcessorConfig, build_processor
config = SGLangEngineProcessorConfig(
model_source="unsloth/Llama-3.1-8B-Instruct",
engine_kwargs=dict(
dtype="half",
mem_fraction_static=0.8,
),
batch_size=32,
concurrency=1,
)
processor = build_processor(
config,
preprocess=lambda row: dict(
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": row["prompt"]},
],
sampling_params=dict(
temperature=0.7,
max_new_tokens=256,
),
),
postprocess=lambda row: dict(
prompt=row["prompt"],
response=row["generated_text"],
),
)
ds = ray.data.from_items(
[
{"prompt": "What is the capital of France?"},
{"prompt": "Explain photosynthesis in one sentence."},
{"prompt": "Write a haiku about programming."},
]
)
ds = processor(ds)
for row in ds.take_all():
print(f"Prompt: {row['prompt']}")
print(f"Response: {row['response']}\n")
@@ -0,0 +1,35 @@
"""Query client for an SGLang model served via Ray Serve LLM.
Prerequisites:
Start a serving example first, e.g.:
RAY_EXPERIMENTAL_NOSET_CUDA_VISIBLE_DEVICES=0 serve run serve_sglang_example:app
Usage:
python query_example.py
"""
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="fake-key")
# Chat completions
print("=== Chat Completions ===")
chat_response = client.chat.completions.create(
model="Llama-3.1-8B-Instruct",
messages=[
{"role": "user", "content": "List 3 countries and their capitals."},
],
temperature=0,
max_tokens=64,
)
print(chat_response.choices[0].message.content)
# Text completions
print("\n=== Text Completions ===")
completion_response = client.completions.create(
model="Llama-3.1-8B-Instruct",
prompt="San Francisco is a",
temperature=0,
max_tokens=30,
)
print(completion_response.choices[0].text)
+27
View File
@@ -0,0 +1,27 @@
# SGLang on Ray Serve LLM
This directory contains example scripts for using SGLang with Ray Serve LLM.
## Examples
| File | Description |
|------|-------------|
| `serve_sglang_example.py` | Single-node SGLang serving with autoscaling |
| `serve_sglang_multinode_example.py` | Multi-node serving with tensor and pipeline parallelism |
| `batch_sglang_example.py` | Batch inference using Ray Data |
| `query_example.py` | OpenAI client for querying a running deployment |
## Prerequisites
```bash
pip install ray[serve,llm] "sglang[all,ray]"
```
Set the environment variable before running:
- **CUDA:** `RAY_EXPERIMENTAL_NOSET_CUDA_VISIBLE_DEVICES=0`
- **ROCm:** `RAY_EXPERIMENTAL_NOSET_HIP_VISIBLE_DEVICES=0`
## Engine implementation
The `SGLangServer` class is located at `ray.llm._internal.serve.engines.sglang` and wraps SGLang's in-process engine with the Ray Serve LLM server protocol.
@@ -0,0 +1,33 @@
"""Single-node SGLang serving example using Ray Serve LLM.
Usage:
RAY_EXPERIMENTAL_NOSET_CUDA_VISIBLE_DEVICES=0 serve run serve_sglang_example:app
"""
from ray import serve
from ray.llm._internal.serve.engines.sglang import SGLangServer
from ray.serve.llm import LLMConfig, build_openai_app
llm_config = LLMConfig(
model_loading_config={
"model_id": "Llama-3.1-8B-Instruct",
"model_source": "unsloth/Llama-3.1-8B-Instruct",
},
deployment_config={
"autoscaling_config": {
"min_replicas": 1,
"max_replicas": 2,
}
},
server_cls=SGLangServer,
engine_kwargs={
"trust_remote_code": True,
"model_path": "unsloth/Llama-3.1-8B-Instruct",
"tp_size": 1,
"mem_fraction_static": 0.8,
},
)
app = build_openai_app({"llm_configs": [llm_config]})
serve.start()
serve.run(app, blocking=True)
@@ -0,0 +1,46 @@
"""Multi-node SGLang serving example with tensor and pipeline parallelism.
Requirements:
- 2 nodes with 4 GPUs each (8 GPUs total for tp_size=4, pp_size=2)
- pip install ray[serve,llm] "sglang[all,ray]"
- Set RAY_EXPERIMENTAL_NOSET_CUDA_VISIBLE_DEVICES=0
Usage:
RAY_EXPERIMENTAL_NOSET_CUDA_VISIBLE_DEVICES=0 serve run serve_sglang_multinode_example:app
"""
from ray import serve
from ray.llm._internal.serve.engines.sglang import SGLangServer
from ray.serve.llm import LLMConfig, build_openai_app
llm_config = LLMConfig(
model_loading_config={
"model_id": "Llama-3.1-70B-Instruct",
"model_source": "meta-llama/Llama-3.1-70B-Instruct",
},
deployment_config={
"autoscaling_config": {
"min_replicas": 1,
"max_replicas": 2,
"target_ongoing_requests": 4,
}
},
# PACK fills GPUs on each node before moving to the next.
# With 8 bundles across 2 nodes (4 GPUs each), each node gets 4 bundles.
placement_group_config={
"placement_group_bundles": [{"GPU": 1}] * 8,
"placement_group_strategy": "PACK",
},
server_cls=SGLangServer,
engine_kwargs={
"model_path": "meta-llama/Llama-3.1-70B-Instruct",
"tp_size": 4,
"pp_size": 2,
"mem_fraction_static": 0.8,
},
)
app = build_openai_app({"llm_configs": [llm_config]})
if __name__ == "__main__":
serve.run(app, blocking=True)