#!/bin/bash # ============================================================================= # GPU Watchdog for AMD BC-250 (Cyan Skillfish) # ============================================================================= # Monitors kernel logs for KIQ fence timeouts and immediately kills the # offending GPU process before the timeout cascade crashes the system. # # BC-250 Crash Pattern: # 1st KIQ timeout → 12s → 2nd KIQ timeout → 12s → cascade → hard crash # Window to act: ~10 seconds after first timeout # # Usage: # ./gpu_watchdog.sh # Monitor and auto-kill # ./gpu_watchdog.sh --dry-run # Monitor only, don't kill # ./gpu_watchdog.sh --max-kiq 2 # Kill after 2 KIQ timeouts # ============================================================================= set -euo pipefail MAX_KIQ_TIMEOUTS=1 CHECK_INTERVAL=2 DRY_RUN=false LOG_FILE="/tmp/gpu_watchdog.log" while [[ $# -gt 0 ]]; do case "$1" in --dry-run) DRY_RUN=true; shift ;; --max-kiq) MAX_KIQ_TIMEOUTS="$2"; shift 2 ;; --interval) CHECK_INTERVAL="$2"; shift 2 ;; *) echo "Unknown: $1"; exit 1 ;; esac done log() { local msg="[$(date '+%H:%M:%S')] $1" echo "$msg" echo "$msg" >> "$LOG_FILE" } get_kiq_count() { local count count=$(journalctl -k -b --no-pager 2>/dev/null | grep -c "timeout waiting for kiq fence" | head -1 | tr -d '[:space:]') echo "${count:-0}" } kill_gpu_processes() { local pids pids=$(fuser /dev/kfd 2>/dev/null || pgrep -f "sdcpp-restapi" 2>/dev/null || true) pids=$(echo "$pids" | xargs) if [[ -z "$pids" ]]; then log "WARN: No GPU processes found" return 1 fi for pid in $pids; do local name name=$(ps -p "$pid" -o comm= 2>/dev/null || echo "?") if [[ "$DRY_RUN" == "true" ]]; then log "DRY-RUN: Would SIGKILL PID $pid ($name)" else log "SIGKILL PID $pid ($name)" kill -9 "$pid" 2>/dev/null || true fi done if [[ "$DRY_RUN" == "false" ]]; then systemctl --user stop zimage 2>/dev/null || true log "Stopped zimage service" fi } echo "==========================================" echo " GPU Watchdog — AMD BC-250" echo " Kill after: $MAX_KIQ_TIMEOUTS KIQ timeout(s)" echo " Interval: ${CHECK_INTERVAL}s" echo " Dry run: $DRY_RUN" echo "==========================================" log "Watchdog started" INITIAL_KIQ=$(get_kiq_count) PREV_KIQ=$INITIAL_KIQ log "Baseline KIQ count: $INITIAL_KIQ" [[ "$INITIAL_KIQ" -gt 0 ]] && log "WARNING: GPU already has $INITIAL_KIQ pre-existing KIQ timeouts" while true; do CUR=$(get_kiq_count) NEW=$((CUR - INITIAL_KIQ)) if [[ "$CUR" -ne "$PREV_KIQ" ]]; then log "!! KIQ timeout #$CUR (new=$NEW)" if [[ "$NEW" -ge "$MAX_KIQ_TIMEOUTS" ]]; then log "!!! THRESHOLD HIT — EMERGENCY KILL !!!" kill_gpu_processes log "GPU processes killed. Reboot needed for clean GPU." INITIAL_KIQ=$CUR log "Watchdog reset, continuing..." fi fi PREV_KIQ=$CUR sleep "$CHECK_INTERVAL" done