NeMo
Megatron-LM / .github /workflows /_release_library.yml
KexuanShi's picture
Upload folder using huggingface_hub
88e6849 verified
Raw
History Blame Contribute Delete
15.8 kB
# Copyright (c) 2020-2021, NVIDIA CORPORATION.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: 'Release'
defaults:
run:
shell: bash -x -e -u -o pipefail {0}
on:
workflow_call:
inputs:
release-ref:
required: true
description: Ref (SHA or branch) to release
type: string
dry-run:
type: boolean
required: true
description: Do not publish a wheel and GitHub release.
version-bump-branch:
type: string
required: true
description: Branch to target for version bump
create-gh-release:
required: false
description: Create a GitHub release
type: boolean
default: true
secrets:
TWINE_USERNAME:
required: true
TWINE_PASSWORD:
required: true
SLACK_WEBHOOK_ADMIN:
required: true
SLACK_WEBHOOK:
required: true
PAT:
required: true
permissions:
contents: write # To read repository content
pull-requests: write # To create PR(s)
jobs:
build-test-publish-wheels-dry-run:
uses: ./.github/workflows/_build_test_publish_wheel.yml
with:
dry-run: true
ref: ${{ inputs.release-ref }}
no-publish: true
secrets:
TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }}
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
bump-next-version:
runs-on: ubuntu-latest
environment: main # ${{ inputs.dry-run == true && 'public' || 'main' }}
needs: build-test-publish-wheels-dry-run
if: |
(
success() || !failure()
)
&& !cancelled()
outputs:
release-version: ${{ steps.bump-version-mcore.outputs.release-version }}
env:
IS_DRY_RUN: ${{ inputs.dry-run }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
path: ${{ github.run_id }}
token: ${{ secrets.PAT }}
fetch-depth: 0
fetch-tags: true
ref: ${{ inputs.release-ref }}
- name: Bump version MCore
id: bump-version-mcore
env:
SRC_DIR: ''
PYPROJECT_NAME: 'megatron.core'
run: |
set +u
cd ${{ github.run_id }}
PACKAGE_INFO_FILE="$SRC_DIR${PYPROJECT_NAME//.//}/package_info.py"
MAJOR=$(cat $PACKAGE_INFO_FILE | awk '/^MAJOR = /' | awk -F"= " '{print $2}')
MINOR=$(cat $PACKAGE_INFO_FILE | awk '/^MINOR = /' | awk -F"= " '{print $2}')
PATCH=$(cat $PACKAGE_INFO_FILE | awk '/^PATCH = /' | awk -F"= " '{print $2}')
PRERELEASE=$(cat $PACKAGE_INFO_FILE | awk '/^PRE_RELEASE = /' | awk -F"= " '{print $2}' | tr -d '"' | tr -d "'")
echo "release-version=$MAJOR.$MINOR.$PATCH$PRERELEASE" | tee -a "$GITHUB_OUTPUT"
if [[ "$PRERELEASE" != "" ]]; then
if [[ "$PRERELEASE" == *rc* ]]; then
NEXT_PATCH=$PATCH
NEXT_PRERELEASE=rc$((${PRERELEASE#rc} + 1))
elif [[ "$PRERELEASE" == *a* ]]; then
NEXT_PATCH=$PATCH
NEXT_PRERELEASE=a$((${PRERELEASE#a} + 1))
else
echo "Unknown pre-release: $PRERELEASE"
exit 1
fi
else
NEXT_PATCH=$((${PATCH} + 1))
NEXT_PRERELEASE=$PRERELEASE
fi
sed -i "/^PATCH/c\PATCH = $NEXT_PATCH" $PACKAGE_INFO_FILE
sed -i "/^PRE_RELEASE/c\PRE_RELEASE = \"$NEXT_PRERELEASE\"" $PACKAGE_INFO_FILE
echo "version=$MAJOR.$MINOR.$NEXT_PATCH$NEXT_PRERELEASE" | tee -a "$GITHUB_OUTPUT"
- name: Bump version MFSDP
id: bump-version-mfsdp
env:
SRC_DIR: 'megatron/core/distributed/fsdp/src/'
PYPROJECT_NAME: 'megatron_fsdp'
run: |
set +u
cd ${{ github.run_id }}
PACKAGE_INFO_FILE="$SRC_DIR${PYPROJECT_NAME//.//}/package_info.py"
MAJOR=$(cat $PACKAGE_INFO_FILE | awk '/^MAJOR = /' | awk -F"= " '{print $2}')
MINOR=$(cat $PACKAGE_INFO_FILE | awk '/^MINOR = /' | awk -F"= " '{print $2}')
PATCH=$(cat $PACKAGE_INFO_FILE | awk '/^PATCH = /' | awk -F"= " '{print $2}')
PRERELEASE=$(cat $PACKAGE_INFO_FILE | awk '/^PRE_RELEASE = /' | awk -F"= " '{print $2}' | tr -d '"' | tr -d "'")
if [[ "$PRERELEASE" != "" ]]; then
if [[ "$PRERELEASE" == *rc* ]]; then
NEXT_PATCH=$PATCH
NEXT_PRERELEASE=rc$((${PRERELEASE#rc} + 1))
elif [[ "$PRERELEASE" == *a* ]]; then
NEXT_PATCH=$PATCH
NEXT_PRERELEASE=a$((${PRERELEASE#a} + 1))
else
echo "Unknown pre-release: $PRERELEASE"
exit 1
fi
else
NEXT_PATCH=$((${PATCH} + 1))
NEXT_PRERELEASE=$PRERELEASE
fi
sed -i "/^PATCH/c\PATCH = $NEXT_PATCH" $PACKAGE_INFO_FILE
sed -i "/^PRE_RELEASE/c\PRE_RELEASE = \"$NEXT_PRERELEASE\"" $PACKAGE_INFO_FILE
echo "version=$MAJOR.$MINOR.$NEXT_PATCH$NEXT_PRERELEASE" | tee -a "$GITHUB_OUTPUT"
- name: Create and push deployment branch
env:
GH_TOKEN: ${{ secrets.PAT }}
run: |
cd ${{ github.run_id }}
TMP_BRANCH="deploy-release/$(uuidgen)"
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b "$TMP_BRANCH"
git add -A .
git commit -m "beep boop 🤖: Bumping versions" || echo "No changes to commit"
git push -u origin "$TMP_BRANCH"
echo "TMP_BRANCH=$TMP_BRANCH" | tee -a $GITHUB_ENV
# Create PR to collect app based status checks that run on PRs only
# (like DCO check)
PR_URL=$(gh pr create \
--base ${{ inputs.version-bump-branch }} \
--head $TMP_BRANCH \
--title "beep boop 🤖: Bumping versions" \
--body "This is an automated PR to bump versions.")
# Extract PR number from URL
PR_NUMBER=$(echo $PR_URL | grep -o '[0-9]*$')
# Add comment to the newly created PR
echo gh pr comment $PR_NUMBER --body "/ok to test $(git rev-parse HEAD)"
- name: Wait for status checks on tmp branch
uses: actions/github-script@v7
id: wait-status
with:
github-token: ${{ secrets.PAT }}
script: |
const branch = process.env.TMP_BRANCH;
const owner = context.repo.owner;
const repo = context.repo.repo;
// Get latest commit SHA of branch
const { data: refData } = await github.rest.git.getRef({
owner,
repo,
ref: `heads/${branch}`, // note: no 'refs/' prefix here
});
const sha = refData.object.sha;
console.log(`Polling status for commit SHA: ${sha}`);
let checksPassed = false;
let maxAttempts = 30;
let attempt = 0;
const delay = ms => new Promise(res => setTimeout(res, ms));
while (!checksPassed && attempt < maxAttempts) {
attempt++;
// Use commit SHA instead of branch ref
const { data: status } = await github.rest.repos.getCombinedStatusForRef({
owner,
repo,
ref: sha,
});
const { data: checks } = await github.rest.checks.listForRef({
owner,
repo,
ref: sha,
});
const allStatuses = status.statuses;
const allChecks = checks.check_runs;
if (allStatuses.length === 0 && allChecks.length === 0) {
console.log(`Attempt ${attempt}: No checks or statuses yet. Waiting...`);
await delay(10000);
continue;
}
const statusesOk = allStatuses.every(s => s.state === 'success');
const checksOk = allChecks.every(c => c.status === 'completed');
if (statusesOk && checksOk) {
console.log('✅ All checks passed.');
checksPassed = true;
break
}
console.log(`Attempt ${attempt}: Checks not complete yet. Waiting...`);
await delay(10000);
}
if (!checksPassed) {
core.setFailed('❌ Status checks did not pass in time');
}
- name: Merge into ${{ inputs.version-bump-branch }}
run: |
cd ${{ github.run_id }}
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
CMD=$(echo -E 'git push origin ${{ inputs.version-bump-branch }}')
if [[ "$IS_DRY_RUN" == "true" ]]; then
echo "dry-run enabled, would have run: $CMD"
else
# Here we account for potential race conditions from multiple concurrent releases.
# Those can be legit (operating on different packages within the monorepo, for example)
# but the pushes would be still rejected purely because of git's inability to
# push non-fast-forward updates to the branch. In this case we would need to let
# a retry.
git fetch origin ${{ inputs.version-bump-branch }}
git checkout ${{ inputs.version-bump-branch }}
git merge ${{ env.TMP_BRANCH }}
for attempt in {1..3}; do
if eval "$CMD"; then
echo "Git push succeeded on attempt $attempt"
break
else
echo "Git push failed on attempt $attempt"
if [[ $attempt -lt 3 ]]; then
sleep $((RANDOM % 3 + 1))
# We refetch, reset and re-merge. Note resetting because the local
# branch is "contaminated" with previous merge attempt.
git fetch origin ${{ inputs.version-bump-branch }}
git reset --hard origin/${{ inputs.version-bump-branch }}
git merge ${{ env.TMP_BRANCH }}
else
echo "Git push failed after 3 attempts"
exit 1
fi
fi
done
fi
- name: Delete ${{ env.TMP_BRANCH }} branch
if: always()
run: |
cd ${{ github.run_id }}
git push -d origin ${{ env.TMP_BRANCH }}
build-test-publish-wheels:
needs: [bump-next-version]
uses: ./.github/workflows/_build_test_publish_wheel.yml
with:
dry-run: false
ref: ${{ inputs.release-ref }}
no-publish: false
secrets:
TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }}
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
create-gh-release:
needs: [build-test-publish-wheels, bump-next-version]
runs-on: ubuntu-latest
environment: ${{ inputs.dry-run == true && 'public' || 'main' }}
if: |
(
success() || !failure()
)
&& inputs.create-gh-release == true
&& !cancelled()
outputs:
is-release-candidate: ${{ steps.version-number.outputs.is-release-candidate }}
env:
REPOSITORY: ${{ github.repository }}
PROJECT_NAME: Megatron Core
VERSION: ${{ needs.bump-next-version.outputs.release-version }}
TAG_PREFIX: ${{ inputs.gh-release-tag-prefix || '' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
path: ${{ github.run_id }}
ref: ${{ inputs.release-ref }}
token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }}
- name: Create release
id: version-number
env:
SHA: ${{ inputs.release-ref }}
GH_TOKEN: ${{ secrets.PAT }}
IS_DRY_RUN: ${{ inputs.dry-run }}
run: |
cd ${{ github.run_id }}
IS_RELEASE_CANDIDATE=$([[ "$VERSION" == *rc* ]] && echo "true" || echo "false")
IS_ALPHA=$([[ "$VERSION" == *a* ]] && echo "true" || echo "false")
IS_PRERELEASE=$([[ "$IS_RELEASE_CANDIDATE" == "true" || "$IS_ALPHA" == "true" ]] && echo "true" || echo "false")
NAME="NVIDIA $PROJECT_NAME ${VERSION}"
if [[ "$IS_RELEASE_CANDIDATE" == "true" ]]; then
DATE=$(date +"%Y-%m-%d")
CHANGELOG="Prerelease: $NAME ($DATE)"
else
CHANGELOG=$(awk '/^## '"$NAME"'/{flag=1; next} /^## /{flag=0} flag' CHANGELOG.md)
CHANGELOG=$(echo "$CHANGELOG" | sed '/./,$!d' | sed ':a;N;$!ba;s/\n$//')
fi
echo "is-release-candidate=$IS_RELEASE_CANDIDATE" | tee -a "$GITHUB_OUTPUT"
PAYLOAD=$(jq -nc \
--arg TAG_NAME "${TAG_PREFIX}v${VERSION}" \
--arg CI_COMMIT_BRANCH "$SHA" \
--arg NAME "$NAME" \
--arg BODY "$CHANGELOG" \
--argjson PRERELEASE "$IS_PRERELEASE" \
'{
"tag_name": $TAG_NAME,
"target_commitish": $CI_COMMIT_BRANCH,
"name": $NAME,
"body": $BODY,
"draft": false,
"prerelease": $PRERELEASE,
"generate_release_notes": false
}'
)
echo -E "$PAYLOAD" > payload.txt
CMD=$(echo -E 'curl -L \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer '"$GH_TOKEN"'" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/'"$REPOSITORY"'/releases \
-d @payload.txt
')
if [[ "$IS_DRY_RUN" == "true" ]]; then
echo -E "$CMD"
else
eval "$CMD"
fi
notify:
needs: [build-test-publish-wheels, create-gh-release]
runs-on: ubuntu-latest
environment: ${{ inputs.dry-run == true && 'public' || 'main' }}
env:
GH_URL: https://github.com/${{ github.repository }}/releases/tag/v${{ needs.build-test-publish-wheels.outputs.version }}
PYPI_URL: https://${{ inputs.dry-run == true && 'test.' || '' }}pypi.org/project/${{ needs.build-test-publish-wheels.outputs.pypi-name }}/${{ needs.build-test-publish-wheels.outputs.version }}/
PROJECT_NAME: Megatron Core
VERSION: ${{ needs.build-test-publish-wheels.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
repository: NVIDIA-NeMo/FW-CI-templates
ref: v0.17.0
path: send-slack-alert
- name: Send Slack alert
uses: ./send-slack-alert/.github/actions/send-slack-alert
env:
MESSAGE: |
${{ inputs.dry-run == true && 'This is a dry-run, nothing actually happened: ' || '' }}We have released `${{ env.VERSION }}` of `NVIDIA ${{ env.PROJECT_NAME }}` 🚀✨🎉
<${{ env.GH_URL }}|GitHub release>
<${{ env.PYPI_URL }}|PyPi release>
with:
message: ${{ env.MESSAGE }}
webhook: ${{ secrets.SLACK_WEBHOOK }}