paddlepaddle--paddlenlp
1012 行
36 KiB
Python
1012 行
36 KiB
Python
# Copyright (c) 2020 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 functools
|
|
import hashlib
|
|
import importlib
|
|
import inspect
|
|
import os
|
|
import re
|
|
import shutil
|
|
import sys
|
|
import warnings
|
|
from contextlib import ExitStack
|
|
from io import StringIO
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING, ContextManager, List, Optional, Type, Union
|
|
|
|
from filelock import FileLock
|
|
|
|
from paddlenlp import __version__
|
|
from paddlenlp.utils.downloader import (
|
|
COMMUNITY_MODEL_PREFIX,
|
|
download_check,
|
|
get_path_from_url_with_filelock,
|
|
is_url,
|
|
url_file_exists,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from paddlenlp.transformers import PretrainedModel
|
|
|
|
import numpy as np
|
|
import paddle
|
|
import tqdm
|
|
from huggingface_hub import hf_hub_download, try_to_load_from_cache
|
|
from huggingface_hub.utils import EntryNotFoundError
|
|
from paddle.common_ops_import import convert_dtype
|
|
from paddle.nn import Layer
|
|
from requests.exceptions import HTTPError
|
|
|
|
from paddlenlp.utils.env import HF_CACHE_HOME, MODEL_HOME
|
|
from paddlenlp.utils.import_utils import import_module
|
|
from paddlenlp.utils.log import logger
|
|
|
|
from ..utils.download import resolve_file_path
|
|
|
|
# TODO(@zewu): upgrade aistudio to the newest version
|
|
try:
|
|
from .aistudio_utils import aistudio_download
|
|
except:
|
|
aistudio_download = None
|
|
|
|
HUGGINGFACE_CO_RESOLVE_ENDPOINT = "https://huggingface.co"
|
|
|
|
|
|
def convert_ndarray_dtype(np_array: np.ndarray, target_dtype: str) -> np.ndarray:
|
|
"""convert ndarray
|
|
|
|
Args:
|
|
np_array (np.ndarray): numpy ndarray instance
|
|
target_dtype (str): the target dtype
|
|
|
|
Returns:
|
|
np.ndarray: converted numpy ndarray instance
|
|
"""
|
|
source_dtype = convert_dtype(np_array.dtype)
|
|
if source_dtype == "uint16" or target_dtype == "bfloat16":
|
|
tensor = paddle.to_tensor(np_array)
|
|
tensor = paddle.cast(tensor, target_dtype)
|
|
return tensor.cpu().numpy()
|
|
|
|
# TODO(wj-Mcat): device_guard will slow the converting
|
|
# with device_guard("cpu"):
|
|
# tensor = paddle.to_tensor(np_array)
|
|
# tensor = paddle.cast(tensor, target_dtype)
|
|
# return tensor.cpu().numpy()
|
|
|
|
if target_dtype == "bfloat16":
|
|
target_dtype = "uint16"
|
|
|
|
return np_array.astype(target_dtype)
|
|
|
|
|
|
def convert_to_dict_message(conversation: List[List[str]]):
|
|
"""Convert the list of chat messages to a role dictionary chat messages."""
|
|
conversations = []
|
|
for index, item in enumerate(conversation):
|
|
assert 1 <= len(item) <= 2, "Each Rounds in conversation should have 1 or 2 elements."
|
|
if isinstance(item[0], str):
|
|
conversations.append({"role": "user", "content": item[0]})
|
|
if len(item) == 2 and isinstance(item[1], str):
|
|
conversations.append({"role": "assistant", "content": item[1]})
|
|
else:
|
|
# If there is only one element in item, it must be the last round.
|
|
# If it is not the last round, it must be an error.
|
|
if index != len(conversation) - 1:
|
|
raise ValueError(f"Round {index} has error round")
|
|
else:
|
|
raise ValueError("Each round in list should be string")
|
|
return conversations
|
|
|
|
|
|
def get_scale_by_dtype(dtype: str = None, return_positive: bool = True) -> float:
|
|
"""get scale value by dtype
|
|
|
|
Args:
|
|
dtype (str): the string dtype value
|
|
|
|
Returns:
|
|
float: the scale value
|
|
"""
|
|
if dtype is None:
|
|
dtype = paddle.get_default_dtype()
|
|
|
|
dtype = convert_dtype(dtype)
|
|
scale_value = 1e6
|
|
|
|
# TODO(wj-Mcaf): support int8, int4 dtypes later
|
|
if dtype == "float16":
|
|
scale_value = 1e4
|
|
|
|
if return_positive:
|
|
return scale_value
|
|
return -1 * scale_value
|
|
|
|
|
|
def fn_args_to_dict(func, *args, **kwargs):
|
|
"""
|
|
Inspect function `func` and its arguments for running, and extract a
|
|
dict mapping between argument names and keys.
|
|
"""
|
|
if hasattr(inspect, "getfullargspec"):
|
|
(spec_args, spec_varargs, spec_varkw, spec_defaults, _, _, _) = inspect.getfullargspec(func)
|
|
else:
|
|
(spec_args, spec_varargs, spec_varkw, spec_defaults) = inspect.getargspec(func)
|
|
# add positional argument values
|
|
init_dict = dict(zip(spec_args, args))
|
|
# add default argument values
|
|
kwargs_dict = dict(zip(spec_args[-len(spec_defaults) :], spec_defaults)) if spec_defaults else {}
|
|
for k in list(kwargs_dict.keys()):
|
|
if k in init_dict:
|
|
kwargs_dict.pop(k)
|
|
kwargs_dict.update(kwargs)
|
|
init_dict.update(kwargs_dict)
|
|
return init_dict
|
|
|
|
|
|
def adapt_stale_fwd_patch(self, name, value):
|
|
"""
|
|
Since there are some monkey patches for forward of PretrainedModel, such as
|
|
model compression, we make these patches compatible with the latest forward
|
|
method.
|
|
"""
|
|
if name == "forward":
|
|
# NOTE(guosheng): In dygraph to static, `layer.forward` would be patched
|
|
# by an instance of `StaticFunction`. And use string compare to avoid to
|
|
# import fluid.
|
|
if type(value).__name__.endswith("StaticFunction") or self.forward.__class__.__name__.endswith(
|
|
"StaticFunction"
|
|
):
|
|
return value
|
|
if type(value).__name__.endswith("WeakMethod") or self.forward.__class__.__name__.endswith("WeakMethod"):
|
|
return value
|
|
|
|
# NOTE(changwenbin & zhoukangkang):
|
|
# When use model = paddle.incubate.jit.inference(model), it reportes errors, we fix it here.
|
|
# is_inference_mode API is only available in PaddlePaddle develop,so we add a try except.
|
|
try:
|
|
from paddle.incubate.jit import is_inference_mode
|
|
|
|
if is_inference_mode(value):
|
|
return value
|
|
except:
|
|
pass
|
|
|
|
if hasattr(inspect, "getfullargspec"):
|
|
(
|
|
patch_spec_args,
|
|
patch_spec_varargs,
|
|
patch_spec_varkw,
|
|
patch_spec_defaults,
|
|
_,
|
|
_,
|
|
_,
|
|
) = inspect.getfullargspec(value)
|
|
(spec_args, spec_varargs, spec_varkw, spec_defaults, _, _, _) = inspect.getfullargspec(self.forward)
|
|
else:
|
|
(patch_spec_args, patch_spec_varargs, patch_spec_varkw, patch_spec_defaults) = inspect.getargspec(value)
|
|
(spec_args, spec_varargs, spec_varkw, spec_defaults) = inspect.getargspec(self.forward)
|
|
new_args = [
|
|
arg
|
|
for arg in ("output_hidden_states", "output_attentions", "return_dict")
|
|
if arg not in patch_spec_args and arg in spec_args
|
|
]
|
|
|
|
if new_args:
|
|
if self.__module__.startswith("paddlenlp"):
|
|
warnings.warn(
|
|
f"The `forward` method of {self.__class__ if isinstance(self, Layer) else self} is patched and the patch "
|
|
"might be based on an old version which missing some "
|
|
f"arguments compared with the latest, such as {new_args}. "
|
|
"We automatically add compatibility on the patch for "
|
|
"these arguments, and maybe the patch should be updated."
|
|
)
|
|
else:
|
|
warnings.warn(
|
|
f"The `forward` method of {self.__class__ if isinstance(self, Layer) else self} "
|
|
"is patched and the patch might be conflict with patches made "
|
|
f"by paddlenlp which seems have more arguments such as {new_args}. "
|
|
"We automatically add compatibility on the patch for "
|
|
"these arguments, and maybe the patch should be updated."
|
|
)
|
|
if isinstance(self, Layer) and inspect.isfunction(value):
|
|
|
|
@functools.wraps(value)
|
|
def wrap_fwd(*args, **kwargs):
|
|
for arg in new_args:
|
|
kwargs.pop(arg, None)
|
|
return value(self, *args, **kwargs)
|
|
|
|
else:
|
|
|
|
@functools.wraps(value)
|
|
def wrap_fwd(*args, **kwargs):
|
|
for arg in new_args:
|
|
kwargs.pop(arg, None)
|
|
return value(*args, **kwargs)
|
|
|
|
return wrap_fwd
|
|
return value
|
|
|
|
|
|
class InitTrackerMeta(type(Layer)):
|
|
"""
|
|
This metaclass wraps the `__init__` method of a class to add `init_config`
|
|
attribute for instances of that class, and `init_config` use a dict to track
|
|
the initial configuration. If the class has `_pre_init` or `_post_init`
|
|
method, it would be hooked before or after `__init__` and called as
|
|
`_pre_init(self, init_fn, init_args)` or `_post_init(self, init_fn, init_args)`.
|
|
Since InitTrackerMeta would be used as metaclass for pretrained model classes,
|
|
which always are Layer and `type(Layer)` is not `type`, thus use `type(Layer)`
|
|
rather than `type` as base class for it to avoid inheritance metaclass
|
|
conflicts.
|
|
"""
|
|
|
|
def __init__(cls, name, bases, attrs):
|
|
init_func = cls.__init__
|
|
# If attrs has `__init__`, wrap it using accessible `_pre_init, _post_init`.
|
|
# Otherwise, no need to wrap again since the super cls has been wrapped.
|
|
# TODO: remove reduplicated tracker if using super cls `__init__`
|
|
pre_init_func = getattr(cls, "_pre_init", None) if "__init__" in attrs else None
|
|
post_init_func = getattr(cls, "_post_init", None) if "__init__" in attrs else None
|
|
cls.__init__ = InitTrackerMeta.init_and_track_conf(init_func, pre_init_func, post_init_func)
|
|
super(InitTrackerMeta, cls).__init__(name, bases, attrs)
|
|
|
|
@staticmethod
|
|
def init_and_track_conf(init_func, pre_init_func=None, post_init_func=None):
|
|
"""
|
|
wraps `init_func` which is `__init__` method of a class to add `init_config`
|
|
attribute for instances of that class.
|
|
Args:
|
|
init_func (callable): It should be the `__init__` method of a class.
|
|
warning: `self` always is the class type of down-stream model, eg: BertForTokenClassification
|
|
pre_init_func (callable, optional): If provided, it would be hooked after
|
|
`init_func` and called as `pre_init_func(self, init_func, *init_args, **init_args)`.
|
|
Default None.
|
|
post_init_func (callable, optional): If provided, it would be hooked after
|
|
`init_func` and called as `post_init_func(self, init_func, *init_args, **init_args)`.
|
|
Default None.
|
|
|
|
Returns:
|
|
function: the wrapped function
|
|
"""
|
|
|
|
@functools.wraps(init_func)
|
|
def __impl__(self, *args, **kwargs):
|
|
# registered helper by `pre_init_func`
|
|
if pre_init_func:
|
|
pre_init_func(self, init_func, *args, **kwargs)
|
|
# keep full configuration
|
|
init_func(self, *args, **kwargs)
|
|
# registered helper by `post_init_func`
|
|
if post_init_func:
|
|
post_init_func(self, init_func, *args, **kwargs)
|
|
self.init_config = kwargs
|
|
if args:
|
|
kwargs["init_args"] = args
|
|
kwargs["init_class"] = self.__class__.__name__
|
|
|
|
return __impl__
|
|
|
|
def __setattr__(self, name, value):
|
|
value = adapt_stale_fwd_patch(self, name, value)
|
|
return super(InitTrackerMeta, self).__setattr__(name, value)
|
|
|
|
|
|
def param_in_func(func, param_field: str) -> bool:
|
|
"""check if the param_field is in `func` method, eg: if the `bert` param is in `__init__` method
|
|
|
|
Args:
|
|
cls (type): the class of PretrainedModel
|
|
param_field (str): the name of field
|
|
|
|
Returns:
|
|
bool: the result of existence
|
|
"""
|
|
|
|
if hasattr(inspect, "getfullargspec"):
|
|
result = inspect.getfullargspec(func)
|
|
else:
|
|
result = inspect.getargspec(func)
|
|
|
|
return param_field in result[0]
|
|
|
|
|
|
def resolve_cache_dir(from_hf_hub: bool, from_aistudio: bool, cache_dir: Optional[str] = None) -> str:
|
|
"""resolve cache dir for PretrainedModel and PretrainedConfig
|
|
|
|
Args:
|
|
from_hf_hub (bool): if load from huggingface hub
|
|
cache_dir (str): cache_dir for models
|
|
"""
|
|
if cache_dir is not None:
|
|
return cache_dir
|
|
if from_aistudio:
|
|
return None
|
|
if from_hf_hub:
|
|
return HF_CACHE_HOME
|
|
return MODEL_HOME
|
|
|
|
|
|
def find_transformer_model_type(model_class: Type) -> str:
|
|
"""get the model type from module name,
|
|
eg:
|
|
BertModel -> bert,
|
|
RobertaForTokenClassification -> roberta
|
|
|
|
Args:
|
|
model_class (Type): the class of model
|
|
|
|
Returns:
|
|
str: the type string
|
|
"""
|
|
from paddlenlp.transformers import PretrainedModel
|
|
|
|
default_model_type = ""
|
|
|
|
if not issubclass(model_class, PretrainedModel):
|
|
return default_model_type
|
|
|
|
module_name: str = model_class.__module__
|
|
if not module_name.startswith("paddlenlp.transformers."):
|
|
return default_model_type
|
|
|
|
tokens = module_name.split(".")
|
|
if len(tokens) < 3:
|
|
return default_model_type
|
|
|
|
return tokens[2]
|
|
|
|
|
|
def find_transformer_model_class_by_name(model_name: str) -> Optional[Type[PretrainedModel]]:
|
|
"""find transformer model_class by name
|
|
|
|
Args:
|
|
model_name (str): the string of class name
|
|
|
|
Returns:
|
|
Optional[Type[PretrainedModel]]: optional pretrained-model class
|
|
"""
|
|
transformer_module = import_module("paddlenlp.transformers")
|
|
|
|
for obj_name in dir(transformer_module):
|
|
if obj_name.startswith("_"):
|
|
continue
|
|
obj = getattr(transformer_module, obj_name, None)
|
|
if obj is None:
|
|
continue
|
|
|
|
name = getattr(obj, "__name__", None)
|
|
if name is None:
|
|
continue
|
|
|
|
if name == model_name:
|
|
return obj
|
|
logger.debug(f"can not find model_class<{model_name}>")
|
|
return None
|
|
|
|
|
|
def convert_file_size_to_int(size: Union[int, str]):
|
|
"""
|
|
Converts a size expressed as a string with digits an unit (like `"5MB"`) to an integer (in bytes).
|
|
Args:
|
|
size (`int` or `str`): The size to convert. Will be directly returned if an `int`.
|
|
Example:
|
|
```py
|
|
>>> convert_file_size_to_int("1MiB")
|
|
1048576
|
|
```
|
|
"""
|
|
if isinstance(size, int):
|
|
return size
|
|
if size.upper().endswith("GIB"):
|
|
return int(size[:-3]) * (2**30)
|
|
if size.upper().endswith("MIB"):
|
|
return int(size[:-3]) * (2**20)
|
|
if size.upper().endswith("KIB"):
|
|
return int(size[:-3]) * (2**10)
|
|
if size.upper().endswith("GB"):
|
|
int_size = int(size[:-2]) * (10**9)
|
|
return int_size // 8 if size.endswith("b") else int_size
|
|
if size.upper().endswith("MB"):
|
|
int_size = int(size[:-2]) * (10**6)
|
|
return int_size // 8 if size.endswith("b") else int_size
|
|
if size.upper().endswith("KB"):
|
|
int_size = int(size[:-2]) * (10**3)
|
|
return int_size // 8 if size.endswith("b") else int_size
|
|
raise ValueError("`size` is not in a valid format. Use an integer followed by the unit, e.g., '5GB'.")
|
|
|
|
|
|
def paddlenlp_hub_download(
|
|
repo_id: str,
|
|
filename: str,
|
|
*,
|
|
subfolder: Optional[str] = None,
|
|
cache_dir: Union[str, Path, None] = None,
|
|
pretrained_model_name_or_path: str = None,
|
|
) -> str:
|
|
if subfolder is None:
|
|
subfolder = ""
|
|
if pretrained_model_name_or_path is not None and is_url(repo_id):
|
|
cache_dir = os.path.join(cache_dir, pretrained_model_name_or_path, subfolder)
|
|
else:
|
|
cache_dir = os.path.join(cache_dir, repo_id, subfolder)
|
|
|
|
# check in cache_dir
|
|
weight_file_path = os.path.join(cache_dir, filename)
|
|
|
|
if os.path.exists(weight_file_path):
|
|
logger.info(f"Already cached {weight_file_path}")
|
|
return weight_file_path
|
|
|
|
# Download from custom model url
|
|
if is_url(repo_id):
|
|
# check whether the target file exist in the community bos server
|
|
if url_file_exists(repo_id):
|
|
logger.info(f"Downloading {repo_id}")
|
|
weight_file_path = get_path_from_url_with_filelock(repo_id, cache_dir)
|
|
# # check the downloaded weight file and registered weight file name
|
|
download_check(repo_id, "paddlenlp_hub_download")
|
|
|
|
# make sure that model states names: model_states.pdparams
|
|
new_weight_file_path = os.path.join(os.path.split(weight_file_path)[0], filename)
|
|
|
|
if weight_file_path != new_weight_file_path:
|
|
# create lock file, which is empty, under the `LOCK_FILE_HOME` directory.
|
|
lock_file_name = hashlib.md5((repo_id + cache_dir).encode("utf-8")).hexdigest()
|
|
# create `.lock` private directory in the cache dir
|
|
lock_file_path = os.path.join(cache_dir, ".lock", lock_file_name)
|
|
|
|
with FileLock(lock_file_path):
|
|
if not os.path.exists(new_weight_file_path):
|
|
shutil.move(weight_file_path, new_weight_file_path)
|
|
|
|
weight_file_path = new_weight_file_path
|
|
|
|
return weight_file_path
|
|
|
|
return None
|
|
|
|
# find in community repo
|
|
url_list = [COMMUNITY_MODEL_PREFIX, repo_id, filename]
|
|
if subfolder != "":
|
|
url_list.insert(2, subfolder)
|
|
community_model_file_path = "/".join(url_list)
|
|
assert is_url(community_model_file_path)
|
|
|
|
# check whether the target file exist in the community bos server
|
|
if url_file_exists(community_model_file_path):
|
|
logger.info(f"Downloading {community_model_file_path}")
|
|
weight_file_path = get_path_from_url_with_filelock(community_model_file_path, cache_dir)
|
|
# # check the downloaded weight file and registered weight file name
|
|
download_check(community_model_file_path, "paddlenlp_hub_download")
|
|
return weight_file_path
|
|
|
|
return None
|
|
|
|
|
|
# Return value when trying to load a file from cache but the file does not exist in the distant repo.
|
|
_CACHED_NO_EXIST = object()
|
|
|
|
|
|
def cached_file(
|
|
path_or_repo_id: Union[str, os.PathLike],
|
|
filename: str,
|
|
cache_dir: Optional[Union[str, os.PathLike]] = None,
|
|
subfolder: str = "",
|
|
from_aistudio: bool = False,
|
|
_raise_exceptions_for_missing_entries: bool = True,
|
|
_raise_exceptions_for_connection_errors: bool = True,
|
|
pretrained_model_name_or_path=None,
|
|
) -> str:
|
|
"""
|
|
Tries to locate a file in a local folder and repo, downloads and cache it if necessary.
|
|
Args:
|
|
path_or_repo_id (`str` or `os.PathLike`):
|
|
This can be either:
|
|
- a string, the *model id* of a model repo on huggingface.co.
|
|
- a path to a *directory* potentially containing the file.
|
|
filename (`str`):
|
|
The name of the file to locate in `path_or_repo`.
|
|
cache_dir (`str` or `os.PathLike`, *optional*):
|
|
Path to a directory in which a downloaded pretrained model configuration should be cached if the standard
|
|
cache should not be used.
|
|
subfolder (`str`, *optional*, defaults to `""`):
|
|
In case the relevant files are located inside a subfolder of the model repo on huggingface.co, you can
|
|
specify the folder name here.
|
|
|
|
Returns:
|
|
`Optional[str]`: Returns the resolved file (to the cache folder if downloaded from a repo).
|
|
Examples:
|
|
```python
|
|
# Download a model weight from the Hub and cache it.
|
|
model_weights_file = cached_file("bert-base-uncased", "pytorch_model.bin")
|
|
```
|
|
"""
|
|
|
|
if subfolder is None:
|
|
subfolder = ""
|
|
|
|
path_or_repo_id = str(path_or_repo_id)
|
|
full_filename = os.path.join(subfolder, filename)
|
|
if os.path.isdir(path_or_repo_id):
|
|
resolved_file = os.path.join(os.path.join(path_or_repo_id, subfolder), filename)
|
|
if not os.path.isfile(resolved_file):
|
|
if _raise_exceptions_for_missing_entries:
|
|
raise EnvironmentError(
|
|
f"{path_or_repo_id} does not appear to have a file named {full_filename}. Checkout "
|
|
f"'https://huggingface.co/{path_or_repo_id}/' for available files."
|
|
)
|
|
else:
|
|
return None
|
|
return resolved_file
|
|
|
|
if cache_dir is not None and isinstance(cache_dir, Path):
|
|
cache_dir = str(cache_dir)
|
|
|
|
if from_aistudio:
|
|
try:
|
|
resolved_file = aistudio_download(
|
|
repo_id=path_or_repo_id, filename=filename, subfolder=subfolder, cache_dir=cache_dir
|
|
)
|
|
except:
|
|
resolved_file = None
|
|
else:
|
|
# if cache_dir is None:
|
|
# cache_dir = os.path.join(MODEL_HOME, ".cache")
|
|
try:
|
|
# Load from URL or cache if already cached
|
|
resolved_file = paddlenlp_hub_download(
|
|
path_or_repo_id,
|
|
filename,
|
|
subfolder=None if len(subfolder) == 0 else subfolder,
|
|
# revision=revision,
|
|
cache_dir=cache_dir,
|
|
pretrained_model_name_or_path=pretrained_model_name_or_path,
|
|
)
|
|
except HTTPError as err:
|
|
# First we try to see if we have a cached version (not up to date):
|
|
resolved_file = try_to_load_from_cache(path_or_repo_id, full_filename, cache_dir=cache_dir)
|
|
if resolved_file is not None and resolved_file != _CACHED_NO_EXIST:
|
|
return resolved_file
|
|
if not _raise_exceptions_for_connection_errors:
|
|
return None
|
|
|
|
raise EnvironmentError(
|
|
f"There was a specific connection error when trying to load {path_or_repo_id}:\n{err}"
|
|
)
|
|
|
|
return resolved_file
|
|
|
|
|
|
def cached_file_for_hf_hub(
|
|
path_or_repo_id: Union[str, os.PathLike],
|
|
filename: str,
|
|
cache_dir: Optional[Union[str, os.PathLike]] = None,
|
|
subfolder: str = "",
|
|
_raise_exceptions_for_missing_entries: bool = True,
|
|
):
|
|
|
|
if subfolder is None:
|
|
subfolder = ""
|
|
|
|
path_or_repo_id = str(path_or_repo_id)
|
|
full_filename = os.path.join(subfolder, filename)
|
|
if os.path.isdir(path_or_repo_id):
|
|
resolved_file = os.path.join(os.path.join(path_or_repo_id, subfolder), filename)
|
|
if not os.path.isfile(resolved_file):
|
|
if _raise_exceptions_for_missing_entries:
|
|
raise EnvironmentError(
|
|
f"{path_or_repo_id} does not appear to have a file named {full_filename}. Checkout "
|
|
f"'https://huggingface.co/{path_or_repo_id}' for available files."
|
|
)
|
|
else:
|
|
return None
|
|
return resolved_file
|
|
|
|
if cache_dir is None:
|
|
cache_dir = os.path.join(MODEL_HOME, ".cache")
|
|
if isinstance(cache_dir, Path):
|
|
cache_dir = str(cache_dir)
|
|
|
|
try:
|
|
# Load from URL or cache if already cached
|
|
download_check(path_or_repo_id, full_filename, addition="from_hf_hub")
|
|
resolved_file = hf_hub_download(
|
|
repo_id=path_or_repo_id,
|
|
filename=filename,
|
|
cache_dir=cache_dir,
|
|
subfolder=subfolder,
|
|
library_name="PaddleNLP",
|
|
library_version=__version__,
|
|
)
|
|
return resolved_file
|
|
except Exception as e:
|
|
print(e)
|
|
msg = f"""
|
|
{path_or_repo_id} is not a local folder and is not a valid model identifier "
|
|
"listed on 'https://huggingface.co/models' If this is a private repository, make sure to "
|
|
"pass a token having permission to this repo with `use_auth_token` or log in with "
|
|
"`huggingface-cli login` and pass `use_auth_token=True`.
|
|
"""
|
|
if _raise_exceptions_for_missing_entries:
|
|
raise EnvironmentError(msg)
|
|
else:
|
|
logger.info(msg)
|
|
return None
|
|
|
|
|
|
def get_checkpoint_shard_files(
|
|
pretrained_model_name_or_path,
|
|
index_filename,
|
|
cache_dir=None,
|
|
subfolder="",
|
|
from_aistudio=False,
|
|
from_hf_hub=False,
|
|
):
|
|
"""
|
|
For a given model:
|
|
- download and cache all the shards of a sharded checkpoint if `pretrained_model_name_or_path` is a model ID on the
|
|
Hub
|
|
- returns the list of paths to all the shards, as well as some metadata.
|
|
For the description of each arg, see [`PretrainedModel.from_pretrained`]. `index_filename` is the full path to the
|
|
index (downloaded and cached if `pretrained_model_name_or_path` is a model ID on the Hub).
|
|
"""
|
|
|
|
import json
|
|
|
|
if not os.path.isfile(index_filename):
|
|
raise ValueError(f"Can't find a checkpoint index ({index_filename}) in {pretrained_model_name_or_path}.")
|
|
|
|
with open(index_filename, "r") as f:
|
|
index = json.loads(f.read())
|
|
|
|
shard_filenames = sorted(set(index["weight_map"].values()))
|
|
sharded_metadata = index["metadata"]
|
|
sharded_metadata["all_checkpoint_keys"] = list(index["weight_map"].keys())
|
|
sharded_metadata["weight_map"] = index["weight_map"].copy()
|
|
|
|
file_map = {file: set() for file in shard_filenames}
|
|
for weight, file in index["weight_map"].items():
|
|
file_map[file].add(weight)
|
|
|
|
sharded_metadata["file_map"] = file_map
|
|
|
|
# First, let's deal with local folder.
|
|
if os.path.isdir(pretrained_model_name_or_path):
|
|
shard_filenames = [os.path.join(pretrained_model_name_or_path, subfolder, f) for f in shard_filenames]
|
|
return shard_filenames, sharded_metadata
|
|
|
|
# At this stage pretrained_model_name_or_path is a model identifier on the Hub
|
|
cached_filenames = []
|
|
# Check if the model is already cached or not. We only try the last checkpoint, this should cover most cases of
|
|
# downloaded (if interrupted).
|
|
last_shard = try_to_load_from_cache(
|
|
pretrained_model_name_or_path,
|
|
shard_filenames[-1],
|
|
cache_dir=cache_dir,
|
|
)
|
|
|
|
show_progress_bar = last_shard is None
|
|
for shard_filename in tqdm.tqdm(shard_filenames, desc="Downloading shards", disable=not show_progress_bar):
|
|
try:
|
|
cached_filename = resolve_file_path(
|
|
pretrained_model_name_or_path,
|
|
[shard_filename],
|
|
subfolder,
|
|
cache_dir=cache_dir,
|
|
from_aistudio=from_aistudio,
|
|
from_hf_hub=from_hf_hub,
|
|
)
|
|
assert (
|
|
cached_filename is not None
|
|
), f"please make sure {shard_filename} under {pretrained_model_name_or_path}"
|
|
# We have already dealt with RepositoryNotFoundError and RevisionNotFoundError when getting the index, so
|
|
# we don't have to catch them here.
|
|
except EntryNotFoundError:
|
|
raise EnvironmentError(
|
|
f"{pretrained_model_name_or_path} does not appear to have a file named {shard_filename} which is "
|
|
"required according to the checkpoint index."
|
|
)
|
|
except HTTPError:
|
|
raise EnvironmentError(
|
|
f"We couldn't connect to '{HUGGINGFACE_CO_RESOLVE_ENDPOINT}' to load {shard_filename}. You should try"
|
|
" again after checking your internet connection."
|
|
)
|
|
|
|
cached_filenames.append(cached_filename)
|
|
|
|
return cached_filenames, sharded_metadata
|
|
|
|
|
|
def is_safetensors_available():
|
|
return importlib.util.find_spec("safetensors") is not None
|
|
|
|
|
|
@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("{}:{}".format(device, dev_id))
|
|
try:
|
|
yield
|
|
finally:
|
|
paddle.set_device(origin_device)
|
|
|
|
|
|
def paddlenlp_load(path, map_location="cpu"):
|
|
assert map_location in ["cpu", "gpu", "xpu", "npu", "numpy", "np"]
|
|
if map_location in ["numpy", "np"]:
|
|
return paddle.load(path, return_numpy=True)
|
|
else:
|
|
with device_guard(map_location):
|
|
return paddle.load(path)
|
|
# TODO(zhonghui03): the following code has problems when hot start optimizer checkpoint.
|
|
if map_location == "cpu":
|
|
from paddle.framework.io import (
|
|
_parse_every_object,
|
|
_to_LodTensor,
|
|
_transformed_from_lodtensor,
|
|
)
|
|
|
|
def _ndarray_to_tensor(obj, return_numpy=False):
|
|
if return_numpy:
|
|
return obj
|
|
if paddle.in_dynamic_mode():
|
|
return paddle.Tensor(obj, zero_copy=True)
|
|
else:
|
|
return _to_LodTensor(obj)
|
|
|
|
state_dict = paddle.load(path, return_numpy=True)
|
|
# Hack for zero copy for saving loading time. for paddle.load there need copy to create paddle.Tensor
|
|
return _parse_every_object(state_dict, _transformed_from_lodtensor, _ndarray_to_tensor)
|
|
|
|
else:
|
|
return paddle.load(path)
|
|
|
|
|
|
def is_paddle_support_lazy_init():
|
|
return hasattr(paddle, "LazyGuard")
|
|
|
|
|
|
class ContextManagers:
|
|
"""
|
|
Wrapper for `contextlib.ExitStack` which enters a collection of context managers. Adaptation of `ContextManagers`
|
|
in the `fastcore` library.
|
|
"""
|
|
|
|
def __init__(self, context_managers: List[ContextManager]):
|
|
self.context_managers = context_managers
|
|
self.stack = ExitStack()
|
|
|
|
def __enter__(self):
|
|
for context_manager in self.context_managers:
|
|
self.stack.enter_context(context_manager)
|
|
|
|
def __exit__(self, *args, **kwargs):
|
|
self.stack.__exit__(*args, **kwargs)
|
|
|
|
|
|
def use_hybrid_parallel():
|
|
try:
|
|
from paddle.distributed import fleet
|
|
|
|
hcg = fleet.get_hybrid_communicate_group()
|
|
return hcg
|
|
except:
|
|
return None
|
|
|
|
|
|
def optimizer_name_suffix():
|
|
hcg = use_hybrid_parallel()
|
|
if hcg is not None:
|
|
name = []
|
|
if hcg.get_model_parallel_world_size() > 1:
|
|
name.append(f"tp{hcg.get_model_parallel_rank():0>2d}")
|
|
if hcg.get_pipe_parallel_world_size() > 1:
|
|
name.append(f"pp{hcg.get_stage_id():0>2d}")
|
|
if hcg.get_sharding_parallel_world_size() > 1:
|
|
name.append(f"shard{hcg.get_sharding_parallel_rank():0>2d}")
|
|
|
|
return "_".join(name)
|
|
else:
|
|
return None
|
|
|
|
|
|
def weight_name_suffix():
|
|
hcg = use_hybrid_parallel()
|
|
if hcg is not None:
|
|
name = []
|
|
if hcg.get_model_parallel_world_size() > 1:
|
|
name.append(f"tp{hcg.get_model_parallel_rank():0>2d}")
|
|
if hcg.get_pipe_parallel_world_size() > 1:
|
|
name.append(f"pp{hcg.get_stage_id():0>2d}")
|
|
return "_".join(name)
|
|
else:
|
|
return None
|
|
|
|
|
|
def dtype_byte_size(dtype):
|
|
"""
|
|
Returns the size (in bytes) occupied by one parameter of type `dtype`.
|
|
|
|
Example:
|
|
|
|
```py
|
|
>>> dtype_byte_size(paddle.float32)
|
|
4
|
|
```
|
|
"""
|
|
if dtype == paddle.bool:
|
|
return 1 / 8
|
|
if dtype == paddle.float8_e4m3fn or dtype == paddle.float8_e5m2:
|
|
return 1
|
|
bit_search = re.search(r"[^\d](\d+)$", str(dtype))
|
|
if bit_search is None:
|
|
raise ValueError(f"`dtype` is not a valid dtype: {dtype}.")
|
|
bit_size = int(bit_search.groups()[0])
|
|
return bit_size // 8
|
|
|
|
|
|
def apply_print_resets(buf):
|
|
return re.sub(r"^.*\r", "", buf, 0, re.M)
|
|
|
|
|
|
class CaptureStd:
|
|
"""
|
|
Context manager to capture:
|
|
|
|
- stdout: replay it, clean it up and make it available via `obj.out`
|
|
- stderr: replay it and make it available via `obj.err`
|
|
|
|
Args:
|
|
out (`bool`, *optional*, defaults to `True`): Whether to capture stdout or not.
|
|
err (`bool`, *optional*, defaults to `True`): Whether to capture stderr or not.
|
|
replay (`bool`, *optional*, defaults to `True`): Whether to replay or not.
|
|
By default each captured stream gets replayed back on context's exit, so that one can see what the test was
|
|
doing. If this is a not wanted behavior and the captured data shouldn't be replayed, pass `replay=False` to
|
|
disable this feature.
|
|
|
|
Examples:
|
|
|
|
```python
|
|
# to capture stdout only with auto-replay
|
|
with CaptureStdout() as cs:
|
|
print("Secret message")
|
|
assert "message" in cs.out
|
|
|
|
# to capture stderr only with auto-replay
|
|
import sys
|
|
|
|
with CaptureStderr() as cs:
|
|
print("Warning: ", file=sys.stderr)
|
|
assert "Warning" in cs.err
|
|
|
|
# to capture both streams with auto-replay
|
|
with CaptureStd() as cs:
|
|
print("Secret message")
|
|
print("Warning: ", file=sys.stderr)
|
|
assert "message" in cs.out
|
|
assert "Warning" in cs.err
|
|
|
|
# to capture just one of the streams, and not the other, with auto-replay
|
|
with CaptureStd(err=False) as cs:
|
|
print("Secret message")
|
|
assert "message" in cs.out
|
|
# but best use the stream-specific subclasses
|
|
|
|
# to capture without auto-replay
|
|
with CaptureStd(replay=False) as cs:
|
|
print("Secret message")
|
|
assert "message" in cs.out
|
|
```"""
|
|
|
|
def __init__(self, out=True, err=True, replay=True):
|
|
self.replay = replay
|
|
|
|
if out:
|
|
self.out_buf = StringIO()
|
|
self.out = "error: CaptureStd context is unfinished yet, called too early"
|
|
else:
|
|
self.out_buf = None
|
|
self.out = "not capturing stdout"
|
|
|
|
if err:
|
|
self.err_buf = StringIO()
|
|
self.err = "error: CaptureStd context is unfinished yet, called too early"
|
|
else:
|
|
self.err_buf = None
|
|
self.err = "not capturing stderr"
|
|
|
|
def __enter__(self):
|
|
if self.out_buf:
|
|
self.out_old = sys.stdout
|
|
sys.stdout = self.out_buf
|
|
|
|
if self.err_buf:
|
|
self.err_old = sys.stderr
|
|
sys.stderr = self.err_buf
|
|
|
|
return self
|
|
|
|
def __exit__(self, *exc):
|
|
if self.out_buf:
|
|
sys.stdout = self.out_old
|
|
captured = self.out_buf.getvalue()
|
|
if self.replay:
|
|
sys.stdout.write(captured)
|
|
self.out = apply_print_resets(captured)
|
|
|
|
if self.err_buf:
|
|
sys.stderr = self.err_old
|
|
captured = self.err_buf.getvalue()
|
|
if self.replay:
|
|
sys.stderr.write(captured)
|
|
self.err = captured
|
|
|
|
def __repr__(self):
|
|
msg = ""
|
|
if self.out_buf:
|
|
msg += f"stdout: {self.out}\n"
|
|
if self.err_buf:
|
|
msg += f"stderr: {self.err}\n"
|
|
return msg
|
|
|
|
|
|
def caculate_llm_per_token_flops(
|
|
hidden_size,
|
|
intermediate_size,
|
|
layer_num,
|
|
vocab_size,
|
|
seq_length=None,
|
|
recompute=False,
|
|
recompute_granularity=None,
|
|
):
|
|
|
|
# TFLOPs formula (from Equation 3 in Section 5.1 of https://arxiv.org/pdf/2104.04473.pdf).
|
|
flops_per_transformer = 0
|
|
flops_recompute_transformer = 0
|
|
|
|
# qkvo matmul
|
|
flops_qkvo_matmul = seq_length * hidden_size**2 * 4
|
|
|
|
# [b,s,h] [b,h,s] bs^2h
|
|
# [b,s,s] [b,s,h] bs^2h
|
|
# q_states * k_states + attn_weight * v_states
|
|
flops_core_attn = seq_length**2 * hidden_size * 2
|
|
|
|
# swiglu, matmul + dot
|
|
flops_ffn = seq_length * hidden_size * intermediate_size * 3 + seq_length * intermediate_size
|
|
|
|
flops_per_transformer = flops_qkvo_matmul + flops_core_attn + flops_ffn
|
|
if recompute:
|
|
if recompute_granularity == "full":
|
|
flops_recompute_transformer = flops_per_transformer
|
|
if recompute_granularity == "full_attn":
|
|
flops_recompute_transformer = flops_qkvo_matmul + flops_core_attn
|
|
if recompute_granularity == "core_attn":
|
|
flops_recompute_transformer = flops_core_attn
|
|
|
|
# final loggits
|
|
flops_loggits = seq_length * hidden_size * vocab_size
|
|
|
|
# 2 for mul + add in matmul
|
|
# 1 for forward, 2 for backwards since we caluate gradients for input_x and input_y
|
|
return 2 * (layer_num * (flops_per_transformer * 3 + flops_recompute_transformer) + 3 * flops_loggits) / seq_length
|