Skip to content

Commit e58711e

Browse files
yiyixuxuasomozaDN6
authored
[Modular] support standard repo (huggingface#11944)
* make modular pipeline work with model_index.json * up * style * up * up * style * up more * Fix MultiControlNet import (huggingface#12118) fix --------- Co-authored-by: Álvaro Somoza <[email protected]> Co-authored-by: Dhruv Nair <[email protected]>
1 parent cbecc33 commit e58711e

File tree

3 files changed

+158
-26
lines changed

3 files changed

+158
-26
lines changed

src/diffusers/modular_pipelines/modular_pipeline.py

Lines changed: 127 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,15 @@ def to_dict(self) -> Dict[str, Any]:
128128
"""
129129
return {**self.__dict__}
130130

131+
def __getattr__(self, name):
132+
"""
133+
Allow attribute access to intermediate values. If an attribute is not found in the object, look for it in the
134+
intermediates dict.
135+
"""
136+
if name in self.intermediates:
137+
return self.intermediates[name]
138+
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
139+
131140
def __repr__(self):
132141
def format_value(v):
133142
if hasattr(v, "shape") and hasattr(v, "dtype"):
@@ -638,7 +647,7 @@ def __call__(self, pipeline, state: PipelineState) -> PipelineState:
638647
break
639648

640649
if block is None:
641-
logger.warning(f"skipping auto block: {self.__class__.__name__}")
650+
logger.info(f"skipping auto block: {self.__class__.__name__}")
642651
return pipeline, state
643652

644653
try:
@@ -1450,9 +1459,10 @@ def __init__(
14501459
Args:
14511460
blocks: `ModularPipelineBlocks` instance. If None, will attempt to load
14521461
default blocks based on the pipeline class name.
1453-
pretrained_model_name_or_path: Path to a pretrained pipeline configuration. If provided,
1454-
will load component specs (only for from_pretrained components) and config values from the saved
1455-
modular_model_index.json file.
1462+
pretrained_model_name_or_path: Path to a pretrained pipeline configuration. Can be None if the pipeline
1463+
does not require any additional loading config. If provided, will first try to load component specs
1464+
(only for from_pretrained components) and config values from `modular_model_index.json`, then
1465+
fallback to `model_index.json` for compatibility with standard non-modular repositories.
14561466
components_manager:
14571467
Optional ComponentsManager for managing multiple component cross different pipelines and apply
14581468
offloading strategies.
@@ -1501,18 +1511,70 @@ def __init__(
15011511

15021512
# update component_specs and config_specs from modular_repo
15031513
if pretrained_model_name_or_path is not None:
1504-
config_dict = self.load_config(pretrained_model_name_or_path, **kwargs)
1505-
1506-
for name, value in config_dict.items():
1507-
# all the components in modular_model_index.json are from_pretrained components
1508-
if name in self._component_specs and isinstance(value, (tuple, list)) and len(value) == 3:
1509-
library, class_name, component_spec_dict = value
1510-
component_spec = self._dict_to_component_spec(name, component_spec_dict)
1511-
component_spec.default_creation_method = "from_pretrained"
1512-
self._component_specs[name] = component_spec
1514+
cache_dir = kwargs.pop("cache_dir", None)
1515+
force_download = kwargs.pop("force_download", False)
1516+
proxies = kwargs.pop("proxies", None)
1517+
token = kwargs.pop("token", None)
1518+
local_files_only = kwargs.pop("local_files_only", False)
1519+
revision = kwargs.pop("revision", None)
1520+
1521+
load_config_kwargs = {
1522+
"cache_dir": cache_dir,
1523+
"force_download": force_download,
1524+
"proxies": proxies,
1525+
"token": token,
1526+
"local_files_only": local_files_only,
1527+
"revision": revision,
1528+
}
1529+
# try to load modular_model_index.json
1530+
try:
1531+
config_dict = self.load_config(pretrained_model_name_or_path, **load_config_kwargs)
1532+
except EnvironmentError as e:
1533+
logger.debug(f"modular_model_index.json not found: {e}")
1534+
config_dict = None
1535+
1536+
# update component_specs and config_specs based on modular_model_index.json
1537+
if config_dict is not None:
1538+
for name, value in config_dict.items():
1539+
# all the components in modular_model_index.json are from_pretrained components
1540+
if name in self._component_specs and isinstance(value, (tuple, list)) and len(value) == 3:
1541+
library, class_name, component_spec_dict = value
1542+
component_spec = self._dict_to_component_spec(name, component_spec_dict)
1543+
component_spec.default_creation_method = "from_pretrained"
1544+
self._component_specs[name] = component_spec
1545+
1546+
elif name in self._config_specs:
1547+
self._config_specs[name].default = value
1548+
1549+
# if modular_model_index.json is not found, try to load model_index.json
1550+
else:
1551+
logger.debug(" loading config from model_index.json")
1552+
try:
1553+
from diffusers import DiffusionPipeline
1554+
1555+
config_dict = DiffusionPipeline.load_config(pretrained_model_name_or_path, **load_config_kwargs)
1556+
except EnvironmentError as e:
1557+
logger.debug(f" model_index.json not found in the repo: {e}")
1558+
config_dict = None
1559+
1560+
# update component_specs and config_specs based on model_index.json
1561+
if config_dict is not None:
1562+
for name, value in config_dict.items():
1563+
if name in self._component_specs and isinstance(value, (tuple, list)) and len(value) == 2:
1564+
library, class_name = value
1565+
component_spec_dict = {
1566+
"repo": pretrained_model_name_or_path,
1567+
"subfolder": name,
1568+
"type_hint": (library, class_name),
1569+
}
1570+
component_spec = self._dict_to_component_spec(name, component_spec_dict)
1571+
component_spec.default_creation_method = "from_pretrained"
1572+
self._component_specs[name] = component_spec
1573+
elif name in self._config_specs:
1574+
self._config_specs[name].default = value
15131575

1514-
elif name in self._config_specs:
1515-
self._config_specs[name].default = value
1576+
if len(kwargs) > 0:
1577+
logger.warning(f"Unexpected input '{kwargs.keys()}' provided. This input will be ignored.")
15161578

15171579
register_components_dict = {}
15181580
for name, component_spec in self._component_specs.items():
@@ -1570,8 +1632,10 @@ def from_pretrained(
15701632
15711633
Args:
15721634
pretrained_model_name_or_path (`str` or `os.PathLike`, optional):
1573-
Path to a pretrained pipeline configuration. If provided, will load component specs (only for
1574-
from_pretrained components) and config values from the modular_model_index.json file.
1635+
Path to a pretrained pipeline configuration. It will first try to load config from
1636+
`modular_model_index.json`, then fallback to `model_index.json` for compatibility with standard
1637+
non-modular repositories. If the repo does not contain any pipeline config, it will be set to None
1638+
during initialization.
15751639
trust_remote_code (`bool`, optional):
15761640
Whether to trust remote code when loading the pipeline, need to be set to True if you want to create
15771641
pipeline blocks based on the custom code in `pretrained_model_name_or_path`
@@ -1607,11 +1671,35 @@ def from_pretrained(
16071671
}
16081672

16091673
try:
1674+
# try to load modular_model_index.json
16101675
config_dict = cls.load_config(pretrained_model_name_or_path, **load_config_kwargs)
1676+
except EnvironmentError as e:
1677+
logger.debug(f" modular_model_index.json not found in the repo: {e}")
1678+
config_dict = None
1679+
1680+
if config_dict is not None:
16111681
pipeline_class = _get_pipeline_class(cls, config=config_dict)
1612-
except EnvironmentError:
1613-
pipeline_class = cls
1614-
pretrained_model_name_or_path = None
1682+
else:
1683+
try:
1684+
logger.debug(" try to load model_index.json")
1685+
from diffusers import DiffusionPipeline
1686+
from diffusers.pipelines.auto_pipeline import _get_model
1687+
1688+
config_dict = DiffusionPipeline.load_config(pretrained_model_name_or_path, **load_config_kwargs)
1689+
except EnvironmentError as e:
1690+
logger.debug(f" model_index.json not found in the repo: {e}")
1691+
1692+
if config_dict is not None:
1693+
logger.debug(" try to determine the modular pipeline class from model_index.json")
1694+
standard_pipeline_class = _get_pipeline_class(cls, config=config_dict)
1695+
model_name = _get_model(standard_pipeline_class.__name__)
1696+
pipeline_class_name = MODULAR_PIPELINE_MAPPING.get(model_name, ModularPipeline.__name__)
1697+
diffusers_module = importlib.import_module("diffusers")
1698+
pipeline_class = getattr(diffusers_module, pipeline_class_name)
1699+
else:
1700+
# there is no config for modular pipeline, assuming that the pipeline block does not need any from_pretrained components
1701+
pipeline_class = cls
1702+
pretrained_model_name_or_path = None
16151703

16161704
pipeline = pipeline_class(
16171705
blocks=blocks,
@@ -1949,17 +2037,31 @@ def update_components(self, **kwargs):
19492037
for name, component in passed_components.items():
19502038
current_component_spec = self._component_specs[name]
19512039

1952-
# warn if type changed
2040+
# log if type changed
19532041
if current_component_spec.type_hint is not None and not isinstance(
19542042
component, current_component_spec.type_hint
19552043
):
1956-
logger.warning(
2044+
logger.info(
19572045
f"ModularPipeline.update_components: adding {name} with new type: {component.__class__.__name__}, previous type: {current_component_spec.type_hint.__name__}"
19582046
)
19592047
# update _component_specs based on the new component
1960-
new_component_spec = ComponentSpec.from_component(name, component)
1961-
if new_component_spec.default_creation_method != current_component_spec.default_creation_method:
2048+
if component is None:
2049+
new_component_spec = current_component_spec
2050+
if hasattr(self, name) and getattr(self, name) is not None:
2051+
logger.warning(f"ModularPipeline.update_components: setting {name} to None (spec unchanged)")
2052+
elif current_component_spec.default_creation_method == "from_pretrained" and not (
2053+
hasattr(component, "_diffusers_load_id") and component._diffusers_load_id is not None
2054+
):
19622055
logger.warning(
2056+
f"ModularPipeline.update_components: {name} has no valid _diffusers_load_id. "
2057+
f"This will result in empty loading spec, use ComponentSpec.load() for proper specs"
2058+
)
2059+
new_component_spec = ComponentSpec(name=name, type_hint=type(component))
2060+
else:
2061+
new_component_spec = ComponentSpec.from_component(name, component)
2062+
2063+
if new_component_spec.default_creation_method != current_component_spec.default_creation_method:
2064+
logger.info(
19632065
f"ModularPipeline.update_components: changing the default_creation_method of {name} from {current_component_spec.default_creation_method} to {new_component_spec.default_creation_method}."
19642066
)
19652067

@@ -1980,7 +2082,7 @@ def update_components(self, **kwargs):
19802082
if current_component_spec.type_hint is not None and not isinstance(
19812083
created_components[name], current_component_spec.type_hint
19822084
):
1983-
logger.warning(
2085+
logger.info(
19842086
f"ModularPipeline.update_components: adding {name} with new type: {created_components[name].__class__.__name__}, previous type: {current_component_spec.type_hint.__name__}"
19852087
)
19862088
# update _component_specs based on the user passed component_spec

src/diffusers/modular_pipelines/stable_diffusion_xl/before_denoise.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from ...guiders import ClassifierFreeGuidance
2323
from ...image_processor import VaeImageProcessor
2424
from ...models import AutoencoderKL, ControlNetModel, ControlNetUnionModel, UNet2DConditionModel
25-
from ...pipelines.controlnet.multicontrolnet import MultiControlNetModel
25+
from ...models.controlnets.multicontrolnet import MultiControlNetModel
2626
from ...schedulers import EulerDiscreteScheduler
2727
from ...utils import logging
2828
from ...utils.torch_utils import randn_tensor, unwrap_module

src/diffusers/pipelines/pipeline_utils.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1709,6 +1709,36 @@ def _get_signature_types(cls):
17091709
logger.warning(f"cannot get type annotation for Parameter {k} of {cls}.")
17101710
return signature_types
17111711

1712+
@property
1713+
def parameters(self) -> Dict[str, Any]:
1714+
r"""
1715+
The `self.parameters` property can be useful to run different pipelines with the same weights and
1716+
configurations without reallocating additional memory.
1717+
1718+
Returns (`dict`):
1719+
A dictionary containing all the optional parameters needed to initialize the pipeline.
1720+
1721+
Examples:
1722+
1723+
```py
1724+
>>> from diffusers import (
1725+
... StableDiffusionPipeline,
1726+
... StableDiffusionImg2ImgPipeline,
1727+
... StableDiffusionInpaintPipeline,
1728+
... )
1729+
1730+
>>> text2img = StableDiffusionPipeline.from_pretrained("stable-diffusion-v1-5/stable-diffusion-v1-5")
1731+
>>> img2img = StableDiffusionImg2ImgPipeline(**text2img.components, **text2img.parameters)
1732+
>>> inpaint = StableDiffusionInpaintPipeline(**text2img.components, **text2img.parameters)
1733+
```
1734+
"""
1735+
expected_modules, optional_parameters = self._get_signature_keys(self)
1736+
pipeline_parameters = {
1737+
k: self.config[k] for k in self.config.keys() if not k.startswith("_") and k in optional_parameters
1738+
}
1739+
1740+
return pipeline_parameters
1741+
17121742
@property
17131743
def components(self) -> Dict[str, Any]:
17141744
r"""

0 commit comments

Comments
 (0)