Prhub

#56955 [CI] Fix NIXL push worker test stub after failure deferral

原始 PR 作者 khluu 合并时间 2026-09-15 17:00 文件变更 1 提交数 1 评论 1 代码增减 +5 / -3

执行摘要

修复 NIXL 推送测试桩,适配失败改为入队的新契约

PR 描述给出了明确的因果链:PR #56104 把 NIXL 推送注册失败从后台线程直接处理改成了进入既有的 _failed_recv_reqs 队列,而 main 上由 PR #56317 新增的测试桩仍然没有该队列、并断言旧的直接处理行为,于是“合并后的 main 树在真正验证目标契约之前就已经失败”。CI 证据是 main 构建 #89015、#89016、#89017、#89022 的 H200 V1 KV Connectors lane 在 TestPushWriterRegSend::test_fails_request_when_engine_not_handshaked 上抛 AttributeError: '_StubWriterWorker' object has no attribute '_failed_recv_reqs',其中 #89016 在同命令下已跑完 1,218 个测试才被这一个失败打断。作者同时说明 PR #56104 的构建 #89004 跑在旧 head 上,因而无法暴露这次跨合并契约错配,并搜索过 _failed_recv_reqs、TestPushWriterRegSend 等关键字确认没有重复修复。

值得精读,但只需 10 分钟量级:它是理解 NIXL 失败上报从“后台线程直接回调”演进到“统一入队”的清晰样本,也是“测试桩手工镜像对象状态”这类反模式导致跨 PR 交叉合并红的最短案例。阅读时重点看三处设计决策:为什么失败要入队而不是留在 _handle_failed_transfer、为什么用 get_nowait() + empty() 双重断言锁定“恰好一次”语义、以及 _pollable_worker() 为什么必须去掉重复初始化。后续建议把 stub 字段初始化改为从基类字段清单或共享 fixture 派生,从根本上消除同型漂移;同时合入前应补一次人工行级复核(fork PR 自动 review 已禁用)。

讨论亮点

由于这是 fork PR,自动评审被禁用,仓库内唯一的 review 反馈是 claude[bot] 的模板评论:“This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.”,没有任何行级技术讨论。真正有价值的内容集中在作者 khluu 的验证评论:“Exact validation is running in Buildkite #89027 at literal signed head 4fda4d2c... The same cross-merge failure is present on current main #89022 in both H200 and MI300 V1 KV Connectors; the PR remains draft pending exact H200 evidence and human line review.” 这既确认了故障是跨合并契约错配而非平台特有问题,也说明该修复在合入前依赖作者自证 + 人工行级复核,属于“证据驱动但评审薄弱”的变更。

实现拆解

  1. 补齐测试桩的基类状态(入口变更):在 _StubWriterWorker.fresh() 的 “Base worker fields touched by start_load_kv / _get_new_notifs” 分组内新增 w._failed_recv_reqs = queue.Queue(),与 _recving_metadata、_pending_recv_notifs、_recv_failures、_recving_transfers 并列。原因是 PR #56104 之后,_do_send_reg_notif 在“未握手”分支改走基类失败队列,桩对象缺字段就会在进入断言前直接抛 AttributeError。
  2. 把回归断言切换到新契约:test_fails_request_when_engine_not_handshaked 重命名为 test_queues_failed_request_when_engine_not_handshaked,断言由 failures == [("req-1", None)] 改为三件事——failures == [](不再触发被替换的 _handle_failed_transfer)、w._failed_recv_reqs.get_nowait() == "req-1"、w._failed_recv_reqs.empty()(失败请求恰好入队一次、无重复),并保留 w.nixl_wrapper.send_notif.assert_not_called() 验证未握手时不会真的发出通知。
  3. 消除重复初始化:删除 _pollable_worker() 里后加的 w._failed_recv_reqs = queue.Queue()。由于 _pollable_worker() 基于 fresh() 构造,该行在字段补齐后成为重复定义,去掉可以避免两处初始化再次漂移。
  4. 测试与 CI 配套:本地 .venv/bin/python -m pytest tests/v1/kv_connector/unit/test_nixl_push_connector.py -q 52 passed,pre-commit run --files 全部适用钩子通过,git diff --check 通过;Buildkite #89027 在精确 signed head 4fda4d2c 上只跑 H200 V1 KV Connectors lane 做定点验证。生产代码、配置、schema 与部署均未改动,因此声明无需模型评测。
文件 模块 状态 重要度
tests/v1/kv_connector/unit/test_nixl_push_connector.py KV 连接器 modified 5.22

关键符号

_StubWriterWorker.fresh _pollable_worker test_queues_failed_request_when_engine_not_handshaked test_fails_request_when_engine_not_handshaked

关键源码片段

tests/v1/kv_connector/unit/test_nixl_push_connector.py test-coverage

本 PR 唯一改动文件:补齐 _StubWriterWorker 缺失的 _failed_recv_reqs 队列,把未握手失败用例改写为入队语义断言,并移除 _pollable_worker 中的重复初始化,直接决定 H200 / MI300 V1 KV Connectors lane 能否恢复绿灯。

class _StubWriterWorker(NixlPushConnectorWorker):
    """不调用 __init__ 构造 worker,从而在不启动 NIXL / torch 的前提下驱动
    匹配与通知逻辑(测试桩)。"""
​
    @classmethod
    def fresh(cls) -> _StubWriterWorker:
        w = object.__new__(cls)
​
        # ---- 推送侧状态:由 NixlPushConnectorWorker 维护 ----
        w._sending_transfers = defaultdict[ReqId, list[TransferHandle]](list)
        w._sending_transfers_lock = threading.Lock()
        w._push_finished_blocks = {}
        w._pending_d_registrations = {}
        w._reg_send_inbox = queue.Queue()
        w._finished_blocks_inbox = queue.Queue()
        w._evict_finished_inbox = queue.Queue()
        w._deferred_push_inbox = queue.Queue()
        w._push_writer_thread = None
​
        # ---- 基类字段:start_load_kv / _get_new_notifs 会访问 ----
        w._recving_metadata = {}
        w._pending_recv_notifs = {}
        # 本 PR 的关键新增:PR #56104 之后,未握手的注册失败不再由推送线程
        # 直接调用 _handle_failed_transfer,而是投递基类失败队列统一处理;
        # 桩对象缺此字段时会在进入断言前抛 AttributeError,让整条 lane 变红。
        w._failed_recv_reqs = queue.Queue()
        w._recv_failures = set()
        w._recving_transfers = defaultdict(list)
        w._reqs_to_process = set()
        w._reqs_to_send = {}
        w.tp_rank = 0
        w.engine_id = "test-decode-engine"
        w._remote_agents = {} # 不放任何 agent ⇒ 走未握手分支
        w._handshake_lock = threading.RLock()
        return w
​
​
# ---- 回归测试:把断言从“直接回调”切换到“入队恰好一次” ----
def test_queues_failed_request_when_engine_not_handshaked(self):
    """未握手时注册失败必须入队且恰好一次,而不是直接回调处理。"""
    w, failures = self._reg_send_worker()
    # _reg_send_worker() 会把 _handle_failed_transfer 替换成收集器 failures,
    # 因此 failures 是否为空,就等于旧直调路径是否仍然被触发。
​
    w._do_send_reg_notif("req-1", _registration_data("req-1"))
​
    assert failures == [] # 旧路径:不再直接回调上报
    assert w._failed_recv_reqs.get_nowait() == "req-1" # 新路径:失败请求进入基类队列
    assert w._failed_recv_reqs.empty() # 且只入队一次,没有重复投递
    w.nixl_wrapper.send_notif.assert_not_called() # 未握手 ⇒ 绝不发出真实通知

评论区精华

跨 PR 合并造成的测试契约错配与验证证据 测试

作者 khluu 在评论中给出完整证据链:“Exact validation is running in Buildkite #89027 at literal signed head 4fda4d2c... the PR remains draft pending exact H200 evidence and human line review.” 并说明同一失败在 main #89022 的 H200 与 MI300 两个 V1 KV Connectors lane 同时复现,PR #56104 自己的构建 #89004 因跑在旧 head 上无法捕获该错配。修复只动测试桩状态与断言。

结论:确认根因是 PR #56104(失败改为入队)与 PR #56317(旧行为断言测试)交叉合并导致,修复方向为补齐 _failed_recv_reqs 并按新契约断言;随后该 PR 被合入。 · 已解决

fork PR 的自动评审被禁用,缺少行级人工讨论 other

claude[bot] 评论:“This pull request is from a fork — automated review is disabled. A repository maintainer can comment `@claude review` to run a one-time review.”,仓库内没有任何行级 review 评论。

结论:该变更的正确性主要依赖作者本地 52 项测试与单条 Buildkite lane 的自证证据,评审深度有限,作者本人在评论中也明确要求 human line review。 · unresolved

风险与影响

1) 测试桩与真实基类手工同步的结构性风险:_StubWriterWorker.fresh() 用 object.__new__ 绕过 __init__,字段靠注释分组手工镜像,本次只补了 _failed_recv_reqs;基类后续再新增状态(例如 _invalid_block_ids、_replicated_pcp_done_sending 这类也在桩里单独补过的字段)时,同类 AttributeError 仍会重复出现。
2) 断言收紧带来的脆弱性:failures == [] 与 _failed_recv_reqs.empty() 把“唯一失败上报路径就是队列”写死,如果将来基类在入队之外再加一条审计/回调路径,该测试会先于生产问题误报。
3) 对 main 绿度的强依赖:不合并则 H200 与 MI300 的 V1 KV Connectors lane 持续红(#89022 已证实双平台复现),会长期掩盖其他 NIXL 回归;但反过来说,本次没有生产代码改动,运行期回归风险接近零。
4) 评审证据薄弱:无人工行级 review、自动 review 被禁用,正确性主要依赖作者本地 52 项测试与单条 Buildkite lane 的自述结果。

对最终用户与模型行为无影响:既不触碰推理路径,也不改 NIXL 传输语义。对开发者与 CI 的影响是直接的——恢复 main 上 H200 / MI300 V1 KV Connectors lane 的绿灯,让 1,218 项同命令测试重新具备可判读性。对 KV connector 维护者则是一次契约提醒:推送注册失败的统一上报入口已经变成 _failed_recv_reqs,新增涉及 _do_send_reg_notif 未握手分支的测试必须按队列语义断言。团队层面暴露了“同一文件上多个 PR 交叉合并导致测试契约漂移”的流程缺口,需要更早引入共享 fixture 或真实基类字段来源。

跨 PR 合并契约漂移 测试桩手工镜像基类状态 断言锁定单一失败上报路径 缺少行级人工评审 生产代码零改动(低运行期风险)

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论