# Recompute CPU Offload Guide

## Overview

`RecomputeCPUOffloadConnector` preserves the KV cache of requests that are
preempted by the Decode-side recompute scheduler. When HBM KV blocks are not
enough, `RecomputeScheduler` may preempt a running Decode request. Without this
connector, the request falls back to the original recompute path and may be sent
back to the Prefill node to run prefill again. With recompute CPU offload
enabled, the already-computed KV blocks are copied from HBM to CPU DRAM before
the HBM blocks are reused, and copied back to HBM when the request is scheduled
again.

This feature is designed for online P/D disaggregation workloads where Decode
nodes are tuned for decode throughput and cannot efficiently recompute long
prefills after preemption. It is opt-in and focuses on correctness first.

In a typical Decode deployment, `max_num_batched_tokens` is often sized around
`max_num_seqs * (1 + num_spec_tokens)` so that the Decode node mainly schedules
one new token, plus speculative tokens if MTP is enabled, per request. This is
efficient for decode but too small for recomputing long prompts after
preemption. Recompute CPU offload avoids sending such requests back through the
full prompt recompute path when their KV state can be preserved locally.

## Key Concepts

* **Recompute preemption**: When Decode-side HBM KV cache is exhausted,
  `RecomputeScheduler` can preempt a running request and later resume it.
* **CPU DRAM preservation**: `RecomputeCPUOffloadConnector` stores the
  preempted request's computed KV blocks in CPU memory.
* **H2D restore**: When the preempted request is scheduled again, the connector
  restores the preserved KV blocks before model forward.
* **Fallback behavior**: If the connector is not configured, the recompute
  scheduler is not enabled, or CPU offload capacity is insufficient, vLLM
  falls back to the original recompute behavior.
* **`MultiConnector` integration**: In P/D disaggregation, use
  `MultiConnector` to combine the P/D connector, such as `MooncakeConnectorV1`,
  with `RecomputeCPUOffloadConnector` on Decode nodes.

## Configuration Parameters

`RecomputeCPUOffloadConnector` is configured through `kv-transfer-config`.

| Parameter | Description |
| :--- | :--- |
| `kv_connector` | Must be set to `RecomputeCPUOffloadConnector`. |
| `kv_role` | Set to `kv_consumer` on Decode nodes. |
| `cpu_bytes_to_use_per_rank` | Optional and recommended. CPU memory budget in bytes used by each rank/card for recompute offload. If set, it overrides `cpu_bytes_to_use / world_size`. |
| `cpu_bytes_to_use` | Optional. Total CPU memory budget in bytes for this vLLM instance. The connector divides it by `world_size` to get the per-rank budget. This is less direct than `cpu_bytes_to_use_per_rank` and is easier to misconfigure in DP deployments. The default is 8 GiB total. |
| `enable_offload_prefix_caching` | Optional. Enables CPU block sharing for full hashed blocks with the same prefix-cache hash. The default is `false`; keep it disabled unless explicitly testing prefix sharing. |

Prefer `cpu_bytes_to_use_per_rank` when you want every rank/card to use the
same offload capacity. For example, set `cpu_bytes_to_use_per_rank` to
`17179869184` for 16 GiB per rank/card.

If you use `cpu_bytes_to_use`, remember that it is divided by `world_size`. In
typical P/D Decode deployments, this means the value is divided by the active
Decode DP size. For example, when starting a DP2TP8 Decode service, setting
`cpu_bytes_to_use` to 16 GiB gives each DP rank's cards about 8 GiB of recompute
offload space. To avoid ambiguity, use `cpu_bytes_to_use_per_rank` for new
deployments.

`recompute_scheduler_enable` must also be enabled in `additional-config` on
P/D-disaggregated Decode nodes:

```bash
--additional-config '{"recompute_scheduler_enable":true}'
```

```{note}
`recompute_scheduler_enable` is only valid in P/D-disaggregated mode
(`kv_role` is `kv_producer` or `kv_consumer`). Do not enable it in PD-mixed
mode (`kv_role` is `kv_both`).
```

## Docker Shared Memory

Recompute CPU offload allocates pinned CPU tensors for the offloaded KV blocks.
When running in Docker, make sure the container's shared memory is large enough.
If `--shm-size` is too small, a large per-card offload budget, such as 16 GiB
per card, can fail to allocate CPU tensors and may cause the service to OOM or
hang during startup.

For typical A3 deployments with a large recompute-offload budget, set
`--shm-size=1024g` when starting the container. The following snippet follows
the Docker style used by the DeepSeek-V4-Flash tutorial:

```bash

export IMAGE=quay.io/ascend/vllm-ascend:|vllm_ascend_version|-a3
docker run --rm \
    --name vllm-ascend \
    --shm-size=1024g \
    --net=host \
    --device /dev/davinci0 \
    --device /dev/davinci1 \
    --device /dev/davinci2 \
    --device /dev/davinci3 \
    --device /dev/davinci4 \
    --device /dev/davinci5 \
    --device /dev/davinci6 \
    --device /dev/davinci7 \
    --device /dev/davinci8 \
    --device /dev/davinci9 \
    --device /dev/davinci10 \
    --device /dev/davinci11 \
    --device /dev/davinci12 \
    --device /dev/davinci13 \
    --device /dev/davinci14 \
    --device /dev/davinci15 \
    --device /dev/davinci_manager \
    --device /dev/devmm_svm \
    --device /dev/hisi_hdc \
    -v /usr/local/dcmi:/usr/local/dcmi \
    -v /usr/local/Ascend/driver/tools/hccn_tool:/usr/local/Ascend/driver/tools/hccn_tool \
    -v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
    -v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \
    -v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \
    -v /etc/ascend_install.info:/etc/ascend_install.info \
    -v /etc/hccn.conf:/etc/hccn.conf \
    -it $IMAGE bash
```

## Usage with P/D Disaggregation

On Decode nodes, configure `MultiConnector` with both the P/D connector and
`RecomputeCPUOffloadConnector`. The P/D connector handles KV transfer from
Prefill to Decode, while `RecomputeCPUOffloadConnector` handles Decode-side
preemption preservation and restore.

The following example uses `MooncakeConnectorV1` for P/D KV transfer.

```bash
python3 -m vllm.entrypoints.openai.api_server \
    --model /path/to/model \
    --port 8200 \
    --trust-remote-code \
    --enforce-eager \
    --tensor-parallel-size 1 \
    --data-parallel-size 1 \
    --max-model-len 32768 \
    --block-size 128 \
    --max-num-batched-tokens 4096 \
    --additional-config '{"recompute_scheduler_enable":true}' \
    --kv-transfer-config \
    '{
      "kv_connector": "MultiConnector",
      "kv_role": "kv_consumer",
      "kv_connector_extra_config": {
        "connectors": [
          {
            "kv_connector": "MooncakeConnectorV1",
            "kv_role": "kv_consumer",
            "kv_port": "28000",
            "kv_connector_extra_config": {
              "prefill": {
                "dp_size": 1,
                "tp_size": 1
              },
              "decode": {
                "dp_size": 1,
                "tp_size": 1
              }
            }
          },
          {
            "kv_connector": "RecomputeCPUOffloadConnector",
            "kv_role": "kv_consumer",
            "kv_connector_extra_config": {
              "cpu_bytes_to_use_per_rank": 17179869184,
            }
          }
        ]
      }
    }'
```

For the Prefill node, keep the normal P/D connector configuration, for example
`MooncakeConnectorV1` with `kv_role` set to `kv_producer`.

When `kv_load_failure_policy` is also needed for the P/D connector, configure
it on the top-level `MultiConnector` `kv-transfer-config`, not inside child
connectors.

## Standalone Connector Example

`RecomputeCPUOffloadConnector` must be used together with the vLLM-Ascend
`RecomputeScheduler`. Because `RecomputeScheduler` is only supported on
P/D-disaggregated Decode nodes, recompute CPU offload is currently only
available for P/D-disaggregated Decode nodes as well.

The following standalone connector configuration is intended only as a minimal
configuration fragment for validating the recompute-offload path on a
P/D-disaggregated Decode node. It is not a PD-mixed deployment mode. In
PD-mixed or normal non-P/D deployments, do not enable recompute CPU offload; the
engine uses the normal vLLM recompute behavior instead.

```python
from vllm.config import KVTransferConfig

kv_transfer_config = KVTransferConfig(
    kv_connector="RecomputeCPUOffloadConnector",
    kv_role="kv_consumer",
    kv_connector_extra_config={
        "cpu_bytes_to_use_per_rank": 17179869184,
        "enable_offload_prefix_caching": False,
    },
)
```

For online serving:

```bash
vllm serve /path/to/model \
    --additional-config '{"recompute_scheduler_enable":true}' \
    --kv-transfer-config '{
        "kv_connector": "RecomputeCPUOffloadConnector",
        "kv_role": "kv_consumer",
        "kv_connector_extra_config": {
            "cpu_bytes_to_use_per_rank": 17179869184,
            "enable_offload_prefix_caching": false
        }
    }'
```

## How It Works

1. When `RecomputeScheduler` cannot allocate enough HBM KV blocks, it selects a
   Decode-side running request for preemption.
2. Before the request's HBM blocks are reused, the scheduler calls the
   connector's preemption hook. If enough CPU blocks are available, the
   connector records the HBM block to CPU block mapping.
3. The model runner calls `handle_preemptions()` before updating worker state.
   The worker copies the selected KV blocks from HBM to pinned CPU tensors.
4. After all workers report that the store is complete, the preempted request
   becomes restorable.
5. When the request is scheduled again, the connector reports the number of
   tokens that can be restored from CPU memory.
6. The scheduler allocates new HBM blocks and the connector builds the CPU block
   to HBM block reload mapping.
7. Before model forward, the worker copies the restored KV blocks back to HBM.
   Forward then continues from the restored KV state.

## Sliding-Window and MTP Support

Sliding-window models can contain logical block tables with null block IDs for
tokens outside the attention window. The connector preserves logical block
positions instead of compressing non-zero block IDs, so a block table such as
`[0, 0, 20, 21]` remains aligned as `[0, 0, cpu_20, cpu_21]` on the CPU side.
Only non-zero blocks are transferred.

With MTP or speculative decode enabled, the scheduler may allocate lookahead
blocks. The reload path clips the H2D range to the GPU block table actually
allocated for the resumed request.

## Notes and Limitations

* This feature requires the vLLM V1 engine and the vLLM-Ascend recompute
  scheduler.
* The feature is only intended for Decode nodes in P/D disaggregation. It is not
  supported in PD-mixed or normal non-P/D deployments.
* Do not enable `recompute_scheduler_enable` in PD-mixed deployments. Without
  P/D-disaggregated Decode-side `RecomputeScheduler`, recompute CPU offload is
  not active and vLLM follows the normal recompute path.
* When running in Docker with a large per-rank offload budget, reserve enough
  shared memory. For typical A3 deployments with 16 GiB per-card offload, use
  `--shm-size=1024g`.
* `enable_offload_prefix_caching` is experimental and disabled by default.
* Current D2H and H2D transfers use basic torch copy operations. They are
  correctness-oriented and not yet optimized for transfer throughput.
* Qwen3.5 with async scheduling is not fully supported yet. Disable async
  scheduling when using recompute CPU offload with Qwen3.5 models.
* If CPU memory capacity is insufficient, the connector skips offload for that
  request and the scheduler falls back to the original recompute behavior.

## FAQ

### How much CPU memory should I configure?

Start with a budget that can hold the expected number of preempted request
blocks. Prefer `cpu_bytes_to_use_per_rank` because it directly controls the
offload budget for each rank/card. For example,
`cpu_bytes_to_use_per_rank=17179869184` gives each rank/card 16 GiB.

`cpu_bytes_to_use` is divided by `world_size`. In a DP2TP8 Decode service,
setting `cpu_bytes_to_use=17179869184` gives each DP rank's cards about 8 GiB
of offload space. Increase the budget if logs show that CPU cache free blocks
are insufficient.

### How do I know the offload path is active?

Look for logs similar to:

```text
Recompute preemption offload enabled for request ...
Created recompute offload state for request ...
Prepared recompute offload H2D load for request ...
```

If offload cannot be prepared, logs may show that CPU cache free blocks are
insufficient, and the request will fall back to the original recompute path.

### Is this the same as KV Cache CPU Offload?

No. `KV Cache CPU Offload` is a prefix-cache offload path for inactive KV cache
blocks. `RecomputeCPUOffloadConnector` is specifically for preserving KV blocks
of requests preempted by the Decode-side recompute scheduler.

## Reference

For design background and implementation details, see
[#10820: Recompute CPU Offload Connector](https://github.com/vllm-project/vllm-ascend/issues/10820).
