183 lines
5.7 KiB
Python
183 lines
5.7 KiB
Python
|
|
#!/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()
|