import os import struct from collections import Counter import gradio as gr import requests from huggingface_hub import HfApi, hf_hub_url DEFAULT_REPO = "AdvancedDataIntelligence/adi-qwen3.5-4b-glm5.2-general-GGUF" DEFAULT_FILE = "adi-qwen3.5-4b-glm5.2-general-q4_k_m.gguf" MAX_RANGE_BYTES = 256 * 1024 * 1024 GGUF_TYPES = { 0: ("UINT8", "B", 1), 1: ("INT8", "b", 1), 2: ("UINT16", "H", 2), 3: ("INT16", "h", 2), 4: ("UINT32", "I", 4), 5: ("INT32", "i", 4), 6: ("FLOAT32", "f", 4), 7: ("BOOL", "?", 1), 8: ("STRING", None, None), 9: ("ARRAY", None, None), 10: ("UINT64", "Q", 8), 11: ("INT64", "q", 8), 12: ("FLOAT64", "d", 8), } TENSOR_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: "TQ1_0", 32: "TQ2_0", } class NeedMoreData(Exception): pass class Reader: def __init__(self, data): self.data = data self.pos = 0 def require(self, size): if self.pos + size > len(self.data): raise NeedMoreData def read(self, size): self.require(size) chunk = self.data[self.pos : self.pos + size] self.pos += size return chunk def unpack(self, fmt): size = struct.calcsize("<" + fmt) return struct.unpack("<" + fmt, self.read(size))[0] def string(self): length = self.unpack("Q") return self.read(length).decode("utf-8", errors="replace") def fetch_prefix(repo_id, filename, revision, size): token = os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACE_HUB_TOKEN") headers = {"Range": f"bytes=0-{size - 1}"} if token: headers["Authorization"] = f"Bearer {token}" url = hf_hub_url(repo_id=repo_id, filename=filename, revision=revision) response = requests.get(url, headers=headers, allow_redirects=True, timeout=60) response.raise_for_status() return response.content def file_size(repo_id, filename, revision): info = HfApi().model_info(repo_id=repo_id, revision=revision, files_metadata=True) for sibling in info.siblings: if sibling.rfilename == filename: return sibling.size return None def read_scalar(reader, value_type): type_name, fmt, size = GGUF_TYPES.get(value_type, (f"UNKNOWN_{value_type}", None, None)) if value_type == 8: return reader.string() if value_type == 9: item_type = reader.unpack("I") item_count = reader.unpack("Q") values = [] for _ in range(min(item_count, 64)): values.append(read_scalar(reader, item_type)) if item_count > 64: skip_value(reader, item_type, item_count - 64) values.append(f"... {item_count - 64} more") return values if fmt is None: raise ValueError(f"Unsupported GGUF metadata type {type_name}") return reader.unpack(fmt) def skip_value(reader, value_type, count=1): for _ in range(count): if value_type == 8: length = reader.unpack("Q") reader.read(length) elif value_type == 9: item_type = reader.unpack("I") item_count = reader.unpack("Q") skip_value(reader, item_type, item_count) else: _name, _fmt, size = GGUF_TYPES.get(value_type, (None, None, None)) if size is None: raise ValueError(f"Unsupported GGUF metadata type {value_type}") reader.read(size) def parse_gguf(data): reader = Reader(data) if reader.read(4) != b"GGUF": raise ValueError("File does not start with GGUF magic bytes.") version = reader.unpack("I") tensor_count = reader.unpack("Q") metadata_count = reader.unpack("Q") metadata = {} metadata_types = {} for _ in range(metadata_count): key = reader.string() value_type = reader.unpack("I") metadata[key] = read_scalar(reader, value_type) metadata_types[key] = GGUF_TYPES.get(value_type, (str(value_type), None, None))[0] tensor_types = Counter() tensor_names = [] tensor_shapes = [] for _ in range(tensor_count): name = reader.string() dims_count = reader.unpack("I") dims = [reader.unpack("Q") for _ in range(dims_count)] tensor_type_id = reader.unpack("I") offset = reader.unpack("Q") tensor_type = TENSOR_TYPES.get(tensor_type_id, f"TYPE_{tensor_type_id}") tensor_types[tensor_type] += 1 if len(tensor_names) < 80: tensor_names.append(name) tensor_shapes.append( { "name": name, "shape": " x ".join(str(d) for d in dims), "type": tensor_type, "offset": offset, } ) return { "version": version, "tensor_count": tensor_count, "metadata_count": metadata_count, "metadata": metadata, "metadata_types": metadata_types, "tensor_types": dict(tensor_types), "tensor_names": tensor_names, "tensor_shapes": tensor_shapes, "header_bytes_read": reader.pos, } def inspect_gguf(repo_id, filename, revision): repo_id = repo_id.strip() filename = filename.strip() revision = (revision or "main").strip() if not repo_id or not filename: raise gr.Error("Repo ID and GGUF filename are required.") last_error = None data = b"" for size in (2, 4, 8, 16, 32, 64, 128, 256): byte_count = size * 1024 * 1024 try: data = fetch_prefix(repo_id, filename, revision, byte_count) parsed = parse_gguf(data) break except NeedMoreData as exc: last_error = exc if byte_count >= MAX_RANGE_BYTES: raise gr.Error("GGUF header is larger than 256 MB; cannot inspect safely.") except requests.HTTPError as exc: raise gr.Error(f"Could not fetch GGUF prefix: HTTP {exc.response.status_code}") else: raise gr.Error(f"Could not parse GGUF metadata: {last_error}") size_bytes = file_size(repo_id, filename, revision) summary = summarize(repo_id, filename, revision, size_bytes, parsed) metadata_rows = metadata_table(parsed["metadata"], parsed["metadata_types"]) tensor_rows = tensor_table(parsed["tensor_shapes"]) return summary, metadata_rows, tensor_rows, parsed def pick(metadata, suffixes): for suffix in suffixes: for key, value in metadata.items(): if key.endswith(suffix): return value return None def summarize(repo_id, filename, revision, size_bytes, parsed): metadata = parsed["metadata"] arch = metadata.get("general.architecture", "unknown") model_name = metadata.get("general.name", filename) quant = metadata.get("general.file_type") quant_name = TENSOR_TYPES.get(quant, quant) if isinstance(quant, int) else quant ctx_train = pick(metadata, [".context_length"]) block_count = pick(metadata, [".block_count"]) embedding = pick(metadata, [".embedding_length"]) head_count = pick(metadata, [".attention.head_count"]) tokenizer = metadata.get("tokenizer.ggml.model", "unknown") tensor_mix = ", ".join( f"{name}: {count}" for name, count in sorted(parsed["tensor_types"].items()) ) warnings = compatibility_notes(arch, metadata, parsed["tensor_names"]) size_text = format_bytes(size_bytes) if size_bytes else "unknown" return f"""# {model_name} **Repo:** `{repo_id}` **File:** `{filename}` **Revision:** `{revision}` **Size:** `{size_text}` **GGUF version:** `{parsed["version"]}` **Architecture:** `{arch}` **File type:** `{quant_name}` **Tensor count:** `{parsed["tensor_count"]}` **Metadata entries:** `{parsed["metadata_count"]}` **Header bytes inspected:** `{format_bytes(parsed["header_bytes_read"])}` ## Model Shape - Training/context length: `{ctx_train}` - Blocks/layers: `{block_count}` - Embedding length: `{embedding}` - Attention heads: `{head_count}` - Tokenizer: `{tokenizer}` ## Tensor Types `{tensor_mix or "none"}` ## Compatibility Notes {warnings} """ def compatibility_notes(arch, metadata, tensor_names): lower_arch = str(arch).lower() keys = " ".join(metadata.keys()).lower() names = " ".join(tensor_names[:80]).lower() haystack = f"{lower_arch} {keys} {names}" notes = [] if "qwen3" in haystack and any(term in haystack for term in ("ssm", "mamba", "gated_delta", "gated-delta")): notes.append( "- Qwen3/Qwen3.5 hybrid SSM or gated-delta signals detected. Use a very recent llama.cpp build." ) elif "qwen3" in haystack: notes.append("- Qwen3-family metadata detected. Prefer recent llama.cpp/llama-cpp-python builds.") if any(term in haystack for term in ("ssm", "mamba", "gated_delta", "gated-delta")): notes.append("- SSM/Mamba-style metadata detected; older llama.cpp builds may fail at model load.") if "tokenizer.ggml.tokens" not in metadata: notes.append("- Token list is not present in the inspected metadata prefix.") if not notes: notes.append("- No obvious metadata-level compatibility red flags found.") return "\n".join(notes) def metadata_table(metadata, metadata_types): rows = [] for key in sorted(metadata): value = metadata[key] if isinstance(value, list): value_text = f"[{len(value)} items] " + repr(value[:8]) else: value_text = repr(value) if len(value_text) > 500: value_text = value_text[:497] + "..." rows.append([key, metadata_types.get(key, ""), value_text]) return rows def tensor_table(tensor_shapes): return [[item["name"], item["shape"], item["type"], item["offset"]] for item in tensor_shapes] def format_bytes(value): if value is None: return "unknown" value = float(value) for unit in ("B", "KB", "MB", "GB", "TB"): if value < 1024 or unit == "TB": return f"{value:.2f} {unit}" if unit != "B" else f"{int(value)} B" value /= 1024 with gr.Blocks(title="ADI GGUF Inspector", fill_width=True) as demo: gr.Markdown("# ADI GGUF Inspector") with gr.Row(): repo = gr.Textbox(label="Model repo", value=DEFAULT_REPO) filename = gr.Textbox(label="GGUF filename", value=DEFAULT_FILE) revision = gr.Textbox(label="Revision", value="main") inspect_btn = gr.Button("Inspect", variant="primary") summary = gr.Markdown() with gr.Tabs(): with gr.Tab("Metadata"): metadata = gr.Dataframe( headers=["Key", "Type", "Value"], datatype=["str", "str", "str"], wrap=True, interactive=False, ) with gr.Tab("Tensors"): tensors = gr.Dataframe( headers=["Name", "Shape", "Type", "Offset"], datatype=["str", "str", "str", "number"], wrap=True, interactive=False, ) with gr.Tab("Raw"): raw = gr.JSON() inspect_btn.click( inspect_gguf, inputs=[repo, filename, revision], outputs=[summary, metadata, tensors, raw], ) if __name__ == "__main__": demo.launch()