|
1 | | -name: PR Comment Sweep |
| 1 | +name: Sweep |
2 | 2 |
|
3 | 3 | on: |
| 4 | + # PR comment trigger |
4 | 5 | issue_comment: |
5 | 6 | types: [created] |
| 7 | + # Manual trigger |
| 8 | + workflow_dispatch: |
| 9 | + inputs: |
| 10 | + pr-number: |
| 11 | + description: PR number to checkout (refs/pull/<num>/head) |
| 12 | + required: false |
| 13 | + type: string |
| 14 | + generator-args: |
| 15 | + description: Args passed to generate_sweep_configs.py (omit /sweep) |
| 16 | + required: false |
| 17 | + type: string |
| 18 | + # Push-based example/testing |
| 19 | + push: |
| 20 | + branches-ignore: |
| 21 | + - main |
| 22 | + - master |
6 | 23 |
|
7 | 24 | concurrency: |
8 | | - group: "PR-SWEEP-${{ github.event.issue.number }}" |
| 25 | + group: ${{ github.event.issue.number && format('PR-SWEEP-{0}', github.event.issue.number) || format('REF-SWEEP-{0}', github.ref_name) }} |
9 | 26 | cancel-in-progress: true |
10 | 27 |
|
11 | 28 | permissions: |
12 | 29 | contents: read |
13 | 30 |
|
14 | 31 | jobs: |
15 | | - parse: |
16 | | - # Run only for PR comments, from trusted authors, starting with /sweep |
17 | | - if: >- |
18 | | - ${{ github.event.issue.pull_request && |
19 | | - startsWith(github.event.comment.body, '/sweep') && |
20 | | - contains('OWNER,MEMBER,COLLABORATOR', github.event.comment.author_association) }} |
| 32 | + prepare: |
21 | 33 | runs-on: ubuntu-latest |
22 | 34 | outputs: |
23 | | - pr-number: ${{ steps.parse.outputs.pr-number }} |
24 | | - generator-args: ${{ steps.parse.outputs.generator-args }} |
| 35 | + pr-number: ${{ steps.parse.outputs.pr-number || steps.resolve.outputs.pr-number }} |
| 36 | + generator-args: ${{ steps.parse.outputs.generator-args || steps.resolve.outputs.generator-args }} |
25 | 37 | steps: |
26 | | - - name: Derive PR number and parse command |
| 38 | + - name: Parse PR comment (/sweep ...) |
27 | 39 | id: parse |
| 40 | + if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/sweep') && contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) }} |
28 | 41 | shell: bash |
29 | 42 | env: |
30 | 43 | BODY: ${{ github.event.comment.body }} |
31 | 44 | PR_NUMBER: ${{ github.event.issue.number }} |
32 | 45 | run: | |
33 | 46 | set -euo pipefail |
34 | | -
|
35 | | - # Extract first line starting with /sweep |
36 | | - cmd_line=$(printf "%s" "$BODY" | awk '/^\/sweep/{print; exit}') |
| 47 | + # Allow optional leading whitespace before /sweep |
| 48 | + cmd_line=$(printf "%s" "$BODY" | awk '/^[[:space:]]*\/sweep/{print; exit}') |
37 | 49 | if [[ -z "$cmd_line" ]]; then |
38 | 50 | echo "No /sweep command found in comment" >&2 |
39 | 51 | exit 1 |
40 | 52 | fi |
41 | | - cmd_args=${cmd_line#* /sweep} |
42 | | - # Handle case when it's exactly '/sweep' (no args) |
| 53 | + # Trim leading spaces then strip the /sweep prefix |
| 54 | + cmd_line=$(echo "$cmd_line" | sed 's/^[[:space:]]*//') |
43 | 55 | if [[ "$cmd_line" == "/sweep" ]]; then |
44 | 56 | cmd_args="" |
45 | 57 | else |
46 | 58 | cmd_args=${cmd_line#/sweep} |
47 | 59 | fi |
48 | 60 | cmd_args=$(echo "$cmd_args" | xargs || true) |
49 | 61 |
|
50 | | - echo "Command args: $cmd_args" |
51 | | -
|
52 | 62 | echo "generator-args=$cmd_args" >> "$GITHUB_OUTPUT" |
53 | 63 | echo "pr-number=$PR_NUMBER" >> "$GITHUB_OUTPUT" |
54 | 64 |
|
55 | | - execute: |
56 | | - needs: parse |
57 | | - uses: ./.github/workflows/sweep-executor.yml |
| 65 | + - name: Find PR for this branch (if any) |
| 66 | + id: find |
| 67 | + if: ${{ github.event_name != 'issue_comment' }} |
| 68 | + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 |
| 69 | + with: |
| 70 | + script: | |
| 71 | + const owner = context.repo.owner; |
| 72 | + const repo = context.repo.repo; |
| 73 | + const branch = context.ref.replace('refs/heads/', ''); |
| 74 | + const res = await github.rest.pulls.list({ owner, repo, state: 'open', head: `${owner}:${branch}` }); |
| 75 | + const num = res.data[0]?.number ? String(res.data[0].number) : ''; |
| 76 | + core.setOutput('pr-number', num); |
| 77 | +
|
| 78 | + - name: Prepare inputs (push/dispatch) |
| 79 | + id: resolve |
| 80 | + if: ${{ github.event_name != 'issue_comment' }} |
| 81 | + shell: bash |
| 82 | + env: |
| 83 | + DISPATCH_PR: ${{ github.event.inputs.pr-number }} |
| 84 | + DISPATCH_ARGS: ${{ github.event.inputs.generator-args }} |
| 85 | + run: | |
| 86 | + set -euo pipefail |
| 87 | + pr_from_branch='${{ steps.find.outputs.pr-number }}' |
| 88 | + pr_number="${DISPATCH_PR:-}"; if [[ -z "$pr_number" ]]; then pr_number="$pr_from_branch"; fi |
| 89 | + gen_args="${DISPATCH_ARGS:-}" |
| 90 | + if [[ -z "$gen_args" ]]; then |
| 91 | + gen_args='full-sweep --single-node --runner-type h200 --model-prefix dsr1 --seq-lens 1k1k --max-conc 4' |
| 92 | + fi |
| 93 | + echo "Resolved PR: $pr_number"; |
| 94 | + echo "Using generator args: $gen_args"; |
| 95 | + echo "pr-number=$pr_number" >> "$GITHUB_OUTPUT" |
| 96 | + echo "generator-args=$gen_args" >> "$GITHUB_OUTPUT" |
| 97 | +
|
| 98 | + generate: |
| 99 | + needs: prepare |
| 100 | + if: ${{ needs.prepare.outputs.pr-number != '' && needs.prepare.outputs.generator-args != '' }} |
| 101 | + runs-on: ubuntu-latest |
| 102 | + outputs: |
| 103 | + search-space-config: ${{ steps.generate.outputs.search-space-config }} |
| 104 | + is-multinode: ${{ steps.detect.outputs.is-multinode }} |
| 105 | + steps: |
| 106 | + - name: Checkout PR head |
| 107 | + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 |
| 108 | + with: |
| 109 | + ref: refs/pull/${{ needs.prepare.outputs.pr-number }}/head |
| 110 | + |
| 111 | + - name: Detect node type from args |
| 112 | + id: detect |
| 113 | + run: | |
| 114 | + set -euo pipefail |
| 115 | + ARGS='${{ needs.prepare.outputs.generator-args }}' |
| 116 | + if [[ "$ARGS" == *"--multi-node"* ]]; then |
| 117 | + echo "is-multinode=true" >> "$GITHUB_OUTPUT" |
| 118 | + else |
| 119 | + echo "is-multinode=false" >> "$GITHUB_OUTPUT" |
| 120 | + fi |
| 121 | +
|
| 122 | + - name: Generate sweep configs |
| 123 | + id: generate |
| 124 | + shell: python |
| 125 | + env: |
| 126 | + GITHUB_WORKSPACE: ${{ github.workspace }} |
| 127 | + run: | |
| 128 | + import json, os, shlex, subprocess, sys |
| 129 | +
|
| 130 | + cmd_args = r'''${{ needs.prepare.outputs.generator-args }}''' |
| 131 | + script = os.path.join(os.environ['GITHUB_WORKSPACE'], 'utils', 'matrix_logic', 'generate_sweep_configs.py') |
| 132 | + cfg_amd = os.path.join(os.environ['GITHUB_WORKSPACE'], '.github', 'configs', 'amd-master.yaml') |
| 133 | + cfg_nv = os.path.join(os.environ['GITHUB_WORKSPACE'], '.github', 'configs', 'nvidia-master.yaml') |
| 134 | + runners = os.path.join(os.environ['GITHUB_WORKSPACE'], '.github', 'configs', 'runners.yaml') |
| 135 | +
|
| 136 | + subprocess.run([sys.executable, '-m', 'pip', 'install', 'pydantic'], check=True) |
| 137 | +
|
| 138 | + argv = [sys.executable, script] |
| 139 | + if cmd_args.strip(): |
| 140 | + argv += shlex.split(cmd_args) |
| 141 | + argv += ['--config-files', cfg_amd, cfg_nv, '--runner-config', runners] |
| 142 | +
|
| 143 | + print('Invoking:', ' '.join(shlex.quote(a) for a in argv)) |
| 144 | + res = subprocess.run(argv, capture_output=True, text=True) |
| 145 | + if res.returncode != 0: |
| 146 | + print('Generator failed. stdout:\n', res.stdout) |
| 147 | + print('stderr:\n', res.stderr, file=sys.stderr) |
| 148 | + raise SystemExit(res.returncode) |
| 149 | +
|
| 150 | + try: |
| 151 | + data = json.loads(res.stdout) |
| 152 | + except Exception as e: |
| 153 | + print('Failed to parse generator output as JSON:', e, file=sys.stderr) |
| 154 | + print('Raw output:\n', res.stdout) |
| 155 | + raise |
| 156 | +
|
| 157 | + print(f"Generated {len(data)} configs") |
| 158 | + with open(os.environ['GITHUB_OUTPUT'], 'a') as f: |
| 159 | + f.write('search-space-config=' + json.dumps(data) + '\n') |
| 160 | +
|
| 161 | + run-single-node: |
| 162 | + needs: generate |
| 163 | + if: ${{ needs.generate.result == 'success' && needs.generate.outputs.search-space-config != '[]' && needs.generate.outputs.is-multinode == 'false' }} |
| 164 | + uses: ./.github/workflows/benchmark-tmpl.yml |
| 165 | + name: Sweep (Single-Node) |
| 166 | + strategy: |
| 167 | + fail-fast: false |
| 168 | + matrix: |
| 169 | + config: ${{ fromJson(needs.generate.outputs.search-space-config) }} |
| 170 | + secrets: inherit |
| 171 | + with: |
| 172 | + exp-name: ${{ matrix.config.exp-name }} |
| 173 | + isl: ${{ matrix.config.isl }} |
| 174 | + osl: ${{ matrix.config.osl }} |
| 175 | + max-model-len: ${{ matrix.config.max-model-len }} |
| 176 | + runner: ${{ matrix.config.runner }} |
| 177 | + image: ${{ matrix.config.image }} |
| 178 | + model: ${{ matrix.config.model }} |
| 179 | + model-prefix: ${{ matrix.config.model-prefix }} |
| 180 | + framework: ${{ matrix.config.framework }} |
| 181 | + precision: ${{ matrix.config.precision }} |
| 182 | + tp: ${{ matrix.config.tp }} |
| 183 | + ep: ${{ matrix.config.ep }} |
| 184 | + dp-attn: ${{ matrix.config.dp-attn }} |
| 185 | + conc: ${{ matrix.config.conc }} |
| 186 | + spec-decoding: ${{ matrix.config.spec-decoding }} |
| 187 | + disagg: ${{ matrix.config.disagg }} |
| 188 | + |
| 189 | + run-multi-node: |
| 190 | + needs: generate |
| 191 | + if: ${{ needs.generate.result == 'success' && needs.generate.outputs.search-space-config != '[]' && needs.generate.outputs.is-multinode == 'true' }} |
| 192 | + uses: ./.github/workflows/benchmark-multinode-tmpl.yml |
| 193 | + name: Sweep (Multi-Node) |
| 194 | + strategy: |
| 195 | + fail-fast: false |
| 196 | + matrix: |
| 197 | + config: ${{ fromJson(needs.generate.outputs.search-space-config) }} |
| 198 | + secrets: inherit |
| 199 | + with: |
| 200 | + exp-name: ${{ matrix.config.exp-name }} |
| 201 | + isl: ${{ matrix.config.isl }} |
| 202 | + osl: ${{ matrix.config.osl }} |
| 203 | + max-model-len: ${{ matrix.config.max-model-len }} |
| 204 | + runner: ${{ matrix.config.runner }} |
| 205 | + image: ${{ matrix.config.image }} |
| 206 | + model: ${{ matrix.config.model }} |
| 207 | + model-prefix: ${{ matrix.config.model-prefix }} |
| 208 | + framework: ${{ matrix.config.framework }} |
| 209 | + precision: ${{ matrix.config.precision }} |
| 210 | + conc-list: ${{ toJson(matrix.config.conc) }} |
| 211 | + spec-decoding: ${{ matrix.config.spec-decoding }} |
| 212 | + disagg: ${{ matrix.config.disagg }} |
| 213 | + |
| 214 | + prefill-num-worker: ${{ matrix.config.prefill.num-worker }} |
| 215 | + prefill-tp: ${{ matrix.config.prefill.tp }} |
| 216 | + prefill-ep: ${{ matrix.config.prefill.ep }} |
| 217 | + prefill-dp-attn: ${{ matrix.config.prefill.dp-attn }} |
| 218 | + prefill-additional-settings: ${{ toJson(matrix.config.prefill.additional-settings) }} |
| 219 | + |
| 220 | + decode-num-worker: ${{ matrix.config.decode.num-worker }} |
| 221 | + decode-tp: ${{ matrix.config.decode.tp }} |
| 222 | + decode-ep: ${{ matrix.config.decode.ep }} |
| 223 | + decode-dp-attn: ${{ matrix.config.decode.dp-attn }} |
| 224 | + decode-additional-settings: ${{ toJson(matrix.config.decode.additional-settings) }} |
| 225 | + |
| 226 | + collect-results: |
| 227 | + needs: [run-single-node, run-multi-node] |
| 228 | + if: ${{ always() && (needs.run-single-node.result == 'success' || needs.run-multi-node.result == 'success') }} |
| 229 | + uses: ./.github/workflows/collect-results.yml |
| 230 | + name: Collect Results |
58 | 231 | secrets: inherit |
59 | 232 | with: |
60 | | - pr-number: ${{ needs.parse.outputs.pr-number }} |
61 | | - generator-args: ${{ needs.parse.outputs.generator-args }} |
| 233 | + exp-name: '' |
62 | 234 |
|
63 | 235 | note-ignored: |
64 | 236 | # Inform when comment doesn't meet criteria (non-PR or not authorized) |
65 | | - if: ${{ !github.event.issue.pull_request || |
66 | | - !startsWith(github.event.comment.body, '/sweep') || |
67 | | - !contains('OWNER,MEMBER,COLLABORATOR', github.event.comment.author_association) }} |
| 237 | + if: ${{ github.event_name == 'issue_comment' && (!github.event.issue.pull_request || !startsWith(github.event.comment.body, '/sweep') || !contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) }} |
68 | 238 | runs-on: ubuntu-latest |
69 | 239 | steps: |
70 | 240 | - run: | |
|
0 commit comments