feat: support full prefill CUDA graphDevelop/full prefill cuda graph - #1360
feat: support full prefill CUDA graphDevelop/full prefill cuda graph#1360siluzhou wants to merge 1 commit into
Conversation
siluzhou
commented
Aug 31, 2026
- Add full-prefill CUDA Graph support for eligible generative models.
LLLLKKKK
left a comment
There was a problem hiding this comment.
AI Code Review - PR #1360
Status: BLOCKING
Summary: P0/1 · P1/2 · P2/12 · P3/6
Reviewed: commit e30c4c00a25e · 2026-08-31 18:47 UTC+8
Blocking Issues
P0
- FallbackTick 与 tickFallback 无任何定义,cuda_graph_impl 必然编译失败 @
rtp_llm/cpp/cuda_graph/cuda_graph_runner.cc:905- 建议:补上限流助手定义(如在匿名 namespace 或
cuda_graph_utils.h定义struct FallbackTick { uint64_t count; bool should_log; };与inline FallbackTick tickFallback(std::atomic<uint64_t>&)),并把canReplaySelectedGraph(:1000-1003)中combo_position_fallback_count_的「首次 + 2 的幂次」重复限流改为复用同一工具,避免两套实现。合入前请实际执行一次bazel build //rtp_llm/cpp/cuda_graph:cuda_graph_impl确认,不要仅依赖增量缓存;当前状态下任何 CI 结论都不可信。
- 建议:补上限流助手定义(如在匿名 namespace 或
P1
- 生产 forward 未透传 input_embeddings,新增的请求级 embedding 覆盖与配套护栏双双失效 @
rtp_llm/cpp/models/PyWrappedModel.cc:928- 建议:抽一个统一填充函数(如
applyRequestInputEmbeddings(py_model_inputs, inputs))供 :812 / :844 / :928 三处调用,消除漏点(该重复本身也已构成 DRY 问题)。若当前有意限定「generative prefill 暂不支持 embedding 覆盖」,则应在静态门禁或canRun处显式拒绝并记录reason=,而不是让 forward 悄悄按 0 段执行。同时补一条经PyWrappedModel::forward(而非测试 runner)的用例,断言「合法 embedding → 输出被覆盖」「非法 locs → 回退 eager」「非本角色 + embedding → 不走图」。
- 建议:抽一个统一填充函数(如
- 关停早退绕过 TP 集合通信不变量,多卡下可能把干净关停换成 NCCL 悬挂 @
rtp_llm/cpp/normal_engine/NormalEngine.cc:686- 建议:让「是否跳过本轮」的判定在 rank 间对齐:把停止意图纳入既有
tpSyncModelInputs负载(复用skip_run语义),使所有 rank 在同一轮一致跳过;或先加parallelism_config.tp_size == 1条件(fake stream 场景另需dp_size == 1)把止损范围限定在单卡,并在 :693-695 注释与shouldSkipFakeStreamForStop日志中写明多卡不适用。无论哪种方案,请补一条 tp_size>1 关停用例固化行为,日志带上被跳过的轮次与 phase 以便事后定位分歧点。
- 建议:让「是否跳过本轮」的判定在 rank 间对齐:把停止意图纳入既有
Non-blocking Suggestions
P2
- dense 模型缺并行度门禁,且 prefill capture 前未做 rank 同步 @
rtp_llm/cpp/models/FullPrefillCudaGraphEligibility.h:14- 建议:在
valid_static_config中补齐params.parallelism_config.world_size == 1(或 dp/pp/ep 各自为 1),使 dense 与 MoE 的并行度门禁一致。若确定要支持 world_size>1,则在prefill_graph_runner_->initCapture()前同样调用syncCudaGraphCaptureRanks(该函数在world_size <= 1时直接 return,补上几乎无成本),并说明第二次 capture 与 decode capture 的相对顺序在所有 rank 上一致。
- 建议:在
- 新增 4 个旋钮在参数层无取值域校验,非法值静默降级且原因误报为模型不支持 @
rtp_llm/server/server_args/hw_kernel_group_args.py:58- 建议:在 argparse 层 fail-fast,把「配置非法」与「模型不支持」分开:给
_parse_prefill_capture_config增加config_name/max_buckets参数,超过 64 桶抛argparse.ArgumentTypeError并回显实际桶数与上限;对max_requests校验> 0、对max_padding_ratio校验[0.0, 1.0]。若保留运行期兜底,请把 reason 细化为invalid_capture_buckets/invalid_max_requests/invalid_padding_ratio并打印实际值。附带修正:_parse_prefill_capture_config的错误信息硬编码prefill_capture_config(:279、:347),解析FULL_PREFILL_CAPTURE_CONFIG出错时会指向错误的旋钮名。
- 建议:在 argparse 层 fail-fast,把「配置非法」与「模型不支持」分开:给
- scratch 分组与 kv_cache_group_tags 靠下标顺序耦合,仅校验数量未校验身份 @
rtp_llm/cpp/cuda_graph/cuda_graph_runner.cc:173- 建议:把
prefill_scratch_kernel_block_ids改为tag -> block_ids映射(或在GraphParams中同时携带 group tag),initPrefillScratchTensors()按 tag 建 map 并在缺 tag 时 fail-fast;若坚持下标形式,至少断言 group 索引与 tag 快照顺序一致,并在注释中写明该不变量由哪个 topology 接口保证。
- 建议:把
- prepare_fmha_impl 第三位置参数契约未同步到既有 Python 覆写,且选择模式为跨语言裸字符串 @
rtp_llm/cpp/cuda_graph/cuda_graph_runner.cc:166- 建议:统一补齐所有
prepare_fmha_impl覆写的cuda_graph_selection_mode形参(即使忽略也应显式接收),并在基类注释中写明「覆写者必须接受并忽略该参数」;C++ 侧改用关键字实参以降低位置耦合,同时把"full_prefill_no_prefix_graph"字面量提升为经 pybind 导出的共享常量或 Literal 枚举,使 C++/Python 只有一个定义源,而非靠人工对齐。
- 建议:统一补齐所有
- replay 元数据构造器的容量契约隐式、末尾判据恒真,且与所在头文件职责不符 @
rtp_llm/cpp/cuda_graph/combo_position_ids_validation.h:10- 建议:把容量作为显式参数传入(或改用
std::span/c10::ArrayRef并在函数内断言input_lengths.size() >= max_request_count + 1、cu_seqlens.size() >= max_request_count + 2、padding_offset.size() >= token_capacity),并为「容量不足」补一条EXPECT_FALSE用例;同时把该 helper 迁到语义相符的头文件(如新建full_prefill_replay_metadata.h,它与文件名表达的 position-ids 校验职责无关),并移除恒真返回判据或改为RTP_LLM_CHECK。
- 建议:把容量作为显式参数传入(或改用
- capture 失败回滚规则在工厂与 PyWrappedModel 中两份实现,且 catch 范围已开始漂移 @
rtp_llm/cpp/models/PyWrappedModel.h:465- 建议:改为复用
createForPrefill,把「可降级 eager vs 必须 fail-fast」的判断放在工厂之外的 catch 里,使 dirty-capture 的处理只有一份实现;若必须保留本地实现,至少把捕获范围对齐为catch (...)。
- 建议:改为复用
- runner 层 sentinel 组装与 fallback 闸门缺集成覆盖,harness 把 padding 闸门硬编码为 1.0 @
rtp_llm/cpp/cuda_graph/tests/cuda_graph_test_runner.cc:65- 建议:给
init_generative_prefill增加max_padding_ratio形参(默认保持 1.0 以兼容现有用例),并新增:max_requests>=2且总 token 小于 bucket 的 replay 用例,断言 graph 输出与同输入 eager 一致、padded 尾部为full_prefill_pad_token_id、空批槽 block table 行为 0、sentinel 行走 scratch 块且真实请求 block 未被污染(scratch block ID 取非 0 值以便与清零区分);再补 ratio 超限、tokens 超最大 bucket、请求数超full_prefill_max_requests、prefix_lengths非零各一条canRun == False的用例。
- 建议:给
- HWKernelConfig pickle 测试打偏,唯一真实发布过的 14 元组布局零覆盖 @
rtp_llm/cpp/pybind/config_pickle_test.py:115- 建议:补三点:一是新增 14 元组用例,断言
enable_native_cuda_graph/num_native_cuda_graph/prefill_capture_seq_lens/decode_capture_batch_sizes/disable_dpc_random/rocm_disable_custom_ag六个尾部字段从t[8..13]正确取到,且 4 个新字段回落 C++ 默认值;二是把 18 元组 round-trip 改为先把全部字段设为区别于默认的取值再逐一断言(padding_ratio用0.375以暴露 int/double 位置写反);三是按 :105 范式补 15/16 应抛Invalid state的用例。建议用subTest表驱动覆盖三个受支持尺寸。
- 建议:补三点:一是新增 14 元组用例,断言
- 新增 3 个标量 server arg 缺 CLI/env 绑定回归测试,而绑定失败仅 warning @
rtp_llm/server/server_args/test/server_args_test.py:193- 建议:按 grammar 组范式补两类用例:一是默认值守卫,断言无输入时 4 个新字段分别为
False / 8 / 0.25 / 19 元稀疏列表(同时覆盖 argparse 字符串默认经type转换这条隐式依赖);二是绑定用例,通过 env 与sys.argv两条路径分别传入--enable_full_prefill_cuda_graph 1 --full_prefill_cuda_graph_max_requests 4 --full_prefill_cuda_graph_max_padding_ratio 0.4,断言取值与类型(bool / int / float)正确。
- 建议:按 grammar 组范式补两类用例:一是默认值守卫,断言无输入时 4 个新字段分别为
- supportsFullPrefillCudaGraphMoe 的多数合取项没有负例 @
rtp_llm/cpp/models/test/ModelDataTest.cc:179- 建议:改为表驱动:以 happy-path config 为基线,每行一个「字段名 + 变异值」,在循环中逐一
EXPECT_FALSE,尤其补齐use_mori_ep、use_deepep_p2p_low_latency、hack_moe_expert、top_k > expert_num、extra_expert_num != 0,保证每个合取项都有唯一对应负例——这些条件正是阻止在 graph-unsafe collective 或分布式拓扑上捕获整图的闸门,后续增删条件时测试应同步暴露。
- 建议:改为表驱动:以 happy-path config 为基线,每行一个「字段名 + 变异值」,在循环中逐一
- 新增 MoE CUDA Graph py_test 缺少同包统一的 open_skip 标签与环境 skip 保护 @
rtp_llm/models_py/modules/factory/fused_moe/impl/cuda/executors/test/BUILD:31- 建议:与同包对齐改为
tags = ["open_skip", "H20"];若确有意让该用例在更多流水线中运行,请在 PR 描述中说明差异原因,并在测试内补has_deep_gemm()/ SM 版本的skipTest保护,使环境不满足时表现为 skip 而非 error。同时补一个 replay token 数小于 capture 容量、尾部为 padding token 的用例,断言 padding 行不影响真实 token 的 MoE 输出。
- 建议:与同包对齐改为
- 单个 PR 混装三条独立主线与无关格式化,其中关停重构未落入任何评审分片 @
rtp_llm/cpp/normal_engine/NormalEngine.cc:539- 建议:把引擎关停重构(含
Executor.h新接口、信号屏蔽、异常吞并)拆成独立 PR——它有自己的 tp_size>1 风险与测试需求,便于单独评审与 CI bisect;纯格式化改动另开 format-only 提交。若因排期必须合并提交,请在 PR description 中显式分节列出三条主线及各自动机、风险面与回滚方式,并点明哪些 hunk 只是 clang-format 副产物,便于 reviewer 跳过。
- 建议:把引擎关停重构(含
P3
- 17 元组 pickle 兼容分支属投机兼容,构成永久死路径 @
rtp_llm/cpp/pybind/ConfigInit.cc:785- 建议:收敛为 14 与 18 两种长度,删除 17 分支并把
_PreviousHWKernelConfig改为 14 元组用例,尺寸检查简化为t.size() != 14 && t.size() != 18、新字段读取用if (t.size() >= 18)。若确有长期跨版本需求,改为在 tuple 首位写显式 version 号或改用命名字段,避免依赖长度嗅探(后续再加字段时任意两个历史长度相撞就会静默错位映射);若确需保留 17,请注明它仅为分支内临时格式及清理时间点。
- 建议:收敛为 14 与 18 两种长度,删除 17 分支并把
- 19 元素默认桶列表在三处硬编码,其 25% padding 不变量无测试守护 @
rtp_llm/server/server_args/hw_kernel_group_args.py:62- 建议:以 C++ 结构体默认值为唯一来源:
--full_prefill_capture_config的default改为None,仅在显式配置时覆盖full_prefill_capture_seq_lens;测试断言改为读取HWKernelConfig().full_prefill_capture_seq_lens而非复制字面量。并追加一条按(bucket - prev - 1) / bucket <= max_padding_ratio校验默认桶序列的用例,把注释里的不变量固化成可执行断言。
- 建议:以 C++ 结构体默认值为唯一来源:
- 析构函数重复调用 py_model_.release(),实际释放顺序与注释意图不符 @
rtp_llm/cpp/models/PyWrappedModel.cc:244- 建议:删除其中一次
py_model_.release()与重复注释,并确认最终保留位置符合「runner 析构后再释放 Python 模型」的意图;建议把两个 runner 改为std::unique_ptr<GraphBase>,析构顺序由成员声明顺序保证,dirty-capture 分支再用显式release()表达「故意泄漏至进程退出」。
- 建议:删除其中一次
- 注释与 help 称 full prefill graph 独立于 decode graph,实际被 enable_cuda_graph 硬门控 @
rtp_llm/cpp/config/ConfigModules.h:262- 建议:把注释收窄为准确表述,例如「requires enable_cuda_graph=true; owns an independent capture bucket list and runner, separate from the decode graph」;并在
--enable_full_prefill_cuda_graph的 help 中补一句「需与--enable_cuda_graph同时开启,否则该开关无效」,让 CLI/env 使用者无需读 C++ 代码即可知道前置条件。
- 建议:把注释收窄为准确表述,例如「requires enable_cuda_graph=true; owns an independent capture bucket list and runner, separate from the decode graph」;并在
- 本角色下 4 个 device 缓冲的 fill 与 D2D 被随后的整表 H2D 覆盖,且 canRun 未校验这两个 device 张量 @
rtp_llm/cpp/cuda_graph/cuda_graph_runner.cc:449- 建议:对
isGenerativePrefillCudaGraph()直接跳过这四个缓冲的 fill 区间与 D2D 拷贝,仅保留 :658-665 的 host→device 整表覆盖;这同时也从根上消除上述断言面,使上游未填时不会从「优雅回退 eager」退化为引擎异常。若选择保留 fill 路径,则请在 canRun 内补cu_seqlens_device/cu_kv_seqlens_device的 defined/is_cuda/int32/contiguous/numel 检查并return fallback("metadata_mismatch")。
- 建议:对
- 生产代码与测试用 getattr 字面量访问 pybind 必然存在的字段,并硬编码 C++ 布局常量 @
rtp_llm/models_py/model_desc/module_base.py:132- 建议:生产代码若确实要兼容轻量 namespace 适配器,请显式定义一个 Protocol 或提供默认属性,而不是靠字面量 getattr;测试内直接访问
inputs.cuda_graph_input_embedding_runtime_meta。把16 + 64 * 24换成从 C++ 侧导出的尺寸常量,或至少提取为带来源注释的命名常量,使布局契约变更时失败信息可自解释。
- 建议:生产代码若确实要兼容轻量 namespace 适配器,请显式定义一个 Protocol 或提供默认属性,而不是靠字面量 getattr;测试内直接访问
Checklist Findings (20 fail / 58 total)
General Principles Checklist
- [6.1] Architecture — 兼容性:外部 HTTP/RPC API、持久数据、配置、环境迁移安全 → issue
注释与 help 称 full prefill graph 独立于 decode graph,实际被 enable_cuda_graph 硬门控
ConfigModules.h:261-263 注释写 "It is intentionally independent from decode CUDA graph and defaults to disabled",但实现对 decode 总开关是强依赖:PyWrappedModel.h:272-275在enable_full_prefill_cuda_graph_ && !enable_cuda_graph_时告警reason=feature_disabled ENABLE_CUDA_GRAPH=0并关闭,真正的初始化分支(:408)嵌套在 :290 的if (enable_cuda_graph_)内部;warmup 期缺kv_cache_layer_layout时 :276-289 会连带关闭它。hw_kernel_group_args.py:37的 help 也未提及该前置条件。考虑到已有带明确 reason 的 WARNING,运营影响有限,故定为 P3。 - [6.1] Architecture — 分层边界:新概念在正确层级,不泄漏内部 → issue
scratch 分组与 kv_cache_group_tags 靠下标顺序耦合,仅校验数量未校验身份
initPrefillScratchTensors()只校验prefill_scratch_kernel_block_ids_.size() == expected_groups(:178-181)与各组非空(:186-187),之后 capture 与 replay(:732-736)都按for (const auto& tag : kv_cache_group_tags_) install_scratch_row(..., group_id++)用下标顺序把 scratch 块绑定到 tag。而 scratch 列表由PyWrappedModel.cc:160-162按resource->kernelBlocks(0, group_id)的数值 group 序生成,kv_cache_group_tags_来自 topology 的 tag 快照。两者顺序一致是隐式假设、无任何断言;一旦 tag 快照顺序与 group 索引顺序解耦,padding token 的 KV 会写进另一个 cache group 的块,表现为难定位的数值污染而非快 - [6.1] Architecture — 可观测性:日志/指标/超时可操作、非噪声 → issue
注释与 help 称 full prefill graph 独立于 decode graph,实际被 enable_cuda_graph 硬门控
ConfigModules.h:261-263 注释写 "It is intentionally independent from decode CUDA graph and defaults to disabled",但实现对 decode 总开关是强依赖:PyWrappedModel.h:272-275在enable_full_prefill_cuda_graph_ && !enable_cuda_graph_时告警reason=feature_disabled ENABLE_CUDA_GRAPH=0并关闭,真正的初始化分支(:408)嵌套在 :290 的if (enable_cuda_graph_)内部;warmup 期缺kv_cache_layer_layout时 :276-289 会连带关闭它。hw_kernel_group_args.py:37的 help 也未提及该前置条件。考虑到已有带明确 reason 的 WARNING,运营影响有限,故定为 P3。 - [6.1] Architecture — 状态不变量:创建/更新/失败/重试/回滚路径有效 → issue
scratch 分组与 kv_cache_group_tags 靠下标顺序耦合,仅校验数量未校验身份
initPrefillScratchTensors()只校验prefill_scratch_kernel_block_ids_.size() == expected_groups(:178-181)与各组非空(:186-187),之后 capture 与 replay(:732-736)都按for (const auto& tag : kv_cache_group_tags_) install_scratch_row(..., group_id++)用下标顺序把 scratch 块绑定到 tag。而 scratch 列表由PyWrappedModel.cc:160-162按resource->kernelBlocks(0, group_id)的数值 group 序生成,kv_cache_group_tags_来自 topology 的 tag 快照。两者顺序一致是隐式假设、无任何断言;一旦 tag 快照顺序与 group 索引顺序解耦,padding token 的 KV 会写进另一个 cache group 的块,表现为难定位的数值污染而非快 - [6.1] Architecture — 错误语义:fail-fast/retry/fallback/silent 行为显式 → issue
本角色下 4 个 device 缓冲的 fill 与 D2D 被随后的整表 H2D 覆盖,且 canRun 未校验这两个 device 张量
本角色下prepareAttentionInputs先在 fused fill 中处理prefix_lengths_device/input_lengths_device/cu_seqlens_device/cu_kv_seqlens_device的 padding 尾部(:449-471),随后又tryAddD2DCopy拷贝这四个缓冲的活跃前缀(:507-518);但 :658-665 紧接着用 host 镜像对同样四个缓冲做了覆盖全部max_bs_(+1) 元素的 H2D 拷贝,前两步结果被完全覆盖,属每次 replay 的冗余 launch 与拷贝(未测量绝对开销)。附带::465-470 的addCudaGraphPrepareFillRegionFromDevice内部有 defined/is_cuda/int32/numel 断言(:133-136),而本角色 canRun(:1047-1171)只校验 host 侧张量,未校验这两个 device 张量。 - [6.1] Quality — Commit 原子、message 与行为匹配 → issue
FallbackTick 与 tickFallback 无任何定义,cuda_graph_impl 必然编译失败
:905、:926、:1049 三处写const FallbackTick tick = tickFallback(full_prefill_fallback_log_count_);并读tick.should_log/tick.count。全仓检索FallbackTick|tickFallback仅命中这 3 个使用点,无任何类型或函数定义:不在本文件匿名 namespace(:19-164 仅含ScopedEnvFlag、streamAsyncReplayPrepEnabled、addCudaGraphPrepareFillRegion*、inferTotalTokensNoSync、getInputEmbeddingSourceType),也不在其 6 个 include 头中;检索should_log亦无相关结构体。成员只有cuda_graph_runner.h:240的计数器。cuda_graph_impl必然失败,进而拖垮th_transformer与全部依赖测试。 - [6.1] Quality — Mega-PR 已拆分为独立变更 → issue
单个 PR 混装三条独立主线与无关格式化,其中关停重构未落入任何评审分片
本 PR 除 full-prefill CUDA Graph 外还含两条无关主线:一是引擎关停重构(NormalEngine::stop()引入stop_started_CAS、Executor新增notifyStop/synchronizeForShutdown、engine loop 吞异常、blockTerminationSignalsInEngineThread屏蔽信号、MtpExecutor6 个早退点),二是请求级input_embeddings覆盖特性。此外ConfigModules.h在PrefillCPConfig::is_enabled、KVCacheConfig、FIFOSchedulerConfig::decode_prefill_ratio、GrammarConfig四处产生纯空白/对齐改动,与本特性无关,应是整文件 clang-format 的副产物。分片路由也因此出现盲区:NormalEngine.cc/.h、NormalExecutor.cc/.h、`engine_ba - [6.1] Quality — PR description 说明动机与设计 → issue
单个 PR 混装三条独立主线与无关格式化,其中关停重构未落入任何评审分片
本 PR 除 full-prefill CUDA Graph 外还含两条无关主线:一是引擎关停重构(NormalEngine::stop()引入stop_started_CAS、Executor新增notifyStop/synchronizeForShutdown、engine loop 吞异常、blockTerminationSignalsInEngineThread屏蔽信号、MtpExecutor6 个早退点),二是请求级input_embeddings覆盖特性。此外ConfigModules.h在PrefillCPConfig::is_enabled、KVCacheConfig、FIFOSchedulerConfig::decode_prefill_ratio、GrammarConfig四处产生纯空白/对齐改动,与本特性无关,应是整文件 clang-format 的副产物。分片路由也因此出现盲区:NormalEngine.cc/.h、NormalExecutor.cc/.h、`engine_ba - [6.1] Quality — 逻辑变更未混入无关格式化 → issue
析构函数重复调用 py_model_.release(),实际释放顺序与注释意图不符
PyWrappedModel.cc:233 与 :244 连续出现两次py_model_.release();,并带两条完全相同的注释。py::object::release()会把m_ptr置空,第二次调用为空操作,属明显的合并残留。更实质的是 :244 的位置暗示作者本意是「在两个 runner 析构之后再释放 py_model」,却被 :233 抢先执行,实际顺序与意图不符。同时graph_runner_/prefill_graph_runner_仍是裸指针 + 析构里手工delete(:234-241),所有权表达不够直观,dirty-capture 分支的「故意泄漏」也只能靠注释表达。_ - [6.1] Software Engineering — DRY:重复非平凡逻辑被抽取或显式复用 → issue
本角色下 4 个 device 缓冲的 fill 与 D2D 被随后的整表 H2D 覆盖,且 canRun 未校验这两个 device 张量
本角色下prepareAttentionInputs先在 fused fill 中处理prefix_lengths_device/input_lengths_device/cu_seqlens_device/cu_kv_seqlens_device的 padding 尾部(:449-471),随后又tryAddD2DCopy拷贝这四个缓冲的活跃前缀(:507-518);但 :658-665 紧接着用 host 镜像对同样四个缓冲做了覆盖全部max_bs_(+1) 元素的 H2D 拷贝,前两步结果被完全覆盖,属每次 replay 的冗余 launch 与拷贝(未测量绝对开销)。附带::465-470 的addCudaGraphPrepareFillRegionFromDevice内部有 defined/is_cuda/int32/numel 断言(:133-136),而本角色 canRun(:1047-1171)只校验 host 侧张量,未校验这两个 device 张量。 - [6.1] Software Engineering — KISS/YAGNI:无投机性抽象 → issue
17 元组 pickle 兼容分支属投机兼容,构成永久死路径
diff 确认 base 校验为t.size() != 14,4 个新字段由本 PR 一次性加入,因此 17 长度(14 + 前 3 个新字段)只可能由本特性分支的中间提交产生,任何已发布版本都不会生成。配置 pickle 只服务 multiprocessing spawn,收发两端始终是同一二进制。该分支在真实版本组合下不可达,却长期占据has_full_prefill_fields/has_full_prefill_capture_fields双层 offset 位移(:797-809);测试类名_PreviousHWKernelConfig与用例名test_previous_format_uses_sparse_default还会让读者误认为 17 是已发布格式,并掩盖了 14 元组未被测试这一事实。 - [6.1] Software Engineering — LSP:子类/重写保持基类契约 → issue
prepare_fmha_impl 第三位置参数契约未同步到既有 Python 覆写,且选择模式为跨语言裸字符串
prepareFmhaImpl在本角色下以三个位置参数调用py_attn_pyobj_method_(inputs, is_cuda_graph, "full_prefill_no_prefix_graph")(:168)。基类module_base.py:79-116已扩展cuda_graph_selection_mode,但deepseek_v4_model.py:1062-1064、deepseek_v4_dspark_model.py:193与测试 stubcuda_graph_tagged_cache_test.py:42、pywrapped_model_cache_store_integration_test.py:25仍是(self, inputs, is_cuda_graph=False)两参数。当前因这些模型走 MLA、被PyWrappedModel.h:413的!use_mla排除而未触发,但属位置型参数的隐式契约漂移:任何模型接入 full prefill 图后会在 `initCapture - [6.1] Software Engineering — SRP:模块/类职责单一 → issue
replay 元数据构造器的容量契约隐式、末尾判据恒真,且与所在头文件职责不符
prepareFullPrefillReplayMetadata只接收三个裸指针和逻辑计数,无任何容量参数,但实际写入越过参数所暗示的边界:input_lengths[max_request_count](:37)要求至少 Bmax+1 个元素,cu_seqlens[slot + 1](slot 取到 Bmax,:48)要求至少 Bmax+2 个元素,padding_offset需 ≥ token_capacity。这些 +1/+2 契约只体现在调用方(生产端max_bs_ = max_requests + 1、cu_seqlens为max_bs_+1,当前恰为末位合法元素、无越界)与测试数组尺寸里,某个新调用方按max_request_count分配即静默越界写。:50 的return packed_offset == token_capacity;在 sentinel 补齐后恒成立(real + (capacity - real)),是永不触发的判据。 - [6.1] Tests — 分布式/跨平台变更有对应覆盖 → issue
新增 MoE CUDA Graph py_test 缺少同包统一的 open_skip 标签与环境 skip 保护
同一 BUILD 内既有的 6 个 py_test 全部带open_skip(:18、:48、:62、:79、:96、:113),新增的deepgemm_masked_executor_v2_cuda_graph_test(:22-33)只写tags = ["H20"]。该用例强依赖 DeepGEMM(per_block_cast_to_fp8、is_deep_gemm_e8m0_used、requant_weight_ue8m0)与 H20 实机,测试体内也没有has_deep_gemm()或 SM 版本的 skip 保护,一旦被不带-open_skip过滤的流水线选中就会以硬失败而非 skip 结束。另外该文件 capture 与 replay 的 token 数恒等,没有 full-prefill 真正会出现的「尾部 padding token 参与 routing」场景。 - [6.1] Tests — 新逻辑有聚焦单测 + 相关集成/smoke 测试 → issue
19 元素默认桶列表在三处硬编码,其 25% padding 不变量无测试守护
同一份 19 元稀疏桶列表1,2,...,117,156,160同时硬编码在三处:hw_kernel_group_args.py:62(CLI 默认字符串)、ConfigModules.h:270-271(C++ 结构体默认值)、config_pickle_test.py:130-133(断言期望值)。运行时真正生效的是 CLI 默认(binding 会覆盖 C++ 默认),而旧 pickle 还原与非 CLI 构造路径用的是 C++ 默认值;只改一侧会静默产生两套 capture 桶,且测试仍会通过(断言复制的是第三份副本)。「序列足够稠密以满足 25% padding 上限」这条不变量只写在 C++ 注释里(经复算最坏 118→156 桶为 0.2436,确实成立),无任何可执行断言守护。 - [6.1] Tests — 边界 case 覆盖(空、单元素、最大值) → issue
生产代码与测试用 getattr 字面量访问 pybind 必然存在的字段,并硬编码 C++ 布局常量
apply_input_embeddings用getattr(inputs, "cuda_graph_input_embedding_runtime_meta", None)做控制流分支(:132-133),测试cuda_graph_tagged_cache_test.py:74同样如此;但PyModelInputs已通过def_readwrite固定暴露该字段。字面量 getattr 既掩盖了契约,又会在字段改名时静默走 else 分支,使runtime_meta_numel == 0之类断言意外通过。测试:344的16 + 64 * 24直接复制了 C++ 侧 header/descriptor 的字节布局,结构体一改测试即以不可读的数字失败。
RTP-LLM Checklist
- [I] 代码质量 — 同一功能用统一工具函数 → issue
capture 失败回滚规则在工厂与 PyWrappedModel 中两份实现,且 catch 范围已开始漂移
CudaGraphRunner::createForPrefill(cuda_graph_runner.cc:1749-1766)已封装「构造 → initCapture → dirty 则泄漏并 rethrow」这一安全规则,测试 harness 走的正是该工厂(cuda_graph_test_runner.cc:40/78)。PyWrappedModel.h:465-500 用new+initCapture()+ 自建 try/catch 再实现了一遍同一规则,且这里只catch (const std::exception&)(:477)而工厂是catch (...)(:1757)——非标准异常在包装层会绕过captureSessionMayBeDirty()判定直接向上传播,落在析构式兜底之外。同一条「部分 capture 不可回滚」的硬约束存在两份实现,后续修一处漏一处的风险很高。
Python Static-First Checklist
- [P.A] 静态结构与类型纪律 — 字符串分发用 Enum/Literal → issue
prepare_fmha_impl 第三位置参数契约未同步到既有 Python 覆写,且选择模式为跨语言裸字符串
prepareFmhaImpl在本角色下以三个位置参数调用py_attn_pyobj_method_(inputs, is_cuda_graph, "full_prefill_no_prefix_graph")(:168)。基类module_base.py:79-116已扩展cuda_graph_selection_mode,但deepseek_v4_model.py:1062-1064、deepseek_v4_dspark_model.py:193与测试 stubcuda_graph_tagged_cache_test.py:42、pywrapped_model_cache_store_integration_test.py:25仍是(self, inputs, is_cuda_graph=False)两参数。当前因这些模型走 MLA、被PyWrappedModel.h:413的!use_mla排除而未触发,但属位置型参数的隐式契约漂移:任何模型接入 full prefill 图后会在 `initCapture - [P.A] 静态结构与类型纪律 — 禁止 getattr/setattr literal 访问 → issue
生产代码与测试用 getattr 字面量访问 pybind 必然存在的字段,并硬编码 C++ 布局常量
apply_input_embeddings用getattr(inputs, "cuda_graph_input_embedding_runtime_meta", None)做控制流分支(:132-133),测试cuda_graph_tagged_cache_test.py:74同样如此;但PyModelInputs已通过def_readwrite固定暴露该字段。字面量 getattr 既掩盖了契约,又会在字段改名时静默走 else 分支,使runtime_meta_numel == 0之类断言意外通过。测试:344的16 + 64 * 24直接复制了 C++ 侧 header/descriptor 的字节布局,结构体一改测试即以不可读的数字失败。 - [P.G] 测试规范 — mock/fake/stub 不得替代本次声称覆盖的生产边界 → issue
runner 层 sentinel 组装与 fallback 闸门缺集成覆盖,harness 把 padding 闸门硬编码为 1.0
唯一的 runner 级端到端用例(cuda_graph_tagged_cache_test.py:329)使用init_generative_prefill(model, 1, ..., [4], ...),而_build_prefill_inputs(:177-200)固定batch_size=1、seq_len=4,故恒有real_request_count==1、real_token_count==token_capacity,sentinel 长度恒为 0:pad 填充、空槽 block table 清零、非零padding_offset的 sentinel KV 路由在 runner 层全不执行。harness 又把full_prefill_max_padding_ratio硬编码为 1.0(:65,无形参可调),故padding_ratio_exceeded(:925)、bucket_miss(:904)、request_capacity_exceeded(:1064)三条决定线上是否回退的分支在测试中不可能触
Strengths
- 图角色由隐式布尔组合升级为显式
CudaGraphRole枚举,AUTO保留旧推导路径,既有 embedding / MTP draft prefill 图的分类未漂移。 - sentinel 布局自洽且可复算:
padding_offset = slot * token_capacity - packed_offset与单测期望值逐项一致;生产侧max_bs_ = Bmax+1、cu_seqlens为max_bs_+1,input_lengths[Bmax]与cu_seqlens[Bmax+1]恰为末位合法元素,当前无越界。 PyWrappedModel.h:477-489与cuda_graph_runner.cc:1755-1764对 capture 中途失败的处理克制且正确:识别captureSessionMayBeDirty()后拒绝删除半捕获 runner、拒绝在被污染的 allocator/stream 上继续或回退 eager,显式泄漏并 rethrow 使模型初始化失败,注释写清了CUDAGraph::reset无法回滚的原因。test_trtllm_fmha_v2_prefill.py:274-436是本 PR 质量最高的一处测试:一次 capture、[24,32]/[64]/[32,32]/[16,16,16,16]四种布局回放,同时比对 eager 输出、逐 block KV 内容、未映射 block 全零,并按 sentinel 长度反向校验 scratch block 是否被写。supportsFullPrefillCudaGraphMoe把 MoE 准入收敛到单一头文件,配 6 个可脱离 GPU 运行的单测;所有降级路径都带结构化reason=日志且按 2 的幂次限流,线上定位「为什么没走 graph」成本很低。- 配置面默认值三处(
ConfigModules.h:264-271、hw_kernel_group_args.py:36-62、config_pickle_test.py:132)逐项一致;默认稀疏桶序列经实算最坏 padding 约 0.2436 ≤ 注释声明的 0.25,不会出现「开了特性却每次都因 padding 超限回退」。 - pickle 的 18 个字段在结构体、
def_readwrite、__getstate__三处完整对齐;14/17/18 三种长度的offset(0/3/4) 索引映射逐槽位复算正确、无越界无错位。 - 特性默认关闭、门槛严格、失败统一降级 eager,回滚只需清空一个 env,运维风险面小。
| @@ -829,17 +937,19 @@ GptModelOutputs PyWrappedModel::forward(const GptModelInputs& inputs) { | |||
| torch::Tensor hidden_states; | |||
There was a problem hiding this comment.
📍 实际位置 rtp_llm/cpp/models/PyWrappedModel.cc:928(不在 diff 展示范围内,就近挂载)
[P1] 生产 forward 未透传 input_embeddings,新增的请求级 embedding 覆盖与配套护栏双双失效
PyModelInputs::input_embeddings/_locs 是本 PR 新增字段(diff 中为 + 行)。prepareAttentionInputs(:812)与 updateKVCacheKernelBlockId(:844)都新增了 has_value() && !empty() 的透传分支,但 :928 的 forward 仍以 8 元素聚合初始化构造 PyModelInputs,该字段保持 nullopt。两个消费点都只在 forward 之后:graph 路径 prepareInputData 先置 segment_count = 0,字段为空则恒为 0;eager 路径 module_base.py:140 读到 None。后果:覆盖被静默丢弃、canRun :1108-1140 的整套校验成为死代码、:1192「有 embedding 则禁用非本角色图」护栏在真正决策点看不到该字段。测试直接调用测试 runner 的 forward,绕过本包装层。
建议: 抽一个统一填充函数(如 applyRequestInputEmbeddings(py_model_inputs, inputs))供 :812 / :844 / :928 三处调用,消除漏点(该重复本身也已构成 DRY 问题)。若当前有意限定「generative prefill 暂不支持 embedding 覆盖」,则应在静态门禁或 canRun 处显式拒绝并记录 reason=,而不是让 forward 悄悄按 0 段执行。同时补一条经 PyWrappedModel::forward(而非测试 runner)的用例,断言「合法 embedding → 输出被覆盖」「非法 locs → 回退 eager」「非本角色 + embedding → 不走图」。
| inline bool supportsFullPrefillCudaGraphMoe(const GptModelDescription& description, | ||
| const ParallelismConfig& parallelism_config, | ||
| const MoeConfig& moe_config) { | ||
| if (!description.ffn_conf.moe_configs.has_value()) { |
There was a problem hiding this comment.
[P2] dense 模型缺并行度门禁,且 prefill capture 前未做 rank 同步
supportsFullPrefillCudaGraphMoe 对 dense 模型(moe_configs 为空)直接 return true(:14-16),把 tp/ep/dp/pp/world_size == 1 全部校验留在 MoE 分支(:23-24);而 PyWrappedModel.h:411-424 的 valid_static_config 只检查 parallelism_config.tp_size == 1,未检查 world_size/dp_size/pp_size。因此 dense 模型在纯 DP 或 PP 多卡部署下仍可进入 full-prefill capture。同时 decode 图在 PyWrappedModel.h:401 显式调用 syncCudaGraphCaptureRanks(..., "after_initialize_before_initCapture") 以保证各 rank 同序进入 graph-held collectives,而 :469 的 `prefill_graph_runner...
建议: 在 valid_static_config 中补齐 params.parallelism_config.world_size == 1(或 dp/pp/ep 各自为 1),使 dense 与 MoE 的并行度门禁一致。若确定要支持 world_size>1,则在 prefill_graph_runner_->initCapture() 前同样调用 syncCudaGraphCaptureRanks(该函数在 world_size <= 1 时直接 return,补上几乎无成本),并说明第二次 capture 与 decode capture 的相对顺序在所有 rank 上一致。
| help="Full Prefill CUDA Graph token bucket 允许的最大 padding 比例", | ||
| ) | ||
|
|
||
| hw_kernel_group.add_argument( |
There was a problem hiding this comment.
[P2] 新增 4 个旋钮在参数层无取值域校验,非法值静默降级且原因误报为模型不支持
--full_prefill_capture_config 的 help 写明 "is limited to 64 buckets",但 type=_parse_prefill_capture_config(:255-353)只过滤空值与 x > 0,不限桶数;--full_prefill_cuda_graph_max_requests 仅 type=int(:44)、--full_prefill_cuda_graph_max_padding_ratio 仅 type=float(:53)。真正的取值域校验在 PyWrappedModel.h:421-424(size() <= 64、max_requests > 0、ratio ∈ [0,1]),且合并进同一个布尔判断;:426-429 的 reason 映射只有 layer_micro_batch_enabled / unsupported_moe_config / unsupported_model 三支,配置错误一律落到 unsupported_model 且不打...
建议: 在 argparse 层 fail-fast,把「配置非法」与「模型不支持」分开:给 _parse_prefill_capture_config 增加 config_name / max_buckets 参数,超过 64 桶抛 argparse.ArgumentTypeError 并回显实际桶数与上限;对 max_requests 校验 > 0、对 max_padding_ratio 校验 [0.0, 1.0]。若保留运行期兜底,请把 reason 细化为 invalid_capture_buckets / invalid_max_requests / invalid_padding_ratio 并打印实际值。附带修正:_parse_prefill_capture_config 的错误信息硬编码 prefill_capture_config(:279、:347),解析 FULL_PREFILL_CAPTURE_CONFIG 出错时会指向错误的旋钮名。
| return py_attn_pyobj_method_(inputs, is_cuda_graph); | ||
| } | ||
|
|
||
| void CudaGraphRunner::initPrefillScratchTensors() { |
There was a problem hiding this comment.
[P2] scratch 分组与 kv_cache_group_tags 靠下标顺序耦合,仅校验数量未校验身份
initPrefillScratchTensors() 只校验 prefill_scratch_kernel_block_ids_.size() == expected_groups(:178-181)与各组非空(:186-187),之后 capture 与 replay(:732-736)都按 for (const auto& tag : kv_cache_group_tags_) install_scratch_row(..., group_id++) 用下标顺序把 scratch 块绑定到 tag。而 scratch 列表由 PyWrappedModel.cc:160-162 按 resource->kernelBlocks(0, group_id) 的数值 group 序生成,kv_cache_group_tags_ 来自 topology 的 tag 快照。两者顺序一致是隐式假设、无任何断言;一旦 tag 快照顺序与 group 索引顺序解耦,padding token 的 KV 会写进另一个 cache group 的块,表现为难定位的数值污染...
建议: 把 prefill_scratch_kernel_block_ids 改为 tag -> block_ids 映射(或在 GraphParams 中同时携带 group tag),initPrefillScratchTensors() 按 tag 建 map 并在缺 tag 时 fail-fast;若坚持下标形式,至少断言 group 索引与 tag 快照顺序一致,并在注释中写明该不变量由哪个 topology 接口保证。
Checklist: [6.1] 分层边界:新概念在正确层级,不泄漏内部;[6.1] 状态不变量:创建/更新/失败/重试/回滚路径有效
|
|
||
| } // namespace | ||
|
|
||
| py::object CudaGraphRunner::prepareFmhaImpl(const PyModelInputs& inputs, bool is_cuda_graph) { |
There was a problem hiding this comment.
[P2] prepare_fmha_impl 第三位置参数契约未同步到既有 Python 覆写,且选择模式为跨语言裸字符串
prepareFmhaImpl 在本角色下以三个位置参数调用 py_attn_pyobj_method_(inputs, is_cuda_graph, "full_prefill_no_prefix_graph")(:168)。基类 module_base.py:79-116 已扩展 cuda_graph_selection_mode,但 deepseek_v4_model.py:1062-1064、deepseek_v4_dspark_model.py:193 与测试 stub cuda_graph_tagged_cache_test.py:42、pywrapped_model_cache_store_integration_test.py:25 仍是 (self, inputs, is_cuda_graph=False) 两参数。当前因这些模型走 MLA、被 PyWrappedModel.h:413 的 !use_mla 排除而未触发,但属位置型参数的隐式契约漂移:任何模型接入 full prefill 图后会在 `initCapt...
建议: 统一补齐所有 prepare_fmha_impl 覆写的 cuda_graph_selection_mode 形参(即使忽略也应显式接收),并在基类注释中写明「覆写者必须接受并忽略该参数」;C++ 侧改用关键字实参以降低位置耦合,同时把 "full_prefill_no_prefix_graph" 字面量提升为经 pybind 导出的共享常量或 Literal 枚举,使 C++/Python 只有一个定义源,而非靠人工对齐。
Checklist: [6.1] LSP:子类/重写保持基类契约;[P.A] 字符串分发用 Enum/Literal
| } | ||
| releaseFullPrefillScratch(); | ||
| // Always release py_model_ since it's always initialized now | ||
| py_model_.release(); |
There was a problem hiding this comment.
[P3] 析构函数重复调用 py_model_.release(),实际释放顺序与注释意图不符
PyWrappedModel.cc:233 与 :244 连续出现两次 py_model_.release();,并带两条完全相同的注释。py::object::release() 会把 m_ptr 置空,第二次调用为空操作,属明显的合并残留。更实质的是 :244 的位置暗示作者本意是「在两个 runner 析构之后再释放 py_model_」,却被 :233 抢先执行,实际顺序与意图不符。同时 graph_runner_ / prefill_graph_runner_ 仍是裸指针 + 析构里手工 delete(:234-241),所有权表达不够直观,dirty-capture 分支的「故意泄漏」也只能靠注释表达。
建议: 删除其中一次 py_model_.release() 与重复注释,并确认最终保留位置符合「runner 析构后再释放 Python 模型」的意图;建议把两个 runner 改为 std::unique_ptr<GraphBase>,析构顺序由成员声明顺序保证,dirty-capture 分支再用显式 release() 表达「故意泄漏至进程退出」。
Checklist: [6.1] 逻辑变更未混入无关格式化
| auto stridedCopyHost = [&pending_host_mirror_d2h](const torch::Tensor& src, torch::Tensor& dst) { | ||
| if (!src.defined() || src.numel() <= 0 || !dst.defined() || dst.is_cuda()) | ||
| return; | ||
| if (src.is_cuda()) { |
There was a problem hiding this comment.
📍 实际位置 rtp_llm/cpp/cuda_graph/cuda_graph_runner.cc:449(不在 diff 展示范围内,就近挂载)
[P3] 本角色下 4 个 device 缓冲的 fill 与 D2D 被随后的整表 H2D 覆盖,且 canRun 未校验这两个 device 张量
本角色下 prepareAttentionInputs 先在 fused fill 中处理 prefix_lengths_device / input_lengths_device / cu_seqlens_device / cu_kv_seqlens_device 的 padding 尾部(:449-471),随后又 tryAddD2DCopy 拷贝这四个缓冲的活跃前缀(:507-518);但 :658-665 紧接着用 host 镜像对同样四个缓冲做了覆盖全部 max_bs_(+1) 元素的 H2D 拷贝,前两步结果被完全覆盖,属每次 replay 的冗余 launch 与拷贝(未测量绝对开销)。附带::465-470 的 addCudaGraphPrepareFillRegionFromDevice 内部有 defined/is_cuda/int32/numel 断言(:133-136),而本角色 canRun(:1047-1171)只校验 host 侧张量,未校验这两个 device 张量。
建议: 对 isGenerativePrefillCudaGraph() 直接跳过这四个缓冲的 fill 区间与 D2D 拷贝,仅保留 :658-665 的 host→device 整表覆盖;这同时也从根上消除上述断言面,使上游未填时不会从「优雅回退 eager」退化为引擎异常。若选择保留 fill 路径,则请在 canRun 内补 cu_seqlens_device/cu_kv_seqlens_device 的 defined/is_cuda/int32/contiguous/numel 检查并 return fallback("metadata_mismatch")。
Checklist: [6.1] 错误语义:fail-fast/retry/fallback/silent 行为显式;[6.1] DRY:重复非平凡逻辑被抽取或显式复用
PR #1360 第 6 轮评审 — BLOCKED(1)
阻塞项(P0/P1,support≥3 且过全部门)[P1] generative prefill 捕获时对空的 input_hiddens 做越界 slice
非阻塞发现(并集报告:单票也保留,降级不丢弃)
已确认修复(recheck)
本轮 KPI{
"reps": 5,
"agentic_reps": 2,
"diff_truncated": false,
"patch_bytes": 202532,
"coverage": 1.0,
"shards": 1,
"discover_ok": 5,
"discover_fail": 0,
"discover_attempts": 5,
"clusters": 8,
"synth_fail": 0,
"confirm": {
"candidates": 0,
"to_block": 0,
"confirm_fail": 0
},
"blocking_dedup_merged": 0,
"fact_check": {
"checked": 0,
"hold": 0,
"refuted": 0,
"parse_fail": 0
},
"model": "whale/DeepSeek-V4-Pro-0813",
"round_index": 6,
"delta_files": 33,
"full_files": 64,
"new_findings": 7,
"merged": 1,
"recheck": {
"fixed": 4,
"still_open": 18,
"cannot_substantiate": 2,
"fail": 0
},
"blocking": 1,
"blocking_on_unchanged": 0
}本报告由 rtp-llm-agent-platform 自动生成,同一 PR 的后续轮次就地更新同一条评论。 rtp-llm-agent-platform review · 第 6 轮 · head |
LLLLKKKK
left a comment
There was a problem hiding this comment.
AI Code Review - PR #1360
Status: BLOCKING
Summary: P0/0 · P1/4 · P2/8 · P3/2
Reviewed: commit 7d4570200548 · 2026-09-01 10:54 UTC+8
Blocking Issues
P1
- 请求级 input_embeddings 在生产模型 forward 中没有任何消费者 @
rtp_llm/models_py/model_desc/module_base.py:122- 建议:支持该能力的模型统一调用
get_inputs_embeds或apply_input_embeddings;不支持的模型调用_reject_input_embeddings快速失败。补充真实 model descriptor 的 eager 与 graph 集成测试。
- 建议:支持该能力的模型统一调用
- MTP 停机早退在异步 prepare 未同步前释放模型缓冲 @
rtp_llm/cpp/normal_engine/speculative/MtpExecutor.cc:1317- 建议:所有 async prepare 启动后的退出路径都应先同步对应 runner,再释放缓冲;建议用作用域 guard 收口,并增加阻塞 prepare worker 后触发
notifyStop()的确定性竞态测试。
- 建议:所有 async prepare 启动后的退出路径都应先同步对应 runner,再释放缓冲;建议用作用域 guard 收口,并增加阻塞 prepare worker 后触发
- 新增 server args 测试访问不存在的配置属性 @
rtp_llm/server/server_args/test/server_args_test.py:196- 建议:将这些断言统一改为访问
py_hw_kernel_config,并执行整个测试文件确认三条配置路径均得到验证。
- 建议:将这些断言统一改为访问
- 文件缺失错误文案变更导致既有测试确定性失败 @
rtp_llm/server/server_args/hw_kernel_group_args.py:327- 建议:同步更新测试期望;若旧文案属于外部诊断契约,则对默认配置保留原文案,仅为新配置使用参数化名称。
Non-blocking Suggestions
P2
- 微批路径未传播请求级 input_embeddings @
rtp_llm/cpp/models/PyWrappedModel.cc:672- 建议:按
TokenSliceInfo切分 embedding 并重定位 loc;若暂不支持该组合,则在进入微批路径前明确 fail-fast,并增加组合测试。
- 建议:按
- 非数字环境变量被静默忽略并回退默认值 @
rtp_llm/server/server_args/hw_kernel_group_args.py:10- 建议:捕获
int()的ValueError并转换为argparse.ArgumentTypeError,同时补充混合 CLI 与非法环境变量的回归测试。
- 建议:捕获
- bucket 上限在完整 range 列表分配之后才校验 @
rtp_llm/server/server_args/hw_kernel_group_args.py:344- 建议:在构造列表前根据
max_seq_len、step和尾项补齐规则计算 bucket 数量,超限立即拒绝。
- 建议:在构造列表前根据
- MoE 图重放测试没有改变各 expert 的 token 数 @
rtp_llm/models_py/modules/factory/fused_moe/impl/cuda/executors/test/deepgemm_masked_executor_v2_cuda_graph_test.py:125- 建议:使用明显偏斜且包含零-token expert 的 replay 路由,先断言捕获与 replay 的 expert 计数不同,再比较 eager 与 replay 输出。
- SM12x 的 MoE CUDA Graph 路径缺少真实回放覆盖 @
rtp_llm/models_py/modules/factory/fused_moe/impl/cuda/executors/test/BUILD:22- 建议:增加 SM12x 测试目标,并覆盖动态 expert 计数及 eager/replay 输出对比。
- 测试库直接引用的符号未声明直接 Bazel 依赖 @
rtp_llm/cpp/cuda_graph/tests/BUILD:32- 建议:在
test_cuda_graph_runner_libs的deps中直接加入//rtp_llm/models_py/bindings/common:common。
- 建议:在
- 类型桩未表达未定义 Tensor 的 None 状态 @
rtp_llm/ops/librtp_compute_ops/__init__.pyi:387- 建议:将这两个属性及其 setter 声明为
torch.Tensor | None。
- 建议:将这两个属性及其 setter 声明为
- 单个 PR 混装三条独立主线,增加评审与回归定位成本 @
rtp_llm/cpp/normal_engine/NormalEngine.cc:539- 建议:将关停重构拆为独立 PR 并配套 drain 顺序测试;至少在 PR 描述中分别说明三条主线的动机、边界与依赖。
P3
- prefill 图各回退原因共用同一限频计数器 @
rtp_llm/cpp/cuda_graph/cuda_graph_runner.cc:1065- 建议:按 reason 维护独立计数,至少保证每种原因首次出现时记录一次,并考虑暴露按原因聚合的指标。
- 文件读取异常未关联原始异常 @
rtp_llm/server/server_args/hw_kernel_group_args.py:332- 建议:使用
raise argparse.ArgumentTypeError(...) from e保留原始异常链。
- 建议:使用
Checklist Findings (15 fail / 142 total)
General Principles Checklist
- [6.1] Architecture — 依赖方向:无循环依赖/跨层惊喜 → issue
测试库直接引用的符号未声明直接 Bazel 依赖
cuda_graph_test_runner.cc直接包含RtpEmbeddingLookup.h并引用applyInputEmbeddingRuntime,但目标未依赖定义该符号的//rtp_llm/models_py/bindings/common:common。当前仅通过平台 binding 的传递依赖获得符号,默认平台或依赖链变化后可能出现链接失败。 - [6.1] Architecture — 兼容性:外部 HTTP/RPC API、持久数据、配置、环境迁移安全 → issue
非数字环境变量被静默忽略并回退默认值
_positive_int的int(value)对非数字输入抛出ValueError;混合 CLI/env 解析路径会静默吞掉该异常。因此PREFILL_CUDA_GRAPH_MAX_REQUESTS=abc回退为默认值 8,而=0会正确报错,同一参数的非法输入语义不一致。 - [6.1] Architecture — 可观测性:日志/指标/超时可操作、非噪声 → issue
prefill 图各回退原因共用同一限频计数器
bucket miss 与canRun中所有 fallback 原因共用prefill_cuda_graph_fallback_log_count_,且仅在聚合计数为 2 的幂时打印。多种原因交织时,某个持续发生的原因可能始终错过采样点,线上难以判断主要回退原因。 - [6.1] Architecture — 状态不变量:创建/更新/失败/重试/回滚路径有效 → issue
微批路径未传播请求级 input_embeddings
forwardMicroBatched()构造PyModelInputs时未调用其他构造点统一使用的propagateRequestInputEmbeddings,并丢弃splitInputsIntoMicroBatches()返回的TokenSliceInfo,因而无法切分 embedding 或重定位 loc。生产模型接入 embedding hook 后,该组合会静默退回 token embedding。 - [6.1] Architecture — 错误语义:fail-fast/retry/fallback/silent 行为显式 → issue
bucket 上限在完整 range 列表分配之后才校验
range 分支先执行list(range(...)),随后才检查 64 个 bucket 的上限。形如100000000:1的配置会先物化巨型列表,可能在启动阶段耗尽内存,而非快速返回配置错误。 - [6.1] Quality — Mega-PR 已拆分为独立变更 → issue
单个 PR 混装三条独立主线,增加评审与回归定位成本
本 PR 同时修改生成式 Prefill CUDA Graph、请求级 embedding 覆盖和基于notifyStop/synchronizeForShutdown的引擎关停流程,共涉及 58 个文件。本轮确认的并发缺陷位于关停主线,与图和 embedding 主线没有必要的原子提交关系。 - [6.1] Quality — PR description 说明动机与设计 → issue
单个 PR 混装三条独立主线,增加评审与回归定位成本
本 PR 同时修改生成式 Prefill CUDA Graph、请求级 embedding 覆盖和基于notifyStop/synchronizeForShutdown的引擎关停流程,共涉及 58 个文件。本轮确认的并发缺陷位于关停主线,与图和 embedding 主线没有必要的原子提交关系。 - [6.1] Tests — 分布式/跨平台变更有对应覆盖 → issue
SM12x 的 MoE CUDA Graph 路径缺少真实回放覆盖
DeepGemmMaskedExecutorV2接受 SM major ≥ 9,并在新架构使用 E8M0 路径;新增图回放 target 仅调度到 H20,测试又显式跳过非 SM90。SM12x 特有的 E8M0、Triton 与 DeepGEMM 组合没有端到端 capture/replay 覆盖。 - [6.1] Tests — 新逻辑有聚焦单测 + 相关集成/smoke 测试 → issue
SM12x 的 MoE CUDA Graph 路径缺少真实回放覆盖
DeepGemmMaskedExecutorV2接受 SM major ≥ 9,并在新架构使用 E8M0 路径;新增图回放 target 仅调度到 H20,测试又显式跳过非 SM90。SM12x 特有的 E8M0、Triton 与 DeepGEMM 组合没有端到端 capture/replay 覆盖。 - [6.1] Tests — 边界 case 覆盖(空、单元素、最大值) → issue
MoE 图重放测试没有改变各 expert 的 token 数
64 个 token、top-k=2、4 个 expert 的轮转构造使 offset=0 和 offset=2 时每个 expert 始终收到 32 个路由项。测试只改变 ID 顺序;即使图错误复用捕获期的expert_num_tokens,测试仍可能通过,且未覆盖偏斜分布或零-token expert。
RTP-LLM Checklist
- [I] 代码质量 — 同一功能用统一工具函数 → issue
微批路径未传播请求级 input_embeddings
forwardMicroBatched()构造PyModelInputs时未调用其他构造点统一使用的propagateRequestInputEmbeddings,并丢弃splitInputsIntoMicroBatches()返回的TokenSliceInfo,因而无法切分 embedding 或重定位 loc。生产模型接入 embedding hook 后,该组合会静默退回 token embedding。
Python Static-First Checklist
- [P.B] 错误处理 — 异常链用 raise X from Y → issue
文件读取异常未关联原始异常
权限、编码等文件读取异常被转换为argparse.ArgumentTypeError时没有使用异常链,原始异常未作为 cause 保留,降低启动配置问题的可诊断性。 - [P.B] 错误处理 — 禁止 bare except 或静默吞异常 → issue
非数字环境变量被静默忽略并回退默认值
_positive_int的int(value)对非数字输入抛出ValueError;混合 CLI/env 解析路径会静默吞掉该异常。因此PREFILL_CUDA_GRAPH_MAX_REQUESTS=abc回退为默认值 8,而=0会正确报错,同一参数的非法输入语义不一致。 - [P.G] 测试规范 — mock/fake/stub 不得替代本次声称覆盖的生产边界 → issue
请求级 input_embeddings 在生产模型 forward 中没有任何消费者
全仓中get_inputs_embeds、apply_input_embeddings、_reject_input_embeddings仅有定义,无生产调用者;Qwen3、GenericMoe、DeepSeekV4 等仍直接调用各自 embedding。C++ 已透传请求字段并允许图执行,但 eager 与 prefill graph 都会静默忽略覆盖值。现有测试由 fake model 自行实现注入,未覆盖生产边界。 - [P.H] 类型标注 — Optional[X] 表示可为 None 而非参数可选 → issue
类型桩未表达未定义 Tensor 的 None 状态
普通 eager 请求中的未定义 C++ Tensor 会映射为 PythonNone,module_base.py也显式判空;但input_embeddings_locs与cuda_graph_input_embedding_runtime_meta的 getter/setter 均声明为必有torch.Tensor,与运行时契约矛盾。
Strengths
CudaGraphRole显式区分 decode、embedding prefill 与生成式 prefill,降低状态混用风险。- 哨兵 scratch block 避免 padding token 的 KV 写入污染真实缓存。
preparePrefillCudaGraphReplayMetadata抽为纯函数,并覆盖容量不足、非法长度与零长度边界。HWKernelConfigpickle 同时支持当前 17 元组和历史 14 元组布局。CudaGraphSelectionMode使用 Enum 并对未知值快速失败,prepare_fmha_impl覆写已同步。- 生成式 prefill 功能默认关闭,保留明确的运维回滚开关。
| except FileNotFoundError: | ||
| raise argparse.ArgumentTypeError( | ||
| f"Prefill capture file not found: {file_path}" | ||
| f"{config_name} file not found: {file_path}" |
There was a problem hiding this comment.
[P1] 文件缺失错误文案变更导致既有测试确定性失败
默认分支现在产生 prefill_capture_config file not found,但既有测试仍以区分大小写的方式断言 Prefill capture file not found。两者大小写和措辞均不匹配,该测试会稳定失败。
建议: 同步更新测试期望;若旧文案属于外部诊断契约,则对默认配置保留原文案,仅为新配置使用参数化名称。
| @@ -704,6 +819,7 @@ void PyWrappedModel::prepareAttentionInputs(const GptModelInputs& inputs, bool s | |||
| } | |||
There was a problem hiding this comment.
📍 实际位置 rtp_llm/cpp/models/PyWrappedModel.cc:672(不在 diff 展示范围内,就近挂载)
[P2] 微批路径未传播请求级 input_embeddings
forwardMicroBatched() 构造 PyModelInputs 时未调用其他构造点统一使用的 propagateRequestInputEmbeddings,并丢弃 splitInputsIntoMicroBatches() 返回的 TokenSliceInfo,因而无法切分 embedding 或重定位 loc。生产模型接入 embedding hook 后,该组合会静默退回 token embedding。
建议: 按 TokenSliceInfo 切分 embedding 并重定位 loc;若暂不支持该组合,则在进入微批路径前明确 fail-fast,并增加组合测试。
Checklist: [6.1] 状态不变量:创建/更新/失败/重试/回滚路径有效;[I] 同一功能用统一工具函数
| @@ -292,7 +348,7 @@ def _parse_prefill_capture_config(config: str) -> List[int]: | |||
| logging.info( | |||
There was a problem hiding this comment.
📍 实际位置 rtp_llm/server/server_args/hw_kernel_group_args.py:344(不在 diff 展示范围内,就近挂载)
[P2] bucket 上限在完整 range 列表分配之后才校验
range 分支先执行 list(range(...)),随后才检查 64 个 bucket 的上限。形如 100000000:1 的配置会先物化巨型列表,可能在启动阶段耗尽内存,而非快速返回配置错误。
建议: 在构造列表前根据 max_seq_len、step 和尾项补齐规则计算 bucket 数量,超限立即拒绝。
Checklist: [6.1] 错误语义:fail-fast/retry/fallback/silent 行为显式
| device="cuda", | ||
| dtype=torch.bfloat16, | ||
| ) | ||
| token_ids = torch.arange(self.NUM_TOKENS, device="cuda") |
There was a problem hiding this comment.
[P2] MoE 图重放测试没有改变各 expert 的 token 数
64 个 token、top-k=2、4 个 expert 的轮转构造使 offset=0 和 offset=2 时每个 expert 始终收到 32 个路由项。测试只改变 ID 顺序;即使图错误复用捕获期的 expert_num_tokens,测试仍可能通过,且未覆盖偏斜分布或零-token expert。
建议: 使用明显偏斜且包含零-token expert 的 replay 路由,先断言捕获与 replay 的 expert 计数不同,再比较 eager 与 replay 输出。
Checklist: [6.1] 边界 case 覆盖(空、单元素、最大值)
| exec_properties = {'gpu':'H20', 'gpu_count':'1'}, | ||
| ) | ||
|
|
||
| py_test ( |
There was a problem hiding this comment.
[P2] SM12x 的 MoE CUDA Graph 路径缺少真实回放覆盖
DeepGemmMaskedExecutorV2 接受 SM major ≥ 9,并在新架构使用 E8M0 路径;新增图回放 target 仅调度到 H20,测试又显式跳过非 SM90。SM12x 特有的 E8M0、Triton 与 DeepGEMM 组合没有端到端 capture/replay 覆盖。
建议: 增加 SM12x 测试目标,并覆盖动态 expert 计数及 eager/replay 输出对比。
Checklist: [6.1] 分布式/跨平台变更有对应覆盖;[6.1] 新逻辑有聚焦单测 + 相关集成/smoke 测试
|
|
||
| cc_library( | ||
| name = "test_cuda_graph_runner_libs", | ||
| srcs = [ |
There was a problem hiding this comment.
📍 实际位置 rtp_llm/cpp/cuda_graph/tests/BUILD:32(不在 diff 展示范围内,就近挂载)
[P2] 测试库直接引用的符号未声明直接 Bazel 依赖
cuda_graph_test_runner.cc 直接包含 RtpEmbeddingLookup.h 并引用 applyInputEmbeddingRuntime,但目标未依赖定义该符号的 //rtp_llm/models_py/bindings/common:common。当前仅通过平台 binding 的传递依赖获得符号,默认平台或依赖链变化后可能出现链接失败。
建议: 在 test_cuda_graph_runner_libs 的 deps 中直接加入 //rtp_llm/models_py/bindings/common:common。
Checklist: [6.1] 依赖方向:无循环依赖/跨层惊喜
7d45702 to
dc0504a
Compare
LLLLKKKK
left a comment
There was a problem hiding this comment.
AI Code Review - PR #1360
Status: BLOCKING
Summary: P0/0 · P1/7 · P2/10 · P3/2
Reviewed: commit dc0504a8e6c7 · 2026-09-01 12:54 UTC+8
Blocking Issues
P1
- 请求级 input_embeddings 在生产模型 forward 中没有任何消费者 @
rtp_llm/models_py/model_desc/module_base.py:122- 建议:支持该能力的模型统一调用
get_inputs_embeds,其余模型显式拒绝,并增加真实 descriptor 的 eager 与 Graph 集成测试。
- 建议:支持该能力的模型统一调用
- MTP 停机早退在异步 prepare 未同步前释放模型缓冲 @
rtp_llm/cpp/normal_engine/speculative/MtpExecutor.cc:1317- 建议:所有异步 prepare 后的早退路径先同步对应 runner,再释放缓冲;用作用域 guard 收口,并增加阻塞 worker 后触发停机的测试。
- 新增 server args 测试访问不存在的配置属性 @
rtp_llm/server/server_args/test/server_args_test.py:196- 建议:将这些引用统一改为
py_hw_kernel_config。
- 建议:将这些引用统一改为
- 文件缺失错误文案变更导致既有测试确定性失败 @
rtp_llm/server/server_args/hw_kernel_group_args.py:327- 建议:默认配置名保留原错误文案,或同步更新既有测试和对外诊断契约。
- attention 工厂参数扩展导致既有路由测试确定性失败 @
rtp_llm/models_py/model_desc/module_base.py:97- 建议:更新 mock 签名并断言 selection mode;新增参数改用关键字传递以降低签名漂移风险。
- Masked DeepGEMM CUDA Graph 回归测试未形成有效门禁 @
rtp_llm/models_py/modules/factory/fused_moe/impl/cuda/executors/test/BUILD:31- 建议:将目标接入 SM90/SM12x CI,并让 replay 使用包含零-token expert 和倾斜负载的不同 histogram。
- 生成式 prefill 图会静默忽略多模态输入 @
rtp_llm/cpp/cuda_graph/cuda_graph_runner.cc:1068- 建议:在生成式 prefill 的
canRun()中拒绝旧版多模态字段,或实现固定地址更新机制,并增加真实模型的 eager/Graph 对比测试。
- 建议:在生成式 prefill 的
Non-blocking Suggestions
P2
- 微批路径未传播请求级 input_embeddings @
rtp_llm/cpp/models/PyWrappedModel.cc:672- 建议:按
TokenSliceInfo切分并重定位 embedding;若暂不支持该组合,应在进入微批路径前明确拒绝。
- 建议:按
- Prefill Graph scratch 分配污染真实 cache-hit 指标 @
rtp_llm/cpp/models/PyWrappedModel.cc:163- 建议:为内部 scratch 增加跳过请求指标的标志,或使用不经过请求指标的保留块接口。
- 非数字环境变量被静默忽略并回退默认值 @
rtp_llm/server/server_args/hw_kernel_group_args.py:10- 建议:捕获整数转换异常并抛出
argparse.ArgumentTypeError,补充非法环境变量与混合 CLI 测试。
- 建议:捕获整数转换异常并抛出
- bucket 上限在完整 range 列表分配之后才校验 @
rtp_llm/server/server_args/hw_kernel_group_args.py:344- 建议:构造列表前计算 bucket 数并拒绝超限输入。
- 类型桩未表达未定义 Tensor 的 None 状态 @
rtp_llm/ops/librtp_compute_ops/__init__.pyi:387- 建议:将相关属性及 setter 参数标注为
torch.Tensor | None。
- 建议:将相关属性及 setter 参数标注为
- 测试库直接引用的符号未声明直接 Bazel 依赖 @
rtp_llm/cpp/cuda_graph/tests/BUILD:32- 建议:为
test_cuda_graph_runner_libs添加该直接依赖。
- 建议:为
- Python RPC 图状态转换缺少兼容测试 @
rtp_llm/cpp/model_rpc/model_rpc_client.py:787- 建议:增加
trans_output的非空状态和缺省字段两个兼容用例。
- 建议:增加
- 普通 smoke 比较器不会校验新增图状态 @
rtp_llm/test/smoke/normal_comparer.py:72- 建议:将该字段加入比较列表,并增加状态不一致时必须失败的 comparer 单测。
- ROCm 新增 Graph embedding 路径没有可执行覆盖 @
rtp_llm/cpp/cuda_graph/tests/BUILD:109- 建议:增加 ROCm binding/fallback 测试;若生成式 prefill 明确不支持 ROCm,则在配置入口拒绝并测试该契约。
- 单个 PR 混装三条独立主线,增加评审与回归定位成本 @
rtp_llm/cpp/normal_engine/NormalEngine.cc:539- 建议:按能力拆分提交或 PR,并分别说明动机、迁移方式和测试矩阵。
P3
- prefill 图各回退原因共用同一限频计数器 @
rtp_llm/cpp/cuda_graph/cuda_graph_runner.cc:1070- 建议:按
PrefillCudaGraphStatus独立计数,或保证每种原因首次出现时记录。
- 建议:按
- 文件读取异常未关联原始异常 @
rtp_llm/server/server_args/hw_kernel_group_args.py:332- 建议:使用
raise argparse.ArgumentTypeError(...) from e。
- 建议:使用
Checklist Findings (19 fail / 151 total)
General Principles Checklist
- [6.1] Architecture — 依赖方向:无循环依赖/跨层惊喜 → issue
测试库直接引用的符号未声明直接 Bazel 依赖
cuda_graph_test_runner.cc直接包含RtpEmbeddingLookup.h并引用applyInputEmbeddingRuntime,目标却未直接依赖定义该头文件和符号的//rtp_llm/models_py/bindings/common:common。 - [6.1] Architecture — 兼容性:外部 HTTP/RPC API、持久数据、配置、环境迁移安全 → issue
Python RPC 图状态转换缺少兼容测试
C++ 测试仅覆盖状态写入 protobuf;Python 客户端测试未验证非空状态透传,也未约束旧服务端缺省字段回退为not_requested。 - [6.1] Architecture — 可观测性:日志/指标/超时可操作、非噪声 → issue
prefill 图各回退原因共用同一限频计数器
bucket miss 与所有canRun()失败原因共用计数器,且仅在聚合计数为二次幂时记录;多个原因交错时,新原因首次发生可能没有日志。 - [6.1] Architecture — 状态不变量:创建/更新/失败/重试/回滚路径有效 → issue
微批路径未传播请求级 input_embeddings
forwardMicroBatched()构造PyModelInputs时未设置 embedding 字段;切分逻辑也未按 token slice 筛选并重定位 embedding 区间。 - [6.1] Architecture — 错误语义:fail-fast/retry/fallback/silent 行为显式 → issue
bucket 上限在完整 range 列表分配之后才校验
range 分支先执行list(range(...)),随后才检查 64 个 bucket 上限;超大max:step误配置可能先耗尽启动进程内存。 - [6.1] Quality — Mega-PR 已拆分为独立变更 → issue
单个 PR 混装三条独立主线,增加评审与回归定位成本
76 个文件同时包含生成式 Prefill CUDA Graph、MTP/DSpARK 生命周期调整及return_all_probs_modeRPC 演进;三者调用链、测试矩阵和回滚风险相互独立。 - [6.1] Quality — PR description 说明动机与设计 → issue
单个 PR 混装三条独立主线,增加评审与回归定位成本
76 个文件同时包含生成式 Prefill CUDA Graph、MTP/DSpARK 生命周期调整及return_all_probs_modeRPC 演进;三者调用链、测试矩阵和回滚风险相互独立。 - [6.1] Tests — 分布式/跨平台变更有对应覆盖 → issue
ROCm 新增 Graph embedding 路径没有可执行覆盖
ROCm 注册了新的 runtime embedding op,PyWrappedModel也编译 Graph 初始化路径,但新增 generative-prefill 与动态 embedding 测试均固定为 H20;ROCm 的 backend 回退和 metadata 行为没有运行时覆盖。 - [6.1] Tests — 新逻辑有聚焦单测 + 相关集成/smoke 测试 → issue
ROCm 新增 Graph embedding 路径没有可执行覆盖
ROCm 注册了新的 runtime embedding op,PyWrappedModel也编译 Graph 初始化路径,但新增 generative-prefill 与动态 embedding 测试均固定为 H20;ROCm 的 backend 回退和 metadata 行为没有运行时覆盖。 - [6.1] Tests — 边界 case 覆盖(空、单元素、最大值) → issue
Python RPC 图状态转换缺少兼容测试
C++ 测试仅覆盖状态写入 protobuf;Python 客户端测试未验证非空状态透传,也未约束旧服务端缺省字段回退为not_requested。
RTP-LLM Checklist
- [B] 正确性与逻辑 — KV cache block 只有在 KV 写入、拷贝或 commit 成功后才能发布为可见或可复用;失败、取消、load_back 必须回滚 device/remote/reuse 状态 → issue
类型桩未表达未定义 Tensor 的 None 状态
未定义的 C++torch::Tensor在 Python 中表现为None,生产代码也显式处理该状态;类型桩却将两个 embedding metadata 属性及 setter 声明为必有torch.Tensor。 - [B] 正确性与逻辑 — Pybind/pyi/C++ struct 输入契约变更必须同步所有仓库内调用点、普通 forward、CUDA Graph replay、host/device mirror 和测试调用面;pybind-only 变化默认按内部正确性回归处理 → issue
类型桩未表达未定义 Tensor 的 None 状态
未定义的 C++torch::Tensor在 Python 中表现为None,生产代码也显式处理该状态;类型桩却将两个 embedding metadata 属性及 setter 声明为必有torch.Tensor。 - [H] 测试与 CI — mock/fake/stub 只能隔离非目标依赖,不能 stub 掉本次声称覆盖的生产边界;涉及 pybind/C++/runtime 的 bug 必须有真实边界集成或 smoke 覆盖 → issue
请求级 input_embeddings 在生产模型 forward 中没有任何消费者
get_inputs_embeds、apply_input_embeddings和_reject_input_embeddings仅在基类定义;生产模型仍直接调用embed_tokens。C++ 虽传播新字段,真实 eager 与 Graph forward 均不消费;现有测试由伪模型自行复制覆盖逻辑。 - [H] 测试与 CI — 新增、迁移或删除测试必须证明目标行为仍在 CI 中执行;DISABLED_、#if 0、open_skip、导入即失败、未被 target 引用的用例不得计为覆盖 → issue
普通 smoke 比较器不会校验新增图状态
AuxInfo已声明prefill_cuda_graph_status,但_compare_aux_info()的普通字段列表未包含它;普通 smoke 中的期望状态与实际状态不一致时不会失败。 - [I] 代码质量 — 同一功能用统一工具函数 → issue
微批路径未传播请求级 input_embeddings
forwardMicroBatched()构造PyModelInputs时未设置 embedding 字段;切分逻辑也未按 token slice 筛选并重定位 embedding 区间。
Python Static-First Checklist
- [P.B] 错误处理 — 异常链用 raise X from Y → issue
文件读取异常未关联原始异常
文件读取异常被包装为ArgumentTypeError时未使用显式异常链,诊断中会丢失原始异常关系。 - [P.B] 错误处理 — 禁止 bare except 或静默吞异常 → issue
非数字环境变量被静默忽略并回退默认值
_positive_int("abc")抛出原始ValueError;EnvArgumentParser会吞掉环境变量转换产生的ValueError,使非法PREFILL_CUDA_GRAPH_MAX_REQUESTS静默回退为默认值 8。 - [P.G] 测试规范 — mock/fake/stub 不得替代本次声称覆盖的生产边界 → issue
请求级 input_embeddings 在生产模型 forward 中没有任何消费者
get_inputs_embeds、apply_input_embeddings和_reject_input_embeddings仅在基类定义;生产模型仍直接调用embed_tokens。C++ 虽传播新字段,真实 eager 与 Graph forward 均不消费;现有测试由伪模型自行复制覆盖逻辑。 - [P.H] 类型标注 — Optional[X] 表示可为 None 而非参数可选 → issue
类型桩未表达未定义 Tensor 的 None 状态
未定义的 C++torch::Tensor在 Python 中表现为None,生产代码也显式处理该状态;类型桩却将两个 embedding metadata 属性及 setter 声明为必有torch.Tensor。
Strengths
- Prefill CUDA Graph 默认关闭,并对拓扑、MoE 策略、输入容量和 attention backend 设置了严格门禁。
- Graph 状态在 C++、protobuf、Python RPC 和 HTTP JSON 间传播一致,旧 RPC 空字段与旧 pickle 格式均有兼容回退。
- replay metadata、scratch KV 隔离和动态 embedding 描述符具备针对性校验。
| except FileNotFoundError: | ||
| raise argparse.ArgumentTypeError( | ||
| f"Prefill capture file not found: {file_path}" | ||
| f"{config_name} file not found: {file_path}" |
There was a problem hiding this comment.
[P1] 文件缺失错误文案变更导致既有测试确定性失败
默认调用现在产生 prefill_capture_config file not found,既有测试仍大小写敏感地断言 Prefill capture file not found,文件不存在用例会稳定失败。
建议: 默认配置名保留原错误文案,或同步更新既有测试和对外诊断契约。
| "//rtp_llm:testlib", | ||
| ], | ||
| env = {"GPU_COUNT": "1"}, | ||
| tags = ["open_skip", "H20"], |
There was a problem hiding this comment.
[P1] Masked DeepGEMM CUDA Graph 回归测试未形成有效门禁
唯一真实 capture/replay 测试带 open_skip、限定 SM90,且 offset 轮转使每个 expert 在 capture/replay 中始终各接收 32 项,无法发现旧 expert_num_tokens 被复用;SM120 smoke 使用 dense 模型。
建议: 将目标接入 SM90/SM12x CI,并让 replay 使用包含零-token expert 和倾斜负载的不同 histogram。
| if (!enable_cuda_graph_) { | ||
| return false; | ||
| } | ||
| if (isGenerativePrefillCudaGraph()) { |
There was a problem hiding this comment.
[P1] 生成式 prefill 图会静默忽略多模态输入
canRun() 未拒绝非空 embedding_inputs 或 multimodal_inputs;CaptureMemoryHold 未保存这些字段,replay 也不刷新。消费文本 mask、多模态特征或位置的模型会使用捕获态空输入并产生错误结果。
建议: 在生成式 prefill 的 canRun() 中拒绝旧版多模态字段,或实现固定地址更新机制,并增加真实模型的 eager/Graph 对比测试。
| @@ -704,6 +819,7 @@ void PyWrappedModel::prepareAttentionInputs(const GptModelInputs& inputs, bool s | |||
| } | |||
There was a problem hiding this comment.
📍 实际位置 rtp_llm/cpp/models/PyWrappedModel.cc:672(不在 diff 展示范围内,就近挂载)
[P2] 微批路径未传播请求级 input_embeddings
forwardMicroBatched() 构造 PyModelInputs 时未设置 embedding 字段;切分逻辑也未按 token slice 筛选并重定位 embedding 区间。
建议: 按 TokenSliceInfo 切分并重定位 embedding;若暂不支持该组合,应在进入微批路径前明确拒绝。
Checklist: [6.1] 状态不变量:创建/更新/失败/重试/回滚路径有效;[I] 同一功能用统一工具函数
| malloc_info.verbose = false; | ||
| malloc_info.reuse_cache = false; | ||
| malloc_info.enable_device_cache = false; | ||
| if (!cache_manager_->malloc(malloc_info).success) { |
There was a problem hiding this comment.
[P2] Prefill Graph scratch 分配污染真实 cache-hit 指标
scratch 使用合成请求调用普通 KVCacheManager::malloc();该入口即使 reuse_cache=false 仍初始化 cache key,并调用 reportPrefillCacheHitMetrics() 记录请求和 token。
建议: 为内部 scratch 增加跳过请求指标的标志,或使用不经过请求指标的保留块接口。
| @@ -292,7 +348,7 @@ def _parse_prefill_capture_config(config: str) -> List[int]: | |||
| logging.info( | |||
There was a problem hiding this comment.
📍 实际位置 rtp_llm/server/server_args/hw_kernel_group_args.py:344(不在 diff 展示范围内,就近挂载)
[P2] bucket 上限在完整 range 列表分配之后才校验
range 分支先执行 list(range(...)),随后才检查 64 个 bucket 上限;超大 max:step 误配置可能先耗尽启动进程内存。
建议: 构造列表前计算 bucket 数并拒绝超限输入。
Checklist: [6.1] 错误语义:fail-fast/retry/fallback/silent 行为显式
| beam_responses: Optional[List[str]] = None | ||
| pd_sep: Optional[bool] = None | ||
| softmax_probs: Optional[List[float]] = None | ||
| prefill_cuda_graph_status: Optional[str] = None |
There was a problem hiding this comment.
[P2] 普通 smoke 比较器不会校验新增图状态
AuxInfo 已声明 prefill_cuda_graph_status,但 _compare_aux_info() 的普通字段列表未包含它;普通 smoke 中的期望状态与实际状态不一致时不会失败。
建议: 将该字段加入比较列表,并增加状态不一致时必须失败的 comparer 单测。
Checklist: [H] 新增、迁移或删除测试必须证明目标行为仍在 CI 中执行;DISABLED_、#if 0、open_skip、导入即失败、未被 target 引用的用例不得计为覆盖
|
|
||
| cc_library( | ||
| name = "test_cuda_graph_runner_libs", | ||
| srcs = [ |
There was a problem hiding this comment.
📍 实际位置 rtp_llm/cpp/cuda_graph/tests/BUILD:109(不在 diff 展示范围内,就近挂载)
[P2] ROCm 新增 Graph embedding 路径没有可执行覆盖
ROCm 注册了新的 runtime embedding op,PyWrappedModel 也编译 Graph 初始化路径,但新增 generative-prefill 与动态 embedding 测试均固定为 H20;ROCm 的 backend 回退和 metadata 行为没有运行时覆盖。
建议: 增加 ROCm binding/fallback 测试;若生成式 prefill 明确不支持 ROCm,则在配置入口拒绝并测试该契约。
Checklist: [6.1] 分布式/跨平台变更有对应覆盖;[6.1] 新逻辑有聚焦单测 + 相关集成/smoke 测试
| } | ||
| if (isGenerativePrefillCudaGraph()) { | ||
| state.prefill_status = PrefillCudaGraphStatus::NOT_REQUESTED; | ||
| auto fallback = [&](const char* reason, PrefillCudaGraphStatus status) { |
There was a problem hiding this comment.
[P3] prefill 图各回退原因共用同一限频计数器
bucket miss 与所有 canRun() 失败原因共用计数器,且仅在聚合计数为二次幂时记录;多个原因交错时,新原因首次发生可能没有日志。
建议: 按 PrefillCudaGraphStatus 独立计数,或保证每种原因首次出现时记录。
Checklist: [6.1] 可观测性:日志/指标/超时可操作、非噪声
dc0504a to
2988ddc
Compare
LLLLKKKK
left a comment
There was a problem hiding this comment.
AI Code Review - PR #1360
Status: BLOCKING
Summary: P0/0 · P1/2 · P2/13 · P3/8
Reviewed: commit 2988ddc25de5 · 2026-09-01 15:46 UTC+8
Blocking Issues
P1
- init_prefill 多传一个位置参数,测试确定性抛出 TypeError @
rtp_llm/cpp/cuda_graph/tests/cuda_graph_tagged_cache_test.py:333- 建议:删除多余实参;若传统 prefill 也需要 position-id factor,请同步扩展 C++ 方法签名与 pybind
py::arg。
- 建议:删除多余实参;若传统 prefill 也需要 position-id factor,请同步扩展 C++ 方法签名与 pybind
- generative prefill 用例未启用 position-id 捕获且期望公式与 fake model 不一致 @
rtp_llm/cpp/cuda_graph/tests/cuda_graph_tagged_cache_test.py:350- 建议:传入
position_id_len_factor=3,将期望改为(input_ids + combo_position_ids[:, :1]) * 10 + hidden_offsets,并另加一个 factor=0(不带 position ids)的用例。
- 建议:传入
Non-blocking Suggestions
P2
- prefill CUDA Graph 后端选择闸门缺少任何真实路径测试 @
rtp_llm/models_py/modules/factory/attention/attn_factory.py:43- 建议:新增无 GPU 的 factory 单测:注册安全/不安全 stub 后端,参数化覆盖默认 mode、非法 mode、高优先级 graph-unsafe 拒绝、
supports_prefill_cuda_graph()==False与无候选场景。
- 建议:新增无 GPU 的 factory 单测:注册安全/不安全 stub 后端,参数化覆盖默认 mode、非法 mode、高优先级 graph-unsafe 拒绝、
- PD 分离的最终响应会丢失 prefill CUDA Graph 状态 @
rtp_llm/cpp/model_rpc/QueryConverter.cc:514- 建议:在 PD 上下文保存 prefill 侧状态,并在回填循环中覆盖所有远端 decode 输出;补充流式与非流式跨节点用例。
- 默认 bucket 上限 160 会让短上下文模型启动失败 @
rtp_llm/cpp/config/ConfigModules.h:267- 建议:按
max_seq_len裁剪默认 bucket 列表,仅对用户显式越界配置 fail-fast,并将校验条件与prefill_cuda_graph_requested_(含allow_cuda_graph)对齐。
- 建议:按
- 非 CUDA 分支在单 KV 组下不清零 position-id padding 尾部 @
rtp_llm/cpp/cuda_graph/cuda_graph_runner.cc:451- 建议:把清零移到
has_tagged_cache判断之外,与 CUDA 分支保持同一层级,并复用共享 helper;补充非 CUDA 平台的单组 replay 覆盖。
- 建议:把清零移到
- moe_config 在多个生产构造点被静默丢失 @
rtp_llm/cpp/models/ModelTypes.h:67- 建议:所有生产构造点显式传递对应
moe_config;并用具名构造函数或 designated initializer 替代易漏字段的长聚合初始化。
- 建议:所有生产构造点显式传递对应
- 模式值与错误分类依赖跨语言自由文本 @
rtp_llm/cpp/models/PyWrappedModel.h:507- 建议:定义专用异常类型(如
PrefillCudaGraphUnsupportedBackend)并按异常类型分类;至少集中定义 mode/标记常量并补一条 C++/Python 契约测试。
- 建议:定义专用异常类型(如
- 静态准入与 scratch 失败被统一压成 capture_unavailable @
rtp_llm/cpp/models/PyWrappedModel.h:454- 建议:为 scratch 不可用、模型不支持、MoE 策略不支持分别设置对应的请求可见状态,使 aux_info 与日志原因一致。
- Prefill Graph scratch 分配污染真实 prefill cache-hit 指标 @
rtp_llm/cpp/models/PyWrappedModel.cc:154- 建议:在 cache 层提供常驻 block 预留接口(不生成 cache key、不上报请求级指标),供图 scratch 使用。
- prepared 标志守卫建立前访问了可能抛异常的输入 @
rtp_llm/cpp/models/PyWrappedModel.cc:864- 建议:在函数入口先建立
PreparedFlagGuard,再执行 buffer 持有与输入读取;或把这些访问移入受保护的try范围。
- 建议:在函数入口先建立
- 请求可见 prefill 状态机与粘滞语义缺少回归测试 @
rtp_llm/cpp/models/PyWrappedModel.cc:955- 建议:参数化覆盖全部状态映射,并在 stream 测试中追加一次默认 decode update,断言最终输出仍为
replayed。
- 建议:参数化覆盖全部状态映射,并在 stream 测试中追加一次默认 decode update,断言最终输出仍为
- masked DeepGEMM replay 未改变每个 expert 的 token 数 @
rtp_llm/models_py/modules/factory/fused_moe/impl/cuda/executors/test/deepgemm_masked_executor_v2_cuda_graph_test.py:119- 建议:replay 使用明显倾斜且包含零-token expert 的路由分布,再与 eager 结果比较。
- Prefill CUDA Graph 端到端门禁缺少多请求、多 bucket 与 SM90 覆盖 @
rtp_llm/test/smoke/suites_sm120.bzl:32- 建议:增加多请求、多 bucket 的 smoke 用例,并在 SM90 套件中复用同样的
prefill_cuda_graph_status=replayed门禁。
- 建议:增加多请求、多 bucket 的 smoke 用例,并在 SM90 套件中复用同样的
- Python RPC 状态转换缺少滚动升级兼容测试 @
rtp_llm/cpp/model_rpc/model_rpc_client.py:787- 建议:参数化补两条断言:字段为
replayed时原样透传;字段未设置时回落not_requested。
- 建议:参数化补两条断言:字段为
P3
- 批次 token 总量超限被分类为 input_metadata_invalid @
rtp_llm/cpp/cuda_graph/cuda_graph_runner.cc:1056- 建议:拆分为单请求合法性校验与批次容量校验,后者返回
INPUT_TOKENS_EXCEED_CAPTURE_LIMIT。
- 建议:拆分为单请求合法性校验与批次容量校验,后者返回
- prefill 图各回退原因共用同一限频计数器 @
rtp_llm/cpp/cuda_graph/cuda_graph_runner.cc:1019- 建议:按状态分桶计数,或在原因首次出现与发生变化时强制打印一次。
- 普通 smoke 比较路径不会校验新增图状态 @
rtp_llm/test/smoke/normal_comparer.py:72- 建议:将字段加入
_compare_aux_info列表并补一条 normal golden;或在文档/校验中明确该字段仅支持required_aux_info门禁。
- 建议:将字段加入
- DSv4 与 DSpARK 覆写忽略 cuda_graph_selection_mode @
rtp_llm/models_py/model_desc/deepseek_v4_model.py:1062- 建议:收到
"prefill_graph"时显式抛出可结构化识别的不支持异常,待实现能力检查后再返回对应实现。
- 建议:收到
- reset() 后 forward 会解引用空 runner @
rtp_llm/cpp/cuda_graph/tests/cuda_graph_test_runner.cc:133- 建议:删除无调用方的接口;若保留,则同步重置
state_,并在未初始化时抛出明确异常。
- 建议:删除无调用方的接口;若保留,则同步重置
- Dirty capture 重抛时资源缺少事务式清理 @
rtp_llm/cpp/models/PyWrappedModel.h:501- 建议:用 RAII(
unique_ptr+ reservation guard)管理 runner 与 scratch;若脏 capture 必须终止进程,请显式落实该语义。
- 建议:用 RAII(
- pickle 新字段插入旧元组中间导致布局脆弱 @
rtp_llm/cpp/pybind/ConfigInit.cc:794- 建议:新字段追加到旧布局尾部,或引入显式版本号;并补一条"合法长度但字段类型错位"的解包测试。
- range 模式新增的 bucket 上限校验没有测试 @
rtp_llm/server/server_args/hw_kernel_group_args.py:356- 建议:参数化补充 range(如
'6400:1')、文件模式与 64/65 桶边界用例。
- 建议:参数化补充 range(如
Checklist Findings (16 fail / 58 total)
General Principles Checklist
- [6.1] Architecture — 兼容性:外部 HTTP/RPC API、持久数据、配置、环境迁移安全 → issue
pickle 新字段插入旧元组中间导致布局脆弱
三个新字段被插到旧索引 8 之前,反序列化通过t.size()==17判别并对后续字段统一加offset=3。当前长度 14/17 可区分,但相邻字段类型相近(bool/int/vector),未来再插字段时偏移错误可能通过类型转换、静默产生错误配置。 - [6.1] Architecture — 分层边界:新概念在正确层级,不泄漏内部 → issue
Prefill Graph scratch 分配污染真实 prefill cache-hit 指标
scratch 用伪造的GenerateInput与专用 request id 调用完整的KVCacheManager::malloc(PyWrappedModel.cc:138-157)。该调用是首次 malloc,会初始化 cache key 并触发reportPrefillCacheHitMetrics(KVCacheManager.cc:314、325-335),把一个非真实请求写入 prefill 命中率统计窗口。 - [6.1] Architecture — 可观测性:日志/指标/超时可操作、非噪声 → issue
prefill 图各回退原因共用同一限频计数器
所有 fallback 原因(含 886 的 token 超限)共享prefill_cuda_graph_fallback_log_count_,只在计数为二次幂时打印。某个高频原因累计后,新出现的其他原因可能长时间没有任何日志,线上定位困难。 - [6.1] Architecture — 回滚路径:风险行为存在运维回滚手段 → issue
默认 bucket 上限 160 会让短上下文模型启动失败
默认 bucket 列表最大为 160,而 PyWrappedModel.h:240-245 硬性要求每个 bucket ≤max_seq_len,校验失败即构造抛错。用户只打开开关而不显式配置 bucket 时,max_seq_len < 160的模型会直接加载失败,而allocatePrefillCudaGraphScratch(PyWrappedModel.cc:126-131)本已具备同类条件的优雅降级分支,无法被触达。该校验还只看hw_kernel_config.enable_prefill_cuda_graph,未考虑 wrapper 的allow_cuda_graph,MTP/embedding wrapper 也会被卡住。 - [6.1] Architecture — 状态不变量:创建/更新/失败/重试/回滚路径有效 → issue
Dirty capture 重抛时资源缺少事务式清理
scratch 已通过allocatePrefillCudaGraphScratch预留后,DirtyCudaGraphCaptureError分支直接throw(501-505),未调用releasePrefillCudaGraphScratch()。构造函数未完成时类析构函数不会执行,裸prefill_graph_runner_与 scratch reservation 无法通过现有路径释放。虽然该分支的语义是让初始化失败、进程随之退出,但资源所有权仍不显式。 - [6.1] Architecture — 错误语义:fail-fast/retry/fallback/silent 行为显式 → issue
DSv4 与 DSpARK 覆写忽略 cuda_graph_selection_mode
两个prepare_fmha_impl覆写接收cuda_graph_selection_mode但完全不读取,仍按is_cuda_graph返回 decode 专用实现。目前 PyWrappedModel.h:451 的!description_.attention_conf.use_mla静态门禁阻止 MLA 进入 generative prefill,因此不可触达;一旦门禁放宽,覆写会绕过新增的 prefill 安全选择语义。 - [6.1] Software Engineering — DIP:高层策略不依赖非必要具体细节 → issue
模式值与错误分类依赖跨语言自由文本
C++ 用字面量"prefill_graph"调用 Python(cuda_graph_runner.cc:164),Python 抛出带"unsupported_backend: ..."文案的RuntimeError(attn_factory.py:135/143/260/278),C++ 再以std::strstr(e.what(), "unsupported_backend")分类。任一侧措辞调整都会把"后端不支持"静默误报为capture_unavailable,且没有跨语言契约测试锁定该约定。 - [6.1] Software Engineering — DRY:重复非平凡逻辑被抽取或显式复用 → issue
Dirty capture 重抛时资源缺少事务式清理
scratch 已通过allocatePrefillCudaGraphScratch预留后,DirtyCudaGraphCaptureError分支直接throw(501-505),未调用releasePrefillCudaGraphScratch()。构造函数未完成时类析构函数不会执行,裸prefill_graph_runner_与 scratch reservation 无法通过现有路径释放。虽然该分支的语义是让初始化失败、进程随之退出,但资源所有权仍不显式。 - [6.1] Software Engineering — KISS/YAGNI:无投机性抽象 → issue
pickle 新字段插入旧元组中间导致布局脆弱
三个新字段被插到旧索引 8 之前,反序列化通过t.size()==17判别并对后续字段统一加offset=3。当前长度 14/17 可区分,但相邻字段类型相近(bool/int/vector),未来再插字段时偏移错误可能通过类型转换、静默产生错误配置。 - [6.1] Software Engineering — LSP:子类/重写保持基类契约 → issue
DSv4 与 DSpARK 覆写忽略 cuda_graph_selection_mode
两个prepare_fmha_impl覆写接收cuda_graph_selection_mode但完全不读取,仍按is_cuda_graph返回 decode 专用实现。目前 PyWrappedModel.h:451 的!description_.attention_conf.use_mla静态门禁阻止 MLA 进入 generative prefill,因此不可触达;一旦门禁放宽,覆写会绕过新增的 prefill 安全选择语义。 - [6.1] Tests — 分布式/跨平台变更有对应覆盖 → issue
Prefill CUDA Graph 端到端门禁缺少多请求、多 bucket 与 SM90 覆盖
唯一 smoke 固定--prefill_cuda_graph_max_requests 1 --prefill_cuda_graph_capture_config '64',仅在 RTX_5000_PRO(SM120) 调度。而后端白名单同时允许 SM90(trt.pysupports_prefill_cuda_graph返回is_sm90() or is_sm12x())。多真实 slot 的 sentinel/前缀和/padding offset 组合与 SM90 启动路径没有任何端到端门禁。 - [6.1] Tests — 新逻辑有聚焦单测 + 相关集成/smoke 测试 → issue
普通 smoke 比较路径不会校验新增图状态
AuxInfo新增prefill_cuda_graph_status后,OpenAI 路径的compare_config.required_aux_info能正常门禁(openai_comparer.py:394-416),但_compare_aux_info的字段列表(normal_comparer.py:385-404)没有该字段。若在 normal golden 的aux_info中写入期望状态,会被静默忽略而非报错。 - [6.1] Tests — 边界 case 覆盖(空、单元素、最大值) → issue
range 模式新增的 bucket 上限校验没有测试
_parse_prefill_capture_config新增了 range 模式(max:step)与文件模式下的max_buckets校验。server_args_test.py只覆盖了逗号列表 65 桶超限与_positive_int("0"),range/文件模式以及恰好 64 桶的边界都没有用例,新分支的分配前校验语义无门禁。
RTP-LLM Checklist
- [I] 代码质量 — 同一功能用统一工具函数 → issue
普通 smoke 比较路径不会校验新增图状态
AuxInfo新增prefill_cuda_graph_status后,OpenAI 路径的compare_config.required_aux_info能正常门禁(openai_comparer.py:394-416),但_compare_aux_info的字段列表(normal_comparer.py:385-404)没有该字段。若在 normal golden 的aux_info中写入期望状态,会被静默忽略而非报错。
Python Static-First Checklist
- [P.A] 静态结构与类型纪律 — 字符串分发用 Enum/Literal → issue
模式值与错误分类依赖跨语言自由文本
C++ 用字面量"prefill_graph"调用 Python(cuda_graph_runner.cc:164),Python 抛出带"unsupported_backend: ..."文案的RuntimeError(attn_factory.py:135/143/260/278),C++ 再以std::strstr(e.what(), "unsupported_backend")分类。任一侧措辞调整都会把"后端不支持"静默误报为capture_unavailable,且没有跨语言契约测试锁定该约定。 - [P.G] 测试规范 — mock/fake/stub 不得替代本次声称覆盖的生产边界 → issue
masked DeepGEMM replay 未改变每个 expert 的 token 数
_make_inputs用(token_ids+offset) % 4与(token_ids+offset+1) % 4做轮转路由。64 token、4 expert、top-k=2 时,capture(offset=0)与 replay(offset=2)都让每个 expert 恰好得到 32 个 token。若expert_num_tokens/masked_m被固化为捕获值,输出仍与 eager 一致,测试无法暴露问题。
Strengths
- 特性默认关闭(
enable_prefill_cuda_graph=false),且强制要求ENABLE_CUDA_GRAPH=1与单卡拓扑,回滚面小。 - attention 后端选择整体 fail-closed:基类
supports_prefill_cuda_graph()默认 False,未显式声明安全的后端不会被捕获。 - sentinel scratch KV 将 padding 写入与真实请求块隔离,
prefill_scratch_kernel_block_ids设计清晰。 - replay metadata 被抽成 host-only 纯函数并配套 cc_test,容量/计数拒绝分支有表驱动覆盖。
- proto 采用追加字段编号,pickle 兼容旧 14 元组,Python 侧对旧 peer 空串回落
not_requested。 - 新增 smoke 通过
required_aux_info强制断言replayed,避免 eager fallback 被误判为成功。
| def test_generative_prefill_uses_bucket_capacity_without_ratio_gate(self) -> None: | ||
| runner = CudaGraphRunner() | ||
| model = TextOnlyMultimodalCapableModel() | ||
| runner.init_generative_prefill( |
There was a problem hiding this comment.
[P1] generative prefill 用例未启用 position-id 捕获且期望公式与 fake model 不一致
调用未传 position_id_len_factor,pybind 默认 0;此时 runner 不分配捕获 buffer(cuda_graph_runner.cc:1236),validateComboPositionIdsForReplay 直接返回 true 且 copy_numel=0(combo_position_ids_validation.h:16),请求的 position ids 不会拷入图。fake model 计算 (ids+pos)*10+offsets(同文件 84-93),断言却写成 ids*10+pos+offsets(377-383)。两种口径下 ids=0..3 的输出都与断言不符,用例必然失败。
建议: 传入 position_id_len_factor=3,将期望改为 (input_ids + combo_position_ids[:, :1]) * 10 + hidden_offsets,并另加一个 factor=0(不带 position ids)的用例。
| return CudaGraphSelectionMode(mode) | ||
|
|
||
|
|
||
| def _matches_cuda_graph_selection_mode( |
There was a problem hiding this comment.
[P2] prefill CUDA Graph 后端选择闸门缺少任何真实路径测试
_matches_cuda_graph_selection_mode 与 PREFILL_GRAPH 下的 unsupported_backend 拒绝(get_fmha_impl 255-264、get_mla_impl 133-145)是防止捕获 graph-unsafe 后端的唯一闸门。但 attention_input_routing_test.py 整体 mock 了 AttnImplFactory.get_fmha_impl,只断言 kwargs 透传;test_trtllm_fmha_v2_prefill.py 直接构造 FlashInferTRTLLMFMHAv2PrefillImpl。mode 归一化、高优先级不安全后端拒绝、无候选失败均无覆盖,闸门退化不会被任何测试发现。
建议: 新增无 GPU 的 factory 单测:注册安全/不安全 stub 后端,参数化覆盖默认 mode、非法 mode、高优先级 graph-unsafe 拒绝、supports_prefill_cuda_graph()==False 与无候选场景。
| for (const auto accepted_tokens : response.aux_info.speculative_accepted_tokens_per_pos) { | ||
| aux_info->add_speculative_accepted_tokens_per_pos(accepted_tokens); | ||
| } | ||
| aux_info->set_prefill_cuda_graph_status(response.aux_info.prefill_cuda_graph_status); |
There was a problem hiding this comment.
[P2] PD 分离的最终响应会丢失 prefill CUDA Graph 状态
converter 会写入 prefill_cuda_graph_status,但 PD 场景最终响应来自远端 decode。PrefillRpcServer::pollRemoteOutput(PrefillRpcServer.cc:588-608)逐条回填 reuse 长度、首字耗时和 multimodal_lengths,却没有回填该状态;decode 侧默认空串,客户端因此回落 not_requested,真实 replay/fallback 结果对用户不可见。
建议: 在 PD 上下文保存 prefill 侧状态,并在回填循环中覆盖所有远端 decode 输出;补充流式与非流式跨节点用例。
| @@ -1060,7 +1060,10 @@ def _prepare_prefill_hidden( | |||
| return h.unsqueeze(-2).repeat(1, self.v4.hc_mult, 1) | |||
|
|
|||
| def prepare_fmha_impl( | |||
There was a problem hiding this comment.
[P3] DSv4 与 DSpARK 覆写忽略 cuda_graph_selection_mode
两个 prepare_fmha_impl 覆写接收 cuda_graph_selection_mode 但完全不读取,仍按 is_cuda_graph 返回 decode 专用实现。目前 PyWrappedModel.h:451 的 !description_.attention_conf.use_mla 静态门禁阻止 MLA 进入 generative prefill,因此不可触达;一旦门禁放宽,覆写会绕过新增的 prefill 安全选择语义。
建议: 收到 "prefill_graph" 时显式抛出可结构化识别的不支持异常,待实现能力检查后再返回对应实现。
Checklist: [6.1] 错误语义:fail-fast/retry/fallback/silent 行为显式;[6.1] LSP:子类/重写保持基类契约
| description_.ffn_conf.moe_configs.has_value() ? | ||
| params.moe_config.moe_strategy.c_str() : | ||
| "dense"); | ||
| } catch (const DirtyCudaGraphCaptureError& e) { |
There was a problem hiding this comment.
[P3] Dirty capture 重抛时资源缺少事务式清理
scratch 已通过 allocatePrefillCudaGraphScratch 预留后,DirtyCudaGraphCaptureError 分支直接 throw(501-505),未调用 releasePrefillCudaGraphScratch()。构造函数未完成时类析构函数不会执行,裸 prefill_graph_runner_ 与 scratch reservation 无法通过现有路径释放。虽然该分支的语义是让初始化失败、进程随之退出,但资源所有权仍不显式。
建议: 用 RAII(unique_ptr + reservation guard)管理 runner 与 scratch;若脏 capture 必须终止进程,请显式落实该语义。
Checklist: [6.1] 状态不变量:创建/更新/失败/重试/回滚路径有效;[6.1] DRY:重复非平凡逻辑被抽取或显式复用
| if max_seq_len <= 0 or step <= 0: | ||
| raise ValueError("max_seq_len and step must be positive integers") | ||
| bucket_count = (max_seq_len + step - 1) // step | ||
| if max_buckets is not None and bucket_count > max_buckets: |
There was a problem hiding this comment.
[P3] range 模式新增的 bucket 上限校验没有测试
_parse_prefill_capture_config 新增了 range 模式(max:step)与文件模式下的 max_buckets 校验。server_args_test.py 只覆盖了逗号列表 65 桶超限与 _positive_int("0"),range/文件模式以及恰好 64 桶的边界都没有用例,新分支的分配前校验语义无门禁。
建议: 参数化补充 range(如 '6400:1')、文件模式与 64/65 桶边界用例。
Checklist: [6.1] 边界 case 覆盖(空、单元素、最大值)
2988ddc to
8b137d7
Compare
LLLLKKKK
left a comment
There was a problem hiding this comment.
AI Code Review - PR #1360
Status: BLOCKING
Summary: P0/0 · P1/3 · P2/5 · P3/0
Reviewed: commit 8b137d7283aa · 2026-09-01 18:20 UTC+8
Blocking Issues
P1
- Prefill CUDA Graph 初始化破坏 CPU 与 ARM 编译 @
rtp_llm/cpp/models/PyWrappedModel.h:512- 建议:将 Prefill runner 创建及异常处理整体置于 GPU 条件编译内,并增加 CPU 或 ARM 编译门禁。
- DeepSeek-V4 忽略 prefill graph 后端选择契约 @
rtp_llm/models_py/model_desc/deepseek_v4_model.py:1062- 建议:在
PREFILL_GRAPH模式下抛出PrefillCudaGraphUnsupportedBackend;若要支持该模型,应先提供 graph-safe 后端并增加真实 capture/replay 测试。
- 建议:在
- Masked DeepGEMM 图回放测试未进入持续门禁 @
rtp_llm/models_py/modules/factory/fused_moe/impl/cuda/executors/test/BUILD:31- 建议:移除真实回放测试的
open_skip并接入 H20 门禁,同时增加启用该 MoE strategy 且断言实际 replay 的 smoke。
- 建议:移除真实回放测试的
Non-blocking Suggestions
P2
- 两阶段 canRun 产生虚假及重复 fallback 观测 @
rtp_llm/cpp/cuda_graph/cuda_graph_runner.cc:979- 建议:使预准备资格探测无日志、无计数副作用,或传入有效位置数据,并补充 prepare→forward 交互测试。
- Prefill Graph 数值配置缺少严格边界校验 @
rtp_llm/server/server_args/hw_kernel_group_args.py:7- 建议:为最大请求数和每个 bucket 设置业务上限及 C++
int范围校验,任一非法元素均应使启动失败,并补充边界测试。
- 建议:为最大请求数和每个 bucket 设置业务上限及 C++
- 补齐 PACKED_QKV 的 prefill graph 契约测试 @
rtp_llm/models_py/modules/factory/attention/cuda_impl/test/test_trtllm_fmha_v2_prefill.py:271- 建议:将动态 prefill graph 用例参数化为 GQA 与 MHA,并分别验证输出、RoPE 和 KV cache。
- 端到端 smoke 未覆盖多请求 Prefill Graph 回放 @
rtp_llm/test/smoke/suites_h20_oss.bzl:210- 建议:增加受控双请求并发 smoke,断言两个请求均 replay,并确认进入同一批图回放。
- CUDA Graph 测试未声明直接依赖 @
rtp_llm/cpp/cuda_graph/tests/BUILD:116- 建议:为该
py_test显式添加//rtp_llm/models_py:modules。
- 建议:为该
Checklist Findings (9 fail / 140 total)
General Principles Checklist
- [6.1] Architecture — 依赖方向:无循环依赖/跨层惊喜 → issue
CUDA Graph 测试未声明直接依赖
cuda_graph_tagged_cache_test.py直接导入attn_factory.CudaGraphSelectionMode,但目标未声明//rtp_llm/models_py:modules,当前依靠其他测试依赖的传递可见性获得该模块。 - [6.1] Architecture — 兼容性:外部 HTTP/RPC API、持久数据、配置、环境迁移安全 → issue
Prefill Graph 数值配置缺少严格边界校验
_positive_int不限制 C++int范围;越界值的 pybind 赋值异常会被绑定层记录后忽略,INT_MAX还会在 C++ 的max_requests + 1中溢出。bucket 列表和文件解析也会静默丢弃非正数或非法行。 - [6.1] Architecture — 可观测性:日志/指标/超时可操作、非噪声 → issue
两阶段 canRun 产生虚假及重复 fallback 观测
异步预准备使用顶层输入为空的探测对象;mRoPE 请求会因缺少combo_position_ids先计一次 fallback,实际 forward 携带位置数据后却可 replay。其他拒绝原因也可能在 prepare 与 forward 两次有副作用的canRun中重复计数。 - [6.1] Architecture — 错误语义:fail-fast/retry/fallback/silent 行为显式 → issue
Prefill Graph 数值配置缺少严格边界校验
_positive_int不限制 C++int范围;越界值的 pybind 赋值异常会被绑定层记录后忽略,INT_MAX还会在 C++ 的max_requests + 1中溢出。bucket 列表和文件解析也会静默丢弃非正数或非法行。 - [6.1] Software Engineering — LSP:子类/重写保持基类契约 → issue
DeepSeek-V4 忽略 prefill graph 后端选择契约
DeepSeek-V4 使用 MQA,可通过 C++ 非 MLA 准入,但该覆盖忽略PREFILL_GRAPH并对普通 prefill 返回None。这不会在捕获前触发 unsupported fallback,随后真实 prefill 内的 GPU.item()等操作会在 capture 阶段失败,形成 dirty capture 并中止模型初始化。 - [6.1] Tests — 分布式/跨平台变更有对应覆盖 → issue
Masked DeepGEMM 图回放测试未进入持续门禁
生产准入允许fp8_per_block_no_dp_masked,但唯一验证动态 routing、空 expert 及 eager/replay 一致性的真实图测试带有open_skip;strategy 测试同样被跳过且仅 mock 能力判断,新增 smoke 又使用 dense 模型。 - [6.1] Tests — 新逻辑有聚焦单测 + 相关集成/smoke 测试 → issue
端到端 smoke 未覆盖多请求 Prefill Graph 回放
H20 与 SM120 smoke 均配置prefill_cuda_graph_max_requests=2,但共享任务仅包含一个 query,且未启用并发测试。现有底层测试覆盖动态槽位,但 scheduler 到响应状态的真实多请求组批链路仍无端到端门禁。 - [6.1] Tests — 边界 case 覆盖(空、单元素、最大值) → issue
端到端 smoke 未覆盖多请求 Prefill Graph 回放
H20 与 SM120 smoke 均配置prefill_cuda_graph_max_requests=2,但共享任务仅包含一个 query,且未启用并发测试。现有底层测试覆盖动态槽位,但 scheduler 到响应状态的真实多请求组批链路仍无端到端门禁。
Python Static-First Checklist
- [P.G] 测试规范 — mock/fake/stub 不得替代本次声称覆盖的生产边界 → issue
Masked DeepGEMM 图回放测试未进入持续门禁
生产准入允许fp8_per_block_no_dp_masked,但唯一验证动态 routing、空 expert 及 eager/replay 一致性的真实图测试带有open_skip;strategy 测试同样被跳过且仅 mock 能力判断,新增 smoke 又使用 dense 模型。
Strengths
- Prefill 与 decode 使用独立 graph runner/state,scratch KV 生命周期清晰。
- HTTP、RPC、Python 与 PD 状态传播完整,并兼容旧 RPC 响应。
HWKernelConfig新字段追加至旧 14 元组末尾,兼容历史 pickle。- 动态槽位、sentinel、KV 写入和 fallback 状态已有较完整单元测试。
| prefill_graph_params.prefill_cuda_graph_pad_token_id = 0; | ||
| prefill_graph_params.prefill_scratch_kernel_block_ids = std::move(scratch_kernel_block_ids); | ||
| try { | ||
| prefill_graph_runner_ = |
There was a problem hiding this comment.
[P1] Prefill CUDA Graph 初始化破坏 CPU 与 ARM 编译
cuda_graph_runner.h 仅在 USING_CUDA || USING_ROCM 时包含,但条件块外仍引用 CudaGraphRunner::createForPrefill、DirtyCudaGraphCaptureError 和 PrefillCudaGraphUnsupportedBackendError。CPU/ARM 编译时这些标识符不可见。
建议: 将 Prefill runner 创建及异常处理整体置于 GPU 条件编译内,并增加 CPU 或 ARM 编译门禁。
| @@ -1060,7 +1060,10 @@ def _prepare_prefill_hidden( | |||
| return h.unsqueeze(-2).repeat(1, self.v4.hc_mult, 1) | |||
|
|
|||
| def prepare_fmha_impl( | |||
There was a problem hiding this comment.
[P1] DeepSeek-V4 忽略 prefill graph 后端选择契约
DeepSeek-V4 使用 MQA,可通过 C++ 非 MLA 准入,但该覆盖忽略 PREFILL_GRAPH 并对普通 prefill 返回 None。这不会在捕获前触发 unsupported fallback,随后真实 prefill 内的 GPU .item() 等操作会在 capture 阶段失败,形成 dirty capture 并中止模型初始化。
建议: 在 PREFILL_GRAPH 模式下抛出 PrefillCudaGraphUnsupportedBackend;若要支持该模型,应先提供 graph-safe 后端并增加真实 capture/replay 测试。
Checklist: [6.1] LSP:子类/重写保持基类契约
| "//rtp_llm:testlib", | ||
| ], | ||
| env = {"GPU_COUNT": "1"}, | ||
| tags = ["open_skip", "H20"], |
There was a problem hiding this comment.
[P1] Masked DeepGEMM 图回放测试未进入持续门禁
生产准入允许 fp8_per_block_no_dp_masked,但唯一验证动态 routing、空 expert 及 eager/replay 一致性的真实图测试带有 open_skip;strategy 测试同样被跳过且仅 mock 能力判断,新增 smoke 又使用 dense 模型。
建议: 移除真实回放测试的 open_skip 并接入 H20 门禁,同时增加启用该 MoE strategy 且断言实际 replay 的 smoke。
Checklist: [6.1] 分布式/跨平台变更有对应覆盖;[P.G] mock/fake/stub 不得替代本次声称覆盖的生产边界
| @@ -800,21 +977,45 @@ bool CudaGraphRunner::canReplaySelectedGraph(const PyModelInputs& inputs, const | |||
| size_t copy_numel = 0; | |||
| const auto& captured_position_ids = graph_it->second.mem_hold_.py_model_inputs_.combo_position_ids; | |||
| if (!validateComboPositionIds(inputs, state, captured_position_ids, copy_numel)) { | |||
There was a problem hiding this comment.
[P2] 两阶段 canRun 产生虚假及重复 fallback 观测
异步预准备使用顶层输入为空的探测对象;mRoPE 请求会因缺少 combo_position_ids 先计一次 fallback,实际 forward 携带位置数据后却可 replay。其他拒绝原因也可能在 prepare 与 forward 两次有副作用的 canRun 中重复计数。
建议: 使预准备资格探测无日志、无计数副作用,或传入有效位置数据,并补充 prepare→forward 交互测试。
Checklist: [6.1] 可观测性:日志/指标/超时可操作、非噪声
| from rtp_llm.server.server_args.util import str2bool | ||
|
|
||
|
|
||
| def _positive_int(value: str) -> int: |
There was a problem hiding this comment.
[P2] Prefill Graph 数值配置缺少严格边界校验
_positive_int 不限制 C++ int 范围;越界值的 pybind 赋值异常会被绑定层记录后忽略,INT_MAX 还会在 C++ 的 max_requests + 1 中溢出。bucket 列表和文件解析也会静默丢弃非正数或非法行。
建议: 为最大请求数和每个 bucket 设置业务上限及 C++ int 范围校验,任一非法元素均应使启动失败,并补充边界测试。
Checklist: [6.1] 兼容性:外部 HTTP/RPC API、持久数据、配置、环境迁移安全;[6.1] 错误语义:fail-fast/retry/fallback/silent 行为显式
|
|
||
| torch.testing.assert_close(graph_output, expect_output, rtol=0, atol=0) | ||
|
|
||
| def test_prefill_cuda_graph_rope_kv_and_dynamic_batch(self): |
There was a problem hiding this comment.
[P2] 补齐 PACKED_QKV 的 prefill graph 契约测试
生产实现对 MHA 使用 PACKED_QKV、对 GQA/MQA 使用 CONTIGUOUS_Q_KV,但动态 graph 用例固定 head_num=8, head_num_kv=2,只覆盖后一分支。
建议: 将动态 prefill graph 用例参数化为 GQA 与 MHA,并分别验证输出、RoPE 和 KV cache。
| native.test_suite( | ||
| name = "smoke_h20_dense", | ||
| tests = [ | ||
| smoke_test( |
There was a problem hiding this comment.
[P2] 端到端 smoke 未覆盖多请求 Prefill Graph 回放
H20 与 SM120 smoke 均配置 prefill_cuda_graph_max_requests=2,但共享任务仅包含一个 query,且未启用并发测试。现有底层测试覆盖动态槽位,但 scheduler 到响应状态的真实多请求组批链路仍无端到端门禁。
建议: 增加受控双请求并发 smoke,断言两个请求均 replay,并确认进入同一批图回放。
Checklist: [6.1] 新逻辑有聚焦单测 + 相关集成/smoke 测试;[6.1] 边界 case 覆盖(空、单元素、最大值)
|
|
||
| cc_library( | ||
| name = "test_cuda_graph_runner_libs", | ||
| srcs = [ |
There was a problem hiding this comment.
📍 实际位置 rtp_llm/cpp/cuda_graph/tests/BUILD:116(不在 diff 展示范围内,就近挂载)
[P2] CUDA Graph 测试未声明直接依赖
cuda_graph_tagged_cache_test.py 直接导入 attn_factory.CudaGraphSelectionMode,但目标未声明 //rtp_llm/models_py:modules,当前依靠其他测试依赖的传递可见性获得该模块。
建议: 为该 py_test 显式添加 //rtp_llm/models_py:modules。
Checklist: [6.1] 依赖方向:无循环依赖/跨层惊喜