206 lines
6.9 KiB
Bash
206 lines
6.9 KiB
Bash
|
|
#!/bin/bash
|
|||
|
|
# SPDX-License-Identifier: Apache-2.0
|
|||
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|||
|
|
#
|
|||
|
|
# Check if Ray LLM can generate lock files that are compatible with this
|
|||
|
|
# version of vllm. Downloads Ray's requirement files and runs a full
|
|||
|
|
# dependency resolution with the installed vllm's constraints to see if
|
|||
|
|
# a valid lock file can be produced.
|
|||
|
|
#
|
|||
|
|
# See: https://github.com/vllm-project/vllm/issues/33599
|
|||
|
|
|
|||
|
|
set -eo pipefail
|
|||
|
|
|
|||
|
|
RAY_BASE_URL="https://raw.githubusercontent.com/ray-project/ray/master/python"
|
|||
|
|
|
|||
|
|
WORK_DIR=$(mktemp -d)
|
|||
|
|
trap 'rm -rf "$WORK_DIR"' EXIT
|
|||
|
|
|
|||
|
|
# Fetch all Ray requirement files used in the LLM depset pipeline
|
|||
|
|
echo ">>> Fetching Ray requirement files"
|
|||
|
|
RAY_FILES=(
|
|||
|
|
"requirements.txt"
|
|||
|
|
"requirements/cloud-requirements.txt"
|
|||
|
|
"requirements/base-test-requirements.txt"
|
|||
|
|
"requirements/llm/llm-requirements.txt"
|
|||
|
|
"requirements/llm/llm-test-requirements.txt"
|
|||
|
|
)
|
|||
|
|
for FILE in "${RAY_FILES[@]}"; do
|
|||
|
|
LOCAL_PATH="${WORK_DIR}/$(basename "$FILE")"
|
|||
|
|
echo " ${FILE}"
|
|||
|
|
curl -fsSL -o "$LOCAL_PATH" "${RAY_BASE_URL}/${FILE}"
|
|||
|
|
done
|
|||
|
|
|
|||
|
|
# Extract installed vllm deps
|
|||
|
|
echo ">>> Extracting installed vllm dependency constraints"
|
|||
|
|
python3 - "${WORK_DIR}/vllm-constraints.txt" <<'PYEOF'
|
|||
|
|
"""Write out the installed vllm's dependencies as pip constraint lines.
|
|||
|
|
|
|||
|
|
Ray uses vllm[audio], so audio-extra deps are included with their extra
|
|||
|
|
markers stripped. The resolver cannot evaluate extra markers for a
|
|||
|
|
package that is not itself being resolved from an index, so we activate
|
|||
|
|
them manually here.
|
|||
|
|
"""
|
|||
|
|
import importlib.metadata
|
|||
|
|
import re
|
|||
|
|
import sys
|
|||
|
|
|
|||
|
|
out_path = sys.argv[1]
|
|||
|
|
raw_reqs = importlib.metadata.requires("vllm") or []
|
|||
|
|
|
|||
|
|
# Ray uses vllm[audio] – activate that extra.
|
|||
|
|
ACTIVE_EXTRAS = {"audio"}
|
|||
|
|
EXTRA_RE = re.compile(r"""extra\s*==\s*['"]([^'"]+)['"]""")
|
|||
|
|
|
|||
|
|
lines = []
|
|||
|
|
for r in raw_reqs:
|
|||
|
|
if ";" not in r:
|
|||
|
|
# Unconditional dep — always include.
|
|||
|
|
lines.append(r.strip())
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
req_part, _, marker_part = r.partition(";")
|
|||
|
|
marker_part = marker_part.strip()
|
|||
|
|
|
|||
|
|
extra_matches = EXTRA_RE.findall(marker_part)
|
|||
|
|
if not extra_matches:
|
|||
|
|
# Non-extra marker (python_version, etc.) — keep as-is.
|
|||
|
|
lines.append(r.strip())
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
if not ACTIVE_EXTRAS.intersection(extra_matches):
|
|||
|
|
continue # Skip inactive extras (tensorizer, bench, …).
|
|||
|
|
|
|||
|
|
# Strip the extra== conditions but keep any remaining markers
|
|||
|
|
# (e.g. python_version).
|
|||
|
|
cleaned = EXTRA_RE.sub("", marker_part)
|
|||
|
|
cleaned = re.sub(r"\band\b\s*\band\b", "and", cleaned)
|
|||
|
|
cleaned = re.sub(r"^\s*and\s+|\s+and\s*$", "", cleaned).strip()
|
|||
|
|
|
|||
|
|
if cleaned:
|
|||
|
|
lines.append(f"{req_part.strip()} ; {cleaned}")
|
|||
|
|
else:
|
|||
|
|
lines.append(req_part.strip())
|
|||
|
|
|
|||
|
|
with open(out_path, "w") as f:
|
|||
|
|
for line in lines:
|
|||
|
|
f.write(line + "\n")
|
|||
|
|
|
|||
|
|
print(f"Wrote {len(lines)} constraints to {out_path}")
|
|||
|
|
PYEOF
|
|||
|
|
|
|||
|
|
echo ">>> Installed vllm deps (first 20 lines):"
|
|||
|
|
head -20 "${WORK_DIR}/vllm-constraints.txt"
|
|||
|
|
|
|||
|
|
# Remove Ray's vllm pin — the installed vllm's transitive deps
|
|||
|
|
# (written above) replace it in the resolution. vllm itself cannot
|
|||
|
|
# be resolved from PyPI for in-development versions, so we test
|
|||
|
|
# whether Ray's requirements can coexist with vllm's dependency
|
|||
|
|
# constraints instead.
|
|||
|
|
sed -i '/^vllm/d' "${WORK_DIR}/llm-requirements.txt"
|
|||
|
|
|
|||
|
|
# Install uv if needed
|
|||
|
|
if ! command -v uv &>/dev/null; then
|
|||
|
|
echo ">>> Installing uv"
|
|||
|
|
pip install uv -q
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# Resolve: given vllm's constraints, can Ray compile a lock file?
|
|||
|
|
#
|
|||
|
|
# vllm's dependency constraints are the fixed side — Ray is flexible and
|
|||
|
|
# can regenerate its lock files. We pass vllm's constraints via -c so
|
|||
|
|
# the resolver treats them as non-negotiable bounds, then check whether
|
|||
|
|
# Ray's own requirements can still be satisfied within those bounds.
|
|||
|
|
echo ""
|
|||
|
|
echo "============================================================"
|
|||
|
|
echo ">>> Resolving: Can Ray generate compatible lock files?"
|
|||
|
|
echo "============================================================"
|
|||
|
|
|
|||
|
|
set +e
|
|||
|
|
uv pip compile \
|
|||
|
|
"${WORK_DIR}/requirements.txt" \
|
|||
|
|
"${WORK_DIR}/cloud-requirements.txt" \
|
|||
|
|
"${WORK_DIR}/base-test-requirements.txt" \
|
|||
|
|
"${WORK_DIR}/llm-requirements.txt" \
|
|||
|
|
"${WORK_DIR}/llm-test-requirements.txt" \
|
|||
|
|
-c "${WORK_DIR}/vllm-constraints.txt" \
|
|||
|
|
--python-version 3.12 \
|
|||
|
|
--python-platform x86_64-manylinux_2_31 \
|
|||
|
|
--extra-index-url https://download.pytorch.org/whl/cu129 \
|
|||
|
|
--index-strategy unsafe-best-match \
|
|||
|
|
--unsafe-package setuptools \
|
|||
|
|
--unsafe-package ray \
|
|||
|
|
--no-header \
|
|||
|
|
-o "${WORK_DIR}/resolved.txt" \
|
|||
|
|
2>&1
|
|||
|
|
EXIT_CODE=$?
|
|||
|
|
set -e
|
|||
|
|
|
|||
|
|
echo ""
|
|||
|
|
echo "=========================================="
|
|||
|
|
if [ $EXIT_CODE -eq 0 ]; then
|
|||
|
|
echo "SUCCESS: Ray can generate lock files compatible with this vllm."
|
|||
|
|
echo ""
|
|||
|
|
echo "Key resolved versions:"
|
|||
|
|
grep -E '^(protobuf|torch|numpy|transformers)==' \
|
|||
|
|
"${WORK_DIR}/resolved.txt" | sort || true
|
|||
|
|
echo "=========================================="
|
|||
|
|
exit 0
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
echo "FAILURE: Ray cannot generate lock files compatible with this vllm."
|
|||
|
|
echo "This means a fundamental dependency conflict exists that Ray"
|
|||
|
|
echo "cannot resolve by regenerating its lock files."
|
|||
|
|
echo "See: https://github.com/vllm-project/vllm/issues/33599"
|
|||
|
|
echo "=========================================="
|
|||
|
|
|
|||
|
|
# Buildkite annotation
|
|||
|
|
if [ -f /usr/bin/buildkite-agent ]; then
|
|||
|
|
buildkite-agent annotate --style 'warning' --context 'ray-compat' << EOF
|
|||
|
|
### :warning: Ray Dependency Compatibility Warning
|
|||
|
|
This PR introduces dependencies that **cannot** be resolved with Ray's requirements.
|
|||
|
|
Ray would not be able to regenerate its lock files to accommodate this vllm version.
|
|||
|
|
|
|||
|
|
Please check the **Ray Dependency Compatibility Check** step logs for details.
|
|||
|
|
See [issue #33599](https://github.com/vllm-project/vllm/issues/33599) for context.
|
|||
|
|
EOF
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# Notify Slack if webhook is configured.
|
|||
|
|
if [ -n "$RAY_COMPAT_SLACK_WEBHOOK_URL" ]; then
|
|||
|
|
echo ">>> Sending Slack notification"
|
|||
|
|
# Single quotes are intentional: the f-string expressions are Python, not shell.
|
|||
|
|
# shellcheck disable=SC2016
|
|||
|
|
PAYLOAD=$(python3 -c '
|
|||
|
|
import json, os, sys
|
|||
|
|
pr = os.getenv("BUILDKITE_PULL_REQUEST", "N/A")
|
|||
|
|
branch = os.getenv("BUILDKITE_BRANCH", "unknown")
|
|||
|
|
url = os.getenv("BUILDKITE_BUILD_URL", "#")
|
|||
|
|
data = {
|
|||
|
|
"text": ":warning: Ray Dependency Compatibility Check Failed",
|
|||
|
|
"blocks": [{
|
|||
|
|
"type": "section",
|
|||
|
|
"text": {
|
|||
|
|
"type": "mrkdwn",
|
|||
|
|
"text": (
|
|||
|
|
"*:warning: Ray Dependency Compatibility Check Failed*\n"
|
|||
|
|
f"PR #{pr} on branch `{branch}` introduces dependencies "
|
|||
|
|
f"that cannot be resolved with Ray'\''s requirements.\n"
|
|||
|
|
f"<{url}|View Build>"
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
}],
|
|||
|
|
}
|
|||
|
|
print(json.dumps(data))
|
|||
|
|
')
|
|||
|
|
|
|||
|
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$RAY_COMPAT_SLACK_WEBHOOK_URL" \
|
|||
|
|
-H 'Content-type: application/json' \
|
|||
|
|
-d "$PAYLOAD")
|
|||
|
|
echo " Slack webhook response: $HTTP_CODE"
|
|||
|
|
else
|
|||
|
|
echo ">>> Skipping Slack notification (RAY_COMPAT_SLACK_WEBHOOK_URL not set)"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
exit 1
|