-
Notifications
You must be signed in to change notification settings - Fork 7.4k
[Modular] Avoid downloading weights when loading from an existing local path #14797
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
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 |
|---|---|---|
|
|
@@ -15,9 +15,11 @@ | |
|
|
||
| import json | ||
| import os | ||
| import shutil | ||
|
|
||
| import pytest | ||
| import torch | ||
| from huggingface_hub import snapshot_download | ||
|
|
||
| from diffusers import AutoModel, ControlNetModel, ModularPipeline, UNet2DConditionModel | ||
| from diffusers.modular_pipelines.modular_pipeline_utils import ComponentSpec | ||
|
|
@@ -276,3 +278,69 @@ def test_init_raises_without_resolvable_blocks(self): | |
| # The base class has no `default_blocks_name`, so with no `blocks` there is nothing to build from. | ||
| with pytest.raises(ValueError, match="No pipeline blocks could be resolved"): | ||
| ModularPipeline() | ||
|
|
||
|
|
||
| class TestLoadFromLocalCopy: | ||
| def test_local_copy_loads_present_components_locally(self, tmp_path): | ||
| """`hf download --local-dir` keeps the index pointing at the Hub; components whose subfolder is present in | ||
| the local copy load from it, the rest keep their recorded spec.""" | ||
| local_dir = str(tmp_path / "local-copy") | ||
| cache_dir = str(tmp_path / "cache") | ||
| snapshot_download("hf-internal-testing/tiny-anima-modular-pipe", local_dir=local_dir) | ||
|
|
||
| pipe = ModularPipeline.from_pretrained(local_dir) | ||
| for name in ("vae", "transformer", "text_encoder", "scheduler"): | ||
| spec = pipe._component_specs[name] | ||
| assert spec.pretrained_model_name_or_path == local_dir, f"{name} should load from the local copy" | ||
| assert spec.revision is None | ||
| assert ( | ||
| pipe._component_specs["t5_tokenizer"].pretrained_model_name_or_path == "hf-internal-testing/tiny-random-t5" | ||
| ) | ||
|
|
||
| pipe.load_components(names=["vae"], dtype=torch.float32, local_files_only=True, cache_dir=cache_dir) | ||
|
Member
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. Should it not error out when I ran the following from diffusers import ModularPipeline
from huggingface_hub import snapshot_download
import tempfile
import pathlib
import torch
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir = pathlib.Path(tmpdir)
local_dir = tmpdir / "local_dir"
cache_dir = tmpdir / "cache_dir"
snapshot_download("hf-internal-testing/tiny-anima-modular-pipe", local_dir=local_dir)
pipe = ModularPipeline.from_pretrained(local_dir)
print(pipe.components.keys())
pipe.load_components(names=["vae"], dtype=torch.float32, local_files_only=True, cache_dir=cache_dir)
print(pipe.vae is not None)And I got: LogsFetching 12 files: 0%| | 0/12 [00:00<?, ?it/s]
Fetching 12 files: 8%|▊ | 1/12 [00:00<00:06, 1.63it/s]
Fetching 12 files: 17%|█▋ | 2/12 [00:00<00:04, 2.27it/s]
Fetching 12 files: 42%|████▏ | 5/12 [00:01<00:01, 6.66it/s]
Fetching 12 files: 58%|█████▊ | 7/12 [00:01<00:00, 8.11it/s]
Fetching 12 files: 75%|███████▌ | 9/12 [00:03<00:01, 2.14it/s]
Fetching 12 files: 83%|████████▎ | 10/12 [00:04<00:01, 1.96it/s]
Fetching 12 files: 92%|█████████▏| 11/12 [00:04<00:00, 2.00it/s]
Fetching 12 files: 100%|██████████| 12/12 [00:05<00:00, 1.34it/s]
Fetching 12 files: 100%|██████████| 12/12 [00:05<00:00, 2.01it/s]
Guiders are currently an experimental feature under active development. The API is subject to breaking changes in future releases.
/Users/sayakpaul/miniconda3/envs/diffusers/lib/python3.10/site-packages/huggingface_hub/utils/_validators.py:205: UserWarning: The `local_dir_use_symlinks` argument is deprecated and ignored in `hf_hub_download`. Downloading to a local directory does not use symlinks anymore.
warnings.warn(
Failed to create component vae:
- Component spec: ComponentSpec(name='vae', type_hint=<class 'diffusers.models.autoencoders.autoencoder_kl_qwenimage.AutoencoderKLQwenImage'>, description=None, config=None, pretrained_model_name_or_path='hf-internal-testing/tiny-anima-modular-pipe', subfolder='vae', variant=None, revision=None, default_creation_method='from_pretrained', repo=None)
- load() called with kwargs: {'dtype': torch.float32, 'local_files_only': True, 'cache_dir': PosixPath('/var/folders/wg/2xcyr_6j3lgc_y5k0344x2b80000gn/T/tmp2xh3y9ma/cache_dir')}
If this component is not required for your workflow you can safely ignore this message.
Traceback:
Traceback (most recent call last):
File "/Users/sayakpaul/Downloads/diffusers/src/diffusers/configuration_utils.py", line 414, in load_config
config_file = hf_hub_download(
File "/Users/sayakpaul/miniconda3/envs/diffusers/lib/python3.10/site-packages/huggingface_hub/utils/_validators.py", line 88, in _inner_fn
return fn(*args, **kwargs)
File "/Users/sayakpaul/miniconda3/envs/diffusers/lib/python3.10/site-packages/huggingface_hub/file_download.py", line 1035, in hf_hub_download
return _hf_hub_download_to_cache_dir(
File "/Users/sayakpaul/miniconda3/envs/diffusers/lib/python3.10/site-packages/huggingface_hub/file_download.py", line 1182, in _hf_hub_download_to_cache_dir
_raise_on_head_call_error(head_call_error, force_download, local_files_only)
File "/Users/sayakpaul/miniconda3/envs/diffusers/lib/python3.10/site-packages/huggingface_hub/file_download.py", line 1910, in _raise_on_head_call_error
raise LocalEntryNotFoundError(
huggingface_hub.errors.LocalEntryNotFoundError: Cannot find the requested files in the disk cache and outgoing traffic has been disabled. To enable hf.co look-ups and downloads online, set 'local_files_only' to False.
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/sayakpaul/Downloads/diffusers/src/diffusers/modular_pipelines/modular_pipeline_utils.py", line 347, in load
component = load_method(pretrained_model_name_or_path, **load_kwargs, **kwargs)
File "/Users/sayakpaul/miniconda3/envs/diffusers/lib/python3.10/site-packages/huggingface_hub/utils/_validators.py", line 88, in _inner_fn
return fn(*args, **kwargs)
File "/Users/sayakpaul/Downloads/diffusers/src/diffusers/models/modeling_utils.py", line 1144, in from_pretrained
config, unused_kwargs, commit_hash = cls.load_config(
File "/Users/sayakpaul/miniconda3/envs/diffusers/lib/python3.10/site-packages/huggingface_hub/utils/_validators.py", line 88, in _inner_fn
return fn(*args, **kwargs)
File "/Users/sayakpaul/Downloads/diffusers/src/diffusers/configuration_utils.py", line 441, in load_config
raise EnvironmentError(
OSError: hf-internal-testing/tiny-anima-modular-pipe does not appear to have a file named config.json.
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/sayakpaul/Downloads/diffusers/src/diffusers/modular_pipelines/modular_pipeline.py", line 2501, in load_components
components_to_register[name] = spec.load(**component_load_kwargs)
File "/Users/sayakpaul/Downloads/diffusers/src/diffusers/modular_pipelines/modular_pipeline_utils.py", line 349, in load
raise ValueError(f"Unable to load {self.name} using load method: {e}")
ValueError: Unable to load vae using load method: hf-internal-testing/tiny-anima-modular-pipe does not appear to have a file named config.json.
dict_keys(['text_encoder', 'tokenizer', 't5_tokenizer', 'guider', 'vae', 'image_processor', 'text_conditioner', 'transformer', 'scheduler'])
pipe.vae is not None=FalseLike the assert just right after should fail. What am I missing? |
||
| assert pipe.vae is not None | ||
| cached_weights = [p for p in (tmp_path / "cache").rglob("*") if p.suffix in (".safetensors", ".bin")] | ||
| assert cached_weights == [], f"weights should not be in the Hub cache: {cached_weights}" | ||
|
|
||
| def test_local_copy_missing_files_keeps_recorded_spec(self, tmp_path): | ||
| """A missing subfolder, or a model subfolder without weight files (e.g. a partial download), keeps the | ||
| recorded spec instead of shadowing it with an unloadable folder.""" | ||
| local_dir = str(tmp_path / "local-copy") | ||
| snapshot_download("hf-internal-testing/tiny-anima-modular-pipe", local_dir=local_dir) | ||
| shutil.rmtree(os.path.join(local_dir, "transformer")) | ||
| for filename in os.listdir(os.path.join(local_dir, "vae")): | ||
| if filename.endswith((".safetensors", ".bin")): | ||
| os.remove(os.path.join(local_dir, "vae", filename)) | ||
|
|
||
|
Comment on lines
+311
to
+314
Member
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. Why not remove the VAE subfolder directly like the |
||
| pipe = ModularPipeline.from_pretrained(local_dir) | ||
| assert ( | ||
| pipe._component_specs["transformer"].pretrained_model_name_or_path | ||
| == "hf-internal-testing/tiny-anima-modular-pipe" | ||
| ) | ||
| assert ( | ||
| pipe._component_specs["vae"].pretrained_model_name_or_path == "hf-internal-testing/tiny-anima-modular-pipe" | ||
| ) | ||
| assert pipe._component_specs["text_encoder"].pretrained_model_name_or_path == local_dir | ||
|
|
||
| def test_local_copy_loads_components_at_root(self, tmp_path): | ||
| """A component recorded without a subfolder is at the root of its repo; when the local copy has its files | ||
| there it is loaded from the copy: weights for a model, the saved config file for anything else.""" | ||
| local_dir = str(tmp_path / "local-copy") | ||
| snapshot_download("hf-internal-testing/tiny-cosmos3-modular-pipe", local_dir=local_dir) | ||
| index_path = os.path.join(local_dir, "modular_model_index.json") | ||
| with open(index_path) as f: | ||
| index = json.load(f) | ||
| root_components = ["transformer", "scheduler", "text_tokenizer"] | ||
| for name in root_components: | ||
| for filename in os.listdir(os.path.join(local_dir, name)): | ||
| shutil.move(os.path.join(local_dir, name, filename), os.path.join(local_dir, filename)) | ||
| index[name][2]["subfolder"] = None | ||
| with open(index_path, "w") as f: | ||
| json.dump(index, f) | ||
|
|
||
| pipe = ModularPipeline.from_pretrained(local_dir) | ||
| for name in root_components: | ||
| assert pipe._component_specs[name].pretrained_model_name_or_path == local_dir, f"{name} not local" | ||
| pipe.load_components(names=root_components, dtype=torch.float32, local_files_only=True) | ||
| for name in root_components: | ||
| assert getattr(pipe, name) is not None, f"{name} did not load from the local copy" | ||
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.
Instead of hardcoding the name of the components, we could call
pipe.components.keys()here?