-
Notifications
You must be signed in to change notification settings - Fork 213
fix: re-download model when cached files fail verification #642
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Dmheath1
wants to merge
2
commits into
qdrant:main
Choose a base branch
from
Dmheath1:fix/redownload-on-verification-failure
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| """Offline cache-verification tests for ModelManagement. | ||
|
|
||
| The offline probe must distinguish a corrupt *model* file (which would make ONNX Runtime | ||
| fail with a cryptic protobuf error) from a benign size drift on an auxiliary file. A corrupt | ||
| model file must raise so ``download_model`` falls through to a forced re-download; an auxiliary | ||
| mismatch must be tolerated so an offline caller is not bricked by best-effort-loadable drift. | ||
| Model files may live in a subdirectory (e.g. ``onnx/model.onnx``), so matching is done on the | ||
| repo-relative path, not the bare filename. | ||
| """ | ||
|
|
||
| import json | ||
| from pathlib import Path | ||
| from typing import Any | ||
|
|
||
| import pytest | ||
|
|
||
| from fastembed.common import model_management | ||
| from fastembed.common.model_management import ModelManagement | ||
|
|
||
| REVISION = "0123456789abcdef0123456789abcdef01234567" | ||
|
|
||
|
|
||
| def _seed_cache(cache: Path, files: dict[str, bytes], metadata: dict[str, Any]) -> Path: | ||
| snapshot = cache / "models--qdrant--fake-onnx" | ||
| for name, blob in files.items(): | ||
| path = snapshot / name | ||
| path.parent.mkdir(parents=True, exist_ok=True) | ||
| path.write_bytes(blob) | ||
| snapshot.mkdir(parents=True, exist_ok=True) | ||
| (snapshot / ModelManagement.METADATA_FILE).write_text(json.dumps(metadata)) | ||
| return snapshot | ||
|
|
||
|
|
||
| def test_offline_probe_raises_when_model_file_is_corrupt(tmp_path: Path) -> None: | ||
| cache = tmp_path / "cache" | ||
| _seed_cache( | ||
| cache, | ||
| files={"model.onnx": b"x" * 10}, | ||
| metadata={"model.onnx": {"size": 999_999, "blob_id": "deadbeef"}}, | ||
| ) | ||
|
|
||
| with pytest.raises(ValueError, match="corrupt"): | ||
| ModelManagement.download_files_from_huggingface( | ||
| "qdrant/fake-onnx", | ||
| cache_dir=str(cache), | ||
| extra_patterns=["model.onnx"], | ||
| local_files_only=True, | ||
| ) | ||
|
|
||
|
|
||
| def test_offline_probe_raises_when_subdir_model_file_is_corrupt(tmp_path: Path) -> None: | ||
| cache = tmp_path / "cache" | ||
| key = f"snapshots/{REVISION}/onnx/model.onnx" | ||
| _seed_cache( | ||
| cache, | ||
| files={key: b"x" * 10}, | ||
| metadata={key: {"size": 999_999, "blob_id": "deadbeef"}}, | ||
| ) | ||
|
|
||
| with pytest.raises(ValueError, match="corrupt"): | ||
| ModelManagement.download_files_from_huggingface( | ||
| "qdrant/fake-onnx", | ||
| cache_dir=str(cache), | ||
| extra_patterns=["onnx/model.onnx"], | ||
| local_files_only=True, | ||
| ) | ||
|
|
||
|
|
||
| def test_offline_probe_tolerates_auxiliary_file_drift( | ||
| tmp_path: Path, monkeypatch: pytest.MonkeyPatch | ||
| ) -> None: | ||
| cache = tmp_path / "cache" | ||
| snapshot = _seed_cache( | ||
| cache, | ||
| files={"model.onnx": b"x" * 10, "config.json": b"y" * 20}, | ||
| metadata={ | ||
| "model.onnx": {"size": 10, "blob_id": "modelblob"}, | ||
| "config.json": {"size": 999, "blob_id": "configblob"}, | ||
| }, | ||
| ) | ||
|
|
||
| # The model file matches metadata; only config.json drifted. The probe must NOT raise: | ||
| # it falls through to snapshot_download (mocked here to return the cached path). | ||
| monkeypatch.setattr(model_management, "snapshot_download", lambda **kwargs: str(snapshot)) | ||
|
|
||
| result = ModelManagement.download_files_from_huggingface( | ||
| "qdrant/fake-onnx", | ||
| cache_dir=str(cache), | ||
| extra_patterns=["model.onnx"], | ||
| local_files_only=True, | ||
| ) | ||
| assert result == str(snapshot) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
huggingface_hub list_repo_tree recursive parameter💡 Result:
In huggingface_hub,
HfApi.list_repo_tree()does not provide arecursiveparameter. Instead, you control what is listed via its other arguments (notablypath/subdirectory scope) and you can get expanded metadata depending on theexpandflag. Hugging Face staff also note that listing files in a repo is costly and that “it is not possible to filter them server-side when listing files from a repo… best solution is still to do the filtering client-side”; however, you can limit the request by only listing files from a subdirectory [1]. Practical implication for your question: if you want a recursive listing, you should treatlist_repo_tree()as the API meant for listing repo trees, and use the supported arguments to restrict scope (e.g., target a subdirectory path). Don’t look for arecursiveparameter; it isn’t part of the documented method signature [1]. References: Hugging Face issue discussion explaining server-side filtering limits forlist_repo_tree[1]; and the officialHfApidocumentation entry point (for method signatures and related API usage) [2].Citations:
Fix
list_repo_treecall: remove unsupportedrecursive=True.fastembed/common/model_management.py(lines 262-268) passesrecursive=Truetohuggingface_hub.list_repo_tree(), butHfApi.list_repo_tree()has norecursiveparameter; this will raise aTypeErrorat runtime. Rework the listing to use supported arguments (e.g., subdirectory scoping viapathand/orexpand, then filter client-side) to collect nested model files.🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
list_repo_treedoes takerecursive. It is in the signature (recursive: bool = False) and has been since the method landed in huggingface_hub 0.23.0, which is also the floor this function already needs, since it callslist_repo_treeregardless of this PR. So there is no new version constraint here. The linked #2676 is about server-side filtering on large repos, not whether the parameter exists. Verified on 1.17.0: the call returns the nested files (onnx/model.onnxplus the quantized variants) without raising, so I have leftrecursive=Trueas is.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Length of output: 8970
🏁 Script executed:
Length of output: 717
@Dmheath1, thanks for the thorough clarification — my initial flag was wrong and I've noted the correction. Unfortunately I don't have permission to resolve the thread programmatically in this repo, so you'd need to resolve it manually. Therecursive=Trueusage is correct and the review comment can be dismissed.(ᵔ ᵕ ᵔ)🐇
🧬 Code Graph Analysis Results
fastembed/common/model_management.pyLines 11-12 (imports
list_repo_tree):Lines 261-277 (calls
list_repo_tree(..., recursive=True)and buildsrepo_filesfrom the returned tree):✏️ Learnings added