-
Notifications
You must be signed in to change notification settings - Fork 469
392 lines (352 loc) · 14.1 KB
/
Copy pathvalidate_vllm.yml
File metadata and controls
392 lines (352 loc) · 14.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
name: Validate New vLLM Release
on:
workflow_dispatch:
inputs:
lite:
description: 'Run in lite mode (smallest model only)'
type: boolean
default: false
pull_request:
types: [opened, synchronize, reopened, labeled]
merge_group:
schedule:
# Sunday at 6:00 PM UTC
- cron: "0 18 * * 0"
permissions:
contents: write
pull-requests: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name == 'pull_request' && 'pr' || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
LEMONADE_CI_MODE: "True"
PYTHONIOENCODING: utf-8
HF_TOKEN: ${{ secrets.HUGGINGFACE_ACCESS_TOKEN }}
LITE_MODE: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' || (github.event_name == 'workflow_dispatch' && inputs.lite == true) }}
jobs:
# ========================================================================
# Discover the latest vllm-rocm release
# ========================================================================
get-latest-releases:
name: Get latest releases
# Only the auto-update runs resolve upstream releases. Everywhere else the
# build validates the pins committed in backend_versions.json, so a bad
# upstream release can't fail a merge or hide a broken pin.
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
outputs:
vllm_rocm_release: ${{ steps.vllm_rocm.outputs.release }}
steps:
- name: Get latest vllm-rocm release
id: vllm_rocm
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
RELEASE=$(gh api repos/lemonade-sdk/vllm-rocm/releases/latest --jq '.tag_name')
echo "Latest vllm-rocm release: $RELEASE"
echo "release=$RELEASE" >> "$GITHUB_OUTPUT"
# ========================================================================
# Build binaries with updated backend version
# ========================================================================
build:
name: Build (Linux)
needs: get-latest-releases
# Label a PR `ci:upgrades` to opt it back in.
if: (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'ci:upgrades')) && always() && !failure() && !cancelled()
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v5
with:
clean: true
fetch-depth: 0
- name: Update backend_versions.json
if: needs.get-latest-releases.result == 'success'
env:
VLLM_ROCM_RELEASE: ${{ needs.get-latest-releases.outputs.vllm_rocm_release }}
run: |
set -e
python3 - << 'PY'
import json, os
path = "src/cpp/resources/backend_versions.json"
with open(path) as f:
data = json.load(f)
release = os.environ["VLLM_ROCM_RELEASE"]
old = data["vllm"].get("rocm", "")
data["vllm"]["rocm"] = release
with open(path, "w") as f:
json.dump(data, f, indent=2)
f.write("\n")
print(f"vllm.rocm: {old} -> {release}")
PY
- name: Build Lemonade server
run: |
set -e
./setup.sh
# Skip the web app; CI only exercises the backend.
cmake -DBUILD_WEB_APP=OFF --preset default
cmake --build --preset default
test -f build/lemond || { echo "lemond not found!"; exit 1; }
echo "Build successful!"
- name: Upload build artifacts
uses: actions/upload-artifact@v7
with:
name: vllm-build
path: |
build/lemond
build/lemonade
build/resources/
retention-days: 7
# ========================================================================
# Validate backend on self-hosted runner
# ========================================================================
validate:
name: Validate vllm (rocm)
needs: [get-latest-releases, build]
if: always() && needs.build.result == 'success'
runs-on: [self-hosted, stx-halo, Linux, lemon-prod]
steps:
- uses: actions/checkout@v5
with:
clean: true
- name: Download build artifacts
uses: actions/download-artifact@v7
with:
name: vllm-build
path: build
- name: Verify binaries
run: |
set -e
chmod +x build/lemond build/lemonade
./build/lemond --version
echo "Binaries verified!"
- name: Setup Python and virtual environment
uses: ./.github/actions/setup-venv
with:
venv-name: '.venv'
python-version: '3.10'
requirements-file: 'test/requirements.txt'
- name: Run validation with lemond
env:
HOME: ${{ github.workspace }}/.home
run: |
set -e
mkdir -p "$HOME"
echo "HOME=$HOME" >> "$GITHUB_ENV"
CACHE_DIR="$(pwd)/ci-cache-vllm"
LOGS_DIR="$(pwd)/server-logs-rocm"
mkdir -p "$CACHE_DIR" "$LOGS_DIR"
mkdir -p "$RUNNER_TEMP/xdg-runtime"
chmod 700 "$RUNNER_TEMP/xdg-runtime"
echo "XDG_RUNTIME_DIR=$RUNNER_TEMP/xdg-runtime" >> "$GITHUB_ENV"
export XDG_RUNTIME_DIR="$RUNNER_TEMP/xdg-runtime"
# This validate job runs on a shared self-hosted runner, so the default
# port 13305 may already be held by another job. lemond now refuses to
# bind an in-use port, so start on a job-unique free port and pass it to
# validate_vllm.py and the shutdown call.
pick_free_port() {
if command -v python3 >/dev/null 2>&1; then
python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1",0)); print(s.getsockname()[1]); s.close()' && return 0
fi
for _ in $(seq 1 50); do
local p=$(( (RANDOM % 20000) + 20000 ))
if ! ss -ltn 2>/dev/null | grep -q ":$p "; then echo "$p"; return 0; fi
done
return 1
}
LEMOND_PID=""
started=0
for start_attempt in $(seq 1 5); do
PORT=$(pick_free_port) || { echo "ERROR: could not find a free port"; exit 1; }
echo "Attempt $start_attempt: starting lemond on 127.0.0.1:$PORT..."
./build/lemond "$CACHE_DIR" --port "$PORT" --host 127.0.0.1 \
> "$LOGS_DIR/lemond.stdout.log" 2> "$LOGS_DIR/lemond.stderr.log" &
LEMOND_PID=$!
for i in $(seq 1 30); do
if curl -sf "http://127.0.0.1:$PORT/api/v1/health" > /dev/null 2>&1; then
echo "lemond is healthy on port $PORT (PID $LEMOND_PID)"
started=1
break
fi
if ! kill -0 "$LEMOND_PID" 2>/dev/null; then
echo "lemond (PID $LEMOND_PID) exited prematurely on port $PORT"
cat "$LOGS_DIR/lemond.stderr.log" 2>/dev/null || true
break
fi
sleep 2
done
[ "$started" = "1" ] && break
echo "Start attempt $start_attempt failed; retrying on a new port..."
kill -9 "$LEMOND_PID" 2>/dev/null || true
sleep 1
done
if [ "$started" != "1" ]; then
echo "ERROR: lemond failed to start after multiple attempts"
exit 1
fi
EXIT_CODE=0
VALIDATION_ARGS=(
test/validate_vllm.py
--backend rocm
--port "$PORT"
--output vllm_validation_rocm.json
--logs-dir "$LOGS_DIR"
)
if [ "${LITE_MODE}" = "true" ]; then
VALIDATION_ARGS+=(--lite)
fi
.venv/bin/python "${VALIDATION_ARGS[@]}" || EXIT_CODE=$?
# Shut the server down cleanly so logs flush.
curl -sf -X POST "http://127.0.0.1:$PORT/internal/shutdown" || true
sleep 2
kill $LEMOND_PID 2>/dev/null || true
exit $EXIT_CODE
- name: Upload results
if: always()
uses: actions/upload-artifact@v7
with:
name: validation-results-rocm
path: vllm_validation_rocm.json
retention-days: 30
- name: Upload server logs
if: always()
uses: actions/upload-artifact@v7
with:
name: server-logs-rocm
path: server-logs-rocm/
retention-days: 30
if-no-files-found: ignore
- name: Cleanup
if: always()
run: |
pkill -9 -f lemond 2>/dev/null || true
pkill -9 -f "vllm" 2>/dev/null || true
pkill -9 -f "resource_tracker" 2>/dev/null || true
# ========================================================================
# Create PR if validation passed (schedule or workflow_dispatch only)
# ========================================================================
create-pr:
name: Create update PR
needs: [get-latest-releases, validate]
runs-on: ubuntu-latest
if: >-
(github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') &&
needs.validate.result == 'success'
steps:
- uses: actions/checkout@v5
- name: Download validation results
uses: actions/download-artifact@v7
with:
pattern: validation-results-*
merge-multiple: true
- name: Update backend_versions.json
env:
VLLM_ROCM_RELEASE: ${{ needs.get-latest-releases.outputs.vllm_rocm_release }}
run: |
python3 - << 'PY'
import json, os
path = 'src/cpp/resources/backend_versions.json'
with open(path) as f:
data = json.load(f)
rocm = os.environ['VLLM_ROCM_RELEASE']
data['vllm']['rocm'] = rocm
with open(path, 'w') as f:
json.dump(data, f, indent=2)
f.write('\n')
print(f"Updated: vllm.rocm = {rocm}")
PY
- name: Generate PR body
env:
VLLM_ROCM_RELEASE: ${{ needs.get-latest-releases.outputs.vllm_rocm_release }}
run: |
python3 << 'PYEOF'
import json, os
lines = []
rocm = os.environ.get("VLLM_ROCM_RELEASE", "unknown")
lines.append("## Auto-update vLLM backend")
lines.append("")
lines.append(f"- **vllm-rocm**: `{rocm}`")
lines.append("")
lines.append("## Validation Results")
lines.append("")
lines.append("| Model | Result | Response | input_tokens | output_tokens | time_to_first_token | tokens_per_second |")
lines.append("|-------|--------|----------|--------------|---------------|---------------------|-------------------|")
results_file = "vllm_validation_rocm.json"
if not os.path.isfile(results_file):
lines.append("| _rocm: no results_ | | | | | | |")
else:
with open(results_file) as f:
results = json.load(f)
for r in results:
status = "PASS" if r["pass"] else "FAIL"
resp = str(r.get("response", ""))[:80].replace("|", "\\|").replace("\n", " ")
ttft = r.get("time_to_first_token", "N/A")
if isinstance(ttft, float):
ttft = f"{ttft:.3f}s"
tps = r.get("tokens_per_second", "N/A")
if isinstance(tps, float):
tps = f"{tps:.1f}"
lines.append(
f"| {r['model']} (rocm) | {status} | {resp} | "
f"{r.get('input_tokens', 'N/A')} | {r.get('output_tokens', 'N/A')} | "
f"{ttft} | {tps} |"
)
lines.append("")
lines.append("---")
lines.append("*Auto-generated by validate_vllm workflow*")
with open("pr_body.md", "w") as f:
f.write("\n".join(lines))
PYEOF
- name: Create Pull Request
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VLLM_ROCM_RELEASE: ${{ needs.get-latest-releases.outputs.vllm_rocm_release }}
run: |
BRANCH="auto/vllm-update-${VLLM_ROCM_RELEASE}"
EXISTING_PR=$(gh pr list --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null || echo "")
if [ -n "$EXISTING_PR" ]; then
echo "PR #$EXISTING_PR already exists for branch $BRANCH. Skipping."
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
if git diff --quiet src/cpp/resources/backend_versions.json; then
echo "backend_versions.json is unchanged — nothing to update."
exit 0
fi
git checkout -b "$BRANCH"
git add src/cpp/resources/backend_versions.json
git commit -m "Update vllm-rocm to ${VLLM_ROCM_RELEASE}"
git push origin "$BRANCH"
gh pr create \
--title "Update vllm-rocm to ${VLLM_ROCM_RELEASE}" \
--body-file pr_body.md \
--base main \
--head "$BRANCH"
# Gate job that ensures: 1. in the merge queue, all jobs in `needs:` ran
# successfully (otherwise the merge is blocked), and 2. these jobs do not need
# to run on ordinary pull request pushes.
validation-gate:
name: vLLM validation
needs: [build, validate]
if: always()
runs-on: ubuntu-latest
steps:
- name: Check gated jobs
env:
NEEDS: ${{ toJSON(needs) }}
run: |
# $NEEDS: {"job-id": {"result": "success|failure|skipped|cancelled"}, ...}
# Fail if any job broke. In the merge queue, also fail if any never ran.
echo "$NEEDS"
broke=$(jq -r 'to_entries[]|select(.value.result=="failure" or .value.result=="cancelled")|.key' <<<"$NEEDS")
if [ -n "$broke" ]; then
echo "FAILED: $broke"
exit 1
fi
if [ "${{ github.event_name }}" = "merge_group" ]; then
absent=$(jq -r 'to_entries[]|select(.value.result!="success")|.key' <<<"$NEEDS")
if [ -n "$absent" ]; then
echo "DID NOT RUN IN MERGE QUEUE: $absent"
exit 1
fi
fi