Spaces:
Running
Running
Download static/app.js from codelion/local-model-explorer: direct link, hf CLI and curl.
- Browser
- Download file 37.7 kB
-
https://huggingface.co/spaces/codelion/local-model-explorer/resolve/main/static/app.js
- Command line
-
hf download hf://spaces/codelion/local-model-explorer/static/app.js
-
curl -L -o app.js https://huggingface.co/spaces/codelion/local-model-explorer/resolve/main/static/app.js
37.7 kB
| ; | |
| (function () { | |
| const $ = (s, root = document) => root.querySelector(s); | |
| const $$ = (s, root = document) => [...root.querySelectorAll(s)]; | |
| function el(tag, attrs, ...kids) { | |
| const n = document.createElement(tag); | |
| for (const [k, v] of Object.entries(attrs || {})) { | |
| if (v === null || v === undefined || v === false) continue; | |
| if (k === "class") n.className = v; | |
| else if (k === "text") n.textContent = v; | |
| else if (k === "style") { | |
| // Property by property: the page's CSP forbids style attributes, not CSSOM writes. | |
| for (const decl of String(v).split(";")) { | |
| const i = decl.indexOf(":"); | |
| if (i > 0) n.style.setProperty(decl.slice(0, i).trim(), decl.slice(i + 1).trim()); | |
| } | |
| } | |
| else if (k.startsWith("on")) n.addEventListener(k.slice(2), v); | |
| else n.setAttribute(k, v === true ? "" : v); | |
| } | |
| for (const kid of kids.flat()) { | |
| if (kid === null || kid === undefined || kid === false) continue; | |
| n.append(kid instanceof Node ? kid : document.createTextNode(String(kid))); | |
| } | |
| return n; | |
| } | |
| const gb = (v, digits = 1) => (v === null || v === undefined ? "?" : `${Number(v).toFixed(digits)} GB`); | |
| const bytesGB = (b) => (b ? gb(b / 1e9) : "?"); | |
| const params = (p) => (p ? (p >= 1e9 ? `${+(p / 1e9).toFixed(p >= 1e10 ? 0 : 1)}B` : `${Math.round(p / 1e6)}M`) : ""); | |
| const compact = (n) => (n >= 1e6 ? `${(n / 1e6).toFixed(1)}M` : n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n || 0)); | |
| const ctxLabel = (c) => (c >= 1024 ? `${Math.round(c / 1024)}k` : String(c)); | |
| const store = { | |
| get(k) { try { return JSON.parse(localStorage.getItem(k)); } catch (e) { return null; } }, | |
| set(k, v) { try { localStorage.setItem(k, JSON.stringify(v)); } catch (e) {} }, | |
| }; | |
| // ------------------------------------------------------------------ telemetry | |
| const Telemetry = (() => { | |
| const gpc = navigator.globalPrivacyControl === true || navigator.doNotTrack === "1"; | |
| let optedOut = gpc; | |
| try { | |
| if (localStorage.getItem("lme-optout") === "1") optedOut = true; | |
| else if (gpc && localStorage.getItem("lme-optin") === "1") optedOut = false; | |
| } catch (e) {} | |
| let sid = null; | |
| try { sid = sessionStorage.getItem("lme-sid"); } catch (e) {} | |
| if (!sid || !/^[a-f0-9]{16}$/.test(sid)) { | |
| const b = new Uint8Array(8); | |
| crypto.getRandomValues(b); | |
| sid = [...b].map((x) => x.toString(16).padStart(2, "0")).join(""); | |
| try { sessionStorage.setItem("lme-sid", sid); } catch (e) {} | |
| } | |
| let enabled = true; | |
| let build = () => null; | |
| let lastSent = ""; | |
| const clean = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => | |
| v !== undefined && v !== null && v !== "" && !(Array.isArray(v) && !v.length))); | |
| function post(events, beacon) { | |
| const body = JSON.stringify({ events }); | |
| if (beacon && navigator.sendBeacon) navigator.sendBeacon("/api/events", new Blob([body], { type: "application/json" })); | |
| else fetch("/api/events", { method: "POST", headers: { "content-type": "application/json" }, body, keepalive: true }).catch(() => {}); | |
| } | |
| function sendSession(beacon) { | |
| if (optedOut || !enabled) return; | |
| const summary = build(); | |
| if (!summary) return; | |
| const ev = clean({ event_type: "session", session_id: sid, ...summary }); | |
| const key = JSON.stringify(ev); | |
| if (key === lastSent) return; | |
| lastSent = key; | |
| post([ev], beacon); | |
| } | |
| document.addEventListener("visibilitychange", () => { if (document.visibilityState === "hidden") sendSession(true); }); | |
| window.addEventListener("pagehide", () => sendSession(true)); | |
| return { | |
| gpc, | |
| get optedOut() { return optedOut; }, | |
| setOptOut(v) { | |
| optedOut = v; | |
| try { | |
| if (v) { localStorage.setItem("lme-optout", "1"); localStorage.removeItem("lme-optin"); } | |
| else { localStorage.removeItem("lme-optout"); if (gpc) localStorage.setItem("lme-optin", "1"); } | |
| } catch (e) {} | |
| }, | |
| setEnabled(v) { enabled = v; }, | |
| setBuilder(fn) { build = fn; }, | |
| sendSession, | |
| async submit(type, fields) { | |
| if (optedOut) return { ok: false, message: "Sending is turned off in the privacy setting at the bottom of the page." }; | |
| const ev = clean({ event_type: type, session_id: sid, ...fields }); | |
| try { | |
| const r = await fetch("/api/events", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ events: [ev] }) }); | |
| const j = await r.json().catch(() => ({})); | |
| if (r.status === 202) return { ok: true, row: (j.rows || [])[0] || {} }; | |
| if (r.status === 429) return { ok: false, message: "Too many submissions from this network. Try again in a minute." }; | |
| const d = (j.details || []).map((x) => `${(x.loc || []).slice(-1)[0]}: ${x.msg}`).join("; "); | |
| return { ok: false, message: `Not accepted${d ? ` (${d})` : ""}.` }; | |
| } catch (e) { | |
| return { ok: false, message: "Couldn't reach the server. Check your connection and try again." }; | |
| } | |
| }, | |
| }; | |
| })(); | |
| // ------------------------------------------------------------------ state | |
| let META = null; | |
| const visit = { families: new Set(), queries: new Set(), viewed: [], clicked: [], copied: new Set(), searched: false, top: null }; | |
| const state = { offset: 0, total: 0, loading: 0, detailKey: null }; | |
| function rig() { | |
| const kind = $('input[name="kind"]:checked').value; | |
| const out = { kind, ram_gb: 32, gpus: [] }; | |
| if (kind === "gpu") { | |
| const cards = (sel, vram, count, fallback) => { | |
| const name = $(sel).value; | |
| const each = name === "custom" ? Math.max(1, Math.min(256, +$(vram).value || fallback)) : META.hardware.gpus[name]; | |
| return Array.from({ length: +$(count).value || 1 }, () => each); | |
| }; | |
| out.gpus = cards("#gpu", "#custom-vram", "#gpu-count", 24); | |
| if (mixedCards()) out.gpus = out.gpus.concat(cards("#gpu2", "#custom-vram2", "#gpu2-count", 8)).slice(0, 16); | |
| out.ram_gb = +$("#ram").value; | |
| out.gpu_model = $("#gpu").value; | |
| } else if (kind === "unified") { | |
| out.unified_system = $("#unified-system").value; | |
| out.ram_gb = +$("#unified-mem").value; | |
| } else { | |
| out.ram_gb = +$("#ram").value; | |
| } | |
| return out; | |
| } | |
| function mixedCards() { return !$("#gpu2-block").hidden; } | |
| function customCard() { return $("#gpu").value === "custom" || (mixedCards() && $("#gpu2").value === "custom"); } | |
| function hardwarePayload(r) { | |
| const h = { kind: r.kind, gpus: r.gpus, ram_gb: r.ram_gb }; | |
| if (r.unified_system) h.unified_system = r.unified_system; | |
| return h; | |
| } | |
| function usableVram(r) { | |
| if (r.kind === "unified") { | |
| const share = (META.hardware.unified_shares || {})[r.unified_system]; | |
| return r.ram_gb * (share || (r.ram_gb <= 36 ? 0.67 : 0.75)); | |
| } | |
| return r.gpus.reduce((a, v) => a + Math.max(v - 0.5, 0), 0); | |
| } | |
| function renderCapacity() { | |
| const r = rig(); | |
| const box = $("#capacity"); | |
| if (r.kind === "gpu") { | |
| const total = r.gpus.reduce((a, v) => a + v, 0); | |
| box.replaceChildren( | |
| el("div", { class: "pool gpu" }, el("b", {}, `${total} GB`), `VRAM${r.gpus.length > 1 ? ` on ${r.gpus.length} cards` : ""}`), | |
| el("div", { class: "pool ram" }, el("b", {}, `${r.ram_gb} GB`), "system RAM")); | |
| } else if (r.kind === "unified") { | |
| box.replaceChildren(el("div", { class: "pool gpu" }, el("b", {}, gb(usableVram(r), 0)), `of ${r.ram_gb} GB usable by the GPU by default`)); | |
| } else { | |
| box.replaceChildren(el("div", { class: "pool ram" }, el("b", {}, `${r.ram_gb} GB`), "system RAM")); | |
| } | |
| } | |
| function saveRig() { | |
| store.set("lme-rig", { | |
| kind: $('input[name="kind"]:checked').value, gpu: $("#gpu").value, vram: $("#custom-vram").value, | |
| count: $("#gpu-count").value, mixed: mixedCards(), gpu2: $("#gpu2").value, vram2: $("#custom-vram2").value, | |
| count2: $("#gpu2-count").value, ram: $("#ram").value, usys: $("#unified-system").value, umem: $("#unified-mem").value, | |
| context: $("#context").value, kv: $("#kv").value, priority: $('input[name="priority"]:checked').value, | |
| minBits: $("#min-bits").value, fits: $("#fits-only").checked, vramOnly: $("#vram-only").checked, mmproj: $("#mmproj").value, | |
| }); | |
| } | |
| function syncKind() { | |
| const kind = $('input[name="kind"]:checked').value; | |
| $$("[data-show]").forEach((n) => { n.hidden = !n.dataset.show.split(" ").includes(kind); }); | |
| $("#custom-vram-field").hidden = kind !== "gpu" || $("#gpu").value !== "custom"; | |
| $("#custom-vram2-field").hidden = $("#gpu2").value !== "custom"; | |
| $("#gpu2-toggle").textContent = mixedCards() ? "Remove the other card" : "Add a different card"; | |
| const note = (META.hardware.unified_notes || {})[$("#unified-system").value]; | |
| $("#unified-note").textContent = note || ""; | |
| $("#unified-note").hidden = !note; | |
| const mems = META.hardware.unified[$("#unified-system").value] || []; | |
| const cur = +$("#unified-mem").value; | |
| $("#unified-mem").replaceChildren(...mems.map((m) => el("option", { value: m, selected: m === cur }, `${m} GB`))); | |
| if (!mems.includes(cur) && mems.length) $("#unified-mem").value = mems.includes(64) ? 64 : mems[mems.length - 1]; | |
| renderCapacity(); | |
| } | |
| function query() { | |
| const r = rig(); | |
| return { | |
| hardware: hardwarePayload(r), context: +$("#context").value, kv_type: $("#kv").value, | |
| priority: $('input[name="priority"]:checked').value, min_bits: +$("#min-bits").value, sort: $("#sort").value, | |
| family: $("#family").value || null, size: $("#size").value || null, search: $("#search").value.trim() || null, | |
| moe_only: $("#moe-only").checked, vision_only: $("#vision-only").checked, fits_only: $("#fits-only").checked, | |
| vram_only: r.kind === "gpu" && $("#vram-only").checked, mmproj: $("#mmproj").value, | |
| }; | |
| } | |
| // ------------------------------------------------------------------ memory rail | |
| const SCORE_PARTS = [["fit", "Fit on your hardware"], ["capacity", "Model size you can run"], | |
| ["popularity", "Downloads"], ["recency", "How new"], ["community", "Community reports"]]; | |
| function scoreBreakdown(m) { | |
| const w = META.score_weights || {}; | |
| return SCORE_PARTS.map(([k, label]) => `${label}: ${Math.round((m.components[k] || 0) * w[k] * 100)} of ${Math.round(w[k] * 100)}`); | |
| } | |
| function rail(best, r) { | |
| const f = best && best.fit; | |
| if (!f) return null; | |
| const box = el("div", { class: "rail", role: "img" }); | |
| const pct = (v, cap) => `${Math.max(0, Math.min(100, (v / cap) * 100))}%`; | |
| if (r.kind === "unified") { | |
| const cap = usableVram(r); | |
| const pool = el("div", { class: "pool", style: `flex:1` }, el("i", { class: "gpu", style: `width:${pct(f.gpu_gb, cap)}` })); | |
| if (f.gpu_gb > cap) pool.append(el("i", { class: "over", style: `width:${pct(f.gpu_gb - cap, f.gpu_gb)}` })); | |
| box.append(pool); | |
| box.setAttribute("aria-label", `Needs ${gb(f.gpu_gb)} of ${gb(cap)} the GPU can use`); | |
| return box; | |
| } | |
| const vram = r.kind === "gpu" ? r.gpus.reduce((a, v) => a + v, 0) : 0; // GPU figures include each card's driver context | |
| const ram = r.ram_gb; | |
| const total = vram + ram; | |
| if (vram) { | |
| box.append(el("div", { class: "pool", style: `flex:${vram / total}` }, | |
| el("i", { class: "gpu", style: `width:${pct(f.gpu_gb, vram)}` }))); | |
| } | |
| const ramPool = el("div", { class: "pool", style: `flex:${ram / total}` }, | |
| el("i", { class: f.tier === "gpu_moe" ? "exp" : "ram", style: `width:${pct(f.ram_gb, ram)}` })); | |
| if (f.tier === "no" || f.tier === "no_vram") ramPool.append(el("i", { class: "over", style: "width:30%" })); | |
| box.append(ramPool); | |
| box.setAttribute("aria-label", `GPU ${gb(f.gpu_gb)} of ${gb(vram)}; RAM ${gb(f.ram_gb)} of ${ram} GB`); | |
| return box; | |
| } | |
| function fitBadge(best) { | |
| if (!best || !best.fit) return el("span", { class: "fit t-cpu" }, "Pick hardware"); | |
| return el("span", { class: `fit t-${best.fit.tier}${best.estimated ? " t-est" : ""}` }, | |
| best.fit.label + (best.estimated ? " (est.)" : "")); | |
| } | |
| // ------------------------------------------------------------------ results | |
| function card(m, r) { | |
| const b = m.best; | |
| const tags = [ | |
| m.params ? el("span", { class: "num" }, params(m.params) + (m.active_params ? ` (${params(m.active_params)} active)` : "")) : null, | |
| m.moe ? el("span", { class: "tag" }, "MoE") : null, | |
| m.vision ? el("span", { class: "tag" }, "vision") : null, | |
| el("span", {}, m.family), | |
| el("span", {}, `${m.repo_count} GGUF repo${m.repo_count === 1 ? "" : "s"}`), | |
| el("span", {}, `${compact(m.downloads)} downloads`), | |
| ]; | |
| const choice = b ? el("div", { class: "r-choice" }, | |
| el("span", { class: "num" }, b.quant), " from ", el("span", {}, b.repo.split("/")[0]), " — ", | |
| el("span", { class: "num" }, bytesGB(b.size)), | |
| b.fit && b.fit.tier !== "no" && b.fit.tier !== "no_vram" ? el("span", { class: "muted" }, `, ${gb(b.fit.gpu_gb)} on GPU, ${gb(b.fit.ram_gb)} in RAM`) : null) | |
| : el("div", { class: "r-choice muted" }, "File sizes not loaded yet."); | |
| return el("li", { class: "result" }, | |
| el("div", {}, | |
| el("div", { class: "r-name" }, el("button", { type: "button", onclick: () => openDetail(m.key) }, m.name)), | |
| el("div", { class: "r-meta" }, tags)), | |
| el("div", { class: "r-side" }, el("div", { class: "score", title: scoreBreakdown(m).join("\n") + "\nA ranking for your hardware, not a measure of model quality." }, Math.round(m.score)), el("div", {}, "score")), | |
| el("div", { class: "r-best" }, choice, fitBadge(b)), | |
| rail(b, r)); | |
| } | |
| let inflight = null; | |
| async function search(append) { | |
| const q = query(); | |
| if (!append) state.offset = 0; | |
| const payload = { ...q, offset: state.offset, limit: 24 }; | |
| const token = Symbol(); | |
| inflight = token; | |
| $("#count").textContent = append ? $("#count").textContent : "Finding models…"; | |
| let res; | |
| try { | |
| const r = await fetch("/api/recommend", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(payload) }); | |
| if (!r.ok) throw new Error(String(r.status)); | |
| res = await r.json(); | |
| } catch (e) { | |
| if (inflight === token) $("#count").textContent = "Couldn't load models. Refresh the page to try again."; | |
| return; | |
| } | |
| if (inflight !== token) return; | |
| const hw = rig(); | |
| const list = $("#results"); | |
| if (!append) list.replaceChildren(); | |
| if (!res.results.length && !append) { | |
| list.append(el("li", { class: "empty" }, q.vram_only | |
| ? `Nothing here fits entirely in VRAM. Untick “Keep the whole model in VRAM”, lower the context${q.min_bits ? ", allow lower quants" : ""}, or try a smaller size.` | |
| : q.fits_only | |
| ? `Nothing here runs on this hardware. Untick “Only models that run”, lower the context${q.min_bits ? ", allow lower quants" : ""}, or try a smaller size.` | |
| : "No models match. Clear the search or pick another family.")); | |
| } | |
| res.results.forEach((m) => list.append(card(m, hw))); | |
| state.total = res.total; | |
| state.offset += res.results.length; | |
| $("#count").textContent = `${res.total.toLocaleString()} model${res.total === 1 ? "" : "s"}`; | |
| $("#more").hidden = state.offset >= res.total; | |
| $("#notice").hidden = !res.notices.length; | |
| $("#notice").textContent = res.notices.join(" "); | |
| // Some results were estimated while their files were still being read; the server keeps | |
| // reading, so ask again for the exact numbers, a few times at most, if the user hasn't moved on. | |
| // (The notice is only set for reads still pending, not for files that can't be read.) | |
| const asked = JSON.stringify(q); | |
| if (state.refineFor !== asked) { state.refineFor = asked; state.refines = 0; } | |
| clearTimeout(state.refine); | |
| if (!append && res.notices.some((n) => n.includes("estimated")) && state.refines < 3) { | |
| state.refines += 1; | |
| state.refine = setTimeout(() => { if (JSON.stringify(query()) === asked && state.offset <= 24) search(false); }, 6000 * state.refines); | |
| } | |
| if (!append) { | |
| const first = res.results[0]; | |
| visit.top = first ? { key: first.key, quant: first.best && first.best.quant, fit: first.best && first.best.fit && first.best.fit.tier } : null; | |
| if (q.family) visit.families.add(q.family); | |
| if (q.search) visit.searched = true; | |
| visit.queries.add(JSON.stringify([q.family, q.size, q.search, q.context, q.priority, q.hardware])); | |
| visit.lastResults = res.total; | |
| } | |
| } | |
| let debounce; | |
| function schedule() { | |
| clearTimeout(debounce); | |
| debounce = setTimeout(() => search(false), 250); | |
| } | |
| // ------------------------------------------------------------------ detail | |
| async function openDetail(key) { | |
| const dlg = $("#detail"); | |
| state.detailKey = key; | |
| $("#d-title").textContent = key.split("/").pop(); | |
| $("#d-kicker").textContent = key.includes("/") ? key.split("/")[0] : "Model"; | |
| $("#d-facts").textContent = ""; | |
| $("#d-body").replaceChildren(el("p", { class: "muted" }, "Reading the model's files… a model nobody has opened yet takes a few seconds.")); | |
| if (!dlg.open) dlg.showModal(); | |
| if (!visit.viewed.includes(key)) visit.viewed.push(key); | |
| Telemetry.sendSession(false); | |
| const q = query(); | |
| let d; | |
| try { | |
| const r = await fetch("/api/model", { method: "POST", headers: { "content-type": "application/json" }, | |
| body: JSON.stringify({ key, hardware: q.hardware, context: q.context, kv_type: q.kv_type, priority: q.priority, min_bits: q.min_bits, vram_only: q.vram_only, mmproj: q.mmproj }) }); | |
| if (!r.ok) throw new Error(String(r.status)); | |
| d = await r.json(); | |
| } catch (e) { | |
| if (state.detailKey === key) $("#d-body").replaceChildren(el("p", { class: "status err" }, "Couldn't load this model. Close and try again.")); | |
| return; | |
| } | |
| if (state.detailKey !== key) return; | |
| renderDetail(d, q); | |
| } | |
| function renderDetail(d, q) { | |
| const hw = rig(); | |
| const g = d.geometry; | |
| $("#d-facts").textContent = [ | |
| d.params ? `${params(d.params)} parameters${d.active_params ? `, ${params(d.active_params)} active` : ""}` : null, | |
| g ? `${g.n_layers} layers` : null, | |
| g && g.moe ? `${g.expert_count} experts, ${g.expert_used} per token` : null, | |
| d.context_length ? `${ctxLabel(d.context_length)} native context` : null, | |
| g && g.notes.length ? g.notes.join(", ") : null, | |
| ].filter(Boolean).join(" • "); | |
| const body = $("#d-body"); | |
| body.replaceChildren(); | |
| if (d.components) { | |
| body.append(el("details", { class: "score-why" }, | |
| el("summary", {}, `Score ${Math.round(d.score)} of 100, and how it's worked out`), | |
| el("ul", {}, scoreBreakdown(d).map((line) => el("li", {}, line))), | |
| el("p", { class: "muted" }, "It ranks what suits your hardware among popular models. It doesn't measure how good a model is: a newer version with fewer downloads can score lower than an older one."))); | |
| } | |
| const opts = d.options || []; | |
| let selected = d.best ? opts.findIndex((o) => o.repo === d.best.repo && o.quant === d.best.quant) : 0; | |
| if (selected < 0) selected = 0; | |
| // command for the selected quant | |
| const cmdBox = el("pre", { class: "cmd" }); | |
| const tabs = el("div", { class: "tabs", role: "tablist" }); | |
| let runtime = "llama.cpp"; | |
| const selLabel = el("p", { class: "muted", style: "margin:4px 0 0;font-size:13px" }); | |
| function showCmd() { | |
| const o = opts[selected]; | |
| if (!o) { cmdBox.textContent = "No quant files could be read for this model."; return; } | |
| cmdBox.textContent = o.commands[runtime]; | |
| $$("button", tabs).forEach((b) => b.setAttribute("aria-selected", String(b.dataset.rt === runtime))); | |
| selLabel.textContent = runtime === "ollama" | |
| ? "Ollama picks GPU offload itself and defaults to a short context; set num_ctx to use more." | |
| : `${o.fit ? o.fit.label + ". " : ""}${o.estimated ? "Sizes are estimated from the parameter count. " : ""}` + | |
| (!o.projector_size ? "" : q.mmproj === "off" ? `The ${gb(o.projector_size / 1e9)} vision projector isn't loaded (--no-mmproj): text only.` | |
| : q.mmproj === "ram" ? `Includes the ${gb(o.projector_size / 1e9)} vision projector, kept in system RAM (--no-mmproj-offload); images encode slower.` | |
| : `Includes the ${gb(o.projector_size / 1e9)} vision projector that llama-server loads onto the GPU with -hf. Change "Vision projector" to keep it in RAM or skip it.`); | |
| } | |
| for (const rt of ["llama.cpp", "ollama"]) { | |
| tabs.append(el("button", { type: "button", role: "tab", "data-rt": rt, onclick: () => { runtime = rt; showCmd(); } }, rt)); | |
| } | |
| const copyBtn = el("button", { type: "button", class: "btn", onclick: async () => { | |
| try { | |
| await navigator.clipboard.writeText(cmdBox.textContent); | |
| copyBtn.textContent = "Copied"; | |
| visit.copied.add(runtime); | |
| setTimeout(() => (copyBtn.textContent = "Copy command"), 1500); | |
| } catch (e) { copyBtn.textContent = "Select and copy"; } | |
| } }, "Copy command"); | |
| body.append(el("h3", {}, "Run it"), el("div", { class: "cmd-row" }, tabs, copyBtn), cmdBox, selLabel); | |
| showCmd(); | |
| // every quant | |
| const rows = opts.map((o, i) => { | |
| const tr = el("tr", { tabindex: "0", "aria-selected": String(i === selected) }, | |
| el("td", {}, el("a", { href: o.url, target: "_blank", rel: "noopener", onclick: (ev) => { | |
| ev.stopPropagation(); | |
| if (!visit.clicked.includes(o.repo) && visit.clicked.length < 5) visit.clicked.push(o.repo); | |
| } }, o.repo.split("/")[0])), | |
| el("td", { class: "num" }, o.quant), | |
| el("td", { class: "num" }, bytesGB(o.size)), | |
| el("td", {}, fitBadge(o)), | |
| el("td", {}, rail(o, hw))); | |
| const pick = () => { | |
| selected = i; | |
| $$("tbody tr", body).forEach((row, j) => row.setAttribute("aria-selected", String(j === i))); | |
| showCmd(); | |
| }; | |
| tr.addEventListener("click", pick); | |
| tr.addEventListener("keydown", (ev) => { if (ev.key === "Enter" || ev.key === " ") { ev.preventDefault(); pick(); } }); | |
| return tr; | |
| }); | |
| body.append( | |
| el("h3", {}, `Every quant (${opts.length})`), | |
| d.hidden_below_min_bits ? el("p", { class: "muted" }, `${d.hidden_below_min_bits} quant(s) below ${q.min_bits}-bit hidden. Change "Lowest quant to consider" to see them.`) : "", | |
| opts.length ? el("div", { class: "tbl-wrap" }, el("table", {}, | |
| el("thead", {}, el("tr", {}, el("th", {}, "Uploader"), el("th", { class: "num" }, "Quant"), el("th", { class: "num" }, "Size"), el("th", {}, "On your hardware"), el("th", {}, "Memory"))), | |
| el("tbody", {}, rows))) | |
| : el("p", { class: "muted" }, "None of this model's GGUF files could be listed. The repos may be gated.")); | |
| if (d.kv_cache_gb) { | |
| body.append(el("h3", {}, `KV cache at ${q.kv_type}`), el("div", { class: "kv-strip" }, | |
| Object.entries(d.kv_cache_gb).map(([c, v]) => el("span", {}, `${ctxLabel(+c)} `, el("b", {}, gb(v, v < 10 ? 2 : 1)))))); | |
| } | |
| // other formats | |
| const fm = d.other_formats || {}; | |
| const formats = []; | |
| const lists = (ids) => el("ul", {}, ids.slice(0, 6).map((id) => el("li", {}, el("a", { href: `https://huggingface.co/${id}`, target: "_blank", rel: "noopener" }, id)))); | |
| if (fm.mlx) formats.push(el("div", { class: "format" }, el("h4", {}, `MLX (${fm.mlx.length})`), | |
| el("p", { style: "margin:0" }, "For Apple Silicon. ", el("a", { href: META.links.mlx, target: "_blank", rel: "noopener" }, "MLX Model Explorer"), " compares them."), lists(fm.mlx))); | |
| if (fm.onnx) formats.push(el("div", { class: "format" }, el("h4", {}, `ONNX (${fm.onnx.length})`), | |
| el("p", { style: "margin:0" }, el("a", { href: META.links.onnx, target: "_blank", rel: "noopener" }, "ONNX Community model explorer")), lists(fm.onnx))); | |
| for (const [k, label] of [["awq", "AWQ"], ["gptq", "GPTQ"], ["fp8", "FP8"], ["exl3", "EXL3"], ["exl2", "EXL2"]]) { | |
| if (fm[k]) formats.push(el("div", { class: "format" }, el("h4", {}, `${label} (${fm[k].length})`), | |
| el("p", { class: "muted", style: "margin:0" }, k.startsWith("exl") ? "For ExLlama / TabbyAPI." : "For vLLM, SGLang and other GPU servers."), lists(fm[k]))); | |
| } | |
| if (formats.length) body.append(el("h3", {}, "Other formats"), el("div", { class: "formats" }, formats)); | |
| // community | |
| body.append(el("h3", {}, "Community")); | |
| const c = d.community; | |
| if (c && c.bench_rows && c.bench_rows.length) { | |
| body.append(el("div", { class: "tbl-wrap" }, el("table", {}, | |
| el("thead", {}, el("tr", {}, el("th", {}, "Quant"), el("th", {}, "Hardware"), el("th", {}, "Backend"), el("th", { class: "num" }, "GPU layers"), el("th", { class: "num" }, "Prompt t/s"), el("th", { class: "num" }, "Generation t/s"))), | |
| el("tbody", {}, c.bench_rows.map((b) => el("tr", {}, | |
| el("td", { class: "num" }, b.quant || "?"), | |
| el("td", {}, b.gpu_name || b.gpu_model || (b.vram_class ? `${b.vram_class} GB GPU` : "CPU")), | |
| el("td", {}, b.backend || "?"), | |
| el("td", { class: "num" }, b.n_gpu_layers ?? "?"), | |
| el("td", { class: "num" }, b.prompt_tps ? Math.round(b.prompt_tps) : "—"), | |
| el("td", { class: "num" }, b.generation_tps ? b.generation_tps.toFixed(1) : "—"))))))); | |
| } else { | |
| body.append(el("p", { class: "muted" }, "No benchmarks for this model yet. Run llama-bench and paste the result below: it is the only speed data this page shows.")); | |
| } | |
| if (c && c.feedback) { | |
| body.append(el("p", {}, `${c.feedback} report${c.feedback === 1 ? "" : "s"}` + (c.positive_share !== null ? `, ${Math.round(c.positive_share * 100)}% worked well.` : "."))); | |
| } | |
| body.append(el("div", { class: "two" }, benchForm(d, opts, () => opts[selected]), feedbackForm(d, () => opts[selected]))); | |
| } | |
| function hardwareFields() { | |
| const r = rig(); | |
| return { | |
| hardware_kind: r.kind, gpu_model: r.kind === "gpu" ? r.gpu_model : null, gpu_count: r.kind === "gpu" ? r.gpus.length : null, | |
| vram_gb: r.kind === "gpu" ? r.gpus.reduce((a, v) => a + v, 0) : null, ram_gb: r.ram_gb, | |
| unified_system: r.kind === "unified" ? r.unified_system : null, hardware_source: customCard() ? "custom" : "preset", | |
| }; | |
| } | |
| function quantSelect(opts, current) { | |
| const s = el("select", {}); | |
| opts.forEach((o, i) => s.append(el("option", { value: i, selected: o === current() }, `${o.quant} — ${o.repo}`))); | |
| return s; | |
| } | |
| function benchForm(d, opts, current) { | |
| const sel = quantSelect(opts, current); | |
| const ta = el("textarea", { rows: "6", placeholder: "| model | size | params | backend | ngl | test | t/s |\n| ... | pp512 | ... |\n| ... | tg128 | ... |", maxlength: "40000" }); | |
| const status = el("p", { class: "status", role: "status" }); | |
| const btn = el("button", { type: "button", class: "btn primary" }, "Share benchmark"); | |
| btn.addEventListener("click", async () => { | |
| const o = opts[+sel.value]; | |
| if (!o || !ta.value.trim()) { status.className = "status err"; status.textContent = "Pick the quant you ran and paste llama-bench's output."; return; } | |
| btn.disabled = true; | |
| const res = await Telemetry.submit("llama_bench", { selected_model: d.key, repo: o.repo, quant: o.quant, bench_output: ta.value, ...hardwareFields() }); | |
| btn.disabled = false; | |
| if (!res.ok) { status.className = "status err"; status.textContent = res.message; return; } | |
| const row = res.row || {}; | |
| if ((row.flags || []).includes("unparseable_benchmark")) { | |
| status.className = "status err"; | |
| status.textContent = "Saved, but no llama-bench table was found in it. Paste the whole table, or run llama-bench with -o json."; | |
| } else { | |
| status.className = "status ok"; | |
| status.textContent = `Thanks. Recorded ${row.generation_tps ? `${row.generation_tps.toFixed(1)} t/s generation` : ""}${row.prompt_tps ? `, ${Math.round(row.prompt_tps)} t/s prompt` : ""} on ${row.backend || "your backend"}.`; | |
| ta.value = ""; | |
| } | |
| }); | |
| return el("div", { class: "panel" }, | |
| el("h4", { style: "margin:0 0 6px" }, "Share a llama-bench result"), | |
| el("p", { class: "muted", style: "margin:0 0 8px;font-size:13px" }, "Run ", el("code", {}, "llama-bench -m model.gguf"), " and paste the output. Only the speeds and settings are kept; file paths are discarded. Your hardware settings on this page are sent with it."), | |
| el("div", { class: "field" }, el("label", {}, "Quant you ran"), sel), | |
| el("div", { class: "field" }, el("label", {}, "llama-bench output"), ta), btn, status); | |
| } | |
| function feedbackForm(d, current) { | |
| const opts = d.options || []; | |
| const sel = quantSelect(opts, current); | |
| const pick = (name, choices) => el("select", { name }, el("option", { value: "" }, "—"), choices.map(([v, l]) => el("option", { value: v }, l))); | |
| const tried = pick("tried", [["yes", "Yes"], ["planning", "Planning to"], ["no", "No"]]); | |
| const how = pick("how", [["excellent", "Worked great"], ["good", "Worked well"], ["acceptable", "Usable"], ["too_slow", "Too slow"], ["out_of_memory", "Ran out of memory"], ["low_quality", "Poor output"], ["didnt_load", "Didn't load"]]); | |
| const runtime = pick("runtime", [["llama.cpp", "llama.cpp"], ["ollama", "Ollama"], ["lm-studio", "LM Studio"], ["jan", "Jan"], ["koboldcpp", "KoboldCpp"], ["other", "Other"]]); | |
| const tps = el("input", { type: "number", min: "0", max: "5000", step: "0.1", placeholder: "optional" }); | |
| const notes = el("input", { type: "text", maxlength: "280", placeholder: "optional, 280 characters" }); | |
| const status = el("p", { class: "status", role: "status" }); | |
| const btn = el("button", { type: "button", class: "btn primary" }, "Send report"); | |
| btn.addEventListener("click", async () => { | |
| if (!tried.value) { status.className = "status err"; status.textContent = "Say whether you've tried it."; return; } | |
| const o = opts[+sel.value]; | |
| const good = ["excellent", "good", "acceptable"].includes(how.value); | |
| btn.disabled = true; | |
| const res = await Telemetry.submit("feedback", { | |
| selected_model: d.key, repo: o && o.repo, quant: o && o.quant, tried: tried.value, | |
| quality_rating: good ? how.value : null, failure_reason: how.value && !good ? how.value : null, | |
| runtime: runtime.value || null, reported_tps: tps.value ? +tps.value : null, reported_context: +$("#context").value, | |
| notes: notes.value || null, ...hardwareFields(), | |
| }); | |
| btn.disabled = false; | |
| status.className = `status ${res.ok ? "ok" : "err"}`; | |
| status.textContent = res.ok ? "Thanks, your report is in." : res.message; | |
| }); | |
| const f = (label, input) => el("div", { class: "field" }, el("label", {}, label), input); | |
| return el("div", { class: "panel" }, | |
| el("h4", { style: "margin:0 0 6px" }, "Have you run this model?"), | |
| f("Quant", sel), el("div", { class: "row2" }, f("Tried it?", tried), f("How did it go?", how)), | |
| el("div", { class: "row2" }, f("Runtime", runtime), f("Tokens/s", tps)), f("Notes", notes), btn, status); | |
| } | |
| // ------------------------------------------------------------------ boot | |
| async function boot() { | |
| try { | |
| const r = await fetch("/api/meta"); | |
| META = await r.json(); | |
| } catch (e) { | |
| $("#count").textContent = "Couldn't reach the server. Refresh the page to try again."; | |
| return; | |
| } | |
| if (!META.collection_enabled) Telemetry.setEnabled(false); | |
| const H = META.hardware; | |
| const gpuOptions = () => [ | |
| ...H.gpu_groups.map(([label, names]) => el("optgroup", { label }, ...names.map((n) => el("option", { value: n }, `${n.replace(/^(NVIDIA|AMD|Intel) /, "")} (${H.gpus[n]} GB)`)))), | |
| el("option", { value: "custom" }, "Other: enter VRAM")]; | |
| $("#gpu").replaceChildren(...gpuOptions()); | |
| $("#gpu").value = "NVIDIA RTX 3090"; | |
| $("#gpu2").replaceChildren(...gpuOptions()); | |
| $("#gpu2").value = "NVIDIA RTX 3060 12GB"; | |
| $("#gpu-count").replaceChildren(...[1, 2, 3, 4, 6, 8].map((n) => el("option", { value: n }, n))); | |
| $("#gpu2-count").replaceChildren(...[1, 2, 3, 4, 6, 8].map((n) => el("option", { value: n }, n))); | |
| $("#ram").replaceChildren(...H.ram.map((v) => el("option", { value: v }, `${v} GB`))); | |
| $("#ram").value = 64; | |
| $("#unified-system").replaceChildren(...Object.keys(H.unified).map((n) => el("option", { value: n }, n))); | |
| $("#context").replaceChildren(...H.contexts.map((c) => el("option", { value: c }, `${ctxLabel(c)} tokens`))); | |
| $("#context").value = 16384; | |
| $("#family").append(...META.families.map((f) => el("option", { value: f.family }, `${f.family} (${f.count})`))); | |
| $("#size").append(...META.sizes.map((s) => el("option", { value: s }, s))); | |
| const saved = store.get("lme-rig"); | |
| if (saved) { | |
| const set = (sel, v) => { if (v !== undefined && v !== null && [...$(sel).options].some((o) => o.value === String(v))) $(sel).value = v; }; | |
| const kindInput = $(`input[name="kind"][value="${saved.kind}"]`); | |
| if (kindInput) kindInput.checked = true; | |
| set("#gpu", saved.gpu); set("#gpu-count", saved.count); set("#ram", saved.ram); set("#unified-system", saved.usys); | |
| set("#context", saved.context); set("#kv", saved.kv); set("#min-bits", saved.minBits); set("#mmproj", saved.mmproj); | |
| if (saved.vram) $("#custom-vram").value = saved.vram; | |
| $("#gpu2-block").hidden = !saved.mixed; | |
| set("#gpu2", saved.gpu2); set("#gpu2-count", saved.count2); | |
| if (saved.vram2) $("#custom-vram2").value = saved.vram2; | |
| const pr = $(`input[name="priority"][value="${saved.priority}"]`); | |
| if (pr) pr.checked = true; | |
| $("#fits-only").checked = !!saved.fits; | |
| $("#vram-only").checked = !!saved.vramOnly; | |
| syncKind(); | |
| set("#unified-mem", saved.umem); | |
| } else { | |
| syncKind(); | |
| detectHint(); | |
| } | |
| renderCapacity(); | |
| $("#rig").addEventListener("change", () => { $("#detect-hint").hidden = true; syncKind(); saveRig(); schedule(); }); | |
| $("#custom-vram").addEventListener("input", () => { renderCapacity(); saveRig(); schedule(); }); | |
| $("#custom-vram2").addEventListener("input", () => { renderCapacity(); saveRig(); schedule(); }); | |
| $("#gpu2-toggle").addEventListener("click", () => { | |
| $("#gpu2-block").hidden = !$("#gpu2-block").hidden; | |
| syncKind(); saveRig(); schedule(); | |
| }); | |
| $("#filters").addEventListener("change", schedule); | |
| $("#filters").addEventListener("submit", (e) => { e.preventDefault(); search(false); }); | |
| $("#search").addEventListener("input", schedule); | |
| $("#more").addEventListener("click", () => search(true)); | |
| $("#d-close").addEventListener("click", () => $("#detail").close()); | |
| $("#detail").addEventListener("click", (e) => { if (e.target === $("#detail")) $("#detail").close(); }); | |
| $("#detail").addEventListener("close", () => { state.detailKey = null; }); | |
| const opt = $("#optout"); | |
| opt.checked = Telemetry.optedOut; | |
| if (Telemetry.gpc) $("#gpc-note").hidden = false; | |
| opt.addEventListener("change", () => Telemetry.setOptOut(opt.checked)); | |
| Telemetry.setBuilder(() => { | |
| if (!META) return null; | |
| const r = rig(); | |
| const q = query(); | |
| return { | |
| ...hardwareFields(), browser_family: browserFamily(), os_family: osFamily(), webgpu_vendor: DETECTED.vendor, | |
| model_family: q.family, size_bucket: q.size, context: q.context, kv_type: q.kv_type, priority: q.priority, min_bits: q.min_bits, | |
| sort: q.sort, fits_only: q.fits_only, result_count: visit.lastResults, families_searched: [...visit.families].slice(0, 5), | |
| distinct_queries: visit.queries.size, searched: visit.searched, | |
| top_model: visit.top && visit.top.key, top_quant: visit.top && visit.top.quant, top_fit: visit.top && visit.top.fit, | |
| models_viewed: visit.viewed.slice(0, 10), repos_clicked: visit.clicked.slice(0, 5), commands_copied: [...visit.copied], | |
| hardware_source: r.kind === "gpu" && customCard() ? "custom" : "preset", | |
| }; | |
| }); | |
| search(false); | |
| } | |
| const DETECTED = { vendor: null }; | |
| async function detectHint() { | |
| // Only a hint: browsers can't tell how much VRAM or memory there is. | |
| try { | |
| const plat = (navigator.userAgentData && navigator.userAgentData.platform) || navigator.platform || ""; | |
| if (/mac/i.test(plat)) { | |
| $('input[name="kind"][value="unified"]').checked = true; | |
| syncKind(); | |
| const h = $("#detect-hint"); | |
| h.hidden = false; | |
| h.textContent = "This looks like a Mac. Set your memory size: browsers can't read it."; | |
| } | |
| if (navigator.gpu) { | |
| const a = await navigator.gpu.requestAdapter(); | |
| const v = a && a.info && a.info.vendor; | |
| if (v && /^[a-z0-9 ._+-]{1,24}$/i.test(v)) DETECTED.vendor = v.toLowerCase(); | |
| } | |
| } catch (e) {} | |
| } | |
| function browserFamily() { | |
| const ua = navigator.userAgent; | |
| if (/Edg\//.test(ua)) return "edge"; | |
| if (/Firefox\//.test(ua)) return "firefox"; | |
| if (/Chrome\//.test(ua)) return "chrome"; | |
| if (/Safari\//.test(ua)) return "safari"; | |
| return "other"; | |
| } | |
| function osFamily() { | |
| const ua = navigator.userAgent; | |
| if (/iPhone|iPad/.test(ua)) return "ios"; | |
| if (/Android/.test(ua)) return "android"; | |
| if (/Mac OS X/.test(ua)) return "macos"; | |
| if (/Windows/.test(ua)) return "windows"; | |
| if (/Linux/.test(ua)) return "linux"; | |
| return "other"; | |
| } | |
| boot(); | |
| })(); | |