# Copyright (c) 2022 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. import os from pathlib import Path from typing import List, Tuple, Type from uvicorn.config import LOGGING_CONFIG from paddlenlp.utils.import_utils import is_package_available # check whether the package is available and give friendly description. if not is_package_available("typer"): raise ModuleNotFoundError( "paddlenlp-cli tools is not installed correctly, you can use the following command" " to install paddlenlp cli tool: >>> pip install paddlenlp[cli]" ) import importlib import inspect import shutil import typer from paddlenlp.cli.download import load_community_models from paddlenlp.cli.install import install_package_from_bos from paddlenlp.cli.server import start_backend from paddlenlp.cli.utils.tabulate import print_example_code, tabulate from paddlenlp.transformers import ( AutoModel, AutoTokenizer, PretrainedModel, PretrainedTokenizer, ) from paddlenlp.transformers.utils import find_transformer_model_type from paddlenlp.utils.downloader import is_url from paddlenlp.utils.log import logger def load_all_models(include_community: bool = False) -> List[Tuple[str, str]]: """load all model_name infos Returns: List[Tuple[str, str]]: [model_type, model_name] """ # 1. load official models module = importlib.import_module("paddlenlp.transformers") model_names = [] model_names_dict = {} for attr_name in dir(module): if attr_name.startswith("_"): continue obj = getattr(module, attr_name) if not inspect.isclass(obj): continue if not issubclass(obj, PretrainedModel): continue obj: Type[PretrainedModel] = obj if not obj.__name__.endswith("PretrainedModel"): continue configurations = obj.pretrained_init_configuration model_type = find_transformer_model_type(obj) for model_name in configurations.keys(): # get model type with refactoring model_names.append((model_type, model_name)) model_names_dict[model_name] = True logger.info(f"find {len(model_names)} official models ...") # 2. load & extend community models if include_community: community_model_names = load_community_models() for model_name in community_model_names: # there are some same model-names between codebase and community models if model_name in model_names_dict: continue model_names.append(model_name) # 3. sort result model_names.sort(key=lambda item: item[0] + item[1]) return model_names app = typer.Typer() @app.command() def download( model_name: str, cache_dir: str = typer.Option( "./pretrained_models", "--cache-dir", "-c", help="cache_dir for download pretrained model" ), force_download: bool = typer.Option(False, "--force-download", "-f", help="force download pretrained model"), ): """download the paddlenlp models with command, you can specific `model_name` >>> paddlenlp download bert \n >>> paddlenlp download -c ./my-models -f bert \n Args:\n model_name (str): pretrained model name, you can checkout all of model from source code. \n cache_dir (str, optional): the cache_dir. Defaults to "./models". """ if not os.path.isabs(cache_dir): cache_dir = os.path.join(os.getcwd(), cache_dir) if is_url(model_name): logger.error(" can not be url") return cache_dir = os.path.join(cache_dir, model_name) if force_download: shutil.rmtree(cache_dir, ignore_errors=True) model: PretrainedModel = AutoModel.from_pretrained(model_name) model.save_pretrained(cache_dir) tokenizer: PretrainedTokenizer = AutoTokenizer.from_pretrained(model_name) tokenizer.save_pretrained(cache_dir) logger.info(f"successfully saved model into <{cache_dir}>") @app.command() def search( query=typer.Argument(..., help="the query of searching model"), include_community: bool = typer.Option( False, "--include-community", "-i", help="whether searching community models" ), ): """search the model with query, eg: paddlenlp search bert >>> paddlenlp search bert \n >>> paddlenlp search -i bert \n Args: \n query (Optional[str]): the str fragment of bert-name \n include_community (Optional[bool]): whether searching community models """ logger.info("start to search models ...") model_names = load_all_models(include_community) tables = [] for model_type, model_name in model_names: # TODO(wj-Mcat): ignore the model_category info if not query or query in model_name: tables.append([model_type, model_name]) tabulate(tables, headers=["model type", "model name"], highlight_word=query) print_example_code() logger.info(f"the retrieved number of models results is {len(tables)} ...") @app.command(help="Start the PaddleNLP SimpleServer.") def server( app: str, host: str = typer.Option("127.0.0.1", "--host", help="Bind socket to this host."), port: int = typer.Option("8000", "--port", help="Bind socket to this port."), app_dir: str = typer.Option(None, "--app_dir", help="The application directory path."), workers: int = typer.Option( None, "--workers", help="Number of worker processes. Defaults to the $WEB_CONCURRENCY environment" " variable if available, or 1. Not valid with --reload.", ), log_level: int = typer.Option(None, "--log_level", help="Log level. [default: info]"), limit_concurrency: int = typer.Option( None, "--limit-concurrency", help="Maximum number of concurrent connections or tasks to allow, before issuing" ), limit_max_requests: int = typer.Option( None, "--limit-max-requests", help="Maximum number of requests to service before terminating the process." ), timeout_keep_alive: int = typer.Option( 15, "--timeout-keep-alive", help="Close Keep-Alive connections if no new data is received within this timeout." ), reload: bool = typer.Option(False, "--reload", help="Reload the server when the app_dir is changed."), ): """The main function for the staring the SimpleServer""" logger.info("starting to PaddleNLP SimpleServer...") if app_dir is None: app_dir = str(Path(os.getcwd())) # Flags of uvicorn backend_kwargs = { "host": host, "port": port, "log_config": LOGGING_CONFIG, "log_level": log_level, "workers": workers, "limit_concurrency": limit_concurrency, "limit_max_requests": limit_max_requests, "timeout_keep_alive": timeout_keep_alive, "app_dir": app_dir, "reload": reload, } start_backend(app, **backend_kwargs) @app.command( help="install the target version of paddlenlp, eg: paddlenlp install / paddlenlp install paddlepaddle==latest" ) def install( package: str = typer.Argument(default="paddlenlp==latest", help="install the target version of paddlenlp") ): """The main function for the staring the SimpleServer""" package = package.replace(" ", "").strip() if not package: raise ValueError("please assign the package name") # 1. parse the version of paddlenlp splits = [item for item in package.split("==")] if len(splits) == 0 or len(splits) > 2: raise ValueError( "please set the valid package: ==, eg: paddlenlp==latest, paddlenlp==3099, " f"but received: {package}" ) tag = "latest" package_name = splits[0] # TODO(wj-Mcat): will support `pipelines`, `ppdiffusers` later. assert package_name in ["paddlenlp"], "we only support paddlenlp" if len(splits) == 2: tag = splits[1] # 2. download & install package from bos server install_package_from_bos(package_name=package_name, tag=tag) def main(): """the PaddleNLP CLI entry""" app() if __name__ == "__main__": main()