cc @vladmandic here
still WIP, but let me know what you think about the API and the use cases it covers
do you have any other specific use case in mind that I did not cover here?
395 | 395 | ||
396 | 396 | # based on https://github.com/guoyww/AnimateDiff/blob/895f3220c06318ea0760131ec70408b466c49333/animatediff/models/unet.py#L459 | |
397 | config = unet.config | ||
397 | config = dict(unet.config) |
@DN6
currently, we will modify the original 2d unet's config - even though we do not use it here, we create a new unet motion model instead
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.
2138 | ``` | ||
2139 | """ | ||
2140 | |||
2141 | if hasattr(pipeline, "_all_hooks") and len(pipeline._all_hooks) > 0: |
this is the part we make sure if the pipeline has previous called enable_model_cpu_offload
, it will still work properly with from_pipe
thanks yiyi!
from high level, it seems to cover all main use cases. if there is something borderline, we can think of that later.
my two comments are:
thanks for the feedback! @vladmandic
to make sure that target pipeline actually works when there are additional components added to it
I have not run into any issues using enable_model_cpu_offload
with additional components, and my tests are pretty extensive (we added a fast test to test all diffusers official pipeline that can use from_pipe
), I think it is not a concern here because I remove all the hooks and reset the offload device in the beginning
target pipeline should inherit pipeline settings
I'm not so sure about this because:
from_pipe
API, so the new pipeline may have different memory requirements, and the user may want different settings. I think it would be simpler to reset instead of inheriting the settings unless they always want to have the same settings for the new pipeline.enable_model_cpu_offload
method working correctly. This would more likely be an issue with community pipelineswith this being said, I think it won't be hard to implement and I'm open to it if you all think it's more intuitive and convenient to let the new pipelines inherit settings. cc @pcuenca here too, let me know what you think!
cc @DN6 @sayakpaul for a final review
let me know what you think about this #7241 (comment) too
I'm slightly in favor of resetting the pipeline settings but I don't feel strongly either way
thanks @yiyixuxu
re: pipeline settings inheritance - IMO it would be more convenient and expected since its a pipeline switch using loaded model components (all or some), but its not a deal breaker - from_pipe
has massive value either way.
actually, now I think most of the enable_*
methods make stateful changes to the model components, and these changes are already naturally carried over to the new pipeline (e.g. these on StableDiffusionMixin
enable_model_cpu_offload
on the other hand, these methods may not work probably with the potential addition or override of new components
e.g.
if we have enable_vae_slicing
enabled on the pipeline, and create a new pipeline with a new vae components, it won't work
pipeline1.enable_vae_slicing()
vae = AutoencoderKL.from_pretrained()
pipe2 = NewPipelineClass.from_pipe(pipe1, vae= vae)
should we handle this on our end or let the user address this? if they just re-apply the settings, it would work as expected; if we are going to handle this on our end, it would be pretty complicated I think
Very nice PR. Let's document this more formally no?
292 | 292 | return class_obj, class_candidates | |
293 | 293 | ||
294 | 294 | ||
295 | def _get_custom_pipeline_class( |
Very nice cleanup here!
1481 | elif get_origin(v.annotation) == Union: | ||
1482 | signature_types[k] = get_args(v.annotation) | ||
1483 | else: | ||
1484 | logger.warn(f"cannot get type annotation for Parameter {k} of {cls}.") |
logger.warning()
please.
1688 | |||
1689 | pipeline_is_offloaded = True if hasattr(pipeline, "_all_hooks") and len(pipeline._all_hooks) > 0 else False | ||
1690 | if pipeline_is_offloaded: | ||
1691 | # `enable_model_cpu_offload` has be called on the pipeline, offload model and remove hook from model |
# `enable_model_cpu_offload` has be called on the pipeline, offload model and remove hook from model | |
# `enable_model_cpu_offload` has been called on the pipeline, offload model and remove hooks from model |
Why is this step needed?
otherwise if you call enable_model_cpu_offload
again on the new pipeline, it won't work correctly,
however, this is no longer needed now we have this PR #7448
I can remove this now :)
1124 | changed_components = {} | ||
1125 | original_signature_types = original_pipeline_class._get_signature_types() | ||
1126 | for k, v in pipe2.components.items(): | ||
1127 | if ( | ||
1128 | k in original_expected_modules | ||
1129 | and v is not None | ||
1130 | and isinstance(v, torch.nn.Module) | ||
1131 | and type(v) not in original_signature_types[k] | ||
1132 | ): | ||
1133 | changed_components[k] = original_pipe_components[k] |
I am really having trouble reading this condition.
original_pipe_additional_components
go into initializing a different pipeline that might not share the same additional components?Maybe fully spelling out what k
and v
are denoting will be helpful.
Why should the original_pipe_additional_components go into initializing a different pipeline that might not share the same additional components?
yeah, I know - not the most straight-forward code to read here
so we only have two pipelines here, the original pipeline would be something like SD, the current pipeline would be something like SAG
Basically in the test we do something like this,
SD (`pipe_original`) -> SAG (`pipe2`) -> SD (`pipe_original_2`)
so you can see that pipe_original_2
and pipe_original
is actually same pipeline class, original_pipe_additional_components
would be components in pipe_original
that not accepted in pipe2
, e.g. image_encoder
(I'm just making it up here), naturally it will go back when we initiate the pipe_original_2
since it is the same pipeline class
maybe I should give them better names!
1142 | inputs = get_dummy_inputs_pipe_original(torch_device) | ||
1143 | output_original2 = pipe_original_2(**inputs)[0] | ||
1144 | |||
1145 | assert pipe1_config == pipe2_config | ||
1146 | assert pipe_original_2_config == original_config |
Could be a little stricter to iterate through the keys and make sure their corresponding values match.
actually, now I think most of the enable_* methods make stateful changes to the model components, and these changes are already naturally carried over to the new pipeline so we probably should make it consistent with enable_model_cpu_offload
on the other hand, these methods may not work probably with the potential addition or override of new components
e.g. if we have enable_vae_slicing enabled on the pipeline, and create a new pipeline with a new vae components, it won't work
that exactly is the problem with model offload compatibiltiy i was referring to early in the conversation.
for example, a very common use-case is to load feature_extractor
and image_encoder
once they are needed by ipadapter.
and then we end up with split-brain pipe: parts want to do offloading, parts do not.
even worse, what if i want to now load a different base model and just reuse those two previously loaded component so i don't have to load them again as well (they are not tiny by any means)?
if we're not going to handle those internally, then at least we need to have opposite of enable_model_cpu_offload
to force-disable it before loading components and then we call enable_model_cpu_offload
it again once pipeline is reconstructed.
Thanks @vladmandic! I didn't think it through before
I updated the model offload methods in a separate PR here. So now, both enable_model_cpu_offload
and enable_sequential_cpu_offload
will work properly when you re-apply them, either from the same pipeline or from a different one.
now all these enable_*
methods have consistent behavior in from_pipe
: we do not do anything to guarantee these pipeline settings are inherited from the previous pipeline; they may not work as it is but if you re-apply them, they will work correctly on the new pipeline
I'm open to inheriting the settings too! let me know what you all think too @sayakpaul @pcuenca @DN6
thanks - if re-apply now works, that's imo sufficient.
1722 | if name in expected_modules and name not in passed_class_obj: | ||
1723 | # for model components, we will not switch over if the class does not matches the type hint in the new pipeline's signature | ||
1724 | if ( | ||
1725 | not isinstance(component, torch.nn.Module) |
Maybe change this to see if it subclasses ModelMixin
here?
LGTM 👍🏽
Login to write a write a comment.
motivated by #6531
Create a stable diffusion pipeline with
from_pretrained
test2: SD -> SAG
test3: run SD again
test4: run
pipe_sd
afterpipe_sag.unload_ip_adapter()
test5: SD -> AnimateDiff
test6: SD -> LPW
test7: run SD again