guanghulab/scripts/distill/watch_distill.py
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

187 lines
7.8 KiB
Python
Raw 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 python3
"""光湖蒸馏实时监控 v1.0 · 在AutoDL终端直接运行
运行方式: python3 watch_distill.py
功能: 实时滚动展示训练数据,不用手动刷新
主权: 冰朔 TCS-0002∞ · 铸渊 ICE-GL-ZY001 守护
"""
import os, sys, time, json, subprocess
from datetime import datetime
LOG = '/root/autodl-tmp/distill_mother.log'
STATUS = '/root/autodl-tmp/training_status.json'
WATCHDOG = '/root/autodl-tmp/watchdog.log'
def get_gpu_info():
try:
r = subprocess.run(
['nvidia-smi', '--query-gpu=temperature.gpu,utilization.gpu,memory.used,memory.total',
'--format=csv,noheader'],
capture_output=True, text=True, timeout=5)
parts = r.stdout.strip().split(', ')
return {'temp': parts[0], 'util': parts[1], 'mem_used': parts[2], 'mem_total': parts[3]}
except:
return None
def get_last_losses(n=10):
if not os.path.isfile(LOG):
return []
losses = []
with open(LOG) as f:
for line in f:
if 'loss=' in line:
losses.append(line.strip())
return losses[-n:]
def get_current_progress():
if not os.path.isfile(LOG):
return None
with open(LOG) as f:
for line in f:
pass
last = line.strip() if line else ''
return last
def get_watchdog():
if not os.path.isfile(WATCHDOG):
return None
with open(WATCHDOG) as f:
for line in f:
pass
return line.strip() if line else None
def get_status():
if not os.path.isfile(STATUS):
return None
with open(STATUS) as f:
return json.load(f)
def draw_progress_bar(pct, width=30):
filled = int(pct * width / 100)
bar = '' * filled + '' * (width - filled)
return f'[{bar}] {pct:.0f}%'
def main():
print('\033[2J\033[H', end='') # clear
print('═══ 光湖蒸馏实时监控 v1.0 ═══')
print('═══ 主权:冰朔 TCS-0002∞ · 铸渊守护 ═══')
print('每5秒自动刷新 · Ctrl+C 退出)')
print()
last_loss_count = 0
loss_history = []
try:
while True:
# 移动光标到顶部
print('\033[H', end='')
gpu = get_gpu_info()
progress = get_current_progress()
wd = get_watchdog()
st = get_status()
losses = get_last_losses(20)
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# === GPU状态 ===
if gpu:
temp = int(gpu['temp'])
temp_warn = ' ⚠ 偏高' if temp > 80 else ''
mem_u = int(gpu['mem_used'].replace(' MiB', ''))
mem_t = int(gpu['mem_total'].replace(' MiB', ''))
mem_pct = mem_u * 100 // mem_t if mem_t > 0 else 0
mem_warn = ' ⚠ 高负载' if mem_pct > 95 else ''
print(f'┌─ GPU 状态 ─────────────────────────────┐')
print(f'│ 温度: {temp}°C{temp_warn}')
print(f'│ 利用率: {gpu["util"]}')
print(f'│ 显存: {mem_u}MB / {mem_t}MB ({mem_pct}%){mem_warn}')
print(f'└────────────────────────────────────────┘')
else:
print('┌─ GPU ──────────────────────────────────┐')
print('│ ❌ 无法获取GPU信息')
print('└────────────────────────────────────────┘')
print()
# === 蒸馏进度 ===
print('┌─ 蒸馏进度 ─────────────────────────────┐')
if progress:
# 解析 Ep 和步数
if 'Ep' in progress and '/' in progress:
ep_part = progress.split('|')[0].strip() if '|' in progress else progress.split()[0]
step_part = progress.split('|')[1].strip().split()[0] if '|' in progress else ''
print(f'│ 当前: {ep_part}')
if '/' in step_part:
cur, total = step_part.split('/')
cur, total = int(cur), int(total)
pct = cur * 100 / total if total > 0 else 0
print(f'│ 步数: {cur}/{total}')
print(f'│ 进度: {draw_progress_bar(pct)}')
# 最近loss行
if losses:
last_loss = losses[-1]
print(f'│ Loss: {last_loss}')
else:
print('│ ⏳ 等待蒸馏开始...')
print('└────────────────────────────────────────┘')
print()
# === Loss趋势 ===
if losses:
print('┌─ 最近Loss趋势 ─────────────────────────┐')
# 找出loss值的范围
loss_vals = []
for l in losses:
try:
# 提取 loss= 后面的数字
idx = l.index('loss=')
end = l.index(' ', idx) if ' ' in l[idx:] else len(l)
val = float(l[idx+5:end])
loss_vals.append(val)
except:
pass
if loss_vals:
min_l, max_l = min(loss_vals), max(loss_vals)
rng = max_l - min_l if max_l > min_l else 1
# 打印mini图表
for i, v in enumerate(loss_vals):
rel = (v - min_l) / rng
bar_len = int((1 - rel) * 20) # loss越低越长
marker = '' if i > 0 and v < loss_vals[i-1] else ('' if i > 0 and v > loss_vals[i-1] else '')
print(f'{marker} loss={v:.4f} {""*bar_len}')
print('└────────────────────────────────────────┘')
print()
# === 看门狗 ===
if wd:
print(f'┌─ 看门狗 ───────────────────────────────┐')
print(f'{wd}')
print(f'└────────────────────────────────────────┘')
print()
# === 状态摘要 ===
if st:
mm = st.get('mother_model', {})
gs = mm.get('gpu', {})
print(f'┌─ 系统状态 ─────────────────────────────┐')
print(f'│ 时间: {now}')
print(f'│ 母模型: {mm.get("status_label", "?")}')
print(f'│ 蒸馏状态: {"🟢 运行中" if mm.get("status") == "training" else "⚪ 待开始/已完成"}')
print(f'│ GPU温度: {gs.get("temp_c", "?")}°C')
print(f'│ 显存: {gs.get("mem_used_mb", 0)//1024}GB/{gs.get("mem_total_mb", 0)//1024}GB')
print(f'└────────────────────────────────────────┘')
# 刷新间隔
time.sleep(5)
except KeyboardInterrupt:
print('\n\n监控已退出。蒸馏继续在后台运行。')
print('重新查看: tail -f /root/autodl-tmp/distill_mother.log')
sys.exit(0)
if __name__ == '__main__':
main()