vllm_serve.sh

script

← Back to skill

Content hash: 2d4449625b48309b75498da98637dd147b9094c9ac7296b45da5f9ab5ad1925e
#!/usr/bin/env bash
#!/usr/bin/env bash
# vLLM model serving: launch, health-check, and load-test an OpenAI-compatible endpoint.
#
# Usage: bash vllm_serve.sh [MODEL]
#   MODEL defaults to a small quantized checkpoint; override for real deployments.

set -euo pipefail

MODEL="${1:-Qwen/Qwen2.5-1.5B-Instruct}"
PORT="${PORT:-8000}"
GPU_MEM="${GPU_MEM:-0.9}"
MAX_SEQS="${MAX_SEQS:-128}"
SERVED_NAME="${SERVED_NAME:-my-llm}"
TP_SIZE="${TP_SIZE:-1}"

echo "=== vLLM Serving Configuration ==="
echo "Model:            $MODEL"
echo "Port:             $PORT"
echo "GPU mem util:     $GPU_MEM"
echo "Max sequences:    $MAX_SEQS"
echo "Served name:      $SERVED_NAME"
echo "Tensor parallel:  $TP_SIZE"

# 1. Start the OpenAI-compatible server (foreground; run in background in practice)
echo ""
echo "Starting vLLM server..."
vllm serve "$MODEL" \
  --tensor-parallel-size "$TP_SIZE" \
  --max-num-seqs "$MAX_SEQS" \
  --gpu-memory-utilization "$GPU_MEM" \
  --served-model-name "$SERVED_NAME" \
  --port "$PORT" \
  --host 0.0.0.0 &

VLLM_PID=$!
trap "kill $VLLM_PID 2>/dev/null || true" EXIT

# 2. Wait for readiness
echo "Waiting for server readiness..."
for i in $(seq 1 120); do
  if curl -sf "http://localhost:$PORT/health" >/dev/null 2>&1; then
    echo "Server ready after ${i}s."
    break
  fi
  if ! kill -0 "$VLLM_PID" 2>/dev/null; then
    echo "ERROR: vLLM process exited during startup."
    exit 1
  fi
  sleep 1
  if [ "$i" -eq 120 ]; then
    echo "ERROR: Timed out waiting for server."
    exit 1
  fi
done

# 3. Single request smoke test
echo ""
echo "=== Smoke test (single request) ==="
curl -s "http://localhost:$PORT/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -d "{
    \"model\": \"$SERVED_NAME\",
    \"messages\": [{\"role\": \"user\", \"content\": \"Say hello in one sentence.\"}],
    \"max_tokens\": 32,
    \"temperature\": 0
  }" | python3 -c "import sys, json; d = json.load(sys.stdin); print(d['choices'][0]['message']['content'])"

# 4. Check model list
echo ""
echo "=== Models endpoint ==="
curl -s "http://localhost:$PORT/v1/models" | python3 -m json.tool

# 5. Load test (20 concurrent requests)
echo ""
echo "=== Load test (20 concurrent) ==="
python3 - <<'PY'
import concurrent.futures
import json
import time
import urllib.request

URL = f"http://localhost:{os.environ.get('PORT', '8000')}/v1/chat/completions"
SERVED = os.environ.get("SERVED_NAME", "my-llm")

def send_request(i):
    payload = json.dumps({
        "model": SERVED,
        "messages": [{"role": "user", "content": f"Write a short fact about number {i}."}],
        "max_tokens": 32,
        "temperature": 0,
    }).encode()
    start = time.perf_counter()
    req = urllib.request.Request(URL, data=payload, headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(req, timeout=120) as resp:
        data = json.loads(resp.read())
    elapsed = time.perf_counter() - start
    return elapsed

start = time.perf_counter()
with concurrent.futures.ThreadPoolExecutor(max_workers=20) as pool:
    latencies = list(pool.map(send_request, range(20)))
total = time.perf_counter() - start

latencies.sort()
p50 = latencies[len(latencies)//2]
p95 = latencies[int(len(latencies)*0.95)]
print(f"  Total wall time: {total:.2f}s")
print(f"  p50 latency:     {p50:.3f}s")
print(f"  p95 latency:     {p95:.3f}s")
print(f"  Throughput:      {20/total:.1f} req/s")
PY

echo ""
echo "=== Done. Check nvidia-smi for memory usage ==="
nvidia-smi --query-gpu=memory.used,memory.total --format=csv,noheader 2>/dev/null || \
  echo "nvidia-smi not available (non-GPU environment)"