add: rl-dpo

pull/524/head
yuyu5333 2025-11-07 05:13:13 +00:00
parent e9d2ccbb2d
commit 5bef3f08ab
3 changed files with 140 additions and 7 deletions

View File

@ -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">

View File

@ -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>

View File

@ -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 '日志文件不存在或已被删除'