guanghulab/scripts/watch_distill.py

183 lines
5.7 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""蒸馏训练实时监控 — 在GPU服务器终端直接运行
不刷新不覆盖只追加新行保持完整的输出历史
用法GPU服务器上
cd /root/autodl-tmp
python3 scripts/watch_distill.py
或者直接
python3 /root/autodl-tmp/scripts/watch_distill.py
"""
import time, re, os, sys
from datetime import datetime
LOG = "/root/autodl-tmp/distill_mother.log"
OUT = "/root/autodl-tmp/output/qwen25-15b-shuangyan-distill"
def fmt_time():
return datetime.now().strftime("%H:%M:%S")
def get_gpu():
"""解析nvidia-smi输出"""
try:
r = os.popen(
"nvidia-smi --query-gpu=memory.used,memory.total,utilization.gpu,temperature.gpu "
"--format=csv,noheader,nounits 2>/dev/null"
).read().strip()
if not r:
return "N/A"
parts = [p.strip() for p in r.split(", ")]
if len(parts) >= 2:
used, total = parts[0], parts[1]
pct = int(used) / int(total) * 100 if int(total) > 0 else 0
gpu_util = parts[2] if len(parts) >= 3 else "?"
temp = parts[3] if len(parts) >= 4 else "?"
return f"显存: {used}/{total} MiB ({pct:.0f}%) | GPU: {gpu_util}% | 温度: {temp}°C"
return r
except:
return "N/A"
def parse_loss(line):
"""从训练日志行解析loss"""
m = re.search(r"'loss':\s*'?([\d.]+)'?", line)
if m:
return float(m.group(1))
m = re.search(r"loss[=:]\s*([\d.]+)", line)
if m:
return float(m.group(1))
m = re.search(r"loss=([\d.]+)", line)
if m:
return float(m.group(1))
return None
def parse_step(line):
"""解析训练步数和epoch"""
m = re.search(r"(\d+)/(\d+)\s+\[", line)
if m:
return int(m.group(1)), int(m.group(2))
m = re.search(r"step=(\d+)", line)
if m:
return int(m.group(1)), None
return None, None
def parse_progress(line):
"""解析进度条百分比"""
m = re.search(r"(\d+)%\|", line)
if m:
return int(m.group(1))
return None
def parse_eta(line):
"""解析剩余时间"""
m = re.search(r"<(\d+:\d+)", line)
if m:
return m.group(1)
return None
def parse_epoch(line):
m = re.search(r"'epoch':\s*'?([\d.]+)'?", line)
if m:
return float(m.group(1))
return None
def main():
print("=" * 60)
print(f" 铸渊蒸馏监控 · {fmt_time()}")
print(f" Watch: {LOG}")
print(f" 不刷新不覆盖,只追加新行")
print("=" * 60)
print()
# 先读已有日志
last_size = 0
if os.path.exists(LOG):
last_size = os.path.getsize(LOG)
with open(LOG) as f:
for line in f:
line = line.rstrip()
if line:
print(f" [{fmt_time()}] {line}")
gpu_interval = 15 # 每15秒打一次GPU状态
last_gpu = 0
last_loss = None
last_step = None
total_steps = None
last_epoch = None
progress = None
print()
print("-" * 40)
print(f" [{fmt_time()}] 🔄 进入实时监控模式每2秒刷新")
print("-" * 40)
sys.stdout.flush()
try:
while True:
now = time.time()
# 读新增日志行
if os.path.exists(LOG):
new_size = os.path.getsize(LOG)
if new_size > last_size:
with open(LOG) as f:
f.seek(last_size)
for line in f:
line = line.rstrip()
if not line:
continue
print(f" [{fmt_time()}] {line}")
# 解析关键指标
loss = parse_loss(line)
if loss is not None:
last_loss = loss
step, total = parse_step(line)
if step is not None:
last_step = step
if total is not None:
total_steps = total
pct = parse_progress(line)
if pct is not None:
progress = pct
epoch = parse_epoch(line)
if epoch is not None:
last_epoch = epoch
last_size = new_size
sys.stdout.flush()
# 每15秒打一次GPU和进度摘要
if now - last_gpu >= gpu_interval:
last_gpu = now
gpu_info = get_gpu()
summary_parts = [f"[{fmt_time()}] 📊 {gpu_info}"]
if last_loss is not None:
summary_parts.append(f"loss={last_loss:.4f}")
if last_step is not None and total_steps is not None:
summary_parts.append(f"step={last_step}/{total_steps}")
if last_epoch is not None:
summary_parts.append(f"epoch={last_epoch:.2f}")
if progress is not None:
summary_parts.append(f"progress={progress}%")
print(f" {' | '.join(summary_parts)}")
sys.stdout.flush()
time.sleep(2)
except KeyboardInterrupt:
print()
print(f" [{fmt_time()}] 👋 监控已退出")
print(f" 最后状态: loss={last_loss}, step={last_step}/{total_steps}, epoch={last_epoch}")
sys.exit(0)
if __name__ == "__main__":
main()