Spaces:
Sleeping
Sleeping
Upload index-6.html
Browse files- templates/index-6.html +303 -73
templates/index-6.html
CHANGED
|
@@ -49,29 +49,26 @@
|
|
| 49 |
.ok{color:#7ee787}.warn{color:#ffd479}.err{color:#ff7b72}
|
| 50 |
.footer{padding:12px 16px;color:var(--muted);border-top:1px solid var(--border);font-size:12px}
|
| 51 |
|
|
|
|
| 52 |
.resizer-v {
|
| 53 |
-
height:
|
| 54 |
cursor: ns-resize;
|
| 55 |
-
background:
|
| 56 |
-
|
| 57 |
-
border-
|
| 58 |
display: flex;
|
| 59 |
align-items: center;
|
| 60 |
justify-content: center;
|
| 61 |
-
|
| 62 |
-
z-index: 50;
|
| 63 |
-
}
|
| 64 |
-
.resizer-v:hover {
|
| 65 |
-
background: #1f6feb; /* Fica azul brilhante ao passar o rato */
|
| 66 |
}
|
|
|
|
| 67 |
.resizer-v::after {
|
| 68 |
content: "";
|
| 69 |
width: 40px;
|
| 70 |
-
height:
|
| 71 |
-
background:
|
| 72 |
border-radius: 2px;
|
| 73 |
}
|
| 74 |
-
|
| 75 |
</style>
|
| 76 |
|
| 77 |
</head>
|
|
@@ -207,7 +204,7 @@
|
|
| 207 |
|
| 208 |
<section class="card">
|
| 209 |
<header>
|
| 210 |
-
<h2>Treinar IA
|
| 211 |
<div class="chips">
|
| 212 |
<span class="pill" id="trainerState">estado: inativo</span>
|
| 213 |
</div>
|
|
@@ -215,7 +212,7 @@
|
|
| 215 |
<div class="content">
|
| 216 |
<div class="row">
|
| 217 |
<div class="col-12">
|
| 218 |
-
<label for="model_path">Caminho do Modelo Base</label>
|
| 219 |
<input type="text" id="model_path" placeholder="/caminho/para/modelo" />
|
| 220 |
</div>
|
| 221 |
<div class="col-12">
|
|
@@ -243,22 +240,81 @@
|
|
| 243 |
|
| 244 |
</div>
|
| 245 |
|
| 246 |
-
|
| 247 |
-
|
|
|
|
|
|
|
|
|
|
| 248 |
</div>
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
<div
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
|
| 263 |
</div>
|
| 264 |
</section>
|
|
@@ -432,6 +488,130 @@
|
|
| 432 |
trainingBox.scrollTop = trainingBox.scrollHeight;
|
| 433 |
}
|
| 434 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 435 |
function startPolling() {
|
| 436 |
if (pollTimer) return;
|
| 437 |
|
|
@@ -441,33 +621,74 @@
|
|
| 441 |
if (!r.ok) throw new Error('HTTP ' + r.status);
|
| 442 |
const data = await r.json();
|
| 443 |
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
// Progresso e estado
|
| 447 |
if (data.progress !== undefined) {
|
| 448 |
const pct = typeof data.progress === 'object' ? data.progress.percent : data.progress;
|
| 449 |
-
content += `Progresso: ${pct}%\n`;
|
| 450 |
const progressBar = $('progressBar');
|
|
|
|
| 451 |
if (progressBar) progressBar.style.width = `${pct}%`;
|
|
|
|
| 452 |
}
|
| 453 |
|
| 454 |
-
|
| 455 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 456 |
|
| 457 |
-
//
|
| 458 |
-
|
| 459 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 460 |
}
|
| 461 |
|
| 462 |
-
//
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 467 |
}
|
| 468 |
|
| 469 |
-
|
| 470 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 471 |
|
| 472 |
} catch (e) {
|
| 473 |
console.error('Erro ao atualizar treinamento:', e);
|
|
@@ -546,6 +767,11 @@
|
|
| 546 |
}
|
| 547 |
}
|
| 548 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 549 |
// =========================
|
| 550 |
// EVENT LISTENERS
|
| 551 |
// =========================
|
|
@@ -563,43 +789,47 @@
|
|
| 563 |
case 'tensorboardBtn': el.addEventListener('click',openTensorBoard); break;
|
| 564 |
case 'reduceBtn': el.addEventListener('click',()=>manualAdjustAccumulation(-1)); break;
|
| 565 |
case 'increaseBtn': el.addEventListener('click',()=>manualAdjustAccumulation(1)); break;
|
| 566 |
-
case 'modelDirBtn': el.addEventListener('click',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 567 |
}
|
| 568 |
});
|
| 569 |
fetchConfigFromServer();
|
| 570 |
});
|
| 571 |
|
| 572 |
-
// Lógica
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
}
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
// Calculamos a posição relativa ao topo da caixa de treino
|
| 589 |
-
const rect = trainingBox.getBoundingClientRect();
|
| 590 |
-
const offset = e.clientY - rect.top;
|
| 591 |
-
|
| 592 |
-
// Aplicamos a nova altura (mínimo 100px, máximo 800px)
|
| 593 |
-
if (offset > 100 && offset < 800) {
|
| 594 |
-
trainingBox.style.height = offset + 'px';
|
| 595 |
}
|
|
|
|
|
|
|
|
|
|
| 596 |
}
|
| 597 |
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
window.removeEventListener('mousemove', startResizing);
|
| 601 |
-
window.removeEventListener('mouseup', stopResizing);
|
| 602 |
-
}
|
| 603 |
|
| 604 |
</script>
|
| 605 |
</body>
|
|
|
|
| 49 |
.ok{color:#7ee787}.warn{color:#ffd479}.err{color:#ff7b72}
|
| 50 |
.footer{padding:12px 16px;color:var(--muted);border-top:1px solid var(--border);font-size:12px}
|
| 51 |
|
| 52 |
+
/* NOVO: Barra de redimensionamento vertical */
|
| 53 |
.resizer-v {
|
| 54 |
+
height: 10px;
|
| 55 |
cursor: ns-resize;
|
| 56 |
+
background: var(--border);
|
| 57 |
+
margin: 6px 0;
|
| 58 |
+
border-radius: 4px;
|
| 59 |
display: flex;
|
| 60 |
align-items: center;
|
| 61 |
justify-content: center;
|
| 62 |
+
transition: background 0.2s;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
}
|
| 64 |
+
.resizer-v:hover { background: var(--accent2); }
|
| 65 |
.resizer-v::after {
|
| 66 |
content: "";
|
| 67 |
width: 40px;
|
| 68 |
+
height: 2px;
|
| 69 |
+
background: var(--muted);
|
| 70 |
border-radius: 2px;
|
| 71 |
}
|
|
|
|
| 72 |
</style>
|
| 73 |
|
| 74 |
</head>
|
|
|
|
| 204 |
|
| 205 |
<section class="card">
|
| 206 |
<header>
|
| 207 |
+
<h2>Treinar IA</h2>
|
| 208 |
<div class="chips">
|
| 209 |
<span class="pill" id="trainerState">estado: inativo</span>
|
| 210 |
</div>
|
|
|
|
| 212 |
<div class="content">
|
| 213 |
<div class="row">
|
| 214 |
<div class="col-12">
|
| 215 |
+
<label for="model_path">Caminho do Modelo Base (exemple: Fedir-Ilina/meta-llamaLlama-3.2-1B)</label>
|
| 216 |
<input type="text" id="model_path" placeholder="/caminho/para/modelo" />
|
| 217 |
</div>
|
| 218 |
<div class="col-12">
|
|
|
|
| 240 |
|
| 241 |
</div>
|
| 242 |
|
| 243 |
+
<!-- BARRA DE PROGRESSO -->
|
| 244 |
+
<div style="margin:12px 0 6px;">
|
| 245 |
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;">
|
| 246 |
+
<span style="font-size:12px;color:var(--muted)">Progresso do Treino</span>
|
| 247 |
+
<span id="progressPct" style="font-size:13px;font-family:var(--mono);color:#7ee787;font-weight:600">0%</span>
|
| 248 |
</div>
|
| 249 |
+
<div style="background:#0a0e14;border:1px solid var(--border);border-radius:999px;height:10px;overflow:hidden;">
|
| 250 |
+
<div id="progressBar" style="width:0%;height:100%;background:linear-gradient(90deg,#1f6feb,#2ea043);border-radius:999px;transition:width 0.5s ease;"></div>
|
| 251 |
+
</div>
|
| 252 |
+
</div>
|
| 253 |
+
|
| 254 |
+
<!-- MÉTRICAS EM TEMPO REAL -->
|
| 255 |
+
<h3>📊 Métricas em Tempo Real</h3>
|
| 256 |
+
<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:8px;margin-bottom:12px;">
|
| 257 |
+
<div style="background:#0c1117;border:1px solid var(--border);border-radius:8px;padding:10px 12px;">
|
| 258 |
+
<div style="font-size:11px;color:var(--muted);margin-bottom:4px;text-transform:uppercase;letter-spacing:.05em">Estado</div>
|
| 259 |
+
<div id="mv-status" style="font-size:16px;font-weight:700;font-family:var(--mono);color:#79c0ff">—</div>
|
| 260 |
+
</div>
|
| 261 |
+
<div style="background:#0c1117;border:1px solid var(--border);border-radius:8px;padding:10px 12px;">
|
| 262 |
+
<div style="font-size:11px;color:var(--muted);margin-bottom:4px;text-transform:uppercase;letter-spacing:.05em">Gradient Accum.</div>
|
| 263 |
+
<div id="mv-accum" style="font-size:16px;font-weight:700;font-family:var(--mono);color:#ffd479">—</div>
|
| 264 |
+
<div id="mv-accum-origin" style="font-size:10px;color:#7aa2d6;margin-top:2px;"></div>
|
| 265 |
</div>
|
| 266 |
+
<div style="background:#0c1117;border:1px solid var(--border);border-radius:8px;padding:10px 12px;">
|
| 267 |
+
<div style="font-size:11px;color:var(--muted);margin-bottom:4px;text-transform:uppercase;letter-spacing:.05em">Effective Batch</div>
|
| 268 |
+
<div id="mv-effbatch" style="font-size:16px;font-weight:700;font-family:var(--mono);color:#7ee787">—</div>
|
| 269 |
+
</div>
|
| 270 |
+
<div style="background:#0c1117;border:1px solid var(--border);border-radius:8px;padding:10px 12px;">
|
| 271 |
+
<div style="font-size:11px;color:var(--muted);margin-bottom:4px;text-transform:uppercase;letter-spacing:.05em">Total Steps</div>
|
| 272 |
+
<div id="mv-steps" style="font-size:16px;font-weight:700;font-family:var(--mono);color:#d2a8ff">—</div>
|
| 273 |
+
</div>
|
| 274 |
+
<div style="background:#0c1117;border:1px solid var(--border);border-radius:8px;padding:10px 12px;position:relative;overflow:hidden;">
|
| 275 |
+
<div style="font-size:11px;color:var(--muted);margin-bottom:4px;text-transform:uppercase;letter-spacing:.05em">RAM Usada</div>
|
| 276 |
+
<div id="mv-ram" style="font-size:16px;font-weight:700;font-family:var(--mono);color:#ff7b72">—</div>
|
| 277 |
+
<div style="position:absolute;bottom:0;left:0;right:0;height:3px;background:#1a1f26;">
|
| 278 |
+
<div id="mv-ram-bar" style="height:100%;width:0%;background:#f85149;transition:width .5s,background .5s;border-radius:0 2px 0 0;"></div>
|
| 279 |
+
</div>
|
| 280 |
+
</div>
|
| 281 |
+
</div>
|
| 282 |
+
|
| 283 |
+
<!-- HISTÓRICO DE AJUSTES DO CALLBACK -->
|
| 284 |
+
<div id="accumHistorySection" style="display:none;margin-bottom:12px;">
|
| 285 |
+
<h3>⚙️ Histórico de Ajustes (DynamicAccumulationCallback)</h3>
|
| 286 |
+
<div id="accumHistoryList" style="background:#0a0e14;border:1px solid var(--border);border-radius:8px;padding:8px;max-height:130px;overflow-y:auto;display:flex;flex-direction:column;gap:4px;"></div>
|
| 287 |
+
</div>
|
| 288 |
+
|
| 289 |
+
<!-- PASSO ATUAL & ETA -->
|
| 290 |
+
<div id="currentStepSection" style="display:none;margin-bottom:10px;">
|
| 291 |
+
<div style="background:#0c1117;border:1px solid var(--border);border-radius:8px;padding:10px 14px;font-family:var(--mono);font-size:13px;display:flex;gap:20px;flex-wrap:wrap;">
|
| 292 |
+
<span><span style="color:var(--muted)">Último passo: </span><span id="mv-laststep" style="color:#ffd479;font-weight:600"></span></span>
|
| 293 |
+
<span><span style="color:var(--muted)">Tempo restante (est.): </span><span id="mv-eta" style="color:#79c0ff;font-weight:600"></span></span>
|
| 294 |
+
<span><span style="color:var(--muted)">Último tempo/passo: </span><span id="mv-steptime" style="color:#7ee787;font-weight:600"></span></span>
|
| 295 |
+
</div>
|
| 296 |
+
</div>
|
| 297 |
+
|
| 298 |
+
<!-- LOSS POR ÉPOCA -->
|
| 299 |
+
<div id="epochLossSection" style="display:none;margin-bottom:12px;">
|
| 300 |
+
<h3>📉 Loss por Época</h3>
|
| 301 |
+
<div id="epochLossBar" style="display:flex;gap:8px;flex-wrap:wrap;align-items:flex-end;"></div>
|
| 302 |
+
</div>
|
| 303 |
+
|
| 304 |
+
<!-- LOGS -->
|
| 305 |
+
<h3>Status do Treino / Logs</h3>
|
| 306 |
+
<div id="trainingBox" style="height:340px;overflow-y:auto;white-space:pre-wrap;border:1px solid var(--border);padding:10px;font-family:var(--mono);font-size:14px;line-height:1.6;background:#0a0e14;border-radius:8px;color:#c9d1d9;">
|
| 307 |
+
À espera do início do treino…
|
| 308 |
+
</div>
|
| 309 |
+
|
| 310 |
+
<!-- INSERE ISTO AQUI -->
|
| 311 |
+
<div id="resizer" class="resizer-v"></div>
|
| 312 |
+
|
| 313 |
+
<h3>Diretório Output</h3>
|
| 314 |
+
<div id="modelDirBox" style="height:200px;overflow-y:auto;border:1px solid var(--border);padding:10px;font-family:var(--mono);font-size:14px;line-height:1.6;background:#0a0e14;border-radius:8px;color:#c9d1d9;">
|
| 315 |
+
A carregar conteúdo do diretório...
|
| 316 |
+
</div>
|
| 317 |
+
<div id="resizer2" class="resizer-v"></div>
|
| 318 |
|
| 319 |
</div>
|
| 320 |
</section>
|
|
|
|
| 488 |
trainingBox.scrollTop = trainingBox.scrollHeight;
|
| 489 |
}
|
| 490 |
|
| 491 |
+
// Guarda histórico de ajustes do callback (apenas em memória, por sessão)
|
| 492 |
+
const accumAdjustHistory = [];
|
| 493 |
+
|
| 494 |
+
function parseMetricsFromLogs(logs) {
|
| 495 |
+
const metrics = {
|
| 496 |
+
accum: null, accumOrigin: null, effbatch: null, steps: null,
|
| 497 |
+
lastStep: null, eta: null, stepTime: null,
|
| 498 |
+
ram: null, newAccumEvents: []
|
| 499 |
+
};
|
| 500 |
+
|
| 501 |
+
for (const line of logs) {
|
| 502 |
+
// ── Padrão do backend: gradient_accumulation_steps atual: N (origem) ──
|
| 503 |
+
const mAccumInfo = line.match(/gradient_accumulation_steps atual:\s*(\d+)\s*\(([^)]+)\)/);
|
| 504 |
+
if (mAccumInfo) { metrics.accum = mAccumInfo[1]; metrics.accumOrigin = mAccumInfo[2]; }
|
| 505 |
+
|
| 506 |
+
// ── Effective Batch via backend ──
|
| 507 |
+
const mEff = line.match(/Effective Batch[^=]+=\s*(\d+)/);
|
| 508 |
+
if (mEff) metrics.effbatch = mEff[1];
|
| 509 |
+
|
| 510 |
+
// ── Total train steps ──
|
| 511 |
+
const mSteps = line.match(/total_train_steps[^:]*:\s*(\d+)/);
|
| 512 |
+
if (mSteps) metrics.steps = mSteps[1];
|
| 513 |
+
|
| 514 |
+
// ── Ajuste MANUAL: 🧭 Ajuste manual aplicado → gradient_accumulation_steps = N (Effective Batch: Y) ──
|
| 515 |
+
const mManual = line.match(/Ajuste manual aplicado.*gradient_accumulation_steps\s*=\s*(\d+).*Effective Batch[:\s]+(\d+)/);
|
| 516 |
+
if (mManual) {
|
| 517 |
+
metrics.accum = mManual[1];
|
| 518 |
+
metrics.effbatch = mManual[2];
|
| 519 |
+
metrics.accumOrigin = 'manual';
|
| 520 |
+
metrics.newAccumEvents.push({ type: 'manual', accum: mManual[1], effbatch: mManual[2], ram: null });
|
| 521 |
+
}
|
| 522 |
+
|
| 523 |
+
// ── RAM ALTA: ⚠️ RAM alta (X%) → diminuindo accumulation_steps de A para B (Effective Batch: Y) ──
|
| 524 |
+
const mHighRam = line.match(/RAM alta \(([0-9.]+)%\).*diminuindo accumulation_steps de (\d+) para (\d+).*Effective Batch[:\s]+(\d+)/);
|
| 525 |
+
if (mHighRam) {
|
| 526 |
+
metrics.accum = mHighRam[3];
|
| 527 |
+
metrics.effbatch = mHighRam[4];
|
| 528 |
+
metrics.accumOrigin = 'auto↓RAM alta';
|
| 529 |
+
metrics.ram = mHighRam[1];
|
| 530 |
+
metrics.newAccumEvents.push({ type: 'high', ram: mHighRam[1], from: mHighRam[2], accum: mHighRam[3], effbatch: mHighRam[4] });
|
| 531 |
+
}
|
| 532 |
+
|
| 533 |
+
// ── RAM OCIOSA: 💡 RAM ociosa (X%) → aumentando accumulation_steps de A para B (Effective Batch: Y) ──
|
| 534 |
+
const mLowRam = line.match(/RAM ociosa \(([0-9.]+)%\).*aumentando accumulation_steps de (\d+) para (\d+).*Effective Batch[:\s]+(\d+)/);
|
| 535 |
+
if (mLowRam) {
|
| 536 |
+
metrics.accum = mLowRam[3];
|
| 537 |
+
metrics.effbatch = mLowRam[4];
|
| 538 |
+
metrics.accumOrigin = 'auto↑RAM ociosa';
|
| 539 |
+
metrics.ram = mLowRam[1];
|
| 540 |
+
metrics.newAccumEvents.push({ type: 'low', ram: mLowRam[1], from: mLowRam[2], accum: mLowRam[3], effbatch: mLowRam[4] });
|
| 541 |
+
}
|
| 542 |
+
|
| 543 |
+
// ── RAM % de qualquer linha de aviso (fallback) ──
|
| 544 |
+
const mRamLine = line.match(/RAM[^(]*\(([0-9.]+)%\)/);
|
| 545 |
+
if (mRamLine && !metrics.ram) metrics.ram = mRamLine[1];
|
| 546 |
+
|
| 547 |
+
// ── Passo: -- | N/M | ... Tempo: | Xs | ──
|
| 548 |
+
const mStep = line.match(/PASSO:\s*-\s*-\s*\|\s*(\d+)\/(\d+)\s*\|[^|]*Tempo:\s*\|\s*([\d.]+)s/);
|
| 549 |
+
if (mStep) {
|
| 550 |
+
metrics.lastStep = `${mStep[1]} / ${mStep[2]}`;
|
| 551 |
+
metrics.stepTime = `${parseFloat(mStep[3]).toFixed(1)}s`;
|
| 552 |
+
const remaining = (parseInt(mStep[2]) - parseInt(mStep[1])) * parseFloat(mStep[3]);
|
| 553 |
+
if (remaining > 0) {
|
| 554 |
+
const h = Math.floor(remaining / 3600), m = Math.floor((remaining % 3600) / 60), s = Math.floor(remaining % 60);
|
| 555 |
+
metrics.eta = h > 0 ? `${h}h ${m}m` : m > 0 ? `${m}m ${s}s` : `${s}s`;
|
| 556 |
+
}
|
| 557 |
+
}
|
| 558 |
+
}
|
| 559 |
+
return metrics;
|
| 560 |
+
}
|
| 561 |
+
|
| 562 |
+
function updateAccumHistory(events) {
|
| 563 |
+
if (!events || events.length === 0) return;
|
| 564 |
+
const section = $('accumHistorySection');
|
| 565 |
+
const list = $('accumHistoryList');
|
| 566 |
+
if (!section || !list) return;
|
| 567 |
+
|
| 568 |
+
// Deduplica: só adiciona eventos realmente novos
|
| 569 |
+
events.forEach(ev => {
|
| 570 |
+
const key = `${ev.type}-${ev.accum}-${ev.effbatch}-${ev.ram}`;
|
| 571 |
+
if (accumAdjustHistory.find(h => h.key === key)) return;
|
| 572 |
+
accumAdjustHistory.push({ key, ...ev, ts: new Date().toLocaleTimeString() });
|
| 573 |
+
|
| 574 |
+
const row = document.createElement('div');
|
| 575 |
+
const isHigh = ev.type === 'high', isManual = ev.type === 'manual';
|
| 576 |
+
const icon = isManual ? '🧭' : isHigh ? '⚠️' : '💡';
|
| 577 |
+
const color = isManual ? '#79c0ff' : isHigh ? '#ff7b72' : '#7ee787';
|
| 578 |
+
const label = isManual
|
| 579 |
+
? `Ajuste manual → accum=${ev.accum}, batch=${ev.effbatch}`
|
| 580 |
+
: `RAM ${ev.ram}% → accum ${ev.from}→${ev.accum} (batch ${ev.effbatch})`;
|
| 581 |
+
row.style = `display:flex;align-items:center;gap:8px;font-family:var(--mono);font-size:11px;padding:3px 6px;border-radius:4px;background:#0c1117;`;
|
| 582 |
+
row.innerHTML = `<span>${icon}</span><span style="color:${color};flex:1">${label}</span><span style="color:var(--muted)">${new Date().toLocaleTimeString()}</span>`;
|
| 583 |
+
list.appendChild(row);
|
| 584 |
+
list.scrollTop = list.scrollHeight;
|
| 585 |
+
});
|
| 586 |
+
|
| 587 |
+
if (accumAdjustHistory.length > 0) section.style.display = '';
|
| 588 |
+
}
|
| 589 |
+
|
| 590 |
+
function updateMetricCard(id, value, fallback='—') {
|
| 591 |
+
const el = $(id); if(el) el.textContent = value ?? fallback;
|
| 592 |
+
}
|
| 593 |
+
|
| 594 |
+
function updateEpochLosses(epoch_losses) {
|
| 595 |
+
const section = $('epochLossSection');
|
| 596 |
+
const bar = $('epochLossBar');
|
| 597 |
+
if (!section || !bar || !epoch_losses || epoch_losses.length === 0) return;
|
| 598 |
+
section.style.display = '';
|
| 599 |
+
bar.innerHTML = '';
|
| 600 |
+
const maxLoss = Math.max(...epoch_losses.map(Number).filter(isFinite));
|
| 601 |
+
epoch_losses.forEach((loss, i) => {
|
| 602 |
+
const val = parseFloat(loss);
|
| 603 |
+
const heightPct = isFinite(val) && maxLoss > 0 ? Math.max(10, (val / maxLoss) * 60) : 30;
|
| 604 |
+
const chip = document.createElement('div');
|
| 605 |
+
chip.style = `display:flex;flex-direction:column;align-items:center;gap:4px;`;
|
| 606 |
+
chip.innerHTML = `
|
| 607 |
+
<div style="font-size:11px;font-family:var(--mono);color:#ffd479;font-weight:600">${isFinite(val)?val.toFixed(4):loss}</div>
|
| 608 |
+
<div style="width:28px;height:${heightPct}px;background:linear-gradient(180deg,#1f6feb,#2ea043);border-radius:4px 4px 0 0;min-height:6px;transition:height .4s;"></div>
|
| 609 |
+
<div style="font-size:10px;color:var(--muted)">É${i+1}</div>
|
| 610 |
+
`;
|
| 611 |
+
bar.appendChild(chip);
|
| 612 |
+
});
|
| 613 |
+
}
|
| 614 |
+
|
| 615 |
function startPolling() {
|
| 616 |
if (pollTimer) return;
|
| 617 |
|
|
|
|
| 621 |
if (!r.ok) throw new Error('HTTP ' + r.status);
|
| 622 |
const data = await r.json();
|
| 623 |
|
| 624 |
+
// — Progresso —
|
|
|
|
|
|
|
| 625 |
if (data.progress !== undefined) {
|
| 626 |
const pct = typeof data.progress === 'object' ? data.progress.percent : data.progress;
|
|
|
|
| 627 |
const progressBar = $('progressBar');
|
| 628 |
+
const progressPct = $('progressPct');
|
| 629 |
if (progressBar) progressBar.style.width = `${pct}%`;
|
| 630 |
+
if (progressPct) progressPct.textContent = `${pct}%`;
|
| 631 |
}
|
| 632 |
|
| 633 |
+
// — Estado —
|
| 634 |
+
const statusColors = { training:'#79c0ff', idle:'var(--muted)', done:'#7ee787', error:'#ff7b72' };
|
| 635 |
+
if (data.status) {
|
| 636 |
+
const el = $('mv-status');
|
| 637 |
+
if (el) { el.textContent = data.status; el.style.color = statusColors[data.status] || '#79c0ff'; }
|
| 638 |
+
}
|
| 639 |
+
|
| 640 |
+
// — Métricas extraídas dos logs (fallback) + campos directos do JSON (prioritários) —
|
| 641 |
+
const allLogs = data.logs || [];
|
| 642 |
+
const m = parseMetricsFromLogs(allLogs);
|
| 643 |
|
| 644 |
+
// Gradient Accumulation: JSON > logs
|
| 645 |
+
const accumVal = data.current_accum_steps ?? m.accum;
|
| 646 |
+
if (accumVal != null) updateMetricCard('mv-accum', String(accumVal));
|
| 647 |
+
|
| 648 |
+
// Effective Batch: JSON > logs
|
| 649 |
+
const effVal = data.effective_batch ?? m.effbatch;
|
| 650 |
+
if (effVal != null) updateMetricCard('mv-effbatch', String(effVal));
|
| 651 |
+
|
| 652 |
+
// Total Steps: JSON > logs
|
| 653 |
+
const stepsVal = data.total_train_steps ?? m.steps;
|
| 654 |
+
if (stepsVal != null) updateMetricCard('mv-steps', String(stepsVal));
|
| 655 |
+
|
| 656 |
+
// Origem do accum (só via logs)
|
| 657 |
+
if (m.accumOrigin) { const el=$('mv-accum-origin'); if(el) el.textContent=m.accumOrigin; }
|
| 658 |
+
|
| 659 |
+
// Passo actual & ETA
|
| 660 |
+
if (m.lastStep || m.eta || m.stepTime) {
|
| 661 |
+
const sec = $('currentStepSection'); if(sec) sec.style.display='';
|
| 662 |
+
if (m.lastStep) updateMetricCard('mv-laststep', m.lastStep);
|
| 663 |
+
if (m.eta) updateMetricCard('mv-eta', m.eta);
|
| 664 |
+
if (m.stepTime) updateMetricCard('mv-steptime', m.stepTime);
|
| 665 |
}
|
| 666 |
|
| 667 |
+
// — RAM: JSON > logs —
|
| 668 |
+
const ramVal = data.ram_percent ?? (m.ram ? parseFloat(m.ram) : null);
|
| 669 |
+
if (ramVal != null) {
|
| 670 |
+
updateMetricCard('mv-ram', `${parseFloat(ramVal).toFixed(1)}%`);
|
| 671 |
+
const bar = $('mv-ram-bar');
|
| 672 |
+
if (bar) {
|
| 673 |
+
bar.style.width = `${ramVal}%`;
|
| 674 |
+
bar.style.background = ramVal > 85 ? '#f85149' : ramVal > 70 ? '#ffd479' : '#7ee787';
|
| 675 |
+
}
|
| 676 |
}
|
| 677 |
|
| 678 |
+
// — Histórico de ajustes do callback —
|
| 679 |
+
updateAccumHistory(m.newAccumEvents);
|
| 680 |
+
|
| 681 |
+
// — Epoch losses —
|
| 682 |
+
updateEpochLosses(data.epoch_losses);
|
| 683 |
+
|
| 684 |
+
// — Logs (só texto limpo) —
|
| 685 |
+
const logsText = allLogs.join('\n');
|
| 686 |
+
const trainingBox = $('trainingBox');
|
| 687 |
+
if (trainingBox) {
|
| 688 |
+
const wasAtBottom = trainingBox.scrollHeight - trainingBox.scrollTop <= trainingBox.clientHeight + 40;
|
| 689 |
+
trainingBox.textContent = logsText;
|
| 690 |
+
if (wasAtBottom) trainingBox.scrollTop = trainingBox.scrollHeight;
|
| 691 |
+
}
|
| 692 |
|
| 693 |
} catch (e) {
|
| 694 |
console.error('Erro ao atualizar treinamento:', e);
|
|
|
|
| 767 |
}
|
| 768 |
}
|
| 769 |
|
| 770 |
+
|
| 771 |
+
|
| 772 |
+
|
| 773 |
+
|
| 774 |
+
|
| 775 |
// =========================
|
| 776 |
// EVENT LISTENERS
|
| 777 |
// =========================
|
|
|
|
| 789 |
case 'tensorboardBtn': el.addEventListener('click',openTensorBoard); break;
|
| 790 |
case 'reduceBtn': el.addEventListener('click',()=>manualAdjustAccumulation(-1)); break;
|
| 791 |
case 'increaseBtn': el.addEventListener('click',()=>manualAdjustAccumulation(1)); break;
|
| 792 |
+
case 'modelDirBtn': el.addEventListener('click', async () => {
|
| 793 |
+
await loadModelDir();
|
| 794 |
+
// Autoscroll para a janela "Diretório Output"
|
| 795 |
+
const box = $('modelDirBox');
|
| 796 |
+
if (box) {
|
| 797 |
+
box.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
| 798 |
+
// Flash visual para indicar que a janela foi atualizada
|
| 799 |
+
box.style.transition = 'box-shadow 0.3s';
|
| 800 |
+
box.style.boxShadow = '0 0 0 2px #1f6feb';
|
| 801 |
+
setTimeout(() => { box.style.boxShadow = 'none'; }, 1200);
|
| 802 |
+
}
|
| 803 |
+
}); break;
|
| 804 |
}
|
| 805 |
});
|
| 806 |
fetchConfigFromServer();
|
| 807 |
});
|
| 808 |
|
| 809 |
+
// Lógica para redimensionar: trainingBox (resizer) e modelDirBox (resizer2)
|
| 810 |
+
function makeResizer(resizerId, targetBoxId) {
|
| 811 |
+
const resizer = document.getElementById(resizerId);
|
| 812 |
+
const box = document.getElementById(targetBoxId);
|
| 813 |
+
if (!resizer || !box) return;
|
| 814 |
+
|
| 815 |
+
resizer.addEventListener('mousedown', function(e) {
|
| 816 |
+
e.preventDefault();
|
| 817 |
+
function onMove(e) {
|
| 818 |
+
const rect = box.getBoundingClientRect();
|
| 819 |
+
const newH = e.clientY - rect.top;
|
| 820 |
+
if (newH > 80 && newH < 1000) box.style.height = newH + 'px';
|
| 821 |
+
}
|
| 822 |
+
function onUp() {
|
| 823 |
+
window.removeEventListener('mousemove', onMove);
|
| 824 |
+
window.removeEventListener('mouseup', onUp);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 825 |
}
|
| 826 |
+
window.addEventListener('mousemove', onMove);
|
| 827 |
+
window.addEventListener('mouseup', onUp);
|
| 828 |
+
});
|
| 829 |
}
|
| 830 |
|
| 831 |
+
makeResizer('resizer', 'trainingBox');
|
| 832 |
+
makeResizer('resizer2', 'modelDirBox');
|
|
|
|
|
|
|
|
|
|
| 833 |
|
| 834 |
</script>
|
| 835 |
</body>
|