Skip to content

Instantly share code, notes, and snippets.

@Interpause
Created March 17, 2026 22:28
Show Gist options
  • Select an option

  • Save Interpause/f63b9e4786987697d6d83125d80dc876 to your computer and use it in GitHub Desktop.

Select an option

Save Interpause/f63b9e4786987697d6d83125d80dc876 to your computer and use it in GitHub Desktop.
Thanks Gemini for this.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GGUF Metadata & Tensor Comparator</title>
<style>
:root {
--bg-color: #f9fafb;
--card-bg: #ffffff;
--border: #e5e7eb;
--primary: #2563eb;
--primary-hover: #1d4ed8;
--text-main: #111827;
--text-muted: #6b7280;
--diff-add-bg: #dcfce7;
--diff-rm-bg: #fee2e2;
--diff-chg-bg: #fef08a;
--diff-rm-text: #991b1b;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background-color: var(--bg-color);
color: var(--text-main);
margin: 0;
padding: 2rem;
line-height: 1.5;
}
.container { max-width: 1400px; margin: 0 auto; }
h1, h2 { margin-top: 0; }
p.subtitle { color: var(--text-muted); margin-bottom: 2rem; }
.card {
background: var(--card-bg);
border: 1px solid var(--border);
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1.5rem;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
.form-group { margin-bottom: 1.25rem; }
label { display: block; font-weight: 600; margin-bottom: 0.5rem; }
input[type="url"], textarea {
width: 100%; padding: 0.75rem; border: 1px solid var(--border);
border-radius: 6px; box-sizing: border-box; font-family: monospace;
}
textarea { resize: vertical; min-height: 100px; }
.controls { display: flex; gap: 1rem; align-items: center; }
button {
background: var(--primary); color: white; border: none;
padding: 0.75rem 1.5rem; border-radius: 6px; cursor: pointer;
font-weight: 600; font-size: 1rem; transition: background 0.2s;
}
button:hover { background: var(--primary-hover); }
button:disabled { background: #9ca3af; cursor: not-allowed; }
.status-container { margin-top: 1rem; font-weight: 500; }
.spinner { display: inline-block; width: 1rem; height: 1rem; border: 2px solid #ccc; border-top-color: var(--primary); border-radius: 50%; animation: spin 1s linear infinite; margin-right: 0.5rem; vertical-align: middle; }
@keyframes spin { to { transform: rotate(360deg); } }
/* Tables */
.table-responsive { width: 100%; overflow-x: auto; margin-top: 1rem; border: 1px solid var(--border); border-radius: 6px; }
table { width: 100%; border-collapse: collapse; min-width: 800px; }
th, td { text-align: left; padding: 1rem; border-bottom: 1px solid var(--border); border-right: 1px solid var(--border); vertical-align: top; }
th { background: #f3f4f6; font-weight: 600; position: sticky; top: 0; z-index: 10; }
th:first-child { min-width: 250px; background: #e5e7eb; left: 0; z-index: 20; position: sticky; }
td:first-child { background: #f9fafb; font-weight: 600; position: sticky; left: 0; z-index: 10; font-size: 0.85rem; word-break: break-all; }
.model-header-repo { font-size: 0.75rem; color: var(--primary); font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; }
.model-header-file { font-size: 0.95rem; word-break: break-all; margin-top: 0.25rem; }
.val-block {
white-space: pre-wrap;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 0.85rem;
background: rgba(0,0,0,0.03);
padding: 0.75rem;
border-radius: 4px;
max-height: 250px;
overflow-y: auto;
word-break: break-all;
}
.cell-changed { background-color: var(--diff-chg-bg) !important; }
.cell-added { background-color: var(--diff-add-bg) !important; }
.cell-missing { background-color: var(--diff-rm-bg) !important; color: var(--diff-rm-text); font-style: italic; }
.cell-error { background-color: #fef2f2 !important; color: #b91c1c; }
.tensor-shape { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.75rem; color: #4b5563; margin-bottom: 0.4rem; font-weight: 600; }
.badge-quant { display: inline-block; padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.8rem; font-weight: 700; background: #e0e7ff; color: #3730a3; border: 1px solid #c7d2fe; font-family: monospace; }
.badge-unknown { background: #fee2e2; color: #991b1b; border-color: #fecaca; }
.error-msg { color: var(--diff-rm-text); font-weight: 600; }
</style>
</head>
<body>
<div class="container">
<h1>GGUF Multi-Model Comparator</h1>
<p class="subtitle">Side-by-side comparison of metadata, tensor shapes, and layer quantization levels.</p>
<div class="card">
<div class="form-group">
<label for="refUrl">Reference GGUF URL</label>
<input type="url" id="refUrl" placeholder="https://huggingface.co/username/repo/resolve/main/model.gguf" value="https://huggingface.co/bartowski/Tesslate_OmniCoder-9B-GGUF/resolve/main/Tesslate_OmniCoder-9B-Q8_0.gguf">
</div>
<div class="form-group">
<label for="targetUrls">Target GGUF URLs (One per line)</label>
<textarea id="targetUrls" placeholder="https://huggingface.co/username/repo/resolve/main/model-v2.gguf"></textarea>
</div>
<div class="controls">
<button id="compareBtn">Compare Models</button>
<label style="display:inline-flex; align-items:center; font-weight:normal; margin:0; cursor:pointer;">
<input type="checkbox" id="showUnchanged" style="margin-right:0.5rem;">
Show Unchanged Rows
</label>
</div>
<div class="status-container" id="statusArea"></div>
</div>
<div id="resultsArea"></div>
</div>
<script type="module">
import { gguf } from "https://cdn.jsdelivr.net/npm/@huggingface/gguf@0.1.5/+esm";
const GGML_QUANT_TYPES = {
0: "F32", 1: "F16", 2: "Q4_0", 3: "Q4_1", 6: "Q5_0", 7: "Q5_1", 8: "Q8_0", 9: "Q8_1",
10: "Q2_K", 11: "Q3_K", 12: "Q4_K", 13: "Q5_K", 14: "Q6_K", 15: "Q8_K", 16: "IQ2_XXS",
17: "IQ2_XS", 18: "IQ3_XXS", 19: "IQ1_S", 20: "IQ4_NL", 21: "IQ3_S", 22: "IQ2_S",
23: "IQ4_XS", 24: "I8", 25: "I16", 26: "I32", 27: "I64", 28: "F64", 29: "IQ1_M",
30: "BF16", 31: "Q4_0_4_4", 32: "Q4_0_4_8", 33: "Q4_0_8_8", 34: "TQ1_0", 35: "TQ2_0"
};
const btn = document.getElementById('compareBtn');
const statusArea = document.getElementById('statusArea');
const resultsArea = document.getElementById('resultsArea');
const showUnchangedCheckbox = document.getElementById('showUnchanged');
showUnchangedCheckbox.addEventListener('change', (e) => {
const rows = document.querySelectorAll('.row-unchanged');
rows.forEach(row => {
row.style.display = e.target.checked ? 'table-row' : 'none';
});
});
function cleanUrl(url) {
url = url.trim();
if (url.includes('huggingface.co') && url.includes('/blob/')) {
return url.replace('/blob/', '/resolve/');
}
return url;
}
function extractModelInfo(url) {
let repo = 'Unknown Repo';
let file = url.split('/').pop() || 'Unknown File';
try {
const u = new URL(url);
if (u.hostname === 'huggingface.co') {
const parts = u.pathname.split('/').filter(Boolean);
if (parts.length >= 2) repo = `${parts[0]}/${parts[1]}`;
}
} catch(e) {}
return { repo, file };
}
function escapeHTML(str) {
return String(str).replace(/[&<>'"]/g, tag =>
({ '&': '&amp;', '<': '&lt;', '>': '&gt;', "'": '&#39;', '"': '&quot;' }[tag] || tag)
);
}
// UTILS FOR FILE SIZE
function formatBytes(bytes) {
if (!+bytes) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
}
function formatSizeDiff(refBytes, targetBytes) {
if (!refBytes || !targetBytes) return '';
const diff = targetBytes - refBytes;
if (diff === 0) return `<span style="color: #6b7280; font-weight: normal;">(Same size)</span>`;
const sign = diff > 0 ? '+' : '-';
const color = diff > 0 ? '#991b1b' : '#166534'; // Red if larger, Green if smaller
return `<span style="color: ${color}; font-weight: bold;">(${sign}${formatBytes(Math.abs(diff))})</span>`;
}
// Helper: JSON Replacer to prevent BigInt crash
const bigIntReplacer = (k, v) => typeof v === 'bigint' ? v.toString() : v;
function formatValue(val) {
if (val === undefined || val === null) return '<i>null</i>';
if (Array.isArray(val) || ArrayBuffer.isView(val)) {
const arr = Array.isArray(val) ? val : Array.from(val);
if (arr.length > 25) {
// FIXED: Included bigIntReplacer here to prevent the crash on array truncation
const head = arr.slice(0, 10).map(v => JSON.stringify(v, bigIntReplacer)).join(',\n ');
const tail = arr.slice(-3).map(v => JSON.stringify(v, bigIntReplacer)).join(',\n ');
return `<div class="val-block">[ \n ${escapeHTML(head)},\n ...\n // ${arr.length - 13} items truncated\n ...\n ${escapeHTML(tail)}\n]</div>`;
}
}
let str = JSON.stringify(val, bigIntReplacer, 2);
if (typeof val === 'string') str = val;
return `<div class="val-block">${escapeHTML(str)}</div>`;
}
function getComparableString(val) {
return JSON.stringify(val, bigIntReplacer);
}
async function fetchGGUFData(url) {
try {
// Attempt to get file size via HEAD request (No download footprint)
let fileSize = null;
try {
const headRes = await fetch(url, { method: 'HEAD' });
const cl = headRes.headers.get('content-length');
if (cl) fileSize = Number(cl);
} catch(e) { console.warn("Could not fetch HEAD for size", e); }
const { metadata, tensorInfos } = await gguf(url);
return { metadata, tensorInfos, fileSize, success: true };
} catch (err) {
return { error: err.message, success: false };
}
}
btn.addEventListener('click', async () => {
const refUrlRaw = document.getElementById('refUrl').value;
const targetUrlsRaw = document.getElementById('targetUrls').value;
const urls = [refUrlRaw, ...targetUrlsRaw.split('\n')].map(cleanUrl).filter(u => u);
if (urls.length < 2) {
alert('Please provide a Reference URL and at least one Target URL.');
return;
}
btn.disabled = true;
resultsArea.innerHTML = '';
const modelsData = [];
for (let i = 0; i < urls.length; i++) {
const label = i === 0 ? "Reference" : `Target ${i}`;
statusArea.innerHTML = `<div class="spinner"></div> Fetching ${label} GGUF...`;
const info = extractModelInfo(urls[i]);
const data = await fetchGGUFData(urls[i]);
modelsData.push({ url: urls[i], info, ...data, isRef: i === 0 });
}
statusArea.innerHTML = `<span style="color: #166534;">✓ Data loaded. Rendering comparison...</span>`;
setTimeout(() => {
renderUnifiedTables(modelsData);
statusArea.innerHTML = '';
btn.disabled = false;
}, 100);
});
function generateHeaderHTML(modelsData) {
let html = `<tr><th>Attribute / Layer</th>`;
modelsData.forEach(model => {
let sizeBadge = '';
if (model.success && model.fileSize) {
const sizeStr = formatBytes(model.fileSize);
let diffHtml = '';
if (!model.isRef && modelsData[0].success && modelsData[0].fileSize) {
diffHtml = ` ${formatSizeDiff(modelsData[0].fileSize, model.fileSize)}`;
}
sizeBadge = `<div style="margin-top: 0.5rem; font-size: 0.8rem; color: #4b5563;">📦 ${sizeStr}${diffHtml}</div>`;
}
html += `
<th>
<div class="model-header-repo">${model.isRef ? '🛡️ REF: ' : ''}${escapeHTML(model.info.repo)}</div>
<div class="model-header-file">${escapeHTML(model.info.file)}</div>
${sizeBadge}
</th>`;
});
html += `</tr>`;
return html;
}
function renderUnifiedTables(modelsData) {
const successfulModels = modelsData.filter(m => m.success);
if (successfulModels.length === 0) {
resultsArea.innerHTML = `<div class="card error-msg">Failed to load any models. Check URLs and CORS.</div>`;
return;
}
const displayToggle = showUnchangedCheckbox.checked ? "table-row" : "none";
// --- 1. METADATA COMPARISON ---
const allMetaKeys = new Set();
successfulModels.forEach(m => Object.keys(m.metadata).forEach(k => allMetaKeys.add(k)));
const sortedMetaKeys = Array.from(allMetaKeys).sort();
let metaHtml = `<div class="card"><h2>Metadata Comparison</h2><div class="table-responsive"><table><thead>${generateHeaderHTML(modelsData)}</thead><tbody>`;
sortedMetaKeys.forEach(key => {
let isUnchanged = true;
let rowCellsHtml = `<td><code>${escapeHTML(key)}</code></td>`;
const refModel = modelsData[0];
const inRef = refModel.success && key in refModel.metadata;
const refValStr = inRef ? getComparableString(refModel.metadata[key]) : null;
modelsData.forEach((model) => {
if (!model.success) {
rowCellsHtml += `<td class="cell-error">Failed to load</td>`;
isUnchanged = false;
return;
}
const inModel = key in model.metadata;
if (inRef !== inModel) isUnchanged = false;
if (!inModel) {
rowCellsHtml += `<td class="cell-missing">Missing</td>`;
} else {
const valStr = getComparableString(model.metadata[key]);
if (inRef && valStr !== refValStr) isUnchanged = false;
let cellClass = "";
if (!model.isRef) {
if (!inRef) cellClass = "cell-added";
else if (valStr !== refValStr) cellClass = "cell-changed";
}
rowCellsHtml += `<td class="${cellClass}">${formatValue(model.metadata[key])}</td>`;
}
});
const rowClass = isUnchanged ? "row-unchanged" : "";
const rowStyle = isUnchanged ? `display: ${displayToggle};` : "";
metaHtml += `<tr class="${rowClass}" style="${rowStyle}">${rowCellsHtml}</tr>`;
});
metaHtml += `</tbody></table></div></div>`;
// --- 2. TENSOR COMPARISON ---
const allTensorNames = new Set();
const tensorMaps = modelsData.map(m => {
const map = {};
if (m.success && m.tensorInfos) {
m.tensorInfos.forEach(t => {
const quantInt = t.dtype ?? t.type ?? t.ggmlType ?? t.ggml_type;
const rawShape = t.shape || t.dimensions || [];
const shapeArr = (Array.isArray(rawShape) || ArrayBuffer.isView(rawShape)) ? Array.from(rawShape).map(Number) : [];
map[t.name] = { quantInt, shapeArr, raw: t };
allTensorNames.add(t.name);
});
}
return map;
});
const sortedTensorNames = Array.from(allTensorNames).sort();
let tensorHtml = `<div class="card"><h2>Tensor Info (Shape & Quantization)</h2><div class="table-responsive"><table><thead>${generateHeaderHTML(modelsData)}</thead><tbody>`;
sortedTensorNames.forEach(tName => {
let isUnchanged = true;
let rowCellsHtml = `<td><code>${escapeHTML(tName)}</code></td>`;
const refMap = tensorMaps[0];
const inRef = tName in refMap;
const refData = inRef ? refMap[tName] : null;
const refShapeStr = inRef ? refData.shapeArr.join(' × ') : null;
modelsData.forEach((model, idx) => {
if (!model.success) {
rowCellsHtml += `<td class="cell-error">N/A</td>`;
isUnchanged = false;
return;
}
const tMap = tensorMaps[idx];
const inModel = tName in tMap;
if (inRef !== inModel) isUnchanged = false;
if (!inModel) {
rowCellsHtml += `<td class="cell-missing">Missing Layer</td>`;
} else {
const tData = tMap[tName];
const shapeStr = tData.shapeArr.length ? tData.shapeArr.join(' × ') : "Unknown Shape";
const isUnknownType = tData.quantInt === undefined;
const quantName = isUnknownType ? "Unknown" : (GGML_QUANT_TYPES[tData.quantInt] || `TYPE_${tData.quantInt}`);
if (inRef && tData.quantInt !== refData.quantInt) isUnchanged = false;
if (inRef && shapeStr !== refShapeStr) isUnchanged = false;
let cellClass = "";
if (!model.isRef) {
if (!inRef) cellClass = "cell-added";
else if (tData.quantInt !== refData.quantInt || shapeStr !== refShapeStr) cellClass = "cell-changed";
}
rowCellsHtml += `<td class="${cellClass}">
<div class="tensor-shape">[ ${escapeHTML(shapeStr)} ]</div>
<span class="badge-quant ${isUnknownType ? 'badge-unknown' : ''}">${escapeHTML(quantName)}</span>
${isUnknownType ? `<div style="font-size: 0.7rem; color: #991b1b; margin-top:0.4rem; word-break:break-all;"><strong>Keys found:</strong> ${escapeHTML(Object.keys(tData.raw).join(', '))}</div>` : ''}
</td>`;
}
});
const rowClass = isUnchanged ? "row-unchanged" : "";
const rowStyle = isUnchanged ? `display: ${displayToggle};` : "";
tensorHtml += `<tr class="${rowClass}" style="${rowStyle}">${rowCellsHtml}</tr>`;
});
tensorHtml += `</tbody></table></div></div>`;
resultsArea.innerHTML = metaHtml + tensorHtml;
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment