173 lines
5.7 KiB
Python
173 lines
5.7 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
"""
|
|||
|
|
蒸馏训练监控面板 — 在你本地终端运行
|
|||
|
|
依赖:sshpass (brew install sshpass)
|
|||
|
|
|
|||
|
|
用法:
|
|||
|
|
export ZY_SSH_PASS="你的SSH密码"
|
|||
|
|
python3 scripts/monitor_distill.py
|
|||
|
|
|
|||
|
|
或者放在 ~/.zshrc 里就不用每次export了
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import subprocess, re, sys, time, shutil, os
|
|||
|
|
|
|||
|
|
SSH_HOST = os.environ.get("ZY_SSH_HOST", "connect.westd.seetacloud.com")
|
|||
|
|
SSH_PORT = os.environ.get("ZY_SSH_PORT", "23647")
|
|||
|
|
SSH_USER = os.environ.get("ZY_SSH_USER", "root")
|
|||
|
|
# 从环境变量读取,不硬编码
|
|||
|
|
SSH_PASS = os.environ.get("ZY_SSH_PASS", "")
|
|||
|
|
LOG_FILE = "/root/autodl-tmp/distill_mother.log"
|
|||
|
|
TRAINING_LOG = "/root/autodl-tmp/train_coder.log"
|
|||
|
|
|
|||
|
|
def ssh(cmd):
|
|||
|
|
try:
|
|||
|
|
r = subprocess.run(
|
|||
|
|
["sshpass", "-p", SSH_PASS, "ssh", "-o", "StrictHostKeyChecking=accept-new",
|
|||
|
|
"-o", "ConnectTimeout=5", "-p", SSH_PORT,
|
|||
|
|
f"{SSH_USER}@{SSH_HOST}", cmd],
|
|||
|
|
capture_output=True, text=True, timeout=10
|
|||
|
|
)
|
|||
|
|
return r.stdout, r.stderr
|
|||
|
|
except:
|
|||
|
|
return "", "timeout"
|
|||
|
|
|
|||
|
|
def get_bar(percent, width=20):
|
|||
|
|
filled = int(percent / 100 * width)
|
|||
|
|
return "\u2588" * filled + "\u2591" * (width - filled)
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
print("=" * 60)
|
|||
|
|
print(" 铸渊蒸馏监控面板")
|
|||
|
|
print(" Qwen2.5-7B \u2192 Qwen2.5-1.5B (霜砚模板)")
|
|||
|
|
print("=" * 60)
|
|||
|
|
|
|||
|
|
while True:
|
|||
|
|
stdout, stderr = ssh(f"tail -30 {LOG_FILE} 2>/dev/null; echo '---GPU---'; nvidia-smi --query-gpu=memory.used,memory.total,utilization.gpu,temperature.gpu --format=csv,noheader 2>/dev/null; echo '---DISK---'; df -h /root/autodl-tmp 2>/dev/null | tail -1; echo '---PROC---'; ps aux | grep distill_mother | grep -v grep | wc -l")
|
|||
|
|
|
|||
|
|
lines = stdout.strip().split('\n')
|
|||
|
|
|
|||
|
|
log_lines = []
|
|||
|
|
gpu_info = ""
|
|||
|
|
disk_info = ""
|
|||
|
|
proc_count = "0"
|
|||
|
|
|
|||
|
|
in_gpu = False
|
|||
|
|
in_disk = False
|
|||
|
|
in_proc = False
|
|||
|
|
|
|||
|
|
for line in lines:
|
|||
|
|
if line == '---GPU---':
|
|||
|
|
in_gpu = True; in_disk = False; in_proc = False; continue
|
|||
|
|
elif line == '---DISK---':
|
|||
|
|
in_gpu = False; in_disk = True; in_proc = False; continue
|
|||
|
|
elif line == '---PROC---':
|
|||
|
|
in_gpu = False; in_disk = False; in_proc = True; continue
|
|||
|
|
|
|||
|
|
if in_gpu:
|
|||
|
|
gpu_info = line
|
|||
|
|
elif in_disk:
|
|||
|
|
disk_info = line
|
|||
|
|
elif in_proc:
|
|||
|
|
proc_count = line.strip()
|
|||
|
|
else:
|
|||
|
|
log_lines.append(line)
|
|||
|
|
|
|||
|
|
progress = 0
|
|||
|
|
loss_str = ""
|
|||
|
|
epoch_str = ""
|
|||
|
|
step_str = ""
|
|||
|
|
eta = ""
|
|||
|
|
|
|||
|
|
for line in log_lines:
|
|||
|
|
m = re.search(r'(\d+)%\|', line)
|
|||
|
|
if m:
|
|||
|
|
progress = int(m.group(1))
|
|||
|
|
|
|||
|
|
m = re.search(r'\|[^\|]*\|\s*(\d+)/(\d+)', line)
|
|||
|
|
if m:
|
|||
|
|
step_str = f"{m.group(1)}/{m.group(2)}"
|
|||
|
|
|
|||
|
|
m = re.search(r"'loss':\s*'?([\d.]+)'?", line)
|
|||
|
|
if m:
|
|||
|
|
loss_str = m.group(1)
|
|||
|
|
|
|||
|
|
m = re.search(r"'epoch':\s*'?([\d.]+)'?", line)
|
|||
|
|
if m:
|
|||
|
|
epoch_str = m.group(1)
|
|||
|
|
|
|||
|
|
m = re.search(r'<(\d+:\d+)', line)
|
|||
|
|
if m:
|
|||
|
|
eta = m.group(1)
|
|||
|
|
|
|||
|
|
subprocess.run(["clear"])
|
|||
|
|
term_width = shutil.get_terminal_size().columns
|
|||
|
|
|
|||
|
|
print("=" * term_width)
|
|||
|
|
title = " \U0001f504 蒸馏训练监控 "
|
|||
|
|
print(f"{title:=^{term_width}}")
|
|||
|
|
print("=" * term_width)
|
|||
|
|
|
|||
|
|
bar = get_bar(progress)
|
|||
|
|
print(f"\n 进度: [{bar}] {progress}%")
|
|||
|
|
if step_str:
|
|||
|
|
print(f" 步数: {step_str} Epoch: {epoch_str or '?'}")
|
|||
|
|
if loss_str:
|
|||
|
|
print(f" Loss: {loss_str}")
|
|||
|
|
if eta:
|
|||
|
|
print(f" 剩余: ~{eta}")
|
|||
|
|
|
|||
|
|
print()
|
|||
|
|
print("-" * term_width)
|
|||
|
|
|
|||
|
|
if gpu_info:
|
|||
|
|
parts = gpu_info.split(', ')
|
|||
|
|
if len(parts) >= 2:
|
|||
|
|
used, total = parts[0].strip(), parts[1].strip()
|
|||
|
|
try:
|
|||
|
|
mem_pct = int(used.split()[0]) / int(total.split()[0]) * 100
|
|||
|
|
mem_bar = get_bar(mem_pct)
|
|||
|
|
print(f" GPU显存: [{mem_bar}] {used}/{total} ({mem_pct:.0f}%)")
|
|||
|
|
except:
|
|||
|
|
print(f" GPU显存: {used}/{total}")
|
|||
|
|
if len(parts) >= 3:
|
|||
|
|
print(f" 利用率: {parts[3] if len(parts)>=4 else parts[2]}")
|
|||
|
|
|
|||
|
|
if disk_info:
|
|||
|
|
parts = disk_info.split()
|
|||
|
|
if len(parts) >= 5:
|
|||
|
|
used_pct = parts[4].rstrip('%')
|
|||
|
|
try:
|
|||
|
|
pct = int(used_pct)
|
|||
|
|
disk_bar = get_bar(pct)
|
|||
|
|
print(f" 数据盘: [{disk_bar}] {parts[2]}/{parts[1]} ({used_pct}%)")
|
|||
|
|
except:
|
|||
|
|
print(f" 数据盘: {parts[2]}/{parts[1]} ({used_pct}%)")
|
|||
|
|
|
|||
|
|
if proc_count == "0":
|
|||
|
|
print(f"\n \u26a0\ufe0f 蒸馏进程未运行!可能已完成或出错")
|
|||
|
|
else:
|
|||
|
|
print(f"\n \u2705 蒸馏训练进行中")
|
|||
|
|
|
|||
|
|
print()
|
|||
|
|
print("-" * term_width)
|
|||
|
|
print(" 最近日志:")
|
|||
|
|
recent = [l for l in log_lines if l.strip() and not l.startswith('\r')][-5:]
|
|||
|
|
for l in recent[-3:]:
|
|||
|
|
cleaned = l.replace('\r', '').strip()
|
|||
|
|
if cleaned:
|
|||
|
|
print(f" {cleaned[:100]}")
|
|||
|
|
|
|||
|
|
print()
|
|||
|
|
print(f" 刷新: 5秒 | {time.strftime('%H:%M:%S')}")
|
|||
|
|
print(" Ctrl+C 退出")
|
|||
|
|
print("=" * term_width)
|
|||
|
|
|
|||
|
|
time.sleep(5)
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
try:
|
|||
|
|
main()
|
|||
|
|
except KeyboardInterrupt:
|
|||
|
|
print("\n 监控已退出")
|
|||
|
|
sys.exit(0)
|