slime CI 深度指南:双层持续集成架构、GPU 端到端测试与工作流自动化实践

发布时间:2026/9/16 11:04:05
slime CI 深度指南:双层持续集成架构、GPU 端到端测试与工作流自动化实践 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 需要双层 CIslime 是面向 RL Scaling 的 LLM 后训练框架其训练路径横跨 Megatron 后端、SGLang rollout 引擎、Ray 集群调度、checkpoint 转换与权重同步等多个子系统。这类系统的回归风险通常潜伏在参数校验、调度逻辑、reward 计算、rollout 数据组织等“静默错误”中——它们不报错但会悄悄污染训练结果。因此 slime 的 CI 刻意拆成两层设计意图非常明确见 CI 文档Always-on CPU 正确性测试每个 PR、每次 push 到main、以及手动workflow_dispatch都会运行用于在无需等待 GPU 集群的前提下快速校验绝大部分 correctness invariantLabel 门控 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是否启用 DeepEPMoE 专家并行通信库SLIME_TEST_USE_FP8_ROLLOUTrollout 侧是否启用 FP8SLIME_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-megatronlabel 是在 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 层包含两个 Jobcpu-unittest默认运行注册的单元测试与契约测试附加依赖为transformers wandbagent-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.pyDP/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.pyreward-model 评分工具mathtest_rm_math.py、DAPO 风格 mathtest_rm_math_dapo.py、GPQAtest_rm_gpqa.py、F1test_rm_f1.py、DeepScalertest_rm_deepscaler.pyrollout 数据与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.pyHF 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.pyRay 放置组、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:latestrun-ci-image则使用slimerl/slime-test:latest做镜像验证。容器参数包含--gpus all、--privileged、--network host、--ipchost、--shm-size16g、--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是一个特殊的“混合型”JobMixed用于在 PR 上快速做定向验证。其实现位于模板末尾的e2e-test-changed-detecte2e-test-changed两个 Job检测阶段以fetch-depth: 0检出完整历史用git diff --name-only --diff-filterAM 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_*.pytests/utils/、tests/observability/等子目录下的测试文件不会被run-ci-changed自动捕获这一点在新写测试时要格外留意需要依赖常规 Job 覆盖。CI Jobs 与触发方式一览触发方式Job类型说明自动运行cpu-unittestCPU常开的单元/契约测试覆盖参数校验、调度、reward、sample、rollout 校验、checkpoint 工具与插件契约自动运行agent-adapter-testCPU常开的 agent 适配器测试含额外 provider SDK 依赖run-ci-sglang-configlabele2e-test-sglang-configGPUSGLang config 测试覆盖高级 rollout engine deployment 与 mixed/offload 场景run-ci-megatronlabele2e-test-megatronGPU核心 Megatron 训练测试覆盖 dense、MoE、PPO、MTP、OPD、async rollout、PD/Mooncake 与 debug replay 路径run-ci-precisionlabele2e-test-precisionGPU数值精度与并行一致性检查run-ci-ckptlabele2e-test-ckptGPUCheckpoint 保存/加载正确性包括 CPU/GPU optimizer state 与 async saverun-ci-imagelabele2e-test-imageGPU在slimerl/slime-test:latest上运行与run-ci-megatron相同的矩阵用于验证镜像run-ci-changedlabele2e-test-changedMixed只运行改动过的测试按每个文件的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-configutils/test_sglang_arguments.py与utils/test_sglang_config.py0 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.py4 GPU全磁盘权重更新、test_release_train.py4 GPU--release-train下 rollout actor 组随磁盘权重更新释放并重建、test_quick_start_glm4_9B.py8 GPU、test_glm4.7_30B_A3B_pd_mooncake.py与test_qwen3.6_35B_A3B_pd_mooncake.py8 GPUPD 分离 Mooncake、test_qwen3_30B_A3B.py/test_qwen3_30B_A3B_r3.py8 GPUMoE DeepEP FP8 rollout、test_qwen3_4B_ppo.py、test_qwen3_4B_ppo_disaggregate.py、test_qwen3_4B_ppo_train_critic_only.py8 GPUPPO 三态、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.py8 GPUdebug replay 路径、test_qwen2.5_0.5B_opd_sglang.pyOPD、test_qwen3_4B_external_pd.py外部 PD 引擎、test_qwen2.5_0.5B_fully_async_short.py与test_qwen3.5_0.8B_gsm8k_async_short.pyasync rollout、test_qwen2.5_0.5B_fanout_short.pyfanout、test_qwen3_4B_streaming_partial_rollout.py流式部分 rollout、test_qwen3.5_0.8B_gsm8k_short.py等e2e-test-precisiontest_glm5_indexer_q_norm.py0 GPU与test_qwen3_0.6B_parallel_check.py8 GPU不同并行设置下数值一致性e2e-test-ckpttest_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 labelsrun-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(fhf 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 测试把训练指标上报到 wandbcommand_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-configMegatron training、loss、checkpoint conversion、model recipe 改动使用run-ci-megatron必要时叠加run-ci-precision数值一致性或run-ci-ckptcheckpoint 组合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),仅供参考