| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>QED-75M Playground</title> |
| <style> |
| body { |
| font-family: system-ui, -apple-system, sans-serif; |
| max-width: 800px; |
| margin: 0 auto; |
| padding: 20px; |
| background: #f5f5f5; |
| } |
| .container { |
| background: white; |
| border-radius: 8px; |
| padding: 20px; |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| } |
| h1 { color: #333; } |
| textarea { |
| width: 100%; |
| min-height: 100px; |
| padding: 12px; |
| border: 1px solid #ddd; |
| border-radius: 4px; |
| font-size: 16px; |
| resize: vertical; |
| } |
| button { |
| background: #007bff; |
| color: white; |
| border: none; |
| padding: 12px 24px; |
| border-radius: 4px; |
| font-size: 16px; |
| cursor: pointer; |
| margin-top: 10px; |
| } |
| button:hover { background: #0056b3; } |
| button:disabled { background: #ccc; cursor: not-allowed; } |
| .output { |
| margin-top: 20px; |
| padding: 15px; |
| background: #f8f9fa; |
| border-radius: 4px; |
| white-space: pre-wrap; |
| line-height: 1.6; |
| } |
| .status { |
| margin-top: 10px; |
| padding: 10px; |
| border-radius: 4px; |
| font-size: 14px; |
| } |
| .loading { background: #fff3cd; color: #856404; } |
| .ready { background: #d4edda; color: #155724; } |
| .error { background: #f8d7da; color: #721c24; } |
| .settings { |
| margin-top: 15px; |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); |
| gap: 10px; |
| } |
| .settings label { |
| display: block; |
| font-size: 14px; |
| margin-bottom: 5px; |
| } |
| .settings input { |
| width: 100%; |
| padding: 8px; |
| border: 1px solid #ddd; |
| border-radius: 4px; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <h1>🧪 QED-75M Playground</h1> |
| |
| <div id="status" class="status loading">Loading model...</div> |
| |
| <div class="settings"> |
| <div> |
| <label>Max tokens: <span id="maxTokensVal">128</span></label> |
| <input type="range" id="maxTokens" min="32" max="512" value="128" step="32"> |
| </div> |
| <div> |
| <label>Temperature: <span id="tempVal">0.7</span></label> |
| <input type="range" id="temperature" min="0.1" max="1.5" value="0.7" step="0.1"> |
| </div> |
| <div> |
| <label>Top K: <span id="topKVal">40</span></label> |
| <input type="range" id="topK" min="10" max="100" value="40" step="10"> |
| </div> |
| </div> |
| |
| <textarea id="prompt" placeholder="Enter your prompt here... |
| |
| Example: <|user|>What is 2+2?<|assistant|>"></textarea> |
| |
| <button id="generateBtn" disabled>Generate</button> |
| |
| <div class="output" id="output"></div> |
| </div> |
|
|
| <script type="module"> |
| |
| import { AutoTokenizer, AutoModelForCausalLM, GenerationConfig } from 'https://cdn.jsdelivr.net/npm/@xenova/transformers@2.14.0'; |
| |
| const MODEL_ID = 'levossadtchi/QED-75M_web'; |
| |
| let tokenizer = null; |
| let model = null; |
| |
| |
| const statusEl = document.getElementById('status'); |
| const promptEl = document.getElementById('prompt'); |
| const outputEl = document.getElementById('output'); |
| const generateBtn = document.getElementById('generateBtn'); |
| const maxTokensEl = document.getElementById('maxTokens'); |
| const tempEl = document.getElementById('temperature'); |
| const topKEl = document.getElementById('topK'); |
| |
| |
| maxTokensEl.addEventListener('input', (e) => { |
| document.getElementById('maxTokensVal').textContent = e.target.value; |
| }); |
| tempEl.addEventListener('input', (e) => { |
| document.getElementById('tempVal').textContent = e.target.value; |
| }); |
| topKEl.addEventListener('input', (e) => { |
| document.getElementById('topKVal').textContent = e.target.value; |
| }); |
| |
| |
| async function loadModel() { |
| try { |
| |
| tokenizer = await AutoTokenizer.from_pretrained(MODEL_ID, { |
| local_files_only: false, |
| }); |
| |
| |
| model = await AutoModelForCausalLM.from_pretrained(MODEL_ID, { |
| quantized: true, |
| dtype: 'q8', |
| device: 'webgpu', |
| }); |
| |
| statusEl.textContent = '✅ Model ready!'; |
| statusEl.className = 'status ready'; |
| generateBtn.disabled = false; |
| } catch (error) { |
| statusEl.textContent = '❌ Error loading model: ' + error.message; |
| statusEl.className = 'status error'; |
| console.error('Model loading error:', error); |
| } |
| } |
| |
| |
| async function generate() { |
| const prompt = promptEl.value.trim(); |
| if (!prompt) return; |
| |
| generateBtn.disabled = true; |
| generateBtn.textContent = 'Generating...'; |
| outputEl.textContent = ''; |
| |
| try { |
| |
| const inputs = await tokenizer(prompt, { |
| return_tensors: 'pt', |
| add_special_tokens: false, |
| }); |
| |
| |
| const outputs = await model.generate({ |
| ...inputs, |
| max_new_tokens: parseInt(maxTokensEl.value), |
| temperature: parseFloat(tempEl.value), |
| top_k: parseInt(topKEl.value), |
| do_sample: parseFloat(tempEl.value) > 0, |
| eos_token_id: tokenizer.eos_token_id, |
| pad_token_id: tokenizer.pad_token_id, |
| }); |
| |
| |
| const text = tokenizer.decode(outputs[0], { |
| skip_special_tokens: false, |
| }); |
| outputEl.textContent = text; |
| } catch (error) { |
| outputEl.textContent = 'Error: ' + error.message; |
| } finally { |
| generateBtn.disabled = false; |
| generateBtn.textContent = 'Generate'; |
| } |
| } |
| |
| generateBtn.addEventListener('click', generate); |
| |
| |
| promptEl.addEventListener('keydown', (e) => { |
| if (e.ctrlKey && e.key === 'Enter') { |
| generate(); |
| } |
| }); |
| |
| |
| loadModel(); |
| </script> |
| </body> |
| </html> |
|
|