-
Notifications
You must be signed in to change notification settings - Fork 0
fix(backends): surface real cause on HF model load failures #21
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
base: main
Are you sure you want to change the base?
Changes from all commits
84a5deb
ec9ac7d
2eeb35b
f8eb23e
7359ca6
3ce22d7
392e824
b5483c7
a33fb6d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| v0.4.5 | ||
| v0.4.6 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: missing trailing newline ( in the diff). POSIX convention — most tools handle it fine, but 0 will report 0 lines and some CI linters flag it. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| """Shared HuggingFace Hub load-error handler for vision backends.""" | ||
| from contextlib import contextmanager | ||
|
|
||
| # Import from huggingface_hub.utils — this path has been stable since hub ~0.16 | ||
| # and is available at the declared floor (>=0.23), unlike huggingface_hub.errors | ||
| # which was introduced later. Both modules expose the same classes. | ||
| from huggingface_hub.utils import ( | ||
| EntryNotFoundError, | ||
| GatedRepoError, | ||
| HfHubHTTPError, | ||
| LocalEntryNotFoundError, | ||
| RepositoryNotFoundError, | ||
| RevisionNotFoundError, | ||
| ) | ||
|
|
||
| # Allowlist of known optional dependencies that produce ImportError. | ||
| # Maps lower-cased dep name → install hint shown to the operator. | ||
| _MISSING_DEP_HINTS = { | ||
| "timm": "pip install timm (needed by DETR / Conditional DETR)", | ||
| "sentencepiece": "pip install sentencepiece", | ||
| "detectron2": "pip install detectron2 — see upstream install guide", | ||
| "torchvision": "pip install torchvision", | ||
| } | ||
|
|
||
|
|
||
| @contextmanager | ||
| def hf_load_error_handler(model_id: str, revision: str, task: str): | ||
| """Wraps AutoImageProcessor + AutoModelFor*.from_pretrained calls with | ||
| targeted, actionable error messages. | ||
|
|
||
| Catch order matters: | ||
| - GatedRepoError before RepositoryNotFoundError (it is a subclass). | ||
| - RepositoryNotFoundError and RevisionNotFoundError before HfHubHTTPError | ||
| (both are subclasses). | ||
| - (LocalEntryNotFoundError, EntryNotFoundError) before HfHubHTTPError | ||
| and before the `except ValueError` fallback: on huggingface-hub 0.23 | ||
| (our declared floor, with the `requests` backbone) both ARE subclasses | ||
| of HfHubHTTPError, and LocalEntryNotFoundError is additionally a | ||
| subclass of ValueError, so any reorder would silently reroute them to | ||
| a less helpful branch. (Hub >=1.0 flattened the hierarchy; keeping the | ||
| order preserves correct behavior on both eras.) | ||
| """ | ||
| try: | ||
| yield | ||
| except ImportError as e: | ||
| msg = str(e).lower() | ||
| for dep, hint in _MISSING_DEP_HINTS.items(): | ||
| if dep in msg: | ||
| raise ImportError( | ||
| f"Model '{model_id}' requires '{dep}': {hint}. " | ||
| f"Original: {repr(e)}" | ||
| ) from e | ||
| raise | ||
| except GatedRepoError as e: | ||
| raise RuntimeError( | ||
| f"Access to '{model_id}' requires accepting its license on the Hub. " | ||
| f"Accept it with an authenticated token " | ||
| f"(set HF_TOKEN environment variable). Detail: {repr(e)}" | ||
| ) from e | ||
| except RepositoryNotFoundError as e: | ||
| raise RuntimeError( | ||
| f"HuggingFace repository '{model_id}' not found. " | ||
| f"Check the model ID or your HF_TOKEN if the repo is gated. " | ||
| f"Detail: {repr(e)}" | ||
| ) from e | ||
| except RevisionNotFoundError as e: | ||
| raise RuntimeError( | ||
| f"Revision '{revision}' not found in '{model_id}'. " | ||
| f"Check the revision (commit SHA, tag, or branch). " | ||
| f"Detail: {repr(e)}" | ||
| ) from e | ||
| # Catch order is load-bearing here: at hub 0.23 (our declared floor) these | ||
| # are subclasses of HfHubHTTPError, and LocalEntryNotFoundError is also a | ||
| # subclass of ValueError; do not reorder this branch below either of them. | ||
| except (LocalEntryNotFoundError, EntryNotFoundError) as e: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two items on this handler:
# Must stay above except ValueError — LocalEntryNotFoundError inherits ValueError at hub 0.23I verified against hub 1.7.1 that the hierarchy was flattened (neither class inherits from
Neither is blocking, but both are easy fixes while you're here.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added the one-line guard comment above the |
||
| raise RuntimeError( | ||
| f"Model entry for '{model_id}@{revision}' could not be resolved: the " | ||
| f"repository may be missing the expected file, or the local cache may " | ||
| f"be incomplete. If running offline, pre-download the model with " | ||
| f"`huggingface-cli download {model_id} --revision {revision}` before " | ||
| f"starting the filter; if concurrent workers share a cache, retry " | ||
| f"after the first worker completes. Detail: {repr(e)}" | ||
| ) from e | ||
|
Comment on lines
+75
to
+83
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Follow-up to the docstring nit above — The existing
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Already in flight: you implemented this on refactor/dry-hf-load-error-tests in #23, which tightens assertIn("cache") to assertIn("not found in cache") in both test files. That is exactly what I was about to push. Since your commit also extracts the shared HFLoadErrorTestsMixin and deletes the two target test classes here, landing my duplicate would just force you to re-rebase, so leaving the assertion tightening to land with your refactor. (Did confirm the substring change has teeth by running a reorder mutation at hub 0.23 in a throwaway venv: the loose assertion passes in both orderings, the tightened one flips red when
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| except HfHubHTTPError as e: | ||
| status = getattr(getattr(e, "response", None), "status_code", None) | ||
| if status == 401: | ||
| raise RuntimeError( | ||
| f"Unauthorized loading '{model_id}@{revision}' — the model may be " | ||
| f"gated or require an HF_TOKEN. Set HF_TOKEN and retry. Detail: {repr(e)}" | ||
| ) from e | ||
| raise RuntimeError( | ||
| f"Could not download '{model_id}@{revision}' from HuggingFace Hub: {repr(e)}" | ||
| ) from e | ||
| except ValueError as e: | ||
| raise RuntimeError( | ||
| f"Model '{model_id}@{revision}' could not be loaded for {task}. " | ||
| f"Detail: {repr(e)}" | ||
| ) from e | ||
| except Exception as e: | ||
| raise RuntimeError( | ||
| f"Unexpected failure loading '{model_id}@{revision}' for {task}: {repr(e)}" | ||
| ) from e | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Follow-up suggestion:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Applied in 1f3de3d. Both |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| """Shared test utilities for HuggingFace Hub error-handling tests.""" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: this bare import (
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Applied in 1f3de3d. Added |
||
| from unittest.mock import MagicMock | ||
|
|
||
|
|
||
| def make_hf_error(cls, message, status_code=404): | ||
| """Build a huggingface_hub error instance with a minimal mock response | ||
| attached, suitable for use in backend load-error tests. | ||
|
|
||
| Pass `status_code` when a test needs to simulate something other than 404 | ||
| (e.g. 401 for gated access, 503 for transient Hub failures). | ||
| """ | ||
| mock_response = MagicMock() | ||
| mock_response.status_code = status_code | ||
| return cls(message, response=mock_response) | ||
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.
Ultra-nit: missing trailing newline (
\ No newline at end of filein the diff). Some Unix tools (e.g.wc -l, POSIXread) behave unexpectedly without it.