slime CI 深度指南:双层持续集成架构、GPU 端到端测试与工作流自动化实践
【免费下载链接】slimeslime is an LLM post-training framework for RL Scaling.项目地址: https://gitcode.com/GitHub_Trending/slime12/slime
本篇指南以 slime 开源仓库的开发者 CI 文档为主线,系统讲解其“CPU 常开正确性测试 + label 门控 GPU 端到端测试”的双层 CI 架构:包括pr-test.yml工作流的自动生成机制、cpu-unittest/agent-adapter-test等 CPU Job 的职责边界、run-ci-megatron等 GPU E2E Job 在自托管 Runner 上的容器化执行链路(含gpu_lock_exec.py的 GPU 锁获取原理)、run-ci-changed的动态测试发现机制,以及面向贡献者的“从零编写新测试并注册进 CI 矩阵”完整流程。读完本文,你将能独立为 slime 添加 CPU 单元测试或 GPU 端到端测试,理解每类检查的触发条件与选型依据,并掌握工作流模板的生成与提交规范。
为什么 slime 需要双层 CI
slime 是面向 RL Scaling 的 LLM 后训练框架,其训练路径横跨 Megatron 后端、SGLang rollout 引擎、Ray 集群调度、checkpoint 转换与权重同步等多个子系统。这类系统的回归风险通常潜伏在参数校验、调度逻辑、reward 计算、rollout 数据组织等“静默错误”中——它们不报错,但会悄悄污染训练结果。
因此 slime 的 CI 刻意拆成两层,设计意图非常明确(见 CI 文档):
- Always-on CPU 正确性测试:每个 PR、每次 push 到
main、以及手动workflow_dispatch都会运行,用于在无需等待 GPU 集群的前提下快速校验绝大部分 correctness invariant; - Label 门控 GPU 端到端测试:在自托管 GPU Runner 上验证真实的 Megatron + SGLang training/rollout 路径,只有给 PR 打上对应 label 才触发。
这种拆分是有意为之:大部分不变式应该在不排队等待 GPU 的情况下被秒级捕获,而完整训练/rollout 行为的覆盖则交给 GPU E2E Job 兜底。
工作流从哪来:pr-test.yml的自动生成机制
CI 工作流定义在.github/workflows/pr-test.yml,但它不是手写维护的,而是由 Jinja2 模板.github/workflows/pr-test.yml.j2自动生成。这是 slime CI 工程化的第一道关键约定:永远不要直接编辑pr-test.yml,所有对固定 CI 矩阵的修改都必须落在.j2模板上。
生成器位于 generate_github_workflows.py,其核心逻辑是:
- 用
jinja2.Environment加载工作流目录下的所有*.yml.j2模板,其中块定界符使用<% %>、变量定界符使用<< >>(区别于 Jinja 默认的{% %}和{{ }},避免与 GitHub Actions 自身的表达式语法冲突); - 渲染后把结果写入同名
.yml文件,并在文件头部自动追加“本文件由 generate_github_workflows.py 自动生成,禁止手动编辑”的警告注释; - 新增模板后需要手动运行生成器,工作流文件才会更新。
python .github/workflows/generate_github_workflows.py从模板.github/workflows/pr-test.yml.j2的源码结构看,CI 矩阵的核心数据是一张“测试清单表”:每条记录声明test_file(测试文件路径)、num_gpus(所需 GPU 数)、以及可选的test_args(透传给测试进程的附加参数)、use_deepep、use_fp8_rollout、enable_eval等环境开关。模板把这些清单渲染成 GitHub Actions 的strategy.matrix,从而把“一份矩阵清单”映射成“一组并行 Job”。
模板还定义了所有 Job 的公共执行环境变量,例如:
SLIME_TEST_USE_DEEPEP:是否启用 DeepEP(MoE 专家并行通信库);SLIME_TEST_USE_FP8_ROLLOUT:rollout 侧是否启用 FP8;SLIME_TEST_ENABLE_EVAL:训练过程中是否执行评测;SLIME_TEST_ENABLE_INFINITE_RUN:配合workflow_dispatch的infinite_run输入,用于长时跑测;GITHUB_COMMIT_NAME:把 commit SHA 与 PR 号拼接进 wandb run 命名,便于回溯。
工作流触发总览
pr-test.yml中声明的触发条件(对应模板.github/workflows/pr-test.yml.j2中的on:段)包括三类事件:
- push 到
main:只触发默认运行的 CPU Job。模板注释解释了原因——防止“两个 PR 单独通过、合并后 main 却坏了”的 PR 对回归;同时 push 事件不触发 GPU Job,避免自托管 GPU 机群被无谓消耗; - pull_request(含
opened/reopened/synchronize/labeled类型):CPU Job 常开,GPU Job 则通过if:条件检查 PR 是否带上了对应 label(如run-ci-megatron),label 是在 PR 上实时打标的,因此labeled事件类型是 GPU Job 能被“事后补跑”的关键; - workflow_dispatch(手动触发):在 GitHub Actions 页面手动选择运行,按工作流条件执行注册的 Job,可用于发布前的整体回归验证。
此外模板为整个工作流声明了concurrency分组(以 PR 号或 ref 为 key)并启用cancel-in-progress: true,保证同一 PR 的旧一轮检查会被新提交的检查抢占取消,避免自托管资源被过时任务占用。
CPU Job:第一道正确性防线
CPU Job 运行在 GitHub-hosted 的ubuntu-latestRunner 上(模板中runs-on: ubuntu-latest),不使用 Docker、不申请 GPU、也不会调用tests/ci/gpu_lock_exec.py。模板中 CPU Job 的执行步骤固定为:
pip install torch --index-url https://download.pytorch.org/whl/cpu # 安装 CPU 版 PyTorch pip install pytest numpy packaging pyyaml omegaconf tqdm httpx requests ray pybase64 pylatexenc sympy aiohttp pillow safetensors psutil pip install -e . --no-deps # 仅安装 slime 本体,跳过重量级依赖 python tests/<test_file>.py # 直接以可执行脚本方式运行测试CPU 层包含两个 Job:
cpu-unittest:默认运行注册的单元测试与契约测试,附加依赖为transformers wandb;agent-adapter-test:以同样方式运行 agent 适配器测试,额外安装openai、openai-agents、anthropic等 provider SDK 依赖(在模板的extra_pip_deps中声明)。Agent 适配器测试被单独拆分,正是因为这些额外 SDK 依赖较重。
当前注册进cpu-unittest的 CPU 测试覆盖了以下关键领域(清单见模板.github/workflows/pr-test.yml.j2中的cpu-unittestjob 定义):
- Megatron 参数与 HF 配置校验:test_megatron_argument_validation.py(顶层声明
NUM_GPUS = 0)、test_megatron_role_config.py、test_megatron_server_arguments.py; - DP/CP 调度与 CP 损失不变性:test_dp_schedule.py、test_cp_utils.py、test_loss_cp_invariance.py、test_advantage_whiten_cp.py;
- 损失与 RL 核心数值逻辑:test_policy_loss.py、test_ppo_logprob_entropy.py、test_ppo_kl_metric.py、test_cispo_loss.py、test_discounted_returns.py、test_value_temperature.py、test_block_fp8_zero_block.py;
- reward-model 评分工具:math(test_rm_math.py)、DAPO 风格 math(test_rm_math_dapo.py)、GPQA(test_rm_gpqa.py)、F1(test_rm_f1.py)、DeepScaler(test_rm_deepscaler.py);
- rollout 数据与
Sample行为:test_sample.py、test_process_rollout_data.py、test_logprob_response_spans.py、test_filter_long_prompt.py、test_fully_async_rollout.py、test_rollout_sample_hooks.py; - 指标上报与分布式聚合:test_metric_report.py、test_metric_report_dist.py、test_rollout_metrics.py、test_train_data_utils.py、test_rollout_data_utils.py;
- HF checkpoint saver 行为:test_hf_checkpoint_saver.py、test_empty_colocated_weight_bucket.py、test_reloadable_process_group_world.py;
- 自定义 hook 契约:rollout 函数、generate 函数、runtime hook、path loading 四类插件契约分别由
tests/plugin_contracts/下的test_plugin_rollout_contracts.py、test_plugin_generate_contracts.py、test_plugin_runtime_hook_contracts.py、test_plugin_path_loading_contracts.py校验; - 其他基础设施:
test_accelerator.py(加速器抽象)、test_placement_group.py(Ray 放置组)、test_hf_to_megatron.py(权重转换)、test_expert_routing.py(专家路由)、test_layerwise_alignment.py与test_glm52_layerwise_comparison.py(逐层对齐)等。
注意一个细节:CPU 测试进程通过python tests/<test_file>.py直接运行,而不是pytest。这意味着每个测试文件都必须自带if __name__ == "__main__"入口(详见下文“编写新测试”)。本地复现也很简单,例如:
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.pyGPU E2E Job:真实训练/rollout 路径的验证
GPU Job 运行在自托管 GPU Runner上,每个 Job 的执行链路(模板中 GPU 分支的Execute步骤)如下:
- 启动 Docker 容器:默认使用
slimerl/slime:latest;run-ci-image则使用slimerl/slime-test:latest做镜像验证。容器参数包含--gpus all、--privileged、--network host、--ipc=host、--shm-size=16g、--ulimit memlock=-1等,以匹配分布式训练对共享内存与资源上限的要求; - 挂载缓存目录:
/mnt/nvme0n1/slime_ci(含 models 与 datasets 子目录)被挂载为/data/slime_ci、/root/models、/root/datasets,模型与数据集可在多次运行间复用,避免重复下载; - 安装 slime:容器内执行
pip install -e . --no-deps --break-system-packages; - 获取 GPU:当
NUM_GPUS > 0时,通过tests/ci/gpu_lock_exec.py --count <num_gpus>申请指定数量的空闲 GPU,再把测试命令作为其后继命令执行; - 执行测试:
python tests/<test_file>.py,与 CPU Job 相同的调用方式。
GPU 测试普遍遵循 e2e 模式:prepare()负责下载模型与数据集,execute()负责构建 CLI 参数并调用U.execute_train(...)。这个U就是 slime/utils/external_utils/command_utils.py 中的命令工具模块,其中execute_train()的调用链体现了整套 e2e 运行机制:先清理残留的 sglang/ray/slime/redis 进程 → 启动 Ray head 节点 → 通过ray job submit提交训练 Job → 在 Job 的 runtime env 中注入PYTHONPATH=/root/Megatron-LM/、MASTER_ADDR、NCCL_NVLS_ENABLE(依据nvidia-smi topo -m探测是否启用 NVLink)等环境变量,并透传scripts/models/<model_type>.sh中定义好的模型参数。
GPU 锁:gpu_lock_exec.py的原理
自托管 Runner 上可能有多个 Job 并发,GPU 分配必须互斥。tests/ci/gpu_lock_exec.py实现了基于fcntl.flock的文件锁方案(锁文件默认位于/dev/shm/custom_gpu_lock_{gpu_id}.lock):
- 通过
--count N从--total-gpus(默认 8)个 GPU 中尽力抢到任意 N 个空闲 GPU,或通过--devices 0,1指定显式设备列表; - 抢锁失败时以 5 秒的随机退避重试,直到
--timeout(默认 24 小时)耗尽则报TimeoutError; - 成功后把所有 GPU ID 写入
--target-env-name指定的环境变量(默认CUDA_VISIBLE_DEVICES),并exec用户命令; - 支持
--print-only探测模式(不加锁只打印空闲 GPU 列表),便于调试; - 进程还负责把 SIGINT/SIGTERM/SIGHUP 转发给子进程并归一化退出码,保证 CI 在中断时能干净收尾。
这正是文档中“CPU Job 不会调用tests/ci/gpu_lock_exec.py”这句话的工程含义:GPU 获取是自托管 GPU 路径的专属步骤,其具体参数定义(--count/--devices/--total-gpus/--timeout/--target-env-name/--lock-path-pattern)均可在该脚本中查阅。
run-ci-changed:只跑改动过的测试
run-ci-changed是一个特殊的“混合型”Job(Mixed),用于在 PR 上快速做定向验证。其实现位于模板末尾的e2e-test-changed-detect+e2e-test-changed两个 Job:
- 检测阶段:以
fetch-depth: 0检出完整历史,用git diff --name-only --diff-filter=AM origin/main...HEAD找出相对origin/main新增或修改的tests/test_*.py与tests/plugin_contracts/test_*.py; - 解析 GPU 需求:对每个改动文件用
grep -oP '^NUM_GPUS\s*=\s*\K\d+'提取顶层NUM_GPUS常量并构建动态 matrix; - 执行阶段:与普通 GPU Job 一样走自托管 Docker 路径;当某文件的
NUM_GPUS = 0时,直接运行测试而不获取 GPU。
这里有两个关键约定:
NUM_GPUS缺失时默认 8:如果一个测试文件没有声明NUM_GPUS却又被改动,CI 会按 8 卡去跑。因此CPU-only 测试必须显式声明NUM_GPUS = 0,否则它会被误当成 8 卡 GPU 测试排队执行;- 变更检测只覆盖
tests/test_*.py与tests/plugin_contracts/test_*.py,tests/utils/、tests/observability/等子目录下的测试文件不会被run-ci-changed自动捕获,这一点在新写测试时要格外留意(需要依赖常规 Job 覆盖)。
CI Jobs 与触发方式一览
| 触发方式 | Job | 类型 | 说明 |
|---|---|---|---|
| 自动运行 | cpu-unittest | CPU | 常开的单元/契约测试,覆盖参数校验、调度、reward、sample、rollout 校验、checkpoint 工具与插件契约 |
| 自动运行 | agent-adapter-test | CPU | 常开的 agent 适配器测试,含额外 provider SDK 依赖 |
run-ci-sglang-configlabel | e2e-test-sglang-config | GPU | SGLang config 测试,覆盖高级 rollout engine deployment 与 mixed/offload 场景 |
run-ci-megatronlabel | e2e-test-megatron | GPU | 核心 Megatron 训练测试,覆盖 dense、MoE、PPO、MTP、OPD、async rollout、PD/Mooncake 与 debug replay 路径 |
run-ci-precisionlabel | e2e-test-precision | GPU | 数值精度与并行一致性检查 |
run-ci-ckptlabel | e2e-test-ckpt | GPU | Checkpoint 保存/加载正确性,包括 CPU/GPU optimizer state 与 async save |
run-ci-imagelabel | e2e-test-image | GPU | 在slimerl/slime-test:latest上运行与run-ci-megatron相同的矩阵,用于验证镜像 |
run-ci-changedlabel | e2e-test-changed | Mixed | 只运行改动过的测试,按每个文件的NUM_GPUS决定是否获取 GPU |
手动场景下,可在 GitHub Actions 页面用workflow_dispatch触发任意 Job;模板还为workflow_dispatch提供了infinite_run布尔输入(通过环境变量SLIME_TEST_ENABLE_INFINITE_RUN传递),用于长时间不中断的训练验证。
GPU E2E 测试覆盖点与选型
GPU E2E 验证的是 CPU 测试无法覆盖的集成训练/rollout 行为。从模板.github/workflows/pr-test.yml.j2的矩阵定义可以精确还原各 Job 的测试清单:
e2e-test-sglang-config:utils/test_sglang_arguments.py与utils/test_sglang_config.py(0 GPU,参数/配置级校验)、test_qwen2.5_0.5B_sglang_config.py、test_qwen2.5_0.5B_sglang_config_distributed.py、test_sglang_config_mixed_offload.py、test_sglang_config_mixed_offload_ft.py(各 8 GPU,覆盖引擎布局与 mixed/offload 部署);e2e-test-megatron(核心矩阵,megatron_tests变量):包括test_full_disk_weight_update.py(4 GPU,全磁盘权重更新)、test_release_train.py(4 GPU,--release-train下 rollout actor 组随磁盘权重更新释放并重建)、test_quick_start_glm4_9B.py(8 GPU)、test_glm4.7_30B_A3B_pd_mooncake.py与test_qwen3.6_35B_A3B_pd_mooncake.py(8 GPU,PD 分离 + Mooncake)、test_qwen3_30B_A3B.py/test_qwen3_30B_A3B_r3.py(8 GPU,MoE + DeepEP + FP8 rollout)、test_qwen3_4B_ppo.py、test_qwen3_4B_ppo_disaggregate.py、test_qwen3_4B_ppo_train_critic_only.py(8 GPU,PPO 三态)、test_moonlight_16B_A3B.py/test_moonlight_16B_A3B_r3.py、test_mimo_7B_mtp_only_grad.py、test_qwen2.5_0.5B_debug_rollout_then_train.py与test_qwen2.5_0.5B_debug_train_dump_e2e.py(8 GPU,debug replay 路径)、test_qwen2.5_0.5B_opd_sglang.py(OPD)、test_qwen3_4B_external_pd.py(外部 PD 引擎)、test_qwen2.5_0.5B_fully_async_short.py与test_qwen3.5_0.8B_gsm8k_async_short.py(async rollout)、test_qwen2.5_0.5B_fanout_short.py(fanout)、test_qwen3_4B_streaming_partial_rollout.py(流式部分 rollout)、test_qwen3.5_0.8B_gsm8k_short.py等;e2e-test-precision:test_glm5_indexer_q_norm.py(0 GPU)与test_qwen3_0.6B_parallel_check.py(8 GPU,不同并行设置下数值一致性);e2e-test-ckpt:test_qwen3_4B_ckpt.py以 8 GPU 跑 5 种组合——--save-optimizer gpu --load-optimizer gpu、gpu→cpu、cpu→cpu、cpu→gpu,以及--async-save(异步保存);e2e-test-image:复用megatron_tests全矩阵,但镜像换成slimerl/slime-test:latest。
选型建议:日常 PR 优先使用 targeted labels;run-ci-image会完整复跑 megatron 矩阵、消耗 GPU 时间显著更多,应谨慎使用。以 test_qwen2.5_0.5B_debug_rollout_then_train.py 为例可以直观看到 e2e 测试的真实结构:它先prepare()下载 Qwen2.5-0.5B-Instruct 与 gsm8k 数据集,再分两阶段execute()——第一阶段debug_rollout_only启动 sglang 生成 2 步 rollout 数据并落盘,第二阶段load_debug_rollout_data完全跳过 sglang、加载落盘数据跑 2 步训练,用于把“rollout 问题”与“训练问题”解耦排查。
编写新测试:从骨架到注册进 CI
编写 CPU 测试
- 把测试放在
tests/test_*.py、tests/utils/test_*.py或tests/plugin_contracts/test_*.py,遵循相邻文件的模式; - 若该文件可能被
run-ci-changed运行,必须在顶层声明NUM_GPUS = 0; - 让文件可直接执行(这也是 CI 用
python tests/<file>.py调用的前提):
if __name__ == "__main__": raise SystemExit(pytest.main([__file__]))- 如需永久进入 CI 矩阵,在
.github/workflows/pr-test.yml.j2的cpu-unittest或agent-adapter-testjob 的 tests 列表中注册(注意test_file以tests/为根的相对路径,模板与执行步骤会自动补全前缀),然后重新生成工作流。
一个可参考的 CPU 测试样板是 test_megatron_argument_validation.py:它在文件顶层声明NUM_GPUS = 0,并通过monkeypatch注入megatron、transformers等模块的假实现来隔离外部依赖,从而在纯 CPU 环境验证参数校验逻辑。
编写 GPU E2E 测试
- 创建
tests/test_<your_test_name>.py,遵循既有prepare()/execute()模式; - 用
NUM_GPUS = <N>声明所需 GPU 数量(决定gpu_lock_exec.py的--count); - 在
prepare()中下载所需模型与数据集(可用U.exec_command、U.hf_download_dataset等辅助,参考 command_utils.py 提供的convert_checkpoint、rsync_simple、fp8_cast_bf16等现成工具); - 在
execute()中构建参数字符串并调用U.execute_train(...); - 在
.github/workflows/pr-test.yml.j2的合适 GPU job 中注册并重新生成工作流。
标准骨架如下(源自 CI 文档,与仓库内真实测试 test_qwen2.5_0.5B_debug_rollout_then_train.py 的结构一致):
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(): # Build argument strings and call 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()骨架末尾“清理代理环境变量”的循环是有实际意义的:GPU Job 的容器继承了宿主机的代理配置,而训练进程(Ray 子进程、Megatron 分布式通信)在代理存在时可能无法正常组网,因此execute()前必须剔除代理变量。
修改 CI 矩阵的正确姿势
如果只是给现有测试换参数或加新测试,无需改动模板语法,只需编辑.github/workflows/pr-test.yml.j2中的测试清单数据(例如megatron_tests的 dict 条目支持test_args传参,这正是test_qwen3_4B_ckpt.py能在同一 Job 中以不同 optimizer 保存/加载组合跑 5 次的机制)。
完整的变更流程是:
- 编辑
.github/workflows/pr-test.yml.j2; - 运行
python .github/workflows/generate_github_workflows.py; - 把
.github/workflows/pr-test.yml.j2与重新生成的.github/workflows/pr-test.yml一起提交。
从生成器源码(generate_github_workflows.py)可见,它会对工作流目录下所有*.yml.j2模板统一渲染,因此新模板加入后也能被自动生成,无需单独接入。
自托管 GPU Runner 的部署与调试
GPU E2E Job 依赖自托管 Runner,仓库在tests/ci/下提供了完整的搭建说明(见 tests/ci/README.md):
- 配置 GitHub secrets:至少需要
WANDB_API_KEY,供 e2e 测试把训练指标上报到 wandb(command_utils.py的get_default_wandb_args会读取它并按GITHUB_COMMIT_NAME拼接 run 名); - 初始化 Runner 环境:通过一个临时容器把官方 actions-runner 镜像
/home/runner/externals目录复制到宿主机(runner 容器必需该目录),并保证目录权限可写; - 以 Docker 方式拉起 Runner 集群:在
tests/ci/github_runner目录执行docker compose up -d。从 docker-compose.yml 可以看到 Runner 服务的要点:镜像使用ghcr.io/actions/actions-runner:2.329.0(注释强调必须用 latest,否则 runner 会要求自升级)、replicas: 8横向扩容、挂载/var/run/docker.sock与/data/slime_ci、entrypoint 用config.sh --unattended --work /data/slime_ci/runner_$(hostname) --disableupdate完成注册后启动run.sh,且每个 runner 用主机名区分工作目录,避免多副本冲突; - 调试手段:
docker compose logs -f查看全部/单个容器日志,docker exec -it github_runner-runner-1 /bin/bash进入容器排查;快速迭代用docker compose down -v && docker compose up -d && docker logs -f github_runner-runner-1。
PR 阶段如何选择检查项
- 纯参数解析、reward、schedule、sample、trajectory、hook-contract 改动:优先依赖 CPU tests(常开、免费、快),把 GPU 资源留给真正需要的变更;
- SGLang topology 或 rollout engine deployment 改动:使用
run-ci-sglang-config; - Megatron training、loss、checkpoint conversion、model recipe 改动:使用
run-ci-megatron,必要时叠加run-ci-precision(数值一致性)或run-ci-ckpt(checkpoint 组合); - Docker 镜像或依赖改动:使用
run-ci-image在slimerl/slime-test:latest上验证; - 新增或修改测试:使用
run-ci-changed做定向验证,记得为 CPU-only 测试声明NUM_GPUS = 0,避免被默认的 8 卡解析误伤。
小结
slime 的 CI 体系以“CPU 快速反馈 + GPU 精准覆盖”为核心原则:模板驱动的工作流生成机制保证了矩阵的可维护性,NUM_GPUS约定让同一个测试文件在 CPU 与 GPU 路径间无缝切换,gpu_lock_exec.py保障了自托管 GPU 机群的并发安全,而run-ci-changed则为 PR 迭代提供了免排队的高频验证通道。对贡献者而言,掌握“改模板 → 重新生成 → 双文件提交”的流程,并理解每类 label 对应的验证边界,就能在改动最少的 GPU 成本下获得最充分的回归保障。
【免费下载链接】slimeslime is an LLM post-training framework for RL Scaling.项目地址: https://gitcode.com/GitHub_Trending/slime12/slime
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考