diff --git a/.github/workflows/bot-bump-flashinfer-version.yml b/.github/workflows/bot-bump-flashinfer-version.yml new file mode 100644 index 000000000..3ad5ac3bf --- /dev/null +++ b/.github/workflows/bot-bump-flashinfer-version.yml @@ -0,0 +1,46 @@ +name: Bot Bump Flashinfer Version + +on: + workflow_dispatch: + inputs: + new_version: + description: 'New flashinfer version (e.g., 0.6.4)' + required: true + type: string + +permissions: + contents: write + pull-requests: write + +jobs: + bump-flashinfer-version: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Configure Git and branch + run: | + git config user.name "sglang-bot" + git config user.email "sglang-bot@users.noreply.github.com" + RANDOM_SUFFIX=$(echo $RANDOM | md5sum | head -c 4) + BRANCH_NAME="bot/bump-flashinfer-version-${{ github.event.inputs.new_version }}-${RANDOM_SUFFIX}" + git checkout -b "$BRANCH_NAME" + echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV + + - name: Run flashinfer version bump script + run: | + python scripts/release/bump_flashinfer_version.py "${{ github.event.inputs.new_version }}" + + - name: Commit and create PR + env: + GH_TOKEN: ${{ secrets.GH_PAT_FOR_PULL_REQUEST }} + run: | + bash scripts/release/commit_and_pr.sh "flashinfer" "${{ github.event.inputs.new_version }}" "$BRANCH_NAME" diff --git a/scripts/release/bump_flashinfer_version.py b/scripts/release/bump_flashinfer_version.py new file mode 100755 index 000000000..7f47374e5 --- /dev/null +++ b/scripts/release/bump_flashinfer_version.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 + +import argparse +import re +import sys +from pathlib import Path + +from utils import compare_versions, get_repo_root, normalize_version, validate_version + +FILES_TO_UPDATE = [ + Path("python/pyproject.toml"), + Path("docker/Dockerfile"), + Path("scripts/ci/cuda/ci_install_dependency.sh"), + Path("python/sglang/srt/entrypoints/engine.py"), + Path("python/sglang/srt/server_args.py"), +] + + +def read_current_flashinfer_version(repo_root: Path) -> str: + """Read the current flashinfer version from python/pyproject.toml.""" + pyproject = repo_root / "python" / "pyproject.toml" + content = pyproject.read_text() + match = re.search(r"flashinfer_python==(\d+\.\d+\.\d+)", content) + if not match: + raise ValueError(f"Could not find flashinfer_python version in {pyproject}") + return match.group(1) + + +def replace_flashinfer_version( + file_path: Path, old_version: str, new_version: str +) -> bool: + if not file_path.exists(): + print(f"Warning: {file_path} does not exist, skipping") + return False + + content = file_path.read_text() + new_content = content + + name = file_path.name + if name == "pyproject.toml": + new_content = new_content.replace( + f"flashinfer_python=={old_version}", f"flashinfer_python=={new_version}" + ) + new_content = new_content.replace( + f"flashinfer_cubin=={old_version}", f"flashinfer_cubin=={new_version}" + ) + elif name == "Dockerfile": + new_content = re.sub( + rf"(ARG FLASHINFER_VERSION=){re.escape(old_version)}", + rf"\g<1>{new_version}", + new_content, + ) + elif name == "ci_install_dependency.sh": + new_content = re.sub( + rf"(FLASHINFER_VERSION=){re.escape(old_version)}", + rf"\g<1>{new_version}", + new_content, + ) + elif name == "engine.py" or name == "server_args.py": + new_content = new_content.replace( + f'flashinfer-python", "{old_version}"', + f'flashinfer-python", "{new_version}"', + ) + new_content = new_content.replace( + f'flashinfer_python", "{old_version}"', + f'flashinfer_python", "{new_version}"', + ) + new_content = new_content.replace( + f"flashinfer-python version is >= {old_version}", + f"flashinfer-python version is >= {new_version}", + ) + + if content == new_content: + print(f"No changes needed in {file_path}") + return False + + file_path.write_text(new_content) + print(f"✓ Updated {file_path}") + return True + + +def main(): + parser = argparse.ArgumentParser( + description="Bump flashinfer version across all relevant files" + ) + parser.add_argument( + "new_version", + help="New version (e.g., 0.6.4, 0.6.4rc0, or 0.6.4.post1)", + ) + args = parser.parse_args() + + new_version = normalize_version(args.new_version) + + if not validate_version(new_version): + print(f"Error: Invalid version format: {new_version}") + print("Expected format: X.Y.Z, X.Y.ZrcN, or X.Y.Z.postN") + print("Examples: 0.6.4, 0.6.4rc0, 0.6.4.post1") + sys.exit(1) + + repo_root = get_repo_root() + old_version = read_current_flashinfer_version(repo_root) + print(f"Current flashinfer version: {old_version}") + print(f"New flashinfer version: {new_version}") + print() + + comparison = compare_versions(new_version, old_version) + if comparison == 0: + print("Error: New version is the same as current version") + sys.exit(1) + elif comparison < 0: + print( + f"Error: New version ({new_version}) is older than current version ({old_version})" + ) + print("Version must be greater than the current version") + sys.exit(1) + + updated_count = 0 + for file_rel in FILES_TO_UPDATE: + file_abs = repo_root / file_rel + if replace_flashinfer_version(file_abs, old_version, new_version): + updated_count += 1 + + print() + print(f"Successfully updated {updated_count} file(s)") + print(f"Flashinfer version bumped from {old_version} to {new_version}") + + print("\nValidating version updates...") + failed_files = [] + for file_rel in FILES_TO_UPDATE: + file_abs = repo_root / file_rel + if not file_abs.exists(): + print(f"Warning: File {file_rel} does not exist, skipping validation.") + continue + + content = file_abs.read_text() + if new_version not in content: + failed_files.append(file_rel) + print(f"✗ {file_rel} does not contain version {new_version}") + else: + print(f"✓ {file_rel} validated") + + if failed_files: + print(f"\nError: {len(failed_files)} file(s) were not updated correctly:") + for file_rel in failed_files: + print(f" - {file_rel}") + sys.exit(1) + + print("\nAll files validated successfully!") + + +if __name__ == "__main__": + main()