Studio: fail closed on index-referenced nested pickle shards in the offline embedding gate (#7366)

* Studio: fail closed on index-referenced nested pickle shards in the offline embedding gate

The offline embedding security gate (HF_HUB_OFFLINE / TRANSFORMERS_OFFLINE)
only scanned the direct files of each SentenceTransformer load root and never
parsed local weight indexes, so a cached snapshot whose pytorch_model.bin.index.json
maps a weight to a nested shard (e.g. shards/pytorch_model-00001-of-00001.bin) was
treated as inert and allowed. The loader then follows the index into the subdir and
unpickles the shard. The online gate already blocks index-referenced subdir pickles,
so the offline path was strictly weaker.

Parse each local weight index in a load root and follow weight_map into nested dirs,
flagging any referenced pickle-extension shard. Paths resolve lexically (normpath),
never Path.resolve(), since HF cache snapshot files symlink into blobs/ and resolving
would leave the snapshot dir and false-block every sharded model offline. An absolute
path, a .. traversal that escapes the snapshot, or an unreadable/invalid index fails
closed. The existing safetensors-sibling suppression is kept.

* Studio: classify offline indexed shards by torch.load path, not pickle extension

load_state_dict picks safetensors vs torch.load per shard by the shard's own
suffix, so two offline-gate gaps remained:

- A model.safetensors.index.json whose weight_map points at a .bin shard was
  suppressed by has_base_safetensors (the index file itself matches the base
  safetensors regex), yet Transformers still torch.loads that shard. Only the
  pytorch index is superseded by a base safetensors now; a safetensors index is
  the chosen archive, so its non-safetensors targets are always flagged.

- A pytorch index can map weights to arbitrary names (shards/payload,
  weights.data); the loader torch.loads any target not ending in .safetensors.
  Flag indexed shards by that rule instead of a pickle-extension allowlist.

Restrict the scan to the two torch-family indexes (tf/flax load via non-pickle
loaders). Add regression tests for both cases.

* Studio: match offline weight-index filenames case-insensitively

The index-name check compared the on-disk filename exactly, while the
surrounding weight and safetensors matches use case-insensitive rules. On a
case-insensitive volume (Windows or macOS) from_pretrained opens an oddly-cased
cache file such as PYTORCH_MODEL.BIN.INDEX.JSON when it requests the canonical
lowercase name, so the exact-case check skipped it and a nested pickle shard it
referenced was allowed through. Lower-case the index name before matching, as
the rest of the gate does, and add a regression test.

* Studio: match load_state_dict format/selection exactly in the offline index scan

Two edge cases in the offline weight-index scan:

- load_state_dict decides safetensors vs torch.load with a case-sensitive
  endswith(".safetensors"), so a shard named payload.SAFETENSORS still
  deserializes via torch.load. Classify indexed shard suffixes case-sensitively
  to match, instead of lower-casing (which treated such a shard as inert).

- A complete direct model.safetensors is selected before either sharded index,
  so a stale model.safetensors.index.json referencing a .bin shard never loads.
  Skip both indexes when a direct model.safetensors is present, so an otherwise
  loadable model is not over-blocked.

Add regression tests for both.

* Studio: read the offline weight index as UTF-8

Path.read_text() uses the locale default, which is cp1252 on Windows, so a
UTF-8 weight index with non-ASCII bytes raised UnicodeDecodeError and the gate
blocked an otherwise loadable model. JSON is UTF-8 by spec (and how the loader
reads it), so pin the encoding.

* Studio: resolve safetensors alternatives via the loader's own filename lookup

The offline gate decided a safetensors alternative existed by case-folding the
directory listing. On a case-sensitive filesystem that let an uppercase decoy
such as MODEL.SAFETENSORS suppress the pickle scan, yet from_pretrained asks for
the canonical lowercase model.safetensors, does not find the decoy, and selects
the pickle (a direct pytorch_model.bin or the pytorch index) and deserializes it.

Probe each alternative with (root / name).is_file() instead, mirroring the
loader: is_file() honors the platform's case rules, so a decoy suppresses only
where the loader would truly open it. Suppression must never fail open; detection
stays case-insensitive (fail closed). Add regression tests for the direct and
indexed pickle decoys (skipped on case-insensitive volumes, where no bypass
exists).

* Studio: resolve indexes and shards exactly as from_pretrained does

Two more loader-fidelity gaps in the offline index scan:

- Shard lookup normalized backslashes to forward slashes. On POSIX a backslash
  is a literal filename character, so an index naming dir\payload.bin matches a
  real pickle of that exact name that Transformers joins and deserializes, while
  the normalized dir/payload.bin missed it. Join the raw weight_map value with
  os.path.join so the probe mirrors the loader on each platform.

- Index detection case-folded the directory listing, so on a case-sensitive
  filesystem an uppercase PYTORCH_MODEL.BIN.INDEX.JSON artifact the loader never
  opens was treated as live and its shard blocked. Probe the canonical name with
  the loader's own is_file lookup instead, so an index counts only where
  from_pretrained would actually load it.

Update the uppercase-index tests to assert the correct per-filesystem behavior
and add a POSIX backslash-shard regression test.

---------

Co-authored-by: danielhanchen <unslothai@gmail.com>
This commit is contained in:
Daniel Han 2026-07-23 20:06:30 -07:00 committed by GitHub
commit c2114d64dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 438 additions and 19 deletions

View file

@ -51,6 +51,27 @@ def _modules_json(*paths):
_COMMIT = "0123456789abcdef0123456789abcdef01234567"
def _fs_case_sensitive(root):
"""Whether root's filesystem is case-sensitive (Linux yes; macOS/Windows usually no). The gate
mirrors the loader, whose file lookups follow the same rule, so some cases only exist on one."""
probe = Path(root) / "_case_probe"
probe.write_text("x")
try:
return not (Path(root) / "_CASE_PROBE").exists()
finally:
probe.unlink()
def _requires_case_sensitive_fs(root):
if not _fs_case_sensitive(root):
pytest.skip("requires a case-sensitive filesystem")
def _requires_case_insensitive_fs(root):
if _fs_case_sensitive(root):
pytest.skip("requires a case-insensitive filesystem")
def _make_cache(
root,
repo_id,
@ -382,6 +403,329 @@ def test_gate_blocks_sharded_pickle(hf_cache):
assert _offline_decision("org/shard").blocked is True
def test_gate_blocks_indexed_pickle_shard_in_subdirectory(hf_cache):
# from_pretrained follows weight_map paths relative to the root index, so these nested shards
# are deserialized even though they are not direct children of the load root (iterdir misses
# them). The online gate blocks index-referenced subdir pickles; the offline gate must too.
_make_cache(
hf_cache,
"org/indexed-shard",
{
"pytorch_model.bin.index.json": (
'{"weight_map": {"layer.weight": "shards/pytorch_model-00001-of-00001.bin"}}'
),
"shards/pytorch_model-00001-of-00001.bin": "pickle",
},
)
with _no_network():
decision = _offline_decision("org/indexed-shard")
assert decision.blocked is True
assert any(
u["path"] == "shards/pytorch_model-00001-of-00001.bin" for u in decision.unsafe_files
)
def test_gate_blocks_indexed_pickle_shard_with_nonstandard_stem(hf_cache):
# The index tells the loader to deserialize this file, so a pickle EXTENSION is enough -- the
# shard's stem need not match the on-disk weight-name heuristic (which only guesses bare files).
_make_cache(
hf_cache,
"org/indexed-odd",
{
"pytorch_model.bin.index.json": '{"weight_map": {"w": "shards/evil-00001-of-00001.bin"}}',
"shards/evil-00001-of-00001.bin": "pickle",
},
)
with _no_network():
decision = _offline_decision("org/indexed-odd")
assert decision.blocked is True
assert any(u["path"] == "shards/evil-00001-of-00001.bin" for u in decision.unsafe_files)
def test_gate_blocks_safetensors_index_pointing_to_pickle_shard(hf_cache):
# load_state_dict picks safetensors vs torch.load by each shard's own suffix, so a
# model.safetensors.index.json that maps a weight to a .bin shard still deserializes it. The
# index's own existence must not suppress the shard it names.
_make_cache(
hf_cache,
"org/st-index-pickle",
{
"model.safetensors.index.json": (
'{"weight_map": {"w": "shards/pytorch_model-00001-of-00001.bin"}}'
),
"shards/pytorch_model-00001-of-00001.bin": "pickle",
},
)
with _no_network():
decision = _offline_decision("org/st-index-pickle")
assert decision.blocked is True
assert any(
u["path"] == "shards/pytorch_model-00001-of-00001.bin" for u in decision.unsafe_files
)
def test_gate_blocks_indexed_shard_with_no_pickle_extension(hf_cache):
# Transformers torch.loads any indexed shard not ending in .safetensors, so an unconventional
# extensionless name is still a deserialization target.
_make_cache(
hf_cache,
"org/indexed-noext",
{
"pytorch_model.bin.index.json": '{"weight_map": {"w": "shards/payload"}}',
"shards/payload": "pickle",
},
)
with _no_network():
decision = _offline_decision("org/indexed-noext")
assert decision.blocked is True
assert any(u["path"] == "shards/payload" for u in decision.unsafe_files)
_UPPER_INDEX_FILES = {
"PYTORCH_MODEL.BIN.INDEX.JSON": (
'{"weight_map": {"w": "shards/pytorch_model-00001-of-00001.bin"}}'
),
"shards/pytorch_model-00001-of-00001.bin": "pickle",
}
def test_gate_blocks_uppercase_index_on_case_insensitive_fs(hf_cache):
# On a case-insensitive volume (Windows/macOS) from_pretrained opens an oddly-cased index when it
# requests the canonical lowercase name, so the loader-mirror lookup resolves it and blocks.
_requires_case_insensitive_fs(hf_cache)
_make_cache(hf_cache, "org/upper-index", _UPPER_INDEX_FILES)
with _no_network():
decision = _offline_decision("org/upper-index")
assert decision.blocked is True
assert any(
u["path"] == "shards/pytorch_model-00001-of-00001.bin" for u in decision.unsafe_files
)
def test_gate_allows_uppercase_index_on_case_sensitive_fs(hf_cache):
# On a case-sensitive FS from_pretrained's os.path.isfile of the canonical lowercase name misses
# the uppercase artifact and never loads its shard, so the gate must not over-block it.
_requires_case_sensitive_fs(hf_cache)
_make_cache(hf_cache, "org/upper-index", _UPPER_INDEX_FILES)
with _no_network():
assert _offline_decision("org/upper-index").blocked is False
def test_gate_blocks_indexed_shard_named_with_backslash(hf_cache):
# On POSIX a backslash is a literal filename char, so from_pretrained joins the raw weight_map
# value and deserializes a file actually named "dir\payload.bin"; the gate must probe it verbatim.
import os
if os.sep != "/":
pytest.skip("backslash is a path separator off POSIX")
_make_cache(
hf_cache,
"org/backslash",
{
"pytorch_model.bin.index.json": '{"weight_map": {"w": "dir\\\\payload.bin"}}',
"dir\\payload.bin": "pickle",
},
)
with _no_network():
decision = _offline_decision("org/backslash")
assert decision.blocked is True
assert any(u["path"] == "dir\\payload.bin" for u in decision.unsafe_files)
def test_gate_blocks_indexed_shard_with_uppercase_safetensors_suffix(hf_cache):
# load_state_dict's endswith(".safetensors") is case-sensitive, so a shard named payload.SAFETENSORS
# falls to torch.load. The gate must classify shard suffixes case-sensitively to match it.
_make_cache(
hf_cache,
"org/upper-suffix",
{
"pytorch_model.bin.index.json": '{"weight_map": {"w": "shards/payload.SAFETENSORS"}}',
"shards/payload.SAFETENSORS": "pickle",
},
)
with _no_network():
decision = _offline_decision("org/upper-suffix")
assert decision.blocked is True
assert any(u["path"] == "shards/payload.SAFETENSORS" for u in decision.unsafe_files)
def test_gate_allows_stale_safetensors_index_beside_direct_safetensors(hf_cache):
# A complete direct model.safetensors is selected before either index, so a stale
# model.safetensors.index.json referencing a .bin shard never deserializes -> must not block.
_make_cache(
hf_cache,
"org/direct-plus-stale-index",
{
"model.safetensors": "tensors",
"model.safetensors.index.json": (
'{"weight_map": {"w": "shards/pytorch_model-00001-of-00001.bin"}}'
),
"shards/pytorch_model-00001-of-00001.bin": "pickle",
},
)
with _no_network():
assert _offline_decision("org/direct-plus-stale-index").blocked is False
def test_gate_blocks_pytorch_index_with_uppercase_safetensors_decoy(hf_cache):
# On a case-sensitive FS, from_pretrained asks for the canonical lowercase model.safetensors, does
# not find an uppercase decoy, and selects the pytorch index instead. The decoy must not suppress.
_requires_case_sensitive_fs(hf_cache)
_make_cache(
hf_cache,
"org/upper-decoy",
{
"MODEL.SAFETENSORS": "decoy",
"pytorch_model.bin.index.json": (
'{"weight_map": {"w": "shards/pytorch_model-00001-of-00001.bin"}}'
),
"shards/pytorch_model-00001-of-00001.bin": "pickle",
},
)
with _no_network():
decision = _offline_decision("org/upper-decoy")
assert decision.blocked is True
assert any(
u["path"] == "shards/pytorch_model-00001-of-00001.bin" for u in decision.unsafe_files
)
def test_gate_blocks_direct_pickle_with_uppercase_safetensors_decoy(hf_cache):
# Same decoy against a direct pytorch_model.bin: the loader selects the pickle, so the uppercase
# safetensors must not suppress it on a case-sensitive FS.
_requires_case_sensitive_fs(hf_cache)
_make_cache(
hf_cache,
"org/upper-decoy-direct",
{"MODEL.SAFETENSORS": "decoy", "pytorch_model.bin": "pickle"},
)
with _no_network():
decision = _offline_decision("org/upper-decoy-direct")
assert decision.blocked is True
assert any(u["path"] == "pytorch_model.bin" for u in decision.unsafe_files)
def test_gate_blocks_indexed_pickle_shard_in_module_subdir(hf_cache):
# A weight index inside a sentence-transformers module load root points at a nested pickle shard.
_make_cache(
hf_cache,
"org/mod-indexed",
{
"modules.json": _modules_json("0_Transformer"),
"0_Transformer/pytorch_model.bin.index.json": (
'{"weight_map": {"w": "shards/pytorch_model-00001-of-00001.bin"}}'
),
"0_Transformer/shards/pytorch_model-00001-of-00001.bin": "pickle",
},
)
with _no_network():
decision = _offline_decision("org/mod-indexed")
assert decision.blocked is True
assert any(
u["path"] == "0_Transformer/shards/pytorch_model-00001-of-00001.bin"
for u in decision.unsafe_files
)
def test_gate_allows_indexed_pickle_shard_with_safetensors_sibling(hf_cache):
# A base model.safetensors makes the loader ignore the pickle index entirely, so it must not
# block (mirrors the direct-file safetensors-sibling suppression).
_make_cache(
hf_cache,
"org/indexed-both",
{
"pytorch_model.bin.index.json": (
'{"weight_map": {"w": "shards/pytorch_model-00001-of-00001.bin"}}'
),
"shards/pytorch_model-00001-of-00001.bin": "pickle",
"model.safetensors": "y",
},
)
with _no_network():
assert _offline_decision("org/indexed-both").blocked is False
def test_gate_allows_indexed_safetensors_shard_in_subdirectory(hf_cache):
# A safetensors index lists inert shards -- following it must never block (guards against a
# scanner that flags every indexed shard regardless of format).
_make_cache(
hf_cache,
"org/st-indexed",
{
"model.safetensors.index.json": (
'{"weight_map": {"w": "shards/model-00001-of-00001.safetensors"}}'
),
"shards/model-00001-of-00001.safetensors": "tensors",
},
)
with _no_network():
assert _offline_decision("org/st-indexed").blocked is False
def test_gate_blocks_on_index_path_traversal(hf_cache):
# A weight_map entry escaping the snapshot via ".." is abnormal/hostile -> fail closed.
_make_cache(
hf_cache,
"org/escape",
{"pytorch_model.bin.index.json": '{"weight_map": {"w": "../../../../etc/evil.bin"}}'},
)
with _no_network():
assert _offline_decision("org/escape").blocked is True
def test_gate_allows_symlinked_sharded_safetensors(tmp_path, monkeypatch):
# Real HF caches store snapshot files as symlinks into blobs/. A resolve()-based containment
# check would escape the snapshot and false-block every sharded model; the lexical gate must not.
import hashlib
import os
from huggingface_hub.file_download import repo_folder_name
root = tmp_path / "hub"
root.mkdir()
monkeypatch.setenv("HF_HOME", str(tmp_path))
monkeypatch.setenv("HF_HUB_CACHE", str(root))
monkeypatch.setattr(
"utils.hf_cache_settings.get_hf_cache_paths",
lambda: SimpleNamespace(hub_cache = root),
)
repo_dir = root / repo_folder_name(repo_id = "org/sym", repo_type = "model")
(repo_dir / "refs").mkdir(parents = True)
(repo_dir / "refs" / "main").write_text(_COMMIT)
blobs = repo_dir / "blobs"
blobs.mkdir()
snapshot = repo_dir / "snapshots" / _COMMIT
(snapshot / "shards").mkdir(parents = True)
def _blobbed(rel, content):
digest = hashlib.sha256(content.encode()).hexdigest()
(blobs / digest).write_text(content)
target = snapshot / rel
target.parent.mkdir(parents = True, exist_ok = True)
target.symlink_to(os.path.relpath(blobs / digest, target.parent))
_blobbed("config.json", "{}")
_blobbed(
"model.safetensors.index.json",
'{"weight_map": {"w": "shards/model-00001-of-00001.safetensors"}}',
)
_blobbed("shards/model-00001-of-00001.safetensors", "tensors")
with _no_network():
assert _offline_decision("org/sym").blocked is False
def test_gate_allows_index_without_weight_map(hf_cache):
# An index whose top-level JSON has no dict weight_map lets the loader resolve no shards, so it
# must not crash or block on its own (only inert safetensors are cached here).
_make_cache(
hf_cache,
"org/no-wm",
{"model.safetensors.index.json": "[]", "model.safetensors": "x"},
)
with _no_network():
assert _offline_decision("org/no-wm").blocked is False
def test_gate_allows_nothing_cached(hf_cache):
with _no_network():
assert _offline_decision("org/missing").blocked is False

View file

@ -46,17 +46,6 @@ _PICKLE_WEIGHT_RE = re.compile(
r"\.(bin|pt|pth|ckpt|pkl|pickle)$",
re.IGNORECASE,
)
# Base-model safetensors set: HF names the base pickle pytorch_model.bin but the safetensors
# model.safetensors (stems differ), so a base pickle is replaced only by these, not an adapter's.
_BASE_SAFETENSORS_RE = re.compile(
r"^(model(-\d+-of-\d+)?\.safetensors|model\.safetensors\.index\.json)$",
re.IGNORECASE,
)
# Adapter (PEFT) safetensors set: adapter_model.safetensors, its shards, or index.
_ADAPTER_SAFETENSORS_RE = re.compile(
r"^(adapter_model(-\d+-of-\d+)?\.safetensors|adapter_model\.safetensors\.index\.json)$",
re.IGNORECASE,
)
# Non-blocking levels: clean or not-yet-finished. Anything else (unsafe/suspicious/
# malicious or a future label) blocks, so Hub schema drift fails CLOSED.
@ -94,6 +83,13 @@ _INERT_SUFFIXES = frozenset(
_SOURCE_SUFFIXES = frozenset({".py", ".pyc", ".pyx", ".pyi"})
# Torch-family weight indexes: from_pretrained feeds each shard they name to load_state_dict, which
# torch.load()s (pickle) any shard whose name does not end in .safetensors, whatever its stem. A
# pytorch index is superseded when a base safetensors is present (the loader prefers it); a
# safetensors index IS the chosen archive, so a non-safetensors target it names still loads. tf/flax
# indexes load via non-pickle loaders, so they are not a torch.load vector here.
_TORCH_INDEX_FILES = ("pytorch_model.bin.index.json", "model.safetensors.index.json")
# Root weight-index files. from_pretrained reads these to find sharded weights, so a
# flagged subdir pickle is a load vector iff a root index references it.
_TRANSFORMERS_INDEX_FILES = (
@ -313,13 +309,72 @@ def _st_load_roots(snapshot: Path) -> list:
return roots
def _indexed_pickle_shards(index_path: Path, root: Path, snapshot: Path) -> list:
"""Shards a torch weight index points a ``from_pretrained`` load at that load_state_dict would
torch.load (pickle): every ``weight_map`` target NOT ending in ``.safetensors``, whatever its
stem (an arbitrary name like ``shards/payload`` still deserializes). Resolved relative to the
index dir (``root``) like the loader, so a shard in a nested dir is followed (iterdir misses it).
Lexical only, never ``Path.resolve()`` (HF snapshot files symlink into ``blobs/``, so resolving
escapes the snapshot and false-blocks every shard). Raises OSError -> caller fails CLOSED on an
unreadable/invalid index or a target escaping the snapshot."""
import json
import os
try:
# JSON is UTF-8 by spec; pin it so a non-ASCII index is not misdecoded (and needlessly
# blocked) under Windows' cp1252 default.
parsed = json.loads(index_path.read_text(encoding = "utf-8"))
except (OSError, ValueError) as exc:
raise OSError(f"unreadable weight index: {index_path}") from exc
weight_map = parsed.get("weight_map") if isinstance(parsed, dict) else None
if not isinstance(weight_map, dict):
return [] # no dict weight_map -> the loader resolves no shards from this index
snapshot_norm = os.path.normpath(str(snapshot))
shards = []
for shard in weight_map.values():
raw = str(shard)
if not raw:
continue
# Join the RAW weight_map value like from_pretrained's os.path.join: on POSIX a backslash is a
# literal filename char (not a separator), so normalizing it would probe a different path than
# the loader opens. normpath + containment stay platform-aware (os.sep) to block "..".
joined = os.path.normpath(os.path.join(str(root), raw))
if joined != snapshot_norm and not joined.startswith(snapshot_norm + os.sep):
raise OSError(f"weight index escapes the snapshot: {index_path}")
shard_path = Path(joined)
# Case-SENSITIVE, mirroring load_state_dict's own endswith(".safetensors"): a shard named
# payload.SAFETENSORS is not treated as safetensors by the loader and falls to torch.load.
if not shard_path.name.endswith(".safetensors") and shard_path.is_file():
shards.append(shard_path)
return shards
def _loader_resolves(root: Path, name: str) -> bool:
"""True iff from_pretrained would open ``name`` under ``root``. ``is_file()`` honors the platform
(case-sensitive on Linux, case-insensitive on Windows/macOS), so it mirrors the loader's own
lookup: an oddly-cased decoy counts as an alternative only where the loader would truly open it.
A name-fold instead would let an uppercase MODEL.SAFETENSORS suppress the scan on Linux while the
loader, asking for the canonical lowercase name, silently falls through to a pickle index."""
return (root / name).is_file()
def _cached_pickle_weight_files(snapshot: Path) -> list:
"""Pickle weight files in snapshot's ST load roots, EXCLUDING those whose weight family also
ships an inert safetensors in the same dir (the loader prefers it): a base pickle is suppressed
only by a base model.safetensors, an adapter pickle only by adapter_model.safetensors -- an
unrelated safetensors is no substitute. Load roots only. Raises OSError if the snapshot root is
unreadable (caller blocks)."""
"""Pickle weight files a SentenceTransformer/Transformers load deserializes from snapshot's ST
load roots, EXCLUDING those whose weight family also ships an inert safetensors in the same dir
(the loader prefers it): a base pickle is suppressed only by a base model.safetensors, an adapter
pickle only by adapter_model.safetensors -- an unrelated safetensors is no substitute. Covers
both direct-child pickles AND pickle shards referenced by a local weight index (which the loader
follows into nested dirs, matching the online gate). Raises OSError -- caller fails CLOSED -- if
the snapshot root or a weight index is unreadable, or an index reference escapes the snapshot."""
blocked = []
seen = set()
def _add(path: Path):
key = str(path)
if key not in seen:
seen.add(key)
blocked.append(path)
for root in _st_load_roots(snapshot):
try:
entries = [p for p in root.iterdir() if p.is_file()]
@ -327,15 +382,35 @@ def _cached_pickle_weight_files(snapshot: Path) -> list:
if root == snapshot:
raise # top-level unreadable -> fail closed
continue # unreadable module subdir: nothing loadable to attest here
has_base_safetensors = any(_BASE_SAFETENSORS_RE.match(p.name) for p in entries)
has_adapter_safetensors = any(_ADAPTER_SAFETENSORS_RE.match(p.name) for p in entries)
# Safetensors alternatives the loader would actually resolve (never a bare name-fold, which
# fails OPEN: see _loader_resolves). A base pickle is replaced only by a base safetensors, an
# adapter pickle only by an adapter one. A single model.safetensors also outranks BOTH indexes.
has_direct_base_safetensors = _loader_resolves(root, "model.safetensors")
has_base_safetensors = has_direct_base_safetensors or _loader_resolves(
root, "model.safetensors.index.json"
)
has_adapter_safetensors = _loader_resolves(root, "adapter_model.safetensors")
for path in entries:
if not _PICKLE_WEIGHT_RE.match(path.name):
continue
is_adapter = path.name.lower().startswith("adapter_model")
has_alternative = has_adapter_safetensors if is_adapter else has_base_safetensors
if not has_alternative:
blocked.append(path)
_add(path)
# A torch weight index makes from_pretrained load nested shards iterdir never sees; the loader
# torch.loads any not ending in .safetensors. Probe the canonical index name with the loader's
# own lookup (_loader_resolves), so an oddly-cased artifact it would never open does not block.
# A direct model.safetensors wins over BOTH indexes; failing that a base safetensors still
# outranks the pytorch index, while a safetensors index is itself the chosen archive.
for index_name in _TORCH_INDEX_FILES:
if not _loader_resolves(root, index_name):
continue
if has_direct_base_safetensors:
continue
if index_name == "pytorch_model.bin.index.json" and has_base_safetensors:
continue
for shard_path in _indexed_pickle_shards(root / index_name, root, snapshot):
_add(shard_path)
return blocked