Prhub

#35396 [Fix] Assert the page-aligned SWA evict floor on both PD decode prealloc paths

原始 PR 作者 hnyls2002 合并时间 2026-08-19 06:48 文件变更 2 提交数 1 评论 3 代码增减 +11 / -3

执行摘要

PD 解码预分配两路径统一页对齐断言并修复 hisparse 测试

PR body 指出:'Mirrors the assert added in #35286 onto the non-hisparse prealloc path, and repairs the hisparse fixture that mocked _swa_tail_len to an unaligned value (currently failing on main in base-a-test-cpu)'。#35286 只在 hisparse 路径校验了 SWA 驱逐长度的页对齐性,非 hisparse 路径仍可能写下未对齐值;旧测试用 mock 绕过真实计算,掩盖了页不对齐问题,导致 main 分支 CI 失败。

值得快速阅读,并与 #35286 一起看以理解页对齐断言的演进脉络。重点学习测试 fixture 的修复手法:不要 mock 计算函数,而应注入真实输入让真实逻辑运行,使单测与生产行为保持一致。

讨论亮点

该 PR 没有 review 评论(review_comments_count = 0),无实质技术交锋。issue 评论仅包含 CI 指令(/tag-and-rerun-ci/rerun-test),机器人报告三个平台的重跑结果全部成功,说明变更通过了针对性回归验证。

实现拆解

  1. 源码断言:在 python/sglang/srt/disaggregation/decode.pyalloc_for_decode_prealloc 函数 uses_swa_tail 分支中,将原先直接赋值 req.kv.swa_evicted_seqlen = fill_len - swa_tail_len 改为先计算局部变量 swa_evicted_seqlen,并断言其非负且 % allocator.page_size == 0,通过断言后才写入请求 KV 元数据。该断言与 #35286 在 hisparse 路径上的一致,补齐非 hisparse 路径的校验缺口。
  2. 测试 fixture 修复:在 test/registered/unit/mem_cache/test_hisparse_allocator.pytest_hisparse_prealloc_uses_swa_tail_for_direct_host_path 中,将 swa_tail_len 期望值从 128 改为 256,新增 sliding_window_size = 200 注入 queue.scheduler,并删除 queue._swa_tail_len 的 mock。这样 _swa_tail_len 的真实计算 floor_align(512 - 200, 256) = 256 生效,使 fill_len - swa_tail_len 页对齐并通过新断言。
  3. 配套验证:作者通过 /rerun-test 重跑 test_hisparse_allocator.pytest_disaggregation_basic.pytest_disaggregation_dsv4.py,ubuntu-latest、2-gpu-h100、8-gpu-h200 三个平台均通过;无新增配置或 schema 改动。
文件 模块 状态 重要度
python/sglang/srt/disaggregation/decode.py PD 解码 modified 5.23
test/registered/unit/mem_cache/test_hisparse_allocator.py 稀疏分配 modified 4.16

关键符号

alloc_for_decode_prealloc test_hisparse_prealloc_uses_swa_tail_for_direct_host_path

关键源码片段

python/sglang/srt/disaggregation/decode.py core-logic

核心变更文件,在 `alloc_for_decode_prealloc` 的 `uses_swa_tail` 分支增加页对齐断言,补齐非 hisparse 路径的 SWA evict 记账校验。

该片段位于 alloc_for_decode_prealloc,处理 Tail-only SWA 分配分支。

if uses_swa_tail:
    # Tail-only SWA 分配:仅在 prefix_len == 0 时有效。
    # 当 prefix_len > 0(radix cache 命中)时回退到 alloc_extend,
    # 按完整页数分配 SWA,SWA 预算可能略低估。
    kv_loc = allocator.alloc_extend_swa_tail(
        prefix_lens=torch.tensor([0], dtype=torch.int64, device=device),
        prefix_lens_cpu=torch.tensor([0], dtype=torch.int64),
        seq_lens=torch.tensor([fill_len], dtype=torch.int64, device=device),
        seq_lens_cpu=torch.tensor([fill_len], dtype=torch.int64),
        last_loc=last_loc,
        extend_num_tokens=fill_len,
        swa_tail_len=swa_tail_len,
        **extra_kwargs,
    )
    # 被窗口驱逐的部分 = 填充长度减去 tail 长度。
    # 该值必须非负且为 allocator.page_size 的整数倍,
    # 否则后续 evict 记账会产生未对齐的页偏移,造成内存账目悬空。
    swa_evicted_seqlen = fill_len - swa_tail_len
    assert (
        swa_evicted_seqlen >= 0
        and swa_evicted_seqlen % allocator.page_size == 0
    )
    req.kv.swa_evicted_seqlen = swa_evicted_seqlen
test/registered/unit/mem_cache/test_hisparse_allocator.py test-coverage

修复 hisparse 测试 fixture,使 mock 值与真实计算逻辑一致,保证新断言在测试中通过,同时验证了页对齐拦截能力。

def test_hisparse_prealloc_uses_swa_tail_for_direct_host_path(self):
    from sglang.srt.disaggregation.decode import DecodePreallocQueue
​
    fill_len = 512
    sliding_window_size = 200
    # _swa_tail_len 会把窗口起点 floor 到页边界:
    # floor_align(512 - 200, 256) = 256,所以 tail 正好是 1 个整页。
    swa_tail_len = 256
​
    # 省略 ReqToTokenPool、allocator、coordinator 等桩构造。
    queue = DecodePreallocQueue.__new__(DecodePreallocQueue)
    queue.scheduler = SimpleNamespace(
        enable_hisparse=True,
        hisparse_coordinator=coordinator,
        server_args=SimpleNamespace(disaggregation_decode_enable_radix_cache=False),
        sliding_window_size=sliding_window_size,
    )
    queue._uses_swa_tail_prealloc = MagicMock(return_value=True)
    # 不再 mock _swa_tail_len,让真实逻辑从 sliding_window_size 计算
    # 页对齐的 tail,从而保证 swa_evicted_seqlen 通过新增断言。
    result = queue._pre_alloc(req)
​
    # 后续断言验证 alloc_extend_swa_tail 收到页对齐的 swa_tail_len,
    # 且 req.kv.swa_evicted_seqlen 等于 fill_len - swa_tail_len。

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

  • 断言严格性swa_evicted_seqlen % allocator.page_size == 0 依赖 swa_tail_len 恒为页对齐值。测试已验证真实 _swa_tail_len 会 floor 到页边界,但未来若有调用方直接传入未对齐的 swa_tail_len,将立即 fail-fast 崩溃。
  • 边界条件swa_evicted_seqlen >= 0 要求 fill_len >= swa_tail_len,若出现填充长度小于窗口尾的场景会触发断言。
  • 测试覆盖:非 hisparse 路径没有新增专项单测,回归保障依赖现有集成测试;hisparse 测试的修复本身验证了新断言能拦截未对齐值。

影响范围限于 PD 解码预分配路径(disaggregation/decode.py),涉及 SWA evict 记账的页对齐一致性。对用户无可见功能变化,但提升了内存账目正确性,能尽早发现页不对齐导致的静默内存损坏。团队层面,本 PR 与 #35286、#35265、#35382 共同构成 SWA/解码分配页对齐的加固系列,降低跨路径回归风险。

核心路径变更 断言依赖页对齐假设 非 hisparse 路径缺专项测试

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论