Files
frankenstallm/source/scripts/monitor_training.sh
ModelHub XC d4abdb70fa 初始化项目,由ModelHub XC社区提供模型
Model: pathcosmos/frankenstallm
Source: Original Platform
2026-07-14 04:21:16 +08:00

245 lines
7.5 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# =============================================================================
# monitor_training.sh — SFT 학습 실시간 모니터링 + 이상 감지
#
# Usage:
# bash scripts/monitor_training.sh # 기본 로그 경로
# bash scripts/monitor_training.sh /path/to/train.log # 커스텀 경로
# bash scripts/monitor_training.sh --check-once # 1회 검사 후 종료
#
# 감시 항목:
# 🔴 loss = 0.0000 (3 step 연속) → Labels 버그
# 🔴 gnorm > 50.0 → 발산 직전
# 🔴 로그 5분 이상 멈춤 → Hang
# 🟠 loss spike (3× 이동평균) → Bad batch / LR
# 🟠 gnorm > 10.0 → 불안정
# 🟠 디스크 > 80% → 정리 필요
# 🟡 GPU util < 50% → 병목
# =============================================================================
set -euo pipefail
# ---- Configuration ----------------------------------------------------------
LOG_FILE="${1:-checkpoints/korean_1b_sft/train.log}"
CHECK_INTERVAL=30 # 초 단위 폴링 간격
ZERO_LOSS_THRESHOLD=3 # N회 연속 loss=0이면 경고
GNORM_WARN=10.0
GNORM_CRITICAL=50.0
LOSS_SPIKE_FACTOR=3.0 # 이동평균 대비 N배 이상이면 spike
STALL_TIMEOUT=300 # 초 (5분) 로그 멈춤 감지
DISK_WARN_PCT=80
GPU_UTIL_WARN=50
CHECK_ONCE=false
if [[ "${1:-}" == "--check-once" ]]; then
CHECK_ONCE=true
LOG_FILE="${2:-checkpoints/korean_1b_sft/train.log}"
fi
# ---- Colors -----------------------------------------------------------------
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
CYAN='\033[0;36m'
NC='\033[0m'
# ---- Helper -----------------------------------------------------------------
timestamp() { date '+%Y-%m-%d %H:%M:%S'; }
alert() {
local level="$1" msg="$2"
case "$level" in
CRITICAL) echo -e "${RED}🔴 [$(timestamp)] [CRITICAL] ${msg}${NC}" ;;
WARNING) echo -e "${YELLOW}🟠 [$(timestamp)] [WARNING] ${msg}${NC}" ;;
INFO) echo -e "${CYAN}🟡 [$(timestamp)] [INFO] ${msg}${NC}" ;;
OK) echo -e "${GREEN}✅ [$(timestamp)] [OK] ${msg}${NC}" ;;
esac
}
# ---- Parse last N log lines -------------------------------------------------
parse_metrics() {
# 로그 형식: [timestamp] [INFO] step XXXX | loss X.XXXX | lr X.XXe-XX | gnorm X.XXX | ...
local n="${1:-20}"
if [[ ! -f "$LOG_FILE" ]]; then
echo ""
return
fi
tail -n "$n" "$LOG_FILE" | grep "step.*loss.*gnorm" || true
}
extract_field() {
# $1=line, $2=field name (loss, gnorm, lr)
echo "$1" | grep -oP "${2}\s+\K[0-9]+\.[0-9e+\-]+" | head -1
}
# ---- Check functions --------------------------------------------------------
check_loss_zero() {
local lines
lines=$(parse_metrics "$ZERO_LOSS_THRESHOLD")
if [[ -z "$lines" ]]; then return; fi
local zero_count=0
while IFS= read -r line; do
local loss
loss=$(extract_field "$line" "loss")
if [[ -n "$loss" ]]; then
# loss < 0.001
if (( $(echo "$loss < 0.001" | bc -l 2>/dev/null || echo 0) )); then
((zero_count++))
fi
fi
done <<< "$lines"
if [[ $zero_count -ge $ZERO_LOSS_THRESHOLD ]]; then
alert CRITICAL "Loss가 ${zero_count}회 연속 ~0! Labels 버그 가능성. 즉시 학습 중단!"
return 1
fi
return 0
}
check_loss_spike() {
local lines
lines=$(parse_metrics 20)
if [[ -z "$lines" ]]; then return 0; fi
local losses=()
while IFS= read -r line; do
local loss
loss=$(extract_field "$line" "loss")
[[ -n "$loss" ]] && losses+=("$loss")
done <<< "$lines"
local count=${#losses[@]}
if [[ $count -lt 5 ]]; then return 0; fi
# 마지막 값과 이전 평균 비교
local last_loss="${losses[$((count-1))]}"
local sum=0
for ((i=0; i<count-1; i++)); do
sum=$(echo "$sum + ${losses[$i]}" | bc -l 2>/dev/null || echo "$sum")
done
local avg=$(echo "$sum / ($count - 1)" | bc -l 2>/dev/null || echo "0")
if [[ "$avg" != "0" ]]; then
local ratio=$(echo "$last_loss / $avg" | bc -l 2>/dev/null || echo "1")
if (( $(echo "$ratio > $LOSS_SPIKE_FACTOR" | bc -l 2>/dev/null || echo 0) )); then
alert WARNING "Loss spike 감지! 현재=${last_loss}, 평균=${avg}, 비율=${ratio}x"
fi
fi
return 0
}
check_gnorm() {
local lines
lines=$(parse_metrics 5)
if [[ -z "$lines" ]]; then return 0; fi
local last_line
last_line=$(echo "$lines" | tail -1)
local gnorm
gnorm=$(extract_field "$last_line" "gnorm")
if [[ -z "$gnorm" ]]; then return 0; fi
if (( $(echo "$gnorm > $GNORM_CRITICAL" | bc -l 2>/dev/null || echo 0) )); then
alert CRITICAL "GNorm=${gnorm} > ${GNORM_CRITICAL}! 발산 직전. 학습 중단 고려."
elif (( $(echo "$gnorm > $GNORM_WARN" | bc -l 2>/dev/null || echo 0) )); then
alert WARNING "GNorm=${gnorm} > ${GNORM_WARN}. 불안정 징후."
fi
return 0
}
check_stall() {
if [[ ! -f "$LOG_FILE" ]]; then
alert INFO "로그 파일 없음: ${LOG_FILE}"
return 0
fi
local last_modified
last_modified=$(stat -c %Y "$LOG_FILE" 2>/dev/null || echo 0)
local now
now=$(date +%s)
local diff=$((now - last_modified))
if [[ $diff -gt $STALL_TIMEOUT ]]; then
alert CRITICAL "로그가 ${diff}초 ($(( diff/60 ))분) 동안 업데이트 없음! Hang 가능성."
fi
return 0
}
check_disk() {
local usage
usage=$(df /PROJECT 2>/dev/null | awk 'NR==2 {print $5}' | tr -d '%')
if [[ -n "$usage" && "$usage" -gt "$DISK_WARN_PCT" ]]; then
alert WARNING "디스크 사용률 ${usage}% > ${DISK_WARN_PCT}%. 체크포인트 정리 필요."
fi
return 0
}
check_gpu() {
if ! command -v nvidia-smi &>/dev/null; then return 0; fi
local low_util=0
local total_gpus=0
while IFS= read -r util; do
((total_gpus++))
if [[ "$util" -lt "$GPU_UTIL_WARN" ]]; then
((low_util++))
fi
done < <(nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits 2>/dev/null)
if [[ $total_gpus -gt 0 && $low_util -gt 0 ]]; then
alert INFO "${low_util}/${total_gpus} GPU utilization < ${GPU_UTIL_WARN}%. 데이터 로딩 병목?"
fi
return 0
}
# ---- Status summary ---------------------------------------------------------
print_status() {
local lines
lines=$(parse_metrics 1)
if [[ -n "$lines" ]]; then
echo -e "${GREEN}최근 로그:${NC} $lines"
fi
if command -v nvidia-smi &>/dev/null; then
echo -e "${CYAN}GPU 메모리:${NC}"
nvidia-smi --query-gpu=index,memory.used,memory.total,utilization.gpu \
--format=csv,noheader 2>/dev/null | head -8
fi
local disk
disk=$(df -h /PROJECT 2>/dev/null | awk 'NR==2 {print "사용: "$3"/"$2" ("$5")"}')
echo -e "${CYAN}디스크:${NC} ${disk}"
}
# ---- Main loop --------------------------------------------------------------
echo "=================================================================="
echo " SFT Training Monitor"
echo " Log file: ${LOG_FILE}"
echo " Check interval: ${CHECK_INTERVAL}s"
echo " Press Ctrl+C to stop"
echo "=================================================================="
run_all_checks() {
check_loss_zero || true
check_loss_spike || true
check_gnorm || true
check_stall || true
check_disk || true
check_gpu || true
echo "---"
print_status
echo ""
}
if $CHECK_ONCE; then
run_all_checks
exit 0
fi
while true; do
run_all_checks
sleep "$CHECK_INTERVAL"
done