File size: 4,953 Bytes
5d037dd
 
 
fc9b90e
f3b6176
fc9b90e
 
5d037dd
f3b6176
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fc9b90e
f3b6176
 
 
 
 
6d7ed4f
f3b6176
 
 
 
82534fe
f3b6176
 
 
 
 
c187faa
f3b6176
 
 
 
 
fc9b90e
a981da6
 
f3b6176
fc9b90e
f3b6176
5d037dd
f3b6176
fc9b90e
a981da6
c187faa
fc9b90e
5d037dd
f3b6176
 
c187faa
f3b6176
fc9b90e
82534fe
 
f3b6176
 
 
 
a981da6
 
fc9b90e
82534fe
fc9b90e
 
 
 
 
 
c187faa
 
f3b6176
c187faa
 
fc9b90e
 
 
f3b6176
fc9b90e
 
f3b6176
 
fc9b90e
f3b6176
 
 
 
fc9b90e
a981da6
 
fc9b90e
 
c187faa
 
fc9b90e
 
c187faa
82534fe
fc9b90e
 
a981da6
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ONNX WebGPU Download & Streaming Text Generation</title>
<script type="module">
import { pipeline } from 'https://cdn.jsdelivr.net/npm/@huggingface/[email protected]/dist/transformers.min.js';

// Helper: download a file with progress
async function downloadWithProgress(url, onProgress) {
    const response = await fetch(url);
    if (!response.ok) throw new Error("Network response was not ok");

    const contentLength = +response.headers.get("Content-Length");
    if (!contentLength) throw new Error("Content-Length header missing");

    const reader = response.body.getReader();
    let received = 0;
    const chunks = [];

    while(true) {
        const { done, value } = await reader.read();
        if (done) break;
        chunks.push(value);
        received += value.length;
        onProgress(received / contentLength);
    }

    const blob = new Blob(chunks);
    return blob;
}

async function runPrompt(prompt) {
    const progressBar = document.getElementById("progress");
    const progressText = document.getElementById("progressText");
    const outputElem = document.getElementById("output");

    // 1️⃣ Download model ONNX file manually
    const modelUrl = "https://huggingface.co/onnx-community/gemma-3-1b-it-ONNX/resolve/main/onnx/model_q4.onnx"; // example file
    console.log("%cDownloading model...", "color: orange; font-weight: bold;");
    outputElem.textContent = "Downloading model...\n";
    progressBar.style.width = "0%";
    progressText.textContent = "0%";

    await downloadWithProgress(modelUrl, (ratio) => {
        const pct = Math.floor(ratio * 100);
        progressBar.style.width = pct + "%";
        progressText.textContent = pct + "%";
    });

    console.log("%cModel downloaded!", "color: green; font-weight: bold;");
    outputElem.textContent += "Model downloaded.\n";

    // 2️⃣ Initialize generator using WebGPU
    outputElem.textContent += "Initializing pipeline...\n";
    const generator = await pipeline(
        "text-generation",
        "onnx-community/gemma-3-1b-it-ONNX",
        { dtype: "q4", providers: ["webgpu"] }
    );
    outputElem.textContent += "Pipeline ready. Generating text...\n";

    // 3️⃣ Stream output
    const messages = [
        { role: "system", content: "You are a helpful assistant." },
        { role: "user", content: prompt },
    ];

    let generatedTokens = 0;
    const maxTokens = 512;

    for await (const token of generator.stream(messages, { max_new_tokens: maxTokens, do_sample: false })) {
        outputElem.textContent += token.content;
        outputElem.scrollTop = outputElem.scrollHeight;

        generatedTokens++;
        const progress = Math.min((generatedTokens / maxTokens) * 100, 100);
        progressBar.style.width = progress + "%";
        progressText.textContent = Math.floor(progress) + "%";
    }

    progressBar.style.width = "100%";
    progressText.textContent = "100%";
    console.log("%cGeneration complete!", "color: blue; font-weight: bold;");
}

window.onload = () => {
    const form = document.getElementById("promptForm");
    form.onsubmit = (e) => {
        e.preventDefault();
        const prompt = document.getElementById("promptInput").value;
        document.getElementById("output").textContent = "";
        runPrompt(prompt);
    }
};
</script>
<style>
body { font-family: 'Segoe UI', sans-serif; background: #1e1e2f; color: #f0f0f0; display: flex; flex-direction: column; align-items: center; padding: 2rem; }
h1 { color: #ffcc00; margin-bottom: 1rem; }
#promptForm { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
#promptInput { padding: 0.5rem; width: 400px; border-radius: 5px; border: none; font-size: 1rem; }
#generateBtn { padding: 0.5rem 1rem; background: #ffcc00; border: none; border-radius: 5px; font-weight: bold; cursor: pointer; color: #1e1e2f; }
#generateBtn:hover { background: #ffdd33; }
#progressContainer { width: 400px; height: 20px; background: #333; border-radius: 5px; overflow: hidden; margin-bottom: 1rem; position: relative; }
#progress { width: 0%; height: 100%; background: #ffcc00; transition: width 0.05s; }
#progressText { position: absolute; right: 10px; top: 0; bottom: 0; display: flex; align-items: center; justify-content: flex-end; font-size: 0.8rem; color: #1e1e2f; font-weight: bold; }
pre#output { width: 400px; background: #2e2e44; padding: 1rem; border-radius: 8px; white-space: pre-wrap; word-wrap: break-word; min-height: 150px; overflow-y: auto; }
</style>
</head>
<body>
<h1>ONNX WebGPU Streaming Text Generation</h1>
<form id="promptForm">
    <input id="promptInput" type="text" placeholder="Type your prompt..." required />
    <button id="generateBtn">Generate</button>
</form>
<div id="progressContainer">
    <div id="progress"></div>
    <div id="progressText">0%</div>
</div>
<pre id="output">Enter a prompt and hit Generate...</pre>
</body>
</html>