Prhub

#6126 [misc] refactor: re-format examples and deprecate old examples

原始 PR 作者 ETOgaosion 合并时间 2026-04-30 11:37 文件变更 323 提交数 16 评论 26 代码增减 +9719 / -16589

执行摘要

重构并规范化示例目录,清理旧脚本,统一命名规范

PR body 明确指出:“Current examples are too heavy, some old ones may need to patch or modify verl's main-stream code and already placed in recipe, clear useless and repeated examples with feature tests.” 其目标是让 examples 仅包含可直接使用主分支功能的轻量脚本,便于用户快速上手和社区维护。

建议在合并前对部分脚本中缺失的变量定义进行补充(如 actor_cp、actor_etp),并确保所有文档中的链接均已更新指向新路径。此外,建议尽快完成 NPU 脚本的统一对齐工作。该 PR 的设计思路(环境变量驱动的标准化脚本)值得后续新算法示例借鉴。

讨论亮点

Review 中核心讨论包括:

  • 未定义变量问题:gemini-code-assist 指出新脚本中多处使用了 actor_cp、actor_etp 等变量但未在用户可调整部分定义,执行时可能失败(文件:run_qwen3_30b_a3b_megatron.sh 等)。
  • 命名规范未完全对齐:sglang_multiturn 目录下的脚本名仍包含特征词(如 tool_agent、mlflow),与规范冲突,后通过删除该目录解决。
  • NPU 脚本暂不修改:wucong25 提议暂时不调整 NPU 定制脚本,作者同意待后续统一对齐。
  • 整体测试覆盖:tardis-key 询问是否测试了所有修改脚本,作者回应需社区协助验证配置可用性。
  • DEVICE 自动检测:wuxibin89 建议用 pip3 show torch-npu 自动识别设备类型,避免用户手动设置。
  • 弃用参数清理:wuxibin89 指出应移除 actor_rollout_ref.rollout.mode=async 等已弃用的 Hydra 配置项。

实现拆解

  1. 定义目录结构与命名规范:按算法名称(ppo_trainer、grpo_trainer 等)组织子目录,规范脚本文件名格式为 run__.sh,通过环境变量暴露所有可调参数,禁止在文件名中体现具体数据集或特征。
  2. 重写所有示例脚本:将原有分散的、硬编码的脚本替换为规范化的模板,每组算法只保留一个 canonical 脚本,并支持通过 DEVICE、INFER_BACKEND、MACHINE 等环境切换 GPU/NPU 及后端(vLLM、SGLang、TensorRT-LLM)。
  3. 删除旧例子和废弃目录:移除 fapo_trainer、split_placement、sglang_multiturn/search_r1_like 等目录,涉及的奖励函数、数据预处理、检索服务器、monkey patch 训练循环等文件一并删除,其功能要么已由主分支直接支持,要么已迁入 recipe 目录。
  4. 添加命名规范检查测试:在 tests/special_sanity 下新增 check_example_naming.py,包含规范化检查函数和对应的 pytest 用例,并由 pre-commit 钩子触发,确保后续新增脚本命名合规。
  5. 同步更新文档与配置:更新 docs/start/agentic_rl.rst、docs/ascend_tutorial 等文档中的脚本链接和路径,调整相关 CI 配置和 config 文件的 Hydra 参数。
文件 模块 状态 重要度
examples/grpo_trainer/run_qwen3_8b_fsdp.sh 示例脚本 added 7.44
tests/special_sanity/check_example_naming.py 命名检查 added 8.24
tests/special_sanity/test_check_example_naming.py 测试工具 added 7.51
examples/fapo_trainer/reward_fn.py 奖励函数 removed 8.36
examples/sglang_multiturn/search_r1_like/local_dense_retriever/retrieval_server.py 检索服务器 removed 8.5
examples/split_placement/main_ppo_split.py 训练脚本 removed 8.68
docs/start/agentic_rl.rst 文档 modified 3.0

关键符号

check_filename collect_scripts _split_tokens _is_ignored verify compute_score_baseline compute_score_fapo post_request _select_rm_score_fn RewardManager.__call__ Encoder.encode BaseRetriever toolcall_shaping_reward compute_score

关键源码片段

examples/sglang_multiturn/search_r1_like/local_dense_retriever/retrieval_server.py deletion

删除的多轮检索示例核心文件,体现对本地检索流水线的清理

# 以下代码来自 examples/sglang_multiturn/search_r1_like/local_dense_retriever/retrieval_server.py,
# 该文件在本 PR 中被删除,因为其功能已被更通用的架构替代。
# 这里展示 Encoder 类实现,供理解旧检索流程参考。class Encoder:
    """检索编码器,支持 e5、bge 等模型,提供查询/文档编码及池化。"""
    def __init__(self, model_name, model_path, pooling_method, max_length, use_fp16):
        self.model_name = model_name
        self.model_path = model_path
        self.pooling_method = pooling_method
        self.max_length = max_length
        self.use_fp16 = use_fp16
        self.model, self.tokenizer = load_model(model_path=model_path, use_fp16=use_fp16)
        self.model.eval()
​
    @torch.no_grad()
    def encode(self, query_list: list[str], is_query=True) -> np.ndarray:
        if isinstance(query_list, str):
            query_list = [query_list]
        # e5 模型使用 query/passage 前缀
        if "e5" in self.model_name.lower():
            prefix = "query: " if is_query else "passage: "
            query_list = [prefix + q for q in query_list]
        # bge 模型使用指令前缀
        if "bge" in self.model_name.lower() and is_query:
            query_list = [
                f"Represent this sentence for searching relevant passages: {q}" for q in query_list
            ]
        inputs = self.tokenizer(
            query_list, max_length=self.max_length, padding=True, truncation=True, return_tensors="pt"
        )
        inputs = {k: v.cuda() for k, v in inputs.items()}
        if "T5" in type(self.model).__name__:
            decoder_input_ids = torch.zeros((inputs["input_ids"].shape[0], 1), dtype=torch.long).to(
                inputs["input_ids"].device
            )
            output = self.model(**inputs, decoder_input_ids=decoder_input_ids, return_dict=True)
            query_emb = output.last_hidden_state[:, 0, :]
        else:
            output = self.model(**inputs, return_dict=True)
            query_emb = pooling(
                output.pooler_output, output.last_hidden_state, inputs["attention_mask"], self.pooling_method
            )
            if "dpr" not in self.model_name.lower():
                query_emb = torch.nn.functional.normalize(query_emb, dim=-1)
        query_emb = query_emb.detach().cpu().numpy().astype(np.float32, order="C")
        del inputs, output
        torch.cuda.empty_cache()
        return query_emb
examples/split_placement/main_ppo_split.py deletion

被删除的 split placement 训练入口,包含自定义 RewardManager 和 monkey patch,体现清理范围

# 以下代码来自 examples/split_placement/main_ppo_split.py,该文件在本 PR 中被删除。
# 展示其自定义 RewardManager 实现,该功能现可由标准 reward 配置替代。class RewardManager:
    def __init__(self, tokenizer, num_examine) -> None:
        self.tokenizer = tokenizer
        self.num_examine = num_examine
​
    def __call__(self, data: DataProto, return_dict: bool = False):
        if "rm_scores" in data.batch.keys():
            return data.batch["rm_scores"]
        reward_tensor = torch.zeros_like(data.batch["responses"], dtype=torch.float32)
        for i in range(len(data)):
            data_item = data[i]
            prompt_ids = data_item.batch["prompts"]
            prompt_length = prompt_ids.shape[-1]
            valid_prompt_length = data_item.batch["attention_mask"][:prompt_length].sum()
            valid_prompt_ids = prompt_ids[-valid_prompt_length:]
            response_ids = data_item.batch["responses"]
            valid_response_length = data_item.batch["attention_mask"][prompt_length:].sum()
            valid_response_ids = response_ids[:valid_response_length]
            sequences = torch.cat((valid_prompt_ids, valid_response_ids))
            sequences_str = self.tokenizer.decode(sequences)
            ground_truth = data_item.non_tensor_batch["reward_model"]["ground_truth"]
            data_source = data_item.non_tensor_batch["data_source"]
            compute_score_fn = _select_rm_score_fn(data_source)
            score = compute_score_fn(solution_str=sequences_str, ground_truth=ground_truth)
            reward_tensor[i, valid_response_length - 1] = score
        if return_dict:
            return {"reward_tensor": reward_tensor}
        else:
            return reward_tensor

评论区精华

脚本中未定义变量 actor_cp / actor_etp 导致运行时失败 正确性

gemini-code-assist 指出 run_qwen3_30b_a3b_megatron.sh 和 run_qwen3_30b_a3b_mindspeed.sh 中使用了未在 user-adjustable 段定义的变量 `actor_cp` 和 `actor_etp`,启用 set -u 后会报错。建议增加默认值。

结论:作者需补充变量定义或调整脚本使其不依赖这些变量。后续提交中可能已部分修复,但 review 时仍存在。 · partially resolved

sglang_multiturn 目录脚本命名未遵循新规范 设计

gemini-code-assist 指出 run_qwen2_5_3b_gsm8k_tool_agent_mlflow_fsdp.sh 等文件名包含数据集和特征词,不符合 `run_<model>_<train-backend>.sh` 规范。

结论:后续提交中删除了 sglang_multiturn 目录,该问题自动解决。 · 已解决

NPU 脚本是否暂不修改以对齐 GPU question

wucong25 建议暂时不要修改 NPU 特定脚本,因为团队计划后续统一调整。

结论:作者同意保持现有 NPU 脚本功能,待后续统一对齐。 · 已解决

测试所有修改脚本的可用性 测试

tardis-key 询问是否会对所有修改的脚本进行测试以确保可用。

结论:作者回应需要社区帮助在不同机器上验证配置,并强调脚本仅为可运行示例,不保证最佳性能。 · 待处理

DEVICE 应自动检测而非用户手动设置 设计

wuxibin89 评论建议使用 `pip3 show torch-npu` 自动检测 NPU,而不是让用户设置 DEVICE 变量,且 `import torch_npu` 较慢。

结论:作者可能已采用建议,在最终脚本中改为自动检测方式。 · 已解决

移除已弃用的 Hydra 参数(async、hybrid_engine) 正确性

wuxibin89 指出 `actor_rollout_ref.rollout.mode=async` 和 `actor_rollout_ref.hybrid_engine=True` 已经废弃,应在脚本中删除。

结论:作者需清理这些配置项,后续提交中可能已处理。 · partially resolved

风险与影响

主要风险包括:

  1. 脚本变量未定义风险:部分新脚本中引用的变量(如 actor_cp)在缺失定义时可能被 shell 的 set -u 设为空或报错,导致运行时失败。
  2. 文档链接失效:大量旧文档中的示例链接指向已删除/重命名的文件,需逐一确认更新。
  3. 删除的示例可能仍有用户依赖:虽然功能已由主分支或 recipe 覆盖,但用户现有工作流若直接引用旧路径会中断。
  4. 命名检查可能遗漏:check_example_naming.py 的规则集可能无法覆盖所有异常情况(如未来新增的后缀 token)。
  5. NPU 脚本兼容性:暂时保留的 NPU 脚本与新规范的统一调整工作尚未完成,可能出现两套风格并存的时期。

用户影响:所有依赖旧 examples 目录结构或脚本名称的用户需迁移至新规范,但新脚本提供更灵活的环境变量控制方式,降低配置成本。团队影响:维护者获得更清晰的示例组织方式和自动化命名检查,减少后续 review 负担。系统影响:无直接核心代码变更,CI 中新增命名检查作业,不影响训练/推理流程。

删除大量示例 新脚本变量定义风险 文档链接需更新 NPU 脚本待后续统一 命名检查可能遗漏

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论