#!/usr/bin/env bash
# ==========================================================================
# STAGE 3a-8 (신규): Prefix Caching 진짜 A/B 비교
# 기존 stage3a 시나리오3은 "on일 때 이 정도"만 봤음 — 여기서는 정확히 동일한
# 32k 고정 프리픽스 반복 워크로드를 on/off 두 번 돌려서 순수 캐싱 효과만 분리.
# ==========================================================================
set -euo pipefail
export HF_HUB_OFFLINE=1
export TRANSFORMERS_OFFLINE=1
export HF_DATASETS_OFFLINE=1
LOCAL_MODEL_ROOT="${LOCAL_MODEL_ROOT:-/mnt/local-nvme-cache/models}"
MODEL_70B="${MODEL_70B:-${LOCAL_MODEL_ROOT}/Meta-Llama-3.1-70B-Instruct}"
[ -d "${MODEL_70B}" ] || { echo "모델 없음: ${MODEL_70B}"; exit 1; }
TP_SIZE=8
VLLM_PORT=8000
SERVER_HOST="127.0.0.1"
LOG_DIR="/var/log/b300_validation/stage3a8_prefixab_$(date +%Y%m%d_%H%M%S)"
mkdir -p "${LOG_DIR}"
log() { echo -e "\n\033[1;32m[$(date '+%Y-%m-%d %H:%M:%S')] $1\033[0m"; }
source ~/vllm-bench-env/bin/activate
run_case() {
local tag=$1 cache_flag=$2
log "서버 기동: ${tag} (${cache_flag})"
python3 -m vllm.entrypoints.openai.api_server \
--model "${MODEL_70B}" --served-model-name "llama-3.1-70b" \
--tensor-parallel-size "${TP_SIZE}" --gpu-memory-utilization 0.90 --max-model-len 65536 \
${cache_flag} \
--port "${VLLM_PORT}" > "${LOG_DIR}/server_${tag}.log" 2>&1 &
SERVER_PID=$!
until curl -s "http://${SERVER_HOST}:${VLLM_PORT}/v1/models" > /dev/null 2>&1; do
kill -0 "${SERVER_PID}" 2>/dev/null || { echo "기동 실패: ${LOG_DIR}/server_${tag}.log"; exit 1; }
sleep 5
done
# 동일한 32k 고정 프리픽스를 8동시성으로 30개 반복 — stage3a 시나리오3과 동일 조건
vllm bench serve \
--backend openai-chat --endpoint /v1/chat/completions \
--host "${SERVER_HOST}" --port "${VLLM_PORT}" \
--model "llama-3.1-70b" --tokenizer "${MODEL_70B}" \
--dataset-name random --random-input-len 32768 --random-output-len 256 \
--random-prefix-len 32000 \
--num-prompts 30 --max-concurrency 8 --request-rate inf \
--save-result --result-dir "${LOG_DIR}" --result-filename "${tag}.json" || true
kill -TERM "${SERVER_PID}" 2>/dev/null || true
wait "${SERVER_PID}" 2>/dev/null || true
sleep 3
}
run_case "prefix_cache_on" "--enable-prefix-caching"
run_case "prefix_cache_off" "--no-enable-prefix-caching"
log "=== 결과 비교 ==="
python3 - << PYEOF
import json, os
log_dir = "${LOG_DIR}"
def load(tag):
fp = os.path.join(log_dir, f"{tag}.json")
if not os.path.exists(fp): return {}
try: return json.load(open(fp))
except Exception: return {}
on = load("prefix_cache_on")
off = load("prefix_cache_off")
ttft_on = on.get("median_ttft_ms", 0)
ttft_off = off.get("median_ttft_ms", 0)
speedup = (ttft_off / ttft_on) if ttft_on else 0
report = f"""# Stage3a-8 Prefix Caching A/B 결과 (동일 32k 고정 프리픽스, concurrency=8)
| 조건 | Median TTFT |
|---|---|
| Cache OFF (기준) | {ttft_off:.1f}ms |
| Cache ON | {ttft_on:.1f}ms |
**단축 배율: {speedup:.1f}x**
## 판정 기준
- 5~10배 이상 단축되면 정상적으로 캐싱이 동작하는 것 (이전 3단계 설계 문서의 Pass Criteria와 동일 기준)
- 단축 배율이 2배 미만이면 캐시가 제대로 히트하지 않고 있다는 신호 — 캐시 블록 크기,
KV 캐시 메모리 여유(--gpu-memory-utilization), 요청 간 프리픽스가 정말 동일한지 재확인 필요
"""
with open(f"{log_dir}/prefix_cache_ab_report.md", "w") as f:
f.write(report)
print(report)
PYEOF
log "Prefix Caching A/B 완료! 리포트: ${LOG_DIR}/prefix_cache_ab_report.md"