/* 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 = '
Architecture read from ' + esc(m.configFrom) + "" + (m.gated ? " because the original repo is gated." : ".") + "
"; 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 () {}); })();