{"course":{"slug":"open-models","title":"The Open Model Ecosystem","url":"https://addaly.com/learn/open-models","level":"intermediate","track":"deep"},"lesson":{"slug":"hugging-face-as-infrastructure","title":"Hugging Face as Infrastructure","url":"https://addaly.com/learn/open-models/hugging-face-as-infrastructure","position":12,"of":71,"minutes":8,"answer":"Never load a pickle you did not create; prefer safetensors, and pin the commit rather than the branch.","keyIdea":"Never load a pickle you did not create; prefer safetensors, and pin the commit rather than the branch."},"body":{"format":"markdown","text":"Hugging Face is not a website with download buttons. It is a git host with large-file storage, an API, and a set of conventions. Once you see it that way, most of it becomes predictable.\n\n## A repo is a git repo\n\nEvery model is a git repository with git-lfs for the big files. It has branches, tags, and commit hashes. `main` moves. People push new quantizations, fix a broken chat template, or silently change a config.\n\nSo pin the revision:\n\n```bash\npip install -U \"huggingface_hub[cli]\"\nhf auth login\n\nhf download Qwen/Qwen3-8B \\\n  --revision 9a3f21c \\\n  --include \"*.safetensors\" \"config.json\" \"tokenizer*\" \\\n  --local-dir ./models/qwen3-8b\n```\n\nThe same in code: `from_pretrained(\"Qwen/Qwen3-8B\", revision=\"9a3f21c\")`. A pinned revision is the difference between a build you can rerun in six months and a build you cannot.\n\nThree environment variables worth knowing, especially on a shared or small machine:\n\n```bash\nexport HF_HOME=/data/hf                  # cache somewhere with room\nexport HF_HUB_ENABLE_HF_TRANSFER=1       # faster parallel downloads\nexport HF_ENDPOINT=https://hf-mirror.com # if the main host is slow or blocked where you are\n```\n\nAnd clean up, because the cache silently grows to hundreds of gigabytes:\n\n```bash\nhf cache scan\n```\n\n## The model card is a statement of intent\n\nThe README has YAML front matter (`license`, `base_model`, `language`, `pipeline_tag`, `tags`) and then prose. Read for six things: context length, the chat template, intended and out-of-scope use, what languages are claimed, what evaluations were run, and stated limitations.\n\nA card that does not state a context length or show a chat template tells you something about how carefully the release was made. That is useful information even before you load anything.\n\n## The one hard rule about file formats\n\n**`.safetensors`** is a JSON header describing tensor shapes and offsets, followed by raw bytes. There is no code in it. Loading it cannot execute anything. It also memory-maps, so it loads faster.\n\n**`.bin`, `.pt`, `.pth`, `.ckpt`** are usually Python pickle. Unpickling *runs code by design*: that is what the format does. A crafted file opens a shell on the machine that loads it. This is not theoretical; malicious models have been found on public hubs. Hugging Face scans uploads, which is a filter, not a proof.\n\n> Figure: Two classes of file in the same repository. Data, and safe to load: safetensors: a JSON header, then raw bytes; GGUF: weights, tokenizer and template in one file; Memory-maps, so it starts before the file is read; Nothing in the file can execute. Code, and it runs on load: bin, pt, pth, ckpt: usually Python pickle; Unpickling executes by design, not by bug; modeling_*.py, run by trust_remote_code; A failure at weights_only=True is the signal. Hub-side scanning is a filter, not a proof: it catches known-bad pickle patterns and cannot see a backdoor in the weights. If a repository has safetensors, use them. If it has only pickle, convert it yourself in a container with no credentials and no network.\n\n**`.gguf`** is llama.cpp's single-file format, carrying weights plus metadata plus the tokenizer. Data, not code. Parsers have had memory-safety bugs, so it is not magic, but it is in a different class from pickle.\n\nIn practice: if a repo has safetensors, use them. If it only has `.bin`, either find a converted mirror, convert it yourself inside a container with no network and no credentials, or skip the model. And keep PyTorch's safe default:\n\n```python\nimport torch\nsd = torch.load(\"pytorch_model.bin\", weights_only=True)  # refuses arbitrary code; the default since torch 2.6\n```\n\nIf loading fails with `weights_only=True`, that is a signal, not an inconvenience to switch off.\n\n## Sizes, so you can plan a download\n\nBefore you start a 140GB transfer on a metered connection, do the arithmetic. Roughly two bytes per parameter at bf16:\n\n| Model | bf16 files | Q8 GGUF | Q4_K_M GGUF |\n|---|---|---|---|\n| 4B | ~8 GB | ~4.3 GB | ~2.5 GB |\n| 8B | ~16 GB | ~8.5 GB | ~4.9 GB |\n| 27B | ~54 GB | ~29 GB | ~16 GB |\n| 70B | ~140 GB | ~75 GB | ~42 GB |\n\nFetch only what you need:\n\n```bash\nhf download bartowski/Qwen2.5-7B-Instruct-GGUF \\\n  --include \"*Q4_K_M.gguf\" --local-dir ./gguf\n```\n\n## The rest of the hub\n\nDatasets are the same git plus parquet arrangement. Spaces are hosted demos, which are useful for trying a model for thirty seconds before committing to a download. Inference providers route API calls to third parties serving those same open weights, which is how you test six models without downloading any of them, the subject of the last lesson."},"check":{"question":"A colleague argues: \"We pinned the exact commit hash and the repo passed Hugging Face's malware scan, so loading its pytorch_model.bin is fine.\" What is the flaw?","options":[{"text":"Pinning fixes which bytes you get, but a pickle still executes code when loaded, and a scanner is a filter rather than a guarantee.","correct":true},{"text":"There is no flaw: a pinned commit means the bytes cannot change, so the load is safe.","correct":false,"tempting":"Confuses integrity (the file is what it was) with safety (what the file does when opened)."},{"text":"The flaw is performance: .bin files load more slowly and use more memory than safetensors.","correct":false,"tempting":"A true property of the format, but it answers a security question with a speed argument."},{"text":"The flaw is precision: .bin checkpoints are stored in fp32, so the model will use twice the memory it should.","correct":false,"tempting":"Confuses the container format with the dtype inside it; either format can hold any precision."}]},"prev":{"slug":"the-families","title":"The Families, and What Each Is Actually For","url":"https://addaly.com/learn/open-models/the-families"},"next":{"slug":"beyond-the-big-six","title":"Regional and specialist models","url":"https://addaly.com/learn/open-models/beyond-the-big-six"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Hugging Face as Infrastructure', in The Open Model Ecosystem. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/open-models/hugging-face-as-infrastructure (Accessed: 31 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Hugging Face as Infrastructure","courseTitle":"The Open Model Ecosystem","module":{"index":2,"slug":"the-map-of-the-ecosystem","title":"The map, and how to read it"},"answer":null,"keyIdea":"Never load a pickle you did not create; prefer safetensors, and pin the commit rather than the branch.","hero":{"kind":"figure","figure":{"t":"figure","label":"Figure","figure":{"left":{"title":"Data, and safe to load","items":["safetensors: a JSON header, then raw bytes","GGUF: weights, tokenizer and template in one file","Memory-maps, so it starts before the file is read","Nothing in the file can execute"]},"type":"compare","right":{"title":"Code, and it runs on load","items":["bin, pt, pth, ckpt: usually Python pickle","Unpickling executes by design, not by bug","modeling_*.py, run by trust_remote_code","A failure at weights_only=True is the signal"]},"title":"Two classes of file in the same repository","caption":"Hub-side scanning is a filter, not a proof: it catches known-bad pickle patterns and cannot see a backdoor in the weights. If a repository has safetensors, use them. If it has only pickle, convert it yourself in a container with no credentials and no network."},"alt":"Figure: Two classes of file in the same repository. Data, and safe to load: safetensors: a JSON header, then raw bytes; GGUF: weights, tokenizer and template in one file; Memory-maps, so it starts before the file is read; Nothing in the file can execute. Code, and it runs on load: bin, pt, pth, ckpt: usually Python pickle; Unpickling executes by design, not by bug; modeling_*.py, run by trust_remote_code; A failure at weights_only=True is the signal. Hub-side scanning is a filter, not a proof: it catches known-bad pickle patterns and cannot see a backdoor in the weights. If a repository has safetensors, use them. If it has only pickle, convert it yourself in a container with no credentials and no network."}},"blocks":[{"t":"p","runs":[{"text":"Hugging Face is not a website with download buttons. It is a git host with large-file storage, an API, and a set of conventions. Once you see it that way, most of it becomes predictable."}],"lede":true},{"t":"h2","id":"a-repo-is-a-git-repo","runs":[{"text":"A repo is a git repo"}]},{"t":"p","runs":[{"text":"Every model is a git repository with git-lfs for the big files. It has branches, tags, and commit hashes. "},{"text":"main","code":true},{"text":" moves. People push new quantizations, fix a broken chat template, or silently change a config."}]},{"t":"p","runs":[{"text":"So pin the revision:"}]},{"t":"code","lang":"bash","label":"Bash","text":"pip install -U \"huggingface_hub[cli]\"\nhf auth login\n\nhf download Qwen/Qwen3-8B \\\n  --revision 9a3f21c \\\n  --include \"*.safetensors\" \"config.json\" \"tokenizer*\" \\\n  --local-dir ./models/qwen3-8b"},{"t":"p","runs":[{"text":"The same in code: "},{"text":"from_pretrained(\"Qwen/Qwen3-8B\", revision=\"9a3f21c\")","code":true},{"text":". A pinned revision is the difference between a build you can rerun in six months and a build you cannot."}]},{"t":"p","runs":[{"text":"Three environment variables worth knowing, especially on a shared or small machine:"}]},{"t":"code","lang":"bash","label":"Bash","text":"export HF_HOME=/data/hf                  # cache somewhere with room\nexport HF_HUB_ENABLE_HF_TRANSFER=1       # faster parallel downloads\nexport HF_ENDPOINT=https://hf-mirror.com # if the main host is slow or blocked where you are"},{"t":"p","runs":[{"text":"And clean up, because the cache silently grows to hundreds of gigabytes:"}]},{"t":"code","lang":"bash","label":"Bash","text":"hf cache scan"},{"t":"h2","id":"the-model-card-is-a-statement-of-intent","runs":[{"text":"The model card is a statement of intent"}]},{"t":"p","runs":[{"text":"The README has YAML front matter ("},{"text":"license","code":true},{"text":", "},{"text":"base_model","code":true},{"text":", "},{"text":"language","code":true},{"text":", "},{"text":"pipeline_tag","code":true},{"text":", "},{"text":"tags","code":true},{"text":") and then prose. Read for six things: context length, the chat template, intended and out-of-scope use, what languages are claimed, what evaluations were run, and stated limitations."}]},{"t":"p","runs":[{"text":"A card that does not state a context length or show a chat template tells you something about how carefully the release was made. That is useful information even before you load anything."}]},{"t":"h2","id":"the-one-hard-rule-about-file-formats","runs":[{"text":"The one hard rule about file formats"}]},{"t":"p","runs":[{"text":".safetensors","b":true,"code":true},{"text":" is a JSON header describing tensor shapes and offsets, followed by raw bytes. There is no code in it. Loading it cannot execute anything. It also memory-maps, so it loads faster."}]},{"t":"p","runs":[{"text":".bin","b":true,"code":true},{"text":", ","b":true},{"text":".pt","b":true,"code":true},{"text":", ","b":true},{"text":".pth","b":true,"code":true},{"text":", ","b":true},{"text":".ckpt","b":true,"code":true},{"text":" are usually Python pickle. Unpickling "},{"text":"runs code by design","i":true},{"text":": that is what the format does. A crafted file opens a shell on the machine that loads it. This is not theoretical; malicious models have been found on public hubs. Hugging Face scans uploads, which is a filter, not a proof."}]},{"t":"p","runs":[{"text":".gguf","b":true,"code":true},{"text":" is llama.cpp’s single-file format, carrying weights plus metadata plus the tokenizer. Data, not code. Parsers have had memory-safety bugs, so it is not magic, but it is in a different class from pickle."}]},{"t":"p","runs":[{"text":"In practice: if a repo has safetensors, use them. If it only has "},{"text":".bin","code":true},{"text":", either find a converted mirror, convert it yourself inside a container with no network and no credentials, or skip the model. And keep PyTorch’s safe default:"}]},{"t":"code","lang":"python","label":"Python","text":"import torch\nsd = torch.load(\"pytorch_model.bin\", weights_only=True)  # refuses arbitrary code; the default since torch 2.6"},{"t":"p","runs":[{"text":"If loading fails with "},{"text":"weights_only=True","code":true},{"text":", that is a signal, not an inconvenience to switch off."}]},{"t":"h2","id":"sizes-so-you-can-plan-a-download","runs":[{"text":"Sizes, so you can plan a download"}]},{"t":"p","runs":[{"text":"Before you start a 140GB transfer on a metered connection, do the arithmetic. Roughly two bytes per parameter at bf16:"}]},{"t":"table","head":[[{"text":"Model"}],[{"text":"bf16 files"}],[{"text":"Q8 GGUF"}],[{"text":"Q4_K_M GGUF"}]],"align":["left","left","left","left"],"rows":[[[{"text":"4B"}],[{"text":"~8 GB"}],[{"text":"~4.3 GB"}],[{"text":"~2.5 GB"}]],[[{"text":"8B"}],[{"text":"~16 GB"}],[{"text":"~8.5 GB"}],[{"text":"~4.9 GB"}]],[[{"text":"27B"}],[{"text":"~54 GB"}],[{"text":"~29 GB"}],[{"text":"~16 GB"}]],[[{"text":"70B"}],[{"text":"~140 GB"}],[{"text":"~75 GB"}],[{"text":"~42 GB"}]]]},{"t":"p","runs":[{"text":"Fetch only what you need:"}]},{"t":"code","lang":"bash","label":"Bash","text":"hf download bartowski/Qwen2.5-7B-Instruct-GGUF \\\n  --include \"*Q4_K_M.gguf\" --local-dir ./gguf"},{"t":"h2","id":"the-rest-of-the-hub","runs":[{"text":"The rest of the hub"}]},{"t":"p","runs":[{"text":"Datasets are the same git plus parquet arrangement. Spaces are hosted demos, which are useful for trying a model for thirty seconds before committing to a download. Inference providers route API calls to third parties serving those same open weights, which is how you test six models without downloading any of them, the subject of the last lesson."}]}],"check":{"question":[{"text":"A colleague argues: “We pinned the exact commit hash and the repo passed Hugging Face’s malware scan, so loading its pytorch_model.bin is fine.” What is the flaw?"}],"options":[{"text":[{"text":"Pinning fixes which bytes you get, but a pickle still executes code when loaded, and a scanner is a filter rather than a guarantee."}],"correct":true,"tempting":null},{"text":[{"text":"There is no flaw: a pinned commit means the bytes cannot change, so the load is safe."}],"correct":false,"tempting":[{"text":"Confuses integrity (the file is what it was) with safety (what the file does when opened)."}]},{"text":[{"text":"The flaw is performance: .bin files load more slowly and use more memory than safetensors."}],"correct":false,"tempting":[{"text":"A true property of the format, but it answers a security question with a speed argument."}]},{"text":[{"text":"The flaw is precision: .bin checkpoints are stored in fp32, so the model will use twice the memory it should."}],"correct":false,"tempting":[{"text":"Confuses the container format with the dtype inside it; either format can hold any precision."}]}],"order":[1,3,0,2],"why":null},"prev":{"slug":"the-families","title":"The Families, and What Each Is Actually For","minutes":9},"next":{"slug":"beyond-the-big-six","title":"Regional and specialist models","minutes":9}}}