#!/bin/bash # 8-instance connector tax microbench: plain vs mooncake_both # # Launches 8×TP1 vLLM instances + cache_aware_proxy, same topology as # elastic_migration_v2. Runs open-loop bench at rates 32,64,128 req/s # with short shape (512 input, 64 output) to maximize decode concurrency. # # Usage: # bash run_8instance.sh --mode plain # no Mooncake # bash run_8instance.sh --mode mooncake # kv_role=kv_both # # Results go to results/8inst__/ set -euo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJ="$(cd "$HERE/../.." && pwd)" PYTHON="$PROJ/.venv/bin/python" VLLM="$PROJ/.venv/bin/vllm" MODEL="${MODEL_PATH:-$HOME/models/Qwen/Qwen3-Coder-30B-A3B-Instruct}" export PYTHONPATH="$PROJ:${PYTHONPATH:-}" N_INSTANCES=8 BASE_PORT=8000 PROXY_PORT=9090 MODE="${1:---mode}" # parse below # Parse --mode plain|mooncake while [[ $# -gt 0 ]]; do case "$1" in --mode) MODE="$2"; shift 2 ;; *) echo "Unknown arg: $1"; exit 1 ;; esac done if [[ "$MODE" != "plain" && "$MODE" != "mooncake" ]]; then echo "Usage: $0 --mode plain|mooncake" exit 1 fi DATE=$(date +%Y%m%d_%H%M) OUTDIR="$HERE/results/8inst_${MODE}_${DATE}" mkdir -p "$OUTDIR" echo "=== 8-Instance Connector Tax Microbench ===" echo "Mode: $MODE" echo "Output: $OUTDIR" echo "" # ── Cleanup ─────────────────────────────────────────────────────────────── cleanup() { echo "[cleanup] Killing all vLLM/proxy processes..." pkill -9 -f "vllm serve" 2>/dev/null || true pkill -9 -f "VLLM::EngineCore" 2>/dev/null || true pkill -9 -f "cache_aware_proxy" 2>/dev/null || true sleep 5 for _ in $(seq 1 20); do total_used=$(nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits | awk '{s+=$1}END{print s}') [[ "$total_used" -lt 1000 ]] && return 0 pkill -9 -f "VLLM::EngineCore" 2>/dev/null || true sleep 3 done echo "[cleanup] WARNING: GPU memory not fully released" } trap cleanup EXIT cleanup # ensure clean start # ── Launch 8 instances ──────────────────────────────────────────────────── echo "[launch] Starting $N_INSTANCES vLLM instances..." for i in $(seq 0 $((N_INSTANCES - 1))); do port=$((BASE_PORT + i)) master=$((29500 + i)) logfile="$OUTDIR/vllm_inst_${i}.log" step_log="$OUTDIR/engine_step_${i}.jsonl" kv_args="" mooncake_env="" if [[ "$MODE" == "mooncake" ]]; then kv_args="--kv-transfer-config {\"kv_connector\":\"MooncakeConnector\",\"kv_role\":\"kv_both\"}" mooncake_env="VLLM_MOONCAKE_BOOTSTRAP_PORT=$((8998 + i))" fi env $mooncake_env \ AGENTIC_STEP_LOG_PATH="$step_log" \ AGENTIC_WORKER_ID="engine_${i}" \ MASTER_PORT=$master \ CUDA_VISIBLE_DEVICES=$i \ $VLLM serve "$MODEL" \ --host 0.0.0.0 --port $port \ --tensor-parallel-size 1 \ --trust-remote-code --enable-prefix-caching \ --dtype auto --gpu-memory-utilization 0.9 --max-model-len 200000 \ --no-enable-log-requests \ $kv_args \ > "$logfile" 2>&1 & echo " inst_$i: GPU=$i port=$port" sleep 2 done # Wait for all instances to be ready echo "[launch] Waiting for all instances..." for i in $(seq 0 $((N_INSTANCES - 1))); do port=$((BASE_PORT + i)) for t in $(seq 1 240); do if curl -sf "http://127.0.0.1:$port/v1/models" >/dev/null 2>&1; then echo " inst_$i ready after ${t}s" break fi if [[ $t -eq 240 ]]; then echo " ERROR: inst_$i did not start within 240s" exit 1 fi sleep 1 done done # ── Launch proxy ────────────────────────────────────────────────────────── echo "[proxy] Starting cache_aware_proxy (policy=load_only)..." combined_args="" for i in $(seq 0 $((N_INSTANCES - 1))); do combined_args="$combined_args http://127.0.0.1:$((BASE_PORT + i))" done $PYTHON "$PROJ/scripts/cache_aware_proxy.py" \ --combined $combined_args \ --port $PROXY_PORT \ --policy load_only \ > "$OUTDIR/proxy.log" 2>&1 & PROXY_PID=$! # Wait for proxy for t in $(seq 1 30); do if curl -sf "http://127.0.0.1:$PROXY_PORT/v1/models" >/dev/null 2>&1; then echo "[proxy] Ready on port $PROXY_PORT" break fi sleep 1 done # ── Run benchmark ───────────────────────────────────────────────────────── echo "" echo "[bench] Running open-loop bench (512 input, 64 output, rates=32,64,128)..." $PYTHON "$HERE/bench_loop.py" \ --url "http://127.0.0.1:$PROXY_PORT/v1/chat/completions" \ --model "$MODEL" \ --phase A \ --rates "32,64,128" \ --shape "512,64" \ --duration 60 \ --min-completed 200 \ --warmup 10 \ --output-dir "$OUTDIR" echo "" echo "[done] Results in $OUTDIR" echo "" # Print summary cat "$OUTDIR/summary_A.json" | $PYTHON -c " import json, sys data = json.load(sys.stdin) print('Rate | TTFT p50 TTFT p90 TTFT p99 | TPOT p50 TPOT p90 TPOT p99 | Thr ratio') print('-' * 100) for c in data: r = c['rate_target'] print(f'{r:>5.0f} | {c.get(\"ttft_ms_p50\",0):>7.0f}ms {c.get(\"ttft_ms_p90\",0):>7.0f}ms {c.get(\"ttft_ms_p99\",0):>7.0f}ms | {c.get(\"tpot_ms_p50\",0):>7.1f}ms {c.get(\"tpot_ms_p90\",0):>7.1f}ms {c.get(\"tpot_ms_p99\",0):>7.1f}ms | {c.get(\"throughput_ratio\",0):>9.2f}') "