NyxKrage's picture
Support gated models (#8)
3930797 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script>
function strToHtml(str) {
let parser = new DOMParser();
return parser.parseFromString(str, "text/html");
}
//Short, jQuery-independent function to read html table and write them into an Array.
//Kudos to RobG at StackOverflow
function tableToObj(table) {
var rows = table.rows;
var propCells = rows[0].cells;
var propNames = [];
var results = [];
var obj, row, cells;
// Use the first row for the property names
// Could use a header section but result is the same if
// there is only one header row
for (var i = 0, iLen = propCells.length; i < iLen; i++) {
propNames.push(
(propCells[i].textContent || propCells[i].innerText).trim()
);
}
// Use the rows for data
// Could use tbody rows here to exclude header & footer
// but starting from 1 gives required result
for (var j = 1, jLen = rows.length; j < jLen; j++) {
cells = rows[j].cells;
obj = {};
for (var k = 0; k < iLen; k++) {
obj[propNames[k]] = (
cells[k].textContent || cells[k].innerText
).trim();
}
results.push(obj);
}
return results;
}
function formatGpu(gpus) {
return gpus.map(
(g) => `${g["Product Name"]} - ${g["Memory"].split(",")[0]}`
);
}
const gguf_quants = {
"IQ1_S": 1.56,
"IQ2_XXS": 2.06,
"IQ2_XS": 2.31,
"IQ2_S": 2.5,
"IQ2_M": 2.7,
"IQ3_XXS": 3.06,
"IQ3_XS": 3.3,
"Q2_K": 3.35,
"Q3_K_S": 3.5,
"IQ3_S": 3.5,
"IQ3_M": 3.7,
"Q3_K_M": 3.91,
"Q3_K_L": 4.27,
"IQ4_XS": 4.25,
"IQ4_NL": 4.5,
"Q4_0": 4.55,
"Q4_K_S": 4.58,
"Q4_K_M": 4.85,
"Q5_0": 5.54,
"Q5_K_S": 5.54,
"Q5_K_M": 5.69,
"Q6_K": 6.59,
"Q8_0": 8.5,
}
async function modelConfig(hf_model, hf_token) {
auth = hf_token == "" ? {} : {
headers: {
'Authorization': `Bearer ${hf_token}`
}
}
let config = await fetch(
`https://huggingface.co/${hf_model}/raw/main/config.json`, auth
).then(r => r.json())
let model_size = 0
try {
model_size = (await fetch(`https://huggingface.co/${hf_model}/resolve/main/model.safetensors.index.json`, auth).then(r => r.json()))["metadata"]["total_size"] / 2
if (isNaN(model_size)) {
throw new Erorr("no size in safetensors metadata")
}
} catch (e) {
try {
model_size = (await fetch(`https://huggingface.co/${hf_model}/resolve/main/pytorch_model.bin.index.json`, auth).then(r => r.json()))["metadata"]["total_size"] / 2
if (isNaN(model_size)) {
throw new Erorr("no size in pytorch metadata")
}
} catch {
let model_page = await fetch(
"https://corsproxy.io/?" + encodeURIComponent(`https://huggingface.co/${hf_model}`)
).then(r => r.text())
let el = document.createElement( 'html' );
el.innerHTML = model_page
let params_el = el.querySelector('div[data-target="ModelSafetensorsParams"]')
if (params_el !== null) {
model_size = JSON.parse(params_el.attributes.getNamedItem("data-props").value)["safetensors"]["total"]
} else {
params_el = el.querySelector('div[data-target="ModelHeader"]')
model_size = JSON.parse(params_el.attributes.getNamedItem("data-props").value)["model"]["safetensors"]["total"]
}
}
}
config.parameters = model_size
return config
}
function inputBuffer(context=8192, model_config, bsz=512) {
/* Calculation taken from github:ggerganov/llama.cpp/llama.cpp:11248
ctx->inp_tokens = ggml_new_tensor_1d(ctx->ctx_input, GGML_TYPE_I32, cparams.n_batch);
ctx->inp_embd = ggml_new_tensor_2d(ctx->ctx_input, GGML_TYPE_F32, hparams.n_embd, cparams.n_batch);
ctx->inp_pos = ggml_new_tensor_1d(ctx->ctx_input, GGML_TYPE_I32, cparams.n_batch);
ctx->inp_KQ_mask = ggml_new_tensor_2d(ctx->ctx_input, GGML_TYPE_F32, cparams.n_ctx, cparams.n_batch);
ctx->inp_K_shift = ggml_new_tensor_1d(ctx->ctx_input, GGML_TYPE_I32, cparams.n_ctx);
ctx->inp_sum = ggml_new_tensor_2d(ctx->ctx_input, GGML_TYPE_F32, 1, cparams.n_batch);
n_embd is hidden size (github:ggeranov/llama.cpp/convert.py:248)
*/
const inp_tokens = bsz
const inp_embd = model_config["hidden_size"] * bsz
const inp_pos = bsz
const inp_KQ_mask = context * bsz
const inp_K_shift = context
const inp_sum = bsz
return inp_tokens + inp_embd + inp_pos + inp_KQ_mask + inp_K_shift + inp_sum
}
function computeBuffer(context=8192, model_config, bsz=512) {
if (bsz != 512) {
alert("batch size other than 512 is currently not supported for the compute buffer, using batchsize 512 for compute buffer calculation, end result result will be an overestimatition")
}
return (context / 1024 * 2 + 0.75) * model_config["num_attention_heads"] * 1024 * 1024
}
function kvCache(context=8192, model_config, cache_bit=16) {
const n_gqa = model_config["num_attention_heads"] / model_config["num_key_value_heads"]
const n_embd_gqa = model_config["hidden_size"] / n_gqa
const n_elements = n_embd_gqa * (model_config["num_hidden_layers"] * context)
const size = 2 * n_elements
return size * (cache_bit / 8)
}
function contextSize(context=8192, model_config, bsz=512, cache_bit=16) {
return Number.parseFloat((inputBuffer(context, model_config, bsz) + kvCache(context, model_config, cache_bit) + computeBuffer(context, model_config, bsz)).toFixed(2))
}
function modelSize(model_config, bpw=4.5) {
return Number.parseFloat((model_config["parameters"] * bpw / 8).toFixed(2))
}
async function calculateSizes(format) {
try {
const model_config = await modelConfig(document.getElementById("modelsearch").value, document.getElementById("hf_token").value)
const context = parseInt(document.getElementById("contextsize").value)
let bsz = 512
let cache_bit = 16
let bpw = 0
if (format === "gguf") {
bsz = parseInt(document.getElementById("batchsize").value)
bpw = gguf_quants[document.getElementById("quantsize").innerText]
} else if (format == "exl2") {
cache_bit = Number.parseInt(document.getElementById("kvCache").value)
bpw = Number.parseFloat(document.getElementById("bpw").value)
}
const model_size = modelSize(model_config, bpw)
const context_size = contextSize(context, model_config, bsz, cache_bit)
const total_size = ((model_size + context_size) / 2**30)
document.getElementById("resultmodel").innerText = (model_size / 2**30).toFixed(2)
document.getElementById("resultcontext").innerText = (context_size / 2**30).toFixed(2)
const result_total_el = document.getElementById("resulttotal");
result_total_el.innerText = total_size.toFixed(2)
const gpu = document.getElementById("gpusearch").value
if (gpu !== "") {
const vram = parseFloat(gpu.split("-")[1].replace("GB", "").trim())
if (vram - total_size > 0.5) {
result_total_el.style.backgroundColor = "#bef264"
} else if (vram - total_size > 0) {
result_total_el.style.backgroundColor = "#facc15"
} else {
result_total_el.style.backgroundColor = "#ef4444"
}
}
} catch(e) {
alert(e);
}
}
</script>
<link href="./styles.css" rel="stylesheet">
<title>Can I run it? - LLM VRAM Calculator</title>
</head>
<body class="p-8">
<div x-data="{ format: 'gguf' }" class="flex flex-col max-h-screen items-center mt-16 gap-10">
<h1 class="text-xl font-semibold leading-6 text-gray-900">
LLM Model, Can I run it?
</h1>
<p>
To support gated or private repos, you need to <a href="https://huggingface.co/settings/tokens" style="color: #4444ff"><b>create an authentification token</b></a>, to check the box <span style="color: #6e1818"><b>"Read access to contents of all public gated repos you can access"</b></span> and then enter the token in the field below.
</p>
<div class="flex flex-col gap-10">
<div class="w-auto flex flex-col gap-4">
<!-- Huggingface Authentification Token -->
<div
class="relative"
x-data="{
results: null,
query: null
}"
>
<label
for="gpusearch"
class="absolute -top-2 left-2 inline-block bg-white px-1 text-xs font-medium text-gray-900"
>Huggingface Token (optional)</label
>
<input
class="block w-full rounded-md border-0 p-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
id="hf_token"
/>
</div>
<!-- GPU Selector -->
<div
class="relative"
x-data="{
results: null,
query: null
}"
>
<label
for="gpusearch"
class="absolute -top-2 left-2 inline-block bg-white px-1 text-xs font-medium text-gray-900"
>GPU (optional)</label
>
<input
class="block w-full rounded-md border-0 p-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
placeholder="GeForce RTX 3090 - 24 GB"
id="gpusearch"
name="gpusearch"
list="gpulist"
x-model="query"
@keypress.debounce.150ms="results = query === '' ? [] : formatGpu(tableToObj(strToHtml(await fetch('https://corsproxy.io/?https://www.techpowerup.com/gpu-specs/?ajaxsrch=' + query).then(r => r.text())).querySelector('table')))"
/>
<datalist id="gpulist">
<template x-for="item in results">
<option :value="item" x-text="item"></option>
</template>
</datalist>
</div>
<!-- Model Selector -->
<div class="flex flex-row gap-4 relative">
<label
for="contextsize"
class="absolute -top-2 left-2 inline-block bg-white px-1 text-xs font-medium text-gray-900"
>
Model (unquantized)
</label>
<div
class="block w-full rounded-md border-0 p-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
x-data="{
open: false,
value: 'Nexusflow/Starling-LM-7B-beta',
results: null,
toggle() {
if (this.open) {
return this.close()
}
this.$refs.input.focus()
this.open = true
},
close(focusAfter) {
if (! this.open) return
this.open = false
focusAfter && focusAfter.focus()
}
}"
x-on:keydown.escape.prevent.stop="close($refs.input)"
x-id="['model-typeahead']"
class="relative"
>
<!-- Input -->
<input
id="modelsearch"
x-ref="input"
x-on:click="toggle()"
@keypress.debounce.150ms="results = (await
fetch('https://huggingface.co/api/quicksearch?type=model&q=' +
encodeURIComponent(value)).then(r => r.json())).models.filter(m => !m.id.includes('GGUF') && !m.id.includes('AWQ') && !m.id.includes('GPTQ') && !m.id.includes('exl2'));"
:aria-expanded="open"
:aria-controls="$id('model-typeahead')"
x-model="value"
class="flex justify-between items-center gap-2 w-full"
/>
<!-- Panel -->
<div
x-ref="panel"
x-show="open"
x-transition.origin.top.left
x-on:click.outside="close($refs.input)"
:id="$id('model-typeahead')"
style="display: none"
class="absolute left-0 mt-4 w-full rounded-md bg-white shadow-sm ring-1 ring-inset ring-gray-300 z-10"
>
<template x-for="result in results">
<a
@click="value = result.id; close($refs.input)"
x-text="result.id"
class="flex cursor-pointer items-center gap-2 w-full first-of-type:rounded-t-md last-of-type:rounded-b-md px-4 py-2.5 text-left text-sm hover:bg-gray-500/5 disabled:text-gray-500"
></a>
</template>
</div>
</div>
</div>
<!-- Context Size Selector -->
<div class="relative">
<label
for="contextsize"
class="absolute -top-2 left-2 inline-block bg-white px-1 text-xs font-medium text-gray-900"
>
Context Size
</label>
<input
value="8192"
type="number"
name="contextsize"
id="contextsize"
step="1024"
class="block w-full rounded-md border-0 p-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
/>
</div>
<!-- Quant Format Selector -->
<div class="relative">
<label
class="absolute -top-2 left-2 inline-block bg-white px-1 text-xs font-medium text-gray-900"
>Quant Format</label
>
<fieldset
x-model="format"
class="block w-full rounded-md border-0 p-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
>
<legend class="sr-only">Quant format</legend>
<div
class="space-y-4 sm:flex sm:items-center sm:space-x-10 sm:space-y-0"
>
<div class="flex items-center">
<input
id="gguf-format"
name="quant-format"
type="radio"
value="gguf"
checked
class="h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-600"
/>
<label
for="gguf-format"
class="ml-3 block text-sm font-medium leading-6 text-gray-900"
>GGUF</label
>
</div>
<div class="flex items-center">
<input
id="exl2-format"
name="quant-format"
type="radio"
value="exl2"
class="h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-600"
/>
<label
for="exl2-format"
class="ml-3 block text-sm font-medium leading-6 text-gray-900"
>EXL2</label
>
</div>
<div class="flex items-center">
<input
id="gptq-format"
name="quant-format"
type="radio"
disabled
value="gptq"
class="h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-600"
/>
<label
for="gptq-format"
class="ml-3 block text-sm font-medium leading-6 text-gray-900"
>GPTQ (coming soon)</label
>
</div>
</div>
</fieldset>
</div>
<!-- EXL2 Options -->
<div x-show="format === 'exl2'" class="flex flex-row gap-4">
<div class="relative flex-grow">
<label
for="bpw"
class="absolute -top-2 left-2 inline-block bg-white px-1 text-xs font-medium text-gray-900"
>
BPW
</label>
<input
value="4.5"
type="number"
step="0.01"
id="bpw"
name="bpw"
class="block w-full rounded-md border-0 p-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
/>
</div>
<div
class="flex-shrink relative rounded-md"
>
<div
class="w-fit p-3 h-full flex items-center gap-2 justify-center rounded-md border-0 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
>
<label
for="kvCache"
class="inline-block bg-white text-xs font-medium text-gray-900"
>
KV Cache
</label>
<select id="kvCache" name="kvCache">
<option value="16">16 bit</option>
<option value="8">8 bit</option>
<option value="4">4 bit</option>
</select>
</div>
</div>
</div>
<!-- GGUF Options -->
<div x-show="format === 'gguf'" class="relative">
<div class="flex flex-row gap-4">
<label
for="contextsize"
class="absolute -top-2 left-2 inline-block bg-white px-1 text-xs font-medium text-gray-900"
>
Quantization Size
</label>
<div
class="block w-full rounded-md border-0 p-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
x-data="{
open: false,
value: '',
toggle() {
if (this.open) {
return this.close()
}
this.$refs.button.focus()
this.open = true
},
close(focusAfter) {
if (! this.open) return
this.open = false
focusAfter && focusAfter.focus()
}
}"
x-on:keydown.escape.prevent.stop="close($refs.button)"
x-id="['dropdown-button']"
class="relative"
>
<!-- Button -->
<button
x-ref="button"
x-on:click="toggle()"
:aria-expanded="open"
:aria-controls="$id('dropdown-button')"
type="button"
id="quantsize"
x-text="value.length === 0 ? 'Q4_K_S' : value"
class="flex justify-between items-center gap-2 w-full"
>
Q4_K_S
<!-- Heroicon: chevron-down -->
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-gray-400"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clip-rule="evenodd"
/>
</svg>
</button>
<!-- Panel -->
<div
x-data="{ quants: [
'IQ1_S',
'IQ2_XXS',
'IQ2_XS',
'IQ2_S',
'IQ2_M',
'IQ3_XXS',
'IQ3_XS',
'Q2_K',
'Q3_K_S',
'IQ3_S',
'IQ3_M',
'Q3_K_M',
'Q3_K_L',
'IQ4_XS',
'IQ4_NL',
'Q4_0',
'Q4_K_S',
'Q4_K_M',
'Q5_0',
'Q5_K_S',
'Q5_K_M',
'Q6_K',
'Q8_0'
]}"
x-ref="panel"
x-show="open"
x-transition.origin.top.left
x-on:click.outside="close($refs.button)"
:id="$id('dropdown-button')"
style="display: none"
class="absolute left-0 mt-4 w-full rounded-md bg-white shadow-sm ring-1 ring-inset ring-gray-300 z-10"
>
<template x-for="quant in quants">
<a
@click="value = quant; close($refs.button)"
x-text="quant"
class="flex cursor-pointer items-center gap-2 w-full first-of-type:rounded-t-md last-of-type:rounded-b-md px-4 py-2.5 text-left text-sm hover:bg-gray-500/5 disabled:text-gray-500"
></a>
</template>
</div>
</div>
<div class="relative">
<label
for="batchsize"
class="absolute -top-2 left-2 inline-block bg-white px-1 text-xs font-medium text-gray-900"
>
Batch Size
</label>
<input
value="512"
type="number"
step="128"
id="batchsize"
class="block w-full rounded-md border-0 p-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
/>
</div>
</div>
</div>
<button
type="button"
class="rounded-md bg-slate-800 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-slate-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
@click="calculateSizes(format)"
>
Submit
</button>
</div>
<div class="w-auto flex flex-col gap-4">
<div class="relative">
<label
class="absolute -top-2 left-2 inline-block bg-white px-1 text-xs font-medium text-gray-900"
>
Model Size (GB)
</label>
<div
id="resultmodel"
class="block w-full rounded-md border-0 p-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
>4.20</div>
</div>
<div class="relative">
<label
class="absolute -top-2 left-2 inline-block bg-white px-1 text-xs font-medium text-gray-900"
>
Context Size (GB)
</label>
<div
id="resultcontext"
class="block w-full rounded-md border-0 p-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
>6.90</div>
</div>
<div class="relative">
<label
class="absolute -top-2 left-2 inline-block bg-white px-1 text-xs font-medium text-gray-900"
>
Total Size (GB)
</label>
<div
id="resulttotal"
class="block w-full rounded-md border-0 p-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
>420.69</div>
</div>
</div>
</div>
</div>
<script
src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"
></script>
<script defer>
calculateSizes("gguf")
</script>
</body>
</html>