模型开始训练界面以及查看日志功能完善

This commit is contained in:
2026-01-29 10:36:59 +08:00
parent a560d24e2f
commit e9e0e21e47
11 changed files with 2485 additions and 179 deletions

View File

@@ -260,6 +260,11 @@
<header class="bg-white border-b border-gray-200 shadow-sm">
<div class="flex items-center justify-between px-6 h-14">
<div class="flex items-center space-x-6">
<!-- 返回按钮(仅外部页面显示) -->
<button id="pageBackBtn" class="hidden text-gray-500 hover:text-gray-700 flex items-center transition-colors" onclick="goBackToList()">
<i class="fa fa-arrow-left mr-1"></i>
<span>返回</span>
</button>
<button class="md:hidden text-gray-500 hover:text-gray-700">
<i class="fa fa-bars"></i>
</button>
@@ -304,28 +309,6 @@
</div>
<script>
// 会话超时检查5分钟
const SESSION_TIMEOUT = 5 * 60 * 1000; // 5分钟
function checkSession() {
const loginTime = localStorage.getItem('loginTime');
if (!loginTime || (Date.now() - parseInt(loginTime)) > SESSION_TIMEOUT) {
// 会话过期,清除并跳转到登录页
localStorage.removeItem('loginTime');
localStorage.removeItem('username');
window.location.href = 'login.html';
return false;
}
// 更新登录时间(用户有活动时续期)
localStorage.setItem('loginTime', Date.now());
return true;
}
// 页面加载时检查会话
if (!checkSession()) {
// 阻止页面渲染
document.body.innerHTML = '';
}
// API 基础地址 - 使用 config.yaml 中的 app.port (7861)
const getApiBase = () => {
const protocol = window.location.protocol;
@@ -430,9 +413,8 @@
createText: '创建训练任务',
columns: [
{ title: '任务名称', key: 'name' },
{ title: '基础模型', key: 'base_model' },
{ title: '基础模型', key: 'base_model', render: (val, row) => `<span class="model-name-cell" data-model-id="${val}">加载中...</span>` },
{ title: '状态', key: 'status', render: (val) => `<span class="px-2 py-1 rounded text-xs ${val === 'running' ? 'bg-green-100 text-green-700' : val === 'failed' ? 'bg-red-100 text-red-700' : 'bg-gray-100 text-gray-700'}">${val}</span>` },
{ title: '进度', key: 'progress', render: (val) => `${val || 0}%` },
{ title: '创建时间', key: 'create_time', render: (val) => val ? new Date(val).toLocaleString('zh-CN') : '-' }
],
actions: ['stop', 'logs', 'delete']
@@ -586,6 +568,12 @@
skipFetch: true,
hasCreate: false,
isExternalPage: true
},
'training-log': {
title: '训练日志',
skipFetch: true,
hasCreate: false,
isExternalPage: true
}
};
@@ -606,6 +594,124 @@
'chat': '对话'
};
// 训练进度缓存
let trainingProgressCache = {};
let progressRefreshTimer = null;
// 渲染训练进度
function renderTrainingProgress(val, row) {
const progressData = trainingProgressCache[row.id];
if (progressData && progressData.status === 'running') {
if (progressData.progress > 0) {
return `
<div class="flex flex-col">
<span class="text-sm font-medium text-primary">${progressData.progress}%</span>
<span class="text-xs text-gray-500">${progressData.step || ''} ${progressData.speed || ''}</span>
<span class="text-xs text-gray-400">ETA: ${progressData.eta || '--:--'}</span>
</div>
`;
}
}
return `${val || 0}%`;
}
// 刷新训练进度
async function refreshTrainingProgress() {
if (currentPage !== 'fine-tune') return;
try {
const response = await fetch(`${API_BASE}/fine-tune`);
const result = await response.json();
if (result.code === 0 && result.data) {
// 刷新运行中或已完成的任务(有进度信息)
const activeTasks = result.data.filter(task =>
task.status === 'running' || task.status === 'pending'
);
for (const task of activeTasks) {
try {
// 并行获取进度和PID状态
const [progressResponse, statusResponse] = await Promise.all([
fetch(`${API_BASE}/fine-tune/progress/${task.id}`),
fetch(`${API_BASE}/fine-tune/${task.id}`)
]);
const progressResult = await progressResponse.json();
const statusResult = await statusResponse.json();
if (progressResult.code === 0 && progressResult.data) {
trainingProgressCache[task.id] = progressResult.data;
}
// 如果状态已改变PID已结束更新表格中的状态显示
if (statusResult.code === 0 && statusResult.data) {
const actualStatus = statusResult.data.status;
if (task.status !== actualStatus) {
// 找到对应的行并更新状态
const row = document.querySelector(`tr[data-id="${task.id}"]`);
if (row) {
const statusCell = row.querySelector('td:nth-child(3)');
if (statusCell) {
statusCell.innerHTML = `<span class="px-2 py-1 rounded text-xs ${actualStatus === 'running' ? 'bg-green-100 text-green-700' : actualStatus === 'failed' ? 'bg-red-100 text-red-700' : 'bg-blue-100 text-blue-700'}">${actualStatus}</span>`;
}
}
}
}
} catch (e) {
console.warn(`获取任务 ${task.id} 信息失败:`, e);
}
}
}
} catch (error) {
console.warn('刷新训练进度失败:', error);
}
}
// 检查并更新任务状态(用于 fine-tune 页面)
async function checkAndUpdateTaskStatus() {
if (currentPage !== 'fine-tune') return;
try {
const response = await fetch(`${API_BASE}/fine-tune`);
const result = await response.json();
if (result.code === 0 && result.data) {
// 获取所有 running 状态的任务
const runningTasks = result.data.filter(task => task.status === 'running');
for (const task of runningTasks) {
try {
// 调用 status API 获取实际状态(会检查 PID
const statusResponse = await fetch(`${API_BASE}/fine-tune/${task.id}`);
const statusResult = await statusResponse.json();
if (statusResult.code === 0 && statusResult.data) {
const actualStatus = statusResult.data.status;
// 如果实际状态不是 running更新表格显示
if (actualStatus !== 'running') {
const row = document.querySelector(`tr[data-id="${task.id}"]`);
if (row) {
const statusCell = row.querySelector('td:nth-child(3)');
if (statusCell) {
const statusClass = actualStatus === 'failed'
? 'bg-red-100 text-red-700'
: 'bg-blue-100 text-blue-700';
statusCell.innerHTML = `<span class="px-2 py-1 rounded text-xs ${statusClass}">${actualStatus}</span>`;
console.log(`[Status] 任务 ${task.id} 状态已更新: running -> ${actualStatus}`);
}
}
}
}
} catch (e) {
console.warn(`检查任务 ${task.id} 状态失败:`, e);
}
}
}
} catch (error) {
console.warn('检查任务状态失败:', error);
}
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
// 从 localStorage 加载自定义工具
@@ -645,6 +751,12 @@
loadPage(defaultPage);
// 启动训练进度自动刷新每5秒
progressRefreshTimer = setInterval(() => {
refreshTrainingProgress();
checkAndUpdateTaskStatus();
}, 5000);
// 更新侧边栏高亮状态
document.querySelectorAll('.nav-link').forEach(link => {
if (link.dataset.page === defaultPage) {
@@ -727,6 +839,14 @@
// 离开日志页面时停止自动刷新
stopLogAutoRefresh();
// 离开模型调优页面时停止进度刷新
if (currentPage === 'fine-tune' && pageName !== 'fine-tune') {
if (progressRefreshTimer) {
clearInterval(progressRefreshTimer);
progressRefreshTimer = null;
}
}
const container = document.getElementById('page-content');
const config = tableConfigs[pageName];
@@ -740,6 +860,14 @@
</div>
`;
// 显示/隐藏返回按钮(外部页面显示,普通页面隐藏)
const backBtn = document.getElementById('pageBackBtn');
if (config.isExternalPage) {
backBtn.classList.remove('hidden');
} else {
backBtn.classList.add('hidden');
}
try {
// 渲染页面
if (config.isExternalPage) {
@@ -747,9 +875,14 @@
const response = await fetch(`${pageName}.html?t=${Date.now()}`);
if (response.ok) {
const html = await response.text();
// 提取内联脚本内容没有src属性的script标签
const scriptMatch = html.match(/<script\b(?![^>]*\bsrc)[^>]*>([\s\S]*?)<\/script>/);
const scriptContent = scriptMatch ? scriptMatch[1] : '';
// 提取所有内联脚本内容没有src属性的script标签
const scriptRegex = /<script\b(?![^>]*\bsrc)[^>]*>([\s\S]*?)<\/script>/g;
const scriptContents = [];
let match;
while ((match = scriptRegex.exec(html)) !== null) {
scriptContents.push(match[1]);
}
const scriptContent = scriptContents.join('\n');
// 移除所有script标签后插入HTML
const htmlWithoutScript = html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/g, '');
@@ -785,10 +918,18 @@
}
container.innerHTML = headerHtml + htmlWithoutScript;
// 执行脚本
// 执行脚本 - 使用 script 元素注入,使函数在全局作用域可用
if (scriptContent && scriptContent.trim()) {
try {
eval(scriptContent);
// 移除可能存在的旧脚本容器
const oldScript = document.getElementById('externalPageScript');
if (oldScript) oldScript.remove();
// 创建新的 script 元素
const scriptEl = document.createElement('script');
scriptEl.id = 'externalPageScript';
scriptEl.textContent = scriptContent;
document.body.appendChild(scriptEl);
} catch (e) {
console.error('执行脚本失败:', e);
}
@@ -812,6 +953,17 @@
const data = await fetchData(`${API_BASE}/${config.api}`);
currentPageData = data; // 保存当前页面数据
container.innerHTML = renderTablePage(config, data);
// 异步更新模型名称单元格
setTimeout(() => {
const modelCells = container.querySelectorAll('.model-name-cell');
modelCells.forEach(cell => {
const modelId = cell.getAttribute('data-model-id');
if (modelId) {
fetchAndUpdateModelName(modelId, cell);
}
});
}, 0);
}
} catch (error) {
console.error('加载数据失败:', error);
@@ -859,6 +1011,44 @@
});
}
// 停止训练任务
async function stopItem(taskId) {
showConfirm('确认停止', '确定要停止这个训练任务吗?进程将被终止。', async () => {
try {
const response = await fetch(`${API_BASE}/fine-tune/stop/${taskId}`, {
method: 'POST'
});
const result = await response.json();
if (result.code === 0) {
showMessage('成功', '训练任务已停止', 'success');
// 刷新当前页面
const activeLink = document.querySelector('.nav-link.sidebar-item-active');
if (activeLink) {
loadPage(activeLink.dataset.page);
}
} else {
showMessage('错误', result.message || '停止失败', 'error');
}
} catch (error) {
showMessage('错误', '停止失败: ' + error.message, 'error');
}
});
}
// 跳转到训练日志二级页面
function navigateToTrainingLog(taskId) {
// 设置 sessionStorage 传递 taskId
sessionStorage.setItem('trainingLogTaskId', taskId.toString());
// 跳转到日志页面
navigateToPage('training-log');
}
// 查看训练日志 - 跳转到日志页面
async function viewTrainingLog(taskId, taskName) {
// 跳转到日志页面
loadPage('logs');
}
// 更新模型用途
async function updateModelPurpose(id, purpose) {
try {
@@ -1133,7 +1323,7 @@
` : ''}
${columns.map(col => `
<td class="px-4 py-4 text-sm text-center">
${col.render ? col.render(item[col.key]) : (item[col.key] || '-')}
${col.render ? col.render(item[col.key], item) : (item[col.key] || '-')}
</td>
`).join('')}
<td class="px-4 py-4 text-sm text-center">
@@ -1141,7 +1331,16 @@
${config.actions.map(action => {
let onclick = '';
let btnClass = 'text-primary hover:text-primary/80';
if (action === 'delete') {
// 对于 fine-tune 的停止按钮,检查状态
if (action === 'stop' && config.api === 'fine-tune') {
// 状态为 completed 或 failed 时隐藏停止按钮
if (item.status === 'completed' || item.status === 'failed') {
return '';
}
onclick = `stopItem(${item.id})`;
btnClass = 'text-orange-500 hover:text-orange-600';
} else if (action === 'delete') {
onclick = `deleteItem('${config.api}', ${item.id})`;
btnClass = 'text-danger hover:text-danger/80';
} else if (action === 'edit') {
@@ -1152,6 +1351,8 @@
onclick = `downloadDataset('${item.id}')`;
} else if (action === 'compare' && config.api === 'model-compare') {
onclick = `startCompare(${item.id})`;
} else if (action === 'logs' && config.api === 'fine-tune') {
onclick = `navigateToTrainingLog(${item.id})`;
} else {
onclick = `showMessage('提示', '${actionLabels[action] || action}功能开发中...', 'info')`;
}
@@ -1189,33 +1390,59 @@
</div>
</div>
<div class="p-4">
<!-- 日期和刷新间隔选择 -->
<div class="flex items-center flex-wrap gap-4 mb-4">
<div class="flex items-center">
<label class="text-sm text-gray-600 mr-3">选择日期:</label>
<input type="date" id="logDatePicker" class="px-3 py-1.5 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none" onchange="loadLogFiles()">
<!-- 日志类型切换 -->
<div class="flex items-center mb-4">
<div class="flex bg-gray-100 rounded-lg p-1">
<button id="logTabSystem" onclick="switchLogTab('system')" class="px-4 py-1.5 text-sm rounded-md transition-colors bg-white shadow-sm text-primary">
系统日志
</button>
<button id="logTabTraining" onclick="switchLogTab('training')" class="px-4 py-1.5 text-sm rounded-md transition-colors text-gray-600 hover:text-gray-800">
训练日志
</button>
</div>
<div class="flex items-center">
<label class="text-sm text-gray-600 mr-3">自动刷新:</label>
<select id="logRefreshInterval" onchange="setRefreshInterval()" class="px-3 py-1.5 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<option value="0">关闭</option>
<option value="5">5秒</option>
<option value="10" selected>10秒</option>
<option value="30">30秒</option>
<option value="60">60秒</option>
</div>
<!-- 系统日志选项 -->
<div id="systemLogOptions">
<!-- 日期选择 -->
<div class="flex items-center flex-wrap gap-4 mb-4">
<div class="flex items-center">
<label class="text-sm text-gray-600 mr-3">选择日期:</label>
<input type="date" id="logDatePicker" class="px-3 py-1.5 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none" onchange="loadLogFiles()">
</div>
<div class="flex items-center">
<label class="text-sm text-gray-600 mr-3">自动刷新:</label>
<select id="logRefreshInterval" onchange="setRefreshInterval()" class="px-3 py-1.5 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<option value="0">关闭</option>
<option value="5">5秒</option>
<option value="10" selected>10秒</option>
<option value="30">30秒</option>
<option value="60">60秒</option>
</select>
</div>
<div id="logRefreshCountdown" class="text-sm text-gray-500 hidden">
<i class="fa fa-clock-o mr-1"></i><span>下次刷新: <span id="countdownNumber">10</span>秒</span>
</div>
</div>
<!-- 日志类型选择 -->
<div class="flex items-center mb-4">
<label class="text-sm text-gray-600 mr-3">日志类型:</label>
<select id="logTypeSelect" onchange="loadSelectedLog()" class="px-3 py-1.5 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<option value="">请选择日志文件</option>
</select>
</div>
<div id="logRefreshCountdown" class="text-sm text-gray-500 hidden">
<i class="fa fa-clock-o mr-1"></i><span>下次刷新: <span id="countdownNumber">10</span>秒</span>
</div>
<!-- 训练日志选项(初始隐藏) -->
<div id="trainingLogOptions" class="hidden">
<div class="flex items-center mb-4">
<label class="text-sm text-gray-600 mr-3">训练日志:</label>
<select id="trainingLogSelect" onchange="loadSelectedTrainingLog()" class="px-3 py-1.5 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none flex-1">
<option value="">请选择训练日志</option>
</select>
</div>
</div>
<!-- 日志类型选择 -->
<div class="flex items-center mb-4">
<label class="text-sm text-gray-600 mr-3">日志类型:</label>
<select id="logTypeSelect" onchange="loadSelectedLog()" class="px-3 py-1.5 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<option value="">请选择日志文件</option>
</select>
</div>
<!-- 日志内容显示 -->
<div class="border border-gray-200 rounded-lg">
<div class="flex items-center justify-between px-4 py-2 bg-gray-50 border-b border-gray-200">
@@ -1233,24 +1460,131 @@
`;
}
// 当前日志类型system 或 training
let currentLogTab = 'system';
// 切换日志类型标签
function switchLogTab(tab) {
currentLogTab = tab;
const systemTab = document.getElementById('logTabSystem');
const trainingTab = document.getElementById('logTabTraining');
const systemOptions = document.getElementById('systemLogOptions');
const trainingOptions = document.getElementById('trainingLogOptions');
if (tab === 'system') {
systemTab.className = 'px-4 py-1.5 text-sm rounded-md transition-colors bg-white shadow-sm text-primary';
trainingTab.className = 'px-4 py-1.5 text-sm rounded-md transition-colors text-gray-600 hover:text-gray-800';
systemOptions.classList.remove('hidden');
trainingOptions.classList.add('hidden');
loadLogFiles();
} else {
trainingTab.className = 'px-4 py-1.5 text-sm rounded-md transition-colors bg-white shadow-sm text-primary';
systemTab.className = 'px-4 py-1.5 text-sm rounded-md transition-colors text-gray-600 hover:text-gray-800';
trainingOptions.classList.remove('hidden');
systemOptions.classList.add('hidden');
loadTrainingLogFiles();
}
}
// 初始化日志查看器
function initLogViewer() {
const datePicker = document.getElementById('logDatePicker');
if (datePicker) {
const today = new Date().toISOString().split('T')[0];
datePicker.value = today;
loadLogFiles();
}
// 加载默认日志类型
loadLogFiles();
// 启动自动刷新
setRefreshInterval();
}
// 加载训练日志文件列表
async function loadTrainingLogFiles() {
const logSelect = document.getElementById('trainingLogSelect');
if (!logSelect) return;
logSelect.innerHTML = '<option value="">加载中...</option>';
try {
const response = await fetch(`${API_BASE}/training-log-files`);
const result = await response.json();
if (result.code === 0 && result.data) {
logSelect.innerHTML = '<option value="">请选择训练日志</option>';
result.data.forEach(log => {
const option = document.createElement('option');
option.value = log.file;
option.textContent = `${log.name} (PID: ${log.pid}, ${log.date}, ${log.size})`;
logSelect.appendChild(option);
});
// 如果有日志文件,自动加载第一个
if (result.data.length > 0) {
logSelect.value = result.data[0].file;
loadSelectedTrainingLog();
} else {
document.getElementById('logContent').textContent = '暂无训练日志';
document.getElementById('logFileInfo').textContent = '无训练日志';
}
} else {
logSelect.innerHTML = '<option value="">暂无训练日志</option>';
document.getElementById('logContent').textContent = '暂无训练日志';
document.getElementById('logFileInfo').textContent = '无训练日志';
}
} catch (error) {
console.error('加载训练日志列表失败:', error);
logSelect.innerHTML = '<option value="">加载失败</option>';
document.getElementById('logContent').textContent = '加载训练日志列表失败: ' + error.message;
}
}
// 加载选中的训练日志
async function loadSelectedTrainingLog() {
const logSelect = document.getElementById('trainingLogSelect');
const logFile = logSelect.value;
const logContent = document.getElementById('logContent');
const logFileInfo = document.getElementById('logFileInfo');
if (!logFile) {
logContent.textContent = '请选择训练日志';
logFileInfo.textContent = '无训练日志';
return;
}
logContent.textContent = '加载中...';
logFileInfo.textContent = '加载中...';
try {
const response = await fetch(`${API_BASE}/training-log-content?file=${encodeURIComponent(logFile)}`);
const result = await response.json();
if (result.code === 0 && result.data) {
logFullContent = result.data.content || '';
logContent.textContent = logFullContent || '(空日志)';
logFileInfo.textContent = result.data.file + ' (' + result.data.size + ')';
// 清空搜索
document.getElementById('logSearchInput').value = '';
document.getElementById('logMatchCount').textContent = '';
// 滚动到底部
scrollToLogBottom();
} else {
logContent.textContent = '加载失败: ' + (result.message || '未知错误');
logFileInfo.textContent = '加载失败';
}
} catch (error) {
console.error('加载训练日志内容失败:', error);
logContent.textContent = '加载失败: ' + error.message;
logFileInfo.textContent = '加载失败';
}
}
// 加载日志文件列表
async function loadLogFiles() {
const datePicker = document.getElementById('logDatePicker');
const logTypeSelect = document.getElementById('logTypeSelect');
const selectedDate = datePicker.value;
const selectedDate = datePicker ? datePicker.value : new Date().toISOString().split('T')[0];
if (!logTypeSelect) return;
logTypeSelect.innerHTML = '<option value="">加载中...</option>';
try {
@@ -1269,6 +1603,10 @@
if (result.data.length > 0) {
logTypeSelect.value = result.data[0].file;
loadSelectedLog();
} else {
logTypeSelect.innerHTML = '<option value="">暂无日志文件</option>';
document.getElementById('logContent').textContent = '该日期暂无日志文件';
document.getElementById('logFileInfo').textContent = '无日志文件';
}
} else {
logTypeSelect.innerHTML = '<option value="">暂无日志文件</option>';
@@ -1324,9 +1662,13 @@
// 刷新日志
function refreshLogs() {
loadLogFiles();
if (document.getElementById('logTypeSelect').value) {
loadSelectedLog();
if (currentLogTab === 'system') {
loadLogFiles();
if (document.getElementById('logTypeSelect').value) {
loadSelectedLog();
}
} else {
loadTrainingLogFiles();
}
// 重置倒计时
const select = document.getElementById('logRefreshInterval');
@@ -2057,10 +2399,63 @@
}
}
// 根据模型ID获取模型名称
// 根据模型ID获取模型名称(同步版本,用于表格渲染)
function getModelName(modelId) {
const model = modelListCache.find(m => m.id === modelId);
return model ? model.name : `模型${modelId}`;
if (!modelId) return '-';
// 尝试多种方式匹配(处理类型不一致的情况)
const model = modelListCache.find(m =>
m.id == modelId ||
m.id === String(modelId) ||
m.id === Number(modelId)
);
if (model) {
return model.name;
}
// 如果缓存中没有找到,尝试直接通过 API 获取单个模型
// 这是一个备用方案,不会阻塞渲染
return `模型${modelId}`;
}
// 异步获取模型名称并更新 DOM用于表格渲染后的更新
async function fetchAndUpdateModelName(modelId, cellElement) {
if (!modelId) {
cellElement.textContent = '-';
return;
}
// 先尝试从缓存中找
let model = modelListCache.find(m =>
m.id == modelId ||
m.id === String(modelId) ||
m.id === Number(modelId)
);
// 如果缓存中没有,尝试直接获取
if (!model) {
try {
const response = await fetch(`${API_BASE}/model-manage`);
const result = await response.json();
if (result.code === 0) {
modelListCache = result.data || [];
model = modelListCache.find(m =>
m.id == modelId ||
m.id === String(modelId) ||
m.id === Number(modelId)
);
}
} catch (e) {
console.error('获取模型列表失败:', e);
}
}
if (model) {
cellElement.textContent = model.name;
} else {
cellElement.textContent = `模型${modelId}`;
}
}
// 根据模型ID列表获取模型名称列表
@@ -2255,12 +2650,12 @@
<td class="p-3 border border-gray-200 text-gray-500 text-sm">循环次数代表模型训练过程中模型学习数据集的次数可理解为看几遍数据一般建议的范围是1-3遍即可</td>
</tr>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">eval_steps</td>
<td class="p-3 border border-gray-200 text-gray-700">save_steps</td>
<td class="p-3 border border-gray-200">
<input type="number" name="eval_steps_lora" value="50" class="w-24 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<input type="number" name="save_steps_lora" value="50" class="w-24 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<span class="text-xs text-gray-400 ml-2">[1,2147483647]</span>
</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">验证步数,训练阶段模型的验证间隔步长,用于阶段性评估模型训练准确率、训练损失</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">保存步数,训练阶段模型的保存间隔步长,用于阶段性保存模型权重</td>
</tr>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">lora_alpha</td>
@@ -2375,12 +2770,12 @@
<td class="p-3 border border-gray-200 text-gray-500 text-sm">循环次数代表模型训练过程中模型学习数据集的次数可理解为看几遍数据一般建议的范围是1-3遍即可可依据需求进行调整</td>
</tr>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">eval_steps</td>
<td class="p-3 border border-gray-200 text-gray-700">save_steps</td>
<td class="p-3 border border-gray-200">
<input type="number" name="eval_steps_full" value="50" class="w-24 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<input type="number" name="save_steps_full" value="50" class="w-24 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<span class="text-xs text-gray-400 ml-2">[1,2147483647]</span>
</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">验证步数,训练阶段模型的验证间隔步长,用于阶段性评估模型训练准确率、训练损失</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">保存步数,训练阶段模型的保存间隔步长,用于阶段性保存模型权重</td>
</tr>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">lr_scheduler_type</td>
@@ -2629,7 +3024,7 @@
'batch_size_lora': '16',
'learning_rate_lora': '3e-4',
'n_epochs_lora': '3',
'eval_steps_lora': '50',
'save_steps_lora': '50',
'lora_alpha': '32',
'lora_dropout': '0.1',
'lora_rank': '8',
@@ -2649,7 +3044,7 @@
'batch_size_full': '16',
'learning_rate_full': '1e-5',
'n_epochs_full': '3',
'eval_steps_full': '50',
'save_steps_full': '50',
'lr_scheduler_type_full': 'linear',
'max_length_full': '8192',
'warmup_ratio_full': '0.05',
@@ -2755,6 +3150,7 @@
const modalConfirmBtn = document.getElementById('modalConfirmBtn');
const modalCancelBtn = document.getElementById('modalCancelBtn');
const modalBtnGroup = document.getElementById('modalBtnGroup');
const modalSingleBtnGroup = document.getElementById('modalSingleBtnGroup');
if (!modalConfirmBtn) {
console.error('modalConfirmBtn not found');
@@ -2771,7 +3167,9 @@
modalIcon.innerHTML = '<div class="w-12 h-12 mx-auto mb-4 rounded-full bg-blue-100 flex items-center justify-center"><i class="fa fa-question text-xl text-blue-600"></i></div>';
}
// 显示双按钮组,隐藏单按钮组
modalBtnGroup.classList.remove('hidden');
modalSingleBtnGroup.classList.add('hidden');
modalConfirmBtn.textContent = '确定';
modalConfirmBtn.className = 'px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors';
@@ -2863,6 +3261,11 @@
}
}
// 返回到列表页(外部页面用)
function goBackToList() {
navigateToPage('fine-tune');
}
// 添加评测维度
function addDimension() {
window.location.href = 'model-dimension-create.html';