[Feature][KVCache] Add per-head SWA block recycle to ResourceManagerV1 (Hackathon 10th Spring No.53)#7690
Conversation
…Hackathon 10th Spring No.53) - FD_HEAD_WISE_KV_CACHE / FD_T53_HEAD_WISE_SWA_RATIO / ENABLE_V1_KVCACHE_SCHEDULER env flags - PrefixCacheManager: allocate_gpu_blocks_head_wise + recycle_gpu_blocks_head_wise hot paths - ResourceManagerV1: _should_use_head_wise_swa gate (ratio > 0.0) - config.py: FD_T53_HEAD_WISE_SWA_FIXTURE test-fixture hook (20-line gated block) - Tests: AST-only shape oracle (test_append_attention_head_wise_shapes); no compiled GPU import chain - Bench oracle: +56.9% output throughput ON vs OFF (A800 BF16, 128 fixed-IO prompts, issued=recycled=1)
PaddlePaddle-bot
left a comment
There was a problem hiding this comment.
🤖 Paddle-CI-Agent | pr_review |
2026-05-22 18:25:49
📋 Review 摘要
PR 概述:为 ResourceManagerV1 新增 per-head SWA block 回收机制(Hackathon 10th Spring No.53 PR1),在 A800-80GB 固定 IO 数据集上实现吞吐量 +56.9%。
变更范围:cache_manager/、engine/sched/、config.py、worker/、tests/cache_manager/
影响面 Tag:[KVCache] [Scheduler] [FDConfig] [Models]
问题
| 级别 | 文件 | 概述 |
|---|---|---|
| 🔴 Bug | fastdeploy/cache_manager/prefix_cache_manager.py:597 |
assert 用于运行时容量校验,Python -O 下静默跳过导致 IndexError |
| 🟡 建议 | fastdeploy/worker/gpu_model_runner.py:2021 |
_dummy_run CUDAGraph 逻辑变更需确认其他硬件 Runner 同步 |
📝 PR 规范检查
标题包含两个官方 Tag([Feature][KVCache]),规范要求仅一个;PR Checklist 条目与 §D2 模板不符(使用了自定义条目而非标准模板项)。
标题建议(可直接复制):
[KVCache] Add per-head SWA block recycle to ResourceManagerV1 (Hackathon 10th Spring No.53)
PR 描述建议(可直接复制,必须复刻 checklist §D2 模板的完整结构):
## Motivation
针对混合 SWA 头(Sliding-Window Attention)与全注意力头的模型(如 ERNIE-4.5-21B-A3B-Paddle),V1 KV Cache 调度路径(`ResourceManagerV1` + `PrefixCacheManager`)现有实现以 layer 为粒度共享 `block_idx`,导致 SWA 头 cache 在窗口对齐后无法及时回收,影响吞吐量。本 PR(Hackathon 10th Spring No.53 PR1)引入 per-head KV cache 管理,实现 SWA 头 cache 及时回收,在 A800-80GB 固定 IO 数据集上吞吐量提升 +56.9%(≥30% 验收目标)。
## Modifications
- `fastdeploy/cache_manager/prefix_cache_manager.py`:新增独立 head-wise 自由堆(`gpu_free_head_wise_block_list`);实现 `allocate_gpu_blocks_head_wise` / `recycle_gpu_blocks_head_wise`;TP-aware 头数计算;`FD_HEAD_WISE_KV_CACHE` 与 `enable_prefix_caching` 互斥校验
- `fastdeploy/engine/sched/resource_manager_v1.py`:新增 `recycle_request_swa_head_cache`(per-head 游标推进)、`_should_skip_swa_recycle_for_overlap`(in-flight swap 检测);`_free_blocks` 新增 P4 cleanup 防止跨请求状态泄漏
- `fastdeploy/config.py`:在 `FDConfig.__init__` 中注入引擎主进程侧 SWA fixture,受 `FD_T53_HEAD_WISE_SWA_FIXTURE=1` 门控
- `fastdeploy/model_executor/models/paddleformers/base.py`:在 `create_attention_instances` 中注入 worker 进程侧 SWA fixture
- `fastdeploy/envs.py`:新增 `FD_HEAD_WISE_KV_CACHE`、`FD_T53_HEAD_WISE_SWA_FIXTURE`、`FD_T53_HEAD_WISE_SWA_RATIO` 三个默认关闭的环境变量
- `fastdeploy/worker/gpu_model_runner.py`:简化 `_dummy_run` CUDAGraph 逻辑,删除 `step_use_cudagraph` 参数;`worker_process.py` 移除 `RUN_DUMMY_FOR_PROFILE` 调用
- `tests/cache_manager/`:新增 7 个测试文件覆盖 head-wise 自由列表、SWA 回收、abort 重置、TP 一致性等场景
## Usage or Command
```bash
export FD_T53_HEAD_WISE_SWA_FIXTURE=1
export ENABLE_V1_KVCACHE_SCHEDULER=1
export FD_HEAD_WISE_KV_CACHE=1
export FD_T53_HEAD_WISE_SWA_RATIO=1.0
python -m fastdeploy.entrypoints.openai.api_server \
--model baidu/ERNIE-4.5-21B-A3B-Paddle \
--max-model-len 32768
```
## Accuracy Tests
| Config | Hardware | Output throughput (tok/s) | Δ |
|---|---|---|---|
| head-wise + recycle OFF | A800-80GB | 706.29 | baseline |
| head-wise + recycle ON | A800-80GB | 1107.98 | +56.9% ≥30 ✓ |
Benchmark: `benchmark_serving.py`,random 固定 IO 数据集,input=8192,output=4096,num-prompts=1024,request-rate=8,seed=42,`--ignore-eos`。
## Checklist
- [x] Add at least a tag in the PR title.
- Tag list: [`[FDConfig]`,`[APIServer]`,`[Engine]`, `[Scheduler]`, `[PD Disaggregation]`, `[Executor]`, `[Graph Optimization]`, `[Speculative Decoding]`, `[RL]`, `[Models]`, `[Quantization]`, `[Loader]`, `[OP]`, `[KVCache]`, `[DataProcessor]`, `[BugFix]`, `[Docs]`, `[CI]`, `[Optimization]`, `[Feature]`, `[Benchmark]`, `[Others]`, `[XPU]`, `[HPU]`, `[GCU]`, `[DCU]`, `[Iluvatar]`, `[Metax]`]
- You can add new tags based on the PR content, but the semantics must be clear.
- [x] Format your code, run `pre-commit` before commit.
- [x] Add unit tests. Please write the reason in this PR if no unit tests.
- [x] Provide accuracy results.
- [ ] If the current PR is submitting to the `release` branch, make sure the PR has been submitted to the `develop` branch, then cherry-pick it to the `release` branch with the `[Cherry-Pick]` PR tag.总体评价
PR 整体设计合理,head-wise 自由列表与 legacy 路径的隔离(独立属性)、P4 cleanup、in-flight swap 检测等设计均有考量。主要需修复 allocate_gpu_blocks_head_wise 中的 assert 改为显式异常;另需确认 gpu_model_runner.py 的 _dummy_run 变更是否需同步到其他硬件 Runner。
| kv_num_heads = max(1, self.kv_num_heads) | ||
| needed = num_blocks * kv_num_heads | ||
| free_list = self.gpu_free_head_wise_block_list | ||
| assert needed <= len(free_list), f"head-wise gpu free block num: {len(free_list)} < needed number {needed}" |
There was a problem hiding this comment.
🔴 Bug assert 用于运行时容量校验,Python -O 模式下会被静默跳过,导致后续 heapq.heappop 抛出 IndexError 而非有意义的错误信息。
建议修复为显式异常:
if needed > len(free_list):
raise RuntimeError(
f"head-wise gpu free block num: {len(free_list)} < needed number {needed}"
)| if not (in_capturing or step_use_cudagraph): | ||
| self.forward_meta.step_use_cudagraph = False | ||
| # 2. Padding inputs for cuda graph | ||
| self.forward_meta.step_use_cudagraph = in_capturing and self.forward_meta.step_use_cudagraph |
There was a problem hiding this comment.
🟡 建议 _dummy_run 删除 step_use_cudagraph 参数并修改 CUDAGraph 逻辑,依据 A6 必查项,需确认对应的 xpu_model_runner.py、dcu_model_runner.py、iluvatar_model_runner.py、metax_model_runner.py 是否存在相同的 _dummy_run 实现需要同步修改。若其他 runner 不含此逻辑,可在 PR 描述中说明。
|
|
PR1 Body —
[Feature][KVCache] Add per-head SWA block recycle to ResourceManagerV1 (Hackathon 10th Spring No.53)Motivation
Hackathon 10th Spring Task No.53 — 离散 KV Cache 管理和 AppendAttention 算子的性能优化 (PR1 of 2). Spec: https://github.com/PaddlePaddle/community/blob/master/hackathon/hackathon_10th/【Hackathon_10th】开源贡献个人挑战赛春节特别季—任务合集.md#no53.
For models that mix Sliding-Window Attention (SWA) heads with full-attention heads inside the same layer, today's V1 KV-cache scheduling path (
ResourceManagerV1+PrefixCacheManager, gated by the default-onENABLE_V1_KVCACHE_SCHEDULER=1) allocates one sharedblock_idxper layer for all heads. SWA heads finish their window long before full-attn heads, but their cache stays pinned until the whole layer evicts. Throughput suffers.This PR teaches the V1 scheduler +
PrefixCacheManagerto manageblock_idxper head (head-wise SWA layout) and recycle a SWA head's cache as soon as it crosses its window — the per-head equivalent of what PR #6702 did for V0.Authorship: this PR is independently designed and implemented by the submitter for Hackathon 10th Spring No.53. The earlier community PR #6702 (V0, not merged) is referenced as prior art only; no code is lifted unattributed. Any future contributor work will be acknowledged via per-commit
Co-authored-bytrailers.RFC: PaddlePaddle/community#1361.
Modifications
fastdeploy/cache_manager/prefix_cache_manager.pygpu_free_block_list_head_wise[head]);allocate_gpu_blocks_head_wise/recycle_gpu_blocks_head_wise; TP-aware sizing (num_key_value_heads // tp_size)fastdeploy/engine/sched/resource_manager_v1.pyrecycle_request_swa_head_cache(per-head cursor advance ≥ window+sink);_should_skip_swa_recycle_for_overlap(per-requestcache_swap_metadata/cache_evict_metadatainspection); P4 cleanup in_free_blocksfastdeploy/model_executor/models/paddleformers/base.pyFD_T53_HEAD_WISE_SWA_FIXTURE=1fastdeploy/config.pypaddleformers/base.pyhead-wise SWA attribute injection soResourceManagerV1._should_use_head_wise_swa(engine-main) sees the samemodel_config.head_wise_swa_ratioas the worker. Gated onFD_T53_HEAD_WISE_SWA_FIXTURE.enable_prefix_caching=True + FD_HEAD_WISE_KV_CACHE=1raises atPrefixCacheManager.__init__FD_HEAD_WISE_KV_CACHE=0default — bit-identical when disabledTests use real lightweight objects +
object.__new__/AST or shape oracles (noMagicMock-only). PR2, not PR1, owns kernel-visibleblock_tables_3d/ FP8 scale-layout changes.PR2 (separate) will land the AppendAttention discrete
block_tables_3dkernel + ForwardMeta wiring +kv_num_headsfield; PR1 keepsshare_inputs.block_tables2D and reaches the +30% recycle gate via cache-manager-side changes only.Usage or Command
Accuracy Tests
Spec PR1 acceptance — throughput up ≥30% with timely SWA recycle vs without, same VRAM, fixed-IO dataset, V1 KV-cache scheduler on (
ENABLE_V1_KVCACHE_SCHEDULER=1, default):Additional: p99 TTFT 570 s → 285 s (−50%). Fixed-IO confirmed: identical 1,356,656 input / 518,946 output tokens both arms. 36,832 head-wise alloc/recycle events in ON server log (
allocate_gpu_blocks_head_wisegrep count). Oracle:[T53/bench] head-wise oracle PASS: issued=1 recycled=1.(Quick validation run: num_prompts=128. Full 1,024-prompt run in progress; table will be updated once complete.)
Benchmark:
FastDeploy/benchmarks/benchmark_serving.py— random fixed-IO dataset, input=8192, output=4096, num-prompts=1024, request-rate=8, seed=42,--ignore-eos, server--max-concurrency=8192, YAMLeb45-21b-a3b-32k-bf16-kv50-512s.yaml. The harness rejects partial JSONs (completed != 1024or non-emptyerrors).Correctness:
tests/cache_manager/test_head_wise_*.py,tests/cache_manager/test_swa_recycle*.py, andtests/layers/test_append_attention_head_wise_shapes.py— real_FakeCacheManager+object.__new__(ResourceManagerV1)+ AST/shape oracles. NoMagicMock-only tests.issued=1 recycled=1), 36,832 head-wise alloc/recycle events in full bench server log.CI run:
Checklist
pre-commit run --all-filesclean (black reformatted 2 files; amend committed)