← all posts
// optimization · storage

Deduplicate the local model collection safely

Hard links, reflinks, manifests, and content-addressed storage can reclaim space without losing provenance.

Disk space is the one resource a workstation running Ollama, llama.cpp, and MLX side by side will not forgive you for wasting: pull the same base checkpoint through three tools and you end up with three copies under three names, on top of whatever Ollama's own blob churn already left behind, and none of those runtimes knows the other two exist. That's the real constraint, not "models are big." Nothing on the machine keeps score across runtimes. Six months of trying new quantizations later, the model directory is a place you're scared to open, and the fix most people reach for first is deleting whatever looks unused, which is exactly how you lose the one file everything else quietly depends on.

four tools, one checkpoint, nobody keeping score

Treat a model file as an artifact with a hash, not a folder with a friendly name. Before touching anything, hash every weights file under every runtime directory and group the exact matches. Fuzzy matching by filename or size will lie to you, because conversion tools rename freely and different quantizations happen to land on suspiciously close sizes.

sha256sum ~/.ollama/models/blobs/* ~/.cache/llama.cpp/models/* ~/.cache/mlx/models/* | sort | uniq -c -w 64

Once you have real duplicates, prefer a copy-on-write link over a plain hard link where the filesystem supports it: reflinks on APFS, Btrfs, or XFS with reflink=1. A reflink reads like an independent file but shares blocks until something writes to it. That "until something writes" clause is the whole game, and it earns a full accounting before you dedupe anything:

  • what actually reads this file: the runtime loader, a conversion script, a quantizer, a checksum tool
  • whether any of those write back to the same path, in place, ever
  • which directory holds the copy you'd point to if someone asked you to prove where the weights came from
  • what your backup or snapshot tool does with a folder full of reflinks, since some quietly expand them back into full copies

If you can't answer the second one with a flat no, don't link that file yet.

the runtime will not tell you it degraded

Here's the failure that actually costs people an afternoon: linking a file some tool later mutates in place, or deleting what looked like the redundant copy and turning out to be the only one with an intact tokenizer config attached. Local runtimes keep running no matter what you hand them. They offload layers to CPU, page memory, miss a cache, or fall back to a generic kernel instead of the one tuned for your GPU, and the answer still comes back looking fine. Fast enough. Coherent enough. Nothing in the terminal is shouting, and that quiet is the problem: a model that silently reloaded from a broken link keeps answering, just slower and occasionally wrong in ways a casual read won't catch. So after any dedup pass, read the startup log, check device placement, and watch memory and swap while a real prompt runs. Change one variable at a time here too, or you'll never know which decision cost you the extra latency you're now chasing.

write down what you deleted, and why

Once a set of files is confirmed byte-identical and confirmed read-only in practice, dedupe it and keep a manifest next to the result: source path, hash, link type, and every runtime that still expects to find something at the original path. Put the reasoning beside the outcome, because in six months you or someone else will find a suspicious symlink and need to know it was on purpose, not an accident waiting to be fixed. Note the condition that should void the manifest too, the way you'd flag a routine model update that changes how a tool opens files it already had: a new model family, an upgraded runtime, or a conversion tool that starts writing metadata back into weights it only used to read. Leave real headroom on the drive after reclaiming the space. The next model you try will be bigger than this one, not smaller, and free space is what absorbs that without a scramble.

The tradeoff I take on purpose: once a file is reflinked across four runtime directories, I've given up the option to casually edit, quantize, or patch any single copy in place without first breaking the link and paying for a real copy again. That's a fine price. I'd rather spend a few extra gigabytes back out on the rare day I need to mutate a checkpoint than spend an evening debugging a model that silently forked itself across a "duplicate" that never actually was one.

#storage#models#optimization