#!/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()