DoruC/Grounded-Segment-Anything
0
1# coding=utf-82# Copyright 2021 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."""16import filecmp17import importlib18import os19import re20import shutil21import signal22import sys23import typing24import warnings25from pathlib import Path26from typing import Any, Dict, List, Optional, Union27 28from .utils import (29 HF_MODULES_CACHE,30 TRANSFORMERS_DYNAMIC_MODULE_NAME,31 cached_file,32 extract_commit_hash,33 is_offline_mode,34 logging,35 try_to_load_from_cache,36)37 38 39logger = logging.get_logger(__name__) # pylint: disable=invalid-name40 41 42def init_hf_modules():43 """44 Creates the cache directory for modules with an init, and adds it to the Python path.45 """46 # This function has already been executed if HF_MODULES_CACHE already is in the Python path.47 if HF_MODULES_CACHE in sys.path:48 return49 50 sys.path.append(HF_MODULES_CACHE)51 os.makedirs(HF_MODULES_CACHE, exist_ok=True)52 init_path = Path(HF_MODULES_CACHE) / "__init__.py"53 if not init_path.exists():54 init_path.touch()55 importlib.invalidate_caches()56 57 58def create_dynamic_module(name: Union[str, os.PathLike]):59 """60 Creates a dynamic module in the cache directory for modules.61 62 Args:63 name (`str` or `os.PathLike`):64 The name of the dynamic module to create.65 """66 init_hf_modules()67 dynamic_module_path = (Path(HF_MODULES_CACHE) / name).resolve()68 # If the parent module does not exist yet, recursively create it.69 if not dynamic_module_path.parent.exists():70 create_dynamic_module(dynamic_module_path.parent)71 os.makedirs(dynamic_module_path, exist_ok=True)72 init_path = dynamic_module_path / "__init__.py"73 if not init_path.exists():74 init_path.touch()75 # It is extremely important to invalidate the cache when we change stuff in those modules, or users end up76 # with errors about module that do not exist. Same for all other `invalidate_caches` in this file.77 importlib.invalidate_caches()78 79 80def get_relative_imports(module_file: Union[str, os.PathLike]) -> List[str]:81 """82 Get the list of modules that are relatively imported in a module file.83 84 Args:85 module_file (`str` or `os.PathLike`): The module file to inspect.86 87 Returns:88 `List[str]`: The list of relative imports in the module.89 """90 with open(module_file, "r", encoding="utf-8") as f:91 content = f.read()92 93 # Imports of the form `import .xxx`94 relative_imports = re.findall(r"^\s*import\s+\.(\S+)\s*$", content, flags=re.MULTILINE)95 # Imports of the form `from .xxx import yyy`96 relative_imports += re.findall(r"^\s*from\s+\.(\S+)\s+import", content, flags=re.MULTILINE)97 # Unique-ify98 return list(set(relative_imports))99 100 101def get_relative_import_files(module_file: Union[str, os.PathLike]) -> List[str]:102 """103 Get the list of all files that are needed for a given module. Note that this function recurses through the relative104 imports (if a imports b and b imports c, it will return module files for b and c).105 106 Args:107 module_file (`str` or `os.PathLike`): The module file to inspect.108 109 Returns:110 `List[str]`: The list of all relative imports a given module needs (recursively), which will give us the list111 of module files a given module needs.112 """113 no_change = False114 files_to_check = [module_file]115 all_relative_imports = []116 117 # Let's recurse through all relative imports118 while not no_change:119 new_imports = []120 for f in files_to_check:121 new_imports.extend(get_relative_imports(f))122 123 module_path = Path(module_file).parent124 new_import_files = [str(module_path / m) for m in new_imports]125 new_import_files = [f for f in new_import_files if f not in all_relative_imports]126 files_to_check = [f"{f}.py" for f in new_import_files]127 128 no_change = len(new_import_files) == 0129 all_relative_imports.extend(files_to_check)130 131 return all_relative_imports132 133 134def get_imports(filename: Union[str, os.PathLike]) -> List[str]:135 """136 Extracts all the libraries (not relative imports this time) that are imported in a file.137 138 Args:139 filename (`str` or `os.PathLike`): The module file to inspect.140 141 Returns:142 `List[str]`: The list of all packages required to use the input module.143 """144 with open(filename, "r", encoding="utf-8") as f:145 content = f.read()146 147 # filter out try/except block so in custom code we can have try/except imports148 content = re.sub(r"\s*try\s*:\s*.*?\s*except\s*.*?:", "", content, flags=re.MULTILINE | re.DOTALL)149 150 # Imports of the form `import xxx`151 imports = re.findall(r"^\s*import\s+(\S+)\s*$", content, flags=re.MULTILINE)152 # Imports of the form `from xxx import yyy`153 imports += re.findall(r"^\s*from\s+(\S+)\s+import", content, flags=re.MULTILINE)154 # Only keep the top-level module155 imports = [imp.split(".")[0] for imp in imports if not imp.startswith(".")]156 return list(set(imports))157 158 159def check_imports(filename: Union[str, os.PathLike]) -> List[str]:160 """161 Check if the current Python environment contains all the libraries that are imported in a file. Will raise if a162 library is missing.163 164 Args:165 filename (`str` or `os.PathLike`): The module file to check.166 167 Returns:168 `List[str]`: The list of relative imports in the file.169 """170 imports = get_imports(filename)171 missing_packages = []172 for imp in imports:173 try:174 importlib.import_module(imp)175 except ImportError:176 missing_packages.append(imp)177 178 if len(missing_packages) > 0:179 raise ImportError(180 "This modeling file requires the following packages that were not found in your environment: "181 f"{', '.join(missing_packages)}. Run `pip install {' '.join(missing_packages)}`"182 )183 184 return get_relative_imports(filename)185 186 187def get_class_in_module(class_name: str, module_path: Union[str, os.PathLike]) -> typing.Type:188 """189 Import a module on the cache directory for modules and extract a class from it.190 191 Args:192 class_name (`str`): The name of the class to import.193 module_path (`str` or `os.PathLike`): The path to the module to import.194 195 Returns:196 `typing.Type`: The class looked for.197 """198 module_path = module_path.replace(os.path.sep, ".")199 module = importlib.import_module(module_path)200 return getattr(module, class_name)201 202 203def get_cached_module_file(204 pretrained_model_name_or_path: Union[str, os.PathLike],205 module_file: str,206 cache_dir: Optional[Union[str, os.PathLike]] = None,207 force_download: bool = False,208 resume_download: bool = False,209 proxies: Optional[Dict[str, str]] = None,210 token: Optional[Union[bool, str]] = None,211 revision: Optional[str] = None,212 local_files_only: bool = False,213 repo_type: Optional[str] = None,214 _commit_hash: Optional[str] = None,215 **deprecated_kwargs,216) -> str:217 """218 Prepares Downloads a module from a local folder or a distant repo and returns its path inside the cached219 Transformers module.220 221 Args:222 pretrained_model_name_or_path (`str` or `os.PathLike`):223 This can be either:224 225 - a string, the *model id* of a pretrained model configuration hosted inside a model repo on226 huggingface.co. Valid model ids can be located at the root-level, like `bert-base-uncased`, or namespaced227 under a user or organization name, like `dbmdz/bert-base-german-cased`.228 - a path to a *directory* containing a configuration file saved using the229 [`~PreTrainedTokenizer.save_pretrained`] method, e.g., `./my_model_directory/`.230 231 module_file (`str`):232 The name of the module file containing the class to look for.233 cache_dir (`str` or `os.PathLike`, *optional*):234 Path to a directory in which a downloaded pretrained model configuration should be cached if the standard235 cache should not be used.236 force_download (`bool`, *optional*, defaults to `False`):237 Whether or not to force to (re-)download the configuration files and override the cached versions if they238 exist.239 resume_download (`bool`, *optional*, defaults to `False`):240 Whether or not to delete incompletely received file. Attempts to resume the download if such a file exists.241 proxies (`Dict[str, str]`, *optional*):242 A dictionary of proxy servers to use by protocol or endpoint, e.g., `{'http': 'foo.bar:3128',243 'http://hostname': 'foo.bar:4012'}.` The proxies are used on each request.244 token (`str` or *bool*, *optional*):245 The token to use as HTTP bearer authorization for remote files. If `True`, will use the token generated246 when running `huggingface-cli login` (stored in `~/.huggingface`).247 revision (`str`, *optional*, defaults to `"main"`):248 The specific model version to use. It can be a branch name, a tag name, or a commit id, since we use a249 git-based system for storing models and other artifacts on huggingface.co, so `revision` can be any250 identifier allowed by git.251 local_files_only (`bool`, *optional*, defaults to `False`):252 If `True`, will only try to load the tokenizer configuration from local files.253 repo_type (`str`, *optional*):254 Specify the repo type (useful when downloading from a space for instance).255 256 <Tip>257 258 Passing `token=True` is required when you want to use a private model.259 260 </Tip>261 262 Returns:263 `str`: The path to the module inside the cache.264 """265 use_auth_token = deprecated_kwargs.pop("use_auth_token", None)266 if use_auth_token is not None:267 warnings.warn(268 "The `use_auth_token` argument is deprecated and will be removed in v5 of Transformers.", FutureWarning269 )270 if token is not None:271 raise ValueError("`token` and `use_auth_token` are both specified. Please set only the argument `token`.")272 token = use_auth_token273 274 if is_offline_mode() and not local_files_only:275 logger.info("Offline mode: forcing local_files_only=True")276 local_files_only = True277 278 # Download and cache module_file from the repo `pretrained_model_name_or_path` of grab it if it's a local file.279 pretrained_model_name_or_path = str(pretrained_model_name_or_path)280 is_local = os.path.isdir(pretrained_model_name_or_path)281 if is_local:282 submodule = os.path.basename(pretrained_model_name_or_path)283 else:284 submodule = pretrained_model_name_or_path.replace("/", os.path.sep)285 cached_module = try_to_load_from_cache(286 pretrained_model_name_or_path, module_file, cache_dir=cache_dir, revision=_commit_hash, repo_type=repo_type287 )288 289 new_files = []290 try:291 # Load from URL or cache if already cached292 resolved_module_file = cached_file(293 pretrained_model_name_or_path,294 module_file,295 cache_dir=cache_dir,296 force_download=force_download,297 proxies=proxies,298 resume_download=resume_download,299 local_files_only=local_files_only,300 token=token,301 revision=revision,302 repo_type=repo_type,303 _commit_hash=_commit_hash,304 )305 if not is_local and cached_module != resolved_module_file:306 new_files.append(module_file)307 308 except EnvironmentError:309 logger.error(f"Could not locate the {module_file} inside {pretrained_model_name_or_path}.")310 raise311 312 # Check we have all the requirements in our environment313 modules_needed = check_imports(resolved_module_file)314 315 # Now we move the module inside our cached dynamic modules.316 full_submodule = TRANSFORMERS_DYNAMIC_MODULE_NAME + os.path.sep + submodule317 create_dynamic_module(full_submodule)318 submodule_path = Path(HF_MODULES_CACHE) / full_submodule319 if submodule == os.path.basename(pretrained_model_name_or_path):320 # We copy local files to avoid putting too many folders in sys.path. This copy is done when the file is new or321 # has changed since last copy.322 if not (submodule_path / module_file).exists() or not filecmp.cmp(323 resolved_module_file, str(submodule_path / module_file)324 ):325 shutil.copy(resolved_module_file, submodule_path / module_file)326 importlib.invalidate_caches()327 for module_needed in modules_needed:328 module_needed = f"{module_needed}.py"329 module_needed_file = os.path.join(pretrained_model_name_or_path, module_needed)330 if not (submodule_path / module_needed).exists() or not filecmp.cmp(331 module_needed_file, str(submodule_path / module_needed)332 ):333 shutil.copy(module_needed_file, submodule_path / module_needed)334 importlib.invalidate_caches()335 else:336 # Get the commit hash337 commit_hash = extract_commit_hash(resolved_module_file, _commit_hash)338 339 # The module file will end up being placed in a subfolder with the git hash of the repo. This way we get the340 # benefit of versioning.341 submodule_path = submodule_path / commit_hash342 full_submodule = full_submodule + os.path.sep + commit_hash343 create_dynamic_module(full_submodule)344 345 if not (submodule_path / module_file).exists():346 shutil.copy(resolved_module_file, submodule_path / module_file)347 importlib.invalidate_caches()348 # Make sure we also have every file with relative349 for module_needed in modules_needed:350 if not (submodule_path / f"{module_needed}.py").exists():351 get_cached_module_file(352 pretrained_model_name_or_path,353 f"{module_needed}.py",354 cache_dir=cache_dir,355 force_download=force_download,356 resume_download=resume_download,357 proxies=proxies,358 token=token,359 revision=revision,360 local_files_only=local_files_only,361 _commit_hash=commit_hash,362 )363 new_files.append(f"{module_needed}.py")364 365 if len(new_files) > 0 and revision is None:366 new_files = "\n".join([f"- {f}" for f in new_files])367 repo_type_str = "" if repo_type is None else f"{repo_type}s/"368 url = f"https://huggingface.co/{repo_type_str}{pretrained_model_name_or_path}"369 logger.warning(370 f"A new version of the following files was downloaded from {url}:\n{new_files}"371 "\n. Make sure to double-check they do not contain any added malicious code. To avoid downloading new "372 "versions of the code file, you can pin a revision."373 )374 375 return os.path.join(full_submodule, module_file)376 377 378def get_class_from_dynamic_module(379 class_reference: str,380 pretrained_model_name_or_path: Union[str, os.PathLike],381 cache_dir: Optional[Union[str, os.PathLike]] = None,382 force_download: bool = False,383 resume_download: bool = False,384 proxies: Optional[Dict[str, str]] = None,385 token: Optional[Union[bool, str]] = None,386 revision: Optional[str] = None,387 local_files_only: bool = False,388 repo_type: Optional[str] = None,389 code_revision: Optional[str] = None,390 **kwargs,391) -> typing.Type:392 """393 Extracts a class from a module file, present in the local folder or repository of a model.394 395 <Tip warning={true}>396 397 Calling this function will execute the code in the module file found locally or downloaded from the Hub. It should398 therefore only be called on trusted repos.399 400 </Tip>401 402 Args:403 class_reference (`str`):404 The full name of the class to load, including its module and optionally its repo.405 pretrained_model_name_or_path (`str` or `os.PathLike`):406 This can be either:407 408 - a string, the *model id* of a pretrained model configuration hosted inside a model repo on409 huggingface.co. Valid model ids can be located at the root-level, like `bert-base-uncased`, or namespaced410 under a user or organization name, like `dbmdz/bert-base-german-cased`.411 - a path to a *directory* containing a configuration file saved using the412 [`~PreTrainedTokenizer.save_pretrained`] method, e.g., `./my_model_directory/`.413 414 This is used when `class_reference` does not specify another repo.415 module_file (`str`):416 The name of the module file containing the class to look for.417 class_name (`str`):418 The name of the class to import in the module.419 cache_dir (`str` or `os.PathLike`, *optional*):420 Path to a directory in which a downloaded pretrained model configuration should be cached if the standard421 cache should not be used.422 force_download (`bool`, *optional*, defaults to `False`):423 Whether or not to force to (re-)download the configuration files and override the cached versions if they424 exist.425 resume_download (`bool`, *optional*, defaults to `False`):426 Whether or not to delete incompletely received file. Attempts to resume the download if such a file exists.427 proxies (`Dict[str, str]`, *optional*):428 A dictionary of proxy servers to use by protocol or endpoint, e.g., `{'http': 'foo.bar:3128',429 'http://hostname': 'foo.bar:4012'}.` The proxies are used on each request.430 token (`str` or `bool`, *optional*):431 The token to use as HTTP bearer authorization for remote files. If `True`, will use the token generated432 when running `huggingface-cli login` (stored in `~/.huggingface`).433 revision (`str`, *optional*, defaults to `"main"`):434 The specific model version to use. It can be a branch name, a tag name, or a commit id, since we use a435 git-based system for storing models and other artifacts on huggingface.co, so `revision` can be any436 identifier allowed by git.437 local_files_only (`bool`, *optional*, defaults to `False`):438 If `True`, will only try to load the tokenizer configuration from local files.439 repo_type (`str`, *optional*):440 Specify the repo type (useful when downloading from a space for instance).441 code_revision (`str`, *optional*, defaults to `"main"`):442 The specific revision to use for the code on the Hub, if the code leaves in a different repository than the443 rest of the model. It can be a branch name, a tag name, or a commit id, since we use a git-based system for444 storing models and other artifacts on huggingface.co, so `revision` can be any identifier allowed by git.445 446 <Tip>447 448 Passing `token=True` is required when you want to use a private model.449 450 </Tip>451 452 Returns:453 `typing.Type`: The class, dynamically imported from the module.454 455 Examples:456 457 ```python458 # Download module `modeling.py` from huggingface.co and cache then extract the class `MyBertModel` from this459 # module.460 cls = get_class_from_dynamic_module("modeling.MyBertModel", "sgugger/my-bert-model")461 462 # Download module `modeling.py` from a given repo and cache then extract the class `MyBertModel` from this463 # module.464 cls = get_class_from_dynamic_module("sgugger/my-bert-model--modeling.MyBertModel", "sgugger/another-bert-model")465 ```"""466 use_auth_token = kwargs.pop("use_auth_token", None)467 if use_auth_token is not None:468 warnings.warn(469 "The `use_auth_token` argument is deprecated and will be removed in v5 of Transformers.", FutureWarning470 )471 if token is not None:472 raise ValueError("`token` and `use_auth_token` are both specified. Please set only the argument `token`.")473 token = use_auth_token474 475 # Catch the name of the repo if it's specified in `class_reference`476 if "--" in class_reference:477 repo_id, class_reference = class_reference.split("--")478 else:479 repo_id = pretrained_model_name_or_path480 module_file, class_name = class_reference.split(".")481 482 if code_revision is None and pretrained_model_name_or_path == repo_id:483 code_revision = revision484 # And lastly we get the class inside our newly created module485 final_module = get_cached_module_file(486 repo_id,487 module_file + ".py",488 cache_dir=cache_dir,489 force_download=force_download,490 resume_download=resume_download,491 proxies=proxies,492 token=token,493 revision=code_revision,494 local_files_only=local_files_only,495 repo_type=repo_type,496 )497 return get_class_in_module(class_name, final_module.replace(".py", ""))498 499 500def custom_object_save(obj: Any, folder: Union[str, os.PathLike], config: Optional[Dict] = None) -> List[str]:501 """502 Save the modeling files corresponding to a custom model/configuration/tokenizer etc. in a given folder. Optionally503 adds the proper fields in a config.504 505 Args:506 obj (`Any`): The object for which to save the module files.507 folder (`str` or `os.PathLike`): The folder where to save.508 config (`PretrainedConfig` or dictionary, `optional`):509 A config in which to register the auto_map corresponding to this custom object.510 511 Returns:512 `List[str]`: The list of files saved.513 """514 if obj.__module__ == "__main__":515 logger.warning(516 f"We can't save the code defining {obj} in {folder} as it's been defined in __main__. You should put "517 "this code in a separate module so we can include it in the saved folder and make it easier to share via "518 "the Hub."519 )520 return521 522 def _set_auto_map_in_config(_config):523 module_name = obj.__class__.__module__524 last_module = module_name.split(".")[-1]525 full_name = f"{last_module}.{obj.__class__.__name__}"526 # Special handling for tokenizers527 if "Tokenizer" in full_name:528 slow_tokenizer_class = None529 fast_tokenizer_class = None530 if obj.__class__.__name__.endswith("Fast"):531 # Fast tokenizer: we have the fast tokenizer class and we may have the slow one has an attribute.532 fast_tokenizer_class = f"{last_module}.{obj.__class__.__name__}"533 if getattr(obj, "slow_tokenizer_class", None) is not None:534 slow_tokenizer = getattr(obj, "slow_tokenizer_class")535 slow_tok_module_name = slow_tokenizer.__module__536 last_slow_tok_module = slow_tok_module_name.split(".")[-1]537 slow_tokenizer_class = f"{last_slow_tok_module}.{slow_tokenizer.__name__}"538 else:539 # Slow tokenizer: no way to have the fast class540 slow_tokenizer_class = f"{last_module}.{obj.__class__.__name__}"541 542 full_name = (slow_tokenizer_class, fast_tokenizer_class)543 544 if isinstance(_config, dict):545 auto_map = _config.get("auto_map", {})546 auto_map[obj._auto_class] = full_name547 _config["auto_map"] = auto_map548 elif getattr(_config, "auto_map", None) is not None:549 _config.auto_map[obj._auto_class] = full_name550 else:551 _config.auto_map = {obj._auto_class: full_name}552 553 # Add object class to the config auto_map554 if isinstance(config, (list, tuple)):555 for cfg in config:556 _set_auto_map_in_config(cfg)557 elif config is not None:558 _set_auto_map_in_config(config)559 560 result = []561 # Copy module file to the output folder.562 object_file = sys.modules[obj.__module__].__file__563 dest_file = Path(folder) / (Path(object_file).name)564 shutil.copy(object_file, dest_file)565 result.append(dest_file)566 567 # Gather all relative imports recursively and make sure they are copied as well.568 for needed_file in get_relative_import_files(object_file):569 dest_file = Path(folder) / (Path(needed_file).name)570 shutil.copy(needed_file, dest_file)571 result.append(dest_file)572 573 return result574 575 576def _raise_timeout_error(signum, frame):577 raise ValueError(578 "Loading this model requires you to execute custom code contained in the model repository on your local"579 "machine. Please set the option `trust_remote_code=True` to permit loading of this model."580 )581 582 583TIME_OUT_REMOTE_CODE = 15584 585 586def resolve_trust_remote_code(trust_remote_code, model_name, has_local_code, has_remote_code):587 if trust_remote_code is None:588 if has_local_code:589 trust_remote_code = False590 elif has_remote_code and TIME_OUT_REMOTE_CODE > 0:591 try:592 signal.signal(signal.SIGALRM, _raise_timeout_error)593 signal.alarm(TIME_OUT_REMOTE_CODE)594 while trust_remote_code is None:595 answer = input(596 f"The repository for {model_name} contains custom code which must be executed to correctly"597 f"load the model. You can inspect the repository content at https://hf.co/{model_name}.\n"598 f"You can avoid this prompt in future by passing the argument `trust_remote_code=True`.\n\n"599 f"Do you wish to run the custom code? [y/N] "600 )601 if answer.lower() in ["yes", "y", "1"]:602 trust_remote_code = True603 elif answer.lower() in ["no", "n", "0", ""]:604 trust_remote_code = False605 signal.alarm(0)606 except Exception:607 # OS which does not support signal.SIGALRM608 raise ValueError(609 f"The repository for {model_name} contains custom code which must be executed to correctly"610 f"load the model. You can inspect the repository content at https://hf.co/{model_name}.\n"611 f"Please pass the argument `trust_remote_code=True` to allow custom code to be run."612 )613 elif has_remote_code:614 # For the CI which puts the timeout at 0615 _raise_timeout_error(None, None)616 617 if has_remote_code and not has_local_code and not trust_remote_code:618 raise ValueError(619 f"Loading {model_name} requires you to execute the configuration file in that"620 " repo on your local machine. Make sure you have read the code there to avoid malicious use, then"621 " set the option `trust_remote_code=True` to remove this error."622 )623 624 return trust_remote_code625 