add: rl-dpo
parent
e9d2ccbb2d
commit
5bef3f08ab
|
|
@ -12,6 +12,14 @@ function openTab(evt, tabName) {
|
||||||
document.getElementById(tabName).classList.remove("hidden");
|
document.getElementById(tabName).classList.remove("hidden");
|
||||||
evt.currentTarget.classList.add("active");
|
evt.currentTarget.classList.add("active");
|
||||||
|
|
||||||
|
// 当切换到其他标签页时,清除所有日志自动更新定时器
|
||||||
|
if (tabName !== 'processes') {
|
||||||
|
for (let processId in logTimers) {
|
||||||
|
clearInterval(logTimers[processId]);
|
||||||
|
delete logTimers[processId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 如果切换到进程页面,刷新进程列表
|
// 如果切换到进程页面,刷新进程列表
|
||||||
if (tabName === 'processes') {
|
if (tabName === 'processes') {
|
||||||
loadProcesses();
|
loadProcesses();
|
||||||
|
|
@ -25,6 +33,8 @@ document.getElementById('train_type').addEventListener('change', function() {
|
||||||
const trainType = this.value;
|
const trainType = this.value;
|
||||||
const pretrainSftFields = document.querySelectorAll('.pretrain-sft');
|
const pretrainSftFields = document.querySelectorAll('.pretrain-sft');
|
||||||
const loraFields = document.querySelectorAll('.lora');
|
const loraFields = document.querySelectorAll('.lora');
|
||||||
|
const dpoFields = document.querySelectorAll('.dpo');
|
||||||
|
const dpoParamCard = document.querySelector('.parameter-card.dpo');
|
||||||
|
|
||||||
pretrainSftFields.forEach(field => {
|
pretrainSftFields.forEach(field => {
|
||||||
field.style.display = (trainType === 'pretrain' || trainType === 'sft') ? 'block' : 'none';
|
field.style.display = (trainType === 'pretrain' || trainType === 'sft') ? 'block' : 'none';
|
||||||
|
|
@ -34,6 +44,14 @@ document.getElementById('train_type').addEventListener('change', function() {
|
||||||
field.style.display = trainType === 'lora' ? 'block' : 'none';
|
field.style.display = trainType === 'lora' ? 'block' : 'none';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
dpoFields.forEach(field => {
|
||||||
|
field.style.display = trainType === 'dpo' ? 'block' : 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dpoParamCard) {
|
||||||
|
dpoParamCard.style.display = trainType === 'dpo' ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
// 设置默认值
|
// 设置默认值
|
||||||
if (trainType === 'pretrain') {
|
if (trainType === 'pretrain') {
|
||||||
document.getElementById('save_dir').value = '../out';
|
document.getElementById('save_dir').value = '../out';
|
||||||
|
|
@ -65,6 +83,17 @@ document.getElementById('train_type').addEventListener('change', function() {
|
||||||
document.getElementById('from_weight').value = 'full_sft';
|
document.getElementById('from_weight').value = 'full_sft';
|
||||||
document.getElementById('log_interval').value = '10';
|
document.getElementById('log_interval').value = '10';
|
||||||
document.getElementById('save_interval').value = '1';
|
document.getElementById('save_interval').value = '1';
|
||||||
|
} else if (trainType === 'dpo') {
|
||||||
|
document.getElementById('save_dir').value = '../out';
|
||||||
|
document.getElementById('save_weight').value = 'dpo';
|
||||||
|
document.getElementById('epochs').value = '1';
|
||||||
|
document.getElementById('batch_size').value = '4';
|
||||||
|
document.getElementById('learning_rate').value = '4e-8';
|
||||||
|
document.getElementById('data_path').value = '../dataset/dpo.jsonl';
|
||||||
|
document.getElementById('from_weight').value = 'full_sft';
|
||||||
|
document.getElementById('log_interval').value = '100';
|
||||||
|
document.getElementById('save_interval').value = '100';
|
||||||
|
document.getElementById('beta').value = '0.1';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -100,6 +129,10 @@ function loadProcesses() {
|
||||||
statusClass = 'status-completed';
|
statusClass = 'status-completed';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置进程数据属性,用于后续检查状态
|
||||||
|
processItem.dataset.processId = process.id;
|
||||||
|
processItem.dataset.processStatus = process.status;
|
||||||
|
|
||||||
processItem.innerHTML = `
|
processItem.innerHTML = `
|
||||||
<div class="process-info">
|
<div class="process-info">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -122,13 +155,52 @@ function loadProcesses() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 存储训练日志定时器的ID
|
||||||
|
let logTimers = {};
|
||||||
|
|
||||||
// 显示日志
|
// 显示日志
|
||||||
function showLogs(processId) {
|
function showLogs(processId) {
|
||||||
const logsContainer = document.getElementById(`logs-${processId}`);
|
const logsContainer = document.getElementById(`logs-${processId}`);
|
||||||
logsContainer.classList.toggle('hidden');
|
logsContainer.classList.toggle('hidden');
|
||||||
|
|
||||||
if (!logsContainer.classList.contains('hidden')) {
|
if (!logsContainer.classList.contains('hidden')) {
|
||||||
|
// 加载日志内容
|
||||||
loadLogContent(processId, logsContainer);
|
loadLogContent(processId, logsContainer);
|
||||||
|
|
||||||
|
// 清除可能存在的旧定时器
|
||||||
|
if (logTimers[processId]) {
|
||||||
|
clearInterval(logTimers[processId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找进程元素,获取其状态
|
||||||
|
const processItem = document.querySelector(`[data-process-id="${processId}"]`);
|
||||||
|
const isRunning = processItem && processItem.dataset.processStatus === '运行中';
|
||||||
|
|
||||||
|
// 只有运行中的进程才设置自动刷新定时器
|
||||||
|
if (isRunning) {
|
||||||
|
logTimers[processId] = setInterval(() => {
|
||||||
|
// 检查日志容器是否可见
|
||||||
|
if (!logsContainer.classList.contains('hidden')) {
|
||||||
|
// 再次检查进程状态,确保仍然是运行中
|
||||||
|
const currentProcessItem = document.querySelector(`[data-process-id="${processId}"]`);
|
||||||
|
const stillRunning = currentProcessItem && currentProcessItem.dataset.processStatus === '运行中';
|
||||||
|
|
||||||
|
if (stillRunning) {
|
||||||
|
loadLogContent(processId, logsContainer);
|
||||||
|
} else {
|
||||||
|
// 如果进程不再运行中,清除定时器
|
||||||
|
clearInterval(logTimers[processId]);
|
||||||
|
delete logTimers[processId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 隐藏日志时清除定时器
|
||||||
|
if (logTimers[processId]) {
|
||||||
|
clearInterval(logTimers[processId]);
|
||||||
|
delete logTimers[processId];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,6 +209,34 @@ function refreshLog(processId) {
|
||||||
const logsContainer = document.getElementById(`logs-${processId}`);
|
const logsContainer = document.getElementById(`logs-${processId}`);
|
||||||
if (!logsContainer.classList.contains('hidden')) {
|
if (!logsContainer.classList.contains('hidden')) {
|
||||||
loadLogContent(processId, logsContainer);
|
loadLogContent(processId, logsContainer);
|
||||||
|
|
||||||
|
// 查找进程元素,获取其状态
|
||||||
|
const processItem = document.querySelector(`[data-process-id="${processId}"]`);
|
||||||
|
const isRunning = processItem && processItem.dataset.processStatus === '运行中';
|
||||||
|
|
||||||
|
// 重置定时器,确保从现在开始每1秒更新一次
|
||||||
|
if (logTimers[processId]) {
|
||||||
|
clearInterval(logTimers[processId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只有运行中的进程才设置定时器
|
||||||
|
if (isRunning) {
|
||||||
|
logTimers[processId] = setInterval(() => {
|
||||||
|
if (!logsContainer.classList.contains('hidden')) {
|
||||||
|
// 再次检查进程状态
|
||||||
|
const currentProcessItem = document.querySelector(`[data-process-id="${processId}"]`);
|
||||||
|
const stillRunning = currentProcessItem && currentProcessItem.dataset.processStatus === '运行中';
|
||||||
|
|
||||||
|
if (stillRunning) {
|
||||||
|
loadLogContent(processId, logsContainer);
|
||||||
|
} else {
|
||||||
|
// 如果进程不再运行中,清除定时器
|
||||||
|
clearInterval(logTimers[processId]);
|
||||||
|
delete logTimers[processId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -214,7 +314,16 @@ function loadLogFiles() {
|
||||||
trainType = 'sft';
|
trainType = 'sft';
|
||||||
} else if (logfile.filename.includes('train_lora_')) {
|
} else if (logfile.filename.includes('train_lora_')) {
|
||||||
trainType = 'lora';
|
trainType = 'lora';
|
||||||
|
} else if (logfile.filename.includes('train_dpo_')) {
|
||||||
|
trainType = 'dpo';
|
||||||
|
} else if (logfile.filename.includes('train_ppo_')) {
|
||||||
|
trainType = 'ppo';
|
||||||
|
} else if (logfile.filename.includes('train_grpo_')) {
|
||||||
|
trainType = 'grpo';
|
||||||
|
} else if (logfile.filename.includes('train_spo_')) {
|
||||||
|
trainType = 'spo';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fileItem.innerHTML = `
|
fileItem.innerHTML = `
|
||||||
<div class="process-info">
|
<div class="process-info">
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>MiniMind 训练 Web UI</title>
|
<title>MiniMind Training Lab</title>
|
||||||
<link rel="stylesheet" href="/static/css/style.css">
|
<link rel="stylesheet" href="/static/css/style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>MiniMind 训练 Web UI</h1>
|
<h1>MiniMind Training Lab</h1>
|
||||||
|
|
||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
<button class="tab active" onclick="openTab(event, 'train')">开始训练</button>
|
<button class="tab active" onclick="openTab(event, 'train')">开始训练</button>
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
<option value="pretrain">预训练 (Pretrain)</option>
|
<option value="pretrain">预训练 (Pretrain)</option>
|
||||||
<option value="sft">监督微调 (Full SFT)</option>
|
<option value="sft">监督微调 (Full SFT)</option>
|
||||||
<option value="lora">LoRA 微调</option>
|
<option value="lora">LoRA 微调</option>
|
||||||
|
<option value="dpo">强化学习 (RL-DPO)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
@ -54,6 +55,17 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 强化学习参数 -->
|
||||||
|
<div class="parameter-card dpo" style="display: none;">
|
||||||
|
<h3 class="card-title">强化学习参数</h3>
|
||||||
|
<div class="parameter-content">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="beta">DPO Beta 参数:</label>
|
||||||
|
<input type="text" id="beta" name="beta" placeholder="0.1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 模型结构参数 -->
|
<!-- 模型结构参数 -->
|
||||||
<div class="parameter-card">
|
<div class="parameter-card">
|
||||||
<h3 class="card-title">模型结构参数</h3>
|
<h3 class="card-title">模型结构参数</h3>
|
||||||
|
|
|
||||||
|
|
@ -43,13 +43,23 @@ def start_training_process(train_type, params):
|
||||||
cmd = [sys.executable, script_path]
|
cmd = [sys.executable, script_path]
|
||||||
if 'lora_name' in params:
|
if 'lora_name' in params:
|
||||||
cmd.extend(['--lora_name', params['lora_name']])
|
cmd.extend(['--lora_name', params['lora_name']])
|
||||||
|
elif train_type == 'dpo':
|
||||||
|
script_path = '../trainer/train_dpo.py'
|
||||||
|
cmd = [sys.executable, script_path]
|
||||||
|
# 添加DPO特定参数
|
||||||
|
if 'beta' in params and params['beta']:
|
||||||
|
cmd.extend(['--beta', params['beta']])
|
||||||
|
if 'accumulation_steps' in params and params['accumulation_steps']:
|
||||||
|
cmd.extend(['--accumulation_steps', params['accumulation_steps']])
|
||||||
|
if 'grad_clip' in params and params['grad_clip']:
|
||||||
|
cmd.extend(['--grad_clip', params['grad_clip']])
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# 添加通用参数
|
# 添加通用参数
|
||||||
for key, value in params.items():
|
for key, value in params.items():
|
||||||
# 跳过特殊参数
|
# 跳过特殊参数和DPO特有参数
|
||||||
if key in ['train_type', 'save_weight', 'lora_name', 'train_monitor']:
|
if key in ['train_type', 'save_weight', 'lora_name', 'train_monitor', 'beta', 'accumulation_steps', 'grad_clip']:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 特殊处理布尔标志参数
|
# 特殊处理布尔标志参数
|
||||||
|
|
@ -64,6 +74,8 @@ def start_training_process(train_type, params):
|
||||||
if 'train_monitor' in params:
|
if 'train_monitor' in params:
|
||||||
if params['train_monitor'] == 'wandb' or params['train_monitor'] == 'swanlab':
|
if params['train_monitor'] == 'wandb' or params['train_monitor'] == 'swanlab':
|
||||||
cmd.append('--use_wandb') # 对于wandb和swanlab,只添加标志,不添加值
|
cmd.append('--use_wandb') # 对于wandb和swanlab,只添加标志,不添加值
|
||||||
|
if params['train_monitor'] == 'wandb':
|
||||||
|
cmd.extend(['--wandb_project', 'minimind_training'])
|
||||||
|
|
||||||
# 创建日志文件
|
# 创建日志文件
|
||||||
with open(log_file, 'w') as f:
|
with open(log_file, 'w') as f:
|
||||||
|
|
@ -170,10 +182,10 @@ def logs(process_id):
|
||||||
if os.path.exists(log_file):
|
if os.path.exists(log_file):
|
||||||
try:
|
try:
|
||||||
with open(log_file, 'r', encoding='utf-8') as f:
|
with open(log_file, 'r', encoding='utf-8') as f:
|
||||||
# 只读取最后500行
|
# 只读取最后200行
|
||||||
lines = f.readlines()
|
lines = f.readlines()
|
||||||
last_500_lines = lines[-500:] if len(lines) > 500 else lines
|
last_200_lines = lines[-200:] if len(lines) > 200 else lines
|
||||||
return ''.join(last_500_lines)
|
return ''.join(last_200_lines)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f'读取日志失败: {str(e)}'
|
return f'读取日志失败: {str(e)}'
|
||||||
return '日志文件不存在或已被删除'
|
return '日志文件不存在或已被删除'
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue