# NSA Prefill CP Shared-KV Batch Size > 1 中文实现计划

> **给后续 agent/工程师的执行要求：** 实现本文档时必须先使用 `superpowers:subagent-driven-development` 或 `superpowers:executing-plans`，按 phase 和测试逐步推进。本文是实现合同与 phase 规划，不是已经完成的代码实现。

**目标：** 在 `--nsa-prefill-cp-mode in-seq-split` + CP shared KV + HiCache 场景下支持真正的 `ForwardBatch(batch_size > 1)`，让线上 cache-hit 后 `200-2000` token 的短 extend 可以通过 batch 填满 compute，同时不破坏 owner-lane、page-aligned cache、direct write、HiCache load/back up、EAGLE/draft 的正确性。

**核心架构：** 所有 CP 规划先以 request 为单位计算，再 flatten 到现有 tensor/kernel 接口。cache 管理仍以 page 为最小单位；每个 request 独立拥有 prefix、extend、page extent、split list、page owner、top-k 范围和 current reuse 区间。规划只能依赖本地 CPU metadata 和统一配置，不允许新增 collective 来“确认计划是否一致”。

**当前约束：** 先保证 target model 同步路径正确，再恢复 current/partial-current reuse，再处理 EAGLE/draft，最后打开 MLA/index L1 prefetch。不能通过删除 `batch_size != 1` guard 来“强行开启”。

- 并行派工版计划：`docs/advanced_features/nsa_prefill_cp_shared_kv_bs_gt1_parallel_workstreams_zh.md`。
- W3/W4-1 target sync 细化文档：`docs/advanced_features/nsa_prefill_cp_shared_kv_bs_gt1_w3_w4_plan_zh.md`。

---

## 1. 背景与优先级变化

旧文档 `docs/advanced_features/nsa_prefill_cp_batch_size_gt1_exploration.md` 当时建议先做 multi-slot overlap，因为那时认为 true batch 的改动面太大，且每个 extend 较长时单请求还能提供足够 compute。

现在观测到线上流量有明显变化：cache hit 很高时，extend 经常只有 `200-2000` tokens。这个量级下，即使 L2->L1 prefetch、L1 materialize、async backup 做得比较好，单请求本身也可能打不满 GPU compute。因此 true `ForwardBatch(batch_size > 1)` 变成必要吞吐优化。

但当前代码里 NSA in-seq CP shared-KV 的多个关键路径仍是 `batch_size == 1` 合同。如果直接把 guard 去掉，flatten 后的多个 request 会被当成一条长序列，导致：

- request boundary 丢失；
- causal/top-k 范围错误；
- page owner 与 compute rank 不一致；
- direct write 写入非 owner lane；
- partial-current reuse 把不同 request 的 current suffix 混在一起；
- EAGLE/draft last-token collect 取错 request 的最后 token；
- HiCache 会正确保存一个已经错误的 owner pattern。

所以 bs>1 不能做成“放开 guard”，必须做成“每个 request 独立规划，最后 flatten”。

---

## 2. 不可破坏的合同

### 2.1 request boundary 必须保留

每个 request 都必须独立记录：

- `extend_prefix_len`；
- `extend_len`；
- page-aligned extent；
- `split_list`；
- `zigzag_index`；
- `kv_len_prev/next`；
- `actual_seq_q_prev/next`；
- current suffix rows；
- prefix pages；
- page owner list。

Flatten 后的 tensor 可以共用一个 buffer，但 metadata 不能退化成单个全局标量。

### 2.2 page 是 cache 最小单位

CP shared KV + HiCache 已经被修正为 page-aligned cache 合同：

```text
valid tokens <= physical page-rounded tokens
padding <= page_size - 1
```

举例：

```text
extend_len = 100
page_size = 64
physical coverage = 128 tokens = 2 pages
```

不能因为 `cp_size=8` 就补到 8 pages 或 16 pages，也不能因短 tail fallback 到 token-balanced split。

### 2.3 owner-lane 计划必须与 flattened out_cache_loc page 顺序一致

每个 request 独立生成 page owners，然后按 batch request 顺序拼接：

```text
batch_page_owners = owners(req0) + owners(req1) + ... + owners(reqN)
```

`out_cache_loc` 的 page 顺序必须与这个 owner list 一致。后续 direct write、HiCache metadata、load back 都依赖这个顺序。

### 2.4 规划不允许新增 collective

所有 CP ranks 必须根据相同的 CPU metadata 和配置独立得出同一个 plan。不能通过新增 all-reduce/all-gather 来同步“这个 batch 的 planning 结果”。

允许后续 materialize/reduce 使用已有 collective，但 collective 顺序必须在所有 rank 上完全一致。

### 2.5 fallback 必须显式

支持范围内不允许 silent fallback。开发阶段建议 fail-fast：

```text
[CP_SHARED_KV_FAIL_FAST][batch_gt1_*]
```

生产阶段如果后续允许 fallback，也必须 warning 级别、限频、带明确原因和性能影响。

### 2.6 target / draft / prefetch 的启用顺序

推荐启用顺序：

```text
target sync correctness
  -> target current/partial-current reuse
  -> HiCache write/load validation
  -> EAGLE/draft mirror
  -> batched MLA/index prefetch
```

不要在 target sync 路径没有证明正确前启用 draft 或 async prefetch。

---

## 3. 已确认的代码事实

这一节是检查记录，避免后续反复搜索同一问题。

### C1. generic scheduler 已经能构造 multi-request extend

相关代码：

- `python/sglang/srt/managers/schedule_batch.py:1481-1540`
  - `ScheduleBatch.prepare_for_extend()` 会构造 flattened `input_ids`；
  - 同时保留 per-request `seq_lens`、`prefix_lens`、`extend_lens`、`extend_num_tokens`。
- `python/sglang/srt/model_executor/forward_batch_info.py:449-500`
  - `ForwardBatch.init_new()` 设置 `batch_size=len(batch.seq_lens)`。

结论：bs>1 的 blocker 不在 generic batching，而在 NSA CP shared-KV 专用路径。

### C2. in-seq CP metadata 是单请求结构

相关代码：

- `python/sglang/srt/layers/attention/nsa/utils.py:1094-1244`
  - `prepare_input_dp_with_cp_dsa()` 注释为 `# just support batch = 1`；
  - `bs_per_cp_group = 1`；
  - 只生成一个 `split_list`、一个 `zigzag_index`、一组 `kv_len_prev/next`。
- `python/sglang/srt/layers/attention/nsa/utils.py:203-244`
  - `NSAContextParallelMetadata` 当前字段基本是 scalar / single-request。

结论：Phase 1 必须扩展 per-request metadata。

### C3. page-aligned split 只在 `len(...) == 1` 时启用

相关代码：

- `python/sglang/srt/layers/attention/nsa/utils.py:300-390`
  - `_build_in_seq_split_for_forward_batch()` 只有在 `len(extend_seq_lens_cpu) == 1` 且 `len(extend_prefix_lens_cpu) == 1` 时使用 page-aligned split；
  - 否则 fallback 到 token-balanced split。

结论：bs>1 下 token-balanced fallback 会破坏 CP shared KV page owner 合同，必须替换为 batched page-aligned split。

### C4. `can_cp_split()` 对 bs>1 的判断可能错误

相关代码：

- `python/sglang/srt/layers/attention/nsa/utils.py:460-483`
  - `can_cp_split(seq_len, ...)` 使用 flattened total tokens；
  - `should_skip_cp_shared_kv_cp_split_for_short_page_extent()` 只支持单请求判断。

风险：多个很短 suffix 合并后 total tokens 可能足够触发 CP split，但每个 request 单独看都小于 `cp_size` pages。这会制造大量 zero segment / 不匹配路径。

### C5. owner-lane allocation 是单请求结构

相关代码：

- `python/sglang/srt/mem_cache/common.py:455-565`
  - 只有 `len(prefix_lens_cpu) == 1` 时才生成 `page_compute_owners`；
  - bs>1 记录 `multi_batch`，然后 fallback 到 legacy allocation。
- `python/sglang/srt/mem_cache/allocator.py:981-1048`
  - `alloc_extend_compute_owner()` 对 batch size 不是 1 直接 raise。

结论：Phase 2 必须实现 batched owner-lane allocation。legacy allocation 对 CP shared KV direct write 是 correctness risk。

### C6. direct write local loc 是单请求 split

相关代码：

- `python/sglang/srt/layers/attention/nsa/utils.py:562-648`
  - `get_cp_shared_kv_local_out_cache_loc()` 使用单个 `metadata.split_list` split 整个 flattened `out_cache_loc`；
  - 然后检查本 rank local loc 是否属于当前 owner lane。
- 调用者：
  - `python/sglang/srt/models/deepseek_common/attention_forward_methods/forward_mla.py`；
  - `python/sglang/srt/layers/attention/nsa/nsa_indexer.py`。

结论：Phase 3 必须以 request 为单位 split `out_cache_loc`，再拼接本 rank local loc。

### C7. current / partial-current reuse 已从单请求结构扩展到 target bs>1

相关代码：

- `python/sglang/srt/layers/attention/nsa/cp_shared_kv_runtime.py:1764-1904`
  - `_current_extend_kv_reuse_miss_reason()` 已移除 `batch_size_not_one`，改成 batch size、prefix/extend/seq len、`out_cache_loc` 的一致性校验；
  - `current_extend_kv_rows_for_reuse()` 按 `sum(extend_seq_lens_cpu)` 返回 flattened valid current rows，避免 padded tail 暴露给 attention。
- `python/sglang/srt/layers/attention/nsa/nsa_indexer.py:309-380`
  - index partial-current compose 对 bs>1 使用 batch prefix slot span；
  - index top-k current-only bs>1 不 materialize，partial-current bs>1 只 materialize 一次并复用 dense buffer。
- `python/sglang/srt/layers/attention/nsa_backend.py:1940-2020`
  - MLA partial-current compose 对 bs>1 使用 batch prefix slot span。

结论：target MLA/index 的同步正确路径已支持 bs>1 current-only 与 partial-current。剩余工作是把这套合同下沉到 variable-length TAI/SGL kernel、L1 prefetch、HiCache load/back up、draft/EAGLE。

### C8. L1 shared-KV prefetch 是单请求结构

相关代码：

- `python/sglang/srt/layers/attention/nsa/cp_shared_kv_prefetch.py:360-455`
  - `CpSharedKVMlaPrefetcher.maybe_create()` 拒绝 `batch_size != 1`。
- `python/sglang/srt/layers/attention/nsa/cp_shared_kv_prefetch.py:1135-1235`
  - `CpSharedKVIndexPrefetcher.maybe_create()` 拒绝 `batch_size != 1`。

结论：Phase 8 再恢复 batched prefetch。不能在 sync path 未正确时先开 prefetch。

### C9. top-k in-seq CP pair 是单请求结构

相关代码：

- `python/sglang/srt/layers/attention/nsa/nsa_indexer.py:1371-1410`
  - `_get_topk_in_seq_cp_pair()` 使用 scalar `kv_len_prev/next` 和 scalar `actual_seq_q_prev/next`；
  - 源码里记录 multi-batch 尚未支持。

结论：W4-1 必须改成 request-local prev/next slices。

### C10. EAGLE / draft local path 对单请求 metadata 敏感

相关代码：

- `python/sglang/srt/models/deepseek_nextn.py:292-399`
  - draft path 使用 `cp_split_and_rebuild_1d`、`cp_split_and_rebuild_position`、`cp_split_and_rebuild_data`、`cp_collect_last_token_hidden()`。
- `python/sglang/srt/layers/attention/nsa/utils.py:1333-1358`
  - exact last-token owner/offset 只对 `bs == 1` 生效。

结论：Phase 7 必须修复 draft per-request split 和 last-token collect。target 未过前不要打开 draft bs>1 local path。

### C11. HiCache load 较接近 batch-capable，但依赖 write-time metadata

相关代码：

- `python/sglang/srt/managers/cache_controller.py:1338-1498`
  - `load_cp()` 可以拼接多个 node 的 `page_owners`，然后 `alloc_pages_with_owners()`。
- `python/sglang/srt/managers/scheduler.py:2630-2655`
  - `_prepare_hicache_write_backups_before_forward()` 已经按 request 循环调用 `prepare_write_backup_for_req(req)`。

结论：HiCache 不是第一 blocker。但如果 write-time batch metadata 错了，HiCache 会复现错误 owner pattern，因此 Phase 6 必须验证。

---

## 4. 目标 metadata 设计

建议扩展 `NSAContextParallelMetadata`，保留单请求兼容字段，同时新增 batch-aware 字段。

```text
NSAContextParallelMetadata
  # 旧字段：batch_size == 1 时继续保持兼容
  split_list
  zigzag_index
  kv_len_prev
  kv_len_next
  actual_seq_q_prev
  actual_seq_q_next

  # 新字段：batch-aware
  batch_size: int
  request_token_offsets: List[int]
  request_padded_token_offsets: List[int]
  request_page_offsets: List[int]
  request_split_infos: List[PageAlignedInSeqSplitInfo]
  request_split_lists: List[List[int]]
  request_zigzag_indices: List[List[int]]
  request_rank_local_tokens: List[int]
  request_kv_len_prev: List[int]
  request_kv_len_next: List[int]
  request_actual_seq_q_prev: List[int]
  request_actual_seq_q_next: List[int]
  request_actual_seq_q_prev_cu_tensor: Tensor[batch_size + 1]
  request_actual_seq_q_next_cu_tensor: Tensor[batch_size + 1]
  request_rank_local_offsets: List[int]
  request_last_token_owner: List[int]
  request_last_token_local_offset: List[int]
  flat_split_list: List[int]
  flat_zigzag_index: List[int]
  flat_segment_request_ids: List[int]
  flat_segment_offsets: List[int]
```

兼容规则：

```text
batch_size == 1:
  split_list == request_split_lists[0]
  zigzag_index == request_zigzag_indices[0]
  kv_len_prev == request_kv_len_prev[0]
  actual_seq_q_prev == request_actual_seq_q_prev[0]

batch_size > 1:
  旧 scalar-only consumer 必须 fail-fast，直到对应 phase 改成 per-request consumer
```

关键点：不要把 batch 的 flattened tokens 当作一条 request 来生成单个 split list。

---

## 5. Phase 规划

### Phase 0：硬 guard 与 red tests

目标：在正式支持 bs>1 前，先防止 silent incorrect fallback。

修改文件：

- `test/registered/unit/layers/test_nsa_cp_utils.py`
- `test/registered/unit/mem_cache/test_cp_shared_kv_layout.py`
- `test/registered/unit/mem_cache/test_cp_shared_kv_runtime.py`
- `python/sglang/srt/layers/attention/nsa/utils.py`
- `python/sglang/srt/mem_cache/common.py`

工作内容：

1. 添加 red tests，证明当前 CP shared-KV bs>1 不允许 silent token-balanced split。
2. 添加 red tests，证明当前 owner-lane allocation 不允许 silent legacy allocation。
3. 对不支持的 bs>1 CP shared-KV path 加明确 fail-fast。
4. 如果多个文件重复判断，可增加一个轻量 helper，例如 `cp_shared_kv_batching_supported(forward_batch)`。

退出标准：

- bs>1 CP shared-KV batch 要么进入新 phase path，要么抛出明确 `[CP_SHARED_KV_FAIL_FAST][batch_gt1_*]`。
- 不允许只打 debug log 后返回 `None` 进入慢 fallback。

### Phase 1：batch-aware page-aligned CP split metadata

目标：每个 request 独立生成 page-aligned in-seq CP split metadata。

修改文件：

- `python/sglang/srt/layers/attention/nsa/utils.py`
- `test/registered/unit/layers/test_nsa_cp_utils.py`

工作内容：

1. 增加纯 CPU helper，例如：

```text
build_batch_page_aligned_in_seq_split_plan(
    extend_lens,
    prefix_lens,
    page_size,
    cp_size,
    cp_rank,
)
```

2. 对每个 request 调用已有 `build_page_aligned_in_seq_split_list()`。
3. 生成 per-request 与 flat 两套视图：
   - per-request split list；
   - per-request page start/end；
   - per-rank local valid token count；
   - per-request `kv_len_prev/next` 与 `actual_seq_q_prev/next`；
   - per-request last-token owner/local offset；
   - per-request rank-local offset，用于 batch compact hidden collect；
   - flattened segment offsets。
4. 更新 `prepare_input_dp_with_cp_dsa()`，让 CP shared-KV bs>1 使用新 metadata。
5. 保留 bs=1 的旧字段兼容。
6. `cp_collect_last_token_hidden()` 必须继续走 phase1 compact/narrow output；不能因为 bs>1 回到 full hidden gather。
7. bs>1 full rerange 在 batch-aware full output metadata/kernels 完成前 fail-fast。

测试重点：

```text
extend_lens=[100, 513]
prefix_lens=[40320, 8192]
page_size=64
cp_size=8
```

期望：

- req0 padded 到 2 pages / 128 tokens；
- req1 padded 到 9 pages / 576 tokens；
- 不补到 `cp_size` 或 `2 * cp_size` pages；
- zero segment 合法；
- non-page-aligned prefix 在进入 CP split 前 floor，否则 fail-fast。

退出标准：

- metadata 能表达 `batch_size=2`，且不会把两个 request 当成一条长序列。
- 普通 prefill bs>1 的 output collection 能按 request order 返回 compact last hidden。
- 需要 full output 语义的 bs>1 请求不会误用 scalar full rerange。
- 对外 helper 能提供 batch plan、按 plan split tensor、以及 flat page-owner plan。

### Phase 2：batch-aware owner-lane allocation

目标：每个 request 的新 page 都从对应 compute-owner lane 分配。

修改文件：

- `python/sglang/srt/mem_cache/cp_shared_kv_compute_owner.py`
- `python/sglang/srt/mem_cache/common.py`
- `python/sglang/srt/mem_cache/allocator.py`
- `test/registered/unit/mem_cache/test_cp_shared_kv_layout.py`
- `test/registered/unit/mem_cache/test_alloc_pages_with_owners.py`

工作内容：

1. 新增 batched owner builder：对每个 request 调用 `build_in_seq_page_compute_owners()`，再按 request 顺序拼接。
2. 新增 `alloc_extend_compute_owner_batched()`，或泛化 `alloc_extend_compute_owner()` 支持多请求。
3. 验证 `alloc_extend_naive()` 对 multi-request 是否按 flattened request order 填充 `out_indices`；如果不能保证，则封装 per-request offset 写入。
4. 更新 `alloc_paged_token_slots_extend()`，让 CP shared-KV bs>1 走 owner-lane allocation，不再走 `multi_batch` legacy fallback。

测试重点：

- 两个 request prefix/extend 不同，拼接 owner list 长度等于新分配 page 数。
- `out_cache_loc` 的 page owner 与拼接 owner list 一致。
- owner-lane 不足时抛 `KVCapacityWaitError`，不 fallback 到 legacy allocation。

退出标准：

- target bs>1 extend 可以正确分配 owner-lane pages。

### Phase 3：batch-aware CP split/rebuild 与 direct write

目标：当前所有用单个 `split_list` split tensor 的路径改成 per-request split，然后拼接本 rank local rows。

修改文件：

- `python/sglang/srt/layers/attention/nsa/utils.py`
- `python/sglang/srt/models/deepseek_common/attention_forward_methods/forward_mla.py`
- `python/sglang/srt/layers/attention/nsa/nsa_indexer.py`
- `test/registered/unit/layers/test_nsa_cp_utils.py`

工作内容：

1. 添加 `cp_split_and_rebuild_batched_1d()`。
2. 添加 `cp_split_and_rebuild_batched_data()`。
3. 让 `cp_split_and_rebuild_1d/data()` 在 metadata `batch_size > 1` 时 dispatch 到 batched helper。
4. 更新 `get_cp_shared_kv_local_out_cache_loc()`：
   - 按 request 切 `out_cache_loc`；
   - 每个 request 内按其 split/zigzag 取本 rank local loc；
   - 拼接后再做 owner-lane validation。
5. 确保 `cp_local_physical_out_cache_loc` cache 不会误复用旧 batch metadata。

测试重点：

- 每个 CP rank 的 local `out_cache_loc` 等于 req0 local + req1 local。
- 任一 request 出现 owner mismatch 都拒绝 direct write。
- bs=1 行为保持一致。

退出标准：

- target MLA KV 和 index direct-write 可以使用 batched local loc。

### W4-1：target index/top-k 同步正确性

目标：不依赖 async prefetch，先让 target index/top-k 在 bs>1 下正确。

修改文件：

- `python/sglang/srt/layers/attention/nsa/nsa_indexer.py`
- `python/sglang/srt/layers/attention/nsa/cp_shared_kv_runtime.py`
- `test/registered/unit/layers/test_nsa_topk_transform.py`
- `test/registered/unit/mem_cache/test_cp_shared_kv_runtime.py`

工作内容：

1. 把 `_get_topk_in_seq_cp_pair()` 从 scalar `kv_len_prev/next` 改成 per-request loop。
2. 每个 request 生成自己的 prev/next top-k pair。
3. 按 flattened local query order 重新拼接 top-k result。
4. index materialize 先走同步路径：构造 per-request prefix page range，再 flatten。
5. W4-1 不打开 bs>1 index prefetch。

测试重点：

- bs=2 top-k 结果与两个 sequential bs=1 请求一致。
- 一个 request 无 prefix、另一个 request 有 page-aligned prefix 时不互相污染。

退出标准：

- target index/top-k 有 CPU/unit 等价证明。

### Phase 5：target MLA current / partial-current reuse

目标：恢复 bs>1 下 current-only 和 partial-current fast path。

修改文件：

- `python/sglang/srt/layers/attention/nsa/cp_shared_kv_runtime.py`
- `python/sglang/srt/layers/attention/nsa_backend.py`
- `test/registered/unit/mem_cache/test_cp_shared_kv_runtime.py`

工作内容：

1. 移除 `batch_size_not_one` 的硬拒绝，改成 batched validation。
2. 校验 flattened `out_cache_loc` 覆盖每个 request 的 current suffix。
3. 为每个 request 记录 current rows 与 current loc slice。
4. 构造 batched current-loc remap。
5. partial-current compose 支持 per-request prefix pages + current slices。
6. padded tail rows 必须 mask，不可暴露给 attention。

测试重点：

- current-only bs=2 不 materialize shared prefix。
- partial-current bs=2 正确拼接 prefix materialized rows 与 valid current rows。
- 短 suffix 小于 `cp_size` pages 仍正确。

退出标准：

- target MLA 在支持 case 下使用 current/partial-current reuse。

### Phase 6：HiCache / radix batched write-load 验证

目标：证明 batched write 后的 HiCache metadata 能被正确 load back。

修改文件：

- `python/sglang/srt/mem_cache/hiradix_cache.py`
- `python/sglang/srt/managers/cache_controller.py`
- `test/registered/unit/mem_cache/test_cp_hicache_metadata.py`
- `test/registered/unit/mem_cache/test_cp_hicache_load_back_owner_lanes.py`

工作内容：

1. 检查 `prepare_write_backup_for_req(req)` 在 batched allocation 下记录：
   - `page_owners`；
   - `owned_positions`；
   - `valid_len`；
   - `padded_len`。
2. 添加两个 node 同时 `load_cp()` 的测试。
3. 验证 chunked prefill tail page：
   - 要么提前 floor 到 page boundary；
   - 要么遇到 in-flight backup split 时 defer，不改树。
4. 确认 `load_cp()` 返回的 visible device indices 顺序符合 radix/request 顺序。

测试重点：

- 两个 host hit node 一次 load，所有 CP rank 上 owner pattern 都匹配。
- chunked prefill 不因 in-flight backup split 崩溃。

退出标准：

- HiCache target nodes 对 bs>1 安全。

### Phase 7：EAGLE / draft bs>1 支持

目标：draft KV 与 target KV 保持镜像合同，hidden state collection 按 request 独立处理。

修改文件：

- `python/sglang/srt/models/deepseek_nextn.py`
- `python/sglang/srt/layers/attention/nsa/utils.py`
- `test/registered/unit/layers/test_nsa_cp_utils.py`

工作内容：

1. draft local input/position/spec-hidden 使用 batch-aware split helper。
2. 实现 per-request in-seq last-token owner/offset collect。
3. draft 不生成自己的 owner plan，一切跟随 target。
4. target bs>1 ETE 通过后，再打开 draft local path。

当前实现状态：

- batch-aware input/position/spec-hidden split 已接到 draft local path；valid CP-local hidden 直接使用，full hidden 走 batch-aware split。
- per-request last-token collect 已在 `cp_collect_last_token_hidden()` 中覆盖 batch plan。
- 为避免打开 gate 后退回不正确的 full/draft fallback，bs>1 + `SGLANG_CP_DRAFT_SHARED_KV=1` + CP shared-KV 时，draft local path 对 missing/mismatch spec hidden、embedding pad metadata、input embeds shape mismatch 均 fail-fast。
- admission gate 仍保持关闭；需要 EAGLE enabled ETE 验证 accept length/output length 后再打开。

测试重点：

- `cp_collect_last_token_hidden()` 对 bs=2 返回两个 request 的 last hidden。
- draft local path 与 full path 在小 deterministic tensor 上一致。

退出标准：

- EAGLE accept length 不因 bs>1 cache-hit 流量掉到 1。

### Phase 8：batched L1 shared-KV prefetch

目标：在 sync correctness 已证明后，恢复 bs>1 的 async L1 prefix prefetch。

修改文件：

- `python/sglang/srt/layers/attention/nsa/cp_shared_kv_prefetch.py`
- `python/sglang/srt/layers/attention/nsa/cp_shared_kv_runtime.py`
- `test/registered/unit/mem_cache/test_cp_shared_kv_runtime.py`

工作内容：

1. `CpSharedKVMlaPrefetcher.maybe_create()` 支持 per-request prefix page descriptors。
2. `CpSharedKVIndexPrefetcher.maybe_create()` 支持 per-request prefix page descriptors。
3. 构造 deterministic flattened prefetch plan：request id 优先，再 page id。
4. 保持 materialize kernel 的 stream 策略与前面已有结论一致：占 SM 的 materialize 不应错误并发。
5. 不新增 planner collective；已有 reduce/materialize collective 顺序必须所有 rank 一致。

测试重点：

- bs=2 且两个 prefix 都 page-aligned、超过阈值时 prefetcher 被创建。
- consume 结果与 sync materialize 相同。
- tiny prefix 关闭 prefetch 时，不影响 current reuse / full current reuse。

退出标准：

- 支持范围内 bs>1 cache-hit path 不再出现 sync prefix materialize hot-path fallback。

### Phase 9：远端 CUDA / ETE / 性能验证

目标：确认 correctness 与是否值得生产启用。

只在远端测试：

- host：`g0034`；
- container：`sglang-glm5-dev-2`；
- container path：`/sgl-workspace/sglang-tai`；
- 不在本地测试 CUDA。

场景：

1. bs=2/4 target-only cache miss / cache hit。
2. bs=2/4 EAGLE enabled。
3. prefix hit 90%+，extend sizes：`200, 512, 1024, 2000`。
4. mixed prefix lengths。
5. 一个 request zero prefix，另一个 request host hit。
6. HiCache L1 eviction 后从 L2 host load back。
7. chunked prefill enabled / disabled。
8. FP8 KV cache enabled / disabled。

必须收集的证据：

- 支持范围内无 `[CP_SHARED_KV_FALLBACK]` / `[CP_SHARED_KV_FAIL_FAST]`。
- `accept len` 不再异常掉到 1。
- 无 output length 0 的 runtime/server path 问题。
- cache hit 非 0，radix entries 保持 page-aligned。
- 短 extend 下吞吐高于 sequential bs=1，否则不 production-enable。

---

## 6. 推荐第一段实现范围

第一段只做：

```text
Phase 0 -> Phase 2
目标只覆盖 target model
不打开 draft local path
不打开 async prefetch
不新增 collective
```

原因：如果 owner-lane allocation 错了，后续 direct write、HiCache backup/load、prefetch 都会稳定复现错误状态。先把 metadata + owner-lane allocation 锁死，是最小但最关键的正确性边界。

---

## 7. 主要风险

1. **tiny suffix zero segment：** 小于 `cp_size` pages 的 request 会产生很多 0-token segment。所有 split/rebuild/top-k path 必须接受 zero segment。
2. **prefix 对齐：** radix/HiCache 命中应 floor 到最近 page boundary。non-page-aligned prefix 进入 CP split 前必须 fail-fast。
3. **chunked prefill：** unfinished request 可能尝试 split in-flight backup node。策略仍是 defer，不动树。
4. **FP8 KV cache：** 必须保持 page-slot compose，不能退化成 compact token buffer。
5. **workspace pressure：** bs>1 materialize buffer 更大，必须测显存和临时 buffer，不只看 latency。
6. **collective 顺序：** 所有 CP rank 必须按完全相同的 layer/request 顺序执行 collective。
7. **fallback 可见性：** 开发阶段优先 fail-fast；生产 fallback 必须 warning 且限频。
8. **W2 allocator CPU descriptor overhead：** 当前 W2 correctness path 仍在
   allocation 热路径中用 Python list/tensor 临时对象构造 batch owner descriptor：
   `extend_lens` / `extend_prefix_lens` list、`flat_page_compute_owners`
   list、`positions_by_owner` list，以及每个 owner lane 的 `position_tensor`。
   这仍复用了 owner free/release bucket 本地记账，比旧全量 scan/sort 路径轻，
   但在线上大量 200-2000 token 短 extend 叠 batch 时可能成为 CPU hot path。
   后续应让 W1 batch plan 直接携带 page-owner descriptor，W2 消费
   tensor/固定 buffer，并用 kernel 或固定 workspace 完成 selected-pages
   gather/scatter，避免每次 allocation 重建 Python descriptor。

---

## 8. 立即执行项

1. 写 Phase 0 red tests：bs>1 CP shared-KV 不能 silent token-balanced split。
2. 写 Phase 0 red tests：bs>1 CP shared-KV 不能 silent legacy owner allocation。
3. 写 Phase 1 纯 CPU batch split-plan tests。
4. 写 Phase 2 batched owner-plan tests，验证 owner list 顺序等于 flattened `out_cache_loc` page 顺序。
5. 通过测试后再改 `prepare_input_dp_with_cp_dsa()` 与 allocator runtime path。
