hetki ennen webgpu inferenssiä
This commit is contained in:
@@ -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>
|
||||
<span style="color:#79c0ff">"prompt"</span>: <span style="color:#a5d6ff">"Write a bubble sort"</span>,<br>
|
||||
<span style="color:#79c0ff">"system"</span>: <span style="color:#a5d6ff">"You are a Python expert. Write only code."</span>,<br>
|
||||
<span style="color:#79c0ff">"max_tokens"</span>: <span style="color:#79c0ff">128</span>,<br>
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user