Skip to content

Commit 6362bd3

Browse files
authored
Update flow to enable beta weekly releases (#2378)
1 parent ef736fe commit 6362bd3

File tree

3 files changed

+148
-4
lines changed

3 files changed

+148
-4
lines changed

.github/scripts/bump_beta_tag.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#!/usr/bin/env python3
2+
"""Resolve the next FA4 beta tag and optionally create + push it.
3+
4+
Usage:
5+
python bump_beta_tag.py # dry-run by default
6+
python bump_beta_tag.py --push # create and push the tag
7+
"""
8+
9+
from __future__ import annotations
10+
11+
import argparse
12+
import os
13+
import re
14+
import subprocess
15+
import sys
16+
17+
TAG_PATTERN = re.compile(r"^(fa4-v.+\.beta)(\d+)$")
18+
19+
20+
def git(*args: str) -> str:
21+
result = subprocess.run(
22+
["git", *args], capture_output=True, text=True, check=True
23+
)
24+
return result.stdout.strip()
25+
26+
27+
def get_beta_tags() -> list[tuple[str, int]]:
28+
raw = git("tag", "-l", "fa4-v*.beta*")
29+
if not raw:
30+
return []
31+
tags = []
32+
for line in raw.splitlines():
33+
m = TAG_PATTERN.match(line.strip())
34+
if m:
35+
tags.append((line.strip(), int(m.group(2))))
36+
return sorted(tags, key=lambda t: t[1])
37+
38+
39+
def tag_exists(tag: str) -> bool:
40+
result = subprocess.run(
41+
["git", "rev-parse", tag], capture_output=True, text=True
42+
)
43+
return result.returncode == 0
44+
45+
46+
def set_github_output(key: str, value: str) -> None:
47+
path = os.environ.get("GITHUB_OUTPUT")
48+
if path:
49+
with open(path, "a") as f:
50+
f.write(f"{key}={value}\n")
51+
52+
53+
def main() -> None:
54+
parser = argparse.ArgumentParser()
55+
parser.add_argument("--push", action="store_true", help="Create and push the tag (default: dry-run)")
56+
args = parser.parse_args()
57+
58+
tags = get_beta_tags()
59+
if not tags:
60+
print("::error::No existing fa4-v*.beta* tags found", file=sys.stderr)
61+
sys.exit(1)
62+
63+
latest_tag, latest_num = tags[-1]
64+
next_num = latest_num + 1
65+
prefix = TAG_PATTERN.match(latest_tag)
66+
if prefix is None:
67+
print(f"::error::Latest tag {latest_tag!r} no longer matches pattern", file=sys.stderr)
68+
sys.exit(1)
69+
next_tag = f"{prefix.group(1)}{next_num}"
70+
71+
already_exists = tag_exists(next_tag)
72+
73+
if already_exists:
74+
print(f"Tag {next_tag} already exists, reusing it")
75+
else:
76+
print(f"Bumping: {latest_tag} -> {next_tag}")
77+
78+
set_github_output("next_tag", next_tag)
79+
80+
if args.push and not already_exists:
81+
try:
82+
git("tag", next_tag)
83+
git("push", "origin", next_tag)
84+
except subprocess.CalledProcessError:
85+
if tag_exists(next_tag):
86+
print(f"Tag {next_tag} was created by a concurrent run, reusing it")
87+
else:
88+
raise
89+
else:
90+
print(f"Pushed {next_tag}")
91+
elif not args.push:
92+
print(f"Dry-run: would create and push {next_tag}")
93+
94+
95+
if __name__ == "__main__":
96+
main()

.github/workflows/README.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,16 @@ This repository uses separate tag lanes so FA2 and FA4 publishing do not collide
1919

2020
### FA4 / CUTE package lane
2121

22-
1. Create a tag matching `fa4-v*` (example: `fa4-v0.1.0`).
23-
2. Push that tag.
24-
3. `publish-fa4.yml` builds from `flash_attn/cute`, creates a GitHub release, and uploads `flash-attn-4` to PyPI.
22+
**Manual release**: create and push a tag matching `fa4-v*` (example: `fa4-v4.0.0`).
23+
24+
**Weekly beta**: `publish-fa4.yml` also runs every Wednesday at 08:00 UTC via cron. The scheduled or manual run creates and pushes the next `fa4-v*.beta*` tag, then continues in the same workflow run to build and publish that beta. Manual dispatch is restricted to the repository default branch so it cannot tag a feature branch commit. The pushed tag matches the `fa4-v*` trigger, but GitHub suppresses workflow runs for events created by `GITHUB_TOKEN`, so no recursive run is triggered.
25+
26+
| Week | Tag created | PyPI version |
27+
| --- | --- | --- |
28+
| 1 | `fa4-v4.0.0.beta5` | `4.0.0b5` |
29+
| 2 | `fa4-v4.0.0.beta6` | `4.0.0b6` |
30+
31+
To stop weekly betas: GitHub repo → Actions → "Publish flash-attn-4 to PyPI" → `···` menu → **Disable workflow**. Re-enable when ready to resume, or switch to manual tag pushes only by removing the `schedule` trigger. Users can still push a `fa4-v*.beta*` tag directly when they need to cut a beta outside the schedule.
2532

2633
## Guardrails
2734

.github/workflows/publish-fa4.yml

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,55 @@ on:
44
push:
55
tags:
66
- 'fa4-v*'
7+
schedule:
8+
- cron: '0 8 * * 3' # Wednesday 08:00 UTC
9+
workflow_dispatch:
710

811
permissions:
912
contents: write
1013

1114
jobs:
15+
prepare-release:
16+
runs-on: ubuntu-latest
17+
outputs:
18+
release_tag: ${{ steps.resolve-tag.outputs.release_tag }}
19+
steps:
20+
- name: Require default branch for manual runs
21+
if: github.event_name == 'workflow_dispatch'
22+
run: |
23+
if [ "${{ github.ref_name }}" != "${{ github.event.repository.default_branch }}" ]; then
24+
echo "::error::Run this workflow from ${{ github.event.repository.default_branch }} only"
25+
exit 1
26+
fi
27+
- uses: actions/checkout@v4
28+
if: github.event_name != 'push'
29+
with:
30+
ref: ${{ github.event.repository.default_branch }}
31+
fetch-depth: 0
32+
- uses: actions/setup-python@v5
33+
if: github.event_name != 'push'
34+
with:
35+
python-version: '3.12'
36+
- name: Bump beta tag
37+
if: github.event_name != 'push'
38+
id: bump
39+
run: python .github/scripts/bump_beta_tag.py --push
40+
- name: Resolve release tag
41+
id: resolve-tag
42+
run: |
43+
if [ "${{ github.event_name }}" = "push" ]; then
44+
echo "release_tag=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
45+
else
46+
echo "release_tag=${{ steps.bump.outputs.next_tag }}" >> "$GITHUB_OUTPUT"
47+
fi
48+
1249
build:
50+
needs: prepare-release
1351
runs-on: ubuntu-latest
1452
steps:
1553
- uses: actions/checkout@v4
1654
with:
55+
ref: ${{ needs.prepare-release.outputs.release_tag }}
1756
fetch-depth: 0
1857
- uses: actions/setup-python@v5
1958
with:
@@ -33,7 +72,7 @@ jobs:
3372
path: flash_attn/cute/dist/
3473

3574
github-release:
36-
needs: build
75+
needs: [prepare-release, build]
3776
runs-on: ubuntu-latest
3877
steps:
3978
- name: Download distribution packages
@@ -44,8 +83,10 @@ jobs:
4483
- name: Create GitHub Release
4584
uses: softprops/action-gh-release@v2
4685
with:
86+
tag_name: ${{ needs.prepare-release.outputs.release_tag }}
4787
files: dist/*
4888
generate_release_notes: true
89+
prerelease: ${{ contains(needs.prepare-release.outputs.release_tag, '.beta') }}
4990

5091
publish-to-pypi:
5192
needs: build

0 commit comments

Comments
 (0)