242 lines
8.2 KiB
Markdown
242 lines
8.2 KiB
Markdown
|
|
# Layerwise KV Pool
|
|||
|
|
|
|||
|
|
Layerwise mode is an optimization for the AscendStore KV Pool that saves and
|
|||
|
|
loads KV cache **layer by layer** instead of as a single bulk copy. By pipelining
|
|||
|
|
the transfer of one layer with the attention computation of the next, it reduces
|
|||
|
|
the stall that occurs when the entire KV cache must arrive before any forward
|
|||
|
|
progress can be made.
|
|||
|
|
|
|||
|
|
Layerwise mode works in **both PD-Mixed** (`kv_role: "kv_both"`) and **PD
|
|||
|
|
disaggregation** (`kv_role: "kv_producer"` / `"kv_consumer"`) scenarios. See the
|
|||
|
|
[KV Pool guide](kv_pool.md) for the general KV Pool architecture and backend
|
|||
|
|
setup.
|
|||
|
|
|
|||
|
|
## How It Works (Brief)
|
|||
|
|
|
|||
|
|
Without layerwise mode, the KV cache for a request is saved to (or loaded from)
|
|||
|
|
the pool as one bulk operation after the full forward pass completes (or before
|
|||
|
|
it starts). For long prompts this bulk transfer introduces a serialization
|
|||
|
|
stall.
|
|||
|
|
|
|||
|
|
Layerwise mode splits the save/load at the layer granularity:
|
|||
|
|
|
|||
|
|
1. **Saving** (producer / kv_both): after computing layer *i*'s attention, the
|
|||
|
|
KV for that layer is immediately sent to the pool backend. The next layer's
|
|||
|
|
computation proceeds in parallel with the transfer.
|
|||
|
|
2. **Loading** (consumer / kv_both): before computing layer *i*'s attention, the
|
|||
|
|
system waits for layer *i*'s KV to arrive from the pool
|
|||
|
|
(`wait_for_layer_load`), then proceeds. The transfer of layer *i+1* overlaps
|
|||
|
|
with the attention computation of layer *i*.
|
|||
|
|
|
|||
|
|
The net effect: save/load latency is amortized across the forward pass rather
|
|||
|
|
than concentrated as a single blocking step.
|
|||
|
|
|
|||
|
|
## Prerequisites
|
|||
|
|
|
|||
|
|
Layerwise mode currently requires the **memcache** backend
|
|||
|
|
(`backend: "memcache"`). Install and configure memcache_hybrid before
|
|||
|
|
proceeding — see the [KV Pool guide](kv_pool.md) for memcache installation,
|
|||
|
|
config files (`mmc-meta.conf` / `mmc-local.conf`), and MetaService startup.
|
|||
|
|
|
|||
|
|
Additional setup:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Huge pages (required by memcache device transfer)
|
|||
|
|
echo 200000 > /proc/sys/vm/nr_hugepages
|
|||
|
|
|
|||
|
|
# Source memcache environment
|
|||
|
|
source /usr/local/memcache_hybrid/set_env.sh
|
|||
|
|
source /usr/local/memfabric_hybrid/set_env.sh
|
|||
|
|
|
|||
|
|
# Uniform hashing across nodes
|
|||
|
|
export PYTHONHASHSEED=0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Configuration
|
|||
|
|
|
|||
|
|
Add `use_layerwise: true` to the `AscendStoreConnector` extra config:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"kv_connector": "AscendStoreConnector",
|
|||
|
|
"kv_role": "kv_both",
|
|||
|
|
"kv_connector_extra_config": {
|
|||
|
|
"backend": "memcache",
|
|||
|
|
"mooncake_rpc_port": "0",
|
|||
|
|
"use_layerwise": true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Change `"kv_role"` to `"kv_producer"` or `"kv_consumer"` for PD disaggregation.
|
|||
|
|
|
|||
|
|
### Key Parameters
|
|||
|
|
|
|||
|
|
| Parameter | Default | Description |
|
|||
|
|
| :--- | :--- | :--- |
|
|||
|
|
| `use_layerwise` | `false` | Enable layer-by-layer KV save/load. Requires `backend: "memcache"`. |
|
|||
|
|
| `backend` | `"mooncake"` | Storage backend. Layerwise currently supports `"memcache"` only. |
|
|||
|
|
| `mooncake_rpc_port` | `"0"` | RPC port for the scheduler↔worker lookup service. Use `"0"` to auto-assign, or a unique port per instance. |
|
|||
|
|
| `layerwise_prefetch_layers` | `1` | Number of layers to prefetch ahead of the compute frontier. Higher values improve overlap at the cost of memory. |
|
|||
|
|
| `layerwise_max_transfer_blocks` | `0` (unlimited) | Maximum number of KV blocks per transfer batch. |
|
|||
|
|
| `layerwise_max_transfer_bytes` | `0` (unlimited) | Maximum bytes per transfer batch. |
|
|||
|
|
| `h2d_stagger_us` | `0` | Stagger delay (microseconds) between H2D copies across TP ranks to avoid bus contention. |
|
|||
|
|
| `discard_partial_chunks` | `true` (non-layerwise) / `false` (layerwise) | Whether to discard KV for incomplete chunk boundaries. Layerwise defaults to `false` to preserve partial layers. |
|
|||
|
|
|
|||
|
|
## Usage Scenarios
|
|||
|
|
|
|||
|
|
### PD-Mixed (kv_both)
|
|||
|
|
|
|||
|
|
A single vLLM instance acts as both producer and consumer. The pool serves as a
|
|||
|
|
shared prefix cache: completed requests' KV is saved layer by layer, and new
|
|||
|
|
requests with overlapping prefixes load KV layer by layer. No proxy is needed.
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
export ASCEND_RT_VISIBLE_DEVICES=0,1
|
|||
|
|
|
|||
|
|
python -m vllm.entrypoints.openai.api_server \
|
|||
|
|
--model /path/to/DeepSeek-V2-Lite \
|
|||
|
|
--port 8100 \
|
|||
|
|
--trust-remote-code \
|
|||
|
|
--enforce-eager \
|
|||
|
|
--no-enable-prefix-caching \
|
|||
|
|
--tensor-parallel-size 1 \
|
|||
|
|
--max-model-len 4096 \
|
|||
|
|
--max-num-batched-tokens 4096 \
|
|||
|
|
--kv-transfer-config '{
|
|||
|
|
"kv_connector": "AscendStoreConnector",
|
|||
|
|
"kv_role": "kv_both",
|
|||
|
|
"kv_connector_extra_config": {
|
|||
|
|
"backend": "memcache",
|
|||
|
|
"mooncake_rpc_port": "0",
|
|||
|
|
"use_layerwise": true
|
|||
|
|
}
|
|||
|
|
}'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Send requests directly to port 8100 — no proxy required.
|
|||
|
|
|
|||
|
|
### PD Disaggregation (kv_producer + kv_consumer)
|
|||
|
|
|
|||
|
|
Separate prefiller and decoder instances. The prefiller saves KV layer by layer;
|
|||
|
|
the decoder loads it layer by layer. A layerwise proxy coordinates request
|
|||
|
|
routing and per-layer KV placement via its `/v1/metaserver` endpoint.
|
|||
|
|
|
|||
|
|
**Prefiller:**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
export ASCEND_RT_VISIBLE_DEVICES=0,1
|
|||
|
|
|
|||
|
|
python -m vllm.entrypoints.openai.api_server \
|
|||
|
|
--model /path/to/DeepSeek-V2-Lite \
|
|||
|
|
--port 8100 \
|
|||
|
|
--trust-remote-code \
|
|||
|
|
--enforce-eager \
|
|||
|
|
--tensor-parallel-size 1 \
|
|||
|
|
--max-model-len 4096 \
|
|||
|
|
--kv-transfer-config '{
|
|||
|
|
"kv_connector": "AscendStoreConnector",
|
|||
|
|
"kv_role": "kv_producer",
|
|||
|
|
"kv_connector_extra_config": {
|
|||
|
|
"backend": "memcache",
|
|||
|
|
"mooncake_rpc_port": "0",
|
|||
|
|
"use_layerwise": true
|
|||
|
|
}
|
|||
|
|
}'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Decoder:**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
export ASCEND_RT_VISIBLE_DEVICES=2,3
|
|||
|
|
|
|||
|
|
python -m vllm.entrypoints.openai.api_server \
|
|||
|
|
--model /path/to/DeepSeek-V2-Lite \
|
|||
|
|
--port 8200 \
|
|||
|
|
--trust-remote-code \
|
|||
|
|
--enforce-eager \
|
|||
|
|
--tensor-parallel-size 1 \
|
|||
|
|
--max-model-len 4096 \
|
|||
|
|
--kv-transfer-config '{
|
|||
|
|
"kv_connector": "AscendStoreConnector",
|
|||
|
|
"kv_role": "kv_consumer",
|
|||
|
|
"kv_connector_extra_config": {
|
|||
|
|
"backend": "memcache",
|
|||
|
|
"mooncake_rpc_port": "0",
|
|||
|
|
"use_layerwise": true
|
|||
|
|
}
|
|||
|
|
}'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Layerwise proxy** (different from the standard disagg proxy — serves
|
|||
|
|
`/v1/metaserver`):
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
python examples/disaggregated_prefill_v1/load_balance_proxy_layerwise_server_example.py \
|
|||
|
|
--host 127.0.0.1 \
|
|||
|
|
--port 9000 \
|
|||
|
|
--prefiller-hosts 127.0.0.1 \
|
|||
|
|
--prefiller-ports 8100 \
|
|||
|
|
--decoder-hosts 127.0.0.1 \
|
|||
|
|
--decoder-ports 8200
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> **Note:** The proxy `--host` must **not** be `0.0.0.0` (wildcard). The
|
|||
|
|
> decoder connects back to `host:port/v1/metaserver`, so use a reachable IP.
|
|||
|
|
|
|||
|
|
Send requests to the proxy:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
curl -s http://127.0.0.1:9000/v1/completions \
|
|||
|
|
-H "Content-Type: application/json" \
|
|||
|
|
-d '{
|
|||
|
|
"model": "/path/to/DeepSeek-V2-Lite",
|
|||
|
|
"prompt": "Hello, my name is",
|
|||
|
|
"max_tokens": 32,
|
|||
|
|
"temperature": 0.0
|
|||
|
|
}'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Tuning
|
|||
|
|
|
|||
|
|
### Prefetch Depth
|
|||
|
|
|
|||
|
|
Increase `layerwise_prefetch_layers` (default `1`) to prefetch more layers
|
|||
|
|
ahead of the compute frontier. This increases transfer/compute overlap but uses
|
|||
|
|
more temporary buffers. Typical values: `1–4`.
|
|||
|
|
|
|||
|
|
### Transfer Batching
|
|||
|
|
|
|||
|
|
Use `layerwise_max_transfer_blocks` or `layerwise_max_transfer_bytes` to limit
|
|||
|
|
the size of each transfer batch. This prevents a single large layer from
|
|||
|
|
monopolizing the transfer bus. Set to `0` (default) for unlimited.
|
|||
|
|
|
|||
|
|
### H2D Stagger
|
|||
|
|
|
|||
|
|
On multi-TP deployments, H2D (host-to-device) copies for all TP ranks can
|
|||
|
|
contend on the PCIe/HCCS bus. Set `h2d_stagger_us` to spread them out (e.g.
|
|||
|
|
`100` for a 100 µs stagger between ranks).
|
|||
|
|
|
|||
|
|
## Supported Models
|
|||
|
|
|
|||
|
|
Layerwise mode integrates with the **MLA** (`mla_v1`) and **SFA** (`sfa_v1`)
|
|||
|
|
attention backends. DeepSeek-V2/V3 and other MLA-based models are supported.
|
|||
|
|
|
|||
|
|
Basic full attention (`attention_v1`) and all context-parallel (CP) variants
|
|||
|
|
(`mla_cp`, `sfa_cp`, `attention_cp`) do **not** yet integrate the layerwise
|
|||
|
|
wait/save calls. Layerwise + CP is future work.
|
|||
|
|
|
|||
|
|
## Limitations
|
|||
|
|
|
|||
|
|
* **Backend**: Only `memcache` is supported for layerwise mode (`mooncake` and
|
|||
|
|
`yuanrong` do not support `use_layerwise`).
|
|||
|
|
* **Hybrid KV cache**: Not supported — layerwise raises
|
|||
|
|
`NotImplementedError` when the model has multiple KV cache group families
|
|||
|
|
(hybrid MLA + sliding-window attention).
|
|||
|
|
* **Context parallel**: Layerwise is not yet integrated with CP attention
|
|||
|
|
backends.
|
|||
|
|
* **PD disaggregation proxy**: When using `kv_producer` / `kv_consumer`, the
|
|||
|
|
dedicated layerwise proxy
|
|||
|
|
(`load_balance_proxy_layerwise_server_example.py`) is required — the standard
|
|||
|
|
disagg proxy does not serve the `/v1/metaserver` endpoint.
|