| <html> |
| <head> |
| <meta content="text/html;charset=utf-8" http-equiv="Content-Type" /> |
| <title>EasyChat - 安风 Rust/Web ASSEMBLY</title> |
| </head> |
| <body></body> |
| </html> |
|
|
| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta charset="UTF-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| <link |
| rel="stylesheet" |
| href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.8.0/build/styles/default.min.css" |
| /> |
| <style> |
| @import url("https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@200;300;400&family=Source+Sans+3:wght@100;200;300;400;500;600;700;800;900&display=swap"); |
| html, |
| body { |
| font-family: "Source Sans 3", sans-serif; |
| } |
| code, |
| output, |
| select, |
| pre { |
| font-family: "Source Code Pro", monospace; |
| } |
| </style> |
| <style type="text/tailwindcss"> |
| .link { |
| @apply underline hover:text-blue-500 hover:no-underline; |
| } |
| </style> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script type="module"> |
| import snarkdown from "https://cdn.skypack.dev/snarkdown"; |
| import hljs from "https://cdn.skypack.dev/highlight.js"; |
| |
| const MODELS = { |
| vp_quantized: { |
| base_url: |
| "https://hf-mirror.com/lmz/candle-quantized-phi/resolve/main/", |
| model: "model-q4k.gguf", |
| tokenizer: "tokenizer.json", |
| config: "phi-1_5.json", |
| quantized: true, |
| seq_len: 2048, |
| size: "800 MB", |
| }, |
| vp_quantized_q8: { |
| base_url: |
| "https://huggingface.co/lmz/candle-quantized-phi/resolve/main/", |
| model: "model-q80.gguf", |
| tokenizer: "tokenizer.json", |
| config: "phi-1_5.json", |
| quantized: true, |
| seq_len: 2048, |
| size: "1.51 GB", |
| }, |
| long_v2_quantized: { |
| base_url: |
| "https://huggingface.co/lmz/candle-quantized-phi/resolve/main/", |
| model: "model-puffin-phi-v2-q4k.gguf", |
| tokenizer: "tokenizer-puffin-phi-v2.json", |
| config: "puffin-phi-v2.json", |
| quantized: true, |
| seq_len: 2048, |
| size: "798 MB", |
| }, |
| long_v2_quantized_q8: { |
| base_url: |
| "https://huggingface.co/lmz/candle-quantized-phi/resolve/main/", |
| model: "model-puffin-phi-v2-q80.gguf", |
| tokenizer: "tokenizer-puffin-phi-v2.json", |
| config: "puffin-phi-v2.json", |
| quantized: true, |
| seq_len: 2048, |
| size: "1.50 GB", |
| }, |
| }; |
| |
| const TEMPLATES = [ |
| { |
| title: "Simple prompt", |
| prompt: `Sebastien is in London today, it’s the middle of July yet it’s raining, so Sebastien is feeling gloomy. He`, |
| }, |
| { |
| title: "Think step by step", |
| prompt: `Suppose Alice originally had 3 apples, then Bob gave Alice 7 apples, then Alice gave Cook 5 apples, and then Tim gave Alice 3x the amount of apples Alice had. How many apples does Alice have now? |
| Let’s think step by step.`, |
| }, |
| { |
| title: "Explaing a code snippet", |
| prompt: `What does this script do? |
| \`\`\`python |
| s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| s.bind(('', 0)) |
| s.listen(1) |
| conn, addr = s.accept() |
| print('Connected by', addr) |
| return conn.getsockname()[1] |
| \`\`\` |
| Let’s think step by step.`, |
| }, |
| { |
| title: "Question answering", |
| prompt: `What is the capital of France? |
| Answer:`, |
| }, |
| { |
| title: "Chat mode", |
| prompt: `Alice: Can you tell me how to create a python application to go through all the files |
| in one directory where the file’s name DOES NOT end with '.json'? |
| Bob:`, |
| }, |
| { |
| title: "Python code completion", |
| prompt: `"""write a python function called batch(function, list) which call function(x) for x in |
| list in parallel""" |
| Solution:`, |
| }, |
| { |
| title: "Python Sample", |
| prompt: `"""Can you make sure those histograms appear side by side on the same plot: |
| \`\`\`python |
| plt.hist(intreps_retrained[0][1].view(64,-1).norm(dim=1).detach().cpu().numpy(), bins = 20) |
| plt.hist(intreps_pretrained[0][1].view(64,-1).norm(dim=1).detach().cpu().numpy(), bins = 20) |
| \`\`\` |
| """`, |
| }, |
| { |
| title: "Write a Twitter post", |
| prompt: `Write a twitter post for the discovery of gravitational wave. |
| Twitter Post:`, |
| }, |
| { |
| title: "Write a review", |
| prompt: `Write a polite review complaining that the video game 'Random Game' was too badly optimized and it burned my laptop. |
| Very polite review:`, |
| }, |
| ]; |
| const phiWorker = new Worker("./phiWorker.js", { |
| type: "module", |
| }); |
| async function generateSequence(controller) { |
| const getValue = (id) => document.querySelector(`#${id}`).value; |
| const modelID = getValue("model"); |
| const model = MODELS[modelID]; |
| const weightsURL = model.base_url + model.model; |
| const tokenizerURL = model.base_url + model.tokenizer; |
| const configURL = model.base_url + model.config; |
| |
| const prompt = getValue("prompt").trim(); |
| const temperature = getValue("temperature"); |
| const topP = getValue("top-p"); |
| const repeatPenalty = getValue("repeat_penalty"); |
| const seed = getValue("seed"); |
| const maxSeqLen = getValue("max-seq"); |
| |
| function updateStatus(data) { |
| const outStatus = document.querySelector("#output-status"); |
| const outGen = document.querySelector("#output-generation"); |
| const outCounter = document.querySelector("#output-counter"); |
| |
| switch (data.status) { |
| case "loading": |
| outStatus.hidden = false; |
| outStatus.textContent = data.message; |
| outGen.hidden = true; |
| outCounter.hidden = true; |
| break; |
| case "generating": |
| const { message, prompt, sentence, tokensSec, totalTime } = data; |
| outStatus.hidden = true; |
| outCounter.hidden = false; |
| outGen.hidden = false; |
| outGen.innerHTML = snarkdown(prompt + sentence); |
| outCounter.innerHTML = `${(totalTime / 1000).toFixed( |
| 2 |
| )}s (${tokensSec.toFixed(2)} tok/s)`; |
| hljs.highlightAll(); |
| break; |
| case "complete": |
| outStatus.hidden = true; |
| outGen.hidden = false; |
| break; |
| } |
| } |
| |
| return new Promise((resolve, reject) => { |
| phiWorker.postMessage({ |
| weightsURL, |
| modelID, |
| tokenizerURL, |
| configURL, |
| quantized: model.quantized, |
| prompt, |
| temp: temperature, |
| top_p: topP, |
| repeatPenalty, |
| seed: seed, |
| maxSeqLen, |
| command: "start", |
| }); |
| |
| const handleAbort = () => { |
| phiWorker.postMessage({ command: "abort" }); |
| }; |
| const handleMessage = (event) => { |
| const { status, error, message, prompt, sentence } = event.data; |
| if (status) updateStatus(event.data); |
| if (error) { |
| phiWorker.removeEventListener("message", handleMessage); |
| reject(new Error(error)); |
| } |
| if (status === "aborted") { |
| phiWorker.removeEventListener("message", handleMessage); |
| resolve(event.data); |
| } |
| if (status === "complete") { |
| phiWorker.removeEventListener("message", handleMessage); |
| resolve(event.data); |
| } |
| }; |
| |
| controller.signal.addEventListener("abort", handleAbort); |
| phiWorker.addEventListener("message", handleMessage); |
| }); |
| } |
| |
| const form = document.querySelector("#form"); |
| const prompt = document.querySelector("#prompt"); |
| const clearBtn = document.querySelector("#clear-btn"); |
| const runBtn = document.querySelector("#run"); |
| const modelSelect = document.querySelector("#model"); |
| const promptTemplates = document.querySelector("#prompt-templates"); |
| let runController = new AbortController(); |
| let isRunning = false; |
| |
| document.addEventListener("DOMContentLoaded", () => { |
| for (const [id, model] of Object.entries(MODELS)) { |
| const option = document.createElement("option"); |
| option.value = id; |
| option.innerText = `${id} (${model.size})`; |
| modelSelect.appendChild(option); |
| } |
| |
| for (const [i, { title, prompt }] of TEMPLATES.entries()) { |
| const div = document.createElement("div"); |
| const input = document.createElement("input"); |
| input.type = "radio"; |
| input.name = "task"; |
| input.id = `templates-${i}`; |
| input.classList.add("font-light", "cursor-pointer"); |
| input.value = prompt; |
| const label = document.createElement("label"); |
| label.htmlFor = `templates-${i}`; |
| label.classList.add("cursor-pointer", "px-1"); |
| label.innerText = title; |
| div.appendChild(input); |
| div.appendChild(label); |
| promptTemplates.appendChild(div); |
| } |
| }); |
| |
| promptTemplates.addEventListener("change", (e) => { |
| const template = e.target.value; |
| prompt.value = template; |
| prompt.style.height = "auto"; |
| prompt.style.height = prompt.scrollHeight + "px"; |
| }); |
| modelSelect.addEventListener("change", (e) => { |
| const model = MODELS[e.target.value]; |
| document.querySelector("#max-seq").max = model.seq_len; |
| document.querySelector("#max-seq").nextElementSibling.value = 200; |
| }); |
| |
| form.addEventListener("submit", async (e) => { |
| e.preventDefault(); |
| if (isRunning) { |
| stopRunning(); |
| } else { |
| startRunning(); |
| await generateSequence(runController); |
| stopRunning(); |
| } |
| }); |
| |
| function startRunning() { |
| isRunning = true; |
| runBtn.textContent = "Stop"; |
| } |
| |
| function stopRunning() { |
| runController.abort(); |
| runController = new AbortController(); |
| runBtn.textContent = "Run"; |
| isRunning = false; |
| } |
| clearBtn.addEventListener("click", (e) => { |
| e.preventDefault(); |
| prompt.value = ""; |
| clearBtn.classList.add("invisible"); |
| runBtn.disabled = true; |
| stopRunning(); |
| }); |
| prompt.addEventListener("input", (e) => { |
| runBtn.disabled = false; |
| if (e.target.value.length > 0) { |
| clearBtn.classList.remove("invisible"); |
| } else { |
| clearBtn.classList.add("invisible"); |
| } |
| }); |
| </script> |
| </head> |
| <body class="container max-w-4xl mx-auto p-4 text-gray-800"> |
| <main class="grid grid-cols-1 gap-8 relative"> |
| <span class="absolute text-5xl -ml-[1em]"> 🕯️ </span> |
| <div> |
| <h1 class="text-5xl font-bold">EasyChat - 安风</h1> |
| <h2 class="text-2xl font-bold">Rust/WASM Demo</h2> |
| <p class="max-w-lg"> |
| The EC-VP model achieves state-of-the-art performance with only 1.3 billion |
| parameters. |
| </p> |
| </div> |
| <div> |
| <p class="text-xs italic max-w-lg"> |
| <b>Note:</b> |
| When first run, the app will download and cache the model, which could |
| take a few minutes. The models are <b>~800MB</b> or <b>~1.51GB</b> in |
| size. |
| </p> |
| </div> |
| <div> |
| <label for="model" class="font-medium">Models Options: </label> |
| <select |
| id="model" |
| class="border-2 border-gray-500 rounded-md font-light" |
| ></select> |
| </div> |
| <div> |
| <h3 class="font-medium">Prompt Templates</h3> |
| <form id="prompt-templates" class="flex flex-col gap-1 my-2"></form> |
| </div> |
| <form |
| id="form" |
| class="flex text-normal px-1 py-1 border border-gray-700 rounded-md items-center" |
| > |
| <input type="submit" hidden /> |
| <textarea |
| type="text" |
| id="prompt" |
| class="font-light w-full px-3 py-2 mx-1 resize-none outline-none" |
| oninput="this.style.height = 0;this.style.height = this.scrollHeight + 'px'" |
| placeholder="Add your prompt here..." |
| > |
| Write a detailed analogy between mathematics and a lighthouse. |
| Answer:</textarea |
| > |
| <button id="clear-btn"> |
| <svg |
| fill="none" |
| xmlns="http://www.w3.org/2000/svg" |
| width="40" |
| viewBox="0 0 70 40" |
| > |
| <path opacity=".5" d="M39 .2v40.2" stroke="#1F2937" /> |
| <path |
| d="M1.5 11.5 19 29.1m0-17.6L1.5 29.1" |
| opacity=".5" |
| stroke="#1F2937" |
| stroke-width="2" |
| /> |
| </svg> |
| </button> |
| <button |
| id="run" |
| class="bg-gray-700 hover:bg-gray-800 text-white font-normal py-2 w-16 rounded disabled:bg-gray-300 disabled:cursor-not-allowed" |
| > |
| Run |
| </button> |
| </form> |
| <details> |
| <summary class="font-medium cursor-pointer">Advanced Options</summary> |
|
|
| <div class="grid grid-cols-3 max-w-md items-center gap-3 py-3"> |
| <label class="text-sm font-medium" for="max-seq" |
| >Maximum length |
| </label> |
| <input |
| type="range" |
| id="max-seq" |
| name="max-seq" |
| min="1" |
| max="2048" |
| step="1" |
| value="200" |
| oninput="this.nextElementSibling.value = Number(this.value)" |
| /> |
| <output |
| class="text-xs w-[50px] text-center font-light px-1 py-1 border border-gray-700 rounded-md" |
| > |
| 200</output |
| > |
| <label class="text-sm font-medium" for="temperature" |
| >Temperature</label |
| > |
| <input |
| type="range" |
| id="temperature" |
| name="temperature" |
| min="0" |
| max="2" |
| step="0.01" |
| value="0.00" |
| oninput="this.nextElementSibling.value = Number(this.value).toFixed(2)" |
| /> |
| <output |
| class="text-xs w-[50px] text-center font-light px-1 py-1 border border-gray-700 rounded-md" |
| > |
| 0.00</output |
| > |
| <label class="text-sm font-medium" for="top-p">Top-p</label> |
| <input |
| type="range" |
| id="top-p" |
| name="top-p" |
| min="0" |
| max="1" |
| step="0.01" |
| value="1.00" |
| oninput="this.nextElementSibling.value = Number(this.value).toFixed(2)" |
| /> |
| <output |
| class="text-xs w-[50px] text-center font-light px-1 py-1 border border-gray-700 rounded-md" |
| > |
| 1.00</output |
| > |
|
|
| <label class="text-sm font-medium" for="repeat_penalty" |
| >Repeat Penalty</label |
| > |
|
|
| <input |
| type="range" |
| id="repeat_penalty" |
| name="repeat_penalty" |
| min="1" |
| max="2" |
| step="0.01" |
| value="1.10" |
| oninput="this.nextElementSibling.value = Number(this.value).toFixed(2)" |
| /> |
| <output |
| class="text-xs w-[50px] text-center font-light px-1 py-1 border border-gray-700 rounded-md" |
| >1.10</output |
| > |
| <label class="text-sm font-medium" for="seed">Seed</label> |
| <input |
| type="number" |
| id="seed" |
| name="seed" |
| value="299792458" |
| class="font-light border border-gray-700 text-right rounded-md p-2" |
| /> |
| <button |
| id="run" |
| onclick="document.querySelector('#seed').value = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)" |
| class="bg-gray-700 hover:bg-gray-800 text-white font-normal py-1 w-[50px] rounded disabled:bg-gray-300 disabled:cursor-not-allowed text-sm" |
| > |
| Rand |
| </button> |
| </div> |
| </details> |
|
|
| <div> |
| <h3 class="font-medium">Generation:</h3> |
| <div |
| class="min-h-[250px] bg-slate-100 text-gray-500 p-4 rounded-md flex flex-col gap-2" |
| > |
| <div |
| id="output-counter" |
| hidden |
| class="ml-auto font-semibold grid-rows-1 text-sm" |
| ></div> |
| <p hidden id="output-generation" class="grid-rows-2"></p> |
| <span id="output-status" class="m-auto font-light" |
| >No output yet</span |
| > |
| </div> |
| </div> |
| </main> |
| </body> |
| </html> |
|
|