From e9d2ccbb2d075bcafb38cab45f5d1e6a46acacbe Mon Sep 17 00:00:00 2001 From: yuyu5333 <1812107659@qq.com> Date: Fri, 7 Nov 2025 03:11:22 +0000 Subject: [PATCH] fix training logs show --- scripts/static/css/style.css | 31 +++++++++++------------ scripts/static/js/script.js | 42 ++++++++++++++++++++----------- scripts/templates/index.html | 24 +++++++----------- scripts/train_web_ui.py | 49 +++++++++++++++++++++++++----------- 4 files changed, 86 insertions(+), 60 deletions(-) diff --git a/scripts/static/css/style.css b/scripts/static/css/style.css index 167b5b0..60b2ea4 100644 --- a/scripts/static/css/style.css +++ b/scripts/static/css/style.css @@ -89,37 +89,30 @@ h1 { border-top: 1px solid #4d4d4d; } -/* 参数内容容器 */ +/* 参数内容容器 - 使用flex布局替代float */ .parameter-content { width: 100%; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: 15px; } .form-group { - width: 40%; - float: left; - margin-bottom: 15px; - margin-right: 10%; + width: 40%; /* 考虑gap间距,确保两个元素能并排 */ + margin-bottom: 0; + margin-right: 5%; box-sizing: border-box; } -.form-group:nth-child(2n) { - margin-right: 0; -} - /* 确保复选框组占满整行 */ .form-group.checkbox-group { width: 100%; - margin-right: 0; } .form-group.pretrain-sft, .form-group.lora { - /* 移除100%宽度设置,让这些参数也遵循每行两个的布局 */ -} - -.parameter-content::after { - content: ""; - display: table; - clear: both; + /* 保持默认宽度,遵循每行两个的布局 */ + width: calc(40% - 8px); } label { display: block; @@ -247,6 +240,10 @@ button:active { background: linear-gradient(135deg, #ff416c 0%, #ff4b2b 100%); color: white; } +.status-manual-stop { + background: linear-gradient(135deg, #4e54c8 0%, #8f94fb 100%); + color: white; +} .btn-stop { background: linear-gradient(135deg, #ff416c 0%, #ff4b2b 100%); padding: 8px 15px; diff --git a/scripts/static/js/script.js b/scripts/static/js/script.js index 9d1756e..1502419 100644 --- a/scripts/static/js/script.js +++ b/scripts/static/js/script.js @@ -89,16 +89,15 @@ function loadProcesses() { processItem.className = 'process-item'; let statusClass = ''; - let statusText = ''; - if (process.running) { + // 根据后端返回的status字段设置状态类 + if (process.status === '运行中') { statusClass = 'status-running'; - statusText = '运行中'; - } else if (process.error) { + } else if (process.status === '手动停止') { + statusClass = 'status-manual-stop'; + } else if (process.status === '出错') { statusClass = 'status-error'; - statusText = '出错'; } else { statusClass = 'status-completed'; - statusText = '已完成'; } processItem.innerHTML = ` @@ -107,11 +106,12 @@ function loadProcesses() { ${process.train_type} - ${process.start_time}
- ${statusText} + ${process.status}
+ ${process.running ? `` : ''}
@@ -128,15 +128,28 @@ function showLogs(processId) { logsContainer.classList.toggle('hidden'); if (!logsContainer.classList.contains('hidden')) { - fetch(`/logs/${processId}`) - .then(response => response.text()) - .then(logs => { - logsContainer.textContent = logs; - logsContainer.scrollTop = logsContainer.scrollHeight; - }); + loadLogContent(processId, logsContainer); } } +// 刷新日志 +function refreshLog(processId) { + const logsContainer = document.getElementById(`logs-${processId}`); + if (!logsContainer.classList.contains('hidden')) { + loadLogContent(processId, logsContainer); + } +} + +// 加载日志内容 +function loadLogContent(processId, logsContainer) { + fetch(`/logs/${processId}`) + .then(response => response.text()) + .then(logs => { + logsContainer.textContent = logs; + logsContainer.scrollTop = logsContainer.scrollHeight; + }); +} + // 停止进程 function stopProcess(processId) { if (confirm('确定要停止这个训练进程吗?')) { @@ -206,10 +219,11 @@ function loadLogFiles() { fileItem.innerHTML = `
- ${trainType} - ${logfile.modified_time} + ${trainType} - ${logfile.filename}
已保存 + ${logfile.modified_time}
diff --git a/scripts/templates/index.html b/scripts/templates/index.html index 06a1afe..1c57c2a 100644 --- a/scripts/templates/index.html +++ b/scripts/templates/index.html @@ -80,17 +80,6 @@
- -
-

硬件配置

-
-
- - -
-
-
-

模型保存与恢复

@@ -129,10 +118,15 @@

其他设置

-
- - -
+ + +
+
+ +
diff --git a/scripts/train_web_ui.py b/scripts/train_web_ui.py index 8feaaf1..8beb5d2 100644 --- a/scripts/train_web_ui.py +++ b/scripts/train_web_ui.py @@ -48,14 +48,22 @@ def start_training_process(train_type, params): # 添加通用参数 for key, value in params.items(): - if key not in ['train_type', 'save_weight', 'lora_name']: - # 特殊处理布尔标志参数 - if key in ['use_wandb', 'from_resume']: - if value == '1': # 只有当值为1时才添加这个标志 - cmd.append(f'--{key}') - else: - # 确保log_interval和save_interval参数正确传递 - cmd.extend([f'--{key}', str(value)]) + # 跳过特殊参数 + if key in ['train_type', 'save_weight', 'lora_name', 'train_monitor']: + continue + + # 特殊处理布尔标志参数 + if key == 'from_resume': + if value == '1': # 只有当值为1时才添加这个标志 + cmd.append(f'--{key}') + else: + # 确保log_interval和save_interval参数正确传递 + cmd.extend([f'--{key}', str(value)]) + + # 单独处理训练监控参数,确保它不会被错误地添加值 + if 'train_monitor' in params: + if params['train_monitor'] == 'wandb' or params['train_monitor'] == 'swanlab': + cmd.append('--use_wandb') # 对于wandb和swanlab,只添加标志,不添加值 # 创建日志文件 with open(log_file, 'w') as f: @@ -118,8 +126,6 @@ def train(): # 处理复选框参数 if 'from_resume' not in params: params['from_resume'] = '0' - if 'use_wandb' not in params: - params['use_wandb'] = '0' # 启动训练进程 process_id = start_training_process(train_type, params) @@ -133,12 +139,18 @@ def train(): def processes(): result = [] for process_id, info in training_processes.items(): + # 确定状态 + status = '运行中' if info['running'] else \ + '手动停止' if 'manually_stopped' in info and info['manually_stopped'] else \ + '出错' if info['error'] else '已完成' + result.append({ 'id': process_id, 'train_type': info['train_type'], 'start_time': info['start_time'], 'running': info['running'], - 'error': info['error'] + 'error': info['error'], + 'status': status }) return jsonify(result) @@ -158,7 +170,10 @@ def logs(process_id): if os.path.exists(log_file): try: with open(log_file, 'r', encoding='utf-8') as f: - return f.read() + # 只读取最后500行 + lines = f.readlines() + last_500_lines = lines[-500:] if len(lines) > 500 else lines + return ''.join(last_500_lines) except Exception as e: return f'读取日志失败: {str(e)}' return '日志文件不存在或已被删除' @@ -186,6 +201,8 @@ def get_logfiles(): }) except Exception as e: continue + # 按修改时间倒序排序,最新的在前面 + logfiles.sort(key=lambda x: x['modified_time'], reverse=True) return jsonify(logfiles) @app.route('/logfile-content/') @@ -219,11 +236,15 @@ def stop(process_id): process.terminate() # 等待进程结束 process.wait(timeout=5) + # 标记为手动停止 + training_processes[process_id]['running'] = False + training_processes[process_id]['manually_stopped'] = True except subprocess.TimeoutExpired: # 如果超时,强制杀死 process.kill() - - training_processes[process_id]['running'] = False + # 标记为手动停止 + training_processes[process_id]['running'] = False + training_processes[process_id]['manually_stopped'] = True return jsonify({'success': True}) return jsonify({'success': False})