Keep CP shared-KV fast paths off dense current collectives
CP shared-KV cache-hit batches should compose long prefix pages and short current pages through page-slot IPC instead of falling back to dense all_reduce. Wire the runtime and prefetch consume paths to the TAI current-staging helpers, fail fast when the configured CUDA fast path cannot run, and document the bs>1 cache-hit benchmark evidence. Constraint: bs>1 prefill must preserve the page-slot contract across fp8/bf16 and zero-lane current tails. Rejected: Silent all_reduce fallback | hides correctness and performance regressions in production. Confidence: medium Scope-risk: moderate Directive: Any future fallback in CP shared-KV CUDA fast paths must be explicit warning/fail-fast and covered by runtime tests. Tested: Local py_compile cp_shared_kv_runtime.py and cp_shared_kv_prefetch.py; remote PYTHONPATH=python pytest -q test/registered/unit/mem_cache/test_cp_shared_kv_runtime.py (144 passed, 21 warnings, 2 subtests passed); remote TAI IPC benchmark fp8 bs>1 cache-hit matrix recorded in docs. Not-tested: Full ETE mixed replay after replacing all current collectives with IPC. (cherry picked from commit 8aa3b4ce59e0ebef5da5b0d07499a5f1d9785997)
This commit is contained in:
+131
-39
@@ -442,6 +442,23 @@ L2->L1 load finished on owner rank
|
||||
|
||||
### P1. 补 bs>1 prefetch plan 单测
|
||||
|
||||
**状态(2026-06-12):已完成 MLA baseline。**
|
||||
|
||||
已新增:
|
||||
|
||||
- `test_mla_prefetch_create_batch_uses_exact_prefix_and_current_spans`
|
||||
- `test_mla_prefetch_batch_consume_reduces_exact_current_spans`
|
||||
|
||||
RED 证据:旧代码在 `batch_size=2` 时 `maybe_create()` 直接返回 `None`,且
|
||||
`CpSharedKVMlaPrefetcher.__init__()` 不接受 `prefix_slot_spans/current_slot_spans`。
|
||||
|
||||
GREEN 证据:远端 `cjy-glm5-new` 容器内
|
||||
`test_cp_shared_kv_runtime.py` 全文件通过:
|
||||
|
||||
```text
|
||||
132 passed, 21 warnings, 2 subtests passed
|
||||
```
|
||||
|
||||
**文件:**
|
||||
|
||||
- 修改:`test/registered/unit/mem_cache/test_cp_shared_kv_runtime.py`
|
||||
@@ -479,6 +496,31 @@ PYTHONPATH=python python -m pytest -q \
|
||||
|
||||
### P2. 改造 MLA prefetcher 支持 bs>1 spans
|
||||
|
||||
**状态(2026-06-12):已完成第一版 spans baseline。**
|
||||
|
||||
当前实现:
|
||||
|
||||
1. `CpSharedKVMlaPrefetcher.maybe_create()` 不再以 `batch_size != 1` 为
|
||||
skip 条件。
|
||||
2. create 阶段基于 `metadata.real_page_table`、`extend_prefix_lens_cpu`、
|
||||
`extend_seq_lens_cpu` 构造:
|
||||
- `prefix_slot_spans`
|
||||
- `current_slot_spans`
|
||||
- `prefix_page_count`
|
||||
- `current_page_count`
|
||||
3. `start_next_layer_prefix()` 只 materialize/reduce `prefix_slot_spans`,
|
||||
不再把 batch flattened page table 当成 `[0:prefix_pages)`。
|
||||
4. `consume_prefix_with_current()` 只 reduce `current_slot_spans`,避免把
|
||||
row gap / 其他 request prefix 一起 reduce。
|
||||
5. `consume()` 的 legacy full-materialize suffix 路径也改为使用
|
||||
`current_slot_spans`,避免 bs>1 bounding suffix。
|
||||
|
||||
当前限制:
|
||||
|
||||
- MLA prefix spans 仍走现有 materialize + async all-reduce baseline;还没有
|
||||
接入 spans-list TAI IPC 或 0SM CE。
|
||||
- index prefetcher 仍未改造,继续由 P3 处理。
|
||||
|
||||
**文件:**
|
||||
|
||||
- 修改:`python/sglang/srt/layers/attention/nsa/cp_shared_kv_prefetch.py`
|
||||
@@ -490,7 +532,8 @@ PYTHONPATH=python python -m pytest -q \
|
||||
2. create 阶段构造 `prefix_slot_spans/current_slot_spans`。
|
||||
3. `start_next_layer_prefix()` 只 materialize prefix spans。
|
||||
4. `consume_prefix_with_current()` 只 fill/reduce current spans。
|
||||
5. `consume()` 如果仍存在 legacy suffix path,bs>1 下 fail-fast,避免错误 bounding suffix。
|
||||
5. `consume()` 如果仍存在 legacy suffix path,必须使用 `current_slot_spans`
|
||||
或 fail-fast,不能回到错误 bounding suffix。
|
||||
|
||||
**第一版允许:**
|
||||
|
||||
@@ -503,6 +546,46 @@ PYTHONPATH=python python -m pytest -q \
|
||||
|
||||
### P3. 改造 index prefetcher 支持 bs>1 spans
|
||||
|
||||
**状态(2026-06-12):已完成第一版 spans baseline。**
|
||||
|
||||
已新增:
|
||||
|
||||
- `test_index_prefetch_create_batch_uses_exact_prefix_and_current_spans`
|
||||
- `test_index_prefetch_batch_consume_reduces_exact_current_spans`
|
||||
|
||||
RED 证据:旧代码在 `batch_size=2` 时以
|
||||
`[CP_SHARED_KV_FALLBACK][index_prefetch] reason=batch_size` 返回 `None`,
|
||||
且 `CpSharedKVIndexPrefetcher.__init__()` 不接受
|
||||
`prefix_slot_spans/current_slot_spans`。
|
||||
|
||||
当前实现:
|
||||
|
||||
1. `CpSharedKVIndexPrefetcher.maybe_create()` 不再以 `batch_size != 1`
|
||||
为 skip 条件。
|
||||
2. create 阶段复用 MLA 同一套 deterministic spans:
|
||||
- `prefix_slot_spans`
|
||||
- `current_slot_spans`
|
||||
- `prefix_page_count`
|
||||
- `current_page_count`
|
||||
3. `start_next_layer_prefix()` 只 materialize/reduce index prefix spans。
|
||||
4. `consume_prefix_with_current()` 只 fill/reduce index current spans。
|
||||
5. `consume()` 的 legacy suffix 路径也改为 `current_slot_spans`,
|
||||
不再使用 batch bounding suffix。
|
||||
|
||||
远端验证:
|
||||
|
||||
```text
|
||||
test_cp_shared_kv_runtime.py
|
||||
134 passed, 21 warnings, 2 subtests passed
|
||||
```
|
||||
|
||||
当前限制:
|
||||
|
||||
- index prefetch 仍使用现有 materialize + async all-reduce baseline;
|
||||
spans-list TAI IPC / 0SM CE 留给后续 P5/P6。
|
||||
- active index layer / index skip 的 runtime hook 当前沿用已有
|
||||
`nsa_backend.py` 调用点;本阶段没有修改 skip 参数语义。
|
||||
|
||||
**文件:**
|
||||
|
||||
- 修改:`python/sglang/srt/layers/attention/nsa/cp_shared_kv_prefetch.py`
|
||||
@@ -547,6 +630,19 @@ PYTHONPATH=python python -m pytest -q \
|
||||
- forward stream 上同步等待 L2->L1。
|
||||
- 用新的 all-reduce 确认所有 rank load 完成。
|
||||
|
||||
**2026-06-12 P4 实施记录:**
|
||||
|
||||
- 当前 HiCache load 已通过 `LayerDoneCounter` 暴露 per-layer readiness:
|
||||
- `LayerDoneCounter.wait_until_on_stream(layer_id - start_layer, stream)`
|
||||
- `MLATokenToKVPool.get_key_buffer_for_prefetch(layer_id, stream)`
|
||||
- `MLATokenToKVPool.get_index_k_with_scale_buffer_for_prefetch(layer_id, stream)`
|
||||
- 发现并修正一个 ordering 疏漏:P1-P3 后 prefix materialize 仍在 current stream 上执行,但 `start_next_layer_prefix()` 把 L2->L1 readiness wait 绑定到了 prefetch stream。这样只能保护后续 reduce,不能保护实际读取 L1 raw pages 的 materialize。
|
||||
- 修正策略:MLA/index 的 `start_next_layer_prefix()` 先取得 `current_stream`,把它传给 prefetch-safe getter,使 L2->L1 ready event 挂到实际读取 KV/index page 的 stream;随后仍保持 `prefetch_stream.wait_stream(current_stream)`,reduce 在 prefetch stream 上异步提交。
|
||||
- 没有新增 collective;没有把 forward stream 改成 CPU 同步等待。
|
||||
- 单测锁住:
|
||||
- `test_mla_prefetch_waits_l2_l1_on_materialize_stream_and_reduces_on_prefetch_stream`
|
||||
- `test_index_prefetch_waits_l2_l1_on_materialize_stream_and_reduces_on_prefetch_stream`
|
||||
|
||||
### P5. TAI SM IPC spans baseline
|
||||
|
||||
**文件:**
|
||||
@@ -735,52 +831,48 @@ P7 GSM8K/replay/Nsight 验证
|
||||
|
||||
不要先做 P6 再修 P2/P3。0SM kernel 只解决 transport 代价,不解决 bs>1 prefix/current slot 语义;如果语义仍是 scalar prefix,kernel 越快只会越快地产生错误。
|
||||
|
||||
## 2026-06-12 追加:symm compose 与旧 IPC kernel 的对比口径
|
||||
---
|
||||
|
||||
`symm-syh` 分支的 compose/symm kernel 可能比旧 CUDA IPC current-staging kernel 单次更快,但不能只比较 kernel elapsed time。需要把同步点作为一等指标,否则可能出现“单 kernel 快,但每层/每 buffer 多一次同步,ETE 更慢”的误判。
|
||||
## 9. P5 当前实现状态:bs>1 prefix/suffix IPC spans
|
||||
|
||||
对比 benchmark / Nsight trace 必须至少拆分以下事件:
|
||||
已补齐一个 TAI SM IPC baseline kernel,用于先替代 bs>1 prefix/suffix 上的
|
||||
“local materialize + all_reduce”:
|
||||
|
||||
1. **IPC capability agreement**
|
||||
- `_agreed_tai_ipc_peer_ptrs()` 里的 group agreement / all-reduce。
|
||||
- 应确认是每个 pool tensor 一次,还是每个 forward/layer 重复触发。
|
||||
```text
|
||||
materialize_cuda_ipc_peer_pages_slot_indices(
|
||||
peer_ptrs,
|
||||
dst,
|
||||
owner_ranks,
|
||||
src_page_indices,
|
||||
dst_page_indices,
|
||||
page_nbytes,
|
||||
)
|
||||
```
|
||||
|
||||
2. **symm barrier**
|
||||
- `cp_symm_barrier()` 调用次数、耗时、等待方差。
|
||||
- 需要按 token KV / index buffer 分开统计。
|
||||
- 当前 tai-kernel 实现是 1 个 block / 1 个 warp 的 CUDA spin barrier,
|
||||
不是 copy-engine/0SM 路径;它占用很少 SM,但会在当前 stream 上形成
|
||||
明确同步点。判断 symm 是否优于旧 IPC 时,必须把这个 barrier 的次数和
|
||||
rank 间等待方差算进去。
|
||||
语义:
|
||||
|
||||
3. **symm mega gather**
|
||||
- `materialize_cuda_ipc_peer_pages_slot_dense()` 在 symm combined ptr table 上的耗时。
|
||||
- 统计 prefix pages、current pages、request 数、dense pages。
|
||||
```text
|
||||
dst[dst_page_indices[i]] = peer_ptrs[owner_ranks[i]][src_page_indices[i]]
|
||||
```
|
||||
|
||||
4. **compact current reduce**
|
||||
- symm 未开启或不可用时的 `_reduce_current_pages_compact()`。
|
||||
- 这是 compact current collective,不是 dense full fallback;但仍会同步/占用通信资源,需要单独计数。
|
||||
与旧 `slot_dense` kernel 的区别:
|
||||
|
||||
5. **dense full fallback reduce**
|
||||
- `_all_reduce_materialized_buffer(... v2_full ...)`。
|
||||
- 生产 CUDA + TAI materialize 开启时不允许静默发生;应 fail-fast 暴露 `CP_SHARED_KV_FAIL_FAST][compose_v2]`。
|
||||
1. 旧 kernel 只能写 `slot i -> dense page i+1`,等价于只支持从 slot 0 开始的一段连续 prefix。
|
||||
2. 新 kernel 显式传入 `dst_page_indices`,支持 bs>1 的多个 prefix spans 和 suffix spans。
|
||||
3. invalid owner/src 会 zero-fill 对应 dst page;未被 descriptor 指向的 dst page 保持原值。
|
||||
4. 该 kernel 仍是 SM copy baseline,不是最终 0SM copy-engine queue。
|
||||
|
||||
6. **CPU descriptor / plan 成本**
|
||||
- `get_or_build_compose_plan()` cache hit/miss。
|
||||
- per-forward 是否复用 descriptor;不要把一次性 build 成本误算到每层 steady state。
|
||||
SGLang 接入位置:
|
||||
|
||||
建议 benchmark 矩阵:
|
||||
1. `cp_shared_kv_runtime.py`
|
||||
- 新增 token/index span IPC helper。
|
||||
- partial-current prefix 支持多 span IPC,prefix 不再因为 bs>1 退回 all_reduce。
|
||||
2. `cp_shared_kv_prefetch.py`
|
||||
- MLA/index prefix prefetch 先尝试 span IPC;成功时在 current stream record event,不再 enqueue prefix all_reduce。
|
||||
- full-cache-hit suffix consume 先尝试 span IPC;成功时跳过 suffix all_reduce。
|
||||
|
||||
- dtype:bf16 / fp8_e4m3;
|
||||
- batch size:1, 2, 5, 10;
|
||||
- extend:1k, 2k, 10k, 40k, 65k;
|
||||
- cached/prefix:100k, 200k, 300k;
|
||||
- case:cache-hit partial-current、current-only、multi-request shared prefix;
|
||||
- mode:legacy dense fallback(只作为基线,不允许生产静默)、old CUDA IPC current staging、symm staging、symm prefetch。
|
||||
尚未覆盖:
|
||||
|
||||
结论标准:保留默认路径必须同时满足:
|
||||
|
||||
- 无 dense full fallback;
|
||||
- 同步点数量不高于旧 IPC 路径,或同步耗时被更少 kernel/更高带宽抵消;
|
||||
- ETE replay 在 cache-hit-heavy 短 extend 场景提升,而不是只在 micro benchmark 提升;
|
||||
- GSM8K cache-hit 二轮精度不掉点。
|
||||
1. partial-current 的 current rows 仍是本 rank 当前 forward 产生的临时 buffer,source layout 不是长期 L1 page buffer;不能直接复用 peer page IPC。
|
||||
2. 该 current rows all_reduce 需要单独设计 owner-aware current-source IPC/fused compose kernel。
|
||||
3. 0SM CE path 仍未实现,本阶段只是先消除 bs>1 prefix/suffix 上不必要的 collective。
|
||||
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
# NSA Prefill CP shared-KV:用自研 IPC collective 替换 materialize all_reduce
|
||||
|
||||
## 目标
|
||||
|
||||
彻底移除 CP shared-KV materialize 热路径上的 NCCL/Gloo `all_reduce`:
|
||||
|
||||
1. prefix / suffix / full-cache-hit:继续使用 L1 page buffer 上的 IPC page gather,失败必须显式 warning / fail-fast,不能静默回退。
|
||||
2. partial-current / current reuse:新增 current staging + ready flag IPC collective,替换当前 dense page fill 后的 slot-range `all_reduce`。
|
||||
3. bs>1:descriptor 必须一次覆盖 batch 内多个 request 的 slot spans,不允许 per-request 循环发射 kernel。
|
||||
4. fp8 / bf16:token KV 与 index page buffer 两条路径都要支持。
|
||||
5. benchmark:必须覆盖 all_reduce baseline、现有 IPC prefix/suffix、current staging IPC,并输出 CPU submit、GPU elapsed、有效带宽、kernel launch 数。
|
||||
|
||||
## 当前 all_reduce 分类
|
||||
|
||||
### 已可用 IPC 替换的路径
|
||||
|
||||
- `materialize_shared_token_kv_buffer` / `materialize_shared_paged_buffer`:full materialize fallback all_reduce。
|
||||
- `materialize_prefix_and_reuse_current_*` 的 prefix spans:已有 `_try_tai_ipc_materialize_*_page_slot_spans_into`。
|
||||
- `CpSharedKV*Mla/IndexPrefetcher.consume()` 的 suffix spans:已有 IPC span gather。
|
||||
- `start_next_layer_prefix()` 的 prefix prefetch:已有 IPC span gather。
|
||||
|
||||
这些路径的源数据是长期存在的 L1 `kv_cache` / `page_buffer`,IPC handle 可以按 storage 缓存,只需在分配/扩容后重新 open。
|
||||
|
||||
### 仍依赖 all_reduce 的路径
|
||||
|
||||
- `materialize_prefix_and_reuse_current_kv_page_slots()`:`fill_current_kv_page_slots_and_remap_locs()` 后,对 current slot spans 做 `_all_reduce_materialized_buffer_range()`。
|
||||
- `materialize_prefix_and_reuse_current_index_page_slots()`:`fill_current_index_page_slots()` 后,对 current page spans 做 `_all_reduce_materialized_buffer_range()`。
|
||||
- `CpSharedKVMlaPrefetcher.consume_prefix_with_current()` / `CpSharedKVIndexPrefetcher.consume_prefix_with_current()`:prefetched prefix + current fill 后仍 reduce current slot spans。
|
||||
|
||||
这些路径的源是每层 forward 产生的临时 `current_kv_cache` / `current_index_k` / `current_index_scale`。不能直接对临时 tensor 做 per-layer IPC handle all_gather,否则只是把 all_reduce 换成另一个高频 collective。
|
||||
|
||||
## 设计选择
|
||||
|
||||
### 方案 A:临时 tensor IPC handle all_gather(拒绝)
|
||||
|
||||
每层对 `current_*` tensor open IPC handle,然后 peer-read current rows。
|
||||
|
||||
拒绝原因:
|
||||
- data_ptr/shape 每层/每 batch 变化,handle cache 命中率低。
|
||||
- 仍需要高频 `all_gather` 交换 handle/offset。
|
||||
- CPU submit 和同步开销不可控,违背“彻底干掉 collective”的目标。
|
||||
|
||||
### 方案 B:persistent current staging + ready flag(采用)
|
||||
|
||||
每个 CP rank 维护长期 CUDA staging buffer 和 ready counter buffer:
|
||||
|
||||
1. current fill kernel 同时把本 rank owner-lane current pages 写入本 rank staging buffer,布局与 dense slot page 对齐。
|
||||
2. publish 完成后在同 stream 写 ready seq:`__threadfence_system()` 后 store seq。
|
||||
3. 所有 rank 用 IPC peer ptrs 读取 owner rank 的 staging pages,gather 到本地 dense buffer;gather kernel 在读取每个 owner 前等待 `peer_ready[owner] >= seq`。
|
||||
4. descriptor 以 dense slot page 为单位,跨 batch request 合并为一个 owner/page/slot list,一次 kernel launch 完成多个 request。
|
||||
|
||||
采用原因:
|
||||
- IPC handle 只在 staging buffer 分配/扩容时交换,热路径无 NCCL/Gloo collective。
|
||||
- current 数据仍按 page 最小单位发布,符合当前 page-aligned cache 合同。
|
||||
- bs>1 可以复用 slot span descriptor,一次 launch 覆盖多个 request。
|
||||
- 可与现有 TAI current fill kernel 融合,避免重复 remap/row-mask 逻辑。
|
||||
|
||||
## staging buffer 合同
|
||||
|
||||
### token KV staging
|
||||
|
||||
- 形状语义:flat bytes,容量至少覆盖 `dense_num_pages * page_size * kv_row_bytes`。
|
||||
- 写入地址:`dense_slot_page * page_nbytes + row_offset * row_nbytes`。
|
||||
- 每次 publish 只保证 valid current rows 正确;为了避免 stale tail,publish kernel 需要对 touched current slot pages 的 tail slack 清零,或 SGLang 必须保证返回 locs 不引用 tail slack。第一版建议在 publish kernel 内按 touched page 清零,优先正确性。
|
||||
|
||||
### index staging
|
||||
|
||||
- 形状语义:flat bytes,容量至少覆盖 `dense_num_pages * index_page_bytes`。
|
||||
- 写入地址:`dense_slot_page * page_bytes`,内部包含 K rows + scale rows。
|
||||
- current K/scale 的 valid rows 写入对应 row offset;tail slack 清零。
|
||||
|
||||
### ready flag
|
||||
|
||||
- 每 rank 一个 `uint64/int64` counter buffer,通过 IPC peer ptrs 打开。
|
||||
- 每次 current publish 使用递增 seq。
|
||||
- gather kernel 对需要读取的 owner 执行 device-side wait,直到 `peer_ready[owner] >= seq`。
|
||||
- wait kernel 需要 watchdog/iteration bound,debug build 可 fail-fast;生产第一版可以保留有限 spin + error flag,避免死锁静默挂住。
|
||||
|
||||
## descriptor 合同
|
||||
|
||||
输入:`current_slot_spans` / `slot_logical_pages` / layout / physical capacity。
|
||||
|
||||
输出:
|
||||
- `owner_ranks[num_pages]`
|
||||
- `src_page_indices[num_pages]`:对 staging 来说等于 dense slot page id;对 persistent L1 prefix/suffix 来说是 physical page index。
|
||||
- `dst_slot_indices[num_pages]`:dense buffer 1-based slot index,保持现有 kernel 合同。
|
||||
|
||||
bs>1 要求:
|
||||
- span list 可以覆盖多个 request。
|
||||
- descriptor 构造只按 merged slot spans 生成一次,不允许 request loop + 多次 kernel。
|
||||
- 如果某些 request 没 current page,descriptor 为空时直接成功。
|
||||
|
||||
## TAI kernel 阶段
|
||||
|
||||
### P1:RED tests / benchmark skeleton
|
||||
|
||||
- SGLang unit:partial-current compose 在 cp_size>1 时应调用 IPC current gather helper,不应调用 `_all_reduce_materialized_buffer_range`。
|
||||
- tai-kernel CUDA test:声明期望 API `publish_current_*_to_staging` 和 `materialize_cuda_ipc_peer_pages_slot_indices_wait_ready`,先验证缺失失败。
|
||||
- benchmark skeleton:同一输入比较 `local fill + all_reduce` 与 `publish + IPC gather`。
|
||||
|
||||
### P2:current publish kernel
|
||||
|
||||
- token:扩展/新增 TAI op,复用 `cp_fill_current_kv_page_slots` remap 逻辑,同时写 staging 和 ready seq。
|
||||
- index:扩展/新增 TAI op,复用 `cp_fill_current_index_page_slots`,同时写 staging 和 ready seq。
|
||||
|
||||
### P3:ready-wait IPC gather kernel
|
||||
|
||||
- 基于现有 `materialize_cuda_ipc_peer_pages_slot_indices` 增加 ready peer ptrs + seq 参数。
|
||||
- 支持 page_nbytes 变长,owner/dst descriptor 长度变长。
|
||||
- 支持 self-rank peer ptr 快路径,便于单机 CUDA unit test。
|
||||
|
||||
### P4:SGLang runtime 接入
|
||||
|
||||
- 新增 runtime helper:`_try_tai_ipc_materialize_current_token_kv_page_slot_spans_into`。
|
||||
- 新增 runtime helper:`_try_tai_ipc_materialize_current_paged_buffer_page_slot_spans_into`。
|
||||
- current compose:先 fill+publish,再 IPC gather current spans;失败时 warning/fail-fast,不再静默 all_reduce。
|
||||
- prefetch consume_prefix_with_current:同样走 current staging IPC。
|
||||
|
||||
### P5:严格化 fallback
|
||||
|
||||
- prefix/suffix/full materialize:IPC 失败在生产 CP shared-KV fast path 下 fail-fast;仅在显式 debug env 下允许 fallback,用 warning 标记。
|
||||
- all_reduce helper 保留给非 CP shared-KV 或测试 reference,不在 fast path 默认触发。
|
||||
|
||||
### P6:验证
|
||||
|
||||
- CPU unit:descriptor / fallback contract / no-allreduce path。
|
||||
- CUDA unit:self-rank staging roundtrip、multi-process cp=2/8 roundtrip、fp8/bf16、bs=1/5/10。
|
||||
- benchmark:4k/16k/40k/80k/160k prefix+current,bs=1/5/10,报告 CPU/GPU 时间和有效带宽。
|
||||
- ETE:GSM8K 两轮 cache-hit 精度不掉点;mixed replay 不出现 detokenizer hang / all_reduce collective mismatch。
|
||||
|
||||
## 风险与约束
|
||||
|
||||
- device-side ready wait 如果某 rank 没有 publish 会死等;必须确保所有 rank 都按同一 seq 进入 publish/gather,即使本 rank current rows 为空也要 publish ready。
|
||||
- staging buffer 扩容会触发一次 IPC handle exchange;必须高水位缓存,不能每层分配。
|
||||
- current staging tail slack 不能污染 attention/index;第一版应清零 touched current pages,后续再优化成 valid-locs 完全约束。
|
||||
- 一次 kernel 同时 publish+peer-gather 在跨进程场景没有全局同步,容易死锁;第一版采用 publish kernel + gather kernel 两阶段。
|
||||
|
||||
## 当前结论
|
||||
|
||||
先实现 staging+ready 的 SM IPC collective,彻底移除 current compose all_reduce。0SM/copy-engine 版本后续单独做;当前优先解决 correctness、CPU collective overhead、bs>1 一次 launch。
|
||||
|
||||
## 2026-06-11 实现与 benchmark 更新
|
||||
|
||||
### 已完成
|
||||
|
||||
- TAI 已新增 `publish_cuda_ipc_slot_pages_and_mark_ready`:把本 rank 已经填好的 dense slot pages 复制到 persistent staging,并写 ready seq。
|
||||
- TAI 已新增 `materialize_cuda_ipc_peer_pages_slot_indices_wait_ready`:按 owner/src/dst descriptor 等待 peer ready 后从 IPC peer staging 复制到本地 dense buffer。
|
||||
- SGLang runtime 的以下 current compose 已接入 current-staging IPC helper:
|
||||
- token KV:`materialize_prefix_and_reuse_current_kv_page_slots()`。
|
||||
- index page buffer:`materialize_prefix_and_reuse_current_index_page_slots()`。
|
||||
- MLA/index prefetch `consume_prefix_with_current()`。
|
||||
- bs>1 descriptor 合同已覆盖:一次 launch 可以用 flattened slot-page descriptor 覆盖多个 request spans;benchmark 用 `--current-batch-requests 5` 验证该形态。
|
||||
|
||||
### 与原计划的差异
|
||||
|
||||
P2 暂时没有把 publish 融入 `cp_fill_current_*` row-fill kernel,而是采用:
|
||||
|
||||
1. current fill 先写 dense buffer;
|
||||
2. `publish_cuda_ipc_slot_pages_and_mark_ready` 再按 page 复制本 rank owner pages 到 staging;
|
||||
3. wait-ready IPC gather 从 peer staging 拉取所有 current pages。
|
||||
|
||||
这样 correctness 风险低,接入面小,但 current path 多一次 page copy + 一个额外 kernel。benchmark 也证明小 current span 下该版本不一定优于 NCCL/Gloo all_reduce;后续要进一步追性能,需要做“current fill 同时写 staging + mark ready”的融合版本。
|
||||
|
||||
### 远端 benchmark 证据
|
||||
|
||||
环境:`g0034` / `cjy-glm5-new` / `torchrun --nproc_per_node=8` / `benchmark/nsa_prefill/benchmark_cp_shared_kv_ipc_gather.py`。
|
||||
|
||||
命令示例:
|
||||
|
||||
```bash
|
||||
PYTHONPATH=python torchrun --standalone --nproc_per_node=8 \
|
||||
benchmark/nsa_prefill/benchmark_cp_shared_kv_ipc_gather.py \
|
||||
--tokens 16384 32768 65536 98304 122880 \
|
||||
--dtype uint8 --kv-dim 656 \
|
||||
--warmup 3 --repeat 8 \
|
||||
--include-current-staging --current-batch-requests 5 --no-check
|
||||
```
|
||||
|
||||
FP8/uint8 MLA page(`kv_dim=656`)current-staging IPC vs current all_reduce:
|
||||
|
||||
| tokens | pages | all_reduce p50 | current IPC p50 | 结论 |
|
||||
| --- | ---: | ---: | ---: | --- |
|
||||
| 16k | 256 | 0.184 ms | 0.199 ms | 当前两阶段 IPC 小幅变慢 |
|
||||
| 32k | 512 | 0.227 ms | 0.217 ms | 基本持平/略快 |
|
||||
| 65k | 1024 | 0.337 ms | 0.253 ms | IPC 快约 25% |
|
||||
| 98k | 1536 | 0.450 ms | 0.307 ms | IPC 快约 32% |
|
||||
| 122k | 1920 | 0.527 ms | 0.362 ms | IPC 快约 31% |
|
||||
|
||||
BF16 MLA page(`kv_dim=576`)current-staging IPC vs current all_reduce:
|
||||
|
||||
| tokens | pages | all_reduce p50 | current IPC p50 | 结论 |
|
||||
| --- | ---: | ---: | ---: | --- |
|
||||
| 16k | 256 | 0.175 ms | 0.175 ms | 持平 |
|
||||
| 65k | 1024 | 0.397 ms | 0.414 ms | 两阶段 IPC 小幅变慢 |
|
||||
| 122k | 1920 | 0.671 ms | 0.594 ms | IPC 快约 11% |
|
||||
|
||||
同一 benchmark 中 prefix/L1 persistent IPC 的 `cuda_ipc_peer_pages_materialize_slot_dense` 仍明显优于 dense all_reduce,例如 FP8:65k tokens 从 0.334 ms 降到 0.164 ms,122k tokens 从 0.517 ms 降到 0.274 ms。这说明 persistent L1 prefix/suffix 场景适合直接 IPC;current 临时数据场景的瓶颈在 publish 阶段,下一步应优先融合 current fill + staging publish。
|
||||
|
||||
### 下一步必须处理的点
|
||||
|
||||
1. fast path fallback 已收窄:当 tensor 在 CUDA 上且 `SGLANG_CP_SHARED_KV_USE_TAI_MATERIALIZE=1` 时,prefix/current IPC 失败会 fail-fast,不再静默走 all_reduce;CPU reference/TAI 显式关闭路径仍保留测试 fallback。
|
||||
2. wait-ready kernel 现在有 `max_spins`,但没有 device error flag;超过 spin 后可能复制 stale data。生产化前应增加 error flag 或明确 fail-fast 检测,避免 silent corruption。
|
||||
3. current publish 未融合,短 current span 可能回退性能。应新增 TAI fused current fill+publish op:fill dense 的同时写 staging,随后只用一个 tiny mark-ready kernel,再 wait-gather。
|
||||
|
||||
## 2026-06-11 bs>1 cache-hit compose benchmark 补充
|
||||
|
||||
### 为什么补这个 benchmark
|
||||
|
||||
之前的 IPC benchmark 只按“总 pages”测 current staging 或 prefix gather,不能覆盖真实线上 cache-hit 形态:
|
||||
|
||||
- 每个 request 有很长 cached prefix(100k-300k tokens)。
|
||||
- 每个 request 的 extend/current 较短(10k-65k tokens)。
|
||||
- prefill bs>1 时一次 batch 内有 2-10 个 request。
|
||||
- 部分短 current tail 只落在少数 CP owner lanes,上游 zero-lane rank 仍必须 publish ready,不能退出 collective 合同。
|
||||
|
||||
因此新增 `--include-cache-hit-compose` / `--cache-hit-only`,显式构造 request-major dense slot layout:
|
||||
|
||||
```text
|
||||
page0(dummy) |
|
||||
req0 cached pages | req0 current pages |
|
||||
req1 cached pages | req1 current pages | ...
|
||||
```
|
||||
|
||||
对比两条路径:
|
||||
|
||||
1. `cache_hit_dense_all_reduce_full`:每 rank 构造本 owner pages 的 dense buffer,然后对整个 dense slot buffer 做 all_reduce。
|
||||
2. `cache_hit_ipc_prefix_current_compose`:cached prefix 从 persistent compact owner staging 走 IPC slot-index materialize;current/extend 从 dense current staging publish + ready-wait IPC gather。
|
||||
|
||||
注意:current owner 分布按完整 request page positions 计算,所以短 extend 可能只触达少数 owner lanes。这是预期现象,不再要求每个 rank 都拥有 current page;zero-lane rank 仍会 publish ready,避免 peer wait 死锁。
|
||||
|
||||
### 远端命令
|
||||
|
||||
```bash
|
||||
PYTHONPATH=python torchrun --standalone --nproc_per_node=8 \
|
||||
benchmark/nsa_prefill/benchmark_cp_shared_kv_ipc_gather.py \
|
||||
--cache-hit-only \
|
||||
--cache-hit-cached-tokens 102400 204800 307200 \
|
||||
--cache-hit-extend-tokens 10240 32768 65536 \
|
||||
--cache-hit-batch-requests 2 5 10 \
|
||||
--dtype uint8 --kv-dim 656 \
|
||||
--warmup 2 --repeat 5 --no-check
|
||||
```
|
||||
|
||||
### FP8/uint8 结果摘要
|
||||
|
||||
环境:`g0034` / `cjy-glm5-new` / 8 ranks / `kv_dim=656` / page size 64。
|
||||
|
||||
| bs | cached/req | extend/req | dense all_reduce p50 | IPC compose p50 | 收益 |
|
||||
| ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| 2 | 100k | 10k | 0.858 ms | 0.686 ms | 1.25x |
|
||||
| 2 | 200k | 65k | 1.960 ms | 1.503 ms | 1.30x |
|
||||
| 2 | 300k | 65k | 2.669 ms | 1.999 ms | 1.34x |
|
||||
| 5 | 100k | 10k | 2.043 ms | 1.525 ms | 1.34x |
|
||||
| 5 | 200k | 65k | 4.792 ms | 3.517 ms | 1.36x |
|
||||
| 5 | 300k | 65k | 6.605 ms | 4.798 ms | 1.38x |
|
||||
| 10 | 100k | 10k | 4.036 ms | 2.951 ms | 1.37x |
|
||||
| 10 | 200k | 65k | 9.441 ms | 6.999 ms | 1.35x |
|
||||
| 10 | 300k | 65k | 13.023 ms | 9.552 ms | 1.36x |
|
||||
|
||||
完整矩阵结论:在 fp8 cache-hit bs>1 形态下,IPC compose 对 dense all_reduce 稳定约 1.25x-1.6x;cached 越长、bs 越大收益越稳定。这个 benchmark 比单独 current staging 更贴近实际,因为真实 cache-hit 主要成本来自长 cached prefix 的 materialize,而这部分 persistent IPC 收益明显。
|
||||
|
||||
### BF16 spot-check
|
||||
|
||||
命令只测代表性 case:`bs=5 cached=200k extend=10k/65k dtype=bf16 kv_dim=656`。
|
||||
|
||||
| bs | cached/req | extend/req | dense all_reduce p50 | IPC compose p50 | 结论 |
|
||||
| ---: | ---: | ---: | ---: | ---: | --- |
|
||||
| 5 | 200k | 10k | 5.929 ms | 5.595 ms | 略快 |
|
||||
| 5 | 200k | 65k | 7.387 ms | 7.366 ms | 基本持平 |
|
||||
|
||||
BF16 下收益不明显,原因是两阶段 current publish 的额外 copy 被放大;当前线上 GLM5 使用 fp8 KV cache,因此优先级仍然是把 fp8 bs>1 fast path 接稳。后续若要兼顾 BF16,需要 fused current fill+publish,减少 current staging 的额外 HBM copy。
|
||||
Reference in New Issue
Block a user