xpyd-proxy 1.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xpyd_proxy-1.2.0/PKG-INFO +307 -0
- xpyd_proxy-1.2.0/README.md +281 -0
- xpyd_proxy-1.2.0/pyproject.toml +46 -0
- xpyd_proxy-1.2.0/setup.cfg +4 -0
- xpyd_proxy-1.2.0/xpyd/__init__.py +0 -0
- xpyd_proxy-1.2.0/xpyd/circuit_breaker.py +165 -0
- xpyd_proxy-1.2.0/xpyd/config.py +631 -0
- xpyd_proxy-1.2.0/xpyd/config_fixer.py +585 -0
- xpyd_proxy-1.2.0/xpyd/discovery.py +224 -0
- xpyd_proxy-1.2.0/xpyd/errors.py +32 -0
- xpyd_proxy-1.2.0/xpyd/health_monitor.py +82 -0
- xpyd_proxy-1.2.0/xpyd/init_config.py +79 -0
- xpyd_proxy-1.2.0/xpyd/metrics.py +256 -0
- xpyd_proxy-1.2.0/xpyd/proxy.py +903 -0
- xpyd_proxy-1.2.0/xpyd/py.typed +0 -0
- xpyd_proxy-1.2.0/xpyd/registry.py +346 -0
- xpyd_proxy-1.2.0/xpyd/resilience.py +181 -0
- xpyd_proxy-1.2.0/xpyd/routes/__init__.py +17 -0
- xpyd_proxy-1.2.0/xpyd/routes/admin.py +102 -0
- xpyd_proxy-1.2.0/xpyd/routes/completions.py +551 -0
- xpyd_proxy-1.2.0/xpyd/routes/forward.py +61 -0
- xpyd_proxy-1.2.0/xpyd/routes/health.py +61 -0
- xpyd_proxy-1.2.0/xpyd/scheduler/__init__.py +21 -0
- xpyd_proxy-1.2.0/xpyd/scheduler/cache_aware.py +241 -0
- xpyd_proxy-1.2.0/xpyd/scheduler/consistent_hash.py +201 -0
- xpyd_proxy-1.2.0/xpyd/scheduler/load_balanced.py +213 -0
- xpyd_proxy-1.2.0/xpyd/scheduler/policy_registry.py +93 -0
- xpyd_proxy-1.2.0/xpyd/scheduler/power_of_two.py +151 -0
- xpyd_proxy-1.2.0/xpyd/scheduler/round_robin.py +51 -0
- xpyd_proxy-1.2.0/xpyd/scheduler/scheduler_base.py +46 -0
- xpyd_proxy-1.2.0/xpyd/topology.py +98 -0
- xpyd_proxy-1.2.0/xpyd/utils.py +105 -0
- xpyd_proxy-1.2.0/xpyd_proxy.egg-info/PKG-INFO +307 -0
- xpyd_proxy-1.2.0/xpyd_proxy.egg-info/SOURCES.txt +36 -0
- xpyd_proxy-1.2.0/xpyd_proxy.egg-info/dependency_links.txt +1 -0
- xpyd_proxy-1.2.0/xpyd_proxy.egg-info/entry_points.txt +2 -0
- xpyd_proxy-1.2.0/xpyd_proxy.egg-info/requires.txt +20 -0
- xpyd_proxy-1.2.0/xpyd_proxy.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xpyd-proxy
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: Lightweight Prefill-Decode proxy for disaggregated LLM serving
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: fastapi>=0.110.0
|
|
8
|
+
Requires-Dist: uvicorn>=0.29.0
|
|
9
|
+
Requires-Dist: uvloop>=0.19.0
|
|
10
|
+
Requires-Dist: pydantic>=2.0.0
|
|
11
|
+
Requires-Dist: httpx>=0.27.0
|
|
12
|
+
Requires-Dist: aiohttp>=3.9.0
|
|
13
|
+
Requires-Dist: requests>=2.31.0
|
|
14
|
+
Requires-Dist: colorlog>=6.8.0
|
|
15
|
+
Requires-Dist: transformers>=4.38.0
|
|
16
|
+
Requires-Dist: prometheus-client>=0.20.0
|
|
17
|
+
Requires-Dist: PyYAML>=6.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
20
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
21
|
+
Requires-Dist: ruff>=0.4.0; extra == "dev"
|
|
22
|
+
Requires-Dist: isort>=5.13.0; extra == "dev"
|
|
23
|
+
Requires-Dist: tiktoken>=0.7.0; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-timeout>=2.3.0; extra == "dev"
|
|
25
|
+
Requires-Dist: xpyd-sim>=0.2.0; extra == "dev"
|
|
26
|
+
|
|
27
|
+
# xPyD Proxy
|
|
28
|
+
|
|
29
|
+
A lightweight Prefill-Decode (PD) proxy for disaggregated LLM serving.
|
|
30
|
+
|
|
31
|
+
## Architecture
|
|
32
|
+
|
|
33
|
+
xPyD Proxy supports two operating modes:
|
|
34
|
+
|
|
35
|
+
### Prefill-Decode (P/D) Disaggregated Mode
|
|
36
|
+
|
|
37
|
+
Requests are routed through two phases with KV cache transfer:
|
|
38
|
+
|
|
39
|
+
1. **Prefill** — KV cache preparation on prefill nodes (`max_tokens=1`)
|
|
40
|
+
2. **Decode** — autoregressive token generation on decode nodes (receives KV cache from prefill)
|
|
41
|
+
|
|
42
|
+
### Dual-Role Mode
|
|
43
|
+
|
|
44
|
+
A single instance handles both prefill and decode in one pass — no KV transfer needed. This simplifies deployment when disaggregation is not required or for smaller-scale setups.
|
|
45
|
+
|
|
46
|
+
The proxy handles scheduling (load-balanced, round-robin, consistent hash, power-of-two, cache-aware), health monitoring, circuit breaking, and dynamic instance management. Multi-model routing allows serving multiple models through a single proxy with per-model scheduler configuration.
|
|
47
|
+
|
|
48
|
+
See [`docs/architecture.md`](docs/architecture.md) for details.
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install .
|
|
54
|
+
|
|
55
|
+
# Verify
|
|
56
|
+
xpyd --version
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Quick Start
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Generate a config template
|
|
63
|
+
xpyd proxy --init-config
|
|
64
|
+
|
|
65
|
+
# Edit xpyd.yaml with your model and node addresses, then:
|
|
66
|
+
xpyd proxy -c xpyd.yaml
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Configuration
|
|
70
|
+
|
|
71
|
+
All configuration is done via YAML. Three config formats are supported.
|
|
72
|
+
|
|
73
|
+
### Format 1: Legacy (Single Model)
|
|
74
|
+
|
|
75
|
+
Simple prefill/decode address lists for a single model:
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
model: /path/to/model
|
|
79
|
+
prefill:
|
|
80
|
+
- "10.0.0.3:8100"
|
|
81
|
+
decode:
|
|
82
|
+
- "10.0.0.1:8200"
|
|
83
|
+
- "10.0.0.2:8200"
|
|
84
|
+
port: 8000
|
|
85
|
+
scheduling: loadbalanced
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Topology-style config is also supported in Format 1:
|
|
89
|
+
|
|
90
|
+
```yaml
|
|
91
|
+
model: /path/to/model
|
|
92
|
+
port: 8868
|
|
93
|
+
|
|
94
|
+
prefill:
|
|
95
|
+
nodes:
|
|
96
|
+
- "10.0.0.1:8100"
|
|
97
|
+
tp_size: 8
|
|
98
|
+
dp_size: 1
|
|
99
|
+
world_size_per_node: 8
|
|
100
|
+
|
|
101
|
+
decode:
|
|
102
|
+
nodes:
|
|
103
|
+
- "10.0.0.2:8200"
|
|
104
|
+
- "10.0.0.3:8200"
|
|
105
|
+
tp_size: 1
|
|
106
|
+
dp_size: 16
|
|
107
|
+
world_size_per_node: 8
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Format 2: Instances (Multi-Model, Per-Instance Role)
|
|
111
|
+
|
|
112
|
+
Explicit per-instance configuration with role and model assignment. Supports `dual` role:
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
instances:
|
|
116
|
+
- address: "10.0.0.1:8000"
|
|
117
|
+
role: prefill
|
|
118
|
+
model: llama-3
|
|
119
|
+
- address: "10.0.0.2:8000"
|
|
120
|
+
role: decode
|
|
121
|
+
model: llama-3
|
|
122
|
+
- address: "10.0.0.3:8000"
|
|
123
|
+
role: dual
|
|
124
|
+
model: qwen-2
|
|
125
|
+
port: 8000
|
|
126
|
+
scheduling: loadbalanced
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Format 3: Models Shorthand (Multi-Model, Per-Model Scheduler)
|
|
130
|
+
|
|
131
|
+
Compact format with per-model scheduler override and `dual` shorthand:
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
models:
|
|
135
|
+
- name: llama-3
|
|
136
|
+
prefill:
|
|
137
|
+
- "10.0.0.1:8000"
|
|
138
|
+
decode:
|
|
139
|
+
- "10.0.0.2:8000"
|
|
140
|
+
scheduler: round_robin
|
|
141
|
+
- name: qwen-2
|
|
142
|
+
dual:
|
|
143
|
+
- "10.0.0.3:8000"
|
|
144
|
+
- "10.0.0.4:8000"
|
|
145
|
+
scheduler: loadbalanced
|
|
146
|
+
port: 8000
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
> **Note:** `instances` and `models` cannot be combined. Legacy `prefill`/`decode` lists cannot be used with `instances` or `models`.
|
|
150
|
+
|
|
151
|
+
See [`examples/proxy.yaml`](examples/proxy.yaml) for a fully-commented example.
|
|
152
|
+
|
|
153
|
+
### CLI Reference
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
xpyd proxy [OPTIONS]
|
|
157
|
+
|
|
158
|
+
Options:
|
|
159
|
+
--config, -c PATH Path to YAML config (default: ./xpyd.yaml or XPYD_CONFIG env)
|
|
160
|
+
--init-config [PATH] Generate a config template and exit
|
|
161
|
+
--validate-config PATH Validate a config file and exit
|
|
162
|
+
--port PORT Override port from config
|
|
163
|
+
--log-level LEVEL Override log level: debug|info|warning|error
|
|
164
|
+
--version, -V Show version and exit
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
xpyd fix-config CONFIG_PATH [OPTIONS]
|
|
169
|
+
|
|
170
|
+
Auto-fix common config mistakes (typos, missing ports, whitespace).
|
|
171
|
+
|
|
172
|
+
Arguments:
|
|
173
|
+
CONFIG_PATH Path to YAML config file to fix
|
|
174
|
+
|
|
175
|
+
Options:
|
|
176
|
+
--write Write fixes back to file (creates timestamped .bak backup).
|
|
177
|
+
Note: does not preserve YAML comments or formatting.
|
|
178
|
+
--interactive Prompt for confirmation on ambiguous suggestions
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Config resolution order
|
|
182
|
+
|
|
183
|
+
1. `--config` / `-c` CLI argument
|
|
184
|
+
2. `XPYD_CONFIG` environment variable
|
|
185
|
+
3. `./xpyd.yaml` in the current directory
|
|
186
|
+
|
|
187
|
+
### YAML Config
|
|
188
|
+
|
|
189
|
+
```yaml
|
|
190
|
+
# Required
|
|
191
|
+
model: /path/to/model
|
|
192
|
+
decode:
|
|
193
|
+
- "10.0.0.1:8200"
|
|
194
|
+
- "10.0.0.2:8200"
|
|
195
|
+
|
|
196
|
+
# Optional
|
|
197
|
+
prefill:
|
|
198
|
+
- "10.0.0.3:8100"
|
|
199
|
+
port: 8000
|
|
200
|
+
log_level: warning
|
|
201
|
+
scheduling: loadbalanced # roundrobin | loadbalanced | consistent_hash | power_of_two | cache_aware
|
|
202
|
+
generator_on_p_node: false
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
See [`examples/proxy.yaml`](examples/proxy.yaml) for a fully-commented example.
|
|
206
|
+
|
|
207
|
+
### YAML Fields Reference
|
|
208
|
+
|
|
209
|
+
| Field | Type | Default | Description |
|
|
210
|
+
|---|---|---|---|
|
|
211
|
+
| `model` | string | — | Model name / path (required in Format 1) |
|
|
212
|
+
| `port` | int | 8000 | Proxy listen port |
|
|
213
|
+
| `log_level` | string | warning | Log level: debug, info, warning, error |
|
|
214
|
+
| `prefill` | list or topology | [] | Prefill node config (Format 1) |
|
|
215
|
+
| `decode` | list or topology | — | Decode node config (Format 1, required) |
|
|
216
|
+
| `instances` | list | — | Per-instance config (Format 2): `{address, role, model}` |
|
|
217
|
+
| `models` | list | — | Per-model shorthand (Format 3): `{name, prefill, decode, dual, scheduler}` |
|
|
218
|
+
| `scheduling` | string | loadbalanced | Global scheduling policy |
|
|
219
|
+
| `scheduling_config` | dict | {} | Policy-specific options |
|
|
220
|
+
| `generator_on_p_node` | bool | false | Generate first token on prefill node |
|
|
221
|
+
| `admin_api_key` | string | — | Admin API key (env `ADMIN_API_KEY` overrides) |
|
|
222
|
+
| `openai_api_key` | string | — | OpenAI API key (env `OPENAI_API_KEY` overrides) |
|
|
223
|
+
| `startup.wait_timeout_seconds` | int | 600 | Max wait for nodes at startup |
|
|
224
|
+
| `startup.probe_interval_seconds` | int | 10 | Health probe interval |
|
|
225
|
+
|
|
226
|
+
**Valid `role` values:** `prefill`, `decode`, `dual`
|
|
227
|
+
|
|
228
|
+
**Valid `scheduling` values:** `loadbalanced`, `roundrobin` (alias: `round_robin`), `load_balanced`, `consistent_hash`, `power_of_two`, `cache_aware`
|
|
229
|
+
|
|
230
|
+
### API
|
|
231
|
+
|
|
232
|
+
The proxy exposes an OpenAI-compatible API:
|
|
233
|
+
|
|
234
|
+
- **`POST /v1/chat/completions`** — Chat completions (streaming and non-streaming)
|
|
235
|
+
- **`POST /v1/completions`** — Text completions (streaming and non-streaming)
|
|
236
|
+
- **`GET /v1/models`** — List all registered models in OpenAI-compatible format
|
|
237
|
+
|
|
238
|
+
### Startup Node Discovery
|
|
239
|
+
|
|
240
|
+
The proxy returns **503** on business endpoints until the minimum instance requirement is met: at least **1 prefill + 1 decode** node, or **1 dual** node per model must respond healthy. Health/status/metrics endpoints are always available.
|
|
241
|
+
|
|
242
|
+
## Docker
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
# Full local topology (prefill + decode + proxy)
|
|
246
|
+
docker compose up --build
|
|
247
|
+
|
|
248
|
+
# Proxy only, connecting to existing GPU nodes
|
|
249
|
+
docker build -t xpyd .
|
|
250
|
+
docker run -p 8868:8868 -v ./config.yaml:/app/xpyd.yaml xpyd
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
See [`docs/deployment.md`](docs/deployment.md) for production deployment.
|
|
254
|
+
|
|
255
|
+
## Benchmark
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
python -m vllm bench serve \
|
|
259
|
+
--base-url http://localhost:8868 \
|
|
260
|
+
--model DeepSeek-R1 \
|
|
261
|
+
--dataset-name sonnet \
|
|
262
|
+
--sonnet-input-len 1024 \
|
|
263
|
+
--sonnet-output-len 128 \
|
|
264
|
+
--num-prompts 100 \
|
|
265
|
+
--request-rate 10
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Development
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
# Install in dev mode
|
|
272
|
+
pip install -e ".[dev]"
|
|
273
|
+
|
|
274
|
+
# Run tests
|
|
275
|
+
python -m pytest tests/unit/ tests/integration/ -v
|
|
276
|
+
|
|
277
|
+
# Lint
|
|
278
|
+
pre-commit run --all-files
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Environment Variables
|
|
282
|
+
|
|
283
|
+
| Variable | Description |
|
|
284
|
+
|---|---|
|
|
285
|
+
| `XPYD_CONFIG` | Default config file path |
|
|
286
|
+
| `ADMIN_API_KEY` | Admin API key (overrides YAML) |
|
|
287
|
+
| `OPENAI_API_KEY` | Bearer token for backend nodes (overrides YAML) |
|
|
288
|
+
| `PREFILL_DELAY_PER_TOKEN` | Simulated prefill latency for dummy nodes (default: 0.001s) |
|
|
289
|
+
| `DECODE_DELAY_PER_TOKEN` | Simulated decode latency for dummy nodes (default: 0.01s) |
|
|
290
|
+
|
|
291
|
+
## Documentation
|
|
292
|
+
|
|
293
|
+
| Document | Description |
|
|
294
|
+
|---|---|
|
|
295
|
+
| [Architecture](docs/architecture.md) | System architecture overview |
|
|
296
|
+
| [API Reference](docs/api_reference.md) | HTTP API endpoints |
|
|
297
|
+
| [Configuration](docs/configuration.md) | YAML config reference |
|
|
298
|
+
| [CLI](docs/cli.md) | xpyd command-line tool |
|
|
299
|
+
| [Scheduling](docs/scheduling.md) | Load balancing strategies |
|
|
300
|
+
| [Resilience](docs/resilience.md) | Health checks, circuit breakers, retry |
|
|
301
|
+
| [Metrics](docs/metrics.md) | Prometheus metrics endpoint |
|
|
302
|
+
| [Deployment](docs/deployment.md) | Deployment and Docker guide |
|
|
303
|
+
| [Contributing](CONTRIBUTING.md) | Contribution guidelines |
|
|
304
|
+
|
|
305
|
+
## License
|
|
306
|
+
|
|
307
|
+
Apache-2.0
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# xPyD Proxy
|
|
2
|
+
|
|
3
|
+
A lightweight Prefill-Decode (PD) proxy for disaggregated LLM serving.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
xPyD Proxy supports two operating modes:
|
|
8
|
+
|
|
9
|
+
### Prefill-Decode (P/D) Disaggregated Mode
|
|
10
|
+
|
|
11
|
+
Requests are routed through two phases with KV cache transfer:
|
|
12
|
+
|
|
13
|
+
1. **Prefill** — KV cache preparation on prefill nodes (`max_tokens=1`)
|
|
14
|
+
2. **Decode** — autoregressive token generation on decode nodes (receives KV cache from prefill)
|
|
15
|
+
|
|
16
|
+
### Dual-Role Mode
|
|
17
|
+
|
|
18
|
+
A single instance handles both prefill and decode in one pass — no KV transfer needed. This simplifies deployment when disaggregation is not required or for smaller-scale setups.
|
|
19
|
+
|
|
20
|
+
The proxy handles scheduling (load-balanced, round-robin, consistent hash, power-of-two, cache-aware), health monitoring, circuit breaking, and dynamic instance management. Multi-model routing allows serving multiple models through a single proxy with per-model scheduler configuration.
|
|
21
|
+
|
|
22
|
+
See [`docs/architecture.md`](docs/architecture.md) for details.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install .
|
|
28
|
+
|
|
29
|
+
# Verify
|
|
30
|
+
xpyd --version
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Generate a config template
|
|
37
|
+
xpyd proxy --init-config
|
|
38
|
+
|
|
39
|
+
# Edit xpyd.yaml with your model and node addresses, then:
|
|
40
|
+
xpyd proxy -c xpyd.yaml
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Configuration
|
|
44
|
+
|
|
45
|
+
All configuration is done via YAML. Three config formats are supported.
|
|
46
|
+
|
|
47
|
+
### Format 1: Legacy (Single Model)
|
|
48
|
+
|
|
49
|
+
Simple prefill/decode address lists for a single model:
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
model: /path/to/model
|
|
53
|
+
prefill:
|
|
54
|
+
- "10.0.0.3:8100"
|
|
55
|
+
decode:
|
|
56
|
+
- "10.0.0.1:8200"
|
|
57
|
+
- "10.0.0.2:8200"
|
|
58
|
+
port: 8000
|
|
59
|
+
scheduling: loadbalanced
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Topology-style config is also supported in Format 1:
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
model: /path/to/model
|
|
66
|
+
port: 8868
|
|
67
|
+
|
|
68
|
+
prefill:
|
|
69
|
+
nodes:
|
|
70
|
+
- "10.0.0.1:8100"
|
|
71
|
+
tp_size: 8
|
|
72
|
+
dp_size: 1
|
|
73
|
+
world_size_per_node: 8
|
|
74
|
+
|
|
75
|
+
decode:
|
|
76
|
+
nodes:
|
|
77
|
+
- "10.0.0.2:8200"
|
|
78
|
+
- "10.0.0.3:8200"
|
|
79
|
+
tp_size: 1
|
|
80
|
+
dp_size: 16
|
|
81
|
+
world_size_per_node: 8
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Format 2: Instances (Multi-Model, Per-Instance Role)
|
|
85
|
+
|
|
86
|
+
Explicit per-instance configuration with role and model assignment. Supports `dual` role:
|
|
87
|
+
|
|
88
|
+
```yaml
|
|
89
|
+
instances:
|
|
90
|
+
- address: "10.0.0.1:8000"
|
|
91
|
+
role: prefill
|
|
92
|
+
model: llama-3
|
|
93
|
+
- address: "10.0.0.2:8000"
|
|
94
|
+
role: decode
|
|
95
|
+
model: llama-3
|
|
96
|
+
- address: "10.0.0.3:8000"
|
|
97
|
+
role: dual
|
|
98
|
+
model: qwen-2
|
|
99
|
+
port: 8000
|
|
100
|
+
scheduling: loadbalanced
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Format 3: Models Shorthand (Multi-Model, Per-Model Scheduler)
|
|
104
|
+
|
|
105
|
+
Compact format with per-model scheduler override and `dual` shorthand:
|
|
106
|
+
|
|
107
|
+
```yaml
|
|
108
|
+
models:
|
|
109
|
+
- name: llama-3
|
|
110
|
+
prefill:
|
|
111
|
+
- "10.0.0.1:8000"
|
|
112
|
+
decode:
|
|
113
|
+
- "10.0.0.2:8000"
|
|
114
|
+
scheduler: round_robin
|
|
115
|
+
- name: qwen-2
|
|
116
|
+
dual:
|
|
117
|
+
- "10.0.0.3:8000"
|
|
118
|
+
- "10.0.0.4:8000"
|
|
119
|
+
scheduler: loadbalanced
|
|
120
|
+
port: 8000
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
> **Note:** `instances` and `models` cannot be combined. Legacy `prefill`/`decode` lists cannot be used with `instances` or `models`.
|
|
124
|
+
|
|
125
|
+
See [`examples/proxy.yaml`](examples/proxy.yaml) for a fully-commented example.
|
|
126
|
+
|
|
127
|
+
### CLI Reference
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
xpyd proxy [OPTIONS]
|
|
131
|
+
|
|
132
|
+
Options:
|
|
133
|
+
--config, -c PATH Path to YAML config (default: ./xpyd.yaml or XPYD_CONFIG env)
|
|
134
|
+
--init-config [PATH] Generate a config template and exit
|
|
135
|
+
--validate-config PATH Validate a config file and exit
|
|
136
|
+
--port PORT Override port from config
|
|
137
|
+
--log-level LEVEL Override log level: debug|info|warning|error
|
|
138
|
+
--version, -V Show version and exit
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
xpyd fix-config CONFIG_PATH [OPTIONS]
|
|
143
|
+
|
|
144
|
+
Auto-fix common config mistakes (typos, missing ports, whitespace).
|
|
145
|
+
|
|
146
|
+
Arguments:
|
|
147
|
+
CONFIG_PATH Path to YAML config file to fix
|
|
148
|
+
|
|
149
|
+
Options:
|
|
150
|
+
--write Write fixes back to file (creates timestamped .bak backup).
|
|
151
|
+
Note: does not preserve YAML comments or formatting.
|
|
152
|
+
--interactive Prompt for confirmation on ambiguous suggestions
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Config resolution order
|
|
156
|
+
|
|
157
|
+
1. `--config` / `-c` CLI argument
|
|
158
|
+
2. `XPYD_CONFIG` environment variable
|
|
159
|
+
3. `./xpyd.yaml` in the current directory
|
|
160
|
+
|
|
161
|
+
### YAML Config
|
|
162
|
+
|
|
163
|
+
```yaml
|
|
164
|
+
# Required
|
|
165
|
+
model: /path/to/model
|
|
166
|
+
decode:
|
|
167
|
+
- "10.0.0.1:8200"
|
|
168
|
+
- "10.0.0.2:8200"
|
|
169
|
+
|
|
170
|
+
# Optional
|
|
171
|
+
prefill:
|
|
172
|
+
- "10.0.0.3:8100"
|
|
173
|
+
port: 8000
|
|
174
|
+
log_level: warning
|
|
175
|
+
scheduling: loadbalanced # roundrobin | loadbalanced | consistent_hash | power_of_two | cache_aware
|
|
176
|
+
generator_on_p_node: false
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
See [`examples/proxy.yaml`](examples/proxy.yaml) for a fully-commented example.
|
|
180
|
+
|
|
181
|
+
### YAML Fields Reference
|
|
182
|
+
|
|
183
|
+
| Field | Type | Default | Description |
|
|
184
|
+
|---|---|---|---|
|
|
185
|
+
| `model` | string | — | Model name / path (required in Format 1) |
|
|
186
|
+
| `port` | int | 8000 | Proxy listen port |
|
|
187
|
+
| `log_level` | string | warning | Log level: debug, info, warning, error |
|
|
188
|
+
| `prefill` | list or topology | [] | Prefill node config (Format 1) |
|
|
189
|
+
| `decode` | list or topology | — | Decode node config (Format 1, required) |
|
|
190
|
+
| `instances` | list | — | Per-instance config (Format 2): `{address, role, model}` |
|
|
191
|
+
| `models` | list | — | Per-model shorthand (Format 3): `{name, prefill, decode, dual, scheduler}` |
|
|
192
|
+
| `scheduling` | string | loadbalanced | Global scheduling policy |
|
|
193
|
+
| `scheduling_config` | dict | {} | Policy-specific options |
|
|
194
|
+
| `generator_on_p_node` | bool | false | Generate first token on prefill node |
|
|
195
|
+
| `admin_api_key` | string | — | Admin API key (env `ADMIN_API_KEY` overrides) |
|
|
196
|
+
| `openai_api_key` | string | — | OpenAI API key (env `OPENAI_API_KEY` overrides) |
|
|
197
|
+
| `startup.wait_timeout_seconds` | int | 600 | Max wait for nodes at startup |
|
|
198
|
+
| `startup.probe_interval_seconds` | int | 10 | Health probe interval |
|
|
199
|
+
|
|
200
|
+
**Valid `role` values:** `prefill`, `decode`, `dual`
|
|
201
|
+
|
|
202
|
+
**Valid `scheduling` values:** `loadbalanced`, `roundrobin` (alias: `round_robin`), `load_balanced`, `consistent_hash`, `power_of_two`, `cache_aware`
|
|
203
|
+
|
|
204
|
+
### API
|
|
205
|
+
|
|
206
|
+
The proxy exposes an OpenAI-compatible API:
|
|
207
|
+
|
|
208
|
+
- **`POST /v1/chat/completions`** — Chat completions (streaming and non-streaming)
|
|
209
|
+
- **`POST /v1/completions`** — Text completions (streaming and non-streaming)
|
|
210
|
+
- **`GET /v1/models`** — List all registered models in OpenAI-compatible format
|
|
211
|
+
|
|
212
|
+
### Startup Node Discovery
|
|
213
|
+
|
|
214
|
+
The proxy returns **503** on business endpoints until the minimum instance requirement is met: at least **1 prefill + 1 decode** node, or **1 dual** node per model must respond healthy. Health/status/metrics endpoints are always available.
|
|
215
|
+
|
|
216
|
+
## Docker
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
# Full local topology (prefill + decode + proxy)
|
|
220
|
+
docker compose up --build
|
|
221
|
+
|
|
222
|
+
# Proxy only, connecting to existing GPU nodes
|
|
223
|
+
docker build -t xpyd .
|
|
224
|
+
docker run -p 8868:8868 -v ./config.yaml:/app/xpyd.yaml xpyd
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
See [`docs/deployment.md`](docs/deployment.md) for production deployment.
|
|
228
|
+
|
|
229
|
+
## Benchmark
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
python -m vllm bench serve \
|
|
233
|
+
--base-url http://localhost:8868 \
|
|
234
|
+
--model DeepSeek-R1 \
|
|
235
|
+
--dataset-name sonnet \
|
|
236
|
+
--sonnet-input-len 1024 \
|
|
237
|
+
--sonnet-output-len 128 \
|
|
238
|
+
--num-prompts 100 \
|
|
239
|
+
--request-rate 10
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Development
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
# Install in dev mode
|
|
246
|
+
pip install -e ".[dev]"
|
|
247
|
+
|
|
248
|
+
# Run tests
|
|
249
|
+
python -m pytest tests/unit/ tests/integration/ -v
|
|
250
|
+
|
|
251
|
+
# Lint
|
|
252
|
+
pre-commit run --all-files
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Environment Variables
|
|
256
|
+
|
|
257
|
+
| Variable | Description |
|
|
258
|
+
|---|---|
|
|
259
|
+
| `XPYD_CONFIG` | Default config file path |
|
|
260
|
+
| `ADMIN_API_KEY` | Admin API key (overrides YAML) |
|
|
261
|
+
| `OPENAI_API_KEY` | Bearer token for backend nodes (overrides YAML) |
|
|
262
|
+
| `PREFILL_DELAY_PER_TOKEN` | Simulated prefill latency for dummy nodes (default: 0.001s) |
|
|
263
|
+
| `DECODE_DELAY_PER_TOKEN` | Simulated decode latency for dummy nodes (default: 0.01s) |
|
|
264
|
+
|
|
265
|
+
## Documentation
|
|
266
|
+
|
|
267
|
+
| Document | Description |
|
|
268
|
+
|---|---|
|
|
269
|
+
| [Architecture](docs/architecture.md) | System architecture overview |
|
|
270
|
+
| [API Reference](docs/api_reference.md) | HTTP API endpoints |
|
|
271
|
+
| [Configuration](docs/configuration.md) | YAML config reference |
|
|
272
|
+
| [CLI](docs/cli.md) | xpyd command-line tool |
|
|
273
|
+
| [Scheduling](docs/scheduling.md) | Load balancing strategies |
|
|
274
|
+
| [Resilience](docs/resilience.md) | Health checks, circuit breakers, retry |
|
|
275
|
+
| [Metrics](docs/metrics.md) | Prometheus metrics endpoint |
|
|
276
|
+
| [Deployment](docs/deployment.md) | Deployment and Docker guide |
|
|
277
|
+
| [Contributing](CONTRIBUTING.md) | Contribution guidelines |
|
|
278
|
+
|
|
279
|
+
## License
|
|
280
|
+
|
|
281
|
+
Apache-2.0
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "xpyd-proxy"
|
|
7
|
+
version = "1.2.0"
|
|
8
|
+
description = "Lightweight Prefill-Decode proxy for disaggregated LLM serving"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"fastapi>=0.110.0",
|
|
13
|
+
"uvicorn>=0.29.0",
|
|
14
|
+
"uvloop>=0.19.0",
|
|
15
|
+
"pydantic>=2.0.0",
|
|
16
|
+
"httpx>=0.27.0",
|
|
17
|
+
"aiohttp>=3.9.0",
|
|
18
|
+
"requests>=2.31.0",
|
|
19
|
+
"colorlog>=6.8.0",
|
|
20
|
+
"transformers>=4.38.0",
|
|
21
|
+
"prometheus-client>=0.20.0",
|
|
22
|
+
"PyYAML>=6.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
dev = [
|
|
27
|
+
"pytest>=8.0.0",
|
|
28
|
+
"pytest-asyncio>=0.23.0",
|
|
29
|
+
"ruff>=0.4.0",
|
|
30
|
+
"isort>=5.13.0",
|
|
31
|
+
"tiktoken>=0.7.0",
|
|
32
|
+
"pytest-timeout>=2.3.0",
|
|
33
|
+
"xpyd-sim>=0.2.0",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
xpyd = "xpyd.proxy:main"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
include = ["xpyd*"]
|
|
41
|
+
|
|
42
|
+
[tool.pytest.ini_options]
|
|
43
|
+
pythonpath = ["."]
|
|
44
|
+
markers = [
|
|
45
|
+
"benchmark: end-to-end benchmark tests (high concurrency, large clusters)",
|
|
46
|
+
]
|
|
File without changes
|