Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .buildkite/gpu_suites.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,24 @@
],
"megatron": [
("test_full_disk_weight_update.py", 4, "", {}),
("test_quick_start_glm4_9B.py", 8, "", {}),
("test_quick_start_glm4_9B.py", 8, "", {"ENABLE_EVAL": "0"}),
("test_glm4.7_30B_A3B_pd_mooncake.py", 8, "", {}),
(
"test_qwen3_30B_A3B.py",
8,
"",
{"USE_DEEPEP": "1", "USE_FP8_ROLLOUT": "1"},
{"USE_DEEPEP": "1", "USE_FP8_ROLLOUT": "1", "ENABLE_EVAL": "0"},
),
("test_qwen3.6_35B_A3B_pd_mooncake.py", 8, "", {"USE_DEEPEP": "1"}),
("test_qwen3_30B_A3B_r3.py", 8, "", {"USE_DEEPEP": "1", "USE_FP8_ROLLOUT": "1", "ENABLE_EVAL": "0"}),
("test_qwen3_30B_A3B_r3.py", 8, "", {"ENABLE_EVAL": "0"}),
("test_qwen3_4B_ppo.py", 8, "", {}),
("test_qwen3_4B_ppo_disaggregate.py", 8, "", {}),
("test_qwen3_4B_ppo_train_critic_only.py", 8, "", {}),
("test_qwen3_4B_ppo.py", 8, "", {"ENABLE_EVAL": "0"}),
("test_qwen3_4B_ppo_disaggregate.py", 8, "", {"ENABLE_EVAL": "0"}),
("test_qwen3_4B_ppo_train_critic_only.py", 8, "", {"ENABLE_EVAL": "0"}),
("test_ppo_logprob_entropy_gpu.py", 2, "", {}),
("test_release_train.py", 4, "", {}),
("test_qwen3_4B_streaming_partial_rollout.py", 8, "", {}),
("test_moonlight_16B_A3B.py", 8, "", {}),
("test_moonlight_16B_A3B.py", 8, "", {"ENABLE_EVAL": "0"}),
("test_moonlight_16B_A3B_r3.py", 8, "", {"ENABLE_EVAL": "0"}),
("test_mimo_7B_mtp_only_grad.py", 8, "", {}),
("test_qwen2.5_0.5B_debug_rollout_then_train.py", 8, "", {}),
Expand Down
43 changes: 16 additions & 27 deletions .claude/skills/add-tests-and-ci/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,23 +37,16 @@ if __name__ == "__main__":
raise SystemExit(pytest.main([__file__]))
```

- `run-ci-changed` extracts a top-level `NUM_GPUS = <N>` constant from added/modified `tests/test_*.py` and `tests/plugin_contracts/test_*.py`; if missing, it defaults to 8 GPUs. Set `NUM_GPUS = 0` for CPU-only tests.
- Set `NUM_GPUS = 0` for CPU-only tests, following the existing test metadata convention.
- For GPU/e2e tests, follow the nearby file pattern (`prepare()`, `execute()`, `NUM_GPUS`, and any model/dataset constants).

### Step 3: Register Tests in GitHub CI
### Step 3: Register Tests in Buildkite CI

Whenever adding, moving, or renaming a test file, update the GitHub workflow template before finishing:
Whenever adding, moving, or renaming a test file, update its Buildkite registration before finishing:

1. Add the test to the appropriate matrix in `.github/workflows/pr-test.yml.j2`.
- CPU-only pytest/unit tests usually belong in `cpu-unittest` with `num_gpus: 0`.
- GPU/e2e tests should be placed beside the nearest similar model/path test with the matching `num_gpus` and environment fields.
2. Regenerate workflows:

```bash
python .github/workflows/generate_github_workflows.py
```

3. Include both `.github/workflows/pr-test.yml.j2` and the generated `.github/workflows/pr-test.yml` in the change set.
1. Register CPU test files in the appropriate command list in `.buildkite/pipeline.yml`, beside similar tests. Agent CPU tests belong in `agent-adapter`.
2. Register GPU/e2e tests in `.buildkite/gpu_suites.py`, with the matching GPU count and environment settings. Update `.buildkite/pipeline.yml` when changing suite selection or wiring.
3. Include the registration changes with the tests. These files are the source of truth; there is no GitHub workflow regeneration step.

Only skip fixed matrix registration when the test is intentionally helper-only or manually invoked; state that reason in the final response.

Expand All @@ -64,34 +57,29 @@ Only skip fixed matrix registration when the test is intentionally helper-only o
- Run repository-wide checks only when they are already part of the task or workflow.
- Avoid documenting placeholder test commands that may not exist in the current tree.

### Step 5: Keep Workflow Template as Source of Truth
### Step 5: Keep Buildkite Sources in Sync

For CI workflow changes unrelated to a new, moved, or renamed test:

1. Edit `.github/workflows/pr-test.yml.j2`
2. Regenerate workflows:

```bash
python .github/workflows/generate_github_workflows.py
```

3. Include both the template and generated workflow file in the change set (`.j2` and `.yml`). If the user asked for a commit, commit both.
1. Edit `.buildkite/pipeline.yml` for always-on CPU commands and pipeline wiring.
2. Edit `.buildkite/gpu_suites.py` for generated GPU jobs rather than editing its generated output.
3. Keep suite definitions, selection, and `.buildkite/README.md` consistent when changing suites.

### Step 6: Provide Verifiable PR Notes

Include:

- Which tests were added/changed
- Where each new/renamed test was registered in `.github/workflows/pr-test.yml.j2`
- Where each new/renamed test was registered in `.buildkite/pipeline.yml` or `.buildkite/gpu_suites.py`
- Exact commands executed
- GPU assumptions for each test path
- Why this coverage protects against regression

## Common Mistakes

- Editing generated workflow file only
- Relying on `run-ci-changed` discovery for a new test that should run in the regular PR matrix
- Forgetting `NUM_GPUS = 0` on a CPU-only changed test, causing `run-ci-changed` to default to 8 GPUs
- Editing generated GPU jobs instead of their source
- Relying on pytest discovery for a new test in a suite with an explicit file list
- Treating a green CPU build as GPU validation; GPU suites require the manual Buildkite gate
- Adding a CPU pytest file that passes under `pytest tests/foo.py` but fails under CI's `python tests/foo.py`
- Adding tests without following existing constants/conventions
- Making tests too large or non-deterministic
Expand All @@ -101,5 +89,6 @@ Include:

- Pytest config: `pyproject.toml`
- Tests: `tests/`
- CI template: `.github/workflows/pr-test.yml.j2`
- CI sources: `.buildkite/pipeline.yml`, `.buildkite/gpu_suites.py`
- Buildkite guide: `.buildkite/README.md`
- CI guide: `docs/en/developer_guide/ci.md`
40 changes: 40 additions & 0 deletions .claude/skills/release/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
name: release
description: Prepare and verify a Vime release, including version metadata, Docker patch-stack validation, and release-specific checks. Use when cutting or auditing a Vime release.
---

# Release Vime

Prepare a release without creating Git tags, GitHub releases, or publishing
images unless the user explicitly requests those external actions.

## Establish the release baseline

- Preserve unrelated changes and compare the previous Vime release tag.
- Confirm the package version, the pinned `BASE_IMAGE`, and the Docker patch
stack in `docker/patch/latest/`.
- Do not upgrade the vLLM base image as part of a release unless Slime has
upgraded its corresponding inference-image baseline.

## Prepare the release PR

- Update `setup.py` and `docs/conf.py` to the requested package version.
- Give `docker/version.txt` a new unique dated image tag.
- Review every remaining occurrence of the old Vime version rather than making
a blind repository-wide replacement.
- Verify every patch under `docker/patch/latest/` is consumed in Dockerfile
application order and applies to its target in separate clean checkouts of
the pinned vLLM and Megatron revisions. Do not validate patch application
against a dirty developer checkout.

## Validate and publish

- Run `python .claude/skills/release/scripts/check_release.py --repo .
--expected-version <version>`, `python setup.py --version`, and
`git diff --check`.
- Build a candidate image from the release commit and run the required E2E
tests before promoting an image tag.
- Merge the green release PR, then create the matching Git tag and GitHub
release at its merge commit.
- Publish the versioned image first. Only update `vllm/vime:latest` after the
candidate has passed and every required vLLM patch has merged upstream.
100 changes: 100 additions & 0 deletions .claude/skills/release/scripts/check_release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""Check Vime release metadata and its Docker patch stack."""

import argparse
import ast
import re
import sys
from pathlib import Path


def setup_version(path: Path) -> str:
tree = ast.parse(path.read_text())
for node in ast.walk(tree):
if not isinstance(node, ast.Call) or getattr(node.func, "id", None) != "setup":
continue
for keyword in node.keywords:
if keyword.arg == "version":
return ast.literal_eval(keyword.value)
raise ValueError(f"setup version not found in {path}")


def assigned_string(path: Path, name: str) -> str:
tree = ast.parse(path.read_text())
for node in tree.body:
if not isinstance(node, ast.Assign):
continue
if any(isinstance(target, ast.Name) and target.id == name for target in node.targets):
return ast.literal_eval(node.value)
raise ValueError(f"{name} not found in {path}")


def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--repo", type=Path, default=Path.cwd())
parser.add_argument("--expected-version")
args = parser.parse_args()

repo = args.repo.resolve()
errors: list[str] = []
package_version = setup_version(repo / "setup.py")
docs_version = assigned_string(repo / "docs/conf.py", "__version__")
if package_version != docs_version:
errors.append(f"setup.py={package_version} but docs/conf.py={docs_version}")
if args.expected_version and package_version != args.expected_version:
errors.append(f"release version is {package_version}, expected {args.expected_version}")

dockerfile = (repo / "docker/Dockerfile").read_text()
image_tag = (repo / "docker/version.txt").read_text().strip()
if not re.fullmatch(r"nightly-dev-\d{8}[a-z]", image_tag):
errors.append(f"unexpected docker/version.txt format: {image_tag}")
if not re.search(r"^ARG BASE_IMAGE=", dockerfile, re.MULTILINE):
errors.append("docker/Dockerfile does not pin BASE_IMAGE")
if not re.search(r"^ARG PATCH_VERSION=latest$", dockerfile, re.MULTILINE):
errors.append("docker/Dockerfile must build from docker/patch/latest")

patch_dir = repo / "docker/patch/latest"
patches = {path.name for path in patch_dir.glob("*.patch")}
copied = {
name
for name in re.findall(r"COPY docker/patch/\$\{PATCH_VERSION\}/([^\s]+\.patch)", dockerfile)
if "*" not in name
}
if "megatron*.patch" in dockerfile:
copied.add("megatron.patch")
if patches != copied:
errors.append(
"Dockerfile patch set differs from docker/patch/latest: "
f"only_patches={sorted(patches - copied)}, "
f"only_dockerfile={sorted(copied - patches)}"
)
applied = set(
re.findall(
r"git apply(?:\s+--?[\w-]+)*\s+(?:/tmp/)?([^ \\]+\.patch)",
dockerfile,
)
)
if patches != applied:
errors.append(
"Dockerfile does not apply every patch: "
f"not_applied={sorted(patches - applied)}, "
f"unknown={sorted(applied - patches)}"
)
for patch in sorted(patches):
if not (patch_dir / patch).read_text().startswith("diff --git "):
errors.append(f"invalid git patch: {patch}")

justfile = (repo / "docker/justfile").read_text()
if 'VERSION="$(cat docker/version.txt | tr -d' not in justfile:
errors.append("docker/justfile does not source docker/version.txt")

if errors:
print(*[f"ERROR: {error}" for error in errors], sep="\n", file=sys.stderr)
return 1

print(f"release={package_version}, image={image_tag}, " f"patches={','.join(sorted(patches))}")
return 0


if __name__ == "__main__":
raise SystemExit(main())
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ The vLLM community horizontally supports many LLM post-training frameworks, incl
- [Quick Start](#quick-start)
- [Agentic RL examples](#agentic-rl-examples)
- [Arguments Walkthrough](#arguments-walkthrough)
- [Engine Deployment](#engine-deployment)
- [Correctness, Stability, and CI](#correctness-stability-and-ci)
- [Code Reading Path](#code-reading-path)
- [Developer Guide](#developer-guide)
- [slime doc](#slime-doc)
Expand Down Expand Up @@ -81,6 +83,29 @@ Arguments in Vime are divided into three categories:

For complete usage instructions, please refer to the [Usage Documentation](docs/en/get_started/usage.md).

## Engine Deployment

Vime keeps the Megatron and vLLM control surfaces close to the upstream engines while adding the RL dataflow around them. Beyond the argument pass-through described above, see:

- [vLLM Config](docs/en/advanced/vllm-config.md) for optional YAML topology configuration, heterogeneous server groups, multi-model serving, and per-group overrides;
- [PD Disaggregation](docs/en/advanced/pd-disaggregation.md) for multi-turn and agentic workloads with different prefill/decode resource needs;
- router policies such as session affinity for multi-turn agents (see [vLLM Config](docs/en/advanced/vllm-config.md));
- [Delta Weight Sync](docs/en/advanced/delta-weight-sync.md) for disk-based updates of disaggregated rollout engines;
- [External Rollout Engines](docs/en/advanced/external-rollout-engines.md) for serving managed outside the training job. Serving can use an independent environment; disk transport avoids an NCCL group between training and serving. Different GPU models or vendors still require compatible model formats, precision, and vLLM hardware support.

## Correctness, Stability, and CI

RL bugs can be silent. Vime keeps the dataflow explicit and supports separate rollout-only and train-only debugging paths. CPU unit tests, customization-hook contract tests, and GPU end-to-end suites protect different parts of this workflow. Buildkite runs always-on CPU checks; GPU suites require the manual gate, so a green CPU build is not GPU validation.

Useful engineering docs:

- [CI](docs/en/developer_guide/ci.md)
- [Debugging](docs/en/developer_guide/debug.md)
- [Reproducibility](docs/en/advanced/reproducibility.md)
- [Fault Tolerance](docs/en/advanced/fault-tolerance.md)
- [Trace Viewer](docs/en/developer_guide/trace.md)
- [Profiling](docs/en/developer_guide/profiling.md)

## Code Reading Path

Start from the training loop and follow the calls only as deep as needed:
Expand Down
25 changes: 25 additions & 0 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ vLLM 社区横向支持许多 LLM post-training 框架,包括(按字母顺
- [快速开始](#快速开始)
- [Agentic RL 示例](#agentic-rl-示例)
- [参数说明](#参数说明)
- [Engine 部署](#engine-部署)
- [正确性、稳定性与 CI](#正确性稳定性与-ci)
- [代码阅读路径](#代码阅读路径)
- [开发指南](#开发指南)
- [slime doc](#slime-doc)
Expand Down Expand Up @@ -81,6 +83,29 @@ Vime 的参数分为三类:

完整使用说明请查阅 [使用文档](docs/zh/get_started/usage.md)。

## Engine 部署

Vime 在 Megatron 与 vLLM 原生控制接口外组织 RL 数据流。除上述参数透传外,请参阅:

- [vLLM Config](docs/zh/advanced/vllm-config.md):可选的 YAML 拓扑配置、异构 server group、多模型 serving 和 per-group override;
- [PD Disaggregation](docs/zh/advanced/pd-disaggregation.md):面向 prefill/decode 资源需求不同的多轮和 agentic 工作负载;
- 面向多轮 agent 的 session affinity 等 router policy,见 [vLLM Config](docs/zh/advanced/vllm-config.md);
- [Delta Weight Sync](docs/zh/advanced/delta-weight-sync.md):分离部署 rollout engine 的磁盘增量更新;
- [External Rollout Engines](docs/zh/advanced/external-rollout-engines.md):由训练任务外部管理 serving。Serving 可以使用独立环境;disk transport 无需训练端和 serving 端组成 NCCL group。不同 GPU 型号或厂商仍需满足模型格式、精度和 vLLM 硬件支持的兼容要求。

## 正确性、稳定性与 CI

RL bug 可能不会立即报错。Vime 保持显式数据流,支持 rollout-only 和 train-only 分离调试。CPU 单测、customization hook contract test 和 GPU 端到端测试分别保护这条链路的不同部分。Buildkite 自动运行 CPU 检查;GPU suite 需要手动开启 gate,因此 CPU 构建通过不代表 GPU 验证通过。

相关工程文档:

- [CI](docs/zh/developer_guide/ci.md)
- [Debugging](docs/zh/developer_guide/debug.md)
- [Reproducibility](docs/zh/advanced/reproducibility.md)
- [Fault Tolerance](docs/zh/advanced/fault-tolerance.md)
- [Trace Viewer](docs/zh/developer_guide/trace.md)
- [Profiling](docs/zh/developer_guide/profiling.md)

## 代码阅读路径

建议从训练主循环开始,只在需要时继续深入:
Expand Down
18 changes: 12 additions & 6 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ ARG FLASH_QLA_COMMIT=821fd9d37ede18fdc2a4e707fefe3770bfc32e58
ARG TRANSFORMER_ENGINE_COMMIT=c9877beb87ad7e711e1869dd0b5062167ede447a
ARG TRANSFORMER_ENGINE_CUDA_ARCHS=90;100a;103a
ARG TMS_COMMIT=8d30c59ca12a68d9deccbc9c6599076a1218cbc5
ARG TMS_CUDA_MAJOR=

ARG ENABLE_CUDA_13=1
ARG FA2_MAX_JOBS=64
Expand Down Expand Up @@ -97,7 +98,7 @@ RUN git clone https://github.com/NVIDIA/Megatron-LM.git --recursive && \

# zhuzilin fork builds, grouped together right after Megatron-LM:
# torch_memory_saver, plus the GLM-5 train/rollout alignment kernels.
RUN TMS_CUDA_MAJOR="$(python -c 'import torch; print(torch.version.cuda.split(".")[0])')" && \
RUN TMS_CUDA_MAJOR="${TMS_CUDA_MAJOR:-$(python -c 'import torch; print(torch.version.cuda.split(".")[0])')}" && \
export TMS_CUDA_MAJOR && \
pip install git+https://github.com/zhuzilin/torch_memory_saver.git@${TMS_COMMIT} --no-cache-dir --force-reinstall

Expand All @@ -118,7 +119,7 @@ RUN git clone https://github.com/zhuzilin/DeepEP.git /root/DeepEP && \
TORCH_CUDA_ARCH_LIST="${CUDA_ARCHS}" MAX_JOBS=64 python setup.py bdist_wheel && \
pip install --force-reinstall --no-deps dist/deep_ep-*.whl && \
cd /root/ && rm -rf DeepEP
RUN pip install nvidia-modelopt[torch]>=0.37.0 --no-build-isolation
RUN pip install "nvidia-modelopt[torch]>=0.37.0" --no-build-isolation

COPY requirements.txt /tmp/requirements.txt
RUN pip install --ignore-installed PyJWT && \
Expand Down Expand Up @@ -148,16 +149,21 @@ RUN cd Megatron-LM && \
rm -f megatron*.patch && \
pip install -e .

# Patch vLLM with vime's local fixes. vLLM is a pip install (not a git checkout)
# so apply with plain `git apply` (no --3way). Pull-weights lands first because
# the general patch also updates gpu_worker.py against the resulting line layout.
# Patch vLLM with vime's local fixes. vLLM is a pip install (not a git checkout),
# so apply the independently maintained patches in their validated order.
COPY docker/patch/${PATCH_VERSION}/vllm-pull_weights.patch /tmp/vllm-pull_weights.patch
COPY docker/patch/${PATCH_VERSION}/vllm.patch /tmp/vllm.patch
COPY docker/patch/${PATCH_VERSION}/vllm-pd-request-metrics.patch /tmp/vllm-pd-request-metrics.patch
COPY docker/patch/${PATCH_VERSION}/vllm-inflight-queue-diagnostics.patch /tmp/vllm-inflight-queue-diagnostics.patch
RUN VLLM_SITE="$(python3 -c 'import os, vllm; print(os.path.dirname(os.path.dirname(vllm.__file__)))')" && \
cd "$VLLM_SITE" && \
git apply -v /tmp/vllm-pull_weights.patch && \
git apply -v --allow-empty /tmp/vllm.patch && \
rm /tmp/vllm-pull_weights.patch /tmp/vllm.patch
git apply -v /tmp/vllm-pd-request-metrics.patch && \
git apply -v /tmp/vllm-inflight-queue-diagnostics.patch && \
rm /tmp/vllm-pull_weights.patch /tmp/vllm.patch \
/tmp/vllm-pd-request-metrics.patch \
/tmp/vllm-inflight-queue-diagnostics.patch

# ====================================== Install main package ============================================

Expand Down
Loading