Skip to content
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

[FEAT] DDUF format #10037

Open
wants to merge 87 commits into
base: main
Choose a base branch
from
Open
Changes from 2 commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
1fb86e3
load and save dduf archive
SunMarc Nov 27, 2024
0389333
style
SunMarc Nov 27, 2024
2eeda25
switch to zip uncompressed
SunMarc Nov 28, 2024
cbee7cb
Merge branch 'main' into dduf
sayakpaul Nov 30, 2024
d840867
updates
sayakpaul Nov 29, 2024
78135f1
Merge branch 'main' into dduf
sayakpaul Dec 4, 2024
7d2c7d5
Update src/diffusers/pipelines/pipeline_utils.py
SunMarc Dec 4, 2024
e66c4d0
Update src/diffusers/pipelines/pipeline_utils.py
SunMarc Dec 4, 2024
b14bffe
first draft
SunMarc Dec 4, 2024
1cd5155
remove print
SunMarc Dec 4, 2024
b8a43e7
switch to dduf_file for consistency
SunMarc Dec 5, 2024
cac988a
Merge remote-tracking branch 'origin/main' into dduf
SunMarc Dec 5, 2024
977baa3
switch to huggingface hub api
SunMarc Dec 5, 2024
81bd097
fix log
SunMarc Dec 6, 2024
c4df147
Merge branch 'main' into dduf
sayakpaul Dec 8, 2024
d0a861c
add a basic test
sayakpaul Dec 8, 2024
1ec988f
Update src/diffusers/configuration_utils.py
SunMarc Dec 9, 2024
5217712
Update src/diffusers/pipelines/pipeline_utils.py
SunMarc Dec 9, 2024
6922226
Update src/diffusers/pipelines/pipeline_utils.py
SunMarc Dec 9, 2024
3b0d84d
fix
SunMarc Dec 9, 2024
1bc953b
Merge remote-tracking branch 'origin/dduf' into dduf
SunMarc Dec 9, 2024
04ecf0e
fix variant
SunMarc Dec 9, 2024
9fff68a
change saving logic
SunMarc Dec 10, 2024
ed6c727
DDUF - Load transformers components manually (#10171)
Wauplin Dec 11, 2024
17d50d1
working version with transformers and tokenizer !
SunMarc Dec 11, 2024
59929a5
add generation_config case
SunMarc Dec 12, 2024
aa0d497
fix tests
sayakpaul Dec 12, 2024
a793066
Merge branch 'main' into dduf
sayakpaul Dec 12, 2024
7602952
remove saving for now
SunMarc Dec 12, 2024
660d7c8
typing
SunMarc Dec 12, 2024
8358ef6
need next version from transformers
SunMarc Dec 12, 2024
4e7d15a
Update src/diffusers/configuration_utils.py
SunMarc Dec 12, 2024
cc75db3
check path corectly
SunMarc Dec 12, 2024
63575af
Merge remote-tracking branch 'origin/dduf' into dduf
SunMarc Dec 12, 2024
1e5ebf5
Apply suggestions from code review
SunMarc Dec 12, 2024
53e100b
udapte
SunMarc Dec 12, 2024
54991ac
Merge remote-tracking branch 'origin/dduf' into dduf
SunMarc Dec 12, 2024
1eb25dc
typing
SunMarc Dec 12, 2024
0cb1b98
remove check for subfolder
SunMarc Dec 12, 2024
5ec3951
quality
SunMarc Dec 12, 2024
1785eaa
revert setup changes
SunMarc Dec 12, 2024
7486016
oups
SunMarc Dec 12, 2024
73e81a5
more readable condition
SunMarc Dec 12, 2024
ea0126d
add loading from the hub test
sayakpaul Dec 13, 2024
3ebdcff
add basic docs.
sayakpaul Dec 13, 2024
5943a60
Merge branch 'main' into dduf
sayakpaul Dec 13, 2024
021abf0
Apply suggestions from code review
SunMarc Dec 13, 2024
af2ca07
add example
SunMarc Dec 13, 2024
9d70b6c
Merge branch 'main' into dduf
sayakpaul Dec 13, 2024
47cb92c
Merge remote-tracking branch 'origin/dduf' into dduf
SunMarc Dec 13, 2024
c9734ab
add
SunMarc Dec 13, 2024
27ebf9e
make functions private
SunMarc Dec 13, 2024
d5dbb5c
Apply suggestions from code review
SunMarc Dec 13, 2024
627aec0
resolve conflicts.
sayakpaul Dec 18, 2024
f0e21a9
Merge branch 'main' into dduf
sayakpaul Dec 18, 2024
e9b7429
minor.
sayakpaul Dec 18, 2024
b8b699a
fixes
sayakpaul Dec 18, 2024
6003176
Merge branch 'main' into dduf
sayakpaul Dec 18, 2024
0e54b06
fix
sayakpaul Dec 18, 2024
a026055
change the precdence of parameterized.
sayakpaul Dec 18, 2024
03e30b4
Merge remote-tracking branch 'upstream/main' into dduf
SunMarc Dec 23, 2024
da48dcb
Merge branch 'main' into dduf
SunMarc Dec 23, 2024
0fbea9a
Merge branch 'main' into dduf
sayakpaul Dec 30, 2024
6a163c7
error out when custom pipeline is passed with dduf_file.
sayakpaul Dec 30, 2024
67b617e
updates
sayakpaul Dec 31, 2024
b40272e
fix
sayakpaul Dec 31, 2024
ce237f3
updates
sayakpaul Dec 31, 2024
454b9b9
fixes
sayakpaul Dec 31, 2024
366aa2f
updates
sayakpaul Dec 31, 2024
6648995
fix xfail condition.
sayakpaul Dec 31, 2024
21ae7ee
fix xfail
sayakpaul Dec 31, 2024
15d4569
fixes
sayakpaul Dec 31, 2024
f3a4ddc
sharded checkpoint compat
SunMarc Jan 3, 2025
a032025
Merge branch 'dduf' of github.com:huggingface/diffusers into dduf
SunMarc Jan 3, 2025
faa0cac
Merge branch 'main' into dduf
sayakpaul Jan 3, 2025
0205cc8
add test for sharded checkpoint
SunMarc Jan 3, 2025
9cda4c1
Merge branch 'dduf' of github.com:huggingface/diffusers into dduf
SunMarc Jan 3, 2025
cd0734e
Merge branch 'main' into dduf
sayakpaul Jan 4, 2025
5037d39
add suggestions
SunMarc Jan 6, 2025
c9e08da
Merge branch 'dduf' of github.com:huggingface/diffusers into dduf
SunMarc Jan 6, 2025
02a368b
Update src/diffusers/models/model_loading_utils.py
SunMarc Jan 7, 2025
7bc9347
from suggestions
SunMarc Jan 7, 2025
fff5954
add class attributes to flag dduf tests
SunMarc Jan 7, 2025
da402da
last one
SunMarc Jan 7, 2025
9ebbf84
Merge branch 'main' into dduf
sayakpaul Jan 8, 2025
aaaa947
Merge branch 'main' into dduf
sayakpaul Jan 8, 2025
290b88d
resolve conflicts.
sayakpaul Jan 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions src/diffusers/pipelines/pipeline_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ def save_pretrained(
variant: Optional[str] = None,
max_shard_size: Optional[Union[int, str]] = None,
push_to_hub: bool = False,
dduf_format: bool = False,
dduf_filename: Optional[Union[str, os.PathLike]] = None,
Copy link
Member

Choose a reason for hiding this comment

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

As mentioned over Slack, why do we need two arguments here? How about just dduf_filename?

Copy link
Member Author

Choose a reason for hiding this comment

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

I will just use that then !

Copy link
Member

@pcuenca pcuenca Nov 28, 2024

Choose a reason for hiding this comment

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

I think that most, if not all, the components of the file name should be inferred. One of the reasons to use DDUF would be to provide confidence about the contents based on filename inspection, so we should try to make it work with something like an optional prefix, but build the rest automatically. So something like dduf_name="my-lora" resulting in the library saving to "my-lora-stable-diffusion-3-medium-diffusers-fp16.dduf", or something like that.

Copy link
Member

Choose a reason for hiding this comment

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

ok to use some kind of convention, but i don't think it should be an absolute requirement, ie. people can rename their files if they want to (but the metadata should be inside the file anyways)

Copy link
Member

Choose a reason for hiding this comment

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

Yeah that is the plan.

SunMarc marked this conversation as resolved.
Show resolved Hide resolved
**kwargs,
):
"""
Expand Down Expand Up @@ -227,6 +229,9 @@ class implements both a save and loading method. The pipeline is easily reloaded
model_index_dict.pop("_module", None)
model_index_dict.pop("_name_or_path", None)

if dduf_format and dduf_filename is None:
raise RuntimeError("You need set dduf_filename if you want to save your model in DDUF format.")

if push_to_hub:
commit_message = kwargs.pop("commit_message", None)
private = kwargs.pop("private", False)
Expand Down Expand Up @@ -301,6 +306,34 @@ def is_saveable_module(name, value):

save_method(os.path.join(save_directory, pipeline_component_name), **save_kwargs)

if dduf_format:
import shutil
import tarfile

dduf_file_path = os.path.join(save_directory, dduf_filename)

if os.path.isdir(dduf_file_path):
logger.warning(
f"Removing the existing folder {dduf_file_path} so that we can save the DDUF archive."
)
SunMarc marked this conversation as resolved.
Show resolved Hide resolved
shutil.rmtree(dduf_file_path)
if (
os.path.exists(dduf_file_path)
and os.path.isfile(dduf_file_path)
and tarfile.is_tarfile(dduf_file_path)
):
# Open in append mode if the file exists
mode = "a"
SunMarc marked this conversation as resolved.
Show resolved Hide resolved
else:
# Open in write mode to create it if it doesn't exist
mode = "w:"
with tarfile.open(dduf_file_path, mode) as tar:
dir_to_archive = os.path.join(save_directory, pipeline_component_name)
if os.path.isdir(dir_to_archive):
SunMarc marked this conversation as resolved.
Show resolved Hide resolved
tar.add(dir_to_archive, arcname=os.path.basename(dir_to_archive))
# remove from save_directory after we added it to the archive
shutil.rmtree(dir_to_archive)
SunMarc marked this conversation as resolved.
Show resolved Hide resolved
julien-c marked this conversation as resolved.
Show resolved Hide resolved

# finally save the config
self.save_config(save_directory)

Expand Down Expand Up @@ -523,6 +556,8 @@ def from_pretrained(cls, pretrained_model_name_or_path: Optional[Union[str, os.P
- A path to a *directory* (for example `./my_pipeline_directory/`) containing pipeline weights
saved using
[`~DiffusionPipeline.save_pretrained`].
- A path to a *directory* (for example `./my_pipeline_directory/`) containing a dduf archive or
folder
torch_dtype (`str` or `torch.dtype`, *optional*):
Override the default `torch.dtype` and load the model with another dtype. If "auto" is passed, the
dtype is automatically derived from the model's weights.
Expand Down Expand Up @@ -617,6 +652,8 @@ def from_pretrained(cls, pretrained_model_name_or_path: Optional[Union[str, os.P
variant (`str`, *optional*):
Load weights from a specified variant filename such as `"fp16"` or `"ema"`. This is ignored when
loading `from_flax`.
dduf(`str`, *optional*):
Load weights from the specified dduf archive or folder.
SunMarc marked this conversation as resolved.
Show resolved Hide resolved

<Tip>

Expand Down Expand Up @@ -666,6 +703,7 @@ def from_pretrained(cls, pretrained_model_name_or_path: Optional[Union[str, os.P
offload_state_dict = kwargs.pop("offload_state_dict", False)
low_cpu_mem_usage = kwargs.pop("low_cpu_mem_usage", _LOW_CPU_MEM_USAGE_DEFAULT)
variant = kwargs.pop("variant", None)
dduf = kwargs.pop("dduf", None)
use_safetensors = kwargs.pop("use_safetensors", None)
use_onnx = kwargs.pop("use_onnx", None)
load_connected_pipeline = kwargs.pop("load_connected_pipeline", False)
Expand Down Expand Up @@ -736,6 +774,7 @@ def from_pretrained(cls, pretrained_model_name_or_path: Optional[Union[str, os.P
custom_pipeline=custom_pipeline,
custom_revision=custom_revision,
variant=variant,
dduf=dduf,
load_connected_pipeline=load_connected_pipeline,
**kwargs,
)
Expand All @@ -762,6 +801,25 @@ def from_pretrained(cls, pretrained_model_name_or_path: Optional[Union[str, os.P
# pop out "_ignore_files" as it is only needed for download
config_dict.pop("_ignore_files", None)

if dduf:
import tarfile

tar_file_path = os.path.join(cached_folder, dduf)
extract_to = os.path.join(cached_folder, f"{dduf}_extracted")
# if tar file, we need to extract the tarfile and remove it
if os.path.isfile(tar_file_path):
if tarfile.is_tarfile(tar_file_path):
with tarfile.open(tar_file_path, "r") as tar:
tar.extractall(extract_to)
# remove tar archive to free memory
os.remove(tar_file_path)
# rename folder to match the name of the dduf archive
os.rename(extract_to, tar_file_path)
Copy link
Member

@pcuenca pcuenca Nov 28, 2024

Choose a reason for hiding this comment

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

I think that expanding the archive and removing it could be problematic.

  • If I understand correctly, we would be removing the symlink to the blob, but not the blob itself. We won't be saving disk space.
  • If we did remove the blob, I think it'd trigger another download next time we call .from_pretrained.
  • Removing the reference could maybe have consequences when checking for newer versions in the Hub (not sure).

When we change to using zips, I wonder if we could extract files progressively in memory during the load process, and keeping the big .dduf file as is. Having a disk snapshot is convenient, but I can see no easy solution to avoid duplicating space.

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree. I will check if it is possible to stream directly from the zip or at least extract progressively in memory as you said

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the link ! I'll have a look !

Copy link
Member

Choose a reason for hiding this comment

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

BTW on the Hub side we want to constraint the types of Zip a lot:

  • no compression only (to make sure it's deduplicated well by XetHub)
  • single disk (internal simplifications of Zip)
  • no encryption etc

Copy link
Collaborator

Choose a reason for hiding this comment

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

To ensure restrictions are met, we'll provide tooling in Python to save/load DDUF files in a consistent way: huggingface/huggingface_hub#2692. This will be part of huggingface_hub to that it can be easily reused by any library without having necessarily diffusers as a dependency.

else:
raise RuntimeError("The dduf path passed is not a tar archive")
# udapte cached folder location as the dduf content is in a seperate folder
cached_folder = tar_file_path

# 2. Define which model components should load variants
# We retrieve the information by matching whether variant model checkpoints exist in the subfolders.
# Example: `diffusion_pytorch_model.safetensors` -> `diffusion_pytorch_model.fp16.safetensors`
Expand Down Expand Up @@ -1227,6 +1285,8 @@ def download(cls, pretrained_model_name, **kwargs) -> Union[str, os.PathLike]:
variant (`str`, *optional*):
Load weights from a specified variant filename such as `"fp16"` or `"ema"`. This is ignored when
loading `from_flax`.
dduf(`str`, *optional*):
Load weights from the specified DDUF archive or folder.
use_safetensors (`bool`, *optional*, defaults to `None`):
If set to `None`, the safetensors weights are downloaded if they're available **and** if the
safetensors library is installed. If set to `True`, the model is forcibly loaded from safetensors
Expand Down Expand Up @@ -1267,6 +1327,7 @@ def download(cls, pretrained_model_name, **kwargs) -> Union[str, os.PathLike]:
use_onnx = kwargs.pop("use_onnx", None)
load_connected_pipeline = kwargs.pop("load_connected_pipeline", False)
trust_remote_code = kwargs.pop("trust_remote_code", False)
dduf = kwargs.pop("dduf", False)

allow_pickle = False
if use_safetensors is None:
Expand Down Expand Up @@ -1346,6 +1407,10 @@ def download(cls, pretrained_model_name, **kwargs) -> Union[str, os.PathLike]:
allow_patterns += [f"{custom_pipeline}.py"] if f"{custom_pipeline}.py" in filenames else []
# also allow downloading config.json files with the model
allow_patterns += [os.path.join(k, "config.json") for k in model_folder_names]
# also allow downloading the dduf
# TODO: check that the file actually exist
if dduf is not None:
allow_patterns += [dduf]
SunMarc marked this conversation as resolved.
Show resolved Hide resolved

allow_patterns += [
SCHEDULER_CONFIG_NAME,
Expand Down
Loading