hetki ennen webgpu inferenssiä

This commit is contained in:
2026-04-02 12:49:40 +03:00
parent d2920e5ab4
commit e1326b145e
10 changed files with 375 additions and 80 deletions

View File

@@ -567,9 +567,37 @@
</div>
</label>
</div>
<div style="display:flex;gap:8px">
<input type="text" id="code-input" placeholder="e.g. Write a Python function that checks if a number is prime" style="flex:1;background:var(--panel-bg);border:1px solid var(--border-color);border-radius:4px;padding:8px 12px;color:var(--text-color);font-size:14px;outline:none">
<button id="code-send-btn" style="background:#238636;color:#fff;border:1px solid rgba(240,246,252,0.1);border-radius:4px;padding:8px 16px;font-size:14px;cursor:pointer">Generate</button>
<div style="display:flex;gap:8px;align-items:start">
<div style="flex:1">
<div style="display:flex;gap:8px;margin-bottom:4px">
<input type="text" id="code-input" placeholder='e.g. Write a Python function that checks if a number is prime' style="flex:1;background:var(--panel-bg);border:1px solid var(--border-color);border-radius:4px;padding:8px 12px;color:var(--text-color);font-size:14px;outline:none;display:block" >
<textarea id="code-input-json" placeholder='{"prompt":"Write a fibonacci function","system":"You are a Python expert","max_tokens":128}' style="flex:1;background:var(--panel-bg);border:1px solid var(--border-color);border-radius:4px;padding:8px 12px;color:var(--text-color);font-size:13px;font-family:Courier New,monospace;outline:none;resize:vertical;min-height:60px;display:none"></textarea>
<button id="code-send-btn" style="background:#238636;color:#fff;border:1px solid rgba(240,246,252,0.1);border-radius:4px;padding:8px 16px;font-size:14px;cursor:pointer;align-self:stretch">Generate</button>
</div>
<div style="display:flex;justify-content:space-between;align-items:center">
<label style="font-size:11px;color:#8b949e;cursor:pointer;display:flex;align-items:center;gap:4px">
<input type="checkbox" id="json-mode-toggle" style="accent-color:var(--accent-color)"> JSON mode
</label>
<details id="json-help" style="font-size:11px;color:#8b949e;display:none">
<summary style="cursor:pointer;color:var(--accent-color)">JSON syntax</summary>
<div style="background:#010409;border:1px solid var(--border-color);border-radius:4px;padding:10px;margin-top:6px;font-family:Courier New,monospace;font-size:12px;line-height:1.6;color:var(--text-color)">
{<br>
&nbsp;&nbsp;<span style="color:#79c0ff">"prompt"</span>: <span style="color:#a5d6ff">"Write a bubble sort"</span>,<br>
&nbsp;&nbsp;<span style="color:#79c0ff">"system"</span>: <span style="color:#a5d6ff">"You are a Python expert. Write only code."</span>,<br>
&nbsp;&nbsp;<span style="color:#79c0ff">"max_tokens"</span>: <span style="color:#79c0ff">128</span>,<br>
&nbsp;&nbsp;<span style="color:#79c0ff">"language"</span>: <span style="color:#a5d6ff">"python"</span><br>
}
<div style="margin-top:8px;color:#8b949e;font-family:sans-serif">
<strong style="color:var(--text-color)">Fields:</strong><br>
<code>prompt</code> (required) — the coding task<br>
<code>system</code> — system prompt override<br>
<code>max_tokens</code> — max tokens to generate (default: 128)<br>
<code>language</code> — hint for syntax highlighting
</div>
</div>
</details>
</div>
</div>
</div>
<div id="code-loading" style="display:none;margin-top:8px;font-size:12px;color:#d29922">Starting Coder model...</div>
</div>
@@ -634,12 +662,19 @@
document.querySelectorAll('.main-panel').forEach(p => p.classList.remove('active'));
document.querySelectorAll('.main-tab').forEach(t => t.classList.remove('active'));
document.getElementById('panel-' + tab).classList.add('active');
event.target.classList.add('active');
document.querySelector(`.main-tab[onclick*="${tab}"]`).classList.add('active');
window.location.hash = tab;
};
// URL-hash navigointi: #codelab tai #network
if (window.location.hash === '#codelab') {
switchMainTab('codelab');
}
// Koodilaboratorion tila
const codeMetrics = { tasks: 0, tokens: 0, lastSpeed: 0 };
let coderJoined = false;
let wasmInitialized = false;
let coderSize = '05b'; // '05b' tai '3b'
// Mallivalinnan radio-napit
@@ -780,6 +815,30 @@
// Kytkemme sivuston UI-puolen (JS) omaan passiiviseen WebSocket-kuuntelijaan.
const uiSocket = new WebSocket(`${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws`);
uiSocket.onopen = () => {
const el = document.getElementById('node-status');
el.textContent = 'Connected';
el.style.color = '#d29922';
// Lähetetään kevyt auth heti — admin näkee kävijän välittömästi
const hasGPU = !!navigator.gpu;
uiSocket.send(JSON.stringify({
type: 'auth',
status: 'viewer',
node_type: 'browser',
platform: navigator.platform || '',
cpu_cores: navigator.hardwareConcurrency || 0,
device_memory_gb: navigator.deviceMemory || 0,
allocated_gb: 0,
selected_task: 'viewer',
has_webgpu: hasGPU,
}));
};
uiSocket.onclose = () => {
const el = document.getElementById('node-status');
el.textContent = 'Disconnected';
el.style.color = '#f85149';
};
uiSocket.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
@@ -980,18 +1039,20 @@
}
const gpuStr = hasWebGPU ? (deviceInfo.gpu?.description || deviceInfo.gpu?.vendor || "WebGPU") : "ei GPU:ta";
const backendStr = hasWebGPU ? "WebGPU" : "CPU (NdArray)";
// Laskenta käyttää aina CPU:ta (Candle), WebGPU on vain tensorilaskennassa (Burn)
const computeBackend = (selectedTask === 'tokenize')
? (hasWebGPU ? "WebGPU + CPU" : "CPU")
: "CPU (Candle Wasm)";
const vramStr = deviceInfo.gpu?.estimated_vram_gb ? `~${deviceInfo.gpu.estimated_vram_gb} GB` : "?";
// navigator.deviceMemory on rajoitettu max 8 GB:iin — merkitään arvio
const ramNote = deviceInfo.device_memory_gb >= 8 ? "8+ GB (selaimen raja)" : `~${deviceInfo.device_memory_gb} GB`;
// Näytetään laitetiedot paneelissa
const diPanel = document.getElementById('device-info');
diPanel.style.display = 'block';
diPanel.innerHTML = [
`Backend: <span>${backendStr}</span>`,
`GPU: <span>${gpuStr}</span>`,
`Laskenta: <span>${computeBackend}</span>`,
hasWebGPU ? `GPU: <span>${gpuStr}</span>` : `GPU: <span style="color:#f85149">ei WebGPU:ta</span>`,
hasWebGPU ? `VRAM: <span>${vramStr}</span>` : null,
`CPU: <span>${deviceInfo.cpu_cores} ydintä</span>`,
`RAM: <span>${ramNote}</span>`,
@@ -1004,7 +1065,7 @@
if (hasWebGPU) {
banner.className = 'compat-banner gpu';
banner.innerHTML = `GPU-kiihdytys aktiivinen — ${gpuStr}`;
banner.innerHTML = `WebGPU tunnistettu — ${gpuStr}. Tokenisaatio käyttää GPU:ta, LLM-inferenssi CPU:ta (Candle Wasm).`;
} else {
// Tunnistetaan selain ohjeen personointia varten
const ua = navigator.userAgent;
@@ -1063,8 +1124,11 @@
}
try {
console.log("Ladataan Burn Wasm -binääriä...");
await init();
if (!wasmInitialized) {
console.log("Ladataan Burn Wasm -binääriä...");
await init();
wasmInitialized = true;
}
window.wasm_active = true;
metrics.startTime = Date.now();
@@ -1095,6 +1159,23 @@
let coderWs = null; // Erillinen WS coder-nodelle
let pendingCodePrompt = null;
// Yksinkertainen Python-syntaksikorostus
function highlightPython(code) {
return code
// Kommentit
.replace(/(#.*)/g, '<span style="color:#8b949e">$1</span>')
// Merkkijonot (f-stringit, tavalliset)
.replace(/(f?"[^"]*"|f?'[^']*')/g, '<span style="color:#a5d6ff">$1</span>')
// Avainsanat
.replace(/\b(def|return|if|elif|else|for|while|in|not|and|or|is|import|from|class|try|except|with|as|lambda|yield|True|False|None|raise|pass|break|continue)\b/g, '<span style="color:#ff7b72">$1</span>')
// Sisäänrakennetut funktiot
.replace(/\b(print|len|range|int|str|float|list|dict|set|tuple|type|isinstance|enumerate|zip|map|filter|sorted|reversed|sum|min|max|abs|round|input|open)\b/g, '<span style="color:#d2a8ff">$1</span>')
// Numerot
.replace(/\b(\d+\.?\d*)\b/g, '<span style="color:#79c0ff">$1</span>')
// Dekoraattorit
.replace(/(@\w+)/g, '<span style="color:#d2a8ff">$1</span>');
}
function addCodeResult(data) {
const model = data.model || 'Coder';
const tokGen = data.tokens_generated || 0;
@@ -1122,7 +1203,7 @@
card.className = 'code-task-card';
card.innerHTML = `
<div class="prompt">${data.prompt || ''}</div>
<div class="code-output">${response}</div>
<div class="code-output">${highlightPython(response)}</div>
<div class="meta">
${model} · ${tokGen} tokenia · ${typeof durMs === 'number' ? durMs.toFixed(0) : durMs}ms · ${tokS} tok/s
</div>`;
@@ -1194,7 +1275,10 @@
setStep('step-wasm', 'active');
try {
await init();
if (!wasmInitialized) {
await init();
wasmInitialized = true;
}
setStep('step-wasm', 'done');
setStep('step-tokenizer', 'active');
@@ -1208,7 +1292,15 @@
selected_task: coderSize === '3b' ? 'qwen-coder-3b' : 'qwen-coder-05b'
};
const taskId = coderSize === '3b' ? 5 : 4;
await start_agent_node(wsUrl, false, JSON.stringify(deviceInfo), taskId);
// Tunnistetaan WebGPU myös koodilaboratorion puolella
let coderHasWebGPU = false;
if (navigator.gpu) {
try {
const adapter = await navigator.gpu.requestAdapter();
coderHasWebGPU = !!adapter;
} catch(e) {}
}
await start_agent_node(wsUrl, coderHasWebGPU, JSON.stringify(deviceInfo), taskId);
document.getElementById('coder-status').textContent = 'Connected';
document.getElementById('coder-status').style.color = '#d29922';
coderWsReady = true;
@@ -1225,6 +1317,24 @@
}
}
// JSON mode toggle
const jsonToggle = document.getElementById('json-mode-toggle');
const jsonHelp = document.getElementById('json-help');
const textInput = document.getElementById('code-input');
const jsonInput = document.getElementById('code-input-json');
jsonToggle?.addEventListener('change', () => {
if (jsonToggle.checked) {
textInput.style.display = 'none';
jsonInput.style.display = 'block';
jsonHelp.style.display = 'block';
} else {
textInput.style.display = 'block';
jsonInput.style.display = 'none';
jsonHelp.style.display = 'none';
}
});
function sendCodeToHub(text) {
if (uiSocket && uiSocket.readyState === 1) {
uiSocket.send(JSON.stringify({ type: 'user_text', text: text, task_type: 'qwen-coder' }));
@@ -1232,15 +1342,34 @@
}
async function handleCodeSubmit() {
const text = codeInput.value.trim();
if (!text) return;
codeInput.value = '';
let promptText;
if (jsonToggle.checked) {
// JSON mode
const raw = jsonInput.value.trim();
if (!raw) return;
try {
const parsed = JSON.parse(raw);
if (!parsed.prompt) { alert('JSON must contain "prompt" field'); return; }
// Lähetetään koko JSON hubille — node lukee promptin ja parametrit
promptText = raw;
} catch(e) {
alert('Invalid JSON: ' + e.message);
return;
}
} else {
// Text mode
promptText = textInput.value.trim();
if (!promptText) return;
textInput.value = '';
}
codeSendBtn.disabled = true;
codeSendBtn.textContent = 'Generating...';
codeLoading.style.display = 'block';
if (!coderJoined) {
pendingCodePrompt = text;
pendingCodePrompt = promptText;
const dlSize = coderSize === '3b' ? '~6.2 GB' : '~990 MB';
codeLoading.textContent = `Loading Qwen2.5-Coder-${coderSize === '3b' ? '3B' : '0.5B'} (${dlSize} on first run)...`;
await ensureCoderNode();
@@ -1248,12 +1377,12 @@
codeLoading.textContent = 'Generating code...';
document.getElementById('coder-status').textContent = 'Computing';
document.getElementById('coder-status').style.color = 'var(--success-color)';
sendCodeToHub(text);
sendCodeToHub(promptText);
}
}
codeSendBtn?.addEventListener('click', handleCodeSubmit);
codeInput?.addEventListener('keydown', (e) => { if (e.key === 'Enter') handleCodeSubmit(); });
textInput?.addEventListener('keydown', (e) => { if (e.key === 'Enter') handleCodeSubmit(); });
</script>
</body>
</html>