🗂️ Cache Refactor - use platformdirs helper (#201)

Co-authored-by: Anthony Wu <pls-file-gh-issue@users.noreply.github.com>
This commit is contained in:
Anthony Wu 2025-06-27 04:59:43 -07:00 committed by GitHub
parent 935d482ffb
commit 6c3ba1e065
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 90 additions and 25 deletions

View File

@ -163,13 +163,9 @@ This is useful for integrating MFLUX into shell scripts or dynamically generatin
⚠️ *If the specific model is not already downloaded on your machine, it will start the download process and fetch the model weights (~34GB in size for the Schnell or Dev model respectively). See the [quantization](#%EF%B8%8F-quantization) section for running compressed versions of the model.* ⚠️ ⚠️ *If the specific model is not already downloaded on your machine, it will start the download process and fetch the model weights (~34GB in size for the Schnell or Dev model respectively). See the [quantization](#%EF%B8%8F-quantization) section for running compressed versions of the model.* ⚠️
*By default, model files are downloaded to the `.cache` folder within your home directory. For example, in my setup, the path looks like this:* *By default, mflux caches files in `~/Library/Caches/mflux/`. The Hugging Face model files themselves are cached separately in the Hugging Face cache directory (e.g., `~/.cache/huggingface/`).*
``` *To change the mflux cache location, set the `MFLUX_CACHE_DIR` environment variable. To change the Hugging Face cache location, you can modify the `HF_HOME` environment variable. For more details on Hugging Face cache settings, please refer to the [Hugging Face documentation](https://huggingface.co/docs/huggingface_hub/en/package_reference/environment_variables)*.
/Users/filipstrand/.cache/huggingface/hub/models--black-forest-labs--FLUX.1-dev
```
*To change this default behavior, you can do so by modifying the `HF_HOME` environment variable. For more details on how to adjust this setting, please refer to the [Hugging Face documentation](https://huggingface.co/docs/huggingface_hub/en/package_reference/environment_variables)*.
🔒 [FLUX.1-dev currently requires granted access to its Huggingface repo. For troubleshooting, see the issue tracker](https://github.com/filipstrand/mflux/issues/14) 🔒 🔒 [FLUX.1-dev currently requires granted access to its Huggingface repo. For troubleshooting, see the issue tracker](https://github.com/filipstrand/mflux/issues/14) 🔒

View File

@ -21,6 +21,7 @@ dependencies = [
"piexif>=1.1.3,<2.0", "piexif>=1.1.3,<2.0",
"pillow>=10.4.0,<11.0; python_version<'3.13'", "pillow>=10.4.0,<11.0; python_version<'3.13'",
"pillow>=11.0,<12.0; python_version>='3.13'", "pillow>=11.0,<12.0; python_version>='3.13'",
"platformdirs>=4.0,<5.0",
"safetensors>=0.4.4,<1.0", "safetensors>=0.4.4,<1.0",
# python 3.13 workaround for now: # python 3.13 workaround for now:
# use temporary community build of py13 wheel, use until official project build # use temporary community build of py13 wheel, use until official project build

View File

@ -1,13 +1,12 @@
import logging import logging
import os
import urllib.error import urllib.error
import urllib.request import urllib.request
from pathlib import Path
import mlx.core as mx import mlx.core as mx
import torch import torch
from mlx.utils import tree_unflatten from mlx.utils import tree_unflatten
from mflux.ui.defaults import MFLUX_CACHE_DIR
from mflux.weights.weight_handler import MetaData from mflux.weights.weight_handler import MetaData
from mflux.weights.weight_util import WeightUtil from mflux.weights.weight_util import WeightUtil
@ -47,7 +46,8 @@ class WeightHandlerDepthPro:
APPLE_MODEL_URL = "https://ml-site.cdn-apple.com/models/depth-pro/depth_pro.pt" APPLE_MODEL_URL = "https://ml-site.cdn-apple.com/models/depth-pro/depth_pro.pt"
# 1. Create cache directory for the model # 1. Create cache directory for the model
cache_dir = Path(os.path.expanduser("~/.cache/mflux/depth_pro")) cache_dir = MFLUX_CACHE_DIR / "depth_pro"
cache_dir.mkdir(parents=True, exist_ok=True) cache_dir.mkdir(parents=True, exist_ok=True)
model_path = cache_dir / "depth_pro.pt" model_path = cache_dir / "depth_pro.pt"

View File

@ -1,3 +1,12 @@
import logging
import os
import shutil
from pathlib import Path
import platformdirs
logger = logging.getLogger(__name__)
BATTERY_PERCENTAGE_STOP_LIMIT = 5 BATTERY_PERCENTAGE_STOP_LIMIT = 5
CONTROLNET_STRENGTH = 0.4 CONTROLNET_STRENGTH = 0.4
DEFAULT_DEV_FILL_GUIDANCE = 30 DEFAULT_DEV_FILL_GUIDANCE = 30
@ -14,3 +23,60 @@ MODEL_INFERENCE_STEPS = {
"schnell": 4, "schnell": 4,
} }
QUANTIZE_CHOICES = [3, 4, 6, 8] QUANTIZE_CHOICES = [3, 4, 6, 8]
def _migrate_legacy_cache(new_cache_dir: Path) -> None:
"""Migrate legacy ~/.cache/mflux to new location if needed."""
legacy_cache = Path.home() / ".cache" / "mflux"
# Skip if legacy path doesn't exist or is already a symlink
if not legacy_cache.exists() or legacy_cache.is_symlink():
return
# Skip if we're already using the legacy path
if new_cache_dir == legacy_cache:
return
try:
logger.warning(f"Migrating cache from {legacy_cache} to {new_cache_dir}")
# Create new directory
new_cache_dir.mkdir(parents=True, exist_ok=True)
# Move all contents from old to new location
for item in legacy_cache.iterdir():
src = legacy_cache / item.name
dst = new_cache_dir / item.name
if dst.exists():
logger.warning(f" Skipping {item.name} (already exists in destination)")
continue
logger.warning(f" Moving {item.name}")
shutil.move(str(src), str(dst))
# Remove the now-empty old directory
legacy_cache.rmdir()
# Create symlink from old location to new location for backward compatibility
legacy_cache.parent.mkdir(parents=True, exist_ok=True)
legacy_cache.symlink_to(new_cache_dir)
logger.info(f"Created symlink: {legacy_cache} -> {new_cache_dir}")
except (OSError, IOError, shutil.Error) as e:
logger.warning(f"Cache migration failed: {e}")
logger.info("Continuing with existing location")
# Determine cache directory
if os.environ.get("MFLUX_CACHE_DIR"):
# User specified cache directory (e.g. external storage)
MFLUX_CACHE_DIR = Path(os.environ["MFLUX_CACHE_DIR"]).resolve()
else:
# macOS-idiomatic cache directory @ /Users/username/Library/Caches/mflux
MFLUX_CACHE_DIR = Path(platformdirs.user_cache_dir(appname="mflux"))
# Perform one-time migration if needed
_migrate_legacy_cache(MFLUX_CACHE_DIR)
MFLUX_LORA_CACHE_DIR = MFLUX_CACHE_DIR / "loras"

View File

@ -1,15 +1,17 @@
import os import shutil
from pathlib import Path from pathlib import Path
from huggingface_hub import snapshot_download from huggingface_hub import snapshot_download
from mflux.ui.defaults import MFLUX_LORA_CACHE_DIR
class WeightHandlerLoRAHuggingFace: class WeightHandlerLoRAHuggingFace:
@staticmethod @staticmethod
def download_loras( def download_loras(
lora_names: list[str] | None = None, lora_names: list[str] | None = None,
repo_id: str | None = None, repo_id: str | None = None,
cache_dir: str | None = None, cache_dir: Path | str | None = None,
) -> list[str]: ) -> list[str]:
if repo_id is None: if repo_id is None:
return [] return []
@ -30,19 +32,21 @@ class WeightHandlerLoRAHuggingFace:
def download_lora( def download_lora(
repo_id: str, repo_id: str,
lora_name: str, lora_name: str,
cache_dir: str | None = None, cache_dir: Path | str | None = None,
) -> str: ) -> str:
# Create cache directory if it doesn't exist # Ensure cache_dir is a Path object
if cache_dir is None: if cache_dir is None:
cache_dir = os.path.join(os.path.expanduser("~"), ".cache", "mflux", "loras") cache_path = MFLUX_LORA_CACHE_DIR
else:
cache_path = Path(cache_dir)
os.makedirs(cache_dir, exist_ok=True) cache_path.mkdir(parents=True, exist_ok=True)
# Check if the file already exists in the cache # Check if the file already exists in the cache
cached_file_path = os.path.join(cache_dir, lora_name) cached_file_path = cache_path / lora_name
if os.path.exists(cached_file_path): if cached_file_path.exists():
print(f"Using cached LoRA: {cached_file_path}") print(f"Using cached LoRA: {cached_file_path}")
return cached_file_path return str(cached_file_path)
# Download the LoRA from Hugging Face # Download the LoRA from Hugging Face
print(f"Downloading LoRA '{lora_name}' from {repo_id}...") print(f"Downloading LoRA '{lora_name}' from {repo_id}...")
@ -50,7 +54,7 @@ class WeightHandlerLoRAHuggingFace:
snapshot_download( snapshot_download(
repo_id=repo_id, repo_id=repo_id,
allow_patterns=[f"*{lora_name}*"], allow_patterns=[f"*{lora_name}*"],
cache_dir=cache_dir, cache_dir=str(cache_path),
) )
) )
@ -58,17 +62,15 @@ class WeightHandlerLoRAHuggingFace:
for file in download_path.glob(f"**/*{lora_name}*"): for file in download_path.glob(f"**/*{lora_name}*"):
if file.is_file() and file.suffix in [".safetensors", ".bin"]: if file.is_file() and file.suffix in [".safetensors", ".bin"]:
# Copy or link the file to the cache directory with the expected name # Copy or link the file to the cache directory with the expected name
target_path = os.path.join(cache_dir, lora_name) target_path = cache_path / lora_name
if not os.path.exists(target_path): if not target_path.exists():
# Create a symlink or copy the file # Create a symlink or copy the file
try: try:
os.symlink(file, target_path) target_path.symlink_to(file)
except (OSError, AttributeError): except (OSError, AttributeError):
import shutil
shutil.copy2(file, target_path) shutil.copy2(file, target_path)
print(f"LoRA downloaded to: {target_path}") print(f"LoRA downloaded to: {target_path}")
return target_path return str(target_path)
raise FileNotFoundError(f"Could not find LoRA file '{lora_name}' in the downloaded repository.") raise FileNotFoundError(f"Could not find LoRA file '{lora_name}' in the downloaded repository.")