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})