#!/usr/bin/env bash # run_probe.sh — short-lived fit probe for the dual-gpu-tuner skill. # # Launches a COPY of the production launcher (never edits it) with a probe # port and an optional -ot override, waits for the listening state, snapshots # rocm-smi, kills the probe, parses the log, and appends a normalized record # to the placement history. # # Usage: # run_probe.sh LAUNCHER [OT_OVERRIDE ...] [options] # # OT_OVERRIDE one or more, e.g. '^blk\.36\.\w[\w.]*$=ROCm1' (optional) # --port N probe port (default 8123) # --timeout N seconds to wait for listening state (default 600) # --fit-target N record a fit target different from the launcher's # --history PATH history file (default /data/placement_history.jsonl) # --force proceed even if a server for this model is already running # --keep leave the probe server running after capture # --dry-run print the probe script and exit # # The launcher's own server must be stopped first (the probe needs the GPUs). # Launcher args must not contain a literal '|' character. set -euo pipefail SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) SKILL_DIR=$(dirname "$SCRIPT_DIR") HISTORY="$SKILL_DIR/data/placement_history.jsonl" PORT=8123 TIMEOUT=600 FIT_TARGET_OVERRIDE="" OTS=() FORCE=0 KEEP=0 DRY_RUN=0 usage() { sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'; exit 2; } LAUNCHER="" while [ $# -gt 0 ]; do case "$1" in --port) PORT="$2"; shift 2 ;; --timeout) TIMEOUT="$2"; shift 2 ;; --fit-target) FIT_TARGET_OVERRIDE="$2"; shift 2 ;; --history) HISTORY="$2"; shift 2 ;; --force) FORCE=1; shift ;; --keep) KEEP=1; shift ;; --dry-run) DRY_RUN=1; shift ;; -h|--help) usage ;; -*) echo "unknown option: $1" >&2; usage ;; *) if [ -z "$LAUNCHER" ]; then LAUNCHER="$1"; else OTS+=("$1"); fi; shift ;; esac done OT_STR=$(IFS=' + '; printf '%s' "${OTS[*]:-}") [ -n "$LAUNCHER" ] || usage [ -f "$LAUNCHER" ] || { echo "error: launcher not found: $LAUNCHER" >&2; exit 2; } # ---------------------------------------------------------------- extraction raw=$(grep -vE '^[[:space:]]*(#|$)' "$LAUNCHER" | sed -E 's/\\[[:space:]]*$//') envs=$(grep -E '^[[:space:]]*export ' "$LAUNCHER" || true) # strip the launcher's own -ot/--tensor-overrides BEFORE the '|' extraction: # override values may contain '|' in regex alternations, which would truncate # the extracted command (value may be single-quoted, double-quoted, or bare) cmdline=$(printf '%s\n' "$raw" | grep -vE '^[[:space:]]*export ' | tr '\n' ' ' \ | sed -E "s/(^|[[:space:]])(-ot|--tensor-overrides)[[:space:]]+('[^']*'|\"[^\"]*\"|[^[:space:]]+)//g") cmd=$(printf '%s' "$cmdline" | grep -oE '[^|]*llama-server[^|]*' | head -1 || true) # trim cmd="${cmd#"${cmd%%[![:space:]]*}"}" cmd="${cmd%"${cmd##*[![:space:]]}"}" # strip trailing 2>&1 (monitor pipe) cmd="${cmd% 2>&1}" cmd="${cmd%"${cmd##*[![:space:]]}"}" [ -n "$cmd" ] || { echo "error: could not extract llama-server command from $LAUNCHER" >&2; exit 2; } MODEL=$(printf '%s' "$cmd" | grep -oE '\-m [^ ]+' | head -1 | awk '{print $2}') MODEL_BASE=$(basename "${MODEL:-unknown}") # safety: a server for this model must not be running (GPUs are busy) if [ "$FORCE" -ne 1 ] && pgrep -f "llama-server.*$MODEL_BASE" >/dev/null 2>&1; then echo "error: a llama-server for $MODEL_BASE is already running." >&2 echo " stop it first (the probe needs the GPUs), or pass --force." >&2 exit 3 fi if ss -ltn 2>/dev/null | grep -qE "[:.]$PORT[[:space:]]"; then echo "error: port $PORT is already in use; pass --port N." >&2 exit 3 fi # ------------------------------------------------------------------ key json KEY=$(printf '%s' "$cmd" | python3 -c ' import re, sys, json s = sys.stdin.read() def g(p): m = re.search(p, s) return m.group(1) if m else None key = { "model_file": (g(r"-m (\S+)") or "").rsplit("/", 1)[-1], "tensor_split": g(r"--tensor-split (\S+)"), "ctk": g(r"-ctk (\S+)"), "ctv": g(r"-ctv (\S+)"), "ctk_draft": g(r"--cache-type-k-draft (\S+)"), "ctv_draft": g(r"--cache-type-v-draft (\S+)"), "vec": g(r"--hip-fa-force-vec (on|off)"), "fit_target": g(r"--fit-target (\S+)"), "devices": g(r"--device (\S+)"), "batch": (g(r"-b (\S+)") or "") + "/" + (g(r"-ub (\S+)") or ""), "ctx_checkpoints": g(r"--ctx-checkpoints (\S+)"), } print(json.dumps({k: v for k, v in key.items() if v})) ') [ -n "$FIT_TARGET_OVERRIDE" ] && KEY=$(python3 -c ' import json,sys k=json.loads(sys.argv[1]); k["fit_target"]=sys.argv[2]; print(json.dumps(k))' "$KEY" "$FIT_TARGET_OVERRIDE") # -------------------------------------------------------------- probe script TS=$(date +%Y%m%d_%H%M%S) STAMP=$(date +%H%M%S) WORKDIR=$(mktemp -d /tmp/otprobe.XXXXXX) PROBE="$WORKDIR/probe_$STAMP.sh" LOG="$WORKDIR/probe_$STAMP.log" SMI="$WORKDIR/probe_$STAMP.rocm" RUNJSON="$WORKDIR/probe_$STAMP.json" { printf '#!/usr/bin/env bash\n' [ -n "$envs" ] && printf '%s\n' "$envs" printf 'exec %s' "$cmd" # exec: wrapper PID becomes the server PID (kill works) printf ' --port %d' "$PORT" for ot in ${OTS[@]+"${OTS[@]}"}; do printf ' -ot %q' "$ot"; done printf '\n' } > "$PROBE" chmod +x "$PROBE" if [ "$DRY_RUN" -eq 1 ]; then echo "# probe script (would run, log -> $LOG):" cat "$PROBE" echo "# key: $KEY" [ -n "$OT_STR" ] && echo "# ot: $OT_STR" exit 0 fi echo "[probe] model=$MODEL_BASE port=$PORT ot=${OT_STR:-none}" echo "[probe] log=$LOG" bash "$PROBE" > "$LOG" 2>&1 & PID=$! LISTENING=0 SECONDS=0 while [ "$SECONDS" -lt "$TIMEOUT" ]; do if grep -q "listening on" "$LOG" 2>/dev/null; then LISTENING=1; break; fi if grep -qE "allocation failed|out of memory|GGML_ASSERT|segmentation fault" "$LOG" 2>/dev/null; then break; fi kill -0 "$PID" 2>/dev/null || break sleep 2 done # rocm-smi snapshot while the probe is alive (authoritative free memory) # (ROCm only — skipped for Vulkan/other backends; fitter budgets are used) if [ "$LISTENING" -eq 1 ] && command -v rocm-smi >/dev/null 2>&1; then sleep 2 rocm-smi --showmeminfo vram > "$SMI" 2>/dev/null || true fi [ -s "$SMI" ] || SMI="" if [ "$KEEP" -ne 1 ]; then kill -TERM "$PID" 2>/dev/null || true pkill -TERM -P "$PID" 2>/dev/null || true for _ in $(seq 1 15); do kill -0 "$PID" 2>/dev/null || break; sleep 1; done kill -9 "$PID" 2>/dev/null || true pkill -9 -P "$PID" 2>/dev/null || true # safety sweep: nothing for this model may survive the probe pkill -9 -f "llama-server.*$MODEL_BASE" 2>/dev/null || true sleep 1 fi # ------------------------------------------------------------- parse + record python3 "$SCRIPT_DIR/parse_llama_log.py" "$LOG" \ ${SMI:+--rocm-smi "$SMI"} \ --key-json "$KEY" \ --ot "$OT_STR" \ --tensor-split "$(printf '%s' "$cmd" | grep -oE -- '--tensor-split [^ ]+' | awk '{print $2}')" \ --append "$HISTORY" \ --quiet > "$RUNJSON" 2>&1 || true RUN=$(python3 - "$RUNJSON" <<'EOF' import json, sys try: d = json.load(open(sys.argv[1]))["run"] except Exception: print("parse-failed"); sys.exit(0) f = d.get("free_mib", {}) m = d.get("model_mib", {}) devs = [x for x in (d.get("devices") or m or {}) if x != "CPU"] or ["ROCm0", "ROCm1"] print(f"ctx={d.get('ctx_fitted')} " f"free0={f.get(devs[0])} free1={f.get(devs[1] if len(devs) > 1 else '')} " f"model0={m.get(devs[0])} model1={m.get(devs[1] if len(devs) > 1 else '')} " f"load={'ok' if d.get('load_ok') else 'FAIL'} splits={d.get('graph_splits')}") EOF ) echo "[probe] $RUN" echo "[probe] log: $LOG" [ -n "$SMI" ] && echo "[probe] rocm-smi: $SMI" echo "[probe] record: $HISTORY" if [ "$LISTENING" -ne 1 ]; then echo "[probe] WARNING: probe did not reach listening state; last log lines:" >&2 tail -5 "$LOG" >&2 || true exit 1 fi exit 0