Skip to content

feat: support full prefill CUDA graphDevelop/full prefill cuda graph - #1360

Open
siluzhou wants to merge 1 commit into
mainfrom
develop/full-prefill-cuda-graph
Open

feat: support full prefill CUDA graphDevelop/full prefill cuda graph#1360
siluzhou wants to merge 1 commit into
mainfrom
develop/full-prefill-cuda-graph

Conversation

@siluzhou

Copy link
Copy Markdown
Collaborator
  • Add full-prefill CUDA Graph support for eligible generative models.

@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 #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 结论都不可信。

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 以便事后定位分歧点。

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 出错时会指向错误的旋钮名。
  • 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 + 1cu_seqlens.size() >= max_request_count + 2padding_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_requestsprefix_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_ratio0.375 以暴露 int/double 位置写反);三是按 :105 范式补 15/16 应抛 Invalid state 的用例。建议用 subTest 表驱动覆盖三个受支持尺寸。
  • 新增 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)正确。
  • supportsFullPrefillCudaGraphMoe 的多数合取项没有负例 @ rtp_llm/cpp/models/test/ModelDataTest.cc:179
    • 建议:改为表驱动:以 happy-path config 为基线,每行一个「字段名 + 变异值」,在循环中逐一 EXPECT_FALSE,尤其补齐 use_mori_epuse_deepep_p2p_low_latencyhack_moe_experttop_k > expert_numextra_expert_num != 0,保证每个合取项都有唯一对应负例——这些条件正是阻止在 graph-unsafe collective 或分布式拓扑上捕获整图的闸门,后续增删条件时测试应同步暴露。
  • 新增 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,请注明它仅为分支内临时格式及清理时间点。
  • 19 元素默认桶列表在三处硬编码,其 25% padding 不变量无测试守护 @ rtp_llm/server/server_args/hw_kernel_group_args.py:62
    • 建议:以 C++ 结构体默认值为唯一来源:--full_prefill_capture_configdefault 改为 None,仅在显式配置时覆盖 full_prefill_capture_seq_lens;测试断言改为读取 HWKernelConfig().full_prefill_capture_seq_lens 而非复制字面量。并追加一条按 (bucket - prev - 1) / bucket <= max_padding_ratio 校验默认桶序列的用例,把注释里的不变量固化成可执行断言。
  • 析构函数重复调用 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++ 代码即可知道前置条件。
  • 本角色下 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++ 侧导出的尺寸常量,或至少提取为带来源注释的命名常量,使布局契约变更时失败信息可自解释。

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-275enable_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-162resource->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-275enable_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-162resource->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 仅含 ScopedEnvFlagstreamAsyncReplayPrepEnabledaddCudaGraphPrepareFillRegion*inferTotalTokensNoSyncgetInputEmbeddingSourceType),也不在其 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 屏蔽信号、MtpExecutor 6 个早退点),二是请求级 input_embeddings 覆盖特性。此外 ConfigModules.hPrefillCPConfig::is_enabledKVCacheConfigFIFOSchedulerConfig::decode_prefill_ratioGrammarConfig 四处产生纯空白/对齐改动,与本特性无关,应是整文件 clang-format 的副产物。分片路由也因此出现盲区:NormalEngine.cc/.hNormalExecutor.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 屏蔽信号、MtpExecutor 6 个早退点),二是请求级 input_embeddings 覆盖特性。此外 ConfigModules.hPrefillCPConfig::is_enabledKVCacheConfigFIFOSchedulerConfig::decode_prefill_ratioGrammarConfig 四处产生纯空白/对齐改动,与本特性无关,应是整文件 clang-format 的副产物。分片路由也因此出现盲区:NormalEngine.cc/.hNormalExecutor.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-1064deepseek_v4_dspark_model.py:193 与测试 stub cuda_graph_tagged_cache_test.py:42pywrapped_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 + 1cu_seqlensmax_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_fp8is_deep_gemm_e8m0_usedrequant_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_embeddingsgetattr(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 之类断言意外通过。测试 :34416 + 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-1064deepseek_v4_dspark_model.py:193 与测试 stub cuda_graph_tagged_cache_test.py:42pywrapped_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_embeddingsgetattr(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 之类断言意外通过。测试 :34416 + 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=1seq_len=4,故恒有 real_request_count==1real_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+1cu_seqlensmax_bs_+1input_lengths[Bmax]cu_seqlens[Bmax+1] 恰为末位合法元素,当前无越界。
  • PyWrappedModel.h:477-489cuda_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-271hw_kernel_group_args.py:36-62config_pickle_test.py:132)逐项一致;默认稀疏桶序列经实算最坏 padding 约 0.2436 ≤ 注释声明的 0.25,不会出现「开了特性却每次都因 padding 超限回退」。
  • pickle 的 18 个字段在结构体、def_readwrite__getstate__ 三处完整对齐;14/17/18 三种长度的 offset(0/3/4) 索引映射逐槽位复算正确、无越界无错位。
  • 特性默认关闭、门槛严格、失败统一降级 eager,回滚只需清空一个 env,运维风险面小。

Comment thread rtp_llm/cpp/cuda_graph/cuda_graph_runner.cc Outdated
@@ -829,17 +937,19 @@ GptModelOutputs PyWrappedModel::forward(const GptModelInputs& inputs) {
torch::Tensor hidden_states;

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/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 → 不走图」。

Comment thread rtp_llm/cpp/normal_engine/NormalEngine.cc Outdated

@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 #1360 (non-blocking suggestions)

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

inline bool supportsFullPrefillCudaGraphMoe(const GptModelDescription& description,
const ParallelismConfig& parallelism_config,
const MoeConfig& moe_config) {
if (!description.ffn_conf.moe_configs.has_value()) {

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] 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-424valid_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(

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] 新增 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_requeststype=int(:44)、--full_prefill_cuda_graph_max_padding_ratiotype=float(:53)。真正的取值域校验在 PyWrappedModel.h:421-424size() <= 64max_requests > 0ratio ∈ [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() {

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] 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-162resource->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) {

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] 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-1064deepseek_v4_dspark_model.py:193 与测试 stub cuda_graph_tagged_cache_test.py:42pywrapped_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

Comment thread rtp_llm/cpp/cuda_graph/combo_position_ids_validation.h Outdated
Comment thread rtp_llm/server/server_args/hw_kernel_group_args.py Outdated
}
releaseFullPrefillScratch();
// Always release py_model_ since it's always initialized now
py_model_.release();

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] 析构函数重复调用 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] 逻辑变更未混入无关格式化

Comment thread rtp_llm/cpp/config/ConfigModules.h Outdated
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()) {

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/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:重复非平凡逻辑被抽取或显式复用

Comment thread rtp_llm/models_py/model_desc/module_base.py Outdated
@rtp-llm-review-bot

rtp-llm-review-bot commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

PR #1360 第 6 轮评审 — BLOCKED(1)

  • 标题:feat: support full prefill CUDA graphDevelop/full prefill cuda graph(@siluzhou,open)
  • head 8b137d7283aa · base f1a54a5f1171 · delta 33 文件 · discover 5/5 成功

阻塞项(P0/P1,support≥3 且过全部门)

[P1] generative prefill 捕获时对空的 input_hiddens 做越界 slice

  • rtp_llm/cpp/cuda_graph/cuda_graph_runner.cc:1691 · 类别 correctness · 票数 3/5 · 首见 r2
  • 依据:因果链完整成立。(1) initCapture 中生成式 prefill 分支将 input_hiddens 置空:cuda_graph_runner.cc:1488 inputs.input_hiddens = torch::empty({0}, options_cuda_float_);(role==GENERATIVE_PREFILL,见 cuda_graph_runner.h:160-161)。(2) capture_mem_hold_ 继承该空张量:cuda_graph_runner.cc:1555 capture_mem_hold_ = CaptureMemoryHold(output, inputs, ...),其构造函数 cuda_graph_utils.h 中 py_model_inputs_.input_hiddens = inputs.input_hiddens; 原样拷贝。(3) capturePrefill 首轮即调用 prepareCaptureInputs:cuda_graph_prefill.cc:20 prepareCaptureInputs(inputs, max_bs_, seq_len)。(4) prepareCaptureInputs 无任何 generative 分支保护,仍执行 cuda_graph_runner.cc:1693 inputs.input_hiddens = capture_mem_hold_.py_model_inputs_.input_hiddens.slice(0, 0, token_slice_len);(该行为既有未改行)。对生成式 prefill,cuda_graph_runner.cc:1690 fixed_capacity_draft_prefill = usesFixedCapacityMtpDraftPrefillCudaGraph() 为 false(cuda_graph_runner.h:163 要求 isMtpDraftPrefillCudaGraph()),故 token_slice_len = seq_len_or_tokens = 桶 seq_len(>0,来自 prefill_capture_seq_lens_,见 getPrefillSequenceLengthsToCapture 校验 result.front()>0)。对 size=0 的 1D 张量执行 slice(0,0,seq_len) 触发 libtorch 'end out of range' 越界异常,使 capturePrefill→initCapture 抛出。触发条件:启用 enable_prefill_cuda_graph 且生成式 prefill 捕获(任意桶 seq_len>0),首次捕获即必现。该异常发生在 captureOneGraphInstance 设置 capture_session_may_be_dirty_=true 之前,故走 createForPrefill 的普通 rethrow,被 PyWrappedModel 构造函数 catch(std::exception) 吞掉并回退 eager(prefill_cuda_graph_init_status_=CAPTURE_UNAVAILABLE),导致整个生成式 prefill CUDA graph 特性永远无法捕获、静默失效。
  • 建议:对 generative prefill 跳过 input_hiddens 的 slice(例如 if (!isGenerativePrefillCudaGraph()) inputs.input_hiddens = ...slice(...);,保持其为空张量),与 initCapture 中“不分配该 buffer”的意图一致。
  • 事实核验:hold — verified(initCapture 在 generative prefill 分支将 inp);verified(capture_mem_hold_ 继承该空张量(CaptureMemoryHo);verified(capturePrefill 首轮即调用 prepareCaptureInput);verified(prepareCaptureInputs 无 generative 分支保护,仍);verified(对 generative prefill,fixed_capacity_draf);verified(桶 seq_len > 0(seq_len_or_tokens 来自 captu);unverifiable(对 size=0 的 1D 张量执行 slice(0,0,seq_len)(se);verified(generative prefill 捕获在生产配置下可达(enable_pre)

非阻塞发现(并集报告:单票也保留,降级不丢弃)

  • [P2] prefill_cuda_graph_status_ 写入不加锁而读取加锁,PD 分离下存在数据竞争 — rtp_llm/cpp/normal_engine/NormalGenerateStream.cc:222 · 票数 2/5
  • [P2] 非 CUDA 分支对 combo_position_ids 清零了错误区间(active 头部而非 padding 尾部) — rtp_llm/cpp/cuda_graph/cuda_graph_runner.cc:465 · 票数 2/5
  • [P2] 多卡/TP 配置下启用 ENABLE_PREFILL_CUDA_GRAPH 直接抛异常导致模型加载失败,而非优雅降级 — rtp_llm/cpp/models/PyWrappedModel.h:202 · 票数 1/5
  • [P2] supports_prefill_cuda_graph 缺少 hasattr/基类默认保护,AttributeError 会以错误状态降级 — rtp_llm/models_py/modules/factory/attention/attn_factory.py:47 · 票数 1/5
  • [P3] prefill_cuda_graph_init_status_ 未在 pre-capture 校验失败时更新,导致对外状态恒为 capture_unavailable — rtp_llm/cpp/models/PyWrappedModel.h:455 · 票数 3/5
  • [P3] FileNotFoundError 分支中 config_name 判断冗余 — rtp_llm/server/server_args/hw_kernel_group_args.py:330 · 票数 2/5
  • [P3] prefill_cuda_graph_pad_token_id 硬编码为 0,未从模型 tokenizer 配置获取 — rtp_llm/cpp/models/PyWrappedModel.h:509 · 票数 2/5
  • [P3] canRun 在一次请求中被调用多次,fallback 计数器被重复累加,fallback_count 指标失真 — rtp_llm/cpp/cuda_graph/cuda_graph_runner.cc:1030 · 票数 2/5

已确认修复(recheck)

  • [P1] PyWrappedModel 析构函数对 py_model_ 重复调用 release() — 析构函数(当前第248-264行)中仅剩第264行一处 py_model_.release(),diff 已删除原第233行处的重复 release,双释放问题已消除。
  • [P3] capture 失败路径故意泄漏 runner,缺少更强的保护性说明 — createForPrefill/createForDecode 的 catch 分支(1721-1725、1749-1753)已补充详细日志说明泄漏原因,并抛出专用 DirtyCudaGraphCaptureError 异常,保护性说明已加强
  • [P1] forward 未将 input_embeddings 传入 py_model_inputs,eager 路径丢失输入嵌入且 graph 路径可能用陈旧 metadata 重放 — forward 在 PyWrappedModel.cc:942-950 构造 py_model_inputs 后新增 propagateRequestInputEmbeddings(py_model_inputs, inputs),补齐了 input_embeddings/input_embeddings_locs,且 prepareAttentionInputs:832、updateKVCacheKernelBlockId:861 均改用同一 helper 保持三个边界一致。
  • [P2] stop_started_/notifyStop/synchronizeForShutdown 等新成员与方法的声明未出现在补丁中,存在编译失败风险 — 当前代码第524-530行 NormalEngine::stop() 仅设置 running_、调用 scheduler_->stop() 并 join,未使用 stop_started_/notifyStop/synchronizeForShutdown,因此不存在因缺失声明导致的编译失败风险。
  • [P3] getPrefillSequenceLengthsToCapture 新增校验在空 bucket 时对 result.front()/back() 解引用为 UB — 第 174 行新增了 RTP_LLM_CHECK_WITH_INFO(!prefill_capture_seq_lens_.empty(), ...) 前置校验,保证 result 非空后再于第 184 行解引用 front()/back()
  • [P2] 非 generative 图在存在 input_embeddings 时被禁用,可能是 decode 性能回退 — 当前 canRun(1066-1189 行)已无 if (!isGenerativePrefillCudaGraph() && inputs.input_embeddings.has_value() && !inputs.input_embeddings->empty()) return false; 分支,本轮 diff 已删除该检查。
  • [P3] DirtyCudaGraphCaptureError 分支未释放已分配的 prefill scratch KV cache — 当前代码 524-527 行新增注释明确说明 DirtyCudaGraphCaptureError 分支有意不释放 scratch,因进程随后退出,不再构成资源泄漏。
  • [P1] test_generative_prefill_uses_bucket_capacity_without_ratio_gate 未传 position_id_len_factor 且 expected 公式与模型 forward 不一致,断言必然失败 — 测试已改为传 position_id_len_factor=3(第363行)并设置 combo_position_ids,expected 公式改为 (input_ids + combo_position_ids[:, :1]) * 10 + arange(第384-392行、410-418行),与 forward 一致;factor=0 场景由新增 test_generative_prefill_without_combo_position_ids(第460-482行)单独覆盖。原问题已消除。

本轮 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 8b137d7283aa · BLOCKED(1) · 同一 PR 的结论就地更新这一条评论

@LLLLKKKK LLLLKKKK left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Code Review - PR #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_embedsapply_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() 的确定性竞态测试。
  • 新增 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_lenstep 和尾项补齐规则计算 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_libsdeps 中直接加入 //rtp_llm/models_py/bindings/common:common
  • 类型桩未表达未定义 Tensor 的 None 状态 @ rtp_llm/ops/librtp_compute_ops/__init__.pyi:387
    • 建议:将这两个属性及其 setter 声明为 torch.Tensor | None
  • 单个 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_intint(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_intint(value) 对非数字输入抛出 ValueError;混合 CLI/env 解析路径会静默吞掉该异常。因此 PREFILL_CUDA_GRAPH_MAX_REQUESTS=abc 回退为默认值 8,而 =0 会正确报错,同一参数的非法输入语义不一致。
  • [P.G] 测试规范 — mock/fake/stub 不得替代本次声称覆盖的生产边界 → issue 请求级 input_embeddings 在生产模型 forward 中没有任何消费者
    全仓中 get_inputs_embedsapply_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 会映射为 Python Nonemodule_base.py 也显式判空;但 input_embeddings_locscuda_graph_input_embedding_runtime_meta 的 getter/setter 均声明为必有 torch.Tensor,与运行时契约矛盾。

Strengths

  • CudaGraphRole 显式区分 decode、embedding prefill 与生成式 prefill,降低状态混用风险。
  • 哨兵 scratch block 避免 padding token 的 KV 写入污染真实缓存。
  • preparePrefillCudaGraphReplayMetadata 抽为纯函数,并覆盖容量不足、非法长度与零长度边界。
  • HWKernelConfig pickle 同时支持当前 17 元组和历史 14 元组布局。
  • CudaGraphSelectionMode 使用 Enum 并对未知值快速失败,prepare_fmha_impl 覆写已同步。
  • 生成式 prefill 功能默认关闭,保留明确的运维回滚开关。

Comment thread rtp_llm/models_py/model_desc/module_base.py Outdated
Comment thread rtp_llm/cpp/normal_engine/speculative/MtpExecutor.cc Outdated
Comment thread rtp_llm/server/server_args/test/server_args_test.py Outdated
except FileNotFoundError:
raise argparse.ArgumentTypeError(
f"Prefill capture file not found: {file_path}"
f"{config_name} file not found: {file_path}"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 文件缺失错误文案变更导致既有测试确定性失败

默认分支现在产生 prefill_capture_config file not found,但既有测试仍以区分大小写的方式断言 Prefill capture file not found。两者大小写和措辞均不匹配,该测试会稳定失败。

建议: 同步更新测试期望;若旧文案属于外部诊断契约,则对默认配置保留原文案,仅为新配置使用参数化名称。

@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 #1360 (non-blocking suggestions)

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

@@ -704,6 +819,7 @@ void PyWrappedModel::prepareAttentionInputs(const GptModelInputs& inputs, bool s
}

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/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] 同一功能用统一工具函数

Comment thread rtp_llm/server/server_args/hw_kernel_group_args.py Outdated
@@ -292,7 +348,7 @@ def _parse_prefill_capture_config(config: str) -> List[int]:
logging.info(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

[P2] bucket 上限在完整 range 列表分配之后才校验

range 分支先执行 list(range(...)),随后才检查 64 个 bucket 的上限。形如 100000000:1 的配置会先物化巨型列表,可能在启动阶段耗尽内存,而非快速返回配置错误。

建议: 在构造列表前根据 max_seq_lenstep 和尾项补齐规则计算 bucket 数量,超限立即拒绝。

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

device="cuda",
dtype=torch.bfloat16,
)
token_ids = torch.arange(self.NUM_TOKENS, device="cuda")

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 图重放测试没有改变各 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 (

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] 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 = [

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/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_libsdeps 中直接加入 //rtp_llm/models_py/bindings/common:common

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

Comment thread rtp_llm/ops/librtp_compute_ops/__init__.pyi Outdated
Comment thread rtp_llm/cpp/normal_engine/NormalEngine.cc
Comment thread rtp_llm/cpp/cuda_graph/cuda_graph_runner.cc Outdated
Comment thread rtp_llm/server/server_args/hw_kernel_group_args.py Outdated
@siluzhou
siluzhou force-pushed the develop/full-prefill-cuda-graph branch from 7d45702 to dc0504a Compare September 1, 2026 04:08

@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 #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 对比测试。

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
  • 测试库直接引用的符号未声明直接 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_mode RPC 演进;三者调用链、测试矩阵和回滚风险相互独立。
  • [6.1] Quality — PR description 说明动机与设计 → issue 单个 PR 混装三条独立主线,增加评审与回归定位成本
    76 个文件同时包含生成式 Prefill CUDA Graph、MTP/DSpARK 生命周期调整及 return_all_probs_mode RPC 演进;三者调用链、测试矩阵和回滚风险相互独立。
  • [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_embedsapply_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") 抛出原始 ValueErrorEnvArgumentParser 会吞掉环境变量转换产生的 ValueError,使非法 PREFILL_CUDA_GRAPH_MAX_REQUESTS 静默回退为默认值 8。
  • [P.G] 测试规范 — mock/fake/stub 不得替代本次声称覆盖的生产边界 → issue 请求级 input_embeddings 在生产模型 forward 中没有任何消费者
    get_inputs_embedsapply_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 描述符具备针对性校验。

Comment thread rtp_llm/models_py/model_desc/module_base.py Outdated
Comment thread rtp_llm/cpp/normal_engine/speculative/MtpExecutor.cc Outdated
Comment thread rtp_llm/server/server_args/test/server_args_test.py Outdated
except FileNotFoundError:
raise argparse.ArgumentTypeError(
f"Prefill capture file not found: {file_path}"
f"{config_name} file not found: {file_path}"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 文件缺失错误文案变更导致既有测试确定性失败

默认调用现在产生 prefill_capture_config file not found,既有测试仍大小写敏感地断言 Prefill capture file not found,文件不存在用例会稳定失败。

建议: 默认配置名保留原错误文案,或同步更新既有测试和对外诊断契约。

Comment thread rtp_llm/models_py/model_desc/module_base.py Outdated
"//rtp_llm:testlib",
],
env = {"GPU_COUNT": "1"},
tags = ["open_skip", "H20"],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 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()) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 生成式 prefill 图会静默忽略多模态输入

canRun() 未拒绝非空 embedding_inputsmultimodal_inputsCaptureMemoryHold 未保存这些字段,replay 也不刷新。消费文本 mask、多模态特征或位置的模型会使用捕获态空输入并产生错误结果。

建议: 在生成式 prefill 的 canRun() 中拒绝旧版多模态字段,或实现固定地址更新机制,并增加真实模型的 eager/Graph 对比测试。

@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 #1360 (non-blocking suggestions)

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

@@ -704,6 +819,7 @@ void PyWrappedModel::prepareAttentionInputs(const GptModelInputs& inputs, bool s
}

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/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) {

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] Prefill Graph scratch 分配污染真实 cache-hit 指标

scratch 使用合成请求调用普通 KVCacheManager::malloc();该入口即使 reuse_cache=false 仍初始化 cache key,并调用 reportPrefillCacheHitMetrics() 记录请求和 token。

建议: 为内部 scratch 增加跳过请求指标的标志,或使用不经过请求指标的保留块接口。

Comment thread rtp_llm/server/server_args/hw_kernel_group_args.py Outdated
@@ -292,7 +348,7 @@ def _parse_prefill_capture_config(config: str) -> List[int]:
logging.info(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

📍 实际位置 rtp_llm/server/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 行为显式

Comment thread rtp_llm/ops/librtp_compute_ops/__init__.pyi Outdated
beam_responses: Optional[List[str]] = None
pd_sep: Optional[bool] = None
softmax_probs: Optional[List[float]] = None
prefill_cuda_graph_status: Optional[str] = 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.

[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 = [

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/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 测试

Comment thread rtp_llm/cpp/normal_engine/NormalEngine.cc
}
if (isGenerativePrefillCudaGraph()) {
state.prefill_status = PrefillCudaGraphStatus::NOT_REQUESTED;
auto fallback = [&](const char* reason, PrefillCudaGraphStatus status) {

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] prefill 图各回退原因共用同一限频计数器

bucket miss 与所有 canRun() 失败原因共用计数器,且仅在聚合计数为二次幂时记录;多个原因交错时,新原因首次发生可能没有日志。

建议:PrefillCudaGraphStatus 独立计数,或保证每种原因首次出现时记录。

Checklist: [6.1] 可观测性:日志/指标/超时可操作、非噪声

Comment thread rtp_llm/server/server_args/hw_kernel_group_args.py Outdated
@siluzhou
siluzhou force-pushed the develop/full-prefill-cuda-graph branch from dc0504a to 2988ddc Compare September 1, 2026 06:51

@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 #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
  • 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 与无候选场景。
  • 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
  • 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 门禁。
  • 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 必须终止进程,请显式落实该语义。
  • 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 桶边界用例。

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.py supports_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 被误判为成功。

Comment thread rtp_llm/cpp/cuda_graph/tests/cuda_graph_tagged_cache_test.py Outdated
def test_generative_prefill_uses_bucket_capacity_without_ratio_gate(self) -> None:
runner = CudaGraphRunner()
model = TextOnlyMultimodalCapableModel()
runner.init_generative_prefill(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[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)的用例。

@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 #1360 (non-blocking suggestions)

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

return CudaGraphSelectionMode(mode)


def _matches_cuda_graph_selection_mode(

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] prefill CUDA Graph 后端选择闸门缺少任何真实路径测试

_matches_cuda_graph_selection_modePREFILL_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);

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] 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 输出;补充流式与非流式跨节点用例。

Comment thread rtp_llm/cpp/config/ConfigModules.h Outdated
Comment thread rtp_llm/cpp/cuda_graph/cuda_graph_runner.cc Outdated
Comment thread rtp_llm/cpp/models/ModelTypes.h Outdated
@@ -1060,7 +1060,10 @@ def _prepare_prefill_hidden(
return h.unsqueeze(-2).repeat(1, self.v4.hc_mult, 1)

def prepare_fmha_impl(

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] 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:子类/重写保持基类契约

Comment thread rtp_llm/cpp/cuda_graph/tests/cuda_graph_test_runner.cc Outdated
description_.ffn_conf.moe_configs.has_value() ?
params.moe_config.moe_strategy.c_str() :
"dense");
} catch (const DirtyCudaGraphCaptureError& e) {

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] 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:重复非平凡逻辑被抽取或显式复用

Comment thread rtp_llm/cpp/pybind/ConfigInit.cc Outdated
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:

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] 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 覆盖(空、单元素、最大值)

@siluzhou
siluzhou force-pushed the develop/full-prefill-cuda-graph branch from 2988ddc to 8b137d7 Compare September 1, 2026 09:41

@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 #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 范围校验,任一非法元素均应使启动失败,并补充边界测试。
  • 补齐 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_ =

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] Prefill CUDA Graph 初始化破坏 CPU 与 ARM 编译

cuda_graph_runner.h 仅在 USING_CUDA || USING_ROCM 时包含,但条件块外仍引用 CudaGraphRunner::createForPrefillDirtyCudaGraphCaptureErrorPrefillCudaGraphUnsupportedBackendError。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(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 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"],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 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 不得替代本次声称覆盖的生产边界

@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 #1360 (non-blocking suggestions)

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

@@ -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)) {

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] 两阶段 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:

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] 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):

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] 补齐 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(

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] 端到端 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 = [

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/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] 依赖方向:无循环依赖/跨层惊喜

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.

3 participants