Spaces:
Running
Running
<html> | |
<head> | |
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" /> | |
<title>Candle Llama.c Rust/WASM</title> | |
</head> | |
<body></body> | |
</html> | |
<html> | |
<head> | |
<meta charset="UTF-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<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> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<script type="module"> | |
// base url for audio examples | |
const MODELS_BASE_URL = | |
"https://huggingface.co/karpathy/tinyllamas/resolve/main"; | |
// models base url | |
const MODELS = { | |
stories15M: { | |
url: "stories15M.bin", | |
seq_len: 256, | |
}, | |
stories42M: { | |
url: "stories42M.bin", | |
seq_len: 1024, | |
}, | |
stories110M: { | |
url: "stories110M.bin", | |
seq_len: 1024, | |
}, | |
}; | |
const llamaWorker = new Worker("./llama2cWorker.js", { | |
type: "module", | |
}); | |
async function generateSequence(controller) { | |
const getValue = (id) => document.querySelector(`#${id}`).value; | |
const modelID = getValue("model"); | |
const model = MODELS[modelID]; | |
const weightsURL = `${MODELS_BASE_URL}/${model.url}`; | |
const prompt = getValue("prompt"); | |
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 = `<span class="font-semibold">${prompt}</span>${sentence.replace( | |
/\<s\>|\<\/s\>/g, | |
"" | |
)}`; | |
outCounter.innerHTML = `${(totalTime / 1000).toFixed( | |
2 | |
)}s (${tokensSec.toFixed(2)} tok/s)`; | |
break; | |
case "complete": | |
outStatus.hidden = true; | |
outGen.hidden = false; | |
break; | |
} | |
} | |
return new Promise((resolve, reject) => { | |
llamaWorker.postMessage({ | |
weightsURL, | |
modelID, | |
tokenizerURL: "tokenizer.json", | |
prompt, | |
temp: temperature, | |
top_p: topP, | |
repeatPenalty, | |
seed: BigInt(seed), | |
maxSeqLen, | |
command: "start", | |
}); | |
const handleAbort = () => { | |
llamaWorker.postMessage({ command: "abort" }); | |
}; | |
const handleMessage = (event) => { | |
const { status, error, message, prompt, sentence } = event.data; | |
if (status) updateStatus(event.data); | |
if (error) { | |
llamaWorker.removeEventListener("message", handleMessage); | |
reject(new Error(error)); | |
} | |
if (status === "aborted") { | |
llamaWorker.removeEventListener("message", handleMessage); | |
resolve(event.data); | |
} | |
if (status === "complete") { | |
llamaWorker.removeEventListener("message", handleMessage); | |
resolve(event.data); | |
} | |
}; | |
controller.signal.addEventListener("abort", handleAbort); | |
llamaWorker.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"); | |
let runController = new AbortController(); | |
let isRunning = false; | |
modelSelect.addEventListener("change", (e) => { | |
const model = MODELS[e.target.value]; | |
document.querySelector("#max-seq").max = model.seq_len; | |
document.querySelector("#max-seq").nextElementSibling.value = | |
model.seq_len; | |
}); | |
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">Candle Llama2.c</h1> | |
<h2 class="text-2xl font-bold">Rust/WASM Demo</h2> | |
<p class="max-w-lg"> | |
<a | |
href="https://github.com/karpathy/llama2.c" | |
target="_blank" | |
class="underline hover:text-blue-500 hover:no-underline" | |
target="_blank" | |
>Llama2.c</a | |
> | |
is Andrey Karpathy's C implementation of the Llama 2 LLM model in C. | |
This demo uses | |
<a | |
href="https://github.com/huggingface/candle/" | |
target="_blank" | |
class="underline hover:text-blue-500 hover:no-underline" | |
>Candle | |
</a> | |
to run Llama2.c in the browser using rust/wasm. | |
</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"> | |
<option value="stories15M" selected>stories 15M (60.8 MB)</option> | |
<option value="stories42M">stories 42M (167 MB)</option> | |
<option value="stories110M">stories 110M (438 MB)</option> | |
</select> | |
</div> | |
<form | |
id="form" | |
class="flex text-normal px-1 py-1 border border-gray-700 rounded-md items-center"> | |
<input type="submit" hidden /> | |
<input | |
type="text" | |
id="prompt" | |
class="font-light w-full px-3 py-2 mx-1 resize-none outline-none" | |
placeholder="Add your prompt here..." | |
value="Once upon a time" /> | |
<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="256" | |
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.40" | |
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.40</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 = BigInt(Math.floor(Math.random() * 2**64-1))" | |
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> | |