Skip to content

feat: add KVCM fallback to master client - #1335

Open
Zhanghyi wants to merge 5 commits into
alibaba:feature/flexlb-kvcmfrom
Zhanghyi:zhy/master-kvcm-fallback-0826
Open

feat: add KVCM fallback to master client#1335
Zhanghyi wants to merge 5 commits into
alibaba:feature/flexlb-kvcmfrom
Zhanghyi:zhy/master-kvcm-fallback-0826

Conversation

@Zhanghyi

Copy link
Copy Markdown

No description provided.

@Zhanghyi
Zhanghyi requested a review from LLLLKKKK as a code owner August 26, 2026 03:20

@LLLLKKKK LLLLKKKK left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Code Review - PR #1335

Status: BLOCKING

Summary: P0/0 · P1/2 · P2/13 · P3/3

Reviewed: commit c054ae8c0c2d · 2026-08-26 12:16 UTC+8

Blocking Issues

P1

  • KVCM leader 解析在 async 路径内同步阻塞调用 VIPServer,停顿整个前端事件循环 @ rtp_llm/server/master_client.py:194
    • 建议:把 bootstrap 目标解析移出事件循环:仿 MasterService 用后台线程周期刷新、resolver 只读缓存快照;或在 _resolve_leader 中用 run_in_executor 包裹并设置明确上限(不超过 master_kvcm_request_timeout_ms 量级)。无论哪种方案都应给底层 VIP HTTP 调用补显式 timeout。补一条断言「解析器不在事件循环线程内被同步调用」的测试。
  • 入口门禁放宽后每请求进入无总预算、无失败负缓存、无熔断的串行 KVCM 探测 @ rtp_llm/server/backend_rpc_server_visitor.py:226
    • 建议:为 KVCM 兜底设置单请求总预算(leader 解析 + query 共享一个 deadline,取 min(TTFT 剩余, 可配置上限)),避免按 target 线性放大;对 leader 解析失败加入短期负缓存/连续失败熔断,冷却窗口内直接跳过 KVCM 走 domain,使双故障退化为改动前的快速回落。补一条「KVCM 全不可用时仍快速域名兜底」的用例,断言不会退化为逐 target 串行等待。若认为熔断超出本 PR 范围,请在 PR description 中记录该降级时延特征与建议的超时/bootstrap 数量上限。

Non-blocking Suggestions

P2

  • 生产路径恒转发原生哈希键,vLLM sha256_cbor 键计算与 master_kvcm_block_size 成为死代码 @ rtp_llm/server/master_client.py:274
    • 建议:明确 KVCM 命名空间的键族约定并只保留一条路径:既然 caller 键优先已是 FlexLB 既有语义,建议删除 calculate_vllm_block_cache_keysmaster_kvcm_block_size(或在 help/注释中写明仅供「调用方不提供 block_cache_keys」的非 RTP-LLM 场景,并说明必须与目标池 block size 一致),避免两套键族并存误导。请补一条传入非空 block_cache_keys 的用例,断言实际下发到 GetHostCacheStateRequest.block_cache_keys 的取值直接来自上游键而非重算结果。
  • KVCM 结果硬编码 PREFILL 角色,非 PREFILL 拓扑下注入无效地址且亲和性静默失效 @ rtp_llm/server/master_client.py:320
    • 建议:按 backend_role_list(或由 MasterClient 构造时显式传入的目标角色)生成 RoleAddr;若当前拓扑不含该角色则直接返回 None 并打一次告警,避免注入无效地址。对 PDFUSION-only 与 DECODE-entrance 两种拓扑各补一条断言最终 role_addrs 角色集合的用例。
  • 硬编码 QT_PREFIX_MATCH,与 FlexLB 按 role/group 解析 query type 的既有契约不一致 @ rtp_llm/server/kvcm_fallback.py:345
    • 建议:将 query type 变为可配置(或从 worker 状态/配置推导),至少新增一个 server arg 并在 help 中写明「必须与目标池 query type 一致」;若短期只支持 QT_PREFIX_MATCH,请在 master_kvcm_fallback_enabled 的 help 与 PR description 中显式声明「仅适用于非 Mamba/混合注意力部署」,并对不匹配情形补一条告警。
  • block_size 默认 0 使开关一启用即在启动期拉挂 backend,且四个 KVCM 字段未接线 @ rtp_llm/server/server_args/master_group_args.py:80
    • 建议:把 KVCM 客户端构造失败降级为「记录 error 日志 + 将 kvcm_fallback_enabled 置回 False」,保证主链路可启动并保留域名兜底;在 _create_kvcm_fallback_client 中与其它必填项一起校验 block_size,所有报错带上 master_kvcm_block_size / MASTER_KVCM_BLOCK_SIZE,help 注明「启用 KVCM 兜底时必填」。四个未接线字段请补齐 server args(lookahead_tokens 需与目标池 block_hash_lookahead_tokens 对齐)或先删除,并对连续 N 次 no_positive_match 输出一次聚合 warning 以便发现错配。
  • HTTP 200 响应体 code=8600 的错误语义与空 master_addr 的 slave 行为被无开关保护地改变 @ rtp_llm/server/master_client.py:519
    • 建议:在 PR description 中显式说明这两项语义修正及影响面(原本报错的请求将改为降级成功;无 master 地址时会尝试 slave),或拆为独立提交以便单独回滚;确认 FlexLB 侧不存在「200 + 8600 表示硬拒绝」的用法,并为 8600 保留一条日志/指标以观测降级量级。同时建议为 master_flexlb_transport_timeout_ms 给出非 0 推荐值,避免 slave 尝试沿用 TTFT 默认超时。
  • KVCM 选路无专属指标且三个新增响应字段无生产消费方,降级在监控面不可见 @ rtp_llm/server/backend_rpc_server_visitor.py:162
    • 建议:在 :162 成功上报时带 route_source 标签(与 :148-152 上报 error_code 标签的既有做法一致),或新增 KVCM 专属 QPS/RT/命中块数指标并把 kvcm_outcomeselected/no_positive_match/no_complete_blocks)作为可聚合维度;对候选解析失败输出限频 warning(含原始 host_ip_port);把「无 master 地址」与「真实连接失败」在日志级别与文案上区分。若短期不消费这三个字段,则删除以免留下无人维护的半成品结构。
  • 降级选路完全不感知负载,热点 prefix 会被确定性地打向同一 worker @ rtp_llm/server/kvcm_fallback.py:162
    • 建议:在 local_blocks 接近的候选间引入随机或轮转 tie-break(如对 top-k 候选随机选择),或引入简单的本地并发计数上限;并在 PR description 中记录该降级模式的容量假设与运维回滚手段(关闭开关)。
  • master_kvcm_use_local 场景下 service_id 中填写的端口被静默丢弃 @ rtp_llm/server/master_client.py:196
    • 建议:use_local 时优先使用条目自带端口,仅在缺省时回落 bootstrap_port;或修正 help 文案为「仅 IP 列表,端口由 master_kvcm_bootstrap_port 决定」并在解析到带端口输入时打 warning。补一条 use_local 的解析用例。
  • 三个新增 py_test 全部标记 manual,且 9 个新 server args 无参数解析测试,本 PR 在 CI 无任何门禁 @ rtp_llm/server/test/BUILD:35
    • 建议:去掉纯内存的 master_client_fallback_testbackend_rpc_server_visitor_fallback_testmanualkvcm_fallback_test 绑定 127.0.0.1:0 无外部依赖通常也可运行,若远端执行器不稳可只对它保留 manual 并在 BUILD 注释原因,同时加入 CI 显式执行清单或新增一个非 manual 的 test_suite 聚合三者。并按现有 test_env_vars_set_to_py_env_configs / test_cmd_args_override_env_vars 的写法补一组 KVCM 用例,至少覆盖两个 str2bool"true"/"false" 解析、整型参数 env 绑定、命令行覆盖 env 的优先级,并断言 MasterConfig.to_string() 含新增字段名。
  • 假输入使用 GenerateInput 不存在的 input_ids 字段,生产装配路径与 token_ids 解包分支零覆盖 @ rtp_llm/server/test/master_client_fallback_test.py:125
    • 建议:_input() 改为提供与生产一致的 token_ids(覆盖二维 (1, N) 与一维两种轻量替身,提供 tolist()/shape),断言 _input_ids_for_kvcm 返回解包后的扁平 int 列表,并保留一条「无 token_ids」用例断言返回 None;同时删除对不存在的 input_ids 字段的探测与 hasattr 分支,直接以 GenerateInput.token_ids 为唯一输入来源。另补一组不注入 fake client 的用例:仅桩掉 vipserver 网络层,断言生成的 KvcmFallbackConfig 与 bootstrap target 字符串(含 IPv6、use_local),并用 assertRaisesRegex 逐一覆盖 service_id 空、instance_id 空、bootstrap_port 越界、block_size=0 四条启动期失败路径。
  • KvcmFallbackClient 的 leader 状态机与全部错误分支零覆盖 @ rtp_llm/server/test/kvcm_fallback_test.py:81
    • 建议:用可配置返回码的 fake servicer 扩展覆盖:非 OK 码断言抛 KvcmFallbackErrorSERVER_NOT_LEADER 断言 _leader 被清空且下次重新 GetClusterInfo;servicer 侧 await asyncio.sleep 超过 request_timeout_ms 断言抛错且 leader 被失效;bootstrap 全不可达且无历史 leader 时断言抛错;补「先成功、后 bootstrap 全挂」用例锁定 stale-leader 保留语义;不设 worker_grpc_port_override 时断言 grpc_port == http_port + 1;补 IPv6 host_ip_port 与各条 __post_init__ 校验(用 assertRaisesRegex);断言 close() 可重复调用。
  • visitor 测试用桩替换了它声称覆盖的生产边界,影子 FlexlbResponse 已与生产漂移 @ rtp_llm/server/test/backend_rpc_server_visitor_fallback_test.py:56
    • 建议:直接导入生产 FlexlbResponse(仅依赖 RoleAddr,可真实导入),或改用 create_autospec(MasterClient) 让签名漂移立即暴露;桩的 get_backend_role_addrs 改为与生产同名同序显式形参并断言收到的关键字。补三条用例:fake client 分别返回 connection_failed=Truefallback=True 时断言 domain_calls == 1 且最终地址来自 domain;master_addr 非空 + 开关开启行为与关闭时一致;batched 输入断言 master_client.calls == 0
  • proto 与 gRPC 生成物三份手工维护,未复用仓内既有 generate_grpc_proto,无漂移校验 @ rtp_llm/server/kvcm_proto/kvcm_meta_service.proto:5
    • 建议:优先复用 generate_grpc_proto 在构建期从单一 .proto 生成,删除手工提交产物,与 rtp_llm/cpp/model_rpc/proto/BUILD 保持一致。若因需与 Java 侧共享而必须签入,请把 .proto 加入某 target 的 srcs,并新增一个非 manual 的一致性测试(用已 pin 的 grpc_tools.protoc 现场编译并比对 descriptor,同时比对两份 .proto 语义),并在 kvcm_proto/__init__.py 写清再生成命令、toolchain 版本与「需改为相对 import」的约束;建议把 Java 侧的字段语义注释(如 host_ip_port 须匹配 WorkerStatus#getIpPort())同步到 Python 副本。

P3

  • 测试在模块导入期全局替换 torch 与整个 rtp_llm 包且不还原 @ rtp_llm/server/test/backend_rpc_server_visitor_fallback_test.py:146
    • 建议:把桩注入移入 setUpModule 或用 unittest.mock.patch.dict(sys.modules, ...) 在退出时自动还原;或通过 BUILD 依赖引入真实模块、只对 MasterClient/HostService 做局部 patch,避免替换 torch 与顶层 rtp_llm 包。顺带移除未使用的 import asyncio
  • 标注 DO NOT EDIT 的生成物被格式化工具重排,且保留未使用的同步阻塞入口 @ rtp_llm/server/kvcm_proto/kvcm_meta_service_pb2_grpc.py:64
    • 建议:若采用构建期生成(见 proto 那条建议)则一并解决;若继续签入手工版本,删除未使用的 MetaService 实验类,仅保留 MetaServiceStubMetaServiceServiceradd_MetaServiceServicer_to_server;并把 rtp_llm/server/kvcm_proto/ 加入 pre-commit exclude 后提交 protoc 原始输出,或在 kvcm_proto/__init__.py 记录「protoc 输出 + 相对 import 改写 + black 格式化」的完整再生流水线。
  • 门禁把 KVCM 开关硬编码进中心路由逻辑,且兜底告警文案语义含混 @ rtp_llm/server/backend_rpc_server_visitor.py:240
    • 建议:在 MasterClient 上提供语义化谓词(如 can_route_without_master_addr()),visitor 调用 bool(master_addr) or self.master_client.can_route_without_master_addr(),使新增兜底机制只需扩展 MasterClient;日志改为先判定原因再输出(如 reason="no_master_route_target" / reason="batched_input"),把三个变量作为结构化上下文,并评估把 batched 这一可预期分支降级为 debug 或采样以减少热路径噪声。

Checklist Findings (23 fail / 48 total)

General Principles Checklist

  • [6.1] Architecture — 兼容性:外部 HTTP/RPC API、持久数据、配置、环境迁移安全 → issue 三个新增 py_test 全部标记 manual,且 9 个新 server args 无参数解析测试,本 PR 在 CI 无任何门禁
    kvcm_fallback_test(:35)、master_client_fallback_test(:45)、backend_rpc_server_visitor_fallback_test(:54)均带 tags = ["manual"],Bazel 语义下会被 //.../:all 排除,全仓也无 test_suite 或脚本按名列出这三个 target(需说明:同包 schedule_meta_testhost_service_test 亦为 manual,仅 vit_proxy_server_test 未带,说明非 manual 可行)。而本 PR 同时变更了默认路径行为(:226 门禁、:491 KVCM 分支、:519 的 8600 语义),恰好守护这些变更的 test_switch_off_preserves_direct_domain_fallback / test_explicit_8600_does_not_query_kvcm 永不在 CI 执行。唯一非 manual 的 `server_args/test/s
  • [6.1] Architecture — 可观测性:日志/指标/超时可操作、非噪声 → issue 门禁把 KVCM 开关硬编码进中心路由逻辑,且兜底告警文案语义含混
    can_attempt_master_route = bool(master_addr) or bool(self.master_client.kvcm_fallback_enabled)(:226)让 visitor 直接依赖 MasterClient 的具体实现细节,而它真正关心的语义只是「无 master 地址时该 client 是否仍能给出路由」;未来新增任何 master 无关的兜底都要回改这处中心门禁,且 visitor 已持有 self.master_config,形成同一开关的两条可达路径。紧随的 warning(:240-246)实际触发条件是 not can_attempt_master_route or input_token_batched,但文案为 "master address: %s, KVCM fallback enabled: %s, or input token batched: %s is not valid",当仅因 batched 输入进入时会输出 "KVCM fallback enabled: False ... is not
  • [6.1] Architecture — 回滚路径:风险行为存在运维回滚手段 → issue 降级选路完全不感知负载,热点 prefix 会被确定性地打向同一 worker
    select_max_local_affinity(-local_blocks, host_ip_port)min,即「最大 local 命中 + 字典序确定性 tie-break」,完全不考虑 worker 负载或队列长度。该路径恰在 FlexLB 整体不可用时才启用,此时全部流量都由它决策;对共享 system prompt 的典型业务,绝大多数请求的最大命中 worker 相同,会被稳定集中到同一台机器,在降级期造成单点过载,反而放大故障。模块 docstring 明确「不重建 FlexLB 负载核算」,但没有任何分散、限流或熔断措施。
  • [6.1] Architecture — 状态不变量:创建/更新/失败/重试/回滚路径有效 → issue KvcmFallbackClient 的 leader 状态机与全部错误分支零覆盖
    _FakeMetaService 的两个 RPC(:37-56)恒返回 kvcm_pb2.OKKvcmFallbackClientTest 只有「命中」与「无正向匹配」两个用例。新模块中风险最高的有状态逻辑因此完全未执行:_resolve_leader 的双重检查缓存与 asyncio.Lockkvcm_fallback.py:247-284)、刷新失败保留 previous_leader(:281-283)、_invalidate_leader(:286)、SERVER_NOT_LEADER 触发失效(:360)、非 OK 状态码抛 KvcmFallbackError、RPC 超时路径(:354)、close()_stub_for 抛错(:230)与 close() 幂等、worker_grpc_port_override 为 None 时的 http_port + 1 推导、_format_target 的 IPv6 分支(:200-209)、__post_init__ 的 7 条校验(:42-60)。K
  • [6.1] Architecture — 错误语义:fail-fast/retry/fallback/silent 行为显式 → issue KvcmFallbackClient 的 leader 状态机与全部错误分支零覆盖
    _FakeMetaService 的两个 RPC(:37-56)恒返回 kvcm_pb2.OKKvcmFallbackClientTest 只有「命中」与「无正向匹配」两个用例。新模块中风险最高的有状态逻辑因此完全未执行:_resolve_leader 的双重检查缓存与 asyncio.Lockkvcm_fallback.py:247-284)、刷新失败保留 previous_leader(:281-283)、_invalidate_leader(:286)、SERVER_NOT_LEADER 触发失效(:360)、非 OK 状态码抛 KvcmFallbackError、RPC 超时路径(:354)、close()_stub_for 抛错(:230)与 close() 幂等、worker_grpc_port_override 为 None 时的 http_port + 1 推导、_format_target 的 IPv6 分支(:200-209)、__post_init__ 的 7 条校验(:42-60)。K
  • [6.1] Quality — Mega-PR 已拆分为独立变更 → issue HTTP 200 响应体 code=8600 的错误语义与空 master_addr 的 slave 行为被无开关保护地改变
    diff 确认 + if code == FALLBACK_ERROR_CODE: return FlexlbResponse.fallback_response() 为本 PR 在 HTTP-200 响应体分支新增(非 200 分支的同名判断为改动前既有)。config/exceptions.py 中不存在 8600(仅 MASTER_NO_AVAILABLE_WORKER = 8400ROUTE_ERROR = 8500),故改动前该情形会经 ExceptionType(8600) 的 ValueError 退化为 MASTER_NO_AVAILABLE_WORKERraise FtRuntimeException,请求直接失败;改动后静默转为 domain routing 成功返回。同一函数还删除了 if not master_addr: return connection_failed_response() 早返回,使「无 master 地址但有 slave 地址」时新增一次 slave HTTP 调度请求。两项都不受 `master_k
  • [6.1] Quality — PR description 说明动机与设计 → issue HTTP 200 响应体 code=8600 的错误语义与空 master_addr 的 slave 行为被无开关保护地改变
    diff 确认 + if code == FALLBACK_ERROR_CODE: return FlexlbResponse.fallback_response() 为本 PR 在 HTTP-200 响应体分支新增(非 200 分支的同名判断为改动前既有)。config/exceptions.py 中不存在 8600(仅 MASTER_NO_AVAILABLE_WORKER = 8400ROUTE_ERROR = 8500),故改动前该情形会经 ExceptionType(8600) 的 ValueError 退化为 MASTER_NO_AVAILABLE_WORKERraise FtRuntimeException,请求直接失败;改动后静默转为 domain routing 成功返回。同一函数还删除了 if not master_addr: return connection_failed_response() 早返回,使「无 master 地址但有 slave 地址」时新增一次 slave HTTP 调度请求。两项都不受 `master_k
  • [6.1] Quality — 无 per-forward 调试日志 / 噪声热路径输出 → issue 门禁把 KVCM 开关硬编码进中心路由逻辑,且兜底告警文案语义含混
    can_attempt_master_route = bool(master_addr) or bool(self.master_client.kvcm_fallback_enabled)(:226)让 visitor 直接依赖 MasterClient 的具体实现细节,而它真正关心的语义只是「无 master 地址时该 client 是否仍能给出路由」;未来新增任何 master 无关的兜底都要回改这处中心门禁,且 visitor 已持有 self.master_config,形成同一开关的两条可达路径。紧随的 warning(:240-246)实际触发条件是 not can_attempt_master_route or input_token_batched,但文案为 "master address: %s, KVCM fallback enabled: %s, or input token batched: %s is not valid",当仅因 batched 输入进入时会输出 "KVCM fallback enabled: False ... is not
  • [6.1] Quality — 逻辑变更未混入无关格式化 → issue 标注 DO NOT EDIT 的生成物被格式化工具重排,且保留未使用的同步阻塞入口
    文件首行声明 DO NOT EDIT!,但内容是 black 风格(双引号、尾随逗号、逐行换行),与 protoc 原始输出不一致;.pre-commit-config.yaml:31 的全局 exclude 仅 ^rtp_llm/ops|^3rdparty,未排除 rtp_llm/server/kvcm_proto/,因此每次重生成都会再被改写,评审者无法与 protoc 输出做字节比对。:64-124MetaService 实验类基于 grpc.experimental.unary_unary(进程级全局 channel 缓存、同步阻塞),全仓无调用点(唯一消费者 kvcm_fallback.py:242MetaServiceStub + grpc.aio),一旦有人在 frontend 的 async 路径误用会阻塞事件循环。
  • [6.1] Software Engineering — DIP:高层策略不依赖非必要具体细节 → issue 门禁把 KVCM 开关硬编码进中心路由逻辑,且兜底告警文案语义含混
    can_attempt_master_route = bool(master_addr) or bool(self.master_client.kvcm_fallback_enabled)(:226)让 visitor 直接依赖 MasterClient 的具体实现细节,而它真正关心的语义只是「无 master 地址时该 client 是否仍能给出路由」;未来新增任何 master 无关的兜底都要回改这处中心门禁,且 visitor 已持有 self.master_config,形成同一开关的两条可达路径。紧随的 warning(:240-246)实际触发条件是 not can_attempt_master_route or input_token_batched,但文案为 "master address: %s, KVCM fallback enabled: %s, or input token batched: %s is not valid",当仅因 batched 输入进入时会输出 "KVCM fallback enabled: False ... is not
  • [6.1] Software Engineering — DRY:重复非平凡逻辑被抽取或显式复用 → issue proto 与 gRPC 生成物三份手工维护,未复用仓内既有 generate_grpc_proto,无漂移校验
    同一 wire 契约在仓内有三份副本:本目录 .proto、手工签入的 _pb2.py/_pb2_grpc.py,以及 flexlb-grpc/src/main/proto/kvcm_meta_service.proto(我已逐项核对当前一致,风险在后续维护),约束仅靠 :5-6 的注释。rtp_llm/server/BUILD 的 glob 只收 kvcm_proto/*.py.proto 未参与任何构建规则。仓内已有统一做法:rtp_llm/cpp/model_rpc/proto/BUILD:2,21//bazel:py_proto.bzlgenerate_grpc_proto 在构建期生成 py 产物,grpcio-tools 依赖已就绪。签入的 gencode 标注 Protobuf 4.25.1,与仓内 pin 的 grpcio-tools==1.57.0 不一致,无法用仓内工具链复现;_pb2_grpc.py:5 的相对 import 还需每次重生成后手工回补。三重人工同步无任何构建期或 CI 校验,字段
  • [6.1] Software Engineering — KISS/YAGNI:无投机性抽象 → issue 标注 DO NOT EDIT 的生成物被格式化工具重排,且保留未使用的同步阻塞入口
    文件首行声明 DO NOT EDIT!,但内容是 black 风格(双引号、尾随逗号、逐行换行),与 protoc 原始输出不一致;.pre-commit-config.yaml:31 的全局 exclude 仅 ^rtp_llm/ops|^3rdparty,未排除 rtp_llm/server/kvcm_proto/,因此每次重生成都会再被改写,评审者无法与 protoc 输出做字节比对。:64-124MetaService 实验类基于 grpc.experimental.unary_unary(进程级全局 channel 缓存、同步阻塞),全仓无调用点(唯一消费者 kvcm_fallback.py:242MetaServiceStub + grpc.aio),一旦有人在 frontend 的 async 路径误用会阻塞事件循环。
  • [6.1] Software Engineering — OCP:本地扩展点优先于修改中心逻辑 → issue 门禁把 KVCM 开关硬编码进中心路由逻辑,且兜底告警文案语义含混
    can_attempt_master_route = bool(master_addr) or bool(self.master_client.kvcm_fallback_enabled)(:226)让 visitor 直接依赖 MasterClient 的具体实现细节,而它真正关心的语义只是「无 master 地址时该 client 是否仍能给出路由」;未来新增任何 master 无关的兜底都要回改这处中心门禁,且 visitor 已持有 self.master_config,形成同一开关的两条可达路径。紧随的 warning(:240-246)实际触发条件是 not can_attempt_master_route or input_token_batched,但文案为 "master address: %s, KVCM fallback enabled: %s, or input token batched: %s is not valid",当仅因 batched 输入进入时会输出 "KVCM fallback enabled: False ... is not
  • [6.1] Tests — 分布式/跨平台变更有对应覆盖 → issue 三个新增 py_test 全部标记 manual,且 9 个新 server args 无参数解析测试,本 PR 在 CI 无任何门禁
    kvcm_fallback_test(:35)、master_client_fallback_test(:45)、backend_rpc_server_visitor_fallback_test(:54)均带 tags = ["manual"],Bazel 语义下会被 //.../:all 排除,全仓也无 test_suite 或脚本按名列出这三个 target(需说明:同包 schedule_meta_testhost_service_test 亦为 manual,仅 vit_proxy_server_test 未带,说明非 manual 可行)。而本 PR 同时变更了默认路径行为(:226 门禁、:491 KVCM 分支、:519 的 8600 语义),恰好守护这些变更的 test_switch_off_preserves_direct_domain_fallback / test_explicit_8600_does_not_query_kvcm 永不在 CI 执行。唯一非 manual 的 `server_args/test/s
  • [6.1] Tests — 新逻辑有聚焦单测 + 相关集成/smoke 测试 → issue proto 与 gRPC 生成物三份手工维护,未复用仓内既有 generate_grpc_proto,无漂移校验
    同一 wire 契约在仓内有三份副本:本目录 .proto、手工签入的 _pb2.py/_pb2_grpc.py,以及 flexlb-grpc/src/main/proto/kvcm_meta_service.proto(我已逐项核对当前一致,风险在后续维护),约束仅靠 :5-6 的注释。rtp_llm/server/BUILD 的 glob 只收 kvcm_proto/*.py.proto 未参与任何构建规则。仓内已有统一做法:rtp_llm/cpp/model_rpc/proto/BUILD:2,21//bazel:py_proto.bzlgenerate_grpc_proto 在构建期生成 py 产物,grpcio-tools 依赖已就绪。签入的 gencode 标注 Protobuf 4.25.1,与仓内 pin 的 grpcio-tools==1.57.0 不一致,无法用仓内工具链复现;_pb2_grpc.py:5 的相对 import 还需每次重生成后手工回补。三重人工同步无任何构建期或 CI 校验,字段
  • [6.1] Tests — 边界 case 覆盖(空、单元素、最大值) → issue visitor 测试用桩替换了它声称覆盖的生产边界,影子 FlexlbResponse 已与生产漂移
    测试把 rtp_llm.server.master_client 整体替换为桩:FlexlbResponse 是本地 dataclass(:55-65),只有 5 个字段,缺本 PR 新增的 route_source/cache_match/kvcm_outcome,形成需人工同步的影子契约;MasterClient=object(:125);_MasterClient.get_backend_role_addrs(self, **_kwargs)(:183)吞掉全部形参名,而生产以 block_cache_keys=/input=/request_id= 关键字调用(backend_rpc_server_visitor.py:141-145),任一形参改名都能让测试全绿而生产 TypeError_visitor() 还用 __new__ 绕过 __init__ 只赋 5 个属性。仅 2 个用例,缺失最关键的不变量:开关开启且 KVCM 返回 connection_failed=True/fallback=True

RTP-LLM Checklist

  • [I] 代码质量 — 同一功能用统一工具函数 → issue proto 与 gRPC 生成物三份手工维护,未复用仓内既有 generate_grpc_proto,无漂移校验
    同一 wire 契约在仓内有三份副本:本目录 .proto、手工签入的 _pb2.py/_pb2_grpc.py,以及 flexlb-grpc/src/main/proto/kvcm_meta_service.proto(我已逐项核对当前一致,风险在后续维护),约束仅靠 :5-6 的注释。rtp_llm/server/BUILD 的 glob 只收 kvcm_proto/*.py.proto 未参与任何构建规则。仓内已有统一做法:rtp_llm/cpp/model_rpc/proto/BUILD:2,21//bazel:py_proto.bzlgenerate_grpc_proto 在构建期生成 py 产物,grpcio-tools 依赖已就绪。签入的 gencode 标注 Protobuf 4.25.1,与仓内 pin 的 grpcio-tools==1.57.0 不一致,无法用仓内工具链复现;_pb2_grpc.py:5 的相对 import 还需每次重生成后手工回补。三重人工同步无任何构建期或 CI 校验,字段

Python Static-First Checklist

  • [P.A] 静态结构与类型纪律 — 禁止 getattr/setattr literal 访问 → issue 假输入使用 GenerateInput 不存在的 input_ids 字段,生产装配路径与 token_ids 解包分支零覆盖
    _input() 构造 SimpleNamespace(..., input_ids=list(range(12)), ...),但真实 GenerateInpututils/base_model_datatypes.py:53-64)只有 token_ids: torch.Tensor没有 input_ids(该字段属于 GenerateOutput)。_input_ids_for_kvcmmaster_client.py:244-262)先 getattr(input, "input_ids", None),故 6 个用例命中的是生产恒为 None 的分支;生产实际走 token_ids,需 .tolist()、二维 [[...]] 解包、int() 强转三步,全部无测试。同时 6 个用例全部注入 kvcm_fallback_client,使 _create_kvcm_fallback_client(约 60 行,含三条启动期 raiseVipServerWrapper 构造、IPv6 方括号拼
  • [P.A] 静态结构与类型纪律 — 禁止 hasattr 做控制流分支 → issue 假输入使用 GenerateInput 不存在的 input_ids 字段,生产装配路径与 token_ids 解包分支零覆盖
    _input() 构造 SimpleNamespace(..., input_ids=list(range(12)), ...),但真实 GenerateInpututils/base_model_datatypes.py:53-64)只有 token_ids: torch.Tensor没有 input_ids(该字段属于 GenerateOutput)。_input_ids_for_kvcmmaster_client.py:244-262)先 getattr(input, "input_ids", None),故 6 个用例命中的是生产恒为 None 的分支;生产实际走 token_ids,需 .tolist()、二维 [[...]] 解包、int() 强转三步,全部无测试。同时 6 个用例全部注入 kvcm_fallback_client,使 _create_kvcm_fallback_client(约 60 行,含三条启动期 raiseVipServerWrapper 构造、IPv6 方括号拼
  • [P.C] 并发与异步 — async def 中禁止 blocking 调用 → issue 标注 DO NOT EDIT 的生成物被格式化工具重排,且保留未使用的同步阻塞入口
    文件首行声明 DO NOT EDIT!,但内容是 black 风格(双引号、尾随逗号、逐行换行),与 protoc 原始输出不一致;.pre-commit-config.yaml:31 的全局 exclude 仅 ^rtp_llm/ops|^3rdparty,未排除 rtp_llm/server/kvcm_proto/,因此每次重生成都会再被改写,评审者无法与 protoc 输出做字节比对。:64-124MetaService 实验类基于 grpc.experimental.unary_unary(进程级全局 channel 缓存、同步阻塞),全仓无调用点(唯一消费者 kvcm_fallback.py:242MetaServiceStub + grpc.aio),一旦有人在 frontend 的 async 路径误用会阻塞事件循环。
  • [P.C] 并发与异步 — async 代码用 asyncio.Lock 非 threading.Lock → issue KVCM leader 解析在 async 路径内同步阻塞调用 VIPServer,停顿整个前端事件循环
    resolve_bootstrap_targets 调用 self._kvcm_vip.get_hosts(refresh=True);非 use_local 时经 host_service.py:120get_host_list_by_domain_nowhost_reactor.refresh_domain_srv_lstvipserver_proxy.req_api,后者先 srv_update_lock.acquire()vipserver_proxy.py:30 的类级 threading.Lock,与后台刷新线程共享),再在 :106-108 执行未设 timeoutrequests.get。该闭包被 kvcm_fallback.py:258async def _resolve_leader 内、且持有 asyncio.Lock 时直接同步调用。master_kvcm_request_timeout_ms 只约束 gRPC deadline,无法约束这段阻塞。leader 过
  • [P.F] 语言陷阱 — 禁止模块级 import 副作用 → issue 测试在模块导入期全局替换 torch 与整个 rtp_llm 包且不还原
    _load_visitor_module() 在模块顶层被直接调用(:146),把 torch(:92)与 rtp_llmrtp_llm.serverrtp_llm.configrtp_llm.cpprtp_llm.metricsrtp_llm.utils(:21-32)连同 rtp_llm.opsrtp_llm.server.host_servicertp_llm.server.master_client 写入全局 sys.modules 且退出不恢复;master_client_fallback_test.py:52-63 同样把 VipServerWrapper 桩为 object。Bazel 下每 target 独立进程故 CI 无跨目标影响,但该文件名按字母序排在两个兄弟测试之前,本地以 pytest/unittest discover 在同一进程批量收集时,后续导入真实模块的测试会拿到桩对象,产生顺序相关失败或静默测桩。另 :1import asyncio 全文未使用,触发 flake8
  • [P.G] 测试规范 — mock/fake/stub 不得替代本次声称覆盖的生产边界 → issue visitor 测试用桩替换了它声称覆盖的生产边界,影子 FlexlbResponse 已与生产漂移
    测试把 rtp_llm.server.master_client 整体替换为桩:FlexlbResponse 是本地 dataclass(:55-65),只有 5 个字段,缺本 PR 新增的 route_source/cache_match/kvcm_outcome,形成需人工同步的影子契约;MasterClient=object(:125);_MasterClient.get_backend_role_addrs(self, **_kwargs)(:183)吞掉全部形参名,而生产以 block_cache_keys=/input=/request_id= 关键字调用(backend_rpc_server_visitor.py:141-145),任一形参改名都能让测试全绿而生产 TypeError_visitor() 还用 __new__ 绕过 __init__ 只赋 5 个属性。仅 2 个用例,缺失最关键的不变量:开关开启且 KVCM 返回 connection_failed=True/fallback=True

Strengths

  • 兜底触发边界收得很准:仅 resp.connection_failed(可用性故障)才查询 KVCM(master_client.py:491),显式 8600 与业务/准入错误(8400)保持原语义,并有 test_flexlb_business_error_does_not_query_kvcm / test_explicit_8600_does_not_query_kvcm 断言 kvcm_client.calls == 0
  • 全部新开关默认保持旧行为(master_kvcm_fallback_enabled=Falsemaster_flexlb_transport_timeout_ms=0 显式保留 TTFT 语义),kvcm_fallback 为惰性 import,关闭时完全不加载新 protobuf/gRPC 栈,回滚只需关开关。
  • KVCM 失败不放大故障:_try_kvcm_fallback 捕获异常返回 None,route_ipsallow_domain_fallback 保证最终仍回落 domain routing,并有 test_kvcm_failure_preserves_final_caller_fallback 覆盖。
  • 跨语言 wire 契约逐项核对无漂移:kvcm_proto/kvcm_meta_service.protoflexlb-grpc/src/main/proto/kvcm_meta_service.proto 的 package(kv_cache_manager.proto.meta)、字段号(block_cache_keys=4p2p_host_count=6meta_rpc_port=3)、枚举值(IO_ERROR=20ERROR_MAX=65535QT_PREFIX_MATCH_WITH_MAMBA=4)与两个 RPC 完全一致;pb2_grpc 两条 RPC 全路径与 servicer 注册名一致,Stub/Servicer 两侧序列化器方向未写反。
  • 哈希实现有跨语言 golden 锚点:kvcm_fallback_test.py:622164874634404590027 与 Java BlockCacheKeyCalculatorTest.java:60 完全一致,并覆盖「丢弃不完整尾块」与 lookahead 分支。
  • 测试不是纯 mock:kvcm_fallback_test 起真实 grpc.aio.server() 绑定回环端口做端到端 round-trip,并断言 instance_idQT_PREFIX_MATCHblock_cache_keys 三项请求字段与 channel 复用。
  • gRPC 客户端细节扎实:leader 缓存 + 双重检查锁、刷新失败保留上一个可用 leader、SERVER_NOT_LEADER/RPC 失败时失效 leader、channel 复用、IPv6 authority 正确加方括号、(-local_blocks, host_ip_port) 确定性 tie-break。
  • 配置与打包接线闭环:MasterConfig 新增 10 个字段与 master_group_args.py 9 个参数的默认值/env/bind_to 逐项一致,to_string() 同步补齐;kvcm_proto/*.py 已纳入 //rtp_llm/server:server 的 srcs glob,不存在「本地可 import、wheel 缺文件」缺口。

Comment thread rtp_llm/server/master_client.py Outdated

master_route_result: Optional[FlexlbResponse] = None
if not role_addrs_specified and master_addr and not input_token_batched:
can_attempt_master_route = bool(master_addr) or bool(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 入口门禁放宽后每请求进入无总预算、无失败负缓存、无熔断的串行 KVCM 探测

改动前 master_addr 为空即直接 domain 路由,零额外开销(diff 中 - if not role_addrs_specified and master_addr and not input_token_batched:)。改后只要 kvcm_fallback_enabled 为真(冷启动、master 域名未配、FlexLB 全不健康时 master_addr 恒空),每请求都会进入 get_master_route_addrs:先做全量 token 哈希,再由 _resolve_leaderkvcm_fallback.py:253-284)在 asyncio.Lock串行遍历全部 bootstrap target,每个各按 request_timeout_ms(默认 100ms)超时后 continue;全部失败且无 previous_leader 时抛错且不更新 _leader_refreshed_at,即无失败负缓存,下一请求完整重跑,高 QPS 下还会在锁上排队串行化。N 个 boo...

建议: 为 KVCM 兜底设置单请求总预算(leader 解析 + query 共享一个 deadline,取 min(TTFT 剩余, 可配置上限)),避免按 target 线性放大;对 leader 解析失败加入短期负缓存/连续失败熔断,冷却窗口内直接跳过 KVCM 走 domain,使双故障退化为改动前的快速回落。补一条「KVCM 全不可用时仍快速域名兜底」的用例,断言不会退化为逐 target 串行等待。若认为熔断超出本 PR 范围,请在 PR description 中记录该降级时延特征与建议的超时/bootstrap 数量上限。

@LLLLKKKK LLLLKKKK left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Code Review - PR #1335 (non-blocking suggestions)

16 条 P2/P3 建议,不阻塞合并。阻塞判定与完整摘要见上一条 review。

return None

try:
result = await self._kvcm_fallback_client.query_and_select(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 生产路径恒转发原生哈希键,vLLM sha256_cbor 键计算与 master_kvcm_block_size 成为死代码

_try_kvcm_fallback 把上游 block_cache_keys 原样传入 query_and_select;该键来自 backend_rpc_server_visitor.py:138get_block_cache_keys,底层为 C++ hashInt64Vectorcpp/utils/HashUtil.h:25,Jenkins 滚动哈希,非 SHA256)。复核后 KVCM 并非只认 vLLM 键:RequestBlockHashService.java:49-53 明确 caller 提供的 keys 优先,CacheMatchQueryOrchestrator.java:84 把该批 caller 键原样送入 KvcmGrpcClient.findMatchingEnginesVllmBlockHashStrategy 仅在调用方只给 input_ids 时兜底,故本 PR 转发语义与 Java 侧一致。残留风险是 kvcm_fallback.py:326 的 vLLM 分支与 `master...

建议: 明确 KVCM 命名空间的键族约定并只保留一条路径:既然 caller 键优先已是 FlexLB 既有语义,建议删除 calculate_vllm_block_cache_keysmaster_kvcm_block_size(或在 help/注释中写明仅供「调用方不提供 block_cache_keys」的非 RTP-LLM 场景,并说明必须与目标池 block size 一致),避免两套键族并存误导。请补一条传入非空 block_cache_keys 的用例,断言实际下发到 GetHostCacheStateRequest.block_cache_keys 的取值直接来自上游键而非重算结果。

return FlexlbResponse.ok(
[
RoleAddr(
role=RoleType.PREFILL,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] KVCM 结果硬编码 PREFILL 角色,非 PREFILL 拓扑下注入无效地址且亲和性静默失效

_try_kvcm_fallback 恒构造 RoleAddr(role=RoleType.PREFILL, ...),与部署角色无关。但 get_backend_role_listbackend_rpc_server_visitor.py:100-123)可产出 [DECODE][PDFUSION][PREFILL, DECODE]。以 PDFUSION-only 前端为例:KVCM 成功后 specified_roles={PREFILL}need_domain_routing 为 True;又因成功时 get_master_route_addrs 返回 None 使 allow_domain_fallback 为 True,get_domain_route_addrsmissing_roles 再补入 PDFUSION。最终 role_addrs[PREFILL(KVCM), PDFUSION(VIP)]:真实流量走 VIP 选出的 PDFUSION,缓存亲和完全失效,同时多出一条该拓扑不存...

建议:backend_role_list(或由 MasterClient 构造时显式传入的目标角色)生成 RoleAddr;若当前拓扑不含该角色则直接返回 None 并打一次告警,避免注入无效地址。对 PDFUSION-only 与 DECODE-entrance 两种拓扑各补一条断言最终 role_addrs 角色集合的用例。

request = kvcm_pb2.GetHostCacheStateRequest(
trace_id=request_id,
instance_id=self.config.instance_id,
query_type=kvcm_pb2.QT_PREFIX_MATCH,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 硬编码 QT_PREFIX_MATCH,与 FlexLB 按 role/group 解析 query type 的既有契约不一致

query_and_select 固定 query_type=kvcm_pb2.QT_PREFIX_MATCH。同一 KVCM 契约的既有消费方在 Java 侧是按角色/分组从 worker 元数据解析的:KvcmWorkerMetadataResolver.resolveQueryType(roleType, group)WITH_MAMBA 返回 QT_PREFIX_MATCH_WITH_MAMBA(proto 中 QueryType=4),且 KvcmGrpcClient:152-157 在解析不到时跳过查询而非用默认值。对混合注意力/Mamba 类部署,Python 兜底会以错误的 query type 查询同一 namespace,返回结果与 FlexLB 正常路径不一致,且失败表现为静默零命中,无任何日志可区分。

建议: 将 query type 变为可配置(或从 worker 状态/配置推导),至少新增一个 server arg 并在 help 中写明「必须与目标池 query type 一致」;若短期只支持 QT_PREFIX_MATCH,请在 master_kvcm_fallback_enabled 的 help 与 PR description 中显式声明「仅适用于非 Mamba/混合注意力部署」,并对不匹配情形补一条告警。

)

master_group.add_argument(
"--master_kvcm_block_size",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] block_size 默认 0 使开关一启用即在启动期拉挂 backend,且四个 KVCM 字段未接线

master_kvcm_block_size 默认 0(master_group_args.py:84py_config_modules.py:394),参数层无校验;_create_kvcm_fallback_client:176-185 只校验 service_id/instance_id/bootstrap_port,block_size 的校验落到 KvcmFallbackConfig.__post_init__:45ValueError("KVCM block_size must be positive")(不含参数名)。该构造在 MasterClient.__init__:148 同步执行,而 MasterClientbackend_rpc_server_visitor.py:78 创建,异常直接终止 backend 启动——按 help 文案只配 enabled + service_id + instance_id 即触发。另 lookahead_tokensminimum_local_blocks、`le...

建议: 把 KVCM 客户端构造失败降级为「记录 error 日志 + 将 kvcm_fallback_enabled 置回 False」,保证主链路可启动并保留域名兜底;在 _create_kvcm_fallback_client 中与其它必填项一起校验 block_size,所有报错带上 master_kvcm_block_size / MASTER_KVCM_BLOCK_SIZE,help 注明「启用 KVCM 兜底时必填」。四个未接线字段请补齐 server args(lookahead_tokens 需与目标池 block_hash_lookahead_tokens 对齐)或先删除,并对连续 N 次 no_positive_match 输出一次聚合 warning 以便发现错配。

code = int(raw_code)
except (TypeError, ValueError):
code = int(ExceptionType.MASTER_NO_AVAILABLE_WORKER)
if code == FALLBACK_ERROR_CODE:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] HTTP 200 响应体 code=8600 的错误语义与空 master_addr 的 slave 行为被无开关保护地改变

diff 确认 + if code == FALLBACK_ERROR_CODE: return FlexlbResponse.fallback_response() 为本 PR 在 HTTP-200 响应体分支新增(非 200 分支的同名判断为改动前既有)。config/exceptions.py 中不存在 8600(仅 MASTER_NO_AVAILABLE_WORKER = 8400ROUTE_ERROR = 8500),故改动前该情形会经 ExceptionType(8600) 的 ValueError 退化为 MASTER_NO_AVAILABLE_WORKERraise FtRuntimeException,请求直接失败;改动后静默转为 domain routing 成功返回。同一函数还删除了 if not master_addr: return connection_failed_response() 早返回,使「无 master 地址但有 slave 地址」时新增一次 slave HTTP 调度请求。两项都不受 `maste...

建议: 在 PR description 中显式说明这两项语义修正及影响面(原本报错的请求将改为降级成功;无 master 地址时会尝试 slave),或拆为独立提交以便单独回滚;确认 FlexLB 侧不存在「200 + 8600 表示硬拒绝」的用法,并为 8600 保留一条日志/指标以观测降级量级。同时建议为 master_flexlb_transport_timeout_ms 给出非 0 推荐值,避免 slave 尝试沿用 TTFT 默认超时。

Checklist: [6.1] Mega-PR 已拆分为独立变更;[6.1] PR description 说明动机与设计

grpc_port: int

@dataclass
class FlexlbResponse:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] visitor 测试用桩替换了它声称覆盖的生产边界,影子 FlexlbResponse 已与生产漂移

测试把 rtp_llm.server.master_client 整体替换为桩:FlexlbResponse 是本地 dataclass(:55-65),只有 5 个字段,缺本 PR 新增的 route_source/cache_match/kvcm_outcome,形成需人工同步的影子契约;MasterClient=object(:125);_MasterClient.get_backend_role_addrs(self, **_kwargs)(:183)吞掉全部形参名,而生产以 block_cache_keys=/input=/request_id= 关键字调用(backend_rpc_server_visitor.py:141-145),任一形参改名都能让测试全绿而生产 TypeError_visitor() 还用 __new__ 绕过 __init__ 只赋 5 个属性。仅 2 个用例,缺失最关键的不变量:开关开启且 KVCM 返回 connection_failed=True/`fallback=Tru...

建议: 直接导入生产 FlexlbResponse(仅依赖 RoleAddr,可真实导入),或改用 create_autospec(MasterClient) 让签名漂移立即暴露;桩的 get_backend_role_addrs 改为与生产同名同序显式形参并断言收到的关键字。补三条用例:fake client 分别返回 connection_failed=Truefallback=True 时断言 domain_calls == 1 且最终地址来自 domain;master_addr 非空 + 开关开启行为与关闭时一致;batched 输入断言 master_client.calls == 0

Checklist: [6.1] 边界 case 覆盖(空、单元素、最大值);[P.G] mock/fake/stub 不得替代本次声称覆盖的生产边界


package kv_cache_manager.proto.meta;

// Wire-compatible subset of KVCacheManager's meta_service.proto. Keep this

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] proto 与 gRPC 生成物三份手工维护,未复用仓内既有 generate_grpc_proto,无漂移校验

同一 wire 契约在仓内有三份副本:本目录 .proto、手工签入的 _pb2.py/_pb2_grpc.py,以及 flexlb-grpc/src/main/proto/kvcm_meta_service.proto(我已逐项核对当前一致,风险在后续维护),约束仅靠 :5-6 的注释。rtp_llm/server/BUILD 的 glob 只收 kvcm_proto/*.py.proto 未参与任何构建规则。仓内已有统一做法:rtp_llm/cpp/model_rpc/proto/BUILD:2,21//bazel:py_proto.bzlgenerate_grpc_proto 在构建期生成 py 产物,grpcio-tools 依赖已就绪。签入的 gencode 标注 Protobuf 4.25.1,与仓内 pin 的 grpcio-tools==1.57.0 不一致,无法用仓内工具链复现;_pb2_grpc.py:5 的相对 import 还需每次重生成后手工回补。三重人工同步无任何构建期或 CI 校验...

建议: 优先复用 generate_grpc_proto 在构建期从单一 .proto 生成,删除手工提交产物,与 rtp_llm/cpp/model_rpc/proto/BUILD 保持一致。若因需与 Java 侧共享而必须签入,请把 .proto 加入某 target 的 srcs,并新增一个非 manual 的一致性测试(用已 pin 的 grpc_tools.protoc 现场编译并比对 descriptor,同时比对两份 .proto 语义),并在 kvcm_proto/__init__.py 写清再生成命令、toolchain 版本与「需改为相对 import」的约束;建议把 Java 侧的字段语义注释(如 host_ip_port 须匹配 WorkerStatus#getIpPort())同步到 Python 副本。

Checklist: [6.1] DRY:重复非平凡逻辑被抽取或显式复用;[6.1] 新逻辑有聚焦单测 + 相关集成/smoke 测试;[I] 同一功能用统一工具函数

return module, RoleAddr, RoleType, FlexlbResponse


visitor_module, RoleAddr, RoleType, FlexlbResponse = _load_visitor_module()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P3] 测试在模块导入期全局替换 torch 与整个 rtp_llm 包且不还原

_load_visitor_module() 在模块顶层被直接调用(:146),把 torch(:92)与 rtp_llmrtp_llm.serverrtp_llm.configrtp_llm.cpprtp_llm.metricsrtp_llm.utils(:21-32)连同 rtp_llm.opsrtp_llm.server.host_servicertp_llm.server.master_client 写入全局 sys.modules 且退出不恢复;master_client_fallback_test.py:52-63 同样把 VipServerWrapper 桩为 object。Bazel 下每 target 独立进程故 CI 无跨目标影响,但该文件名按字母序排在两个兄弟测试之前,本地以 pytest/unittest discover 在同一进程批量收集时,后续导入真实模块的测试会拿到桩对象,产生顺序相关失败或静默测桩。另 :1import asyncio 全文未使用,触发 fla...

建议: 把桩注入移入 setUpModule 或用 unittest.mock.patch.dict(sys.modules, ...) 在退出时自动还原;或通过 BUILD 依赖引入真实模块、只对 MasterClient/HostService 做局部 patch,避免替换 torch 与顶层 rtp_llm 包。顺带移除未使用的 import asyncio

Checklist: [P.F] 禁止模块级 import 副作用

server.add_generic_rpc_handlers((generic_handler,))


# This class is part of an EXPERIMENTAL API.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P3] 标注 DO NOT EDIT 的生成物被格式化工具重排,且保留未使用的同步阻塞入口

文件首行声明 DO NOT EDIT!,但内容是 black 风格(双引号、尾随逗号、逐行换行),与 protoc 原始输出不一致;.pre-commit-config.yaml:31 的全局 exclude 仅 ^rtp_llm/ops|^3rdparty,未排除 rtp_llm/server/kvcm_proto/,因此每次重生成都会再被改写,评审者无法与 protoc 输出做字节比对。:64-124MetaService 实验类基于 grpc.experimental.unary_unary(进程级全局 channel 缓存、同步阻塞),全仓无调用点(唯一消费者 kvcm_fallback.py:242MetaServiceStub + grpc.aio),一旦有人在 frontend 的 async 路径误用会阻塞事件循环。

建议: 若采用构建期生成(见 proto 那条建议)则一并解决;若继续签入手工版本,删除未使用的 MetaService 实验类,仅保留 MetaServiceStubMetaServiceServiceradd_MetaServiceServicer_to_server;并把 rtp_llm/server/kvcm_proto/ 加入 pre-commit exclude 后提交 protoc 原始输出,或在 kvcm_proto/__init__.py 记录「protoc 输出 + 相对 import 改写 + black 格式化」的完整再生流水线。

Checklist: [6.1] 逻辑变更未混入无关格式化;[6.1] KISS/YAGNI:无投机性抽象;[P.C] async def 中禁止 blocking 调用

GaugeMetrics.MASTER_ROUTE_RT_METRIC, master_route_timer.cost_ms()
)
elif not role_addrs_specified:
route_logger.warning(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P3] 门禁把 KVCM 开关硬编码进中心路由逻辑,且兜底告警文案语义含混

can_attempt_master_route = bool(master_addr) or bool(self.master_client.kvcm_fallback_enabled)(:226)让 visitor 直接依赖 MasterClient 的具体实现细节,而它真正关心的语义只是「无 master 地址时该 client 是否仍能给出路由」;未来新增任何 master 无关的兜底都要回改这处中心门禁,且 visitor 已持有 self.master_config,形成同一开关的两条可达路径。紧随的 warning(:240-246)实际触发条件是 not can_attempt_master_route or input_token_batched,但文案为 "master address: %s, KVCM fallback enabled: %s, or input token batched: %s is not valid",当仅因 batched 输入进入时会输出 "KVCM fallback enabled: False ... is ...

建议:MasterClient 上提供语义化谓词(如 can_route_without_master_addr()),visitor 调用 bool(master_addr) or self.master_client.can_route_without_master_addr(),使新增兜底机制只需扩展 MasterClient;日志改为先判定原因再输出(如 reason="no_master_route_target" / reason="batched_input"),把三个变量作为结构化上下文,并评估把 batched 这一可预期分支降级为 debug 或采样以减少热路径噪声。

Checklist: [6.1] 可观测性:日志/指标/超时可操作、非噪声;[6.1] 无 per-forward 调试日志 / 噪声热路径输出;[6.1] DIP:高层策略不依赖非必要具体细节;[6.1] OCP:本地扩展点优先于修改中心逻辑

@LLLLKKKK LLLLKKKK left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Code Review - PR #1335

Status: BLOCKING

Summary: P0/1 · P1/5 · P2/13 · P3/4

Reviewed: commit 88ef3abba516 · 2026-08-26 19:35 UTC+8

Blocking Issues

P0

  • 无 package 的 worker_status proto 与引擎 model_rpc_service_pb2 符号重名,开关一开即 duplicate symbol 崩溃 @ rtp_llm/server/worker_status_proto/worker_status_service.proto:6
    • 建议:优先直接复用 rtp_llm.cpp.model_rpc.proto.model_rpc_service_pb2{,_grpc}:其 RpcServiceStub.GetWorkerStatus 方法路径同为 /RpcService/GetWorkerStatus、请求/响应类型逐字段一致,且由 model_rpc/proto/BUILDgenerate_grpc_proto 构建期生成、已随前端打包;utils/grpc_client_wrapper.py:12multimodal/vit_proxy_start_server.py:29 已有同类先例。删除 worker_status_proto/ 可同时消解本条与下一条打包缺失。若确需与引擎解耦的独立副本,注意给 proto 加 package 会改变方法全名破坏 wire 兼容,只能把描述符注册到私有 descriptor_pool.DescriptorPool() 并用 channel.unary_unary("/RpcService/GetWorkerStatus", ...) 显式固定路径。无论选哪条路,都请补一条非 manual 的回归测试:同进程内先 import 引擎 pb2 再 import kvcm_fallback,把该不变量固化下来。

P1

  • server py_library 的 srcs glob 漏掉 worker_status_proto,wheel 与 runfiles 均缺该模块 @ rtp_llm/server/BUILD:5
    • 建议:若采纳上一条建议删除该目录,本条自动消解;否则在 glob 中补 "worker_status_proto/*.py",或改为 glob(["*.py", "server_args/*.py", "*_proto/*.py"]) 以避免后续新增 proto 子目录时同类遗漏复现。补齐后请用一个非 manual 的 py_test 真实 import rtp_llm.server.kvcm_fallback,让打包完整性有构建期约束而非依赖人工记忆,并在沙箱/远程执行下验证一次(本地 local spawn 可能因源码树可见而假通过)。
  • master_kvcm_block_size 默认 0 与「必须为正」校验直接冲突,开关打开即启动期拉挂 @ rtp_llm/config/py_config_modules.py:394
    • 建议:二选一并保持一致:要么给一个可用的正默认值(并在 help 中说明与引擎 seq_size_per_block 的关系),要么在 _create_kvcm_fallback_client 中补一条与 service_id 同风格、点名参数的前置校验,例如 if block_size <= 0: raise ValueError("master_kvcm_block_size must be positive when KVCM fallback is enabled")。请同时补一条断言「开关打开 + 默认 block_size → ValueError」的单测——这是开关打开后的首个失败点,目前零覆盖。
  • KVCM leader 解析在事件循环内持锁执行无 timeout 的同步 vipserver 请求,且失败无负缓存 @ rtp_llm/server/kvcm_fallback.py:569
    • 建议:把 self._bootstrap_resolver() 移出事件循环(asyncio.to_thread / run_in_executor),或改为消费 get_hosts(refresh=False) 的缓存快照配合独立后台刷新;同时建议给 vipserver_proxy.pyrequests.get 补 timeout(该文件不在本 PR 范围内,可另行提单)。为 _resolve_leader 增加不超过兜底预算的整体 deadline,并用 asyncio.gather 并发探测取首个成功者;对「leader 解析失败」加短周期负缓存。结合入口门禁放宽,还建议为「连续 N 次 KVCM 无结果」加短时间窗熔断,避免 FlexLB 与 KVCM 双故障时把 KVCM 超时叠加到每个请求的 TTFT 上(改动前该场景是零成本直接 domain 路由)。
  • 块→token 折算 stride 与实际发出键的 stride 无一致性校验,vLLM sha256 路径在生产不可达 @ rtp_llm/server/kvcm_fallback.py:370
    • 建议:把键与 stride 收敛为单一来源:删除 sha256 分支与 master_kvcm_block_size,折算直接用 seq_size_per_block;或不复用 FlexLB 的 block_cache_keys、只传 input_idsquery_and_select 统一按 master_kvcm_block_size 生成。若仍需手工配置,请在构造期断言 master_kvcm_block_size == seq_size_per_block——注意 FlexLB KvcmWorkerMetadataResolver.resolveNamespace:49-57 会把 namespace 拼成 <name>_<blockSize>,而 master_kvcm_instance_id 是静态字符串,同一个 stride 目前需要运维在两处手工对齐。折算所需的 block_size / lookahead_tokens 建议直接取协议已返回的 WorkerStatusPB.block_size(字段 18)/ block_hash_lookahead_tokens(字段 21),而 worker_load_snapshot(:348-360) 当前完全不读。另请一并对齐 query type:kvcm_fallback.py:635 硬编码 QT_PREFIX_MATCH,而 FlexLB KvcmGrpcClient:151-157 会按 role/group 解析并在不可用时跳过查询,请确认该简化在所有部署形态下成立。最后补一个端到端用例,断言实际发往 KVCM 的 keys 与选定折算方式一致。
  • 三个新增 py_test 全部 manual 且用桩替换了本该守住三个阻塞缺陷的生产边界 @ rtp_llm/server/test/BUILD:35
    • 建议:补一条不 stub master_client/model_rpc_client 的集成用例:同进程内先 import rtp_llm.cpp.model_rpc.model_rpc_client,再以真实 MasterConfig(master_kvcm_fallback_enabled=True) 构造 MasterClient 并断言构造成功——一条用例即可同时守住三个阻塞点。这三个测试是纯 Python、无 GPU/权重依赖、只绑 127.0.0.1:0,属可常态执行的 hermetic 单测,建议去掉 manual;需说明同目录 schedule_meta_test/host_service_test 也是 manual(vit_proxy_server_test 不是),但正是该惯例让这个目录长期缺少自动覆盖,本次不宜继续沿用。若内部 CI 确实通过显式 target 列表执行,请在 PR 描述中给出注册位置以便确认。

Non-blocking Suggestions

P2

  • 新开关被 host_service.service_available 前置门控静默旁路 @ rtp_llm/server/backend_rpc_server_visitor.py:324
    • 建议:在 BackendRPCServerVisitor.__init__ 中检测 master_client.kvcm_fallback_enabled and not host_service.service_available 并打一条明确 warning(说明 KVCM fallback 因未配置 service domain 而不会生效),或把该开关纳入 service_available 的判定。至少需要一条可观测提示,避免线上开了开关却以为已生效。
  • KVCM 选中 worker 的 role 被丢弃并硬编码为 PREFILL,与打分层的 PDFUSION 支持不一致 @ rtp_llm/server/master_client.py:409
    • 建议:在 KvcmFallbackResult 中带上选中 snapshot 的 role 并在 _try_kvcm_fallback 中映射为对应 RoleType;或在进入 KVCM 兜底前校验 self.backend_role_list 是否包含 RoleType.PREFILL,不含则直接走 domain 路由。若本次只支持 PREFILL,则从 select_cache_affinity_first 的过滤条件中去掉 PDFUSION,使两处语义一致。并补一条 backend_role_list=[PDFUSION] 的用例,断言不会混入多余的 PREFILL 地址。
  • 10 个新增 MasterConfig 字段没有任何 CLI/env 绑定,线上实际不可配置 @ rtp_llm/config/py_config_modules.py:397
    • 建议:为这 10 个字段补齐 add_argumentenv_name + bind_to + type),尤其直接影响线上时延与选点质量的 master_sync_request_timeout_msmaster_kvcm_worker_status_concurrencymaster_kvcm_candidate_pool_size——这是注释里「与同机 FlexLB 对齐」的前置条件,也是选路偏差时唯一的免发版调参与回滚手段。若部分字段确定不暴露,则从 MasterConfig 移除、改为 kvcm_fallback.py 内的模块级常量,避免「看起来可配置、实际写死」。同时把 master_client.pygetattr 改为直接属性访问,让 MasterConfig.__init__ 成为默认值唯一来源,并补一个遍历 MasterConfig.__dict__ 断言每字段都有 binding 的测试防止再漏。另注意 master_sync_request_timeout_msKvcmFallbackConfig.worker_status_timeout_ms 的命名不对应,建议统一。
  • local_fallback_addr 在生产链路从不传入,本机保底候选不可达 @ rtp_llm/server/backend_rpc_server_visitor.py:141
    • 建议:二选一并在测试中体现:若该能力属本次交付范围,请在 get_master_route_addrs 中把本机(或 domain 解析出的)worker 作为 local_fallback_addr 传下去,并补一条经 route_ips 的用例断言候选池包含它;若暂不启用,请删除该参数与 build_candidate_pool 的本机合并分支并同步修正 docstring,避免不可达代码与自证测试同时沉淀。
  • KVCM 兜底缺少专属指标,三个新增响应字段无任何生产消费方 @ rtp_llm/server/backend_rpc_server_visitor.py:162
    • 建议:上报时带 {"route_source": route_result.route_source} 标签,或为 KVCM 单列 QPS/RT 指标并按 selection_reason/outcome 打 tag(并暴露 status_success_countlatency_us/status_latency_us),使看板能区分两类路由并对兜底生效与命中率做告警;route_logger.debug("master route success...") 建议附带 route_sourcekvcm_outcome 便于按请求追溯。若暂不打点,请精简 15 项 cache_match 只保留日志实际使用的字段,并把 per-request info 日志降级为 debug 或加采样。
  • gRPC channel 与选中时间字典只增不减,滚动发布后累积僵尸连接 @ rtp_llm/server/kvcm_fallback.py:510
    • 建议:给两个 channel 字典加容量上限或 LRU/TTL 淘汰(淘汰时 await channel.close()),或在 _invalidate_leader、worker 探测连续失败时主动关闭并移除对应 channel;_last_selected_ns 同样按 pool 规模定期裁剪。
  • getattr 字面量访问造成默认值双份维护,并对强类型 GenerateInput 做猜测式访问 @ rtp_llm/server/master_client.py:156
    • 建议:master_configKvcmFallbackResult 均为内部强类型对象,直接属性访问即可,默认值只保留在 MasterConfig 一处;_input_ids_for_kvcm 收敛为基于 input.token_ids 的确定性处理(只保留二维 squeeze 这一条真实需要的分支),去掉 getattr/hasattr 猜测式访问,让字段改名在静态检查阶段暴露而非静默降级。
  • FlexLB body 级 8600 从硬失败改为静默 domain fallback,且不受 KVCM 开关保护 @ rtp_llm/server/master_client.py:611
    • 建议:在 PR description / 发布说明中显式列出这条与 KVCM 无关的兼容性变更及其预期影响(8400 硬失败 → domain 兜底),并与 FlexLB 侧确认 8600 的期望语义;必要时评估是否需要独立开关或限流保护。这部分与 KVCM 特性正交,建议拆为独立提交以便单独回滚与 CI 二分。
  • 手工签入 pb2_grpc 绕开构建期 codegen,格式不一致且与 .proto 无漂移校验 @ rtp_llm/server/worker_status_proto/worker_status_service_pb2_grpc.py:47
    • 建议:优先改为构建期生成(复用 generate_grpc_proto)或直接复用引擎既有生成模块,从根上去掉签入产物。若必须签入:统一格式来源(两目录都保持 protoc 原样并加入 pre-commit exclude,或生成后统一跑同一 formatter),把 protoc/grpcio-tools 版本与参数固化到脚本或 BUILD 规则以便逐字节复现;并补漂移检测测试——比对本副本 DESCRIPTORmodel_rpc_service_pb2WorkerStatusPB/TaskInfoPB 的字段号与类型集合,出现新增字段即失败,KVCM 侧同理比对 FlexLB 事实源。
  • 测试在模块导入期永久污染 sys.modules 且不恢复,含 torch 与整棵 rtp_llm 包树 @ rtp_llm/server/test/backend_rpc_server_visitor_fallback_test.py:146
    • 建议:把 stub 注入收敛到可恢复作用域:用 setUpModule / unittest.addModuleCleanupunittest.mock.patch.dict(sys.modules, {...}) 完成注入并在退出时还原快照;torch.inference_mode 这类依赖建议只 patch 属性而非替换整个 torch 模块。更彻底的做法是让被测模块的依赖通过构造参数注入,从而无需改写 sys.modules
  • 测试自建影子生产类型已与生产漂移,契约变更不会被发现 @ rtp_llm/server/test/master_client_fallback_test.py:41
    • 建议:对 RoleAddrExceptionTypeFlexlbResponseMasterClient 这类被断言的生产类型请直接 import 真实实现,只 stub 真正的重量级边界(torchModelRpcClientkmonitor);MasterClient.__init__ 已提供 kvcm_fallback_client 注入参数(master_client.py:133),正是为此设计。用真实 MasterConfig 构造实例、只 stub 最外层 client,即可在契约漂移时立刻失败。
  • 测试从源码树重建包路径加载生产模块,绕开 Bazel 依赖图 @ rtp_llm/server/test/kvcm_fallback_test.py:12
    • 建议:改为直接 import rtp_llm.server.kvcm_fallback,让 BUILD 的 deps 真正成为被验证的约束。若目的是回避 rtp_llm/__init__.py 的重量级导入,更合适的做法是把 kvcm_fallback 需要的最小依赖拆成独立 py_library 并在 py_test 中精确声明,而不是在测试里重建包路径——这样一旦 BUILD 漏声明源文件,测试会立刻失败而非静默通过。
  • 并发上界断言使用测试自建 semaphore,关键降级分支无覆盖 @ rtp_llm/server/test/kvcm_fallback_test.py:250
    • 建议:并发用例改为经公开入口 query_and_select 驱动:构造多于 worker_status_concurrency 的候选(如 4 候选 + 并发 2),由 fake servicer 统计 max_active 并断言 <= 2,锁住「配置项 → gather」的接线;通道复用改为断言 servicer 侧连接数或 RPC 次数等可观测行为,减少对私有属性的耦合。visitor 侧把 _MasterClient 改为可注入返回值,补齐 allow_domain_fallback 三条分支(断言 host_service.domain_calls == 1 且最终落到 domain 地址)以及上述两条既有边界组合。

P3

  • master_kvcm_use_local 场景下 service_id 中填写的端口被静默丢弃 @ rtp_llm/server/master_client.py:195
    • 建议:use_local 时优先使用 host 自带端口,仅在缺省时回落 bootstrap_port;或在 help 中明确说明端口部分会被忽略、必须通过 --master_kvcm_bootstrap_port 指定。
  • token_ids 到 list 的 O(prompt_len) 转换在路由热路径上重复执行两次 @ rtp_llm/server/backend_rpc_server_visitor.py:133
    • 建议:将已展开的 token_ids 作为参数传入 get_backend_role_addrs 直接复用;若担心接口膨胀,也可只在 KVCM 兜底真正触发时惰性转换(当前 _input_ids_for_kvcm 已在兜底分支内调用,浪费主要在于重复展开而非提前展开)。
  • 回落 domain 路由的 warning 措辞与实际条件不符且为逐请求热路径噪声 @ rtp_llm/server/backend_rpc_server_visitor.py:240
    • 建议:改为直接陈述判定结果,例如 "master route skipped, master_addr=%s, kvcm_fallback_enabled=%s, input_token_batched=%s, fallback to domain routing";并考虑对该 warning 做采样或降级为 debug。
  • 测试脚手架存在未使用 import 与可参数化的重复用例体 @ rtp_llm/server/test/backend_rpc_server_visitor_fallback_test.py:1
    • 建议:删除未使用的 import asyncio;两条用例可用 subTest 或参数化表((kvcm_enabled, expect_master_calls, expect_domain_calls, expect_ip))驱动,后续新增降级路径用例时只需加一行数据。

Checklist Findings (19 fail / 48 total)

General Principles Checklist

  • [6.1] Architecture — 兼容性:外部 HTTP/RPC API、持久数据、配置、环境迁移安全 → issue FlexLB body 级 8600 从硬失败改为静默 domain fallback,且不受 KVCM 开关保护
    经 diff 确认 if code == FALLBACK_ERROR_CODE: return FlexlbResponse.fallback_response()(:611-612) 是本次新增行(FALLBACK_ERROR_CODE = 8600 与 :475-476 的非 200 分支均为存量)。exceptions.py 只定义到 MASTER_NO_AVAILABLE_WORKER = 8400ROUTE_ERROR = 8500,无 8600,故此前 HTTP 200 响应体中 code=8600 会走 ExceptionType(8600) → ValueError → 回落 8400 并 raise FtRuntimeException(请求以 8400 失败)。改动后同一响应变成 fallback=Truebackend_rpc_server_visitor.py:251allow_domain_fallback 成立 → 静默 domain routing。该变化**不在 `master_kvcm_fal
  • [6.1] Architecture — 可观测性:日志/指标/超时可操作、非噪声 → issue 回落 domain 路由的 warning 措辞与实际条件不符且为逐请求热路径噪声
    日志文案为 "master address: %s, KVCM fallback enabled: %s, or input token batched: %s is not valid, fallback to domain routing"。进入该分支的真实条件是「master_addr 为空 KVCM 开关关闭」或「输入为批量」,把 KVCM fallback enabled=False 描述为 "is not valid" 语义混乱;且该 warning 在 master 域名未配置的部署里是每请求一条,属热路径噪声。
  • [6.1] Architecture — 回滚路径:风险行为存在运维回滚手段 → issue 10 个新增 MasterConfig 字段没有任何 CLI/env 绑定,线上实际不可配置
    MasterConfig.__init__ 新增 19 个字段(:389-411),master_group_args.py 只注册 9 个新 add_argument(全文 13 个,其中 4 个为存量)。缺绑定的 10 个经逐一比对为 master_kvcm_worker_status_port(:397)、candidate_pool_size(:398)、hot_candidate_pool_size(:399)、worker_status_concurrency(:400)、master_sync_request_timeout_ms(:402)、prefill_queue_size_threshold(:403)、p2p_hit_discount(:404)、cache_affinity_first_max_extra_work_tokens(:405)、outstanding_uncached_tokens_threshold(:406)、cache_affinity_first_min_hit_rate(:407)。`
  • [6.1] Architecture — 状态不变量:创建/更新/失败/重试/回滚路径有效 → issue 测试在模块导入期永久污染 sys.modules 且不恢复,含 torch 与整棵 rtp_llm 包树
    本行在模块顶层(而非 setUp 内)直接调用 _load_visitor_module()。该函数通过 :21-32 的循环与 _module()(:11-16),把 torch(:92 整体替换为只有 inference_mode 的桩)、rtp_llmrtp_llm.serverrtp_llm.configrtp_llm.cpprtp_llm.cpp.model_rpcrtp_llm.metricsrtp_llm.utils 共 7 个伪包 + 10 个伪模块写入 sys.modules,全程无备份与恢复(无 addModuleCleanup、无 try/finally)。当前依赖 Bazel 每个 py_test 独立进程才侥幸成立;一旦该文件与其他用例同进程被收集(合并 target 或直接对该目录跑 pytest),后续任何 import torchimport rtp_llm.* 都会拿到假模块,产生只表现为 AttributeError 的连锁失败。
  • [6.1] Architecture — 错误语义:fail-fast/retry/fallback/silent 行为显式 → issue master_kvcm_use_local 场景下 service_id 中填写的端口被静默丢弃
    master_group_args.py:58 的 help 说明 master_kvcm_service_id 是「KVCM bootstrap service id or local IP:port list」,:112 进一步说明 use_local 时「Treat service id as a comma-separated local IP:port list」,host_service.py:95-102 也确实按 ip, port = addr.split(":") 构造 Host(ip, port)。但 resolve_bootstrap_targets(:195-203) 只取 host.ip 并统一拼接 bootstrap_port,用户在 service_id 中写的端口被静默忽略,实际连接的是 master_kvcm_bootstrap_port(默认 6381)。这在 use_local 的测试/联调场景下会产生「配置看起来生效、实际连错端口」的困惑。
  • [6.1] Quality — 无 per-forward 调试日志 / 噪声热路径输出 → issue 回落 domain 路由的 warning 措辞与实际条件不符且为逐请求热路径噪声
    日志文案为 "master address: %s, KVCM fallback enabled: %s, or input token batched: %s is not valid, fallback to domain routing"。进入该分支的真实条件是「master_addr 为空 KVCM 开关关闭」或「输入为批量」,把 KVCM fallback enabled=False 描述为 "is not valid" 语义混乱;且该 warning 在 master 域名未配置的部署里是每请求一条,属热路径噪声。
  • [6.1] Quality — 逻辑变更未混入无关格式化 → issue 手工签入 pb2_grpc 绕开构建期 codegen,格式不一致且与 .proto 无漂移校验
    全仓其余 proto 均由 rtp_llm/cpp/model_rpc/proto/BUILDgenerate_grpc_proto + *_py_proto 在构建期产出,本次新增的两个 *_pb2_grpc.py 是唯一手工签入的生成物,形成两套并行机制,且 rtp_llm/server/BUILD 只有一个 py_library glob、无 protoc 规则与一致性校验。两者风格还不一致:kvcm_meta_service_pb2_grpc.py:17-21 已被 black 改写(双引号、尾随逗号),本文件 :17-21 保留 protoc 原样(单引号、8 空格续行),本行(:47) 还带前导空格。.pre-commit-config.yaml 的 exclude 只有 ^rtp_llm/ops|^3rdparty,二者仍会被继续改写,与 DO NOT EDIT 头矛盾,下次重生成会产生大量与逻辑无关的 diff。worker_status_service.proto 又是引擎 proto 的手工裁剪副本,引擎侧新增字段不会同
  • [6.1] Software Engineering — DRY:重复非平凡逻辑被抽取或显式复用 → issue token_ids 到 list 的 O(prompt_len) 转换在路由热路径上重复执行两次
    get_master_route_addrs(:133-138) 已把 input.token_ids 展开为 python list 并算出 block_cache_keys,但不把该 list 传下去;master_client._input_ids_for_kvcm(:298-317) 在 KVCM 兜底时再次 tolist() 并做 [int(t) for t in ...] 全量转换。改动后 master_addr 为空的部署每个请求都会命中这条路径,长 prompt(数万 token)下等于在路由热路径上多做一遍纯 Python 逐元素转换。本条未做基准测量,仅为代码层面的重复开销。
  • [6.1] Software Engineering — KISS/YAGNI:无投机性抽象 → issue 测试脚手架存在未使用 import 与可参数化的重复用例体
    本行的 import asyncio 经全文检索确认仅出现在此、从未使用(异步用例走 unittest.IsolatedAsyncioTestCase);仓库 .flake8ignore = W292,F401 未被豁免,该行会触发 lint。另外 :211 与 :221 两个用例体结构完全一致,仅 _visitor(True/False) 与三处期望值不同,属可数据驱动化的重复脚手架。
  • [6.1] Tests — 分布式/跨平台变更有对应覆盖 → issue KVCM 选中 worker 的 role 被丢弃并硬编码为 PREFILL,与打分层的 PDFUSION 支持不一致
    kvcm_fallback.py:389 的可用性过滤同时接纳 role 含 PREFILLPDFUSION 的 worker,说明打分层设计上支持 PDFUSION 部署;但 WorkerLoadSnapshot.role(:355) 只用于过滤,未写入 KvcmFallbackResultselected 是无 role 字段的 KvcmCacheCandidate),最终本行固定构造 RoleAddr(role=RoleType.PREFILL, ...)。叠加本次入口门禁放宽,在 pdfusion_domain 部署下 backend_role_list=[PDFUSION]backend_rpc_server_visitor.py:248need_domain_routing 恒为真,于是既追加一次冗余 domain 兜底,又把一条角色标签与真实 worker 不符的 PREFILL 地址一并下发给引擎,下游按角色取址的行为未验证。
  • [6.1] Tests — 新逻辑有聚焦单测 + 相关集成/smoke 测试 → issue 并发上界断言使用测试自建 semaphore,关键降级分支无覆盖
    测试在 :213 传入 worker_status_concurrency=2,但 :250-258 自建 asyncio.Semaphore(2) 并直接调用私有 client._probe_worker(candidate, semaphore) 后断言 max_active <= 2——生产上界由 kvcm_fallback.py:724query_and_select 内构造,而前半段(:218-226)只有 1 个候选,故该断言只验证了 asyncio.Semaphore 自身语义:把 :724 改成任意大值或删除 semaphore,测试仍通过;:177/:226 还直接断言私有属性 _channels/_worker_status_channels 的长度。visitor 侧 _MasterClient.get_backend_role_addrs(:183-187) 恒返回成功响应,本次改动真正的风险路径全部无覆盖:KVCM 无结果时 connection_failed=True 能否经 `allow_domain_fa
  • [6.1] Tests — 边界 case 覆盖(空、单元素、最大值) → issue master_kvcm_use_local 场景下 service_id 中填写的端口被静默丢弃
    master_group_args.py:58 的 help 说明 master_kvcm_service_id 是「KVCM bootstrap service id or local IP:port list」,:112 进一步说明 use_local 时「Treat service id as a comma-separated local IP:port list」,host_service.py:95-102 也确实按 ip, port = addr.split(":") 构造 Host(ip, port)。但 resolve_bootstrap_targets(:195-203) 只取 host.ip 并统一拼接 bootstrap_port,用户在 service_id 中写的端口被静默忽略,实际连接的是 master_kvcm_bootstrap_port(默认 6381)。这在 use_local 的测试/联调场景下会产生「配置看起来生效、实际连错端口」的困惑。

RTP-LLM Checklist

  • [I] 代码质量 — 同一功能用统一工具函数 → issue 测试脚手架存在未使用 import 与可参数化的重复用例体
    本行的 import asyncio 经全文检索确认仅出现在此、从未使用(异步用例走 unittest.IsolatedAsyncioTestCase);仓库 .flake8ignore = W292,F401 未被豁免,该行会触发 lint。另外 :211 与 :221 两个用例体结构完全一致,仅 _visitor(True/False) 与三处期望值不同,属可数据驱动化的重复脚手架。

Python Static-First Checklist

  • [P.A] 静态结构与类型纪律 — 禁止 getattr/setattr literal 访问 → issue getattr 字面量访问造成默认值双份维护,并对强类型 GenerateInput 做猜测式访问
    _create_kvcm_fallback_client(:156-265) 用了 13 处 getattr(self.master_config, "字面量", 默认值),每个默认值(6381/100/3/2/200/1024/0.2/5.0…)都与 py_config_modules.py:389-411 重复一份,漂移时无告警。_input_ids_for_kvcm(:298-317) 对强类型 dataclass 用 getattr(input, "input_ids", None)——已核实 base_model_datatypes.py:53-58GenerateInput 只有 token_ids: torch.Tensorinput_ids 属于 GenerateOutput),该分支在生产恒为 None;随后又用 hasattr(raw_input_ids, "tolist") 做控制流分支,而 token_ids 按声明恒为 Tensor。cache_match 与日志中另有 10 处 `getattr(r
  • [P.A] 静态结构与类型纪律 — 禁止 hasattr 做控制流分支 → issue getattr 字面量访问造成默认值双份维护,并对强类型 GenerateInput 做猜测式访问
    _create_kvcm_fallback_client(:156-265) 用了 13 处 getattr(self.master_config, "字面量", 默认值),每个默认值(6381/100/3/2/200/1024/0.2/5.0…)都与 py_config_modules.py:389-411 重复一份,漂移时无告警。_input_ids_for_kvcm(:298-317) 对强类型 dataclass 用 getattr(input, "input_ids", None)——已核实 base_model_datatypes.py:53-58GenerateInput 只有 token_ids: torch.Tensorinput_ids 属于 GenerateOutput),该分支在生产恒为 None;随后又用 hasattr(raw_input_ids, "tolist") 做控制流分支,而 token_ids 按声明恒为 Tensor。cache_match 与日志中另有 10 处 `getattr(r
  • [P.C] 并发与异步 — async def 中禁止 blocking 调用 → issue KVCM leader 解析在事件循环内持锁执行无 timeout 的同步 vipserver 请求,且失败无负缓存
    _resolve_leaderasync with self._leader_lock(:564) 内同步调用 self._bootstrap_resolver()(:569),即 master_client.py:195-203resolve_bootstrap_targetsVipServerWrapper.get_hosts(refresh=True)host_service.py:117-122,非 use_local 时走 get_host_list_by_domain_now)→ vipserver_proxy.py:106requests.get(...):同步、经核实无 timeout 参数、失败时逐个 srv 重试。这会阻塞前端整个 asyncio 事件循环,拖住同进程所有在途请求。其后 :571 是串行 for target in targets,每个等 request_timeout_ms,无整体 deadline;全部失败且无 previous_leader 时抛异常但不写负缓
  • [P.F] 语言陷阱 — 禁止模块级 import 副作用 → issue 测试从源码树重建包路径加载生产模块,绕开 Bazel 依赖图
    kvcm_fallback_test.py:12master_client_fallback_test.py:11backend_rpc_server_visitor_fallback_test.py:20 均用 Path(__file__).resolve().parents[2] 反推包根,再通过注入 __path__spec_from_file_location 加载生产模块。.resolve() 会跟随 runfiles 符号链接,导入目标取决于测试进程实际落在哪棵目录树上,而不是 BUILD 中声明的 deps。这正是 worker_status_proto glob 缺失未被任何测试暴露的机制性原因:源码树里文件在,runfiles 里不在,而测试读的是源码树。
  • [P.G] 测试规范 — mock/fake/stub 不得替代本次声称覆盖的生产边界 → issue 并发上界断言使用测试自建 semaphore,关键降级分支无覆盖
    测试在 :213 传入 worker_status_concurrency=2,但 :250-258 自建 asyncio.Semaphore(2) 并直接调用私有 client._probe_worker(candidate, semaphore) 后断言 max_active <= 2——生产上界由 kvcm_fallback.py:724query_and_select 内构造,而前半段(:218-226)只有 1 个候选,故该断言只验证了 asyncio.Semaphore 自身语义:把 :724 改成任意大值或删除 semaphore,测试仍通过;:177/:226 还直接断言私有属性 _channels/_worker_status_channels 的长度。visitor 侧 _MasterClient.get_backend_role_addrs(:183-187) 恒返回成功响应,本次改动真正的风险路径全部无覆盖:KVCM 无结果时 connection_failed=True 能否经 `allow_domain_fa
  • [P.G] 测试规范 — 数据驱动测试用 pytest.mark.parametrize → issue 测试脚手架存在未使用 import 与可参数化的重复用例体
    本行的 import asyncio 经全文检索确认仅出现在此、从未使用(异步用例走 unittest.IsolatedAsyncioTestCase);仓库 .flake8ignore = W292,F401 未被豁免,该行会触发 lint。另外 :211 与 :221 两个用例体结构完全一致,仅 _visitor(True/False) 与三处期望值不同,属可数据驱动化的重复脚手架。

Strengths

  • 开关默认关闭且 py_config_modules.py:388 显式注释「Keep disabled by default so existing FlexLB/domain routing is unchanged」;开关为假时 can_attempt_master_route 退化为原 bool(master_addr) 语义,route_ips 各分支行为与改动前逐条一致,存量部署零行为变更,回滚手段明确。
  • 兜底触发条件收敛精准:master_client.py:582 仅在 resp.connection_failed 时进入 KVCM,显式 FlexLB fallback 与全部业务/准入错误码保留原语义,未把可用性兜底与业务降级混为一谈;master_client_fallback_test.py:200-264 用三个用例断言 kvcm_client.calls == 0 锁死这条边界。
  • 二级降级链路经核实成立:KVCM 无候选时返回 None,resp.connection_failed 仍为 True,backend_rpc_server_visitor.py:251allow_domain_fallback 依然满足,不会把请求打死在 ROUTE_ERROR 上;全部 KVCM 异常在 master_client.py:353 收敛为返回 None,失败面收敛。
  • KvcmFallbackConfig.__post_init__ 按「必须为正 / 不得为负 / 合法端口 / 浮点非负」四组做完整前置校验并 fail-fast,且显式排除 bool 混入 int,配置错误在构造期暴露而非在请求路径上产生难定位行为。
  • select_cache_affinity_first 忠实复刻 FlexLB CacheAffinityFirst 语义:alive/role/队列长度过滤、estimated_ttft_work 排序、min_hit_rate 门限、outstanding 守护回退、recent 打散并列项,并在 _matched_tokens:369 注释中说明对齐 Java Math.round 行为。
  • worker_status_service.proto 逐字段对齐引擎侧字段号,完整保留 reserved 2 / reserved 19, 20 / reserved "inter_request_id"optional presence 语义;kvcm_meta_service.proto 与 flexlb-grpc 侧事实源逐字段核对后 package、字段号、类型、枚举值完全一致,wire 兼容性考虑到位。
  • kvcm_fallback_test.py:114-124 用真实 grpc.aio.server() + 127.0.0.1:0 动态端口做端到端验证而非 mock 掉传输层,真实覆盖序列化与方法路径;:90-108 用 golden vectors 锁定 block hash 并单独覆盖「丢弃不足一块的尾部」边界;:150 用畸形 endpoint 做对抗性用例。
  • rtp_llm/BUILD:80rtp_llm/server/BUILD:11-12 显式声明 grpcio/protobuf 直接依赖,修正了此前 import grpc 靠传递依赖的隐式接线;grpcio==1.62.0 已在 lock 文件(rtp_llm/BUILD:606)中,跨平台构建无缺依赖风险。
  • master_flexlb_transport_timeout_ms 默认 0 并在 master_client.py:558min() 收敛,完整保留「用请求 TTFT 超时」的原行为;_split_ip_port/_format_target 对 IPv6 bracket 形式处理正确;close() 幂等且同时清理两组 channel。

// Keep this file package-less: the deployed engine serves
// /RpcService/GetWorkerStatus.

message StatusVersionPB {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P0] 无 package 的 worker_status proto 与引擎 model_rpc_service_pb2 符号重名,开关一开即 duplicate symbol 崩溃

新文件注释「Keep this file package-less」(:3-4),在全局命名空间定义 StatusVersionPB(:6)、TaskInfoPB(:11)、KvCacheGroupModePB(:42)、WorkerStatusPB(:48)、service RpcService(:73)。已核实 model_rpc_service.proto 全文无 package,并在 :342/:390/:439/:445/:654 定义完全同名符号,:655 已有 rpc GetWorkerStatus(StatusVersionPB) returns (WorkerStatusPB)。两份 pb2 均调 _descriptor_pool.Default().AddSerializedFile(本副本见 worker_status_service_pb2.py:17),文件名不同而全限定名相同。链路确定:backend_rpc_server_visitor.py:10model_rpc_client.py:10 已加...

建议: 优先直接复用 rtp_llm.cpp.model_rpc.proto.model_rpc_service_pb2{,_grpc}:其 RpcServiceStub.GetWorkerStatus 方法路径同为 /RpcService/GetWorkerStatus、请求/响应类型逐字段一致,且由 model_rpc/proto/BUILDgenerate_grpc_proto 构建期生成、已随前端打包;utils/grpc_client_wrapper.py:12multimodal/vit_proxy_start_server.py:29 已有同类先例。删除 worker_status_proto/ 可同时消解本条与下一条打包缺失。若确需与引擎解耦的独立副本,注意给 proto 加 package 会改变方法全名破坏 wire 兼容,只能把描述符注册到私有 descriptor_pool.DescriptorPool() 并用 channel.unary_unary("/RpcService/GetWorkerStatus", ...) 显式固定路径。无论选哪条路,都请补一条非 manual 的回归测试:同进程内先 import 引擎 pb2 再 import kvcm_fallback,把该不变量固化下来。

Comment thread rtp_llm/server/BUILD
@@ -4,9 +4,12 @@ py_library(
name = "server",
srcs = glob([

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] server py_library 的 srcs glob 漏掉 worker_status_proto,wheel 与 runfiles 均缺该模块

py_library(name="server") 的 glob 为 ["*.py", "server_args/*.py", "kvcm_proto/*.py"],本次显式补了 kvcm_proto/ 却漏掉 worker_status_proto/,而 bazel glob 不递归;该目录下也无独立 BUILD,全仓 BUILD 检索 worker_status_proto 零命中(仅 kvcm_fallback.py:22-23kvcm_fallback_test.py:30,33 引用)。rtp_llm/BUILD:417,491 表明 //rtp_llm/server:serverrtp_llm/server/** 进入 wheel 与 py_binary 的唯一来源,故 __init__.pyworker_status_service_pb2.py..._pb2_grpc.py 既不在 wheel 也不在 runfiles,而 kvcm_fallback.py:22-25 模块级无条件导入它 → ...

建议: 若采纳上一条建议删除该目录,本条自动消解;否则在 glob 中补 "worker_status_proto/*.py",或改为 glob(["*.py", "server_args/*.py", "*_proto/*.py"]) 以避免后续新增 proto 子目录时同类遗漏复现。补齐后请用一个非 manual 的 py_test 真实 import rtp_llm.server.kvcm_fallback,让打包完整性有构建期约束而非依赖人工记忆,并在沙箱/远程执行下验证一次(本地 local spawn 可能因源码树可见而假通过)。

self.master_kvcm_bootstrap_port: int = 6381
# Exact KVCM instance_id/namespace, for example <prefill-deployment>_128.
self.master_kvcm_instance_id: str = ""
self.master_kvcm_block_size: int = 0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] master_kvcm_block_size 默认 0 与「必须为正」校验直接冲突,开关打开即启动期拉挂

master_kvcm_block_size 默认 0(本行,master_group_args.py:84 同为 default=0)。而 kvcm_fallback.py:58-69block_size 归入必须为正组:if isinstance(value, bool) or not isinstance(value, int) or value <= 0: raise ValueError(f"KVCM {name} must be positive")。该异常经 master_client.py:165→208 传入配置后由 MasterClient.__init__(:149) 抛出、再从 BackendRPCServerVisitor.__init__ 冒泡,即仅打开开关而未显式设值就会启动失败。这是同一路径上第三个独立阻塞。同函数对 service_id/instance_id/bootstrap_port 都有点名到配置项的报错(master_client.py:179-188),block_size 却只得到 `K...

建议: 二选一并保持一致:要么给一个可用的正默认值(并在 help 中说明与引擎 seq_size_per_block 的关系),要么在 _create_kvcm_fallback_client 中补一条与 service_id 同风格、点名参数的前置校验,例如 if block_size <= 0: raise ValueError("master_kvcm_block_size must be positive when KVCM fallback is enabled")。请同时补一条断言「开关打开 + 默认 block_size → ValueError」的单测——这是开关打开后的首个失败点,目前零覆盖。

Comment thread rtp_llm/server/kvcm_fallback.py Outdated
p2p_hit_discount: float,
) -> int:
# Java Math.round for a non-negative value is floor(value + 0.5).
tokens = int(effective_cache_blocks(candidate, p2p_hit_discount) * block_size + 0.5)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 块→token 折算 stride 与实际发出键的 stride 无一致性校验,vLLM sha256 路径在生产不可达

生产路径 backend_rpc_server_visitor.py:138get_block_cache_keys(token_ids, self.seq_size_per_block) 生成键(ops/__init__.py:176-184seq_size_per_block 切块并走 C++ blockUtil.cc:8hashInt64Vector),原样透传至 master_client.py:349kvcm_fallback.py:689 keys = list(block_cache_keys)仅当 keys 为空才调 calculate_vllm_block_cache_keys(:691)。故 prompt ≥ seq_size_per_block 时 sha256 分支与 master_group_args.py:85「used for vLLM sha256_cbor hashing」的声明用途完全不可达;短 prompt 时反而会发出 KVCM 未索引的键空间。更关键的是 `...

建议: 把键与 stride 收敛为单一来源:删除 sha256 分支与 master_kvcm_block_size,折算直接用 seq_size_per_block;或不复用 FlexLB 的 block_cache_keys、只传 input_idsquery_and_select 统一按 master_kvcm_block_size 生成。若仍需手工配置,请在构造期断言 master_kvcm_block_size == seq_size_per_block——注意 FlexLB KvcmWorkerMetadataResolver.resolveNamespace:49-57 会把 namespace 拼成 <name>_<blockSize>,而 master_kvcm_instance_id 是静态字符串,同一个 stride 目前需要运维在两处手工对齐。折算所需的 block_size / lookahead_tokens 建议直接取协议已返回的 WorkerStatusPB.block_size(字段 18)/ block_hash_lookahead_tokens(字段 21),而 worker_load_snapshot(:348-360) 当前完全不读。另请一并对齐 query type:kvcm_fallback.py:635 硬编码 QT_PREFIX_MATCH,而 FlexLB KvcmGrpcClient:151-157 会按 role/group 解析并在不可用时跳过查询,请确认该简化在所有部署形态下成立。最后补一个端到端用例,断言实际发往 KVCM 的 keys 与选定折算方式一致。

Comment thread rtp_llm/server/test/BUILD
"//rtp_llm:grpcio",
"//rtp_llm:protobuf",
],
tags = ["manual"],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 三个新增 py_test 全部 manual 且用桩替换了本该守住三个阻塞缺陷的生产边界

kvcm_fallback_test(:35)、master_client_fallback_test(:45)、backend_rpc_server_visitor_fallback_test(:54) 均带 tags = ["manual"],被 //... 与 test_suite 展开排除,本 PR 变更中无任何显式注册处,802 行新模块与 route_ips 准入改动在 CI 中零自动执行。且即便执行也无法发现上述缺陷:visitor 测试 :104 把 model_rpc_client.ModelRpcClient 桩为 object、:125 把 MasterClient 桩为 object、:92 整体替换 torch,两份 pb2 都不会被导入;master_client_fallback_test.py:114 用 5 字段 SimpleNamespace 替代真实 MasterConfig,7 个用例全部注入 fake client,_create_kvcm_fallback_client()(`...

建议: 补一条不 stub master_client/model_rpc_client 的集成用例:同进程内先 import rtp_llm.cpp.model_rpc.model_rpc_client,再以真实 MasterConfig(master_kvcm_fallback_enabled=True) 构造 MasterClient 并断言构造成功——一条用例即可同时守住三个阻塞点。这三个测试是纯 Python、无 GPU/权重依赖、只绑 127.0.0.1:0,属可常态执行的 hermetic 单测,建议去掉 manual;需说明同目录 schedule_meta_test/host_service_test 也是 manual(vit_proxy_server_test 不是),但正是该惯例让这个目录长期缺少自动覆盖,本次不宜继续沿用。若内部 CI 确实通过显式 target 列表执行,请在 PR 描述中给出注册位置以便确认。

@LLLLKKKK LLLLKKKK left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Code Review - PR #1335 (non-blocking suggestions)

17 条 P2/P3 建议,不阻塞合并。阻塞判定与完整摘要见上一条 review。

self.master_client.kvcm_fallback_enabled,
input_token_batched,
)
specified_roles = {addr.role for addr in input.generate_config.role_addrs}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

📍 实际位置 rtp_llm/server/backend_rpc_server_visitor.py:324(不在 diff 展示范围内,就近挂载)

[P2] 新开关被 host_service.service_available 前置门控静默旁路

route_ips 只在 self.host_service.service_available 为真时被调用(本行,batch_enqueue :335-337 同理)。已核实 host_service.py:551service_available = bool(self.master_vip.domain) or any(self.role_vip_map.values()),仅由 master/role domain 决定,完全不感知 master_kvcm_fallback_enabled。因此运维若只打开 KVCM 开关而未配置任何 domain,route_ips 根本不执行,本次放宽的 can_attempt_master_route 判定永不生效,且日志无任何提示——属跨层配置传播缺口导致的静默失效。

建议:BackendRPCServerVisitor.__init__ 中检测 master_client.kvcm_fallback_enabled and not host_service.service_available 并打一条明确 warning(说明 KVCM fallback 因未配置 service domain 而不会生效),或把该开关纳入 service_available 的判定。至少需要一条可观测提示,避免线上开了开关却以为已生效。

)
return FlexlbResponse.ok(
[
RoleAddr(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] KVCM 选中 worker 的 role 被丢弃并硬编码为 PREFILL,与打分层的 PDFUSION 支持不一致

kvcm_fallback.py:389 的可用性过滤同时接纳 role 含 PREFILLPDFUSION 的 worker,说明打分层设计上支持 PDFUSION 部署;但 WorkerLoadSnapshot.role(:355) 只用于过滤,未写入 KvcmFallbackResultselected 是无 role 字段的 KvcmCacheCandidate),最终本行固定构造 RoleAddr(role=RoleType.PREFILL, ...)。叠加本次入口门禁放宽,在 pdfusion_domain 部署下 backend_role_list=[PDFUSION]backend_rpc_server_visitor.py:248need_domain_routing 恒为真,于是既追加一次冗余 domain 兜底,又把一条角色标签与真实 worker 不符的 PREFILL 地址一并下发给引擎,下游按角色取址的行为未验证。

建议:KvcmFallbackResult 中带上选中 snapshot 的 role 并在 _try_kvcm_fallback 中映射为对应 RoleType;或在进入 KVCM 兜底前校验 self.backend_role_list 是否包含 RoleType.PREFILL,不含则直接走 domain 路由。若本次只支持 PREFILL,则从 select_cache_affinity_first 的过滤条件中去掉 PDFUSION,使两处语义一致。并补一条 backend_role_list=[PDFUSION] 的用例,断言不会混入多余的 PREFILL 地址。

Checklist: [6.1] 分布式/跨平台变更有对应覆盖

Comment thread rtp_llm/config/py_config_modules.py Outdated
@@ -223,16 +223,25 @@ async def route_ips(self, input: GenerateInput):
input_token_batched = True

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

📍 实际位置 rtp_llm/server/backend_rpc_server_visitor.py:141(不在 diff 展示范围内,就近挂载)

[P2] local_fallback_addr 在生产链路从不传入,本机保底候选不可达

kvcm_fallback.py:3-4 的模块 docstring 声称打分范围是「the caller's local worker plus KVCM cache hits」,build_candidate_pool(:281-337) 专门实现了「always retaining the local worker」并合并其真实 cache match。但唯一生产调用点(本行 :141-145)只传 block_cache_keys/input/request_id;全仓检索 local_fallback_addr 的外部写入方仅 master_client_fallback_test.py:171 一处。结果是生产上 local_candidate 恒为 None(master_client.py:329-344 不可达),pool 退化为 hot[:hot_candidate_pool_size](默认 2 而非 3),且 KVCM 无正向命中时 pool 直接为空 → no_candidates → 兜底恒返回 None,「至少保底...

建议: 二选一并在测试中体现:若该能力属本次交付范围,请在 get_master_route_addrs 中把本机(或 domain 解析出的)worker 作为 local_fallback_addr 传下去,并补一条经 route_ips 的用例断言候选池包含它;若暂不启用,请删除该参数与 build_candidate_pool 的本机合并分支并同步修正 docstring,避免不可达代码与自证测试同时沉淀。

@@ -223,16 +223,25 @@ async def route_ips(self, input: GenerateInput):
input_token_batched = True

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

📍 实际位置 rtp_llm/server/backend_rpc_server_visitor.py:162(不在 diff 展示范围内,就近挂载)

[P2] KVCM 兜底缺少专属指标,三个新增响应字段无任何生产消费方

准入放宽后 master_addr 为空也进入计时块,KVCM 的 gRPC 耗时被记入 GaugeMetrics.MASTER_ROUTE_RT_METRIC(:236-238),成功时统一上报 AccMetrics.MASTER_ROUTE_QPS_METRIC(本行),与 FlexLB 混在同一指标,无法区分兜底占比与 KVCM 时延。FlexlbResponse 已带 route_source/cache_match/kvcm_outcomemaster_client.py:47-49、:407-418),但全仓检索显示除 master_client.py 自身赋值/回填与两个测试断言外无任何生产消费方;对 rtp_llm/server grep kmonitor 确认 master_client.pykvcm_fallback.py 均未新增任何指标。唯一可观测手段是 master_client.py:394 的每请求 route_logger.info,而该分支只在 FlexLB 故障时触发——正是日...

建议: 上报时带 {"route_source": route_result.route_source} 标签,或为 KVCM 单列 QPS/RT 指标并按 selection_reason/outcome 打 tag(并暴露 status_success_countlatency_us/status_latency_us),使看板能区分两类路由并对兜底生效与命中率做告警;route_logger.debug("master route success...") 建议附带 route_sourcekvcm_outcome 便于按请求追溯。若暂不打点,请精简 15 项 cache_match 只保留日志实际使用的字段,并把 per-request info 日志降级为 debug 或加采样。

Comment thread rtp_llm/server/test/kvcm_fallback_test.py Outdated
bool(getattr(self.master_config, "master_kvcm_use_local", False)),
)

def resolve_bootstrap_targets() -> List[str]:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P3] master_kvcm_use_local 场景下 service_id 中填写的端口被静默丢弃

master_group_args.py:58 的 help 说明 master_kvcm_service_id 是「KVCM bootstrap service id or local IP:port list」,:112 进一步说明 use_local 时「Treat service id as a comma-separated local IP:port list」,host_service.py:95-102 也确实按 ip, port = addr.split(":") 构造 Host(ip, port)。但 resolve_bootstrap_targets(:195-203) 只取 host.ip 并统一拼接 bootstrap_port,用户在 service_id 中写的端口被静默忽略,实际连接的是 master_kvcm_bootstrap_port(默认 6381)。这在 use_local 的测试/联调场景下会产生「配置看起来生效、实际连错端口」的困惑。

建议: use_local 时优先使用 host 自带端口,仅在缺省时回落 bootstrap_port;或在 help 中明确说明端口部分会被忽略、必须通过 --master_kvcm_bootstrap_port 指定。

Checklist: [6.1] 错误语义:fail-fast/retry/fallback/silent 行为显式;[6.1] 边界 case 覆盖(空、单元素、最大值)

@@ -223,16 +223,25 @@ async def route_ips(self, input: GenerateInput):
input_token_batched = True

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

📍 实际位置 rtp_llm/server/backend_rpc_server_visitor.py:133(不在 diff 展示范围内,就近挂载)

[P3] token_ids 到 list 的 O(prompt_len) 转换在路由热路径上重复执行两次

get_master_route_addrs(:133-138) 已把 input.token_ids 展开为 python list 并算出 block_cache_keys,但不把该 list 传下去;master_client._input_ids_for_kvcm(:298-317) 在 KVCM 兜底时再次 tolist() 并做 [int(t) for t in ...] 全量转换。改动后 master_addr 为空的部署每个请求都会命中这条路径,长 prompt(数万 token)下等于在路由热路径上多做一遍纯 Python 逐元素转换。本条未做基准测量,仅为代码层面的重复开销。

建议: 将已展开的 token_ids 作为参数传入 get_backend_role_addrs 直接复用;若担心接口膨胀,也可只在 KVCM 兜底真正触发时惰性转换(当前 _input_ids_for_kvcm 已在兜底分支内调用,浪费主要在于重复展开而非提前展开)。

Checklist: [6.1] DRY:重复非平凡逻辑被抽取或显式复用

GaugeMetrics.MASTER_ROUTE_RT_METRIC, master_route_timer.cost_ms()
)
elif not role_addrs_specified:
route_logger.warning(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P3] 回落 domain 路由的 warning 措辞与实际条件不符且为逐请求热路径噪声

日志文案为 "master address: %s, KVCM fallback enabled: %s, or input token batched: %s is not valid, fallback to domain routing"。进入该分支的真实条件是「master_addr 为空 KVCM 开关关闭」或「输入为批量」,把 KVCM fallback enabled=False 描述为 "is not valid" 语义混乱;且该 warning 在 master 域名未配置的部署里是每请求一条,属热路径噪声。

建议: 改为直接陈述判定结果,例如 "master route skipped, master_addr=%s, kvcm_fallback_enabled=%s, input_token_batched=%s, fallback to domain routing";并考虑对该 warning 做采样或降级为 debug。

Checklist: [6.1] 可观测性:日志/指标/超时可操作、非噪声;[6.1] 无 per-forward 调试日志 / 噪声热路径输出

@@ -0,0 +1,233 @@
import asyncio

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P3] 测试脚手架存在未使用 import 与可参数化的重复用例体

本行的 import asyncio 经全文检索确认仅出现在此、从未使用(异步用例走 unittest.IsolatedAsyncioTestCase);仓库 .flake8ignore = W292,F401 未被豁免,该行会触发 lint。另外 :211 与 :221 两个用例体结构完全一致,仅 _visitor(True/False) 与三处期望值不同,属可数据驱动化的重复脚手架。

建议: 删除未使用的 import asyncio;两条用例可用 subTest 或参数化表((kvcm_enabled, expect_master_calls, expect_domain_calls, expect_ip))驱动,后续新增降级路径用例时只需加一行数据。

Checklist: [6.1] KISS/YAGNI:无投机性抽象;[P.G] 数据驱动测试用 pytest.mark.parametrize;[I] 同一功能用统一工具函数

@LLLLKKKK LLLLKKKK left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Code Review - PR #1335

Status: BLOCKING

Summary: P0/1 · P1/5 · P2/13 · P3/5

Reviewed: commit f400f0248a3f · 2026-08-26 21:38 UTC+8

Blocking Issues

P0

  • 无 package 的 worker_status proto 与引擎 model_rpc_service_pb2 符号重名,开关一开即 duplicate symbol 崩溃 @ rtp_llm/server/worker_status_proto/worker_status_service.proto:6
    • 建议:治本方案是删除该 vendored 目录,直接复用 rtp_llm.cpp.model_rpc.proto.model_rpc_service_pb2StatusVersionPB/WorkerStatusPBmodel_rpc_service_pb2_grpc.RpcServiceStub:定义与 wire 路径完全等价,utils/grpc_client_wrapper.py:12multimodal/vit_proxy_start_server.py:29 已在前端进程内消费,可一并消除下一条打包缺失与双份 stub 维护。注意不要用「给新 proto 加 package」来修——那会把路径变成 /<pkg>.RpcService/GetWorkerStatus,正好破坏该注释要保护的线上契约。若确需独立副本,则改用私有 descriptor_pool.DescriptorPool() 构建,并补一条「同进程内先后真实导入两份 _pb2」的共存回归用例把冲突钉死。

P1

  • server py_library 的 srcs glob 漏掉 worker_status_proto,wheel 与 runfiles 均缺该模块 @ rtp_llm/server/BUILD:5
    • 建议:在 glob 中补 "worker_status_proto/*.py";若采纳上一条改为复用引擎 pb2,则整体删除该目录与本条。更稳妥的做法是改用递归 glob(配合 exclude),从根上消除「新增子包忘记登记」这类缺陷。另建议新增一个不带 manual 标签、按真实包路径执行 import rtp_llm.server.kvcm_fallback 的最小用例,把打包完整性固化进 CI——当前唯一常态执行的新 target master_group_args_testspec_from_file_location 按文件路径加载,起不到这个作用。
  • master_kvcm_block_size 默认 0 与「必须为正」校验直接冲突,开关打开即启动期拉挂 @ rtp_llm/config/py_config_modules.py:394
    • 建议:建议直接从引擎已有的 seq_size_per_block 推导(在 BackendRPCServerVisitor 构造 MasterClient 时传下去),删掉这个必须手填、默认值又非法的独立参数;若因 KVCM namespace 约定必须可覆写,则至少把默认值改为合法值,并在 _create_kvcm_fallback_client 中补一条与 service_id 同风格的显式校验(错误信息带上 master_kvcm_block_size 参数名),同时在 help 文案中写明该参数为必填。
  • KVCM leader 解析在事件循环内持锁执行无 timeout 的同步 vipserver 请求,且失败无负缓存 @ rtp_llm/server/kvcm_fallback.py:569
    • 建议:把 bootstrap 解析移出事件循环:用 await loop.run_in_executor(None, ...) 包裹,或复用 HostService 已有的后台刷新线程持有 KVCM host 快照、协程只读快照;并为该 HTTP 调用补显式 timeout。同时引入失败负缓存与退避(记录 _leader_failed_at,窗口内直接抛错快速回落域名路由),给 _resolve_leader 加总时长预算,bootstrap 探测改为有界并发 + 首个成功即返回,避免 FlexLB 与 KVCM 同时不可用时路由吞吐被压到约 1/(N×timeout)——该阻塞恰在流量最紧张时刻发生。
  • 块→token 折算 stride 与实际发出键的 stride 无一致性校验,vLLM sha256 路径在生产不可达 @ rtp_llm/server/kvcm_fallback.py:370
    • 建议:对齐 FlexLB 语义:WorkerBlockHashConfigResolver:116-119 从存活 worker 上报的 blockSize/blockHashLookaheadTokens 动态解析,RequestBlockHashService:98-102 在自带 keys 时强校验 block_size > 0。建议优先从 WorkerStatusPB.block_size/block_hash_lookahead_tokens 读取折算 stride 与 lookahead(proto 字段已具备),或直接从引擎 seq_size_per_block 传入;无论哪种方案都应在 _create_kvcm_fallback_client 校验其与 seq_size_per_block 相等、不等即 fail-fast。同时删除生产不可达的 calculate_vllm_block_cache_keys 分支(keys 为空时直接返回 no_complete_blocks 不发 RPC),并修正 master_group_args.py:85 只提「vLLM sha256_cbor hashing」的 help 文案。
  • 三个新增 py_test 全部 manual 且用桩替换了本该守住阻塞缺陷的生产边界 @ rtp_llm/server/test/BUILD:35
    • 建议:至少去掉 master_client_fallback_testbackend_rpc_server_visitor_fallback_testmanualkvcm_fallback_test 可用 tags=["local"] 替代。补三类缺失覆盖:一是不注入 fallback client、由 _config() 提供完整 KVCM 字段真实构造 MasterClient 并断言生成的 KvcmFallbackConfig,配 service_id 缺失 / instance_id 缺失 / bootstrap_port 越界三个 assertRaises(ValueError);二是同进程共存加载两份 _pb2;三是 master_client 返回 connection_failed=True 时断言 route_ips 仍回落域名路由(当前 _MasterClient 永远成功,最关键降级分支无断言)。若确有环境限制须保留 manual,请在 BUILD 注释写明原因并挂进显式 test_suite

Non-blocking Suggestions

P2

  • 新开关被 host_service.service_available 前置门控静默旁路 @ rtp_llm/server/backend_rpc_server_visitor.py:324
    • 建议:若 KVCM 兜底被定位为「FlexLB 已配置但不可达」时的可用性兜底,请在参数 help 与 PR 描述中写明该前置约束;若期望它能独立承担选路,则应把 master_client.client_fallback_enabled 纳入 service_available 的计算,或在 enqueue 的门控处一并判断,并补一条覆盖该组合的用例。
  • KVCM 选中 worker 的 role 被丢弃并硬编码为 PREFILL,与打分层的 PDFUSION 支持不一致 @ rtp_llm/server/master_client.py:430
    • 建议:把 WorkerLoadSnapshot.role 透出到 KvcmFallbackResult,按真实角色映射为 RoleType.PREFILL/RoleType.PDFUSION 后再构造 RoleAddr;并在 get_master_route_addrs 接受 KVCM 结果前校验角色属于 self.backend_role_list,不属于则视为不可用并打 warning。补一条 PDFUSION 部署下的兜底选路用例,断言不会同时下发 PREFILL 与 PDFUSION 两条地址。
  • local_fallback_addr 在生产链路从不传入,本机保底候选不可达 @ rtp_llm/server/backend_rpc_server_visitor.py:141
    • 建议:二选一并保持自洽:其一,在 get_master_route_addrs 中构造本机(或 host_service 可解析的本组 worker)RoleAddr 传入,并补一条「视图层传参 → 候选池含本机 → KVCM 零命中仍可路由」的端到端用例;其二,先移除该形参、_try_kvcm_fallbacklocal_candidate 装配与 build_candidate_pool 的本机分支,同步修正 docstring,待真正接线时再引入,避免不可达代码掩盖能力缺口。
  • KVCM 兜底缺少专属指标,三个新增响应字段无任何生产消费方 @ rtp_llm/server/backend_rpc_server_visitor.py:162
    • 建议:在 :162:236 上报时带上 {"route_source": route_result.route_source} 维度(:148-152 已有 tag 用法),或新增独立的 KVCM 降级 QPS/命中率/时延/selection_reason 分布指标,使灰度与回滚有量化依据。把「master 缺失 + 兜底未命中且已有域名兜底」的日志按 connection_failed 降为 warning 或按窗口聚合,并补对应 error QPS;对 _parse_candidates:604-608 静默 continue 丢弃的非法候选补限流日志或计数。
  • gRPC channel 与选中时间字典只增不减,滚动发布后累积僵尸连接 @ rtp_llm/server/kvcm_fallback.py:510
    • 建议:给这四个 dict 加容量上限与 LRU/TTL 淘汰(淘汰时 await channel.close()),或在 _invalidate_leader 与 worker 探测连续失败时主动关闭并移除对应 channel/stub;_last_selected_ns 建议按 candidate_pool_size 的数倍设上限并按时间淘汰陈旧条目。
  • FlexLB body 级 8600 从硬失败改为静默 domain fallback,且不受 KVCM 开关保护 @ rtp_llm/server/master_client.py:631
    • 建议:若这是有意与非 200 分支(:495-497)对齐的修正,请在 PR 描述与 commit message 中明确声明该兼容性变更及灰度方案,并在该分支补一条 info/warning 级日志与错误码维度指标,使「FlexLB 主动要求降级」的发生量可观测;若不希望现网默认行为改变,则把该分支纳入 client_fallback_enabled 或独立开关之下,并补一条开关关闭态的用例锁定预期语义。
  • getattr 字面量访问造成默认值三处重复维护,并对强类型 GenerateInput 做猜测式访问 @ rtp_llm/server/master_client.py:156
    • 建议:以 MasterConfig 字段默认值为唯一来源:master_group_args.pyMasterConfig() 取默认值,master_client.py 改为直接读属性(该配置对象由本仓构造,属内部可信边界)。_input_ids_for_kvcm 直接使用 input.token_ids 并按 shape 判断维度,与 visitor:133-137 保持同一写法。若短期不便重构,请在 master_group_args_test 补一条遍历 MasterConfig() 全部 master_* 字段、校验每字段都有对应 argument 且 default 与字段初值相等的断言。
  • 手工签入 pb2/pb2_grpc 绕开构建期 codegen,格式不一致且与 .proto 无漂移校验 @ rtp_llm/server/worker_status_proto/worker_status_service_pb2_grpc.py:47
    • 建议:worker_status_proto 优先按第一条 finding 删除并复用引擎 stub。KVCM 侧二选一:由 Bazel 直接以权威 .proto 为唯一 src 在构建期生成 pb2,消除副本;或保留副本但把两个目录加入 .pre-commit-config.yaml 的 exclude、统一保留 protoc 原始输出以便与再生成结果比对,并补一条轻量测试锁定消息名、字段号与 ErrorCode/QueryType 枚举值集合,同时在目录内注明再生成命令与 grpcio-tools 版本。
  • 测试在模块导入期永久污染 sys.modules 且不恢复,含 torch 与整棵 rtp_llm 包树 @ rtp_llm/server/test/backend_rpc_server_visitor_fallback_test.py:146
    • 建议:统一收敛为一种守卫风格:改用 unittest.mock.patch.dict(sys.modules, ...) 在用例作用域内替换并自动回滚,或把伪造逻辑收进 setUpModule/tearDownModule(配合 addModuleCleanup)在结束时恢复原条目;至少参照同 PR 的 master_group_args_test.pytry/finally 还原。建议把这套 stub 装配抽到 test/ 下的共享 helper,避免三份实现继续分叉。
  • 测试自建影子生产类型已与生产漂移,契约变更不会被发现 @ rtp_llm/server/test/master_client_fallback_test.py:41
    • 建议:从生产模块导入 FlexlbResponse/RoleType/RoleAddr,只 stub 真正不可用的重依赖(torchrtp_llm.ops);_visitor() 改为走真实 __init__(必要时注入轻量 host_service/master_client),使 client_fallback_enabledmaster_config.master_client_fallback 推导的链路(master_client.py:143-145)被真正覆盖。并补齐「传入生产格式非空 block_cache_keys 时实际发往 KVCM 的 keys 与入参一致」、input_token_batched=Truerole_addrs 预置时开关被正确短路三个边界断言。
  • 测试从源码树重建包路径加载生产模块,绕开 Bazel 依赖图 @ rtp_llm/server/test/kvcm_fallback_test.py:12
    • 建议:去掉命名空间 shim,改为按真实包路径 from rtp_llm.server.kvcm_fallback import ...//rtp_llm/server:server 已把 server_args/*.pyutil.py 纳入 srcs),让 Bazel 依赖图与 BUILD 登记成为测试通过的前置条件;master_group_args_test 同样改为直接 from rtp_llm.server.server_args.master_group_args import init_master_group_args,必要时补 //rtp_llm:config_ops 依赖。
  • 并发上界断言使用测试自建 semaphore,生产装配路径零覆盖 @ rtp_llm/server/test/kvcm_fallback_test.py:250
    • 建议:改为构造多候选池后走公开入口断言:让 FakeMetaService 返回 4 个不同 host_ip_portHostCacheMatch(配各自 worker_status 端口),worker_status_concurrency=2,再断言 worker_service.max_active <= 2,使断言由生产侧 Semaphore(config.worker_status_concurrency) 承担,同时去掉对私有方法 _probe_worker 及其签名的隐式依赖。另补 leader 解析失败、GetHostCacheState 返回非 OK 与 SERVER_NOT_LEADER 的降级分支用例。
  • master_group_args_test 用假模块替换 str2bool,且未断言 type 与 default @ rtp_llm/server/server_args/test/master_group_args_test.py:29
    • 建议:去掉 sys.modules 顶替、直接 from rtp_llm.server.server_args.master_group_args import init_master_group_args,并补断言 arguments["--master_client_fallback"]["type"] is str2boolarguments["--master_kvcm_use_local"]["type"] is str2bool,以及全部 19 个参数的 defaultMasterConfig() 字段初值相等。建议再加一条 env 解析用例,断言 MASTER_CLIENT_FALLBACK=false 时开关确实为 False,把「默认关闭 + env 可正确关闭」这一兼容性承诺锁进用例。

P3

  • 路由可用性判断跨类重复,visitor 直接读取 MasterClient 内部开关 @ rtp_llm/server/backend_rpc_server_visitor.py:227
    • 建议:由 MasterClient 暴露单一 can_route()(或 is_available())方法封装全部准入条件,visitor 只依赖该方法,避免可用性判断在两处漂移;顺带便于为熔断态预留扩展点。
  • master_kvcm_use_local 场景下 service_id 中填写的端口被静默丢弃 @ rtp_llm/server/master_client.py:201
    • 建议:在 use_local 模式下优先使用 host.port(存在且合法时),仅在缺省时回落 bootstrap_port;或在解析出的 port 与 bootstrap_port 不一致时打一条明确 warning 说明取值来源。并在 help 文案中写清 use_local 模式下端口的实际取值规则。
  • token_ids 到 list 的 O(prompt_len) 转换在路由路径上重复执行两次 @ rtp_llm/server/backend_rpc_server_visitor.py:133
    • 建议:在 get_master_route_addrs 中把已转换好的 token_ids 列表随参数传给 get_backend_role_addrs(或缓存在请求上下文中),让 _try_kvcm_fallback 直接复用,删除 _input_ids_for_kvcm 中的二次 tolist() 与逐元素 int()
  • 回落 domain 路由的 warning 措辞与实际条件不符且为逐请求热路径噪声 @ rtp_llm/server/backend_rpc_server_visitor.py:240
    • 建议:改为结论式表述:先计算 reason = "batched_input" if input_token_batched else "no_master_addr_and_fallback_disabled",再打印 reason 与三个原始取值,并按窗口聚合或降为 debug,便于日志检索聚合且避免热路径噪声。
  • 测试脚手架存在未使用 import 与可参数化的重复用例体 @ rtp_llm/server/test/backend_rpc_server_visitor_fallback_test.py:1
    • 建议:删除未使用的 import asyncio;两个用例体用 subTest 或参数化表((enabled, expected_master_calls, expected_domain_calls, expected_ip))合并为一个,减少后续新增分支时的复制成本。

Checklist Findings (20 fail / 48 total)

General Principles Checklist

  • [6.1] Architecture — 依赖方向:无循环依赖/跨层惊喜 → issue 无 package 的 worker_status proto 与引擎 model_rpc_service_pb2 符号重名,开关一开即 duplicate symbol 崩溃
    该 proto 第 3-4 行注释为保住 /RpcService/GetWorkerStatus 而刻意不加 package,顶层定义 StatusVersionPB(6)、TaskInfoPB(11)、KvCacheGroupModePB(42)、WorkerStatusPB(48)、service RpcService(73)。rtp_llm/cpp/model_rpc/proto/model_rpc_service.proto 同样无 package,且在 342/390/439/445/654 行定义完全同名符号(其 RpcService 已含同签名 GetWorkerStatus)。worker_status_service_pb2.py:17_descriptor_pool.Default() 二次 AddSerializedFile 这批 FQN。backend_rpc_server_visitor.py:10model_rpc_client.py:10 先注册前者,同文件 :78 构造 `Maste
  • [6.1] Architecture — 兼容性:外部 HTTP/RPC API、持久数据、配置、环境迁移安全 → issue master_group_args_test 用假模块替换 str2bool,且未断言 type 与 default
    本次给 master_group_args.py:1 新增了模块级依赖 from rtp_llm.server.server_args.util import str2bool,而测试在第 29 行用 fake_util_module.str2bool = bool 顶替该模块并按文件路径加载被测模块:util.str2bool 若被改名或删除,用例仍全绿而生产在 import 期即失败。断言(105-114)也只覆盖 flag/env_name/bind_to,完全未断言 typedefault——若 --master_client_fallback 被误写成 type=boolbool("false") 为真,MASTER_CLIENT_FALLBACK=false 反而开启开关),本用例依然通过,而它塞进去的假件恰好就是 bool。这是本 PR 唯一进入 CI 常态执行的新 target。
  • [6.1] Architecture — 可观测性:日志/指标/超时可操作、非噪声 → issue 回落 domain 路由的 warning 措辞与实际条件不符且为逐请求热路径噪声
    :240-246 文案为 "master address: %s, client fallback enabled: %s, or input token batched: %s is not valid, fallback to domain routing",把三个状态并列后接单数 "is not valid"。该分支实际触发条件是 (master_addr 为空 且 开关关闭) or input_token_batched,既不指明究竟哪一项导致降级,排障还需人工反推布尔组合;且在 master 常态缺失的部署下按请求量刷屏。
  • [6.1] Architecture — 回滚路径:风险行为存在运维回滚手段 → issue KVCM 兜底缺少专属指标,三个新增响应字段无任何生产消费方
    全仓检索显示新增的 route_source/cache_match/kvcm_outcomemaster_client.py:47-49:436-438 填充)仅在 master_client.py 内部赋值与 master_client_fallback_test.py:191,228 断言,无任何生产读取方。:162 对 KVCM 兜底成功与 FlexLB 正常成功一律上报同一个无 tag 的 MASTER_ROUTE_QPS_METRIC,运维无法度量降级流量占比,也没有 KVCM 命中率/时延/失败原因指标。同时开关开启且 master_addr 为空时 MasterClient 不发 HTTP 直接返回 connection_failed=True:165route_logger.error 会按请求量刷 ERROR,而该分支并不上报 MASTER_ROUTE_ERROR_QPS_METRIC(仅 :148 异常路径上报),形成「日志洪水 + 指标空白」。
  • [6.1] Architecture — 状态不变量:创建/更新/失败/重试/回滚路径有效 → issue 测试在模块导入期永久污染 sys.modules 且不恢复,含 torch 与整棵 rtp_llm 包树
    第 146 行在模块作用域直接执行 _load_visitor_module(),该函数在 19-32 行无条件把 rtp_llmrtp_llm.serverrtp_llm.configrtp_llm.cpprtp_llm.metricsrtp_llm.utils 等 7 个真实包替换为空壳,并另有 12 处 _module(...)(92-133,含顶替 torchrtp_llm.ops),全程无 tearDownModule 恢复。master_client_fallback_test.py:12-20 同为无守卫写法,kvcm_fallback_test.py:13-20if "rtp_llm" not in sys.modules 守卫,同 PR 的 master_group_args_test.py:40-44 则用 try/finally 正确还原——同一问题三种风格。当前仅因 Bazel py_test 单进程隔离才不出事。
  • [6.1] Architecture — 错误语义:fail-fast/retry/fallback/silent 行为显式 → issue master_kvcm_use_local 场景下 service_id 中填写的端口被静默丢弃
    master_kvcm_use_local=TrueVipServerWrapper.__init__host_service.py:95-102)把 service_id 按逗号切分、再按 ip:port 解析成 Host(ip, port)。但 resolve_bootstrap_targets(201-206)只取 host.ip,端口一律用 bootstrap_port 拼接。因此运维按 help 文案(master_group_args.py:58,211「KVCM bootstrap service id or local IP:port list」「Treat service id as a comma-separated local IP:port list」)填写 10.0.0.1:7000 后,实际连接的是 10.0.0.1:6381,端口被静默忽略且无任何日志提示;若省略端口则 addr.split(":") 直接抛 ValueError。
  • [6.1] Quality — 无 per-forward 调试日志 / 噪声热路径输出 → issue 回落 domain 路由的 warning 措辞与实际条件不符且为逐请求热路径噪声
    :240-246 文案为 "master address: %s, client fallback enabled: %s, or input token batched: %s is not valid, fallback to domain routing",把三个状态并列后接单数 "is not valid"。该分支实际触发条件是 (master_addr 为空 且 开关关闭) or input_token_batched,既不指明究竟哪一项导致降级,排障还需人工反推布尔组合;且在 master 常态缺失的部署下按请求量刷屏。
  • [6.1] Quality — 逻辑变更未混入无关格式化 → issue 手工签入 pb2/pb2_grpc 绕开构建期 codegen,格式不一致且与 .proto 无漂移校验
    两个包均为手工签入的 DO NOT EDIT 产物,未复用仓内 rtp_llm/cpp/model_rpc/proto/BUILD 的构建期生成模式,protoc 版本与再生成命令均无记录(worker_status_service_pb2.py:4 标注 Protobuf Python Version 4.25.1,而 rtp_llm/BUILD:580,597 锁定 grpcio-tools 1.57.0 / protobuf 4.25)。.pre-commit-config.yaml:31 的全局 exclude 仅为 ^rtp_llm/ops|^3rdparty,两个目录都在 black 作用域内:kvcm_meta_service_pb2_grpc.py:17-26 已被 black 重排(双引号、4 空格续行),而本文件仍是 protoc 原始输出(单引号、8 空格续行,第 47 行行首残留一个空格),下次 run --all-files 会再次改写生成文件。
  • [6.1] Software Engineering — DIP:高层策略不依赖非必要具体细节 → issue 路由可用性判断跨类重复,visitor 直接读取 MasterClient 内部开关
    门控直接读 self.master_client.client_fallback_enabled(:227) 判断能否尝试 master 路由,而 MasterClient 内部真正的准入前置是「client_fallback_enabled 为真 _kvcm_fallback_client 非空」(master_client.py:148-149:342-343)。同一判断被拆到两个类:后续若 MasterClient 增加准入条件(如 KVCM 熔断态、客户端创建失败降级),visitor 侧门控会静默失配,请求先进入注定失败的路径再回落。
  • [6.1] Software Engineering — DRY:重复非平凡逻辑被抽取或显式复用 → issue 测试脚手架存在未使用 import 与可参数化的重复用例体
    第 1 行 import asyncio 全文件未使用(两个异步用例由 unittest.IsolatedAsyncioTestCase 驱动)。:211-231 的两个用例体高度重复:均为「构造 visitor → await route_ips → 断言 master_client.calls / domain_calls / 首个 addr 的 ip」,仅 client_fallback_enabled 与三组期望值不同。
  • [6.1] Software Engineering — KISS/YAGNI:无投机性抽象 → issue 测试脚手架存在未使用 import 与可参数化的重复用例体
    第 1 行 import asyncio 全文件未使用(两个异步用例由 unittest.IsolatedAsyncioTestCase 驱动)。:211-231 的两个用例体高度重复:均为「构造 visitor → await route_ips → 断言 master_client.calls / domain_calls / 首个 addr 的 ip」,仅 client_fallback_enabled 与三组期望值不同。
  • [6.1] Tests — 分布式/跨平台变更有对应覆盖 → issue KVCM 选中 worker 的 role 被丢弃并硬编码为 PREFILL,与打分层的 PDFUSION 支持不一致
    select_cache_affinity_firstkvcm_fallback.py:389)把 role 含 PREFILLPDFUSION 的 worker 都视为可用,worker_load_snapshot:355 也保存了真实 role,但 KvcmFallbackResult.selected 只回传不含角色的 KvcmCacheCandidate,角色信息在此丢失,第 430 行固定构造 RoleType.PREFILL。PDFUSION 部署下(backend_role_list=[PDFUSION],visitor:118-120):master 路由成功时 get_master_route_addrs 返回 None(visitor:163)使 :251allow_domain_fallback 恒为 True,:248need_domain_routing{PREFILL} 不覆盖 [PDFUSION] 亦为 True,于是再补一条域名 PDFUSION 地址,最终把角色标
  • [6.1] Tests — 新逻辑有聚焦单测 + 相关集成/smoke 测试 → issue master_group_args_test 用假模块替换 str2bool,且未断言 type 与 default
    本次给 master_group_args.py:1 新增了模块级依赖 from rtp_llm.server.server_args.util import str2bool,而测试在第 29 行用 fake_util_module.str2bool = bool 顶替该模块并按文件路径加载被测模块:util.str2bool 若被改名或删除,用例仍全绿而生产在 import 期即失败。断言(105-114)也只覆盖 flag/env_name/bind_to,完全未断言 typedefault——若 --master_client_fallback 被误写成 type=boolbool("false") 为真,MASTER_CLIENT_FALLBACK=false 反而开启开关),本用例依然通过,而它塞进去的假件恰好就是 bool。这是本 PR 唯一进入 CI 常态执行的新 target。
  • [6.1] Tests — 边界 case 覆盖(空、单元素、最大值) → issue master_kvcm_use_local 场景下 service_id 中填写的端口被静默丢弃
    master_kvcm_use_local=TrueVipServerWrapper.__init__host_service.py:95-102)把 service_id 按逗号切分、再按 ip:port 解析成 Host(ip, port)。但 resolve_bootstrap_targets(201-206)只取 host.ip,端口一律用 bootstrap_port 拼接。因此运维按 help 文案(master_group_args.py:58,211「KVCM bootstrap service id or local IP:port list」「Treat service id as a comma-separated local IP:port list」)填写 10.0.0.1:7000 后,实际连接的是 10.0.0.1:6381,端口被静默忽略且无任何日志提示;若省略端口则 addr.split(":") 直接抛 ValueError。

RTP-LLM Checklist

  • [I] 代码质量 — 同一功能用统一工具函数 → issue token_ids 到 list 的 O(prompt_len) 转换在路由路径上重复执行两次
    get_master_route_addrs:133-137 已把 input.token_ids 整条 tolist() 为 Python list 用于 get_block_cache_keys,但只把结果 keys 传给 MasterClient;进入 KVCM 兜底后 _input_ids_for_kvcm:320-331 又对同一 tensor 再做一次 tolist() 并逐元素 int() 转换。同一份 prompt 在同一请求内被完整物化两次,长 prompt 下是纯浪费;且第二次转换只在兜底路径需要,而兜底恰好发生在 FlexLB 故障、流量最紧张的时刻。本条未做基准测量,按可读性/DRY 问题定级。

Python Static-First Checklist

  • [P.A] 静态结构与类型纪律 — 禁止 getattr/setattr literal 访问 → issue getattr 字面量访问造成默认值三处重复维护,并对强类型 GenerateInput 做猜测式访问
    同一批默认值写了三遍:py_config_modules.py:389-411 字段初值、master_group_args.py:43-224default=、以及 master_client.py:156-278 约 19 处 getattr(self.master_config, "<字面量>", <默认值>) 的兜底值(如 bootstrap 端口 6381 三处并存),三份当前一致但无任何测试守卫。另 _input_ids_for_kvcm:315 先探 getattr(input, "input_ids", None),而 base_model_datatypes.py:53-55GenerateInput 只有 token_idsinput_idsGenerateOutput,:123),该首选分支在生产恒为 None;:320hasattr(raw_input_ids, "tolist") 亦为控制流分支。测试的 fake input 恰好带 input_ids,掩盖了这一点。
  • [P.A] 静态结构与类型纪律 — 禁止 hasattr 做控制流分支 → issue getattr 字面量访问造成默认值三处重复维护,并对强类型 GenerateInput 做猜测式访问
    同一批默认值写了三遍:py_config_modules.py:389-411 字段初值、master_group_args.py:43-224default=、以及 master_client.py:156-278 约 19 处 getattr(self.master_config, "<字面量>", <默认值>) 的兜底值(如 bootstrap 端口 6381 三处并存),三份当前一致但无任何测试守卫。另 _input_ids_for_kvcm:315 先探 getattr(input, "input_ids", None),而 base_model_datatypes.py:53-55GenerateInput 只有 token_idsinput_idsGenerateOutput,:123),该首选分支在生产恒为 None;:320hasattr(raw_input_ids, "tolist") 亦为控制流分支。测试的 fake input 恰好带 input_ids,掩盖了这一点。
  • [P.C] 并发与异步 — async def 中禁止 blocking 调用 → issue KVCM leader 解析在事件循环内持锁执行无 timeout 的同步 vipserver 请求,且失败无负缓存
    第 569 行 self._bootstrap_resolver() 是同步调用,且位于 async with self._leader_lock(564)临界区内。实参为 master_client.py:199-207resolve_bootstrap_targetsVipServerWrapper.get_hosts(refresh=True)get_host_list_by_domain_nowvipserver_proxy.py:106requests.get(...)——未设 timeout 且持有 threading.Lock。仓内既有代码刻意把同一调用隔离到 rtp_llm_master_addr_refresh 后台线程(host_service.py:220)。随后 571-578 行还在同一把锁内串行探测全部 bootstrap。且仅当 previous_leader 非空才在 589-591 行更新时间戳形成退避;从未成功过时第 592 行直接抛错、不记失败时间,leader 持续
  • [P.F] 语言陷阱 — 禁止模块级 import 副作用 → issue 测试在模块导入期永久污染 sys.modules 且不恢复,含 torch 与整棵 rtp_llm 包树
    第 146 行在模块作用域直接执行 _load_visitor_module(),该函数在 19-32 行无条件把 rtp_llmrtp_llm.serverrtp_llm.configrtp_llm.cpprtp_llm.metricsrtp_llm.utils 等 7 个真实包替换为空壳,并另有 12 处 _module(...)(92-133,含顶替 torchrtp_llm.ops),全程无 tearDownModule 恢复。master_client_fallback_test.py:12-20 同为无守卫写法,kvcm_fallback_test.py:13-20if "rtp_llm" not in sys.modules 守卫,同 PR 的 master_group_args_test.py:40-44 则用 try/finally 正确还原——同一问题三种风格。当前仅因 Bazel py_test 单进程隔离才不出事。
  • [P.G] 测试规范 — mock/fake/stub 不得替代本次声称覆盖的生产边界 → issue master_group_args_test 用假模块替换 str2bool,且未断言 type 与 default
    本次给 master_group_args.py:1 新增了模块级依赖 from rtp_llm.server.server_args.util import str2bool,而测试在第 29 行用 fake_util_module.str2bool = bool 顶替该模块并按文件路径加载被测模块:util.str2bool 若被改名或删除,用例仍全绿而生产在 import 期即失败。断言(105-114)也只覆盖 flag/env_name/bind_to,完全未断言 typedefault——若 --master_client_fallback 被误写成 type=boolbool("false") 为真,MASTER_CLIENT_FALLBACK=false 反而开启开关),本用例依然通过,而它塞进去的假件恰好就是 bool。这是本 PR 唯一进入 CI 常态执行的新 target。

Strengths

  • 兜底严格限定在「传输层可用性失败」语义:仅 resp.connection_failed 才查 KVCM(master_client.py:602),显式 FlexLB fallback(8600) 与业务/准入错误码保持原有含义,注释写明了这条边界,定位为可用性兜底而非调度改写。
  • 上一轮评审指出的「10 个新增 MasterConfig 字段无 CLI/env 绑定」已修复:19 个字段现在全部具备 --flag + env_name + bind_to 三元组,默认值与 MasterConfig 声明一致。
  • --master_client_fallback--master_kvcm_use_local 使用 type=str2boolmaster_group_args.py:47,209)而非 bool,规避了 MASTER_CLIENT_FALLBACK=false 反被解析为 True 的经典陷阱。
  • KvcmFallbackConfig.__post_init__ 集中校验正数、非负数、端口区间与浮点阈值,并显式排除 bool(避免 True1 通过整数校验),配置错误在构造期 fail-fast。
  • calculate_vllm_block_cache_keyskvcm_fallback.py:220-248)用定长 CBOR + sha256 精确复刻 vLLM/FlexLB 语义,含 NONE_HASH 种子、lookahead 与「丢弃末尾不完整块」,并有基于 hashlib 的 golden 向量固定,跨进程确定。
  • 默认值刻意保留旧语义:flexlb_transport_timeout_ms=0 时完全等价于原 ttft_timeout_msworker_grpc_port_override=0 时走 HTTP+1(与 host_service.py:574 的仓内约定一致),回滚只需关开关。
  • kvcm_meta_service.proto 保留 package kv_cache_manager.proto.meta,与 flexlb-grpc 侧字段号、枚举值逐项一致;生成代码用相对导入并配套 __init__.py,规避 protoc 绝对导入在包内失效的坑。
  • build_candidate_pool 把候选面限制为「本机 + 少量缓存热点」,WorkerStatus 探测配 asyncio.Semaphore 与独立 deadline,每次 RPC 均带 timeout=,避免兜底把单请求放大成全集群扫描或无界等待。
  • 纯函数分层清晰(build_candidate_pool/select_cache_affinity_first/effective_cache_blocks/worker_load_snapshot 均无 I/O),_split_ip_port/_format_target 正确处理 IPv6 方括号形式;kvcm_fallback_test.py 用真实 grpc.aio.server() 搭双服务端到端验证序列化与 channel 复用,127.0.0.1:0 动态端口、asyncSetUp/asyncTearDown 成对关闭。

// Keep this file package-less: the deployed engine serves
// /RpcService/GetWorkerStatus.

message StatusVersionPB {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P0] 无 package 的 worker_status proto 与引擎 model_rpc_service_pb2 符号重名,开关一开即 duplicate symbol 崩溃

该 proto 第 3-4 行注释为保住 /RpcService/GetWorkerStatus 而刻意不加 package,顶层定义 StatusVersionPB(6)、TaskInfoPB(11)、KvCacheGroupModePB(42)、WorkerStatusPB(48)、service RpcService(73)。rtp_llm/cpp/model_rpc/proto/model_rpc_service.proto 同样无 package,且在 342/390/439/445/654 行定义完全同名符号(其 RpcService 已含同签名 GetWorkerStatus)。worker_status_service_pb2.py:17_descriptor_pool.Default() 二次 AddSerializedFile 这批 FQN。backend_rpc_server_visitor.py:10model_rpc_client.py:10 先注册前者,同文件 :78 构造 `Ma...

建议: 治本方案是删除该 vendored 目录,直接复用 rtp_llm.cpp.model_rpc.proto.model_rpc_service_pb2StatusVersionPB/WorkerStatusPBmodel_rpc_service_pb2_grpc.RpcServiceStub:定义与 wire 路径完全等价,utils/grpc_client_wrapper.py:12multimodal/vit_proxy_start_server.py:29 已在前端进程内消费,可一并消除下一条打包缺失与双份 stub 维护。注意不要用「给新 proto 加 package」来修——那会把路径变成 /<pkg>.RpcService/GetWorkerStatus,正好破坏该注释要保护的线上契约。若确需独立副本,则改用私有 descriptor_pool.DescriptorPool() 构建,并补一条「同进程内先后真实导入两份 _pb2」的共存回归用例把冲突钉死。

Checklist: [6.1] 依赖方向:无循环依赖/跨层惊喜

Comment thread rtp_llm/server/BUILD
@@ -4,9 +4,12 @@ py_library(
name = "server",
srcs = glob([

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] server py_library 的 srcs glob 漏掉 worker_status_proto,wheel 与 runfiles 均缺该模块

py_library(name="server") 的 glob 为 ["*.py", "server_args/*.py", "kvcm_proto/*.py"],本次补了 kvcm_proto 却漏了同批新增的 worker_status_proto。全仓检索 worker_status_proto 仅命中 kvcm_fallback.py:22-25kvcm_fallback_test.py:30,33,无任何 BUILD 收录,目录下也无 BUILD,且不存在覆盖 server/**/*.py 的递归 glob。但被 "*.py" 收录的 kvcm_fallback.py 在模块顶层 import 该包。//rtp_llm/server:server 是 server 源码进入 runfiles 与 py_package 的唯一供源(rtp_llm/BUILD:417,491),故这 3 个文件两处皆缺,开关打开时会先于符号冲突抛 ModuleNotFoundError。

建议: 在 glob 中补 "worker_status_proto/*.py";若采纳上一条改为复用引擎 pb2,则整体删除该目录与本条。更稳妥的做法是改用递归 glob(配合 exclude),从根上消除「新增子包忘记登记」这类缺陷。另建议新增一个不带 manual 标签、按真实包路径执行 import rtp_llm.server.kvcm_fallback 的最小用例,把打包完整性固化进 CI——当前唯一常态执行的新 target master_group_args_testspec_from_file_location 按文件路径加载,起不到这个作用。

self.master_kvcm_bootstrap_port: int = 6381
# Exact KVCM instance_id/namespace, for example <prefill-deployment>_128.
self.master_kvcm_instance_id: str = ""
self.master_kvcm_block_size: int = 0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] master_kvcm_block_size 默认 0 与「必须为正」校验直接冲突,开关打开即启动期拉挂

master_kvcm_block_size 字段默认 0,master_group_args.py:84default=0master_client.py:165getattr(..., 0) 兜底同为 0;而 kvcm_fallback.py:59,68-69block_size 列入「必须为正」白名单,0 直接抛 ValueError("KVCM block_size must be positive")_create_kvcm_fallback_client 只为 service_id、instance_id、bootstrap_port 写了三条带参数名的显式校验(master_client.py:183-192),唯独 block_size 没有,只能落到 dataclass 的通用报错。因此按现有 help 文案配齐 service_id + instance_id 打开开关,前端进程即在 MasterClient.__init__ 抛异常、服务起不来。

建议: 建议直接从引擎已有的 seq_size_per_block 推导(在 BackendRPCServerVisitor 构造 MasterClient 时传下去),删掉这个必须手填、默认值又非法的独立参数;若因 KVCM namespace 约定必须可覆写,则至少把默认值改为合法值,并在 _create_kvcm_fallback_client 中补一条与 service_id 同风格的显式校验(错误信息带上 master_kvcm_block_size 参数名),同时在 help 文案中写明该参数为必填。

Comment thread rtp_llm/server/kvcm_fallback.py Outdated
p2p_hit_discount: float,
) -> int:
# Java Math.round for a non-negative value is floor(value + 0.5).
tokens = int(effective_cache_blocks(candidate, p2p_hit_discount) * block_size + 0.5)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 块→token 折算 stride 与实际发出键的 stride 无一致性校验,vLLM sha256 路径在生产不可达

生产链路 backend_rpc_server_visitor.py:138 传入的是 rtp_llm.ops.get_block_cache_keys,底层 blockUtil.cc:8hashInt64Vector 链式滚动哈希、stride 为引擎 seq_size_per_blockops/__init__.py:180-183 对长度 ≥ 一块的 prompt 必然返回非空,故 query_and_select:690-695 的 vLLM sha256 分支在生产恒不执行。但 _matched_tokens:370:736 用独立的 config.block_size 把命中块折算成 token,两个 stride 之间没有任何校验,不等时命中 token 整体缩放,使 cache_affinity_first_min_hit_ratemax_extra_work_tokens 判定全部失真且无报错。此外 lookahead_tokens/p2p_host_count 未从 `_create_kv...

建议: 对齐 FlexLB 语义:WorkerBlockHashConfigResolver:116-119 从存活 worker 上报的 blockSize/blockHashLookaheadTokens 动态解析,RequestBlockHashService:98-102 在自带 keys 时强校验 block_size > 0。建议优先从 WorkerStatusPB.block_size/block_hash_lookahead_tokens 读取折算 stride 与 lookahead(proto 字段已具备),或直接从引擎 seq_size_per_block 传入;无论哪种方案都应在 _create_kvcm_fallback_client 校验其与 seq_size_per_block 相等、不等即 fail-fast。同时删除生产不可达的 calculate_vllm_block_cache_keys 分支(keys 为空时直接返回 no_complete_blocks 不发 RPC),并修正 master_group_args.py:85 只提「vLLM sha256_cbor hashing」的 help 文案。

Comment thread rtp_llm/server/test/BUILD
"//rtp_llm:grpcio",
"//rtp_llm:protobuf",
],
tags = ["manual"],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 三个新增 py_test 全部 manual 且用桩替换了本该守住阻塞缺陷的生产边界

kvcm_fallback_test(:35)、master_client_fallback_test(:45)、backend_rpc_server_visitor_fallback_test(:54) 均带 tags=["manual"],Bazel 会从 //...:all 通配中排除;同包 vit_proxy_server_test 未加该标签(schedule_meta_test/host_service_test 带),故并非统一约定。后两个为纯进程内 fake、无网络无 GPU 依赖,kvcm_fallback_test 仅需 loopback。三者又恰好绕开本次三个阻塞边界:kvcm_fallback_test:24-34 从不加载 model_rpc_service_pb2(无法命中符号冲突)、backend_rpc_server_visitor_fallback_test:122-126MasterClient 换成 object、7 个 master_client 用例一律注入 `kvcm_...

建议: 至少去掉 master_client_fallback_testbackend_rpc_server_visitor_fallback_testmanualkvcm_fallback_test 可用 tags=["local"] 替代。补三类缺失覆盖:一是不注入 fallback client、由 _config() 提供完整 KVCM 字段真实构造 MasterClient 并断言生成的 KvcmFallbackConfig,配 service_id 缺失 / instance_id 缺失 / bootstrap_port 越界三个 assertRaises(ValueError);二是同进程共存加载两份 _pb2;三是 master_client 返回 connection_failed=True 时断言 route_ips 仍回落域名路由(当前 _MasterClient 永远成功,最关键降级分支无断言)。若确有环境限制须保留 manual,请在 BUILD 注释写明原因并挂进显式 test_suite

@LLLLKKKK LLLLKKKK left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Code Review - PR #1335 (non-blocking suggestions)

18 条 P2/P3 建议,不阻塞合并。阻塞判定与完整摘要见上一条 review。

self.master_client.client_fallback_enabled,
input_token_batched,
)
specified_roles = {addr.role for addr in input.generate_config.role_addrs}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

📍 实际位置 rtp_llm/server/backend_rpc_server_visitor.py:324(不在 diff 展示范围内,就近挂载)

[P2] 新开关被 host_service.service_available 前置门控静默旁路

enqueue(:324) 与 batch_enqueue(:335) 仅在 self.host_service.service_available 为真时才调用 route_ips,而该标志由 host_service.py:551 计算为 bool(master_vip.domain) or any(role_vip_map.values())——完全不感知新增的 master_client_fallback。因此在「不配 FlexLB master 域名、也不配 backend 角色域名,仅靠 KVCM 选路」的部署下,即使开关打开,route_ips 整段被跳过,新链路永不执行且无任何日志说明。新增的 visitor 测试直接调用 route_ips,绕过了这层门控,无法暴露该组合。

建议: 若 KVCM 兜底被定位为「FlexLB 已配置但不可达」时的可用性兜底,请在参数 help 与 PR 描述中写明该前置约束;若期望它能独立承担选路,则应把 master_client.client_fallback_enabled 纳入 service_available 的计算,或在 enqueue 的门控处一并判断,并补一条覆盖该组合的用例。

return FlexlbResponse.ok(
[
RoleAddr(
role=RoleType.PREFILL,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] KVCM 选中 worker 的 role 被丢弃并硬编码为 PREFILL,与打分层的 PDFUSION 支持不一致

select_cache_affinity_firstkvcm_fallback.py:389)把 role 含 PREFILLPDFUSION 的 worker 都视为可用,worker_load_snapshot:355 也保存了真实 role,但 KvcmFallbackResult.selected 只回传不含角色的 KvcmCacheCandidate,角色信息在此丢失,第 430 行固定构造 RoleType.PREFILL。PDFUSION 部署下(backend_role_list=[PDFUSION],visitor:118-120):master 路由成功时 get_master_route_addrs 返回 None(visitor:163)使 :251allow_domain_fallback 恒为 True,:248need_domain_routing{PREFILL} 不覆盖 [PDFUSION] 亦为 True,于是再补一条域名 PDFUSION 地址,最终把...

建议:WorkerLoadSnapshot.role 透出到 KvcmFallbackResult,按真实角色映射为 RoleType.PREFILL/RoleType.PDFUSION 后再构造 RoleAddr;并在 get_master_route_addrs 接受 KVCM 结果前校验角色属于 self.backend_role_list,不属于则视为不可用并打 warning。补一条 PDFUSION 部署下的兜底选路用例,断言不会同时下发 PREFILL 与 PDFUSION 两条地址。

Checklist: [6.1] 分布式/跨平台变更有对应覆盖

@@ -223,16 +223,25 @@ async def route_ips(self, input: GenerateInput):
input_token_batched = True

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

📍 实际位置 rtp_llm/server/backend_rpc_server_visitor.py:141(不在 diff 展示范围内,就近挂载)

[P2] local_fallback_addr 在生产链路从不传入,本机保底候选不可达

kvcm_fallback.py:3-4build_candidate_pool:286 均声明打分集合为「caller's local worker plus KVCM cache hits」「always retaining the local worker」,master_client.py:527 也新增了 local_fallback_addr 形参。但全仓该参数唯一传入点是 master_client_fallback_test.py:171;唯一生产调用方 get_master_route_addrs:141-145 只传 block_cache_keys/input/request_id。故线上 local_candidate 恒为 None,master_client.py:346build_candidate_pool:305-328 的本机合并分支恒不执行——KVCM 无正向命中时本机 worker 也不会被兜底考虑,而测试制造了该能力已生效的假象。

建议: 二选一并保持自洽:其一,在 get_master_route_addrs 中构造本机(或 host_service 可解析的本组 worker)RoleAddr 传入,并补一条「视图层传参 → 候选池含本机 → KVCM 零命中仍可路由」的端到端用例;其二,先移除该形参、_try_kvcm_fallbacklocal_candidate 装配与 build_candidate_pool 的本机分支,同步修正 docstring,待真正接线时再引入,避免不可达代码掩盖能力缺口。

@@ -223,16 +223,25 @@ async def route_ips(self, input: GenerateInput):
input_token_batched = True

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

📍 实际位置 rtp_llm/server/backend_rpc_server_visitor.py:162(不在 diff 展示范围内,就近挂载)

[P2] KVCM 兜底缺少专属指标,三个新增响应字段无任何生产消费方

全仓检索显示新增的 route_source/cache_match/kvcm_outcomemaster_client.py:47-49:436-438 填充)仅在 master_client.py 内部赋值与 master_client_fallback_test.py:191,228 断言,无任何生产读取方。:162 对 KVCM 兜底成功与 FlexLB 正常成功一律上报同一个无 tag 的 MASTER_ROUTE_QPS_METRIC,运维无法度量降级流量占比,也没有 KVCM 命中率/时延/失败原因指标。同时开关开启且 master_addr 为空时 MasterClient 不发 HTTP 直接返回 connection_failed=True:165route_logger.error 会按请求量刷 ERROR,而该分支并不上报 MASTER_ROUTE_ERROR_QPS_METRIC(仅 :148 异常路径上报),形成「日志洪水 + 指标空白」。

建议::162:236 上报时带上 {"route_source": route_result.route_source} 维度(:148-152 已有 tag 用法),或新增独立的 KVCM 降级 QPS/命中率/时延/selection_reason 分布指标,使灰度与回滚有量化依据。把「master 缺失 + 兜底未命中且已有域名兜底」的日志按 connection_failed 降为 warning 或按窗口聚合,并补对应 error QPS;对 _parse_candidates:604-608 静默 continue 丢弃的非法候选补限流日志或计数。

Checklist: [6.1] 回滚路径:风险行为存在运维回滚手段

) -> None:
self.config = config
self._bootstrap_resolver = bootstrap_resolver
self._channels: dict[str, grpc.aio.Channel] = {}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] gRPC channel 与选中时间字典只增不减,滚动发布后累积僵尸连接

_channels/_stubs/_worker_status_channels/_worker_status_stubs(510-513)与 _last_selected_ns(517)以 target 为键只做写入:_stub_for:523-536_worker_status_stub_for:544-557 未命中即新建并永久登记,query_and_select:746 也只写不清。唯一回收点是 close()(771-784),而 MasterClient.close() 全仓仅被测试调用、生产无调用方。_invalidate_leader:594-597 只清 _leader 字符串,不关闭对应 channel。前端进程长驻且 worker 会滚动替换,被 KVCM 返回过的每个历史 host 都会永久留下一个带 30s keepalive 的 grpc.aio.insecure_channel(持续对已下线 IP 发心跳),_last_selected_ns 同样无限增长。

建议: 给这四个 dict 加容量上限与 LRU/TTL 淘汰(淘汰时 await channel.close()),或在 _invalidate_leader 与 worker 探测连续失败时主动关闭并移除对应 channel/stub;_last_selected_ns 建议按 candidate_pool_size 的数倍设上限并按时间淘汰陈旧条目。

master_route_result: Optional[FlexlbResponse] = None
if not role_addrs_specified and master_addr and not input_token_batched:
can_attempt_master_route = bool(master_addr) or bool(
self.master_client.client_fallback_enabled

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P3] 路由可用性判断跨类重复,visitor 直接读取 MasterClient 内部开关

门控直接读 self.master_client.client_fallback_enabled(:227) 判断能否尝试 master 路由,而 MasterClient 内部真正的准入前置是「client_fallback_enabled 为真 _kvcm_fallback_client 非空」(master_client.py:148-149:342-343)。同一判断被拆到两个类:后续若 MasterClient 增加准入条件(如 KVCM 熔断态、客户端创建失败降级),visitor 侧门控会静默失配,请求先进入注定失败的路径再回落。

建议:MasterClient 暴露单一 can_route()(或 is_available())方法封装全部准入条件,visitor 只依赖该方法,避免可用性判断在两处漂移;顺带便于为熔断态预留扩展点。

Checklist: [6.1] DIP:高层策略不依赖非必要具体细节

Comment thread rtp_llm/server/master_client.py Outdated
@@ -223,16 +223,25 @@ async def route_ips(self, input: GenerateInput):
input_token_batched = True

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

📍 实际位置 rtp_llm/server/backend_rpc_server_visitor.py:133(不在 diff 展示范围内,就近挂载)

[P3] token_ids 到 list 的 O(prompt_len) 转换在路由路径上重复执行两次

get_master_route_addrs:133-137 已把 input.token_ids 整条 tolist() 为 Python list 用于 get_block_cache_keys,但只把结果 keys 传给 MasterClient;进入 KVCM 兜底后 _input_ids_for_kvcm:320-331 又对同一 tensor 再做一次 tolist() 并逐元素 int() 转换。同一份 prompt 在同一请求内被完整物化两次,长 prompt 下是纯浪费;且第二次转换只在兜底路径需要,而兜底恰好发生在 FlexLB 故障、流量最紧张的时刻。本条未做基准测量,按可读性/DRY 问题定级。

建议:get_master_route_addrs 中把已转换好的 token_ids 列表随参数传给 get_backend_role_addrs(或缓存在请求上下文中),让 _try_kvcm_fallback 直接复用,删除 _input_ids_for_kvcm 中的二次 tolist() 与逐元素 int()

Checklist: [I] 同一功能用统一工具函数

GaugeMetrics.MASTER_ROUTE_RT_METRIC, master_route_timer.cost_ms()
)
elif not role_addrs_specified:
route_logger.warning(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P3] 回落 domain 路由的 warning 措辞与实际条件不符且为逐请求热路径噪声

:240-246 文案为 "master address: %s, client fallback enabled: %s, or input token batched: %s is not valid, fallback to domain routing",把三个状态并列后接单数 "is not valid"。该分支实际触发条件是 (master_addr 为空 且 开关关闭) or input_token_batched,既不指明究竟哪一项导致降级,排障还需人工反推布尔组合;且在 master 常态缺失的部署下按请求量刷屏。

建议: 改为结论式表述:先计算 reason = "batched_input" if input_token_batched else "no_master_addr_and_fallback_disabled",再打印 reason 与三个原始取值,并按窗口聚合或降为 debug,便于日志检索聚合且避免热路径噪声。

Checklist: [6.1] 可观测性:日志/指标/超时可操作、非噪声;[6.1] 无 per-forward 调试日志 / 噪声热路径输出

@@ -0,0 +1,235 @@
import asyncio

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P3] 测试脚手架存在未使用 import 与可参数化的重复用例体

第 1 行 import asyncio 全文件未使用(两个异步用例由 unittest.IsolatedAsyncioTestCase 驱动)。:211-231 的两个用例体高度重复:均为「构造 visitor → await route_ips → 断言 master_client.calls / domain_calls / 首个 addr 的 ip」,仅 client_fallback_enabled 与三组期望值不同。

建议: 删除未使用的 import asyncio;两个用例体用 subTest 或参数化表((enabled, expected_master_calls, expected_domain_calls, expected_ip))合并为一个,减少后续新增分支时的复制成本。

Checklist: [6.1] DRY:重复非平凡逻辑被抽取或显式复用;[6.1] KISS/YAGNI:无投机性抽象

@LLLLKKKK LLLLKKKK left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Code Review - PR #1335

Status: BLOCKING

Summary: P0/2 · P1/3 · P2/14 · P3/6

Reviewed: commit 1b8cfbee4553 · 2026-08-27 04:20 UTC+8

Blocking Issues

P0

  • 无 package 的 worker_status proto 与引擎 model_rpc_service proto 根符号完全重名,开关一开即 duplicate symbol 崩溃 @ rtp_llm/server/worker_status_proto/worker_status_service.proto:3
    • 建议:优先删除整份副本,直接复用已 //visibility:public//rtp_llm/cpp/model_rpc/proto:model_rpc_service_py_proto,改用其 RpcServiceStub(wire path /RpcService/GetWorkerStatus 与新桩逐字一致,StatusVersionPB/WorkerStatusPB 也已导出),可一并消除重复 descriptor 与人工同步漂移。若确需独立副本,必须声明专属 package(如 rtp_llm.fallback.worker_status)或加载到私有 DescriptorPool 隔离——proto 线格式不含类型名,客户端以 channel.unary_unary("/RpcService/GetWorkerStatus", ...) 即可保持路径不变。无论哪种,都要补一条「同进程先 import 引擎 pb2 再 import kvcm_fallback」的非 manual 用例固化该不变量。
  • server py_library 的 srcs glob 漏掉 worker_status_proto,wheel 与 runfiles 均缺该模块 @ rtp_llm/server/BUILD:5
    • 建议:在 glob 中补上 "worker_status_proto/*.py",与同一 hunk 的 kvcm_proto/*.py 对称(不建议改 **/*.py,会误吞子包测试文件)。若采纳上一条建议复用引擎 stub,则该目录与本问题一并消失。同时增加一条非 manual 的最小 py_test,只做 import rtp_llm.server.kvcm_fallback 且走真实包路径(不伪造 rtp_llm 命名空间、不用 spec_from_file_location),让「新增子包未进 srcs」在构建/测试阶段而非上线时暴露。

P1

  • 三个新增兜底测试全部标记 manual,且伪造掉两个 P0 所在的生产边界,新特性 CI 零覆盖 @ rtp_llm/server/test/BUILD:35
    • 建议:master_client_fallback_test(已替换 _send_schedule_request)与 backend_rpc_server_visitor_fallback_test(纯 asyncio、无网络无 GPU)应直接去掉 manualkvcm_fallback_test 仅用本地环回 grpc,也适合作为常规 CPU 单测。若沙箱内 grpc.aio 端口确实不稳定,请把纯逻辑用例(VllmBlockHashTest、候选池/打分用例)拆到独立的非 manual target,而非整体排除,并在 BUILD 注释写明原因。同时补一条不伪造 ModelRpcClient/MasterClient 的用例:真实构造 MasterClient(master_config=<开关开启且三项必填已给值>) 并断言构造成功,可一次锁定符号注册与打包完整性两个不变量。
  • KVCM leader 解析失败无负缓存且被单锁串行,故障期逐请求放大延迟 @ rtp_llm/server/kvcm_fallback.py:641
    • 建议:失败路径同样写入时间戳(引入独立 _leader_failed_at + 指数退避),退避窗口内直接快速失败并交由上层走 domain 路由;_leader_lock 改为「单飞刷新 + 其余请求不等待直接返回失败」;并对整个 bootstrap 扫描加总时限或改为并发探测取首个成功者。leader_refresh_interval_ms 目前仅有 dataclass 默认值、无法从 server args 配置,建议一并暴露或在代码中注明为固定常量。
  • 兜底链路无整体时间预算,WorkerStatus 进程级信号量获取也无 deadline @ rtp_llm/server/kvcm_fallback.py:746
    • 建议:用 asyncio.wait_forquery_and_select 施加「本请求剩余 TTFT 预算」上限,超时即返回 None 让上层走 domain fallback;给信号量获取单独加 deadline(可复用 worker_status_timeout_ms,超时视为该候选探测失败);并在连续失败后引入短路/熔断(N 次连续失败后冷却窗口内跳过探测)。同时按预期兜底 QPS 上调 master_client_fallback_worker_status_concurrency 默认值或改为按请求限流,并在 help 中给出按 QPS 计算的推荐值。

Non-blocking Suggestions

P2

  • 兜底返回的 RoleAddr 硬编码为 PREFILL,与打分层的 PDFUSION 支持不一致 @ rtp_llm/server/master_client.py:501
    • 建议:用探测到的 WorkerStatusPB.role 反查 RoleType 后再构造 RoleAddr,无法映射的候选直接跳过;候选快照 resolver 按 backend_role_list 覆盖 PDFUSION 而非固定 RoleType.PREFILL。并补一条 PDFUSION 部署的用例,断言不会产出该部署不存在的角色地址。
  • block→token 折算所用 block_size 与引擎 seq_size_per_block 无一致性校验,且 help 文案与实际用途不符 @ rtp_llm/server/kvcm_fallback.py:442
    • 建议:让 master_kvcm_block_size 缺省时直接取引擎 seq_size_per_block(构造 BackendRPCServerVisitor 时已可得),降级为可选覆盖项;若必须显式配置,请在 _create_kvcm_fallback_client 中与 seq_size_per_block 比对,不一致时 fail-fast 并在错误信息中带上参数名与环境变量名。修正 help 文案为「块数↔token 折算步长;缺失时用于 vLLM sha256_cbor 回退哈希」。补一条以真实 get_block_cache_keys 输出为入参的用例(现有测试传任意整数 [1, 2],恰好掩盖该路径)。
  • FlexLB body 级 8600 由硬失败改为静默 domain fallback,且不受新开关保护 @ rtp_llm/server/master_client.py:702
    • 建议:若这是有意的语义修正,请在 PR 描述与 commit message 中明确说明,并把它与 KVCM 兜底拆成独立提交,便于单独回滚;若只希望在新开关下生效,请把该分支置于 client_fallback_enabled 门控内。无论哪种,都应补一条断言「body code=8600 → domain fallback 且不抛异常」的非 manual 用例,并为该转换加上可观测的计数或限频日志。
  • 兜底路径缺少专属指标,三个新增响应字段无生产消费方,且无 master 地址时逐请求打 ERROR @ rtp_llm/server/backend_rpc_server_visitor.py:162
    • 建议:在 get_master_route_addrs 成功分支读取 route_source/kvcm_outcome,为兜底单独上报带 route_source 标签的 QPS 与 RT,并增加 selection_reason 维度,使降级窗口可被告警与灰度观察、进而支撑开关回滚决策;把「无可用 master 地址」与「真实连接失败」在语义上区分(前者按 WARNING 或独立 outcome 限频记录),不要复用 connection_failed 的建连失败含义;_parse_candidates(kvcm_fallback.py:689-696) 丢弃格式不符或端口越界的候选时至少记一次聚合计数,避免 KVCM 返回域名时无声退化为纯冷路由。
  • flexlb_transport_timeout_ms 默认 0 使挂死场景兜底不可达,且开关关闭时也照样生效 @ rtp_llm/server/master_client.py:639
    • 建议:给该参数设置有界非零默认值(与 master_kvcm_request_timeout_ms=100 同量级的百毫秒级),或在开启 master_client_fallback 且该值为 0 时打印明确启动告警说明「兜底仅对连接级失败生效」;help 中补充 0 的含义;同时让该参数仅在 client_fallback_enabled 为真时生效,避免关闭开关时改变既有超时语义。并在 PR description 中给出推荐值与回滚方式。
  • 兜底必填配置项带永远非法的默认值,help 未标注且缺失项需逐轮启崩试错 @ rtp_llm/server/server_args/master_group_args.py:80
    • 建议:在三个 flag 的 help 中显式标注 required-when-master_client_fallback;在 init_master_group_args 调用点之后(或给 MasterConfig 增加校验钩子)对开关打开做一次联合校验,一次性汇总输出全部缺失字段清单;异常信息中带上 --master_kvcm_* / MASTER_KVCM_* 名称便于定位。block_size 按前述建议缺省取 seq_size_per_block,从必填项降级为可选覆盖项。
  • gRPC channel 与选择历史字典只增不减,滚动发布后累积僵尸连接 @ rtp_llm/server/kvcm_fallback.py:594
    • 建议:给两组 channel 字典加上按容量或空闲时长的淘汰(淘汰时 await channel.close()),_last_selected_ns 同样按容量截断,或只保留当前候选集合与最近一个 stale 窗口内的 target。
  • 兜底链路存在无消费方的死代码与无法配置的隐藏参数 @ rtp_llm/server/kvcm_fallback.py:394
    • 建议:删除 build_candidate_poolselect_max_local_affinitylocal_fallback_addr/local_candidate 参数链(连同 __all__ 条目),相关测试改为直接测 build_candidate_plan/select_cache_affinity_first。四项隐藏参数要么补上 flag,要么在代码中注明为固定常量;其中 p2p_host_count=0 与 FlexLB KvcmConfig.DEFAULT_P2P_HOST_COUNT 一致,但既已暴露 --master_client_fallback_p2p_hit_discount,建议在其 help 中说明「仅当 KVCM 返回 p2p 匹配时生效」,避免运维误以为该折扣当前已起作用。
  • 手工签入 pb2/pb2_grpc 绕开构建期 codegen,无漂移校验且格式风格分叉 @ rtp_llm/server/worker_status_proto/worker_status_service_pb2_grpc.py:1
    • 建议:优先按既有约定改为 Bazel 构建期生成(复用 generate_grpc_proto),入库仅保留 .proto。若因外部服务 proto 拷贝必须签入,请补充固定 grpcio-tools 版本的再生成命令、在 CI 增加「重新生成后 diff 为空」的 drift 检查,并在文件头注明 canonical 来源路径与同步要求;同时二选一统一格式策略:把这两个生成目录加入 pre-commit exclude 以保持与生成器逐字节一致,或在再生成流程中固定「生成后立即 black」。
  • 测试在模块导入期永久污染 sys.modules 且不恢复,影子生产类型已与生产漂移 @ rtp_llm/server/test/backend_rpc_server_visitor_fallback_test.py:146
    • 建议:抽出共享测试辅助模块(如 rtp_llm/server/test/fake_rtp_namespace.py),统一提供「注入并在退出时恢复」的 context manager 供三份测试复用;把替身安装移入 setUpModule/tearDownModuleunittest.mock.patch.dict(sys.modules, ...) 限定作用域,避免模块导入期的不可逆全局副作用。同时删除影子 FlexlbResponse,直接从生产模块导入(master_client_fallback_test.py:66-72 已证明该模块可独立加载),把契约钉在生产类上。
  • master_group_args_test 绑定空 SimpleNamespace 并 stub str2bool,断言退化为字面量自证 @ rtp_llm/server/server_args/test/master_group_args_test.py:29
    • 建议:改为数据驱动:以真实 MasterConfig() 实例作为 master_config 传入,遍历其全部 master_* 属性,断言每个属性都存在同名 flag、bind_to 目标 hasattr 为真、且 arguments[flag]["default"] == getattr(MasterConfig(), field);引入真实 str2bool 并断言布尔类参数 type is str2bool,同时删除抄写的字面量清单。更彻底的做法是参考同目录 server_args_test.py 走真实 setup_args(),断言 env 与命令行两条注入路径下 master_config.<field> 的最终取值。
  • 对 MasterConfig 全量使用 getattr 字面量访问,默认值在三处重复维护 @ rtp_llm/server/master_client.py:161
    • 建议:以 MasterConfig 为单一事实来源:master_group_args.pydefault= 直接引用传入的 master_config 实例属性,master_client.py 改用直接属性访问;_input_ids_for_kvcm 改为按 GenerateInput 的实际字段名直接取值。测试改为数据驱动:遍历 MasterConfig() 的全部 master_* 属性,断言每个属性都有同名 flag 且 default 与实例初值一致。
  • 新增的 VipHostSnapshot 后台刷新状态机没有任何测试覆盖 @ rtp_llm/server/host_service.py:125
    • 建议:新增一个针对 VipHostSnapshot 的非 manual py_test(注入假 VipServerWrapper 与可控时钟,直接调 refresh_now() 驱动而不依赖真实线程时序,避免 sleep 类不稳定测试),至少覆盖:首次构造复用初始 hosts、刷新抛异常但未过期时保留旧快照、超过 stale 阈值后返回空、按 (ip, port) 去重、close() 后线程退出且幂等。
  • 门槛放宽后的关键分支未覆盖,包括兜底失败回落 domain 与批量输入 @ rtp_llm/server/test/backend_rpc_server_visitor_fallback_test.py:210
    • 建议:补四个用例(可用 subTest 参数化「兜底返回态」避免复制):FlexlbResponse(role_addrs=None, connection_failed=True) 时断言 host_service.domain_calls == 1 且不抛异常;构造 shape=(2,4)size(0)==2 的 token_ids 断言 master_client.calls == 0role_addrs 已指定时断言不调用 master_client;backend_role_list=[PREFILL, DECODE] 而 master 仅返回 PREFILL 时断言 domain 补齐 DECODE。

P3

  • 路由准入判定跨类重复,visitor 直接读取 MasterClient 内部开关 @ rtp_llm/server/backend_rpc_server_visitor.py:227
    • 建议:在 MasterClient 上暴露语义化方法(如 can_attempt_route()),内部聚合 master_addrslave_addr_kvcm_fallback_client 是否就绪返回布尔值;route_ips 只调用该方法,使准入条件只有一个权威定义点,新增兜底通道时也无需改动调用方。
  • token_ids 到 list 的 O(prompt_len) 转换在路由路径上重复执行两次 @ rtp_llm/server/backend_rpc_server_visitor.py:133
    • 建议:把 :133-137 已算好的 token_ids 列表随 block_cache_keys 一并传入 get_backend_role_addrs/_try_kvcm_fallback,删除 _input_ids_for_kvcm 中的重复转换(其 hasattr/getattr 猜测式访问也可一并去掉,GenerateInput 是强类型数据类)。
  • 并发上界为单边断言,无法区分正确限流与并发彻底失效 @ rtp_llm/server/test/kvcm_fallback_test.py:280
    • 建议:把断言改为双边,例如 assertEqual(2, self.worker_service.max_active)(配合足够的 delay_s 保证观测窗口),或至少加上 assertGreater(self.worker_service.max_active, 1),使限流退化为串行时能被检出。
  • master_kvcm_use_local 场景下 service_id 中填写的端口被静默丢弃 @ rtp_llm/server/master_client.py:213
    • 建议:use_local 分支优先使用解析出的 host.port,仅当其缺失或非法时回退 bootstrap_port;或在 help 中明确「IP:port 列表中的端口会被 --master_kvcm_bootstrap_port 覆盖」,并在构造 target 时对被替换的端口打印一次启动日志。
  • 新开关被 host_service.service_available 前置门控静默旁路 @ rtp_llm/server/backend_rpc_server_visitor.py:324
    • 建议:在 master_client_fallback 的 help 中说明「需至少配置一个 VIP 域名,否则路由入口不会被触发」;或在启动时若开关打开而 service_available 为 False 则打印一次明确 warning。
  • 测试文件存在未使用的 import @ rtp_llm/server/test/backend_rpc_server_visitor_fallback_test.py:1
    • 建议:删除 import asyncio

Checklist Findings (24 fail / 48 total)

General Principles Checklist

  • [6.1] Architecture — 依赖方向:无循环依赖/跨层惊喜 → issue 无 package 的 worker_status proto 与引擎 model_rpc_service proto 根符号完全重名,开关一开即 duplicate symbol 崩溃
    该文件注释写明「Keep this file package-less」,顶层定义 StatusVersionPB/TaskInfoPB/WorkerStatusPB/KvCacheGroupModePBservice RpcService(:73)。已用 ^package 检索确认 model_rpc_service.proto 全文无 package,且在 342/390/439/445/654 行定义完全同名符号。worker_status_service_pb2.py:17 的序列化描述符中符号均无命名空间前缀(.TaskInfoPB.KvCacheGroupModePB),并向 _descriptor_pool.Default() 执行 AddSerializedFile。同进程链路:backend_rpc_server_visitor.py:10model_rpc_client.py 注册第一份,同文件 :78 构造 MasterClient,开关开启时 `master_client.py:149-1
  • [6.1] Architecture — 兼容性:外部 HTTP/RPC API、持久数据、配置、环境迁移安全 → issue master_group_args_test 绑定空 SimpleNamespace 并 stub str2bool,断言退化为字面量自证
    _load_init_master_group_args(:25-44) 把 util 整体替换为 str2bool = bool 的假模块,测试又未断言 type,因此 MASTER_CLIENT_FALLBACK=falsebool() 误判为 True 这一典型环境变量陷阱测不出来。master_config = SimpleNamespace()(:50) 是空对象,bind_to 只被记录从未回写,:117-120 的断言只是在比对测试里抄写的 15 条字符串与 master_group_args.py 中的同名字面量,无法发现「绑定到 MasterConfig 上不存在的字段」。default 也未与 MasterConfig() 初值比对;唯一负向断言 assertNotIn("--master_kvcm_fallback_enabled")(:126) 针对一个从未存在的名字。这是本 PR 唯一进 CI 的新测试。
  • [6.1] Architecture — 分层边界:新概念在正确层级,不泄漏内部 → issue 路由准入判定跨类重复,visitor 直接读取 MasterClient 内部开关
    :226-228:244 让 visitor 直接读 self.master_client.client_fallback_enabled 决定「能否尝试 master 路由」,而 _try_kvcm_fallback 内部已用 if not self.client_fallback_enabled or self._kvcm_fallback_client is None: return None(master_client.py:426-427) 做过同类判断。同一准入语义在两层重复:后续若演进为「开关打开 + 客户端已就绪 + 未熔断」,两处必须同步修改,漏改一处即行为不一致;高层路由策略也因此依赖了 MasterClient 的实现细节。该属性还成了每请求读取的公开契约,一旦改名或延迟初始化即产生每请求 AttributeError,而当前无任何 CI 用例守护。
  • [6.1] Architecture — 可观测性:日志/指标/超时可操作、非噪声 → issue token_ids 到 list 的 O(prompt_len) 转换在路由路径上重复执行两次
    get_master_route_addrs:133-137 已对 input.token_ids 执行一次 tolist() 用于 get_block_cache_keys,随后 _try_kvcm_fallback 又通过 _input_ids_for_kvcmmaster_client.py:404-405)对同一张量再次 tolist(),并在 :415 逐元素 int() 转换。开关开启后每个走兜底的请求都要在路由路径上重复一次 O(prompt_len) 的张量转列表与整型转换。未做基准测量,故仅记为可零成本消除的冗余计算。
  • [6.1] Architecture — 回滚路径:风险行为存在运维回滚手段 → issue 新开关被 host_service.service_available 前置门控静默旁路
    enqueue(:324) 与 batch_enqueue(:335) 均以 if self.host_service.service_available: 门控 route_ips。该字段在 host_service.py:660-662 定义为 bool(self.master_vip.domain) or any(self.role_vip_map.values())。因此当运维未配置任何 master 域名与角色域名、期望纯靠 KVCM 客户端兜底选点(KVCM 热候选并不依赖 role VIP)时,route_ips 根本不会被调用,master_client_fallback=True 被静默忽略且无任何日志提示。该配置组合较为边缘,故仅记为语义说明缺口。
  • [6.1] Architecture — 状态不变量:创建/更新/失败/重试/回滚路径有效 → issue 新增的 VipHostSnapshot 后台刷新状态机没有任何测试覆盖
    本 PR 新增 VipHostSnapshot(:125):构造期起 daemon 线程(:157-163)、_publish(:180-183) 对空结果提前 return 以保留 last-known-good、refresh_now(:188) 在超过 stale_timeout_seconds 后清空快照(:208-214)、get_hosts(:221) 再做一次 stale 判定、close(:232) 负责 join。它是兜底候选池与 KVCM bootstrap target 的共同数据源(master_client.py:217-231 直接依赖),docstring 明确承诺「保留上一个非空值直到过期」。但三个新增测试文件无一覆盖它,rtp_llm/server/test/BUILD 也未为它新增 target,host_service_test.py 不在本次改动范围且其 target 亦为 manual。
  • [6.1] Architecture — 错误语义:fail-fast/retry/fallback/silent 行为显式 → issue 新开关被 host_service.service_available 前置门控静默旁路
    enqueue(:324) 与 batch_enqueue(:335) 均以 if self.host_service.service_available: 门控 route_ips。该字段在 host_service.py:660-662 定义为 bool(self.master_vip.domain) or any(self.role_vip_map.values())。因此当运维未配置任何 master 域名与角色域名、期望纯靠 KVCM 客户端兜底选点(KVCM 热候选并不依赖 role VIP)时,route_ips 根本不会被调用,master_client_fallback=True 被静默忽略且无任何日志提示。该配置组合较为边缘,故仅记为语义说明缺口。
  • [6.1] Quality — Commit 原子、message 与行为匹配 → issue FlexLB body 级 8600 由硬失败改为静默 domain fallback,且不受新开关保护
    已核对本 PR 的 diff:if code == FALLBACK_ERROR_CODE: return FlexlbResponse.fallback_response() 为新增行。改动前 HTTP 200 且 body code=8600 会走到 :704-715——ExceptionType(8600) 抛 ValueError(exceptions.py:78 确认 8600 非枚举成员,仅有 MASTER_NO_AVAILABLE_WORKER = 8400)→ 降级为 MASTER_NO_AVAILABLE_WORKERraise FtRuntimeException,请求显式失败。改动后返回 fallback 响应,使 backend_rpc_server_visitor.py:251-253allow_domain_fallback 成立并静默改走 domain 路由。该分支不在 client_fallback_enabled 门控之内,因此所有部署(含未开启新开关者)的错误语义都被改变,PR 描述未说明该
  • [6.1] Quality — PR description 说明动机与设计 → issue 兜底必填配置项带永远非法的默认值,help 未标注且缺失项需逐轮启崩试错
    --master_kvcm_block_size 默认 0(py_config_modules.py:394),而 KvcmFallbackConfig.__post_init__kvcm_fallback.py:63-75)对 block_sizemust be positive 校验,即默认值恒为非法;--master_kvcm_service_id(:53) 与 --master_kvcm_instance_id(:71) 默认空串,同样在 master_client.py:202-209 抛 ValueError。三者 help 文案均未标注「启用 master_client_fallback 时必填」,parser 层无交叉校验;且 :202-211 是逐条 raise,运维只能靠把进程启崩、每轮读一个异常信息来试错。异常文案 KVCM block_size must be positive 也未带对应 flag/env 名。
  • [6.1] Quality — 无 per-forward 调试日志 / 噪声热路径输出 → issue 兜底路径缺少专属指标,三个新增响应字段无生产消费方,且无 master 地址时逐请求打 ERROR
    全仓 py 检索确认 route_source/kvcm_outcome/cache_match 仅在 master_client.py 内被赋值传递,并只被 master_client_fallback_test.py:189,226 断言,无任何 kmonitor 上报。兜底成功后 route_result.is_ok 为真,:155-163 照旧上报无 tag 的 MASTER_ROUTE_QPS_METRIC,兜底的 KVCM 查询 + 探测耗时也被折叠进 :236-238MASTER_ROUTE_RT_METRIC。另一侧:开关开且 master_addr 为空时不发任何 HTTP,master_client.py:655 直接以 connection_failed_response() 起始,兜底未命中仍带 connection_failed=True,导致 :165 每请求打一条 ERROR(改动前该场景仅 :240 一条 WARNING)。运维因此无法区分「FlexLB 正常」与「全靠兜底」。
  • [6.1] Quality — 逻辑变更未混入无关格式化 → issue 测试文件存在未使用的 import
    第 1 行 import asyncio,但全文未出现任何 asyncio. 引用(异步用例由 unittest.IsolatedAsyncioTestCase 驱动)。仓库 flake8 默认启用 F401,未使用 import 会在 pre-commit / lint 阶段报错,属可零成本消除的噪声。
  • [6.1] Software Engineering — DIP:高层策略不依赖非必要具体细节 → issue 路由准入判定跨类重复,visitor 直接读取 MasterClient 内部开关
    :226-228:244 让 visitor 直接读 self.master_client.client_fallback_enabled 决定「能否尝试 master 路由」,而 _try_kvcm_fallback 内部已用 if not self.client_fallback_enabled or self._kvcm_fallback_client is None: return None(master_client.py:426-427) 做过同类判断。同一准入语义在两层重复:后续若演进为「开关打开 + 客户端已就绪 + 未熔断」,两处必须同步修改,漏改一处即行为不一致;高层路由策略也因此依赖了 MasterClient 的实现细节。该属性还成了每请求读取的公开契约,一旦改名或延迟初始化即产生每请求 AttributeError,而当前无任何 CI 用例守护。
  • [6.1] Software Engineering — DRY:重复非平凡逻辑被抽取或显式复用 → issue token_ids 到 list 的 O(prompt_len) 转换在路由路径上重复执行两次
    get_master_route_addrs:133-137 已对 input.token_ids 执行一次 tolist() 用于 get_block_cache_keys,随后 _try_kvcm_fallback 又通过 _input_ids_for_kvcmmaster_client.py:404-405)对同一张量再次 tolist(),并在 :415 逐元素 int() 转换。开关开启后每个走兜底的请求都要在路由路径上重复一次 O(prompt_len) 的张量转列表与整型转换。未做基准测量,故仅记为可零成本消除的冗余计算。
  • [6.1] Software Engineering — ISP:调用方不依赖无关大接口 → issue 兜底链路存在无消费方的死代码与无法配置的隐藏参数
    全仓 py 检索确认 build_candidate_pool(:394) 与 select_max_local_affinity(:286) 只出现在定义处与 __all__(:923,928),零调用方(连测试都没有),却被注释为「compatibility helper」——而该模块是本 PR 全新引入,不存在需兼容的历史调用方。local_fallback_addrget_backend_role_addrs(master_client.py:598) 一路透传到 _try_kvcm_fallback 后被 del 丢弃(:431),唯一生产调用方 backend_rpc_server_visitor.py:141-145 并未传该参数,query_and_selectlocal_candidate 因此永远为 None。p2p_host_countlookahead_tokensminimum_local_blocksleader_refresh_interval_ms 四项均无对应 CLI
  • [6.1] Software Engineering — KISS/YAGNI:无投机性抽象 → issue 兜底链路存在无消费方的死代码与无法配置的隐藏参数
    全仓 py 检索确认 build_candidate_pool(:394) 与 select_max_local_affinity(:286) 只出现在定义处与 __all__(:923,928),零调用方(连测试都没有),却被注释为「compatibility helper」——而该模块是本 PR 全新引入,不存在需兼容的历史调用方。local_fallback_addrget_backend_role_addrs(master_client.py:598) 一路透传到 _try_kvcm_fallback 后被 del 丢弃(:431),唯一生产调用方 backend_rpc_server_visitor.py:141-145 并未传该参数,query_and_selectlocal_candidate 因此永远为 None。p2p_host_countlookahead_tokensminimum_local_blocksleader_refresh_interval_ms 四项均无对应 CLI
  • [6.1] Tests — 分布式/跨平台变更有对应覆盖 → issue 兜底返回的 RoleAddr 硬编码为 PREFILL,与打分层的 PDFUSION 支持不一致
    select_cache_affinity_first(kvcm_fallback.py:461) 明确接受 role 含 PREFILLPDFUSION 的 worker,但 _try_kvcm_fallback 恒返回 role=RoleType.PREFILL,候选快照也固定 enable_role_snapshot(RoleType.PREFILL)(master_client.py:325) 与 get_snapshot(RoleType.PREFILL)(:333)。PDFUSION 部署下 enable_role_snapshotrole_vip_map 无 PREFILL 直接返回 False(host_service.py:712-714),冷候选为空;被选中的 PDFUSION worker 仍被标为 PREFILL,backend_rpc_server_visitor.py:248need_domain_routingbackend_role_list=[PDFUSION] 不被
  • [6.1] Tests — 新逻辑有聚焦单测 + 相关集成/smoke 测试 → issue 门槛放宽后的关键分支未覆盖,包括兜底失败回落 domain 与批量输入
    两个用例只覆盖「开关开 + 兜底成功」(:211) 与「开关关 + 直接 domain」(:223),实际只验证了 can_attempt_master_route 这一布尔门控。生产门控为 not role_addrs_specified and can_attempt_master_route and not input_token_batched(backend_rpc_server_visitor.py:229-233),而测试的 _TokenIds.size 恒返回 1(:152-154)、role_addrs 恒为空(:206)、get_master_addr 恒返回 None(:170)。因此「批量输入时即使开关开也必须跳过 master 路由」「role_addrs 已指定时不得调用 master_client」「返回 connection_failed=True/fallback=True 时必须回落 domain 且不抛 ROUTE_ERROR」「backend_role_list 含 DECODE 而 master 仅返
  • [6.1] Tests — 边界 case 覆盖(空、单元素、最大值) → issue master_kvcm_use_local 场景下 service_id 中填写的端口被静默丢弃
    --master_kvcm_service_id 的 help 写「KVCM bootstrap service id or local IP:port list」,master_kvcm_use_local=TrueVipServerWrapper.__init__host_service.py:95-102)确实按 ip:port 逐项解析为 Host(ip, port)。但 resolve_bootstrap_targetsmaster_client.py:223-231)只读 host.ip,端口一律改用 bootstrap_port(默认 6381),用户在 service_id 中写的端口被静默忽略。本地联调若填 127.0.0.1:7000 而未同时设置 --master_kvcm_bootstrap_port,会连到一个未监听的端口,且无任何日志提示端口被替换。

RTP-LLM Checklist

  • [I] 代码质量 — 同一功能用统一工具函数 → issue 手工签入 pb2/pb2_grpc 绕开构建期 codegen,无漂移校验且格式风格分叉
    仓库既有约定是构建期生成:rtp_llm/cpp/model_rpc/proto/BUILD:21-42generate_grpc_proto 产出并以 public 暴露 model_rpc_service_py_proto.gitignore:45-46 明确忽略生成的 model_rpc_service_pb2(_grpc).py。本 PR 反向签入四份标注 DO NOT EDIT! 的生成物,既无 BUILD 生成规则,也无再生成脚本或 grpcio-tools 版本说明,.proto 与桩之间无一致性校验;kvcm_meta_service.proto:5-6 还要求与 FlexLB Java 侧 proto 手工同步。两份桩风格已分叉:kvcm_meta_service_pb2_grpc.py:17-26 是 black 化结果(双引号、4 空格、尾随逗号),worker_status_service_pb2_grpc.py:17-21 保留 protoc 原始单引号输出,而 pre-commit 的 black exclud

Python Static-First Checklist

  • [P.A] 静态结构与类型纪律 — 禁止 getattr/setattr literal 访问 → issue 对 MasterConfig 全量使用 getattr 字面量访问,默认值在三处重复维护
    _create_kvcm_fallback_client 中 20 余处均为 getattr(self.master_config, "字面量", 默认值)(:161-308),同一批默认值同时写在 py_config_modules.py:389-414 的字段初值与 master_group_args.py:44-254 的 argparse default= 中,共三份。MasterConfig 已保证字段存在,getattr 兜底既掩盖拼写错误又制造第三份默认值来源;_input_ids_for_kvcm:399-404 还对强类型 GenerateInputgetattr("input_ids"/"token_ids", None)hasattr("tolist") 猜测式访问。而 master_group_args_test 只断言 env_name/bind_to,不断言 default,任一处漂移都不会被发现。
  • [P.A] 静态结构与类型纪律 — 禁止 hasattr 做控制流分支 → issue 对 MasterConfig 全量使用 getattr 字面量访问,默认值在三处重复维护
    _create_kvcm_fallback_client 中 20 余处均为 getattr(self.master_config, "字面量", 默认值)(:161-308),同一批默认值同时写在 py_config_modules.py:389-414 的字段初值与 master_group_args.py:44-254 的 argparse default= 中,共三份。MasterConfig 已保证字段存在,getattr 兜底既掩盖拼写错误又制造第三份默认值来源;_input_ids_for_kvcm:399-404 还对强类型 GenerateInputgetattr("input_ids"/"token_ids", None)hasattr("tolist") 猜测式访问。而 master_group_args_test 只断言 env_name/bind_to,不断言 default,任一处漂移都不会被发现。
  • [P.B] 错误处理 — 资源获取用 with context manager → issue gRPC channel 与选择历史字典只增不减,滚动发布后累积僵尸连接
    _channels/_stubs(:584-585) 按 bootstrap target 缓存,_worker_status_channels/_worker_status_stubs(:586-587) 按 worker status target 缓存,_last_selected_ns(:594) 按 route_target 只写不删累积;前四者仅在 close()(:897-910) 中清理,_last_selected_nsclose() 都不清理,全部无 LRU 或 TTL 淘汰。VIP 扩缩容、worker 迁移或滚动升级导致 IP 变化后,旧 target 的 grpc.aio channel 仍保留并按 grpc.keepalive_time_ms=30_000(:606,627) 持续心跳,长期运行的前端进程会持续累积连接与内存。
  • [P.F] 语言陷阱 — 禁止模块级 import 副作用 → issue 测试在模块导入期永久污染 sys.modules 且不恢复,影子生产类型已与生产漂移
    :146 在模块顶层执行 _load_visitor_module(),把 rtp_llmrtp_llm.serverrtp_llm.configrtp_llm.cpprtp_llm.metricsrtp_llm.utils(:21-32) 以及 torch(:92)、rtp_llm.ops(:111)、rtp_llm.cpp.model_rpc.model_rpc_client(:104)、rtp_llm.server.master_client(:122) 写入 sys.modules 后从不恢复;master_client_fallback_test.py:76 同样在导入期无条件覆写。而 kvcm_fallback_test.py:14-21 用「不存在才注入」守卫、master_group_args_test.py:40-44finally 中恢复——同一需求三套不一致实现。测试内自建的影子 FlexlbResponse(:56-65) 已缺少生产新增的 route_source/`c
  • [P.G] 测试规范 — mock/fake/stub 不得替代本次声称覆盖的生产边界 → issue 门槛放宽后的关键分支未覆盖,包括兜底失败回落 domain 与批量输入
    两个用例只覆盖「开关开 + 兜底成功」(:211) 与「开关关 + 直接 domain」(:223),实际只验证了 can_attempt_master_route 这一布尔门控。生产门控为 not role_addrs_specified and can_attempt_master_route and not input_token_batched(backend_rpc_server_visitor.py:229-233),而测试的 _TokenIds.size 恒返回 1(:152-154)、role_addrs 恒为空(:206)、get_master_addr 恒返回 None(:170)。因此「批量输入时即使开关开也必须跳过 master 路由」「role_addrs 已指定时不得调用 master_client」「返回 connection_failed=True/fallback=True 时必须回落 domain 且不抛 ROUTE_ERROR」「backend_role_list 含 DECODE 而 master 仅返

Strengths

  • 兜底触发条件收得很准:仅 resp.connection_failed 时进入(master_client.py:673),显式保留 8600 与业务/准入错误的原有语义,注释与实现一致。
  • 开关默认 Falsepy_config_modules.py:389)且惰性 import,关闭时 can_attempt_master_route 等价于原 bool(master_addr),具备明确的运维回滚手段。
  • 打分逻辑(select_cache_affinity_firsteffective_cache_blocksbuild_candidate_plan_cold_rank)全部为无副作用纯函数,排序键均带 route_target 确定性 tie-breaker,线上决策可重放。
  • 默认值与现网 FlexLB 逐项对齐且可交叉验证:prefill_queue_size_threshold=1024p2p_hit_discount=0.2max_extra_work_tokens=0min_hit_rate=5.0FlexlbConfig.java:116,124,136,155 一一对应;p2p_host_count=0 也与 KvcmConfig.java:23 一致。
  • 缓存键派生策略与 FlexLB 一致:均为「上游键优先、缺失时按 vLLM sha256_cbor 重算」,_encode_vllm_hash_input 的 CBOR 编码与 BlockCacheKeyCalculator 语义对齐。
  • KvcmFallbackConfig.__post_init__(:60-118)对端口区间、正数、非负数、hot<=total 集中校验并显式排除 bool,为 fail-fast 而非静默降级。
  • VipHostSnapshot 把阻塞式服务发现移出请求路径到 daemon 线程,读侧只持短锁并返回不可变 tuple,last-known-good + stale 语义清楚;_resolve_leader 也用 asyncio.to_thread 避免阻塞事件循环。
  • kvcm_meta_service.proto:3 显式声明 package kv_cache_manager.proto.meta,方法全名带完整命名空间前缀,天然规避符号污染,是两份 proto 中处理正确的一份。
  • worker_status_service.proto 对废弃字段正确使用 reserved 2 / reserved 19, 20 并保留 optional presence,与引擎 canonical proto 逐字段比对当前一致,字段号复用风险已规避。
  • kvcm_fallback_test.py 用真实 grpc.aio.server() + 127.0.0.1:0 动态端口搭建假 MetaService/WorkerStatus 服务,并以 golden 向量锁死 calculate_vllm_block_cache_keys,是本 PR 最扎实的一层覆盖。

@@ -0,0 +1,75 @@
syntax = "proto3";

// Keep this file package-less: the deployed engine serves

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P0] 无 package 的 worker_status proto 与引擎 model_rpc_service proto 根符号完全重名,开关一开即 duplicate symbol 崩溃

该文件注释写明「Keep this file package-less」,顶层定义 StatusVersionPB/TaskInfoPB/WorkerStatusPB/KvCacheGroupModePBservice RpcService(:73)。已用 ^package 检索确认 model_rpc_service.proto 全文无 package,且在 342/390/439/445/654 行定义完全同名符号。worker_status_service_pb2.py:17 的序列化描述符中符号均无命名空间前缀(.TaskInfoPB.KvCacheGroupModePB),并向 _descriptor_pool.Default() 执行 AddSerializedFile。同进程链路:backend_rpc_server_visitor.py:10model_rpc_client.py 注册第一份,同文件 :78 构造 MasterClient,开关开启时 `master_client.py:14...

建议: 优先删除整份副本,直接复用已 //visibility:public//rtp_llm/cpp/model_rpc/proto:model_rpc_service_py_proto,改用其 RpcServiceStub(wire path /RpcService/GetWorkerStatus 与新桩逐字一致,StatusVersionPB/WorkerStatusPB 也已导出),可一并消除重复 descriptor 与人工同步漂移。若确需独立副本,必须声明专属 package(如 rtp_llm.fallback.worker_status)或加载到私有 DescriptorPool 隔离——proto 线格式不含类型名,客户端以 channel.unary_unary("/RpcService/GetWorkerStatus", ...) 即可保持路径不变。无论哪种,都要补一条「同进程先 import 引擎 pb2 再 import kvcm_fallback」的非 manual 用例固化该不变量。

Checklist: [6.1] 依赖方向:无循环依赖/跨层惊喜

Comment thread rtp_llm/server/BUILD
@@ -4,9 +4,12 @@ py_library(
name = "server",
srcs = glob([

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P0] server py_library 的 srcs glob 漏掉 worker_status_proto,wheel 与 runfiles 均缺该模块

py_library(name="server") 的 srcs 为 glob(["*.py", "server_args/*.py", "kvcm_proto/*.py"])glob 不递归子目录。作者已为同批新增的 kvcm_proto/ 显式追加条目,却遗漏 worker_status_proto/;该目录内无自有 BUILD,全仓 BUILD* 检索 worker_status_proto 零命中,而磁盘上确实存在 __init__.py.proto_pb2.py_pb2_grpc.py 四个文件。rtp_llm/BUILD:548-569py_package 只收集 deps 闭包中已声明的 srcs,故该包既不进 wheel 也不进 py_test runfiles;而 kvcm_fallback.py:24-27 是模块级无条件绝对导入,master_client.py:149-150 无 try/except,开关打开时 MasterClient.__init__ 直接抛 `Module...

建议: 在 glob 中补上 "worker_status_proto/*.py",与同一 hunk 的 kvcm_proto/*.py 对称(不建议改 **/*.py,会误吞子包测试文件)。若采纳上一条建议复用引擎 stub,则该目录与本问题一并消失。同时增加一条非 manual 的最小 py_test,只做 import rtp_llm.server.kvcm_fallback 且走真实包路径(不伪造 rtp_llm 命名空间、不用 spec_from_file_location),让「新增子包未进 srcs」在构建/测试阶段而非上线时暴露。

Comment thread rtp_llm/server/test/BUILD
"//rtp_llm:grpcio",
"//rtp_llm:protobuf",
],
tags = ["manual"],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 三个新增兜底测试全部标记 manual,且伪造掉两个 P0 所在的生产边界,新特性 CI 零覆盖

kvcm_fallback_test(:35)、master_client_fallback_test(:45)、backend_rpc_server_visitor_fallback_test(:54) 均带 tags = ["manual"],被 //...:all 排除;同包 vit_proxy_server_test(:57-64) 未加 manual,说明该目录并非整体豁免。本 PR 唯一进 CI 的新测试是 master_group_args_test,只断言参数名映射。更关键的是被 stub 的位置恰好覆盖两个 P0:kvcm_fallback_test.py:12-24 自建 rtp_llm/rtp_llm.server 命名空间并把 __path__ 指向源码树,使引擎 model_rpc_service_pb2 永不被导入(符号冲突不可见);backend_rpc_server_visitor_fallback_test.py:104,122-126ModelRpcClient=object、...

建议: master_client_fallback_test(已替换 _send_schedule_request)与 backend_rpc_server_visitor_fallback_test(纯 asyncio、无网络无 GPU)应直接去掉 manualkvcm_fallback_test 仅用本地环回 grpc,也适合作为常规 CPU 单测。若沙箱内 grpc.aio 端口确实不稳定,请把纯逻辑用例(VllmBlockHashTest、候选池/打分用例)拆到独立的非 manual target,而非整体排除,并在 BUILD 注释写明原因。同时补一条不伪造 ModelRpcClient/MasterClient 的用例:真实构造 MasterClient(master_config=<开关开启且三项必填已给值>) 并断言构造成功,可一次锁定符号注册与打包完整性两个不变量。

refresh_interval_s = self.config.leader_refresh_interval_ms / 1_000.0
if self._leader and now - self._leader_refreshed_at < refresh_interval_s:
return self._leader
async with self._leader_lock:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] KVCM leader 解析失败无负缓存且被单锁串行,故障期逐请求放大延迟

_resolve_leader:636-676 的快速路径依赖 self._leader 非空。冷启动且全部 bootstrap target 不可达时,:655-669 的循环全部 continueprevious_leader 为 None,于是 :676 抛错且不更新 _leader_refreshed_at——完全没有负缓存(只有 :673-675 的 stale-leader 分支才刷时间戳)。锁内每次都串行遍历全部 target,每个 target 超时 request_timeout_ms(默认 100ms)。兜底恰在 FlexLB 故障期逐请求触发,所有并发请求排在同一把 _leader_lock(:641) 之后并各自重跑完整扫描,第 N 个请求约需 N × len(targets) × 100ms,比改动前「立即 domain fallback」明显更差。失败虽被 :780-790 吞掉不致请求失败,但耗时已计入首包。

建议: 失败路径同样写入时间戳(引入独立 _leader_failed_at + 指数退避),退避窗口内直接快速失败并交由上层走 domain 路由;_leader_lock 改为「单飞刷新 + 其余请求不等待直接返回失败」;并对整个 bootstrap 扫描加总时限或改为并发探测取首个成功者。leader_refresh_interval_ms 目前仅有 dataclass 默认值、无法从 server args 配置,建议一并暴露或在代码中注明为固定常量。

self,
candidate: KvcmCacheCandidate,
) -> Optional[WorkerLoadSnapshot]:
async with self._worker_status_semaphore:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 兜底链路无整体时间预算,WorkerStatus 进程级信号量获取也无 deadline

_probe_workerasync with self._worker_status_semaphore 无超时获取进程级信号量(:591-593,asyncio.Semaphore(worker_status_concurrency),默认 3,help 亦写明「across fallback requests in one process」)。单次 RPC 有 200ms deadline,但排队等待无任何 deadline;query_and_select(:761) 与 _try_kvcm_fallback(master_client.py:433-438) 外层均无 asyncio.wait_for 整体预算,route_ips 侧也无连续失败短路。最坏耗时 = leader 串行扫描 + GetHostCacheState 100ms + 多轮探测(轮数随候选池增长)。FlexLB 整体不可用时全部流量同时涌入兜底,探测吞吐上限约 3/0.2s,队列持续堆积且无准入控制,兜底路由时延不可控——恰好发生在该特性最需要生效的场景。

建议:asyncio.wait_forquery_and_select 施加「本请求剩余 TTFT 预算」上限,超时即返回 None 让上层走 domain fallback;给信号量获取单独加 deadline(可复用 worker_status_timeout_ms,超时视为该候选探测失败);并在连续失败后引入短路/熔断(N 次连续失败后冷却窗口内跳过探测)。同时按预期兜底 QPS 上调 master_client_fallback_worker_status_concurrency 默认值或改为按请求限流,并在 help 中给出按 QPS 计算的推荐值。

@LLLLKKKK LLLLKKKK left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Code Review - PR #1335 (non-blocking suggestions)

20 条 P2/P3 建议,不阻塞合并。阻塞判定与完整摘要见上一条 review。

return FlexlbResponse.ok(
[
RoleAddr(
role=RoleType.PREFILL,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 兜底返回的 RoleAddr 硬编码为 PREFILL,与打分层的 PDFUSION 支持不一致

select_cache_affinity_first(kvcm_fallback.py:461) 明确接受 role 含 PREFILLPDFUSION 的 worker,但 _try_kvcm_fallback 恒返回 role=RoleType.PREFILL,候选快照也固定 enable_role_snapshot(RoleType.PREFILL)(master_client.py:325) 与 get_snapshot(RoleType.PREFILL)(:333)。PDFUSION 部署下 enable_role_snapshotrole_vip_map 无 PREFILL 直接返回 False(host_service.py:712-714),冷候选为空;被选中的 PDFUSION worker 仍被标为 PREFILL,backend_rpc_server_visitor.py:248need_domain_routingbackend_role_list=[PDFUSION] ...

建议: 用探测到的 WorkerStatusPB.role 反查 RoleType 后再构造 RoleAddr,无法映射的候选直接跳过;候选快照 resolver 按 backend_role_list 覆盖 PDFUSION 而非固定 RoleType.PREFILL。并补一条 PDFUSION 部署的用例,断言不会产出该部署不存在的角色地址。

Checklist: [6.1] 分布式/跨平台变更有对应覆盖

p2p_hit_discount: float,
) -> int:
# Java Math.round for a non-negative value is floor(value + 0.5).
tokens = int(effective_cache_blocks(candidate, p2p_hit_discount) * block_size + 0.5)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] block→token 折算所用 block_size 与引擎 seq_size_per_block 无一致性校验,且 help 文案与实际用途不符

生产键由 backend_rpc_server_visitor.py:138get_block_cache_keys(token_ids, self.seq_size_per_block) 生成;核对 FlexLB RequestBlockHashService.java:49-52prepareProvidedBlockCacheKeys:98-102 确认 KVCM 侧查询的正是这套上游键,且 FlexLB 强制要求「提供键时 block_size 必须 > 0」并用该值折算,故 Python 侧透传行为本身正确。但 _matched_tokens:442query_and_select:842另一个独立配置 config.block_sizemaster_kvcm_block_size,默认 0、需手填)把命中块数与块数折算回 token,与实际发键所用的 seq_size_per_block 无任何比对;填错即 hit_cache_tokenshit_rate_pct(:524) 系统性偏差、静...

建议:master_kvcm_block_size 缺省时直接取引擎 seq_size_per_block(构造 BackendRPCServerVisitor 时已可得),降级为可选覆盖项;若必须显式配置,请在 _create_kvcm_fallback_client 中与 seq_size_per_block 比对,不一致时 fail-fast 并在错误信息中带上参数名与环境变量名。修正 help 文案为「块数↔token 折算步长;缺失时用于 vLLM sha256_cbor 回退哈希」。补一条以真实 get_block_cache_keys 输出为入参的用例(现有测试传任意整数 [1, 2],恰好掩盖该路径)。

code = int(raw_code)
except (TypeError, ValueError):
code = int(ExceptionType.MASTER_NO_AVAILABLE_WORKER)
if code == FALLBACK_ERROR_CODE:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] FlexLB body 级 8600 由硬失败改为静默 domain fallback,且不受新开关保护

已核对本 PR 的 diff:if code == FALLBACK_ERROR_CODE: return FlexlbResponse.fallback_response() 为新增行。改动前 HTTP 200 且 body code=8600 会走到 :704-715——ExceptionType(8600) 抛 ValueError(exceptions.py:78 确认 8600 非枚举成员,仅有 MASTER_NO_AVAILABLE_WORKER = 8400)→ 降级为 MASTER_NO_AVAILABLE_WORKERraise FtRuntimeException,请求显式失败。改动后返回 fallback 响应,使 backend_rpc_server_visitor.py:251-253allow_domain_fallback 成立并静默改走 domain 路由。该分支不在 client_fallback_enabled 门控之内,因此所有部署(含未开启新开关者)的错误语义都被改变,PR 描述未...

建议: 若这是有意的语义修正,请在 PR 描述与 commit message 中明确说明,并把它与 KVCM 兜底拆成独立提交,便于单独回滚;若只希望在新开关下生效,请把该分支置于 client_fallback_enabled 门控内。无论哪种,都应补一条断言「body code=8600 → domain fallback 且不抛异常」的非 manual 用例,并为该转换加上可观测的计数或限频日志。

Checklist: [6.1] Commit 原子、message 与行为匹配

@@ -223,16 +223,25 @@ async def route_ips(self, input: GenerateInput):
input_token_batched = True

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

📍 实际位置 rtp_llm/server/backend_rpc_server_visitor.py:162(不在 diff 展示范围内,就近挂载)

[P2] 兜底路径缺少专属指标,三个新增响应字段无生产消费方,且无 master 地址时逐请求打 ERROR

全仓 py 检索确认 route_source/kvcm_outcome/cache_match 仅在 master_client.py 内被赋值传递,并只被 master_client_fallback_test.py:189,226 断言,无任何 kmonitor 上报。兜底成功后 route_result.is_ok 为真,:155-163 照旧上报无 tag 的 MASTER_ROUTE_QPS_METRIC,兜底的 KVCM 查询 + 探测耗时也被折叠进 :236-238MASTER_ROUTE_RT_METRIC。另一侧:开关开且 master_addr 为空时不发任何 HTTP,master_client.py:655 直接以 connection_failed_response() 起始,兜底未命中仍带 connection_failed=True,导致 :165 每请求打一条 ERROR(改动前该场景仅 :240 一条 WARNING)。运维因此无法区分「FlexLB 正常」与「全靠兜底」。

建议:get_master_route_addrs 成功分支读取 route_source/kvcm_outcome,为兜底单独上报带 route_source 标签的 QPS 与 RT,并增加 selection_reason 维度,使降级窗口可被告警与灰度观察、进而支撑开关回滚决策;把「无可用 master 地址」与「真实连接失败」在语义上区分(前者按 WARNING 或独立 outcome 限频记录),不要复用 connection_failed 的建连失败含义;_parse_candidates(kvcm_fallback.py:689-696) 丢弃格式不符或端口越界的候选时至少记一次聚合计数,避免 KVCM 返回域名时无声退化为纯冷路由。

Checklist: [6.1] 无 per-forward 调试日志 / 噪声热路径输出


resp = await self._send_schedule_request(
master_addr, payload, ttft_timeout_ms, request_id
flexlb_timeout_ms = ttft_timeout_ms

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] flexlb_transport_timeout_ms 默认 0 使挂死场景兜底不可达,且开关关闭时也照样生效

master_client_fallback_flexlb_transport_timeout_ms 默认 0(py_config_modules.py:414master_group_args.py:252),:649 因此不收敛,flexlb_timeout_ms = ttft_timeout_ms;请求未带 ttft_timeout_ms/timeout_ms:615-620 退化为 master_default_timeout_ms(默认 3600000ms)。兜底只在 resp.connection_failed 时触发(:673),而该状态需 _send_schedule_request 走到 :573-591 的异常分支。因此 FlexLB 快速拒连时兜底有效,但 FlexLB 已建连不回包(最典型的挂死故障)时要等满整个请求预算才切换,兜底形同虚设。另外 :640-653 读取该参数时未判断 client_fallback_enabled,开关关闭时用户配置该项也会单方面压缩 FlexLB 超时而无任何补...

建议: 给该参数设置有界非零默认值(与 master_kvcm_request_timeout_ms=100 同量级的百毫秒级),或在开启 master_client_fallback 且该值为 0 时打印明确启动告警说明「兜底仅对连接级失败生效」;help 中补充 0 的含义;同时让该参数仅在 client_fallback_enabled 为真时生效,避免关闭开关时改变既有超时语义。并在 PR description 中给出推荐值与回滚方式。

@@ -223,16 +223,25 @@ async def route_ips(self, input: GenerateInput):
input_token_batched = True

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

📍 实际位置 rtp_llm/server/backend_rpc_server_visitor.py:133(不在 diff 展示范围内,就近挂载)

[P3] token_ids 到 list 的 O(prompt_len) 转换在路由路径上重复执行两次

get_master_route_addrs:133-137 已对 input.token_ids 执行一次 tolist() 用于 get_block_cache_keys,随后 _try_kvcm_fallback 又通过 _input_ids_for_kvcmmaster_client.py:404-405)对同一张量再次 tolist(),并在 :415 逐元素 int() 转换。开关开启后每个走兜底的请求都要在路由路径上重复一次 O(prompt_len) 的张量转列表与整型转换。未做基准测量,故仅记为可零成本消除的冗余计算。

建议::133-137 已算好的 token_ids 列表随 block_cache_keys 一并传入 get_backend_role_addrs/_try_kvcm_fallback,删除 _input_ids_for_kvcm 中的重复转换(其 hasattr/getattr 猜测式访问也可一并去掉,GenerateInput 是强类型数据类)。

Checklist: [6.1] 可观测性:日志/指标/超时可操作、非噪声;[6.1] DRY:重复非平凡逻辑被抽取或显式复用

*(client._probe_worker(candidate) for candidate in extra_candidates)
)
self.assertTrue(all(snapshot is not None for snapshot in snapshots))
self.assertLessEqual(self.worker_service.max_active, 2)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P3] 并发上界为单边断言,无法区分正确限流与并发彻底失效

worker_status_concurrency=2(:239) 下并发发起 4 个 client._probe_worker(:276-278),仅断言 assertLessEqual(self.worker_service.max_active, 2)(:280)。该断言是单边的:若信号量实现退化为完全串行(max_active == 1),测试同样通过,无法区分「正确限流到 2」与「并发彻底失效」这两种截然不同的性能语义。_FakeWorkerStatusService 已维护精确的 max_active 计数,具备双边断言能力。

建议: 把断言改为双边,例如 assertEqual(2, self.worker_service.max_active)(配合足够的 delay_s 保证观测窗口),或至少加上 assertGreater(self.worker_service.max_active, 1),使限流退化为串行时能被检出。

if not 1 <= bootstrap_port <= 65_535:
raise ValueError("master_kvcm_bootstrap_port must be a valid port")

self._kvcm_vip = VipServerWrapper(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P3] master_kvcm_use_local 场景下 service_id 中填写的端口被静默丢弃

--master_kvcm_service_id 的 help 写「KVCM bootstrap service id or local IP:port list」,master_kvcm_use_local=TrueVipServerWrapper.__init__host_service.py:95-102)确实按 ip:port 逐项解析为 Host(ip, port)。但 resolve_bootstrap_targetsmaster_client.py:223-231)只读 host.ip,端口一律改用 bootstrap_port(默认 6381),用户在 service_id 中写的端口被静默忽略。本地联调若填 127.0.0.1:7000 而未同时设置 --master_kvcm_bootstrap_port,会连到一个未监听的端口,且无任何日志提示端口被替换。

建议: use_local 分支优先使用解析出的 host.port,仅当其缺失或非法时回退 bootstrap_port;或在 help 中明确「IP:port 列表中的端口会被 --master_kvcm_bootstrap_port 覆盖」,并在构造 target 时对被替换的端口打印一次启动日志。

Checklist: [6.1] 边界 case 覆盖(空、单元素、最大值)

self.master_client.client_fallback_enabled,
input_token_batched,
)
specified_roles = {addr.role for addr in input.generate_config.role_addrs}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

📍 实际位置 rtp_llm/server/backend_rpc_server_visitor.py:324(不在 diff 展示范围内,就近挂载)

[P3] 新开关被 host_service.service_available 前置门控静默旁路

enqueue(:324) 与 batch_enqueue(:335) 均以 if self.host_service.service_available: 门控 route_ips。该字段在 host_service.py:660-662 定义为 bool(self.master_vip.domain) or any(self.role_vip_map.values())。因此当运维未配置任何 master 域名与角色域名、期望纯靠 KVCM 客户端兜底选点(KVCM 热候选并不依赖 role VIP)时,route_ips 根本不会被调用,master_client_fallback=True 被静默忽略且无任何日志提示。该配置组合较为边缘,故仅记为语义说明缺口。

建议:master_client_fallback 的 help 中说明「需至少配置一个 VIP 域名,否则路由入口不会被触发」;或在启动时若开关打开而 service_available 为 False 则打印一次明确 warning。

Checklist: [6.1] 回滚路径:风险行为存在运维回滚手段;[6.1] 错误语义:fail-fast/retry/fallback/silent 行为显式

@@ -0,0 +1,235 @@
import asyncio

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P3] 测试文件存在未使用的 import

第 1 行 import asyncio,但全文未出现任何 asyncio. 引用(异步用例由 unittest.IsolatedAsyncioTestCase 驱动)。仓库 flake8 默认启用 F401,未使用 import 会在 pre-commit / lint 阶段报错,属可零成本消除的噪声。

建议: 删除 import asyncio

Checklist: [6.1] 逻辑变更未混入无关格式化

@LLLLKKKK LLLLKKKK left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Code Review - PR #1335

Status: BLOCKING

Summary: P0/2 · P1/3 · P2/13 · P3/5

Reviewed: commit 7f627e40ff44 · 2026-08-28 16:03 UTC+8

Blocking Issues

P0

  • 无 package 的 worker_status proto 与引擎 model_rpc_service proto 根符号完全重名,开关一开即 duplicate symbol 崩溃 @ rtp_llm/server/worker_status_proto/worker_status_service.proto:3
    • 建议:优先复用已有 //rtp_llm/cpp/model_rpc/proto:model_rpc_service_py_proto:它已提供字段号完全一致的 StatusVersionPB/WorkerStatusPB 与方法路径同为 /RpcService/GetWorkerStatusRpcServiceStub,把 kvcm_fallback.py 改为从 rtp_llm.cpp.model_rpc.proto.model_rpc_service_pb2(_grpc) 导入并删除 worker_status_proto/ 整个目录(同目录 vit_proxy_server.py:16 已是此模式),一并消除 DRY 与后续 proto 漂移。若确需保留独立轻量副本,必须为其声明专属 package 隔离符号(stub 中保留 '/RpcService/GetWorkerStatus' 字面路径即可维持 wire 兼容),或改用私有 descriptor_pool.DescriptorPool();并补一个在真实 rtp_llm 包下同进程先后 import 两个 pb2 的回归用例锁死该不变量。
  • server py_library 的 srcs glob 漏掉 worker_status_proto,wheel 与 runfiles 均缺该模块 @ rtp_llm/server/BUILD:5
    • 建议:在 glob 中补 "worker_status_proto/*.py",与 kvcm_proto/*.py 对称。更稳妥的是按上一条建议复用 model_rpc_service_py_proto 并删除该目录,或按仓库既有约定(rtp_llm/cpp/model_rpc/proto/BUILD:21-42generate_grpc_proto + py_library)为 proto 目录建独立 target 并显式 deps,由 bazel 托管 srcs,从结构上消除后续新增 proto 子包时再次漏配的可能。修复后请以非 manual 方式实跑 //rtp_llm/server/test:kvcm_fallback_test 验证 runfiles 完整,并补一个通过真实包路径 import rtp_llm.server.kvcm_fallback(不改写 sys.modules)的轻量用例,使此类打包缺失能被 CI 直接拦住。

P1

  • 三个新增兜底测试全部标记 manual,且伪造掉两个 P0 所在的生产边界,新特性 CI 零覆盖 @ rtp_llm/server/test/BUILD:35
    • 建议:去掉这三个目标的 manual 标记纳入 CI 通配;若 kvcm_fallback_test 确因远端沙箱限制 loopback 端口而必须 manual,请在 BUILD 中注明原因,并至少保证完全不涉网络的另两个目标在 CI 执行。补齐关键边界用例:fake 返回 connection_failed=True / fallback=True 时断言 domain_calls == 1 且最终取到 domain 地址、未抛 ROUTE_ERROR;input_token_batched=True 时断言跳过 master 路由;get_master_addr() 非空时断言行为与改动前一致。并额外补一个非 manual 的轻量导入型测试,通过真实 rtp_llm.server 包路径 import kvcm_fallback,使打包与符号注册类缺陷有测试兜底。提交前请实跑全部新增 target,确保「已加测试」与「测试可执行」一致。
  • KVCM leader 解析失败无负缓存且被单锁串行,故障期逐请求放大延迟 @ rtp_llm/server/kvcm_fallback.py:640
    • 建议:补充失败负缓存与熔断:解析失败时也记录 _leader_failed_at,在一个短冷却窗口(如 request_timeout_ms 的数倍)内让后续请求直接快速失败而不重复扫描,冷却后半开重试;bootstrap 目标改为并发 asyncio.gather 或限定单次扫描的目标数量,避免串行累加;并为 leader 解析失败/超时与「已熔断」状态上报 kmonitor 计数指标,使故障期行为可观测、可告警。
  • 兜底链路无整体时间预算,WorkerStatus 信号量获取也无 deadline @ rtp_llm/server/kvcm_fallback.py:790
    • 建议:用 asyncio.wait_forquery_and_select(或 _try_kvcm_fallback)加整体预算,预算由 TTFT 剩余时间或专用配置派生,超时即返回 None 交由 domain fallback;信号量按并发规模放大或改为按请求限流并给获取加超时;同时为兜底整体延迟与「已超预算」次数增加 kmonitor 指标,使 fail-fast 与 fallback 的边界显式可控可告警。

Non-blocking Suggestions

P2

  • 兜底返回的 RoleAddr 硬编码为 PREFILL,与打分层的 PDFUSION 支持不一致 @ rtp_llm/server/master_client.py:514
    • 建议:按 worker 上报角色(worker_load_snapshot 已保留 role)或部署形态的 backend_role_list 返回正确的 RoleType,PDFUSION 部署应返回 PDFUSION,使兜底结果能满足 need_domain_routing 而不被 domain 覆盖;对「兜底已给出正确角色、仅需补齐其余 role」的情形把日志降为 debug 或改用区分文案,避免把正常补齐误报为 domain 降级并污染 DOMAIN_ROUTE_QPS 基线。若当前只打算支持纯 PREFILL 部署,则应在 select_cache_affinity_first 中同步收窄 role 过滤并在配置校验处显式拒绝其他形态,让两层契约一致,并补一个 PDFUSION 形态下断言返回角色的用例。
  • 兜底链路存在无调用方的死代码与未装配的隐藏参数,p2p_hit_discount 成为无效旋钮 @ rtp_llm/server/kvcm_fallback.py:397
    • 建议:删除 build_candidate_poolselect_max_local_affinity 及其 __all__ 条目(若仅测试需要,改为直接调用 build_candidate_plan)。对 p2p 二选一:要么补上 p2p_host_count 的配置项与装配使 p2p_hit_discount 真正生效,要么先移除 master_client_fallback_p2p_hit_discount 这个无效 flag(YAGNI),待 p2p 能力接通时再一并引入,避免运维误认为可调。同时把 leader_refresh_interval_msminimum_local_blocks 暴露为 master_* 配置,或在注释中说明为何刻意固定,避免出现只能改代码才能调整的隐藏行为。
  • flexlb_transport_timeout_ms 不受 kill switch 约束,且默认 0 使挂死场景兜底不可达 @ rtp_llm/server/master_client.py:652
    • 建议:二选一:(a) 把该 min() 收敛进 if self.client_fallback_enabled 分支,使 master_client_fallback 成为真正的单一 kill switch,并给一个开关开启时生效的合理非零默认(或在开关开启且该值为 0 时启动期告警),确保挂死场景能在有界时间内触发兜底;(b) 若确实希望它独立于开关生效,则改名为 master_flexlb_transport_timeout_ms(含 env_name)并同步修正 help 与 py_config_modules.py 附近注释,避免运维误判回滚范围。
  • 兜底路径缺少专属指标,三个新增响应字段无生产消费方 @ rtp_llm/server/backend_rpc_server_visitor.py:162
    • 建议:在 :162/:236 读取 route_result.route_source 作为 kmonitor tag 上报(或为 CLIENT_FALLBACK 新增独立 QPS/RT 指标),避免污染既有 FlexLB RT 基线;为 kvcm_outcomeselection_reasoncache_query_outcome(含 no_positive_match)与 block hash 契约不匹配各增计数指标,使兜底触发率、选点原因与配置错填可被告警发现;兜底路径的失败日志按去重或采样输出,避免热路径逐请求 ERROR。
  • gRPC channel 与选择历史字典只增不减,滚动发布后累积僵尸连接 @ rtp_llm/server/kvcm_fallback.py:587
    • 建议:给四个连接字典引入有界 LRU(容量按活跃 worker 数量级或 candidate_pool_size 的若干倍设定),淘汰时 await channel.close()_invalidate_leader 同步关闭并移除对应 channel/stub。_last_selected_ns_logged_worker_hash_contract_mismatches 改为有界结构,或在候选快照刷新时按当前存活 target 集合收敛清理。
  • 校验顺序导致后台线程泄漏,且 MasterClient/HostService 的 close 在生产中从未被调用 @ rtp_llm/server/master_client.py:229
    • 建议:把 KvcmFallbackConfig 的构造与校验全部提前到 VipServerWrapper/VipHostSnapshot 创建之前,使非法配置在任何线程启动前 fail-fast;或用 try/except 包裹,在失败路径上显式 close() 已创建的快照。同时在服务关停链路(frontend/backend 的 shutdown 钩子)中显式调用 MasterClient.close()HostService.close(),让 close() 不再是只有测试会走的死路径。
  • 手工签入 pb2/pb2_grpc 绕开构建期 codegen,无漂移校验且格式风格分叉 @ rtp_llm/server/worker_status_proto/worker_status_service_pb2_grpc.py:1
    • 建议:为两个 proto 目录各加 generate_grpc_proto 目标,让 //rtp_llm/server:server 依赖生成出的 py_library 并从源码树移除签入产物(天然同时修掉打包漏项与格式分叉)。若因跨仓分发必须签入,请补一个 CI 校验目标:以仓库锁定的 grpcio-tools 重新生成并 diff 为空,另加一个解析权威 proto、断言相关 message 的字段名/字段号/reserved/optional 逐项相等的用例;并把这两个目录加入 .pre-commit-config.yaml 的 exclude,统一保持 protoc 原样输出以便逐字比对。
  • 对 MasterConfig 全量使用 getattr 字面量访问,默认值在多处重复维护 @ rtp_llm/server/master_client.py:161
    • 建议:改为直接属性访问 self.master_config.master_kvcm_block_size,让缺失/改名在启动期即报 AttributeError,默认值只保留在配置声明一处;删除 host_service 上的 getattr + callable 探测与 KvcmFallbackResultgetattr 包装;并把配置装配抽到 kvcm_fallback.pyKvcmFallbackConfig.from_master_config(master_config) 工厂方法,让 MasterClient 只负责生命周期。
  • master_kvcm_block_size 与引擎 seq_size_per_block 双写且无一致性校验,必填项默认值永远非法 @ rtp_llm/server/server_args/master_group_args.py:80
    • 建议:由 BackendRPCServerVisitor 把已有的 seq_size_per_block 传给 MasterClient 并作为 block_size 的默认来源,仅在确需覆盖时才允许配置,并在启动期与引擎值比对、不一致即 fail-fast;同时为所有「默认值永远非法」的必填项在 help 中标注 required,并在开关开启时一次性汇总所有缺失/非法项后一次报错,而非逐项 raise 让运维反复启崩。help 文案同步修正为该值同时用于 KVCM block↔token 折算与 WorkerStatus 契约校验。
  • FlexLB body 级 8600 由硬失败改为静默 domain fallback,且不受新开关保护 @ rtp_llm/server/master_client.py:715
    • 建议:在 PR description 与 commit message 中显式记录这次语义变更(当前提交聚焦 KVCM 兜底,未体现对既有 FlexLB 错误处理的影响);为该分支补单测断言返回 fallback=True 且不抛异常,并为「body 级 8600 触发 domain 兜底」上报独立计数指标,使这条静默降级在线上可观测。若不希望该变更随本 PR 一起上线,应将其收敛进 client_fallback_enabled 分支或拆为独立提交。
  • 测试在模块导入期永久污染 sys.modules 且不恢复,影子生产类型已与生产漂移 @ rtp_llm/server/test/backend_rpc_server_visitor_fallback_test.py:146
    • 建议:统一为 master_group_args_test.py:27-44 的「先检查是否已存在 + try/finally 恢复」模式,并把桩注入收敛到 setUpModule/fixture 而非模块顶层执行。把与生产共享的纯 Python 类型/常量(FlexlbResponseRoleAddrRoleTypeExceptionTypeGaugeMetrics)改为从真实模块导入,只对 torchrtp_llm.ops 等重依赖打桩;无法导入时至少断言 stub 字段集合与真实类型一致。桩加载器抽到共享 py_library(如 fallback_test_stubs.py)消除三份不一致副本,并顺手删除本文件 :1 未使用的 import asyncio
  • master_group_args_test 绑定空 SimpleNamespace 并 stub str2bool,断言退化为字面量自证 @ rtp_llm/server/server_args/test/master_group_args_test.py:29
    • 建议:改用真实 MasterConfig 实例并对每个 flag 断言 default == getattr(master_config, field),使字段缺失与默认值多处双写漂移都能被捕获;改用真实 str2bool(把它移出依赖 rtp_llm.ops 的模块,或只对 rtp_llm.ops 打桩而不替换整个 util 模块),对两个布尔开关断言 type is str2booldefault is False,并用 subTest/parametrize"false"/"0"/"off"/"true"/"1"/"on" 逐一断言解析结果;删除 :130 恒真的否定断言(若想固化命名约定,改为遍历所有 --master_* flag 断言含 fallback 者前缀均为 master_client_fallback_);并在 server_args_test.pyMASTER_CLIENT_FALLBACK 等关键项的 env→config 端到端用例。
  • 新增的 VipHostSnapshot 后台刷新状态机没有任何测试覆盖 @ rtp_llm/server/host_service.py:125
    • 建议:为 VipHostSnapshot 补一组不依赖真实 VIP 的纯同步单测:注入 fake vip 直接驱动 refresh_now()/get_hosts() 而不依赖线程时序,分别断言首次发布成功、刷新抛异常时保留旧快照、连续空结果在超过 stale_timeout_seconds 后返回空、之后恢复非空、重复 host 去重、vip.domain 为空时线程未启动、close() 后线程退出。同时为 HostService.enable_role_snapshot/get_backend_role_addr_snapshot/close 补重复启用幂等性与线程回收断言,并确保这些用例进入 CI(非 manual)。

P3

  • 路由准入判定跨类重复,visitor 直接读取 MasterClient 内部开关且日志归因错误 @ rtp_llm/server/backend_rpc_server_visitor.py:226
    • 建议:把准入判断下沉为 master client 的一个方法(如 master_client.can_route()),由其内部一次性读取 master_addr 并结合 client_fallback_enabled_kvcm_fallback_client 就绪状态给出结论,visitor 只消费该布尔结果与返回的诊断信息;同时按实际命中的条件分别打日志:batched 输入单独一条明确说明跳过 master 路由,can_attempt_master_route 为假时再打一条含 master_addr 与开关状态的日志,避免单条日志混合归因。
  • token_ids 到 list 的 O(prompt_len) 转换在路由路径上重复执行两次 @ rtp_llm/server/backend_rpc_server_visitor.py:133
    • 建议:把 get_master_route_addrs 中已转换好的 token 列表作为参数传入 get_backend_role_addrs/_try_kvcm_fallback,复用同一份 list 而不在下游重新转换;_input_ids_for_kvcmgetattr/hasattr 探测改为依赖 GenerateInput 的显式字段与类型契约(或在函数签名上直接要求 List[int])。
  • 新开关被 host_service.service_available 前置门控静默旁路 @ rtp_llm/server/backend_rpc_server_visitor.py:324
    • 建议:在 BackendRPCServerVisitor.__init__ 中检测 master_client.client_fallback_enabled and not self.host_service.service_available,命中时打一条 warning 明确说明该开关因 host service 不可用而不会生效;或把 client_fallback_enabled 也纳入 service_available 的判定,使开关语义与实际生效范围一致。
  • 并发上界为单边断言,无法区分正确限流与并发彻底失效 @ rtp_llm/server/test/kvcm_fallback_test.py:280
    • 建议:补一条下界断言(候选数大于 1 且服务端已引入延迟时断言 max_active >= 2),使「正确限流」与「并发彻底失效」可区分;或直接断言 max_active == 期望并发数,让配置回归能被捕获。
  • master_kvcm_use_local 场景下 service_id 中填写的端口被静默丢弃 @ rtp_llm/server/master_client.py:225
    • 建议:在 use_local 或 service_id 含端口时保留原端口,或在检测到 host 自带端口与 bootstrap_port 不一致时打一条 warning 明确说明以哪个为准;若确实只支持统一端口,请同步修正 help 文案去掉 "IP:port list" 的表述。

Checklist Findings (24 fail / 48 total)

General Principles Checklist

  • [6.1] Architecture — 依赖方向:无循环依赖/跨层惊喜 → issue 无 package 的 worker_status proto 与引擎 model_rpc_service proto 根符号完全重名,开关一开即 duplicate symbol 崩溃
    该文件注释「Keep this file package-less」,故 StatusVersionPB(:6)、TaskInfoPB(:11)、KvCacheGroupModePB(:42)、WorkerStatusPB(:48)、service RpcService(:73) 全注册为顶层符号。已核实 model_rpc_service.proto 同样无 package 声明,且在 342/390/439/445/654 行定义完全同名的这 5 个符号。worker_status_service_pb2.py:7,17_descriptor_pool.Default().AddSerializedFile(...),落进程级全局 pool;model_rpc_service_pb2generate_grpc_proto 生成、同样落默认 pool。两者在同一前端进程加载:backend_rpc_server_visitor.py:10 导入 ModelRpcClient(`model_rpc_client.py:10
  • [6.1] Architecture — 兼容性:外部 HTTP/RPC API、持久数据、配置、环境迁移安全 → issue FlexLB body 级 8600 由硬失败改为静默 domain fallback,且不受新开关保护
    get_backend_role_addrs:715-716 if code == FALLBACK_ERROR_CODE: return FlexlbResponse.fallback_response() 使 HTTP 200 + body code=8600 的响应与非 200 分支(:579)语义对齐。语义对齐本身合理,但这是一次不受 master_client_fallback 约束的既有行为变更:已核实 8600 不在 ExceptionType 枚举中(exceptions.py 仅到 MASTER_NO_AVAILABLE_WORKER=8400、ROUTE_ERROR=8500),故改动前该分支会经 except ValueError 退化为 FtRuntimeException(MASTER_NO_AVAILABLE_WORKER) 向用户报错;改动后变为 fallback=Trueroute_ips:251 放行 domain 兜底,即从可见硬失败转为静默降级。存量部署无需开启任何开关即受影响,且该分支无测试
  • [6.1] Architecture — 分层边界:新概念在正确层级,不泄漏内部 → issue 路由准入判定跨类重复,visitor 直接读取 MasterClient 内部开关且日志归因错误
    :226can_attempt_master_route = bool(master_addr) or bool(self.master_client.client_fallback_enabled) 把「客户端能否路由」这一策略判断复制到调用方,而 master_client.py:439 的真实前置条件还包含 self._kvcm_fallback_client is not None;一旦前置条件演进,visitor 的门禁会静默与之偏离。同时 visitor 在 :218 读一次 host_service.get_master_addr() 用于门禁与日志,master_client.py:620 内部又独立读一次同一 snapshot,两次读取之间后台刷新线程可能更新,导致日志中的 master_addr 与实际发请求的地址不一致。另外 :239-246elif 分支在开关开启时只可能因 input_token_batched=True 进入(此时 can_attempt_master_route 恒为真),但文案仍把三
  • [6.1] Architecture — 可观测性:日志/指标/超时可操作、非噪声 → issue 新开关被 host_service.service_available 前置门控静默旁路
    enqueue(:324) 与 batch_enqueue(:335) 仅在 self.host_service.service_available 为真时才调用 route_ips,而 host_service.py:660service_available = bool(self.master_vip.domain) or any(self.role_vip_map.values())。若运维只打开 master_client_fallback 而未配置 master domain 与任何 role domain,新增的 can_attempt_master_route 分支永远不会被执行,开关静默无效且日志中没有任何提示,排查成本高。
  • [6.1] Architecture — 回滚路径:风险行为存在运维回滚手段 → issue 新开关被 host_service.service_available 前置门控静默旁路
    enqueue(:324) 与 batch_enqueue(:335) 仅在 self.host_service.service_available 为真时才调用 route_ips,而 host_service.py:660service_available = bool(self.master_vip.domain) or any(self.role_vip_map.values())。若运维只打开 master_client_fallback 而未配置 master domain 与任何 role domain,新增的 can_attempt_master_route 分支永远不会被执行,开关静默无效且日志中没有任何提示,排查成本高。
  • [6.1] Architecture — 状态不变量:创建/更新/失败/重试/回滚路径有效 → issue 新增的 VipHostSnapshot 后台刷新状态机没有任何测试覆盖
    VipHostSnapshot(:125-235) 承担兜底路径全部冷候选来源,其状态机有多个非平凡不变量:_publish(:180-183) 对空结果直接 return 以保留 last-known-good、refresh_now(:188-214) 在异常与空结果时按 stale_timeout_seconds 决定是否清空、get_hosts(:221-230) 读侧再做一次过期判断、__init__ 复用 VipServerWrapper 构造期发现结果、:162 有「domain 为空则不启动线程」的分支、close(:232) 以 1s join 回收线程。这些正是典型的边界易错逻辑(首次成功、刷新异常保留旧值、超过 stale 阈值清空、清空后恢复、线程回收),但三个新增测试与既有 host_service_test 中均无对应用例;同批新增的 enable_role_snapshot/get_backend_role_addr_snapshot/close(:703-745) 亦无覆盖。
  • [6.1] Architecture — 错误语义:fail-fast/retry/fallback/silent 行为显式 → issue master_kvcm_use_local 场景下 service_id 中填写的端口被静默丢弃
    :225-228VipServerWrapper(service_id, use_local) 构造发现源,随后 resolve_bootstrap_targets(:235-243) 一律用配置的 bootstrap_port 重组 ip:port,忽略 host 自带端口。而 --master_kvcm_service_id 的 help 明确写作 "KVCM bootstrap service id or local IP:port list"(master_group_args.py:58),即文档上支持直连 IP:port 形态;本地/直连调试时若按该说明填写 host:port,端口会被静默替换为 master_kvcm_bootstrap_port(默认 6381),既不报错也无日志提示,容易误判为 KVCM 不可达。
  • [6.1] Quality — Commit 原子、message 与行为匹配 → issue FlexLB body 级 8600 由硬失败改为静默 domain fallback,且不受新开关保护
    get_backend_role_addrs:715-716 if code == FALLBACK_ERROR_CODE: return FlexlbResponse.fallback_response() 使 HTTP 200 + body code=8600 的响应与非 200 分支(:579)语义对齐。语义对齐本身合理,但这是一次不受 master_client_fallback 约束的既有行为变更:已核实 8600 不在 ExceptionType 枚举中(exceptions.py 仅到 MASTER_NO_AVAILABLE_WORKER=8400、ROUTE_ERROR=8500),故改动前该分支会经 except ValueError 退化为 FtRuntimeException(MASTER_NO_AVAILABLE_WORKER) 向用户报错;改动后变为 fallback=Trueroute_ips:251 放行 domain 兜底,即从可见硬失败转为静默降级。存量部署无需开启任何开关即受影响,且该分支无测试
  • [6.1] Quality — 无 per-forward 调试日志 / 噪声热路径输出 → issue 兜底路径缺少专属指标,三个新增响应字段无生产消费方
    FlexlbResponse 新增 route_source(:47)、cache_matchkvcm_outcome(:49),兜底成功时置为 "CLIENT_FALLBACK" 与 18 项 cache_match 明细(master_client.py:472-522)。但全仓检索 route_source/kvcm_outcome 仅命中 master_client.py 自身与 master_client_fallback_test.py:189,226,生产路由层完全不读取。get_master_route_addrs:162 对兜底成功与真实 FlexLB 成功一律上报同一个无标签 MASTER_ROUTE_QPS_METRICroute_ips:236MASTER_ROUTE_RT_METRIC 也无维度且混入 KVCM 发现与 worker 探测耗时,既无法度量降级触发率与恢复,也会让基于该 RT 的既有看板/告警基线漂移。另有静默失效路径:kvcm_fallback.py:774-787
  • [6.1] Quality — 逻辑变更未混入无关格式化 → issue 手工签入 pb2/pb2_grpc 绕开构建期 codegen,无漂移校验且格式风格分叉
    仓库既有约定是构建期生成:rtp_llm/cpp/model_rpc/proto/BUILD:2,21-25,38-42generate_grpc_proto + py_library.proto 生成 stub,源码树中不存在 model_rpc_service_pb2.py.gitignore 显式忽略),已有多个 Python 消费者。本 PR 反其道而行,把两套 _pb2.py/_pb2_grpc.py 直接签入并靠 BUILD glob 装配(.proto 本身因 glob 只匹配 *.py 而完全未进入依赖跟踪),文件头硬编码 Protobuf Python Version: 4.25.1 而仓库锁定 grpcio-tools==1.57.0,生成工具链在构建体系之外。两份 proto 又都是仓内/上游权威副本的人工镜像,仅靠「Keep this file in sync with ...」注释维持,三处均无自动化校验。此外 kvcm_meta_service_pb2_grpc.py 已被 Black 重排而
  • [6.1] Software Engineering — DIP:高层策略不依赖非必要具体细节 → issue 路由准入判定跨类重复,visitor 直接读取 MasterClient 内部开关且日志归因错误
    :226can_attempt_master_route = bool(master_addr) or bool(self.master_client.client_fallback_enabled) 把「客户端能否路由」这一策略判断复制到调用方,而 master_client.py:439 的真实前置条件还包含 self._kvcm_fallback_client is not None;一旦前置条件演进,visitor 的门禁会静默与之偏离。同时 visitor 在 :218 读一次 host_service.get_master_addr() 用于门禁与日志,master_client.py:620 内部又独立读一次同一 snapshot,两次读取之间后台刷新线程可能更新,导致日志中的 master_addr 与实际发请求的地址不一致。另外 :239-246elif 分支在开关开启时只可能因 input_token_batched=True 进入(此时 can_attempt_master_route 恒为真),但文案仍把三
  • [6.1] Software Engineering — DRY:重复非平凡逻辑被抽取或显式复用 → issue token_ids 到 list 的 O(prompt_len) 转换在路由路径上重复执行两次
    get_master_route_addrs:133-138 已把 input.token_ids 整体 tolist() 一次用于 get_block_cache_keys。开关开启且走兜底时,master_client._input_ids_for_kvcm(:411-430) 又对同一 token_ids 做第二次 tolist()(:417-418) 并额外执行 [int(token_id) for token_id in raw_input_ids](:428) 全量遍历,即在每请求路由路径上多一次 O(prompt_len) 的 Python 层拷贝与逐元素转换;长 prompt 场景下是可避免的重复开销(未做基准测量,故按 P3 记录)。该函数还用 getattr(input, "input_ids", None)/hasattr(raw_input_ids, "tolist") 做类型探测,与 GenerateInput 的既有字段契约脱耦。
  • [6.1] Software Engineering — KISS/YAGNI:无投机性抽象 → issue master_kvcm_block_size 与引擎 seq_size_per_block 双写且无一致性校验,必填项默认值永远非法
    --master_kvcm_block_size 默认 0(:84),而 KvcmFallbackConfig.__post_init__:74 要求其为正数,故仅开启 --master_client_fallback 而不设该值必然启动崩溃;--master_kvcm_service_id(:58)、--master_kvcm_instance_id(:76) 同为空默认的必填项,三者 help 均未标注必填(对照 :99 的 lookahead 项已正确标注 "required when fallback is enabled"),运维只能逐轮启崩试错。更根本的是该值与引擎侧同一物理量重复:backend_rpc_server_visitor.py:138 用引擎的 self.seq_size_per_block 计算送给 KVCM 的 block_cache_keys,而 kvcm_fallback.py:445/:871 用运维手填的 config.block_size 做 block↔token 折算,两者无任何交叉校验;唯一保
  • [6.1] Software Engineering — LSP:子类/重写保持基类契约 → issue 兜底返回的 RoleAddr 硬编码为 PREFILL,与打分层的 PDFUSION 支持不一致
    select_cache_affinity_first:461-464 明确接纳 "PREFILL" in role or "PDFUSION" in role 的 worker,但 _try_kvcm_fallback:511-519 无条件构造 RoleAddr(role=RoleType.PREFILL, ...),丢弃了 worker 实际上报的角色(kvcm_fallback.py:430 已从 WorkerStatusPB.role 取到),即 PDFUSION worker 会被贴上 PREFILL 标签。而 get_backend_role_list:118-120 在 FRONTEND 配置 pdfusion_domain 时产出 [PDFUSION]。此时 get_master_route_addrs:163 成功返回 None,route_ips:251allow_domain_fallback 恒为 True,need_domain_routing(:248) 因 {PREFILL} 不覆盖
  • [6.1] Software Engineering — SRP:模块/类职责单一 → issue 对 MasterConfig 全量使用 getattr 字面量访问,默认值在多处重复维护
    _create_kvcm_fallback_client 中约 20 处形如 int(getattr(self.master_config, "master_client_fallback_discovery_refresh_ms", 1000)) 的字面量属性访问。这些字段已在 master_group_args.pyadd_argument(default=...) + bind_to)与 py_config_modules.py:389-416 两处声明,正常初始化的 config 上必然存在,于是同一默认值被写在三处并需手工保持一致(当前比对一致,但无任何自动化保护);一旦配置项改名或拼错,代码会静默退回硬编码默认而非 fail-fast,字符串字面量也让全仓搜索失效。同类问题::326-336getattr(self.host_service, "enable_role_snapshot", None) + callable(...) 做 hasattr 式控制流(而 host_service.py:703 本 PR 已定义该方
  • [6.1] Tests — 分布式/跨平台变更有对应覆盖 → issue 三个新增兜底测试全部标记 manual,且伪造掉两个 P0 所在的生产边界,新特性 CI 零覆盖
    kvcm_fallback_test(:35)、master_client_fallback_test(:45)、backend_rpc_server_visitor_fallback_test(:54) 均带 tags=["manual"],按 bazel 语义不会被 //... 展开,本 PR 也未改任何 CI 配置去点名,故约 2000 行路由改动在 CI 中唯一会执行的是只断言 flag 名的 master_group_args_test。三者均不依赖 //rtp_llm:testlib、无 GPU 需求,而同文件 vit_proxy_server_test(:57) 反而依赖 testlib 且未标 manual。三份测试又都用 sys.modules 合成 rtp_llm.* 命名空间(kvcm_fallback_test.py:12-24__path__ 指向源码目录),恰好绕过两个 P0 所在的真实打包与 descriptor pool 边界。覆盖面亦不足:`backend_rpc_server_visitor_fa
  • [6.1] Tests — 新逻辑有聚焦单测 + 相关集成/smoke 测试 → issue 新增的 VipHostSnapshot 后台刷新状态机没有任何测试覆盖
    VipHostSnapshot(:125-235) 承担兜底路径全部冷候选来源,其状态机有多个非平凡不变量:_publish(:180-183) 对空结果直接 return 以保留 last-known-good、refresh_now(:188-214) 在异常与空结果时按 stale_timeout_seconds 决定是否清空、get_hosts(:221-230) 读侧再做一次过期判断、__init__ 复用 VipServerWrapper 构造期发现结果、:162 有「domain 为空则不启动线程」的分支、close(:232) 以 1s join 回收线程。这些正是典型的边界易错逻辑(首次成功、刷新异常保留旧值、超过 stale 阈值清空、清空后恢复、线程回收),但三个新增测试与既有 host_service_test 中均无对应用例;同批新增的 enable_role_snapshot/get_backend_role_addr_snapshot/close(:703-745) 亦无覆盖。
  • [6.1] Tests — 边界 case 覆盖(空、单元素、最大值) → issue 并发上界为单边断言,无法区分正确限流与并发彻底失效
    test_worker_status_is_fresh_concurrency_bounded_and_channels_reused(:279) 通过 fake WorkerStatus 服务端统计并发峰值,最终只在 :336 断言 assertLessEqual(self.worker_service.max_active, 2),即只有上界没有下界。若信号量实现退化为完全串行(max_active == 1),断言仍然通过,而这正是本 PR 探测阶段最关心的性能属性;同样地,kvcm_fallback.py:750 的信号量若被误设为 1,该测试不会失败。用例已在 :286 设置 worker_service.delay_s = 0.01 刻意引入延迟,具备断言下界的条件。

RTP-LLM Checklist

  • [I] 代码质量 — 同一功能用统一工具函数 → issue 手工签入 pb2/pb2_grpc 绕开构建期 codegen,无漂移校验且格式风格分叉
    仓库既有约定是构建期生成:rtp_llm/cpp/model_rpc/proto/BUILD:2,21-25,38-42generate_grpc_proto + py_library.proto 生成 stub,源码树中不存在 model_rpc_service_pb2.py.gitignore 显式忽略),已有多个 Python 消费者。本 PR 反其道而行,把两套 _pb2.py/_pb2_grpc.py 直接签入并靠 BUILD glob 装配(.proto 本身因 glob 只匹配 *.py 而完全未进入依赖跟踪),文件头硬编码 Protobuf Python Version: 4.25.1 而仓库锁定 grpcio-tools==1.57.0,生成工具链在构建体系之外。两份 proto 又都是仓内/上游权威副本的人工镜像,仅靠「Keep this file in sync with ...」注释维持,三处均无自动化校验。此外 kvcm_meta_service_pb2_grpc.py 已被 Black 重排而

Python Static-First Checklist

  • [P.A] 静态结构与类型纪律 — 禁止 getattr/setattr literal 访问 → issue token_ids 到 list 的 O(prompt_len) 转换在路由路径上重复执行两次
    get_master_route_addrs:133-138 已把 input.token_ids 整体 tolist() 一次用于 get_block_cache_keys。开关开启且走兜底时,master_client._input_ids_for_kvcm(:411-430) 又对同一 token_ids 做第二次 tolist()(:417-418) 并额外执行 [int(token_id) for token_id in raw_input_ids](:428) 全量遍历,即在每请求路由路径上多一次 O(prompt_len) 的 Python 层拷贝与逐元素转换;长 prompt 场景下是可避免的重复开销(未做基准测量,故按 P3 记录)。该函数还用 getattr(input, "input_ids", None)/hasattr(raw_input_ids, "tolist") 做类型探测,与 GenerateInput 的既有字段契约脱耦。
  • [P.A] 静态结构与类型纪律 — 禁止 hasattr 做控制流分支 → issue token_ids 到 list 的 O(prompt_len) 转换在路由路径上重复执行两次
    get_master_route_addrs:133-138 已把 input.token_ids 整体 tolist() 一次用于 get_block_cache_keys。开关开启且走兜底时,master_client._input_ids_for_kvcm(:411-430) 又对同一 token_ids 做第二次 tolist()(:417-418) 并额外执行 [int(token_id) for token_id in raw_input_ids](:428) 全量遍历,即在每请求路由路径上多一次 O(prompt_len) 的 Python 层拷贝与逐元素转换;长 prompt 场景下是可避免的重复开销(未做基准测量,故按 P3 记录)。该函数还用 getattr(input, "input_ids", None)/hasattr(raw_input_ids, "tolist") 做类型探测,与 GenerateInput 的既有字段契约脱耦。
  • [P.F] 语言陷阱 — 禁止模块级 import 副作用 → issue 测试在模块导入期永久污染 sys.modules 且不恢复,影子生产类型已与生产漂移
    :146 在模块顶层直接执行 _load_visitor_module(),把 rtp_llmrtp_llm.serverrtp_llm.configrtp_llm.cpprtp_llm.metricsrtp_llm.utilstorch 无条件写入 sys.modules(:15/:32/:105/:141) 且无 finally 恢复;master_client_fallback_test.py:12-20,76 同样如此。相比之下 kvcm_fallback_test.py:14 用了存在性守卫、master_group_args_test.py:27-44 已实现完整的保存 + try/finally 恢复,三份实现行为不一致。更关键的是影子类型已与生产漂移::56-65 自定义一份 FlexlbResponse 并在 :122-126 整体替换真实模块、:191__new__ 绕过 __init__ 手填属性、`master_client_fallback_test.py
  • [P.G] 测试规范 — mock/fake/stub 不得替代本次声称覆盖的生产边界 → issue master_group_args_test 绑定空 SimpleNamespace 并 stub str2bool,断言退化为字面量自证
    :29fake_util_module.str2bool = bool 顶替生产实现以绕开 util 模块对 rtp_llm.ops 的依赖,:50 又以空 SimpleNamespace() 作 master_config。于是 :123assertIs(arguments[flag]["bind_to"][0], master_config) 恒真、只验证对象同一性,无法发现真实 MasterConfig 缺少该字段;:124 比对的是测试自己硬编码的字符串;各新 flag 的 typedefault 全无断言。这恰好绕过最关键的边界:master_client_fallback 是总开关,bool("false")bool("0") 均为 True,只有真实 str2bool 才会解析为 False,若 type=str2bool 被误改为 type=boolMASTER_CLIENT_FALLBACK=false 会在本应关闭的部署上静默打开兜底而该测试仍通过。此外 :130
  • [P.G] 测试规范 — 数据驱动测试用 pytest.mark.parametrize → issue master_group_args_test 绑定空 SimpleNamespace 并 stub str2bool,断言退化为字面量自证
    :29fake_util_module.str2bool = bool 顶替生产实现以绕开 util 模块对 rtp_llm.ops 的依赖,:50 又以空 SimpleNamespace() 作 master_config。于是 :123assertIs(arguments[flag]["bind_to"][0], master_config) 恒真、只验证对象同一性,无法发现真实 MasterConfig 缺少该字段;:124 比对的是测试自己硬编码的字符串;各新 flag 的 typedefault 全无断言。这恰好绕过最关键的边界:master_client_fallback 是总开关,bool("false")bool("0") 均为 True,只有真实 str2bool 才会解析为 False,若 type=str2bool 被误改为 type=boolMASTER_CLIENT_FALLBACK=false 会在本应关闭的部署上静默打开兜底而该测试仍通过。此外 :130

Strengths

  • KVCM 被严格限定为可用性兜底:仅 resp.connection_failed 触发(master_client.py:686),FlexLB 显式 8600 与业务/准入错误语义保持不变;master_addr 为空时 respconnection_failed_response() 起步,route_ips:251allow_domain_fallback 仍成立,domain 安全网未被绕过。开关默认关闭(py_config_modules.py:389),翻转单个 env 即可回滚。
  • VipHostSnapshothost_service.py:125-235)把阻塞式服务发现搬到 daemon 线程,读侧只取短锁并返回不可变 tuple,用 stale_timeout_seconds 约束 last-known-good 生命周期,并复用 VipServerWrapper 构造期的首次发现结果避免首请求竞态。
  • KvcmFallbackConfig.__post_init__kvcm_fallback.py:60-121)对正数、非负、端口范围、hot_candidate_pool_size <= candidate_pool_size 做集中校验,并显式排除 bool(避开 isinstance(True, int) 陷阱);master_client.py:209-223 对必填项抛出点名配置项的可读 ValueError
  • _probe_workerkvcm_fallback.py:763-787)校验 worker 上报的 block_sizeblock_hash_lookahead_tokens 与本端一致,不一致即排除该 worker,属正确的 fail-closed 哈希契约保护,并按 (target, block_size, lookahead) 去重日志避免每请求刷屏。
  • 选点确定性好:_cold_ranksha256(request_id|instance_id|route_target) 打散冷候选,ttft_key/cache_leader 的排序键均以 route_target 收尾兜底,同分不随机化、结果可复现可回放;候选身份统一收敛为 (IP, route gRPC port) 并在应用 Top-H 前去重。
  • 两份 proto 与仓内权威副本逐字段核对后 wire 兼容:字段号、reserved 2reserved 19,20optional 标记与 model_rpc_service.proto:439-470 逐项一致,/RpcService/GetWorkerStatus 与线上引擎路径逐字相同;kvcm_meta_service.proto 声明了完整 kv_cache_manager.proto.meta 命名空间,不污染全局符号表。
  • 消费侧 gRPC 使用稳妥:kvcm_fallback.py:607-638 按 target 缓存 channel/stub 并配置 keepalive,:663/:730/:759 每次 RPC 都显式传 timeout,无无界等待。
  • master_client_fallback_block_hash_lookahead_tokens-1 而非 0 作未设置哨兵(py_config_modules.py:396),正确区分「未配置」与「引擎上报 0」;新增测试用真实 grpc.aio.server() 做端到端 RPC 验证,calculate_vllm_block_cache_keys 有 FlexLB/vLLM 双侧金标向量与「丢弃末尾不完整 block」用例。

@@ -0,0 +1,75 @@
syntax = "proto3";

// Keep this file package-less: the deployed engine serves

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P0] 无 package 的 worker_status proto 与引擎 model_rpc_service proto 根符号完全重名,开关一开即 duplicate symbol 崩溃

该文件注释「Keep this file package-less」,故 StatusVersionPB(:6)、TaskInfoPB(:11)、KvCacheGroupModePB(:42)、WorkerStatusPB(:48)、service RpcService(:73) 全注册为顶层符号。已核实 model_rpc_service.proto 同样无 package 声明,且在 342/390/439/445/654 行定义完全同名的这 5 个符号。worker_status_service_pb2.py:7,17_descriptor_pool.Default().AddSerializedFile(...),落进程级全局 pool;model_rpc_service_pb2generate_grpc_proto 生成、同样落默认 pool。两者在同一前端进程加载:backend_rpc_server_visitor.py:10 导入 ModelRpcClient(`model_rpc_client.py...

建议: 优先复用已有 //rtp_llm/cpp/model_rpc/proto:model_rpc_service_py_proto:它已提供字段号完全一致的 StatusVersionPB/WorkerStatusPB 与方法路径同为 /RpcService/GetWorkerStatusRpcServiceStub,把 kvcm_fallback.py 改为从 rtp_llm.cpp.model_rpc.proto.model_rpc_service_pb2(_grpc) 导入并删除 worker_status_proto/ 整个目录(同目录 vit_proxy_server.py:16 已是此模式),一并消除 DRY 与后续 proto 漂移。若确需保留独立轻量副本,必须为其声明专属 package 隔离符号(stub 中保留 '/RpcService/GetWorkerStatus' 字面路径即可维持 wire 兼容),或改用私有 descriptor_pool.DescriptorPool();并补一个在真实 rtp_llm 包下同进程先后 import 两个 pb2 的回归用例锁死该不变量。

Checklist: [6.1] 依赖方向:无循环依赖/跨层惊喜

Comment thread rtp_llm/server/BUILD
@@ -4,9 +4,12 @@ py_library(
name = "server",
srcs = glob([

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P0] server py_library 的 srcs glob 漏掉 worker_status_proto,wheel 与 runfiles 均缺该模块

py_library(name="server")srcs = glob(["*.py", "server_args/*.py", "kvcm_proto/*.py"])(:5-9) 本次只补了 kvcm_proto/*.py,遗漏同批新增的 worker_status_proto/*.py;bazel 的 *.py 不递归子目录。全仓 BUILD 检索确认唯一命中为本文件 :8 的 kvcm_proto,不存在 worker_status_proto/BUILDrtp_llm/BUILD 亦无 server/** 兜底 glob,故该目录 4 个文件不属于任何 target。而 kvcm_fallback.py:24-27 是模块级 import,master_client.py:149-150client_fallback_enabled 时于 __init__ 中无 try 保护调用 _create_kvcm_fallback_client():155 即 import)。故 bazel 产物/wh...

建议: 在 glob 中补 "worker_status_proto/*.py",与 kvcm_proto/*.py 对称。更稳妥的是按上一条建议复用 model_rpc_service_py_proto 并删除该目录,或按仓库既有约定(rtp_llm/cpp/model_rpc/proto/BUILD:21-42generate_grpc_proto + py_library)为 proto 目录建独立 target 并显式 deps,由 bazel 托管 srcs,从结构上消除后续新增 proto 子包时再次漏配的可能。修复后请以非 manual 方式实跑 //rtp_llm/server/test:kvcm_fallback_test 验证 runfiles 完整,并补一个通过真实包路径 import rtp_llm.server.kvcm_fallback(不改写 sys.modules)的轻量用例,使此类打包缺失能被 CI 直接拦住。

Comment thread rtp_llm/server/test/BUILD
"//rtp_llm:grpcio",
"//rtp_llm:protobuf",
],
tags = ["manual"],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 三个新增兜底测试全部标记 manual,且伪造掉两个 P0 所在的生产边界,新特性 CI 零覆盖

kvcm_fallback_test(:35)、master_client_fallback_test(:45)、backend_rpc_server_visitor_fallback_test(:54) 均带 tags=["manual"],按 bazel 语义不会被 //... 展开,本 PR 也未改任何 CI 配置去点名,故约 2000 行路由改动在 CI 中唯一会执行的是只断言 flag 名的 master_group_args_test。三者均不依赖 //rtp_llm:testlib、无 GPU 需求,而同文件 vit_proxy_server_test(:57) 反而依赖 testlib 且未标 manual。三份测试又都用 sys.modules 合成 rtp_llm.* 命名空间(kvcm_fallback_test.py:12-24__path__ 指向源码目录),恰好绕过两个 P0 所在的真实打包与 descriptor pool 边界。覆盖面亦不足:`backend_rpc_server_visitor...

建议: 去掉这三个目标的 manual 标记纳入 CI 通配;若 kvcm_fallback_test 确因远端沙箱限制 loopback 端口而必须 manual,请在 BUILD 中注明原因,并至少保证完全不涉网络的另两个目标在 CI 执行。补齐关键边界用例:fake 返回 connection_failed=True / fallback=True 时断言 domain_calls == 1 且最终取到 domain 地址、未抛 ROUTE_ERROR;input_token_batched=True 时断言跳过 master 路由;get_master_addr() 非空时断言行为与改动前一致。并额外补一个非 manual 的轻量导入型测试,通过真实 rtp_llm.server 包路径 import kvcm_fallback,使打包与符号注册类缺陷有测试兜底。提交前请实跑全部新增 target,确保「已加测试」与「测试可执行」一致。

Checklist: [6.1] 分布式/跨平台变更有对应覆盖

self._worker_status_stubs[target] = stub
return stub

async def _resolve_leader(self, trace_id: str) -> str:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] KVCM leader 解析失败无负缓存且被单锁串行,故障期逐请求放大延迟

_resolve_leaderasync with self._leader_lock(:645) 内先 asyncio.to_thread 做 bootstrap 发现,再顺序遍历全部 target 逐个 GetClusterInfo,每个用 request_timeout_ms(默认 100ms)。全部失败且 previous_leader 为空时 :680 直接 raise,而 self._leader_refreshed_at 仍为初始 0.0——无失败时间戳、无熔断、无 backoff(全仓亦搜不到 breaker/cooldown)。:643/:647 的双检查以 self._leader 为前提,leader 为 None 时恒假,故每个排队请求都重跑完整串行扫描并互相阻塞在同一把锁上。_invalidate_leader(:682) 在 GetHostCacheState 失败(:733)与 SERVER_NOT_LEADER(:738) 时都把 _leader 置 None,使该路径在 KVC...

建议: 补充失败负缓存与熔断:解析失败时也记录 _leader_failed_at,在一个短冷却窗口(如 request_timeout_ms 的数倍)内让后续请求直接快速失败而不重复扫描,冷却后半开重试;bootstrap 目标改为并发 asyncio.gather 或限定单次扫描的目标数量,避免串行累加;并为 leader 解析失败/超时与「已熔断」状态上报 kmonitor 计数指标,使故障期行为可观测、可告警。

return None
return worker_load_snapshot(candidate, response)

async def query_and_select(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 兜底链路无整体时间预算,WorkerStatus 信号量获取也无 deadline

query_and_select(:790-924) 与 master_client._try_kvcm_fallback(:432) 全链路无任何 asyncio.wait_for,只有单次 RPC 级 timeout;backend_rpc_server_visitor.py:235 的调用点同样未加超时,请求级 TTFT 预算对兜底路径不生效。_worker_status_semaphore(:594) 按 worker_status_concurrency(默认 3)在 __init__ 创建,为该客户端所有并发请求共享;_probe_worker(:750) 的 async with 无获取超时且跨整个 RPC 持有。默认 candidate_pool_size=3cold_candidate_batch_size=3,单请求首轮 asyncio.gather(:875) 即占满全部许可;FlexLB 故障时所有请求都走该路径,探测排队、延迟随并发线性放大且无上界,plan.batches() 的多轮探测(:873-...

建议:asyncio.wait_forquery_and_select(或 _try_kvcm_fallback)加整体预算,预算由 TTFT 剩余时间或专用配置派生,超时即返回 None 交由 domain fallback;信号量按并发规模放大或改为按请求限流并给获取加超时;同时为兜底整体延迟与「已超预算」次数增加 kmonitor 指标,使 fail-fast 与 fallback 的边界显式可控可告警。

@LLLLKKKK LLLLKKKK left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Code Review - PR #1335 (non-blocking suggestions)

18 条 P2/P3 建议,不阻塞合并。阻塞判定与完整摘要见上一条 review。

return FlexlbResponse.ok(
[
RoleAddr(
role=RoleType.PREFILL,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 兜底返回的 RoleAddr 硬编码为 PREFILL,与打分层的 PDFUSION 支持不一致

select_cache_affinity_first:461-464 明确接纳 "PREFILL" in role or "PDFUSION" in role 的 worker,但 _try_kvcm_fallback:511-519 无条件构造 RoleAddr(role=RoleType.PREFILL, ...),丢弃了 worker 实际上报的角色(kvcm_fallback.py:430 已从 WorkerStatusPB.role 取到),即 PDFUSION worker 会被贴上 PREFILL 标签。而 get_backend_role_list:118-120 在 FRONTEND 配置 pdfusion_domain 时产出 [PDFUSION]。此时 get_master_route_addrs:163 成功返回 None,route_ips:251allow_domain_fallback 恒为 True,need_domain_routing(:248) 因 {PREFILL} ...

建议: 按 worker 上报角色(worker_load_snapshot 已保留 role)或部署形态的 backend_role_list 返回正确的 RoleType,PDFUSION 部署应返回 PDFUSION,使兜底结果能满足 need_domain_routing 而不被 domain 覆盖;对「兜底已给出正确角色、仅需补齐其余 role」的情形把日志降为 debug 或改用区分文案,避免把正常补齐误报为 domain 降级并污染 DOMAIN_ROUTE_QPS 基线。若当前只打算支持纯 PREFILL 部署,则应在 select_cache_affinity_first 中同步收窄 role 过滤并在配置校验处显式拒绝其他形态,让两层契约一致,并补一个 PDFUSION 形态下断言返回角色的用例。

Checklist: [6.1] LSP:子类/重写保持基类契约

return CandidatePlan(tuple(hot), tuple(cold[:cold_budget]))


def build_candidate_pool(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 兜底链路存在无调用方的死代码与未装配的隐藏参数,p2p_hit_discount 成为无效旋钮

build_candidate_pool(:397) 自述为「Compatibility wrapper around build_candidate_plan」,但该模块本 PR 全新引入、无历史版本需兼容;全仓检索确认它与 select_max_local_affinity(:289) 除自身定义与 __all__(:953,958) 外无任何调用方,属投机性抽象。另有三个只在 KvcmFallbackConfig 带默认值、无任何 CLI/env 入口的隐藏参数:leader_refresh_interval_ms=10_000(:43,直接决定故障期 leader 重扫频率)、p2p_host_count=0(:44)、minimum_local_blocks=1(:48)。其中 p2p_host_count 直接写入 GetHostCacheStateRequest(:725) 且全文检索 master_client.py 无任何装配点,恒为 0 → KVCM 不返回 p2p 匹配、p2p_fetch_blocks 恒为 0,...

建议: 删除 build_candidate_poolselect_max_local_affinity 及其 __all__ 条目(若仅测试需要,改为直接调用 build_candidate_plan)。对 p2p 二选一:要么补上 p2p_host_count 的配置项与装配使 p2p_hit_discount 真正生效,要么先移除 master_client_fallback_p2p_hit_discount 这个无效 flag(YAGNI),待 p2p 能力接通时再一并引入,避免运维误认为可调。同时把 leader_refresh_interval_msminimum_local_blocks 暴露为 master_* 配置,或在注释中说明为何刻意固定,避免出现只能改代码才能调整的隐藏行为。


resp = await self._send_schedule_request(
master_addr, payload, ttft_timeout_ms, request_id
flexlb_timeout_ms = ttft_timeout_ms

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] flexlb_transport_timeout_ms 不受 kill switch 约束,且默认 0 使挂死场景兜底不可达

get_backend_role_addrs:652-666 无条件 getattr 读取 master_client_fallback_flexlb_transport_timeout_ms 并执行 min(ttft_timeout_ms, configured),与 client_fallback_enabled 完全无关,同时作用于 master(:670) 与 slave(:680)。两个后果:其一,该参数以 master_client_fallback_ 前缀命名、help 写作 "Per-FlexLB-attempt fallback trigger timeout"(master_group_args.py:268),灰度中先配好该值再把开关回滚为 False 时,FlexLB 仍按缩短后的 deadline 超时且此时已无 KVCM 兜底,只能退化为 domain routing 并丢失 cache 亲和路由;其二,默认值为 0(py_config_modules.py:416),此时 `flexlb_timeout_ms...

建议: 二选一:(a) 把该 min() 收敛进 if self.client_fallback_enabled 分支,使 master_client_fallback 成为真正的单一 kill switch,并给一个开关开启时生效的合理非零默认(或在开关开启且该值为 0 时启动期告警),确保挂死场景能在有界时间内触发兜底;(b) 若确实希望它独立于开关生效,则改名为 master_flexlb_transport_timeout_ms(含 env_name)并同步修正 help 与 py_config_modules.py 附近注释,避免运维误判回滚范围。

@@ -223,16 +223,25 @@ async def route_ips(self, input: GenerateInput):
input_token_batched = True

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

📍 实际位置 rtp_llm/server/backend_rpc_server_visitor.py:162(不在 diff 展示范围内,就近挂载)

[P2] 兜底路径缺少专属指标,三个新增响应字段无生产消费方

FlexlbResponse 新增 route_source(:47)、cache_matchkvcm_outcome(:49),兜底成功时置为 "CLIENT_FALLBACK" 与 18 项 cache_match 明细(master_client.py:472-522)。但全仓检索 route_source/kvcm_outcome 仅命中 master_client.py 自身与 master_client_fallback_test.py:189,226,生产路由层完全不读取。get_master_route_addrs:162 对兜底成功与真实 FlexLB 成功一律上报同一个无标签 MASTER_ROUTE_QPS_METRICroute_ips:236MASTER_ROUTE_RT_METRIC 也无维度且混入 KVCM 发现与 worker 探测耗时,既无法度量降级触发率与恢复,也会让基于该 RT 的既有看板/告警基线漂移。另有静默失效路径:`kvcm_fallback.py:774-7...

建议::162/:236 读取 route_result.route_source 作为 kmonitor tag 上报(或为 CLIENT_FALLBACK 新增独立 QPS/RT 指标),避免污染既有 FlexLB RT 基线;为 kvcm_outcomeselection_reasoncache_query_outcome(含 no_positive_match)与 block hash 契约不匹配各增计数指标,使兜底触发率、选点原因与配置错填可被告警发现;兜底路径的失败日志按去重或采样输出,避免热路径逐请求 ERROR。

Checklist: [6.1] 无 per-forward 调试日志 / 噪声热路径输出

self.config = config
self._bootstrap_resolver = bootstrap_resolver
self._candidate_snapshot_resolver = candidate_snapshot_resolver
self._channels: dict[str, grpc.aio.Channel] = {}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] gRPC channel 与选择历史字典只增不减,滚动发布后累积僵尸连接

KvcmFallbackClient 用四个普通 dict 缓存连接:_channels_stubs_worker_status_channels_worker_status_stubs(:587-590);_stub_for(:601) 与 _worker_status_stub_for(:619) 只做「没有就建」,无容量上限、TTL 或失败淘汰。_invalidate_leader(:682) 只把 _leader 置 None,对应 channel 与 stub 仍留在字典中。_logged_worker_hash_contract_mismatches(:597) 与 _last_selected_ns(:598) 同样只增不减(后者在 :899 持续写入)。这些 key 都是 IP:port 形态的 route_target,在 Pod 滚动更新/扩缩容频繁的集群中会随历史节点数持续累积,长期运行进程将累积 grpc.aio channel(连同 fd 与后台 IO 资源)而永不释放;只有 `close()...

建议: 给四个连接字典引入有界 LRU(容量按活跃 worker 数量级或 candidate_pool_size 的若干倍设定),淘汰时 await channel.close()_invalidate_leader 同步关闭并移除对应 channel/stub。_last_selected_ns_logged_worker_hash_contract_mismatches 改为有界结构,或在候选快照刷新时按当前存活 target 集合收敛清理。


master_route_result: Optional[FlexlbResponse] = None
if not role_addrs_specified and master_addr and not input_token_batched:
can_attempt_master_route = bool(master_addr) or bool(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P3] 路由准入判定跨类重复,visitor 直接读取 MasterClient 内部开关且日志归因错误

:226can_attempt_master_route = bool(master_addr) or bool(self.master_client.client_fallback_enabled) 把「客户端能否路由」这一策略判断复制到调用方,而 master_client.py:439 的真实前置条件还包含 self._kvcm_fallback_client is not None;一旦前置条件演进,visitor 的门禁会静默与之偏离。同时 visitor 在 :218 读一次 host_service.get_master_addr() 用于门禁与日志,master_client.py:620 内部又独立读一次同一 snapshot,两次读取之间后台刷新线程可能更新,导致日志中的 master_addr 与实际发请求的地址不一致。另外 :239-246elif 分支在开关开启时只可能因 input_token_batched=True 进入(此时 can_attempt_master_route 恒为真),但文案...

建议: 把准入判断下沉为 master client 的一个方法(如 master_client.can_route()),由其内部一次性读取 master_addr 并结合 client_fallback_enabled_kvcm_fallback_client 就绪状态给出结论,visitor 只消费该布尔结果与返回的诊断信息;同时按实际命中的条件分别打日志:batched 输入单独一条明确说明跳过 master 路由,can_attempt_master_route 为假时再打一条含 master_addr 与开关状态的日志,避免单条日志混合归因。

Checklist: [6.1] 分层边界:新概念在正确层级,不泄漏内部;[6.1] DIP:高层策略不依赖非必要具体细节

@@ -223,16 +223,25 @@ async def route_ips(self, input: GenerateInput):
input_token_batched = True

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

📍 实际位置 rtp_llm/server/backend_rpc_server_visitor.py:133(不在 diff 展示范围内,就近挂载)

[P3] token_ids 到 list 的 O(prompt_len) 转换在路由路径上重复执行两次

get_master_route_addrs:133-138 已把 input.token_ids 整体 tolist() 一次用于 get_block_cache_keys。开关开启且走兜底时,master_client._input_ids_for_kvcm(:411-430) 又对同一 token_ids 做第二次 tolist()(:417-418) 并额外执行 [int(token_id) for token_id in raw_input_ids](:428) 全量遍历,即在每请求路由路径上多一次 O(prompt_len) 的 Python 层拷贝与逐元素转换;长 prompt 场景下是可避免的重复开销(未做基准测量,故按 P3 记录)。该函数还用 getattr(input, "input_ids", None)/hasattr(raw_input_ids, "tolist") 做类型探测,与 GenerateInput 的既有字段契约脱耦。

建议:get_master_route_addrs 中已转换好的 token 列表作为参数传入 get_backend_role_addrs/_try_kvcm_fallback,复用同一份 list 而不在下游重新转换;_input_ids_for_kvcmgetattr/hasattr 探测改为依赖 GenerateInput 的显式字段与类型契约(或在函数签名上直接要求 List[int])。

Checklist: [6.1] DRY:重复非平凡逻辑被抽取或显式复用;[P.A] 禁止 getattr/setattr literal 访问;[P.A] 禁止 hasattr 做控制流分支

self.master_client.client_fallback_enabled,
input_token_batched,
)
specified_roles = {addr.role for addr in input.generate_config.role_addrs}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

📍 实际位置 rtp_llm/server/backend_rpc_server_visitor.py:324(不在 diff 展示范围内,就近挂载)

[P3] 新开关被 host_service.service_available 前置门控静默旁路

enqueue(:324) 与 batch_enqueue(:335) 仅在 self.host_service.service_available 为真时才调用 route_ips,而 host_service.py:660service_available = bool(self.master_vip.domain) or any(self.role_vip_map.values())。若运维只打开 master_client_fallback 而未配置 master domain 与任何 role domain,新增的 can_attempt_master_route 分支永远不会被执行,开关静默无效且日志中没有任何提示,排查成本高。

建议:BackendRPCServerVisitor.__init__ 中检测 master_client.client_fallback_enabled and not self.host_service.service_available,命中时打一条 warning 明确说明该开关因 host service 不可用而不会生效;或把 client_fallback_enabled 也纳入 service_available 的判定,使开关语义与实际生效范围一致。

Checklist: [6.1] 可观测性:日志/指标/超时可操作、非噪声;[6.1] 回滚路径:风险行为存在运维回滚手段

await client.close()

async def test_worker_status_is_fresh_concurrency_bounded_and_channels_reused(self):
self.service.hosts = [

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P3] 并发上界为单边断言,无法区分正确限流与并发彻底失效

test_worker_status_is_fresh_concurrency_bounded_and_channels_reused(:279) 通过 fake WorkerStatus 服务端统计并发峰值,最终只在 :336 断言 assertLessEqual(self.worker_service.max_active, 2),即只有上界没有下界。若信号量实现退化为完全串行(max_active == 1),断言仍然通过,而这正是本 PR 探测阶段最关心的性能属性;同样地,kvcm_fallback.py:750 的信号量若被误设为 1,该测试不会失败。用例已在 :286 设置 worker_service.delay_s = 0.01 刻意引入延迟,具备断言下界的条件。

建议: 补一条下界断言(候选数大于 1 且服务端已引入延迟时断言 max_active >= 2),使「正确限流」与「并发彻底失效」可区分;或直接断言 max_active == 期望并发数,让配置回归能被捕获。

Checklist: [6.1] 边界 case 覆盖(空、单元素、最大值)

"when KVCM fallback is enabled"
)

self._kvcm_vip = VipServerWrapper(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P3] master_kvcm_use_local 场景下 service_id 中填写的端口被静默丢弃

:225-228VipServerWrapper(service_id, use_local) 构造发现源,随后 resolve_bootstrap_targets(:235-243) 一律用配置的 bootstrap_port 重组 ip:port,忽略 host 自带端口。而 --master_kvcm_service_id 的 help 明确写作 "KVCM bootstrap service id or local IP:port list"(master_group_args.py:58),即文档上支持直连 IP:port 形态;本地/直连调试时若按该说明填写 host:port,端口会被静默替换为 master_kvcm_bootstrap_port(默认 6381),既不报错也无日志提示,容易误判为 KVCM 不可达。

建议:use_local 或 service_id 含端口时保留原端口,或在检测到 host 自带端口与 bootstrap_port 不一致时打一条 warning 明确说明以哪个为准;若确实只支持统一端口,请同步修正 help 文案去掉 "IP:port list" 的表述。

Checklist: [6.1] 错误语义:fail-fast/retry/fallback/silent 行为显式

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.

2 participants