8000 Require full WSL2 qualification for v0.11 by DaoyuanLi2816 · Pull Request #99 · DaoyuanLi2816/mini-verl · GitHub
[go: up one dir, main page]

Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/actionlint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ self-hosted-runner:
labels:
- cuda
- rtx4080
- wsl2
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ jobs:
"PROJECT_STATE.md",
"environments/known-good-rtx4080-cu130.json",
"environments/known-good-rtx4080-cu130.txt",
"environments/known-good-rtx4080-wsl2-cu130.json",
"environments/known-good-rtx4080-wsl2-cu130.txt",
"scripts/run_v011_profile_qualification.py",
"LICENSE",
"README.md",
):
Expand Down
187 changes: 115 additions & 72 deletions .github/workflows/gpu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
workflow_dispatch:
inputs:
qualification_level:
description: "Release smoke, or all three canonical full workloads"
description: "Release smoke, or the complete v0.11 WSL2 qualification"
type: choice
required: true
default: smoke
Expand Down Expand Up @@ -62,23 +62,21 @@ jobs:
retention-days: 90

qualify:
name: qualify exact candidate on RTX 4080
name: qualify exact candidate on WSL2 RTX 4080
needs: build-candidate
runs-on: [self-hosted, cuda, rtx4080]
runs-on: [self-hosted, cuda, rtx4080, wsl2]
timeout-minutes: 360
env:
PYTHONUTF8: "1"
PYTHONPATH: ""
PYTHONHOME: ""
defaults:
run:
shell: pwsh
steps:
- name: Require a fresh workflow dispatch
run: |
if ($env:GITHUB_RUN_ATTEMPT -ne "1") {
throw "GPU qualification cannot use a rerun attempt; start a new workflow_dispatch run."
}
if [ "$GITHUB_RUN_ATTEMPT" != "1" ]; then
echo "::error::GPU qualification cannot use a rerun attempt; start a new workflow_dispatch run."
exit 1
fi
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
Expand All @@ -89,58 +87,72 @@ jobs:
path: candidate
- name: Verify the dedicated measured device
run: |
$name = nvidia-smi --query-gpu=name --format=csv,noheader
if (($name | Measure-Object).Count -ne 1) { throw "exactly one visible GPU is required" }
if ($name.Trim() -ne "NVIDIA GeForce RTX 4080") { throw "unexpected GPU: $name" }
mapfile -t names < <(nvidia-smi --query-gpu=name --format=csv,noheader)
if [ "${#names[@]}" -ne 1 ]; then echo "exactly one visible GPU is required"; exit 1; fi
if [ "${names[0]}" != "NVIDIA GeForce RTX 4080" ]; then
echo "unexpected GPU: ${names[0]}"
exit 1
fi
grep -qi microsoft /proc/sys/kernel/osrelease
nvidia-smi
python --version
python3 --version
test -x "$HOME/.local/bin/uv"
"$HOME/.local/bin/uv" --version
- name: Validate candidate before installation
run: |
python -m venv .gpu-qualification-venv
$python = (Resolve-Path '.gpu-qualification-venv/Scripts/python.exe').Path
& $python -m pip install --upgrade pip
& $python -m pip install pydantic==2.13.4 PyYAML==6.0.3
& $python scripts/build_release_candidate.py `
--output candidate `
--commit $env:GITHUB_SHA `
--repository $env:GITHUB_REPOSITORY `
--workflow-path .github/workflows/gpu.yml `
--run-id $env:GITHUB_RUN_ID `
--run-attempt $env:GITHUB_RUN_ATTEMPT `
"$HOME/.local/bin/uv" venv --seed --python 3.12.13 .gpu-qualification-venv
qualification_python="$PWD/.gpu-qualification-venv/bin/python"
"$qualification_python" -m pip install --upgrade pip
"$qualification_python" -m pip install pydantic==2.13.4 PyYAML==6.0.3
"$qualification_python" scripts/build_release_candidate.py \
--output candidate \
--commit "$GITHUB_SHA" \
--repository "$GITHUB_REPOSITORY" \
--workflow-path .github/workflows/gpu.yml \
--run-id "$GITHUB_RUN_ID" \
--run-attempt "$GITHUB_RUN_ATTEMPT" \
--check
$manifest = Get-Content -Raw -LiteralPath 'candidate/candidate-manifest.json' | ConvertFrom-Json
$wheel = (Resolve-Path (Join-Path 'candidate' $manifest.wheel.filename)).Path
"QUALIFICATION_WHEEL=$wheel" | Out-File -FilePath $env:GITHUB_ENV -Append
"QUALIFICATION_PYTHON=$python" | Out-File -FilePath $env:GITHUB_ENV -Append
"$qualification_python" - <<'PY'
import json, os
from pathlib import Path
manifest = json.loads(Path("candidate/candidate-manifest.json").read_text())
wheel = (Path("candidate") / manifest["wheel"]["filename"]).resolve()
with Path(os.environ["GITHUB_ENV"]).open("a", encoding="utf-8") as handle:
handle.write(f"QUALIFICATION_WHEEL={wheel}\n")
handle.write(f"QUALIFICATION_WHEEL_SHA256={manifest['wheel']['sha256']}\n")
handle.write(f"QUALIFICATION_PYTHON={Path('.gpu-qualification-venv/bin/python').resolve()}\n")
PY
- name: Install the exact candidate wheel
run: |
& $env:QUALIFICATION_PYTHON -m pip install 'torch==2.13.0+cu130' --index-url https://download.pytorch.org/whl/cu130
& $env:QUALIFICATION_PYTHON -m pip install "$env:QUALIFICATION_WHEEL[dev,train,cuda]" --constraint environ 8000 ments/known-good-rtx4080-cu130.txt
(Resolve-Path '.gpu-qualification-venv/Scripts').Path | Out-File -FilePath $env:GITHUB_PATH -Append
"$QUALIFICATION_PYTHON" -m pip install 'torch==2.13.0+cu130' --index-url https://download.pytorch.org/whl/cu130
"$QUALIFICATION_PYTHON" -m pip install \
"${QUALIFICATION_WHEEL}[dev,train,cuda,rollout-vllm]" \
--constraint environments/known-good-rtx4080-wsl2-cu130.txt
echo "$PWD/.gpu-qualification-venv/bin" >> "$GITHUB_PATH"
- name: Run wheel-installed release smoke
run: |
$offline = @()
if ('${{ inputs.offline }}' -eq 'true') { $offline = @('--offline') }
New-Item -ItemType Directory -Path qualification | Out-Null
Copy-Item -LiteralPath $env:QUALIFICATION_WHEEL -Destination qualification
& $env:QUALIFICATION_PYTHON scripts/run_gpu_qualification.py `
--commit $env:GITHUB_SHA `
--wheel (Join-Path 'qualification' (Split-Path $env:QUALIFICATION_WHEEL -Leaf)) `
--candidate-manifest candidate/candidate-manifest.json `
--known-good environments/known-good-rtx4080-cu130.json `
--output qualification `
--work qualification-work `
@offline
$candidate = Get-Content -Raw candidate/candidate-manifest.json | ConvertFrom-Json
$manifestSha = (Get-FileHash -Algorithm SHA256 candidate/candidate-manifest.json).Hash.ToLower()
& $env:QUALIFICATION_PYTHON scripts/validate_gpu_qualification.py `
qualification/qualification.json `
--commit $env:GITHUB_SHA `
--wheel-sha256 $candidate.wheel.sha256 `
--candidate-manifest-sha256 $manifestSha `
offline=()
if [ '${{ inputs.offline }}' = 'true' ]; then offline=(--offline); fi
mkdir qualification
cp -- "$QUALIFICATION_WHEEL" qualification/
wheel_copy="qualification/$(basename "$QUALIFICATION_WHEEL")"
"$QUALIFICATION_PYTHON" scripts/run_gpu_qualification.py \
--commit "$GITHUB_SHA" \
--wheel "$wheel_copy" \
--candidate-manifest candidate/candidate-manifest.json \
--known-good environments/known-good-rtx4080-wsl2-cu130.json \
--output qualification \
--work qualification-work \
"${offline[@]}"
manifest_sha=$(sha256sum candidate/candidate-manifest.json | cut -d' ' -f1)
"$QUALIFICATION_PYTHON" scripts/validate_gpu_qualification.py \
qualification/qualification.json \
--commit "$GITHUB_SHA" \
--wheel-sha256 "$QUALIFICATION_WHEEL_SHA256" \
--candidate-manifest-sha256 "$manifest_sha" \
--required-gpu-name 'NVIDIA GeForce RTX 4080'
- name: Run GPU regression tests
run: '& $env:QUALIFICATION_PYTHON -m pytest -q -m gpu -ra'
run: '"$QUALIFICATION_PYTHON" -m pytest -q -m gpu -ra'
- name: Upload exact-commit release smoke
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
Expand All @@ -151,19 +163,42 @@ jobs:
- name: Run full canonical qualification workloads
if: ${{ inputs.qualification_level == 'full' }}
run: |
$offline = @()
if ('${{ inputs.offline }}' -eq 'true') { $offline = @('--offline') }
& $env:QUALIFICATION_PYTHON -m pip install 'hydra-core>=1.3,<2'
& $env:QUALIFICATION_PYTHON -m pip install --no-deps 'git+https://github.com/verl-project/verl.git@7aed6b230776f963fa09509c10d9c3a767d1102c'
New-Item -ItemType Directory -Path qualification-full | Out-Null
& $env:QUALIFICATION_PYTHON scripts/run_verl_opd_reference_workload.py --out qualification-full-work/direct --result qualification-full/direct.json @offline
& $env:QUALIFICATION_PYTHON scripts/run_verl_pg_k1_workload.py --out qualification-full-work/pg-k1 --result qualification-full/pg-k1.json @offline
& $env:QUALIFICATION_PYTHON scripts/run_smollm2_opd_reference_workload.py --out qualification-full-work/smollm2 --result qualification-full/smollm2.json @offline
& $env:QUALIFICATION_PYTHON scripts/promote_full_gpu_qualification.py `
--qualification qualification/qualification.json `
--direct qualification-full/direct.json `
--pg-k1 qualification-full/pg-k1.json `
--smollm2 qualification-full/smollm2.json
offline=()
if [ '${{ inputs.offline }}' = 'true' ]; then offline=(--offline); fi
"$QUALIFICATION_PYTHON" -m pip install 'hydra-core>=1.3,<2'
"$QUALIFICATION_PYTHON" -m pip install --no-deps 'git+https://github.com/verl-project/verl.git@7aed6b230776f963fa09509c10d9c3a767d1102c'
mkdir qualification-full
"$QUALIFICATION_PYTHON" scripts/run_verl_opd_reference_workload.py --out qualification-full-work/direct --result qualification-full/direct.json "${offline[@]}"
"$QUALIFICATION_PYTHON" scripts/run_verl_pg_k1_workload.py --out qualification-full-work/pg-k1 --result qualification-full/pg-k1.json "${offline[@]}"
"$QUALIFICATION_PYTHON" scripts/run_smollm2_opd_reference_workload.py --out qualification-full-work/smollm2 --result qualification-full/smollm2.json "${offline[@]}"
"$QUALIFICATION_PYTHON" scripts/run_v011_profile_qualification.py \
--commit "$GITHUB_SHA" \
--wheel "$QUALIFICATION_WHEEL" \
--wheel-sha256 "$QUALIFICATION_WHEEL_SHA256" \
--output qualification-full/v011-profiles.json \
--work qualification-full-work/v011-profiles \
"${offline[@]}"
"$QUALIFICATION_PYTHON" scripts/benchmark_rollout_runtime_v2_selected.py \
--backend hf_cached \
--wheel "$QUALIFICATION_WHEEL" \
--expected-commit "$GITHUB_SHA" \
--output qualification-full/hf-cached-runtime.json \
--repetitions 3
"$QUALIFICATION_PYTHON" scripts/benchmark_rollout_runtime_v2_selected.py \
--backend vllm \
--wheel "$QUALIFICATION_WHEEL" \
--expected-commit "$GITHUB_SHA" \
--output qualification-full/vllm-runtime.json \
--repetitions 3
"$QUALIFICATION_PYTHON" scripts/promote_full_gpu_qualification.py \
--qualification qualification/qualification.json \
--direct qualification-full/direct.json \
--pg-k1 qualification-full/pg-k1.json \
--smollm2 qualification-full/smollm2.json \
--v011-profiles qualification-full/v011-profiles.json \
--hf-cached-runtime qualification-full/hf-cached-runtime.json \
--vllm-runtime qualification-full/vllm-runtime.json \
--hf-reference benchmarks/results/rollout-runtime-v2-hf-reference.json
- name: Upload full qualification records
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ success() && inputs.qualification_level == 'full' }}
Expand All @@ -175,11 +210,19 @@ jobs:
- name: Remove runner-local environments and model work products
if: always()
run: |
foreach ($relative in @('.gpu-qualification-venv', 'qualification-work', 'qualification-full-work')) {
if (Test-Path -LiteralPath $relative) {
$target = (Resolve-Path -LiteralPath $relative).Path
$workspace = (Resolve-Path -LiteralPath $env:GITHUB_WORKSPACE).Path
if (-not $target.StartsWith($workspace, [System.StringComparison]::OrdinalIgnoreCase)) { throw "refusing cleanup outside workspace: $target" }
Remove-Item -LiteralPath $target -Recurse -Force
}
}
python3 - <<'PY'
import os, shutil
from pathlib import Path
workspace = Path(os.environ["GITHUB_WORKSPACE"]).resolve()
for relative in (
".gpu-qualification-venv",
"qualification-work",
"qualification-full-work",
"qualification-full",
):
target = (workspace / relative).resolve()
if target.parent != workspace:
raise SystemExit(f"refusing cleanup outside workspace: {target}")
if target.exists():
shutil.rmtree(target)
PY
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
--qualification-artifact-name gpu-full-qualification \
--required-level full_qualification \
--required-gpu-name "NVIDIA GeForce RTX 4080" \
--known-good environments/known-good-rtx4080-cu130.json \
--known-good environments/known-good-rtx4080-wsl2-cu130.json \
--output verified-qualified-candidate
- name: Preserve the verified byte chain
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
Expand Down
27 changes: 18 additions & 9 deletions docs/release-qualification.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ workflow is manual, not continuous GPU CI and not a pull-request required check.
| level | cadence | executed scope |
| --- | --- | --- |
| release smoke | diagnostic use | install the hosted-runner candidate wheel, verify import/CLI origin, run CLI doctor/plan/dry-run, pinned Qwen actor and teacher, one rollout/score/update, PEFT export/reload and CUDA teardown |
| full qualification | every formal release | release smoke plus the unchanged direct-GKD, sampled-k1 and SmolLM2 canonical workloads, including interruption/resume and their existing export/materialization checks |
| full qualification | every formal release | release smoke, the three canonical resume workloads, and the v0.11 direct n=1, grouped PG n=4, rewarded PG n=4, `hf_cached` and vLLM runtime gates |

The smoke budget is intentionally small and is never substituted for a frozen
full workload or a scientific benchmark. Both levels record runtime
Expand All @@ -26,17 +26,20 @@ from the same successful run and first attempt.
GitHub-hosted `build-candidate` job creates exactly one wheel and one sdist with
the pinned build toolchain, validates them with Twine, and uploads
`candidate-distributions` with canonical checksums and a strict manifest. The
dependent `[self-hosted, cuda, rtx4080]` job downloads that same-run artifact,
dependent `[self-hosted, cuda, rtx4080, wsl2]` job downloads that same-run artifact,
validates it before installation, then installs the candidate wheel in a fresh
virtual environment using the [known-good stack](single-gpu-guide.md). It does
not build a distribution. `qualification.json` binds the candidate manifest,
full source SHA, exact wheel hash, workflow run, profile identity, model
revisions, inputs, measured environment and output hashes. It also proves that
both the imported package and `miniverl` executable came from the qualification
environment rather than a checkout or user site.
For a full run, the workflow additionally validates each canonical result's
exact resume, resource, PEFT and scale-out fields, then promotes that same
record to `full_qualification`; three loose result files cannot claim the
For a v0.11 full run, the workflow additionally performs three real one-update
profiles, all 24 preregistered cells on each rollout backend, eight policy
refreshes per backend, conformance and teardown. Promotion verifies the exact
source, wheel and environment bindings; speed and memory thresholds; grouped
trajectory identity; reward/advantage completion; and vLLM's
policy-gradient fail-closed boundary. Loose result files cannot claim the
higher level.

Every dispatch is single-use. Both jobs reject `GITHUB_RUN_ATTEMPT` values
Expand All @@ -54,7 +57,8 @@ SHA-256. Cross-origin artifact redirects retain ordinary API headers but strip
authorization, proxy authorization and cookies. It publishes the accepted
wheel and sdist without rebuilding them. Future release runs retain the full
qualification record, four principal workload JSON files and a deterministic
subordinate-evidence archive in the canonical layout below. A committed
subordinate-evidence archive. For v0.11, that archive also contains the three
profile/backend qualification records. A committed
JSON file, manual upload, fork run, different workflow or cross-run artifact
pair cannot satisfy this gate.

Expand All @@ -80,9 +84,10 @@ qualification-evidence-manifest.json
```

The four principal workload records remain directly inspectable. Adapter
configuration and safetensors, its miniVERL manifest, input prompts and the run
summary live once in the deterministic archive; its manifest binds every member
to its semantic role, byte count and SHA-256. `SHA256SUMS` covers only the wheel
configuration and safetensors, its miniVERL manifest, input prompts, run
summary and version-specific qualification records live once in the
deterministic archive; its manifest binds every member to its semantic role,
byte count and SHA-256. `SHA256SUMS` covers only the wheel
and sdist. `qualification-SHA256SUMS` covers the nine provenance and evidence
assets in canonical order. Hashes prove byte integrity, not code signing or
third-party endorsement.
Expand Down Expand Up @@ -127,6 +132,10 @@ portable bounded artifacts, and checks cleanup targets remain under
`GITHUB_WORKSPACE`. Model caches remain runner-local and are not uploaded.
Rotate the runner token after suspected exposure.

The v0.11 release runner is Ubuntu under WSL2 with Python 3.12.13 and the
checked `known-good-rtx4080-wsl2-cu130` stack. The older Windows qualification
record remains historical and cannot authorize v0.11.

## Measured release state

The first exact-commit full run completed for v0.10.1 on attempt 1:
Expand Down
7 changes: 5 additions & 2 deletions docs/single-gpu-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,16 @@ not select a CUDA-enabled PyTorch build on its own.
For the exact stack measured by the maintainer on one RTX 4080, use the
machine-readable
[`environments/known-good-rtx4080-cu130.json`](https://github.com/DaoyuanLi2816/mini-verl/blob/main/environments/known-good-rtx4080-cu130.json)
is the historical Windows v0.10.1 record. The active v0.11 release
qualification uses
[`environments/known-good-rtx4080-wsl2-cu130.json`](https://github.com/DaoyuanLi2816/mini-verl/blob/main/environments/known-good-rtx4080-wsl2-cu130.json)
and its constraints:

```bash
python -m pip install "torch==2.13.0+cu130" \
--index-url https://download.pytorch.org/whl/cu130
python -m pip install "miniverl[train,cuda]" \
--constraint environments/known-good-rtx4080-cu130.txt
python -m pip install "miniverl[train,cuda,rollout-vllm]" \
--constraint environments/known-good-rtx4080-wsl2-cu130.txt
python scripts/check_known_good_environment.py
```

Expand Down
Loading
0