[CI] unsloth-llms-finetuning / quick-train-unsloth failed on r9700 (linux) #24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Copyright Advanced Micro Devices, Inc. | |
| # | |
| # SPDX-License-Identifier: MIT | |
| # | |
| # ChatOps: comment `!orc` on a PR to run that PR's playbooks via OrchestrAI and | |
| # get a sticky pass/fail comment back. | |
| # | |
| # !orc -> the playbooks changed in the PR | |
| # !orc all -> every playbook | |
| # !orc a,b,c -> the named playbooks | |
| # | |
| # Only users with write access (OWNER/MEMBER/COLLABORATOR) can trigger it, since | |
| # it runs internal hardware with the OrchestrAI pipeline secrets. issue_comment runs from the | |
| # default branch, so this file must be on the default branch to take effect. | |
| name: OrchestrAI PR command | |
| on: | |
| issue_comment: | |
| types: [created] | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| issues: write | |
| concurrency: | |
| group: orchestrai-orc-${{ github.event.issue.number }} | |
| cancel-in-progress: true | |
| jobs: | |
| command: | |
| name: "!orc" | |
| if: github.event.issue.pull_request && startsWith(github.event.comment.body, '!orc') && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) | |
| runs-on: ${{ vars.ORCHESTRAI_CONTROL_RUNNER || 'ubuntu-latest' }} | |
| outputs: | |
| matrix: ${{ steps.build-matrix.outputs.matrix }} | |
| has_entries: ${{ steps.build-matrix.outputs.has_entries }} | |
| build_urls: ${{ steps.trigger.outputs.build_urls }} | |
| head_sha: ${{ steps.pr.outputs.head_sha }} | |
| head_ref: ${{ steps.pr.outputs.head_ref }} | |
| steps: | |
| - name: Resolve PR + acknowledge | |
| id: pr | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| API: ${{ github.api_url }} | |
| REPO: ${{ github.repository }} | |
| PR: ${{ github.event.issue.number }} | |
| COMMENT_ID: ${{ github.event.comment.id }} | |
| run: | | |
| curl -s -X POST -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ | |
| "$API/repos/$REPO/issues/comments/$COMMENT_ID/reactions" -d '{"content":"eyes"}' >/dev/null || true | |
| data=$(curl -s -H "Authorization: Bearer $GH_TOKEN" "$API/repos/$REPO/pulls/$PR") | |
| py() { printf '%s' "$data" | python3 -c "import sys,json;print(json.load(sys.stdin)$1)"; } | |
| # SECURITY: only same-repo PRs. issue_comment carries the repo secrets | |
| # even for fork PRs, so allowing forks would let untrusted PR code run | |
| # on internal hardware with the OrchestrAI pipeline token. Refuse forks outright. | |
| HEAD_REPO_FULL=$(py "['head']['repo']['full_name']") | |
| if [ "$HEAD_REPO_FULL" != "$REPO" ]; then | |
| echo "fork=true" >> "$GITHUB_OUTPUT" | |
| echo "::warning::!orc refused: PR head '$HEAD_REPO_FULL' is a fork of '$REPO'" | |
| curl -s -X POST -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ | |
| "$API/repos/$REPO/issues/$PR/comments" \ | |
| -d '{"body":"🔒 `!orc` is only available for pull requests from branches in this repository, not forks."}' >/dev/null || true | |
| exit 0 | |
| fi | |
| { | |
| echo "fork=false" | |
| echo "head_sha=$(py "['head']['sha']")" | |
| echo "base_sha=$(py "['base']['sha']")" | |
| echo "head_ref=$(py "['head']['ref']")" | |
| echo "head_repo=$(py "['head']['repo']['clone_url']")" | |
| } >> "$GITHUB_OUTPUT" | |
| - if: steps.pr.outputs.fork == 'false' | |
| uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 | |
| with: | |
| ref: ${{ steps.pr.outputs.head_sha }} | |
| fetch-depth: 0 | |
| - if: steps.pr.outputs.fork == 'false' | |
| uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 | |
| with: | |
| python-version: '3.12' | |
| - if: steps.pr.outputs.fork == 'false' | |
| run: pip install --quiet pyyaml | |
| - name: Require OrchestrAI config | |
| if: steps.pr.outputs.fork == 'false' | |
| env: | |
| JU: ${{ secrets.ORCHESTRAI_PIPELINE_USER }} | |
| JT: ${{ secrets.ORCHESTRAI_PIPELINE_TOKEN }} | |
| run: | | |
| if [ -z "$JU" ] || [ -z "$JT" ]; then | |
| echo "::error::OrchestrAI secrets (ORCHESTRAI_PIPELINE_USER/ORCHESTRAI_PIPELINE_TOKEN) are not set"; exit 1 | |
| fi | |
| - name: Parse command into playbook list | |
| id: detect | |
| if: steps.pr.outputs.fork == 'false' | |
| env: | |
| BODY: ${{ github.event.comment.body }} | |
| BASE_SHA: ${{ steps.pr.outputs.base_sha }} | |
| HEAD_SHA: ${{ steps.pr.outputs.head_sha }} | |
| run: | | |
| arg=$(printf '%s' "$BODY" | head -n1 | sed -E 's/^!orc[[:space:]]*//') | |
| if [ "$arg" = "all" ]; then | |
| PB=$(find playbooks/core playbooks/supplemental -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sed 's|playbooks/[^/]*/||' | sort -u | jq -R -s -c 'split("\n") | map(select(length > 0))') | |
| elif [ -n "$arg" ]; then | |
| PB=$(printf '%s' "$arg" | tr ', ' '\n\n' | sed 's/^ *//;s/ *$//' | jq -R -s -c 'split("\n") | map(select(length > 0))') | |
| else | |
| if ! CHANGED=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" -- 'playbooks/core/*' 'playbooks/supplemental/*'); then | |
| echo "::error::git diff failed (base $BASE_SHA not in history?)"; exit 1 | |
| fi | |
| PB=$(echo "$CHANGED" | grep -E '^playbooks/(core|supplemental)/[^/]+/' | sed 's|playbooks/[^/]*/\([^/]*\)/.*|\1|' | sort -u | jq -R -s -c 'split("\n") | map(select(length > 0))') | |
| fi | |
| echo "playbooks=${PB:-[]}" >> "$GITHUB_OUTPUT" | |
| - name: Build matrix and batches | |
| id: build-matrix | |
| if: steps.pr.outputs.fork == 'false' | |
| env: | |
| PLAYBOOKS: ${{ steps.detect.outputs.playbooks }} | |
| ORCHESTRAI_DEVICE_TAGS: ${{ vars.ORCHESTRAI_DEVICE_TAGS }} | |
| run: python3 .github/scripts/orchestrai_matrix.py | |
| - name: Trigger OrchestrAI pipeline builds (PR code) | |
| id: trigger | |
| if: steps.pr.outputs.fork == 'false' && steps.build-matrix.outputs.has_entries == 'true' | |
| env: | |
| BATCHES_JSON: ${{ steps.build-matrix.outputs.batches }} | |
| GIT_REF: ${{ steps.pr.outputs.head_ref }} | |
| ORCHESTRAI_PIPELINE_USER: ${{ secrets.ORCHESTRAI_PIPELINE_USER }} | |
| ORCHESTRAI_PIPELINE_TOKEN: ${{ secrets.ORCHESTRAI_PIPELINE_TOKEN }} | |
| ORCHESTRAI_PIPELINE_URL: ${{ vars.ORCHESTRAI_PIPELINE_URL }} | |
| ORCHESTRAI_PIPELINE_JOB: ${{ vars.ORCHESTRAI_PIPELINE_JOB }} | |
| ORCHESTRAI_THEROCK_URL: ${{ vars.ORCHESTRAI_THEROCK_URL }} | |
| ORCHESTRAI_ROCM_MULTI_ARCH_INDEX_URL: ${{ vars.ORCHESTRAI_ROCM_MULTI_ARCH_INDEX_URL }} | |
| ORCHESTRAI_LINUX_DRIVER_SOURCE: ${{ vars.ORCHESTRAI_LINUX_DRIVER_SOURCE }} | |
| ORCHESTRAI_LINUX_DRIVER_SOURCES_JSON: ${{ vars.ORCHESTRAI_LINUX_DRIVER_SOURCES_JSON }} | |
| ORCHESTRAI_RYZENAI_NPU_XRT_URL: ${{ secrets.ORCHESTRAI_RYZENAI_NPU_XRT_URL }} | |
| ORCHESTRAI_WINDOWS_DRIVER_SOURCE: ${{ secrets.ORCHESTRAI_WINDOWS_DRIVER_SOURCE }} | |
| # Test the PR's own code, not the configured repo@main. | |
| ORCHESTRAI_PLAYBOOK_REPO: ${{ steps.pr.outputs.head_repo }} | |
| ORCHESTRAI_PLAYBOOK_SHA: ${{ steps.pr.outputs.head_sha }} | |
| run: python3 .github/scripts/orchestrai_trigger.py | |
| test-playbooks: | |
| needs: command | |
| if: needs.command.outputs.has_entries == 'true' | |
| runs-on: ${{ vars.ORCHESTRAI_WAIT_RUNNER || 'ubuntu-latest' }} | |
| continue-on-error: ${{ !matrix.required }} | |
| strategy: | |
| fail-fast: false | |
| max-parallel: 16 | |
| matrix: | |
| include: ${{ fromJson(needs.command.outputs.matrix) }} | |
| steps: | |
| - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 | |
| with: | |
| ref: ${{ needs.command.outputs.head_sha }} | |
| - name: Wait for OrchestrAI pipeline build | |
| id: wait | |
| env: | |
| BUILD_URLS_JSON: ${{ needs.command.outputs.build_urls }} | |
| BATCH_ID: ${{ matrix.batch_id }} | |
| ORCHESTRAI_PIPELINE_USER: ${{ secrets.ORCHESTRAI_PIPELINE_USER }} | |
| ORCHESTRAI_PIPELINE_TOKEN: ${{ secrets.ORCHESTRAI_PIPELINE_TOKEN }} | |
| run: | | |
| BUILD_URL=$(python3 -c "import json,os;print(json.loads(os.environ.get('BUILD_URLS_JSON','{}') or '{}').get(os.environ['BATCH_ID'],''))") | |
| if [ -z "$BUILD_URL" ]; then | |
| echo "::error::No OrchestrAI pipeline build URL for batch ${BATCH_ID}"; exit 1 | |
| fi | |
| echo "build_url=${BUILD_URL}" >> "$GITHUB_OUTPUT" | |
| echo "Waiting for OrchestrAI pipeline build ..." | |
| # SECURITY: pass credentials via a 0600 netrc file, never as `curl -u | |
| # user:token` — argv is world-readable (/proc/<pid>/cmdline, ps) for the | |
| # life of each curl, which would leak the pipeline token to any other | |
| # process on a shared/persistent self-hosted runner. mktemp creates the | |
| # file 0600; printf is a shell builtin, so the token never hits argv. | |
| NETRC="$(mktemp)" | |
| trap 'rm -f "$NETRC"' EXIT | |
| NETRC_HOST=$(printf '%s' "$BUILD_URL" | sed -E 's#^[a-zA-Z][a-zA-Z0-9+.-]*://##; s#[:/].*$##') | |
| printf 'machine %s login %s password %s\n' \ | |
| "$NETRC_HOST" "$ORCHESTRAI_PIPELINE_USER" "$ORCHESTRAI_PIPELINE_TOKEN" > "$NETRC" | |
| MAX=1080; FAIL_MAX=20; i=0; fails=0 | |
| while [ "$i" -lt "$MAX" ]; do | |
| if data=$(curl -sf --connect-timeout 10 --max-time 30 --netrc-file "$NETRC" "${BUILD_URL}api/json?tree=result,building" 2>/dev/null); then | |
| fails=0 | |
| building=$(echo "$data" | python3 -c "import sys,json;print(json.load(sys.stdin).get('building',True))" 2>/dev/null || echo "True") | |
| result=$(echo "$data" | python3 -c "import sys,json;print(json.load(sys.stdin).get('result') or '')" 2>/dev/null || echo "") | |
| if [ "$building" = "False" ] && [ -n "$result" ]; then | |
| echo "Batch build finished: $result"; break | |
| fi | |
| else | |
| fails=$((fails + 1)) | |
| if [ "$fails" -ge "$FAIL_MAX" ]; then | |
| echo "::error::OrchestrAI pipeline unreachable"; exit 1 | |
| fi | |
| fi | |
| i=$((i + 1)); sleep 15 | |
| done | |
| if [ "$i" -ge "$MAX" ]; then echo "::error::Timed out waiting for OrchestrAI pipeline build"; exit 1; fi | |
| - name: Resolve per-playbook verdict | |
| if: always() | |
| env: | |
| BUILD_URL: ${{ steps.wait.outputs.build_url }} | |
| PLAYBOOK_ID: ${{ matrix.playbook }} | |
| PLATFORM: ${{ matrix.platform }} | |
| ORCHESTRAI_PIPELINE_USER: ${{ secrets.ORCHESTRAI_PIPELINE_USER }} | |
| ORCHESTRAI_PIPELINE_TOKEN: ${{ secrets.ORCHESTRAI_PIPELINE_TOKEN }} | |
| run: python3 .github/scripts/orchestrai_verdict.py | |
| - name: Upload test artifacts | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: test-results-${{ matrix.playbook }}-${{ matrix.platform }}-${{ matrix.arch }} | |
| path: test-results/ | |
| if-no-files-found: ignore | |
| report: | |
| needs: [command, test-playbooks] | |
| if: always() && needs.command.result == 'success' && needs.command.outputs.head_sha != '' | |
| runs-on: ${{ vars.ORCHESTRAI_CONTROL_RUNNER || 'ubuntu-latest' }} | |
| steps: | |
| - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 | |
| with: | |
| ref: ${{ needs.command.outputs.head_sha }} | |
| - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 | |
| with: | |
| python-version: '3.12' | |
| - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| path: artifacts | |
| continue-on-error: true | |
| - name: Build + upsert sticky comment | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| API: ${{ github.api_url }} | |
| REPO: ${{ github.repository }} | |
| PR: ${{ github.event.issue.number }} | |
| HEAD_SHA: ${{ needs.command.outputs.head_sha }} | |
| HEAD_REF: ${{ needs.command.outputs.head_ref }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: | | |
| python3 .github/scripts/orchestrai_report.py \ | |
| --artifacts artifacts --run-url "$RUN_URL" --sha "$HEAD_SHA" --ref "$HEAD_REF" > /tmp/body.md | |
| python3 -c "import json;print(json.dumps({'body':open('/tmp/body.md').read()}))" > /tmp/payload.json | |
| MARKER='<!-- orchestrai-orc-report -->' | |
| existing=$(curl -s -H "Authorization: Bearer $GH_TOKEN" "$API/repos/$REPO/issues/$PR/comments?per_page=100" \ | |
| | python3 -c "import sys,json;c=[x for x in json.load(sys.stdin) if str(x.get('body','')).startswith('$MARKER')];print(c[0]['id'] if c else '')") | |
| if [ -n "$existing" ]; then | |
| curl -s -X PATCH -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ | |
| "$API/repos/$REPO/issues/comments/$existing" -d @/tmp/payload.json >/dev/null | |
| else | |
| curl -s -X POST -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ | |
| "$API/repos/$REPO/issues/$PR/comments" -d @/tmp/payload.json >/dev/null | |
| fi | |
| echo "Posted results to PR #$PR" |