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

317 lines
12 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_3b.sh — 3B 학습 실시간 모니터링 + 이상 감지 + 자동 체크포인트 정리
#
# Usage:
# bash scripts/monitor_3b.sh # 기본 감시
# bash scripts/monitor_3b.sh --check-once # 1회 검사
# bash scripts/monitor_3b.sh --auto-cleanup # 자동 오래된 체크포인트 삭제
#
# 3B 특화 사항:
# - 체크포인트 27GB/개 → 디스크 감시 강화
# - NCCL hang 감지 + 자동 재시작 옵션
# - 예상 완료 시간 실시간 계산
# - 프로세스 중복 실행 방지
# =============================================================================
set -euo pipefail
# ---- Configuration ----------------------------------------------------------
RUN_NAME="${RUN_NAME:-korean_3b_fp8_run1}"
LOG_FILE="${1:-checkpoints/${RUN_NAME}/train.log}"
CKPT_DIR="checkpoints/${RUN_NAME}"
CHECK_INTERVAL=60 # 3B는 step 간격 더 김 → 60초
ZERO_LOSS_THRESHOLD=3
GNORM_WARN=10.0
GNORM_CRITICAL=50.0
LOSS_SPIKE_FACTOR=3.0
STALL_TIMEOUT=600 # 10분 (3B는 step 더 오래 걸림)
DISK_WARN_PCT=85
DISK_CRITICAL_PCT=92
GPU_UTIL_WARN=50
MAX_CHECKPOINTS=15 # 최대 보관 체크포인트 수 (15 × 27GB = 405GB)
CHECK_ONCE=false
AUTO_CLEANUP=false
AUTO_RESTART=false
# Parse args
for arg in "$@"; do
case "$arg" in
--check-once) CHECK_ONCE=true ;;
--auto-cleanup) AUTO_CLEANUP=true ;;
--auto-restart) AUTO_RESTART=true ;;
esac
done
# Fix LOG_FILE if first arg was a flag
if [[ "$LOG_FILE" == --* ]]; then
LOG_FILE="checkpoints/${RUN_NAME}/train.log"
fi
# ---- Colors -----------------------------------------------------------------
RED='\033[0;31m'; YELLOW='\033[1;33m'; GREEN='\033[0;32m'
CYAN='\033[0;36m'; MAGENTA='\033[0;35m'; NC='\033[0m'
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 metrics ----------------------------------------------------------
parse_metrics() {
local n="${1:-20}"
[[ -f "$LOG_FILE" ]] || return
tail -n "$n" "$LOG_FILE" | grep "step.*loss.*gnorm" || true
}
extract_field() {
echo "$1" | grep -oP "${2}\s+\K[0-9]+\.[0-9e+\-]+" | head -1
}
extract_step() {
echo "$1" | grep -oP "step\s+\K[0-9]+" | head -1
}
# ---- Check: Loss = 0 -------------------------------------------------------
check_loss_zero() {
local lines
lines=$(parse_metrics "$ZERO_LOSS_THRESHOLD")
[[ -z "$lines" ]] && return 0
local zero_count=0
while IFS= read -r line; do
local loss=$(extract_field "$line" "loss")
if [[ -n "$loss" ]] && (( $(echo "$loss < 0.001" | bc -l 2>/dev/null || echo 0) )); then
((zero_count++))
fi
done <<< "$lines"
if [[ $zero_count -ge $ZERO_LOSS_THRESHOLD ]]; then
alert CRITICAL "Loss가 ${zero_count}회 연속 ~0! Labels 버그. 즉시 중단!"
return 1
fi
}
# ---- Check: Loss spike -----------------------------------------------------
check_loss_spike() {
local lines=$(parse_metrics 20)
[[ -z "$lines" ]] && return 0
local losses=()
while IFS= read -r line; do
local loss=$(extract_field "$line" "loss")
[[ -n "$loss" ]] && losses+=("$loss")
done <<< "$lines"
local count=${#losses[@]}
[[ $count -lt 5 ]] && return 0
local last="${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 / $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}, 평균=${avg}, 비율=${ratio}x"
fi
fi
}
# ---- Check: Gradient norm ---------------------------------------------------
check_gnorm() {
local lines=$(parse_metrics 5)
[[ -z "$lines" ]] && return 0
local gnorm=$(extract_field "$(echo "$lines" | tail -1)" "gnorm")
[[ -z "$gnorm" ]] && return 0
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} 불안정."
fi
}
# ---- Check: Stall / NCCL hang ----------------------------------------------
check_stall() {
[[ ! -f "$LOG_FILE" ]] && return 0
local last_mod=$(stat -c %Y "$LOG_FILE" 2>/dev/null || echo 0)
local now=$(date +%s)
local diff=$((now - last_mod))
if [[ $diff -gt $STALL_TIMEOUT ]]; then
alert CRITICAL "로그 ${diff}초 ($(( diff/60 ))분) 멈춤! NCCL hang 가능성."
# NCCL hang 자동 재시작
if $AUTO_RESTART; then
alert WARNING "자동 재시작 시도..."
local pid=$(pgrep -f "pretrain.py.*korean_3b" | head -1 || true)
if [[ -n "$pid" ]]; then
kill -9 "$pid" 2>/dev/null || true
sleep 5
alert INFO "이전 프로세스 종료. launch_3b_pretrain.sh 재실행 필요."
fi
fi
fi
}
# ---- Check: Disk (3B 강화) --------------------------------------------------
check_disk() {
local usage=$(df /PROJECT 2>/dev/null | awk 'NR==2 {print $5}' | tr -d '%')
if [[ -n "$usage" && "$usage" -gt "$DISK_CRITICAL_PCT" ]]; then
alert CRITICAL "디스크 ${usage}% > ${DISK_CRITICAL_PCT}%! 즉시 정리 필요!"
$AUTO_CLEANUP && cleanup_old_checkpoints
elif [[ -n "$usage" && "$usage" -gt "$DISK_WARN_PCT" ]]; then
alert WARNING "디스크 ${usage}% > ${DISK_WARN_PCT}%. 체크포인트 정리 권장."
fi
}
# ---- Check: GPU utilization -------------------------------------------------
check_gpu() {
command -v nvidia-smi &>/dev/null || return 0
local low=0 total=0
while IFS= read -r util; do
((total++))
[[ "$util" -lt "$GPU_UTIL_WARN" ]] && ((low++))
done < <(nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits 2>/dev/null)
[[ $total -gt 0 && $low -gt 0 ]] && alert INFO "${low}/${total} GPU util < ${GPU_UTIL_WARN}%"
}
# ---- Check: 체크포인트 무결성 -----------------------------------------------
check_checkpoint_integrity() {
local latest=$(ls -d "${CKPT_DIR}"/checkpoint-* 2>/dev/null | sort -V | tail -1 || true)
[[ -z "$latest" ]] && return 0
# 최소 파일 존재 확인
if [[ ! -f "${latest}/model.pt" ]] && [[ ! -f "${latest}/model.safetensors" ]]; then
alert WARNING "최근 체크포인트에 모델 파일 없음: ${latest}"
fi
# 크기 확인 (3B model.pt는 최소 2GB)
local size=$(du -sb "${latest}" 2>/dev/null | awk '{print $1}')
if [[ -n "$size" && "$size" -lt 2000000000 ]]; then
alert WARNING "체크포인트 크기 비정상 (${size} bytes < 2GB): ${latest}"
fi
}
# ---- Cleanup: 오래된 체크포인트 자동 삭제 ------------------------------------
cleanup_old_checkpoints() {
local ckpts=($(ls -d "${CKPT_DIR}"/checkpoint-* 2>/dev/null | sort -V))
local count=${#ckpts[@]}
if [[ $count -le $MAX_CHECKPOINTS ]]; then
alert OK "체크포인트 ${count}개 ≤ ${MAX_CHECKPOINTS}. 정리 불필요."
return
fi
# 이정표 체크포인트 보존 (매 10K step)
local deletable=()
local preserved=()
for ckpt in "${ckpts[@]}"; do
local step_num=$(basename "$ckpt" | grep -oP '\d+' || echo "0")
if (( step_num % 10000 == 0 && step_num > 0 )); then
preserved+=("$ckpt")
else
deletable+=("$ckpt")
fi
done
# 최근 MAX_CHECKPOINTS개는 무조건 보존
local n_deletable=${#deletable[@]}
local total_keep=$(( ${#preserved[@]} + MAX_CHECKPOINTS ))
local to_delete=$(( count - total_keep ))
[[ $to_delete -le 0 ]] && { alert OK "정리 불필요 (이정표 ${#preserved[@]}개 + 최근 ${MAX_CHECKPOINTS}개 보존)."; return; }
alert INFO "${count}개 체크포인트 → ${to_delete}개 삭제 (이정표 ${#preserved[@]}개 영구 보존)"
local deleted=0
for ckpt in "${deletable[@]}"; do
[[ $deleted -ge $to_delete ]] && break
local ckpt_size=$(du -sh "$ckpt" 2>/dev/null | awk '{print $1}')
echo " 삭제: $ckpt (${ckpt_size})"
rm -rf "$ckpt"
((deleted++))
done
alert OK "체크포인트 정리 완료. (${deleted}개 삭제)"
}
# ---- ETA 계산 ---------------------------------------------------------------
estimate_eta() {
[[ ! -f "$LOG_FILE" ]] && return
# 최근 step 번호 + 시간
local lines=$(parse_metrics 50)
[[ -z "$lines" ]] && return
local last_line=$(echo "$lines" | tail -1)
local first_line=$(echo "$lines" | head -1)
local cur_step=$(extract_step "$last_line")
local max_steps=$(grep -oP "max_steps.*?(\d+)" "${CKPT_DIR}/train.log" 2>/dev/null | head -1 | grep -oP '\d+$' || echo "57000")
[[ -z "$cur_step" || "$cur_step" == "0" ]] && return
# step/sec from log timestamps (approximate)
local remaining=$((max_steps - cur_step))
if [[ $remaining -le 0 ]]; then
echo -e "${MAGENTA}📊 진행: ${cur_step}/${max_steps} (완료!)${NC}"
return
fi
# 파일 수정 시간 기반 rough ETA
local first_time=$(head -20 "$LOG_FILE" 2>/dev/null | grep -oP '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}' | head -1 || true)
if [[ -n "$first_time" ]]; then
local start_epoch=$(date -d "$first_time" +%s 2>/dev/null || echo 0)
local now=$(date +%s)
if [[ $start_epoch -gt 0 && $cur_step -gt 0 ]]; then
local elapsed=$((now - start_epoch))
local sec_per_step=$(echo "$elapsed / $cur_step" | bc -l 2>/dev/null || echo "0")
local eta_sec=$(echo "$remaining * $sec_per_step" | bc 2>/dev/null | cut -d. -f1 || echo "0")
local eta_hours=$(echo "$eta_sec / 3600" | bc 2>/dev/null || echo "?")
local pct=$(echo "scale=1; $cur_step * 100 / $max_steps" | bc 2>/dev/null || echo "?")
echo -e "${MAGENTA}📊 진행: ${cur_step}/${max_steps} (${pct}%) | 남은 시간: ~${eta_hours}h | ${sec_per_step}s/step${NC}"
fi
else
echo -e "${MAGENTA}📊 진행: ${cur_step}/${max_steps}${NC}"
fi
}
# ---- Status summary ---------------------------------------------------------
print_status() {
local lines=$(parse_metrics 1)
[[ -n "$lines" ]] && echo -e "${GREEN}최근:${NC} $lines"
estimate_eta
if command -v nvidia-smi &>/dev/null; then
echo -e "${CYAN}GPU:${NC}"
nvidia-smi --query-gpu=index,memory.used,memory.total,utilization.gpu,temperature.gpu \
--format=csv,noheader 2>/dev/null | head -8
fi
local ckpt_count=$(ls -d "${CKPT_DIR}"/checkpoint-* 2>/dev/null | wc -l)
local ckpt_size=$(du -sh "${CKPT_DIR}" 2>/dev/null | awk '{print $1}')
echo -e "${CYAN}체크포인트:${NC} ${ckpt_count}개 (${ckpt_size})"
local disk=$(df -h /PROJECT 2>/dev/null | awk 'NR==2 {print $3"/"$2" ("$5")"}')
echo -e "${CYAN}디스크:${NC} ${disk}"
}
# ---- Main -------------------------------------------------------------------
echo "=================================================================="
echo " 3B Training Monitor"
echo " Run: ${RUN_NAME}"
echo " Log: ${LOG_FILE}"
echo " Interval: ${CHECK_INTERVAL}s"
echo " Auto-cleanup: ${AUTO_CLEANUP} | Auto-restart: ${AUTO_RESTART}"
echo " 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
check_checkpoint_integrity || 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