CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
242 lines
10 KiB
Bash
Executable File
242 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Smoke-test serving vLLM through mirage on an *emulated* MI350X.
|
|
#
|
|
# It launches the upstream `vllm/vllm-openai-rocm:latest` image via
|
|
# `mirage run --daemon`, serves `facebook/opt-125m`, and then exercises
|
|
# the OpenAI-compatible API with a single text completion ("a
|
|
# generation").
|
|
#
|
|
# Notes on how this maps onto mirage's CLI:
|
|
# * `--image vllm/vllm-openai-rocm:latest` -> run the node inside that
|
|
# container image.
|
|
# * `--mount $HF_CACHE:/root/.cache/huggingface` -> becomes the
|
|
# container `-v $HF_CACHE:/root/.cache/huggingface` bind mount, so the
|
|
# model weights are cached on the host and reused across runs.
|
|
# * `--port $PORT:$PORT` -> publishes the container port on the host
|
|
# (docker `-p`), so the server is reachable at `localhost:$PORT`.
|
|
# * `--daemon` -> run the rocjitsu emulator in out-of-process daemon
|
|
# mode (as requested).
|
|
#
|
|
# This uses the manylinux-built mirage from
|
|
# `scripts/mirage-docker-build.sh` rather than `cargo run`. mirage
|
|
# bind-mounts its own binary (and the rocjitsu KMD interposer) into the
|
|
# vLLM workload container; a binary built on a modern host links a newer
|
|
# glibc than the vLLM image carries and fails with `GLIBC_2.39 not
|
|
# found`. The manylinux build links an old, broadly-compatible glibc so
|
|
# it runs inside the vLLM container without the `--hack` workaround. The
|
|
# script builds it on demand when missing.
|
|
#
|
|
# Env knobs:
|
|
# PROFILE mirage profile (default: mi350x)
|
|
# IMAGE container image (default: vllm/vllm-openai-rocm:latest)
|
|
# MODEL model to serve (default: facebook/opt-125m)
|
|
# HF_CACHE host HuggingFace cache (default: ~/.cache/huggingface)
|
|
# PORT in-container server port (default: 8000)
|
|
# PROVIDER container provider binary (default: autodetect docker/podman)
|
|
# READY_TIMEOUT seconds to wait for the server (default: 600)
|
|
# MIRAGE_BIN manylinux mirage binary (default: <mirage>/build/manylinux/bin/mirage)
|
|
# SKIP_BUILD set to 1 to reuse an existing MIRAGE_BIN without rebuilding
|
|
# GPU_MEM_UTIL vLLM --gpu-memory-utilization (default: 0.2)
|
|
# MAX_MODEL_LEN vLLM --max-model-len (default: 2048)
|
|
# VLLM_EXTRA_ARGS extra args appended verbatim to `vllm serve`
|
|
# LOG_KERNELS when 1, log every kernel execution from BOTH torch/HIP and
|
|
# the rocjitsu emulator to the server log (default: 0)
|
|
#
|
|
# Kernel-execution logging (LOG_KERNELS=1) surfaces two independent traces in
|
|
# the vLLM server log ($RUN_LOG):
|
|
# * torch/HIP dispatch -> AMD_LOG_LEVEL=3 makes the ROCm runtime print every
|
|
# HIP kernel launch; PYTORCH_JIT_LOG_LEVEL / PYTORCH_SHOW_DISPATCH_TRACE add
|
|
# the aten dispatch trace on instrumented torch builds.
|
|
# * rocjitsu emulator -> `--plugin logging` adds the logging plugin to the
|
|
# resolved profile config; its default stderr sink streams each emulated
|
|
# kernel to the server log.
|
|
#
|
|
# Why the vLLM init is constrained: the emulated MI350X advertises ~288
|
|
# GiB of VRAM, and vLLM's engine-core init runs a memory-profiling
|
|
# forward pass that by default grabs 90% of it and captures HIP graphs.
|
|
# That stresses the functional emulator into a GPU memory access fault
|
|
# ("Engine core initialization failed" / "Memory critical error by
|
|
# agent"). `--enforce-eager`, a small `--gpu-memory-utilization`, a
|
|
# short `--max-model-len`, and `--max-num-seqs 1` keep init light enough
|
|
# for the emulator to service.
|
|
#
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
MIRAGE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
|
PROFILE="${PROFILE:-mi350x}"
|
|
IMAGE="${IMAGE:-docker.io/vllm/vllm-openai-rocm:latest}"
|
|
MODEL="${MODEL:-facebook/opt-125m}"
|
|
HF_CACHE="${HF_CACHE:-$HOME/.cache/huggingface}"
|
|
PORT="${PORT:-8000}"
|
|
READY_TIMEOUT="${READY_TIMEOUT:-600}"
|
|
MIRAGE_BIN="${MIRAGE_BIN:-$MIRAGE_DIR/build/manylinux/bin/mirage}"
|
|
GPU_MEM_UTIL="${GPU_MEM_UTIL:-0.2}"
|
|
MAX_MODEL_LEN="${MAX_MODEL_LEN:-2048}"
|
|
LOG_KERNELS="${LOG_KERNELS:-0}"
|
|
|
|
# Build the list of arguments that enable kernel-execution logging. Only
|
|
# populated when LOG_KERNELS=1 so the default run stays quiet.
|
|
KERNEL_LOG_ARGS=()
|
|
if [[ "$LOG_KERNELS" == "1" ]]; then
|
|
KERNEL_LOG_ARGS+=(
|
|
# torch / HIP kernel dispatch trace.
|
|
--env AMD_LOG_LEVEL=3
|
|
--env PYTORCH_JIT_LOG_LEVEL=kernels
|
|
--env PYTORCH_SHOW_DISPATCH_TRACE=1
|
|
# rocjitsu kernel logging, merged into the runtime config by mirage.
|
|
--plugin logging
|
|
)
|
|
fi
|
|
|
|
log() { printf '==> %s\n' "$*"; }
|
|
fail() { printf 'FAIL: %s\n' "$*" >&2; exit 1; }
|
|
|
|
# Build the glibc-compatible mirage binary in the manylinux container
|
|
# unless it already exists (or SKIP_BUILD=1 forces reuse).
|
|
if [[ "${SKIP_BUILD:-0}" != "1" && ! -x "$MIRAGE_BIN" ]]; then
|
|
log "building manylinux mirage via scripts/mirage-docker-build.sh"
|
|
"$MIRAGE_DIR/scripts/mirage-docker-build.sh" "$MIRAGE_DIR/build/manylinux"
|
|
fi
|
|
[[ -x "$MIRAGE_BIN" ]] || fail "mirage binary not found: $MIRAGE_BIN (run scripts/mirage-docker-build.sh)"
|
|
log "using mirage binary: $MIRAGE_BIN"
|
|
|
|
# Pick a container provider so we can inspect the launched node container.
|
|
if [[ -n "${PROVIDER:-}" ]]; then
|
|
:
|
|
elif command -v docker >/dev/null 2>&1; then
|
|
PROVIDER="docker"
|
|
elif command -v podman >/dev/null 2>&1; then
|
|
PROVIDER="podman"
|
|
else
|
|
fail "no container provider found (need docker or podman)"
|
|
fi
|
|
log "using container provider: $PROVIDER"
|
|
|
|
# Make sure the HuggingFace cache directory exists on the host so the
|
|
# weights survive across runs.
|
|
mkdir -p "$HF_CACHE"
|
|
log "caching model weights in $HF_CACHE"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Launch the server through mirage in the background. mirage execs the
|
|
# given argv inside the node container, so we invoke vLLM's `serve` CLI
|
|
# directly (the image ENTRYPOINT does not apply to `exec`ed commands).
|
|
# Bind to 0.0.0.0 so the port is reachable over the session network.
|
|
#
|
|
# The serve flags keep engine-core init light enough for the functional
|
|
# emulator (see header): eager mode (no HIP graph capture), a small
|
|
# fraction of the emulated VRAM, a short context, and a single sequence.
|
|
# ---------------------------------------------------------------------------
|
|
RUN_LOG="$(mktemp -t mirage-vllm.XXXXXX.log)"
|
|
log "starting vLLM (model=$MODEL) via mirage run --daemon"
|
|
if [[ "$LOG_KERNELS" == "1" ]]; then
|
|
log "kernel-execution logging enabled (torch/HIP + rocjitsu) -> $RUN_LOG"
|
|
export RJ_LOG_GROUPS="CP" # enable all rocjitsu log groups for the fixture run
|
|
fi
|
|
cd "$MIRAGE_DIR"
|
|
"$MIRAGE_BIN" run \
|
|
--daemon \
|
|
--profile "$PROFILE" \
|
|
--image "$IMAGE" \
|
|
--container-provider "$PROVIDER" \
|
|
--port "$PORT:$PORT" \
|
|
--mount "$HF_CACHE:/root/.cache/huggingface" \
|
|
"${KERNEL_LOG_ARGS[@]}" \
|
|
-- vllm serve "$MODEL" --host 0.0.0.0 --port "$PORT" \
|
|
--enforce-eager \
|
|
--gpu-memory-utilization "$GPU_MEM_UTIL" \
|
|
--max-model-len "$MAX_MODEL_LEN" \
|
|
--max-num-seqs 1 \
|
|
${VLLM_EXTRA_ARGS:-} \
|
|
>"$RUN_LOG" 2>&1 &
|
|
MIRAGE_PID=$!
|
|
|
|
CONTAINER=""
|
|
cleanup() {
|
|
log "cleaning up"
|
|
# Stop the backgrounded mirage run, which tears down the session and
|
|
# its container/network.
|
|
if kill -0 "$MIRAGE_PID" 2>/dev/null; then
|
|
kill "$MIRAGE_PID" 2>/dev/null || true
|
|
wait "$MIRAGE_PID" 2>/dev/null || true
|
|
fi
|
|
# Fall back to force-removing the node container if it lingers.
|
|
if [[ -n "$CONTAINER" ]]; then
|
|
"$PROVIDER" rm -f "$CONTAINER" >/dev/null 2>&1 || true
|
|
fi
|
|
rm -f "$RUN_LOG"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Wait for mirage to launch the node container (named
|
|
# `mirage-<session>-node-0`) and learn its IP on the session network.
|
|
# ---------------------------------------------------------------------------
|
|
log "waiting for the node container to appear"
|
|
DEADLINE=$(( $(date +%s) + READY_TIMEOUT ))
|
|
while :; do
|
|
CONTAINER="$("$PROVIDER" ps --filter 'name=mirage-' --filter 'status=running' \
|
|
--format '{{.Names}}' 2>/dev/null | grep -E 'mirage-.*-node-0$' | head -n1 || true)"
|
|
[[ -n "$CONTAINER" ]] && break
|
|
if ! kill -0 "$MIRAGE_PID" 2>/dev/null; then
|
|
cat "$RUN_LOG" >&2 || true
|
|
fail "mirage run exited before the container started"
|
|
fi
|
|
(( $(date +%s) < DEADLINE )) || { cat "$RUN_LOG" >&2; fail "timed out waiting for container"; }
|
|
sleep 2
|
|
done
|
|
log "node container: $CONTAINER"
|
|
|
|
CONTAINER_IP="$("$PROVIDER" inspect -f \
|
|
'{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$CONTAINER" 2>/dev/null || true)"
|
|
log "container IP on session network: ${CONTAINER_IP:-<unknown>}"
|
|
|
|
# Helper: run curl against the server. The `--port` publish makes the
|
|
# server reachable at `localhost:$PORT` on the host; fall back to the
|
|
# container IP, then to running curl inside the container.
|
|
api_curl() {
|
|
local path="$1"; shift
|
|
if curl -fsS --max-time 5 "http://localhost:$PORT$path" "$@" 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
if [[ -n "$CONTAINER_IP" ]] && \
|
|
curl -fsS --max-time 5 "http://$CONTAINER_IP:$PORT$path" "$@" 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
"$PROVIDER" exec "$CONTAINER" \
|
|
curl -fsS --max-time 5 "http://localhost:$PORT$path" "$@"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Wait for the OpenAI server to report healthy.
|
|
# ---------------------------------------------------------------------------
|
|
log "waiting for the vLLM server to become ready (model load can take a while)"
|
|
while :; do
|
|
if api_curl /health >/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
if ! kill -0 "$MIRAGE_PID" 2>/dev/null; then
|
|
cat "$RUN_LOG" >&2 || true
|
|
fail "mirage run exited before the server became ready"
|
|
fi
|
|
(( $(date +%s) < DEADLINE )) || { cat "$RUN_LOG" >&2; fail "timed out waiting for server"; }
|
|
sleep 3
|
|
done
|
|
log "server is healthy"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test a generation through the OpenAI-compatible completions endpoint.
|
|
# ---------------------------------------------------------------------------
|
|
log "requesting a completion from $MODEL"
|
|
REQ="$(printf '{"model":"%s","prompt":"The capital of France is","max_tokens":6,"temperature":0}' "$MODEL")"
|
|
RESP="$(api_curl /v1/completions \
|
|
-H 'Content-Type: application/json' \
|
|
-d "$REQ")" || { cat "$RUN_LOG" >&2; fail "completion request failed"; }
|
|
|
|
printf 'response: %s\n' "$RESP"
|
|
grep -q '"choices"' <<<"$RESP" || fail "no choices in completion response"
|
|
log "PASS: vLLM served $MODEL through mirage and returned a generation"
|