3333from requests .exceptions import HTTPError
3434from tqdm .auto import tqdm
3535
36- import diffusers
37-
3836from .. import __version__
3937from ..configuration_utils import ConfigMixin
4038from ..models .modeling_utils import _LOW_CPU_MEM_USAGE_DEFAULT
@@ -305,13 +303,23 @@ def maybe_raise_or_warn(
305303 )
306304
307305
308- def get_class_obj_and_candidates (library_name , class_name , importable_classes , pipelines , is_pipeline_module ):
306+ def get_class_obj_and_candidates (
307+ library_name , class_name , importable_classes , pipelines , is_pipeline_module , component_name = None , cache_dir = None
308+ ):
309309 """Simple helper method to retrieve class object of module as well as potential parent class objects"""
310+ component_folder = os .path .join (cache_dir , component_name )
311+
310312 if is_pipeline_module :
311313 pipeline_module = getattr (pipelines , library_name )
312314
313315 class_obj = getattr (pipeline_module , class_name )
314316 class_candidates = {c : class_obj for c in importable_classes .keys ()}
317+ elif os .path .isfile (os .path .join (component_folder , library_name + ".py" )):
318+ # load custom component
319+ class_obj = get_class_from_dynamic_module (
320+ component_folder , module_file = library_name + ".py" , class_name = class_name
321+ )
322+ class_candidates = {c : class_obj for c in importable_classes .keys ()}
315323 else :
316324 # else we just import it from the library.
317325 library = importlib .import_module (library_name )
@@ -323,19 +331,35 @@ def get_class_obj_and_candidates(library_name, class_name, importable_classes, p
323331
324332
325333def _get_pipeline_class (
326- class_obj , config , load_connected_pipeline = False , custom_pipeline = None , cache_dir = None , revision = None
334+ class_obj ,
335+ config ,
336+ load_connected_pipeline = False ,
337+ custom_pipeline = None ,
338+ repo_id = None ,
339+ hub_revision = None ,
340+ class_name = None ,
341+ cache_dir = None ,
342+ revision = None ,
327343):
328344 if custom_pipeline is not None :
329345 if custom_pipeline .endswith (".py" ):
330346 path = Path (custom_pipeline )
331347 # decompose into folder & file
332348 file_name = path .name
333349 custom_pipeline = path .parent .absolute ()
350+ elif repo_id is not None :
351+ file_name = f"{ custom_pipeline } .py"
352+ custom_pipeline = repo_id
334353 else :
335354 file_name = CUSTOM_PIPELINE_FILE_NAME
336355
337356 return get_class_from_dynamic_module (
338- custom_pipeline , module_file = file_name , cache_dir = cache_dir , revision = revision
357+ custom_pipeline ,
358+ module_file = file_name ,
359+ class_name = class_name ,
360+ repo_id = repo_id ,
361+ cache_dir = cache_dir ,
362+ revision = revision if hub_revision is None else hub_revision ,
339363 )
340364
341365 if class_obj != DiffusionPipeline :
@@ -383,11 +407,18 @@ def load_sub_model(
383407 variant : str ,
384408 low_cpu_mem_usage : bool ,
385409 cached_folder : Union [str , os .PathLike ],
410+ revision : str = None ,
386411):
387412 """Helper method to load the module `name` from `library_name` and `class_name`"""
388413 # retrieve class candidates
389414 class_obj , class_candidates = get_class_obj_and_candidates (
390- library_name , class_name , importable_classes , pipelines , is_pipeline_module
415+ library_name ,
416+ class_name ,
417+ importable_classes ,
418+ pipelines ,
419+ is_pipeline_module ,
420+ component_name = name ,
421+ cache_dir = cached_folder ,
391422 )
392423
393424 load_method_name = None
@@ -414,14 +445,15 @@ def load_sub_model(
414445 load_method = getattr (class_obj , load_method_name )
415446
416447 # add kwargs to loading method
448+ diffusers_module = importlib .import_module (__name__ .split ("." )[0 ])
417449 loading_kwargs = {}
418450 if issubclass (class_obj , torch .nn .Module ):
419451 loading_kwargs ["torch_dtype" ] = torch_dtype
420- if issubclass (class_obj , diffusers .OnnxRuntimeModel ):
452+ if issubclass (class_obj , diffusers_module .OnnxRuntimeModel ):
421453 loading_kwargs ["provider" ] = provider
422454 loading_kwargs ["sess_options" ] = sess_options
423455
424- is_diffusers_model = issubclass (class_obj , diffusers .ModelMixin )
456+ is_diffusers_model = issubclass (class_obj , diffusers_module .ModelMixin )
425457
426458 if is_transformers_available ():
427459 transformers_version = version .parse (version .parse (transformers .__version__ ).base_version )
@@ -501,7 +533,8 @@ class DiffusionPipeline(ConfigMixin, PushToHubMixin):
501533
502534 def register_modules (self , ** kwargs ):
503535 # import it here to avoid circular import
504- from diffusers import pipelines
536+ diffusers_module = importlib .import_module (__name__ .split ("." )[0 ])
537+ pipelines = getattr (diffusers_module , "pipelines" )
505538
506539 for name , module in kwargs .items ():
507540 # retrieve library
@@ -1080,11 +1113,21 @@ def from_pretrained(cls, pretrained_model_name_or_path: Optional[Union[str, os.P
10801113
10811114 # 3. Load the pipeline class, if using custom module then load it from the hub
10821115 # if we load from explicit class, let's use it
1116+ custom_class_name = None
1117+ if os .path .isfile (os .path .join (cached_folder , f"{ custom_pipeline } .py" )):
1118+ custom_pipeline = os .path .join (cached_folder , f"{ custom_pipeline } .py" )
1119+ elif isinstance (config_dict ["_class_name" ], (list , tuple )) and os .path .isfile (
1120+ os .path .join (cached_folder , f"{ config_dict ['_class_name' ][0 ]} .py" )
1121+ ):
1122+ custom_pipeline = os .path .join (cached_folder , f"{ config_dict ['_class_name' ][0 ]} .py" )
1123+ custom_class_name = config_dict ["_class_name" ][1 ]
1124+
10831125 pipeline_class = _get_pipeline_class (
10841126 cls ,
10851127 config_dict ,
10861128 load_connected_pipeline = load_connected_pipeline ,
10871129 custom_pipeline = custom_pipeline ,
1130+ class_name = custom_class_name ,
10881131 cache_dir = cache_dir ,
10891132 revision = custom_revision ,
10901133 )
@@ -1223,6 +1266,7 @@ def load_module(name, value):
12231266 variant = variant ,
12241267 low_cpu_mem_usage = low_cpu_mem_usage ,
12251268 cached_folder = cached_folder ,
1269+ revision = revision ,
12261270 )
12271271 logger .info (
12281272 f"Loaded { name } as { class_name } from `{ name } ` subfolder of { pretrained_model_name_or_path } ."
@@ -1542,6 +1586,10 @@ def download(cls, pretrained_model_name, **kwargs) -> Union[str, os.PathLike]:
15421586 will never be downloaded. By default `use_onnx` defaults to the `_is_onnx` class attribute which is
15431587 `False` for non-ONNX pipelines and `True` for ONNX pipelines. ONNX weights include both files ending
15441588 with `.onnx` and `.pb`.
1589+ trust_remote_code (`bool`, *optional*, defaults to `False`):
1590+ Whether or not to allow for custom pipelines and components defined on the Hub in their own files. This
1591+ option should only be set to `True` for repositories you trust and in which you have read the code, as
1592+ it will execute code present on the Hub on your local machine.
15451593
15461594 Returns:
15471595 `os.PathLike`:
@@ -1569,6 +1617,7 @@ def download(cls, pretrained_model_name, **kwargs) -> Union[str, os.PathLike]:
15691617 use_safetensors = kwargs .pop ("use_safetensors" , None )
15701618 use_onnx = kwargs .pop ("use_onnx" , None )
15711619 load_connected_pipeline = kwargs .pop ("load_connected_pipeline" , False )
1620+ trust_remote_code = kwargs .pop ("trust_remote_code" , False )
15721621
15731622 allow_pickle = False
15741623 if use_safetensors is None :
@@ -1604,15 +1653,34 @@ def download(cls, pretrained_model_name, **kwargs) -> Union[str, os.PathLike]:
16041653 )
16051654
16061655 config_dict = cls ._dict_from_json_file (config_file )
1607-
16081656 ignore_filenames = config_dict .pop ("_ignore_files" , [])
16091657
16101658 # retrieve all folder_names that contain relevant files
1611- folder_names = [k for k , v in config_dict .items () if isinstance (v , list )]
1659+ folder_names = [k for k , v in config_dict .items () if isinstance (v , list ) and k != "_class_name" ]
16121660
16131661 filenames = {sibling .rfilename for sibling in info .siblings }
16141662 model_filenames , variant_filenames = variant_compatible_siblings (filenames , variant = variant )
16151663
1664+ diffusers_module = importlib .import_module (__name__ .split ("." )[0 ])
1665+ pipelines = getattr (diffusers_module , "pipelines" )
1666+
1667+ # optionally create a custom component <> custom file mapping
1668+ custom_components = {}
1669+ for component in folder_names :
1670+ module_candidate = config_dict [component ][0 ]
1671+
1672+ if module_candidate is None :
1673+ continue
1674+
1675+ candidate_file = os .path .join (component , module_candidate + ".py" )
1676+
1677+ if candidate_file in filenames :
1678+ custom_components [component ] = module_candidate
1679+ elif module_candidate not in LOADABLE_CLASSES and not hasattr (pipelines , module_candidate ):
1680+ raise ValueError (
1681+ f"{ candidate_file } as defined in `model_index.json` does not exist in { pretrained_model_name } and is not a module in 'diffusers/pipelines'."
1682+ )
1683+
16161684 if len (variant_filenames ) == 0 and variant is not None :
16171685 deprecation_message = (
16181686 f"You are trying to load the model files of the `variant={ variant } `, but no such modeling files are available."
@@ -1636,12 +1704,21 @@ def download(cls, pretrained_model_name, **kwargs) -> Union[str, os.PathLike]:
16361704
16371705 model_folder_names = {os .path .split (f )[0 ] for f in model_filenames if os .path .split (f )[0 ] in folder_names }
16381706
1707+ custom_class_name = None
1708+ if custom_pipeline is None and isinstance (config_dict ["_class_name" ], (list , tuple )):
1709+ custom_pipeline = config_dict ["_class_name" ][0 ]
1710+ custom_class_name = config_dict ["_class_name" ][1 ]
1711+
16391712 # all filenames compatible with variant will be added
16401713 allow_patterns = list (model_filenames )
16411714
16421715 # allow all patterns from non-model folders
16431716 # this enables downloading schedulers, tokenizers, ...
16441717 allow_patterns += [f"{ k } /*" for k in folder_names if k not in model_folder_names ]
1718+ # add custom component files
1719+ allow_patterns += [f"{ k } /{ f } .py" for k , f in custom_components .items ()]
1720+ # add custom pipeline file
1721+ allow_patterns += [f"{ custom_pipeline } .py" ] if f"{ custom_pipeline } .py" in filenames else []
16451722 # also allow downloading config.json files with the model
16461723 allow_patterns += [os .path .join (k , "config.json" ) for k in model_folder_names ]
16471724
@@ -1652,12 +1729,32 @@ def download(cls, pretrained_model_name, **kwargs) -> Union[str, os.PathLike]:
16521729 CUSTOM_PIPELINE_FILE_NAME ,
16531730 ]
16541731
1732+ load_pipe_from_hub = custom_pipeline is not None and f"{ custom_pipeline } .py" in filenames
1733+ load_components_from_hub = len (custom_components ) > 0
1734+
1735+ if load_pipe_from_hub and not trust_remote_code :
1736+ raise ValueError (
1737+ f"The repository for { pretrained_model_name } contains custom code in { custom_pipeline } .py which must be executed to correctly "
1738+ f"load the model. You can inspect the repository content at https://hf.co/{ pretrained_model_name } /blob/main/{ custom_pipeline } .py.\n "
1739+ f"Please pass the argument `trust_remote_code=True` to allow custom code to be run."
1740+ )
1741+
1742+ if load_components_from_hub and not trust_remote_code :
1743+ raise ValueError (
1744+ f"The repository for { pretrained_model_name } contains custom code in { '.py, ' .join ([os .path .join (k , v ) for k ,v in custom_components .items ()])} which must be executed to correctly "
1745+ f"load the model. You can inspect the repository content at { ', ' .join ([f'https://hf.co/{ pretrained_model_name } /{ k } /{ v } .py' for k ,v in custom_components .items ()])} .\n "
1746+ f"Please pass the argument `trust_remote_code=True` to allow custom code to be run."
1747+ )
1748+
16551749 # retrieve passed components that should not be downloaded
16561750 pipeline_class = _get_pipeline_class (
16571751 cls ,
16581752 config_dict ,
16591753 load_connected_pipeline = load_connected_pipeline ,
16601754 custom_pipeline = custom_pipeline ,
1755+ repo_id = pretrained_model_name if load_pipe_from_hub else None ,
1756+ hub_revision = revision ,
1757+ class_name = custom_class_name ,
16611758 cache_dir = cache_dir ,
16621759 revision = custom_revision ,
16631760 )
@@ -1754,9 +1851,10 @@ def download(cls, pretrained_model_name, **kwargs) -> Union[str, os.PathLike]:
17541851
17551852 # retrieve pipeline class from local file
17561853 cls_name = cls .load_config (os .path .join (cached_folder , "model_index.json" )).get ("_class_name" , None )
1757- cls_name = cls_name [4 :] if cls_name .startswith ("Flax" ) else cls_name
1854+ cls_name = cls_name [4 :] if isinstance ( cls_name , str ) and cls_name .startswith ("Flax" ) else cls_name
17581855
1759- pipeline_class = getattr (diffusers , cls_name , None )
1856+ diffusers_module = importlib .import_module (__name__ .split ("." )[0 ])
1857+ pipeline_class = getattr (diffusers_module , cls_name , None ) if isinstance (cls_name , str ) else None
17601858
17611859 if pipeline_class is not None and pipeline_class ._load_connected_pipes :
17621860 modelcard = ModelCard .load (os .path .join (cached_folder , "README.md" ))
0 commit comments