Spaces:
Running
Running
File size: 18,144 Bytes
19a0974 f3fd622 19a0974 f3fd622 afcfd33 f3fd622 afcfd33 f3fd622 afcfd33 f3fd622 afcfd33 f3fd622 afcfd33 f3fd622 afcfd33 f3fd622 afcfd33 f3fd622 afcfd33 f3fd622 afcfd33 f3fd622 afcfd33 f3fd622 3e2ce32 f3fd622 afcfd33 f3fd622 afcfd33 f3fd622 | 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 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 | /* Vramwise UI. © 2026 CyberMax. All rights reserved. */
(function () {
"use strict";
var F = window.Vramwise;
var HF = "https://huggingface.co";
var $ = function (id) { return document.getElementById(id); };
var PRESETS = [
["Qwen3 8B", "Qwen/Qwen3-8B"], ["Llama 3.1 8B", "meta-llama/Llama-3.1-8B-Instruct"], ["gpt-oss 20B", "openai/gpt-oss-20b"],
["Qwen3 30B-A3B", "Qwen/Qwen3-30B-A3B"], ["Gemma 3 27B", "google/gemma-3-27b-it"], ["Mistral Small 24B", "mistralai/Mistral-Small-3.2-24B-Instruct-2506"],
["Qwen3 32B", "Qwen/Qwen3-32B"], ["Llama 3.3 70B", "meta-llama/Llama-3.3-70B-Instruct"], ["gpt-oss 120B", "openai/gpt-oss-120b"],
["DeepSeek V3", "deepseek-ai/DeepSeek-V3"]
];
// One-pass decision / scoring models (no text generation). Ids checked on the Hugging Face API 15 Oct 2026; sizes are
// always read live from each repo, never stored here.
var DECISION_PRESETS = [
["MATILDA-jev v1", "Maincode/matilda-jev-v1"], ["MATILDA-jev v1.5", "Maincode/matilda-jev-v1.5"],
["MATILDA-jev FP4", "Maincode/matilda-jev-fp4"], ["Matilda Decision v1", "Maincode/matilda-decision-v1"],
["JEV 9B", "autotrust/JEV-9B"], ["JEV 27B", "autotrust/JEV-27B"], ["JEV 35B (MoE)", "autotrust/JEV-35B"],
["JEV 35B NVFP4", "autotrust/JEV-35B-NVFP4"], ["pplx-decider 27B", "perplexity-ai/pplx-decider-v1.1-27b"],
["Decider 2B", "Mapika/decider-2b"], ["Decider 4B", "Mapika/decider-4b"]
];
var CTX = [2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144];
var model = null; // describe() output, possibly edited
var kind = "gpu";
var lastRows = [];
var loadSeq = 0;
function toast(msg) { var t = $("toast"); t.textContent = msg; t.classList.add("show"); setTimeout(function () { t.classList.remove("show"); }, 1800); }
function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { "&": "&", "<": "<", ">": ">", '"': """ }[c]; }); }
function opt(v, t, sel) { var o = document.createElement("option"); o.value = v; o.textContent = t; if (sel) o.selected = true; return o; }
function save() { try { localStorage.setItem("fitcheck", JSON.stringify(state())); } catch (e) {} }
function load() { try { return JSON.parse(localStorage.getItem("fitcheck") || "{}"); } catch (e) { return {}; } }
// ---------- hardware controls ----------
F.GPUS.forEach(function (g) { $("gpu").appendChild(opt(g.name, g.name, g.name === "RTX 4090 24GB")); });
F.APPLE.forEach(function (a) { $("chip").appendChild(opt(a.name, a.name, a.name === "M4 Pro")); });
F.UNIFIED.forEach(function (u) { $("unified").appendChild(opt(u.name, u.name)); });
CTX.forEach(function (c) { $("ctx").appendChild(opt(c, F.fmtCtx(c) + " tokens", c === 8192)); });
function fillMacMem(pref) {
var chip = F.APPLE.filter(function (a) { return a.name === $("chip").value; })[0];
var sel = $("mac-mem"); sel.innerHTML = "";
chip.mem.forEach(function (m) { sel.appendChild(opt(m, m + " GB", String(m) === String(pref))); });
if (!sel.value || sel.selectedIndex < 0) sel.selectedIndex = sel.options.length - 1;
}
fillMacMem(48);
function setKind(k) {
kind = k;
document.querySelectorAll(".seg button").forEach(function (b) { b.setAttribute("aria-checked", String(b.dataset.kind === k)); });
document.querySelectorAll("[data-hw]").forEach(function (d) { d.hidden = d.dataset.hw !== k; });
render();
}
document.querySelectorAll(".seg button").forEach(function (b) { b.addEventListener("click", function () { setKind(b.dataset.kind); }); });
function hardware() {
if (kind === "mac") return F.appleHardware($("chip").value, Number($("mac-mem").value), $("mac-raised").checked);
if (kind === "unified") return F.unifiedHardware($("unified").value);
if (kind === "custom") return { label: "Custom " + $("c-mem").value + " GB", usable: Number($("c-mem").value) || 0, bw: Number($("c-bw").value) || 1, ramGB: 0 };
return F.gpuHardware($("gpu").value, $("gpu-count").value, $("ram").value, $("ram-bw").value);
}
// ---------- model loading ----------
function getJSON(url, retried) {
return fetch(url).then(function (r) {
if (r.status === 429 && !retried) return new Promise(function (ok) { setTimeout(ok, 2500); }).then(function () { return getJSON(url, true); });
if (!r.ok) { var e = new Error("HTTP " + r.status); e.status = r.status; throw e; }
return r.json();
});
}
function configFor(ids) {
// Try each repo's config.json in turn (gated repos return 401).
var i = 0;
function next() {
if (i >= ids.length) return Promise.resolve(null);
var id = ids[i++];
return getJSON(HF + "/" + id + "/resolve/main/config.json").then(function (c) { c.__from = id; return c; }, next);
}
return next();
}
function baseModels(meta) {
var b = meta.cardData && meta.cardData.base_model;
return (Array.isArray(b) ? b : b ? [b] : []).filter(function (x) { return typeof x === "string" && x.indexOf("/") > 0; });
}
function loadModel(raw) {
var id = F.parseModelId(raw);
if (!id) { info('<span class="err">Enter a model id like <b>Qwen/Qwen3-8B</b> or paste its Hugging Face URL.</span>'); return Promise.resolve(); }
$("model").value = id;
var seq = ++loadSeq;
info("Reading <b>" + esc(id) + "</b> from Hugging Face…");
return getJSON(HF + "/api/models/" + id + "?expand[]=safetensors&expand[]=gguf&expand[]=config&expand[]=gated&expand[]=cardData&expand[]=pipeline_tag&expand[]=tags")
.then(function (meta) {
var name = id.split("/")[1], org = id.split("/")[0];
// Gated repos (Llama, Gemma) refuse config.json without a login: use a public mirror of the same weights.
var tries = meta.gated ? [] : [id];
baseModels(meta).forEach(function (b) { if (!(meta.gated && b.split("/")[0] === org)) tries.push(b); });
if (meta.gated || !tries.length || !(meta.safetensors || meta.config)) tries.push("unsloth/" + name);
return configFor(tries).then(function (cfg) { return { meta: meta, cfg: cfg }; });
})
.then(function (x) {
if (seq !== loadSeq) return;
if (!x.meta.id) x.meta.id = id;
model = F.describe(x.meta, x.cfg);
model.configFrom = x.cfg && x.cfg.__from;
fillAdv();
showInfo(x.meta);
render();
save();
})
.catch(function (e) {
if (seq !== loadSeq) return;
info('<span class="err">' + (e.status === 404 || e.status === 401 ? "Model not found (or private): <b>" + esc(id) + "</b>. Check the spelling, or fill in the model details below." : "Couldn't reach Hugging Face (" + esc(e.message) + "). Try again, or fill in the model details below.") + "</span>");
$("adv").open = true;
});
}
function info(html) { $("model-info").innerHTML = html; }
function showInfo(meta) {
var m = model, tags = [];
tags.push(F.fmtParams(m.params) + " parameters");
if (m.decision) tags.push("one-pass model · no text generation");
if (m.moe) tags.push("MoE · " + F.fmtParams(m.activeParams) + " active" + (m.experts ? " · " + m.topK + " of " + m.experts + " experts" : ""));
if (m.layers) tags.push(m.layers + " layers");
if (m.mla) tags.push("MLA cache"); else if (m.kvHeads) tags.push(m.kvHeads + " KV heads");
if (m.window) tags.push(m.slidingLayers + " sliding-window layers");
if (m.linearLayers) tags.push(m.linearLayers + " linear-attention layers (fixed state)");
if (m.maxContext) tags.push(F.fmtCtx(m.maxContext) + " max context");
if (m.nativeLabel) tags.push("files: " + m.nativeLabel);
var note = "";
if (m.configFrom && m.configFrom !== m.id) note = '<div class="hint">Architecture read from <a href="' + HF + "/" + esc(m.configFrom) + '" target="_blank" rel="noopener">' + esc(m.configFrom) + "</a>" + (m.gated ? " because the original repo is gated." : ".") + "</div>";
if (!m.hasShape) note += '<div class="hint err">Couldn\'t read layers/KV heads, so the KV cache is not counted. Fill them in under Model details.</div>';
if (!m.params) note += '<div class="hint err">No parameter count published for this repo. Enter it under Model details.</div>';
info('<a href="' + HF + "/" + esc(m.id) + '" target="_blank" rel="noopener"><b>' + esc(m.id) + '</b></a><div class="tags">' +
tags.map(function (t) { return '<span class="tag">' + esc(t) + "</span>"; }).join("") + "</div>" + note);
}
function fillAdv() {
$("m-params").value = model.params ? +(model.params / 1e9).toFixed(2) : "";
$("m-active").value = model.activeParams ? +(model.activeParams / 1e9).toFixed(2) : "";
$("m-layers").value = model.layers || "";
$("m-kvheads").value = model.mla ? "" : model.kvHeads || "";
$("m-headdim").value = model.mla ? "" : model.headDim || "";
$("m-maxctx").value = model.maxContext || "";
$("m-kvheads").disabled = $("m-headdim").disabled = !!model.mla;
}
function readAdv() {
if (!model) model = F.describe({ id: "custom" }, {});
var p = Number($("m-params").value) * 1e9, a = Number($("m-active").value) * 1e9;
if (p) { if (model.nativeBytes && model.params) model.nativeBytes *= p / model.params; model.params = p; }
model.activeParams = a && a <= model.params ? a : model.params;
model.moe = model.activeParams < model.params * 0.97;
model.layers = Number($("m-layers").value) || 0;
if (!model.mla) { model.kvHeads = Number($("m-kvheads").value) || 0; model.headDim = Number($("m-headdim").value) || 0; }
model.fullLayers = model.layers - (model.slidingLayers || 0);
if (model.fullLayers < 0) { model.fullLayers = model.layers; model.slidingLayers = 0; }
model.maxContext = Number($("m-maxctx").value) || 0;
model.hasShape = !!(model.layers && (model.mla || (model.kvHeads && model.headDim)));
render(); save();
}
["m-params", "m-active", "m-layers", "m-kvheads", "m-headdim", "m-maxctx"].forEach(function (id) { $(id).addEventListener("change", readAdv); });
// ---------- results ----------
function render() {
var tb = document.querySelector("#table tbody");
if (!model || !model.params) { tb.innerHTML = ""; $("verdict").textContent = "Pick a model to see if it fits."; lastRows = []; return; }
var hw = hardware(), ctx = Number($("ctx").value), kvBits = F.KV_TYPES[$("kv").value];
var rows = F.quantRows(model).map(function (q) { return { q: q, r: F.estimate(model, hw, q, ctx, kvBits) }; });
lastRows = rows;
var best = rows.filter(function (x) { return x.r.fit === "yes"; })[0] || rows.filter(function (x) { return x.r.fit === "tight"; })[0];
var off = rows.filter(function (x) { return x.r.fit === "offload"; })[0];
var name = model.id.split("/").pop();
var dec = !!model.decision;
var ms = function (r) { var x = r.minMs || 0; return x >= 10 ? x.toFixed(0) : x.toFixed(1); };
var v;
if (best) v = "Yes: " + esc(name) + " runs on " + esc(hw.label) + " at <u>" + esc(best.q.label.split(" /")[0]) + "</u>" +
"<small>" + F.fmtGB(best.r.total) + " GB of " + F.fmtGB(best.r.usable) + " GB usable at " + F.fmtCtx(ctx) + (dec ? " input tokens · one forward pass per call, at least " + ms(best.r) + " ms each (memory floor)" : " context · about " + Math.round(best.r.tpsLo) + "–" + Math.round(best.r.tpsHi) + " tokens/s") + "</small>";
else if (off) v = "Only with CPU offload: " + esc(name) + " at " + esc(off.q.label.split(" /")[0]) + " on " + esc(hw.label) + "<small>" + Math.round(off.r.gpuShare * 100) + "% of the weights fit on the GPU; the rest runs from system RAM" + (dec ? ", so every call is slow." : " at roughly " + off.r.tpsLo.toFixed(1) + "–" + off.r.tpsHi.toFixed(1) + " tokens/s.") + "</small>";
else v = "No: " + esc(name) + " doesn't fit " + esc(hw.label) + "<small>Even " + esc(rows[rows.length - 1].q.label) + " needs " + F.fmtGB(rows[rows.length - 1].r.total) + " GB and " + F.fmtGB(rows[rows.length - 1].r.usable) + " GB is usable. Try a shorter context, a smaller model or more memory.</small>";
$("verdict").innerHTML = v;
tb.innerHTML = rows.map(function (x) {
var r = x.r, pct = Math.min(100, r.total / r.usable * 100);
var fitTxt = { yes: "Fits", tight: "Tight", offload: "Offload", no: "No" }[r.fit];
var speed = r.fit === "no" ? "—" : dec ? (r.minMs ? "≥ " + ms(r) + " ms" : "slow") : r.tpsHi >= 10 ? Math.round(r.tpsLo) + "–" + Math.round(r.tpsHi) : r.tpsLo.toFixed(1) + "–" + r.tpsHi.toFixed(1);
return '<tr class="' + (best && x === best ? "best" : "") + '"><td><span class="q">' + esc(x.q.label) + '</span><span class="note">' + x.q.bits.toFixed(2).replace(/\.?0+$/, "") + " bits · " + esc(x.q.note) + "</span></td>" +
'<td class="num">' + F.fmtGB(r.weights) + ' GB</td><td class="num">' + F.fmtGB(r.kv) + ' GB</td>' +
'<td class="num">' + F.fmtGB(r.total) + ' GB<div class="meter"><i class="' + (r.total > r.usable ? "over" : "") + '" style="width:' + pct.toFixed(0) + '%"></i></div></td>' +
'<td><span class="pill ' + r.fit + '">' + fitTxt + "</span></td>" +
'<td class="num">' + (r.maxCtx ? F.fmtCtx(r.maxCtx) : "—") + '</td><td class="num">' + speed + "</td></tr>";
}).join("");
$("th-speed").textContent = dec ? "Per call" : "Speed (tok/s)";
$("th-ctx").textContent = dec ? "Max input" : "Max context";
$("decision-note").hidden = !dec;
$("legend").textContent = hw.label + ": " + F.fmtGB(hw.usable * F.GB) + " GB usable" + (hw.unified ? " (unified memory)" : "") + ", " + hw.bw + " GB/s." + (kind === "gpu" && hw.ramGB ? " Offload uses up to " + Math.round(hw.ramGB * 0.85) + " GB of system RAM." : "");
syncUrl();
}
["gpu", "gpu-count", "ram", "ram-bw", "mac-mem", "mac-raised", "unified", "c-mem", "c-bw", "ctx", "kv"].forEach(function (id) {
$(id).addEventListener("change", function () { render(); save(); });
$(id).addEventListener("input", function () { if ($(id).type === "number") render(); });
});
$("chip").addEventListener("change", function () { fillMacMem($("mac-mem").value); render(); save(); });
// ---------- state, share, CSV ----------
function state() {
return { model: model ? model.id : $("model").value, kind: kind, gpu: $("gpu").value, n: $("gpu-count").value, ram: $("ram").value, rbw: $("ram-bw").value,
chip: $("chip").value, mem: $("mac-mem").value, raised: $("mac-raised").checked ? 1 : 0, uni: $("unified").value, cm: $("c-mem").value, cbw: $("c-bw").value,
ctx: $("ctx").value, kv: $("kv").value };
}
function applyState(s) {
if (s.kind) kind = s.kind;
[["gpu", "gpu"], ["n", "gpu-count"], ["ram", "ram"], ["rbw", "ram-bw"], ["chip", "chip"], ["uni", "unified"], ["cm", "c-mem"], ["cbw", "c-bw"], ["ctx", "ctx"], ["kv", "kv"]].forEach(function (p) {
if (s[p[0]] !== undefined && s[p[0]] !== null && s[p[0]] !== "") $(p[1]).value = s[p[0]];
});
fillMacMem(s.mem || 48);
$("mac-raised").checked = String(s.raised) === "1";
}
function syncUrl() {
var s = state(), q = new URLSearchParams();
Object.keys(s).forEach(function (k) { if (s[k] !== "" && s[k] !== undefined) q.set(k, s[k]); });
history.replaceState(null, "", "?" + q.toString());
}
$("btn-share").addEventListener("click", function () {
var url = location.href;
(navigator.clipboard ? navigator.clipboard.writeText(url) : Promise.reject()).then(function () { toast("Link copied"); }, function () { prompt("Copy this link", url); });
});
$("btn-csv").addEventListener("click", function () {
if (!lastRows.length) return;
var hw = hardware();
var lines = [["model", "hardware", "context", "quantization", "bits_per_weight", "weights_gb", "kv_cache_gb", "total_gb", "usable_gb", "fits", "max_context", "tok_s_low", "tok_s_high", "min_ms_per_call"].join(",")];
lastRows.forEach(function (x) {
var r = x.r;
lines.push([model.id, '"' + hw.label + '"', $("ctx").value, '"' + x.q.label + '"', x.q.bits.toFixed(2), F.fmtGB(r.weights), F.fmtGB(r.kv), F.fmtGB(r.total), F.fmtGB(r.usable), r.fit, r.maxCtx || 0, model.decision ? "" : r.tpsLo.toFixed(1), model.decision ? "" : r.tpsHi.toFixed(1), model.decision && r.minMs ? r.minMs.toFixed(1) : ""].join(","));
});
var a = document.createElement("a");
a.href = URL.createObjectURL(new Blob([lines.join("\n") + "\n"], { type: "text/csv" }));
a.download = "vramwise-" + model.id.replace(/[^\w.-]+/g, "_") + ".csv";
document.body.appendChild(a); a.click(); a.remove();
});
// ---------- model input ----------
[[PRESETS, "presets"], [DECISION_PRESETS, "presets-decision"]].forEach(function (g) {
g[0].forEach(function (p) {
var b = document.createElement("button"); b.className = "chip"; b.textContent = p[0]; b.type = "button";
b.addEventListener("click", function () { loadModel(p[1]); });
$(g[1]).appendChild(b);
});
});
$("btn-load").addEventListener("click", function () { loadModel($("model").value); });
$("model").addEventListener("keydown", function (e) { if (e.key === "Enter") loadModel($("model").value); });
var suggestTimer;
$("model").addEventListener("input", function () {
clearTimeout(suggestTimer);
var q = $("model").value.trim();
if (q.length < 3 || /\s/.test(q)) return;
suggestTimer = setTimeout(function () {
getJSON(HF + "/api/models?search=" + encodeURIComponent(q) + "&sort=downloads&limit=8").then(function (list) {
var dl = $("model-suggest"); dl.innerHTML = "";
list.forEach(function (m) { dl.appendChild(opt(m.id, m.id)); });
}, function () {});
}, 300);
});
// ---------- start ----------
var params = new URLSearchParams(location.search);
var init = {};
params.forEach(function (v, k) { init[k] = v; });
if (!init.model) init = load();
applyState(init);
setKind(kind);
loadModel(init.model || "Qwen/Qwen3-8B");
window.fitcheckApp = { loadModel: loadModel, rows: function () { return lastRows; }, model: function () { return model; } };
if ("serviceWorker" in navigator && location.protocol === "https:") navigator.serviceWorker.register("sw.js").catch(function () {});
})();
|