Skip to content

Studio: honor custom HF_HOME for model download and load - #6510

Merged
danielhanchen merged 5 commits into
mainfrom
fix/hf-home-hub-cache
Jun 22, 2026
Merged

danielhanchen merged 5 commits into
mainfrom
fix/hf-home-hub-cache

Conversation

@shimmyshimmer

Copy link
Copy Markdown
Member

Fixes #5182.

Problem

When HF_HOME points 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/huggingface instead and re-downloads from scratch. Reported on the issue by @ivanfioravanti.

Cause

_setup_cache_env() in studio/backend/utils/paths/storage_roots.py always derived the HF caches from XDG_CACHE_HOME / ~/.cache and ignored a user-set HF_HOME:

hf_default = xdg_cache / "huggingface"
defaults = {
    "HF_HOME":      str(hf_default),
    "HF_HUB_CACHE": str(hf_default / "hub"),   # forced to ~/.cache, not $HF_HOME/hub
    "HF_XET_CACHE": str(hf_default / "xet"),
    ...
}

It sets HF_HUB_CACHE explicitly, and that variable takes precedence over HF_HOME in huggingface_hub, so the hub cache (where models live) is pinned to the standard location even though HF_HOME points elsewhere. The download workers call snapshot_download without a cache_dir on both the Xet path and the HTTP-fallback path, so both inherit the wrong root.

Fix

Seed HF_HUB_CACHE and HF_XET_CACHE from HF_HOME when the user set it (HF's own defaults are $HF_HOME/hub and $HF_HOME/xet), and honor the legacy HUGGINGFACE_HUB_CACHE alias. Explicit HF_HUB_CACHE / HF_XET_CACHE are 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:

Env the user set HF_HUB_CACHE before HF_HUB_CACHE after
only HF_HOME=/custom ~/.cache/huggingface/hub /custom/hub
nothing ~/.cache/huggingface/hub ~/.cache/huggingface/hub (unchanged)
explicit HF_HUB_CACHE preserved preserved
legacy HUGGINGFACE_HUB_CACHE overridden preserved

Added studio/backend/tests/test_setup_cache_env_hf_home.py covering all four cases (4 passed).

_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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +289 to +290
hf_home = os.environ.get("HF_HOME")
hf_base = Path(hf_home).expanduser() if hf_home else xdg_cache / "huggingface"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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"

Comment on lines +11 to +13
import importlib.util
import sys
from pathlib import Path

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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"))

pre-commit-ci Bot and others added 2 commits June 20, 2026 12:43
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.
@shimmyshimmer

Copy link
Copy Markdown
Member Author

Simulated the fix against the real huggingface_hub in an isolated venv (no network). Summary:

Assumptions verified against real huggingface_hub (both the floor 0.34.0 and current 1.20.1), each in a fresh subprocess since the cache constants freeze at import:

  • HF_HUB_CACHE overrides HF_HOME (so pinning it to the standard path is the bug)
  • HF's own defaults are HF_HUB_CACHE=$HF_HOME/hub and HF_XET_CACHE=$HF_HOME/xet (the fix seeds the identical values)
  • HUGGINGFACE_HUB_CACHE is honored, so the fix preserves it

End to end (no network): placed a model in the HF cache layout under a custom HF_HOME, then ran the real _setup_cache_env() and try_to_load_from_cache(). Old logic resolved to the standard cache and the model was not found (re-download); the fix resolved to HF_HOME and the model was found (no re-download).

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 _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. Made the mkdir best-effort so the env var is still set and HF reports a clear error at download time. Added a regression test for it. Full suite for this module is 5 passing.

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.
@shimmyshimmer

Copy link
Copy Markdown
Member Author

Both review points were correct and are now addressed in 2cd5da1.

  1. Whitespace HF_HOME: verified that HF_HOME=" " resolved HF_HUB_CACHE to ' /hub' (a relative dir named space). Now stripping with (os.environ.get("HF_HOME") or "").strip(), matching studio_root() on line 45, so a blank value falls back to the platform default. Added a regression test.

  2. Test isolation: confirmed the tests were creating UV_CACHE_DIR / VLLM_CACHE_ROOT under the real ~/.unsloth/studio/cache because UNSLOTH_STUDIO_HOME was unset. Added an autouse fixture that points it at tmp_path, so the tests no longer touch the real home.

Module suite is now 6 passing.

@danielhanchen

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: d6ff398512

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

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".

@danielhanchen
danielhanchen merged commit 7bd8e64 into main Jun 22, 2026
32 checks passed
@danielhanchen
danielhanchen deleted the fix/hf-home-hub-cache branch June 22, 2026 15:22
@ivanfioravanti

Copy link
Copy Markdown

Thanks @danielhanchen 🙌🏻

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Install / download location

3 participants