From 9bfb58ad1e482c4d122beb74a0632159d72f370b Mon Sep 17 00:00:00 2001 From: yuyu5333 <1812107659@qq.com> Date: Thu, 6 Nov 2025 22:34:22 +0800 Subject: [PATCH 01/35] Init Web UI --- .gitignore | 6 +- scripts/static/css/style.css | 338 +++++++++++++++++++++++++++++++++++ scripts/static/js/script.js | 242 +++++++++++++++++++++++++ scripts/templates/index.html | 163 +++++++++++++++++ scripts/train_web_ui.py | 231 ++++++++++++++++++++++++ 5 files changed, 979 insertions(+), 1 deletion(-) create mode 100644 scripts/static/css/style.css create mode 100644 scripts/static/js/script.js create mode 100644 scripts/templates/index.html create mode 100644 scripts/train_web_ui.py diff --git a/.gitignore b/.gitignore index aee52b1..8675a0e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ +__pycache__ model/__pycache__ out website/ -docs-minimind/ \ No newline at end of file +docs-minimind/ +logfile +dataset +checkpoints \ No newline at end of file diff --git a/scripts/static/css/style.css b/scripts/static/css/style.css new file mode 100644 index 0000000..167b5b0 --- /dev/null +++ b/scripts/static/css/style.css @@ -0,0 +1,338 @@ +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + line-height: 1.6; + color: #e0e0e0; + max-width: 1200px; + margin: 0 auto; + padding: 20px; + background-color: #121212; + min-height: 100vh; +} +h1 { + color: #ffffff; + text-align: center; + margin-bottom: 30px; + text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5); + font-size: 2.5em; + font-weight: 700; +} +.tabs { + display: flex; + justify-content: space-between; + margin-bottom: 20px; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); +} +.tab { + padding: 12px 0; + cursor: pointer; + background-color: #2a2a2a; + border: none; + font-size: 16px; + font-weight: 500; + transition: all 0.3s ease; + position: relative; + color: #cccccc; + width: 30%; + text-align: center; +} +.tab.active { + background: linear-gradient(135deg, #4a148c 0%, #8e24aa 100%); + color: white; + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); +} +.form-container { + background: rgba(30, 30, 30, 0.9); + padding: 30px; + border-radius: 15px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); + margin-bottom: 30px; + border: 1px solid #333; +} + +/* 参数卡片样式 */ +.parameter-card { + background-color: #2d2d2d; + border-radius: 8px; + padding: 20px; + margin-bottom: 20px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); + transition: transform 0.2s ease, box-shadow 0.2s ease; + padding-left: 5%; + padding-right: 5%; +} + +.parameter-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4); +} + +/* 卡片标题样式 */ +.card-title { + color: #e0e0e0; + font-size: 1.4rem; + font-weight: bold; + margin-top: 0; + margin-bottom: 20px; + padding-bottom: 10px; + border-bottom: 1px solid #404040; + width: 100%; +} + +/* 提交按钮容器 */ +.submit-container { + text-align: center; + margin-top: 30px; + padding-top: 20px; + border-top: 1px solid #4d4d4d; +} + +/* 参数内容容器 */ +.parameter-content { + width: 100%; +} + +.form-group { + width: 40%; + float: left; + margin-bottom: 15px; + margin-right: 10%; + 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; +} +label { + display: block; + margin-bottom: 5px; + color: #ffffff; + font-weight: 600; + font-size: 0.9rem; + text-transform: uppercase; + letter-spacing: 0.5px; +} +input[type="text"], input[type="number"], select { + width: 100%; + padding: 12px 15px; + border: 2px solid #444; + border-radius: 8px; + font-size: 0.9rem; + transition: all 0.3s ease; + background-color: #2a2a2a; + color: #ffffff; +} + +/* 确保textarea也适应两列布局 */ +textarea { + width: 100%; + padding: 12px 15px; + border: 2px solid #444; + border-radius: 8px; + font-size: 0.9rem; + transition: all 0.3s ease; + background-color: #2a2a2a; + color: #ffffff; + resize: vertical; +} + +input[type="text"]:focus, input[type="number"]:focus, select:focus { + outline: none; + border-color: #8e24aa; + box-shadow: 0 0 0 3px rgba(142, 36, 170, 0.2); + background-color: #333; +} +.checkbox-group { + display: flex; + align-items: center; +} +.checkbox-group input[type="checkbox"] { + width: auto; + margin-right: 10px; +} +button { + background: linear-gradient(135deg, #4a148c 0%, #8e24aa 100%); + color: white; + border: none; + padding: 12px 25px; + border-radius: 8px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); + position: relative; + overflow: hidden; +} +button:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15); +} + +button:active { + transform: translateY(0); +} +.logs-container { + background-color: #0d0d0d; + color: #e0e0e0; + padding: 20px; + border-radius: 10px; + max-height: 300px; + overflow-y: auto; + margin-top: 15px; + font-family: 'Courier New', monospace; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.4); + transition: all 0.3s ease; + border: 1px solid #333; +} + +.logs-container:hover { + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3); +} +.process-item { + background: rgba(30, 30, 30, 0.9); + padding: 20px; + margin-bottom: 15px; + border-radius: 12px; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); + transition: all 0.3s ease; + border: 1px solid #444; +} + +.process-item:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15); +} +.process-info { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; +} +.process-status { + padding: 5px 12px; + border-radius: 20px; + font-size: 12px; + font-weight: bold; + text-transform: uppercase; + letter-spacing: 0.5px; +} +.status-running { + background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); + color: white; +} +.status-completed { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); + color: white; +} +.status-error { + background: linear-gradient(135deg, #ff416c 0%, #ff4b2b 100%); + color: white; +} +.btn-stop { + background: linear-gradient(135deg, #ff416c 0%, #ff4b2b 100%); + padding: 8px 15px; + font-size: 14px; + border-radius: 6px; +} +.btn-stop:hover { + transform: translateY(-1px); + box-shadow: 0 4px 10px rgba(255, 65, 108, 0.3); +} +.btn-logs { + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); + padding: 8px 15px; + font-size: 14px; + margin-right: 10px; + border-radius: 6px; +} +.btn-logs:hover { + transform: translateY(-1px); + box-shadow: 0 4px 10px rgba(79, 172, 254, 0.3); +} +.hidden { + display: none; +} +.section-title { + color: #ffffff; + font-size: 20px; + margin-bottom: 25px; + text-shadow: 0 2px 5px rgba(0, 0, 0, 0.5); + font-weight: 600; + padding-bottom: 10px; + border-bottom: 3px solid rgba(142, 36, 170, 0.3); +} + +/* 添加滚动条样式 */ +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.05); + border-radius: 10px; +} + +::-webkit-scrollbar-thumb { + background: linear-gradient(135deg, #4a148c 0%, #8e24aa 100%); + border-radius: 10px; +} + +::-webkit-scrollbar-thumb:hover { + background: linear-gradient(135deg, #6a1b9a 0%, #ab47bc 100%); +} + +/* 添加动画效果 */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.tab-content { + animation: fadeIn 0.5s ease-out; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + body { + padding: 10px; + } + + .tabs { + flex-direction: column; + } + + .tab { + margin-right: 0; + margin-bottom: 5px; + border-radius: 5px; + } + + .form-container { + padding: 20px; + } +} \ No newline at end of file diff --git a/scripts/static/js/script.js b/scripts/static/js/script.js new file mode 100644 index 0000000..9d1756e --- /dev/null +++ b/scripts/static/js/script.js @@ -0,0 +1,242 @@ +// 标签切换功能 +function openTab(evt, tabName) { + var i, tabContent, tabLinks; + tabContent = document.getElementsByClassName("tab-content"); + for (i = 0; i < tabContent.length; i++) { + tabContent[i].classList.add("hidden"); + } + tabLinks = document.getElementsByClassName("tab"); + for (i = 0; i < tabLinks.length; i++) { + tabLinks[i].classList.remove("active"); + } + document.getElementById(tabName).classList.remove("hidden"); + evt.currentTarget.classList.add("active"); + + // 如果切换到进程页面,刷新进程列表 + if (tabName === 'processes') { + loadProcesses(); + } else if (tabName === 'logfiles') { + loadLogFiles(); + } +} + +// 根据训练类型显示/隐藏特定参数 +document.getElementById('train_type').addEventListener('change', function() { + const trainType = this.value; + const pretrainSftFields = document.querySelectorAll('.pretrain-sft'); + const loraFields = document.querySelectorAll('.lora'); + + pretrainSftFields.forEach(field => { + field.style.display = (trainType === 'pretrain' || trainType === 'sft') ? 'block' : 'none'; + }); + + loraFields.forEach(field => { + field.style.display = trainType === 'lora' ? 'block' : 'none'; + }); + + // 设置默认值 + if (trainType === 'pretrain') { + document.getElementById('save_dir').value = '../out'; + document.getElementById('save_weight').value = 'pretrain'; + document.getElementById('epochs').value = '1'; + document.getElementById('batch_size').value = '32'; + document.getElementById('learning_rate').value = '5e-4'; + document.getElementById('data_path').value = '../dataset/pretrain_hq.jsonl'; + document.getElementById('from_weight').value = 'none'; + document.getElementById('log_interval').value = '100'; + document.getElementById('save_interval').value = '100'; + } else if (trainType === 'sft') { + document.getElementById('save_dir').value = '../out'; + document.getElementById('save_weight').value = 'full_sft'; + document.getElementById('epochs').value = '2'; + document.getElementById('batch_size').value = '16'; + document.getElementById('learning_rate').value = '5e-7'; + document.getElementById('data_path').value = '../dataset/sft_mini_512.jsonl'; + document.getElementById('from_weight').value = 'pretrain'; + document.getElementById('log_interval').value = '100'; + document.getElementById('save_interval').value = '100'; + } else if (trainType === 'lora') { + document.getElementById('save_dir').value = '../out/lora'; + document.getElementById('lora_name').value = 'lora_identity'; + document.getElementById('epochs').value = '50'; + document.getElementById('batch_size').value = '32'; + document.getElementById('learning_rate').value = '1e-4'; + document.getElementById('data_path').value = '../dataset/lora_identity.jsonl'; + document.getElementById('from_weight').value = 'full_sft'; + document.getElementById('log_interval').value = '10'; + document.getElementById('save_interval').value = '1'; + } +}); + +// 初始触发一次change事件以设置默认值 +document.getElementById('train_type').dispatchEvent(new Event('change')); + +// 加载进程列表 +function loadProcesses() { + fetch('/processes') + .then(response => response.json()) + .then(data => { + const processList = document.getElementById('process-list'); + processList.innerHTML = ''; + + if (data.length === 0) { + processList.innerHTML = '
暂无训练进程
'; + return; + } + + data.forEach(process => { + const processItem = document.createElement('div'); + processItem.className = 'process-item'; + + let statusClass = ''; + let statusText = ''; + if (process.running) { + statusClass = 'status-running'; + statusText = '运行中'; + } else if (process.error) { + statusClass = 'status-error'; + statusText = '出错'; + } else { + statusClass = 'status-completed'; + statusText = '已完成'; + } + + processItem.innerHTML = ` +暂无日志文件
'; + return; + } + + // 按日期倒序排序 + data.sort((a, b) => new Date(b.modified_time) - new Date(a.modified_time)); + + data.forEach(logfile => { + const fileItem = document.createElement('div'); + fileItem.className = 'process-item'; + + // 从文件名提取训练类型 + let trainType = '未知'; + if (logfile.filename.includes('train_pretrain_')) { + trainType = 'pretrain'; + } else if (logfile.filename.includes('train_sft_')) { + trainType = 'sft'; + } else if (logfile.filename.includes('train_lora_')) { + trainType = 'lora'; + } + + fileItem.innerHTML = ` +I9=SL3(-jF}NT2__+;BEAS#3ytBx1Pg
zk1ntfEO_w%UEq{#kXt^DPpbGcxm0xWJ@vQq3UK&~95FDhI%N|;2ypuDN<$TqHPiL2
znGwkI)hBq0t&C+TSW#2cwN>6ah^@faF2mH^b#}< B4}pI!+yN_7g~jq0#i
zI2^x55byagC2;Ae@H+O5!xzbByc
z3P@#hi{x@k=@e!=g;mFsqqoqGuRZWGyDujjt4tBcb6n^~g(e^g*`bL0&?$OMQxmdJ
zT~mfx9MHhFGWeQTZV}b_8}j&SBcx`Fe|&9a^9B75CMD_;MHsx8-4phU`sR-rb>i{P
z(&Yr0nq)c1-0Q$PJU#}N!o-mrzE4_tNl8UzWxE0M0*H*6IXlO@H+zp=?KsEihPbsb
z;StPu6QAzBn2(JHhvdCJ)HFu8G^)V^2RUfUJlX$-9
zX-5osq>xd>fIJJ65YQWpJQ^dv_c`GGM8O#{wm=WonBGNVO?3d>&IR{UDy71Yhnb@0
zfviDmpM;~&;o?H@i%PW!R1onU)&1*7pHlAH7#!YedB0cI_B7aqWc|*oiYC6FcCY-5Yv@_-
z`5!jmD0*VZ_CeUU`}av^Bww&*U><
z+^LHwGRJawq*Rmd%-+Hd4xx&G!=9R=Z(s!mk0I<=;yKklDq*qD!|g_|_S~$VM86K<
z9C)4zALn0{{M1{JJ(Acx&+@)<(54{dsiF_@5O!_Mobq{Xm?&CJkEk!Mqv<{qWvRV3tP4p!1B|~5TP`DsvCGNxO;#-!
Wi?Vf&aK4A8knDzh$IZ|4M
z9AEL
9ai@XFpd56uf>30Gy%rSC)ge8vm&&
z(gTFApX)om9w!gB)7#eL_MiwP@M@~6t^-2*Ml(RDgJ}n3p!@t#@zyUA(|>%dU_rZB
zOppD6fR(ORENfe5y%1Fh0t`h$W!|B`n){TJF*sC+uR&+3(R*0*&Y+hQM`5q9N<-mH
z4|AnkibIX{P+6Gl!PN3qxp@b%n+d<4xdzq7iFkYve)lC(GOQ(f@
w-fp0)Fj(*E0D5Sk=?_U`nS(*QNV@g)RKl