Studio: honor custom HF_HOME for model download and load - #6510
Conversation
_setup_cache_env always derived HF_HUB_CACHE and HF_XET_CACHE from XDG_CACHE_HOME / ~/.cache, ignoring a user-set HF_HOME. Because it sets HF_HUB_CACHE explicitly and that variable takes precedence over HF_HOME in huggingface_hub, the hub cache was pinned to the standard location: a model already present under a custom HF_HOME was detected but then re-downloaded from scratch on load. Seed HF_HUB_CACHE and HF_XET_CACHE from HF_HOME when the user set it (HF's own default is $HF_HOME/hub and $HF_HOME/xet), and honor the legacy HUGGINGFACE_HUB_CACHE alias. The hub download workers call snapshot_download without a cache_dir for both the Xet and HTTP-fallback paths, so they follow HF_HUB_CACHE; fixing it here unifies detection and both transports on one root. Explicit HF_HUB_CACHE / HF_XET_CACHE stay untouched. Adds tests for the custom-HF_HOME, default, explicit-override, and legacy-alias cases. Fixes #5182.
There was a problem hiding this comment.
Code Review
This pull request updates _setup_cache_env() to correctly seed HF_HUB_CACHE and HF_XET_CACHE from a user-set HF_HOME environment variable, falling back to platform defaults if unset, and honors the legacy HUGGINGFACE_HUB_CACHE alias. It also adds a comprehensive test suite for these changes. Feedback includes stripping whitespace from HF_HOME to prevent unexpected directory creation and mocking UNSLOTH_STUDIO_HOME in tests to avoid side effects in the developer's real home directory.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| hf_home = os.environ.get("HF_HOME") | ||
| hf_base = Path(hf_home).expanduser() if hf_home else xdg_cache / "huggingface" |
There was a problem hiding this comment.
If HF_HOME is set to a whitespace-only string (e.g., " "), hf_home will evaluate to True, leading to unexpected directory creation and path resolution (e.g., paths starting with spaces).
To prevent this and maintain consistency with how other environment variables are retrieved in this file (such as UNSLOTH_STUDIO_HOME on line 45), we should strip the environment variable value and handle empty/whitespace-only values safely.
| hf_home = os.environ.get("HF_HOME") | |
| hf_base = Path(hf_home).expanduser() if hf_home else xdg_cache / "huggingface" | |
| hf_home = (os.environ.get("HF_HOME") or "").strip() | |
| hf_base = Path(hf_home).expanduser() if hf_home else xdg_cache / "huggingface" |
| import importlib.util | ||
| import sys | ||
| from pathlib import Path |
There was a problem hiding this comment.
Calling _setup_cache_env() creates UV_CACHE_DIR and VLLM_CACHE_ROOT directories under the user's real home directory (~/.unsloth/studio/cache/...) because UNSLOTH_STUDIO_HOME is not mocked/set in the tests.
To keep the tests isolated and prevent polluting the developer's or CI environment's home directory, we should add an autouse fixture that mocks UNSLOTH_STUDIO_HOME to a safe subdirectory of tmp_path.
import importlib.util
import sys
from pathlib import Path
import pytest
@pytest.fixture(autouse=True)
def mock_studio_home(monkeypatch, tmp_path):
monkeypatch.setenv("UNSLOTH_STUDIO_HOME", str(tmp_path / "studio"))for more information, see https://pre-commit.ci
Seeding HF_HUB_CACHE/HF_XET_CACHE from HF_HOME means _setup_cache_env now mkdir's under a user-controlled path. A non-writable or not-yet-mounted HF_HOME (typo, offline drive) would raise and crash startup, where the old code silently fell back. Make the mkdir best-effort; the env var is still set, so HF reports a clear error at download time. Adds a regression test.
|
Simulated the fix against the real huggingface_hub in an isolated venv (no network). Summary: Assumptions verified against real huggingface_hub (both the floor
End to end (no network): placed a model in the HF cache layout under a custom HF_HOME, then ran the real Cross-OS + edge cases: path derivation holds for Windows (drive, UserProfile, UNC) and POSIX via pathlib; fuzzed HF_HOME shapes (trailing slash, tilde, spaces, unicode, deep nesting, empty -> default, explicit override, legacy alias) all resolve correctly. Not browser-facing, so no browser matrix applies here. One issue found and fixed (new commit): seeding the caches from HF_HOME means |
Address review: a whitespace-only HF_HOME no longer derives " /hub"; strip it and fall back to the default (matches studio_root). Tests set UNSLOTH_STUDIO_HOME to a tmp dir so _setup_cache_env's UV/VLLM mkdirs do not touch the real ~/.unsloth/studio. Adds a whitespace regression test.
|
Both review points were correct and are now addressed in 2cd5da1.
Module suite is now 6 passing. |
for more information, see https://pre-commit.ci
|
@codex review |
|
Codex Review: Didn't find any major issues. Keep it up! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
Thanks @danielhanchen 🙌🏻 |
Fixes #5182.
Problem
When
HF_HOMEpoints at a non-standard location (for example/Users/Shared/.cache/huggingface), Studio detects an already-downloaded model there, but loading it searches the standard~/.cache/huggingfaceinstead and re-downloads from scratch. Reported on the issue by @ivanfioravanti.Cause
_setup_cache_env()instudio/backend/utils/paths/storage_roots.pyalways derived the HF caches fromXDG_CACHE_HOME/~/.cacheand ignored a user-setHF_HOME:It sets
HF_HUB_CACHEexplicitly, and that variable takes precedence overHF_HOMEinhuggingface_hub, so the hub cache (where models live) is pinned to the standard location even thoughHF_HOMEpoints elsewhere. The download workers callsnapshot_downloadwithout acache_diron both the Xet path and the HTTP-fallback path, so both inherit the wrong root.Fix
Seed
HF_HUB_CACHEandHF_XET_CACHEfromHF_HOMEwhen the user set it (HF's own defaults are$HF_HOME/huband$HF_HOME/xet), and honor the legacyHUGGINGFACE_HUB_CACHEalias. ExplicitHF_HUB_CACHE/HF_XET_CACHEare still left untouched, so user overrides keep working. This unifies detection and both download transports on a single cache root.Verified
Against the real
_setup_cache_env:HF_HOME=/custom~/.cache/huggingface/hub/custom/hub~/.cache/huggingface/hub~/.cache/huggingface/hub(unchanged)HF_HUB_CACHEHUGGINGFACE_HUB_CACHEAdded
studio/backend/tests/test_setup_cache_env_hf_home.pycovering all four cases (4 passed).