Spaces:
Running
Running
| // Fetches top GGUF models from HF, parses quants + file sizes, writes static/models.json. | |
| import { writeFileSync } from 'node:fs'; | |
| import { fileURLToPath } from 'node:url'; | |
| import { dirname, resolve } from 'node:path'; | |
| const __dirname = dirname(fileURLToPath(import.meta.url)); | |
| const OUT = resolve(__dirname, '..', 'static', 'models.json'); | |
| const LIMIT = 100; | |
| const HF_TOKEN = process.env.HF_TOKEN; | |
| const headers = HF_TOKEN ? { Authorization: `Bearer ${HF_TOKEN}` } : {}; | |
| const QUANT_RE = /(IQ\d_[A-Z0-9_]+|Q\d+_[A-Z0-9_]+|Q\d+|F16|BF16|F32|FP8|MXFP4(?:_MOE)?)/i; | |
| const SHARD_RE = /-(\d{5})-of-(\d{5})/; | |
| const PARAM_RE = /(\d+(?:\.\d+)?)\s*[bB](?![a-z])/; | |
| const TEXT_GEN_TAGS = new Set(['text-generation', 'conversational', 'image-text-to-text']); | |
| async function fetchJson(url) { | |
| const res = await fetch(url, { headers }); | |
| if (!res.ok) throw new Error(`${res.status} ${url}`); | |
| return res.json(); | |
| } | |
| function parseQuant(filename) { | |
| const base = filename.split('/').pop(); | |
| const m = base.match(QUANT_RE); | |
| return m ? m[1].toUpperCase() : null; | |
| } | |
| function parseParams(modelId, fileNames, ggufMeta) { | |
| // Try gguf metadata first | |
| if (ggufMeta?.total) { | |
| const b = ggufMeta.total / 1e9; | |
| if (b > 0.1 && b < 2000) return Math.round(b * 10) / 10; | |
| } | |
| // Try filename (e.g. "Llama-3.1-8B") | |
| for (const f of [modelId, ...fileNames]) { | |
| const m = f.match(PARAM_RE); | |
| if (m) return parseFloat(m[1]); | |
| } | |
| return null; | |
| } | |
| function groupShards(ggufFiles) { | |
| // Group multi-part shards (e.g. "-00001-of-00003.gguf") into one logical file. | |
| const groups = new Map(); | |
| for (const f of ggufFiles) { | |
| const sm = f.path.match(SHARD_RE); | |
| let key; | |
| if (sm) { | |
| key = f.path.replace(SHARD_RE, ''); | |
| } else { | |
| key = f.path; | |
| } | |
| if (!groups.has(key)) groups.set(key, { path: key, size: 0, parts: 0 }); | |
| const g = groups.get(key); | |
| g.size += f.size || 0; | |
| g.parts += 1; | |
| } | |
| return [...groups.values()]; | |
| } | |
| async function processModel(m) { | |
| try { | |
| const tree = await fetchJson( | |
| `https://huggingface.co/api/models/${m.id}/tree/main?recursive=true` | |
| ); | |
| const detail = await fetchJson(`https://huggingface.co/api/models/${m.id}`); | |
| const ggufFiles = tree | |
| .filter((t) => { | |
| if (t.type !== 'file' || !t.size) return false; | |
| const p = t.path.toLowerCase(); | |
| if (!p.endsWith('.gguf')) return false; | |
| // Skip auxiliary files: multimodal projectors, imatrix calibration, embeddings | |
| if (p.includes('mmproj') || p.includes('projector')) return false; | |
| if (p.includes('imatrix')) return false; | |
| return true; | |
| }) | |
| .map((t) => ({ path: t.path, size: t.size })); | |
| if (ggufFiles.length === 0) return null; | |
| const grouped = groupShards(ggufFiles); | |
| const quants = []; | |
| for (const g of grouped) { | |
| const quant = parseQuant(g.path); | |
| if (!quant) continue; | |
| quants.push({ | |
| path: g.path, | |
| size: g.size, | |
| sizeGB: +(g.size / 1024 ** 3).toFixed(2), | |
| quant, | |
| sharded: g.parts > 1 | |
| }); | |
| } | |
| if (quants.length === 0) return null; | |
| const params = parseParams(m.id, ggufFiles.map((f) => f.path), detail.gguf); | |
| const arch = detail.gguf?.architecture || detail.config?.model_type || null; | |
| return { | |
| id: m.id, | |
| author: m.id.split('/')[0], | |
| name: m.id.split('/').slice(1).join('/'), | |
| downloads: m.downloads || 0, | |
| likes: m.likes || 0, | |
| pipeline_tag: m.pipeline_tag || null, | |
| params_b: params, | |
| arch, | |
| n_layers: detail.gguf?.n_layers || null, | |
| n_kv_heads: detail.gguf?.n_kv_heads || detail.gguf?.n_heads || null, | |
| n_embd: detail.gguf?.n_embd || null, | |
| context_length: detail.gguf?.context_length || null, | |
| tags: m.tags || [], | |
| quants: quants.sort((a, b) => a.size - b.size) | |
| }; | |
| } catch (err) { | |
| console.warn(` skip ${m.id}: ${err.message}`); | |
| return null; | |
| } | |
| } | |
| async function main() { | |
| console.log(`Fetching top ${LIMIT} GGUF models...`); | |
| const list = await fetchJson( | |
| `https://huggingface.co/api/models?filter=gguf&sort=downloads&direction=-1&limit=${LIMIT}` | |
| ); | |
| console.log(`Got ${list.length} models. Filtering to text-generation...`); | |
| const candidates = list.filter( | |
| (m) => !m.pipeline_tag || TEXT_GEN_TAGS.has(m.pipeline_tag) | |
| ); | |
| console.log(`${candidates.length} candidates after filter.`); | |
| const results = []; | |
| let i = 0; | |
| for (const m of candidates) { | |
| i++; | |
| process.stdout.write(`[${i}/${candidates.length}] ${m.id}... `); | |
| const out = await processModel(m); | |
| if (out) { | |
| results.push(out); | |
| console.log(`OK (${out.quants.length} quants)`); | |
| } else { | |
| console.log('skip'); | |
| } | |
| } | |
| results.sort((a, b) => b.downloads - a.downloads); | |
| writeFileSync( | |
| OUT, | |
| JSON.stringify( | |
| { generated_at: new Date().toISOString(), count: results.length, models: results }, | |
| null, | |
| 2 | |
| ) | |
| ); | |
| console.log(`\nWrote ${results.length} models to ${OUT}`); | |
| } | |
| main().catch((e) => { | |
| console.error(e); | |
| process.exit(1); | |
| }); | |