Aluode/PerceptionLabPortable
0
1# Copyright 2021 The HuggingFace Inc. team.2#3# Licensed under the Apache License, Version 2.0 (the "License");4# you may not use this file except in compliance with the License.5# You may obtain a copy of the License at6#7# http://www.apache.org/licenses/LICENSE-2.08#9# Unless required by applicable law or agreed to in writing, software10# distributed under the License is distributed on an "AS IS" BASIS,11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12# See the License for the specific language governing permissions and13# limitations under the License.14"""Utilities to dynamically load objects from the Hub."""15 16import ast17import filecmp18import hashlib19import importlib20import importlib.metadata21import importlib.util22import keyword23import os24import re25import shutil26import signal27import sys28import threading29import warnings30from pathlib import Path31from types import ModuleType32from typing import Any, Optional, Union33 34from huggingface_hub import try_to_load_from_cache35from packaging import version36 37from .utils import (38 HF_MODULES_CACHE,39 TRANSFORMERS_DYNAMIC_MODULE_NAME,40 cached_file,41 extract_commit_hash,42 is_offline_mode,43 logging,44)45from .utils.import_utils import VersionComparison, split_package_version46 47 48logger = logging.get_logger(__name__) # pylint: disable=invalid-name49 50 51def _sanitize_module_name(name: str) -> str:52 r"""53 Tries to sanitize a module name so that it can be used as a Python module.54 55 The following transformations are applied:56 57 1. Replace `.` in module names with `_dot_`.58 2. Replace `-` in module names with `_hyphen_`.59 3. If the module name starts with a digit, prepend it with `_`.60 4. Warn if the sanitized name is a Python reserved keyword or not a valid identifier.61 62 If the input name is already a valid identifier, it is returned unchanged.63 """64 # We not replacing `\W` characters with `_` to avoid collisions. Because `_` is a very common65 # separator used in module names, replacing `\W` with `_` would create too many collisions.66 # Once a module is imported, it is cached in `sys.modules` and the second import would return67 # the first module, which might not be the expected behavior if name collisions happen.68 new_name = name.replace(".", "_dot_").replace("-", "_hyphen_")69 if new_name and new_name[0].isdigit():70 new_name = f"_{new_name}"71 if keyword.iskeyword(new_name):72 logger.warning(73 f"The module name {new_name} (originally {name}) is a reserved keyword in Python. "74 "Please rename the original module to avoid import issues."75 )76 elif not new_name.isidentifier():77 logger.warning(78 f"The module name {new_name} (originally {name}) is not a valid Python identifier. "79 "Please rename the original module to avoid import issues."80 )81 return new_name82 83 84_HF_REMOTE_CODE_LOCK = threading.Lock()85 86 87def init_hf_modules():88 """89 Creates the cache directory for modules with an init, and adds it to the Python path.90 """91 # This function has already been executed if HF_MODULES_CACHE already is in the Python path.92 if HF_MODULES_CACHE in sys.path:93 return94 95 sys.path.append(HF_MODULES_CACHE)96 os.makedirs(HF_MODULES_CACHE, exist_ok=True)97 init_path = Path(HF_MODULES_CACHE) / "__init__.py"98 if not init_path.exists():99 init_path.touch()100 importlib.invalidate_caches()101 102 103def create_dynamic_module(name: Union[str, os.PathLike]) -> None:104 """105 Creates a dynamic module in the cache directory for modules.106 107 Args:108 name (`str` or `os.PathLike`):109 The name of the dynamic module to create.110 """111 init_hf_modules()112 dynamic_module_path = (Path(HF_MODULES_CACHE) / name).resolve()113 # If the parent module does not exist yet, recursively create it.114 if not dynamic_module_path.parent.exists():115 create_dynamic_module(dynamic_module_path.parent)116 os.makedirs(dynamic_module_path, exist_ok=True)117 init_path = dynamic_module_path / "__init__.py"118 if not init_path.exists():119 init_path.touch()120 # It is extremely important to invalidate the cache when we change stuff in those modules, or users end up121 # with errors about module that do not exist. Same for all other `invalidate_caches` in this file.122 importlib.invalidate_caches()123 124 125def get_relative_imports(module_file: Union[str, os.PathLike]) -> list[str]:126 """127 Get the list of modules that are relatively imported in a module file.128 129 Args:130 module_file (`str` or `os.PathLike`): The module file to inspect.131 132 Returns:133 `list[str]`: The list of relative imports in the module.134 """135 with open(module_file, encoding="utf-8") as f:136 content = f.read()137 138 # Imports of the form `import .xxx`139 relative_imports = re.findall(r"^\s*import\s+\.(\S+)\s*$", content, flags=re.MULTILINE)140 # Imports of the form `from .xxx import yyy`141 relative_imports += re.findall(r"^\s*from\s+\.(\S+)\s+import", content, flags=re.MULTILINE)142 # Unique-ify143 return list(set(relative_imports))144 145 146def get_relative_import_files(module_file: Union[str, os.PathLike]) -> list[str]:147 """148 Get the list of all files that are needed for a given module. Note that this function recurses through the relative149 imports (if a imports b and b imports c, it will return module files for b and c).150 151 Args:152 module_file (`str` or `os.PathLike`): The module file to inspect.153 154 Returns:155 `list[str]`: The list of all relative imports a given module needs (recursively), which will give us the list156 of module files a given module needs.157 """158 no_change = False159 files_to_check = [module_file]160 all_relative_imports = []161 162 # Let's recurse through all relative imports163 while not no_change:164 new_imports = []165 for f in files_to_check:166 new_imports.extend(get_relative_imports(f))167 168 module_path = Path(module_file).parent169 new_import_files = [f"{str(module_path / m)}.py" for m in new_imports]170 files_to_check = [f for f in new_import_files if f not in all_relative_imports]171 172 no_change = len(files_to_check) == 0173 all_relative_imports.extend(files_to_check)174 175 return all_relative_imports176 177 178def get_imports(filename: Union[str, os.PathLike]) -> list[str]:179 """180 Extracts all the libraries (not relative imports this time) that are imported in a file.181 182 Args:183 filename (`str` or `os.PathLike`): The module file to inspect.184 185 Returns:186 `list[str]`: The list of all packages required to use the input module.187 """188 with open(filename, encoding="utf-8") as f:189 content = f.read()190 imported_modules = set()191 192 import transformers.utils193 194 def recursive_look_for_imports(node):195 if isinstance(node, ast.Try):196 return # Don't recurse into Try blocks and ignore imports in them197 elif isinstance(node, ast.If):198 test = node.test199 for condition_node in ast.walk(test):200 if isinstance(condition_node, ast.Call):201 check_function = getattr(condition_node.func, "id", "")202 if (203 check_function.endswith("available")204 and check_function.startswith("is_flash_attn")205 or hasattr(transformers.utils.import_utils, check_function)206 ):207 # Don't recurse into "if flash_attn_available()" or any "if library_available" blocks208 # that appears in `transformers.utils.import_utils` and ignore imports in them209 return210 elif isinstance(node, ast.Import):211 # Handle 'import x' statements212 for alias in node.names:213 top_module = alias.name.split(".")[0]214 if top_module:215 imported_modules.add(top_module)216 elif isinstance(node, ast.ImportFrom):217 # Handle 'from x import y' statements, ignoring relative imports218 if node.level == 0 and node.module:219 top_module = node.module.split(".")[0]220 if top_module:221 imported_modules.add(top_module)222 223 # Recursively visit all children224 for child in ast.iter_child_nodes(node):225 recursive_look_for_imports(child)226 227 tree = ast.parse(content)228 recursive_look_for_imports(tree)229 230 return sorted(imported_modules)231 232 233def check_imports(filename: Union[str, os.PathLike]) -> list[str]:234 """235 Check if the current Python environment contains all the libraries that are imported in a file. Will raise if a236 library is missing.237 238 Args:239 filename (`str` or `os.PathLike`): The module file to check.240 241 Returns:242 `list[str]`: The list of relative imports in the file.243 """244 imports = get_imports(filename)245 missing_packages = []246 for imp in imports:247 try:248 importlib.import_module(imp)249 except ImportError as exception:250 logger.warning(f"Encountered exception while importing {imp}: {exception}")251 # Some packages can fail with an ImportError because of a dependency issue.252 # This check avoids hiding such errors.253 # See https://github.com/huggingface/transformers/issues/33604254 if "No module named" in str(exception):255 missing_packages.append(imp)256 else:257 raise258 259 if len(missing_packages) > 0:260 raise ImportError(261 "This modeling file requires the following packages that were not found in your environment: "262 f"{', '.join(missing_packages)}. Run `pip install {' '.join(missing_packages)}`"263 )264 265 return get_relative_imports(filename)266 267 268def get_class_in_module(269 class_name: str,270 module_path: Union[str, os.PathLike],271 *,272 force_reload: bool = False,273) -> type:274 """275 Import a module on the cache directory for modules and extract a class from it.276 277 Args:278 class_name (`str`): The name of the class to import.279 module_path (`str` or `os.PathLike`): The path to the module to import.280 force_reload (`bool`, *optional*, defaults to `False`):281 Whether to reload the dynamic module from file if it already exists in `sys.modules`.282 Otherwise, the module is only reloaded if the file has changed.283 284 Returns:285 `typing.Type`: The class looked for.286 """287 name = os.path.normpath(module_path)288 name = name.removesuffix(".py")289 name = name.replace(os.path.sep, ".")290 module_file: Path = Path(HF_MODULES_CACHE) / module_path291 with _HF_REMOTE_CODE_LOCK:292 if force_reload:293 sys.modules.pop(name, None)294 importlib.invalidate_caches()295 cached_module: Optional[ModuleType] = sys.modules.get(name)296 module_spec = importlib.util.spec_from_file_location(name, location=module_file)297 298 # Hash the module file and all its relative imports to check if we need to reload it299 module_files: list[Path] = [module_file] + sorted(map(Path, get_relative_import_files(module_file)))300 module_hash: str = hashlib.sha256(b"".join(bytes(f) + f.read_bytes() for f in module_files)).hexdigest()301 302 module: ModuleType303 if cached_module is None:304 module = importlib.util.module_from_spec(module_spec)305 # insert it into sys.modules before any loading begins306 sys.modules[name] = module307 else:308 module = cached_module309 # reload in both cases, unless the module is already imported and the hash hits310 if getattr(module, "__transformers_module_hash__", "") != module_hash:311 module_spec.loader.exec_module(module)312 module.__transformers_module_hash__ = module_hash313 return getattr(module, class_name)314 315 316def get_cached_module_file(317 pretrained_model_name_or_path: Union[str, os.PathLike],318 module_file: str,319 cache_dir: Optional[Union[str, os.PathLike]] = None,320 force_download: bool = False,321 resume_download: Optional[bool] = None,322 proxies: Optional[dict[str, str]] = None,323 token: Optional[Union[bool, str]] = None,324 revision: Optional[str] = None,325 local_files_only: bool = False,326 repo_type: Optional[str] = None,327 _commit_hash: Optional[str] = None,328 **deprecated_kwargs,329) -> str:330 """331 Prepares Downloads a module from a local folder or a distant repo and returns its path inside the cached332 Transformers module.333 334 Args:335 pretrained_model_name_or_path (`str` or `os.PathLike`):336 This can be either:337 338 - a string, the *model id* of a pretrained model configuration hosted inside a model repo on339 huggingface.co.340 - a path to a *directory* containing a configuration file saved using the341 [`~PreTrainedTokenizer.save_pretrained`] method, e.g., `./my_model_directory/`.342 343 module_file (`str`):344 The name of the module file containing the class to look for.345 cache_dir (`str` or `os.PathLike`, *optional*):346 Path to a directory in which a downloaded pretrained model configuration should be cached if the standard347 cache should not be used.348 force_download (`bool`, *optional*, defaults to `False`):349 Whether or not to force to (re-)download the configuration files and override the cached versions if they350 exist.351 resume_download:352 Deprecated and ignored. All downloads are now resumed by default when possible.353 Will be removed in v5 of Transformers.354 proxies (`dict[str, str]`, *optional*):355 A dictionary of proxy servers to use by protocol or endpoint, e.g., `{'http': 'foo.bar:3128',356 'http://hostname': 'foo.bar:4012'}.` The proxies are used on each request.357 token (`str` or *bool*, *optional*):358 The token to use as HTTP bearer authorization for remote files. If `True`, will use the token generated359 when running `hf auth login` (stored in `~/.huggingface`).360 revision (`str`, *optional*, defaults to `"main"`):361 The specific model version to use. It can be a branch name, a tag name, or a commit id, since we use a362 git-based system for storing models and other artifacts on huggingface.co, so `revision` can be any363 identifier allowed by git.364 local_files_only (`bool`, *optional*, defaults to `False`):365 If `True`, will only try to load the tokenizer configuration from local files.366 repo_type (`str`, *optional*):367 Specify the repo type (useful when downloading from a space for instance).368 369 <Tip>370 371 Passing `token=True` is required when you want to use a private model.372 373 </Tip>374 375 Returns:376 `str`: The path to the module inside the cache.377 """378 use_auth_token = deprecated_kwargs.pop("use_auth_token", None)379 if use_auth_token is not None:380 warnings.warn(381 "The `use_auth_token` argument is deprecated and will be removed in v5 of Transformers. Please use `token` instead.",382 FutureWarning,383 )384 if token is not None:385 raise ValueError("`token` and `use_auth_token` are both specified. Please set only the argument `token`.")386 token = use_auth_token387 388 if is_offline_mode() and not local_files_only:389 logger.info("Offline mode: forcing local_files_only=True")390 local_files_only = True391 392 # Download and cache module_file from the repo `pretrained_model_name_or_path` of grab it if it's a local file.393 pretrained_model_name_or_path = str(pretrained_model_name_or_path)394 is_local = os.path.isdir(pretrained_model_name_or_path)395 if is_local:396 submodule = _sanitize_module_name(os.path.basename(pretrained_model_name_or_path))397 else:398 submodule = os.path.sep.join(map(_sanitize_module_name, pretrained_model_name_or_path.split("/")))399 cached_module = try_to_load_from_cache(400 pretrained_model_name_or_path, module_file, cache_dir=cache_dir, revision=_commit_hash, repo_type=repo_type401 )402 403 new_files = []404 try:405 # Load from URL or cache if already cached406 resolved_module_file = cached_file(407 pretrained_model_name_or_path,408 module_file,409 cache_dir=cache_dir,410 force_download=force_download,411 proxies=proxies,412 resume_download=resume_download,413 local_files_only=local_files_only,414 token=token,415 revision=revision,416 repo_type=repo_type,417 _commit_hash=_commit_hash,418 )419 if not is_local and cached_module != resolved_module_file:420 new_files.append(module_file)421 422 except OSError:423 logger.info(f"Could not locate the {module_file} inside {pretrained_model_name_or_path}.")424 raise425 426 # Check we have all the requirements in our environment427 modules_needed = check_imports(resolved_module_file)428 429 # Now we move the module inside our cached dynamic modules.430 full_submodule = TRANSFORMERS_DYNAMIC_MODULE_NAME + os.path.sep + submodule431 create_dynamic_module(full_submodule)432 submodule_path = Path(HF_MODULES_CACHE) / full_submodule433 if submodule == _sanitize_module_name(os.path.basename(pretrained_model_name_or_path)):434 # We copy local files to avoid putting too many folders in sys.path. This copy is done when the file is new or435 # has changed since last copy.436 if not (submodule_path / module_file).exists() or not filecmp.cmp(437 resolved_module_file, str(submodule_path / module_file)438 ):439 (submodule_path / module_file).parent.mkdir(parents=True, exist_ok=True)440 shutil.copy(resolved_module_file, submodule_path / module_file)441 importlib.invalidate_caches()442 for module_needed in modules_needed:443 module_needed = Path(module_file).parent / f"{module_needed}.py"444 module_needed_file = os.path.join(pretrained_model_name_or_path, module_needed)445 if not (submodule_path / module_needed).exists() or not filecmp.cmp(446 module_needed_file, str(submodule_path / module_needed)447 ):448 shutil.copy(module_needed_file, submodule_path / module_needed)449 importlib.invalidate_caches()450 else:451 # Get the commit hash452 commit_hash = extract_commit_hash(resolved_module_file, _commit_hash)453 454 # The module file will end up being placed in a subfolder with the git hash of the repo. This way we get the455 # benefit of versioning.456 submodule_path = submodule_path / commit_hash457 full_submodule = full_submodule + os.path.sep + commit_hash458 full_submodule_module_file_path = os.path.join(full_submodule, module_file)459 create_dynamic_module(Path(full_submodule_module_file_path).parent)460 461 if not (submodule_path / module_file).exists():462 shutil.copy(resolved_module_file, submodule_path / module_file)463 importlib.invalidate_caches()464 # Make sure we also have every file with relative465 for module_needed in modules_needed:466 if not ((submodule_path / module_file).parent / f"{module_needed}.py").exists():467 get_cached_module_file(468 pretrained_model_name_or_path,469 f"{Path(module_file).parent / module_needed}.py",470 cache_dir=cache_dir,471 force_download=force_download,472 resume_download=resume_download,473 proxies=proxies,474 token=token,475 revision=revision,476 local_files_only=local_files_only,477 _commit_hash=commit_hash,478 )479 new_files.append(f"{module_needed}.py")480 481 if len(new_files) > 0 and revision is None:482 new_files = "\n".join([f"- {f}" for f in new_files])483 repo_type_str = "" if repo_type is None else f"{repo_type}s/"484 url = f"https://huggingface.co/{repo_type_str}{pretrained_model_name_or_path}"485 logger.warning(486 f"A new version of the following files was downloaded from {url}:\n{new_files}"487 "\n. Make sure to double-check they do not contain any added malicious code. To avoid downloading new "488 "versions of the code file, you can pin a revision."489 )490 491 return os.path.join(full_submodule, module_file)492 493 494def get_class_from_dynamic_module(495 class_reference: str,496 pretrained_model_name_or_path: Union[str, os.PathLike],497 cache_dir: Optional[Union[str, os.PathLike]] = None,498 force_download: bool = False,499 resume_download: Optional[bool] = None,500 proxies: Optional[dict[str, str]] = None,501 token: Optional[Union[bool, str]] = None,502 revision: Optional[str] = None,503 local_files_only: bool = False,504 repo_type: Optional[str] = None,505 code_revision: Optional[str] = None,506 **kwargs,507) -> type:508 """509 Extracts a class from a module file, present in the local folder or repository of a model.510 511 <Tip warning={true}>512 513 Calling this function will execute the code in the module file found locally or downloaded from the Hub. It should514 therefore only be called on trusted repos.515 516 </Tip>517 518 519 520 Args:521 class_reference (`str`):522 The full name of the class to load, including its module and optionally its repo.523 pretrained_model_name_or_path (`str` or `os.PathLike`):524 This can be either:525 526 - a string, the *model id* of a pretrained model configuration hosted inside a model repo on527 huggingface.co.528 - a path to a *directory* containing a configuration file saved using the529 [`~PreTrainedTokenizer.save_pretrained`] method, e.g., `./my_model_directory/`.530 531 This is used when `class_reference` does not specify another repo.532 module_file (`str`):533 The name of the module file containing the class to look for.534 class_name (`str`):535 The name of the class to import in the module.536 cache_dir (`str` or `os.PathLike`, *optional*):537 Path to a directory in which a downloaded pretrained model configuration should be cached if the standard538 cache should not be used.539 force_download (`bool`, *optional*, defaults to `False`):540 Whether or not to force to (re-)download the configuration files and override the cached versions if they541 exist.542 resume_download:543 Deprecated and ignored. All downloads are now resumed by default when possible.544 Will be removed in v5 of Transformers.545 proxies (`dict[str, str]`, *optional*):546 A dictionary of proxy servers to use by protocol or endpoint, e.g., `{'http': 'foo.bar:3128',547 'http://hostname': 'foo.bar:4012'}.` The proxies are used on each request.548 token (`str` or `bool`, *optional*):549 The token to use as HTTP bearer authorization for remote files. If `True`, will use the token generated550 when running `hf auth login` (stored in `~/.huggingface`).551 revision (`str`, *optional*, defaults to `"main"`):552 The specific model version to use. It can be a branch name, a tag name, or a commit id, since we use a553 git-based system for storing models and other artifacts on huggingface.co, so `revision` can be any554 identifier allowed by git.555 local_files_only (`bool`, *optional*, defaults to `False`):556 If `True`, will only try to load the tokenizer configuration from local files.557 repo_type (`str`, *optional*):558 Specify the repo type (useful when downloading from a space for instance).559 code_revision (`str`, *optional*, defaults to `"main"`):560 The specific revision to use for the code on the Hub, if the code leaves in a different repository than the561 rest of the model. It can be a branch name, a tag name, or a commit id, since we use a git-based system for562 storing models and other artifacts on huggingface.co, so `revision` can be any identifier allowed by git.563 564 <Tip>565 566 Passing `token=True` is required when you want to use a private model.567 568 </Tip>569 570 Returns:571 `typing.Type`: The class, dynamically imported from the module.572 573 Examples:574 575 ```python576 # Download module `modeling.py` from huggingface.co and cache then extract the class `MyBertModel` from this577 # module.578 cls = get_class_from_dynamic_module("modeling.MyBertModel", "sgugger/my-bert-model")579 580 # Download module `modeling.py` from a given repo and cache then extract the class `MyBertModel` from this581 # module.582 cls = get_class_from_dynamic_module("sgugger/my-bert-model--modeling.MyBertModel", "sgugger/another-bert-model")583 ```"""584 use_auth_token = kwargs.pop("use_auth_token", None)585 if use_auth_token is not None:586 warnings.warn(587 "The `use_auth_token` argument is deprecated and will be removed in v5 of Transformers. Please use `token` instead.",588 FutureWarning,589 )590 if token is not None:591 raise ValueError("`token` and `use_auth_token` are both specified. Please set only the argument `token`.")592 token = use_auth_token593 594 # Catch the name of the repo if it's specified in `class_reference`595 if "--" in class_reference:596 repo_id, class_reference = class_reference.split("--")597 else:598 repo_id = pretrained_model_name_or_path599 module_file, class_name = class_reference.split(".")600 601 if code_revision is None and pretrained_model_name_or_path == repo_id:602 code_revision = revision603 # And lastly we get the class inside our newly created module604 final_module = get_cached_module_file(605 repo_id,606 module_file + ".py",607 cache_dir=cache_dir,608 force_download=force_download,609 resume_download=resume_download,610 proxies=proxies,611 token=token,612 revision=code_revision,613 local_files_only=local_files_only,614 repo_type=repo_type,615 )616 return get_class_in_module(class_name, final_module, force_reload=force_download)617 618 619def custom_object_save(obj: Any, folder: Union[str, os.PathLike], config: Optional[dict] = None) -> list[str]:620 """621 Save the modeling files corresponding to a custom model/configuration/tokenizer etc. in a given folder. Optionally622 adds the proper fields in a config.623 624 Args:625 obj (`Any`): The object for which to save the module files.626 folder (`str` or `os.PathLike`): The folder where to save.627 config (`PretrainedConfig` or dictionary, `optional`):628 A config in which to register the auto_map corresponding to this custom object.629 630 Returns:631 `list[str]`: The list of files saved.632 """633 if obj.__module__ == "__main__":634 logger.warning(635 f"We can't save the code defining {obj} in {folder} as it's been defined in __main__. You should put "636 "this code in a separate module so we can include it in the saved folder and make it easier to share via "637 "the Hub."638 )639 return640 641 def _set_auto_map_in_config(_config):642 module_name = obj.__class__.__module__643 last_module = module_name.split(".")[-1]644 full_name = f"{last_module}.{obj.__class__.__name__}"645 # Special handling for tokenizers646 if "Tokenizer" in full_name:647 slow_tokenizer_class = None648 fast_tokenizer_class = None649 if obj.__class__.__name__.endswith("Fast"):650 # Fast tokenizer: we have the fast tokenizer class and we may have the slow one has an attribute.651 fast_tokenizer_class = f"{last_module}.{obj.__class__.__name__}"652 if getattr(obj, "slow_tokenizer_class", None) is not None:653 slow_tokenizer = getattr(obj, "slow_tokenizer_class")654 slow_tok_module_name = slow_tokenizer.__module__655 last_slow_tok_module = slow_tok_module_name.split(".")[-1]656 slow_tokenizer_class = f"{last_slow_tok_module}.{slow_tokenizer.__name__}"657 else:658 # Slow tokenizer: no way to have the fast class659 slow_tokenizer_class = f"{last_module}.{obj.__class__.__name__}"660 661 full_name = (slow_tokenizer_class, fast_tokenizer_class)662 663 if isinstance(_config, dict):664 auto_map = _config.get("auto_map", {})665 auto_map[obj._auto_class] = full_name666 _config["auto_map"] = auto_map667 elif getattr(_config, "auto_map", None) is not None:668 _config.auto_map[obj._auto_class] = full_name669 else:670 _config.auto_map = {obj._auto_class: full_name}671 672 # Add object class to the config auto_map673 if isinstance(config, (list, tuple)):674 for cfg in config:675 _set_auto_map_in_config(cfg)676 elif config is not None:677 _set_auto_map_in_config(config)678 679 result = []680 # Copy module file to the output folder.681 object_file = sys.modules[obj.__module__].__file__682 dest_file = Path(folder) / (Path(object_file).name)683 shutil.copy(object_file, dest_file)684 result.append(dest_file)685 686 # Gather all relative imports recursively and make sure they are copied as well.687 for needed_file in get_relative_import_files(object_file):688 dest_file = Path(folder) / (Path(needed_file).name)689 shutil.copy(needed_file, dest_file)690 result.append(dest_file)691 692 return result693 694 695def _raise_timeout_error(signum, frame):696 raise ValueError(697 "Loading this model requires you to execute custom code contained in the model repository on your local "698 "machine. Please set the option `trust_remote_code=True` to permit loading of this model."699 )700 701 702TIME_OUT_REMOTE_CODE = 15703 704 705def resolve_trust_remote_code(706 trust_remote_code, model_name, has_local_code, has_remote_code, error_message=None, upstream_repo=None707):708 """709 Resolves the `trust_remote_code` argument. If there is remote code to be loaded, the user must opt-in to loading710 it.711 712 Args:713 trust_remote_code (`bool` or `None`):714 User-defined `trust_remote_code` value.715 model_name (`str`):716 The name of the model repository in huggingface.co.717 has_local_code (`bool`):718 Whether the model has local code.719 has_remote_code (`bool`):720 Whether the model has remote code.721 error_message (`str`, *optional*):722 Custom error message to display if there is remote code to load and the user didn't opt-in. If unset, the error723 message will be regarding loading a model with custom code.724 725 Returns:726 The resolved `trust_remote_code` value.727 """728 if error_message is None:729 if upstream_repo is not None:730 error_message = (731 f"The repository {model_name} references custom code contained in {upstream_repo} which "732 f"must be executed to correctly load the model. You can inspect the repository "733 f"content at https://hf.co/{upstream_repo} .\n"734 )735 elif os.path.isdir(model_name):736 error_message = (737 f"The repository {model_name} contains custom code which must be executed "738 f"to correctly load the model. You can inspect the repository "739 f"content at {os.path.abspath(model_name)} .\n"740 )741 else:742 error_message = (743 f"The repository {model_name} contains custom code which must be executed "744 f"to correctly load the model. You can inspect the repository "745 f"content at https://hf.co/{model_name} .\n"746 )747 748 if trust_remote_code is None:749 if has_local_code:750 trust_remote_code = False751 elif has_remote_code and TIME_OUT_REMOTE_CODE > 0:752 prev_sig_handler = None753 try:754 prev_sig_handler = signal.signal(signal.SIGALRM, _raise_timeout_error)755 signal.alarm(TIME_OUT_REMOTE_CODE)756 while trust_remote_code is None:757 answer = input(758 f"{error_message} You can inspect the repository content at https://hf.co/{model_name}.\n"759 f"You can avoid this prompt in future by passing the argument `trust_remote_code=True`.\n\n"760 f"Do you wish to run the custom code? [y/N] "761 )762 if answer.lower() in ["yes", "y", "1"]:763 trust_remote_code = True764 elif answer.lower() in ["no", "n", "0", ""]:765 trust_remote_code = False766 signal.alarm(0)767 except Exception:768 # OS which does not support signal.SIGALRM769 raise ValueError(770 f"{error_message} You can inspect the repository content at https://hf.co/{model_name}.\n"771 f"Please pass the argument `trust_remote_code=True` to allow custom code to be run."772 )773 finally:774 if prev_sig_handler is not None:775 signal.signal(signal.SIGALRM, prev_sig_handler)776 signal.alarm(0)777 elif has_remote_code:778 # For the CI which puts the timeout at 0779 _raise_timeout_error(None, None)780 781 if has_remote_code and not has_local_code and not trust_remote_code:782 raise ValueError(783 f"{error_message} You can inspect the repository content at https://hf.co/{model_name}.\n"784 f"Please pass the argument `trust_remote_code=True` to allow custom code to be run."785 )786 787 return trust_remote_code788 789 790def check_python_requirements(path_or_repo_id, requirements_file="requirements.txt", **kwargs):791 """792 Tries to locate `requirements_file` in a local folder or repo, and confirms that the environment has all the793 python dependencies installed.794 795 Args:796 path_or_repo_id (`str` or `os.PathLike`):797 This can be either:798 - a string, the *model id* of a model repo on huggingface.co.799 - a path to a *directory* potentially containing the file.800 kwargs (`dict[str, Any]`, *optional*):801 Additional arguments to pass to `cached_file`.802 """803 failed = [] # error messages regarding requirements804 try:805 requirements = cached_file(path_or_repo_id=path_or_repo_id, filename=requirements_file, **kwargs)806 with open(requirements, "r") as f:807 requirements = f.readlines()808 809 for requirement in requirements:810 requirement = requirement.strip()811 if not requirement or requirement.startswith("#"): # skip empty lines and comments812 continue813 814 try:815 # e.g. "torch>2.6.0" -> "torch", ">", "2.6.0"816 package_name, delimiter, version_number = split_package_version(requirement)817 except ValueError: # e.g. "torch", as opposed to "torch>2.6.0"818 package_name = requirement819 delimiter, version_number = None, None820 821 try:822 local_package_version = importlib.metadata.version(package_name)823 except importlib.metadata.PackageNotFoundError:824 failed.append(f"{requirement} (installed: None)")825 continue826 827 if delimiter is not None and version_number is not None:828 is_satisfied = VersionComparison.from_string(delimiter)(829 version.parse(local_package_version), version.parse(version_number)830 )831 else:832 is_satisfied = True833 834 if not is_satisfied:835 failed.append(f"{requirement} (installed: {local_package_version})")836 837 except OSError: # no requirements.txt838 pass839 840 if failed:841 raise ImportError(842 f"Missing requirements in your local environment for `{path_or_repo_id}`:\n" + "\n".join(failed)843 )844 