Files
paddlepaddle--paddle/python/paddle/hapi/hub.py
T
2026-07-13 12:40:42 +08:00

467 lines
14 KiB
Python

# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import contextlib
import errno
import os
import shutil
import sys
import warnings
import zipfile
from typing import TYPE_CHECKING, Literal, TypeAlias
from urllib.parse import urlparse
import paddle
from paddle.utils.download import (
_download,
_safe_extract_zip,
get_path_from_url,
)
if TYPE_CHECKING:
import builtins
from typing import Any
__all__ = []
_Source: TypeAlias = Literal["github", "gitee", "local"]
DEFAULT_CACHE_DIR: str = '~/.cache'
VAR_DEPENDENCY: str = 'dependencies'
MODULE_HUBCONF: str = 'hubconf.py'
HUB_DIR: str = os.path.expanduser(os.path.join('~', '.cache', 'paddle', 'hub'))
def _remove_if_exists(path):
if os.path.exists(path):
if os.path.isfile(path):
os.remove(path)
else:
shutil.rmtree(path)
def _import_module(name, repo_dir):
sys.path.insert(0, repo_dir)
try:
hub_module = __import__(name)
sys.modules.pop(name)
except ImportError:
sys.path.remove(repo_dir)
raise RuntimeError(
'Please make sure config exists or repo error messages above fixed when importing'
)
sys.path.remove(repo_dir)
return hub_module
def _git_archive_link(repo_owner, repo_name, branch, source):
if source == 'github':
return (
f'https://github.com/{repo_owner}/{repo_name}/archive/{branch}.zip'
)
elif source == 'gitee':
return f'https://gitee.com/{repo_owner}/{repo_name}/repository/archive/{branch}.zip'
def _parse_repo_info(repo, source):
branch = 'main' if source == 'github' else 'master'
if ':' in repo:
repo_info, branch = repo.split(':')
else:
repo_info = repo
repo_owner, repo_name = repo_info.split('/')
return repo_owner, repo_name, branch
def _make_dirs(dirname):
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
Path(dirname).mkdir(exist_ok=True)
def _get_cache_or_reload(repo, force_reload, verbose=True, source='github'):
# Setup hub_dir to save downloaded files
hub_dir = HUB_DIR
_make_dirs(hub_dir)
# Parse github/gitee repo information
repo_owner, repo_name, branch = _parse_repo_info(repo, source)
# Github allows branch name with slash '/',
# this causes confusion with path on both Linux and Windows.
# Backslash is not allowed in Github branch name so no need to
# to worry about it.
normalized_br = branch.replace('/', '_')
# Github renames folder repo/v1.x.x to repo-1.x.x
# We don't know the repo name before downloading the zip file
# and inspect name from it.
# To check if cached repo exists, we need to normalize folder names.
repo_dir = os.path.join(
hub_dir, '_'.join([repo_owner, repo_name, normalized_br])
)
use_cache = (not force_reload) and os.path.exists(repo_dir)
if use_cache:
if verbose:
sys.stderr.write(f'Using cache found in {repo_dir}\n')
else:
cached_file = os.path.join(hub_dir, normalized_br + '.zip')
_remove_if_exists(cached_file)
url = _git_archive_link(repo_owner, repo_name, branch, source=source)
fpath = get_path_from_url(
url,
hub_dir,
check_exist=not force_reload,
decompress=False,
)
shutil.move(fpath, cached_file)
with zipfile.ZipFile(cached_file) as cached_zipfile:
extracted_repo_name = cached_zipfile.infolist()[0].filename
extracted_repo = os.path.join(hub_dir, extracted_repo_name)
_remove_if_exists(extracted_repo)
# Unzip the code and rename the base folder
_safe_extract_zip(cached_zipfile, hub_dir)
_remove_if_exists(cached_file)
_remove_if_exists(repo_dir)
# Rename the repo
shutil.move(extracted_repo, repo_dir)
return repo_dir
def _load_entry_from_hubconf(m, name):
'''load entry from hubconf'''
if not isinstance(name, str):
raise ValueError(
'Invalid input: model should be a str of function name'
)
func = getattr(m, name, None)
if func is None or not callable(func):
raise RuntimeError(f'Cannot find callable {name} in hubconf')
return func
def _check_module_exists(name):
try:
__import__(name)
return True
except ImportError:
return False
def _check_dependencies(m):
dependencies = getattr(m, VAR_DEPENDENCY, None)
if dependencies is not None:
missing_deps = [
pkg for pkg in dependencies if not _check_module_exists(pkg)
]
if len(missing_deps):
raise RuntimeError(
'Missing dependencies: {}'.format(', '.join(missing_deps))
)
def list(
repo_dir: str,
source: _Source = 'github',
force_reload: bool = False,
) -> builtins.list[str]:
r"""
List all entrypoints available in `github` hubconf.
Args:
repo_dir(str): Github or local path.
- github path (str): A string with format "repo_owner/repo_name[:tag_name]" with an optional
tag/branch. The default branch is `main` if not specified.
- local path (str): Local repo path.
source (str): `github` | `gitee` | `local`. Default is `github`.
force_reload (bool, optional): Whether to discard the existing cache and force a fresh download. Default is `False`.
Returns:
entrypoints: A list of available entrypoint names.
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.hub.list('lyuwenyu/paddlehub_demo:main', source='github', force_reload=False)
"""
if source not in ('github', 'gitee', 'local'):
raise ValueError(
f'Unknown source: "{source}". Allowed values: "github" | "gitee" | "local".'
)
if source in ('github', 'gitee'):
repo_dir = _get_cache_or_reload(
repo_dir, force_reload, True, source=source
)
hub_module = _import_module(MODULE_HUBCONF.split('.')[0], repo_dir)
entrypoints = [
f
for f in dir(hub_module)
if callable(getattr(hub_module, f)) and not f.startswith('_')
]
return entrypoints
def help(
repo_dir: str,
model,
source: _Source = 'github',
force_reload: bool = False,
) -> str:
"""
Show help information of model
Args:
repo_dir(str): Github or local path.
- github path (str): A string with format "repo_owner/repo_name[:tag_name]" with an optional
tag/branch. The default branch is `main` if not specified.
- local path (str): Local repo path.
model (str): Model name.
source (str): `github` | `gitee` | `local`. Default is `github`.
force_reload (bool, optional): Default is `False`.
Returns:
docs
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.hub.help('lyuwenyu/paddlehub_demo:main', model='MM', source='github')
"""
if source not in ('github', 'gitee', 'local'):
raise ValueError(
f'Unknown source: "{source}". Allowed values: "github" | "gitee" | "local".'
)
if source in ('github', 'gitee'):
repo_dir = _get_cache_or_reload(
repo_dir, force_reload, True, source=source
)
hub_module = _import_module(MODULE_HUBCONF.split('.')[0], repo_dir)
entry = _load_entry_from_hubconf(hub_module, model)
return entry.__doc__
def load(
repo_dir: str,
model: str,
source: _Source = 'github',
force_reload: bool = False,
**kwargs: Any,
) -> paddle.nn.Layer:
"""
Load model
Args:
repo_dir(str): Github or local path.
- github path (str): A string with format "repo_owner/repo_name[:tag_name]" with an optional
tag/branch. The default branch is `main` if not specified.
- local path (str): Local repo path.
model (str): Model name.
source (str): `github` | `gitee` | `local`. Default is `github`.
force_reload (bool, optional): Default is `False`.
**kwargs: Parameters using for model.
Returns:
paddle model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.hub.load('lyuwenyu/paddlehub_demo:main', model='MM', source='github')
"""
if source not in ('github', 'gitee', 'local'):
raise ValueError(
f'Unknown source: "{source}". Allowed values: "github" | "gitee" | "local".'
)
if source in ('github', 'gitee'):
repo_dir = _get_cache_or_reload(
repo_dir, force_reload, True, source=source
)
hub_module = _import_module(MODULE_HUBCONF.split('.')[0], repo_dir)
_check_dependencies(hub_module)
entry = _load_entry_from_hubconf(hub_module, model)
return entry(**kwargs)
def load_state_dict_from_url(
url: str,
model_dir: str | None = None,
check_hash: bool = False,
file_name: str | None = None,
map_location: (
Literal["cpu", "gpu", "xpu", "npu", "numpy", "np"] | None
) = None,
) -> Any:
"""Download Paddle's model weights (i.e., state_dict)
from the specified URL and extract the downloaded file if necessary
Args:
url (str): URL of the object to download.
model_dir (Optional[str], optional): Directory in which to save the object.
check_hash (bool, optional): If True, the filename part of the URL should follow the naming convention filename-<sha256>.ext where <sha256> is the first eight or more digits of the SHA256 hash of the contents of the file. The hash is used to ensure unique names and to verify the contents of the file. Default: False.
file_name (Optional[str], optional): Name for the downloaded file. Filename from URL will be used if not set.
map_location (Optional[Literal["cpu", "gpu", "xpu", "npu", "numpy", "np"]], optional): Specifies how to remap storage locations. Default: None.
Returns:
Any: A target object that can be used in Paddle.
Examples:
.. code-block:: pycon
>>> # doctest: +TIMEOUT(60)
>>> import paddle
>>> paddle.hub.load_state_dict_from_url(
... url='https://paddle-hapi.bj.bcebos.com/models/resnet18.pdparams', model_dir="./paddle/test_load_from_url"
... )
>>> paddle.hub.load_state_dict_from_url(
... url='https://x2paddle.bj.bcebos.com/resnet18.zip', model_dir="./paddle/test_file_is_zip"
... )
"""
if model_dir is None:
hub_dir = get_dir()
model_dir = os.path.join(hub_dir, 'checkpoints')
try:
os.makedirs(model_dir)
except OSError as e:
if e.errno == errno.EEXIST:
pass
else:
raise
parts = urlparse(url)
filename = os.path.basename(parts.path)
if file_name is not None:
filename = file_name
cached_file = os.path.join(model_dir, filename)
if not os.path.exists(cached_file):
sys.stderr.write(f'Downloading: "{url}" to {cached_file}\n')
hash_prefix = None
if check_hash:
hash_prefix = check_hash
_download(url, model_dir, hash_prefix)
if map_location:
assert map_location in ["cpu", "gpu", "xpu", "npu", "numpy", "np"]
if _is_legacy_zip_format(cached_file):
return _legacy_zip_load(cached_file, model_dir, map_location)
if map_location in ["numpy", "np"]:
return paddle.load(cached_file, return_numpy=True)
else:
with device_guard(map_location):
return paddle.load(cached_file)
else:
if _is_legacy_zip_format(cached_file):
return _legacy_zip_load(cached_file, model_dir)
return paddle.load(cached_file)
def _is_legacy_zip_format(filename):
if zipfile.is_zipfile(filename):
infolist = zipfile.ZipFile(filename).infolist()
return len(infolist) == 1 and not infolist[0].is_dir()
return False
def _legacy_zip_load(filename, model_dir, map_location=None):
with zipfile.ZipFile(filename) as f:
members = f.infolist()
if len(members) != 1:
raise RuntimeError(
'Only one file(not dir) is allowed in the zipfile'
)
_safe_extract_zip(f, model_dir)
extracted_name = members[0].filename
extracted_file = os.path.join(model_dir, extracted_name)
if map_location:
if map_location in ["numpy", "np"]:
return paddle.load(extracted_file, return_numpy=True)
else:
with device_guard(map_location):
return paddle.load(extracted_file)
else:
return paddle.load(extracted_file)
def get_dir():
if os.getenv('PADDLE_HUB'):
warnings.warn(
'PADDLE_HUB is deprecated, please use env PADDLE_HOME instead'
)
return os.path.join(_get_paddle_home(), 'hub')
def _get_paddle_home():
paddle_home = os.path.expanduser(
os.getenv(
'PADDLE_HOME',
os.path.join(os.getenv('XDG_CACHE_HOME', '~/.cache'), 'paddle'),
)
)
return paddle_home
@contextlib.contextmanager
def device_guard(device="cpu", dev_id=0):
origin_device = paddle.device.get_device()
if device == "cpu":
paddle.set_device(device)
elif device in ["gpu", "xpu", "npu"]:
paddle.set_device(f"{device}:{dev_id}")
try:
yield
finally:
paddle.set_device(origin_device)