Model distribution  / field guide
Practitioner field guide · 18 September 2026

Keep the format dumb: ten years of Hugging Face, measured from its own releases

A decade of decisions about distributing other people's model files, reconstructed from 242 published releases, thirteen shipped wheels, four rejected pull requests and the security advisories filed against the result. The reader leaves able to argue where a capability belongs, in the file format or in the loader, with dated evidence for both sides, and able to measure the same thing in their own dependencies before Monday.

20 graded sources 5 production systems 17 incident records Evidence through September 2026 Read: 31 min
01

The territory

An organisation whose product is other people's binary artefacts has to make those artefacts cheap to move, safe to open, and loadable by whatever runtime the user already has. None of those three problems is a machine learning problem.

113
TensorFlow and Flax model files deleted in one release, after fifteen months frozen at exactly that count
17.7%
Fall in shipped Python lines from 4.57.0 to 5.0.0, while model directories rose from 383 to 412
64 KiB
Target chunk for content-defined deduplication in the Hub's transfer client, fixed in source
6 weeks
From the Rust transfer client shipping as an opt-in extra to being a default dependency

Hugging Face is usually described as a model hub, which tells you what it sells and nothing about what it had to build. Read the company through its published artefacts instead, and a much narrower engineering problem appears, one that was set in 2018 and has not changed since: somebody uploads a multi-gigabyte binary file, somebody else downloads it and opens it inside their own process. Every architectural decision in the ten years that follow is an answer to some part of that sentence. What the file contains. How many times it crosses the network. What happens in your process when it opens.

This guide takes the decade as a single technical argument about one question: when a new capability arrives, whether it is quantization, a faster disk path, or the ability to define a model nobody has implemented yet, does it go into the file format that every reader must parse, or into the loader that only you control? Hugging Face answered that question the same way for ten years, and the one time it answered differently is where every incident in this guide comes from.

The finding that surprised me

The v5 release in January 2026 deleted two entire backends, 113 model files of TensorFlow and Flax code, and shrank the library by 17.7% while adding 29 model directories. It did not touch trust_remote_code, the switch that lets a downloaded repository execute its own Python in your process: occurrences of that identifier went from 231 in 4.57.0 to 230 in 5.17.0. The cleanup removed the two things that were merely expensive and kept the one thing that is dangerous, because the dangerous one is the feature that lets the Hub carry models the library has never heard of. Every transformers advisory published in 2026 is an attack on that switch or on something downstream that hardcoded it.

Five systems solve versions of this problem in production and publish enough to be read. Hugging Face invented a new interchange format and a new transfer protocol. PyTorch kept its format and constrained its reader. Keras kept its format and gated the dangerous construct at load time. The ggml project put everything in the file, quantization included. And the serving layer, the vLLMs and lmdeploys that consume all of this, mostly inherited whatever policy the library above them defaulted to, which turns out to matter a great deal.

How this page was built, and what it cannot show

Every engineering blog, video, paper and vendor host this research touched was blocked by the session's network egress policy, including huggingface.co itself. Six hosts answered: GitHub, its raw content service, PyPI, the Python file host, crates.io and the npm registry. So this guide contains no blog posts, no conference talks and no papers, and the narrative between artefacts is reconstruction rather than reporting. In exchange it contains measurements nobody has published, taken directly from thirteen shipped wheels. Where a claim is mine rather than someone's, it says so. The scope is the artefact path from 2016 to 2026: format, transfer, loader and execution policy. It excludes training, the Hub's server side, GPU economics, inference pricing and every number that exists only on the website.

Figure 1 · Four answers to one question

in the file

in the loader

in the reader

at the gate

Producer trains
and exports weights

Where does the
capability live?

GGUF
single file, quant
types in the spec

safetensors
dumb file,
clever reader

torch.load
same pickle file,
constrained unpickler

Keras v3
same archive,
lambda denied

Consumer process

in the file

in the loader

in the reader

at the gate

Producer trains
and exports weights

Where does the
capability live?

GGUF
single file, quant
types in the spec

safetensors
dumb file,
clever reader

torch.load
same pickle file,
constrained unpickler

Keras v3
same archive,
lambda denied

Consumer process

The landscape is not a set of competing formats, it is a set of different answers to where capability should live. Reconstructed from the safetensors README, PyTorch 2.6.0 release notes, Keras saving API and the GGUF specification.
Diagram source
02

How it is actually built

Six components recur across all five systems. Only two of them are the format, which is where almost all of the public argument happens.

Strip the five systems down to what each one actually does between a trained model and a running process, and the same six stages appear: an export step that writes tensors and a description of how to rebuild the graph; an interchange format; a transfer layer that moves bytes across the network; a local cache; a loader that turns bytes into device memory; and an execution policy that decides how much of the downloaded artefact is allowed to be code. The systems differ at exactly two of those stages, the format and the policy, and they differ in ways that are stable enough to predict.

The format Hugging Face shipped in September 2022 is deliberately close to trivial. Its README specifies it in eleven lines: eight bytes holding a little-endian unsigned 64-bit header length, then a JSON header naming each tensor with its dtype, shape and byte offsets, then a packed buffer. The interesting parts are the prohibitions. The header is capped at 100 MB, and the specification states that "the byte buffer needs to be entirely indexed, and cannot contain holes", which the README explains "prevents the creation of polyglot files". There is no compression, no code, no extension mechanism and no versioning scheme worth the name. The comparison table in the same README rejects seven alternatives with one reason each, and the reason given for rejecting HDF5 is instructive: it is not a security argument at all but a maintenance one, "210k lines of code vs ~400 lines for this lib currently".

The transfer layer arrived two and a half years later and is the opposite in character: small interface, substantial machinery. The xet-core repository describes "chunk-based deduplication, efficient storage/retrieval with local disk caching, and backwards compatibility with Git LFS", and the constants file fixes the geometry that matters. Chunking is content-defined using a gear hash, with a target chunk of 64 KiB, a floor of one eighth of that and a ceiling of twice it, so chunk boundaries survive an insertion in the middle of a file. Chunks are packed into blocks the code calls xorbs, capped at 64 MiB and 8,192 chunks. The release notes for the client that ships it put the design claim plainly: "Unlike LFS, which deduplicates files, Xet operates at the chunk level."

That distinction is the whole economic argument, and it is worth being precise about who benefits. File-level deduplication does nothing when a fine-tune rewrites 1% of a checkpoint, because the file hash changes and the whole artefact is new. Chunk-level deduplication with content-defined boundaries charges you only for the chunks that actually changed. The population this helps is the population the Hub has: thousands of derivative checkpoints that differ from their parent by a small fraction of their bytes. Neither the README nor the release notes publish an achieved deduplication ratio, and no figure for it exists in this corpus, so treat any specific ratio you have read elsewhere as uncorroborated here.

The loader is where the decade's complexity actually accumulated, and it is the component the public conversation ignores. It is also where the library's most contested design rule lives. The philosophy document states the rule as a tenet: "One Model, One File. Core inference and training logic is visible top-to-bottom in the model file users read", with the qualifier "DRY* (Repeat when it helps users)". That policy has a measurable cost, and in section five I measure it: markers reading # Copied from transformers. peaked at 3,395 across the library. What is interesting is not the cost but the repair. Rather than repealing the policy, they built a compiler for it. The modular-transformers document describes the mechanism in two sentences: "A converter generates standalone files from the modular file. Users get the same single-file interface they already know." Contributors write a small shard that inherits; a generator expands it into the flat file the user reads and debugs.

Figure 2 · The reference architecture, with the policy gate marked

Load

Move

Publish

deny by default

trust granted

Export
tensors plus config

Interchange format
header, offsets, buffer

Gear-hash chunker
64 KiB target

Content-addressed blocks
64 MiB, 8192 chunks

Local chunk cache

Loader
mmap, lazy, placement

Execution
policy

Runtime

Repository Python
runs in your process

Load

Move

Publish

deny by default

trust granted

Export
tensors plus config

Interchange format
header, offsets, buffer

Gear-hash chunker
64 KiB target

Content-addressed blocks
64 MiB, 8192 chunks

Local chunk cache

Loader
mmap, lazy, placement

Execution
policy

Runtime

Repository Python
runs in your process

Five of the six stages are uncontroversial. The one that decides whether your process runs a stranger's Python is one branch in the loader. Reconstructed from the xet-core README, its constants and the LightGlue advisory.
Diagram source

Interchange format

A length prefix, a JSON header of offsets, a packed buffer, and a set of refusals. No compression, no code, no strides, no holes. Roughly 400 lines of Rust by the README's own count, against 210,000 for the HDF5 alternative it rejected.

Runs this way at: Hugging Face, and deliberately not at ggml.

Content-addressed transfer

Content-defined chunking so a small edit to a large checkpoint moves only the changed chunks, packed into blocks for storage efficiency. The client is Rust, called from Python, and ships on every supported CPU architecture whether or not the user asked.

Runs this way at: Hugging Face. Contrast with plain Git LFS.

Execution policy

The single branch that decides whether the artefact is data or a program. Hugging Face denies by default and allows opt-in; PyTorch constrains its unpickler; Keras denies lambda deserialization unless safe_mode=False; GGUF has no code path to gate.

Compare: PyTorch 2.6, Keras.

The divergence at the policy stage is sharper than the divergence at the format stage, and it is where the three organisations argue with each other in public. PyTorch's 2.6.0 release in January 2025 flipped torch.load to weights_only=True, a breaking change its notes justify in one line, "the increased security by default is a tradeoff that is worth it", and then immediately concede the limit of the repair: "we still recommend only loading trusted checkpoints and rely on more constrained (and even safer) formats like safetensors for un-trusted checkpoints". That is the maintainers of the format safetensors was built to replace, in their own release notes, recommending safetensors for the case that motivated it. Keras takes the third road, documenting the gate rather than the format: "safe_mode: Boolean, whether to disallow unsafe lambda deserialization ... Defaults to True". Keras also, in the same file, reaches into the Hub directly, loading from an hf:// prefix through huggingface_hub.snapshot_download, which is a quiet statement about who won the distribution layer even among frameworks that did not adopt the format.

03

The decisions that matter

Five forks, each with the artefact that records it, and the condition that would make the rejected option correct.

The useful thing about reading a company through pull requests is that the rejections are preserved with their reasons attached. Two closed pull requests on the safetensors repository carry more architectural information than any amount of documentation, because in both cases a capability was offered, was genuinely useful, and was refused on a stated principle.

Decision: should the interchange format understand quantization?

Chosen
  • It should not. Quantized weights are stored as opaque tensors and the meaning lives elsewhere
  • The pull request that would have added Q4 support was opened by the format's own author in March 2023 and closed unmerged in December 2023
Rejected
  • Native Q4_0 and Q4_1 support, following ggml's encoding
  • Stated reason: no convergence. Competing schemes pack differently, and the author wrote "I will only merge this after being showcased in a real model example"
Flips when
  • One encoding wins in the wild, and you can version the format without stranding old readers
  • The counter-experiment already ran: GGUF put quantization types in its spec and the header file now carries "GGML_TYPE_Q4_2 = 4, support has been removed"

Decision: where does a faster disk path belong?

Chosen
  • In a loading backend, invisible to users and to the file on disk
  • A maintainer closed the GPUDirect Storage pull request in April 2026: "we're in the midst of building an optimised loading backend for safetensors which will eventually support GDS directly"
Rejected
  • A user-facing GDS path in the format library, proposed November 2025
  • Reviewer objection on the merits: the implementation "still goes through CPU, it's negating the entire benefit", and on principle, "GDS should be an implementation detail, not something users should care about"
Flips when
  • The optimisation cannot be hidden, because it changes the byte layout readers must parse rather than the order in which they read it

Decision: repeal the single-file policy, or build a compiler for it?

Chosen
  • Keep the policy, generate the files. Contributors write a small inheriting shard; a converter expands it into the flat file users read
  • Generated modular files went from 5 in October 2024 to 294 by September 2026, measured from the wheels
Rejected
  • Shared base classes across models, the ordinary answer to 3,395 copy markers
  • Stated reason is in the tenets: "Code is the Product. Optimize for reading and diff-ing", and "Standardize, Don't Abstract"
Flips when
  • Your readers are maintainers rather than modifiers. The policy buys debuggability for people who fork one file; it costs you every cross-cutting change

Decision: keep four backends, or one?

Chosen
  • One. The philosophy document now opens "Transformers is a PyTorch-first library"
  • v5.0.0 shipped 26 January 2026 and removed all 73 TensorFlow and 40 Flax model files
Rejected
  • Maintaining parity across frameworks, which had been the library's distinguishing feature since 2019
  • The decision was visible in the artefacts long before it was announced: those counts sat at exactly 73 and 40 from October 2024 to October 2025
Flips when
  • A second runtime has users you cannot reach any other way. Note what did not happen: the browser runtime was kept, as a separate npm package, because it reaches a platform Python cannot

Decision: can the serving product carry a restrictive licence?

Chosen
  • Apache 2.0, after a nine-month experiment with something else
  • Commits on one file tell the whole story: "Create LICENSE" October 2022, "chore: update license to HFOIL" 28 July 2023, "Revert license to Apache 2.0" 8 April 2024
Rejected
  • The Hugging Face Optimized Inference License, which restricted commercial hosted use
  • No public rationale is attached to the reverting commit in this corpus. The reversion itself is the evidence
Flips when
  • Your component is an endpoint rather than a dependency. A serving layer that others embed is worth more adopted than protected, and the licence is load-bearing only while the component is embedded
DecisionChosenRejectedBecauseEvidence
Quantization in the formatNoNative Q4 supportNo converged encoding to standardise onPR 197, closed 2023-12-24
Direct GPU storage pathIn a loading backendIn the format libraryTransport is an implementation detailPR 676, closed 2026-04-13
Duplication across modelsGenerate flat filesShared base classesReadability of the file users debugModular transformers doc
Framework backendsPyTorch onlyTensorFlow and Flax parityFiles frozen for fifteen months before deletionv5.0.0, 2026-01-26
Serving licenceApache 2.0HFOIL restrictionReverted after nine monthsLICENSE history
Transfer client rolloutDefault on by architectureOpt-in extraAdoption gated on CPU support, not consentDependency metadata, 2025-05

Figure 3 · Where should the capability go?

no

yes

yes

no

Does it change the bytes
every reader must parse?

Put it in the loader.
Ship it today

Has the ecosystem settled
on one encoding?

Put it in the format
and version the reader

Keep it out. Ship it
in the loader behind a flag

Check: can a reader of
last year's files still read them?

no

yes

yes

no

Does it change the bytes
every reader must parse?

Put it in the loader.
Ship it today

Has the ecosystem settled
on one encoding?

Put it in the format
and version the reader

Keep it out. Ship it
in the loader behind a flag

Check: can a reader of
last year's files still read them?

The rule these five systems converge on, stated as a tree. Terminal nodes are actions, and the middle branch is the one that has cost GGUF two removed quantization types. Derived by the author from the decisions above.
Diagram source
04

What broke in production

Three advisories and fourteen withdrawn releases, in two classes. One class is about who is allowed to execute code in your process. The other is about releases that should not have shipped.

There is no incident narrative to quote here, and the reason is worth stating plainly: the organisations in this guide do not publish postmortems to a host this research could reach. What exists instead are two artefacts that behave like very short postmortems. A security advisory records the false assumption, the mechanism and the structural fix, though never the blast radius. A PyPI yank notice records a maintainer withdrawing a shipped release and saying why, with a date. Both are graded as postmortems in the evidence wall, and the limits of that grading are stated there.

Advisory

The artefact supplied its own permission

AssumptionPassing trust_remote_code=False means no downloaded code runs.
What happenedOn the LightGlue loading path, "the trust_remote_code value from the untrusted config.json file" was propagated into downstream configuration loading, so the file being validated decided whether it should be trusted.
Blast radiusAll versions below 5.5.0. The advisory names inference servers, notebooks, CI pipelines and evaluation workers as the exposed environments. No duration or count is published.
FixPatched in 5.5.0, by not letting the loaded configuration override the caller's setting.
Design ruleA permission that travels inside the object it governs is not a permission. Resolve trust from the caller's context before the artefact is parsed, and never re-read it from the parsed result.
Advisory

The layer above deleted the user's refusal

AssumptionA safe default in the library protects everyone who depends on the library.
What happenedThe advisory states that "LMDeploy unilaterally passes trust_remote_code=True to transformers.AutoConfig.from_pretrained()", so "a malicious HF repo with a configuration_*.py shim runs Python code as the LMDeploy user at the very first call".
Blast radiusEvery version up to and including 0.12.3. The advisory is explicit that this is not a supply-chain compromise: the user chose the repository, and only lost the ability to refuse its code.
FixA CLI flag defaulting to false, restoring the choice the library had offered since 4.30.
Design ruleA safe default is a property of a call site, not of a library. If you ship a dangerous capability behind a parameter, audit who is passing it, because your consumers will hardcode it to make their quickstart work.
Advisory

The class did not go away, it moved

AssumptionReplacing pickle with a format that cannot express code removes arbitrary execution from model loading.
What happenedOne page of the advisory database for this query in 2026 lists code execution or trust bypass during model loading in transformers, diffusers, sentence-transformers, xinference, lmdeploy and vLLM. The weights stopped being executable; the configuration, the chat template and the initialisation path did not.
Blast radiusEcosystem-wide and current. A transformers path traversal through chat template names was published on 2 August 2026, three and a half years after safetensors shipped.
FixPer-advisory patches. No structural fix exists, because the capability is the product feature.
Design ruleHardening the biggest file in the artefact moves the attacker to the smallest one. Threat-model the whole directory, including the JSON nobody thinks of as code.
Yank

The release channel is the incident channel

AssumptionReleases are the safe, boring part of the system.
What happenedSix transformers releases, seven hub releases and one transfer-client release have been withdrawn. The reasons are process failures, not algorithmic ones: 5.10.0 in June 2026 was withdrawn because "we pushed from a week old main branch", 4.57.0 for "error in the setup causing installation issues", hub 0.26.4 because it "contains duplicate code ... that was accidentally merged".
Blast radiusUnbounded downward. A yank hides a release from resolvers but does not uninstall it, and hf-xet 1.2.1 in November 2025 "contains a regression that enables the disk cache by default" in a component that by then installed automatically on every mainstream CPU architecture.
FixA follow-up release in every case. No change to the release process is visible in this corpus.
Design ruleWhen you promote a component from opt-in to default, you inherit its release process as a production dependency. Ask who reviews that component's releases before you make it unavoidable.

Figure 4 · How a refusal became an acceptance

Python importModel repositoryfrom_pretrainedCallerPython importModel repositoryfrom_pretrainedCallerflag from the file is propagatedinto the sub-config loadload(repo,trust_remote_code=False)fetch config.jsonconfig carrying its own trust flagimport module from the repositoryrepository code runs as the caller
Python importModel repositoryfrom_pretrainedCallerPython importModel repositoryfrom_pretrainedCallerflag from the file is propagatedinto the sub-config loadload(repo,trust_remote_code=False)fetch config.jsonconfig carrying its own trust flagimport module from the repositoryrepository code runs as the caller
The LightGlue path, drawn from the advisory text. Notice that nothing here is a parsing bug: every step works as designed, and the defect is that a value crosses from the untrusted side to the trusted one. Source: GHSA-fgcw-684q-jj6r.
Diagram source
05

Numbers you can plan against

The first table is measured from thirteen published wheels for this guide. It is the cost of the single-file policy, and then its repair, in one view.

ReleaseDateModel dirsPyTorch filesTF filesFlax filesModular filesCopy markersPython lines
2.0.02019-09-26flatflatflatflat0023,453
4.0.02020-11-304440283084134,080
4.15.02021-12-22978343250711351,525
4.30.02023-06-08207193693502,539727,865
4.46.02024-10-24273280734053,3951,002,911
4.52.02025-05-203223297340683,0351,034,009
4.57.02025-10-0338339073401322,9141,165,404
5.0.02026-01-26412396001761,670958,925
5.17.02026-09-09516501002941,1391,130,638

Four things in that table are worth carrying into a design review. The first is the shape of the growth: 44 model directories in November 2020 and 516 in September 2026, which is a library whose unit of work is a model and whose cost per model is almost entirely fixed by policy rather than by the model. The second is the freeze. TensorFlow files sat at exactly 73 and Flax files at exactly 40 for every release measured between October 2024 and October 2025, which means the decision to stop investing preceded the decision to delete by at least fifteen months and was legible in the artefacts the whole time. The third is the repair working: copy markers fell from 3,395 to 1,139 while generated modular files rose from 5 to 294. The fourth is the one that would not fit any narrative I expected: between 4.57.0 and 5.0.0 the library lost 206,479 lines of Python, 17.7% of itself, and gained 29 model directories in the same step.

MetricValueAtContextAs ofSource
Target chunk size64 KiBHugging FaceGear-hash content-defined chunking; floor 8 KiB, ceiling 128 KiB2026-09constants.rs
Storage block cap64 MiBHugging FaceAlso capped at 8,192 chunks per block2026-09constants.rs
Header size cap100 MBHugging FaceAnti-denial-of-service limit written into the format spec2026-09safetensors README
BLOOM load on 8 GPUs10 min to 45 sHugging FaceAuthor's own figure for lazy loading; conditions not stated2026-09safetensors README
CPU load speedup76.6×Hugging Facegpt2 on one Xeon; the same doc says it "is actually possible to do on pure pytorch"2026-09safetensors docs
Format implementation size~400 linesHugging FaceSelf-reported, against 210,000 for the rejected HDF5 option2026-09safetensors README
safetensors crate downloads25,903,163crates.ioAll-time, Rust only; crate created 2022-12-132026-09-18crates.io
tokenizers crate downloads32,865,800crates.ioAll-time; crate created 2019-08-082026-09-18crates.io
Withdrawn releases14Hugging FaceSix transformers, seven huggingface-hub, one hf-xet2026-09-18PyPI
Opt-in to default40 daysHugging Facehf-xet extra on 2025-03-27; unconditional dependency on 2025-05-062025-05PyPI metadata

Figure 5 · The decade, as its artefacts date it

2016 · PyPI name held
by an unrelated project

2018 · First release:
one model, one package

2019 · Two renames in ten weeks;
Rust arrives with tokenizers

2020 · v4 introduces the
per-model directory layout

2022 · safetensors 0.0.1:
the format refuses code

2023 · Serving licence restricted
in July, reverted April 2024

2024 · Library passes
one million lines

2025 · Transfer client goes
default in forty days

2026 · v5 deletes
113 backend files

2016 · PyPI name held
by an unrelated project

2018 · First release:
one model, one package

2019 · Two renames in ten weeks;
Rust arrives with tokenizers

2020 · v4 introduces the
per-model directory layout

2022 · safetensors 0.0.1:
the format refuses code

2023 · Serving licence restricted
in July, reverted April 2024

2024 · Library passes
one million lines

2025 · Transfer client goes
default in forty days

2026 · v5 deletes
113 backend files

Every date here comes from a registry upload time or a commit, not from an announcement, which is why the sequence reads differently from the usual telling: the transfer layer is the newest component, and the framework consolidation is the last. Sources: PyPI release histories and commit dates.
Diagram source
Read these carefully

Measured for this guide: every row in the first table, plus the 40-day and 14-release figures, computed from published artefacts using the script in the next section. They are reproducible and refutable. Claimed by the author of the component: the 76.6× speedup and the BLOOM figure, both from the safetensors documentation, both without an independent measurement anywhere in this corpus, and the first one carrying its own caveat. Derived: 17.7% is 206,479 divided by 1,165,404. Unknown: the numbers an architect would most want. There is no published figure here for achieved deduplication ratio, bytes stored, egress saved, or the cost of any of it, because the only host that publishes those was unreachable. If your decision turns on the dedup ratio, you must measure it on your own corpus.

06

The evidence wall

Every source behind this page, graded. Two grading calls are stated in the ledger: security advisories and yank notices are graded as postmortems, because each records a false assumption, a mechanism and a fix, and neither records a blast radius.

Postmortem GitHub Advisory Database2026-06

GHSA-fgcw-684q-jj6r, arbitrary code execution during model initialization

The clearest single artefact in this guide. An untrusted config.json supplied the trust_remote_code value that was then propagated into downstream config loading, so an explicit refusal by the caller was overridden by the file being loaded.

Carry forwardTrust must be resolved from the caller before the artefact is parsed, never re-read from it.
github.com/advisories/GHSA-fgcw-684q-jj6r
Postmortem GitHub Advisory Database2026-05

GHSA-9xq9-36w5-q796, hardcoded trust_remote_code in a serving layer

A downstream inference server passed trust_remote_code=True unconditionally, removing the user's ability to refuse. The advisory is careful to say this is not a supply chain attack: the user picked the repository, and lost only the right to say no to it.

Carry forwardAudit who calls your dangerous parameter; a safe default protects nobody whose framework overrides it.
github.com/advisories/GHSA-9xq9-36w5-q796
Postmortem GitHub Advisory Database2026-09

Advisory listing for model-loading code execution

One query returns 2026 advisories for transformers, diffusers, sentence-transformers, xinference, lmdeploy and vLLM, all variations on executing code while loading a model. The class outlived the format change that was supposed to end it.

Carry forwardSecuring the weights file relocates the attack to the config, the template and the init path.
github.com/advisories?query=transformers
Postmortem Hugging Face on PyPI2025-11

hf-xet 1.2.1 withdrawn: default-on disk cache

The yank reason is one sentence: "This release contains a regression that enables the disk cache by default." The component had become a default dependency for most architectures seven months earlier, so the regression reached users who never chose it.

Carry forwardPromoting a component to default makes its release process part of your blast radius.
pypi.org/project/hf-xet
Postmortem Hugging Face on PyPI2026-06

transformers yank notices, 2021 to 2026

Six withdrawals, each with a reason written by a maintainer. The most recent: "We pushed from a week old main branch ... mostly it is missing a bunch of fixes!" Every reason is a release-process failure rather than a defect in the library's logic.

Carry forwardA project's yank log is the cheapest available audit of its release discipline. Read it before you pin.
pypi.org/project/transformers
Decision record Hugging Face2023-12

safetensors PR 197, Q4 quantization support, closed unmerged

The format's own author opened it, marked it a draft, wrote "I will only merge this after being showcased in a real model example", and let it expire nine months later. The reason given in the thread is that competing quantization schemes pack differently.

Carry forwardDo not standardise an encoding the ecosystem has not converged on; the format outlives the fashion.
github.com/huggingface/safetensors/pull/197
Decision record Hugging Face2026-04

safetensors PR 676, GPUDirect Storage, closed unmerged

Five months of review ending in a one-paragraph refusal: the work belongs in "an optimised loading backend for safetensors which will eventually support GDS directly". A reviewer adds the principle: "GDS should be an implementation detail".

Carry forwardTransport optimisations belong behind the interface, not in the artefact everyone parses.
github.com/huggingface/safetensors/pull/676
Decision record Hugging Face2026-09

Library philosophy and core tenets

A written, enforced review rule: "One Model, One File", "Code is the Product", "Standardize, Don't Abstract", and the deliberately asterisked "DRY* (Repeat when it helps users)". The first line now reads "Transformers is a PyTorch-first library".

Carry forwardIf duplication is your policy, write it down as one, with the condition attached, or it reads as decay.
github.com/huggingface/transformers philosophy.md
Decision record Hugging Face2026-09

Modular transformers: a compiler for the single-file policy

Contributors write an inheriting shard; a converter generates the flat file. "Maintainers review the shard; users hack the expanded file." The document is careful that this does not replace hand-written model files.

Carry forwardWhen a policy's cost becomes unbearable, generating the output is often cheaper than repealing the policy.
github.com/huggingface/transformers modular_transformers.md
Decision record Hugging Face2024-04

text-generation-inference LICENSE history

Three commits on one file: Apache in October 2022, "chore: update license to HFOIL" in July 2023, "Revert license to Apache 2.0" in April 2024. The nine-month experiment and its reversal are legible without any announcement.

Carry forwardA licence file's git history is a decision record nobody thinks to redact.
github.com/huggingface/text-generation-inference LICENSE history
Decision record PyTorch2025-01

PyTorch 2.6.0: weights_only defaults to True

The incumbent format repaired rather than replaced, with the trade-off stated: "the increased security by default is a tradeoff that is worth it". The notes then recommend "more constrained (and even safer) formats like safetensors for un-trusted checkpoints".

Carry forwardConstraining the reader is the cheap fix, and its own authors will tell you where it stops working.
github.com/pytorch/pytorch releases v2.6.0
Decision record ggml2026-09

The GGUF specification

The opposite bet, stated as design goals: "Single-file deployment", "Extensible", "mmap compatibility", "no need for external libraries". Quantization types live in the spec, and the enum carries the receipt: "GGML_TYPE_Q4_2 = 4, support has been removed".

Carry forwardPutting capability in the format works, and the price is removals you cannot take back quietly.
github.com/ggml-org/ggml docs/gguf.md
Source Hugging Face2026-09

safetensors format specification and rationale

Eleven lines of specification, a comparison table rejecting seven alternatives with a reason each, and the prohibitions that matter: a 100 MB header cap, and a buffer that "cannot contain holes", which "prevents the creation of polyglot files".

Carry forwardA format's security properties are mostly the things it refuses to be able to express.
github.com/huggingface/safetensors
Source Hugging Face2026-09

The attacks directory

Working exploits for pickle, PaddlePaddle and Keras H5 shipped inside the repository, plus a record of the team red-teaming its own format. One line is struck through in place: "Proposal 4: The offsets could overlap. ~~This is actually OK.~~ This is NOT ok."

Carry forwardShip the attack alongside the format. It is the only way a reviewer can check the claim.
github.com/huggingface/safetensors attacks/README.md
Source Hugging Face2026-09

xet-core chunking constants

The deduplication geometry, fixed in source: 64 KiB target chunk, a floor of one eighth and a ceiling of twice that, blocks capped at 64 MiB and 8,192 chunks, with gear-hash content-defined boundaries so an insertion does not reshuffle every chunk after it.

Carry forwardChunk size is the whole trade-off: smaller finds more duplicates and costs more index.
github.com/huggingface/xet-core constants.rs
Source Hugging Face2025-03

huggingface_hub v0.30.0 release notes

Introduces the transfer layer with its design claim in one sentence: "Unlike LFS, which deduplicates files, Xet operates at the chunk level." At this point it is an opt-in extra behind a waitlist.

Carry forwardWatch the gap between a capability's announcement and its promotion to default; here it was forty days.
github.com/huggingface/huggingface_hub releases v0.30.0
Source Keras2026-09

Keras load_model and its safe_mode gate

The third strategy, documented in the signature: "safe_mode: Boolean, whether to disallow unsafe lambda deserialization ... Defaults to True". The same module loads from an hf:// prefix by calling snapshot_download.

Carry forwardIf you cannot change the format, name the one dangerous construct and deny it by default.
github.com/keras-team/keras saving_api.py
Source PyPI and npm2026-09

The name lineage: three packages, two renames, one squatted name

pytorch-pretrained-bert from November 2018, pytorch-transformers for two months in 2019, then transformers from 26 September 2019. The PyPI name itself carries a 2016 release by an unrelated author, summarised "Experimental module for AST transformations". The browser runtime followed the same pattern, adopted from a community package in August 2024 with its version numbering continued rather than reset.

Carry forwardRegistry metadata dates decisions that no announcement does, including the ones a company would rather not date.
pypi.org/project/transformers/0.1
Measurement This guide2026-09-18

Thirteen wheels, measured

Model directories, per-framework file counts, generated modular files, copy markers and total Python lines across nine release points from 2019 to 2026. Not published anywhere; computed from the wheels themselves with the script in the next section.

Carry forwardYou can date any library's architectural decisions from its own published artefacts, in about twenty minutes.
files.pythonhosted.org, the 5.0.0 wheel measured
Measurement crates.io2026-09-18

The Rust half of the stack, by adoption

tokenizers at 32,865,800 all-time downloads since August 2019, safetensors at 25,903,163 since December 2022, hf-xet at 1,197,063. Three separate occasions on which a performance problem in Python was answered with a Rust component rather than with Python.

Carry forwardRegistry download counts are a weak proxy for usage but a strong one for how long a decision has been load-bearing.
crates.io/api/v1/crates/safetensors
What is missing, and why

There is not a single engineering blog post, conference talk or paper in that wall. The session's egress policy blocked every host that carries them, so the absence is a property of this collection, not of the field. The practical consequence for the reader: where a narrative account would normally explain intent, this guide infers it from artefacts, and every inference is marked as one. If you can reach the blogs, start there and use this page for the measurements and the rejected pull requests, which are the parts the blogs do not cover.

07

Build a miniature, then productionise it

Six rungs. The crossing from toy to real is rung four, where you stop trusting the file.

Write the dumb format

Implement a tensor container from the safetensors specification: a 64-bit little-endian header length, a JSON header of dtype, shape and offsets, a packed buffer. Round-trip a real checkpoint against the reference library.

Done when: your reader loads a file written by the reference implementation and vice versa.  Teaches: how little a format needs to contain to be useful, and how much of the work is offset arithmetic.

Break the old one

Run the exploits in the safetensors attacks directory in a container you are happy to lose. Then write the equivalent for your own artefact format, whatever it is: a config file, a plugin manifest, a saved workflow.

Done when: you have a file that looks inert and executes code on load.  Teaches: that deserialisation is code execution unless someone deliberately stopped it.

Try the abuse cases against your own reader

Feed it a header larger than the file, negative offsets, overlapping offsets, and offsets that disagree with the declared shape. The reference project's notes record that overlapping offsets were initially judged acceptable and then found to be a denial of service.

Done when: every malformed input produces a bounded error rather than a bounded allocation.  Teaches: that validation rules are the format, more than the layout is.

Add a deny-by-default execution policy

Give your loader the ability to run code shipped alongside the data, then make it refuse unless the caller opts in. Now write the test that the LightGlue advisory describes: a file that sets the trust flag on itself, and a caller that said no.

Done when: the self-granting file is refused, and the test fails if anyone reorders the trust resolution.  Teaches: where the trust boundary actually sits, which is earlier than most code puts it.

Deduplicate a real corpus

Take twenty checkpoints from one model family. Chunk them with a rolling hash at a 64 KiB target, then measure unique chunks against total chunks. Repeat at 16 KiB and 256 KiB and plot index size against bytes saved.

Done when: you can state your own corpus's dedup ratio at three chunk sizes.  Teaches: that the published chunk size encodes somebody else's corpus, and yours may not look like it.

Audit your own dependency's release discipline

For the three libraries you rely on most, pull the registry metadata: every release date, every yank and its reason, and the point at which any optional dependency became mandatory. The script in the next section does the transformers case.

Done when: you can name, for each, the last withdrawn release and why.  Teaches: that supply-chain risk is mostly release-process risk, and it is published.

08

Keep hunting

The page will go stale; the method will not. These are the queries and the one script that produced everything above.

Rejected decisions, which is where the reasons are

  • repo:huggingface/safetensors is:pr is:closed is:unmerged sort:comments-desc
  • repo:OWNER/REPO is:issue is:closed reason:"not planned" label:design
  • path:docs/source/en philosophy OR tenets OR modular

Dating a decision without an announcement

  • curl -s https://pypi.org/pypi/PACKAGE/json | jq '.releases|to_entries[]|{v:.key,yanked:.value[0].yanked_reason}'
  • curl -s https://pypi.org/pypi/PACKAGE/VERSION/json | jq -r '.info.requires_dist[]'
  • https://github.com/OWNER/REPO/commits/main/LICENSE
  • curl -s -H "User-Agent: you" https://crates.io/api/v1/crates/CRATE | jq .crate.created_at

Incidents, when nobody publishes postmortems

  • https://github.com/advisories?query=PACKAGE
  • https://github.com/OWNER/REPO/security/advisories
  • site:pypi.org PACKAGE yanked reason

Measuring a library's architecture from its wheels

  • python -c "import json,urllib.request as u;d=json.load(u.urlopen('https://pypi.org/pypi/transformers/5.0.0/json'));print([f['url'] for f in d['urls']])"
  • unzip -l transformers-5.0.0-py3-none-any.whl | grep -c 'models/.*/modeling_tf_'
  • unzip -p WHEEL '*.py' | grep -c '# Copied from transformers\.'

Two mechanical notes, because both cost me time. Repository web pages are increasingly rendered client side, so fetch files through the raw content service rather than the repository page when you want exact text, and fetch registry data from the JSON API rather than the human page. The wheels themselves are the most underused artefact of all: the exact file measured for the table above is one URL away, and it is the only source in this guide that cannot have been edited since publication.

The last group is the one worth generalising. Any library that publishes wheels publishes its own architectural history in a form nobody has to write a blog post about: the file layout tells you what the units of work are, per-directory file counts tell you which backends are still being invested in, and a grep for a policy marker tells you the cost of the policy. The freeze in TensorFlow file counts, which is the most useful single fact in this guide, was not announced anywhere. It was sitting in nine wheels for fifteen months.

09

References

  1. Hugging Face, transformers release history PyPI, 2019 to 2026. Checked 2026-09-18.
  2. transformers 0.1, "Experimental module for AST transformations" PyPI, 17 August 2016. Checked 2026-09-18.
  3. Hugging Face, pytorch-pretrained-bert PyPI, November 2018 to April 2019. Checked 2026-09-18.
  4. Hugging Face, pytorch-transformers PyPI, July to September 2019. Checked 2026-09-18.
  5. Hugging Face, huggingface-hub releases and dependency metadata PyPI, 2020 to 2026. Checked 2026-09-18.
  6. Hugging Face, hf-xet releases and yank notices PyPI, 2025 to 2026. Checked 2026-09-18.
  7. Hugging Face, safetensors releases PyPI, from 22 September 2022. Checked 2026-09-18.
  8. Hugging Face, tokenizers releases PyPI, from 1 November 2019. Checked 2026-09-18.
  9. Hugging Face, safetensors README, specification and rationale GitHub. Checked 2026-09-18.
  10. Hugging Face, safetensors attacks directory GitHub. Checked 2026-09-18.
  11. safetensors PR 197, Q4 quantization support GitHub, opened 17 March 2023, closed 24 December 2023. Checked 2026-09-18.
  12. safetensors PR 676, nvidia gds support GitHub, opened 22 November 2025, closed 13 April 2026. Checked 2026-09-18.
  13. Hugging Face, transformers philosophy and core tenets GitHub. Checked 2026-09-18.
  14. Hugging Face, modular transformers GitHub. Checked 2026-09-18.
  15. Hugging Face, transformers v5.0.0 release notes GitHub, 26 January 2026. Checked 2026-09-18.
  16. transformers issue 43489, "Transformers' version 5 is out!" GitHub, 26 January 2026. Checked 2026-09-18.
  17. Hugging Face, xet-core README GitHub. Checked 2026-09-18.
  18. xet-core chunk and block constants GitHub. Checked 2026-09-18.
  19. xet-core gear-hash chunker GitHub. Checked 2026-09-18.
  20. huggingface_hub v0.30.0 release notes GitHub, 31 March 2025. Checked 2026-09-18.
  21. huggingface_hub constants, including the hf_transfer deprecation GitHub. Checked 2026-09-18.
  22. text-generation-inference LICENSE commit history GitHub, 2022 to 2024. Checked 2026-09-18.
  23. Hugging Face, datasets 3.0.0 release notes GitHub, 11 September 2024. Checked 2026-09-18.
  24. GHSA-fgcw-684q-jj6r, arbitrary code execution during model initialization GitHub Advisory Database, 3 June 2026. Checked 2026-09-18.
  25. GHSA-9xq9-36w5-q796, hardcoded trust_remote_code GitHub Advisory Database, May 2026. Checked 2026-09-18.
  26. GitHub Advisory Database, model-loading advisories GitHub, 2026. Checked 2026-09-18.
  27. PyTorch 2.6.0 release notes GitHub, January 2025. Checked 2026-09-18.
  28. Keras, load_model and safe_mode GitHub. Checked 2026-09-18.
  29. Keras, saving_lib and the hf:// load path GitHub. Checked 2026-09-18.
  30. ggml, GGUF specification GitHub. Checked 2026-09-18.
  31. crates.io, safetensors crate metadata crates.io. Checked 2026-09-18.
  32. crates.io, tokenizers crate metadata crates.io. Checked 2026-09-18.
  33. npm registry, @huggingface/transformers npm, from 8 August 2024. Checked 2026-09-18.