ci(sgl-router): add PR test workflow (pre-positioned for feature PR) (#25854)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
8131641bc6
commit
a1843524a5
@@ -0,0 +1,347 @@
|
||||
name: PR Test (sgl-router)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "experimental/sgl-router/**"
|
||||
- ".github/workflows/pr-test-sgl-router.yml"
|
||||
- "scripts/ci/cuda/ci_install_dependency.sh"
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types: [opened, synchronize, reopened, labeled]
|
||||
paths:
|
||||
- "experimental/sgl-router/**"
|
||||
- ".github/workflows/pr-test-sgl-router.yml"
|
||||
- "scripts/ci/cuda/ci_install_dependency.sh"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: sgl-router-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
SGLANG_IS_IN_CI: true
|
||||
|
||||
jobs:
|
||||
sgl-router-lint:
|
||||
name: tier-1 — lint
|
||||
if: |
|
||||
github.event_name != 'pull_request' ||
|
||||
(github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'run-ci')) ||
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'run-ci')
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
# Scoped per-job (not workflow-wide) to avoid SMG's documented breakage
|
||||
# of self-hosted-runner pip installs that compile Rust extensions.
|
||||
RUSTC_WRAPPER: sccache
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
# Supply-chain guard: deny.toml allowlists the dynamo git source but
|
||||
# cannot pin a SHA. Bumps to this constant must be a deliberate PR.
|
||||
DYNAMO_TOKENIZERS_EXPECTED_REV: "1efdd4dcb901caeae636131321094090d252c8d6"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Verify dynamo-* SHA pin
|
||||
run: |
|
||||
set -euo pipefail
|
||||
revs=$(grep -E '^dynamo-[a-z]+[[:space:]]*=.*\brev[[:space:]]*=' experimental/sgl-router/Cargo.toml \
|
||||
| sed -E 's/.*rev[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/')
|
||||
if [ -z "${revs:-}" ]; then
|
||||
echo "::error::no dynamo-* git deps with rev= found in experimental/sgl-router/Cargo.toml"
|
||||
exit 1
|
||||
fi
|
||||
count=0
|
||||
while IFS= read -r r; do
|
||||
count=$((count + 1))
|
||||
if [ "$r" != "$DYNAMO_TOKENIZERS_EXPECTED_REV" ]; then
|
||||
echo "::error::dynamo-* SHA pin mismatch in experimental/sgl-router/Cargo.toml"
|
||||
echo " expected: $DYNAMO_TOKENIZERS_EXPECTED_REV"
|
||||
echo " actual: $r"
|
||||
echo "All revs found:"
|
||||
echo "$revs" | sed 's/^/ /'
|
||||
exit 1
|
||||
fi
|
||||
done <<< "$revs"
|
||||
echo "dynamo-* SHA pin OK: $count entries all at $DYNAMO_TOKENIZERS_EXPECTED_REV"
|
||||
|
||||
# sccache install is best-effort: the GitHub release CDN intermittently
|
||||
# returns 5xx and the action's built-in retry is shallow. If it fails,
|
||||
# we unset RUSTC_WRAPPER below and proceed without compile caching —
|
||||
# rust-cache still covers the bulk of warm-cache wins.
|
||||
- name: Configure sccache
|
||||
id: sccache
|
||||
continue-on-error: true
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
with:
|
||||
version: "v0.12.0"
|
||||
disable_annotations: true
|
||||
|
||||
- name: Disable sccache if install failed
|
||||
if: steps.sccache.outcome != 'success'
|
||||
run: |
|
||||
echo "::warning::sccache install failed (${{ steps.sccache.outcome }}); proceeding without compile caching"
|
||||
echo "RUSTC_WRAPPER=" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: experimental/sgl-router
|
||||
shared-key: "sgl-router-cache"
|
||||
cache-all-crates: true
|
||||
cache-on-failure: true
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: cargo check
|
||||
working-directory: experimental/sgl-router
|
||||
run: cargo check --all-targets
|
||||
|
||||
- name: cargo clippy
|
||||
working-directory: experimental/sgl-router
|
||||
run: cargo clippy --all-targets -- -D warnings
|
||||
|
||||
- name: cargo fmt
|
||||
working-directory: experimental/sgl-router
|
||||
run: |
|
||||
rustup toolchain install nightly --profile minimal --component rustfmt
|
||||
cargo +nightly fmt -- --check
|
||||
|
||||
# cargo-deny 0.19.6+ required for CVSS 4.0 parsing.
|
||||
- name: Install cargo-deny
|
||||
run: cargo install --locked cargo-deny@0.19.6
|
||||
|
||||
- name: cargo deny check
|
||||
working-directory: experimental/sgl-router
|
||||
run: cargo deny check licenses bans sources advisories
|
||||
|
||||
- name: Show sccache stats
|
||||
if: always() && steps.sccache.outcome == 'success'
|
||||
run: sccache --show-stats
|
||||
|
||||
sgl-router-build-and-test:
|
||||
name: tier-2 — build + test
|
||||
needs: sgl-router-lint
|
||||
if: |
|
||||
github.event_name != 'pull_request' ||
|
||||
(github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'run-ci')) ||
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'run-ci')
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUSTC_WRAPPER: sccache
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install OS deps (libssl, pkg-config)
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libssl-dev pkg-config
|
||||
|
||||
# sccache install is best-effort: the GitHub release CDN intermittently
|
||||
# returns 5xx and the action's built-in retry is shallow. If it fails,
|
||||
# we unset RUSTC_WRAPPER below and proceed without compile caching —
|
||||
# rust-cache still covers the bulk of warm-cache wins.
|
||||
- name: Configure sccache
|
||||
id: sccache
|
||||
continue-on-error: true
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
with:
|
||||
version: "v0.12.0"
|
||||
disable_annotations: true
|
||||
|
||||
- name: Disable sccache if install failed
|
||||
if: steps.sccache.outcome != 'success'
|
||||
run: |
|
||||
echo "::warning::sccache install failed (${{ steps.sccache.outcome }}); proceeding without compile caching"
|
||||
echo "RUSTC_WRAPPER=" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: experimental/sgl-router
|
||||
shared-key: "sgl-router-cache"
|
||||
cache-all-crates: true
|
||||
cache-on-failure: true
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: cargo build (record wall time)
|
||||
working-directory: experimental/sgl-router
|
||||
run: |
|
||||
START=$(date +%s)
|
||||
cargo build --release --all-targets
|
||||
END=$(date +%s)
|
||||
BUILD_SECONDS=$((END - START))
|
||||
echo "tier_2_build_duration_seconds=${BUILD_SECONDS}" >> "$GITHUB_ENV"
|
||||
echo "::notice title=Tier-2 build duration::${BUILD_SECONDS}s"
|
||||
|
||||
- name: cargo test
|
||||
working-directory: experimental/sgl-router
|
||||
# Skip tokenizer_parity here: ubuntu-latest has no HuggingFace cache,
|
||||
# so the matrix would hard-fail (see the test docstring). The e2e job
|
||||
# runs it after pytest populates the Qwen3-0.6B tokenizer.json.
|
||||
run: cargo test --release -- --skip tokenizer_parity
|
||||
|
||||
# Regenerate the cross-impl block-hash parity fixture and fail if it
|
||||
# differs from the committed file. The Python script replicates
|
||||
# SGLang's `radix_cache.hash_page` algorithm verbatim (no
|
||||
# `import sglang`); a diff here means EITHER the Rust port's algorithm
|
||||
# drifted from the script, OR the script drifted from SGLang's actual
|
||||
# code — in both cases the operator must investigate and re-commit
|
||||
# the fixture in the same PR as the algorithm change.
|
||||
#
|
||||
# `set -euo pipefail` makes a Python-side failure (syntax error,
|
||||
# missing import) cause this step to fail instead of falling
|
||||
# through to a no-diff "pass" when no fixture was actually
|
||||
# regenerated.
|
||||
- name: kv_events hash parity fixture (drift check)
|
||||
working-directory: experimental/sgl-router
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 tests/scripts/generate_kv_events_hash_parity.py
|
||||
if ! git diff --exit-code -- tests/fixtures/kv_events_hash_parity.json; then
|
||||
echo "::error::kv_events hash parity fixture drifted." \
|
||||
"Re-run experimental/sgl-router/tests/scripts/generate_kv_events_hash_parity.py" \
|
||||
"and commit the updated tests/fixtures/kv_events_hash_parity.json."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Show sccache stats
|
||||
if: always() && steps.sccache.outcome == 'success'
|
||||
run: sccache --show-stats
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Tier-2 results" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- Build duration: \`${tier_2_build_duration_seconds:-(not recorded)}\` s" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
sgl-router-docker-build-test:
|
||||
name: tier-3 — docker (placeholder)
|
||||
needs: sgl-router-build-and-test
|
||||
if: |
|
||||
github.event_name != 'pull_request' ||
|
||||
(github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'run-ci')) ||
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'run-ci')
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Placeholder
|
||||
run: |
|
||||
echo "docker-build-test not implemented yet."
|
||||
exit 0
|
||||
|
||||
sgl-router-k8s-integration:
|
||||
name: tier-3 — k8s integration
|
||||
needs: sgl-router-build-and-test
|
||||
if: |
|
||||
github.event_name != 'pull_request' ||
|
||||
(github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'run-ci')) ||
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'run-ci')
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Install kind
|
||||
run: |
|
||||
curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v0.24.0/kind-linux-amd64
|
||||
chmod +x /tmp/kind && sudo mv /tmp/kind /usr/local/bin/kind
|
||||
- name: Install kubectl
|
||||
run: |
|
||||
curl -Lo /tmp/kubectl https://dl.k8s.io/release/v1.31.0/bin/linux/amd64/kubectl
|
||||
chmod +x /tmp/kubectl && sudo mv /tmp/kubectl /usr/local/bin/kubectl
|
||||
- name: Build router image
|
||||
run: docker build -t sgl-router:e2e -f experimental/sgl-router/tests/e2e/k8s_integration/Dockerfile.router .
|
||||
- name: Build fake_worker image
|
||||
run: |
|
||||
docker build -t sgl-router-fake-worker:e2e \
|
||||
-f experimental/sgl-router/tests/e2e/k8s_integration/Dockerfile.fake_worker \
|
||||
experimental/sgl-router/tests/e2e/k8s_integration/
|
||||
- name: Bootstrap kind + deploy
|
||||
run: bash experimental/sgl-router/tests/e2e/k8s_integration/setup.sh
|
||||
- name: Set up Python
|
||||
run: |
|
||||
python3 -m venv /tmp/e2e-venv
|
||||
/tmp/e2e-venv/bin/pip install -r experimental/sgl-router/tests/e2e/k8s_integration/requirements.txt
|
||||
- name: Run E2E
|
||||
run: /tmp/e2e-venv/bin/pytest experimental/sgl-router/tests/e2e/k8s_integration/ -v --tb=short
|
||||
- name: Dump router logs on failure
|
||||
if: failure()
|
||||
run: kubectl -n sgl-router-test logs deploy/sgl-router --tail=200 || true
|
||||
|
||||
sgl-router-e2e:
|
||||
name: tier-3 — e2e
|
||||
needs: sgl-router-build-and-test
|
||||
if: |
|
||||
github.event_name != 'pull_request' ||
|
||||
(github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'run-ci')) ||
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'run-ci')
|
||||
runs-on: 2-gpu-h100
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: experimental/sgl-router
|
||||
shared-key: "sgl-router-cache"
|
||||
- name: cargo build (release)
|
||||
working-directory: experimental/sgl-router
|
||||
run: cargo build --release
|
||||
|
||||
# Install SGLang from the local checkout in editable mode (no PyPI
|
||||
# version pin) — mirrors `pr-test-rust.yml` so the router e2e runs
|
||||
# against whatever SGLang version is on the branch. Replaces the
|
||||
# previous `pip install sglang[srt]==X.Y.Z` pin, which forced the
|
||||
# e2e to test against a stale wheel and would silently miss any
|
||||
# new ServerArgs field (e.g. `disaggregation_mode` /
|
||||
# `disaggregation_bootstrap_port`) added in the branch.
|
||||
- name: Install SGLang dependencies
|
||||
run: |
|
||||
bash scripts/ci/cuda/ci_install_dependency.sh
|
||||
|
||||
- name: Install e2e test dependencies
|
||||
run: |
|
||||
python3 -m pip install -r experimental/sgl-router/tests/e2e/requirements.txt
|
||||
|
||||
- name: Run e2e
|
||||
working-directory: experimental/sgl-router
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
||||
run: python3 -m pytest tests/e2e/ -v -s --tb=short
|
||||
|
||||
- name: Tokenizer parity matrix (uses HF cache populated by e2e)
|
||||
working-directory: experimental/sgl-router
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
||||
# Runs after pytest so the Qwen3-0.6B tokenizer.json is cached by the
|
||||
# SGLang worker. Cells without a cached snapshot are skipped; if no
|
||||
# cells could be checked, the test hard-fails under SGLANG_IS_IN_CI.
|
||||
run: cargo test --release --test component tokenizer::parity
|
||||
|
||||
sgl-router-finish:
|
||||
name: finish
|
||||
needs:
|
||||
- sgl-router-lint
|
||||
- sgl-router-build-and-test
|
||||
- sgl-router-docker-build-test
|
||||
- sgl-router-k8s-integration
|
||||
- sgl-router-e2e
|
||||
# Gate finish on the same label as upstream jobs to avoid false-green on
|
||||
# unlabeled PRs (skipped needs => success), and fail when any upstream
|
||||
# job actually failed or was cancelled.
|
||||
if: |
|
||||
always() &&
|
||||
!contains(needs.*.result, 'failure') &&
|
||||
!contains(needs.*.result, 'cancelled') &&
|
||||
(
|
||||
github.event_name != 'pull_request' ||
|
||||
(github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'run-ci')) ||
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'run-ci')
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: All required checks completed
|
||||
run: echo "OK"
|
||||
Reference in New Issue
Block a user