#!/bin/bash # Experiment matrix that rigorously evaluates PD separation vs Combined on # the agentic trace (traces/w600_r0.0015_st30.jsonl), using cuda graphs by # default and capturing step-level KV utilization for the C3 time-series # figure. # # Matrix (default minimal set, ~2 h wall-clock on 8 x H20): # Configs: combined-ca pdsep-4p4d pdsep-6p2d # Modes: cudagraph # Seeds: 1, 2, 3 # # Optional extensions: # --with-rr also run combined-rr (refresh C7 routing-lever data) # --with-eager also run each config with --enforce-eager (cuda-graph # ablation; this doubles wall-clock) # # Output structure (on the host that runs this): # outputs/pd_matrix/ # __seed/ # config.json, metrics.jsonl, metrics.summary.json # breakdown.json, stats.json, apc.txt # proxy.log, replayer.log, gpu_util.csv, gpu_snapshot.csv # vllm_inst_*.log <-- step-level "KV cache: X%" lines for C3 # # Run on dash0: # cd ~/agentic-kv # bash analysis/pd_sep_paper_section/scripts/bench_pd_matrix.sh set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" BENCH="$PROJECT_DIR/scripts/bench.sh" TRACE_DEFAULT="$PROJECT_DIR/traces/w600_r0.0015_st30.jsonl" # Defaults TRACE="$TRACE_DEFAULT" REQUESTS=850 SEEDS=3 WITH_RR=false WITH_EAGER=false DRY_RUN=false TAG_PREFIX="pd_matrix" ONLY="" # comma-separated list of tags to run (subset of the matrix) while [[ $# -gt 0 ]]; do case "$1" in --trace) TRACE="$2"; shift 2 ;; --requests) REQUESTS="$2"; shift 2 ;; --seeds) SEEDS="$2"; shift 2 ;; --with-rr) WITH_RR=true; shift ;; --with-eager) WITH_EAGER=true; shift ;; --tag-prefix) TAG_PREFIX="$2"; shift 2 ;; --only) ONLY="$2"; shift 2 ;; --dry-run) DRY_RUN=true; shift ;; -h|--help) sed -n '2,30p' "$0"; exit 0 ;; *) echo "Unknown: $1"; exit 1 ;; esac done # Build set of allowed tags from --only (if provided). declare -A ALLOWED if [ -n "$ONLY" ]; then IFS=',' read -ra _tags <<< "$ONLY" for t in "${_tags[@]}"; do ALLOWED["$(echo "$t" | xargs)"]=1 done fi is_allowed() { # if ONLY not set, everything is allowed [ -z "$ONLY" ] && return 0 [ -n "${ALLOWED[$1]:-}" ] } if [ ! -f "$TRACE" ]; then echo "[ERROR] trace not found: $TRACE" exit 1 fi if [ ! -x "$BENCH" ]; then echo "[ERROR] bench.sh not found or not executable: $BENCH" exit 1 fi # Config table: name | bench.sh args declare -a CONFIGS CONFIGS+=("combined-ca|--mode baseline --policy linear") CONFIGS+=("pdsep-4p4d|--mode pdsep --pd-ratio 4:4 --policy linear") CONFIGS+=("pdsep-6p2d|--mode pdsep --pd-ratio 6:2 --policy linear") if [ "$WITH_RR" = "true" ]; then # round-robin via lmetric without affinity is the cleanest proxy for RR; # to get pure RR you'd need to add a --policy rr to cache_aware_proxy. # For now this slot is a placeholder so the matrix script is uniform. CONFIGS+=("combined-rr|--mode baseline --policy lmetric") fi declare -a MODES MODES+=("cudagraph|") if [ "$WITH_EAGER" = "true" ]; then MODES+=("eager|--eager") fi # Pretty-print matrix echo "==================================================================" echo " PD-sep paper section experiment matrix" echo " trace = $TRACE" echo " requests = $REQUESTS" echo " seeds = 1..$SEEDS" echo " configs = ${#CONFIGS[@]}" echo " modes = ${#MODES[@]}" echo " total runs = $(( ${#CONFIGS[@]} * ${#MODES[@]} * SEEDS ))" echo "==================================================================" run_one() { local config_name="$1"; local config_args="$2" local mode_name="$3"; local mode_args="$4" local seed="$5" local tag="$TAG_PREFIX/${config_name}_${mode_name}_seed${seed}" local outdir="$PROJECT_DIR/outputs/$tag" if [ -d "$outdir" ] && [ -f "$outdir/metrics.summary.json" ]; then echo "[skip] $tag (already complete)" return 0 fi rm -rf "$outdir" # clear partial runs local cmd="bash $BENCH --tag $tag $config_args $mode_args \ --trace $TRACE --requests $REQUESTS" echo echo "[run] $tag" echo " cmd: $cmd" if [ "$DRY_RUN" = "true" ]; then return 0 fi # PYTHONHASHSEED is set inside bench.sh for elastic, but not for the # other modes. We export a different seed per run for reproducibility # of any RNG inside the proxy/replayer. PYTHONHASHSEED=$((42 + seed)) eval "$cmd" local rc=$? if [ $rc -ne 0 ]; then echo "[FAIL] $tag exited rc=$rc" return $rc fi echo "[done] $tag" } START_TS=$(date +%s) N_DONE=0 N_FAIL=0 N_TOTAL=$(( ${#CONFIGS[@]} * ${#MODES[@]} * SEEDS )) for c in "${CONFIGS[@]}"; do cfg_name="${c%%|*}"; cfg_args="${c##*|}" for m in "${MODES[@]}"; do mode_name="${m%%|*}"; mode_args="${m##*|}" for s in $(seq 1 $SEEDS); do tag_name="${cfg_name}_${mode_name}_seed${s}" if ! is_allowed "$tag_name"; then continue fi if run_one "$cfg_name" "$cfg_args" "$mode_name" "$mode_args" "$s"; then N_DONE=$((N_DONE + 1)) else N_FAIL=$((N_FAIL + 1)) fi ELAPSED=$(( $(date +%s) - START_TS )) echo "[progress] $N_DONE done, $N_FAIL failed, ${ELAPSED}s elapsed" done done done echo echo "==================================================================" echo " MATRIX COMPLETE: $N_DONE/$N_TOTAL succeeded, $N_FAIL failed" echo " elapsed: $(( ($(date +%s) - START_TS) / 60 )) min" echo " outputs: $PROJECT_DIR/outputs/$TAG_PREFIX/" echo "=================================================================="