diff --git a/.github/actions/check-maintenance/action.yml b/.github/actions/check-maintenance/action.yml index 595283dcd..f064cad52 100644 --- a/.github/actions/check-maintenance/action.yml +++ b/.github/actions/check-maintenance/action.yml @@ -1,5 +1,5 @@ name: Check Maintenance Mode -description: Blocks CI when maintenance mode is active (issue #21065 is open), unless the PR has the bypass-maintenance label, or env SGLANG_PR_TEST_BYPASS_MAINTENANCE_ON_MAIN=true (PR Test workflow on main only). Merging non-CI-fix PRs is prohibited during maintenance mode; in severe cases, merge permissions may be revoked. +description: Blocks CI when maintenance mode is active (issue #21065 is open), unless the PR has the bypass-maintenance label, or env PR_TEST_BYPASS_MAINTENANCE_ON_MAIN=true (PR Test workflow on main only). Merging non-CI-fix PRs is prohibited during maintenance mode; in severe cases, merge permissions may be revoked. inputs: github-token: @@ -20,7 +20,7 @@ runs: PR_NUMBER="${{ github.event.pull_request.number }}" # PR Test workflow only: scheduled runs and runs on main (dispatch / workflow_call) set this env - if [[ "${SGLANG_PR_TEST_BYPASS_MAINTENANCE_ON_MAIN:-}" == "true" ]]; then + if [[ "${PR_TEST_BYPASS_MAINTENANCE_ON_MAIN:-}" == "true" ]]; then echo "✅ PR Test on main branch; bypassing maintenance gate." exit 0 fi diff --git a/.github/workflows/nightly-test-nvidia.yml b/.github/workflows/nightly-test-nvidia.yml index d5ce7930b..0f4c754ab 100644 --- a/.github/workflows/nightly-test-nvidia.yml +++ b/.github/workflows/nightly-test-nvidia.yml @@ -91,7 +91,7 @@ jobs: # Match pr-test-jit-kernel workflow for consistent JIT warmup behavior SGLANG_JIT_DEEPGEMM_FAST_WARMUP: true # Allow maintenance bypass on default branch (same semantics as PR JIT workflow) - SGLANG_PR_TEST_BYPASS_MAINTENANCE_ON_MAIN: ${{ github.ref == 'refs/heads/main' && 'true' || 'false' }} + PR_TEST_BYPASS_MAINTENANCE_ON_MAIN: ${{ github.ref == 'refs/heads/main' && 'true' || 'false' }} steps: - name: Checkout code uses: actions/checkout@v4 @@ -121,7 +121,7 @@ jobs: env: SGLANG_JIT_KERNEL_RUN_FULL_TESTS: "1" SGLANG_JIT_DEEPGEMM_FAST_WARMUP: true - SGLANG_PR_TEST_BYPASS_MAINTENANCE_ON_MAIN: ${{ github.ref == 'refs/heads/main' && 'true' || 'false' }} + PR_TEST_BYPASS_MAINTENANCE_ON_MAIN: ${{ github.ref == 'refs/heads/main' && 'true' || 'false' }} steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/.github/workflows/pr-test-jit-kernel.yml b/.github/workflows/pr-test-jit-kernel.yml index 772e69392..90b232b07 100644 --- a/.github/workflows/pr-test-jit-kernel.yml +++ b/.github/workflows/pr-test-jit-kernel.yml @@ -36,7 +36,7 @@ env: SGLANG_IS_IN_CI: true SGLANG_CUDA_COREDUMP: "1" SGLANG_JIT_DEEPGEMM_FAST_WARMUP: true - SGLANG_PR_TEST_BYPASS_MAINTENANCE_ON_MAIN: ${{ github.ref == 'refs/heads/main' && 'true' || 'false' }} + PR_TEST_BYPASS_MAINTENANCE_ON_MAIN: ${{ github.ref == 'refs/heads/main' && 'true' || 'false' }} SKIP_STAGE_HEALTH_CHECK: ${{ inputs.skip_stage_health_check == true && 'true' || 'false' }} jobs: diff --git a/.github/workflows/pr-test-multimodal-gen.yml b/.github/workflows/pr-test-multimodal-gen.yml index 742e43757..110c812c0 100644 --- a/.github/workflows/pr-test-multimodal-gen.yml +++ b/.github/workflows/pr-test-multimodal-gen.yml @@ -46,7 +46,7 @@ on: env: SGLANG_IS_IN_CI: true SGLANG_CUDA_COREDUMP: "1" - SGLANG_PR_TEST_BYPASS_MAINTENANCE_ON_MAIN: ${{ github.ref == 'refs/heads/main' && 'true' || 'false' }} + PR_TEST_BYPASS_MAINTENANCE_ON_MAIN: ${{ github.ref == 'refs/heads/main' && 'true' || 'false' }} SKIP_STAGE_HEALTH_CHECK: ${{ inputs.skip_stage_health_check == 'true' }} jobs: diff --git a/.github/workflows/pr-test-sgl-kernel.yml b/.github/workflows/pr-test-sgl-kernel.yml index 0cfaf43d4..43d490a31 100644 --- a/.github/workflows/pr-test-sgl-kernel.yml +++ b/.github/workflows/pr-test-sgl-kernel.yml @@ -27,7 +27,7 @@ on: env: SGLANG_IS_IN_CI: true SGLANG_CUDA_COREDUMP: "1" - SGLANG_PR_TEST_BYPASS_MAINTENANCE_ON_MAIN: ${{ github.ref == 'refs/heads/main' && 'true' || 'false' }} + PR_TEST_BYPASS_MAINTENANCE_ON_MAIN: ${{ github.ref == 'refs/heads/main' && 'true' || 'false' }} SKIP_STAGE_HEALTH_CHECK: ${{ inputs.skip_stage_health_check == true && 'true' || 'false' }} jobs: diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index 28f310926..93ff2b38e 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -69,7 +69,7 @@ env: SGLANG_JIT_DEEPGEMM_FAST_WARMUP: true SKIP_STAGE_HEALTH_CHECK: ${{ inputs.skip_stage_health_check == true && 'true' || 'false' }} # Schedule / main-branch dispatch / workflow_call from main use refs/heads/main; PR events use refs/pull/*/merge - SGLANG_PR_TEST_BYPASS_MAINTENANCE_ON_MAIN: ${{ github.ref == 'refs/heads/main' && 'true' || 'false' }} + PR_TEST_BYPASS_MAINTENANCE_ON_MAIN: ${{ github.ref == 'refs/heads/main' && 'true' || 'false' }} USE_VENV: false permissions: @@ -337,20 +337,20 @@ jobs: { echo "## Change Detection" echo "" - echo "| Component | Changed |" - echo "|-------------------|---------|" - echo "| main_package | ${{ steps.filter-api.outputs.main_package || steps.filter.outputs.main_package || steps.run-mode.outputs.run_all_tests }} |" - echo "| sgl_kernel (raw) | ${{ steps.filter-api.outputs.sgl_kernel || steps.filter.outputs.sgl_kernel }} |" - echo "| sgl_kernel (used) | ${{ (!inputs.target_stage || inputs.include_wheel_build) && (steps.filter-api.outputs.sgl_kernel || steps.filter.outputs.sgl_kernel) }} |" - echo "| jit_kernel | ${{ steps.filter-api.outputs.jit_kernel || steps.filter.outputs.jit_kernel || steps.run-mode.outputs.run_all_tests }} |" - echo "| multimodal_gen | ${{ steps.filter-api.outputs.multimodal_gen || steps.filter.outputs.multimodal_gen || steps.run-mode.outputs.run_all_tests }} |" - echo "| target_stage | ${{ inputs.target_stage || '(none)' }} |" - echo "| detection_method | ${{ inputs.target_stage && 'GitHub API' || 'dorny/paths-filter' }} |" - echo "| max_parallel | ${{ steps.set-parallel.outputs.parallel_level }} (h100=${{ steps.set-parallel.outputs.max_parallel }}, 5090=${{ steps.set-parallel.outputs.max_parallel_small }}, 2gpu=${{ steps.set-parallel.outputs.max_parallel_2gpu }}) |" - echo "| b200_runner | ${{ steps.set-runner.outputs.b200_runner }} |" + echo "| Component | Changed |" + echo "|----------------------|---------|" + echo "| main_package | ${{ steps.filter-api.outputs.main_package || steps.filter.outputs.main_package || steps.run-mode.outputs.run_all_tests }} |" + echo "| sgl_kernel (raw) | ${{ steps.filter-api.outputs.sgl_kernel || steps.filter.outputs.sgl_kernel }} |" + echo "| sgl_kernel (used) | ${{ (!inputs.target_stage || inputs.include_wheel_build) && (steps.filter-api.outputs.sgl_kernel || steps.filter.outputs.sgl_kernel) }} |" + echo "| jit_kernel | ${{ steps.filter-api.outputs.jit_kernel || steps.filter.outputs.jit_kernel || steps.run-mode.outputs.run_all_tests }} |" + echo "| multimodal_gen | ${{ steps.filter-api.outputs.multimodal_gen || steps.filter.outputs.multimodal_gen || steps.run-mode.outputs.run_all_tests }} |" + echo "| target_stage | ${{ inputs.target_stage || '(none)' }} |" + echo "| detection_method | ${{ inputs.target_stage && 'GitHub API' || 'dorny/paths-filter' }} |" + echo "| max_parallel | ${{ steps.set-parallel.outputs.parallel_level }} (h100=${{ steps.set-parallel.outputs.max_parallel }}, 5090=${{ steps.set-parallel.outputs.max_parallel_small }}, 2gpu=${{ steps.set-parallel.outputs.max_parallel_2gpu }}) |" + echo "| b200_runner | ${{ steps.set-runner.outputs.b200_runner }} |" echo "| b200_low_disk_runner | ${{ steps.set-runner.outputs.b200_low_disk_runner }} |" - echo "| enable_retry | ${{ steps.set-retry.outputs.enable_retry }} |" - echo "| continue_on_error | ${{ steps.set-continue-on-error.outputs.continue_on_error }} |" + echo "| enable_retry | ${{ steps.set-retry.outputs.enable_retry }} |" + echo "| continue_on_error | ${{ steps.set-continue-on-error.outputs.continue_on_error }} |" } >> $GITHUB_STEP_SUMMARY # =============================================== Wait Jobs for Sequential PR Execution ==================================================== @@ -757,7 +757,6 @@ jobs: - name: Install dependencies timeout-minutes: 20 run: | - source /etc/profile.d/sglang-ci.sh CUSTOM_BUILD_SGL_KERNEL=${{needs.check-changes.outputs.sgl_kernel}} bash scripts/ci/cuda/ci_install_dependency.sh - name: Run test @@ -765,7 +764,6 @@ jobs: env: CONTINUE_ON_ERROR_FLAG: ${{ needs.check-changes.outputs.continue_on_error == 'true' && '--continue-on-error' || '' }} run: | - source /etc/profile.d/sglang-ci.sh cd test/ python3 run_suite.py --hw cuda --suite stage-b-test-1-gpu-small --auto-partition-id ${{ matrix.partition }} --auto-partition-size 8 $CONTINUE_ON_ERROR_FLAG diff --git a/scripts/ci/cuda/ci_install_dependency.sh b/scripts/ci/cuda/ci_install_dependency.sh index 1a8294aae..bd0f6b650 100755 --- a/scripts/ci/cuda/ci_install_dependency.sh +++ b/scripts/ci/cuda/ci_install_dependency.sh @@ -1,93 +1,16 @@ #!/bin/bash -# Install the dependency in CI. +# Install dependencies for CUDA CI jobs. # -# Structure (see section banners below): -# - Configuration & timing -# - Host / runner detection (arch, Blackwell, pip vs uv) -# - Kill existing processes -# - Install apt packages -# - Python package site hygiene & install protoc -# - Pip / uv toolchain & stale package cleanup -# - Uninstall Flashinfer -# - Install main package -# - Install sglang-kernel -# - Install sglang-router -# - Download flashinfer artifacts -# - Install extra dependency -# - Fix other dependencies -# - Prepare runner -# - Verify imports +# CU_VERSION (default: cu130) controls PyTorch index URL, FlashInfer JIT cache +# index, and nvrtc variant selection. set -euxo pipefail -# ------------------------------------------------------------------------------ -# Configuration & timing -# ------------------------------------------------------------------------------ -# Set up environment variables -# -# CU_VERSION controls: -# - PyTorch index URL (pytorch.org/whl/${CU_VERSION}) -# - FlashInfer JIT cache index (flashinfer.ai/whl/${CU_VERSION}) -# - nvrtc variant selection (cu12 vs cu13) - -CU_VERSION="${CU_VERSION:-cu130}" -CU_STRIP="${CU_VERSION#cu}" -CU_MAJOR="${CU_STRIP:0:2}" - -# Nvidia package versions we override (torch pins older versions). -# Used both as pip constraints during install and for post-install verification. -NVIDIA_CUDNN_VERSION="9.16.0.29" -NVIDIA_NVSHMEM_VERSION="3.4.5" -OPTIONAL_DEPS="${1:-}" - -# Whether to create a uv venv. Default false; set USE_VENV=false to install -# directly into system Python (useful for runners where uv venv misbehaves). -USE_VENV="${USE_VENV:-0}" -echo "USE_VENV=${USE_VENV}" - -# uv must be available on system Python (to create the venv, or to run -# `uv pip install --system` when venv mode is disabled). Install if missing. -python3 -m pip install --upgrade pip -if ! command -v uv >/dev/null 2>&1; then - pip install uv -fi - -SYS_PYTHON_VER=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')") - -if [ "$USE_VENV" = "1" ]; then - # Per-job unique path. Include $$ (shell PID) so concurrent/back-to-back jobs - # on the same runner never target the same directory even if GITHUB_JOB - # doesn't differentiate matrix partitions. - UV_VENV="/tmp/sglang-ci-${GITHUB_RUN_ID:-norun}-${GITHUB_JOB:-nojob}-$$" - # --seed installs pip/setuptools into the venv so bare `pip` calls in - # cache_nvidia_wheels.sh and the human-eval setup resolve to the venv's - # pip (rather than silently falling back to system Python). - uv venv "$UV_VENV" --python "python${SYS_PYTHON_VER}" --seed - # shellcheck disable=SC1091 - source "$UV_VENV/bin/activate" - # Assert activation actually took effect. A misconfigured activate script - # would otherwise leave us silently running against system Python. - [ "${VIRTUAL_ENV:-}" = "$UV_VENV" ] || { echo "FATAL: venv activation did not set VIRTUAL_ENV correctly"; exit 1; } - [ "$(command -v python3)" = "$UV_VENV/bin/python3" ] || { echo "FATAL: python3 still resolves outside venv (got $(command -v python3))"; exit 1; } - - # Propagate to subsequent workflow steps. GITHUB_ENV/GITHUB_PATH only - # affect *later* steps, never the current one. - if [ -n "${GITHUB_ENV:-}" ]; then - echo "VIRTUAL_ENV=$UV_VENV" >> "$GITHUB_ENV" - echo "SGLANG_CI_VENV_PATH=$UV_VENV" >> "$GITHUB_ENV" - # Set BASH_ENV early so subsequent steps auto-source the venv's env script. - # LD_LIBRARY_PATH is written to this file later (after packages are installed) - # and gets picked up even if GITHUB_ENV becomes unavailable at that point. - echo "BASH_ENV=$UV_VENV/env.sh" >> "$GITHUB_ENV" - touch "$UV_VENV/env.sh" - fi - if [ -n "${GITHUB_PATH:-}" ]; then - echo "$UV_VENV/bin" >> "$GITHUB_PATH" - fi -else - echo "USE_VENV=0: skipping uv venv creation, installing into system Python" - UV_VENV="" -fi +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" +# --------------------------------------------------------------------------- +# Timing helper +# --------------------------------------------------------------------------- SECONDS=0 _CI_MARK_PREV=${SECONDS} @@ -100,323 +23,313 @@ mark_step_done() { _CI_MARK_PREV=${now} } -mark_step_done "Configuration" +# --------------------------------------------------------------------------- +# Functions +# --------------------------------------------------------------------------- -# ------------------------------------------------------------------------------ -# Host / runner detection (CPU arch, Blackwell, USE_UV) -# ------------------------------------------------------------------------------ -# Detect CPU architecture (x86_64 or aarch64) -ARCH=$(uname -m) -echo "Detected architecture: ${ARCH}" +configure_environment() { + # CU_VERSION controls PyTorch index URL, FlashInfer JIT cache index, and + # nvrtc variant selection (cu12 vs cu13). + CU_VERSION="${CU_VERSION:-cu130}" + CU_STRIP="${CU_VERSION#cu}" + CU_MAJOR="${CU_STRIP:0:2}" -# Detect GPU architecture (blackwell or not) -if [ "${IS_BLACKWELL+set}" = set ]; then - case "$IS_BLACKWELL" in 1 | true | yes) IS_BLACKWELL=1 ;; *) IS_BLACKWELL=0 ;; esac - echo "IS_BLACKWELL=${IS_BLACKWELL} (manually set via environment)" -else - IS_BLACKWELL=0 - if command -v nvidia-smi >/dev/null 2>&1; then - while IFS= read -r cap; do - major="${cap%%.*}" - if [ "${major:-0}" -ge 10 ] 2>/dev/null; then - IS_BLACKWELL=1 - break - fi - done <<< "$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader 2>/dev/null || true)" + # Nvidia package versions we pin (torch ships older versions). + NVIDIA_CUDNN_VERSION="9.16.0.29" + NVIDIA_NVSHMEM_VERSION="3.4.5" + OPTIONAL_DEPS="${1:-}" + + # Whether to create a uv venv (set USE_VENV=1). Default: 0. + USE_VENV="${USE_VENV:-0}" + echo "USE_VENV=${USE_VENV}" + + python3 -m pip install --upgrade pip + if ! command -v uv >/dev/null 2>&1; then + pip install uv fi - echo "IS_BLACKWELL=${IS_BLACKWELL} (auto-detected via nvidia-smi)" -fi -# Whether to use pip or uv to install dependencies -if [ "${USE_UV+set}" != set ]; then - if [ "$IS_BLACKWELL" = "1" ]; then - # Our current b200 runners have some issues with uv, so we default to pip - # It is a runner specific issue, not a GPU architecture issue. - USE_UV=false - else - USE_UV=true - fi -fi -case "$(printf '%s' "$USE_UV" | tr '[:upper:]' '[:lower:]')" in 1 | true | yes) USE_UV=1 ;; *) USE_UV=0 ;; esac -echo "USE_UV=${USE_UV}" + SYS_PYTHON_VER=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')") -mark_step_done "Host / runner detection" + if [ "$USE_VENV" = "1" ]; then + UV_VENV="/tmp/sglang-ci-${GITHUB_RUN_ID:-norun}-${GITHUB_JOB:-nojob}-$$" + uv venv "$UV_VENV" --python "python${SYS_PYTHON_VER}" --seed + # shellcheck disable=SC1091 + source "$UV_VENV/bin/activate" + [ "${VIRTUAL_ENV:-}" = "$UV_VENV" ] || { echo "FATAL: venv activation did not set VIRTUAL_ENV correctly"; exit 1; } + [ "$(command -v python3)" = "$UV_VENV/bin/python3" ] || { echo "FATAL: python3 still resolves outside venv (got $(command -v python3))"; exit 1; } -# ------------------------------------------------------------------------------ -# Kill existing processes -# ------------------------------------------------------------------------------ -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" -python3 "${REPO_ROOT}/python/sglang/cli/killall.py" -KILLALL_EXIT=$? -echo "CUDA_VISIBLE_DEVICES=${CUDA_VISIBLE_DEVICES:-}" - -if [ $KILLALL_EXIT -ne 0 ]; then - echo "ERROR: killall.py detected uncleanable GPU memory. Aborting CI." - exit 1 -fi - -mark_step_done "Kill existing processes" - -# ------------------------------------------------------------------------------ -# Install apt packages -# ------------------------------------------------------------------------------ -# Install apt packages (including python3/pip which may be missing on some runners) -# Use --no-install-recommends and ignore errors from unrelated broken packages on the runner -# The NVIDIA driver packages may have broken dependencies that are unrelated to these packages -# Run apt-get update first to refresh package index (stale index causes 404 on security.ubuntu.com) -apt-get update || true -CI_APT_PACKAGES=( - python3 python3-pip python3-venv python3-dev git libnuma-dev libssl-dev pkg-config - libibverbs-dev libibverbs1 ibverbs-providers ibverbs-utils - ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev -) -apt-get install -y --no-install-recommends "${CI_APT_PACKAGES[@]}" || { - echo "Warning: apt-get install failed, checking if required packages are available..." - for pkg in "${CI_APT_PACKAGES[@]}"; do - if ! dpkg -l "$pkg" 2>/dev/null | grep -q "^ii"; then - echo "ERROR: Required package $pkg is not installed and apt-get failed" - exit 1 + if [ -n "${GITHUB_ENV:-}" ]; then + echo "VIRTUAL_ENV=$UV_VENV" >> "$GITHUB_ENV" + echo "SGLANG_CI_VENV_PATH=$UV_VENV" >> "$GITHUB_ENV" + echo "BASH_ENV=$UV_VENV/env.sh" >> "$GITHUB_ENV" + touch "$UV_VENV/env.sh" fi - done - echo "All required packages are already installed, continuing..." + if [ -n "${GITHUB_PATH:-}" ]; then + echo "$UV_VENV/bin" >> "$GITHUB_PATH" + fi + else + echo "USE_VENV=0: skipping uv venv creation, installing into system Python" + UV_VENV="" + fi + + mark_step_done "${FUNCNAME[0]}" } -mark_step_done "Install apt packages" +detect_host() { + ARCH=$(uname -m) + echo "Detected architecture: ${ARCH}" -# ------------------------------------------------------------------------------ -# Python package site hygiene & install protoc -# ------------------------------------------------------------------------------ -# Clear torch compilation cache -python3 -c 'import os, shutil, tempfile, getpass; cache_dir = os.environ.get("TORCHINDUCTOR_CACHE_DIR") or os.path.join(tempfile.gettempdir(), "torchinductor_" + getpass.getuser()); shutil.rmtree(cache_dir, ignore_errors=True)' - -# Remove broken dist-info directories (missing METADATA per PEP 376) -SITE_PACKAGES=$(python3 -c "import site; print(site.getsitepackages()[0])") -if [ -d "$SITE_PACKAGES" ]; then - { set +x; } 2>/dev/null - find "$SITE_PACKAGES" -maxdepth 1 -name "*.dist-info" -type d | while read -r d; do - if [ ! -f "$d/METADATA" ]; then - echo "Removing broken dist-info: $d" - rm -rf "$d" + if [ "${IS_BLACKWELL+set}" = set ]; then + case "$IS_BLACKWELL" in 1 | true | yes) IS_BLACKWELL=1 ;; *) IS_BLACKWELL=0 ;; esac + echo "IS_BLACKWELL=${IS_BLACKWELL} (manually set via environment)" + else + IS_BLACKWELL=0 + if command -v nvidia-smi >/dev/null 2>&1; then + while IFS= read -r cap; do + major="${cap%%.*}" + if [ "${major:-0}" -ge 10 ] 2>/dev/null; then + IS_BLACKWELL=1 + break + fi + done <<< "$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader 2>/dev/null || true)" fi - done - set -x -fi + echo "IS_BLACKWELL=${IS_BLACKWELL} (auto-detected via nvidia-smi)" + fi -# Install protoc -bash "${SCRIPT_DIR}/../utils/install_protoc.sh" + if [ "${USE_UV+set}" != set ]; then + if [ "$IS_BLACKWELL" = "1" ]; then + USE_UV=false + else + USE_UV=true + fi + fi + case "$(printf '%s' "$USE_UV" | tr '[:upper:]' '[:lower:]')" in 1 | true | yes) USE_UV=1 ;; *) USE_UV=0 ;; esac + echo "USE_UV=${USE_UV}" -# Install Rust toolchain (needed by crates built via setuptools-rust, e.g. the -# native gRPC extension bundled into the sglang wheel). -bash "${SCRIPT_DIR}/../utils/install_rustup.sh" -export PATH="${CARGO_HOME:-$HOME/.cargo}/bin:${PATH}" + mark_step_done "${FUNCNAME[0]}" +} -mark_step_done "Python package site hygiene & install protoc + rust" +kill_existing_processes() { + python3 "${REPO_ROOT}/python/sglang/cli/killall.py" + KILLALL_EXIT=$? + echo "CUDA_VISIBLE_DEVICES=${CUDA_VISIBLE_DEVICES:-}" -# ------------------------------------------------------------------------------ -# Pip / uv toolchain & stale package cleanup -# ------------------------------------------------------------------------------ -# Install pip and uv (use python3 -m pip for robustness since some runners only have pip3). -# In venv mode this upgrades the venv's pip (the bootstrap block near the top -# already upgraded system pip before `uv venv`). -python3 -m pip install --upgrade pip + if [ $KILLALL_EXIT -ne 0 ]; then + echo "ERROR: killall.py detected uncleanable GPU memory. Aborting CI." + exit 1 + fi -# uv is already installed on system Python (above). -# - Venv mode: the venv is active and `uv pip` targets it automatically. -# - Non-venv mode: UV_SYSTEM_PYTHON=1 makes `uv pip` operate on system Python -# (otherwise uv refuses to run outside a venv). -if [ "$USE_VENV" != "1" ]; then - export UV_SYSTEM_PYTHON=1 -fi + mark_step_done "${FUNCNAME[0]}" +} -export UV_LINK_MODE=copy -PIP_CMD="uv pip" -PIP_INSTALL_SUFFIX="--index-strategy unsafe-best-match --prerelease allow" -PIP_UNINSTALL_CMD="uv pip uninstall" -PIP_UNINSTALL_SUFFIX="" +install_apt_packages() { + apt-get update || true + CI_APT_PACKAGES=( + python3 python3-pip python3-venv python3-dev git libnuma-dev libssl-dev pkg-config + libibverbs-dev libibverbs1 ibverbs-providers ibverbs-utils + ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev + ) + apt-get install -y --no-install-recommends "${CI_APT_PACKAGES[@]}" || { + echo "Warning: apt-get install failed, checking if required packages are available..." + for pkg in "${CI_APT_PACKAGES[@]}"; do + if ! dpkg -l "$pkg" 2>/dev/null | grep -q "^ii"; then + echo "ERROR: Required package $pkg is not installed and apt-get failed" + exit 1 + fi + done + echo "All required packages are already installed, continuing..." + } + mark_step_done "${FUNCNAME[0]}" +} -# Clean up existing installations -$PIP_UNINSTALL_CMD sgl-kernel sglang-kernel sglang sgl-fa4 flash-attn-4 $PIP_UNINSTALL_SUFFIX || true +clean_site_packages() { + # Clear torch compilation cache + python3 -c 'import os, shutil, tempfile, getpass; cache_dir = os.environ.get("TORCHINDUCTOR_CACHE_DIR") or os.path.join(tempfile.gettempdir(), "torchinductor_" + getpass.getuser()); shutil.rmtree(cache_dir, ignore_errors=True)' -mark_step_done "Pip / uv toolchain & stale package cleanup" + # Remove broken dist-info directories (missing METADATA per PEP 376) + SITE_PACKAGES=$(python3 -c "import site; print(site.getsitepackages()[0])") + if [ -d "$SITE_PACKAGES" ]; then + { set +x; } 2>/dev/null + find "$SITE_PACKAGES" -maxdepth 1 -name "*.dist-info" -type d | while read -r d; do + if [ ! -f "$d/METADATA" ]; then + echo "Removing broken dist-info: $d" + rm -rf "$d" + fi + done + set -x + fi -# ------------------------------------------------------------------------------ -# Uninstall Flashinfer -# ------------------------------------------------------------------------------ -# Keep flashinfer packages installed if version matches to avoid re-downloading: -# - flashinfer-cubin: 150+ MB -# - flashinfer-jit-cache: 1.2+ GB, by far the largest download in CI -FLASHINFER_PYTHON_REQUIRED=$(grep -Po -m1 '(?<=flashinfer_python==)[0-9A-Za-z\.\-]+' python/pyproject.toml || echo "") -FLASHINFER_CUBIN_REQUIRED=$(grep -Po -m1 '(?<=flashinfer_cubin==)[0-9A-Za-z\.\-]+' python/pyproject.toml || echo "") -FLASHINFER_CUBIN_INSTALLED=$(pip show flashinfer-cubin 2>/dev/null | grep "^Version:" | awk '{print $2}' || echo "") -FLASHINFER_JIT_INSTALLED=$(pip show flashinfer-jit-cache 2>/dev/null | grep "^Version:" | awk '{print $2}' | sed 's/+.*//' || echo "") -FLASHINFER_JIT_CU_VERSION=$(pip show flashinfer-jit-cache 2>/dev/null | grep "^Version:" | awk '{print $2}' | sed -n 's/.*+//p' || echo "") + # Install protoc + bash "${SCRIPT_DIR}/../utils/install_protoc.sh" -UNINSTALL_CUBIN=true -UNINSTALL_JIT_CACHE=true + # Install Rust toolchain (needed by setuptools-rust, e.g. the native gRPC extension) + bash "${SCRIPT_DIR}/../utils/install_rustup.sh" + export PATH="${CARGO_HOME:-$HOME/.cargo}/bin:${PATH}" -if [ "$FLASHINFER_CUBIN_INSTALLED" = "$FLASHINFER_CUBIN_REQUIRED" ] && [ -n "$FLASHINFER_CUBIN_REQUIRED" ]; then - echo "flashinfer-cubin==${FLASHINFER_CUBIN_REQUIRED} already installed, keeping it" - UNINSTALL_CUBIN=false -else - echo "flashinfer-cubin version mismatch (installed: ${FLASHINFER_CUBIN_INSTALLED:-none}, required: ${FLASHINFER_CUBIN_REQUIRED}), reinstalling" -fi + mark_step_done "${FUNCNAME[0]}" +} -if [ "$FLASHINFER_JIT_INSTALLED" = "$FLASHINFER_PYTHON_REQUIRED" ] && [ -n "$FLASHINFER_PYTHON_REQUIRED" ]; then - echo "flashinfer-jit-cache==${FLASHINFER_PYTHON_REQUIRED} already installed, keeping it" - UNINSTALL_JIT_CACHE=false -else - echo "flashinfer-jit-cache version mismatch (installed: ${FLASHINFER_JIT_INSTALLED:-none}, required: ${FLASHINFER_PYTHON_REQUIRED}), will reinstall" -fi +setup_pip_toolchain() { + python3 -m pip install --upgrade pip -if [ "$UNINSTALL_JIT_CACHE" = false ] && [ "$FLASHINFER_JIT_CU_VERSION" != "$CU_VERSION" ]; then - echo "flashinfer-jit-cache CUDA version mismatch (installed: ${FLASHINFER_JIT_CU_VERSION:-none}, required: ${CU_VERSION}), will reinstall" + if [ "$USE_VENV" != "1" ]; then + export UV_SYSTEM_PYTHON=1 + fi + + export UV_LINK_MODE=copy + PIP_CMD="uv pip" + PIP_INSTALL_SUFFIX="--index-strategy unsafe-best-match --prerelease allow" + PIP_UNINSTALL_CMD="uv pip uninstall" + PIP_UNINSTALL_SUFFIX="" + + $PIP_UNINSTALL_CMD sgl-kernel sglang-kernel sglang sgl-fa4 flash-attn-4 $PIP_UNINSTALL_SUFFIX || true + + mark_step_done "${FUNCNAME[0]}" +} + +uninstall_stale_flashinfer() { + # Keep flashinfer packages if version matches to avoid re-downloading: + # - flashinfer-cubin: 150+ MB + # - flashinfer-jit-cache: 1.2+ GB + FLASHINFER_PYTHON_REQUIRED=$(grep -Po -m1 '(?<=flashinfer_python==)[0-9A-Za-z\.\-]+' python/pyproject.toml || echo "") + FLASHINFER_CUBIN_REQUIRED=$(grep -Po -m1 '(?<=flashinfer_cubin==)[0-9A-Za-z\.\-]+' python/pyproject.toml || echo "") + FLASHINFER_CUBIN_INSTALLED=$(pip show flashinfer-cubin 2>/dev/null | grep "^Version:" | awk '{print $2}' || echo "") + FLASHINFER_JIT_INSTALLED=$(pip show flashinfer-jit-cache 2>/dev/null | grep "^Version:" | awk '{print $2}' | sed 's/+.*//' || echo "") + FLASHINFER_JIT_CU_VERSION=$(pip show flashinfer-jit-cache 2>/dev/null | grep "^Version:" | awk '{print $2}' | sed -n 's/.*+//p' || echo "") + + UNINSTALL_CUBIN=true UNINSTALL_JIT_CACHE=true -fi -# Build uninstall list based on what needs updating -FLASHINFER_UNINSTALL="flashinfer-python" -[ "$UNINSTALL_CUBIN" = true ] && FLASHINFER_UNINSTALL="$FLASHINFER_UNINSTALL flashinfer-cubin" -[ "$UNINSTALL_JIT_CACHE" = true ] && FLASHINFER_UNINSTALL="$FLASHINFER_UNINSTALL flashinfer-jit-cache" -$PIP_UNINSTALL_CMD $FLASHINFER_UNINSTALL $PIP_UNINSTALL_SUFFIX || true -$PIP_UNINSTALL_CMD opencv-python opencv-python-headless $PIP_UNINSTALL_SUFFIX || true - -mark_step_done "Uninstall Flashinfer" - -# ------------------------------------------------------------------------------ -# Install main package -# ------------------------------------------------------------------------------ -# Install the main package -EXTRAS="dev,runai,tracing" -if [ -n "$OPTIONAL_DEPS" ]; then - EXTRAS="dev,runai,tracing,${OPTIONAL_DEPS}" -fi -echo "Installing python extras: [${EXTRAS}]" -# source "${SCRIPT_DIR}/cache_nvidia_wheels.sh" -$PIP_CMD install -e "python[${EXTRAS}]" $PIP_INSTALL_SUFFIX - -mark_step_done "Install main package" - -# ------------------------------------------------------------------------------ -# Install torch/sglang-kernel -# ------------------------------------------------------------------------------ -SGL_KERNEL_VERSION_FROM_KERNEL=$(grep -Po '(?<=^version = ")[^"]*' sgl-kernel/pyproject.toml) -SGL_KERNEL_VERSION_FROM_SRT=$(grep -Po -m1 '(?<=sglang-kernel==)[0-9A-Za-z\.\-]+' python/pyproject.toml) -echo "SGL_KERNEL_VERSION_FROM_KERNEL=${SGL_KERNEL_VERSION_FROM_KERNEL} SGL_KERNEL_VERSION_FROM_SRT=${SGL_KERNEL_VERSION_FROM_SRT}" - - -if [ "${CUSTOM_BUILD_SGL_KERNEL:-}" = "true" ] && [ -d "sgl-kernel/dist" ]; then - ls -alh sgl-kernel/dist - # Determine wheel architecture - if [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then - WHEEL_ARCH="aarch64" + if [ "$FLASHINFER_CUBIN_INSTALLED" = "$FLASHINFER_CUBIN_REQUIRED" ] && [ -n "$FLASHINFER_CUBIN_REQUIRED" ]; then + echo "flashinfer-cubin==${FLASHINFER_CUBIN_REQUIRED} already installed, keeping it" + UNINSTALL_CUBIN=false else - WHEEL_ARCH="x86_64" + echo "flashinfer-cubin version mismatch (installed: ${FLASHINFER_CUBIN_INSTALLED:-none}, required: ${FLASHINFER_CUBIN_REQUIRED}), reinstalling" fi - # Wheel filenames carry a +cuXYZ local version tag (e.g. sglang_kernel-0.4.0+cu130-...). - # Pick the one matching the test runner's $CU_VERSION; any other tag (or an untagged - # wheel of unknown CUDA origin) would at best hit the PyPI-reinstall branch below and - # silently replace the PR-built wheel with the public main-branch wheel, and at worst - # ABI-crash at cuInit. Better to fail loudly — the error message + dist/ listing below - # makes the diagnosis obvious. - # `|| true` swallows `ls`'s exit-2-on-no-match so `set -o pipefail` doesn't abort the - # script before we reach the explicit error check. - KERNEL_WHL=$(ls sgl-kernel/dist/sglang_kernel-${SGL_KERNEL_VERSION_FROM_KERNEL}+${CU_VERSION}-cp310-abi3-manylinux2014_${WHEEL_ARCH}.whl 2>/dev/null | head -1 || true) - if [ -z "$KERNEL_WHL" ]; then - echo "ERROR: No matching sgl-kernel wheel found in sgl-kernel/dist/ for version ${SGL_KERNEL_VERSION_FROM_KERNEL} arch ${WHEEL_ARCH} cuda ${CU_VERSION}" - ls -alh sgl-kernel/dist/ - exit 1 - fi - echo "Installing sgl-kernel wheel: $KERNEL_WHL" - $PIP_CMD install "$KERNEL_WHL" --force-reinstall $PIP_INSTALL_SUFFIX -else - if [ "${CUSTOM_BUILD_SGL_KERNEL:-}" = "true" ] && [ ! -d "sgl-kernel/dist" ]; then - # CUSTOM_BUILD_SGL_KERNEL was set but artifacts not available (e.g., stage rerun without wheel build) - # Fail instead of falling back to PyPI - we need to test the built kernel, not PyPI version - echo "ERROR: CUSTOM_BUILD_SGL_KERNEL=true but sgl-kernel/dist not found." - echo "This usually happens when rerunning a stage without the sgl-kernel-build-wheels job." - echo "Please re-run the full workflow using /tag-and-rerun-ci to rebuild the kernel." - exit 1 - fi -fi -# Now we are running torch with cuda13 in CI environment, so the torch packages will be reinstalled if they are still at CU129 version -# TODO: Remove this part after torch has been upgraded to 2.11, where cu13 is enabled by default -TORCH_CUDA_VER=$(python3 -c "import torch; v=torch.version.cuda; parts=v.split('.'); print(f'cu{parts[0]}{parts[1]}')") -echo "Detected torch CUDA version: ${TORCH_CUDA_VER}" -if [ "${TORCH_CUDA_VER}" != "${CU_VERSION}" ]; then - TORCH_VER=$(pip show torch 2>/dev/null | grep "^Version:" | awk '{print $2}' | sed 's/+.*//') - TORCHAUDIO_VER=$(pip show torchaudio 2>/dev/null | grep "^Version:" | awk '{print $2}' | sed 's/+.*//') - TORCHVISION_VER=$(pip show torchvision 2>/dev/null | grep "^Version:" | awk '{print $2}' | sed 's/+.*//') - echo "Reinstalling torch==${TORCH_VER} torchaudio==${TORCHAUDIO_VER} torchvision==${TORCHVISION_VER} from ${CU_VERSION} index to match torch..." - $PIP_CMD install "torch==${TORCH_VER}" "torchaudio==${TORCHAUDIO_VER}" "torchvision==${TORCHVISION_VER}" --index-url "https://download.pytorch.org/whl/${CU_VERSION}" --force-reinstall --no-deps $PIP_INSTALL_SUFFIX -fi - -# sglang-kernel wheels carry a +cuXYZ local version tag (e.g. 0.4.1+cu130). -# If it doesn't match CU_VERSION, reinstall from the matching index. -SGL_KERNEL_FULL_VER=$(pip show sglang-kernel 2>/dev/null | grep "^Version:" | awk '{print $2}' || echo "") -SGL_KERNEL_CUDA_VER=$(printf '%s' "$SGL_KERNEL_FULL_VER" | sed -n 's/.*+//p') -echo "Detected sglang-kernel version: ${SGL_KERNEL_FULL_VER} (CUDA tag: ${SGL_KERNEL_CUDA_VER:-none})" -if [ -n "$SGL_KERNEL_CUDA_VER" ] && [ "$SGL_KERNEL_CUDA_VER" != "$CU_VERSION" ]; then - SGL_KERNEL_VER="${SGL_KERNEL_FULL_VER%+*}" - echo "Reinstalling sglang-kernel==${SGL_KERNEL_VER} from ${CU_VERSION} index to match torch..." - if [ "$CU_MAJOR" = "13" ]; then - $PIP_CMD install "sglang-kernel==${SGL_KERNEL_VER}" --index-url "https://docs.sglang.ai/whl/${CU_VERSION}/" --force-reinstall --no-deps $PIP_INSTALL_SUFFIX + if [ "$FLASHINFER_JIT_INSTALLED" = "$FLASHINFER_PYTHON_REQUIRED" ] && [ -n "$FLASHINFER_PYTHON_REQUIRED" ]; then + echo "flashinfer-jit-cache==${FLASHINFER_PYTHON_REQUIRED} already installed, keeping it" + UNINSTALL_JIT_CACHE=false else - $PIP_CMD install "sglang-kernel==${SGL_KERNEL_VER}" --force-reinstall --no-deps $PIP_INSTALL_SUFFIX + echo "flashinfer-jit-cache version mismatch (installed: ${FLASHINFER_JIT_INSTALLED:-none}, required: ${FLASHINFER_PYTHON_REQUIRED}), will reinstall" fi -fi -mark_step_done "Install sglang-kernel" + if [ "$UNINSTALL_JIT_CACHE" = false ] && [ "$FLASHINFER_JIT_CU_VERSION" != "$CU_VERSION" ]; then + echo "flashinfer-jit-cache CUDA version mismatch (installed: ${FLASHINFER_JIT_CU_VERSION:-none}, required: ${CU_VERSION}), will reinstall" + UNINSTALL_JIT_CACHE=true + fi -# ------------------------------------------------------------------------------ -# Install sglang-router -# ------------------------------------------------------------------------------ -# Install router for pd-disagg test -$PIP_CMD install sglang-router $PIP_INSTALL_SUFFIX + FLASHINFER_UNINSTALL="flashinfer-python" + [ "$UNINSTALL_CUBIN" = true ] && FLASHINFER_UNINSTALL="$FLASHINFER_UNINSTALL flashinfer-cubin" + [ "$UNINSTALL_JIT_CACHE" = true ] && FLASHINFER_UNINSTALL="$FLASHINFER_UNINSTALL flashinfer-jit-cache" + $PIP_UNINSTALL_CMD $FLASHINFER_UNINSTALL $PIP_UNINSTALL_SUFFIX || true + $PIP_UNINSTALL_CMD opencv-python opencv-python-headless $PIP_UNINSTALL_SUFFIX || true -# Show current packages -$PIP_CMD list + mark_step_done "${FUNCNAME[0]}" +} -mark_step_done "Install sglang-router" +install_sglang() { + EXTRAS="dev,runai,tracing" + if [ -n "$OPTIONAL_DEPS" ]; then + EXTRAS="dev,runai,tracing,${OPTIONAL_DEPS}" + fi + echo "Installing python extras: [${EXTRAS}]" + $PIP_CMD install -e "python[${EXTRAS}]" $PIP_INSTALL_SUFFIX -# ------------------------------------------------------------------------------ -# Download flashinfer artifacts -# ------------------------------------------------------------------------------ -# Download flashinfer jit cache -UNINSTALL_JIT_CACHE="$UNINSTALL_JIT_CACHE" \ - FLASHINFER_PYTHON_REQUIRED="$FLASHINFER_PYTHON_REQUIRED" \ - CU_VERSION="$CU_VERSION" \ - PIP_CMD="$PIP_CMD" \ - PIP_INSTALL_SUFFIX="$PIP_INSTALL_SUFFIX" \ - bash "${SCRIPT_DIR}/ci_download_flashinfer_jit_cache.sh" + mark_step_done "${FUNCNAME[0]}" +} -mark_step_done "Download flashinfer artifacts" +install_sglang_kernel() { + SGL_KERNEL_VERSION_FROM_KERNEL=$(grep -Po '(?<=^version = ")[^"]*' sgl-kernel/pyproject.toml) + SGL_KERNEL_VERSION_FROM_SRT=$(grep -Po -m1 '(?<=sglang-kernel==)[0-9A-Za-z\.\-]+' python/pyproject.toml) + echo "SGL_KERNEL_VERSION_FROM_KERNEL=${SGL_KERNEL_VERSION_FROM_KERNEL} SGL_KERNEL_VERSION_FROM_SRT=${SGL_KERNEL_VERSION_FROM_SRT}" + + if [ "${CUSTOM_BUILD_SGL_KERNEL:-}" = "true" ] && [ -d "sgl-kernel/dist" ]; then + ls -alh sgl-kernel/dist + if [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then + WHEEL_ARCH="aarch64" + else + WHEEL_ARCH="x86_64" + fi + KERNEL_WHL=$(ls sgl-kernel/dist/sglang_kernel-${SGL_KERNEL_VERSION_FROM_KERNEL}+${CU_VERSION}-cp310-abi3-manylinux2014_${WHEEL_ARCH}.whl 2>/dev/null | head -1 || true) + if [ -z "$KERNEL_WHL" ]; then + echo "ERROR: No matching sgl-kernel wheel found in sgl-kernel/dist/ for version ${SGL_KERNEL_VERSION_FROM_KERNEL} arch ${WHEEL_ARCH} cuda ${CU_VERSION}" + ls -alh sgl-kernel/dist/ + exit 1 + fi + echo "Installing sgl-kernel wheel: $KERNEL_WHL" + $PIP_CMD install "$KERNEL_WHL" --force-reinstall $PIP_INSTALL_SUFFIX + else + if [ "${CUSTOM_BUILD_SGL_KERNEL:-}" = "true" ] && [ ! -d "sgl-kernel/dist" ]; then + echo "ERROR: CUSTOM_BUILD_SGL_KERNEL=true but sgl-kernel/dist not found." + echo "This usually happens when rerunning a stage without the sgl-kernel-build-wheels job." + echo "Please re-run the full workflow using /tag-and-rerun-ci to rebuild the kernel." + exit 1 + fi + fi + + # Reinstall torch with matching CUDA version if needed + # TODO: Remove after torch 2.11 where cu13 is enabled by default + TORCH_CUDA_VER=$(python3 -c "import torch; v=torch.version.cuda; parts=v.split('.'); print(f'cu{parts[0]}{parts[1]}')") + echo "Detected torch CUDA version: ${TORCH_CUDA_VER}" + if [ "${TORCH_CUDA_VER}" != "${CU_VERSION}" ]; then + TORCH_VER=$(pip show torch 2>/dev/null | grep "^Version:" | awk '{print $2}' | sed 's/+.*//') + TORCHAUDIO_VER=$(pip show torchaudio 2>/dev/null | grep "^Version:" | awk '{print $2}' | sed 's/+.*//') + TORCHVISION_VER=$(pip show torchvision 2>/dev/null | grep "^Version:" | awk '{print $2}' | sed 's/+.*//') + echo "Reinstalling torch==${TORCH_VER} torchaudio==${TORCHAUDIO_VER} torchvision==${TORCHVISION_VER} from ${CU_VERSION} index to match torch..." + $PIP_CMD install "torch==${TORCH_VER}" "torchaudio==${TORCHAUDIO_VER}" "torchvision==${TORCHVISION_VER}" --index-url "https://download.pytorch.org/whl/${CU_VERSION}" --force-reinstall --no-deps $PIP_INSTALL_SUFFIX + fi + + # Reinstall sglang-kernel with matching CUDA version if needed + SGL_KERNEL_FULL_VER=$(pip show sglang-kernel 2>/dev/null | grep "^Version:" | awk '{print $2}' || echo "") + SGL_KERNEL_CUDA_VER=$(printf '%s' "$SGL_KERNEL_FULL_VER" | sed -n 's/.*+//p') + echo "Detected sglang-kernel version: ${SGL_KERNEL_FULL_VER} (CUDA tag: ${SGL_KERNEL_CUDA_VER:-none})" + if [ -n "$SGL_KERNEL_CUDA_VER" ] && [ "$SGL_KERNEL_CUDA_VER" != "$CU_VERSION" ]; then + SGL_KERNEL_VER="${SGL_KERNEL_FULL_VER%+*}" + echo "Reinstalling sglang-kernel==${SGL_KERNEL_VER} from ${CU_VERSION} index to match torch..." + if [ "$CU_MAJOR" = "13" ]; then + $PIP_CMD install "sglang-kernel==${SGL_KERNEL_VER}" --index-url "https://docs.sglang.ai/whl/${CU_VERSION}/" --force-reinstall --no-deps $PIP_INSTALL_SUFFIX + else + $PIP_CMD install "sglang-kernel==${SGL_KERNEL_VER}" --force-reinstall --no-deps $PIP_INSTALL_SUFFIX + fi + fi + + mark_step_done "${FUNCNAME[0]}" +} + +install_sglang_router() { + $PIP_CMD install sglang-router $PIP_INSTALL_SUFFIX + $PIP_CMD list + + mark_step_done "${FUNCNAME[0]}" +} + +download_flashinfer_cache() { + UNINSTALL_JIT_CACHE="$UNINSTALL_JIT_CACHE" \ + FLASHINFER_PYTHON_REQUIRED="$FLASHINFER_PYTHON_REQUIRED" \ + CU_VERSION="$CU_VERSION" \ + PIP_CMD="$PIP_CMD" \ + PIP_INSTALL_SUFFIX="$PIP_INSTALL_SUFFIX" \ + bash "${SCRIPT_DIR}/ci_download_flashinfer_jit_cache.sh" + + mark_step_done "${FUNCNAME[0]}" +} + +stabilize_flashinfer_jit_paths() { + # In venv mode, FlashInfer JIT writes build.ninja with hardcoded -isystem + # paths. Per-job venvs get unique paths, but the JIT cache is shared on the + # host mount. Fix by symlinking venv copies to a stable host-mounted path. + if [ "$USE_VENV" != "1" ]; then + return + fi -# ------------------------------------------------------------------------------ -# Stabilize FlashInfer JIT cache paths -# ------------------------------------------------------------------------------ -# FlashInfer JIT writes build.ninja with hardcoded -isystem paths pointing to the -# venv's flashinfer/data/ and tvm_ffi/include/. With per-job venvs each job gets -# a unique /tmp/sglang-ci---/ path, but the JIT cache is shared -# on the host mount. When the next job's venv has a different path and the old one -# is cleaned up, ninja fails because source files no longer exist at the cached path. -# -# Fix (two parts): -# 1. Clear only STALE cached_ops (build.ninja referencing non-existent venv paths). -# Do NOT clear all cached_ops — they contain compiled .so files that take 10-20 min -# to recompile. Only remove entries where the source paths no longer exist. -# 2. Copy source files to a stable host-mounted path and symlink each venv's -# copy there. build.ninja then references the stable path across all jobs. -# -# Part 1: Clear stale cached_ops (keep valid compiled kernels) -if [ "$USE_VENV" = "1" ]; then STABLE_FI_DIR="${HOME}/.cache/flashinfer/_stable_src" + + # Clear stale cached_ops (keep valid compiled kernels) if [ -d "${HOME}/.cache/flashinfer" ]; then STALE_COUNT=0 while IFS= read -r ninja_file; do - # Check for stale venv paths (/tmp/sglang-ci-*) or old stable path (flashinfer-src) STALE_PATH=$(grep -o '/tmp/sglang-ci-[^ ]*\|flashinfer-src' "$ninja_file" 2>/dev/null | head -1 || true) if [ -n "$STALE_PATH" ]; then if echo "$STALE_PATH" | grep -q "flashinfer-src" || [ ! -d "$STALE_PATH" ]; then @@ -428,7 +341,7 @@ if [ "$USE_VENV" = "1" ]; then echo "Cleaned $STALE_COUNT stale FlashInfer cached_ops (kept valid ones)" fi - # Part 2: Stabilize paths (STABLE_FI_DIR set above in Part 1) + # Copy source files to stable path and symlink venv copies there FI_DATA=$(python3 -c "import flashinfer, os; print(os.path.join(os.path.dirname(flashinfer.__file__), 'data'))") TVM_INC=$(python3 -c "import tvm_ffi, os; print(os.path.join(os.path.dirname(tvm_ffi.__file__), 'include'))") @@ -451,127 +364,132 @@ if [ "$USE_VENV" = "1" ]; then ln -s "$STABLE_FI_DIR/tvm-ffi-include" "$TVM_INC_PARENT/include" echo "Symlinked venv flashinfer/tvm_ffi -> $STABLE_FI_DIR" - mark_step_done "Stabilize FlashInfer JIT cache paths" -fi + mark_step_done "${FUNCNAME[0]}" +} +install_extra_deps() { + if [ "$CU_MAJOR" = "13" ]; then + MOONCAKE_PKG="mooncake-transfer-engine-cuda13==0.3.10.post2" + EXTRA_NVIDIA_SPECS="nvidia-cuda-nvrtc" + else + MOONCAKE_PKG="mooncake-transfer-engine==0.3.10.post2" + EXTRA_NVIDIA_SPECS="nvidia-cuda-nvrtc-cu12" + fi + $PIP_CMD install ${MOONCAKE_PKG} ${EXTRA_NVIDIA_SPECS} py-spy scipy huggingface_hub[hf_xet] pytest $PIP_INSTALL_SUFFIX -# ------------------------------------------------------------------------------ -# Install extra dependency -# ------------------------------------------------------------------------------ -# Install other python dependencies. -# Match on CUDA major version so future minor bumps (cu131, etc.) don't fall -# through to the wrong branch. Prefer NVCC_VER (set in the venv path); otherwise -# parse the first two digits of CU_VERSION (pytorch convention is cu{major}{minor} -# with a single-digit minor, e.g. cu126, cu129, cu130). -if [ "$CU_MAJOR" = "13" ]; then - MOONCAKE_PKG="mooncake-transfer-engine-cuda13==0.3.10.post2" - EXTRA_NVIDIA_SPECS="nvidia-cuda-nvrtc" -else - MOONCAKE_PKG="mooncake-transfer-engine==0.3.10.post2" - EXTRA_NVIDIA_SPECS="nvidia-cuda-nvrtc-cu12" -fi -$PIP_CMD install ${MOONCAKE_PKG} ${EXTRA_NVIDIA_SPECS} py-spy scipy huggingface_hub[hf_xet] pytest $PIP_INSTALL_SUFFIX + if [ "$IS_BLACKWELL" != "1" ]; then + git clone --branch v0.5 --depth 1 https://github.com/EvolvingLMMs-Lab/lmms-eval.git + $PIP_CMD install -e lmms-eval/ $PIP_INSTALL_SUFFIX + fi + $PIP_CMD uninstall xformers || true -# Install other test dependencies -if [ "$IS_BLACKWELL" != "1" ]; then - # For lmms_evals evaluating MMMU - git clone --branch v0.5 --depth 1 https://github.com/EvolvingLMMs-Lab/lmms-eval.git - $PIP_CMD install -e lmms-eval/ $PIP_INSTALL_SUFFIX -fi -$PIP_CMD uninstall xformers || true + mark_step_done "${FUNCNAME[0]}" +} -mark_step_done "Install extra dependency" +fix_nvidia_deps() { + if [ "$CU_MAJOR" = "13" ]; then + NVSHMEM_PKG="nvidia-nvshmem-cu13" + CUDNN_PKG="nvidia-cudnn-cu13" + else + NVSHMEM_PKG="nvidia-nvshmem-cu12" + CUDNN_PKG="nvidia-cudnn-cu12" + fi -# ------------------------------------------------------------------------------ -# Fix other dependencies -# ------------------------------------------------------------------------------ + # DeepEP depends on nvshmem 3.4.5 + INSTALLED_NVSHMEM=$(pip show ${NVSHMEM_PKG} 2>/dev/null | grep "^Version:" | awk '{print $2}' || echo "") + if [ "$INSTALLED_NVSHMEM" = "$NVIDIA_NVSHMEM_VERSION" ]; then + echo "${NVSHMEM_PKG}==${NVIDIA_NVSHMEM_VERSION} already installed, skipping reinstall" + else + $PIP_CMD install ${NVSHMEM_PKG}==${NVIDIA_NVSHMEM_VERSION} $PIP_INSTALL_SUFFIX + fi -# Pick cu12 vs cu13 variants of nvshmem / cudnn based on CU_VERSION -if [ "$CU_MAJOR" = "13" ]; then - NVSHMEM_PKG="nvidia-nvshmem-cu13" - CUDNN_PKG="nvidia-cudnn-cu13" -else - NVSHMEM_PKG="nvidia-nvshmem-cu12" - CUDNN_PKG="nvidia-cudnn-cu12" -fi + # cudnn < 9.16.0.29 causes Conv3D performance regression + INSTALLED_CUDNN=$(pip show ${CUDNN_PKG} 2>/dev/null | grep "^Version:" | awk '{print $2}' || echo "") + if [ "$INSTALLED_CUDNN" = "$NVIDIA_CUDNN_VERSION" ]; then + echo "${CUDNN_PKG}==${NVIDIA_CUDNN_VERSION} already installed, skipping reinstall" + else + $PIP_CMD install ${CUDNN_PKG}==${NVIDIA_CUDNN_VERSION} $PIP_INSTALL_SUFFIX + fi -# Fix dependencies: DeepEP depends on nvshmem 3.4.5 — skip reinstall when already correct (avoids pip races / wasted work) -INSTALLED_NVSHMEM=$(pip show ${NVSHMEM_PKG} 2>/dev/null | grep "^Version:" | awk '{print $2}' || echo "") -if [ "$INSTALLED_NVSHMEM" = "$NVIDIA_NVSHMEM_VERSION" ]; then - echo "${NVSHMEM_PKG}==${NVIDIA_NVSHMEM_VERSION} already installed, skipping reinstall" -else - $PIP_CMD install ${NVSHMEM_PKG}==${NVIDIA_NVSHMEM_VERSION} $PIP_INSTALL_SUFFIX -fi + mark_step_done "${FUNCNAME[0]}" +} -# Fix dependencies: Cudnn with version less than 9.16.0.29 will cause performance regression on Conv3D kernel -INSTALLED_CUDNN=$(pip show ${CUDNN_PKG} 2>/dev/null | grep "^Version:" | awk '{print $2}' || echo "") -if [ "$INSTALLED_CUDNN" = "$NVIDIA_CUDNN_VERSION" ]; then - echo "${CUDNN_PKG}==${NVIDIA_CUDNN_VERSION} already installed, skipping reinstall" -else - $PIP_CMD install ${CUDNN_PKG}==${NVIDIA_CUDNN_VERSION} $PIP_INSTALL_SUFFIX -fi +install_test_tools() { + # Download kernels from kernels community + kernels download python || true + kernels lock python || true + [ -e "${HOME}/.cache/sglang" ] && [ ! -d "${HOME}/.cache/sglang" ] && rm -f "${HOME}/.cache/sglang" + mkdir -p "${HOME}/.cache/sglang/" + mv python/kernels.lock "${HOME}/.cache/sglang/" || true -mark_step_done "Fix other dependencies" + # Install human-eval (subshell keeps cd local) + $PIP_CMD install "setuptools==70.0.0" $PIP_INSTALL_SUFFIX + [ -d human-eval ] || git clone https://github.com/merrymercy/human-eval.git + ( + cd human-eval + $PIP_CMD install -e . --no-build-isolation $PIP_INSTALL_SUFFIX + ) -# Download kernels from kernels community -kernels download python || true -kernels lock python || true -# Ensure target is a directory — on fresh containers or after a previous buggy -# `mv` that created a FILE at this path, mkdir -p would fail silently. -[ -e "${HOME}/.cache/sglang" ] && [ ! -d "${HOME}/.cache/sglang" ] && rm -f "${HOME}/.cache/sglang" -mkdir -p "${HOME}/.cache/sglang/" -mv python/kernels.lock "${HOME}/.cache/sglang/" || true + mark_step_done "${FUNCNAME[0]}" +} -# Install human-eval. This script is sourced from ci_install_deepep.sh, so a -# bare `cd human-eval` would leave the caller stuck in that directory for the -# rest of its execution. The subshell keeps the cd local to the pip install. -$PIP_CMD install "setuptools==70.0.0" $PIP_INSTALL_SUFFIX -[ -d human-eval ] || git clone https://github.com/merrymercy/human-eval.git -( - cd human-eval - $PIP_CMD install -e . --no-build-isolation $PIP_INSTALL_SUFFIX) +prepare_runner() { + bash "${SCRIPT_DIR}/prepare_runner.sh" -# ------------------------------------------------------------------------------ -# Prepare runner -# ------------------------------------------------------------------------------ -# Prepare the CI runner (cleanup HuggingFace cache, etc.) -bash "${SCRIPT_DIR}/prepare_runner.sh" + mark_step_done "${FUNCNAME[0]}" +} -mark_step_done "Prepare runner" +setup_ld_library_path() { + # NVIDIA pip packages and torch ship .so files under site-packages that are + # not on the default LD_LIBRARY_PATH. + SITE_PACKAGES=$(python3 -c "import site, sys; print(site.getsitepackages()[0])") + NVIDIA_LIBS=$(find "$SITE_PACKAGES" -path "*/nvidia/*/lib" -type d 2>/dev/null | tr '\n' ':') + TORCH_LIB="$SITE_PACKAGES/torch/lib" + VENV_LD="${NVIDIA_LIBS}${TORCH_LIB}" + export LD_LIBRARY_PATH="${VENV_LD}${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" -# ------------------------------------------------------------------------------ -# LD_LIBRARY_PATH discovery -# ------------------------------------------------------------------------------ -# NVIDIA pip packages (cublas, cudnn, nccl, nvrtc, ...) and torch ship .so files -# under site-packages. In venv mode these are NOT on the default LD_LIBRARY_PATH, -# so dlopen('libcublas.so.12') from torch would fail. Prepend them here. -# In non-venv mode, system site-packages may also need this if the runner's -# default ld config doesn't cover the NVIDIA pip layout. -SITE_PACKAGES=$(python3 -c "import site, sys; print(site.getsitepackages()[0])") -# Glob matches NVIDIA pip-package layout: -# site-packages/nvidia//lib/lib*.so. If NVIDIA restructures -# packaging, this may need updating. -NVIDIA_LIBS=$(find "$SITE_PACKAGES" -path "*/nvidia/*/lib" -type d 2>/dev/null | tr '\n' ':') -TORCH_LIB="$SITE_PACKAGES/torch/lib" -VENV_LD="${NVIDIA_LIBS}${TORCH_LIB}" -export LD_LIBRARY_PATH="${VENV_LD}${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" -# Write LD_LIBRARY_PATH to the venv's env.sh (always succeeds — local file) -# so subsequent steps auto-source it via BASH_ENV. In non-venv mode, skip the -# env.sh write and rely on GITHUB_ENV propagation. -if [ "$USE_VENV" = "1" ] && [ -n "$UV_VENV" ]; then - echo "export LD_LIBRARY_PATH=\"$LD_LIBRARY_PATH\"" >> "$UV_VENV/env.sh" -fi -# Also try GITHUB_ENV (may fail if runner temp file was cleaned up during long installs). -if [ -n "${GITHUB_ENV:-}" ]; then - echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> "$GITHUB_ENV" || echo "WARNING: GITHUB_ENV write failed; LD_LIBRARY_PATH will be set via BASH_ENV instead" -fi -echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" + if [ "$USE_VENV" = "1" ] && [ -n "$UV_VENV" ]; then + echo "export LD_LIBRARY_PATH=\"$LD_LIBRARY_PATH\"" >> "$UV_VENV/env.sh" + fi + if [ -n "${GITHUB_ENV:-}" ]; then + echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> "$GITHUB_ENV" || echo "WARNING: GITHUB_ENV write failed; LD_LIBRARY_PATH will be set via BASH_ENV instead" + fi + echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" + mark_step_done "${FUNCNAME[0]}" +} -# ------------------------------------------------------------------------------ -# Verify imports -# ------------------------------------------------------------------------------ -# Show current packages -$PIP_CMD list -python3 -c "import torch; print(torch.version.cuda)" -python3 -c "import cutlass; import cutlass.cute;" +verify_imports() { + $PIP_CMD list + python3 -c "import torch; print(torch.version.cuda)" + python3 -c "import cutlass; import cutlass.cute;" + + mark_step_done "${FUNCNAME[0]}" +} + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +main() { + configure_environment "$@" + detect_host + kill_existing_processes + install_apt_packages + clean_site_packages + setup_pip_toolchain + uninstall_stale_flashinfer + install_sglang + install_sglang_kernel + install_sglang_router + download_flashinfer_cache + stabilize_flashinfer_jit_paths + install_extra_deps + fix_nvidia_deps + install_test_tools + prepare_runner + setup_ld_library_path + verify_imports +} + +main "$@"