Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions .github/scripts/spur-monitoring-cpu-smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,11 @@ if [[ "${AIC_SMOKE_USE_REGISTRY}" == "1" ]]; then
docker pull "${AIC_IMAGE}"
else
echo "=== Loading image from ${TARBALL_DIR} ==="
tarball="$(find "${TARBALL_DIR}" -maxdepth 1 \( -name '*.tar.zst' -o -name '*.tar.gz' -o -name '*.tar' \) 2>/dev/null | head -1)"
[[ -n "${tarball}" ]] || { echo "ERROR: no tarball in ${TARBALL_DIR}" >&2; ls -la "${TARBALL_DIR}" >&2 || true; exit 1; }
# Match only the main image tarball: run-build-distribute.sh names it
# "${base}-${arch_tag}.{ext}" where base = AIC_IMAGE with '/:' -> '--'.
img_base="$(printf '%s' "${AIC_IMAGE}" | tr '/:' '--')"
tarball="$(find "${TARBALL_DIR}" -maxdepth 1 -name "${img_base}-*.tar.zst" -o -name "${img_base}-*.tar.gz" -o -name "${img_base}-*.tar" 2>/dev/null | head -1)"
[[ -n "${tarball}" ]] || { echo "ERROR: no tarball for ${AIC_IMAGE} in ${TARBALL_DIR}" >&2; ls -la "${TARBALL_DIR}" >&2 || true; exit 1; }
case "${tarball}" in
*.tar.zst) zstd -dc "${tarball}" | docker load ;;
*.tar.gz) gzip -dc "${tarball}" | docker load ;;
Expand All @@ -100,7 +103,7 @@ else
fi

MON_DIR="${WORKDIR}/monitoring"
METRICS_DIR="/tmp/aic-prom-tsdb-${SHORT}"
METRICS_DIR="${METRICS_PAGE_DIR}/prom-tsdb"
mkdir -p "${METRICS_DIR}" "${METRICS_PAGE_DIR}"

export AIC_IMAGE MON_DIR AIC_METRICS_DIR="${METRICS_DIR}"
Expand Down Expand Up @@ -175,12 +178,13 @@ docker run -d --name aic-prometheus \
# ---------------------------------------------------------------------------
echo "=== Starting vLLM emulator (8000) ==="
docker run -d --name aic-vllm-emulator \
--entrypoint python3 \
--network host \
-v "${WORKDIR}/monitoring/scripts/vllm_emulator_server.py:/app/vllm_emulator_server.py:ro" \
-e VLLM_MODEL="${VLLM_CPU_MODEL:-facebook/opt-125m}" \
-e PYTHONUNBUFFERED=1 \
"${AIC_IMAGE}" \
python3 /app/vllm_emulator_server.py
/app/vllm_emulator_server.py

# ---------------------------------------------------------------------------
# 4. Wait for vLLM emulator to be healthy (up to 3 min)
Expand Down
60 changes: 33 additions & 27 deletions monitoring/scripts/vllm_emulator_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,15 @@
# ---------------------------------------------------------------------------
MODEL = os.environ.get("VLLM_MODEL", "facebook/opt-125m")

_emulator = None
try:
from vllm.engine.emulator import LLMEmulator # type: ignore[import]
_emulator = LLMEmulator(model=MODEL, emulate_latency=True)
log.info("LLMEmulator loaded for model %s", MODEL)
except Exception as exc: # pragma: no cover # emulator may not exist in all builds
log.warning("LLMEmulator unavailable (%s); falling back to vllm.LLM --device cpu", exc)
from vllm import LLM, SamplingParams as _SP # type: ignore[import]
_emulator = LLM(model=MODEL, device="cpu")
except Exception as exc:
# This vLLM build is GPU-only; fall back to a pure-Python stub that
# returns canned completions so the smoke test can run on CPU-only nodes.
log.warning("LLMEmulator unavailable (%s); using pure-Python stub", exc)

# ---------------------------------------------------------------------------
# FastAPI application
Expand Down Expand Up @@ -115,33 +116,38 @@ async def completions(req: CompletionRequest) -> JSONResponse:
prompts = [req.prompt] if isinstance(req.prompt, str) else req.prompt
t0 = time.perf_counter()

try:
# SamplingParams is the same for both LLMEmulator and LLM
from vllm import SamplingParams # type: ignore[import]
params = SamplingParams(
max_tokens=req.max_tokens or 16,
temperature=req.temperature or 1.0,
top_p=req.top_p or 1.0,
)
outputs = _emulator.generate(prompts, params)
except Exception as exc:
raise HTTPException(status_code=500, detail=str(exc)) from exc

elapsed = time.perf_counter() - t0
choices = []

if _emulator is not None:
try:
from vllm import SamplingParams # type: ignore[import]
params = SamplingParams(
max_tokens=req.max_tokens or 16,
temperature=req.temperature or 1.0,
top_p=req.top_p or 1.0,
)
outputs = _emulator.generate(prompts, params)
except Exception as exc:
raise HTTPException(status_code=500, detail=str(exc)) from exc
elapsed = time.perf_counter() - t0
for i, out in enumerate(outputs):
text = out.outputs[0].text if out.outputs else ""
_tokens_generated.labels(model=req.model).inc(
len(out.outputs[0].token_ids) if out.outputs else 0
)
choices.append({"index": i, "text": text, "logprobs": None, "finish_reason": "length"})
else:
# Pure-Python stub: return a canned completion so CPU-only smoke tests pass.
elapsed = time.perf_counter() - t0
for i, prompt in enumerate(prompts):
text = f"[stub] {prompt[:20]} ..."
_tokens_generated.labels(model=req.model).inc(4)
choices.append({"index": i, "text": text, "logprobs": None, "finish_reason": "length"})

_requests_total.labels(model=req.model).inc()
_request_duration.labels(model=req.model).observe(elapsed)

choices = []
for i, out in enumerate(outputs):
text = out.outputs[0].text if out.outputs else ""
_tokens_generated.labels(model=req.model).inc(len(out.outputs[0].token_ids) if out.outputs else 0)
choices.append({
"index": i,
"text": text,
"logprobs": None,
"finish_reason": "length",
})

return JSONResponse({
"id": f"cmpl-{uuid.uuid4().hex}",
"object": "text_completion",
Expand Down
Loading