CoolFace
Apppublic

declare-lab/tango2

sourceHugging Faceupdated 2y agoView on Hugging Face
92likes
dynamic_modules_utils.py457 linesDownload Raw Back to utils
1# coding=utf-82# Copyright 2023 The HuggingFace Inc. team.3#4# Licensed under the Apache License, Version 2.0 (the "License");5# you may not use this file except in compliance with the License.6# You may obtain a copy of the License at7#8#     http://www.apache.org/licenses/LICENSE-2.09#10# Unless required by applicable law or agreed to in writing, software11# distributed under the License is distributed on an "AS IS" BASIS,12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13# See the License for the specific language governing permissions and14# limitations under the License.15"""Utilities to dynamically load objects from the Hub."""16 17import importlib18import inspect19import json20import os21import re22import shutil23import sys24from distutils.version import StrictVersion25from pathlib import Path26from typing import Dict, Optional, Union27from urllib import request28 29from huggingface_hub import HfFolder, cached_download, hf_hub_download, model_info30 31from .. import __version__32from . import DIFFUSERS_DYNAMIC_MODULE_NAME, HF_MODULES_CACHE, logging33 34 35COMMUNITY_PIPELINES_URL = (36    "https://raw.githubusercontent.com/huggingface/diffusers/{revision}/examples/community/{pipeline}.py"37)38 39 40logger = logging.get_logger(__name__)  # pylint: disable=invalid-name41 42 43def get_diffusers_versions():44    url = "https://pypi.org/pypi/diffusers/json"45    releases = json.loads(request.urlopen(url).read())["releases"].keys()46    return sorted(releases, key=StrictVersion)47 48 49def init_hf_modules():50    """51    Creates the cache directory for modules with an init, and adds it to the Python path.52    """53    # This function has already been executed if HF_MODULES_CACHE already is in the Python path.54    if HF_MODULES_CACHE in sys.path:55        return56 57    sys.path.append(HF_MODULES_CACHE)58    os.makedirs(HF_MODULES_CACHE, exist_ok=True)59    init_path = Path(HF_MODULES_CACHE) / "__init__.py"60    if not init_path.exists():61        init_path.touch()62 63 64def create_dynamic_module(name: Union[str, os.PathLike]):65    """66    Creates a dynamic module in the cache directory for modules.67    """68    init_hf_modules()69    dynamic_module_path = Path(HF_MODULES_CACHE) / name70    # If the parent module does not exist yet, recursively create it.71    if not dynamic_module_path.parent.exists():72        create_dynamic_module(dynamic_module_path.parent)73    os.makedirs(dynamic_module_path, exist_ok=True)74    init_path = dynamic_module_path / "__init__.py"75    if not init_path.exists():76        init_path.touch()77 78 79def get_relative_imports(module_file):80    """81    Get the list of modules that are relatively imported in a module file.82 83    Args:84        module_file (`str` or `os.PathLike`): The module file to inspect.85    """86    with open(module_file, "r", encoding="utf-8") as f:87        content = f.read()88 89    # Imports of the form `import .xxx`90    relative_imports = re.findall("^\s*import\s+\.(\S+)\s*$", content, flags=re.MULTILINE)91    # Imports of the form `from .xxx import yyy`92    relative_imports += re.findall("^\s*from\s+\.(\S+)\s+import", content, flags=re.MULTILINE)93    # Unique-ify94    return list(set(relative_imports))95 96 97def get_relative_import_files(module_file):98    """99    Get the list of all files that are needed for a given module. Note that this function recurses through the relative100    imports (if a imports b and b imports c, it will return module files for b and c).101 102    Args:103        module_file (`str` or `os.PathLike`): The module file to inspect.104    """105    no_change = False106    files_to_check = [module_file]107    all_relative_imports = []108 109    # Let's recurse through all relative imports110    while not no_change:111        new_imports = []112        for f in files_to_check:113            new_imports.extend(get_relative_imports(f))114 115        module_path = Path(module_file).parent116        new_import_files = [str(module_path / m) for m in new_imports]117        new_import_files = [f for f in new_import_files if f not in all_relative_imports]118        files_to_check = [f"{f}.py" for f in new_import_files]119 120        no_change = len(new_import_files) == 0121        all_relative_imports.extend(files_to_check)122 123    return all_relative_imports124 125 126def check_imports(filename):127    """128    Check if the current Python environment contains all the libraries that are imported in a file.129    """130    with open(filename, "r", encoding="utf-8") as f:131        content = f.read()132 133    # Imports of the form `import xxx`134    imports = re.findall("^\s*import\s+(\S+)\s*$", content, flags=re.MULTILINE)135    # Imports of the form `from xxx import yyy`136    imports += re.findall("^\s*from\s+(\S+)\s+import", content, flags=re.MULTILINE)137    # Only keep the top-level module138    imports = [imp.split(".")[0] for imp in imports if not imp.startswith(".")]139 140    # Unique-ify and test we got them all141    imports = list(set(imports))142    missing_packages = []143    for imp in imports:144        try:145            importlib.import_module(imp)146        except ImportError:147            missing_packages.append(imp)148 149    if len(missing_packages) > 0:150        raise ImportError(151            "This modeling file requires the following packages that were not found in your environment: "152            f"{', '.join(missing_packages)}. Run `pip install {' '.join(missing_packages)}`"153        )154 155    return get_relative_imports(filename)156 157 158def get_class_in_module(class_name, module_path):159    """160    Import a module on the cache directory for modules and extract a class from it.161    """162    module_path = module_path.replace(os.path.sep, ".")163    module = importlib.import_module(module_path)164 165    if class_name is None:166        return find_pipeline_class(module)167    return getattr(module, class_name)168 169 170def find_pipeline_class(loaded_module):171    """172    Retrieve pipeline class that inherits from `DiffusionPipeline`. Note that there has to be exactly one class173    inheriting from `DiffusionPipeline`.174    """175    from ..pipelines import DiffusionPipeline176 177    cls_members = dict(inspect.getmembers(loaded_module, inspect.isclass))178 179    pipeline_class = None180    for cls_name, cls in cls_members.items():181        if (182            cls_name != DiffusionPipeline.__name__183            and issubclass(cls, DiffusionPipeline)184            and cls.__module__.split(".")[0] != "diffusers"185        ):186            if pipeline_class is not None:187                raise ValueError(188                    f"Multiple classes that inherit from {DiffusionPipeline.__name__} have been found:"189                    f" {pipeline_class.__name__}, and {cls_name}. Please make sure to define only one in"190                    f" {loaded_module}."191                )192            pipeline_class = cls193 194    return pipeline_class195 196 197def get_cached_module_file(198    pretrained_model_name_or_path: Union[str, os.PathLike],199    module_file: str,200    cache_dir: Optional[Union[str, os.PathLike]] = None,201    force_download: bool = False,202    resume_download: bool = False,203    proxies: Optional[Dict[str, str]] = None,204    use_auth_token: Optional[Union[bool, str]] = None,205    revision: Optional[str] = None,206    local_files_only: bool = False,207):208    """209    Prepares Downloads a module from a local folder or a distant repo and returns its path inside the cached210    Transformers module.211 212    Args:213        pretrained_model_name_or_path (`str` or `os.PathLike`):214            This can be either:215 216            - a string, the *model id* of a pretrained model configuration hosted inside a model repo on217              huggingface.co. Valid model ids can be located at the root-level, like `bert-base-uncased`, or namespaced218              under a user or organization name, like `dbmdz/bert-base-german-cased`.219            - a path to a *directory* containing a configuration file saved using the220              [`~PreTrainedTokenizer.save_pretrained`] method, e.g., `./my_model_directory/`.221 222        module_file (`str`):223            The name of the module file containing the class to look for.224        cache_dir (`str` or `os.PathLike`, *optional*):225            Path to a directory in which a downloaded pretrained model configuration should be cached if the standard226            cache should not be used.227        force_download (`bool`, *optional*, defaults to `False`):228            Whether or not to force to (re-)download the configuration files and override the cached versions if they229            exist.230        resume_download (`bool`, *optional*, defaults to `False`):231            Whether or not to delete incompletely received file. Attempts to resume the download if such a file exists.232        proxies (`Dict[str, str]`, *optional*):233            A dictionary of proxy servers to use by protocol or endpoint, e.g., `{'http': 'foo.bar:3128',234            'http://hostname': 'foo.bar:4012'}.` The proxies are used on each request.235        use_auth_token (`str` or *bool*, *optional*):236            The token to use as HTTP bearer authorization for remote files. If `True`, will use the token generated237            when running `transformers-cli login` (stored in `~/.huggingface`).238        revision (`str`, *optional*, defaults to `"main"`):239            The specific model version to use. It can be a branch name, a tag name, or a commit id, since we use a240            git-based system for storing models and other artifacts on huggingface.co, so `revision` can be any241            identifier allowed by git.242        local_files_only (`bool`, *optional*, defaults to `False`):243            If `True`, will only try to load the tokenizer configuration from local files.244 245    <Tip>246 247    You may pass a token in `use_auth_token` if you are not logged in (`huggingface-cli long`) and want to use private248    or [gated models](https://huggingface.co/docs/hub/models-gated#gated-models).249 250    </Tip>251 252    Returns:253        `str`: The path to the module inside the cache.254    """255    # Download and cache module_file from the repo `pretrained_model_name_or_path` of grab it if it's a local file.256    pretrained_model_name_or_path = str(pretrained_model_name_or_path)257 258    module_file_or_url = os.path.join(pretrained_model_name_or_path, module_file)259 260    if os.path.isfile(module_file_or_url):261        resolved_module_file = module_file_or_url262        submodule = "local"263    elif pretrained_model_name_or_path.count("/") == 0:264        available_versions = get_diffusers_versions()265        # cut ".dev0"266        latest_version = "v" + ".".join(__version__.split(".")[:3])267 268        # retrieve github version that matches269        if revision is None:270            revision = latest_version if latest_version in available_versions else "main"271            logger.info(f"Defaulting to latest_version: {revision}.")272        elif revision in available_versions:273            revision = f"v{revision}"274        elif revision == "main":275            revision = revision276        else:277            raise ValueError(278                f"`custom_revision`: {revision} does not exist. Please make sure to choose one of"279                f" {', '.join(available_versions + ['main'])}."280            )281 282        # community pipeline on GitHub283        github_url = COMMUNITY_PIPELINES_URL.format(revision=revision, pipeline=pretrained_model_name_or_path)284        try:285            resolved_module_file = cached_download(286                github_url,287                cache_dir=cache_dir,288                force_download=force_download,289                proxies=proxies,290                resume_download=resume_download,291                local_files_only=local_files_only,292                use_auth_token=False,293            )294            submodule = "git"295            module_file = pretrained_model_name_or_path + ".py"296        except EnvironmentError:297            logger.error(f"Could not locate the {module_file} inside {pretrained_model_name_or_path}.")298            raise299    else:300        try:301            # Load from URL or cache if already cached302            resolved_module_file = hf_hub_download(303                pretrained_model_name_or_path,304                module_file,305                cache_dir=cache_dir,306                force_download=force_download,307                proxies=proxies,308                resume_download=resume_download,309                local_files_only=local_files_only,310                use_auth_token=use_auth_token,311            )312            submodule = os.path.join("local", "--".join(pretrained_model_name_or_path.split("/")))313        except EnvironmentError:314            logger.error(f"Could not locate the {module_file} inside {pretrained_model_name_or_path}.")315            raise316 317    # Check we have all the requirements in our environment318    modules_needed = check_imports(resolved_module_file)319 320    # Now we move the module inside our cached dynamic modules.321    full_submodule = DIFFUSERS_DYNAMIC_MODULE_NAME + os.path.sep + submodule322    create_dynamic_module(full_submodule)323    submodule_path = Path(HF_MODULES_CACHE) / full_submodule324    if submodule == "local" or submodule == "git":325        # We always copy local files (we could hash the file to see if there was a change, and give them the name of326        # that hash, to only copy when there is a modification but it seems overkill for now).327        # The only reason we do the copy is to avoid putting too many folders in sys.path.328        shutil.copy(resolved_module_file, submodule_path / module_file)329        for module_needed in modules_needed:330            module_needed = f"{module_needed}.py"331            shutil.copy(os.path.join(pretrained_model_name_or_path, module_needed), submodule_path / module_needed)332    else:333        # Get the commit hash334        # TODO: we will get this info in the etag soon, so retrieve it from there and not here.335        if isinstance(use_auth_token, str):336            token = use_auth_token337        elif use_auth_token is True:338            token = HfFolder.get_token()339        else:340            token = None341 342        commit_hash = model_info(pretrained_model_name_or_path, revision=revision, token=token).sha343 344        # The module file will end up being placed in a subfolder with the git hash of the repo. This way we get the345        # benefit of versioning.346        submodule_path = submodule_path / commit_hash347        full_submodule = full_submodule + os.path.sep + commit_hash348        create_dynamic_module(full_submodule)349 350        if not (submodule_path / module_file).exists():351            shutil.copy(resolved_module_file, submodule_path / module_file)352        # Make sure we also have every file with relative353        for module_needed in modules_needed:354            if not (submodule_path / module_needed).exists():355                get_cached_module_file(356                    pretrained_model_name_or_path,357                    f"{module_needed}.py",358                    cache_dir=cache_dir,359                    force_download=force_download,360                    resume_download=resume_download,361                    proxies=proxies,362                    use_auth_token=use_auth_token,363                    revision=revision,364                    local_files_only=local_files_only,365                )366    return os.path.join(full_submodule, module_file)367 368 369def get_class_from_dynamic_module(370    pretrained_model_name_or_path: Union[str, os.PathLike],371    module_file: str,372    class_name: Optional[str] = None,373    cache_dir: Optional[Union[str, os.PathLike]] = None,374    force_download: bool = False,375    resume_download: bool = False,376    proxies: Optional[Dict[str, str]] = None,377    use_auth_token: Optional[Union[bool, str]] = None,378    revision: Optional[str] = None,379    local_files_only: bool = False,380    **kwargs,381):382    """383    Extracts a class from a module file, present in the local folder or repository of a model.384 385    <Tip warning={true}>386 387    Calling this function will execute the code in the module file found locally or downloaded from the Hub. It should388    therefore only be called on trusted repos.389 390    </Tip>391 392    Args:393        pretrained_model_name_or_path (`str` or `os.PathLike`):394            This can be either:395 396            - a string, the *model id* of a pretrained model configuration hosted inside a model repo on397              huggingface.co. Valid model ids can be located at the root-level, like `bert-base-uncased`, or namespaced398              under a user or organization name, like `dbmdz/bert-base-german-cased`.399            - a path to a *directory* containing a configuration file saved using the400              [`~PreTrainedTokenizer.save_pretrained`] method, e.g., `./my_model_directory/`.401 402        module_file (`str`):403            The name of the module file containing the class to look for.404        class_name (`str`):405            The name of the class to import in the module.406        cache_dir (`str` or `os.PathLike`, *optional*):407            Path to a directory in which a downloaded pretrained model configuration should be cached if the standard408            cache should not be used.409        force_download (`bool`, *optional*, defaults to `False`):410            Whether or not to force to (re-)download the configuration files and override the cached versions if they411            exist.412        resume_download (`bool`, *optional*, defaults to `False`):413            Whether or not to delete incompletely received file. Attempts to resume the download if such a file exists.414        proxies (`Dict[str, str]`, *optional*):415            A dictionary of proxy servers to use by protocol or endpoint, e.g., `{'http': 'foo.bar:3128',416            'http://hostname': 'foo.bar:4012'}.` The proxies are used on each request.417        use_auth_token (`str` or `bool`, *optional*):418            The token to use as HTTP bearer authorization for remote files. If `True`, will use the token generated419            when running `transformers-cli login` (stored in `~/.huggingface`).420        revision (`str`, *optional*, defaults to `"main"`):421            The specific model version to use. It can be a branch name, a tag name, or a commit id, since we use a422            git-based system for storing models and other artifacts on huggingface.co, so `revision` can be any423            identifier allowed by git.424        local_files_only (`bool`, *optional*, defaults to `False`):425            If `True`, will only try to load the tokenizer configuration from local files.426 427    <Tip>428 429    You may pass a token in `use_auth_token` if you are not logged in (`huggingface-cli long`) and want to use private430    or [gated models](https://huggingface.co/docs/hub/models-gated#gated-models).431 432    </Tip>433 434    Returns:435        `type`: The class, dynamically imported from the module.436 437    Examples:438 439    ```python440    # Download module `modeling.py` from huggingface.co and cache then extract the class `MyBertModel` from this441    # module.442    cls = get_class_from_dynamic_module("sgugger/my-bert-model", "modeling.py", "MyBertModel")443    ```"""444    # And lastly we get the class inside our newly created module445    final_module = get_cached_module_file(446        pretrained_model_name_or_path,447        module_file,448        cache_dir=cache_dir,449        force_download=force_download,450        resume_download=resume_download,451        proxies=proxies,452        use_auth_token=use_auth_token,453        revision=revision,454        local_files_only=local_files_only,455    )456    return get_class_in_module(class_name, final_module.replace(".py", ""))457