fetch_models.py - HuggingFace Model Categorizer

fetch_models.py

Comprehensive GGUF Model Fetcher and Categorizer

Fetches GGUF models from all major quantization uploaders on HuggingFace. Filters by param count (0.1B-14B). Categorizes by keyword rules. Anything not matched goes into Auto/ via a second-pass clusterer.


Overview

fetch_models.py is a production-grade tool that solves a critical problem: How do you make sense of thousands of LLM models on HuggingFace?

It fetches models from all major GGUF quantization uploaders, intelligently categorizes them, and produces a clean, structured output that can be used to power model selection interfaces, analysis tools, or any application that needs to work with the LLM ecosystem at scale.


Problem Statement

As of mid-2026, there are:

Manual categorization is:

fetch_models.py automates this entirely.


Features

1. Comprehensive Coverage

Fetches from 23 major uploaders:

UPLOADERS = [
    "Lewdiculous", "QuantFactory", "brittlewis12", "RichardErkhov",
    "second-state", "mradermacher", "lmstudio-community", "MaziyarPanahi",
    "bartowski", "TheBloke", "unsloth", "mmnga", "SanctumAI",
    "OuteAI", "crusoeai", "NousResearch", "instructlab",
    "tiiuae", "stabilityai", "xtuner", "mlabonne",
    "adrienbrault", "tensorblock", "ChaoticNeutrals",
]

2. Intelligent Categorization

Two-stage categorization system:

Stage 1: Primary Categories (15 categories, first match wins)

CategoryMatching PatternsExamples
Audiotts, speech, voice, whisper, bark, oute, text-to-speechWhisper, Bark, Outé
Visionvl, vision, llava, moondream, minicpm, bakllava, visual, idefics, paligemma, internvl, pixmo, image-text, multimodal, qvqLLaVA, MiniCPM-V
Codingcoder, starcoder, codellama, codegemma, deepseek-coder, octocoder, codestral, santacoder, wizardcoder, phind, codegeex, openhands, deepseek-v2, deepseek-v3StarCoder2, DeepSeek-Coder
Reasoning-r1, deepseek-r, qwq, reasoning, math, skywork, -o1, longwriter, thinking, cot, science, deepscaler, reflection, sky-t1, prm, orm, verifDeepSeek-R1, QWQ, Skywork
Medicalmedical, clinical, health, nurse, doctor, medllama, biomed, pubmed, med-, meditron, medialpacaMedLlama, Meditron
Legallegal, law, lawyer, juris, contracts, court, attorneyLawLlama, JurisAI
Financefinance, financial, trading, econom, bloomberg, stockFinLlama, BloombergGPT
Roleplayroleplay, rpg, persona, narrative, fiction, novel, dungeon, tavern, simulatorTavernAI
Uncensoreduncensored, unfiltered, unrestricted, adult, nsfw, lewdplay, gemmasutra, eris, stheno, spicy, erotic, unchained, abliterat, veniceUncensoredLlama
Creativedolphin, hermes, openchat, zephyr, capybara, wizard, neural, samantha, mytho, airoboros, manticore, orca, platypus, starling, neuralhermes, beluga, chimeraDolphin, Hermes, Zephyr
Long Contextlong, context, 128k, 256k, extended, infini, yarn, longchatLongLlama, Infini, Yarn
Multilingualmultilingual, bloom, xglm, arabic, japanese, korean, german, french, spanish, hindi, turkish, aya, command-r, c4ai, glm, glm-4Bloom, XGLM, GLM-4
Mergedmerge, franken, slerp, ties, dare, hybrid, fusion, amalgam, supernovaFrankenLlama, Supernova
Tinysmollm, tinyllama, smol-, 135m, 360m, phi-1, edge, mobile, nano, on-deviceTinyLlama, Phi-1, SmolLM
Instructinstruct, chat, assistant, sft, rlhf, dpo, orpo, chatqa, nemotron, orchestrat, phi-4, phi-3, phi-2Llama-3-Instruct, Phi-3

Stage 2: Auto-Categorization (14 subcategories)

For models that don’t match primary categories:

CategoryMatching Patterns
Auto/Gemmagemma
Auto/Llamallama
Auto/Qwenqwen
Auto/Mistralmistral, mistrale
Auto/Phiphi
Auto/Falconfalcon
Auto/DeepSeekdeepseek
Auto/GLMglm
Auto/Rekareka
Auto/Nvidianvidia, nemotron
Auto/SPPOsppo
Auto/Commandcommand
Auto/Yiyi-
Auto/Other(catch-all)

3. Smart Parameter Extraction

Handles complex parameter notation:

def extract_params(text):
    text = text.replace("_", "-")
    
    # Handle MoE notation like 30B-A3B (active params)
    moe = re.search(r'(\d+(?:\.\d+)?)[Bb]-[Aa]\d', text)
    if moe:
        return float(moe.group(1))  # Use total, not active
    
    # Standard notation: 7B, 13B, 70B
    matches = re.findall(r'(\d+(?:\.\d+)?)\s*([BbMm])(?:[^a-zA-Z]|$)', text)
    for num, unit in matches:
        val = float(num)
        if unit.upper() == 'M':
            val = val / 1000.0
        if val > 200:  # Exclude models > 200B
            continue
        return val
    return None

4. Intelligent File Selection

For each model repository with multiple files, picks the best GGUF file:

def pick_sibling(siblings):
    # Get GGUF files
    names = [s.get("rfilename", "") for s in siblings 
             if s.get("rfilename", "").endswith(".gguf")]
    
    def bad(n):
        # Exclude: 4/4, 4/8, 8/8 quantizations, fp16, f32, bf16, slash-containing
        return any(x in n for x in 
                   ["_4_4", "_4_8", "_8_8", "-fp16", "f16.", "f32.", "bf16.", "/"])
    
    # Priority order: Q4_K_M > Q4_K_S > Q4_0 > other Q4 > others
    for pat in ["Q4_K_M", "Q4_K_S", "Q4_0"]:
        for n in names:
            if pat in n and not bad(n):
                return n
    
    for n in names:
        if "Q4" in n and not bad(n):
            return n
    
    for n in names:
        if not bad(n) and not any(x in n for x in ["f16","f32","bf16","Q8","Q6","Q5"]):
            return n
    
    return None

5. Human-Readable Size Estimation

def human_size(filename, params):
    if params is None:
        return "?"
    
    # Bits per byte for different quantizations
    bpb = {"Q2":0.28e9,"Q3":0.38e9,"Q4":0.50e9,
           "Q5":0.62e9,"Q6":0.75e9,"Q8":1.00e9}
    
    # Find quantization in filename
    quant = next((k for k,v in bpb.items() if k in filename), "Q4")
    bytes = params * bpb.get(quant, 0.50e9)
    
    if bytes >= 1e9:
        return f"{bytes/1e9:.1f}GB"
    else:
        return f"{bytes/1e6:.0f}MB"

Usage

Basic Usage

# Fetch all models, cache in ./cache, output to models.txt
python3 fetch_models.py ./cache models.txt

Arguments

ArgumentDescriptionRequired
<cache_dir>Directory to cache fetched JSONYes
<model_list_out>Output file pathYes

Output Format

Pipe-delimited format for easy parsing:

# llama-menu model list — fetched from HuggingFace
# NAME|CATEGORY|REPO|FILE|SIZE|LIKES|DOWNLOADS|DATE
Llama-3-8B-Instruct|Instruct|meta-llama/Meta-Llama-3-8B-Instruct-GGUF|Q4_K_M.gguf|6.8GB|15000|85000|2026-07-15
Mistral-7B-Instruct|Instruct|TheBloke/Mistral-7B-Instruct-v0.2-GGUF|Q4_K_M.gguf|4.1GB|12000|75000|2026-07-10
Phi-3-mini-4k-instruct|Instruct|mradermacher/Phi-3-mini-4k-instruct-4k-GGUF|Q4_K_M.gguf|2.1GB|8000|45000|2026-07-20
CodeLlama-7B-Instruct|Coding|TheBloke/CodeLlama-7B-Instruct-GGUF|Q4_K_M.gguf|4.2GB|9000|50000|2026-07-05
DeepSeek-R1|Reasoning|TheBloke/DeepSeek-R1-GGUF|Q4_K_M.gguf|6.5GB|2500|12000|2026-07-25

Example: Filter Coding Models

# Get all coding models
grep "|Coding|" models.txt

# Output:
CodeLlama-7B-Instruct|Coding|TheBloke/CodeLlama-7B-Instruct-GGUF|Q4_K_M.gguf|4.2GB|9000|50000|2026-07-05
StarCoder2-7B|Coding|TheBloke/StarCoder2-7B-GGUF|Q4_K_M.gguf|4.8GB|7000|35000|2026-07-18
DeepSeek-Coder-V2|Coding|TheBloke/DeepSeek-Coder-V2-GGUF|Q4_K_M.gguf|6.7GB|5000|20000|2026-07-22

Example: Integration with Shell Scripts

#!/bin/bash

# Parse model list and create a menu
MODEL_FILE="models.txt"

# Skip header lines
tail -n +3 "$MODEL_FILE" | while IFS='|' read -r name category repo file size likes downloads date; do
    # Only show Instruct models
    if [ "$category" = "Instruct" ]; then
        # Format for display
        printf "%-30s %10s %s\n" "$name" "$size" "[$category]"
    fi
done

Technical Implementation

Fetching Strategy

def fetch_json(url, timeout=20):
    try:
        req = urllib.request.urlopen(url, timeout=timeout)
        return json.loads(req.read().decode())
    except Exception as e:
        print(f"  WARN {url[:60]}: {e}", flush=True)
        return None

# Main fetch loop
for uploader in UPLOADERS:
    print(f"\nFetching {uploader}...", flush=True)
    url = (f"https://huggingface.co/api/models"
           f"?author={uploader}&sort=likes&direction=-1&limit=500&full=true")
    data = fetch_json(url)
    if not data:
        continue
    # Process models...

Categorization Logic

def categorize(repo, filename):
    haystack = (repo + " " + filename).lower()
    
    # Stage 1: Try primary categories (first match wins)
    for cat, patterns in PRIMARY_RULES:
        for pat in patterns:
            if re.search(pat, haystack):
                return cat, False  # Matched primary, not auto
    
    # Stage 2: Auto-categorize by architecture
    for cat, patterns in AUTO_RULES:
        for pat in patterns:
            if re.search(pat, haystack):
                return cat, True  # Auto-categorized
    
    return "Auto/Other", True

Name Shortening

def short_name(repo):
    n = repo.split("/")[-1]  # Get model name from repo
    
    # Remove common suffixes
    for s in ["-GGUF", "-gguf", "-Instruct", "-instruct",
              "-it", "-IT", "-chat", "-Chat", "-hf", "-HF"]:
        n = n.replace(s, "")
    
    return n[:36]  # Truncate to 36 chars

Performance

Runtime

Caching

Memory Usage


Limitations & Future Work

Current Limitations

Future Enhancements


Source Code

Complete source available:

Download fetch_models.py

Or copy directly:

cp /opt/aurora/work/fetch_models.py ./fetch_models.py
chmod +x ./fetch_models.py

License

This tool is provided as-is for educational and production use. No warranty is provided. Use at your own risk.



In a world of model chaos, good categorization is the first step toward understanding.