fix: probe HF cache before model_info for local-only GGUF saves (#7481)
Always resolve tokenizer.model from the local Hub cache before calling model_info, and skip Hub metadata when the tokenizer was loaded with local_files_only or offline env vars. Fixes Codex review on PR #7482.
This commit is contained in:
parent
222a416480
commit
b41bbbef7d
4 changed files with 91 additions and 28 deletions
|
|
@ -95,8 +95,6 @@ def test_real_cached_unsloth_helpers_offline(monkeypatch):
|
|||
_offline_env(monkeypatch)
|
||||
_block_network(monkeypatch)
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
from unsloth.models.loader_utils import _load_pretrained_tokenizer_fast
|
||||
from unsloth.save import _has_tokenizer_model
|
||||
|
||||
|
|
@ -106,4 +104,4 @@ def test_real_cached_unsloth_helpers_offline(monkeypatch):
|
|||
cache_dir = str(CACHE_ROOT / "hub"),
|
||||
)
|
||||
assert tok.vocab_size > 0
|
||||
assert _has_tokenizer_model(SimpleNamespace(name_or_path = REPO, tokenizer = None)) is True
|
||||
assert _has_tokenizer_model(tok) is True
|
||||
|
|
|
|||
|
|
@ -180,3 +180,39 @@ def test_has_tokenizer_model_offline_skips_model_info(tmp_path, monkeypatch):
|
|||
with patch("huggingface_hub.HfApi.model_info") as model_info:
|
||||
model_info.side_effect = AssertionError("model_info must not run offline")
|
||||
assert _has_tokenizer_model(tok, token = None) is False
|
||||
|
||||
|
||||
def test_has_tokenizer_model_probes_cache_before_model_info(tmp_path, monkeypatch):
|
||||
from unsloth.save import _TOKENIZER_MODEL_CACHE, _has_tokenizer_model
|
||||
|
||||
snap = _write_gemma4_cache(tmp_path)
|
||||
(snap / "tokenizer.model").write_bytes(b"sp-model")
|
||||
monkeypatch.delenv("HF_HUB_OFFLINE", raising = False)
|
||||
monkeypatch.delenv("TRANSFORMERS_OFFLINE", raising = False)
|
||||
monkeypatch.setenv("HF_HUB_CACHE", str(tmp_path))
|
||||
_TOKENIZER_MODEL_CACHE.clear()
|
||||
|
||||
tok = SimpleNamespace(name_or_path = _REPO)
|
||||
|
||||
with patch("huggingface_hub.HfApi.model_info") as model_info:
|
||||
model_info.side_effect = AssertionError("model_info must not run when cache hit")
|
||||
assert _has_tokenizer_model(tok, token = None) is True
|
||||
|
||||
|
||||
def test_has_tokenizer_model_local_files_only_skips_model_info(tmp_path, monkeypatch):
|
||||
from unsloth.save import _TOKENIZER_MODEL_CACHE, _has_tokenizer_model
|
||||
|
||||
_write_gemma4_cache(tmp_path)
|
||||
monkeypatch.delenv("HF_HUB_OFFLINE", raising = False)
|
||||
monkeypatch.delenv("TRANSFORMERS_OFFLINE", raising = False)
|
||||
monkeypatch.setenv("HF_HUB_CACHE", str(tmp_path))
|
||||
_TOKENIZER_MODEL_CACHE.clear()
|
||||
|
||||
tok = SimpleNamespace(
|
||||
name_or_path = _REPO,
|
||||
init_kwargs = {"local_files_only": True},
|
||||
)
|
||||
|
||||
with patch("huggingface_hub.HfApi.model_info") as model_info:
|
||||
model_info.side_effect = AssertionError("model_info must not run with local_files_only")
|
||||
assert _has_tokenizer_model(tok, token = None) is False
|
||||
|
|
|
|||
|
|
@ -843,6 +843,14 @@ def _get_effective_local_files_only(kwargs):
|
|||
return _env_says_offline()
|
||||
|
||||
|
||||
def _tokenizer_wants_local_only(tokenizer):
|
||||
"""True when Hub metadata probes should be skipped for this tokenizer."""
|
||||
if _env_says_offline():
|
||||
return True
|
||||
init_kwargs = getattr(tokenizer, "init_kwargs", None) or {}
|
||||
return bool(init_kwargs.get("local_files_only"))
|
||||
|
||||
|
||||
def _is_offline_related_error(exc):
|
||||
"""True if exc (or its cause/context chain) is a lost-connection error, not a
|
||||
missing file. Plain FileNotFoundError propagates; LocalEntryNotFoundError is offline."""
|
||||
|
|
|
|||
|
|
@ -52,7 +52,12 @@ import traceback
|
|||
import psutil
|
||||
import re
|
||||
from transformers.models.llama.modeling_llama import logger
|
||||
from .models.loader_utils import get_model_name, _env_says_offline, _resolve_hub_repo_cached_file
|
||||
from .models.loader_utils import (
|
||||
get_model_name,
|
||||
_env_says_offline,
|
||||
_resolve_hub_repo_cached_file,
|
||||
_tokenizer_wants_local_only,
|
||||
)
|
||||
from .models._utils import _convert_torchao_model
|
||||
from .ollama_template_mappers import OLLAMA_TEMPLATES, MODEL_TO_OLLAMA_TEMPLATE_MAPPER
|
||||
from transformers import ProcessorMixin, PreTrainedTokenizerBase
|
||||
|
|
@ -446,18 +451,25 @@ def _has_tokenizer_model(tokenizer, token = None):
|
|||
if source in _TOKENIZER_MODEL_CACHE:
|
||||
return _TOKENIZER_MODEL_CACHE[source]
|
||||
|
||||
# Offline: probe the local cache instead of model_info (issue #7481).
|
||||
if _env_says_offline():
|
||||
cached_path = _resolve_hub_repo_cached_file(
|
||||
source,
|
||||
"tokenizer.model",
|
||||
token = token,
|
||||
local_files_only = True,
|
||||
cache_dir = os.environ.get("HF_HUB_CACHE"),
|
||||
)
|
||||
if cached_path is not None:
|
||||
_TOKENIZER_MODEL_CACHE[source] = True
|
||||
return True
|
||||
# Hub repo id: probe local cache before model_info (issue #7481).
|
||||
cache_dir = os.environ.get("HF_HUB_CACHE")
|
||||
if not cache_dir:
|
||||
hf_home = os.environ.get("HF_HOME")
|
||||
if hf_home:
|
||||
cache_dir = os.path.join(hf_home, "hub")
|
||||
|
||||
cached_path = _resolve_hub_repo_cached_file(
|
||||
source,
|
||||
"tokenizer.model",
|
||||
token = token,
|
||||
local_files_only = True,
|
||||
cache_dir = cache_dir,
|
||||
)
|
||||
if cached_path is not None:
|
||||
_TOKENIZER_MODEL_CACHE[source] = True
|
||||
return True
|
||||
|
||||
if _tokenizer_wants_local_only(tokenizer):
|
||||
return False
|
||||
|
||||
try:
|
||||
|
|
@ -519,15 +531,19 @@ def _preserve_sentencepiece_tokenizer_assets(
|
|||
if os.path.isfile(local_path):
|
||||
downloaded_path = local_path
|
||||
else:
|
||||
cached_path = None
|
||||
if _env_says_offline():
|
||||
cached_path = _resolve_hub_repo_cached_file(
|
||||
source,
|
||||
"tokenizer.model",
|
||||
token = token,
|
||||
local_files_only = True,
|
||||
cache_dir = os.environ.get("HF_HUB_CACHE"),
|
||||
)
|
||||
cache_dir = os.environ.get("HF_HUB_CACHE")
|
||||
if not cache_dir:
|
||||
hf_home = os.environ.get("HF_HOME")
|
||||
if hf_home:
|
||||
cache_dir = os.path.join(hf_home, "hub")
|
||||
|
||||
cached_path = _resolve_hub_repo_cached_file(
|
||||
source,
|
||||
"tokenizer.model",
|
||||
token = token,
|
||||
local_files_only = True,
|
||||
cache_dir = cache_dir,
|
||||
)
|
||||
if cached_path is not None:
|
||||
downloaded_path = cached_path
|
||||
else:
|
||||
|
|
@ -537,8 +553,8 @@ def _preserve_sentencepiece_tokenizer_assets(
|
|||
repo_id = source,
|
||||
filename = "tokenizer.model",
|
||||
token = token,
|
||||
local_files_only = _env_says_offline(),
|
||||
cache_dir = os.environ.get("HF_HUB_CACHE"),
|
||||
local_files_only = _tokenizer_wants_local_only(tokenizer),
|
||||
cache_dir = cache_dir,
|
||||
)
|
||||
except Exception:
|
||||
downloaded_path = None
|
||||
|
|
@ -3821,7 +3837,12 @@ def unsloth_convert_lora_to_ggml_and_save_locally(
|
|||
return _unsloth_save_lora_gguf(self, tokenizer, save_directory, outtype = outtype)
|
||||
|
||||
|
||||
from .models.loader_utils import get_model_name, _env_says_offline, _resolve_hub_repo_cached_file
|
||||
from .models.loader_utils import (
|
||||
get_model_name,
|
||||
_env_says_offline,
|
||||
_resolve_hub_repo_cached_file,
|
||||
_tokenizer_wants_local_only,
|
||||
)
|
||||
from unsloth_zoo.saving_utils import (
|
||||
merge_and_overwrite_lora,
|
||||
prepare_saving,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue