Skip to content

feat: enable Qwen3.5 W8A8 integration - #1323

Open
Tanmo-ai wants to merge 4 commits into
alibaba:mainfrom
Tanmo-ai:feature/ppu-qwen35-cu130
Open

feat: enable Qwen3.5 W8A8 integration#1323
Tanmo-ai wants to merge 4 commits into
alibaba:mainfrom
Tanmo-ai:feature/ppu-qwen35-cu130

Conversation

@Tanmo-ai

@Tanmo-ai Tanmo-ai commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

背景

本 PR 汇总 PPU Qwen3.5 支持所需的通用外源能力,便于统一 review、CI 验证和合入。

PPU 专属 FA3、DeepGEMM、W8A8 INT8 kernel、router 和 executor 仍保留在内源;本 PR 只提供开源侧可复用的配置、加载、注册契约和正确性修复。

主要改动

1. compressed-tensors W8A8 INT8

  • 识别 INT8 per-output-channel 权重和 INT8 per-token 动态激活配置。
  • 加载 INT8 权重与 FP32 per-channel scale,并复用 TP/EP tensor mapping 和切分逻辑。
  • 支持 checkpoint 自定义名称的单配置组及 group_0 兼容行为。
  • 在 DeepEP Low-Latency sizing 中识别 per-token activation quantization。
  • 为 DeepEP Normal 提供 backend-neutral _prepare_dispatch_input() 扩展契约。
  • 阻止量化 checkpoint 静默进入 BF16/no-quant executor。
  • 对非对称 W8A8、FP8 KV cache、不支持的 targets/scheme/regex ignore 等组合 fail-fast。
  • concrete ignore 仅在实际部分命中可量化 {i} 模板时拒绝,兼容真实 checkpoint 中与量化权重无关的具体层条目。

2. out-of-tree backend 延迟注册

  • 新增设备无关的 register_backend_hook() / run_backend_registrations()
  • 提供 linearfused_moeattentionmoe_strategy_choices 四个扩展槽位。
  • Factory 在完成自身 registry 或实现列表初始化后执行 hook。
  • moe_strategy_choices 对每个新 parser 重放,其余 Factory 槽位只执行一次。
  • hook 异常和过晚注册显式报错,未安装外部 backend 时保持 no-op。
  • 增加接入文档、生命周期测试及 parser 重建测试。

内源 PPU backend 在启动时只登记 hook;外源 Factory 消费 hook 后,将内源 FA3、DeepGEMM Linear/MoE strategy 注册到公共 Factory,避免外源直接依赖设备专属实现或使用 monkey patch。

3. Qwen3.5 PPU model 入口

  • 增加统一的 is_ppu() 设备判断。
  • 将 PPU 加入 Qwen3.5 Python model 的设备 allowlist。
  • 不支持设备时输出真实设备类型。

该改动仅开放模型构建入口,具体 PPU Attention、Linear 和 MoE 实现仍由内源 backend 提供。

4. 正确性修复

  • CUDA Graph replay 时同步清理 device block table 与 host mirror。
  • padding row 统一指向保留的 block 0,避免不同 graph batch size 之间残留 block ID 导致跨请求 KV cache 污染。
  • P/D 更新 proposal 状态时保留 MTP proposal 的 GPU tensor,避免后续 MTP decode 使用失效状态。

内外源职责

本 PR 负责:

  • W8A8 checkpoint 配置识别和权重加载;
  • 通用 backend 注册框架;
  • DeepEP dispatch 扩展契约和安全门;
  • Qwen3.5 PPU model 入口;
  • CUDA Graph 与 MTP/PD 公共正确性修复。

本 PR 不包含:

  • PPU INT8 quant kernel;
  • PPU FA3 Attention;
  • PPU DeepGEMM Linear/MoE executor;
  • PPU DeepEP router override 和 strategy 实现。

相关实现放置于内源

Commit 组织

  1. feat(quant): support compressed-tensors W8A8 INT8 checkpoints
  2. feat(runtime): add deferred out-of-tree backend registration
  3. feat(ppu): enable Qwen3.5 runtime and correctness fixes

三个 commit 分别对应量化加载、扩展机制、模型入口与公共正确性修复,最终代码与此前验证组合保持一致。

验证

在本 PR 最终代码与内源 PPU backend 的组合上完成:

  • W8A8 INT8 smoke:4/4 通过
    • MTP TP8/DP1
    • MTP TP8/DP2
    • MTP + CUDA Graph TP8/DP2
    • Prefill/Decode TP8 → TP8
  • BF16 smoke:5/5 通过
    • DeepEP Low-Latency + CUDA Graph
    • DeepEP Normal + DeepGEMM
    • Prefill/Decode TP4 → TP4
    • MTP TP4
    • Prefix Cache TP4
  • 生产镜像构建通过。
  • Whale 部署验证通过。
  • git diff --check 通过。

Supersedes

This PR consolidates and supersedes:

@Tanmo-ai Tanmo-ai changed the title feat(ppu): enable Qwen3.5 W8A8 integration feat: enable Qwen3.5 W8A8 integration Aug 20, 2026

@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 #1323

Status: LGTM

Summary: P0/0 · P1/0 · P2/15 · P3/9

Reviewed: commit 131f35d17f8a · 2026-08-20 21:21 UTC+8

lgtm ready to ci

Non-blocking Suggestions

P2

  • propose_tokens_gpu 两个写入方形状与取值语义分叉,混合 batch 改换取值路径 @ rtp_llm/cpp/model_rpc/DecodeRpcServer.cc:338
    • 建议:在 GenerateStream.h:91 补上与 draft_token_gpu 一致的形状/内容契约注释(明确是“全部 propose_step 个 draft”还是“单 token 种子”),让 DecodeRpcServerStreamCacheResource 两个 producer 写入同一约定。若确认 decode gRPC 交接按设计只需单 token,请在 DecodeRpcServer.cc:333 注明与 P2P 多 token 语义的差异并对 propose_step>1 加断言;否则按契约写入全部 draft。补一条 propose_step>1 单测,断言 pickOneStepDraftTokencollectLegacyProposeSlices 在 gRPC/P2P 两种来源下取到同一 token。
  • backend hook 在全局 RLock 内执行,与 Python import 锁构成锁序反转 @ rtp_llm/utils/backend_registry.py:96
    • 建议:锁内只做生命周期判定与 hook 快照,出锁后执行:with _lock: ...; hooks = tuple(_hooks.get(slot, ())),再在 with 块外循环 hook(**context)_started/_repeatable 置位保留在锁内。若需保持“槽位消费前注册已生效”的可见性,可配合 per-slot threading.Event 让并发的第二个调用方等待首次完成,或改为 per-slot 锁打断跨槽位环路。并在文档「失败语义」一节写明 hook 执行期间持有哪些锁。
  • 一次性槽位在 hook 失败或 owner 重建后静默 no-op,退化为部分注册 @ rtp_llm/utils/backend_registry.py:91
    • 建议:区分“同一 owner 重复消费”与“新 owner”:在 except 中把该槽位从 _started/_repeatable 移除后 re-raise,使状态与实际注册结果一致;或记录已服务过的 owner 身份,对新 owner 重放冻结后的 hook 集合。若坚持当前一次性语义,请在文档「失败语义」中显式写明“hook 失败后该槽位不再执行”,并让第二次调用记录 warning 而非完全静默。
  • moe_strategy_choices 把 argparse 私有内部固化为跨仓契约,且文档与测试给出两种互不兼容写法 @ rtp_llm/server/server_args/moe_group_args.py:201
    • 建议:把 context 收窄为可直接操作的对象:在 moe_group_argsEnvArgumentParser 上提供具名扩展函数(如 add_moe_strategy_choices(*names),内部封装 action 定位、去重与容器类型统一),以 run_backend_registrations("moe_strategy_choices", repeatable=True, add_choices=...) 传出;文档与测试统一改用该公开入口,不再示范 _actions。若为兼容既有外部 hook 必须保留 parser=,请标注为过渡期契约并给出迁移目标。
  • 设备无关的中心工厂硬编码 W8A8 后端专属文案,且绕过统一的 quant_method 取值工具 @ rtp_llm/models_py/modules/factory/fused_moe/strategy_registry.py:87
    • 建议:删除两处 if 分支,改为在通用 ValueError/logger.error 中带上 quant_method 与已注册策略类名列表;若确需后端定制文案,让后端在注册 hook 时向 registry 登记 quant_method -> 提示语 映射,把知识放回后端侧。把方法名收敛为 CompressedW8A8Int8PerChannelQuantConfig.get_method() 的引用(deepep_wrapper.py 同),并把 strategy_registry.py:74-78 统一为 MoeConfigResolver().get_quant_method(config)
  • ROCm EP 量化白名单与 executor 映射构成多源真值,早退丢失 ConditionChecker 诊断 @ rtp_llm/models_py/modules/factory/fused_moe/impl/rocm/strategy/ep.py:25
    • 建议:将白名单与映射合并为单一数据结构(如 Dict[Optional[str], Tuple[executor, FusedMoEQuantConfig]]),can_handle 用 keys 过滤、_resolve_executor_and_quant 直接查表,使二者不可能漂移;确认内源 quant_config 同样不产出后,一并清理上述三处死名。前置过滤改到 check_conditions()checker.check(...) 以保留拒绝原因,与 CUDA 侧一致。补正向断言(如 CompressedW8A8Int8PerChannelQuantConfig.get_method() not in _SUPPORTED_QUANT_METHODS、各支持类 get_method()in 白名单)。若此改动改变了以往未支持格式的 BF16 兜底行为,请在 PR description 标注对存量 ROCm 部署的影响。
  • QuantMethod→字符串映射在两处 switch 重复维护且各自漏枚举值,worker status 持续打 ERROR 并对外上报 UNKNOWN @ rtp_llm/cpp/model_rpc/LocalRpcServer.cc:439
    • 建议:将映射收敛为一个共享函数(例如在 QuantInfo.h/QuantInfo.cc 暴露 quantMethodToString),LocalRpcServerModelConfig::to_string 均复用,并顺带补齐 ModelOptFP4QuarkMXFP4。建议去掉 default: 改为穷举全部枚举值,让下一次新增 QuantMethod 在编译期暴露缺口,而不是在生产以每次轮询一条 ERROR 日志的形式暴露。
  • _prepare_dispatch_input 元组返回的下游契约未表达,scale 归一与 topk 重映射仍由基类隐式分支决定 @ rtp_llm/models_py/modules/factory/fused_moe/impl/cuda/routers/deepep_normal_router.py:151
    • 建议:在 docstring 补齐元组返回的完整下游契约(“非 FP8 元组返回将走 bf16 topk 重映射且 scale 不做 [:,0] 归一”),或为 scale 后处理与 topk 重映射也提供可覆写钩子,避免外部后端依赖隐式基类分支。并在 hook 测试补一条 expert_topk_ids 断言把该语义钉住;另建议对非元组分支补回类型检查,使非张量返回在源头失败而非延后到 expert_x.device
  • 槽位名与上下文键为自由字符串 + kwargs 透传,测试用生产不存在的槽位名,拼写错误静默丢注册 @ rtp_llm/utils/test/backend_registry_test.py:34
    • 建议:将槽位名收敛为共享 Enum/Literal(如 BackendSlot.LINEAR/FUSED_MOE/ATTENTION/MOE_STRATEGY_CHOICES),register_backend_hook/run_backend_registrations 只接受该类型;测试改用常量,并补一条用例:向各槽位注册 fake hook 后 import 对应工厂,断言 hook 被调用且收到预期 context 键(fused_moe 收 registry、linear 收 factory、attention 收四个 imps),使拼写错误在 CI 而非推理数值上暴露。
  • 两个新增量化准入守卫类的唯一回归覆盖落在带 open_skip 的 target @ rtp_llm/models_py/modules/factory/fused_moe/tests/test_cuda_strategies.py:159
    • 建议:确认 open_skip 在当前流水线的实际语义;若确为跳过,请把这两个测试类拆到不带该标签的 target(或移除该 target 的标签),并在 PR 描述说明由哪条 CI 任务保障。ROCm 侧补正向断言(如各支持类 get_method()in _SUPPORTED_QUANT_METHODS),并考虑迁到 tags=["rocm"] 的 target,使白名单在两平台都有门禁。
  • FP8 dtype 残留检查退化为源码文本计数断言 @ rtp_llm/model_loader/test/test_compressed_w8a8_int8_per_channel.py:484
    • 建议:删除该源码扫描用例,改为参数化行为断言:对 INT8 与 FP8 两种 quant_config 分别 WeightModule.create,遍历各权重名的 kernel 子权重,断言其 data_type 与配置声明 dtype 一致(含 MoE/stacked 调用点),从张量属性而非源码文本真正覆盖“某个调用点漏改”的风险。
  • DeepEP hook 测试用 object.new 绕过构造函数,字段与位置参数契约不受保护 @ rtp_llm/models_py/modules/factory/fused_moe/impl/cuda/routers/test/deepep_normal_router_hook_test.py:55
    • 建议:保留 fake buffer,但把实例构造改为对真实 __init__ 的最小化调用(仅 patch DeepEPWrapper.get_instanceDeepepWrapperConfig.from_config_adapter),使字段重命名立即让测试失败;dispatch 改用具名关键字或显式解包完整位置参数;补一条只 stub _do_quant 的 FP8 用例,断言 expert_x_scale 被收窄为一维。
  • 工厂守卫测试仅覆盖空/伪注册表分支,未覆盖真实注册表状态 @ rtp_llm/model_loader/test/test_compressed_w8a8_int8_per_channel.py:151
    • 建议:补一条用真实注册表的用例:构造 W8A8 的 MoE config,断言导入 fused_moe 包后的生产 StrategyRegistry 抛 W8A8 专属 ValueError;再补一条“未量化配置在真实注册表下仍能选出策略”的反向基线。这两类断言测的是 models_py/modules/factory 的行为,建议把 BackendAvailabilityGuardTest 迁到该目录下的测试目标。
  • server_args 新用例把其声称覆盖的入口加载边界整体 mock 掉且缺反向基线 @ rtp_llm/server/server_args/test/server_args_test.py:18
    • 建议:保留现有时序断言,另补:(a) 不 mock 加载函数、向 sys.modules 注入模块级调用 register_backend_hook 的临时入口模块,走真实链路;(b) 无 hook 时 setup_args(["--moe_strategy","external_test_strategy"]) 触发 SystemExit,锁定 hook 是放行外部取值的唯一来源;(c) 槽位启动后注册抛 RuntimeError(带 assertRaisesRegex)。并把该用例移入独立测试类,清理改由 setUp/addCleanup 管理。
  • reset_backend_registrations 无法恢复入口导入缓存,可能污染同进程后续用例 @ rtp_llm/utils/backend_registry.py:99
    • 建议:在 reset_backend_registrations() 中同时调用 import_optional_internal_source_entrypoint.cache_clear()(或提供 reset_backend_entrypoint_cache() 供测试显式调用),使入口可重新导入并重新登记;并在该函数 docstring 与文档职责表补一句“仅在已 patch 入口加载函数的测试中安全——单纯 cache_clear() 也无法让 sys.modules 中的入口模块重新执行”。

P3

  • setup_args 与 run_backend_registrations 重复触发入口加载且丢弃返回值 @ rtp_llm/server/server_args/server_args.py:534
    • 建议:二选一:删除这次重复调用,由槽位消费点统一负责加载;或保留但消费返回值(如 logging.info("internal backend entrypoint loaded=%s", loaded)),使 --moe_strategy 出现 invalid choice 时能从启动日志一眼定位到 entrypoint 未加载。若保留,请在 :532-533 注释中说明它与 registry 内部加载的关系(仅让时机可读,不承担正确性)。
  • 库目标未声明新增的 //rtp_llm:utils 依赖,仅测试目标补了 @ rtp_llm/server/server_args/moe_group_args.py:2
    • 建议:在 rtp_llm/server/BUILDserver 目标 deps 补上 "//rtp_llm:utils",使运行期依赖与声明一致,避免仅依赖 //rtp_llm/server:server 的下游目标 runfiles 缺少 backend_registry.py。同包内既有同类欠账可另开变更统一清理,不必在本 PR 扩大范围。
  • GenerateStream 新用例只覆盖“缺失则保留”,未覆盖“提供则刷新” @ rtp_llm/cpp/engine_base/stream/test/GenerateStreamTest.cc:117
    • 建议:追加一次带已定义 draft_token_gpu(如值 11)的 specUpdate,断言 propose_tokens_gpu 被刷新为 11,使两条分支同时被钉住;改用指定初始化或具名构造避免位置错位;顺带断言 CPU tokens 的哨兵值以表达“为何不能回退到 CPU tokens”的意图。
  • choices 扩展点只保护走 argparse 校验的通道,env 补齐通道静默绕过 @ rtp_llm/server/server_args/server_args.py:366
    • 建议:不必在本 PR 修改解析器行为,但建议在该扩展点或 backend_registry 文档中说明:choices 扩展仅对走 argparse 校验的通道生效,env 补齐通道不校验取值。若后续要收口,可在 _apply_config_bindings 之前对带 choices 的 action 统一补一次 _check_value,并把当前静默 pass 改为显式 self.error(...),使非法 env 取值 fail-fast。
  • W8A8 分支对 input_activations 缺失键抛裸 KeyError,与同函数具名报错风格不一致 @ rtp_llm/config/quant_config.py:268
    • 建议:对这三个键改用 .get(...) 判空或读取前做键存在性校验,使畸形 W8A8 checkpoint 落入既有的 unsupported compressed-tensors scheme ... 明确报错分支,保持 fail-fast 信息风格一致。
  • backend_registry 生命周期异常断言未校验错误消息 @ rtp_llm/utils/test/backend_registry_test.py:66
    • 建议:四处改为 assertRaisesRegex,分别带上 "already initialised""lifecycle changed""backend is broken" 等匹配串,与本 PR 其他测试保持一致,并使未来分支演化不会让用例悄悄换成另一条路径通过。
  • no_auant 拼写错误的策略取值已出现第三处副本,且测试绕过生产决策入口 @ rtp_llm/models_py/modules/factory/fused_moe/tests/test_cuda_strategies.py:179
    • 建议:将策略名收敛为模块级常量,供 argparse choices、策略比较与测试共同引用(顺带在一处修正拼写,并按需保留旧取值作为兼容别名);_conditions_pass 补注释说明“为规避 get_attributes() 的 deep_ep/deepgemm 惰性导入才绕过 can_handle”,避免后续读者误以为覆盖了生产决策入口。
  • PPU 设备识别依赖环境变量真值判断且无任何日志,CUDA 主机可能被静默重分类 @ rtp_llm/device/device_type.py:20
    • 建议:在设备类型解析处补一条 info 日志,写明判定结果与依据(PPU_HOME 命中或 torch 版本串命中),使误判可从启动日志一眼看出;并考虑把 PPU_HOME 的真值判断收紧为路径存在性校验,避免空目录或残留变量触发重分类。请作者确认该判定分支的引入范围与既有 CUDA 部署的兼容性。
  • arch.py 新增的 is_ppu 自身未使用,仅作为隐式再导出通道 @ rtp_llm/models_py/utils/arch.py:11
    • 建议:让 qwen3_next.py 直接从 rtp_llm.device.device_type 导入这四个符号,并移除 arch.py 中未使用的导入;若确实要把 arch 作为设备判定门面,请显式声明 __all__ 并在模块 docstring 写明其再导出职责,使该约定可被工具与读者识别。

Checklist Findings (19 fail / 55 total)

General Principles Checklist

  • [6.1] Architecture — 依赖方向:无循环依赖/跨层惊喜 → issue 库目标未声明新增的 //rtp_llm:utils 依赖,仅测试目标补了
    本次为测试目标补了 "//rtp_llm:utils"server_args/test/BUILD),但真正新增模块级 import 的是库代码:moe_group_args.py:2server_args.py:68 导入 rtp_llm.utils.backend_registry,而 rtp_llm/server/BUILD:8-22py_library(name="server") deps 只有 :request_headers//rtp_llm:warmup//rtp_llm:vipserver 与一个 proto 目标,无 //rtp_llm:utils。同包内已存在同类未声明导入,故属既有欠账;//rtp_llm:utils 以 glob 收纳 utils/**/*.py,新文件本身会被自动纳入。
  • [6.1] Architecture — 兼容性:外部 HTTP/RPC API、持久数据、配置、环境迁移安全 → issue PPU 设备识别依赖环境变量真值判断且无任何日志,CUDA 主机可能被静默重分类
    get_device_type()torch.cuda.is_available() 分支内以 os.environ.get("PPU_HOME") or "ppu" in getattr(torch,"__version__","").lower():20-24)判定 DeviceType.Ppu,全程无日志。一旦 CUDA 主机存在任意非空 PPU_HOME(例如残留环境或并装工具链),is_cuda() 即翻为 False,连带改变 fused_moe/__init__.py 的 FP4 策略注册、arch.py 的 SM 判定与 get_num_device_sms(),而启动日志中没有任何可据以定位的线索。该文件未被任何分片认领,由集成侧补验。
  • [6.1] Architecture — 分层边界:新概念在正确层级,不泄漏内部 → issue arch.py 新增的 is_ppu 自身未使用,仅作为隐式再导出通道
    arch.py:6-12 新增导入 is_ppu,但全文件仅第 11 行出现该符号,无任何使用;其唯一消费者是 qwen3_next.py:238-248,它从 rtp_llm.models_py.utils.arch 而非源头 rtp_llm.device.device_type 导入 get_device_type/is_cuda/is_hip/is_ppu。该文件既无 __all__ 也非包 __init__,因此这是一个未声明的再导出通道:linter 会将其报为未使用导入,任何“清理未使用导入”的改动都会直接打断 qwen3_next.pyis_hip/get_device_type 已存在同样情况,属既有约定。
  • [6.1] Architecture — 可观测性:日志/指标/超时可操作、非噪声 → issue PPU 设备识别依赖环境变量真值判断且无任何日志,CUDA 主机可能被静默重分类
    get_device_type()torch.cuda.is_available() 分支内以 os.environ.get("PPU_HOME") or "ppu" in getattr(torch,"__version__","").lower():20-24)判定 DeviceType.Ppu,全程无日志。一旦 CUDA 主机存在任意非空 PPU_HOME(例如残留环境或并装工具链),is_cuda() 即翻为 False,连带改变 fused_moe/__init__.py 的 FP4 策略注册、arch.py 的 SM 判定与 get_num_device_sms(),而启动日志中没有任何可据以定位的线索。该文件未被任何分片认领,由集成侧补验。
  • [6.1] Architecture — 状态不变量:创建/更新/失败/重试/回滚路径有效 → issue reset_backend_registrations 无法恢复入口导入缓存,可能污染同进程后续用例
    reset_backend_registrations() 只清 _hooks/_started/_repeatable:101-104),但入口导入由 import_optional_internal_source_entrypoint@lru_cache(maxsize=None) 缓存(import_util.py:26),且已导入模块留在 sys.modules 不会二次执行模块体。清理后进程仍认为入口“已加载”,ensure_backend_entrypoint_loaded() 直接命中缓存返回 True,而入口在导入期登记的 hook 已丢失且无法再登记。这正是 server_args_test.py:18 那条用例必须双重 mock 入口加载函数来规避的限制;在存在内源且未 patch 加载函数的环境中,任何依赖“reset 后重放真实入口”的用例都会静默失去 --moe_strategy 扩展。
  • [6.1] Architecture — 错误语义:fail-fast/retry/fallback/silent 行为显式 → issue W8A8 分支对 input_activations 缺失键抛裸 KeyError,与同函数具名报错风格不一致
    W8A8 判定链在 isinstance(activation_config, dict) 之后直接下标 activation_config["type"]["num_bits"]["strategy"]:268-270),仅 dynamic 用了 .get(..., False)。若 checkpoint 提供了 input_activations 字典但缺这三个键,会抛裸 KeyError;而同函数其它不支持场景(非对称 INT8、未识别 scheme)均给出带上下文的 ValueError:277:317),错误语义不一致。
  • [6.1] Software Engineering — DRY:重复非平凡逻辑被抽取或显式复用 → issue no_auant 拼写错误的策略取值已出现第三处副本,且测试绕过生产决策入口
    TestCudaNoQuantFallbackStrategies 硬编码 "no_auant_cpp":179,188)与 "no_auant_dp_normal":197,205)。该拼写错误(应为 no_quant_*)已存在于 moe_group_args.py:167-169 的 argparse choicesno_quant.py:60,88 的比较中,本测试成为第三处副本,日后修正 CLI 取值需同步三处。另外 _conditions_pass:162-172)直接调用 strategy.check_conditions 而非生产入口 can_handle,若 MoeStrategy.can_handle 不再调用 check_conditions,测试仍通过(同文件 ROCm 用例用的是 can_handle,两者不一致)。
  • [6.1] Software Engineering — ISP:调用方不依赖无关大接口 → issue moe_strategy_choices 把 argparse 私有内部固化为跨仓契约,且文档与测试给出两种互不兼容写法
    run_backend_registrations("moe_strategy_choices", repeatable=True, parser=parser):201)只把裸 parser 交给 hook,定位逻辑全部落给外部后端。仓内两处示例都因此遍历私有属性:文档 backend_registration.md:107-115 遍历 parser._actionsaction.choices = list(...) 重新赋值;新增测试 server_args_test.py:28-32 则原地 .choices.append(...)。两种写法对容器类型假设不同:choices 改为 tuple 时文档写法可用而测试写法抛 AttributeError--moe_strategy 一旦重命名,next(...)StopIteration。两种失败都在服务启动路径,且开源侧重构无法通过仓内引用搜索发现下游消费者。文档 :166-169 还承诺保留 parser= 上下文,进一步固化耦合。
  • [6.1] Software Engineering — KISS/YAGNI:无投机性抽象 → issue arch.py 新增的 is_ppu 自身未使用,仅作为隐式再导出通道
    arch.py:6-12 新增导入 is_ppu,但全文件仅第 11 行出现该符号,无任何使用;其唯一消费者是 qwen3_next.py:238-248,它从 rtp_llm.models_py.utils.arch 而非源头 rtp_llm.device.device_type 导入 get_device_type/is_cuda/is_hip/is_ppu。该文件既无 __all__ 也非包 __init__,因此这是一个未声明的再导出通道:linter 会将其报为未使用导入,任何“清理未使用导入”的改动都会直接打断 qwen3_next.pyis_hip/get_device_type 已存在同样情况,属既有约定。
  • [6.1] Software Engineering — LSP:子类/重写保持基类契约 → issue _prepare_dispatch_input 元组返回的下游契约未表达,scale 归一与 topk 重映射仍由基类隐式分支决定
    新钩子(:228-249)允许后端在 use_fp8=False 时也返回 (activation, scale),dispatch 输出随之改为 isinstance(output, tuple) 驱动。但下游 if use_fp8 and self.quant_config.is_per_act_token: expert_x_scale = expert_x_scale[:,0].contiguous():154-155)对非 FP8 元组不做归一;if recv_topk_idx.numel()!=0 and (not use_fp8) and (not use_fp4):167-172)又会对该路径施加 bf16 风格的 expert_topk_ids 重映射。钩子 docstring(:235-241)只说“tuple 返回须保持该后端 executor 期望的 scale 布局”,未提及 topk 语义。新增 hook 测试(deepep_normal_router_hook_test.py:88)只断言 scale 形状
  • [6.1] Software Engineering — OCP:本地扩展点优先于修改中心逻辑 → issue 设备无关的中心工厂硬编码 W8A8 后端专属文案,且绕过统一的 quant_method 取值工具
    StrategyRegistry.get_strategy:87-93)与 LinearFactory.createlinear/factory.py:117-123)各自硬编码 quant_method == "W8A8_INT8_PER_CHANNEL_COMPRESSED" 抛定制文案,而两者都是全设备共享的中心选择层,该量化格式执行能力由外部后端提供——PR 文档自己要求公共注册机制保持设备/厂商无关(backend_registration.md:171-172)。每新增一种仅外部后端可消费的格式都要回到两处中心加 if(违反 OCP)。根因是通用报错(strategy_registry.py:94-97)未输出 quant_method(linear 侧 :128 已输出 quant_config)。该字面量另散落在 deepep_wrapper.py:235quant_config.py:949,全仓无共享常量。另 strategy_registry.py:74-78 直接 `quant_config.get_
  • [6.1] Tests — 分布式/跨平台变更有对应覆盖 → issue 两个新增量化准入守卫类的唯一回归覆盖落在带 open_skip 的 target
    TestCudaNoQuantFallbackStrategies:159)是本 PR“no-quant 策略必须拒绝量化 ckpt”(no_quant.py:58,86 新增 quant_method is None)的唯一直接覆盖,TestRocmEpStrategyQuantFiltering:211)是 ROCm 过滤守卫的唯一覆盖。两者都在 test_cuda_strategies.py,而 tests/BUILD:16 该 target 带 tags=["open_skip"] 且本 PR 未改。作者在同 PR 的 routers/test/BUILD:10 为新 target 刻意只加 H20 未加 open_skip(同文件其余 target 带),说明该标签确实影响执行范围。ROCm 用例还只断言 assertFalse(can_handle):227),对白名单中仍应放行的 None/FP8 系列无正向断言,字面量拼错会静默排除 ROCm FP8 EP 而测试全绿;且该断言运行在 gpu:H20
  • [6.1] Tests — 新逻辑有聚焦单测 + 相关集成/smoke 测试 → issue GenerateStream 新用例只覆盖“缺失则保留”,未覆盖“提供则刷新”
    mtpUpdateKeepsLastGpuProposalWhenNextProposalIsMissing:117)只构造 draft_token_gpu 未定义的 StreamSpecUpdateInfo:127-132),断言旧 GPU 镜像值 9 被保留。生产逻辑是 if (update_info.draft_token_gpu.defined()) 才覆盖(GenerateStream.cc:1024-1025),另一条“定义时刷新”分支无用例:若该条件被误写成恒假或整块删除,PDFUSION 路径将永久使用陈旧 propose token,而本测试依然通过。StreamSpecUpdateInfo 采用 6 字段位置聚合初始化,中间插入同类型字段会静默错位;用例也未断言 tokens-1 哨兵语义。
  • [6.1] Tests — 边界 case 覆盖(空、单元素、最大值) → issue GenerateStream 新用例只覆盖“缺失则保留”,未覆盖“提供则刷新”
    mtpUpdateKeepsLastGpuProposalWhenNextProposalIsMissing:117)只构造 draft_token_gpu 未定义的 StreamSpecUpdateInfo:127-132),断言旧 GPU 镜像值 9 被保留。生产逻辑是 if (update_info.draft_token_gpu.defined()) 才覆盖(GenerateStream.cc:1024-1025),另一条“定义时刷新”分支无用例:若该条件被误写成恒假或整块删除,PDFUSION 路径将永久使用陈旧 propose token,而本测试依然通过。StreamSpecUpdateInfo 采用 6 字段位置聚合初始化,中间插入同类型字段会静默错位;用例也未断言 tokens-1 哨兵语义。

RTP-LLM Checklist

  • [I] 代码质量 — 删除或重命名内部 file、registry entry、model name、metric enum、op binding、plugin symbol 时,必须全仓搜索消费者,并提供替代实现、迁移说明或 smoke 覆盖;只有暴露到 HTTP/RPC/config/persisted format 时才按外部兼容性处理 → issue ROCm EP 量化白名单与 executor 映射构成多源真值,早退丢失 ConditionChecker 诊断
    _SUPPORTED_QUANT_METHODS:25-31)与 _resolve_executor_and_quant 的 if/elif(:64-97)是同一份知识两处表达:新增分支时漏改集合会让 can_handle 先返回 False,最终以“No suitable MOE strategy found”收场,线索指向配置而非漏配白名单。同类漂移已发生——FP8_PER_BLOCK_QUARK/FP4_PER_GROUP/FP4_PER_GROUP_QUARK 仍留在 rocm_moe.py:289,404-405pure_tp_router.py:209linear/factory.py:248 的判定中,而经核验 quant_config.py 全部 get_method():389-949)均不产出这三个名字。此外 can_handle:44-45)直接 return False 绕过 ConditionChecker,运维看不到拒绝原因,与本 PR 在 no_quant.py 采用的
  • [I] 代码质量 — 同一功能用统一工具函数 → issue no_auant 拼写错误的策略取值已出现第三处副本,且测试绕过生产决策入口
    TestCudaNoQuantFallbackStrategies 硬编码 "no_auant_cpp":179,188)与 "no_auant_dp_normal":197,205)。该拼写错误(应为 no_quant_*)已存在于 moe_group_args.py:167-169 的 argparse choicesno_quant.py:60,88 的比较中,本测试成为第三处副本,日后修正 CLI 取值需同步三处。另外 _conditions_pass:162-172)直接调用 strategy.check_conditions 而非生产入口 can_handle,若 MoeStrategy.can_handle 不再调用 check_conditions,测试仍通过(同文件 ROCm 用例用的是 can_handle,两者不一致)。

Python Static-First Checklist

  • [P.A] 静态结构与类型纪律 — 字符串分发用 Enum/Literal → issue 槽位名与上下文键为自由字符串 + kwargs 透传,测试用生产不存在的槽位名,拼写错误静默丢注册
    生产槽位仅四个:linearfused_moeattentionmoe_strategy_choices(各 factory __init__.pymoe_group_args.py:201),全为内联字面量、无共享常量。测试却用 "moe":34-37)、"parser":64,70,73)、"nobody_registered_here":116);真实 MoE 槽位名 fused_moe 从未在本单测被 drain。register_backend_hook 仅在“槽位已启动”时报错(backend_registry.py:62),名字或 context 键写错既不报错也不执行 hook——正是模块 docstring 自述的“wrong numerics rather than a startup failure”。attention 的四个 context 键同样无契约断言。
  • [P.G] 测试规范 — mock/fake/stub 不得替代本次声称覆盖的生产边界 → issue reset_backend_registrations 无法恢复入口导入缓存,可能污染同进程后续用例
    reset_backend_registrations() 只清 _hooks/_started/_repeatable:101-104),但入口导入由 import_optional_internal_source_entrypoint@lru_cache(maxsize=None) 缓存(import_util.py:26),且已导入模块留在 sys.modules 不会二次执行模块体。清理后进程仍认为入口“已加载”,ensure_backend_entrypoint_loaded() 直接命中缓存返回 True,而入口在导入期登记的 hook 已丢失且无法再登记。这正是 server_args_test.py:18 那条用例必须双重 mock 入口加载函数来规避的限制;在存在内源且未 patch 加载函数的环境中,任何依赖“reset 后重放真实入口”的用例都会静默失去 --moe_strategy 扩展。
  • [P.G] 测试规范 — pytest.raises 带 match 参数 → issue backend_registry 生命周期异常断言未校验错误消息
    :66(repeatable 拒绝迟到 hook)、:72(生命周期变更)、:101(drain 后再注册)、:112(hook 异常)均只用 assertRaises。生产侧存在两条语义不同的 RuntimeErrorwas already initialisedbackend_registry.py:64)与 lifecycle changed after it started:86)。经核验二者当前分属 register_backend_hookrun_backend_registrations,故这些用例暂无歧义;但一旦其中一处新增/合并分支,测试将无法证明被测不变量。本 PR 其他新增测试(test_compressed_w8a8_int8_per_channel.py:153deepep_normal_router_hook_test.py:99)均用 assertRaisesRegex,同一 PR 内断言风格不一致。

Strengths

  • 枚举与跨语言契约扩展稳妥:W8A8INT8PTPC 追加为 12,ModelOptFP4=10/QuarkMXFP4=11 编号未变,pybind、.pyi 存根与 C++ 三方同步,get_algo()="w8a8_int8_per_channel" 的 python→C++ 回环由 QuantAlgoBindingTesttest_compressed_w8a8_int8_per_channel.py:491)钉住。
  • 权重加载子类化克制且分派唯一:supported_quant_config_types 使基类只认 FP8、子类只认 W8A8,配合 WeightModule.create 的“多类命中即报错”约束保证每个 config 恰好一个 loader;INT8 走 byte-exact 路径,test_int8_skips_the_fp8_conversion:450)以 convert_calls==0 钉住不经 FP8 设备转换。
  • 失败语义普遍 fail-fast 且带上下文:W8A8 部分层 exclude 命中量化模板主动抛错而非静默数值降级;未识别的 compressed-tensors 方案抛具名 ValueErrorquant_config.py:317)替代抽象类实例化 TypeError;ROCm EP 兜底 else: raise ValueErrorep.py:95)把量化 checkpoint 被静默当 bf16 执行改成 fail-fast。
  • _pick_config_groupquant_config.py:24)保住 group_0 优先语义,仅在原先根本读不到的单命名组形状上扩展,多 group 时告警而非静默。
  • CUDA Graph 修复定位准确:host 镜像清零 kv_cache_kernel_block_id.fill_(0)cuda_graph_runner.cc:395)与设备侧 fill_(0):379,383)语义对称,注释(:389-394)准确写出“padding 行残留旧 block ID → KV 写入活跃请求 block”的触发链,Block 0 为保留安全目标。
  • backend_registry.py 分层选址有据:模块级仅依赖 logging/threading/typing,对 import_util 采用函数内延迟导入以规避它自身要解决的 eager import 循环;moe_strategy_choicesrepeatable=True 正确处理同进程多次 setup_args() 各自新建 parser 的场景;hook 异常不吞。
  • StrategyRegistry.get_strategy 将带副作用的 get_attributes() 由多次调用收敛为一次并注释说明其惰性导入/日志副作用(strategy_registry.py:99-102),语义与原 priority 等价。
  • 测试多为行为断言而非白盒:_RecordingDevice.convert_calls 计数验证 FP8 设备转换门控,registered MOE/Linear compute backend 报错用 assertRaisesRegex 具备判别力,BackendAvailabilityGuardTestsetUp/tearDown 快照恢复 LinearFactory._strategies 避免跨用例污染。

auto accept_tokens = torch::zeros({1, static_cast<int64_t>(propose_step + 1)}, cuda_i32);
accept_tokens[0][0] = sp_output_buffer->tokens[0][0];
propose_tokens_gpu[0] = sp_output_buffer->tokens[0][1];
sp_output_buffer->propose_tokens_gpu = propose_tokens_gpu;

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] propose_tokens_gpu 两个写入方形状与取值语义分叉,混合 batch 改换取值路径

GenerateStream.h:58-61draft_token_gpu 写明 shape:[propose_step],经 GenerateStream.cc:1025 流入 propose_tokens_gpu:91 无形状契约)。同字段现有三种写入形状:gRPC 交接(本次新增)写 1 维 {1} 且仅含 tokens[0][1](首个 draft,DecodeRpcServer.cc:333,337);P2P 写 2 维 [1,N] 含已提交 token(StreamCacheResource.cc:223);device-state 分支 narrow(1,1,N-1) 并注释“应持有全部 propose_step draft”(:234-236)。消费方统一 lastColumnAsFlatMtpBatchStreamProcessor.cc:242,250,372):propose_step==1 等价,>1 时 gRPC 取首个、P2P 取末位。`collectLegacyPropos...

建议:GenerateStream.h:91 补上与 draft_token_gpu 一致的形状/内容契约注释(明确是“全部 propose_step 个 draft”还是“单 token 种子”),让 DecodeRpcServerStreamCacheResource 两个 producer 写入同一约定。若确认 decode gRPC 交接按设计只需单 token,请在 DecodeRpcServer.cc:333 注明与 P2P 多 token 语义的差异并对 propose_step>1 加断言;否则按契约写入全部 draft。补一条 propose_step>1 单测,断言 pickOneStepDraftTokencollectLegacyProposeSlices 在 gRPC/P2P 两种来源下取到同一 token。

_repeatable.add(slot)
for hook in tuple(_hooks.get(slot, ())):
logger.debug("running backend registration hook for slot %r", slot)
hook(**context)

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] backend hook 在全局 RLock 内执行,与 Python import 锁构成锁序反转

run_backend_registrationswith _lock::81)内直接 hook(**context):96),而文档明确鼓励“较重的实现模块放在回调内部导入”(backend_registration.md:88-89,126-127),示例 :95-104 亦在 hook 内 import。四个槽位消费点均位于模块体(import 期):fused_moe/__init__.py:120linear/__init__.py:31attention/__init__.py:159。于是可形成相反加锁顺序:线程 A 持 _lock 等模块 M 的 import 锁;线程 B 持 M 的 import 锁后在模块体消费另一槽位并等 _lock。环中一条边是 threading.RLock,CPython 的 import 死锁检测无法识别,表现为启动期无日志挂起。仓内 import_util.py:77-81 正是刻意先出锁再 importlib.import_module

建议: 锁内只做生命周期判定与 hook 快照,出锁后执行:with _lock: ...; hooks = tuple(_hooks.get(slot, ())),再在 with 块外循环 hook(**context)_started/_repeatable 置位保留在锁内。若需保持“槽位消费前注册已生效”的可见性,可配合 per-slot threading.Event 让并发的第二个调用方等待首次完成,或改为 per-slot 锁打断跨槽位环路。并在文档「失败语义」一节写明 hook 执行期间持有哪些锁。

Comment thread rtp_llm/utils/backend_registry.py Outdated

# Out-of-tree backends ship MoE strategies the public parser must not
# advertise, so let them extend the accepted choices here instead.
run_backend_registrations("moe_strategy_choices", repeatable=True, parser=parser)

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] moe_strategy_choices 把 argparse 私有内部固化为跨仓契约,且文档与测试给出两种互不兼容写法

run_backend_registrations("moe_strategy_choices", repeatable=True, parser=parser):201)只把裸 parser 交给 hook,定位逻辑全部落给外部后端。仓内两处示例都因此遍历私有属性:文档 backend_registration.md:107-115 遍历 parser._actionsaction.choices = list(...) 重新赋值;新增测试 server_args_test.py:28-32 则原地 .choices.append(...)。两种写法对容器类型假设不同:choices 改为 tuple 时文档写法可用而测试写法抛 AttributeError--moe_strategy 一旦重命名,next(...)StopIteration。两种失败都在服务启动路径,且开源侧重构无法通过仓内引用搜索发现下游消费者。文档 :166-169 还承诺保留 parser= 上下文,进一步固化耦合。

建议: 把 context 收窄为可直接操作的对象:在 moe_group_argsEnvArgumentParser 上提供具名扩展函数(如 add_moe_strategy_choices(*names),内部封装 action 定位、去重与容器类型统一),以 run_backend_registrations("moe_strategy_choices", repeatable=True, add_choices=...) 传出;文档与测试统一改用该公开入口,不再示范 _actions。若为兼容既有外部 hook 必须保留 parser=,请标注为过渡期契约并给出迁移目标。

Checklist: [6.1] ISP:调用方不依赖无关大接口

f"tp_size={config.tp_size}, "
f"use_deepep_low_latency={config.moe_config.use_deepep_low_latency if config.moe_config else False}"
)
if quant_method == "W8A8_INT8_PER_CHANNEL_COMPRESSED":

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] 设备无关的中心工厂硬编码 W8A8 后端专属文案,且绕过统一的 quant_method 取值工具

StrategyRegistry.get_strategy:87-93)与 LinearFactory.createlinear/factory.py:117-123)各自硬编码 quant_method == "W8A8_INT8_PER_CHANNEL_COMPRESSED" 抛定制文案,而两者都是全设备共享的中心选择层,该量化格式执行能力由外部后端提供——PR 文档自己要求公共注册机制保持设备/厂商无关(backend_registration.md:171-172)。每新增一种仅外部后端可消费的格式都要回到两处中心加 if(违反 OCP)。根因是通用报错(strategy_registry.py:94-97)未输出 quant_method(linear 侧 :128 已输出 quant_config)。该字面量另散落在 deepep_wrapper.py:235quant_config.py:949,全仓无共享常量。另 strategy_registry.py:74-78 直接 `quant_config.g...

建议: 删除两处 if 分支,改为在通用 ValueError/logger.error 中带上 quant_method 与已注册策略类名列表;若确需后端定制文案,让后端在注册 hook 时向 registry 登记 quant_method -> 提示语 映射,把知识放回后端侧。把方法名收敛为 CompressedW8A8Int8PerChannelQuantConfig.get_method() 的引用(deepep_wrapper.py 同),并把 strategy_registry.py:74-78 统一为 MoeConfigResolver().get_quant_method(config)

Checklist: [6.1] OCP:本地扩展点优先于修改中心逻辑

Comment thread rtp_llm/config/quant_config.py Outdated
def test_repeatable_slot_rejects_late_hooks(self):
run_backend_registrations("parser", repeatable=True, parser="first")

with self.assertRaises(RuntimeError):

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] backend_registry 生命周期异常断言未校验错误消息

:66(repeatable 拒绝迟到 hook)、:72(生命周期变更)、:101(drain 后再注册)、:112(hook 异常)均只用 assertRaises。生产侧存在两条语义不同的 RuntimeErrorwas already initialisedbackend_registry.py:64)与 lifecycle changed after it started:86)。经核验二者当前分属 register_backend_hookrun_backend_registrations,故这些用例暂无歧义;但一旦其中一处新增/合并分支,测试将无法证明被测不变量。本 PR 其他新增测试(test_compressed_w8a8_int8_per_channel.py:153deepep_normal_router_hook_test.py:99)均用 assertRaisesRegex,同一 PR 内断言风格不一致。

建议: 四处改为 assertRaisesRegex,分别带上 "already initialised""lifecycle changed""backend is broken" 等匹配串,与本 PR 其他测试保持一致,并使未来分支演化不会让用例悄悄换成另一条路径通过。

Checklist: [P.G] pytest.raises 带 match 参数

self._conditions_pass(
CudaNoQuantCppStrategy,
create_model_config_without_quant(),
"no_auant_cpp",

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] no_auant 拼写错误的策略取值已出现第三处副本,且测试绕过生产决策入口

TestCudaNoQuantFallbackStrategies 硬编码 "no_auant_cpp":179,188)与 "no_auant_dp_normal":197,205)。该拼写错误(应为 no_quant_*)已存在于 moe_group_args.py:167-169 的 argparse choicesno_quant.py:60,88 的比较中,本测试成为第三处副本,日后修正 CLI 取值需同步三处。另外 _conditions_pass:162-172)直接调用 strategy.check_conditions 而非生产入口 can_handle,若 MoeStrategy.can_handle 不再调用 check_conditions,测试仍通过(同文件 ROCm 用例用的是 can_handle,两者不一致)。

建议: 将策略名收敛为模块级常量,供 argparse choices、策略比较与测试共同引用(顺带在一处修正拼写,并按需保留旧取值作为兼容别名);_conditions_pass 补注释说明“为规避 get_attributes() 的 deep_ep/deepgemm 惰性导入才绕过 can_handle”,避免后续读者误以为覆盖了生产决策入口。

Checklist: [6.1] DRY:重复非平凡逻辑被抽取或显式复用;[I] 同一功能用统一工具函数

@@ -32,3 +32,7 @@ def is_cuda() -> 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.

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

[P3] PPU 设备识别依赖环境变量真值判断且无任何日志,CUDA 主机可能被静默重分类

get_device_type()torch.cuda.is_available() 分支内以 os.environ.get("PPU_HOME") or "ppu" in getattr(torch,"__version__","").lower():20-24)判定 DeviceType.Ppu,全程无日志。一旦 CUDA 主机存在任意非空 PPU_HOME(例如残留环境或并装工具链),is_cuda() 即翻为 False,连带改变 fused_moe/__init__.py 的 FP4 策略注册、arch.py 的 SM 判定与 get_num_device_sms(),而启动日志中没有任何可据以定位的线索。该文件未被任何分片认领,由集成侧补验。

建议: 在设备类型解析处补一条 info 日志,写明判定结果与依据(PPU_HOME 命中或 torch 版本串命中),使误判可从启动日志一眼看出;并考虑把 PPU_HOME 的真值判断收紧为路径存在性校验,避免空目录或残留变量触发重分类。请作者确认该判定分支的引入范围与既有 CUDA 部署的兼容性。

Checklist: [6.1] 兼容性:外部 HTTP/RPC API、持久数据、配置、环境迁移安全;[6.1] 可观测性:日志/指标/超时可操作、非噪声

get_device_type,
is_cuda,
is_hip,
is_ppu,

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] arch.py 新增的 is_ppu 自身未使用,仅作为隐式再导出通道

arch.py:6-12 新增导入 is_ppu,但全文件仅第 11 行出现该符号,无任何使用;其唯一消费者是 qwen3_next.py:238-248,它从 rtp_llm.models_py.utils.arch 而非源头 rtp_llm.device.device_type 导入 get_device_type/is_cuda/is_hip/is_ppu。该文件既无 __all__ 也非包 __init__,因此这是一个未声明的再导出通道:linter 会将其报为未使用导入,任何“清理未使用导入”的改动都会直接打断 qwen3_next.pyis_hip/get_device_type 已存在同样情况,属既有约定。

建议:qwen3_next.py 直接从 rtp_llm.device.device_type 导入这四个符号,并移除 arch.py 中未使用的导入;若确实要把 arch 作为设备判定门面,请显式声明 __all__ 并在模块 docstring 写明其再导出职责,使该约定可被工具与读者识别。

Checklist: [6.1] 分层边界:新概念在正确层级,不泄漏内部;[6.1] KISS/YAGNI:无投机性抽象

@Tanmo-ai
Tanmo-ai force-pushed the feature/ppu-qwen35-cu130 branch from 131f35d to ab5ca04 Compare August 20, 2026 14:44

@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 #1323

Status: LGTM

Summary: P0/0 · P1/0 · P2/19 · P3/14

Reviewed: commit ab5ca04e147f · 2026-08-20 23:47 UTC+8

lgtm ready to ci

Non-blocking Suggestions

P2

  • propose_tokens_gpu 两个生产者的形状与取值语义分叉 @ rtp_llm/cpp/model_rpc/DecodeRpcServer.cc:338
    • 建议:统一形状契约:建议与 StreamCacheResource.cc:236 对齐,按 tokens.narrow(1, 1, size-1) 发布全部剩余 draft(也使 accept_tokenspropose_step + 1 列语义自洽);若确认该路径只支持单步提案,请就地断言 propose_step == 1,并在 SpeculativeExecutorStreamOutput::propose_tokens_gpu 字段声明处写明「仅首个 draft token」。同时在 MtpBatchStreamProcessorTest.cc 补一条 propose_step >= 2 且 gRPC 与 P2P 握手流混于同一 batch 的用例。
  • W8A8 部分 exclude 的 fail-fast 守卫对 MoE {expert_id} 模板完全失效 @ rtp_llm/model_loader/compressed_w8a8_int8_per_channel_weight.py:38
    • 建议:在 _exclude_pattern_for 中把 {expert_id}(及其它在用占位符)一并替换为 \d+,使 MoE 模板可被匹配;或在 CompressedW8A8Int8PerChannelWeight.support 中对含未知占位符的模板显式拒绝并说明「该模板无法校验 exclude,暂不支持」,避免守卫在 MoE 路径上静默失效。同时修正类 docstring 中「exclude handling 与 FP8 路径一致」的表述以标注该盲区,并补一条「MoE split 格式 + 单 expert ignore」的用例。
  • 逐层枚举式的完整 exclude 被误拒,且 support() 由纯谓词变为可抛异常 @ rtp_llm/model_loader/compressed_w8a8_int8_per_channel_weight.py:46
    • 建议:让 support() 恢复为纯 bool:把 partial-exclude 校验移到 __init__(此时已确定选中本 loader,上下文更准确),或在 WeightModule.create 选出 target_cls 后增加显式 validate() 步骤,把「不适用」与「配置非法」两种语义分开。校验请下移到能拿到 num_layers 的阶段,命中条目数等于层数时按完整 exclude 走非量化回退;若坚持启动期 fail-fast,请在错误信息中列出命中的具体条目并指向 model.layers.{i}.xxx 模板写法(test_exact_template_ignore_can_use_unquantized_fallback 已覆盖该形式)。第 8 行跨模块导入了私有函数 _ckpt_base_matches_quant_exclude,若校验留在 loader 层,建议提升为公开 helper 或基类 protected classmethod。
  • QuantMethod→字符串映射在两处 switch 重复维护且各自漏枚举值,worker status 持续打 ERROR 并对外上报 UNKNOWN @ rtp_llm/cpp/model_rpc/LocalRpcServer.cc:439
    • 建议:在 QuantInfo.h/.cc 提供唯一的 quantMethodToString(QuantMethod),两处统一调用(LocalRpcServerNone → "FP16" 的特例单独 case 保留),新增枚举值只需在一处登记;顺带补齐当前落入 defaultModelOptFP4QuarkMXFP4,并考虑去掉 default: 分支让 -Wswitch 在下次新增枚举时强制补齐,ERROR 日志也才恢复可操作性。
  • backend hook 在持有全局 RLock 时执行,与 Python import 锁构成锁序反转 @ rtp_llm/utils/backend_registry.py:96
    • 建议:分离「状态变更」与「hook 执行」:锁内完成 _started/_repeatable 判定并 tuple(_hooks.get(slot, ())) 快照,释放锁后再执行 hook;若需保证后到线程看到注册完成,用 per-slot threading.Event(首个线程执行完 set(),后到线程不持锁 wait())而非长期持锁。同时补一个多线程用例(两线程同时 drain 同一槽位、hook 内做 import),并在 docstring 与文档「失败语义」一节补一条约束:hook 内不得 import 会反向 import 槽位消费者的模块。
  • 一次性槽位先标记 started 再执行 hook,失败或 owner 重建后静默退化为部分注册 @ rtp_llm/utils/backend_registry.py:91
    • 建议:把 started 标记推迟到 hook 全部成功之后,或在异常路径上回滚 _started/_repeatable(try/except 后重新 raise),使失败的槽位下次仍会重试并保持「全部注册或整体失败」的原子语义;若确实希望「失败即终态」,请显式记录失败并让后续调用重新抛出同一异常,而不是静默 no-op。补两条用例:hook 抛异常后再次 drain 的行为,以及多 hook 场景下部分成功时的最终状态。
  • 后端 hook 执行结果缺少默认级别可观测性 @ rtp_llm/utils/backend_registry.py:95
    • 建议:槽位在启动期最多执行数次,INFO 级日志不构成噪声。建议在 run_backend_registrations() 中当 hook 数量大于 0 时打一条 INFO,记录 slot 名与本次执行的 hook 数量(可含 getattr(hook, "__qualname__", repr(hook)));并把 ensure_backend_entrypoint_loaded() 的返回值在首次调用时以 INFO 记录一次「可选后端入口已加载 / 未找到」,使运维仅凭默认日志即可判定外部后端是否参与了实现选择。
  • moe_strategy_choices 把 argparse 私有结构固化为跨仓契约,且文档与测试给出两种写法 @ rtp_llm/server/server_args/moe_group_args.py:201
    • 建议:把上下文收窄为最小接口:在 init_moe_group_args 中保存 add_argument 返回的 action,改为下发回调(如 run_backend_registrations("moe_strategy_choices", repeatable=True, add_choices=...)),或在 EnvArgumentParser 上提供稳定方法 extend_choices("--moe_strategy", [...]),并同步更新文档示例与测试,使三方共用同一份契约。这样 --moe_strategy 的名称与查找方式重新变成公共侧内部细节,还能在 in-tree 统一做重名校验。
  • attention / fused_moe 的注册 hook 未被设备导入失败保护,与 linear 槽位行为不一致 @ rtp_llm/models_py/modules/factory/attention/__init__.py:159
    • 建议:参照 linear/__init__.py,把 attention / fused_moe 的设备分支导入包进 try/except 并记录 warning,使 run_backend_registrations 在设备实现导入失败时仍能执行;attention 侧还应把 else 分支按 DeviceType 细分,避免非 CUDA 设备强依赖 flashinfer,并补一个与 linear/factory.py:117 同风格的「无可用实现」显式报错,让缺后端时的失败文案指向设备支持而不是 import 栈。三个槽位的失败语义应在文档「失败语义」一节统一声明。
  • ROCm EP 量化 allow-list 与 executor 映射构成双份真值,收窄后 QuarkMXFP4 改为启动失败且被删字符串仍被消费 @ rtp_llm/models_py/modules/factory/fused_moe/impl/rocm/strategy/ep.py:25
    • 建议:消除双份真值:提取 _QUANT_METHOD_TO_EXECUTOR 映射表,can_handle() 直接判成员关系,并删除不可达的 else raise_get_quant_method() 返回类型由 Any 收紧为 Optional[str]。对三个被删字符串二选一:确认为死字符串则同步清理上述 5 处引用并在 commit message 说明,否则保留在表中,避免同一权重格式在 EP 与 pure-TP/Linear 得出不同结论。行为收窄请在 PR description 给出受影响 ROCm 配置矩阵与回退方式(例如临时降级 pure TP 的步骤),并在可执行 ROCm 的 target 上补一条选择用例。
  • 设备无关的中心工厂硬编码 W8A8 后端专属文案(两处复制),且绕过统一的 quant_method 取值工具 @ rtp_llm/models_py/modules/factory/fused_moe/strategy_registry.py:87
    • 建议:本 PR 已引入 backend_registry 扩展点,建议用同一思路替代中心 if:让后端在注册 hook 时登记「可消费的量化方法 → 诊断提示」映射,中心逻辑只做查表;退一步也可把提示收敛为共享数据表 _UNSERVED_QUANT_HINTS,由 linear/factory.pystrategy_registry.py 共用。量化方法名请提升为 Enum/Literal 或模块级常量,由 quant_config 单点定义、其余三处引用。文案保留并行度信息(quant_method=<resolved> + 「若已安装后端请检查 ep_size / moe_strategy」),并统一通过 MoeConfigResolver().get_quant_method(config) 取值。
  • _prepare_dispatch_input 元组返回的下游契约未表达,topk 重映射仍由 use_fp8 隐式决定 @ rtp_llm/models_py/modules/factory/fused_moe/impl/cuda/routers/deepep_normal_router.py:167
    • 建议:把 :167 的判据从 use_fp8/use_fp4 改为「本次 dispatch 是否产出了 scale」(例如 expert_x_scale is None),使量化与非量化路径的 topk 处理由同一事实驱动;若确实希望 tuple 路径沿用 BF16 式重映射,请在 _prepare_dispatch_input 的 docstring 中把该约定与 scale 布局并列写清,并在 deepep_normal_router_hook_test.py 补一条 payload.expert_topk_ids 断言把契约钉住。
  • compressed-tensors targets 校验按组名不对称,同内容 checkpoint 结果取决于组名 @ rtp_llm/config/quant_config.py:55
    • 建议:把「是否校验 targets」从「组名是不是 group_0」解耦为「该 scheme 是否要求全模型 Linear 作用域」:_pick_config_group 只负责选组不做校验,由各 scheme 分支自行决定——W8A8 保留 :273 的调用,FP8/W4A8 等需维持历史宽松语义的分支显式不调用并注释原因。这样 :55 的重复调用可以删除,接受条件也不再隐含依赖组名。
  • Mega-PR:MTP/PD handoff 与 CUDA Graph 修复混入 W8A8 量化与后端注册特性 @ rtp_llm/cpp/engine_base/stream/GenerateStream.cc:1034
    • 建议:将 MTP/PD handoff 与 CUDA Graph 清零(GenerateStream.cc/.hDecodeRpcServer.ccGenerateStreamTest.cccuda_graph_runner.cc)拆为独立 PR 先行合入,本 PR 只保留 W8A8 量化与 backend 注册两条主线;若因发布节奏无法拆分,至少保证 commit 原子、message 分别说明动机,并在 PR description 中分节交代三条主线及各自的回滚边界。
  • 新增 py_test 缺设备标签却 eager import 整个 factory 包,且工厂守卫用例错位、仅覆盖空注册表 @ rtp_llm/model_loader/test/BUILD:40
    • 建议:优先拆分:把 BackendAvailabilityGuardTest 移到 linear/test/fused_moe/tests/ 下已有设备标签的目标(改动这两个 factory 的人才会跑到),让 test_compressed_w8a8_int8_per_channel 保持仅依赖 quant_config + model_loader 的纯 CPU 测试;若必须同文件,则补上与 W4A8 兄弟目标一致的 tags=["H20"]exec_properties。守卫覆盖请至少补一条基于真实注册表状态的用例(真实 registry 下传入 W8A8 config 断言文案),避免只验证「空表必报错」这一自明结论。
  • dtype 模板化改造靠源码文本计数兜底,漏掉两处位置传参调用点 @ rtp_llm/model_loader/test/test_compressed_w8a8_int8_per_channel.py:484
    • 建议:删除源码文本扫描(它测文本而非行为,且在 zip / 仅 pyc 部署下 inspect.getsource 不可用),改为对 w8a8_weight_list 中各 key 参数化(subTest)构造 WeightModule.create,至少补上 linear_attn_qkvz_wlinear_attn_out_wattn_qkv_wffn_w13moe_w1moe_w2,断言 weight.kernel.data_type is torch.int8weight.scale.data_type is torch.float32;同一模板再用 FP8 config 断言 float8_e4m3fn。这样既覆盖位置传参调用点,也不依赖源码可读性。
  • ROCm EP 量化过滤用例在其实际运行的 CUDA 机型上恒真,且唯一覆盖落在 open_skip target @ rtp_llm/models_py/modules/factory/fused_moe/tests/test_cuda_strategies.py:214
    • 建议:让 False 只可能来自量化过滤:patch.object(MoeStrategy, "can_handle", return_value=True) 后再断言仍为 False,或用 spy 断言 get_attributes 未被调用;并补一条正向用例(FP8_PER_CHANNEL_COMPRESSEDquant_config=None 不被拦截),否则白名单写漏一项会让 ROCm FP8 部署静默失配。ROCm 语义建议迁到带 rocm tag 的 target(同仓 test_inline_fp8_quant 有先例),并与作者确认能否把该量化准入回归移出 open_skip 以获得 CI 保护。
  • 测试用 reset_backend_registrations 清空进程级注册表,污染不可逆且 mock 掉其声称覆盖的入口边界 @ rtp_llm/server/server_args/test/server_args_test.py:37
    • 建议:改为快照/恢复而非清空:在 backend_registry 暴露仅供测试的 context manager(进入时保存三个容器副本、退出时还原),测试以 self.addCleanup 使用;backend_registry_test.pysetUp/tearDown(:13、:22)宜一并改造。同时补两条直接断言:hook 执行后 --moe_strategy action 的 choices 确实包含新值,以及未注册的策略名会被 SystemExit 拒绝,使「外部后端扩展生效」不依赖间接推断。
  • 槽位名与上下文键为自由字符串 + kwargs 透传,测试使用生产不存在的槽位名 @ rtp_llm/utils/test/backend_registry_test.py:34
    • 建议:把四个槽位名收敛为模块级常量或 Enum/Literal(由 backend_registry 导出),register_backend_hook/run_backend_registrations 只接受该类型并对未知槽位显式报错;上下文建议改为按槽位定义的 TypedDict 或具名参数,避免 **context 把键名错误推迟到 hook 内部。测试改为引用同一批常量,这样任一侧改名都会被静态检查或用例立即拦住。

P3

  • backend_registry 生命周期异常断言未校验消息,且 noop 用例无任何断言 @ rtp_llm/utils/test/backend_registry_test.py:66
    • 建议:统一改用 assertRaisesRegex 并锚定关键片段::72 用 "lifecycle changed",:66/:101 用 "already initialised",:112 用 "backend is broken",使不同错误路径互不掩盖;:115 显式断言无 hook 被执行(如调用计数列表仍为空),让「noop」语义可验证。
  • GenerateStream 新用例只覆盖「缺失则保留」,未覆盖「提供则刷新」与 commit-only 陈旧提案边界 @ rtp_llm/cpp/engine_base/stream/test/GenerateStreamTest.cc:117
    • 建议:补两个断言方向:(1) 传入已定义的 draft_token_gpu,断言 propose_tokens_gpu 被刷新为新值而非停留旧值;(2) 在 MtpBatchStreamProcessorTest.cc 补一条 commit-only 步之后的 gather 用例,明确断言陈旧 GPU 提案不会被当作本轮提案消费,或断言其回落到 CPU tokens 路径。另建议去掉 :124 的 .to(torch::kCUDA)——被验证语义与设备无关,同文件同类用例用的是 CPU 张量,改回 CPU 可使断言在非 CUDA 后端复用。
  • CUDA Graph host block table 每步全量清零落在 decode 热路径且新不变量无覆盖 @ rtp_llm/cpp/cuda_graph/cuda_graph_runner.cc:397
    • 建议:把清零范围收窄为 padding 区间(narrow(0, current_batch_size, graph_bs - current_batch_size).fill_(0)),保留同样的安全性而避免对有效行做无用写入;若确认 fillParams 也会改写有效行或全量 memset 成本可忽略,请在注释中说明原因或实测量级。另建议补一条 CUDA Graph 层面的用例:先以大 batch replay 写入 block ID,再以小 batch replay,断言 padding 行的 host mirror 为 0。
  • 非 FP8 分支移除了 dispatch 返回值的类型校验,错误定位后移 @ rtp_llm/models_py/modules/factory/fused_moe/impl/cuda/routers/deepep_normal_router.py:161
    • 建议:在 else 分支恢复一条 isinstance(output, torch.Tensor) 校验,或改为与 FP8 分支对称的 raise ValueError,把错误定位保持在 dispatch 边界上。
  • LoadQuantPerChannelFp8Weight 绕过了新引入的 supported_quant_config_types 单一来源 @ rtp_llm/model_loader/per_channel_fp8_quant_weight.py:707
    • 建议:让该子类也声明 supported_quant_config_types = (Fp8PerChannelCompressedQuantConfig,) 并改为读取该属性做 isinstance 判定(is_quanted() 取反的差异单独保留),使「哪个 loader 认哪些 config」在整条继承链上只有一处定义。
  • setup_args 中的入口加载与槽位内部保证重复,且返回值被丢弃 @ rtp_llm/server/server_args/server_args.py:534
    • 建议:二选一收敛加载责任:删除该显式调用完全依赖槽位内部加载,或让 run_backend_registrations 不再自行加载、由调用方统一加载一次。若保留是为了让入口 import 异常在建 parser 之前尽早暴露,请把这层 fail-fast 意图写进注释(当前注释描述的顺序已由槽位保证),并记录一次加载结果。
  • W8A8 分支混用强下标与 get,input_activations 缺键时抛裸 KeyError @ rtp_llm/config/quant_config.py:268
    • 建议:把该条件内的三个强下标统一改为 .get(...)(与同分支其余字段一致),让任何不匹配形态都落到 :317 的具名 ValueError;并补一条「input_activations 为 dict 但缺 type/num_bits/strategy」的用例,断言得到具名错误而非 KeyError。
  • choices 扩展点只保护 argparse 校验通道,env 补齐通道静默绕过 @ rtp_llm/server/server_args/server_args.py:366
    • 建议:在 env 补齐路径中,当 action.choices 非空时校验转换结果并在不合法时走 self.error(...),使 CLI 与 env 两条通道共享同一准入集合;except (ValueError, TypeError): pass 至少应记录一条 warning 说明哪个环境变量被忽略。至少应在文档「扩展槽位」一节说明该槽位只影响 CLI 校验通道,避免外部后端误以为 env 亦被覆盖。
  • no_auant 拼写错误的策略取值出现第三处副本 @ rtp_llm/models_py/modules/factory/fused_moe/tests/test_cuda_strategies.py:179
    • 建议:将策略名收敛为共享常量或 Enum/Literal(例如放在 fused_moe/defs),由 parser choices、策略条件判断与测试共同引用,后续纠正拼写可在一处收敛并配合别名兼容。最低成本过渡措施:加一行注释指明「no_auant_* 是生产侧既有拼写,非本测试笔误」;本用例本意是隔离 quant 校验,也可直接传 "auto" 规避对具体名称的耦合。
  • PPU 设备识别依赖环境变量真值且无任何日志,CUDA 主机可能被静默重分类 @ rtp_llm/device/device_type.py:20
    • 建议:把判定收紧为显式解析(例如要求 PPU_HOME 指向存在的目录、复用 str2bool,或引入显式覆盖变量并对非法值报错),并在首次解析出非默认设备类型时打一条 INFO 记录判定依据(env 命中还是 torch 版本命中),使混装环境中的误分类在启动日志中即可发现。
  • arch.py 新增的 is_ppu 自身未使用,仅作为隐式再导出通道 @ rtp_llm/models_py/utils/arch.py:11
    • 建议:让 qwen3_next.py 直接从 rtp_llm.device.device_type 导入这四个函数,并删除 arch.py 中未使用的 is_ppu 导入;若确实希望 arch 作为统一门面,请显式声明 __all__ 并在注释中说明这是有意的再导出,使意图可检查而不是靠未使用的 import 承载。
  • server 库目标未声明新增的 //rtp_llm:utils 依赖,仅测试目标补了 @ rtp_llm/server/server_args/moe_group_args.py:2
    • 建议:在 //rtp_llm/server:server 的 deps 中显式加入 //rtp_llm:utils,与测试目标保持一致,使依赖声明匹配实际 import;顺带确认是否存在 utils 反向依赖 server 的环,若有则应把 backend_registry 的依赖边界再收窄一层。
  • 路由钩子测试用 object.new 绕过构造函数并依赖魔法位置下标 @ rtp_llm/models_py/modules/factory/fused_moe/impl/cuda/routers/test/deepep_normal_router_hook_test.py:55
    • 建议:改为经真实 __init__ 构造(必要时对 deepep_buffer_wrapper 等外部依赖做窄粒度 patch 或参数注入),使字段契约受保护;若确因外部依赖过重无法构造,请在 _new_router 上加一行注释说明绕过原因。fake buffer 的 dispatch 改为按关键字接收(或至少为 args[5]/args[6] 注明对应参数名),并补一条对 payload.expert_topk_ids 的断言以覆盖 topk 重映射契约。
  • 三个 no-quant 策略重复同一量化判定,其中两处与 executor 完全冗余且新增用例绕过 can_handle() @ rtp_llm/models_py/modules/factory/fused_moe/impl/cuda/strategy/no_quant.py:58
    • 建议:把三处重复判定抽为共享 mixin 或基类方法,并复用 resolver.has_quantization(config)(与 executor 同一工具函数)作为单一来源;若确认 :58/:86 与 executor 侧完全冗余,删除这两处、仅保留 executor 约束更清晰。测试改为经 can_handle(config) 断言候选筛选结果,从而覆盖 strategy/router/executor 条件组合后的真实选择行为。

Checklist Findings (26 fail / 55 total)

General Principles Checklist

  • [6.1] Architecture — 依赖方向:无循环依赖/跨层惊喜 → issue server 库目标未声明新增的 //rtp_llm:utils 依赖,仅测试目标补了
    moe_group_args.py:2server_args.py 新增了 from rtp_llm.utils.backend_registry import ...,两者都被 //rtp_llm/server:serverglob(["*.py", "server_args/*.py"]) 收录(server/BUILD:8-22),但该目标的 deps 仅有 :request_headers//rtp_llm:warmup//rtp_llm:vipserver 与一个 proto 目标;//rtp_llm:warmup 只含 utils/warmup.py 且无 deps,无法提供该模块。本 PR 只给测试目标 server_args/test/BUILD:8 补了 //rtp_llm:utils,库侧仍依赖传递闭包成立。
  • [6.1] Architecture — 兼容性:外部 HTTP/RPC API、持久数据、配置、环境迁移安全 → issue PPU 设备识别依赖环境变量真值且无任何日志,CUDA 主机可能被静默重分类
    get_device_type()os.environ.get("PPU_HOME") 的真值(任意非空值,包括 "0")或 torch 版本串含 ppu 判定为 DeviceType.Ppu(:20-24),且全程无日志。判定逻辑本身是既有代码,但本 PR 新增的 is_ppu()(:37-38)把它接入 qwen3_next.py:248 的模型 allowlist,放大了误分类的影响面:一台 CUDA 主机若残留 PPU_HOME 即被静默重分类,is_cuda() 转为 False,arch.pyis_sm90/is_blackwell 全部退化,attention/linear/fused_moe 落入 else 分支、FP4 策略不再注册,而运维在默认日志中看不到任何线索。
  • [6.1] Architecture — 分层边界:新概念在正确层级,不泄漏内部 → issue arch.py 新增的 is_ppu 自身未使用,仅作为隐式再导出通道
    arch.py:6-12 新增从 device_type 导入 is_ppu,但全文件没有任何使用点(is_sm90/is_blackwell 等均只调用 is_cuda())。它的唯一作用是让 qwen3_next.py:238-243 能从 models_py.utils.arch 一次性导入 get_device_type, is_cuda, is_hip, is_ppu——即把 arch(架构能力判定层)当成 device_type(设备识别层)的隐式再导出通道。__all__ 未声明,静态检查也无法区分「有意再导出」与「未使用导入」。
  • [6.1] Architecture — 可观测性:日志/指标/超时可操作、非噪声 → issue PPU 设备识别依赖环境变量真值且无任何日志,CUDA 主机可能被静默重分类
    get_device_type()os.environ.get("PPU_HOME") 的真值(任意非空值,包括 "0")或 torch 版本串含 ppu 判定为 DeviceType.Ppu(:20-24),且全程无日志。判定逻辑本身是既有代码,但本 PR 新增的 is_ppu()(:37-38)把它接入 qwen3_next.py:248 的模型 allowlist,放大了误分类的影响面:一台 CUDA 主机若残留 PPU_HOME 即被静默重分类,is_cuda() 转为 False,arch.pyis_sm90/is_blackwell 全部退化,attention/linear/fused_moe 落入 else 分支、FP4 策略不再注册,而运维在默认日志中看不到任何线索。
  • [6.1] Architecture — 回滚路径:风险行为存在运维回滚手段 → issue ROCm EP 量化 allow-list 与 executor 映射构成双份真值,收窄后 QuarkMXFP4 改为启动失败且被删字符串仍被消费
    _SUPPORTED_QUANT_METHODS(:25-31)与 _resolve_executor_and_quant() 的 if/elif 链(:64-97)编码同一份 allow-list 需人工同步,且因 can_handle 已前置过滤,:95 的 raise ValueError 在正常路径不可达。方向认可(原 else 把任意方法映射到 BF16 executor,属静默错数值),但 QuarkMXFP4 是真实方法(quant_config.py:651)且 ROCm 在 gfx950 上注册了 RocmMXFp4PureTPStrategy(fused_moe/init.py:64-65),故 gfx950 + MXFP4 + EP 由「静默选错 executor」变为启动即 No suitable MOE strategy found。被删的 FP8_PER_BLOCK_QUARK/FP4_PER_GROUP*(diff:2456/2476)仍被 rocm_moe.py:289,404-405、`pure_tp
  • [6.1] Architecture — 状态不变量:创建/更新/失败/重试/回滚路径有效 → issue 测试用 reset_backend_registrations 清空进程级注册表,污染不可逆且 mock 掉其声称覆盖的入口边界
    用例在 :37(patch 生效之前)与 finally(:64)各调用一次 reset_backend_registrations(),该函数清空进程级 _hooks/_started/_repeatable(backend_registry.py:99-104)。而真实 hook 由入口模块 import 副作用注册,import_optional_internal_source_entrypoint@lru_cache(maxsize=None)(import_util.py:26)且内部走 importlib.import_module(:38),两层缓存都保证注册不会重放,被清掉的 hook 在本进程内永久丢失;按 unittest 字典序本类先于 ServerArgsSetTest 执行,之后十余处 setup_args() 都会以空 hook 集把槽位标记 started。该用例还把 server_argsbackend_registry 两处 ensure_backend_entrypoint_loaded 全部
  • [6.1] Architecture — 错误语义:fail-fast/retry/fallback/silent 行为显式 → issue choices 扩展点只保护 argparse 校验通道,env 补齐通道静默绕过
    EnvArgumentParser 的 env 补齐路径(:342-371)在 CLI 未显式提供该参数时用 action.type(env_value) 转换后直接 setattr,全程不校验 action.choices;:366-368 对 ValueError/TypeError 还直接 pass 静默跳过。因此 MOE_STRATEGY=<任意字符串> 可绕过 --moe_strategy 的 choices 白名单(含本 PR 新增的扩展点),最终以未知策略名进入 MoE 选择逻辑并表现为「无候选」。该行为是 env 通道既有实现,但本 PR 的扩展点使「choices 即准入集合」的预期更强,故值得记录。
  • [6.1] Quality — Commit 原子、message 与行为匹配 → issue Mega-PR:MTP/PD handoff 与 CUDA Graph 修复混入 W8A8 量化与后端注册特性
    本 PR 横跨 6 个风险类,至少包含三条互不依赖的主线:(1) compressed-tensors W8A8 INT8 识别与加载;(2) out-of-tree backend 注册机制(backend_registry.py + 四槽位 + 文档);(3) MTP/PD 分离 handoff 的 device-state 修复(GenerateStream.cc:1034-1036 保留最后有效 GPU 镜像、DecodeRpcServer.cc:333-338 发布、GenerateStreamTest.cc 用例)与 cuda_graph_runner.cc:397 的 host mirror 清零。第三条与前两条无任何调用或数据依赖(位于 speculative handoff 段落,与量化配置链毫无交集),却与量化枚举改动共处同一文件集,回滚时无法只撤销其中一条。
  • [6.1] Quality — Mega-PR 已拆分为独立变更 → issue Mega-PR:MTP/PD handoff 与 CUDA Graph 修复混入 W8A8 量化与后端注册特性
    本 PR 横跨 6 个风险类,至少包含三条互不依赖的主线:(1) compressed-tensors W8A8 INT8 识别与加载;(2) out-of-tree backend 注册机制(backend_registry.py + 四槽位 + 文档);(3) MTP/PD 分离 handoff 的 device-state 修复(GenerateStream.cc:1034-1036 保留最后有效 GPU 镜像、DecodeRpcServer.cc:333-338 发布、GenerateStreamTest.cc 用例)与 cuda_graph_runner.cc:397 的 host mirror 清零。第三条与前两条无任何调用或数据依赖(位于 speculative handoff 段落,与量化配置链毫无交集),却与量化枚举改动共处同一文件集,回滚时无法只撤销其中一条。
  • [6.1] Quality — PR description 说明动机与设计 → issue Mega-PR:MTP/PD handoff 与 CUDA Graph 修复混入 W8A8 量化与后端注册特性
    本 PR 横跨 6 个风险类,至少包含三条互不依赖的主线:(1) compressed-tensors W8A8 INT8 识别与加载;(2) out-of-tree backend 注册机制(backend_registry.py + 四槽位 + 文档);(3) MTP/PD 分离 handoff 的 device-state 修复(GenerateStream.cc:1034-1036 保留最后有效 GPU 镜像、DecodeRpcServer.cc:333-338 发布、GenerateStreamTest.cc 用例)与 cuda_graph_runner.cc:397 的 host mirror 清零。第三条与前两条无任何调用或数据依赖(位于 speculative handoff 段落,与量化配置链毫无交集),却与量化枚举改动共处同一文件集,回滚时无法只撤销其中一条。
  • [6.1] Software Engineering — DIP:高层策略不依赖非必要具体细节 → issue moe_strategy_choices 把 argparse 私有结构固化为跨仓契约,且文档与测试给出两种写法
    该槽位以 parser=parser 把整个 EnvArgumentParser 交给外源 hook,而 hook 真正需要的只是 --moe_strategy 的 choices。于是 hook 必须遍历私有属性 parser._actions 并读写 action.option_strings/action.choicesbackend_registration.md:107-115list(action.choices or ()) + 判重 + 重新赋值 action.choicesserver_args_test.py:28-32 却用 next(...).choices.append(...)(无默认值、无判重),两份唯一参考实现已不一致。参数改名或 choices 改为 tuple 会让前者静默失效、后者抛 StopIteration/AttributeError;因异常有意不吞,故障表现为启动期一个与 MoE 无关的晦涩异常。该上下文还允许 hook 增删任意无关 action。
  • [6.1] Software Engineering — DRY:重复非平凡逻辑被抽取或显式复用 → issue 三个 no-quant 策略重复同一量化判定,其中两处与 executor 完全冗余且新增用例绕过 can_handle()
    no_quant.py 的 :29、:58、:86 三处各自 MoeConfigResolver() + get_quant_method + check(quant_method is None)。其中 :58/:86 两个策略的 executor 均为 TritonFusedMoeExecutor,其 check_conditions 已断言 not resolver.has_quantization(config)(triton_fused_executor.py:36-38),两条件逻辑等价;而 MoeStrategy.can_handle()(strategy_base.py:71-77)必然调用 executor 检查,故这两处新增行为无实际变化(:29 因 executor 接受 [None, "FP8_PER_BLOCK"] 而确有价值)。新增 4 个用例直接调用 strategy.check_conditions(...)(test_cuda_strategies.py:170-172)绕过 can_handle(),仅能证
  • [6.1] Software Engineering — ISP:调用方不依赖无关大接口 → issue moe_strategy_choices 把 argparse 私有结构固化为跨仓契约,且文档与测试给出两种写法
    该槽位以 parser=parser 把整个 EnvArgumentParser 交给外源 hook,而 hook 真正需要的只是 --moe_strategy 的 choices。于是 hook 必须遍历私有属性 parser._actions 并读写 action.option_strings/action.choicesbackend_registration.md:107-115list(action.choices or ()) + 判重 + 重新赋值 action.choicesserver_args_test.py:28-32 却用 next(...).choices.append(...)(无默认值、无判重),两份唯一参考实现已不一致。参数改名或 choices 改为 tuple 会让前者静默失效、后者抛 StopIteration/AttributeError;因异常有意不吞,故障表现为启动期一个与 MoE 无关的晦涩异常。该上下文还允许 hook 增删任意无关 action。
  • [6.1] Software Engineering — KISS/YAGNI:无投机性抽象 → issue arch.py 新增的 is_ppu 自身未使用,仅作为隐式再导出通道
    arch.py:6-12 新增从 device_type 导入 is_ppu,但全文件没有任何使用点(is_sm90/is_blackwell 等均只调用 is_cuda())。它的唯一作用是让 qwen3_next.py:238-243 能从 models_py.utils.arch 一次性导入 get_device_type, is_cuda, is_hip, is_ppu——即把 arch(架构能力判定层)当成 device_type(设备识别层)的隐式再导出通道。__all__ 未声明,静态检查也无法区分「有意再导出」与「未使用导入」。
  • [6.1] Software Engineering — LSP:子类/重写保持基类契约 → issue _prepare_dispatch_input 元组返回的下游契约未表达,topk 重映射仍由 use_fp8 隐式决定
    改动前 use_fp8=False 且 dispatch 返回 tuple 会被 assert isinstance(output, torch.Tensor) 拦死;现在 :151 改为 isinstance(output, tuple),该组合成为合法路径。此时 :154 正确跳过 expert_x_scale[:, 0] 归约,但 :167 的 (not use_fp8) and (not use_fp4) 仍然成立,expert_topk_ids 会走 torch.where(recv_topk_idx == -1, ...) + rank_expert_offset 重映射——这是 BF16 无量化路径的语义。即新的量化 tuple 路径同时拿到「scale 直通」与「BF16 式 topk 重映射」,判据是 use_fp8 而非「dispatch 是否携带 scale」。_prepare_dispatch_input 的 docstring(:235-241)只约定 scale 布局,未提 topk 分支;hook 测试也只断言 `exp
  • [6.1] Software Engineering — OCP:本地扩展点优先于修改中心逻辑 → issue 设备无关的中心工厂硬编码 W8A8 后端专属文案(两处复制),且绕过统一的 quant_method 取值工具
    get_strategy() 在无候选时新增 if quant_method == "W8A8_INT8_PER_CHANNEL_COMPRESSED" 的专用文案(:87-93),同样的硬编码分支在 linear/factory.py:117-123 又重复一次。该方法名在仓内无任何策略消费(仅 quant_config.py:949 产出),完全为外部后端服务,并被 deepep_wrapper.py:235 第四次以裸字面量比较,四处互相锁死、无共享常量。该分支只按量化方法判定:W8A8 权重叠加错误 ep_size/moe_strategy 导致无候选时,用户拿到的是「去安装后端」的提示,比原通用提示更具误导性。此外 :74-78 自行 get_method() 并判空,而仓库统一访问器是 MoeConfigResolver.get_quant_method(config)(config_resolver.py:52-64)。
  • [6.1] Software Engineering — SRP:模块/类职责单一 → issue 新增 py_test 缺设备标签却 eager import 整个 factory 包,且工厂守卫用例错位、仅覆盖空注册表
    新 target(BUILD:40-44)未声明 tags/exec_properties,而同类兄弟 test_compressed_w4a8_int4_per_channel(:24-32)声明了 tags=["H20"] + exec_properties。该测试 :22/:25 导入 factory.fused_moe.strategy_registryfactory.linear.factory,会先执行父包 factory/__init__.py:6-8(attention/fused_moe/linear 全量导入),其中 attention 三个分支都无保护地 import flashinfer 系列;该导入还会执行三个 run_backend_registrations,把槽位加入 _started。而 BackendAvailabilityGuardTest(:130-179)断言的是两个 factory 的守卫文案,与权重加载职责无关,还直接改写 LinearFactory._strategies 私有属性,:
  • [6.1] Tests — 分布式/跨平台变更有对应覆盖 → issue ROCm EP 量化过滤用例在其实际运行的 CUDA 机型上恒真,且唯一覆盖落在 open_skip target
    TestRocmEpStrategyQuantFiltering 断言 RocmEpNormalStrategy().can_handle(config) 为 False(:227),其 target 配置为 exec_properties={'gpu':'H20'}(CUDA 机型)。若移除新增白名单(ep.py:44-46),can_handle 会走 super().can_handleget_attributes() → 导入 impl/rocm/executors/rocm_moe.py(其 :4 即模块级 import aiter),在 H20 上抛 ModuleNotFoundError,被 strategy_base.py:58-64except ImportError 捕获后同样 return False:两条路径结果相同,删除生产守卫用例仍绿。该 target 还带 tags=["open_skip"],意味着这是本次量化过滤修复的唯一定向覆盖却不在开源 CI 中执行。
  • [6.1] Tests — 新逻辑有聚焦单测 + 相关集成/smoke 测试 → issue 路由钩子测试用 object.__new__ 绕过构造函数并依赖魔法位置下标
    _new_routerobject.__new__(router_class) 跳过 DeepepNormalRouterBase.__init__ 并手工赋 7 个字段(:55-72),因此 __init__ 新增或重命名必需字段不会被本测试发现,反而会以指向测试自身的 AttributeError 变红;_TupleDispatchBuffer.dispatch 又以 args[5]/args[6] 读取 topk_ids/topk_weights(:26-27),与生产端 buffer.dispatch 的位置参数顺序(deepep_normal_router.py:137-147)形成无注释的隐式耦合,调整参数顺序时该 fake 会静默取错值。
  • [6.1] Tests — 边界 case 覆盖(空、单元素、最大值) → issue GenerateStream 新用例只覆盖「缺失则保留」,未覆盖「提供则刷新」与 commit-only 陈旧提案边界
    用例仅构造 draft_token_gpu 未定义的情形,断言 propose_tokens_gpu 仍为旧值 9(:135-136)。两处缺口:一是 defined() 为真时应刷新旧值的分支(GenerateStream.cc:1034-1036)无用例,而这是 PDFUSION 每步都走的主路径;二是本用例的 draft_token=-1 正是 commit-only 步,GenerateStream.cc:1023propose_token_.clear(),而消费方 MtpBatchStreamProcessor.cc:249-252 只判断 defined() 便返回该张量,即「无下一步提案却保留了上一步 GPU 提案」这一新引入的陈旧读风险没有任何测试固定其安全边界。

RTP-LLM Checklist

  • [I] 代码质量 — 删除或重命名内部 file、registry entry、model name、metric enum、op binding、plugin symbol 时,必须全仓搜索消费者,并提供替代实现、迁移说明或 smoke 覆盖;只有暴露到 HTTP/RPC/config/persisted format 时才按外部兼容性处理 → issue ROCm EP 量化 allow-list 与 executor 映射构成双份真值,收窄后 QuarkMXFP4 改为启动失败且被删字符串仍被消费
    _SUPPORTED_QUANT_METHODS(:25-31)与 _resolve_executor_and_quant() 的 if/elif 链(:64-97)编码同一份 allow-list 需人工同步,且因 can_handle 已前置过滤,:95 的 raise ValueError 在正常路径不可达。方向认可(原 else 把任意方法映射到 BF16 executor,属静默错数值),但 QuarkMXFP4 是真实方法(quant_config.py:651)且 ROCm 在 gfx950 上注册了 RocmMXFp4PureTPStrategy(fused_moe/init.py:64-65),故 gfx950 + MXFP4 + EP 由「静默选错 executor」变为启动即 No suitable MOE strategy found。被删的 FP8_PER_BLOCK_QUARK/FP4_PER_GROUP*(diff:2456/2476)仍被 rocm_moe.py:289,404-405、`pure_tp
  • [I] 代码质量 — 同一功能用统一工具函数 → issue no_auant 拼写错误的策略取值出现第三处副本
    新用例在 :179、:188、:197、:206 直接写入 "no_auant_cpp" / "no_auant_dp_normal"。该拼写(应为 no_quant)目前同时存在于 moe_group_args.py:167-169--moe_strategy choices 与 no_quant.py:60/88 的条件判断中,因此测试当前是对的;但测试以裸字符串再复制一遍且无任何说明,使三处独立字面量互相锁死——将来纠正拼写时这些用例会莫名失败,读者也难以判断哪边才是笔误。

Python Static-First Checklist

  • [P.A] 静态结构与类型纪律 — 字符串分发用 Enum/Literal → issue no_auant 拼写错误的策略取值出现第三处副本
    新用例在 :179、:188、:197、:206 直接写入 "no_auant_cpp" / "no_auant_dp_normal"。该拼写(应为 no_quant)目前同时存在于 moe_group_args.py:167-169--moe_strategy choices 与 no_quant.py:60/88 的条件判断中,因此测试当前是对的;但测试以裸字符串再复制一遍且无任何说明,使三处独立字面量互相锁死——将来纠正拼写时这些用例会莫名失败,读者也难以判断哪边才是笔误。
  • [P.F] 语言陷阱 — 禁止模块级 import 副作用 → issue 新增 py_test 缺设备标签却 eager import 整个 factory 包,且工厂守卫用例错位、仅覆盖空注册表
    新 target(BUILD:40-44)未声明 tags/exec_properties,而同类兄弟 test_compressed_w4a8_int4_per_channel(:24-32)声明了 tags=["H20"] + exec_properties。该测试 :22/:25 导入 factory.fused_moe.strategy_registryfactory.linear.factory,会先执行父包 factory/__init__.py:6-8(attention/fused_moe/linear 全量导入),其中 attention 三个分支都无保护地 import flashinfer 系列;该导入还会执行三个 run_backend_registrations,把槽位加入 _started。而 BackendAvailabilityGuardTest(:130-179)断言的是两个 factory 的守卫文案,与权重加载职责无关,还直接改写 LinearFactory._strategies 私有属性,:
  • [P.G] 测试规范 — mock/fake/stub 不得替代本次声称覆盖的生产边界 → issue 三个 no-quant 策略重复同一量化判定,其中两处与 executor 完全冗余且新增用例绕过 can_handle()
    no_quant.py 的 :29、:58、:86 三处各自 MoeConfigResolver() + get_quant_method + check(quant_method is None)。其中 :58/:86 两个策略的 executor 均为 TritonFusedMoeExecutor,其 check_conditions 已断言 not resolver.has_quantization(config)(triton_fused_executor.py:36-38),两条件逻辑等价;而 MoeStrategy.can_handle()(strategy_base.py:71-77)必然调用 executor 检查,故这两处新增行为无实际变化(:29 因 executor 接受 [None, "FP8_PER_BLOCK"] 而确有价值)。新增 4 个用例直接调用 strategy.check_conditions(...)(test_cuda_strategies.py:170-172)绕过 can_handle(),仅能证
  • [P.G] 测试规范 — pytest.raises 带 match 参数 → issue backend_registry 生命周期异常断言未校验消息,且 noop 用例无任何断言
    backend_registry.py 有两处语义不同的 RuntimeError:register_backend_hook 抛「slot ... was already initialised」(:63-66),run_backend_registrations 抛「slot ... lifecycle changed after it started」(:85-87)。测试 4 处 assertRaises 均不带消息校验(:66、:72、:101、:112),其中 :72 本意校验 lifecycle 冲突,但只要任何 RuntimeError 被抛出即通过;若实现退化成从 register 路径报错,该用例仍绿。:115 test_draining_slot_without_hooks_is_noop 无任何断言。同 PR 的另两个新测试文件全程使用 assertRaisesRegex,断言精度不一致。

Strengths

  • 复用而非复制:PerChannelFp8Weight 抽出 weight_dtype / apply_fp8_device_conversion / supported_quant_config_types 三个类属性后(per_channel_fp8_quant_weight.py:266-289),INT8 子类仅 3 行属性即完成接入(compressed_w8a8_int8_per_channel_weight.py:22-24),未复制整套加载器。
  • apply_fp8_device_conversion=False 有真实必要性而非投机抽象:ROCm 的 convert_fp8_weight_params 开头即断言 FP8 dtype,若不关闭 INT8 权重会在加载期断言失败;CUDA 侧该函数是恒等实现,故 CUDA 数值行为零变化,并由 _RecordingDevice 的调用计数从行为侧钉住(test:450-482)。
  • 跨语言枚举链路完整闭环且以追加方式扩展:QuantInfo.h:19 的 value=12 未重排既有 0–11,ConfigInit.cc:1150-1187.pyi:1385/1432 同批更新,QuantAlgoBindingTest(test:494-504)用真实 pybind 对象钉住往返;.pyi 更新还顺手补回 __members__ 中此前遗漏的 QuarkMXFP4(diff:2905-2906),修掉一处既有存根漂移。
  • 配置解析兼容性把控到位:_pick_config_group 明确保留 group_0 优先并在多组时告警(quant_config.py:32-44),把新增命名组回退严格限制在原先完全读不到的场景,既有 checkpoint 加载路径零变化;未识别组合从「抽象类实例化 TypeError」改为附带 weights/input_activations 原文的具名 ValueError(:317)。
  • CompressedW8A8Int8PerChannelQuantConfig 全具名参数、allowed_keys 白名单拒绝未知键(:972-983)、显式拒绝 re: 模式(:929-936),无 **kwargs 兜底,明显优于同文件其他吞参的量化配置类;get_supported_kv_cache_dtypes 主动收窄为 fp16/bf16 并注明「未验证组合宁可启动失败」(:962-968),把 --fp8_kv_cache 误配拦在启动期。
  • _ckpt_base_matches_quant_exclude 的新增 Note(per_channel_fp8_quant_weight.py:83-92)如实记录「按 template 而非 layer 实例匹配会在部分排除时产生静默数值错误」这一既有缺陷,并说明为何只在新 W8A8 路径 fail-fast,是对行为边界的诚实交代。
  • 注册机制的分层选址正确且动机可追溯:backend_registry.py 放在 rtp_llm.utils 而非 factory 旁,避免参数解析阶段被迫导入 models_py.modules.factory 触发全部 factory 与通信库 eager import(docstring:22-31 写明动机),文件内确实不含任何设备或厂商名。
  • deepep_normal_router.py:151 把分支判据从 use_fp8 标志改为 isinstance(output, tuple),与 DeepEP「输入 tuple 则输出 tuple」的实际契约一致;157-160 把 assert 换成 ValueError,避免 python -O 下 FP8 缺 scale 被带到 executor,并有 assertRaisesRegex 定向覆盖。
  • CudaNoQuantEpLowLatencyStrategy 新增的 quant_method is None 检查是真实修复:其 executor DeepGemmMaskedExecutor.check_conditions 接受 [None, "FP8_PER_BLOCK"](deepgemm_masked_executor.py:56),此前 FP8_PER_BLOCK 配置可能命中 no-quant 策略而拿到 quant_dtype=None
  • RocmEpNormalStrategy.can_handle() 用 allow-list 前置过滤替代原 else: RocmExpertsBf16 + quant_dtype=None(diff:2485-2492 确认),把量化专家权重被 BF16 executor 消费的「静默错数值」前移为启动失败,方向正确。
  • cuda_graph_runner.cc:390-398 的 host mirror 清零补齐了与 tagged 分支 zero_()(:473-475)对称的缺口,注释交代了触发条件与「block 0 为保留块」前提,且位于 stridedCopyHost 回填(:525)之前,顺序正确。
  • GenerateStreamTest.cc:117-137 是有效回归测试:draft_token=-1 走 commit-only 分支、accept_token_num=1 跳过 block swap,可真实抵达 GenerateStream.cc:1034 的新守卫;改动前 propose_tokens_gpu 会被赋成 undefined 而使 ASSERT_TRUE 失败。
  • DecodeRpcServer.cc:333-338 先做 torch::Tensor 拷贝赋值、:359 才 std::move,引用计数顺序正确,sp_output_buffer->propose_tokens_gpu 不会因移动而失效。
  • docs/backend/backend_registration.md 与实现逐条对得上(入口加载时机、四槽位表、上下文关键字、失败语义、测试清单),并显式声明该机制必须保持设备与厂商无关。

auto accept_tokens = torch::zeros({1, static_cast<int64_t>(propose_step + 1)}, cuda_i32);
accept_tokens[0][0] = sp_output_buffer->tokens[0][0];
propose_tokens_gpu[0] = sp_output_buffer->tokens[0][1];
sp_output_buffer->propose_tokens_gpu = propose_tokens_gpu;

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] propose_tokens_gpu 两个生产者的形状与取值语义分叉

本行把 torch::empty({1})、仅含 tokens[0][1](首个 draft)的张量写入 sp_output_buffer->propose_tokens_gpu。同一字段的另一生产者 StreamCacheResource.cc:223 写入完整 {1,N},其 :234-236 注释明确「MTP verify path expects propose_tokens_gpu to hold all propose_step draft tokens」。共同消费者 MtpBatchStreamProcessor.cc:250/372 统一施加 lastColumnAsFlat()(:147-150 取最后一列):对 {1} 得首个 draft,对 {1,N} 得第 N-1 个;CPU 回落 columnAsFlat(tokens, 1)(:252)取的是首个。propose_step==1 时三者等价,默认配置无回归;propose_step>=2 时同一 batch 内两类 handoff 流取值来源不同。

建议: 统一形状契约:建议与 StreamCacheResource.cc:236 对齐,按 tokens.narrow(1, 1, size-1) 发布全部剩余 draft(也使 accept_tokenspropose_step + 1 列语义自洽);若确认该路径只支持单步提案,请就地断言 propose_step == 1,并在 SpeculativeExecutorStreamOutput::propose_tokens_gpu 字段声明处写明「仅首个 draft token」。同时在 MtpBatchStreamProcessorTest.cc 补一条 propose_step >= 2 且 gRPC 与 P2P 握手流混于同一 batch 的用例。

or src_weight_info.name not in cls.w8a8_weight_list
):
return False
for ckpt_w in src_weight_info.weights:

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] W8A8 部分 exclude 的 fail-fast 守卫对 MoE {expert_id} 模板完全失效

新守卫依赖 _ckpt_base_matches_quant_exclude,而 _exclude_pattern_for(per_channel_fp8_quant_weight.py:61-64)只把 {i} 替换为 \d+{expert_id}re.escape 成字面量。目标模型的 MoE split 模板正是 layers.{i}.mlp.experts.{expert_id}.down_proj.weight(qwen3_next_weight.py:409/422/429),support() 看到的仍是未展开的占位符,生成的正则永远无法匹配 checkpoint 中形如 model.layers.3.mlp.experts.5.down_proj 的 ignore 项。而 W.moe_w1/moe_w2 都在 w8a8_weight_list(:276-277)中,于是既不抛 ValueError 也不返回 False:被排除 expert 的 bf16 张量会与其它 expert 一起以 `data_typ...

建议:_exclude_pattern_for 中把 {expert_id}(及其它在用占位符)一并替换为 \d+,使 MoE 模板可被匹配;或在 CompressedW8A8Int8PerChannelWeight.support 中对含未知占位符的模板显式拒绝并说明「该模板无法校验 exclude,暂不支持」,避免守卫在 MoE 路径上静默失效。同时修正类 docstring 中「exclude handling 与 FP8 路径一致」的表述以标注该盲区,并补一条「MoE split 格式 + 单 expert ignore」的用例。

base_name, quant_config.exclude_modules
)
):
raise ValueError(

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] 逐层枚举式的完整 exclude 被误拒,且 support() 由纯谓词变为可抛异常

两个问题同源。一,support() 只看到单个权重模板、入参无 LoadConfig,拿不到 num_layers,无法区分「只排除第 7 层」与「逐层列出全部 N 层」;而 __init__ 又明确拒绝 re: 模式(quant_config.py:929-936),用户没有正则退路,一份把某模块在所有层都排除的合法 checkpoint 会启动即失败,文案 "per-layer fallback is not supported" 未给出规避方式。二,weight_module.py:73-77support() 当纯谓词用于列表推导筛选 valid_classesmodel_loader 下其余 loader(含同文件 LoadQuantPerChannelFp8Weight.support:703-712)一律只返回 bool,本处 raise 会中断整张注册表扫描。

建议:support() 恢复为纯 bool:把 partial-exclude 校验移到 __init__(此时已确定选中本 loader,上下文更准确),或在 WeightModule.create 选出 target_cls 后增加显式 validate() 步骤,把「不适用」与「配置非法」两种语义分开。校验请下移到能拿到 num_layers 的阶段,命中条目数等于层数时按完整 exclude 走非量化回退;若坚持启动期 fail-fast,请在错误信息中列出命中的具体条目并指向 model.layers.{i}.xxx 模板写法(test_exact_template_ignore_can_use_unquantized_fallback 已覆盖该形式)。第 8 行跨模块导入了私有函数 _ckpt_base_matches_quant_exclude,若校验留在 loader 层,建议提升为公开 helper 或基类 protected classmethod。

break;
case QuantMethod::None:
status_info.precision = "FP16";
break;

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/cpp/model_rpc/LocalRpcServer.cc:439(不在 diff 展示范围内,就近挂载)

[P2] QuantMethod→字符串映射在两处 switch 重复维护且各自漏枚举值,worker status 持续打 ERROR 并对外上报 UNKNOWN

同一份映射手工维护在 ModelConfig.cc:53 quantMethodToStringLocalRpcServer.cc:405 getWorkerStatusInfo 两处,本 PR 又各补一条 W8A8INT8PTPC。对照 QuantInfo.h:6-20,漂移已发生:ModelConfig.ccQuarkMXFP4(11),LocalRpcServer.cc 同时缺 ModelOptFP4(10) 与 QuarkMXFP4(11);两处都有 default:-Wswitch 不会告警。而 QuarkMXFP4 是真实可产出的方法(quant_config.py:651)。后果是以这些格式部署时,每次 worker status 查询都走 :439 default 分支,打 RTP_LLM_LOG_ERROR("unknown quant method") 并把对外的 status_info.precision 置为 "UNKNOWN"

建议:QuantInfo.h/.cc 提供唯一的 quantMethodToString(QuantMethod),两处统一调用(LocalRpcServerNone → "FP16" 的特例单独 case 保留),新增枚举值只需在一处登记;顺带补齐当前落入 defaultModelOptFP4QuarkMXFP4,并考虑去掉 default: 分支让 -Wswitch 在下次新增枚举时强制补齐,ERROR 日志也才恢复可操作性。

_repeatable.add(slot)
for hook in tuple(_hooks.get(slot, ())):
logger.debug("running backend registration hook for slot %r", slot)
hook(**context)

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] backend hook 在持有全局 RLock 时执行,与 Python import 锁构成锁序反转

run_backend_registrations()with _lock:(:81)块内直接 hook(**context)(:96),而文档与 docstring 都要求 hook 内部做惰性 import(backend_registration.md:95-104 的接入示例即 from my_backend.moe import ...)。三个 Factory 槽位的消费点都位于模块体内(fused_moe/init.py:120、attention/init.py:159、linear/init.py:31),调用线程此时已持有该模块的 import lock。若线程 A 持 _lock 执行 hook 并 import 模块 M,线程 B 正在 import M 且其 import 又调用 run_backend_registrations(),两者互等即死锁。文档 141 行自述该锁用于「避免并发导入时重复消费槽位」,说明并发导入在预期内,但 11 条单测无任何并发场景。

建议: 分离「状态变更」与「hook 执行」:锁内完成 _started/_repeatable 判定并 tuple(_hooks.get(slot, ())) 快照,释放锁后再执行 hook;若需保证后到线程看到注册完成,用 per-slot threading.Event(首个线程执行完 set(),后到线程不持锁 wait())而非长期持锁。同时补一个多线程用例(两线程同时 drain 同一槽位、hook 内做 import),并在 docstring 与文档「失败语义」一节补一条约束:hook 内不得 import 会反向 import 槽位消费者的模块。

@@ -32,3 +32,7 @@ def is_cuda() -> 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.

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

[P3] PPU 设备识别依赖环境变量真值且无任何日志,CUDA 主机可能被静默重分类

get_device_type()os.environ.get("PPU_HOME") 的真值(任意非空值,包括 "0")或 torch 版本串含 ppu 判定为 DeviceType.Ppu(:20-24),且全程无日志。判定逻辑本身是既有代码,但本 PR 新增的 is_ppu()(:37-38)把它接入 qwen3_next.py:248 的模型 allowlist,放大了误分类的影响面:一台 CUDA 主机若残留 PPU_HOME 即被静默重分类,is_cuda() 转为 False,arch.pyis_sm90/is_blackwell 全部退化,attention/linear/fused_moe 落入 else 分支、FP4 策略不再注册,而运维在默认日志中看不到任何线索。

建议: 把判定收紧为显式解析(例如要求 PPU_HOME 指向存在的目录、复用 str2bool,或引入显式覆盖变量并对非法值报错),并在首次解析出非默认设备类型时打一条 INFO 记录判定依据(env 命中还是 torch 版本命中),使混装环境中的误分类在启动日志中即可发现。

Checklist: [6.1] 兼容性:外部 HTTP/RPC API、持久数据、配置、环境迁移安全;[6.1] 可观测性:日志/指标/超时可操作、非噪声

get_device_type,
is_cuda,
is_hip,
is_ppu,

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] arch.py 新增的 is_ppu 自身未使用,仅作为隐式再导出通道

arch.py:6-12 新增从 device_type 导入 is_ppu,但全文件没有任何使用点(is_sm90/is_blackwell 等均只调用 is_cuda())。它的唯一作用是让 qwen3_next.py:238-243 能从 models_py.utils.arch 一次性导入 get_device_type, is_cuda, is_hip, is_ppu——即把 arch(架构能力判定层)当成 device_type(设备识别层)的隐式再导出通道。__all__ 未声明,静态检查也无法区分「有意再导出」与「未使用导入」。

建议:qwen3_next.py 直接从 rtp_llm.device.device_type 导入这四个函数,并删除 arch.py 中未使用的 is_ppu 导入;若确实希望 arch 作为统一门面,请显式声明 __all__ 并在注释中说明这是有意的再导出,使意图可检查而不是靠未使用的 import 承载。

Checklist: [6.1] 分层边界:新概念在正确层级,不泄漏内部;[6.1] KISS/YAGNI:无投机性抽象

@@ -1,4 +1,5 @@
from rtp_llm.server.server_args.util import str2bool
from rtp_llm.utils.backend_registry import run_backend_registrations

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] server 库目标未声明新增的 //rtp_llm:utils 依赖,仅测试目标补了

moe_group_args.py:2server_args.py 新增了 from rtp_llm.utils.backend_registry import ...,两者都被 //rtp_llm/server:serverglob(["*.py", "server_args/*.py"]) 收录(server/BUILD:8-22),但该目标的 deps 仅有 :request_headers//rtp_llm:warmup//rtp_llm:vipserver 与一个 proto 目标;//rtp_llm:warmup 只含 utils/warmup.py 且无 deps,无法提供该模块。本 PR 只给测试目标 server_args/test/BUILD:8 补了 //rtp_llm:utils,库侧仍依赖传递闭包成立。

建议://rtp_llm/server:server 的 deps 中显式加入 //rtp_llm:utils,与测试目标保持一致,使依赖声明匹配实际 import;顺带确认是否存在 utils 反向依赖 server 的环,若有则应把 backend_registry 的依赖边界再收窄一层。

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

class DeepepNormalRouterHookTest(TestCase):
@staticmethod
def _new_router(router_class, quant_dtype):
router = object.__new__(router_class)

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] 路由钩子测试用 object.new 绕过构造函数并依赖魔法位置下标

_new_routerobject.__new__(router_class) 跳过 DeepepNormalRouterBase.__init__ 并手工赋 7 个字段(:55-72),因此 __init__ 新增或重命名必需字段不会被本测试发现,反而会以指向测试自身的 AttributeError 变红;_TupleDispatchBuffer.dispatch 又以 args[5]/args[6] 读取 topk_ids/topk_weights(:26-27),与生产端 buffer.dispatch 的位置参数顺序(deepep_normal_router.py:137-147)形成无注释的隐式耦合,调整参数顺序时该 fake 会静默取错值。

建议: 改为经真实 __init__ 构造(必要时对 deepep_buffer_wrapper 等外部依赖做窄粒度 patch 或参数注入),使字段契约受保护;若确因外部依赖过重无法构造,请在 _new_router 上加一行注释说明绕过原因。fake buffer 的 dispatch 改为按关键字接收(或至少为 args[5]/args[6] 注明对应参数名),并补一条对 payload.expert_topk_ids 的断言以覆盖 topk 重映射契约。

Checklist: [6.1] 新逻辑有聚焦单测 + 相关集成/smoke 测试

def check_conditions(cls, checker: Any, config: MoEConfigAdapter) -> None:
resolver = MoeConfigResolver()
quant_method = resolver.get_quant_method(config)
checker.check(quant_method is None)

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] 三个 no-quant 策略重复同一量化判定,其中两处与 executor 完全冗余且新增用例绕过 can_handle()

no_quant.py 的 :29、:58、:86 三处各自 MoeConfigResolver() + get_quant_method + check(quant_method is None)。其中 :58/:86 两个策略的 executor 均为 TritonFusedMoeExecutor,其 check_conditions 已断言 not resolver.has_quantization(config)(triton_fused_executor.py:36-38),两条件逻辑等价;而 MoeStrategy.can_handle()(strategy_base.py:71-77)必然调用 executor 检查,故这两处新增行为无实际变化(:29 因 executor 接受 [None, "FP8_PER_BLOCK"] 而确有价值)。新增 4 个用例直接调用 strategy.check_conditions(...)(test_cuda_strategies.py:170-172)绕过 can_handle(),...

建议: 把三处重复判定抽为共享 mixin 或基类方法,并复用 resolver.has_quantization(config)(与 executor 同一工具函数)作为单一来源;若确认 :58/:86 与 executor 侧完全冗余,删除这两处、仅保留 executor 约束更清晰。测试改为经 can_handle(config) 断言候选筛选结果,从而覆盖 strategy/router/executor 条件组合后的真实选择行为。

Checklist: [6.1] DRY:重复非平凡逻辑被抽取或显式复用;[P.G] mock/fake/stub 不得替代本次声称覆盖的生产边界

@Tanmo-ai
Tanmo-ai force-pushed the feature/ppu-qwen35-cu130 branch 2 times, most recently from ab5ca04 to 51a75af Compare August 20, 2026 16:06

@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 #1323

Status: LGTM

Summary: P0/0 · P1/0 · P2/21 · P3/14

Reviewed: commit 51a75afea42b · 2026-08-21 01:07 UTC+8

lgtm ready to ci

Non-blocking Suggestions

P2

  • propose_tokens_gpu 两个生产者的形状与取值语义分叉 @ rtp_llm/cpp/model_rpc/DecodeRpcServer.cc:338
    • 建议:统一该字段契约:建议 gRPC 侧改为与 P2P 一致的 sp_output_buffer->tokens.narrow(1, 1, propose_tokens.size() - 1).to(cuda_i32, /*non_blocking=*/true){1, propose_step}、全部 draft),并在 GenerateStream.h:96 处用注释固化「shape {batch, propose_step}、最后一列为最新 draft」。若确定只支持单步语义,请在此处补 RTP_LLM_CHECK_WITH_INFO(propose_step == 1, ...) 让多步配置显式失败,而不是依赖 select(-1) 恰好吸收形状差异。另补一条 propose_step >= 2 的用例,断言两条通道经 pickOneStepDraftToken 得到同一 token。
  • W8A8 部分 exclude 的 fail-fast 守卫对 MoE {expert_id} 模板完全失效 @ rtp_llm/model_loader/compressed_w8a8_int8_per_channel_weight.py:38
    • 建议:让 _exclude_pattern_for 通用化:把模板中所有 {...} 形式的占位符统一替换为 \d+(或显式支持 {expert_id}/{i_1}),并为 moe_w1/moe_w2 补一条「ignore 命中单个 expert 时行为明确」的用例(raise 或整模板回退,二选一并写进注释)。若不打算支持 MoE 逐专家排除,请在 support() 处显式判定模板是否含非 {i} 占位符并直接报错,避免守卫给出虚假保障。
  • 逐层枚举式的完整 exclude 被误拒,且 support() 由纯谓词变为可抛异常 @ rtp_llm/model_loader/compressed_w8a8_int8_per_channel_weight.py:46
    • 建议:按具体层实例而非模板判定:用 _exclude_pattern_for(base_name) 收集命中的层集合,与 load_config.num_layers 覆盖的层集合比较,全覆盖时 return False 走未量化回退,仅部分覆盖时 raise,并把实际命中与缺失的层号写进错误信息。若暂不实现,请把文案改为陈述限制本身(如「该权重模板存在具体层排除项,W8A8 路径不支持逐层回退」)。同时把校验从 support 移到 loader 构造或独立的 validate_quant_config 阶段以保持谓词纯净;若坚持保留,请在基类文档中声明「support 允许抛出配置级致命错误」并说明 FP8/W4A8 未对齐的原因。补一条「全层具名枚举」的边界用例。
  • QuantMethod→字符串映射在两处 switch 重复维护且各自漏枚举值,worker status 持续打 ERROR 并对外上报 UNKNOWN @ rtp_llm/cpp/model_rpc/LocalRpcServer.cc:433
    • 建议:把映射收敛到一处:将 quantMethodToString 提升到 rtp_llm/cpp/model_utils/QuantInfo.h/.cc(与 enum 同处)并导出,LocalRpcServer::getWorkerStatusInfo 直接复用,仅把 QuantMethod::None → "FP16" 这一处业务差异留在调用点覆盖;同时补齐 ModelOptFP4/QuarkMXFP4 分支以消除虚假 ERROR 日志与 UNKNOWN 上报。此后新增枚举值只需改一个 switch。
  • backend hook 在持有全局 RLock 时执行,与 Python import 锁构成锁序反转 @ rtp_llm/utils/backend_registry.py:96
    • 建议:把「取快照」与「执行 hook」分离:锁内完成 _started/_repeatable 判定并复制出待执行的 hook 元组,释放锁后再逐个调用 hook(**context);一次性语义仍由 _started 保证。若确需串行化 hook 执行,改用槽位级独立锁而非覆盖全部槽位的进程级锁,把锁粒度与 import 锁解耦;backend_registration.md:141-142 关于「进程级锁保护」的表述需同步更新。
  • 一次性槽位先标记 started 再执行 hook,失败后静默退化为部分注册且无默认级别日志 @ rtp_llm/utils/backend_registry.py:91
    • 建议:失败时恢复槽位状态,或引入 _failed 集合:某槽位执行失败后,后续对该槽位的 run_backend_registrations() 直接重新抛出原因异常而不是按「已完成」静默返回;抛出前用 logger.error 记录槽位名与 hook 标识,并把成功执行的 hook 数量与槽位名提升到 logger.info,使「外部后端是否注册成功」在默认日志级别可观测。相应在 backend_registry_test.py 补一条「hook 失败后再次消费该槽位仍然报错」的用例。
  • moe_strategy_choices 把 argparse 私有结构固化为跨仓契约,且文档与测试给出两种写法 @ rtp_llm/server/server_args/moe_group_args.py:201
    • 建议:把上下文收窄为契约明确的对象:保留 moe_group.add_argument("--moe_strategy", ...) 的返回值,改为 run_backend_registrations("moe_strategy_choices", repeatable=True, moe_strategy_action=action),或下发 add_choice: Callable[[str], None] 闭包并在其中断言 choices 为 list、新名未重复。文档与测试 hook 统一到同一写法,不再示范 parser._actions 遍历;若为兼容既有 hook 必须保留 parser=,请把新入口标为推荐、parser= 标为待废弃。
  • attention / fused_moe 的注册 hook 未被设备导入失败保护,与 linear 槽位行为不一致 @ rtp_llm/models_py/modules/factory/attention/__init__.py:159
    • 建议:为 DeviceType.Ppu 增加显式分支(哪怕只是「不注册任何 in-tree impl,交由钩子填充」),或把 else 分支的 CUDA impl 导入收窄为仅 DeviceType.Cuda,让未知设备走「空列表 + 钩子填充」;至少与 linear 槽位对齐,用 try/except 包住设备导入并记录 warning,保证钩子一定被执行。补一条「未知设备下 attention / fused_moe 钩子仍会执行」的断言,并在 backend_registration.md 的槽位表中写明该保证。
  • ROCm EP 量化 allow-list 与 executor 映射构成双份真值,收窄后 QuarkMXFP4 改为启动失败且被删字符串仍被消费 @ rtp_llm/models_py/modules/factory/fused_moe/impl/rocm/strategy/ep.py:25
    • 建议:把量化方法到 (executor_class, FusedMoEQuantConfig) 的映射收敛为单个类级 dict,can_handle() 判定 quant_method in _QUANT_TABLE_resolve_executor_and_quant() 直接查表,消除双份枚举;同一 PR 内清理上述 5 处残留声明(R.I.2 要求删除内部 registry entry 时全仓搜索消费者),并补一条「受支持量化方法在 can_handle 中不被过滤」的正向用例。QuarkMXFP4 的启动失败变化请在 PR description 记录,并说明运维旁路(是否需显式 --moe_strategy 或关闭 EP)。
  • 设备无关的中心工厂硬编码 W8A8 后端专属文案(两处复制),且量化方法串在三处字面量消费 @ rtp_llm/models_py/modules/factory/fused_moe/strategy_registry.py:87
    • 建议:删除两处专用分支,把已解析出的 quant_method 直接写进通用异常文案(如「no registered MOE/Linear backend can consume quant_method=; ep_size=..., tp_size=...」),对任何量化方法都给出同等可诊断信息且无需再改中心逻辑;quant_method 解析改为复用 MoeConfigResolver().get_quant_method(config)。同时在 QuantizationConfig 上提供能力查询(如 is_per_act_token() / 返回 Enum 的 activation_granularity)由各子类声明,deepep_wrapper 改为询问能力而非比较字面量;get_method() 的返回值建议改为 Enum 或 Literal 以获得静态检查。
  • _prepare_dispatch_input 元组返回的下游契约未表达,scale 布局与 topk 重映射仍由 use_fp8 隐式决定 @ rtp_llm/models_py/modules/factory/fused_moe/impl/cuda/routers/deepep_normal_router.py:167
    • 建议:把「dispatch 是否返回量化数据」从 use_fp8 抽出为独立判据(例如依据 expert_x_scale is None,或在基类增设 dispatch_is_quantized 属性 / 让 hook 返回显式的 dispatch 描述对象),:154 与 :167 改用该判据;在 docstring 中写明覆写方对 expert_topk_ids-1 语义要求(由 executor 处理还是需重映射);对 tuple 返回补 len(output) == 2 校验;并在 hook 测试中用含 -1recv_topk_idx 与非零 rank_expert_offset 显式断言 payload.expert_topk_ids
  • compressed-tensors targets 校验按组名不对称,同内容 checkpoint 结果取决于组名 @ rtp_llm/config/quant_config.py:55
    • 建议:将「多 group」与「targets 校验」两个判定从组名解耦:在 _pick_config_group 返回前对选中的 group 无条件调用 _validate_compressed_targets(可去掉 :273 的重复调用),并让 len(config_groups) > 1 在两条分支上采用同一策略——建议统一抛错并列出全部组名。若确需兼容既有 group_0 多组 checkpoint,用显式开关承载放行而非隐含在组名上;若有意保留 group_0 的 legacy 宽松行为,请在 :55 处注释说明「group_0 分支的 targets 校验交由各方案分支决定」,避免后续改动误删其中一处。
  • server 库新增模块级依赖但只在测试 target 补了 //rtp_llm:utils @ rtp_llm/server/server_args/moe_group_args.py:2
    • 建议:在 rtp_llm/server/BUILDpy_library(name = "server") 的 deps 中加入 //rtp_llm:utils,让依赖显式化(//rtp_llm:utils 当前 deps 不含 server,无环形风险)。加上库级依赖后,server_args/test/BUILD 里的那条可保留亦可移除,但不应作为唯一修复手段。
  • 测试用 reset_backend_registrations 清空进程级注册表,污染不可逆且双重 mock 掩盖其声称覆盖的入口边界 @ rtp_llm/server/server_args/test/server_args_test.py:37
    • 建议:改为局部隔离:在 backend_registry 提供仅测试可见的 snapshot/restore(返回并接受不透明 state,或 contextmanager),或用 patch.dict/patch.object 替换三个容器的副本,退出时精确恢复;并去掉对 rtp_llm.utils.backend_registry.ensure_backend_entrypoint_loaded 的 patch,只 patch import_optional_internal_source_entrypoint(或注入真实的 fake entrypoint 模块),让自加载分支真实执行。同时把用例改名为反映实际断言(如「每个新 parser 都能解析外部 MoE 策略」)。若坚持沿用 reset_backend_registrations(),请在其 docstring 中写明「调用后同进程内生产 hook 不可恢复」,并把相关用例隔离到独立 py_test target。
  • 工厂「缺少计算后端」守卫用空注册表自证,未验证真实注册表不会误认领 @ rtp_llm/model_loader/test/test_compressed_w8a8_int8_per_channel.py:152
    • 建议:保留现有文案用例,另加基于真实注册表的断言:setUp(:132)已保存 list(LinearFactory._strategies),可按 cls.__module__.startswith("rtp_llm.") 筛出在树策略并断言无一 can_handle W8A8 配置;MOE 侧通过生产工厂初始化路径取得已填充的 StrategyRegistry 后再断言,并改用真实 MoEConfigAdapter 构造 helper(test_cuda_strategies.py 已有)替代 SimpleNamespace。私有属性替换建议由 factory 提供测试专用的 set_strategies 或 contextmanager 入口。
  • dtype 模板化改造靠源码文本计数兜底,漏掉两处位置传参调用点 @ rtp_llm/model_loader/test/test_compressed_w8a8_int8_per_channel.py:484
    • 建议:改为行为断言替代源码文本断言:参数化遍历 w8a8_weight_list 的关键 key(至少补齐 linear_attn_qkvz_wlinear_attn_out_wattn_qkv_wmoe_w1),经 WeightModule.create 生成 INT8 loader 并断言 weight.kernel.data_type == torch.int8 且 scale 为 torch.float32,从而覆盖位置传参站点并移除对 inspect.getsource 的依赖;退一步也可把断言改为 assertNotIn("float8_e4m3fn", source) 以覆盖位置参数写法。
  • ROCm EP 量化过滤用例在其实际运行的 CUDA 机型上恒真,且唯一覆盖落在 open_skip target @ rtp_llm/models_py/modules/factory/fused_moe/tests/test_cuda_strategies.py:214
    • 建议:把「过滤在 super().can_handle() 之前短路」这一契约直接断言出来(例如 patch/spy RocmEpNormalStrategy.get_attributes 断言其未被调用,或先 patch 为可用 attributes 再断言不支持方法返回 False、且至少一个受支持方法返回 True——正向用例目前完全缺失),使 CUDA 机型上的通过具有判别力;并把不依赖 GPU 的条件类用例拆到同目录下无 open_skip、无 GPU exec_properties 的新 py_test target。若受限于 tag 无法调整,请在 PR 描述中说明这些断言仅在内部 CI 生效。
  • 三个 no-quant 策略重复同一量化判定,其中两处与 router 完全冗余且新增用例绕过 can_handle() @ rtp_llm/models_py/modules/factory/fused_moe/impl/cuda/strategy/no_quant.py:58
    • 建议:把测试断言改为调用 strategy.can_handle(config) 以覆盖真实选择边界,并用表驱动(本文件已有 subTest)把三个策略 × 两种 model_config 组合起来,至少补上 CudaNoQuantEpLowLatencyStrategy + no_auant_ep_low_latency 的接受/拒绝两条断言;三处重复的三行抽成共享 helper 或基类实现。若 Cpp/DpNormal 两处确为纵深防御,请在注释中写明「与 router 条件重复、仅为防御」。
  • 槽位名与上下文键为自由字符串 + kwargs 透传,测试使用生产不存在的槽位名 @ rtp_llm/utils/test/backend_registry_test.py:34
    • 建议:把槽位定义为 Enum 或模块级常量(并让文档槽位表引用同一常量),register_backend_hook/run_backend_registrations 只接受该类型并对未知槽位 fail-fast;上下文改由每个槽位的 TypedDict/dataclass 描述,或至少在文档与类型标注中固定各槽位的键集合。测试改用真实槽位常量,使「槽位名拼错」成为导入期错误而非静默 no-op。
  • W8A8 新增的 act_qscheme 与 worker precision 映射零测试覆盖 @ rtp_llm/cpp/engine_base/Executor.h:64
    • 建议:为 act_qscheme 选择补一条覆盖(若有 pybind 暴露则在 Python 侧断言,否则在 rtp_llm/cpp 侧加轻量 cc_test,参数化遍历 PerTensor/FP8PTPC/W4A8/W8A8 四条分支);worker status 的 precision 映射在收敛为单一 quantMethodToString 后,补一条「每个 QuantMethod 枚举值都有非 UNKNOWN 映射」的穷尽性用例。
  • Mega-PR:MTP/PD handoff 与 CUDA Graph 修复混入 W8A8 量化与后端注册特性 @ rtp_llm/cpp/engine_base/stream/GenerateStream.cc:1034
    • 建议:按主线拆分为独立 PR:先合入 (3) handoff 修复与 (4) CUDA Graph 清零修复(各自附回归用例),再合入 (2) 注册机制(纯基建、无行为变化),最后合入 (1) W8A8 特性与 PPU 启用。若因发布窗口必须合并,请在 PR description 中分节说明四条主线的动机、相互无依赖关系、各自的验证方式与独立回滚手段,并在 commit 粒度上保持一一对应,便于 revert 单条线。

P3

  • CUDA Graph host block table 每步全量清零落在 decode 热路径且新不变量无覆盖 @ rtp_llm/cpp/cuda_graph/cuda_graph_runner.cc:397
    • 建议:参照 :539-543 把清零窄化为 slice(0, state.current_batch_size, selected_graph_batch_size).fill_(0)current_batch_size >= selected_graph_batch_size 时跳过)。若活跃行的列尾同样会被 fillParams 解引用,请改为「padding 行整行清零 + 活跃行列尾清零」两段窄化填充并在注释写明该约束;若确认必须整表清零,请在注释中补上典型 graph_bs/max_blocks 下的实测每步开销。同时补一条 cc_test:构造 current_batch_size < selected_graph_batch_size 且上一轮写过非零 block ID 的场景,断言 padding 行为 0。
  • GenerateStream 新用例只覆盖「缺失则保留」,未覆盖「提供则刷新」与 commit-only 陈旧提案边界 @ rtp_llm/cpp/engine_base/stream/test/GenerateStreamTest.cc:117
    • 建议:补对偶用例:同样预置 9,但第 6 个字段传 torch::tensor({11}, torch::kInt32).to(torch::kCUDA),断言变为 11;再补一条 commit-only 步(accept_token_num 使 propose_token_ 被清空)后 pickOneStepDraftToken 取值的断言,固化「保留陈旧 GPU 镜像」是有意行为。建议把位置聚合初始化改为指派初始化或加字段名注释。
  • backend_registry 生命周期异常断言未校验消息,且 noop 用例无任何断言 @ rtp_llm/utils/test/backend_registry_test.py:66
    • 建议:改用 assertRaisesRegex 并各自匹配特征片段(already initialisedlifecycle changedbackend is broken);no-op 用例补上可观察断言(例如先注册另一槽位的 hook,调用空槽位后断言该 hook 未被执行、且空槽位再次消费仍不报错)。
  • 非 FP8 分支移除了 dispatch 返回值的类型校验,错误定位后移 @ rtp_llm/models_py/modules/factory/fused_moe/impl/cuda/routers/deepep_normal_router.py:161
    • 建议:tuple 分支加 len(output) == 2 校验,else 分支保留一次显式类型检查,并在消息中指明 _prepare_dispatch_input 的返回契约。
  • LoadQuantPerChannelFp8Weight 绕过了新引入的 supported_quant_config_types 单一来源 @ rtp_llm/model_loader/per_channel_fp8_quant_weight.py:707
    • 建议:让该子类显式声明 supported_quant_config_types = (Fp8PerChannelCompressedQuantConfig,) 并在 support() 中改用 isinstance(quant_config, cls.supported_quant_config_types),使「认领范围」在整个继承树上只有一种表达方式;若刻意保持与基类不同的语义(仅 on-the-fly 量化),请在该属性或方法上注释说明差异原因。
  • setup_args 中的入口加载与槽位内部保证重复,注释描述的契约在生产路径不成立 @ rtp_llm/server/server_args/server_args.py:534
    • 建议:二选一并让注释与实现自洽:若目的是「启动早期 fail-fast,避免 entrypoint 的 ImportError 深埋在工厂构造里」,把注释改成这个理由并处理返回值(如 logger.info 记录是否加载到外部后端);若无此需求,直接删除该行与对应 import,依赖 run_backend_registrations 的自加载。
  • W8A8 分支混用强下标与 get,input_activations 缺键时抛裸 KeyError @ rtp_llm/config/quant_config.py:268
    • 建议:把条件中的下标统一改为 .get()(如 activation_config.get("strategy") == "token"),让不满足条件的 checkpoint 自然落到末尾带 weights=/input_activations= 上下文的 raise ValueErrorweights_config 的三处一并处理,并补一条「缺 strategy 字段」的边界用例断言错误类型与文案。
  • choices 扩展点只保护 argparse 校验通道,env 补齐通道静默绕过 @ rtp_llm/server/server_args/server_args.py:366
    • 建议:在 :358-371 的补齐分支中复用 action 的校验(对有 choices 的 action 调用 self._check_value(action, converted_value),失败时 self.error(...) 给出与 argparse 一致的报错),并把 except (ValueError, TypeError): pass 改为报错或至少 warning;这样 --moe_strategy 的取值集合成为唯一真值来源,扩展点在两条通道上语义一致。补一条用例:给定一个命令行参数 + 非法 MOE_STRATEGY,断言解析期即失败。
  • ignore 中的 re: 模式被硬失败且无运维旁路 @ rtp_llm/config/quant_config.py:932
    • 建议:在错误信息中给出可操作的迁移指引(把 re: 展开为字面量模块名,并指明需展开到 ignore 的哪一层),或在 _ignore_patterns 侧支持将 re: 编译后交给 W8A8 loader 的 exclude 判定;同时明确 W4A8 的静默忽略是否为预期。若本次不打算支持,请在 PR description 与该 raise 处记录已知限制及受影响的 checkpoint 来源,便于部署前筛查。
  • PPU 设备识别依赖环境变量真值且无任何日志,CUDA 主机可能被静默重分类 @ rtp_llm/device/device_type.py:20
    • 建议:收紧判定并补可观测性:只接受明确的开关值(如要求 PPU_HOME 指向存在的目录,或改用 RTP_LLM_DEVICE_TYPE=ppu 这类显式量),并在返回非 Cuda 结果时用 logger.info 打印判定依据(命中的变量名与取值),使误分类在启动日志中可见;建议对 get_device_type()functools.lru_cache 固化单进程内判定结果,并补一条覆盖「CUDA 可用 + PPU_HOME 为空串/0」的用例。
  • arch.py 新增的 is_ppu 自身未使用,仅作为隐式再导出通道 @ rtp_llm/models_py/utils/arch.py:11
    • 建议:让 qwen3_next.py 直接从 rtp_llm.device.device_type 导入这些谓词(与 is_cuda/is_hip 的来源保持一致),删除 arch.py 中未使用的导入;若确实希望 arch.py 作为统一门面,请显式声明 __all__ 并在模块注释中说明它对外重导出设备判定函数。
  • no_auant 拼写错误的策略取值出现第三处副本 @ rtp_llm/models_py/modules/factory/fused_moe/tests/test_cuda_strategies.py:179
    • 建议:把策略名收敛为 Enum 或模块级常量,由 CLI choices 与策略实现共同引用,测试引用同一常量;若要纠正拼写,请在同一变更中保留旧名作为兼容别名并在文档中标注该已知拼写。
  • hook 测试白盒构造 router,且伪造 buffer 依赖 dispatch 位置参数下标 @ rtp_llm/models_py/modules/factory/fused_moe/impl/cuda/routers/test/deepep_normal_router_hook_test.py:55
    • 建议:fake 改用完整命名签名(def dispatch(self, x, handle, num_tokens_per_rank, num_tokens_per_rdma_rank, is_token_in_rank, num_tokens_per_expert, topk_idx, topk_weights, *, expert_alignment)),使参数顺序变更成为调用期错误;或改为保留 __init__、只替换 DeepEPWrapper.get_instance 来构造 router,并至少断言一次由 __init__ 推导的字段(如以 ep_size=2 构造后校验 rank_expert_offset)。
  • 单个测试文件跨五个模块,且直接改写生产私有状态 @ rtp_llm/model_loader/test/test_compressed_w8a8_int8_per_channel.py:130
    • 建议:按归属拆分:LinearFactory/StrategyRegistry 的守卫测试移入各自 factory 的 test 目标,DeepEP 分桶测试移入 rtp_llm/models_py/distributed 的 test 目标,quant_config 解析测试移入配置侧 test 目标;本文件只保留 loader 与 dtype/postprocess 用例。私有属性替换建议由 factory 提供测试专用的 set_strategies 或 contextmanager 入口。

Checklist Findings (26 fail / 68 total)

General Principles Checklist

  • [6.1] Architecture — 依赖方向:无循环依赖/跨层惊喜 → issue server 库新增模块级依赖但只在测试 target 补了 //rtp_llm:utils
    moe_group_args.py:2server_args.py:68 新增模块级 from rtp_llm.utils.backend_registry import ...。两文件都被 rtp_llm/server/BUILD:10-15 的 glob(*.py + server_args/*.py)纳入 //rtp_llm/server:server,而该 target 的 deps(:16-21)只有 :request_headers//rtp_llm:warmup(srcs 仅 utils/warmup.py,见 rtp_llm/BUILD:180-182)、//rtp_llm:vipserver 与 flexlb proto,均不提供 rtp_llm/utils/backend_registry.py(它归 //rtp_llm:utilsutils/**/*.py glob)。本 PR 只在 server_args/test/BUILD:8 补了 //rtp_llm:utils,即修在消费方
  • [6.1] Architecture — 兼容性:外部 HTTP/RPC API、持久数据、配置、环境迁移安全 → issue PPU 设备识别依赖环境变量真值且无任何日志,CUDA 主机可能被静默重分类
    get_device_type()torch.cuda.is_available() 且非 HIP 时,只要 os.environ.get("PPU_HOME") 为任意真值(含 "0"、任意残留路径)或 torch 版本串含 ppu,就返回 DeviceType.Ppu(:20-24),无值内容校验、无日志、无缓存。后果落在本 PR 涉及的分支上:is_cuda() 变为 False,attention/__init__.pyfused_moe/__init__.py 从 CUDA 分支跌入 else 兜底(丢掉 headwise/TRT-LLM/XQA/MLA 等 impl 与 fused_moe/__init__.py:106 的 FP4 策略注册),arch.py 的 SM 判定全部转 False,并直接改变本 PR 新加的 qwen3_next.py:248 白名单判定——一台正常 CUDA 机器只因残留环境变量就被静默降级,排查时无任何线索。
  • [6.1] Architecture — 分层边界:新概念在正确层级,不泄漏内部 → issue arch.py 新增的 is_ppu 自身未使用,仅作为隐式再导出通道
    arch.py:6-12rtp_llm.device.device_type 导入 DeviceType/get_device_type/is_cuda/is_hip/is_ppu,但 is_ppu 在本文件内没有任何使用点(全仓 is_ppu 仅 3 个引用:定义处、arch.py:11qwen3_next.py:242)。真正的消费者 qwen3_next.py:238-243models_py.utils.arch 而非定义模块导入,使 arch.py 成为设备判定的隐式再导出层:既无 __all__ 声明该意图,也让「设备判定归属 rtp_llm.device」这一分层被模糊,后续清理未使用 import 的工具会直接删掉它并打挂 qwen3_next 的设备门禁。
  • [6.1] Architecture — 可观测性:日志/指标/超时可操作、非噪声 → issue PPU 设备识别依赖环境变量真值且无任何日志,CUDA 主机可能被静默重分类
    get_device_type()torch.cuda.is_available() 且非 HIP 时,只要 os.environ.get("PPU_HOME") 为任意真值(含 "0"、任意残留路径)或 torch 版本串含 ppu,就返回 DeviceType.Ppu(:20-24),无值内容校验、无日志、无缓存。后果落在本 PR 涉及的分支上:is_cuda() 变为 False,attention/__init__.pyfused_moe/__init__.py 从 CUDA 分支跌入 else 兜底(丢掉 headwise/TRT-LLM/XQA/MLA 等 impl 与 fused_moe/__init__.py:106 的 FP4 策略注册),arch.py 的 SM 判定全部转 False,并直接改变本 PR 新加的 qwen3_next.py:248 白名单判定——一台正常 CUDA 机器只因残留环境变量就被静默降级,排查时无任何线索。
  • [6.1] Architecture — 回滚路径:风险行为存在运维回滚手段 → issue ignore 中的 re: 模式被硬失败且无运维旁路
    CompressedW8A8Int8PerChannelQuantConfig.__init__(:929-936)对任何以 re: 开头的 ignore 模式抛 ValueError。llm-compressor 生成的 compressed-tensors checkpoint 普遍在 ignore 中混用字面量与 re: 正则(如 re:.*mlp.gate$),此类 checkpoint 将完全无法加载,且没有任何开关可临时放行或降级,运维侧只能改写 checkpoint 的 config.json。对比同族的 CompressedW4A8Int4PerChannelQuantConfig(:859-874):它只把 ignore_patterns 存入 _ignore_patterns 且从不写入 exclude_modules,因此 re: 在 W4A8 路径上被静默忽略——同一 checkpoint 字段在两条路径上一边硬失败、一边静默忽略。该限制与上文「逐层完整枚举被误拒」叠加后,实际可用的 exclude 写法非常狭窄。
  • [6.1] Architecture — 状态不变量:创建/更新/失败/重试/回滚路径有效 → issue 测试用 reset_backend_registrations 清空进程级注册表,污染不可逆且双重 mock 掩盖其声称覆盖的入口边界
    用例在 :37 与 finally(:64)各调用一次 reset_backend_registrations()(清空 _hooks/_started/_repeatablebackend_registry.py:99-104),两次都在 patch(...) 作用域之外。真实 hook 只在 backend entrypoint 首次 import 时注册,而 import_optional_internal_source_entrypoint@lru_cache(maxsize=None)import_util.py:26)且模块已在 sys.modules 中,被清空的 hook 无法重新登记;因此在含 internal_source 的构建里,本用例结束后同进程所有槽位退化为空 no-op,同文件按类名排在其后的 ServerArgsSetTest 多次 setup_args() 静默拿不到外部策略。用例还同时 patch 了 `server_args.ensure_backend_entrypoint_loade
  • [6.1] Architecture — 错误语义:fail-fast/retry/fallback/silent 行为显式 → issue choices 扩展点只保护 argparse 校验通道,env 补齐通道静默绕过
    EnvArgumentParser.parse_args 有两条 env 路径:无命令行参数时把 env 拼成 --option value 交给 argparse(:263-291,choices 生效);存在任意命令行参数(has_cmd_args=True)时,对未显式提供的项走 :343-371 的补齐分支,只做 action.type(env_value) 转换后 setattr(:362-363),完全不校验 action.choices,且 except (ValueError, TypeError): pass(:366-368)会静默丢弃转换失败。因此 MOE_STRATEGY=<任意串> 配合任一命令行参数即可绕过 choices,落到 StrategyRegistry 才以「No suitable MOE strategy found」失败,错误位置远离根因。该绕过早于本 PR,但本 PR 恰好把 choices 建成了外部后端的正式扩展契约。
  • [6.1] Quality — Commit 原子、message 与行为匹配 → issue Mega-PR:MTP/PD handoff 与 CUDA Graph 修复混入 W8A8 量化与后端注册特性
    41 个改动文件至少含四条彼此无依赖的主线:(1) compressed-tensors W8A8 INT8 加载链路与枚举暴露(quant_config.pymodel_loader/**QuantInfo.*ConfigInit.ccExecutor.h.pyi,约 15 文件);(2) out-of-tree 后端延迟注册机制(backend_registry.py + 四个槽位消费点 + server_args* + docs,约 10 文件);(3) MTP/PD 交接语义(GenerateStream.cc:1034 由无条件赋值改为条件赋值、DecodeRpcServer.cc:338 新增发布);(4) CUDA Graph host block table 清零修复(cuda_graph_runner.cc:397);外加 PPU 设备启用(device_type.pyarch.pyqwen3_next.py)。(3)(4) 是可独立回滚验证的 KV/token 正确性修复,一旦 (1) 或 (2)
  • [6.1] Quality — Mega-PR 已拆分为独立变更 → issue Mega-PR:MTP/PD handoff 与 CUDA Graph 修复混入 W8A8 量化与后端注册特性
    41 个改动文件至少含四条彼此无依赖的主线:(1) compressed-tensors W8A8 INT8 加载链路与枚举暴露(quant_config.pymodel_loader/**QuantInfo.*ConfigInit.ccExecutor.h.pyi,约 15 文件);(2) out-of-tree 后端延迟注册机制(backend_registry.py + 四个槽位消费点 + server_args* + docs,约 10 文件);(3) MTP/PD 交接语义(GenerateStream.cc:1034 由无条件赋值改为条件赋值、DecodeRpcServer.cc:338 新增发布);(4) CUDA Graph host block table 清零修复(cuda_graph_runner.cc:397);外加 PPU 设备启用(device_type.pyarch.pyqwen3_next.py)。(3)(4) 是可独立回滚验证的 KV/token 正确性修复,一旦 (1) 或 (2)
  • [6.1] Quality — PR description 说明动机与设计 → issue Mega-PR:MTP/PD handoff 与 CUDA Graph 修复混入 W8A8 量化与后端注册特性
    41 个改动文件至少含四条彼此无依赖的主线:(1) compressed-tensors W8A8 INT8 加载链路与枚举暴露(quant_config.pymodel_loader/**QuantInfo.*ConfigInit.ccExecutor.h.pyi,约 15 文件);(2) out-of-tree 后端延迟注册机制(backend_registry.py + 四个槽位消费点 + server_args* + docs,约 10 文件);(3) MTP/PD 交接语义(GenerateStream.cc:1034 由无条件赋值改为条件赋值、DecodeRpcServer.cc:338 新增发布);(4) CUDA Graph host block table 清零修复(cuda_graph_runner.cc:397);外加 PPU 设备启用(device_type.pyarch.pyqwen3_next.py)。(3)(4) 是可独立回滚验证的 KV/token 正确性修复,一旦 (1) 或 (2)
  • [6.1] Software Engineering — DIP:高层策略不依赖非必要具体细节 → issue moe_strategy_choices 把 argparse 私有结构固化为跨仓契约,且文档与测试给出两种写法
    槽位只需扩展 --moe_strategychoices,却把整个 EnvArgumentParser 交给 hook(parser=parser),于是唯一可行写法是遍历私有属性 parser._actions 反查 action,同时 hook 获得改写任意其它参数(含 bind_to 绑定)的能力。该泄漏已固化为两种不一致实现:backend_registration.md:107-115 重建列表后回写 action.choices,带幂等判断与缺失时的 RuntimeErrorserver_args_test.py:28-32 用无 default 的 next(...)(缺失时抛 StopIteration)并原地 choices.append,无幂等判断。一旦 --moe_strategy 改名、choices 改为 tuple,或 EnvArgumentParser 改为包装/重建 action,所有外部后端同时失效,且只在解析期以 AttributeError/StopIteration
  • [6.1] Software Engineering — DRY:重复非平凡逻辑被抽取或显式复用 → issue setup_args 中的入口加载与槽位内部保证重复,注释描述的契约在生产路径不成立
    run_backend_registrationsbackend_registry.py:79)在取 _lock、在 _started.add(slot) 之前就已调用 ensure_backend_entrypoint_loaded(),因此任何槽位的首次消费都会先完成入口加载与 hook 登记,register_backend_hook 不会因槽位已启动而抛错。删掉 server_args.py:534 后,init_moe_group_args 里的 run_backend_registrations 仍会自行加载并正确应用 hook,结果完全一致;返回值也被丢弃。而相邻注释(:532-533)把这行描述成必要的时序保证,与实现不符——新测试对 rtp_llm.utils.backend_registry.ensure_backend_entrypoint_loaded 的额外 patch 恰好掩盖了这一冗余。该行还让只走 init_all_group_args 的离线工具路径与 setup_args 路径的加载时机不一致。
  • [6.1] Software Engineering — ISP:调用方不依赖无关大接口 → issue moe_strategy_choices 把 argparse 私有结构固化为跨仓契约,且文档与测试给出两种写法
    槽位只需扩展 --moe_strategychoices,却把整个 EnvArgumentParser 交给 hook(parser=parser),于是唯一可行写法是遍历私有属性 parser._actions 反查 action,同时 hook 获得改写任意其它参数(含 bind_to 绑定)的能力。该泄漏已固化为两种不一致实现:backend_registration.md:107-115 重建列表后回写 action.choices,带幂等判断与缺失时的 RuntimeErrorserver_args_test.py:28-32 用无 default 的 next(...)(缺失时抛 StopIteration)并原地 choices.append,无幂等判断。一旦 --moe_strategy 改名、choices 改为 tuple,或 EnvArgumentParser 改为包装/重建 action,所有外部后端同时失效,且只在解析期以 AttributeError/StopIteration
  • [6.1] Software Engineering — KISS/YAGNI:无投机性抽象 → issue setup_args 中的入口加载与槽位内部保证重复,注释描述的契约在生产路径不成立
    run_backend_registrationsbackend_registry.py:79)在取 _lock、在 _started.add(slot) 之前就已调用 ensure_backend_entrypoint_loaded(),因此任何槽位的首次消费都会先完成入口加载与 hook 登记,register_backend_hook 不会因槽位已启动而抛错。删掉 server_args.py:534 后,init_moe_group_args 里的 run_backend_registrations 仍会自行加载并正确应用 hook,结果完全一致;返回值也被丢弃。而相邻注释(:532-533)把这行描述成必要的时序保证,与实现不符——新测试对 rtp_llm.utils.backend_registry.ensure_backend_entrypoint_loaded 的额外 patch 恰好掩盖了这一冗余。该行还让只走 init_all_group_args 的离线工具路径与 setup_args 路径的加载时机不一致。
  • [6.1] Software Engineering — LSP:子类/重写保持基类契约 → issue LoadQuantPerChannelFp8Weight 绕过了新引入的 supported_quant_config_types 单一来源
    本 PR 在基类引入 supported_quant_config_types(:286-289)作为「该 loader 认领哪些 config」的单一来源,并在注释中声明子类必须收窄以维持 WeightModule.create 的唯一匹配不变量,基类 support()(:295-297)据此判定。但同文件的子类 LoadQuantPerChannelFp8Weight.support()(:707-709)仍硬编码 isinstance(quant_config, Fp8PerChannelCompressedQuantConfig),与继承来的类属性(含 Fp8PerChannelQuarkQuantConfig)不一致。当前因它要求 is_quanted() 为 False 而与 W8A8 无交集,不构成缺陷;但后续通过修改类属性调整认领范围时,这条路径不会跟随,破坏刚建立的约定。
  • [6.1] Software Engineering — OCP:本地扩展点优先于修改中心逻辑 → issue 设备无关的中心工厂硬编码 W8A8 后端专属文案(两处复制),且量化方法串在三处字面量消费
    StrategyRegistry.get_strategy() 是设备与量化格式无关的通用选择器,现在为 W8A8_INT8_PER_CHANNEL_COMPRESSED 单独加了分支与专用文案(:87-93);linear/factory.py:113-123 有一份同款复制,两处文案需人工保持一致。该格式只有外部后端能执行,等于把具体后端的知识写进中心逻辑:下一个由外部后端承载的量化方法又要再改这两处。该串唯一权威定义在 quant_config.py:949get_method(),却以字面量第三次出现在 deepep_wrapper.py:235is_per_act_token 元组;漏改该处不会报错,只会把 low-latency 分桶静默退回 [64,128] 或抛 "Unsupported quantization config"(:261)。另外 :74-78 用内联三元重算 quant_method,重复了 MoeConfigResolver.get_quant_method()
  • [6.1] Software Engineering — SRP:模块/类职责单一 → issue 单个测试文件跨五个模块,且直接改写生产私有状态
    该文件挂在 //rtp_llm/model_loader/test 下,却同时覆盖 LinearFactoryStrategyRegistry(:22-25)、quant_config 的 checkpoint 解析(:189)、DeepepWrapperConfig.calc_low_latency_max_token_per_rank(:318)与 pybind QuantAlgo(:491-504)五个模块;BackendAvailabilityGuardTest 还直接赋值生产私有属性 LinearFactory._strategies(:132/135/152/162)。同风险类的兄弟测试 test_compressed_w4a8_int4_per_channel.py 仅一个类、无跨模块导入。后果是这些模块自身的测试目标保持沉默,回归定位会统一指向 model_loader;且对 linear/factory.py:119strategy_registry.py:89 的错误文案与 _strategies 属性名形成隐式耦
  • [6.1] Tests — 分布式/跨平台变更有对应覆盖 → issue ROCm EP 量化过滤用例在其实际运行的 CUDA 机型上恒真,且唯一覆盖落在 open_skip target
    test_unsupported_quant_methods_return_false(:214-227)断言 RocmEpNormalStrategy().can_handle(config) 为 False。但在 CUDA 机器上,即使删掉 ep.py:44 的 allow-list,MoeStrategy.can_handlestrategy_base.py:58-64)也会因 get_attributes()ep.py:101-104)惰性导入 ROCm executor 抛 ImportError 而返回 False——断言在两种情况下都通过,无法区分「被 allow-list 过滤」与「因缺 ROCm 依赖被过滤」。同时该用例所在 target 带 tags = ["open_skip"]exec_properties = {'gpu':'H20'}fused_moe/tests/BUILD:16-17,本 PR 未修改),而这两组条件类用例只调用 check_conditions/can_handle,并不需
  • [6.1] Tests — 新逻辑有聚焦单测 + 相关集成/smoke 测试 → issue 单个测试文件跨五个模块,且直接改写生产私有状态
    该文件挂在 //rtp_llm/model_loader/test 下,却同时覆盖 LinearFactoryStrategyRegistry(:22-25)、quant_config 的 checkpoint 解析(:189)、DeepepWrapperConfig.calc_low_latency_max_token_per_rank(:318)与 pybind QuantAlgo(:491-504)五个模块;BackendAvailabilityGuardTest 还直接赋值生产私有属性 LinearFactory._strategies(:132/135/152/162)。同风险类的兄弟测试 test_compressed_w4a8_int4_per_channel.py 仅一个类、无跨模块导入。后果是这些模块自身的测试目标保持沉默,回归定位会统一指向 model_loader;且对 linear/factory.py:119strategy_registry.py:89 的错误文案与 _strategies 属性名形成隐式耦
  • [6.1] Tests — 边界 case 覆盖(空、单元素、最大值) → issue W8A8 分支混用强下标与 get,input_activations 缺键时抛裸 KeyError
    新增分支的判定条件混用两种取值风格:activation_config["type"](:268)、["num_bits"](:269)、["strategy"](:270)为直接下标,而同一条件里的 dynamic/symmetric.get(..., False)/.get(..., True)weights_config["num_bits"](:228)与 ["type"]/["strategy"](:230-232)同样是强下标。compressed-tensors 的 QuantizationArgsnum_bits/type/strategy 都定义了默认值,手写或旧版本导出的 config.json 可能省略其中任一字段,此时条件求值本身抛 KeyError: 'strategy',直接跳过同一 PR 在 :312-320 精心添加的带上下文 ValueError,排查体验回到改动前水平。

RTP-LLM Checklist

  • [D] 性能 — 推理热路径禁止引入 per-forward Python callback、GIL 持有、.item()/.cpu()/.tolist()、同步 flush 或隐式 GPU sync;必要时必须证明 default-off、异步或限频 → issue CUDA Graph host block table 每步全量清零落在 decode 热路径且新不变量无覆盖
    kv_cache_kernel_block_id[graph_bs, max_blocks] 的 pinned CPU 视图,fill_(0) 每次 prepareAttentionInputs 都 memset 全部 graph_bs * max_blocks * 4 字节,而该函数每个 decode step 都执行且位于 replay 前的串行关键路径;按注释自述只有 padding 行需要清理,活跃行随后被 :525 的 stridedCopyHost 覆盖。同文件对同类 padding 清理已有窄区间约定::539-543(sequence_lengths)只 slice(0, fill_start, selected_graph_batch_size),本次新增与该约定不一致。此外「padding 行必须为 0,否则 KV 写入活跃请求 block」这一新不变量没有任何单测或断言。本轮未做基准测量,故按无实测证据的性能项定级。
  • [I] 代码质量 — 删除或重命名内部 file、registry entry、model name、metric enum、op binding、plugin symbol 时,必须全仓搜索消费者,并提供替代实现、迁移说明或 smoke 覆盖;只有暴露到 HTTP/RPC/config/persisted format 时才按外部兼容性处理 → issue ROCm EP 量化 allow-list 与 executor 映射构成双份真值,收窄后 QuarkMXFP4 改为启动失败且被删字符串仍被消费
    _SUPPORTED_QUANT_METHODS(:25-31)与 _resolve_executor_and_quant() 的 if/elif(:64-97)必须手工同步:只加分支不加集合 → can_handle 在 :44 提前 False,方法静默不可用;只加集合不加分支 → :95 抛 ValueError,而 strategy_base.py:58-64 只捕获 ImportError,异常会冲出策略选择流程。被删的三个串在消费侧仍被声明为可支持:rocm/executors/rocm_moe.py:289:404-405rocm/routers/pure_tp_router.py:209linear/impl/rocm/fp8_deepgemm_linear.py:40linear/factory.py:248。已核对全部 get_method() 返回值,确认无 config 产出这三个串(非现网破坏),但留下自相矛盾的支持声明。另有真实行为变化:QuarkMXFP4 是可达取值,原先落 else 得到
  • [I] 代码质量 — 同一功能用统一工具函数 → issue no_auant 拼写错误的策略取值出现第三处副本
    新增用例把 "no_auant_cpp" / "no_auant_dp_normal"no_quant 的拼写错误)作为字面量再复制一份(:179、:188、:197、:206),与 no_quant.py:60/88moe_group_args.py:167-169--moe_strategy choices 构成第三处副本。该取值同时是 CLI 对外可见的字符串,纠正需要同步生产、测试与参数 choices 三处,副本越多越难修。

Python Static-First Checklist

  • [P.A] 静态结构与类型纪律 — 字符串分发用 Enum/Literal → issue no_auant 拼写错误的策略取值出现第三处副本
    新增用例把 "no_auant_cpp" / "no_auant_dp_normal"no_quant 的拼写错误)作为字面量再复制一份(:179、:188、:197、:206),与 no_quant.py:60/88moe_group_args.py:167-169--moe_strategy choices 构成第三处副本。该取值同时是 CLI 对外可见的字符串,纠正需要同步生产、测试与参数 choices 三处,副本越多越难修。
  • [P.G] 测试规范 — mock/fake/stub 不得替代本次声称覆盖的生产边界 → issue hook 测试白盒构造 router,且伪造 buffer 依赖 dispatch 位置参数下标
    _new_routerobject.__new__(router_class)(:55)跳过 DeepepNormalRouterBase.__init__,手工赋 7 个内部属性,因此 expert_num_per_rankrank_expert_offset 的真实推导关系(deepep_normal_router.py:68-70)未被验证,测得的 rank_expert_offset=0(:69)是手工设定值——而该值恰好决定 :167-172 的 topk 重映射结果。_TupleDispatchBuffer.dispatch(:23-33)通过 args[5]/args[6] 取 topk_ids/topk_weights,与生产 prepare 的位置实参强耦合:调用顺序调整时 fake 会静默回显错误张量而断言仍通过;__init__ 新增 prepare 依赖的字段时,测试会以 AttributeError 失败而非表达契约破坏。
  • [P.G] 测试规范 — pytest.raises 带 match 参数 → issue backend_registry 生命周期异常断言未校验消息,且 noop 用例无任何断言
    test_repeatable_slot_rejects_late_hooks(:66)、test_slot_lifecycle_cannot_change_after_start(:72)、test_registering_after_drain_raises(:101)都只断言 RuntimeError 类型,而 backend_registry.py 有两条语义完全不同的 RuntimeError(:63-66 "already initialised" 与 :85-87 "lifecycle changed"):任一被误抛成另一条,三个用例仍全绿,区分能力完全丢失。test_hook_exception_propagates(:112)同理未校验是否为 hook 自身异常。test_draining_slot_without_hooks_is_noop(:115-116)只调用一次函数、无任何断言,无法区分「no-op」与「异常被静默吞掉」。

Strengths

  • W8A8 跨语言契约一次改全且被测试钉住:QuantInfo.h:19 追加枚举 12 未重排既有编号,QuantInfo.cc:49-52 强制 group_size=0/bits=8ConfigInit.cc.pyiExecutor.hModelConfig.cc 逐项对齐,QuantAlgoBindingTest 直接断言 Python get_algo() → C++ isW8a8Int8PTPC() 往返;stub 还顺带补回 __members__ 中漏掉的 QuarkMXFP4: 11
  • INT8 加载器只声明 weight_dtypeapply_fp8_device_conversionsupported_quant_config_types 三个类属性即复用 FP8 的全部 tensor 映射与 TP/EP 切分(compressed_w8a8_int8_per_channel_weight.py:22-24),属本地扩展点而非改中心逻辑;apply_fp8_device_conversion 门控有硬必要性(ROCm convert_fp8_weight_params 带 FP8 dtype 断言),而 CUDA 基类为恒等返回,行为零变化。
  • supported_quant_config_types 的收窄服务于 WeightModule.create 的「唯一匹配」不变量,且 W8A8 config 与 FP8/W4A8 无 isinstance 交集,不会触发多 loader 命中报错。
  • CompressedW8A8Int8PerChannelQuantConfig 彻底避免 **kwargs 汇聚:__init__ 全具名参数,_from_configquant_config.py:971-983)以 allowed_keys 白名单显式 TypeError 拒绝未知 key,拼错配置键不会静默退化成空 exclude_modules——与同文件 W4A8 的 kwargs.get(...) 相比是明确改进。
  • _pick_config_groupquant_config.py:32-44)保持 group_0 优先返回,只在无 group_0 且恰有唯一命名组时才走新分支,既支持 Qwen3.5 的命名组,又保证现存 checkpoint 解析结果逐字节不变。
  • 未识别的 compressed-tensors 组合从「抽象类实例化 TypeError」改为带 weights=/input_activations= 上下文的 ValueErrorquant_config.py:312-320),并用 isinstance(activation_config, dict) 守卫(:245/:267)让 weight-only checkpoint 不再崩在无意义位置。
  • get_supported_kv_cache_dtypes 主动收窄为 [float16, bfloat16] 并注明「fp8 KV cache 组合未验证,宁可启动即失败」(quant_config.py:962-968),是保守且可回滚的默认值选择。
  • backend_registry.py 保持设备与厂商无关,只依赖稳定入口名 models_py;生命周期语义清晰(槽位启动后冻结 hook 集合、拒绝过晚注册、拒绝生命周期切换、不吞 hook 异常),模块 docstring 明确交代「静默丢注册 → 选错实现 → 错数值而非启动失败」的失效模式,并有 11 条定向用例。
  • docs/backend/backend_registration.md 的槽位表与四个消费点的上下文键(factory=registry=、四个 attention 列表、parser=)逐项对得上,并已接入 docs/index.rstmoe_strategy_choicesrepeatable=True 与「每次新建 choices list 字面量」(moe_group_args.py:165-185)配合正确,server_args_test.py:51-56 的两次连续 setup_args 正是该语义的有效回归点。
  • strategy_registry.py:102get_attributes() 从每候选多次收敛为一次性物化并复用于排序与日志,消除了原先经懒加载 import 与后端日志反复触发的重复副作用,排序数值与稳定性与原实现等价。
  • deepep_normal_router.py:157 把 FP8 dispatch 缺 scale 从 assert 改为带说明的 ValueError,在 -O 运行下同样生效,并有定向用例覆盖;ROCm ep.py 把未识别量化方法从「静默回落 bf16 executor」改为显式 ValueError
  • cuda_graph_runner.cc:390-398 的注释完整交代了触发条件、后果(padding 行残留上次 replay 的 block ID 并把 KV 写入活跃请求的 block)与「block 0 为保留块」的前提;清零位于 :525 stridedCopyHost 回填之前,顺序正确,tagged cache 分支已由 :473-476 单独覆盖。
  • PerChannelFp8PostprocessTest 用记录调用次数的假 device 与哨兵张量断言 _postprocess 的实际行为(INT8 跳过转换、byte-exact、kernel/scale 形状重排),压的是行为而非驱动它的类属性。
  • _ckpt_base_matches_quant_exclude 的 Note(per_channel_fp8_quant_weight.py:83-92)诚实记录了 FP8/W4A8 既有的静默数值错误风险及不在本 PR 修复的理由;qwen3_next.py:248-252get_device_type().name 写进报错文案,使允许清单增长后信息仍然真实。

auto accept_tokens = torch::zeros({1, static_cast<int64_t>(propose_step + 1)}, cuda_i32);
accept_tokens[0][0] = sp_output_buffer->tokens[0][0];
propose_tokens_gpu[0] = sp_output_buffer->tokens[0][1];
sp_output_buffer->propose_tokens_gpu = propose_tokens_gpu;

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] propose_tokens_gpu 两个生产者的形状与取值语义分叉

新增 sp_output_buffer->propose_tokens_gpu = propose_tokens_gpu,该张量为 torch::empty({1})(:333)且只写入 tokens[0][1](第一个 draft,:337)。同字段另一生产者 StreamCacheResource.cc:223tokens.to(cuda_i32),形状 {1,N}、含 index 0 的已提交 token,其 MtpAsyncDeviceState 版本用 narrow(1,1,N-1) 并在 :234-235 注明「MTP verify 路径期望持有全部 propose_step 个 draft」。消费方 pickOneStepDraftTokenMtpBatchStreamProcessor.cc:250)统一走 lastColumnAsFlat(:147-150 取最后一列):{1} 得第一个 draft,{1,N} 得最后一个。propose_step==1 时两路等价(也与被替代的 `columnAs...

建议: 统一该字段契约:建议 gRPC 侧改为与 P2P 一致的 sp_output_buffer->tokens.narrow(1, 1, propose_tokens.size() - 1).to(cuda_i32, /*non_blocking=*/true){1, propose_step}、全部 draft),并在 GenerateStream.h:96 处用注释固化「shape {batch, propose_step}、最后一列为最新 draft」。若确定只支持单步语义,请在此处补 RTP_LLM_CHECK_WITH_INFO(propose_step == 1, ...) 让多步配置显式失败,而不是依赖 select(-1) 恰好吸收形状差异。另补一条 propose_step >= 2 的用例,断言两条通道经 pickOneStepDraftToken 得到同一 token。

or src_weight_info.name not in cls.w8a8_weight_list
):
return False
for ckpt_w in src_weight_info.weights:

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] W8A8 部分 exclude 的 fail-fast 守卫对 MoE {expert_id} 模板完全失效

守卫依赖 _ckpt_base_matches_quant_exclude,其正则由 _exclude_pattern_forper_channel_fp8_quant_weight.py:57-64)生成,只把 {i} 替换为 \d+,其余字符全部 re.escape。而 MoE 专家权重模板含第二个占位符,例如 qwen3_next_weight.py:409layers.{i}.mlp.experts.{expert_id}.down_proj.weight;编译后 \{expert_id\} 成为字面量,永远匹配不上 checkpoint 里的 model.layers.3.mlp.experts.5.down_projw8a8_weight_list 明确含 W.moe_w1/W.moe_w2(:276-277),因此 MoE 专家被 ignore 时既不触发新增的 raise,基类 support()(:303-309)也不会返回 False,仍按 INT8 加载并索取不存在的 `.weight_sca...

建议:_exclude_pattern_for 通用化:把模板中所有 {...} 形式的占位符统一替换为 \d+(或显式支持 {expert_id}/{i_1}),并为 moe_w1/moe_w2 补一条「ignore 命中单个 expert 时行为明确」的用例(raise 或整模板回退,二选一并写进注释)。若不打算支持 MoE 逐专家排除,请在 support() 处显式判定模板是否含非 {i} 占位符并直接报错,避免守卫给出虚假保障。

base_name, quant_config.exclude_modules
)
):
raise ValueError(

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] 逐层枚举式的完整 exclude 被误拒,且 support() 由纯谓词变为可抛异常

判定式为 base_name not in exclude_modules and _ckpt_base_matches_quant_exclude(...)(:40-45):base_name 是带 {i} 的内部模板,真实 compressed-tensors 的 ignore 只写具体层名,永远不等于模板串;re: 又已在 quant_config.py:932 被拒。于是即使 checkpoint 在全部层逐一列出某模块(数值上安全的完整回退),也与「部分排除」走同一分支 raise,super().support 的未量化回退对真实 checkpoint 不可达;文案却断言 "ignore targets only some instances",误导排查。同时 WeightModule.support 是声明为 -> bool 的抽象谓词、且被 create 对全部注册类求值,此处 raise 让一次能力探测可中断整个 create,而 FP8/W4A8 同形态仍静默返回 False。

建议: 按具体层实例而非模板判定:用 _exclude_pattern_for(base_name) 收集命中的层集合,与 load_config.num_layers 覆盖的层集合比较,全覆盖时 return False 走未量化回退,仅部分覆盖时 raise,并把实际命中与缺失的层号写进错误信息。若暂不实现,请把文案改为陈述限制本身(如「该权重模板存在具体层排除项,W8A8 路径不支持逐层回退」)。同时把校验从 support 移到 loader 构造或独立的 validate_quant_config 阶段以保持谓词纯净;若坚持保留,请在基类文档中声明「support 允许抛出配置级致命错误」并说明 FP8/W4A8 未对齐的原因。补一条「全层具名枚举」的边界用例。

case QuantMethod::W4A8INT4PTPC:
status_info.precision = "W4A8INT4PTPC";
break;
case QuantMethod::W8A8INT8PTPC:

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] QuantMethod→字符串映射在两处 switch 重复维护且各自漏枚举值,worker status 持续打 ERROR 并对外上报 UNKNOWN

本 PR 对两份语义相同的映射各加了一次 W8A8INT8PTPCModelConfig.cc:53-82quantMethodToString(调试串)与 LocalRpcServer.cc:405-442 的 switch(经 set_precision 外发的 gRPC worker status)。两份已发散:LocalRpcServerModelOptFP4(10)QuarkMXFP4(11),这两种部署每次状态上报都落入 default:,打一条 RTP_LLM_LOG_ERROR("unknown quant method: %d") 并把 precision 报成 "UNKNOWN"quantMethodToString 覆盖了 ModelOptFP4 但对 QuarkMXFP4 返回 UNKNOWN(11)None 在前者是 "None"、后者是 "FP16"QuarkMXFP4 是可达取值(QuantInfo.cc:53mxfp4-quark 产...

建议: 把映射收敛到一处:将 quantMethodToString 提升到 rtp_llm/cpp/model_utils/QuantInfo.h/.cc(与 enum 同处)并导出,LocalRpcServer::getWorkerStatusInfo 直接复用,仅把 QuantMethod::None → "FP16" 这一处业务差异留在调用点覆盖;同时补齐 ModelOptFP4/QuarkMXFP4 分支以消除虚假 ERROR 日志与 UNKNOWN 上报。此后新增枚举值只需改一个 switch。

_repeatable.add(slot)
for hook in tuple(_hooks.get(slot, ())):
logger.debug("running backend registration hook for slot %r", slot)
hook(**context)

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] backend hook 在持有全局 RLock 时执行,与 Python import 锁构成锁序反转

hook(**context)(:96)在 with _lock:(:81)内执行,而全部四个槽位消费点都发生在模块 import 期间(fused_moe/__init__.py:120attention/__init__.py:159linear/__init__.py:31moe_group_args.py:201),此时线程已持有对应模块的 import lock;文档推荐写法(backend_registration.md:95-104)也会在 hook 体内 import 实现模块。于是形成两种加锁顺序:A 线程「import lock(fused_moe) → _lock → hook 内 import 后端模块」,B 线程「import lock(该模块) → 等待 _lock」。其中一条边是普通 RLock,CPython 的模块锁死锁检测无法识别,表现为启动期挂死而非报错。

建议: 把「取快照」与「执行 hook」分离:锁内完成 _started/_repeatable 判定并复制出待执行的 hook 元组,释放锁后再逐个调用 hook(**context);一次性语义仍由 _started 保证。若确需串行化 hook 执行,改用槽位级独立锁而非覆盖全部槽位的进程级锁,把锁粒度与 import 锁解耦;backend_registration.md:141-142 关于「进程级锁保护」的表述需同步更新。

@@ -32,3 +32,7 @@ def is_cuda() -> 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.

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

[P3] PPU 设备识别依赖环境变量真值且无任何日志,CUDA 主机可能被静默重分类

get_device_type()torch.cuda.is_available() 且非 HIP 时,只要 os.environ.get("PPU_HOME") 为任意真值(含 "0"、任意残留路径)或 torch 版本串含 ppu,就返回 DeviceType.Ppu(:20-24),无值内容校验、无日志、无缓存。后果落在本 PR 涉及的分支上:is_cuda() 变为 False,attention/__init__.pyfused_moe/__init__.py 从 CUDA 分支跌入 else 兜底(丢掉 headwise/TRT-LLM/XQA/MLA 等 impl 与 fused_moe/__init__.py:106 的 FP4 策略注册),arch.py 的 SM 判定全部转 False,并直接改变本 PR 新加的 qwen3_next.py:248 白名单判定——一台正常 CUDA 机器只因残留环境变量就被静默降级,排查时无任何线索。

建议: 收紧判定并补可观测性:只接受明确的开关值(如要求 PPU_HOME 指向存在的目录,或改用 RTP_LLM_DEVICE_TYPE=ppu 这类显式量),并在返回非 Cuda 结果时用 logger.info 打印判定依据(命中的变量名与取值),使误分类在启动日志中可见;建议对 get_device_type()functools.lru_cache 固化单进程内判定结果,并补一条覆盖「CUDA 可用 + PPU_HOME 为空串/0」的用例。

Checklist: [6.1] 兼容性:外部 HTTP/RPC API、持久数据、配置、环境迁移安全;[6.1] 可观测性:日志/指标/超时可操作、非噪声

get_device_type,
is_cuda,
is_hip,
is_ppu,

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] arch.py 新增的 is_ppu 自身未使用,仅作为隐式再导出通道

arch.py:6-12rtp_llm.device.device_type 导入 DeviceType/get_device_type/is_cuda/is_hip/is_ppu,但 is_ppu 在本文件内没有任何使用点(全仓 is_ppu 仅 3 个引用:定义处、arch.py:11qwen3_next.py:242)。真正的消费者 qwen3_next.py:238-243models_py.utils.arch 而非定义模块导入,使 arch.py 成为设备判定的隐式再导出层:既无 __all__ 声明该意图,也让「设备判定归属 rtp_llm.device」这一分层被模糊,后续清理未使用 import 的工具会直接删掉它并打挂 qwen3_next 的设备门禁。

建议:qwen3_next.py 直接从 rtp_llm.device.device_type 导入这些谓词(与 is_cuda/is_hip 的来源保持一致),删除 arch.py 中未使用的导入;若确实希望 arch.py 作为统一门面,请显式声明 __all__ 并在模块注释中说明它对外重导出设备判定函数。

Checklist: [6.1] 分层边界:新概念在正确层级,不泄漏内部

self._conditions_pass(
CudaNoQuantCppStrategy,
create_model_config_without_quant(),
"no_auant_cpp",

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] no_auant 拼写错误的策略取值出现第三处副本

新增用例把 "no_auant_cpp" / "no_auant_dp_normal"no_quant 的拼写错误)作为字面量再复制一份(:179、:188、:197、:206),与 no_quant.py:60/88moe_group_args.py:167-169--moe_strategy choices 构成第三处副本。该取值同时是 CLI 对外可见的字符串,纠正需要同步生产、测试与参数 choices 三处,副本越多越难修。

建议: 把策略名收敛为 Enum 或模块级常量,由 CLI choices 与策略实现共同引用,测试引用同一常量;若要纠正拼写,请在同一变更中保留旧名作为兼容别名并在文档中标注该已知拼写。

Checklist: [P.A] 字符串分发用 Enum/Literal;[I] 同一功能用统一工具函数

class DeepepNormalRouterHookTest(TestCase):
@staticmethod
def _new_router(router_class, quant_dtype):
router = object.__new__(router_class)

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] hook 测试白盒构造 router,且伪造 buffer 依赖 dispatch 位置参数下标

_new_routerobject.__new__(router_class)(:55)跳过 DeepepNormalRouterBase.__init__,手工赋 7 个内部属性,因此 expert_num_per_rankrank_expert_offset 的真实推导关系(deepep_normal_router.py:68-70)未被验证,测得的 rank_expert_offset=0(:69)是手工设定值——而该值恰好决定 :167-172 的 topk 重映射结果。_TupleDispatchBuffer.dispatch(:23-33)通过 args[5]/args[6] 取 topk_ids/topk_weights,与生产 prepare 的位置实参强耦合:调用顺序调整时 fake 会静默回显错误张量而断言仍通过;__init__ 新增 prepare 依赖的字段时,测试会以 AttributeError 失败而非表达契约破坏。

建议: fake 改用完整命名签名(def dispatch(self, x, handle, num_tokens_per_rank, num_tokens_per_rdma_rank, is_token_in_rank, num_tokens_per_expert, topk_idx, topk_weights, *, expert_alignment)),使参数顺序变更成为调用期错误;或改为保留 __init__、只替换 DeepEPWrapper.get_instance 来构造 router,并至少断言一次由 __init__ 推导的字段(如以 ep_size=2 构造后校验 rank_expert_offset)。

Checklist: [P.G] mock/fake/stub 不得替代本次声称覆盖的生产边界

return _AcceptingMoeAttributes()


class BackendAvailabilityGuardTest(unittest.TestCase):

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] 单个测试文件跨五个模块,且直接改写生产私有状态

该文件挂在 //rtp_llm/model_loader/test 下,却同时覆盖 LinearFactoryStrategyRegistry(:22-25)、quant_config 的 checkpoint 解析(:189)、DeepepWrapperConfig.calc_low_latency_max_token_per_rank(:318)与 pybind QuantAlgo(:491-504)五个模块;BackendAvailabilityGuardTest 还直接赋值生产私有属性 LinearFactory._strategies(:132/135/152/162)。同风险类的兄弟测试 test_compressed_w4a8_int4_per_channel.py 仅一个类、无跨模块导入。后果是这些模块自身的测试目标保持沉默,回归定位会统一指向 model_loader;且对 linear/factory.py:119strategy_registry.py:89 的错误文案与 _strategies 属性名形成...

建议: 按归属拆分:LinearFactory/StrategyRegistry 的守卫测试移入各自 factory 的 test 目标,DeepEP 分桶测试移入 rtp_llm/models_py/distributed 的 test 目标,quant_config 解析测试移入配置侧 test 目标;本文件只保留 loader 与 dtype/postprocess 用例。私有属性替换建议由 factory 提供测试专用的 set_strategies 或 contextmanager 入口。

Checklist: [6.1] SRP:模块/类职责单一;[6.1] 新逻辑有聚焦单测 + 相关集成/smoke 测试

Add W8A8 configuration parsing, INT8 per-channel loading, dispatch contracts, strategy guards, and fail-fast validation.
Provide backend-neutral registration slots for Linear, Fused MoE, Attention, and repeatable MoE strategy parser choices, with tests and integration documentation.
@Tanmo-ai
Tanmo-ai force-pushed the feature/ppu-qwen35-cu130 branch from 51a75af to 1f03a0e Compare August 21, 2026 05:33

@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 #1323

Status: LGTM

Summary: P0/0 · P1/0 · P2/26 · P3/10

Reviewed: commit 1f03a0e83161 · 2026-08-21 14:39 UTC+8

lgtm ready to ci

Non-blocking Suggestions

P2

  • propose_tokens_gpu 两个生产者的形状与取值语义分叉 @ rtp_llm/cpp/model_rpc/DecodeRpcServer.cc:338
    • 建议:在 SpeculativeExecutorStreamOutput::propose_tokens_gpu 声明处写明唯一 shape 契约(「仅下一步单个 draft」还是「全部 propose_step drafts」),把构造逻辑抽成两条通道共用的工具函数;若确为一步语义,同步修正 StreamCacheResource.cc 的实现与其 :234-235 注释。兜底可在 collectLegacyProposeSlices:360-375)对各 slice 的 numel() 一致性加 RTP_LLM_CHECK。请补一条 propose_step=1 与 >1 的 gRPC 握手回归用例,断言 GPU 路径与 host 回落取到同一 draft token。
  • 握手快照发布无门控与回滚开关,CPU 回落路径被本 PR 变为不可达 @ rtp_llm/cpp/model_rpc/DecodeRpcServer.cc:353
    • 建议:给 :338 的发布加上与 MtpAsyncDeviceState 一致的环境变量门控(或独立开关),保留出问题时回退到 CPU tokens 的运维手段;或在 specUpdate 中当 draft_token_gpu 缺失而 draft_token >= 0 时主动清空 propose_tokens_gpu,让 reader 显式回落。请补一条「GPU 镜像未刷新而 CPU token 已前进」的回归用例。
  • QuantMethod→字符串映射在两处 switch 重复维护且各自漏枚举值 @ rtp_llm/cpp/model_rpc/LocalRpcServer.cc:433
    • 建议:把枚举到字符串的映射收敛为 QuantInfo.h/cc 中唯一一个 quantMethodToString()ModelConfig.cc:53 已有同名静态函数可直接上提),两处均改为调用它(precision 只需对 QuantMethod::None 保留 "FP16" 展示层特例);同时补齐 ModelOptFP4QuarkMXFP4,避免这两类部署的高频轮询接口持续输出 ERROR 并对外上报 UNKNOWN。
  • W8A8 部分排除守卫对 re: 单层正则留有静默全层降级缺口 @ rtp_llm/model_loader/compressed_w8a8_int8_per_channel_weight.py:46
    • 建议:让 re: 与具体路径走同一层感知判定:至少代入两个不同层号(如 01)分别求值,全部命中才认定为整模板排除,否则按部分排除 fail-fast;若能拿到 num_layers,在真实层区间上展开模板统计命中层数,全覆盖返回 False、部分覆盖 raise。补「regex 仅命中部分层」与「regex 命中全部层」两条用例。
  • 守卫与 exclude 匹配对 MoE {expert_id} 模板完全失效 @ rtp_llm/model_loader/compressed_w8a8_int8_per_channel_weight.py:43
    • 建议:把模板→正则的构造统一为「所有 {...} 占位符都展开为通配」(复用 _exclude_pattern_for 并支持任意占位符名),使 {expert_id} 模板与 MoE 的 ignore 条目能正常比对;随后守卫对部分排除按 fail-fast、对完整排除走非量化回退。请补一条 MoE {expert_id} 模板被 ignore 覆盖(部分与全部两种)的用例。
  • 逐层枚举排除全部层的合法 checkpoint 被误判为部分排除并启动失败 @ rtp_llm/model_loader/compressed_w8a8_int8_per_channel_weight.py:39
    • 建议:把匹配到的具体层索引收集起来与层数比较:全覆盖返回 False 走非量化回退,仅部分覆盖时才 raise。若此处确实拿不到层数,退一步改为 logging.warning + 返回 False(失败安全)并在文档中声明部分排除不受支持,同时补一条「逐层枚举全部层」的用例。另请把 supportquant_config 注解从 CompressedW8A8Int8PerChannelQuantConfig 还原为基类 QuantizationConfig——注册表以任意配置调用它,收窄参数类型与实际契约相反,需要窄类型时在 isinstance 判定后用局部变量表达。
  • 共享基类的 re: 语义被改变,但 docstring 声称行为未变且 FP8/Quark 侧无覆盖 @ rtp_llm/model_loader/per_channel_fp8_quant_weight.py:100
    • 建议:修正 docstring,明确「re: 解释为正则」是本次引入的新语义且适用于所有 per-channel 配置;为 FP8/Quark 配置补一条带 re: 条目的回归用例固定新语义。同时请确认 quant_config.py:381-382 只从 quant_config["exclude"] 填充 FP8 侧 exclude_modules(compressed-tensors 实际用 ignore)是有意为之——若是遗漏,则被 ignore 的模块当前会被错误量化,影响面远大于本变更。
  • 正则排除匹配逻辑在两个函数中逐行重复实现 @ rtp_llm/model_loader/per_channel_fp8_quant_weight.py:114
    • 建议:抽出单一返回「匹配来源」的公开模块级函数(如返回 None / LITERAL / REGEX),两处共用;_ckpt_base_matches_quant_exclude 内只保留通配分支并调用该函数;把跨模块使用的 helper 提升为非下划线公开名,并让 W8A8 support 复用一次计算结果而不是先自查再交给基类重算。
  • dispatch 元组返回契约脱离 use_fp8,scale 布局与 topk 重映射不自洽 @ rtp_llm/models_py/modules/factory/fused_moe/impl/cuda/routers/deepep_normal_router.py:167
    • 建议:让扩展点显式声明是否携带 scale(新增子类属性或 _dispatch_returns_scale() 钩子),base 侧对「未声明携带 scale 却收到 tuple」继续 fail-fast,并恢复非 tuple 分支的张量类型校验;把 :167 的重映射条件从 not use_fp8 改为「dispatch 未返回 scale」以与新判定对齐,同时在 _prepare_dispatch_input docstring 中写明 scale 布局与 topk 重映射契约。补一个 recv_topk_idx 含 -1 的 tuple-dispatch 用例,锁定 padding token 的 expert id 与 scale 形状。
  • PPU 无 in-tree attention 分支,落入 CUDA else 分支且 hook 在其后才运行 @ rtp_llm/models_py/modules/factory/attention/__init__.py:137
    • 建议:为 DeviceType.Ppu 增加显式分支(可为空,仅依赖 hook 注册),或把 else 分支的 flashinfer 导入收窄为 Cuda/未知设备并允许无 in-tree 实现的设备跳过;同时在 allowlist 通过后追加一次后端就绪校验(确认 PREFILL_MHA_IMPS / MoE registry 中存在 PPU 实现),缺失时给出「需要安装或注册 PPU backend」的可操作错误。建议顺带在 get_device_type() 首次判定处打一条 INFO 记录判定依据。
  • ROCm EP 支持的量化方法存在两处真值来源,被删除的方法名仍被 executor/router 接受 @ rtp_llm/models_py/modules/factory/fused_moe/impl/rocm/strategy/ep.py:25
    • 建议:用一个 Dict[Optional[str], Tuple[executor_class, quant_config_factory]] 把映射定义一次,_SUPPORTED_QUANT_METHODS 由其 keys 派生;并同步清理或注释说明 rocm_moe.py 与 ROCm pure_tp_router.py 中已不可由 get_method() 产生的同名分支。mxfp4-quark 在 ROCm EP 上由「静默回落 BF16 executor」改为「策略不可选」是改善,但属行为变化,请在 PR description 中显式说明。
  • ROCm EP 量化过滤的唯一用例在其实际运行的 CUDA 机型上恒真 @ rtp_llm/models_py/modules/factory/fused_moe/tests/test_cuda_strategies.py:214
    • 建议:改为断言过滤动作本身而非最终布尔值:用 patch.object(RocmEpNormalStrategy, "get_attributes") 记录调用次数,断言不受支持方法下未被调用、受支持方法(如 quant_config=NoneFp8BlockWiseQuantConfig)下被调用一次,形成正反对照。并把 ROCm 用例移到不带 open_skip、不申请 GPU 的 CPU-only target(utils/test/BUILD:218-226 已有该模式),避免 CUDA target 顶层导入 impl/rocm/strategy 包初始化。
  • 设备无关的中心工厂硬编码 W8A8 后端专属文案,量化方法串三处字面量消费 @ rtp_llm/models_py/modules/factory/fused_moe/strategy_registry.py:87
    • 建议:把提示语通用化:在通用报错中带上 quant_method 与当前已注册策略类名列表,并提示「可能缺少对应后端的计算实现」,无需枚举具体方法名;若必须保留特判,请引用 CompressedW8A8Int8PerChannelQuantConfig.get_method() 而非字面量,或把说明文本下沉到后端注册扩展点。
  • 一次性槽位在 hook 失败后被整体重置,重试会重放已成功的 hook 且重新放开迟到登记 @ rtp_llm/utils/backend_registry.py:136
    • 建议:明确失败语义并写入文档:要么按 hook 粒度记录完成状态(重试只补跑未完成项),要么保留 _started 让该槽位永久失败(与「注册失败即启动失败」的初衷一致);同时新增「已开始但失败」状态以继续拒绝迟到登记,并在文档中要求 hook 自身幂等。
  • 注册机制的并发与重入协议无任何测试,等待既无超时也无可观测性 @ rtp_llm/utils/backend_registry.py:119
    • 建议:补两条纯同步用例(hook 内递归调用同槽位断言 RuntimeError("re-entrant");hook 内调用 reset_backend_registrations() 断言 cannot reset)与一条双线程用例(hook 内阻塞占位,第二线程断言等待后正常返回、占位失败时收到含 "retry is allowed" 的 RuntimeError__cause__ 为原异常)。同时给 event.wait() 加超时并输出槽位名与 owner 线程;若并发路径确实不可达,建议直接用 with _lock: 删掉 _inflight/Event 这层投机抽象(YAGNI),并相应修正文档措辞。
  • 槽位名与上下文键为自由字符串 + kwargs 透传,测试用的槽位名与生产不一致 @ rtp_llm/utils/test/backend_registry_test.py:34
    • 建议:把四个槽位名与其 context 参数收敛为模块级常量或 Literal/Enum(例如 BackendSlot.FUSED_MOE),register_backend_hook 对未知槽位 fail-fast;测试中把 "moe" / "parser" 换成从生产模块导入的槽位常量,使名字漂移可被捕获。
  • moe_strategy_choices 槽位以整个 parser 为上下文,把 argparse 私有结构固化为跨仓契约 @ rtp_llm/server/server_args/moe_group_args.py:201
    • 建议:把 moe_group.add_argument("--moe_strategy", ...) 的返回值存为局部 action 变量,改为传精确上下文(如 add_moe_strategy_choice=<闭包,内部做去重与类型校验>),由 moe_group_args.py 内部完成查找,把 argparse 细节封闭在公共 parser 模块内;同步统一文档示例与测试 hook 的写法。若为兼容已上线外部 hook 必须保留 parser=,请在文档槽位表中显式声明「依赖 _actions 属私有契约」并给出迁移期限。
  • 测试裸调 reset_backend_registrations 会永久丢弃同进程内已登记的真实 backend hook @ rtp_llm/server/server_args/test/server_args_test.py:37
    • 建议:不要在共享进程里裸调 reset_backend_registrations()。建议在用例内对三个容器做「快照—恢复」(如 patch.dict 分别替换),或由 backend_registry 提供返回上下文管理器的隔离入口;并让 reset_backend_registrations() 一并调用 import_optional_internal_source_entrypoint.cache_clear(),使 reset 成为真正可恢复的操作。另请补一条只保留最底层 mock(向 sys.modules 注入假的入口模块)的用例:当前用例同时 patch 了 server_args.ensure_backend_entrypoint_loaded:41-45)与 backend_registry.ensure_backend_entrypoint_loaded:46-49),真实入口发现路径全程未执行。
  • FP8 per-tensor compressed 既有路径把 input_activations.dynamic 从必填放宽为默认 False @ rtp_llm/config/quant_config.py:257
    • 建议:对 dynamic 保持必填:改为显式判断(if "dynamic" not in activation_config: raise ValueError(...))后再取值,或沿用下标访问。若确实要给默认值,请在注释中写明该默认对应 compressed-tensors 规范的哪一条,并补一个「缺失 dynamic」的解析用例锁定预期。
  • compressed-tensors targets 校验按组名不对称,同内容 checkpoint 结果取决于组名 @ rtp_llm/config/quant_config.py:55
    • 建议:若目标是「不改变今天能加载的 checkpoint」,请在 group_0 分支加 logging.warning 说明 targets 未被校验、按全模型量化处理并给出失效表现,避免同内容不同组名结果不一致且无任何提示;若可接受收紧,则对两条分支统一调用 _validate_compressed_targets,并在 PR description 中列出会因此启动失败的 checkpoint 形态。
  • server 库新增模块级依赖但只在测试 target 补了 //rtp_llm:utils @ rtp_llm/server/server_args/moe_group_args.py:2
    • 建议:在 rtp_llm/server/BUILDpy_library(name = "server") deps 中直接加入 //rtp_llm:utils,让声明与实际 import 对齐;测试 target 的显式依赖可以保留。
  • 用源码文本计数替代 dtype 行为断言,漏掉两处位置传参调用点 @ rtp_llm/model_loader/test/test_compressed_w8a8_int8_per_channel.py:504
    • 建议:删除源码文本计数,改为参数化行为覆盖:用 subTest 遍历 w8a8_weight_list 全部 weight key,对 INT8 与 FP8 两种配置分别构建 weight 并断言 kernel.data_type == cls.weight_dtypescale.data_type == torch.float32,覆盖全部 _get_*_quant_weight 分支且不受格式影响。
  • 工厂「缺少计算后端」守卫用空注册表自证,未验证真实注册表不会误认领 @ rtp_llm/model_loader/test/test_compressed_w8a8_int8_per_channel.py:152
    • 建议:把两组守卫用例移到 .../factory/linear/test.../factory/fused_moe/tests 下对应目标;断言改为在真实注册表上传入 W8A8 配置,验证没有任何 in-tree 策略认领它(候选集为空)并抛出该专用错误,从而同时锁住「文案正确」与「无策略误认领」两个方向。
  • DeepEP dispatch stub 用魔法位置下标耦合生产签名,签名变化时测试静默通过 @ rtp_llm/models_py/modules/factory/fused_moe/impl/cuda/routers/test/deepep_normal_router_hook_test.py:26
    • 建议:把 stub 的 dispatch 改为按生产签名声明具名参数(或先 assertEqual(len(args), 7) 再取值),并补断言 payload.expert_topk_ids/expert_topk_weights 与输入的对应关系;把两个裸 assert 换成 raise AssertionError/self.fail() 等不受 -O 影响的形式。
  • W8A8 新增的 act_qscheme 与 worker precision 映射零测试覆盖 @ rtp_llm/cpp/engine_base/Executor.h:64
    • 建议:补一条 C++ 单测:构造 quant_algo.setQuantAlgo("w8a8_int8_per_channel", 8, 0)ModelConfig,断言 Executor::genModelDescription(...) 得到的 act_qscheme == QScheme::Qint8PerToken;并为 LocalRpcServer::getWorkerStatusInfo 的 precision 映射补一条覆盖 W8A8INT8PTPCModelOptFP4/QuarkMXFP4 的表驱动用例,同时钉住不落 default(可与统一后的 quantMethodToString() 一起做)。
  • Mega-PR:MTP/PD handoff 与 CUDA Graph 修复混入 W8A8 量化与后端注册特性 @ rtp_llm/cpp/pybind/ConfigInit.cc:1163
    • 建议:按方向拆分为独立 PR:后端注册机制(含文档与测试)、W8A8 INT8 量化链路、MTP propose token 修复、CUDA graph 清零、PPU 放行。若因发布节奏必须合并,请在 PR description 中按方向分节说明动机、影响面与各自的回滚方式(当前 description 未做此说明)。

P3

  • host 侧全表同步清零落在 decode 热路径,且与 tagged 分支的守卫强度不一致 @ rtp_llm/cpp/cuda_graph/cuda_graph_runner.cc:397
    • 建议:用 RTP_LLM_PROFILE_SCOPE 包住该清零以便归因,并在注释中给出量级(max_bs × max_blocks × 4B);若实测占比可观,可只清 [current_bs, graph_bs) 行加上有效行的列尾区间。把 host/device 两侧清零收敛到一个 helper 以统一守卫与不变量。同时在 rtp_llm/cpp/cuda_graph/tests/cuda_graph_decode_padding.py 中补「先大 batch 再小 batch 两次 replay」用例,断言第二次 prepare 后 host 镜像 [current_bs, graph_bs) 行全为 0,tagged 场景补同类断言。
  • 三个 hook 消费点对设备导入失败的处理不一致 @ rtp_llm/models_py/modules/factory/linear/__init__.py:27
    • 建议:统一三处的失败语义:要么都 fail-fast(推荐,与 backend_registry 模块 docstring「注册失败即启动失败」的取向一致),要么都降级并在文档职责表中写明降级后 factory 可能为空、以及此时期望的报错位置。若保留 linear 的宽松行为,请把 warning 提为 error 并带上 device_type 与异常类型。
  • no-quant 策略新增校验与 executor 既有条件重复,且新测试绕过 can_handle @ rtp_llm/models_py/modules/factory/fused_moe/impl/cuda/strategy/no_quant.py:58
    • 建议:二选一:删除策略侧重复校验,改由 executor check_conditions 单点负责,测试改为经 can_handle() 断言选择结果;或保留策略侧校验并在注释中说明它是为「executor 被替换后仍不误认领量化 checkpoint」而存在的独立护栏。无论哪种,请补 moe_strategy="auto" 的用例。
  • setup_args 中的入口预加载与 run_backend_registrations 内部自加载重复 @ rtp_llm/server/server_args/server_args.py:534
    • 建议:二选一:删除该显式调用,让加载时序完全由 run_backend_registrations 负责,测试改为只 mock 最底层的 import_optional_internal_source_entrypoint;或保留并把注释改成「防御性预加载:即使未来出现更早消费槽位的参数组也能保证顺序」,明确它是冗余但有意的护栏。
  • choices 扩展点只保护 argparse 校验通道,env 补齐通道静默绕过 @ rtp_llm/server/server_args/server_args.py:366
    • 建议:让 env 回填通道复用同一校验:回填前查对应 action 的 choices 并在不匹配时报可读错误。同时补两条用例:不注册 hook 时 --moe_strategy external_test_strategy 触发 SystemExit;以及经 MOE_STRATEGY 注入未知取值时被拒绝。若暂不修,请把「有 CLI 参数时 env 回填不校验 choices」记入文档已知行为。
  • 量化方案以硬编码字符串元组在中心函数集中分派 @ rtp_llm/models_py/distributed/deepep_wrapper.py:230
    • 建议:在 QuantizationConfig 上增加 is_per_act_token()(或 low_latency_token_buckets())由各配置类自行声明,该函数改为查询该能力;若暂不重构,至少把字符串集合提为模块级常量并注明与 get_method() 的同步要求。
  • 设备判定函数经 models_py.utils.arch 隐式 re-export @ rtp_llm/models/qwen3_next/qwen3_next.py:238
    • 建议:直接从 rtp_llm.device.device_type 导入这四个符号;若确实需要 arch.py 作为设备门面,请在其中声明 __all__ 并注明门面职责,避免被当作未使用导入清理掉。
  • LoadQuantPerChannelFp8Weight 绕过了新引入的 supported_quant_config_types 单一来源 @ rtp_llm/model_loader/per_channel_fp8_quant_weight.py:733
    • 建议:让该子类也通过 cls.supported_quant_config_types 判定(必要时在子类上覆写该属性),使「认领哪些配置」只有一个声明位置。
  • GenerateStream 新用例只覆盖「缺失则保留」,未覆盖「提供则刷新」 @ rtp_llm/cpp/engine_base/stream/test/GenerateStreamTest.cc:117
    • 建议:补一条对称用例:构造 draft_token_gpu = torch::tensor({11}, torch::kInt32).to(torch::kCUDA),调用 specUpdate 后断言 propose_tokens_gpu.cpu().item<int32_t>() == 11(从 9 刷新为 11);并补一条 commit-only(propose_token_ 清空)路径的边界断言。
  • backend_registry 生命周期异常断言未校验消息,且 noop 用例无任何断言 @ rtp_llm/utils/test/backend_registry_test.py:66
    • 建议:三条异常用例改用 assertRaisesRegex 钉住各自消息关键片段(如 "already initialised" / "re-entrant" / "lifecycle changed");noop 用例补断言(例如随后 register_backend_hook 应因槽位已开始而 raise,以证明状态确实被记录)。

Checklist Findings (25 fail / 55 total)

General Principles Checklist

  • [6.1] Architecture — 依赖方向:无循环依赖/跨层惊喜 → issue 设备判定函数经 models_py.utils.arch 隐式 re-export
    改动前 is_hip 直接来自 rtp_llm.device.device_type,改动后 get_device_type/is_cuda/is_hip/is_ppu 全部改从 rtp_llm.models_py.utils.arch 导入(:238-243)。arch.py:6-12 只是 from rtp_llm.device.device_type import ... 的透传,其中 get_device_type/is_hip/is_ppu/DeviceType 在该模块内均未被使用,也未声明 __all__,属隐式 re-export:任何 unused-import 清理(autoflake / ruff F401)都会让本文件运行时 ImportError,mypy 严格模式同样报错;同一批符号出现两个可用导入路径,后续容易分叉。
  • [6.1] Architecture — 兼容性:外部 HTTP/RPC API、持久数据、配置、环境迁移安全 → issue choices 扩展点只保护 argparse 校验通道,env 补齐通道静默绕过
    扩展点的前提是「合法取值由 --moe_strategy 的 choices 收口」。但 server_args.py:343-371 在存在任意命令行参数时(has_cmd_args 为真)对未经 CLI 提供的 dest 用 setattr(parsed_args, dest, ...) 回填 env 值,完全绕过 choices 校验;只有「零命令行参数」分支(:263-291)以 --flag value 形式回灌因而受校验。于是 MOE_STRATEGY=<未注册策略> 在混合部署下既不会被拒绝也不会有任何提示,扩展点在该场景形同虚设。新测试也未覆盖「未注册任何 backend 时未知 strategy 应被拒绝」这一反向路径。
  • [6.1] Architecture — 分层边界:新概念在正确层级,不泄漏内部 → issue 设备判定函数经 models_py.utils.arch 隐式 re-export
    改动前 is_hip 直接来自 rtp_llm.device.device_type,改动后 get_device_type/is_cuda/is_hip/is_ppu 全部改从 rtp_llm.models_py.utils.arch 导入(:238-243)。arch.py:6-12 只是 from rtp_llm.device.device_type import ... 的透传,其中 get_device_type/is_hip/is_ppu/DeviceType 在该模块内均未被使用,也未声明 __all__,属隐式 re-export:任何 unused-import 清理(autoflake / ruff F401)都会让本文件运行时 ImportError,mypy 严格模式同样报错;同一批符号出现两个可用导入路径,后续容易分叉。
  • [6.1] Architecture — 可观测性:日志/指标/超时可操作、非噪声 → issue host 侧全表同步清零落在 decode 热路径,且与 tagged 分支的守卫强度不一致
    kv_cache_kernel_block_id 形状为 [max_bs_, max_blocks]:939-940),新增的 fill_(0) 每次 replay 准备都会同步 memset 整表,在大 max_bs / max_seq_len 部署下是数百 KB 级 CPU 写,位于 decode 每步关键路径;与 device 侧不同——device 清零走 invokeCudaGraphPrepareFill 异步 kernel 且有 RTP_LLM_PROFILE_SCOPE:333),host 这一行既是同步阻塞又落在任何 profile scope 之外。同时该行对字段无条件调用,而同文件 tagged 分支先判 defined() && !is_cuda()zero_():473-476):同一不变量在相邻代码里被表述为两种强度。修复本身也无任何断言。
  • [6.1] Architecture — 回滚路径:风险行为存在运维回滚手段 → issue PPU 无 in-tree attention 分支,落入 CUDA else 分支且 hook 在其后才运行
    qwen3_next.py:248 把 PPU 加入 Qwen3Next 允许列表(改前非 cuda/rocm 立即 RuntimeError),使该组合成为受支持路径。但 attention 工厂是 if ROCm(:32) / elif Cuda(:52) / else(:137),PPU 落到 else:138-154 无条件 from ...cuda_impl.py_flashinfer_mha import ...cuda_cp_impl.prefill_cp_flashinfer import CPFlashInferImpl,并把 CUDA 实现注册进 PREFILL_MHA_IMPSrun_backend_registrations("attention", ...):159,其后才运行。PPU 环境无 flashinfer 时模块导入即失败、hook 永不执行;有 flashinfer 而树外 backend 未加载时,则带着 CUDA 实现继续走下去。另 device_type.py:20-24 由 `PP
  • [6.1] Architecture — 状态不变量:创建/更新/失败/重试/回滚路径有效 → issue 测试裸调 reset_backend_registrations 会永久丢弃同进程内已登记的真实 backend hook
    用例在 :37finally:64)各调一次 reset_backend_registrations(),清空进程级 _hooks/_started/_repeatable/_failuresbackend_registry.py:148-156)。但入口由 import_optional_internal_source_entrypoint 加载,该函数带 @lru_cache(maxsize=None)import_util.py:26)且模块体被 sys.modules 缓存,reset 后 ensure_backend_entrypoint_loaded() 是 no-op,被清掉的 hook 无法重新登记。若同进程中先有任何路径触发过入口导入,本用例结束后 linear/fused_moe/attention 槽位的真实 hook 即永久丢失——正是 backend_registry.py:28-31 自述要避免的「静默丢弃注册导致 Factory 选到另一实现,表现为错误数值」。
  • [6.1] Architecture — 错误语义:fail-fast/retry/fallback/silent 行为显式 → issue choices 扩展点只保护 argparse 校验通道,env 补齐通道静默绕过
    扩展点的前提是「合法取值由 --moe_strategy 的 choices 收口」。但 server_args.py:343-371 在存在任意命令行参数时(has_cmd_args 为真)对未经 CLI 提供的 dest 用 setattr(parsed_args, dest, ...) 回填 env 值,完全绕过 choices 校验;只有「零命令行参数」分支(:263-291)以 --flag value 形式回灌因而受校验。于是 MOE_STRATEGY=<未注册策略> 在混合部署下既不会被拒绝也不会有任何提示,扩展点在该场景形同虚设。新测试也未覆盖「未注册任何 backend 时未知 strategy 应被拒绝」这一反向路径。
  • [6.1] Quality — Commit 原子、message 与行为匹配 → issue Mega-PR:MTP/PD handoff 与 CUDA Graph 修复混入 W8A8 量化与后端注册特性
    41 个文件承载至少四组无共同动机的变更:(1) W8A8 INT8 量化链路(quant_config.pyQuantInfo.*ConfigInit.ccModelConfig.ccLocalRpcServer.cc.pyi、model_loader、工厂特判);(2) 树外后端延迟注册机制(backend_registry.py + 四个槽位 + 文档 + server_args);(3) MTP 投机握手 propose_tokens_gpuDecodeRpcServer.ccGenerateStream.cc)与 CUDA graph host block table 清零(cuda_graph_runner.cc);(4) PPU 设备放行(device_type.pyarch.pyqwen3_next.py)。packet 的 risk_tags 达 13 个并触发 large_pr_route。后果:任一方向出问题需整体回滚,二分定位与回滚粒度被放大。
  • [6.1] Quality — Mega-PR 已拆分为独立变更 → issue Mega-PR:MTP/PD handoff 与 CUDA Graph 修复混入 W8A8 量化与后端注册特性
    41 个文件承载至少四组无共同动机的变更:(1) W8A8 INT8 量化链路(quant_config.pyQuantInfo.*ConfigInit.ccModelConfig.ccLocalRpcServer.cc.pyi、model_loader、工厂特判);(2) 树外后端延迟注册机制(backend_registry.py + 四个槽位 + 文档 + server_args);(3) MTP 投机握手 propose_tokens_gpuDecodeRpcServer.ccGenerateStream.cc)与 CUDA graph host block table 清零(cuda_graph_runner.cc);(4) PPU 设备放行(device_type.pyarch.pyqwen3_next.py)。packet 的 risk_tags 达 13 个并触发 large_pr_route。后果:任一方向出问题需整体回滚,二分定位与回滚粒度被放大。
  • [6.1] Quality — PR description 说明动机与设计 → issue Mega-PR:MTP/PD handoff 与 CUDA Graph 修复混入 W8A8 量化与后端注册特性
    41 个文件承载至少四组无共同动机的变更:(1) W8A8 INT8 量化链路(quant_config.pyQuantInfo.*ConfigInit.ccModelConfig.ccLocalRpcServer.cc.pyi、model_loader、工厂特判);(2) 树外后端延迟注册机制(backend_registry.py + 四个槽位 + 文档 + server_args);(3) MTP 投机握手 propose_tokens_gpuDecodeRpcServer.ccGenerateStream.cc)与 CUDA graph host block table 清零(cuda_graph_runner.cc);(4) PPU 设备放行(device_type.pyarch.pyqwen3_next.py)。packet 的 risk_tags 达 13 个并触发 large_pr_route。后果:任一方向出问题需整体回滚,二分定位与回滚粒度被放大。
  • [6.1] Software Engineering — DIP:高层策略不依赖非必要具体细节 → issue moe_strategy_choices 槽位以整个 parser 为上下文,把 argparse 私有结构固化为跨仓契约
    槽位语义只需往 --moe_strategychoices 追加一个值,但 context 传的是整个 top-level parser。docs/backend/backend_registration.md:107-115server_args_test.py:28-32 都必须遍历 argparse 私有属性 parser._actions、按 "--moe_strategy" 字面量匹配再改 choices;两处写法还不一致(文档重建 list 且做幂等去重、找不到时 raise RuntimeError,测试用裸 next() + 就地 .choices.append(...),参数改名会抛裸 StopIteration)。hook 拿到 parser 后可改写任意参数,作用域远超声明语义;--moe_strategy 改用子解析器或 choices 改为 tuple/Enum 会让所有外部 hook 同时静默失效。
  • [6.1] Software Engineering — DRY:重复非平凡逻辑被抽取或显式复用 → issue LoadQuantPerChannelFp8Weight 绕过了新引入的 supported_quant_config_types 单一来源
    本 PR 引入 supported_quant_config_types 作为「本 loader 认领哪些配置」的单一来源(:309-315 注释明确说明它保证 WeightModule.create 只匹配一个 loader),基类 support:321-323)与 INT8 子类都据此判定;但同一继承链上的 LoadQuantPerChannelFp8Weight.support:729-738)仍直接写 isinstance(quant_config, Fp8PerChannelCompressedQuantConfig),未使用该类属性。新增第三种 per-channel 配置时,这一处会被漏改,且漏改的表现是 loader 冲突(weight_module.py:80 报错)或静默不认领。
  • [6.1] Software Engineering — ISP:调用方不依赖无关大接口 → issue moe_strategy_choices 槽位以整个 parser 为上下文,把 argparse 私有结构固化为跨仓契约
    槽位语义只需往 --moe_strategychoices 追加一个值,但 context 传的是整个 top-level parser。docs/backend/backend_registration.md:107-115server_args_test.py:28-32 都必须遍历 argparse 私有属性 parser._actions、按 "--moe_strategy" 字面量匹配再改 choices;两处写法还不一致(文档重建 list 且做幂等去重、找不到时 raise RuntimeError,测试用裸 next() + 就地 .choices.append(...),参数改名会抛裸 StopIteration)。hook 拿到 parser 后可改写任意参数,作用域远超声明语义;--moe_strategy 改用子解析器或 choices 改为 tuple/Enum 会让所有外部 hook 同时静默失效。
  • [6.1] Software Engineering — KISS/YAGNI:无投机性抽象 → issue setup_args 中的入口预加载与 run_backend_registrations 内部自加载重复
    run_backend_registrations()backend_registry.py:82)在取锁之前已无条件调用 ensure_backend_entrypoint_loaded();全仓搜索确认 setup_args 路径上唯一的槽位消费者是 moe_group_args.py:201,它在紧随其后的 init_all_group_argsserver_args.py:544)内被调用,没有更早的参数组消费槽位。因此 :534 这次显式调用在当前代码下不产生额外效果,注释(:532-533)所称的顺序保证由被调方自身提供。副作用是新用例完全建立在这个冗余调用之上(通过 patch 它注入 hook),使「删除冗余调用」这一无害重构会直接打破测试。
  • [6.1] Software Engineering — LSP:子类/重写保持基类契约 → issue LoadQuantPerChannelFp8Weight 绕过了新引入的 supported_quant_config_types 单一来源
    本 PR 引入 supported_quant_config_types 作为「本 loader 认领哪些配置」的单一来源(:309-315 注释明确说明它保证 WeightModule.create 只匹配一个 loader),基类 support:321-323)与 INT8 子类都据此判定;但同一继承链上的 LoadQuantPerChannelFp8Weight.support:729-738)仍直接写 isinstance(quant_config, Fp8PerChannelCompressedQuantConfig),未使用该类属性。新增第三种 per-channel 配置时,这一处会被漏改,且漏改的表现是 loader 冲突(weight_module.py:80 报错)或静默不认领。
  • [6.1] Software Engineering — OCP:本地扩展点优先于修改中心逻辑 → issue 量化方案以硬编码字符串元组在中心函数集中分派
    calc_low_latency_max_token_per_rank 通过 quant_config.get_method() in (...) 的五元素字符串元组(:230-236)判断 per-act-token,本次为 W8A8 追加第五个字符串(与 quant_config.py:945 的返回值一致)。每新增一个 per-token 方案都必须回到这个中心函数改一行,且字符串与 get_method() 的对应关系没有类型层面约束——拼错只会静默落到错误分桶或 raise ValueError("Unsupported quantization config"),而 token bucket 直接决定 DeepEP low-latency 缓冲尺寸。
  • [6.1] Software Engineering — SRP:模块/类职责单一 → issue 工厂「缺少计算后端」守卫用空注册表自证,未验证真实注册表不会误认领
    BackendAvailabilityGuardTest:130-180)测的是 LinearFactoryStrategyRegistrymodels_py/modules/factory)的行为,却挂在 model_loader 的测试目标下,改动 linear/moe 工厂的人不会想到运行它。手法是直写私有类属性 LinearFactory._strategies = []:152:162)与新建空 StrategyRegistry():173:176),因此断言的只是「候选为空时的错误文案」,与生产环境中由 fused_moe/__init__.py:120linear/__init__.py:31 填充的真实注册表对 W8A8 的判定无关——即使将来某个 CUDA 策略错误声称能处理 W8A8,这两条用例仍会通过。
  • [6.1] Tests — 分布式/跨平台变更有对应覆盖 → issue W8A8 新增的 act_qscheme 与 worker precision 映射零测试覆盖
    全仓搜索 W8A8INT8PTPC / isW8a8Int8PTPC 的消费者只有 Executor.h:64-65act_qscheme = QScheme::Qint8PerToken)、LocalRpcServer.cc:433-434(对外 precision 字段)、ModelConfig.cc:75-76 与 pybind 绑定;唯一测试 QuantAlgoBindingTesttest_compressed_w8a8_int8_per_channel.py:514-524)只断言枚举、bits 与 groupwise,未覆盖 act_qscheme 选择与 precision 字符串。若 Executor.h 的 else-if 被插到 isFp8PTPC() 之前的错误位置或误写为 Qfp8PerToken,激活量化方案与权重 INT8 不匹配,只会表现为数值错误;precision 是对外 gRPC 字段取值域,改错同样无 CI 拦截。
  • [6.1] Tests — 新逻辑有聚焦单测 + 相关集成/smoke 测试 → issue backend_registry 生命周期异常断言未校验消息,且 noop 用例无任何断言
    test_repeatable_slot_rejects_late_hooks:65-69)、test_slot_lifecycle_cannot_change_after_start:71-75)与 test_registering_after_drain_raises:98-104)都只用 assertRaises(RuntimeError),不校验消息。backend_registry.py 里三条 RuntimeError 文案各不相同(:66-69 迟到登记、:92-94 重入、:98-100 生命周期变更),任一分支被误改成另一分支的抛出点,三条用例仍全绿。test_draining_slot_without_hooks_is_noop:141-142)只调用一次函数、无任何断言,不区分「正常 no-op」与「静默吞掉后续异常」。
  • [6.1] Tests — 边界 case 覆盖(空、单元素、最大值) → issue GenerateStream 新用例只覆盖「缺失则保留」,未覆盖「提供则刷新」
    本次把 GenerateStream.cc:1035 的无条件赋值改为 if (update_info.draft_token_gpu.defined()),引入两个分支。新增用例 mtpUpdateKeepsLastGpuProposalWhenNextProposalIsMissing:117-137)只传 undefined 张量,断言旧值 9 被保留。全仓搜索 propose_tokens_gpu 后确认:没有任何测试通过 specUpdate 断言「传入 defined 张量时会刷新为新值」——MtpBatchStreamProcessorTest.cc 各处都直接对 state/sp_output_buffer 赋值,绕过 specUpdate。若该判断被误写为恒假(propose_tokens_gpu 永不刷新,每步都吃上一轮 proposal),全部测试仍会通过,而这正是 MTP gather 读到过期 GPU proposal 的回归形态。

RTP-LLM Checklist

  • [I] 代码质量 — 同一功能用统一工具函数 → issue LoadQuantPerChannelFp8Weight 绕过了新引入的 supported_quant_config_types 单一来源
    本 PR 引入 supported_quant_config_types 作为「本 loader 认领哪些配置」的单一来源(:309-315 注释明确说明它保证 WeightModule.create 只匹配一个 loader),基类 support:321-323)与 INT8 子类都据此判定;但同一继承链上的 LoadQuantPerChannelFp8Weight.support:729-738)仍直接写 isinstance(quant_config, Fp8PerChannelCompressedQuantConfig),未使用该类属性。新增第三种 per-channel 配置时,这一处会被漏改,且漏改的表现是 loader 冲突(weight_module.py:80 报错)或静默不认领。

Python Static-First Checklist

  • [P.A] 静态结构与类型纪律 — 公开 API 禁止 **kwargs 透传 → issue 槽位名与上下文键为自由字符串 + kwargs 透传,测试用的槽位名与生产不一致
    register_backend_hook(slot: str, ...)run_backend_registrations(slot, **context) 的槽位名和上下文键都是自由字符串,无 Enum/Literal 约束,也无「未知槽位」校验:外部后端把 "fused_moe" 拼成 "moe" 时不会报错,只会静默不执行(正是模块 docstring :28-31 声称要避免的失败形态)。测试恰好演示了这一点::36-39"moe":66-75"parser",而生产槽位是 "fused_moe"fused_moe/__init__.py:120)与 "moe_strategy_choices"moe_group_args.py:201)。这两个真实槽位名及其 context 键在测试中没有约束点,改名不会被 CI 捕获。
  • [P.A] 静态结构与类型纪律 — 字符串分发用 Enum/Literal → issue 量化方案以硬编码字符串元组在中心函数集中分派
    calc_low_latency_max_token_per_rank 通过 quant_config.get_method() in (...) 的五元素字符串元组(:230-236)判断 per-act-token,本次为 W8A8 追加第五个字符串(与 quant_config.py:945 的返回值一致)。每新增一个 per-token 方案都必须回到这个中心函数改一行,且字符串与 get_method() 的对应关系没有类型层面约束——拼错只会静默落到错误分桶或 raise ValueError("Unsupported quantization config"),而 token bucket 直接决定 DeepEP low-latency 缓冲尺寸。
  • [P.G] 测试规范 — mock/fake/stub 不得替代本次声称覆盖的生产边界 → issue no-quant 策略新增校验与 executor 既有条件重复,且新测试绕过 can_handle
    no_quant.py:58(CudaNoQuantCppStrategy)与 :86(CudaNoQuantDpNormalStrategy)新增的 checker.check(quant_method is None)TritonFusedMoeExecutor.check_conditionstriton_fused_executor.py:36-38not resolver.has_quantization(config))等价,而基类 can_handlestrategy_base.py:71-77)本来就会对 strategy/router/executor 逐个调用 check_conditions,故新增校验在生产路径上是冗余防御。新测试 TestCudaNoQuantFallbackStrategiestest_cuda_strategies.py:159-208)直接调 strategy.check_conditions(checker, config) 而不经 can_handle(),因此只能
  • [P.G] 测试规范 — pytest.raises 带 match 参数 → issue backend_registry 生命周期异常断言未校验消息,且 noop 用例无任何断言
    test_repeatable_slot_rejects_late_hooks:65-69)、test_slot_lifecycle_cannot_change_after_start:71-75)与 test_registering_after_drain_raises:98-104)都只用 assertRaises(RuntimeError),不校验消息。backend_registry.py 里三条 RuntimeError 文案各不相同(:66-69 迟到登记、:92-94 重入、:98-100 生命周期变更),任一分支被误改成另一分支的抛出点,三条用例仍全绿。test_draining_slot_without_hooks_is_noop:141-142)只调用一次函数、无任何断言,不区分「正常 no-op」与「静默吞掉后续异常」。

Strengths

  • QuantMethod 新值在枚举尾部追加(QuantInfo.h:19 W8A8INT8PTPC = 12),既有编号不位移;六个消费点 QuantInfo.cc:49QuantInfo.h:54Executor.h:64ModelConfig.cc:75LocalRpcServer.cc:433ConfigInit.cc:1163/1180.pyi:1419/1432 全部同步。
  • QuantAlgoBindingTesttest_compressed_w8a8_int8_per_channel.py:514-524)用真实 rtp_llm.ops.QuantAlgo 而非 mock 钉住 Python → C++ 字符串契约:w8a8_int8_per_channelW8A8INT8PTPC、bits=8、group_size=0、非 groupwise,与 QuantInfo.cc:49-52 完全一致,是本 PR 质量最高的一组测试。
  • PerChannelFp8Weight 通过 weight_dtype / apply_fp8_device_conversion / supported_quant_config_types 三个类属性参数化(:292-315),让 INT8 子类零复制复用全部 tensor 映射与 TP/EP 切分规则。apply_fp8_device_conversion=False 是必需门控而非装饰:ROCm 的 convert_fp8_weight_paramsassert weight.dtype == torch.float8_e4m3fn,INT8 走该路径会直接断言失败。
  • supported_quant_config_types 让基类与 INT8 子类的 isinstance 判定互斥(子类 super().support()cls 仍是子类),规避了 weight_module.py:80len(valid_classes) > 1 报错,loader 唯一性成立。
  • 未识别的 compressed-tensors 组合改为抛出携带 weights / input_activations 内容的 ValueErrorquant_config.py:315-323)。原路径会落到通用尾部实例化抽象基类并抛难以定位的 TypeError,这是严格的错误语义改善。
  • CompressedW8A8Int8PerChannelQuantConfig.__init__ 全部具名参数、无 **kwargs 兜底,_from_configallowed_keys 白名单显式拒绝未知键(quant_config.py:966-979);get_supported_kv_cache_dtypes():958-964)主动收窄为 fp16/bf16 并在注释中说明理由,把未验证的 --fp8_kv_cache 组合挡在启动期。
  • _pick_config_group 的兼容性设计克制:group_0 存在时行为与改动前逐字节一致(:32-44),多 group 时补 warning,回退只覆盖原先根本无法读取的「单一自定义命名 group」。
  • backend_registry hook 严格在锁外执行(:127-133),backend_registry_test.py:131-139 直接断言 _lock._is_owned() == False,避免 hook 内 import 与注册锁形成锁序反转;模块选址在 rtp_llm.utils 而非 factory 旁(docstring :22-26),从根上规避参数解析路径 eager import 通信库。上一轮评审提出的锁序反转与「先标记 started」两点已修复。
  • hook 异常刻意不吞,模块 docstring(:28-31)明确说明「静默丢弃注册会表现为数值错误而不是启动失败」,错误语义显式。
  • CUDA graph host mirror 清零(cuda_graph_runner.cc:390-398)补齐了 device 侧 fused fill(:331-350,全表 0..numel())的对称性,注释准确交代触发条件、后果(padding 行残留 block ID 把 KV 写进活跃请求的块)与块 0 为何是安全目标;清零位于 D2D/H2H 回填之前,顺序正确,且该 mirror 由 :939-940 恒分配为 pinned CPU,不会崩。
  • DecodeRpcServer.cc:338 的语句顺序正确:先向 sp_output_buffer 赋值再于 :359 对局部变量 std::move,避免 use-after-move;取值 tokens[0][1] 与 reader 的 legacy CPU 回退 columnAsFlat(tokens, 1)MtpBatchStreamProcessor.cc:252)严格一致。
  • _prepare_dispatch_inputdeepep_normal_router.py:228-249)让树外后端无需 fork router 即可携带量化元数据,且 FP8 输出契约校验用 raise ValueError:156-160)而非 assert,在 -O 下不会被剥离。
  • strategy_registry.py:99-105get_attributes() 由每候选多次调用收敛为一次并复用,消除重复 lazy import 与日志噪声;排序键 calculate_priority() 与旧 priority 语义等价,属零风险重构。
  • PerChannelFp8PostprocessTest._runtest_compressed_w8a8_int8_per_channel.py:450-463)刻意用非方阵 (2,3) 输入让 _postprocess 的 reshape 可观测,并用与输入无法混淆的哨兵张量而非类属性验证 FP8 转换是否被调用,是正确的行为级断言写法。

auto accept_tokens = torch::zeros({1, static_cast<int64_t>(propose_step + 1)}, cuda_i32);
accept_tokens[0][0] = sp_output_buffer->tokens[0][0];
propose_tokens_gpu[0] = sp_output_buffer->tokens[0][1];
sp_output_buffer->propose_tokens_gpu = propose_tokens_gpu;

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] propose_tokens_gpu 两个生产者的形状与取值语义分叉

:333-338sp_output_buffer->propose_tokens_gpu 写入 {1} 一维 CUDA 张量,只含 tokens[0][1](首个 draft)。同一字段的另一生产者 StreamCacheResource.cc:223sp_output_buffer->tokens.to(cuda_i32),即完整 {1,N}(index 0 是 target token)。唯一消费者 MtpBatchStreamProcessor.cc:250 / :372 统一用 lastColumnAsFlat:147-149 取最后一列):对 {1} 得首个 draft,对 {1,N} 得最后一个。propose_step > 1 时两条握手通道经同一 reader 给出不同 token。

建议:SpeculativeExecutorStreamOutput::propose_tokens_gpu 声明处写明唯一 shape 契约(「仅下一步单个 draft」还是「全部 propose_step drafts」),把构造逻辑抽成两条通道共用的工具函数;若确为一步语义,同步修正 StreamCacheResource.cc 的实现与其 :234-235 注释。兜底可在 collectLegacyProposeSlices:360-375)对各 slice 的 numel() 一致性加 RTP_LLM_CHECK。请补一条 propose_step=1 与 >1 的 gRPC 握手回归用例,断言 GPU 路径与 host 回落取到同一 draft token。

sp_output_buffer->propose_tokens_gpu = propose_tokens_gpu;

auto next_seq_len = torch::ones({1}, cuda_i32);
next_seq_len[0] = generate_stream->seqLength();

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/cpp/model_rpc/DecodeRpcServer.cc:353(不在 diff 展示范围内,就近挂载)

[P2] 握手快照发布无门控与回滚开关,CPU 回落路径被本 PR 变为不可达

紧邻的 setMtpAsyncDeviceState:353-365)受 RTP_LLM_STREAM_ASYNC / RTP_LLM_MTP_ASYNC_DEVICE_STATE 门控,:344-348 注释明写「未启用对应流水线时发布静态快照会留下永久陈旧状态」;而 :338 的发布无任何开关。GenerateStream.cc 改动前注释是「PD-disaggregate path leaves it undefined and readers fall back to the CPU tokens tensor」,改后(:1034)仅在 draft_token_gpu.defined() 时刷新,而 MtpBatchStreamProcessor.cc:588 只在 on_gpu && next_batch_size > 0 时填该字段、:596-603 却仍刷新 CPU draft_token,此时 pickOneStepDraftToken:249-250)优先读陈旧 GPU 镜像。

建议::338 的发布加上与 MtpAsyncDeviceState 一致的环境变量门控(或独立开关),保留出问题时回退到 CPU tokens 的运维手段;或在 specUpdate 中当 draft_token_gpu 缺失而 draft_token >= 0 时主动清空 propose_tokens_gpu,让 reader 显式回落。请补一条「GPU 镜像未刷新而 CPU token 已前进」的回归用例。

case QuantMethod::W4A8INT4PTPC:
status_info.precision = "W4A8INT4PTPC";
break;
case QuantMethod::W8A8INT8PTPC:

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] QuantMethod→字符串映射在两处 switch 重复维护且各自漏枚举值

LocalRpcServer.cc:405-442ModelConfig.cc:53-82 是两份各自手工维护的 QuantMethod → string switch,本 PR 在两处各复制一个 W8A8INT8PTPC case。漂移已发生:ModelConfig.ccQuarkMXFP4(11);LocalRpcServer.cc 同时缺 ModelOptFP4(10) 与 QuarkMXFP4(11)。两值均可由 QuantInfo.cc:53,57mxfp4-quark / modelopt_fp4)到达。落 default 后 ModelConfig::to_string() 打印 UNKNOWN(11)LocalRpcServer.cc:440 每次 worker status 轮询打一条 RTP_LLM_LOG_ERROR 并把对外 RPC 字段 precision 置为 "UNKNOWN"

建议: 把枚举到字符串的映射收敛为 QuantInfo.h/cc 中唯一一个 quantMethodToString()ModelConfig.cc:53 已有同名静态函数可直接上提),两处均改为调用它(precision 只需对 QuantMethod::None 保留 "FP16" 展示层特例);同时补齐 ModelOptFP4QuarkMXFP4,避免这两类部署的高频轮询接口持续输出 ERROR 并对外上报 UNKNOWN。

and _ckpt_base_matches_quant_exclude(
base_name, quant_config.exclude_modules
)
and not _ckpt_base_matches_regex_exclude(

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] W8A8 部分排除守卫对 re: 单层正则留有静默全层降级缺口

_ckpt_base_matches_regex_excludeper_channel_fp8_quant_weight.py:114-127)把 {i} 一律替换为 "0",只对第 0 层求值,无法区分「整模板正则」与「单层正则」。若 ignorere:model\.layers\.0\.self_attn\.gate,第三个条件为 True,守卫不抛错;随后 super().support():329-335)因 _ckpt_base_matches_quant_exclude 命中返回 False,weight_module.py:78-79 原样返回未量化 AtomicWeight,所有层把 INT8 字节按 compute dtype 读取,无 scale、无报错。等价的具体路径 model.layers.0.self_attn.gate 反而硬失败(test_..._fails:418-423),两条语义相互矛盾。测试只覆盖 re:^model\.layers\.\d+\....$ 整模板正则...

建议:re: 与具体路径走同一层感知判定:至少代入两个不同层号(如 01)分别求值,全部命中才认定为整模板排除,否则按部分排除 fail-fast;若能拿到 num_layers,在真实层区间上展开模板统计命中层数,全覆盖返回 False、部分覆盖 raise。补「regex 仅命中部分层」与「regex 命中全部层」两条用例。

base_name = ckpt_w.name.rsplit(".", 1)[0]
if (
base_name not in quant_config.exclude_modules
and _ckpt_base_matches_quant_exclude(

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] 守卫与 exclude 匹配对 MoE {expert_id} 模板完全失效

MoE 权重的 ckpt 模板同时含 {i}{expert_id}(如 model.layers.{i}.mlp.experts.{expert_id}.down_proj.weight,见 compressed_w4a8_int4_per_channel_weight.py:116ffn_weight.py:350)。_exclude_pattern_forper_channel_fp8_quant_weight.py:57-64)只把 {i} 展开为 \d+_ckpt_base_matches_* 也只 replace("{i}", "0"){expert_id} 原样留在候选串里,因此任何具体 expert 路径与任何正则都匹配不上:守卫第二个条件恒为 False → 永不抛错,基类 exclude 检查同样恒为 False → 该权重仍按量化加载。被 ignore 的 expert 缺少 weight_scale,失败推迟到权重加载阶段变成与根因无关的缺张量错误。

建议: 把模板→正则的构造统一为「所有 {...} 占位符都展开为通配」(复用 _exclude_pattern_for 并支持任意占位符名),使 {expert_id} 模板与 MoE 的 ignore 条目能正常比对;随后守卫对部分排除按 fail-fast、对完整排除走非量化回退。请补一条 MoE {expert_id} 模板被 ignore 覆盖(部分与全部两种)的用例。

@@ -232,6 +232,7 @@ def calc_low_latency_max_token_per_rank(
"FP8_DYNAMIC_PER_TENSOR",

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/models_py/distributed/deepep_wrapper.py:230(不在 diff 展示范围内,就近挂载)

[P3] 量化方案以硬编码字符串元组在中心函数集中分派

calc_low_latency_max_token_per_rank 通过 quant_config.get_method() in (...) 的五元素字符串元组(:230-236)判断 per-act-token,本次为 W8A8 追加第五个字符串(与 quant_config.py:945 的返回值一致)。每新增一个 per-token 方案都必须回到这个中心函数改一行,且字符串与 get_method() 的对应关系没有类型层面约束——拼错只会静默落到错误分桶或 raise ValueError("Unsupported quantization config"),而 token bucket 直接决定 DeepEP low-latency 缓冲尺寸。

建议:QuantizationConfig 上增加 is_per_act_token()(或 low_latency_token_buckets())由各配置类自行声明,该函数改为查询该能力;若暂不重构,至少把字符串集合提为模块级常量并注明与 get_method() 的同步要求。

Checklist: [6.1] OCP:本地扩展点优先于修改中心逻辑;[P.A] 字符串分发用 Enum/Literal


from rtp_llm.device.device_type import is_hip
from rtp_llm.models_py.utils.arch import is_cuda
from rtp_llm.models_py.utils.arch import (

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] 设备判定函数经 models_py.utils.arch 隐式 re-export

改动前 is_hip 直接来自 rtp_llm.device.device_type,改动后 get_device_type/is_cuda/is_hip/is_ppu 全部改从 rtp_llm.models_py.utils.arch 导入(:238-243)。arch.py:6-12 只是 from rtp_llm.device.device_type import ... 的透传,其中 get_device_type/is_hip/is_ppu/DeviceType 在该模块内均未被使用,也未声明 __all__,属隐式 re-export:任何 unused-import 清理(autoflake / ruff F401)都会让本文件运行时 ImportError,mypy 严格模式同样报错;同一批符号出现两个可用导入路径,后续容易分叉。

建议: 直接从 rtp_llm.device.device_type 导入这四个符号;若确实需要 arch.py 作为设备门面,请在其中声明 __all__ 并注明门面职责,避免被当作未使用导入清理掉。

Checklist: [6.1] 依赖方向:无循环依赖/跨层惊喜;[6.1] 分层边界:新概念在正确层级,不泄漏内部

)
processed_res[self.scale.name] = scale_weight
processed_res[self.kernel.name] = kernel_weight

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/model_loader/per_channel_fp8_quant_weight.py:733(不在 diff 展示范围内,就近挂载)

[P3] LoadQuantPerChannelFp8Weight 绕过了新引入的 supported_quant_config_types 单一来源

本 PR 引入 supported_quant_config_types 作为「本 loader 认领哪些配置」的单一来源(:309-315 注释明确说明它保证 WeightModule.create 只匹配一个 loader),基类 support:321-323)与 INT8 子类都据此判定;但同一继承链上的 LoadQuantPerChannelFp8Weight.support:729-738)仍直接写 isinstance(quant_config, Fp8PerChannelCompressedQuantConfig),未使用该类属性。新增第三种 per-channel 配置时,这一处会被漏改,且漏改的表现是 loader 冲突(weight_module.py:80 报错)或静默不认领。

建议: 让该子类也通过 cls.supported_quant_config_types 判定(必要时在子类上覆写该属性),使「认领哪些配置」只有一个声明位置。

Checklist: [6.1] DRY:重复非平凡逻辑被抽取或显式复用;[6.1] LSP:子类/重写保持基类契约;[I] 同一功能用统一工具函数

auto stream2 = builder.createDecoderStream({1, 2, 3, 4, 5}, {1, 2, 3});
}

TEST_F(GenerateStreamTest, mtpUpdateKeepsLastGpuProposalWhenNextProposalIsMissing) {

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] GenerateStream 新用例只覆盖「缺失则保留」,未覆盖「提供则刷新」

本次把 GenerateStream.cc:1035 的无条件赋值改为 if (update_info.draft_token_gpu.defined()),引入两个分支。新增用例 mtpUpdateKeepsLastGpuProposalWhenNextProposalIsMissing:117-137)只传 undefined 张量,断言旧值 9 被保留。全仓搜索 propose_tokens_gpu 后确认:没有任何测试通过 specUpdate 断言「传入 defined 张量时会刷新为新值」——MtpBatchStreamProcessorTest.cc 各处都直接对 state/sp_output_buffer 赋值,绕过 specUpdate。若该判断被误写为恒假(propose_tokens_gpu 永不刷新,每步都吃上一轮 proposal),全部测试仍会通过,而这正是 MTP gather 读到过期 GPU proposal 的回归形态。

建议: 补一条对称用例:构造 draft_token_gpu = torch::tensor({11}, torch::kInt32).to(torch::kCUDA),调用 specUpdate 后断言 propose_tokens_gpu.cpu().item<int32_t>() == 11(从 9 刷新为 11);并补一条 commit-only(propose_token_ 清空)路径的边界断言。

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

self.assertEqual(seen, ["first", "second"])

def test_repeatable_slot_rejects_late_hooks(self):
run_backend_registrations("parser", repeatable=True, parser="first")

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] backend_registry 生命周期异常断言未校验消息,且 noop 用例无任何断言

test_repeatable_slot_rejects_late_hooks:65-69)、test_slot_lifecycle_cannot_change_after_start:71-75)与 test_registering_after_drain_raises:98-104)都只用 assertRaises(RuntimeError),不校验消息。backend_registry.py 里三条 RuntimeError 文案各不相同(:66-69 迟到登记、:92-94 重入、:98-100 生命周期变更),任一分支被误改成另一分支的抛出点,三条用例仍全绿。test_draining_slot_without_hooks_is_noop:141-142)只调用一次函数、无任何断言,不区分「正常 no-op」与「静默吞掉后续异常」。

建议: 三条异常用例改用 assertRaisesRegex 钉住各自消息关键片段(如 "already initialised" / "re-entrant" / "lifecycle changed");noop 用例补断言(例如随后 register_backend_hook 应因槽位已开始而 raise,以证明状态确实被记录)。

Checklist: [6.1] 新逻辑有聚焦单测 + 相关集成/smoke 测试;[P.G] pytest.raises 带 match 参数

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