/* 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('Enter a model id like Qwen/Qwen3-8B or paste its Hugging Face URL.'); return Promise.resolve(); }
$("model").value = id;
var seq = ++loadSeq;
info("Reading " + esc(id) + " 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('' + (e.status === 404 || e.status === 401 ? "Model not found (or private): " + esc(id) + ". 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.") + "");
$("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 = '
";
if (!m.hasShape) note += 'Couldn\'t read layers/KV heads, so the KV cache is not counted. Fill them in under Model details.
';
if (!m.params) note += 'No parameter count published for this repo. Enter it under Model details.
';
info('' + esc(m.id) + '' +
tags.map(function (t) { return '' + esc(t) + ""; }).join("") + "
" + 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 " + esc(best.q.label.split(" /")[0]) + "" +
"" + 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") + "";
else if (off) v = "Only with CPU offload: " + esc(name) + " at " + esc(off.q.label.split(" /")[0]) + " on " + esc(hw.label) + "" + 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.") + "";
else v = "No: " + esc(name) + " doesn't fit " + esc(hw.label) + "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.";
$("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 '| ' + esc(x.q.label) + '' + x.q.bits.toFixed(2).replace(/\.?0+$/, "") + " bits · " + esc(x.q.note) + " | " +
'' + F.fmtGB(r.weights) + ' GB | ' + F.fmtGB(r.kv) + ' GB | ' +
'' + F.fmtGB(r.total) + ' GB
| ' +
'' + fitTxt + " | " +
'' + (r.maxCtx ? F.fmtCtx(r.maxCtx) : "—") + ' | ' + speed + " |
";
}).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 () {});
})();