llm-tuner 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.es.md +187 -64
- package/README.md +190 -67
- package/package.json +1 -1
- package/src/cli/index.js +78 -23
- package/src/cli/profile.js +14 -1
- package/src/core/engines/lmstudio.js +2 -1
- package/src/core/engines/ollama.js +2 -1
- package/src/core/index.js +2 -0
- package/src/core/telemetry.js +73 -0
- package/src/core/tuner.js +113 -78
- package/src/i18n/en.js +7 -2
- package/src/i18n/es.js +7 -2
- package/src/web/public/app.js +78 -0
- package/src/web/public/index.html +20 -0
- package/src/web/public/style.css +16 -0
package/src/web/public/app.js
CHANGED
|
@@ -202,6 +202,7 @@ async function chooseEngine(e) {
|
|
|
202
202
|
step('model');
|
|
203
203
|
$('#model').innerHTML = `<option>${esc(t('web.readingModels'))}</option>`;
|
|
204
204
|
const { models } = await api(`/api/models?engine=${e.id}`);
|
|
205
|
+
models.sort((a, b) => (a.sizeBytes ?? 0) - (b.sizeBytes ?? 0) || (a.name || '').localeCompare(b.name || ''));
|
|
205
206
|
$('#model').innerHTML = models.length
|
|
206
207
|
? models.map((m) => `<option value="${esc(m.key)}">${esc(m.name)} · ${gb(m.sizeBytes)}${m.quant ? ' · ' + esc(m.quant) : ''}</option>`).join('')
|
|
207
208
|
: `<option value="">${esc(t('web.noModels'))}</option>`;
|
|
@@ -386,6 +387,59 @@ function onCtxChange() {
|
|
|
386
387
|
}
|
|
387
388
|
$('#ctx').oninput = onCtxChange;
|
|
388
389
|
|
|
390
|
+
function updateTelemetry(e) {
|
|
391
|
+
const panel = $('#telemetry-panel');
|
|
392
|
+
if (!panel || !e) return;
|
|
393
|
+
panel.hidden = false;
|
|
394
|
+
|
|
395
|
+
if (e.cpu) {
|
|
396
|
+
const cpuVal = $('#telemetry-cpu-val');
|
|
397
|
+
const cpuFill = $('#telemetry-cpu-fill');
|
|
398
|
+
if (cpuVal) cpuVal.textContent = `${e.cpu.percent}%`;
|
|
399
|
+
if (cpuFill) cpuFill.style.width = `${Math.min(100, Math.max(0, e.cpu.percent))}%`;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (e.ram) {
|
|
403
|
+
const ramVal = $('#telemetry-ram-val');
|
|
404
|
+
const ramFill = $('#telemetry-ram-fill');
|
|
405
|
+
if (ramVal) ramVal.textContent = `${gb(e.ram.usedBytes)} / ${gb(e.ram.totalBytes)} (${e.ram.percent}%)`;
|
|
406
|
+
if (ramFill) ramFill.style.width = `${Math.min(100, Math.max(0, e.ram.percent))}%`;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (e.gpus) {
|
|
410
|
+
const box = $('#telemetry-gpus');
|
|
411
|
+
if (box) {
|
|
412
|
+
box.innerHTML = e.gpus.map((g) => {
|
|
413
|
+
const name = g.name.replace(/^NVIDIA GeForce /, '');
|
|
414
|
+
return `<div class="telemetry-card">
|
|
415
|
+
<div class="telemetry-header"><span>GPU ${g.index} (${esc(name)})</span> <strong>${gb(g.usedBytes)} / ${gb(g.totalBytes)} (${g.percent}%)</strong></div>
|
|
416
|
+
<div class="meter-bar"><div class="meter-fill" style="width: ${Math.min(100, Math.max(0, g.percent))}%"></div></div>
|
|
417
|
+
</div>`;
|
|
418
|
+
}).join('');
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function updateProgress(p, label) {
|
|
424
|
+
const wrap = $('#load-progress-wrap');
|
|
425
|
+
if (!wrap || !p) return;
|
|
426
|
+
wrap.hidden = false;
|
|
427
|
+
|
|
428
|
+
const bar = $('#load-progress-bar');
|
|
429
|
+
const pct = $('#load-progress-pct');
|
|
430
|
+
const lbl = $('#load-progress-label');
|
|
431
|
+
|
|
432
|
+
if (pct) pct.textContent = `${p.pct}%`;
|
|
433
|
+
if (bar) bar.style.width = `${p.pct}%`;
|
|
434
|
+
if (lbl && label !== undefined) {
|
|
435
|
+
let text = label || '';
|
|
436
|
+
if (p.phase === 'bench' && p.current && p.total) {
|
|
437
|
+
text = `[${p.current}/${p.total}] ${text}`.trim();
|
|
438
|
+
}
|
|
439
|
+
lbl.textContent = text;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
389
443
|
// ---------- Load ----------
|
|
390
444
|
$('#load-btn').onclick = async () => {
|
|
391
445
|
const model = $('#model').value;
|
|
@@ -407,6 +461,17 @@ $('#load-btn').onclick = async () => {
|
|
|
407
461
|
$('#load-result').innerHTML = '';
|
|
408
462
|
const log = $('#load-log');
|
|
409
463
|
log.textContent = '';
|
|
464
|
+
// Reset progress and telemetry
|
|
465
|
+
$('#load-progress-wrap').hidden = false;
|
|
466
|
+
$('#load-progress-bar').style.width = '0%';
|
|
467
|
+
$('#load-progress-pct').textContent = '0%';
|
|
468
|
+
$('#load-progress-label').textContent = '';
|
|
469
|
+
$('#telemetry-panel').hidden = false;
|
|
470
|
+
$('#telemetry-cpu-val').textContent = '0%';
|
|
471
|
+
$('#telemetry-cpu-fill').style.width = '0%';
|
|
472
|
+
$('#telemetry-ram-val').textContent = '0 / 0 GB (0%)';
|
|
473
|
+
$('#telemetry-ram-fill').style.width = '0%';
|
|
474
|
+
$('#telemetry-gpus').innerHTML = '';
|
|
410
475
|
// One element per line, so a warning can be styled on its own.
|
|
411
476
|
const addLog = (s, level = 'note') => {
|
|
412
477
|
const line = document.createElement('span');
|
|
@@ -424,6 +489,17 @@ $('#load-btn').onclick = async () => {
|
|
|
424
489
|
// Progress event before the current one: a fallback that repeats its reason is not logged twice.
|
|
425
490
|
let previous = null;
|
|
426
491
|
const done = await follow(jobId, (e) => {
|
|
492
|
+
if (e.type === 'telemetry') {
|
|
493
|
+
updateTelemetry(e);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
if (e.progress) {
|
|
497
|
+
let label = '';
|
|
498
|
+
if (e.type === 'status' && e.code) label = t(e.code, e.params);
|
|
499
|
+
else if (e.type === 'candidate-start') label = describe(e.candidate);
|
|
500
|
+
else if (e.type === 'bench-progress' && e.phase) label = e.phase === 'deep' ? t('common.longPrompt', { tokens: e.promptTokens }) : '';
|
|
501
|
+
updateProgress(e.progress, label);
|
|
502
|
+
}
|
|
427
503
|
if (e.type === 'variant-picked' || e.type === 'profile-fallback') {
|
|
428
504
|
for (const line of profileEventLines(e, t, { engineName: state.engine.name, previous })) addLog(line.text, line.level);
|
|
429
505
|
}
|
|
@@ -456,6 +532,8 @@ $('#load-btn').onclick = async () => {
|
|
|
456
532
|
const loaded = done.result?.loaded;
|
|
457
533
|
throw new Error(done.code || done.error ? errText(done) : loaded && !loaded.ok ? benchErr(loaded) : t('bench.loadFailed'));
|
|
458
534
|
}
|
|
535
|
+
updateProgress({ pct: 100, phase: 'done' });
|
|
536
|
+
|
|
459
537
|
const r = done.result;
|
|
460
538
|
if (r.report?.best) {
|
|
461
539
|
const idx = r.report.results.findIndex((x) => x.candidate.id === r.report.best.candidate.id);
|
|
@@ -75,6 +75,26 @@
|
|
|
75
75
|
|
|
76
76
|
<section id="sec-load" class="panel" hidden>
|
|
77
77
|
<header><h2 id="load-title" data-i18n="web.loading">Loading</h2><span class="pill" id="load-status" data-i18n="web.inProgress">in progress</span></header>
|
|
78
|
+
<div id="load-progress-wrap" class="progress-wrap" hidden>
|
|
79
|
+
<div class="progress-bar-track">
|
|
80
|
+
<div id="load-progress-bar" class="progress-bar-fill" style="width: 0%"></div>
|
|
81
|
+
</div>
|
|
82
|
+
<div class="progress-meta">
|
|
83
|
+
<span id="load-progress-label"></span>
|
|
84
|
+
<span id="load-progress-pct">0%</span>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
<div id="telemetry-panel" class="telemetry-panel" hidden>
|
|
88
|
+
<div class="telemetry-card">
|
|
89
|
+
<div class="telemetry-header"><span data-i18n="web.telemetryCpu">CPU</span> <strong id="telemetry-cpu-val">0%</strong></div>
|
|
90
|
+
<div class="meter-bar"><div id="telemetry-cpu-fill" class="meter-fill" style="width: 0%"></div></div>
|
|
91
|
+
</div>
|
|
92
|
+
<div class="telemetry-card">
|
|
93
|
+
<div class="telemetry-header"><span data-i18n="web.telemetryRam">RAM</span> <strong id="telemetry-ram-val">0 / 0 GB (0%)</strong></div>
|
|
94
|
+
<div class="meter-bar"><div id="telemetry-ram-fill" class="meter-fill" style="width: 0%"></div></div>
|
|
95
|
+
</div>
|
|
96
|
+
<div id="telemetry-gpus" class="telemetry-gpus"></div>
|
|
97
|
+
</div>
|
|
78
98
|
<div id="bench" hidden>
|
|
79
99
|
<p class="muted small" data-i18n="web.benchIntro">There is no preset for this context and profile: several configurations are tested on your hardware. It takes a few minutes and only happens once.</p>
|
|
80
100
|
<div class="table-wrap">
|
package/src/web/public/style.css
CHANGED
|
@@ -328,3 +328,19 @@ td .err { color: var(--bad); white-space: normal; }
|
|
|
328
328
|
.chat-form input { flex: 1; min-width: 0; }
|
|
329
329
|
.chat-form button { flex-shrink: 0; }
|
|
330
330
|
|
|
331
|
+
/* Progress bar and real-time telemetry */
|
|
332
|
+
.progress-wrap { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
|
|
333
|
+
.progress-bar-track { width: 100%; height: 8px; background: var(--code-bg); border: 1px solid var(--line); border-radius: 4px; overflow: hidden; }
|
|
334
|
+
.progress-bar-fill { height: 100%; background: var(--accent); transition: width .2s ease; }
|
|
335
|
+
.progress-meta { display: flex; justify-content: space-between; align-items: center; font: 500 .8rem var(--mono); color: var(--muted); }
|
|
336
|
+
.progress-meta strong { color: var(--ink); }
|
|
337
|
+
|
|
338
|
+
.telemetry-panel { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; margin-top: 10px; }
|
|
339
|
+
.telemetry-card { background: var(--bg); border: 1px solid var(--line); border-radius: 8px; padding: 10px 12px; display: grid; gap: 6px; }
|
|
340
|
+
.telemetry-header { display: flex; justify-content: space-between; align-items: center; font: 500 .8rem var(--mono); color: var(--muted); }
|
|
341
|
+
.telemetry-header strong { color: var(--ink); }
|
|
342
|
+
.telemetry-gpus { grid-column: 1 / -1; display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
|
|
343
|
+
.meter-bar { width: 100%; height: 8px; background: var(--code-bg); border: 1px solid var(--line); border-radius: 4px; overflow: hidden; }
|
|
344
|
+
.meter-fill { height: 100%; background: var(--gpu); transition: width .2s ease; }
|
|
345
|
+
#telemetry-cpu-fill { background: var(--accent); }
|
|
346
|
+
|