A reader left a note under my last post that I have not been able to put down. He said, in effect: pin the revision before you fetch the config and the model metadata, because both of the URLs in your example follow a mutable head independently, and a nonzero residual can describe a mixed observation rather than a missing term in the formula.
He is right, and it is not a theoretical race. The snippet he was reading asks two URLs:
https://huggingface.co/{repo}/raw/main/config.json
https://huggingface.co/api/models/{repo}
Both follow main. Two reads, two chances for the branch to move between them, and then a comparison between a number computed from one and a number computed from the other. The residual in that post was the gap between the parameter count a model card claims and the parameter count the checkpoint's own config implies. If the two reads landed on different commits, that gap could be describing a mix of two states instead of a property of one.
So this week I measured what it costs to stop doing that.
I did not fetch two URLs because I like two URLs. The API response cannot answer "how many parameters does this checkpoint store", because the config object inside it does not carry the fields that answer it.
Across seven repos, api/models/{repo} returned a config object with three keys — architectures, model_type, tokenizer_config — and four for Phi-3-mini, which adds auto_map. None of the seven carried vocab_size, num_hidden_layers or hidden_size. Those three live in config.json, and I could not reach them from the metadata call.
So the shape is forced: one call for what the API knows, one call for the file the API does not inline. The only open question is which revision the second call names.
Seven repos, four model families, all ungated, read in one sitting:
| repo | sha (first 12) | safetensors.total | main vs pinned |
|---|---|---|---|
| HuggingFaceTB/SmolLM2-135M-Instruct | 12fd25f77366 |
134,515,008 | same |
| HuggingFaceTB/SmolLM2-360M-Instruct | a10cc1512eab |
361,821,120 | same |
| HuggingFaceTB/SmolLM2-1.7B-Instruct | 31b70e2e869a |
1,711,376,384 | same |
| Qwen/Qwen2.5-0.5B-Instruct | 7ae557604adf |
494,032,768 | same |
| Qwen/Qwen2.5-1.5B-Instruct | 989aa7980e4c |
1,543,714,304 | same |
| microsoft/Phi-3-mini-4k-instruct | f39ac1d28e92 |
3,821,079,552 | same |
| allenai/OLMo-1B-hf | aee7752d9c08 |
1,176,764,416 | same |
"Same" is not a shrug in that column. Both paths answered 200, both returned the same config values (I compared vocabulary size, layer count, hidden size, tie flag, dtype and architecture string), both returned the same byte count — 861, 846, 908, 659, 660, 967 and 632 — and both carried the same ETag header. Seven out of seven.
That last part is what I did not expect, and it is exactly the trap. An equality that holds today, with nothing behind it that keeps it holding. The ETag earns you one true sentence: these two paths currently resolve to the same object. It does not promise they will at the moment your script runs tomorrow, because neither path is asking for a particular commit. Only the sha does that, for the plain reason that the sha is the request.
In the reply I sent that reader, I wrote that pinning the revision costs one extra request. Measured properly, that sentence is missing its condition, so let me put the condition back.
If your script already calls api/models/{repo} — mine did, because it needed safetensors.total — then the sha arrives in that very response, sitting next to the total. Pinning then costs zero extra requests: the same two calls, with one of them naming a sha instead of main.
The extra request only appears in the other case: a script whose only read is raw/main/config.json, because all it wants is the config. That one goes from one call to two.
So the honest version is: zero if you were already asking the API for anything, one if you were not. I wrote down the second half as if it were the whole answer. Not a large error, but it is the kind that survives because it sounds like a cost, and costs are the thing you are allowed to round.
Three steps, and no new dependency:
GET api/models/{repo} and keep sha — plus safetensors.total if your comparison needs it.GET raw/{sha}/config.json, and recompute what the checkpoint stores from the config you just pinned.
After that, if the comparison still fails, the difference is real: you can no longer be comparing two dates by accident.
The change is one string in a URL. What it buys is that every number in your table belongs to the same commit, and that someone who re-runs your script next month gets the same artifact you described — or an error, which is also an answer.
ETag s only say the paths agree now. The failure my reader described is plausible and cheap to prevent; I did not observe it.raw/{sha} differently from raw/main, and I do not know which way. main had moved at all since the post my reader was reading.
Seven repos is not a survey. It is seven repos, named above, so you can run the same seven and check me.