slime 项目 CI 体系深度解析:双层测试架构、GPU E2E 工作流与测试编写实战
2026/9/16 16:00:57 网站建设 项目流程

slime 项目 CI 体系深度解析:双层测试架构、GPU E2E 工作流与测试编写实战

【免费下载链接】slimeslime is an LLM post-training framework for RL Scaling.项目地址: https://gitcode.com/GitHub_Trending/slime12/slime

本篇技术指南聚焦 slime(LLM post-training / RL Scaling 训练框架)仓库的持续集成体系:它如何以「默认运行的 CPU 正确性测试 + label 触发的 GPU end-to-end 测试」双层结构,在快速反馈与真实 Megatron + SGLang 训练路径覆盖之间取得平衡。读完本文,你将掌握 slime CI 的完整工作原理、每个 label 与 job 的触发关系、prepare()/execute()e2e 测试编写范式、GPU 锁申请机制,以及如何通过 Jinja2 模板安全地修改 CI matrix。

为什么 CI 要拆成两层

slime 的训练与 rollout 涉及 Megatron 训练后端、SGLang 推理引擎、Ray 调度、分布式 checkpoint 等大量组件,完整跑一次训练会消耗大量 GPU 时间。因此 CI 被有意拆成两层,这个设计决策贯穿整个仓库:

  1. 默认运行的 CPU 正确性测试:每个 PR、每次 push 到main、以及手动workflow_dispatch都会运行。它覆盖纯逻辑层面的正确性不变量(correctness invariant),不等待 GPU 集群即可快速检查。
  2. 通过 label 触发的 GPU end-to-end 测试:在自托管 GPU runner 上验证真实的 Megatron + SGLang training/rollout 路径。

正如文档所述,这个拆分是刻意的:大部分 correctness invariant 应该在不等待 GPU 集群的情况下快速检查;真正依赖完整训练和 rollout 的行为,则由 GPU e2e job 覆盖。前者的目标是"快",后者的目标是"真",两者互补而非替代。

工作原理:模板驱动的工作流

slime 的 CI workflow 定义在 .github/workflows/pr-test.yml,但不要直接编辑它——该文件由 Jinja2 模板 .github/workflows/pr-test.yml.j2 自动生成。

CPU Jobs

CPU job 运行在 GitHub-hosted 的ubuntu-latestrunner 上,有两个 job:

  • cpu-unittest:安装 CPU 版 PyTorch 和轻量依赖,然后通过python tests/<test_file>.py运行注册的 unit/contract tests。
  • agent-adapter-test:以同样方式运行 agent adapter tests,但额外安装openaiopenai-agentsanthropic等 provider SDK 依赖(这部分测试被单独拆出,正是因为它们有额外的第三方依赖)。

从模板源码(pr-test.yml.j2)可以看到 CPU job 的依赖安装细节:通过pip install torch --index-url https://download.pytorch.org/whl/cpu安装纯 CPU PyTorch,再安装pytest numpy packaging pyyaml omegaconf tqdm httpx requests ray pybase64 pylatexenc sympy aiohttp pillow safetensors psutil等轻量依赖,最后pip install -e . --no-deps安装 slime 本体。

CPU job不使用 Docker、不申请 GPU、也不会调用tests/ci/gpu_lock_exec.py。执行时通过matrix.info.num_gpus判断:为0时直接运行,否则走 GPU 锁路径。

GPU E2E Jobs

GPU job 运行在自托管 GPU runner上,每个 job 依次执行四个步骤:

  1. 启动 Docker container,通常使用slimerl/slime:latest;镜像验证使用slimerl/slime-test:latest
  2. 在容器内通过pip install -e . --no-deps --break-system-packages安装 slime。
  3. 通过python tests/ci/gpu_lock_exec.py --count <num_gpus>申请所需 GPU。
  4. 执行注册的测试文件:python tests/<test_file>.py

模板中的 Docker 启动参数值得注意,它完整反映了 GPU 训练环境的需求(见 pr-test.yml.j2):

docker run --pull=always --rm \ --privileged \ --cap-add SYS_NICE \ --security-opt seccomp=unconfined \ --network host \ --gpus all \ --ipc=host \ --shm-size=16g \ --ulimit memlock=-1 \ --ulimit stack=67108864 \ --memory=0 \ --memory-swap=0 \ ...

其中--network host用于 Ray/SGLang 的分布式通信、--shm-size=16g保证共享内存充足、--ulimit memlock=-1取消内存锁定限制(Megatron/ NCCL 常用)、--memory=0 --memory-swap=0不限制容器内存。容器还会挂载宿主机共享目录-v /mnt/nvme0n1/slime_ci/models:/root/models-v /mnt/nvme0n1/slime_ci/datasets:/root/datasets,用于缓存已下载的模型与数据集,避免每个 job 重复下载。

GPU 测试通常遵循e2e 模式prepare()下载模型和数据集,execute()构建 CLI 参数并调用U.execute_train(...)U是 slime/utils/external_utils/command_utils.py 的别名)。U.execute_train内部会先清理残留的 sglang/ray/redis/slime 进程,随后ray start --head拉起 Ray 集群,最后通过ray job submit将训练任务提交到集群执行,并注入PYTHONPATHCUDA_DEVICE_MAX_CONNECTIONSNCCL_NVLS_ENABLE(按是否检测到 NVLink 自动设置)等运行时环境变量。

Changed-Test Job

run-ci-changed是一个动态检测机制:它相对origin/main找出新增或修改的tests/test_*.pytests/plugin_contracts/test_*.py文件,并为每个文件构建 matrix。

关键逻辑在模板的e2e-test-changed-detectjob 中:

CHANGED=$(git diff --name-only --diff-filter=AM origin/main...HEAD -- 'tests/test_*.py' 'tests/plugin_contracts/test_*.py' || true) # 对每个文件提取 NUM_GPUS,缺省时默认为 8 NGPU=$(grep -oP '^NUM_GPUS\s*=\s*\K\d+' "$filepath" | head -1) NGPU=${NGPU:-8}

也就是说:如果测试文件缺少NUM_GPUS常量,CI 会默认使用8,因此纯 CPU 测试文件必须显式声明:

NUM_GPUS = 0

changed-test job 本身走 self-hosted Docker 路径;当NUM_GPUS = 0时,它直接运行测试、不申请 GPU。

CI Jobs 与触发方式一览

TriggerJob类型说明
自动运行cpu-unittestCPU默认运行的 unit/contract tests,覆盖 argument validation、schedule、reward、sample、rollout validation、checkpoint utilities 和 plugin contracts。
自动运行agent-adapter-testCPU默认运行的 agent adapter tests,包含额外 provider SDK 依赖。
run-ci-sglang-confige2e-test-sglang-configGPUSGLang config 测试,覆盖高级 rollout engine deployment 和 mixed/offload 场景。
run-ci-megatrone2e-test-megatronGPU核心 Megatron 训练测试,覆盖 dense、MoE、PPO、MTP、OPD、async rollout、PD/Mooncake 和 debug replay 路径。
run-ci-precisione2e-test-precisionGPU数值精度和并行一致性检查。
run-ci-ckpte2e-test-ckptGPUCheckpoint save/load 正确性,包括 CPU/GPU optimizer state 和 async save。
run-ci-imagee2e-test-imageGPUslimerl/slime-test:latest上运行与run-ci-megatron相同的 matrix。
run-ci-changede2e-test-changedMixed只运行 changed tests,并使用每个文件中的NUM_GPUS

也可以在 Actions 页面通过workflow_dispatch手动验证,它会按照 workflow 条件运行注册的 jobs。workflow_dispatch还提供一个infinite_run布尔输入,配合环境变量SLIME_TEST_ENABLE_INFINITE_RUN可以让训练无限运行,用于长时间稳定性验证。

从模板源码可以进一步确认 label 的判定逻辑(pr-test.yml.j2):

if: (github.event_name == 'workflow_dispatch') || (github.event.pull_request && contains(github.event.pull_request.labels.*.name, '<label>'))

即 GPU job 只在两种情况下触发:手动 workflow_dispatch,或 PR 被打上对应 label。而自动 job 的条件是github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.event_name == 'push'。此外 workflow 声明了concurrency分组和cancel-in-progress: true,同一 PR/ref 的新提交会取消旧运行;push 到main专门触发默认的 CPU job,用于捕获"两个 PR 单独通过、合并后 main 损坏"的 PR-pair 回归(见模板顶部注释)。

CPU Unit Tests:正确性第一道防线

CPU suite 是 correctness 的第一道防线,用来在进入昂贵 GPU run 之前捕获 silent RL infrastructure bugs。

当前注册的 CPU suite 覆盖(对应 pr-test.yml.j2 中cpu-unittest的 tests 列表,共 50+ 个文件):

  • Megatron argument 和 HF config validation(如 tests/test_megatron_argument_validation.py);
  • DP/CP scheduling utilities 和 CP loss invariance(tests/test_dp_schedule.py、tests/test_cp_utils.py、tests/test_loss_cp_invariance.py);
  • metric reporting 和 distributed metric aggregation(tests/test_metric_report.py、tests/test_metric_report_dist.py);
  • math、GPQA、F1、DeepScaler、DAPO-style math 等 reward-model grading utilities(tests/test_rm_math.py、tests/test_rm_gpqa.py、tests/test_rm_f1.py、tests/test_rm_deepscaler.py、tests/test_rm_math_dapo.py);
  • Sample行为、rollout validation 和 agent trajectory merging(tests/test_sample.py、tests/test_process_rollout_data.py);
  • HF checkpoint saver 行为(tests/utils/test_hf_checkpoint_saver.py);
  • rollout function、generate function、runtime hook 和 path loading 的 customization hook contracts(tests/plugin_contracts/test_plugin_generate_contracts.py 等四个 contract 测试);
  • 另有 PPO loss/advantage/whiten、FP8、stateless Adam、layerwise alignment、reloadable process group、placement group、expert routing 等基础设施测试。

Agent adapter tests(tests/test_agent/ 下的 5 个文件)单独放在agent-testjob 中,因为它们需要额外 SDK 依赖。

常用本地命令(在仓库根目录直接执行):

python tests/test_agent/test_trajectory_manager_branching.py python -m pytest tests/test_megatron_argument_validation.py tests/plugin_contracts/test_plugin_generate_contracts.py

值得说明的是,CPU 测试文件普遍以顶层NUM_GPUS = 0声明自身不需要 GPU(见 tests/test_megatron_argument_validation.py 第 9 行),这样即便被run-ci-changed动态拾取,也只会走纯 CPU 路径。

GPU E2E Tests:验证 CPU 测不到的真实训练路径

GPU e2e tests 验证 CPU tests 无法覆盖的集成训练/rollout 行为:

  • run-ci-sglang-config:高级 SGLang deployment path,包括 config-based engine layouts(tests/test_sglang_config_mixed_offload.py、tests/test_qwen2.5_0.5B_sglang_config_distributed.py 等 6 个文件)。
  • run-ci-megatron:主要 Megatron backend coverage。从模板中的megatron_tests变量可以看到其矩阵规模——约 25 条测试条目,覆盖 dense/MoE recipe(tests/test_qwen3_30B_A3B.py、tests/test_moonlight_16B_A3B.py)、async rollout(tests/test_qwen2.5_0.5B_fully_async_short.py)、OPD(tests/test_qwen2.5_0.5B_opd_sglang.py)、PPO-style path(tests/test_qwen3_4B_ppo.py、tests/test_qwen3_4B_ppo_disaggregate.py)、PD/Mooncake(tests/test_qwen3.6_35B_A3B_pd_mooncake.py)和 debug rollout-then-train replay(tests/test_qwen2.5_0.5B_debug_rollout_then_train.py)。单个 job 还可以携带test_args(如 checkpoint 测试的 optimizer state 组合)、use_deepepuse_fp8_rolloutenable_eval等 matrix 附加字段。
  • run-ci-precision:不同并行设置下的数值一致性(tests/test_qwen3_0.6B_parallel_check.py)。
  • run-ci-ckpt:checkpoint save/load 组合和 async save——同一测试文件 tests/test_qwen3_4B_ckpt.py 以不同test_args注册 5 次:--save-optimizer gpu --load-optimizer gpugpu/cpucpu/cpucpu/gpu四种 optimizer state 迁移组合,外加--async-save异步保存。
  • run-ci-image:与run-ci-megatron相同的 matrix,但运行在slimerl/slime-test:latestrelease/test image 上,用于验证新镜像没有破坏既有测试。

日常 PR 优先使用 targeted labels。run-ci-image消耗 GPU 时间较多(相当于把整个 megatron matrix 重跑一遍),应谨慎使用。

GPU 锁机制:gpu_lock_exec 深入

GPU job 的核心步骤之一是 tests/ci/gpu_lock_exec.py,它在共享的自托管 GPU 机器上实现基于fcntl.flock的文件锁调度。其命令行接口:

python tests/ci/gpu_lock_exec.py --count <num_gpus> [--devices 0,1] [--total-gpus 8] [--timeout 86400] \ [--target-env-name CUDA_VISIBLE_DEVICES] [--lock-path-pattern "/dev/shm/custom_gpu_lock_{gpu_id}.lock"] \ [--print-only] -- <command>

主要参数与行为:

  • --count:申请任意N个空闲 GPU;--devices则指定具体的 GPU id 列表(二者互斥)。
  • --total-gpus:宿主机 GPU 总数,默认8(即单台 H100 节点的标准配置)。
  • --timeout:等待锁的超时秒数,默认 24 小时;超时抛TimeoutError
  • --target-env-name:默认将获取到的设备列表写入CUDA_VISIBLE_DEVICES环境变量(模板中的默认用法),也可以改成其他变量名。
  • --lock-path-pattern:锁文件路径模式,默认/dev/shm/custom_gpu_lock_{gpu_id}.lock(放在/dev/shm是 tmpfs,进程退出自动清理,避免残留锁文件)。
  • --print-only:只探测并打印当前空闲 GPU,不持有锁。

获取锁后,工具将 GPU 列表写入指定环境变量(如CUDA_VISIBLE_DEVICES=0,2,3,5),再以子进程方式执行--之后的命令。获取锁采用非阻塞flock(LOCK_EX | LOCK_NB)+ 指数退避重试(SLEEP_BACKOFF = 5.0乘以随机因子)的策略;工具还会接管SIGINT/SIGTERM/SIGHUP,在收到信号时先终止子进程再退出,并归一化负 returncode(128 - returncode)。获取到--devices指定的 GPU 时会按顺序逐个阻塞等待。该工具支持lslocks排查锁状态(源码注释中明确提示)。

在 workflow 中的典型用法(CPU 为 0 时不加锁,直接运行):

if [ "$NUM_GPUS" = "0" ]; then python "$TEST_PATH" "${TEST_ARGS_ARRAY[@]}" else python tests/ci/gpu_lock_exec.py --count "$NUM_GPUS" -- python "$TEST_PATH" "${TEST_ARGS_ARRAY[@]}" fi

编写新测试

CPU Tests

对于 CPU-only tests,遵循 4 个步骤:

  1. 按照附近文件的模式,将测试放在tests/test_*.pytests/utils/test_*.pytests/plugin_contracts/test_*.py下。
  2. 如果这个文件可能被run-ci-changed运行,添加顶层NUM_GPUS = 0
  3. 让文件可以直接执行:
if __name__ == "__main__": raise SystemExit(pytest.main([__file__]))
  1. 如果测试需要永久进入 CI matrix,在 .github/workflows/pr-test.yml.j2 的cpu-unittestagent-adapter-testjob 中注册,然后重新生成 workflow(见下文"Workflow 生成")。

GPU E2E Tests

对于 GPU e2e tests:

  1. 创建tests/test_<your_test_name>.py,遵循已有的prepare()/execute()模式。
  2. NUM_GPUS = <N>声明所需 GPU 数量。
  3. prepare()中下载所需模型和数据集。
  4. execute()中构建参数并调用U.execute_train(...)
  5. 在 .github/workflows/pr-test.yml.j2 的合适 GPU job 中注册测试,然后重新生成 workflow。

官方示例骨架(含清理代理环境变量的细节——因为容器内下载需要代理,而训练进程通常不应带代理):

import os import slime.utils.external_utils.command_utils as U MODEL_NAME = "Qwen2.5-0.5B-Instruct" MODEL_TYPE = "qwen2.5-0.5B" NUM_GPUS = 4 def prepare(): U.exec_command("mkdir -p /root/models /root/datasets") U.exec_command(f"hf download Qwen/{MODEL_NAME} --local-dir /root/models/{MODEL_NAME}") def execute(): # 构建参数字符串并调用 U.execute_train(...) ... if __name__ == "__main__": prepare() for proxy_var in ("http_proxy", "https_proxy", "HTTP_PROXY", "HTTPS_PROXY"): os.environ.pop(proxy_var, None) execute()

一个真实参照是 tests/test_qwen2.5_0.5B_debug_rollout_then_train.py:它声明MODEL_NAME = "Qwen2.5-0.5B-Instruct"MODEL_TYPE = "qwen2.5-0.5B"NUM_GPUS = 8prepare()中下载模型和 GSM8K 数据集;execute()拆成两阶段——execute_rollout_only--debug-rollout-only启动 SGLang 生成两轮 rollout 并保存到临时目录,execute_train_only则用--load-debug-rollout-data跳过 SGLang、加载已保存数据直接训练两步,验证了 rollout-then-train 的两阶段调试链路。

U(slime/utils/external_utils/command_utils.py)还提供了convert_checkpoint(HF → torch_dist 权重转换,自动读取scripts/models/<type>.sh中的MODEL_ARGS)、hf_download_datasetfp8_cast_bf16get_default_wandb_args(自动根据测试文件名生成--use-wandb --wandb-project slime-<test_name>参数)等便捷函数,以及NUM_GPUS_OF_HARDWARE = {"H100": 8, "GB200": 4, "GB300": 4}等硬件配置常量,可用于编写多硬件适配的测试。

Workflow 生成

pr-test.yml由 Jinja2 模板pr-test.yml.j2自动生成,不要直接编辑pr-test.yml——生成的 YAML 文件头部带有"auto-generated"警告注释,直接修改会在下次生成时被覆盖。

如果要修改固定 CI matrix:

  1. 编辑 .github/workflows/pr-test.yml.j2。
  2. 运行生成脚本:
python .github/workflows/generate_github_workflows.py
  1. 同时提交.github/workflows/pr-test.yml.j2和生成的.github/workflows/pr-test.yml

生成器 .github/workflows/generate_github_workflows.py 使用自定义定界符的 Jinja2 Environment(<% %>为块、<< >>为变量),扫描*.yml.j2文件渲染后写入同名.yml,并在头部写入自动生成声明。模板中还使用了<< config.tests | tojson(indent=2) | indent(10, true) >>将 Python 侧的测试矩阵数据序列化为 YAML,因此修改矩阵时只需增删模板顶部的tests列表条目,无需手写大段 YAML。当前目录下的其他 workflow(如conda-ci.ymlpre-commit.yml)也遵循同一套 .j2 模板 + 生成器模式。

PR 应该选择哪些检查

文档给出了清晰的决策路径,按改动类型对号入座:

  • 纯 argument parsing、reward、schedule、sample、trajectory 或 hook-contract 改动:优先依赖 CPU tests,它们默认运行且反馈最快。
  • SGLang topology 或 rollout engine deployment 改动:使用run-ci-sglang-config,其矩阵同时包含 GPU 为 0 的纯配置校验(tests/utils/test_sglang_arguments.py、tests/utils/test_sglang_config.py)和真实部署测试。
  • Megatron training、loss、checkpoint conversion 或 model recipe 改动:使用run-ci-megatron;必要时加run-ci-precision(数值一致性)或run-ci-ckpt(checkpoint 组合)。
  • Docker image 或 dependency 改动:使用run-ci-imageslimerl/slime-test:latest上验证。
  • 新增或修改测试:使用run-ci-changed做 targeted validation——它只运行你改动的测试文件,并把NUM_GPUS作为 GPU 数量,是最省资源的回归手段。

附:自托管 runner 的部署要点

GPU e2e job 依赖自托管 runner,部署说明见 tests/ci/README.md 与 tests/ci/github_runner/docker-compose.yml:

  • 需在仓库 Secrets 中配置WANDB_API_KEY(用于 e2e 测试的指标上报)。
  • runner 以 Docker 方式部署,compose 文件启动 8 个ghcr.io/actions/actions-runner副本,挂载/var/run/docker.sock(使 runner 能启动 CI 所需的训练容器)、/data/slime_ci(与 CI 容器共享模型/数据集缓存)和/home/runner/externals,并通过GITHUB_RUNNER_URL/GITHUB_RUNNER_TOKEN环境变量完成注册;token 会定期失效,需要及时更新。
  • runner 以privileged: trueuser: root运行,entrypoint 中先config.sh注册、再run.sh启动。

总结

slime 的 CI 体系是一套为"RL 训练框架"量身定制的工程实践:CPU 层用 50+ 个轻量测试守住参数校验、奖励计算、采样、checkpoint 工具与插件契约等纯逻辑不变量;GPU 层用 label 门控的自托管 e2e 测试覆盖 dense/MoE 训练、PPO/OPD、async rollout、PD/Mooncake、数值精度与 checkpoint 组合等真实训练路径;run-ci-changed则提供了按文件粒度的动态回归。理解这套分层与触发机制,是在该仓库贡献代码时快速定位"该跑哪个 label、怎么写一个新测试"的关键。对开发者而言,把测试放进tests/test_*.py、声明正确的NUM_GPUS、遵循prepare()/execute()模式,再在 .j2 模板中注册并重新生成 workflow,即可让新功能获得完整的 CI 保护。

【免费下载链接】slimeslime is an LLM post-training framework for RL Scaling.项目地址: https://gitcode.com/GitHub_Trending/slime12/slime

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询