From ba9074035a9879ebdd36f955610c2928a9049e05 Mon Sep 17 00:00:00 2001 From: Baizhou Zhang Date: Thu, 6 Aug 2026 02:07:36 -0700 Subject: [PATCH] Build and release sgl-deep-ep wheels (#33498) --- .github/workflows/release-whl-deepep.yml | 261 +++++++++++++++++++++++ docker/sgl-deep-ep.Dockerfile | 75 +++++++ scripts/build_sgl_deepep.sh | 197 +++++++++++++++++ scripts/update_deepep_whl_index.py | 82 +++++++ 4 files changed, 615 insertions(+) create mode 100644 .github/workflows/release-whl-deepep.yml create mode 100644 docker/sgl-deep-ep.Dockerfile create mode 100755 scripts/build_sgl_deepep.sh create mode 100644 scripts/update_deepep_whl_index.py diff --git a/.github/workflows/release-whl-deepep.yml b/.github/workflows/release-whl-deepep.yml new file mode 100644 index 000000000..8c637b9e9 --- /dev/null +++ b/.github/workflows/release-whl-deepep.yml @@ -0,0 +1,261 @@ +name: Release sgl-deep-ep + +on: + workflow_dispatch: + inputs: + version: + description: "Public wheel version (for example, 0.1.0 or 0.1.1rc0)" + type: string + required: true + target: + description: "CUDA target" + type: choice + required: false + default: "all" + options: + - "all" + - "cu129" + - "cu130" + packaging-ref: + description: "DeepEP ref containing the shared sgl_deep_ep overlay" + type: string + required: false + default: "sgl-deepep-packaging" + +concurrency: + group: release-sgl-deep-ep + cancel-in-progress: false + +env: + TORCH_VERSION: "2.11.0" + +jobs: + build-cu129-matrix: + if: | + github.repository == 'sgl-project/sglang' && + (inputs.target == 'all' || inputs.target == 'cu129') + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.12"] + arch: [x86_64, aarch64] + include: + - arch: x86_64 + runner: x64-kernel-build-node + source-branch: sgl-deepep-x86 + - arch: aarch64 + runner: arm-kernel-build-node + source-branch: sgl-deepep-cu12-arm + runs-on: ${{ matrix.runner }} + steps: + - name: Clean workspace + run: | + docker run --rm -v "${{ github.workspace }}:/workspace" alpine:3 \ + sh -c 'rm -rf /workspace/..?* /workspace/.[!.]* /workspace/*' || true + + - name: Checkout SGLang + uses: actions/checkout@v4 + + - name: Checkout DeepEP implementation + uses: actions/checkout@v4 + with: + repository: sgl-project/DeepEP + ref: ${{ matrix.source-branch }} + path: DeepEP-source + submodules: recursive + + - name: Checkout DeepEP packaging overlay + uses: actions/checkout@v4 + with: + repository: sgl-project/DeepEP + ref: ${{ inputs['packaging-ref'] || 'sgl-deepep-packaging' }} + path: DeepEP-packaging + sparse-checkout: sgl_deep_ep + + - name: Set wheel version + env: + RELEASE_VERSION: ${{ inputs.version }} + run: | + printf '%s' "${RELEASE_VERSION}" > DeepEP-packaging/sgl_deep_ep/VERSION + + - name: Build wheel + run: | + chmod +x scripts/build_sgl_deepep.sh DeepEP-packaging/sgl_deep_ep/build_sgl_deep_ep.sh + scripts/build_sgl_deepep.sh \ + "${{ matrix.python-version }}" \ + "12.9" \ + "${{ github.workspace }}/DeepEP-source" \ + "${{ github.workspace }}/DeepEP-packaging/sgl_deep_ep" \ + "${{ matrix.arch }}" + + - name: Upload wheel + uses: actions/upload-artifact@v4 + with: + name: deepep-wheel-cu129-py${{ matrix.python-version }}-${{ matrix.arch }} + path: DeepEP-source/dist/*.whl + if-no-files-found: error + + build-cu130-matrix: + if: | + github.repository == 'sgl-project/sglang' && + (inputs.target == 'all' || inputs.target == 'cu130') + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13"] + arch: [x86_64, aarch64] + include: + - arch: x86_64 + runner: x64-kernel-build-node + source-branch: sgl-deepep-x86 + - arch: aarch64 + runner: arm-kernel-build-node + source-branch: sgl-deepep-arm + runs-on: ${{ matrix.runner }} + steps: + - name: Clean workspace + run: | + docker run --rm -v "${{ github.workspace }}:/workspace" alpine:3 \ + sh -c 'rm -rf /workspace/..?* /workspace/.[!.]* /workspace/*' || true + + - name: Checkout SGLang + uses: actions/checkout@v4 + + - name: Checkout DeepEP implementation + uses: actions/checkout@v4 + with: + repository: sgl-project/DeepEP + ref: ${{ matrix.source-branch }} + path: DeepEP-source + submodules: recursive + + - name: Checkout DeepEP packaging overlay + uses: actions/checkout@v4 + with: + repository: sgl-project/DeepEP + ref: ${{ inputs['packaging-ref'] || 'sgl-deepep-packaging' }} + path: DeepEP-packaging + sparse-checkout: sgl_deep_ep + + - name: Set wheel version + env: + RELEASE_VERSION: ${{ inputs.version }} + run: | + printf '%s' "${RELEASE_VERSION}" > DeepEP-packaging/sgl_deep_ep/VERSION + + - name: Build wheels + run: | + chmod +x scripts/build_sgl_deepep.sh DeepEP-packaging/sgl_deep_ep/build_sgl_deep_ep.sh + scripts/build_sgl_deepep.sh \ + "${{ matrix.python-version }}" \ + "13.0" \ + "${{ github.workspace }}/DeepEP-source" \ + "${{ github.workspace }}/DeepEP-packaging/sgl_deep_ep" \ + "${{ matrix.arch }}" + + - name: Upload CUDA-tagged wheel + uses: actions/upload-artifact@v4 + with: + name: deepep-wheel-cu130-py${{ matrix.python-version }}-${{ matrix.arch }} + path: DeepEP-source/dist/*.whl + if-no-files-found: error + + - name: Upload PyPI wheel + uses: actions/upload-artifact@v4 + with: + name: deepep-pypi-cu130-py${{ matrix.python-version }}-${{ matrix.arch }} + path: DeepEP-source/dist-pypi/*.whl + if-no-files-found: error + + release-whl: + needs: [build-cu129-matrix, build-cu130-matrix] + if: | + always() && + github.repository == 'sgl-project/sglang' && + ( + (inputs.target == 'cu129' && needs.build-cu129-matrix.result == 'success') || + (inputs.target == 'cu130' && needs.build-cu130-matrix.result == 'success') || + ( + inputs.target == 'all' && + needs.build-cu129-matrix.result == 'success' && + needs.build-cu130-matrix.result == 'success' + ) + ) + runs-on: ubuntu-latest + steps: + - name: Checkout SGLang + uses: actions/checkout@v4 + + - name: Download CUDA-tagged wheels + uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + pattern: deepep-wheel-* + + - name: Upload release assets + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ inputs.version }} + repository: sgl-project/whl + token: ${{ secrets.GH_PAT_FOR_WHL_RELEASE }} + fail_on_unmatched_files: true + files: dist/*.whl + + - name: Clone wheel index + env: + WHL_TOKEN: ${{ secrets.GH_PAT_FOR_WHL_RELEASE }} + run: | + git clone --branch gh-pages \ + "https://oauth2:${WHL_TOKEN}@github.com/sgl-project/whl.git" sgl-whl + + - name: Update wheel indexes + run: | + if [[ "${{ inputs.target }}" == "all" || "${{ inputs.target }}" == "cu129" ]]; then + python3 scripts/update_deepep_whl_index.py --cuda 129 + fi + if [[ "${{ inputs.target }}" == "all" || "${{ inputs.target }}" == "cu130" ]]; then + python3 scripts/update_deepep_whl_index.py --cuda 130 + fi + + - name: Push wheel indexes + env: + RELEASE_VERSION: ${{ inputs.version }} + run: | + cd sgl-whl + git config --local user.name "sglang-bot" + git config --local user.email "sglangbot@gmail.com" + git add -A + if git diff --cached --quiet; then + echo "Wheel indexes are already up to date" + else + git commit -m "update sgl-deep-ep whl indexes for v${RELEASE_VERSION}" + git push origin gh-pages + fi + + release-pypi: + needs: [build-cu129-matrix, build-cu130-matrix] + if: | + always() && + github.repository == 'sgl-project/sglang' && + needs.build-cu130-matrix.result == 'success' && + ( + inputs.target == 'cu130' || + (inputs.target == 'all' && needs.build-cu129-matrix.result == 'success') + ) + runs-on: ubuntu-latest + steps: + - name: Download PyPI wheels + uses: actions/download-artifact@v4 + with: + path: dist-pypi + merge-multiple: true + pattern: deepep-pypi-cu130-* + + - name: Upload to PyPI + env: + TWINE_PASSWORD: ${{ secrets.SGL_DEEP_EP_PYPI_TOKEN }} + TWINE_USERNAME: __token__ + run: | + python3 -m pip install --upgrade twine "packaging>=24.2" + python3 -m twine upload --skip-existing dist-pypi/*.whl diff --git a/docker/sgl-deep-ep.Dockerfile b/docker/sgl-deep-ep.Dockerfile new file mode 100644 index 000000000..9b671fa2c --- /dev/null +++ b/docker/sgl-deep-ep.Dockerfile @@ -0,0 +1,75 @@ +ARG BASE_IMAGE=pytorch/manylinux2_28-builder +ARG CUDA_VERSION=13.0 + +FROM ${BASE_IMAGE}:cuda${CUDA_VERSION} + +ARG ARCHITECTURE=x86_64 +ARG CUDA_TAG=cu130 +ARG CUDA_VERSION=13.0 +ARG GDRCOPY_VERSION=2.5.1 +ARG PYTHON_TAG=cp312-cp312 +ARG TORCH_VERSION=2.11.0 + +ENV CUDA_HOME=/usr/local/cuda +ENV GDRCOPY_HOME=/usr/local +ENV LD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib64:/usr/local/cuda/lib64:${LD_LIBRARY_PATH} +ENV PATH=/opt/python/${PYTHON_TAG}/bin:${PATH} +ENV PYTHON_BIN=/opt/python/${PYTHON_TAG}/bin/python + +# These mirror the build and RDMA dependencies used by ci_install_deepep.sh. +# The image builds only GDRCopy's user-space library: the host owns gdrdrv and +# passes /dev/gdrdrv through at runtime. +RUN yum install -y --nogpgcheck \ + cmake \ + curl \ + gcc \ + gcc-c++ \ + git \ + infiniband-diags \ + libfabric \ + libfabric-devel \ + libibverbs \ + libibverbs-devel \ + libibverbs-utils \ + librdmacm \ + librdmacm-devel \ + make \ + openssh-server \ + patchelf \ + perftest \ + pkgconfig \ + rdma-core \ + wget \ + && yum clean all \ + && rm -rf /var/cache/yum + +RUN set -eux; \ + if [ "${ARCHITECTURE}" = aarch64 ]; then cuda_target=sbsa; else cuda_target="${ARCHITECTURE}"; fi; \ + cuda_stub="/usr/local/cuda-${CUDA_VERSION}/targets/${cuda_target}-linux/lib/stubs/libcuda.so"; \ + test -f "${cuda_stub}"; \ + mkdir -p /usr/lib64 "/usr/lib/${ARCHITECTURE}-linux-gnu"; \ + ln -sf "${cuda_stub}" /usr/lib64/libcuda.so; \ + ln -sf "${cuda_stub}" "/usr/lib/${ARCHITECTURE}-linux-gnu/libcuda.so" + +RUN git clone --depth 1 --branch "v${GDRCOPY_VERSION}" \ + https://github.com/NVIDIA/gdrcopy.git /opt/gdrcopy \ + && make -C /opt/gdrcopy CUDA="${CUDA_HOME}" prefix=/usr/local lib_install \ + && ldconfig \ + && test -f /usr/local/include/gdrapi.h \ + && ldconfig -p | grep -q libgdrapi + +RUN --mount=type=cache,id=sgl-deep-ep-pip-${CUDA_TAG}-${PYTHON_TAG}-${ARCHITECTURE},target=/root/.cache/pip \ + set -eux; \ + "${PYTHON_BIN}" -m pip uninstall -y deep-ep sgl-deep-ep || true; \ + "${PYTHON_BIN}" -m pip install --upgrade pip; \ + "${PYTHON_BIN}" -m pip install --force-reinstall \ + "torch==${TORCH_VERSION}" \ + --index-url "https://download.pytorch.org/whl/${CUDA_TAG}"; \ + "${PYTHON_BIN}" -m pip install \ + "auditwheel>=6.0" \ + build \ + ninja \ + packaging \ + setuptools \ + wheel; \ + "${PYTHON_BIN}" -c 'import torch; assert torch.__version__.startswith("2.11.0"); print(torch.__version__, torch.version.cuda)' diff --git a/scripts/build_sgl_deepep.sh b/scripts/build_sgl_deepep.sh new file mode 100755 index 000000000..9212290d1 --- /dev/null +++ b/scripts/build_sgl_deepep.sh @@ -0,0 +1,197 @@ +#!/usr/bin/env bash +# Build sgl-deep-ep wheels in a CUDA-versioned manylinux container. +# +# Usage: +# build_sgl_deepep.sh [architecture] +# +# Writes CUDA-tagged wheels to /dist. CUDA 13 builds also write +# PyPI-ready wheels without the local CUDA version to /dist-pypi. + +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: build_sgl_deepep.sh [architecture] + + python-version: 3.10, 3.11, 3.12, or 3.13 + cuda-version: 12.9 or 13.0 + deepep-source: checkout of the selected DeepEP implementation branch + packaging-overlay: path to the shared DeepEP sgl_deep_ep directory + architecture: x86_64 or aarch64 (defaults to the current machine) +EOF +} + +if [[ $# -lt 4 || $# -gt 5 ]]; then + usage >&2 + exit 2 +fi + +PYTHON_VERSION="$1" +CUDA_VERSION="$2" +DEEPEP_SOURCE="$(cd "$3" && pwd)" +PACKAGING_OVERLAY="$(cd "$4" && pwd)" +ARCHITECTURE="${5:-$(uname -m)}" + +if [[ "${ARCHITECTURE}" == arm64 ]]; then + ARCHITECTURE=aarch64 +fi + +case "${PYTHON_VERSION}" in + 3.10|3.11|3.12|3.13) ;; + *) + echo "Unsupported Python version: ${PYTHON_VERSION}" >&2 + exit 2 + ;; +esac + +case "${CUDA_VERSION}" in + 12.9) + CUDA_TAG=cu129 + ;; + 13.0) + CUDA_TAG=cu130 + ;; + *) + echo "Unsupported CUDA version: ${CUDA_VERSION}" >&2 + exit 2 + ;; +esac + +case "${ARCHITECTURE}" in + x86_64) + BASE_IMAGE=pytorch/manylinux2_28-builder + ;; + aarch64) + BASE_IMAGE=pytorch/manylinuxaarch64-builder + ;; + *) + echo "Unsupported architecture: ${ARCHITECTURE}" >&2 + exit 2 + ;; +esac + +for required_file in setup.py deep_ep/__init__.py; do + if [[ ! -f "${DEEPEP_SOURCE}/${required_file}" ]]; then + echo "DeepEP source is missing ${required_file}: ${DEEPEP_SOURCE}" >&2 + exit 1 + fi +done +for required_file in build_sgl_deep_ep.sh setup.py VERSION; do + if [[ ! -f "${PACKAGING_OVERLAY}/${required_file}" ]]; then + echo "Packaging overlay is missing ${required_file}: ${PACKAGING_OVERLAY}" >&2 + exit 1 + fi +done +if ! command -v docker >/dev/null; then + echo "docker is required to build sgl-deep-ep" >&2 + exit 1 +fi + +PYTHON_TAG="cp${PYTHON_VERSION//.}-cp${PYTHON_VERSION//.}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPOSITORY_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +DOCKERFILE="${REPOSITORY_ROOT}/docker/sgl-deep-ep.Dockerfile" +IMAGE_TAG="sgl-deep-ep-builder:cuda${CUDA_VERSION}-${PYTHON_TAG}-${ARCHITECTURE}" +DIST_DIR="${DEEPEP_SOURCE}/dist" +PYPI_DIST_DIR="${DEEPEP_SOURCE}/dist-pypi" + +mkdir -p "${DIST_DIR}" "${PYPI_DIST_DIR}" + +echo "----------------------------------------" +echo "Python: ${PYTHON_VERSION} (${PYTHON_TAG})" +echo "CUDA: ${CUDA_VERSION} (${CUDA_TAG})" +echo "Architecture: ${ARCHITECTURE}" +echo "Base image: ${BASE_IMAGE}:cuda${CUDA_VERSION}" +echo "DeepEP source: ${DEEPEP_SOURCE}" +echo "Packaging overlay: ${PACKAGING_OVERLAY}" +echo "Builder image: ${IMAGE_TAG}" +echo "----------------------------------------" + +docker build \ + --file "${DOCKERFILE}" \ + --build-arg BASE_IMAGE="${BASE_IMAGE}" \ + --build-arg CUDA_VERSION="${CUDA_VERSION}" \ + --build-arg CUDA_TAG="${CUDA_TAG}" \ + --build-arg PYTHON_TAG="${PYTHON_TAG}" \ + --build-arg ARCHITECTURE="${ARCHITECTURE}" \ + --build-arg TORCH_VERSION="${TORCH_VERSION:-2.11.0}" \ + --tag "${IMAGE_TAG}" \ + --network=host \ + "${REPOSITORY_ROOT}/docker" + +docker run --rm \ + --network=host \ + --env ARCHITECTURE="${ARCHITECTURE}" \ + --env CUDA_TAG="${CUDA_TAG}" \ + --env CUDA_VERSION="${CUDA_VERSION}" \ + --env MAX_JOBS="${MAX_JOBS:-8}" \ + --volume "${DEEPEP_SOURCE}:/deepep:ro" \ + --volume "${PACKAGING_OVERLAY}:/packaging:ro" \ + --volume "${DIST_DIR}:/output/dist" \ + --volume "${PYPI_DIST_DIR}:/output/dist-pypi" \ + "${IMAGE_TAG}" \ + bash -euo pipefail -c ' +find /output/dist -maxdepth 1 -type f -name "sgl_deep_ep-*.whl" -delete +find /output/dist-pypi -maxdepth 1 -type f -name "sgl_deep_ep-*.whl" -delete +raw_dir="$(mktemp -d -t sgl-deep-ep-raw.XXXXXX)" +trap '\''rm -rf -- "${raw_dir}"'\'' EXIT + +bash /packaging/build_sgl_deep_ep.sh \ + /deepep /packaging "${raw_dir}" "${CUDA_VERSION}" "${ARCHITECTURE}" + +shopt -s nullglob +raw_wheels=("${raw_dir}"/*.whl) +if [[ ${#raw_wheels[@]} -ne 1 ]]; then + echo "Expected exactly one raw wheel, found ${#raw_wheels[@]}" >&2 + exit 1 +fi + +auditwheel repair \ + --plat "manylinux_2_28_${ARCHITECTURE}" \ + --wheel-dir /output/dist \ + --exclude libcuda.so.1 \ + --exclude libcudart.so.12 \ + --exclude libcudart.so.13 \ + --exclude libc10.so \ + --exclude libc10_cuda.so \ + --exclude libtorch.so \ + --exclude libtorch_cpu.so \ + --exclude libtorch_cuda.so \ + --exclude libtorch_python.so \ + --exclude libnvshmem_host.so.1 \ + --exclude libnvshmem_host.so.2 \ + --exclude libnvshmem_host.so.3 \ + --exclude libnccl.so.2 \ + --exclude libgdrapi.so.2 \ + --exclude libnvToolsExt.so.1 \ + "${raw_wheels[0]}" + +if [[ "${CUDA_TAG}" == cu130 ]]; then + tagged_wheels=(/output/dist/sgl_deep_ep-*+cu130-*.whl) + if [[ ${#tagged_wheels[@]} -ne 1 ]]; then + echo "Expected exactly one CUDA 13 wheel, found ${#tagged_wheels[@]}" >&2 + exit 1 + fi + unpack_root="$(mktemp -d -t sgl-deep-ep-pypi.XXXXXX)" + python -m wheel unpack "${tagged_wheels[0]}" --dest "${unpack_root}" + unpacked="$(find "${unpack_root}" -mindepth 1 -maxdepth 1 -type d | head -1)" + dist_info="$(find "${unpacked}" -maxdepth 1 -type d -name "*.dist-info" | head -1)" + metadata="${dist_info}/METADATA" + original_version="$(sed -n "s/^Version:[[:space:]]*//p" "${metadata}" | head -1)" + public_version="${original_version%+cu130}" + if [[ "${original_version}" == "${public_version}" ]]; then + echo "CUDA 13 wheel metadata lacks the +cu130 local version" >&2 + exit 1 + fi + sed -i "s/^Version:.*/Version: ${public_version}/" "${metadata}" + old_dist_info="$(basename "${dist_info}")" + new_dist_info="${old_dist_info/${original_version}/${public_version}}" + mv "${dist_info}" "$(dirname "${dist_info}")/${new_dist_info}" + python -m wheel pack "${unpacked}" --dest-dir /output/dist-pypi +fi + +ls -lh /output/dist/*.whl +if [[ "${CUDA_TAG}" == cu130 ]]; then + ls -lh /output/dist-pypi/*.whl +fi +' diff --git a/scripts/update_deepep_whl_index.py b/scripts/update_deepep_whl_index.py new file mode 100644 index 000000000..fd9b09720 --- /dev/null +++ b/scripts/update_deepep_whl_index.py @@ -0,0 +1,82 @@ +"""Update the PEP 503 indexes for sgl-deep-ep release wheels.""" + +import argparse +import hashlib +import pathlib +import re + +SUPPORTED_CUDA_VERSIONS = ("129", "130") +WHEEL_PATTERN = re.compile( + r"^sgl_deep_ep-(?P[0-9][^-]*)-[^-]+-[^-]+-[^-]+\.whl$" +) +ANCHOR_PATTERN = re.compile(r'^(?P[^<]+)
$') + + +def _sha256(path: pathlib.Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as wheel_file: + for chunk in iter(lambda: wheel_file.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def _ensure_root_link(cuda_root: pathlib.Path) -> None: + root_index = cuda_root / "index.html" + lines = root_index.read_text().splitlines() if root_index.exists() else [] + doctype = [line for line in lines if line.startswith("sgl-deep-ep' + if link not in anchors: + anchors.append(link) + content = [*(doctype or [""]), *sorted(set(anchors))] + root_index.write_text("\n".join(content) + "\n") + + +def update_wheel_index( + cuda_version: str, + wheel_dir: pathlib.Path, + repository: pathlib.Path, +) -> None: + if cuda_version not in SUPPORTED_CUDA_VERSIONS: + raise ValueError(f"Unsupported CUDA version: {cuda_version}") + + cuda_root = repository / f"cu{cuda_version}" + index_dir = cuda_root / "sgl-deep-ep" + index_dir.mkdir(exist_ok=True, parents=True) + _ensure_root_link(cuda_root) + + index_path = index_dir / "index.html" + entries = {} + if index_path.exists(): + for line in index_path.read_text().splitlines(): + match = ANCHOR_PATTERN.match(line) + if match: + entries[match.group("filename")] = line + + suffix = f"+cu{cuda_version}" + release_base = "https://github.com/sgl-project/whl/releases/download" + for path in sorted(wheel_dir.glob("*.whl")): + match = WHEEL_PATTERN.match(path.name) + if not match or suffix not in match.group("version"): + continue + public_version = match.group("version").split("+", 1)[0] + url = f"{release_base}/v{public_version}/{path.name}#sha256={_sha256(path)}" + entries[path.name] = f'{path.name}
' + + content = ["", *(entries[name] for name in sorted(entries))] + index_path.write_text("\n".join(content) + "\n") + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--cuda", required=True, choices=SUPPORTED_CUDA_VERSIONS) + parser.add_argument("--wheel-dir", type=pathlib.Path, default=pathlib.Path("dist")) + parser.add_argument( + "--repository", type=pathlib.Path, default=pathlib.Path("sgl-whl") + ) + args = parser.parse_args() + update_wheel_index(args.cuda, args.wheel_dir, args.repository) + + +if __name__ == "__main__": + main()