Skip to content

[Feature][KVCache] Add per-head SWA block recycle to ResourceManagerV1 (Hackathon 10th Spring No.53)#7690

Draft
chris-cloudforge wants to merge 1 commit into
PaddlePaddle:developfrom
CloudForge-Solutions:feat/h10-053-headwise-swa-recycle-r2
Draft

[Feature][KVCache] Add per-head SWA block recycle to ResourceManagerV1 (Hackathon 10th Spring No.53)#7690
chris-cloudforge wants to merge 1 commit into
PaddlePaddle:developfrom
CloudForge-Solutions:feat/h10-053-headwise-swa-recycle-r2

Conversation

@chris-cloudforge

Copy link
Copy Markdown

PR1 Body — [Feature][KVCache] Add per-head SWA block recycle to ResourceManagerV1 (Hackathon 10th Spring No.53)

Push-ready version (all commits staged, bench gate passed). Saved here, not in /tmp.
5 required sections per FastDeploy CI gate. Word budget ≤600.


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-on ENABLE_V1_KVCACHE_SCHEDULER=1) allocates one shared block_idx per 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 + PrefixCacheManager to manage block_idx per 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-by trailers.

RFC: PaddlePaddle/community#1361.

Modifications

Area Change
fastdeploy/cache_manager/prefix_cache_manager.py Per-request head-wise GPU free list (gpu_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.py recycle_request_swa_head_cache (per-head cursor advance ≥ window+sink); _should_skip_swa_recycle_for_overlap (per-request cache_swap_metadata / cache_evict_metadata inspection); P4 cleanup in _free_blocks
fastdeploy/model_executor/models/paddleformers/base.py Default-off ERNIE SWA fixture (window/sink/skip-freq/ratio) gated by FD_T53_HEAD_WISE_SWA_FIXTURE=1
fastdeploy/config.py +20 — Engine-main FDConfig fixture: mirror the paddleformers/base.py head-wise SWA attribute injection so ResourceManagerV1._should_use_head_wise_swa (engine-main) sees the same model_config.head_wise_swa_ratio as the worker. Gated on FD_T53_HEAD_WISE_SWA_FIXTURE.
Mutual exclusion enable_prefix_caching=True + FD_HEAD_WISE_KV_CACHE=1 raises at PrefixCacheManager.__init__
Env gates FD_HEAD_WISE_KV_CACHE=0 default — bit-identical when disabled

Tests use real lightweight objects + object.__new__/AST or shape oracles (no MagicMock-only). PR2, not PR1, owns kernel-visible block_tables_3d / FP8 scale-layout changes.

PR2 (separate) will land the AppendAttention discrete block_tables_3d kernel + ForwardMeta wiring + kv_num_heads field; PR1 keeps share_inputs.block_tables 2D and reaches the +30% recycle gate via cache-manager-side changes only.

Usage or Command

# Enable head-wise V1 cache + timely SWA recycle.
# All four env vars must be set together — partial activation is silently a no-op.
# Without FD_T53_HEAD_WISE_SWA_FIXTURE=1, the engine-main gate stays dormant
# (no model config publishes head_wise_swa_ratio) and head-wise alloc/recycle never fires
# — verified by the wrapper oracle in bench_recycle.sh.
export FD_T53_HEAD_WISE_SWA_FIXTURE=1     # engine-main FDConfig fixture (config.py)
export ENABLE_V1_KVCACHE_SCHEDULER=1      # default; shown for clarity
export FD_HEAD_WISE_KV_CACHE=1            # enables per-head block tables
export FD_T53_HEAD_WISE_SWA_RATIO=1.0     # SWA recycle ratio (>0 = recycle active)
python -m fastdeploy.entrypoints.openai.api_server \
    --model baidu/ERNIE-4.5-21B-A3B-Paddle \
    --max-model-len 32768

Accuracy Tests

Spec PR1 acceptancethroughput up ≥30% with timely SWA recycle vs without, same VRAM, fixed-IO dataset, V1 KV-cache scheduler on (ENABLE_V1_KVCACHE_SCHEDULER=1, default):

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 ✓

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_wise grep 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, YAML eb45-21b-a3b-32k-bf16-kv50-512s.yaml. The harness rejects partial JSONs (completed != 1024 or non-empty errors).

Hardware note for reviewers: spec does not pin PR1 hardware. Numbers above are A800-80GB (SM80) via Baidu AI Studio. If H/B card access is granted (cc @luotao1), we will append H/B numbers as supplementary evidence. PR2 (5% TTFT/TBT) does require H/B per spec; tracked separately.

Correctness:

  • CPU pytest coverage under tests/cache_manager/test_head_wise_*.py, tests/cache_manager/test_swa_recycle*.py, and tests/layers/test_append_attention_head_wise_shapes.py — real _FakeCacheManager + object.__new__(ResourceManagerV1) + AST/shape oracles. No MagicMock-only tests.
  • A800 smoke (bsz=4, seq=1024) + long-context recycle smoke — oracle PASS (issued=1 recycled=1), 36,832 head-wise alloc/recycle events in full bench server log.
  • GSM8K parity (head-wise vs non-head-wise abs diff ≤ 0.5 pp) — 1024-prompt bench in progress; parity check scheduled after bench completion.

CI run:

Checklist

  • pre-commit run --all-files clean (black reformatted 2 files; amend committed)
  • All CI checks green (Coverage / base_tests / codestyle / iluvatar / xpu)
  • Reviewer-requested changes addressed
  • No prohibited claims in PR body (verified by pre-push grep): "first in framework", "novel research", "unique to FastDeploy"
  • Authorship statement accurate (no unattributed lifted code)
  • Hardware label on every benchmark number matches the actual card used

…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)
@cloudforge1

Copy link
Copy Markdown
Contributor

Closing — superseded by #7717/#7718 (v4, active review). All T53 work consolidated there.

@PaddlePaddle-bot PaddlePaddle-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.pyworker/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}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 建议 _dummy_run 删除 step_use_cudagraph 参数并修改 CUDAGraph 逻辑,依据 A6 必查项,需确认对应的 xpu_model_runner.pydcu_model_runner.pyiluvatar_model_runner.pymetax_model_runner.py 是否存在相同的 _dummy_run 实现需要同步修改。若其他 runner 不含此逻辑,可在 PR 描述中说明。

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants