Reduce CP shared KV overhead without changing ownership semantics

The shared-KV path now keeps more CP metadata on-device and reuses
physical out-cache locations across MLA and NSA index writes, so each
layer avoids repeating logical-to-physical remaps. The in-seq CP
all-gather rerange path now delegates to tai-kernel when available and
falls back to the existing torch split/cat path with an explicit log.

This also extends the Phase8 prefetch machinery to cover shared KV
materialization metadata and keeps debug/fallback behavior gated so the
fast path is not polluted by diagnostic checks.

Constraint: Custom CP kernels must live in tai-kernel and be imported lazily from SGLang
Constraint: Decode does not use CP; these changes target NSA prefill CP in-seq-split shared KV
Rejected: Recompute physical local cache locations separately for MLA and index writes | repeats the same remap work every layer
Rejected: Keep the in-seq rerange Triton code inline in SGLang | duplicates kernel ownership and blocks tai-kernel reuse
Confidence: medium
Scope-risk: moderate
Directive: Keep CP collective ordering identical across ranks; do not add rank-local fallback decisions inside shared KV materialize paths
Tested: Remote g0034 container py_compile for modified SGLang/tai-kernel files; remote pytest test/registered/unit/layers/test_nsa_cp_utils.py passed with 24 tests
Not-tested: Full multi-node GLM5 prefill/decode throughput after the final commit boundary
This commit is contained in:
laoyao0822
2026-05-06 05:27:43 +08:00
parent 5e5ac5e2e7
commit 43ad2fe52d
10 changed files with 1152 additions and 46 deletions
@@ -1,8 +1,8 @@
# NSA Prefill CP Phase 8: MLA prefix prefetch
# NSA Prefill CP Phase 8: MLA / index prefix prefetch
Phase 8 的目标是在不改变 Phase 2-7 shared KV 语义的前提下,为 chunked prefill / radix cache hit 场景引入 **MLA KV one-layer-ahead prefix prefetch**,把历史 prefix 的 shared KV materialize 从当前层 attention 前的同步阻塞路径里移出,并把等待点延迟到下一层真正消费 prefetched KV 时。
Phase 8 的目标是在不改变 Phase 2-7 shared KV 语义的前提下,为 chunked prefill / radix cache hit 场景引入 **MLA KV 与 NSA index K/scale 的 one-layer-ahead prefix prefetch**,把历史 prefix 的 shared KV/index materialize 从当前层同步阻塞路径里移出,并把等待点延迟到下一层真正消费 prefetched buffer 时。
本阶段只做 MLA KV prefix prefetch。暂时不做 index K/scale prefetch不引入 `SGLANG_CP_SHARED_KV_LAYER_PREFETCH_KIND` 这类选择型环境变量,也不保留 `SGLANG_CP_SHARED_KV_MLA_PREFETCH_WAIT_AFTER_ATTENTION` 这类 wait 策略开关。
本阶段不引入 `SGLANG_CP_SHARED_KV_LAYER_PREFETCH_KIND` 这类选择型环境变量,也不保留 `SGLANG_CP_SHARED_KV_MLA_PREFETCH_WAIT_AFTER_ATTENTION` 这类 wait 策略开关。`SGLANG_CP_SHARED_KV_ENABLE_MLA_PREFETCH=1` 现在同时打开 MLA KV prefix prefetch 和 NSA index K/scale prefix prefetch。
---
@@ -12,15 +12,16 @@ Phase 8 的目标是在不改变 Phase 2-7 shared KV 语义的前提下,为 ch
Phase 8 已按 deferred-consume 策略实现:
```text
Layer L index/topk 后启动 Layer L+1 prefix index K/scale prefetch
Layer L attention 前启动 Layer L+1 prefix MLA KV prefetch
Layer L forward_extend 返回时不等待 prefetch
Layer L+1 consume prefetched KV 时 wait event,再补齐 suffix/current pages
Layer L attention 返回时不强制等待 prefetch
Layer L+1 consume prefetched buffer 时 wait event,再补齐 suffix/current pages
```
保留的环境变量:
```text
SGLANG_CP_SHARED_KV_ENABLE_MLA_PREFETCH=0/1 # 生产开关,默认关闭
SGLANG_CP_SHARED_KV_ENABLE_MLA_PREFETCH=0/1 # 生产开关,默认关闭;同时控制 MLA KV 与 index K/scale prefetch
SGLANG_CP_SHARED_KV_LOG_MLA_PREFETCH=0/1 # 调试日志,只打印 probe layer,默认关闭
```
@@ -48,13 +49,13 @@ owner-sharded physical MLA KV on each CP rank
-> existing NSA attention kernel
```
在 chunked prefill 的第二个 chunk 及之后,或者 radix cache 命中时,`extend_prefix_len > 0`。这部分 prefix KV 已经在之前的 chunk/request 中写入 persistent KV pool;当前层 attention 仍会同步 materialize 整个可见 KV,包括历史 prefix 和当前 suffix。
在 chunked prefill 的第二个 chunk 及之后,或者 radix cache 命中时,`extend_prefix_len > 0`。这部分 prefix KV 与 index K/scale 已经在之前的 chunk/request 中写入 persistent pool;当前层 index/topk 和 attention 仍会同步 materialize 整个可见范围,包括历史 prefix 和当前 suffix。
Phase 8 的机会是:
```text
历史 prefix 部分已经存在,可以提前为下一层 materialize。
当前 suffix 部分必须等下一层 prepare 写入后才能 materialize。
当前 suffix 部分必须等下一层 prepare/index write 写入后才能 materialize。
```
因此 Phase 8 不做跨 chunk 的 dense KV cache 复用,而是做 **每次 forward 内、相邻 layer 之间的一层提前预取**
@@ -173,12 +174,12 @@ _get_topk_in_seq_cp_pair(...)
-> materialize_shared_paged_buffer(...)
```
Phase 8 暂时不碰 index path。原因
Index K/scale prefetch 在 MLA KV prefetch 之后补齐。当前约束
1. index materialize 已经完成 Phase 6 的一次合并;
2. indexer 在 MLA attention 之前执行,调度窗口不同;
3. 本阶段目标是先验证 MLA prefix prefetch 是否能显著隐藏最大块的 KV materialize
4. 避免同时改变 topk/index 和 attention KV 两条路径,降低正确性风险
3. 当前实现先落地 MLA KV,再补 index K/scale;两者共用同一个 page-aligned slot layout
4. index K/scale prefetch 只覆盖 PAGED topk 路径,RAGGED 仍不进入本阶段
---
@@ -192,9 +193,16 @@ Phase 8 暂时不碰 index path。原因:
Layer L attention 计算期间,
提前为 Layer L+1 materialize prefix MLA KV。
Layer L topk/indexer 完成后,
提前为 Layer L+1 materialize prefix index K/scale。
Layer L+1 attention 前,
复用已经 materialize 完成的 prefix dense KV
只同步补齐 current/suffix pages。
Layer L+1 topk/indexer 前,
复用已经 materialize 完成的 prefix dense index buffer
只同步补齐 current/suffix pages。
```
### 3.2 性能目标
@@ -225,7 +233,6 @@ next layer consume waits only if prefetch has not finished
Phase 8 不做:
- 不做 NSA index K/scale prefetch
- 不引入 `SGLANG_CP_SHARED_KV_LAYER_PREFETCH_KIND`
- 不做 bandwidth throttle/page budget
- 不做多层 dense KV 常驻缓存;
@@ -576,7 +583,7 @@ disable MLA prefetch and fallback sync materialize
2. 太早会和当前层 index materialize/topk 抢带宽;
3. 太早会增加 NCCL collective 顺序风险。
推荐在 `NativeSparseAttnBackend.forward_extend(...)` 中启动:
MLA KV 推荐在 `NativeSparseAttnBackend.forward_extend(...)` 中启动:
```text
1. 当前层需要的 kv_cache/page_table_1 已经准备好;
@@ -614,6 +621,19 @@ if prefetcher is not None:
return attn_output
```
Index K/scale 的启动点在 `Indexer.forward_cuda(...)` 末尾:
```python
topk_result = run_current_layer_topk(...)
index_prefetcher.start_next_layer_prefix(
next_layer_id=layer_id + 1,
token_to_kv_pool=forward_batch.token_to_kv_pool,
)
return topk_result
```
当前层 `_maybe_materialize_shared_index_buffer(...)` 优先消费 `cp_shared_kv_index_prefetcher.consume(...)`miss 时回退原来的 `materialize_shared_paged_buffer(...)`,因此关闭环境变量或 prefetch 条件不满足时语义不变。
`wait_attention_window()` 仍在 `forward_extend(...)` 的 finally 中调用,但当前实现不做同步等待,只保留 deferred 日志/状态检查入口。真正的同步点在下一层 `consume(...)`
```text
@@ -790,9 +810,10 @@ Phase 8 的 local copy/remap helper 应尽量复用 Phase 7 的 tai materialize
```text
SGLANG_CP_SHARED_KV_ENABLE_MLA_PREFETCH = EnvBool(False)
forward_batch.cp_shared_kv_mla_prefetcher
forward_batch.cp_shared_kv_index_prefetcher
```
### Step 2: 拆分 token materialize helper
### Step 2: 拆分 token/page materialize helper
修改:
@@ -803,6 +824,8 @@ forward_batch.cp_shared_kv_mla_prefetcher
```text
slot remap builder
range local token KV materialize into existing dense buffer
range local paged buffer materialize into existing dense buffer
logical page -> slot dense page remap
sync range all-reduce
async all-reduce wrapper
```
@@ -813,7 +836,7 @@ async all-reduce wrapper
full materialize 结果 == prefix materialize + suffix materialize 结果
```
### Step 3: 新增 MLA prefetcher
### Step 3: 新增 MLA / index prefetcher
新增:
@@ -822,7 +845,7 @@ full materialize 结果 == prefix materialize + suffix materialize 结果
职责:
```text
gate
common gate
allocate dense buffer
prefetch prefix range
record event
@@ -831,7 +854,15 @@ defer wait until consume
fallback
```
### Step 4: 接入 `nsa_backend.forward_extend`
Index K/scale prefetch 与 MLA prefetch 使用相同 slot-layout page table
```text
real_page_table.reshape(-1)[slot] -> dense page slot + 1
```
区别是 MLA KV materialize 的单位是 token rows,需要 `page_size` 展开;index K/scale materialize 的单位是 page rowsprefix/suffix range 直接对应 dense page 行。
### Step 4: 接入 `nsa_backend.forward_extend` / `nsa_indexer`
修改:
@@ -840,7 +871,7 @@ fallback
接入位置:
```text
shared KV PAGED path 的 MLA materialize 分支。
shared KV PAGED path 的 MLA materialize 分支,以及 NSA indexer 的 `_maybe_materialize_shared_index_buffer(...)`
```
逻辑:
@@ -853,6 +884,15 @@ run attention
do not wait before return; consume waits before use
```
Index K/scale 的启动点在当前层 topk/indexer 完成后:
```text
Layer L writes current layer index K/scale
Layer L materializes/uses current layer index K/scale for topk
Layer L starts Layer L+1 prefix index K/scale prefetch
Layer L+1 indexer consumes prefetched prefix and fills suffix
```
### Step 5: 单元测试
修改:
@@ -861,12 +901,13 @@ do not wait before return; consume waits before use
新增覆盖:
1. prefix/suffix range materialize 拼接结果等价于 full materialize
1. token/paged prefix/suffix range materialize 拼接结果等价于 full materialize
2. prefix page-aligned gate
3. prefix_len=0 不 prefetch
4. non-PAGED / batch_size>1 / debug enabled fallback
5. consume handle layer mismatch fallback
6. started async handle 必须 wait。
7. index materialize 优先消费 prefetched buffermiss 后回退 full materialize
### Step 6: 远端集成验证
@@ -885,7 +926,7 @@ router: g0034
3. 重复请求触发 radix hit
4. 检查输出质量;
5. 检查没有 collective hang
6. profile 确认 `cp_shared_kv.materialize.token` 同步时间下降或被后续计算 overlap,并能看到下一层 consume 前的必要 wait。
6. profile 确认 `cp_shared_kv.materialize.token` / index paged materialize 同步时间下降或被后续计算 overlap,并能看到下一层 consume 前的必要 wait。
---
@@ -972,4 +1013,3 @@ Fallback
debug enabled 时自动回到现有同步 materialize
pynccl unavailable 时自动回到现有同步 materialize
```