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

242 lines
9.1 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
# =============================================================================
# hourly_status.sh — FRANKENSTALLM 3B Hourly Training Status Report (Telegram)
# Run: every hour via cron
# Sends a rich formatted message with progress, loss, ETA, GPU/disk summary.
# =============================================================================
set -euo pipefail
# ─── Paths ───────────────────────────────────────────────────────────────────
WORKDIR="${WORKDIR:-$(cd "$(dirname "$0")/.." && pwd)}"
CKPT_DIR="$WORKDIR/checkpoints/korean_3b_fp8_run1"
LOG_FILE="$CKPT_DIR/train.log"
PID_FILE="$CKPT_DIR/train.pid"
HOURLY_LOG="$CKPT_DIR/hourly_status.log"
NOTIFY="python3 $WORKDIR/scripts/telegram_notify.py"
TOTAL_STEPS=57000
TOTAL_TOKENS_B=114 # billion tokens target (57K steps × batch)
# ─── Helpers ─────────────────────────────────────────────────────────────────
ts() { date '+%Y-%m-%d %H:%M:%S'; }
log() { echo "[$(ts)] $*"; }
# Safely get last matching value from log
parse_last() {
local pattern="$1"
grep -oP "$pattern" "$LOG_FILE" 2>/dev/null | tail -1 || echo ""
}
# ─── Parse training log ───────────────────────────────────────────────────────
parse_log() {
if [[ ! -f "$LOG_FILE" ]]; then
echo "NO_LOG"
return 1
fi
# Get the last step line
LAST_LINE=$(grep -E 'step\s+[0-9]+.*loss' "$LOG_FILE" 2>/dev/null | tail -1 || echo "")
if [[ -z "$LAST_LINE" ]]; then
echo "NO_STEPS"
return 1
fi
CURRENT_STEP=$(echo "$LAST_LINE" | grep -oP 'step\s+\K[0-9]+' || echo "0")
CURRENT_LOSS=$(echo "$LAST_LINE" | grep -oP 'loss\s+\K[0-9.]+' || echo "N/A")
CURRENT_LR=$(echo "$LAST_LINE" | grep -oP 'lr\s+\K[0-9.e+-]+' || echo "N/A")
CURRENT_GNORM=$(echo "$LAST_LINE" | grep -oP 'gnorm\s+\K[0-9.]+' || echo "N/A")
CURRENT_TOKPS=$(echo "$LAST_LINE" | grep -oP 'tok/s\s+\K[\d,]+' | tr -d ',' || echo "0")
CURRENT_MEM=$(echo "$LAST_LINE" | grep -oP 'mem\s+\K[0-9.]+GB' || echo "N/A")
CURRENT_EPOCH=$(echo "$LAST_LINE" | grep -oP 'epoch\s+\K[0-9]+' || echo "0")
# Log timestamp — parse from the line itself
LOG_TS=$(echo "$LAST_LINE" | grep -oP '\[\K[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}' || echo "unknown")
return 0
}
# ─── Calculate progress & ETA ─────────────────────────────────────────────────
compute_eta() {
local step="$1"
local tokps="$2"
# Progress
PROGRESS_PCT=$(echo "scale=1; $step * 100 / $TOTAL_STEPS" | bc -l 2>/dev/null || echo "0")
# Steps remaining
STEPS_LEFT=$(( TOTAL_STEPS - step ))
# Tokens processed so far (approx: step × 2M tokens/step for 3B, bs=4, seqlen=4096, 8gpu)
# bs=4, accum=8, 8gpu → effective batch = 4*8*8=256 sequences × 4096 tokens = 1,048,576 ≈ 1M tok/step
TOKENS_PROCESSED_B=$(echo "scale=2; $step * 1048576 / 1000000000" | bc -l 2>/dev/null || echo "0")
# ETA using current tok/s
if [[ "$tokps" -gt 0 ]]; then
# tokens remaining
local tokens_left_b
tokens_left_b=$(echo "scale=2; ($TOTAL_STEPS - $step) * 1048576 / 1000000000" | bc -l 2>/dev/null || echo "0")
local tokens_left
tokens_left=$(echo "scale=0; ($TOTAL_STEPS - $step) * 1048576" | bc -l 2>/dev/null || echo "0")
local secs_left
secs_left=$(echo "scale=0; $tokens_left / $tokps" | bc -l 2>/dev/null || echo "0")
ETA_HOURS=$(echo "scale=1; $secs_left / 3600" | bc -l 2>/dev/null || echo "N/A")
if [[ "$ETA_HOURS" != "N/A" ]]; then
local eta_epoch
eta_epoch=$(( $(date +%s) + secs_left ))
ETA_DATETIME=$(date -d "@$eta_epoch" '+%m/%d %H:%M' 2>/dev/null || echo "N/A")
else
ETA_DATETIME="N/A"
fi
else
ETA_HOURS="N/A"
ETA_DATETIME="N/A"
fi
}
# ─── GPU summary ─────────────────────────────────────────────────────────────
get_gpu_summary() {
if ! command -v nvidia-smi &>/dev/null; then
GPU_SUMMARY="nvidia-smi not available"
GPU_AVG_UTIL="N/A"
GPU_TOTAL_MEM="N/A"
return
fi
local raw
raw=$(nvidia-smi --query-gpu=index,utilization.gpu,memory.used,memory.total \
--format=csv,noheader,nounits 2>/dev/null || echo "")
if [[ -z "$raw" ]]; then
GPU_SUMMARY="GPU query failed"
GPU_AVG_UTIL="N/A"
GPU_TOTAL_MEM="N/A"
return
fi
# avg util
GPU_AVG_UTIL=$(echo "$raw" | awk -F', ' '{sum+=$2; count++} END {printf "%.0f%%", sum/count}')
# total mem used / total
GPU_TOTAL_MEM=$(echo "$raw" | awk -F', ' \
'{used+=$3; total+=$4} END {printf "%.1f / %.1f GiB", used/1024, total/1024}')
# Per-GPU one-liner: "G0:95% 48G | G1:94% 48G | ..."
GPU_SUMMARY=$(echo "$raw" | awk -F', ' \
'{printf "G%s:%s%% %sMiB | ", $1, $2, $3}' | sed 's/ | $//')
}
# ─── Disk usage ──────────────────────────────────────────────────────────────
get_disk_info() {
DISK_INFO=$(df -h "$CKPT_DIR" 2>/dev/null | awk 'NR==2 {printf "%s used / %s total (%s)", $3, $2, $5}' || echo "N/A")
CKPT_COUNT=$(ls -d "$CKPT_DIR"/checkpoint-* 2>/dev/null | wc -l || echo "0")
LAST_CKPT=$(ls -dt "$CKPT_DIR"/checkpoint-* 2>/dev/null | head -1 | xargs basename 2>/dev/null || echo "none")
}
# ─── Process status ───────────────────────────────────────────────────────────
get_process_status() {
PROC_STATUS="UNKNOWN"
if [[ -f "$PID_FILE" ]]; then
local pid
pid=$(cat "$PID_FILE" 2>/dev/null | tr -d '[:space:]')
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
PROC_STATUS="RUNNING (PID $pid)"
else
PROC_STATUS="STOPPED (PID $pid)"
fi
else
PROC_STATUS="NO PID FILE"
fi
}
# ─── Build & send message ────────────────────────────────────────────────────
build_and_send() {
local step="$CURRENT_STEP"
local loss="$CURRENT_LOSS"
local tokps="$CURRENT_TOKPS"
# Status icon
local status_icon
if [[ "$PROC_STATUS" == RUNNING* ]]; then
status_icon="✅" # green check
else
status_icon="❌" # red X
fi
# Progress bar (20 chars)
local bar_filled=$(echo "scale=0; $PROGRESS_PCT * 20 / 100" | bc -l 2>/dev/null || echo "0")
local bar_empty=$(( 20 - bar_filled ))
PROGRESS_BAR=$(printf '%0.s█' $(seq 1 $bar_filled 2>/dev/null) ; printf '%0.s░' $(seq 1 $bar_empty 2>/dev/null)) || PROGRESS_BAR="[$PROGRESS_PCT%]"
local msg
msg="$(cat <<EOF
<b>FRANKENSTALLM 3B — Hourly Status</b>
<i>$(ts)</i>
$status_icon <b>Process:</b> $PROC_STATUS
<b>Progress</b>
Step: <code>$step / $TOTAL_STEPS</code> ($PROGRESS_PCT%)
Tokens: <code>${TOKENS_PROCESSED_B}B / ${TOTAL_TOKENS_B}B</code>
Epoch: <code>$CURRENT_EPOCH</code>
Last log: <code>$LOG_TS</code>
<b>Training Metrics</b>
Loss: <code>$loss</code>
LR: <code>$CURRENT_LR</code>
Gnorm: <code>$CURRENT_GNORM</code>
Tok/s: <code>$tokps</code>
Mem: <code>$CURRENT_MEM</code>
<b>ETA</b>
Steps left: <code>$STEPS_LEFT</code>
Remaining: <code>~$ETA_HOURS h</code>
Est. done: <code>$ETA_DATETIME</code>
<b>GPU</b>
Avg util: <code>$GPU_AVG_UTIL</code>
Total mem: <code>$GPU_TOTAL_MEM</code>
<b>Checkpoints</b>
Last saved: <code>$LAST_CKPT</code>
Total: <code>$CKPT_COUNT</code> checkpoints
<b>Disk</b>
<code>$DISK_INFO</code>
EOF
)"
log "Sending hourly status report (step $step)..."
$NOTIFY "$msg" || {
log "ERROR: Failed to send Telegram message."
return 1
}
log "Status report sent."
}
# ─── Main ────────────────────────────────────────────────────────────────────
main() {
log "=== Hourly status START ==="
parse_log || {
log "Cannot parse log — sending minimal status."
$NOTIFY "<b>FRANKENSTALLM 3B</b> — Status check at $(ts)
<b>WARNING:</b> Cannot read training log at:
<code>$LOG_FILE</code>
Process status: $(cat "$PID_FILE" 2>/dev/null && echo "(PID found)" || echo "(no PID file)")" || true
return 0
}
compute_eta "$CURRENT_STEP" "$CURRENT_TOKPS"
get_gpu_summary
get_disk_info
get_process_status
build_and_send
log "=== Hourly status END ==="
}
main "$@"