@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / d86060122a
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 5, 22:15 UTC

[CI/Build] enable Intel XPU test flow with prebuilt image (#37447)

Signed-off-by: wendyliu235 <wenjun.liu@intel.com>

wenjun liu committed Mar 27, 2026 at 09:16 UTC d86060122aaf68eb2387c8999f1ac94bf469c2a8
4 files changed +397
.buildkite/ci_config_intel.yaml new
+23
@@ -0,0 +1,23 @@
1 +name: vllm_intel_ci
2 +job_dirs:
3 + - ".buildkite/intel_jobs"
4 +run_all_patterns:
5 + - "docker/Dockerfile"
6 + - "CMakeLists.txt"
7 + - "requirements/common.txt"
8 + - "requirements/xpu.txt"
9 + - "requirements/build.txt"
10 + - "requirements/test.txt"
11 + - "setup.py"
12 + - "csrc/"
13 + - "cmake/"
14 +run_all_exclude_patterns:
15 + - "docker/Dockerfile."
16 + - "csrc/cpu/"
17 + - "csrc/rocm/"
18 + - "cmake/hipify.py"
19 + - "cmake/cpu_extension.cmake"
20 +registries: public.ecr.aws/q9t5s3a7
21 +repositories:
22 + main: "vllm-ci-test-repo"
23 + premerge: "vllm-ci-test-repo"
.buildkite/image_build/image_build_xpu.sh new
+34
@@ -0,0 +1,34 @@
1 +#!/bin/bash
2 +set -e
3 +
4 +if [[ $# -lt 3 ]]; then
5 + echo "Usage: $0 <registry> <repo> <commit>"
6 + exit 1
7 +fi
8 +
9 +REGISTRY=$1
10 +REPO=$2
11 +BUILDKITE_COMMIT=$3
12 +
13 +# authenticate with AWS ECR
14 +aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin "$REGISTRY"
15 +aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 936637512419.dkr.ecr.us-east-1.amazonaws.com
16 +
17 +# skip build if image already exists
18 +if ! docker manifest inspect "$REGISTRY"/"$REPO":"$BUILDKITE_COMMIT"-xpu &> /dev/null; then
19 + echo "Image not found, proceeding with build..."
20 +else
21 + echo "Image found"
22 + exit 0
23 +fi
24 +
25 +# build
26 +docker build \
27 + --file docker/Dockerfile.xpu \
28 + --build-arg max_jobs=16 \
29 + --build-arg buildkite_commit="$BUILDKITE_COMMIT" \
30 + --tag "$REGISTRY"/"$REPO":"$BUILDKITE_COMMIT"-xpu \
31 + --progress plain .
32 +
33 +# push
34 +docker push "$REGISTRY"/"$REPO":"$BUILDKITE_COMMIT"-xpu
.buildkite/intel_jobs/test-intel.yaml new
+64
@@ -0,0 +1,64 @@
1 +group: Intel
2 +steps:
3 + - label: ":docker: Build XPU image"
4 + soft_fail: true
5 + depends_on: []
6 + key: image-build-xpu
7 + commands:
8 + - bash -lc '.buildkite/image_build/image_build_xpu.sh "public.ecr.aws/q9t5s3a7" "vllm-ci-test-repo" "$BUILDKITE_COMMIT"'
9 + env:
10 + DOCKER_BUILDKIT: "1"
11 + retry:
12 + automatic:
13 + - exit_status: -1 # Agent was lost
14 + limit: 2
15 + - exit_status: -10 # Agent was lost
16 + limit: 2
17 + - label: "XPU example Test"
18 + depends_on:
19 + - image-build-xpu
20 + timeout_in_minutes: 30
21 + device: intel_gpu
22 + no_plugin: true
23 + env:
24 + REGISTRY: "public.ecr.aws/q9t5s3a7"
25 + REPO: "vllm-ci-test-repo"
26 + source_file_dependencies:
27 + - vllm/
28 + - .buildkite/intel_jobs/test-intel.yaml
29 + commands:
30 + - >-
31 + bash .buildkite/scripts/hardware_ci/run-intel-test.sh
32 + 'pip install tblib==3.1.0 &&
33 + python3 examples/basic/offline_inference/generate.py --model facebook/opt-125m --block-size 64 --enforce-eager &&
34 + python3 examples/basic/offline_inference/generate.py --model facebook/opt-125m --block-size 64 -O3 -cc.cudagraph_mode=NONE &&
35 + python3 examples/basic/offline_inference/generate.py --model facebook/opt-125m --block-size 64 --enforce-eager -tp 2 --distributed-executor-backend mp &&
36 + python3 examples/basic/offline_inference/generate.py --model facebook/opt-125m --block-size 64 --enforce-eager --attention-backend=TRITON_ATTN &&
37 + python3 examples/basic/offline_inference/generate.py --model facebook/opt-125m --block-size 64 --enforce-eager --quantization fp8 &&
38 + python3 examples/basic/offline_inference/generate.py --model superjob/Qwen3-4B-Instruct-2507-GPTQ-Int4 --block-size 64 --enforce-eager --max-model-len 8192 &&
39 + python3 examples/basic/offline_inference/generate.py --model ibm-research/PowerMoE-3b --block-size 64 --enforce-eager -tp 2 &&
40 + python3 examples/basic/offline_inference/generate.py --model ibm-research/PowerMoE-3b --block-size 64 --enforce-eager -tp 2 --enable-expert-parallel'
41 + - label: "XPU V1 test"
42 + depends_on:
43 + - image-build-xpu
44 + timeout_in_minutes: 30
45 + device: intel_gpu
46 + no_plugin: true
47 + env:
48 + REGISTRY: "public.ecr.aws/q9t5s3a7"
49 + REPO: "vllm-ci-test-repo"
50 + source_file_dependencies:
51 + - vllm/
52 + - .buildkite/intel_jobs/test-intel.yaml
53 + commands:
54 + - >-
55 + bash .buildkite/scripts/hardware_ci/run-intel-test.sh
56 + 'cd tests &&
57 + pytest -v -s v1/core --ignore=v1/core/test_reset_prefix_cache_e2e.py --ignore=v1/core/test_scheduler_e2e.py &&
58 + pytest -v -s v1/engine --ignore=v1/engine/test_output_processor.py &&
59 + pytest -v -s v1/sample --ignore=v1/sample/test_logprobs.py --ignore=v1/sample/test_logprobs_e2e.py &&
60 + pytest -v -s v1/worker --ignore=v1/worker/test_gpu_model_runner.py --ignore=v1/worker/test_worker_memory_snapshot.py &&
61 + pytest -v -s v1/structured_output &&
62 + pytest -v -s v1/test_serial_utils.py &&
63 + pytest -v -s v1/spec_decode --ignore=v1/spec_decode/test_max_len.py --ignore=v1/spec_decode/test_tree_attention.py --ignore=v1/spec_decode/test_speculators_eagle3.py --ignore=v1/spec_decode/test_acceptance_length.py &&
64 + pytest -v -s v1/kv_connector/unit --ignore=v1/kv_connector/unit/test_multi_connector.py --ignore=v1/kv_connector/unit/test_nixl_connector.py --ignore=v1/kv_connector/unit/test_example_connector.py --ignore=v1/kv_connector/unit/test_lmcache_integration.py'
.buildkite/scripts/hardware_ci/run-intel-test.sh new
+276
@@ -0,0 +1,276 @@
1 +#!/bin/bash
2 +
3 +# This script runs tests inside the Intel XPU docker container.
4 +# It mirrors the structure of run-amd-test.sh while keeping Intel-specific
5 +# container setup and allowing commands to be sourced from YAML or env.
6 +#
7 +# Command sources (in priority order):
8 +# 1) VLLM_TEST_COMMANDS env var (preferred, preserves quoting)
9 +# 2) Positional args (legacy)
10 +# 3) One or more YAML files with a commands list (test-area style)
11 +###############################################################################
12 +set -o pipefail
13 +
14 +DRY_RUN=${DRY_RUN:-0}
15 +if [[ "${1:-}" == "--dry-run" ]]; then
16 + DRY_RUN=1
17 + shift
18 +fi
19 +
20 +# Export Python path
21 +export PYTHONPATH=".."
22 +
23 +###############################################################################
24 +# Helper Functions
25 +###############################################################################
26 +
27 +cleanup_docker() {
28 + docker_root=$(docker info -f '{{.DockerRootDir}}')
29 + if [ -z "$docker_root" ]; then
30 + echo "Failed to determine Docker root directory." >&2
31 + exit 1
32 + fi
33 + echo "Docker root directory: $docker_root"
34 +
35 + disk_usage=$(df "$docker_root" | tail -1 | awk '{print $5}' | sed 's/%//')
36 + threshold=70
37 + if [ "$disk_usage" -gt "$threshold" ]; then
38 + echo "Disk usage is above $threshold%. Cleaning up Docker images and volumes..."
39 + docker image prune -f
40 + docker volume prune -f && docker system prune --force --filter "until=72h" --all
41 + echo "Docker images and volumes cleanup completed."
42 + else
43 + echo "Disk usage is below $threshold%. No cleanup needed."
44 + fi
45 +}
46 +
47 +re_quote_pytest_markers() {
48 + local input="$1"
49 + local output=""
50 + local collecting=false
51 + local marker_buf=""
52 +
53 + local flat="${input//$'\n'/ }"
54 + local restore_glob
55 + restore_glob="$(shopt -p -o noglob 2>/dev/null || true)"
56 + set -o noglob
57 + local -a words
58 + read -ra words <<< "$flat"
59 + eval "$restore_glob"
60 +
61 + for word in "${words[@]}"; do
62 + if $collecting; then
63 + if [[ "$word" == *"'"* ]]; then
64 + if [[ -n "$marker_buf" ]]; then
65 + output+="${marker_buf} "
66 + marker_buf=""
67 + fi
68 + output+="${word} "
69 + collecting=false
70 + continue
71 + fi
72 +
73 + local is_boundary=false
74 + case "$word" in
75 + "&&"|"||"|";"|"|")
76 + is_boundary=true ;;
77 + --*)
78 + is_boundary=true ;;
79 + -[a-zA-Z])
80 + is_boundary=true ;;
81 + */*)
82 + is_boundary=true ;;
83 + *.py|*.py::*)
84 + is_boundary=true ;;
85 + *=*)
86 + if [[ "$word" =~ ^[A-Z_][A-Z0-9_]*= ]]; then
87 + is_boundary=true
88 + fi
89 + ;;
90 + esac
91 +
92 + if $is_boundary; then
93 + if [[ "$marker_buf" == *" "* || "$marker_buf" == *"("* ]]; then
94 + output+="'${marker_buf}' "
95 + else
96 + output+="${marker_buf} "
97 + fi
98 + collecting=false
99 + marker_buf=""
100 + if [[ "$word" == "-m" || "$word" == "-k" ]]; then
101 + output+="${word} "
102 + collecting=true
103 + else
104 + output+="${word} "
105 + fi
106 + else
107 + if [[ -n "$marker_buf" ]]; then
108 + marker_buf+=" ${word}"
109 + else
110 + marker_buf="${word}"
111 + fi
112 + fi
113 + elif [[ "$word" == "-m" || "$word" == "-k" ]]; then
114 + output+="${word} "
115 + collecting=true
116 + marker_buf=""
117 + else
118 + output+="${word} "
119 + fi
120 + done
121 +
122 + if $collecting && [[ -n "$marker_buf" ]]; then
123 + if [[ "$marker_buf" == *" "* || "$marker_buf" == *"("* ]]; then
124 + output+="'${marker_buf}'"
125 + else
126 + output+="${marker_buf}"
127 + fi
128 + fi
129 +
130 + echo "${output% }"
131 +}
132 +
133 +apply_intel_test_overrides() {
134 + local cmds="$1"
135 + # Placeholder for Intel-specific exclusions/overrides.
136 + echo "$cmds"
137 +}
138 +
139 +is_yaml_file() {
140 + local p="$1"
141 + [[ -f "$p" && "$p" == *.yaml ]]
142 +}
143 +
144 +extract_yaml_commands() {
145 + local yaml_path="$1"
146 + awk '
147 + $1 == "commands:" { in_cmds=1; next }
148 + in_cmds && $0 ~ /^[[:space:]]*-[[:space:]]/ {
149 + sub(/^[[:space:]]*-[[:space:]]/, "");
150 + print;
151 + next
152 + }
153 + in_cmds && $0 ~ /^[^[:space:]]/ { exit }
154 + ' "$yaml_path"
155 +}
156 +
157 +###############################################################################
158 +# Main
159 +###############################################################################
160 +
161 +default_image_name="${REGISTRY}/${REPO}:${BUILDKITE_COMMIT}-xpu"
162 +#default_image_name="public.ecr.aws/q9t5s3a7/vllm-ci-test-repo:${BUILDKITE_COMMIT}-xpu"
163 +image_name="${IMAGE_TAG_XPU:-${default_image_name}}"
164 +container_name="xpu_${BUILDKITE_COMMIT}_$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 10; echo)"
165 +
166 +# ---- Command source selection ----
167 +commands=""
168 +if [[ -n "${VLLM_TEST_COMMANDS:-}" ]]; then
169 + commands="${VLLM_TEST_COMMANDS}"
170 + echo "Commands sourced from VLLM_TEST_COMMANDS (quoting preserved)"
171 +elif [[ $# -gt 0 ]]; then
172 + all_yaml=true
173 + for arg in "$@"; do
174 + if ! is_yaml_file "$arg"; then
175 + all_yaml=false
176 + break
177 + fi
178 + done
179 +
180 + if $all_yaml; then
181 + for yaml in "$@"; do
182 + mapfile -t COMMANDS < <(extract_yaml_commands "$yaml")
183 + if [[ ${#COMMANDS[@]} -eq 0 ]]; then
184 + echo "Error: No commands found in ${yaml}" >&2
185 + exit 1
186 + fi
187 + for cmd in "${COMMANDS[@]}"; do
188 + if [[ -z "$commands" ]]; then
189 + commands="${cmd}"
190 + else
191 + commands+=" && ${cmd}"
192 + fi
193 + done
194 + done
195 + echo "Commands sourced from YAML files: $*"
196 + else
197 + commands="$*"
198 + echo "Commands sourced from positional args (legacy mode)"
199 + fi
200 +else
201 + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
202 + DEFAULT_YAML="${SCRIPT_DIR}/intel-test.yaml"
203 + if [[ ! -f "${DEFAULT_YAML}" ]]; then
204 + echo "Error: YAML file not found: ${DEFAULT_YAML}" >&2
205 + exit 1
206 + fi
207 + mapfile -t COMMANDS < <(extract_yaml_commands "${DEFAULT_YAML}")
208 + if [[ ${#COMMANDS[@]} -eq 0 ]]; then
209 + echo "Error: No commands found in ${DEFAULT_YAML}" >&2
210 + exit 1
211 + fi
212 + for cmd in "${COMMANDS[@]}"; do
213 + if [[ -z "$commands" ]]; then
214 + commands="${cmd}"
215 + else
216 + commands+=" && ${cmd}"
217 + fi
218 + done
219 + echo "Commands sourced from default YAML: ${DEFAULT_YAML}"
220 +fi
221 +
222 +if [[ -z "$commands" ]]; then
223 + echo "Error: No test commands provided." >&2
224 + exit 1
225 +fi
226 +
227 +echo "Raw commands: $commands"
228 +commands=$(re_quote_pytest_markers "$commands")
229 +echo "After re-quoting: $commands"
230 +commands=$(apply_intel_test_overrides "$commands")
231 +echo "Final commands: $commands"
232 +
233 +# Dry-run mode prints final commands and exits before Docker.
234 +if [[ "$DRY_RUN" == "1" ]]; then
235 + echo "DRY_RUN=1 set, skipping Docker execution."
236 + exit 0
237 +fi
238 +
239 +# --- Docker housekeeping ---
240 +cleanup_docker
241 +
242 +# --- Build or pull test image ---
243 +if [[ -n "${IMAGE_TAG_XPU:-}" ]]; then
244 + echo "Using prebuilt XPU image: ${IMAGE_TAG_XPU}"
245 + docker pull "${IMAGE_TAG_XPU}"
246 +else
247 + echo "Using prebuilt XPU image: ${image_name}"
248 + docker pull "${image_name}"
249 +fi
250 +
251 +remove_docker_container() {
252 + docker rm -f "${container_name}" || true
253 + docker image rm -f "${image_name}" || true
254 + docker system prune -f || true
255 +}
256 +trap remove_docker_container EXIT
257 +
258 +# --- Single-node job ---
259 +
260 +if [[ -z "${ZE_AFFINITY_MASK:-}" ]]; then
261 + echo "Warning: ZE_AFFINITY_MASK is not set. Proceeding without device affinity." >&2
262 +fi
263 +
264 +docker run \
265 + --device /dev/dri:/dev/dri \
266 + --net=host \
267 + --ipc=host \
268 + --privileged \
269 + -v /dev/dri/by-path:/dev/dri/by-path \
270 + --entrypoint="" \
271 + -e "HF_TOKEN=${HF_TOKEN:-}" \
272 + -e "ZE_AFFINITY_MASK=${ZE_AFFINITY_MASK:-}" \
273 + -e "CMDS=${commands}" \
274 + --name "${container_name}" \
275 + "${image_name}" \
276 + bash -c 'set -e; echo "ZE_AFFINITY_MASK is ${ZE_AFFINITY_MASK:-}"; eval "$CMDS"'