Workspace IndexDev Notes › Hugging Face is a package manager, and almost nobody pins the version

#135PoC

Hugging Face is a package manager, and almost nobody pins the version

Every repo is a git repo and revision= takes a commit SHA — but the default is a floating branch, trust_remote_code=True executes code from that branch, and the licence that decides whether you can ship is per-repo. The build is a five-field lockfile.

Not yet scoped, and the first artifact is a file rather than a notebook. For every model and dataset this project actually loads, record five fields: repo id, the resolved commit SHA (not main), the licence as stated in that revision, whether loading it requires trust_remote_code, and the tokenizer's own repo and SHA where it is a separate artifact. Pin the SHAs in code. Then re-resolve the same list thirty days later and diff it — what changed under a name you did not change is the finding, and it is the argument for the lockfile that no amount of explaining will make on its own. Alongside it, one read: open a model card you already depend on and mark which numbers on it are self-reported and which are independently verifiable.

Why

Hugging Face is used as a website and behaves as a package manager, and the gap between those two mental models is where the surprises live. Every repo is a git repository with LFS. Every load resolves a name to a revision. Every dependency has a licence, a maintainer and an update cadence you do not control. The ecosystem invented all of that vocabulary a decade ago for npm and PyPI, along with the practices that make it survivable — lockfiles, integrity hashes, vendoring, dependency review. The primitives are all present here and the defaults are the unsafe ones.

Start with the default that matters most: from_pretrained("org/model") resolves to a branch, not a commit. That branch can be updated, re-quantised, re-tokenised, re-licensed, gated behind an access request, or removed, and none of those events change a single character in your code. The result is a build that is not reproducible and, worse, one that fails later rather than at the moment of the change — a re-quantised checkpoint does not throw an exception, it just answers slightly differently. revision= accepts a commit SHA and fixes this completely. It is one keyword argument, and it is skipped almost universally.

Then the one that is a security decision rather than a hygiene one. trust_remote_code=True downloads Python from the repo and executes it in your process. This is the same act as piping a URL into a shell, performed by a line that reads like configuration. third-party-blast-radius argues that the useful question about an authorized component is not was it authorized but what can it do, and this is that question in its most literal form: the flag is not a permission to load a model, it is a permission to run someone's code with your credentials, your filesystem and your network. The mitigation is not to ban it — some architectures genuinely require it — but to make it a recorded, pinned, reviewed decision: which repos, at which SHA, and who read the file.

The licence is the field that decides whether any of it can ship, and it is per-repo rather than per-organisation. A permissive model can sit next to a research-only sibling under the same account. A model's licence does not carry the licence of the dataset it was trained on, and a dataset repo can be more restrictive than everything built on top of it. This is exactly the kind of obligation that is invisible while building and expensive at the point where someone asks — which, for this project, is the same institutional counterparty that event-contract-plumbing says will ask about the middle column.

What Hugging Face genuinely gives you is worth stating too, because the criticism above is not an argument against using it. Model cards, when filled in, are the most honest artifact in this part of the industry: intended use, limitations, training data, and known failure modes, written by the people who built the thing. Datasets are versioned and streamable. The Hub is where the robotics stack in robotics-entry-decision actually lives. The point is not to depend on it less — it is to depend on it the way a package manager is depended on, with the version written down.

How it works

Three services wearing one name

What you lean on What you are actually depending on What breaks it
Registry — model weights A git repo owned by someone else, resolved by branch An update, a re-quantisation, a gate, a takedown
Data host — datasets A separate repo with its own licence and its own revision A licence change that does not touch the model you trained
Runtimetransformers, diffusers, Inference Endpoints, Spaces A library API surface and, optionally, remote code execution A breaking release, or a repo whose custom code changed

These fail independently and lock you in to different degrees. Weights are portable; the runtime is where the switching cost accumulates, because a pipeline written against one library's abstractions is not a set of weights any more.

The lockfile, in five fields

Field Why it is separate
repo_id The name — the only part most projects record
revision (commit SHA) The actual dependency. main is not a version
licence Per-repo, and it can change between revisions
trust_remote_code A yes here is an execution grant, not a load option
tokenizer_repo + SHA A separate artifact that versions independently, and a mismatch degrades output silently rather than erroring

The last row is the one that produces the strangest bugs. A tokenizer and a checkpoint that disagree do not crash — they produce fluent, slightly wrong output, which is the failure mode hardest to notice and hardest to attribute afterwards.

The measurement that makes the argument for you

Pin the list today, re-resolve it in thirty days, and diff. The output is a count: how many of your dependencies moved under a name you did not change. If the answer is zero, the lockfile cost you an afternoon. If it is not zero, you have just found the set of results you could not have reproduced, and no further argument is needed.

This is the same shape as the measurement in demonstration-is-not-a-specification — run it twice and diff — and the same shape as the gap in the-split-is-the-experiment. A single observation is an anecdote; two observations of the same thing are a measurement. That pattern is doing a lot of work across this section and it is worth noticing as a method rather than as three separate ideas.

What to read on a model card, and what to distrust

Model cards are the good artifact here, but they are self-published. Intended use, limitations and training-data description are the valuable parts — they are claims the author had no incentive to invent, and their absence is itself informative. Benchmark numbers are the part to discount, since they are self-reported, frequently on evaluation code that is not published alongside them. The practical rule is the one this catalogue keeps arriving at from different directions: read the primary artifact, and treat any number you did not see produced as a claim rather than a fact.

← All Dev Notes · Workspace Index · Top ↑

허깅페이스는 패키지 매니저인데, 버전을 고정하는 사람이 거의 없다

모든 저장소가 git 저장소이고 revision= 은 커밋 SHA 를 받습니다 — 그런데 기본값은 떠다니는 브랜치이고, trust_remote_code=True 는 그 브랜치의 코드를 실행하며, 출시 가능 여부를 정하는 라이선스는 저장소마다 다릅니다. 만들 것은 다섯 칸짜리 락파일입니다.

아직 범위 미정이고, 첫 결과물은 노트북이 아니라 파일 하나입니다. 이 프로젝트가 실제로 불러오는 모든 모델·데이터셋에 대해 다섯 칸을 기록합니다: 저장소 id, 해석된 커밋 SHA(main 이 아니라), 그 리비전에 적힌 라이선스, 로딩에 trust_remote_code 가 필요한지, 그리고 토크나이저가 별도 산출물이면 그쪽 저장소와 SHA. 코드에 SHA 를 고정합니다. 그다음 30일 뒤 같은 목록을 다시 해석해 diff 합니다 — 내가 바꾸지 않은 이름 아래에서 바뀐 것이 곧 발견이고, 그게 말로 아무리 설명해도 안 되는 락파일의 논거입니다. 함께 읽을 것 하나: 이미 의존하고 있는 모델 카드를 열어 어느 숫자가 자기 보고이고 어느 숫자가 독립 검증 가능한지 표시해 보십시오.

허깅페이스는 웹사이트처럼 쓰이는데 패키지 매니저처럼 동작하고, 그 두 모형 사이의 간극에 놀랄 일들이 삽니다. 모든 저장소가 LFS 를 쓰는 git 저장소입니다. 모든 로딩이 이름을 리비전으로 해석합니다. 모든 의존성에 내가 통제하지 않는 라이선스·관리자·갱신 주기가 있습니다. 업계는 그 어휘를 십 년 전 npm·PyPI 에서 이미 발명했고, 살아남는 관행도 같이 만들었습니다 — 락파일, 무결성 해시, 벤더링, 의존성 리뷰. 여기엔 그 재료가 전부 있고, 기본값이 안전하지 않은 쪽입니다.

가장 중요한 기본값부터: from_pretrained("org/model") 은 커밋이 아니라 브랜치로 해석됩니다. 그 브랜치는 갱신되고, 재양자화되고, 토크나이저가 바뀌고, 라이선스가 바뀌고, 접근 요청 뒤로 게이팅되고, 삭제될 수 있는데 — 그중 어느 것도 내 코드의 글자 하나 바꾸지 않습니다. 결과는 재현 불가능한 빌드이고, 더 나쁜 건 변경 시점이 아니라 나중에 실패한다는 점입니다 — 재양자화된 체크포인트는 예외를 던지지 않고 그냥 조금 다르게 답합니다. revision= 은 커밋 SHA 를 받고 이걸 완전히 해결합니다. 키워드 인자 하나이고, 거의 보편적으로 생략됩니다.

그다음은 위생이 아니라 보안 결정인 것. trust_remote_code=True 는 저장소의 파이썬을 내려받아 내 프로세스에서 실행합니다. 이건 URL 을 셸에 파이프로 넣는 것과 같은 행위인데, 설정처럼 생긴 한 줄로 수행됩니다. third-party-blast-radius 는 인가된 구성요소에 대한 쓸모 있는 질문이 인가됐는가가 아니라 무엇을 할 수 있는가라고 주장하는데, 이건 그 질문의 가장 문자 그대로의 형태입니다: 이 플래그는 모델을 로드할 권한이 아니라, 내 자격증명·파일시스템·네트워크로 남의 코드를 실행할 권한입니다. 처방은 금지가 아니라 — 일부 아키텍처는 진짜로 필요합니다 — 그것을 기록되고, 고정되고, 검토된 결정으로 만드는 것입니다: 어느 저장소를, 어느 SHA 에서, 누가 그 파일을 읽었는가.

라이선스가 출시 가능 여부를 정하는 칸이고, 조직 단위가 아니라 저장소 단위입니다. 관대한 모델이 같은 계정 아래 연구 전용 형제 옆에 앉아 있을 수 있습니다. 모델의 라이선스는 그 모델이 학습된 데이터셋의 라이선스를 같이 가져오지 않고, 데이터셋 저장소가 그 위에 지어진 모든 것보다 더 제한적일 수 있습니다. 이건 만드는 동안엔 안 보이고 누군가 물을 때 비싸지는 종류의 의무이고 — 이 프로젝트에서 그 "누군가"는 event-contract-plumbing가운데 칸을 물어볼 것이라고 한 바로 그 기관 상대입니다.

허깅페이스가 진짜로 주는 것도 적어둘 값어치가 있습니다. 위의 비판은 쓰지 말라는 논거가 아니기 때문입니다. 모델 카드는 채워져 있을 때 이 바닥에서 가장 정직한 산출물입니다 — 의도된 용도, 한계, 학습 데이터, 알려진 실패 양식을 만든 사람이 직접 적습니다. 데이터셋은 버전 관리되고 스트리밍됩니다. robotics-entry-decision 의 로보틱스 스택이 실제로 사는 곳이 Hub 입니다. 요점은 덜 의존하라는 게 아니라 — 패키지 매니저에 의존하는 방식으로 의존하라는 것입니다. 버전을 적어두고서.

동작 방식

이름 하나를 쓴 서비스 셋

무엇에 기대나 실제로 의존하는 것 무엇이 그것을 깨나
레지스트리 — 모델 가중치 남이 소유한 git 저장소, 브랜치로 해석됨 갱신, 재양자화, 게이팅, 삭제
데이터 호스트 — 데이터셋 자기 라이선스와 자기 리비전을 가진 별도 저장소 내가 학습시킨 모델은 안 건드리는 라이선스 변경
런타임transformers·diffusers·Inference Endpoints·Spaces 라이브러리 API 표면, 그리고 선택적으로 원격 코드 실행 파괴적 릴리스, 또는 커스텀 코드가 바뀐 저장소

셋은 독립적으로 깨지고 락인의 정도도 다릅니다. 가중치는 옮길 수 있지만, 전환 비용이 쌓이는 곳은 런타임입니다 — 한 라이브러리의 추상화에 맞춰 쓴 파이프라인은 더 이상 가중치 묶음이 아니기 때문입니다.

락파일, 다섯 칸

왜 따로인가
repo_id 이름 — 대부분의 프로젝트가 기록하는 유일한 부분
revision (커밋 SHA) 실제 의존성. main 은 버전이 아닙니다
licence 저장소 단위이고 리비전 사이에 바뀔 수 있음
trust_remote_code 여기의 "예"는 로딩 옵션이 아니라 실행 허가
tokenizer_repo + SHA 독립적으로 버전되는 별도 산출물. 불일치는 오류가 아니라 조용한 품질 저하로 나타남

가장 이상한 버그를 만드는 건 마지막 줄입니다. 토크나이저와 체크포인트가 어긋나면 터지지 않습니다유창하고 살짝 틀린 출력을 내놓고, 그건 알아채기도 원인 찾기도 가장 어려운 실패 양식입니다.

논거를 대신 만들어 주는 측정

오늘 목록을 고정하고, 30일 뒤 다시 해석해 diff 합니다. 결과는 숫자 하나입니다 — 내가 바꾸지 않은 이름 아래에서 몇 개가 움직였는가. 0이면 락파일은 반나절이 든 것이고, 0이 아니면 재현할 수 없었을 결과들의 집합을 방금 찾은 것이라 더 이상 논증이 필요 없습니다.

이건 demonstration-is-not-a-specification 의 측정(두 번 돌리고 diff)과 같은 모양이고, the-split-is-the-experiment 의 격차와도 같은 모양입니다. 관측 하나는 일화이고, 같은 것에 대한 관측 둘은 측정입니다. 이 패턴이 이 섹션 전체에서 많은 일을 하고 있고, 서로 다른 세 아이디어가 아니라 하나의 방법으로 알아둘 값어치가 있습니다.

모델 카드에서 읽을 것과 의심할 것

모델 카드는 여기서 좋은 산출물이지만 자기 출판물입니다. 의도된 용도·한계·학습 데이터 서술이 값어치 있는 부분입니다 — 저자가 지어낼 유인이 없는 주장이고, 그것이 비어 있다는 사실 자체가 정보입니다. 할인해서 읽을 부분은 벤치마크 숫자입니다 — 자기 보고이고, 평가 코드가 함께 공개되지 않은 경우가 잦습니다. 실무 규칙은 이 카탈로그가 여러 방향에서 계속 도달하는 그것입니다: 1차 산출물을 읽고, 생성되는 것을 직접 보지 않은 숫자는 사실이 아니라 주장으로 다룰 것.

← 전체 개발 노트 · 워크스페이스 인덱스 · 맨 위 ↑