Prhub

#6672 [ci] chore: add two-node Ascend RayJob E2E workflow

原始 PR 作者 wangdongleix 合并时间 2026-06-16 15:23 文件变更 2 提交数 1 评论 3 代码增减 +307 / -0

执行摘要

新增双节点 Ascend NPU RayJob E2E CI 工作流

该项目缺少 Ascend NPU 上的多节点 E2E 测试,此 PR 旨在填补该空白,通过在 CI 中定期运行双节点 PPO 训练来提前发现多节点环境下的兼容性故障。PR body 中提及 "Search for similar PRs" 表明该工作流是经过调研后新增的。

建议 CI 维护者仔细检查工作流依赖的基础设施是否稳定,并考虑采纳 review 中关于 shell 脚本健壮性的建议。对于希望了解 verl 如何集成 RayJob 进行多节点测试的开发者,此 PR 提供了一份可参考的实现。

讨论亮点
  • Shell 脚本健壮性:gemini-code-assist[bot] 建议在 shell 脚本中启用 set -xeuo pipefail 以尽早捕获错误,并对所有变量引用添加双引号,特别是 SKIP_STEPS='[1]' 可能因通配符展开导致意外行为。该建议未被标记为 resolved,但 PR 最终获得批准,表明团队可能接受当前简化写法或将在后续迭代中修复。

实现拆解

  1. 新增 CI 工作流 (.github/workflows/e2e_ascend_multinode.yml):定义 workflow 触发的条件(main 分支 push/PR,限定特定路径),设置并发和权限。主要 job ascend-rayjob-e2e 在专用 runner 上运行,使用容器执行。步骤包括:检出代码、准备共享缓存、创建 submit_and_wait_rayjob.sh 辅助脚本(用于通过 REST API 提交 RayJob 并等待其完成,同时流式输出日志)、最后执行测试脚本并检查最终状态。
  2. 新增测试脚本 (tests/special_npu/run_qwen3_06b_ppo_multinode.sh):该脚本封装了多节点 PPO 训练命令,使用 verl 的 main_ppo 入口,配置为 Qwen3-0.6B 模型、2 节点各 4 GPU、训练 1 步。脚本使用环境变量允许外部覆盖模型路径等参数,并利用 skip.rollout 功能跳过 rollout 阶段以加速测试(仅验证训练流程完整性)。脚本中包含了必要的 FSDP 配置、Ulysses 序列并行等 Ascend 常用设置。
  3. 集成方式:工作流在 runner 上通过 kubectl 或类似工具与 RayJob 提交器交互(由辅助脚本实现),将测试脚本作为 RayJob 的任务提交,等待完成后根据失败标志决定 CI 结论。工作流会流式输出 RayJob 的日志到 GitHub Actions 日志,方便调试。
文件 模块 状态 重要度
.github/workflows/e2e_ascend_multinode.yml CI 配置 added 6.09
tests/special_npu/run_qwen3_06b_ppo_multinode.sh 测试脚本 added 5.52

关键源码片段

tests/special_npu/run_qwen3_06b_ppo_multinode.sh test-coverage

测试脚本,实际运行多节点 PPO 训练命令,是工作流的核心负载。

#!/usr/bin/env bash
# set -x # 开启命令回显(调试用)
# 注意:未使用 set -euo pipefail,review 建议添加
​
MODEL_ID=${MODEL_ID:-Qwen/Qwen3-0.6B}
MODEL_PATH=${MODEL_PATH:-${HOME}/.cache/models/${MODEL_ID}}
​
SKIP_ENABLE=True
SKIP_DUMP_DIR=${SKIP_DUMP_DIR:-${HOME}/data/rollout_dump_sync}
SKIP_STEPS='[1]' # 方括号在未引用的变量中可能被 shell 视为 glob 模式
SKIP_ACTION=cache
​
python3 -m verl.trainer.main_ppo \
    algorithm.adv_estimator=gae \
    data.train_files="${HOME}/data/gsm8k/train.parquet" \
    data.val_files="${HOME}/data/gsm8k/test.parquet" \
    data.train_batch_size=16 \
    data.max_prompt_length=512 \
    data.max_response_length=128 \
    data.shuffle=False \
    actor_rollout_ref.model.path="${MODEL_PATH}" \
    actor_rollout_ref.model.use_remove_padding=True \
    actor_rollout_ref.model.enable_gradient_checkpointing=True \
    actor_rollout_ref.actor.optim.lr=1e-6 \
    actor_rollout_ref.actor.ppo_mini_batch_size=8 \
    actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu=1 \
    actor_rollout_ref.actor.fsdp_config.param_offload=True \
    actor_rollout_ref.actor.fsdp_config.optimizer_offload=True \
    actor_rollout_ref.actor.use_kl_loss=False \
    actor_rollout_ref.actor.ulysses_sequence_parallel_size=2 \
    actor_rollout_ref.actor.use_dynamic_bsz=True \
    actor_rollout_ref.actor.use_torch_compile=False \
    actor_rollout_ref.rollout.log_prob_micro_batch_size_per_gpu=1 \
    actor_rollout_ref.rollout.tensor_model_parallel_size=1 \
    actor_rollout_ref.rollout.name=vllm \
    actor_rollout_ref.rollout.gpu_memory_utilization=0.8 \
    actor_rollout_ref.rollout.log_prob_use_dynamic_bsz=True \
    actor_rollout_ref.rollout.enable_chunked_prefill=True \
    actor_rollout_ref.rollout.enforce_eager=False \
    +actor_rollout_ref.rollout.engine_kwargs.vllm.compilation_config.cudagraph_mode="FULL_AND_PIECEWISE" \
    critic.optim.lr=1e-5 \
    critic.model.use_remove_padding=True \
    critic.model.path="${MODEL_PATH}" \
    critic.model.enable_gradient_checkpointing=True \
    critic.ppo_micro_batch_size_per_gpu=1 \
    critic.ulysses_sequence_parallel_size=2 \
    critic.fsdp.param_offload=True \
    critic.fsdp.optimizer_offload=True \
    critic.use_dynamic_bsz=True \
    trainer.critic_warmup=0 \
    trainer.logger='["console"]' \
    trainer.project_name='verl_ppo_example_gsm8k_qwen3' \
    trainer.experiment_name='qwen3_06b_fsdp_multinode_2x4' \
    trainer.n_gpus_per_node=4 \
    trainer.nnodes=2 \
    trainer.save_freq=-1 \
    trainer.test_freq=-1 \
    trainer.total_epochs=1 \
    trainer.total_training_steps=1 \
    skip.rollout.enable=${SKIP_ENABLE} \
    skip.rollout.dump_dir=${SKIP_DUMP_DIR} \
    skip.rollout.steps=${SKIP_STEPS} \
    skip.rollout.action=${SKIP_ACTION} \
    "$@"

评论区精华

Shell 脚本健壮性建议:启用 set -u 和引用变量 正确性

gemini-code-assist[bot] 指出测试脚本中缺少 `set -xeuo pipefail`,且变量引用未加双引号,特别是 `SKIP_STEPS=[1]` 可能被 shell 解释为 glob 模式,建议修改。

结论:该建议未被作者采纳或回应,但 PR 最终获得批准,可能团队认为测试脚本在受控环境下可接受当前写法。 · unresolved

风险与影响

  • 外部依赖风险:工作流依赖 SUBMITTER_URL(RayJob 提交器服务)和预构建的 Docker 镜像 IMAGE。如果这些基础设施不可用或版本不匹配,工作流将失败,且难以通过单元测试覆盖。
  • Shell 脚本健壮性:测试脚本未启用 set -u,未引用变量可能因通配符导致意外行为;未设置 pipefail 可能掩盖管道错误。这些在非预期环境中可能导致误通过或难以调试的失败。
  • 环境限制:工作流仅在特定 runner (linux-aarch64-a2b3-8) 上运行,该 runner 可能不具备通用性,在其他环境无法直接复现。
  • 用户影响:无。不修改任何运行时代码或 API。
  • 系统影响:增加一个 CI 工作流,在 main 分支 push 和 PR 时触发(限定路径),增加 CI 资源消耗,但仅针对 Ascend 相关变更。
  • 团队影响:为 Ascend CI 维护者提供了多节点测试覆盖,有助于提前发现多节点兼容性问题。
外部依赖 Shell 脚本健壮性 环境限制

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论