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"'