From 26a923723788fe5e8331af666a5e296aff9fbd56 Mon Sep 17 00:00:00 2001 From: liunux4odoo <41217877+liunux4odoo@users.noreply.github.com> Date: Thu, 31 Aug 2023 16:18:12 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E9=81=BF=E5=85=8Dconfigs=E5=AF=B9torch?= =?UTF-8?q?=E7=9A=84=E4=BE=9D=E8=B5=96=EF=BC=9Bwebui=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E4=BB=8Econfigs=E8=8E=B7=E5=8F=96api=E5=9C=B0=E5=9D=80(close?= =?UTF-8?q?=20#1319)=20(#1328)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- configs/model_config.py.example | 21 ++++++++++++++++----- webui.py | 4 +++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/configs/model_config.py.example b/configs/model_config.py.example index f46dad6..b9dd1de 100644 --- a/configs/model_config.py.example +++ b/configs/model_config.py.example @@ -1,6 +1,5 @@ import os import logging -import torch # 日志格式 LOG_FORMAT = "%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s" logger = logging.getLogger() @@ -8,6 +7,19 @@ logger.setLevel(logging.INFO) logging.basicConfig(format=LOG_FORMAT) +# 分布式部署时,不运行LLM的机器上可以不装torch +def default_device(): + try: + import torch + if torch.cuda.is_available(): + return "cuda" + if torch.backends.mps.is_available(): + return "mps" + except: + pass + return "cpu" + + # 在以下字典中修改属性值,以指定本地embedding模型存储位置 # 如将 "text2vec": "GanymedeNil/text2vec-large-chinese" 修改为 "text2vec": "User/Downloads/text2vec-large-chinese" # 此处请写绝对路径 @@ -33,7 +45,7 @@ embedding_model_dict = { EMBEDDING_MODEL = "m3e-base" # Embedding 模型运行设备 -EMBEDDING_DEVICE = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu" +EMBEDDING_DEVICE = default_device() llm_model_dict = { @@ -76,7 +88,6 @@ llm_model_dict = { }, } - # LLM 名称 LLM_MODEL = "chatglm2-6b" @@ -84,7 +95,7 @@ LLM_MODEL = "chatglm2-6b" HISTORY_LEN = 3 # LLM 运行设备 -LLM_DEVICE = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu" +LLM_DEVICE = default_device() # 日志存储路径 LOG_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "logs") @@ -166,4 +177,4 @@ BING_SUBSCRIPTION_KEY = "" # 是否开启中文标题加强,以及标题增强的相关配置 # 通过增加标题判断,判断哪些文本为标题,并在metadata中进行标记; # 然后将文本与往上一级的标题进行拼合,实现文本信息的增强。 -ZH_TITLE_ENHANCE = False \ No newline at end of file +ZH_TITLE_ENHANCE = False diff --git a/webui.py b/webui.py index 58fc0e3..0cda9eb 100644 --- a/webui.py +++ b/webui.py @@ -10,8 +10,10 @@ from streamlit_option_menu import option_menu from webui_pages import * import os from configs import VERSION +from server.utils import api_address -api = ApiRequest(base_url="http://127.0.0.1:7861", no_remote_api=False) + +api = ApiRequest(base_url=api_address()) if __name__ == "__main__": st.set_page_config( From b1201a5f23142d8b0e9d167f3f66acbc26eb6c13 Mon Sep 17 00:00:00 2001 From: liunux4odoo <41217877+liunux4odoo@users.noreply.github.com> Date: Thu, 31 Aug 2023 17:44:48 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E4=BC=98=E5=8C=96LLM=E5=92=8CEmbedding?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E8=BF=90=E8=A1=8C=E8=AE=BE=E5=A4=87=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=EF=BC=8C=E5=8F=AF=E8=AE=BE=E4=B8=BAauto=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E6=A3=80=E6=B5=8B=EF=BC=9B=E4=BF=AE=E5=A4=8D=EF=BC=9A?= =?UTF-8?q?=E9=87=8D=E5=BB=BA=E7=9F=A5=E8=AF=86=E5=BA=93=E6=97=B6FAISS?= =?UTF-8?q?=E6=9C=AA=E4=BF=9D=E5=AD=98=20(#1330)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 避免configs对torch的依赖; * webui自动从configs获取api地址(close #1319) * bug fix: 重建知识库时FAISS未保存 * 优化LLM和Embedding模型运行设备配置,可设为auto自动检测 --- .gitignore | 2 +- configs/model_config.py.example | 21 +++---------- server/knowledge_base/kb_service/base.py | 5 ++-- .../kb_service/faiss_kb_service.py | 18 ++++++----- .../kb_service/pg_kb_service.py | 6 ++-- server/knowledge_base/migrate.py | 8 ++++- server/llm_api.py | 6 ++-- server/utils.py | 30 +++++++++++++++++-- startup.py | 11 +++---- 9 files changed, 66 insertions(+), 41 deletions(-) diff --git a/.gitignore b/.gitignore index c4178a9..a7ef90f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ logs .idea/ __pycache__/ -knowledge_base/ +/knowledge_base/ configs/*.py .vscode/ .pytest_cache/ diff --git a/configs/model_config.py.example b/configs/model_config.py.example index b9dd1de..5466f7f 100644 --- a/configs/model_config.py.example +++ b/configs/model_config.py.example @@ -7,19 +7,6 @@ logger.setLevel(logging.INFO) logging.basicConfig(format=LOG_FORMAT) -# 分布式部署时,不运行LLM的机器上可以不装torch -def default_device(): - try: - import torch - if torch.cuda.is_available(): - return "cuda" - if torch.backends.mps.is_available(): - return "mps" - except: - pass - return "cpu" - - # 在以下字典中修改属性值,以指定本地embedding模型存储位置 # 如将 "text2vec": "GanymedeNil/text2vec-large-chinese" 修改为 "text2vec": "User/Downloads/text2vec-large-chinese" # 此处请写绝对路径 @@ -44,8 +31,8 @@ embedding_model_dict = { # 选用的 Embedding 名称 EMBEDDING_MODEL = "m3e-base" -# Embedding 模型运行设备 -EMBEDDING_DEVICE = default_device() +# Embedding 模型运行设备。设为"auto"会自动检测,也可手动设定为"cuda","mps","cpu"其中之一。 +EMBEDDING_DEVICE = "auto" llm_model_dict = { @@ -94,8 +81,8 @@ LLM_MODEL = "chatglm2-6b" # 历史对话轮数 HISTORY_LEN = 3 -# LLM 运行设备 -LLM_DEVICE = default_device() +# LLM 运行设备。可选项同Embedding 运行设备。 +LLM_DEVICE = "auto" # 日志存储路径 LOG_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "logs") diff --git a/server/knowledge_base/kb_service/base.py b/server/knowledge_base/kb_service/base.py index ca8d1ae..79b1518 100644 --- a/server/knowledge_base/kb_service/base.py +++ b/server/knowledge_base/kb_service/base.py @@ -18,11 +18,12 @@ from server.db.repository.knowledge_file_repository import ( ) from configs.model_config import (kbs_config, VECTOR_SEARCH_TOP_K, SCORE_THRESHOLD, - EMBEDDING_DEVICE, EMBEDDING_MODEL) + EMBEDDING_MODEL) from server.knowledge_base.utils import ( get_kb_path, get_doc_path, load_embeddings, KnowledgeFile, list_kbs_from_folder, list_files_from_folder, ) +from server.utils import embedding_device from typing import List, Union, Dict @@ -45,7 +46,7 @@ class KBService(ABC): self.doc_path = get_doc_path(self.kb_name) self.do_init() - def _load_embeddings(self, embed_device: str = EMBEDDING_DEVICE) -> Embeddings: + def _load_embeddings(self, embed_device: str = embedding_device()) -> Embeddings: return load_embeddings(self.embed_model, embed_device) def create_kb(self): diff --git a/server/knowledge_base/kb_service/faiss_kb_service.py b/server/knowledge_base/kb_service/faiss_kb_service.py index 3601b57..f17b2da 100644 --- a/server/knowledge_base/kb_service/faiss_kb_service.py +++ b/server/knowledge_base/kb_service/faiss_kb_service.py @@ -5,7 +5,6 @@ from configs.model_config import ( KB_ROOT_PATH, CACHED_VS_NUM, EMBEDDING_MODEL, - EMBEDDING_DEVICE, SCORE_THRESHOLD ) from server.knowledge_base.kb_service.base import KBService, SupportedVSType @@ -15,7 +14,7 @@ from langchain.vectorstores import FAISS from langchain.embeddings.base import Embeddings from typing import List from langchain.docstore.document import Document -from server.utils import torch_gc +from server.utils import torch_gc, embedding_device _VECTOR_STORE_TICKS = {} @@ -25,10 +24,10 @@ _VECTOR_STORE_TICKS = {} def load_faiss_vector_store( knowledge_base_name: str, embed_model: str = EMBEDDING_MODEL, - embed_device: str = EMBEDDING_DEVICE, + embed_device: str = embedding_device(), embeddings: Embeddings = None, tick: int = 0, # tick will be changed by upload_doc etc. and make cache refreshed. -): +) -> FAISS: print(f"loading vector store in '{knowledge_base_name}'.") vs_path = get_vs_path(knowledge_base_name) if embeddings is None: @@ -74,13 +73,18 @@ class FaissKBService(KBService): def get_kb_path(self): return os.path.join(KB_ROOT_PATH, self.kb_name) - def load_vector_store(self): + def load_vector_store(self) -> FAISS: return load_faiss_vector_store( knowledge_base_name=self.kb_name, embed_model=self.embed_model, tick=_VECTOR_STORE_TICKS.get(self.kb_name, 0), ) + def save_vector_store(self, vector_store: FAISS = None): + vector_store = vector_store or self.load_vector_store() + vector_store.save_local(self.vs_path) + return vector_store + def refresh_vs_cache(self): refresh_vs_cache(self.kb_name) @@ -117,11 +121,11 @@ class FaissKBService(KBService): if not kwargs.get("not_refresh_vs_cache"): vector_store.save_local(self.vs_path) self.refresh_vs_cache() + return vector_store def do_delete_doc(self, kb_file: KnowledgeFile, **kwargs): - embeddings = self._load_embeddings() vector_store = self.load_vector_store() ids = [k for k, v in vector_store.docstore._dict.items() if v.metadata["source"] == kb_file.filepath] @@ -133,7 +137,7 @@ class FaissKBService(KBService): vector_store.save_local(self.vs_path) self.refresh_vs_cache() - return True + return vector_store def do_clear_vs(self): shutil.rmtree(self.vs_path) diff --git a/server/knowledge_base/kb_service/pg_kb_service.py b/server/knowledge_base/kb_service/pg_kb_service.py index 4b52625..3e3dd52 100644 --- a/server/knowledge_base/kb_service/pg_kb_service.py +++ b/server/knowledge_base/kb_service/pg_kb_service.py @@ -6,16 +6,16 @@ from langchain.vectorstores import PGVector from langchain.vectorstores.pgvector import DistanceStrategy from sqlalchemy import text -from configs.model_config import EMBEDDING_DEVICE, kbs_config +from configs.model_config import kbs_config from server.knowledge_base.kb_service.base import SupportedVSType, KBService, EmbeddingsFunAdapter, \ score_threshold_process from server.knowledge_base.utils import load_embeddings, KnowledgeFile - +from server.utils import embedding_device as get_embedding_device class PGKBService(KBService): pg_vector: PGVector - def _load_pg_vector(self, embedding_device: str = EMBEDDING_DEVICE, embeddings: Embeddings = None): + def _load_pg_vector(self, embedding_device: str = get_embedding_device(), embeddings: Embeddings = None): _embeddings = embeddings if _embeddings is None: _embeddings = load_embeddings(self.embed_model, embedding_device) diff --git a/server/knowledge_base/migrate.py b/server/knowledge_base/migrate.py index af506e2..4285b79 100644 --- a/server/knowledge_base/migrate.py +++ b/server/knowledge_base/migrate.py @@ -69,6 +69,7 @@ def folder2db( print(result) if kb.vs_type() == SupportedVSType.FAISS: + kb.save_vector_store() kb.refresh_vs_cache() elif mode == "fill_info_only": files = list_files_from_folder(kb_name) @@ -85,6 +86,7 @@ def folder2db( kb.update_doc(kb_file, not_refresh_vs_cache=True) if kb.vs_type() == SupportedVSType.FAISS: + kb.save_vector_store() kb.refresh_vs_cache() elif mode == "increament": db_files = kb.list_files() @@ -102,6 +104,7 @@ def folder2db( print(result) if kb.vs_type() == SupportedVSType.FAISS: + kb.save_vector_store() kb.refresh_vs_cache() else: print(f"unspported migrate mode: {mode}") @@ -131,7 +134,10 @@ def prune_db_files(kb_name: str): files = list(set(files_in_db) - set(files_in_folder)) kb_files = file_to_kbfile(kb_name, files) for kb_file in kb_files: - kb.delete_doc(kb_file) + kb.delete_doc(kb_file, not_refresh_vs_cache=True) + if kb.vs_type() == SupportedVSType.FAISS: + kb.save_vector_store() + kb.refresh_vs_cache() return kb_files def prune_folder_files(kb_name: str): diff --git a/server/llm_api.py b/server/llm_api.py index 7ef5891..d9667e4 100644 --- a/server/llm_api.py +++ b/server/llm_api.py @@ -4,8 +4,8 @@ import sys import os sys.path.append(os.path.dirname(os.path.dirname(__file__))) -from configs.model_config import llm_model_dict, LLM_MODEL, LLM_DEVICE, LOG_PATH, logger -from server.utils import MakeFastAPIOffline, set_httpx_timeout +from configs.model_config import llm_model_dict, LLM_MODEL, LOG_PATH, logger +from server.utils import MakeFastAPIOffline, set_httpx_timeout, llm_device host_ip = "0.0.0.0" @@ -34,7 +34,7 @@ def create_model_worker_app( worker_address=base_url.format(model_worker_port), controller_address=base_url.format(controller_port), model_path=llm_model_dict[LLM_MODEL].get("local_model_path"), - device=LLM_DEVICE, + device=llm_device(), gpus=None, max_gpu_memory="20GiB", load_8bit=False, diff --git a/server/utils.py b/server/utils.py index 167b672..d716582 100644 --- a/server/utils.py +++ b/server/utils.py @@ -5,8 +5,8 @@ import torch from fastapi import FastAPI from pathlib import Path import asyncio -from configs.model_config import LLM_MODEL -from typing import Any, Optional +from configs.model_config import LLM_MODEL, LLM_DEVICE, EMBEDDING_DEVICE +from typing import Literal, Optional class BaseResponse(BaseModel): @@ -201,6 +201,7 @@ def get_model_worker_config(model_name: str = LLM_MODEL) -> dict: config = FSCHAT_MODEL_WORKERS.get("default", {}).copy() config.update(llm_model_dict.get(model_name, {})) config.update(FSCHAT_MODEL_WORKERS.get(model_name, {})) + config["device"] = llm_device(config.get("device")) return config @@ -256,3 +257,28 @@ def set_httpx_timeout(timeout: float = None): httpx._config.DEFAULT_TIMEOUT_CONFIG.connect = timeout httpx._config.DEFAULT_TIMEOUT_CONFIG.read = timeout httpx._config.DEFAULT_TIMEOUT_CONFIG.write = timeout + + +# 自动检查torch可用的设备。分布式部署时,不运行LLM的机器上可以不装torch +def detect_device() -> Literal["cuda", "mps", "cpu"]: + try: + import torch + if torch.cuda.is_available(): + return "cuda" + if torch.backends.mps.is_available(): + return "mps" + except: + pass + return "cpu" + + +def llm_device(device: str = LLM_DEVICE) -> Literal["cuda", "mps", "cpu"]: + if device not in ["cuda", "mps", "cpu"]: + device = detect_device() + return device + + +def embedding_device(device: str = EMBEDDING_DEVICE) -> Literal["cuda", "mps", "cpu"]: + if device not in ["cuda", "mps", "cpu"]: + device = detect_device() + return device diff --git a/startup.py b/startup.py index 64a3bcc..07630d9 100644 --- a/startup.py +++ b/startup.py @@ -14,12 +14,13 @@ except: pass sys.path.append(os.path.dirname(os.path.dirname(__file__))) -from configs.model_config import EMBEDDING_DEVICE, EMBEDDING_MODEL, llm_model_dict, LLM_MODEL, LLM_DEVICE, LOG_PATH, \ +from configs.model_config import EMBEDDING_MODEL, llm_model_dict, LLM_MODEL, LOG_PATH, \ logger from configs.server_config import (WEBUI_SERVER, API_SERVER, OPEN_CROSS_DOMAIN, FSCHAT_CONTROLLER, FSCHAT_MODEL_WORKERS, FSCHAT_OPENAI_API, ) from server.utils import (fschat_controller_address, fschat_model_worker_address, - fschat_openai_api_address, set_httpx_timeout) + fschat_openai_api_address, set_httpx_timeout, + llm_device, embedding_device, get_model_worker_config) from server.utils import MakeFastAPIOffline, FastAPI import argparse from typing import Tuple, List @@ -195,7 +196,7 @@ def run_model_worker( ): import uvicorn - kwargs = FSCHAT_MODEL_WORKERS[model_name].copy() + kwargs = get_model_worker_config(model_name) host = kwargs.pop("host") port = kwargs.pop("port") model_path = llm_model_dict[model_name].get("local_model_path", "") @@ -331,9 +332,9 @@ def dump_server_info(after_start=False): print(f"项目版本:{VERSION}") print(f"langchain版本:{langchain.__version__}. fastchat版本:{fastchat.__version__}") print("\n") - print(f"当前LLM模型:{LLM_MODEL} @ {LLM_DEVICE}") + print(f"当前LLM模型:{LLM_MODEL} @ {llm_device()}") pprint(llm_model_dict[LLM_MODEL]) - print(f"当前Embbedings模型: {EMBEDDING_MODEL} @ {EMBEDDING_DEVICE}") + print(f"当前Embbedings模型: {EMBEDDING_MODEL} @ {embedding_device()}") if after_start: print("\n") print(f"服务端运行信息:") From 72b9da2649ca7c22d3dac11b5573fcee8be0a041 Mon Sep 17 00:00:00 2001 From: liunux4odoo <41217877+liunux4odoo@users.noreply.github.com> Date: Thu, 31 Aug 2023 22:55:07 +0800 Subject: [PATCH 3/5] =?UTF-8?q?startup.py=E5=A2=9E=E5=8A=A0=E5=8F=82?= =?UTF-8?q?=E6=95=B0-q=20|=20quiet=EF=BC=8C=E5=8F=AF=E4=BB=A5=E8=BF=87?= =?UTF-8?q?=E6=BB=A4fastchat=E7=9A=84controller/model=5Fworker=E4=B8=8D?= =?UTF-8?q?=E5=BF=85=E8=A6=81=E7=9A=84=E6=97=A5=E5=BF=97=E8=BE=93=E5=87=BA?= =?UTF-8?q?=20(#1333)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * startup.py增加参数`-q | quiet`,可以过滤fastchat的controller/model_worker不必要的日志输出 --- startup.py | 85 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 22 deletions(-) diff --git a/startup.py b/startup.py index 07630d9..5ef05ce 100644 --- a/startup.py +++ b/startup.py @@ -29,10 +29,12 @@ from configs import VERSION def create_controller_app( dispatch_method: str, + log_level: str = "INFO", ) -> FastAPI: import fastchat.constants fastchat.constants.LOGDIR = LOG_PATH - from fastchat.serve.controller import app, Controller + from fastchat.serve.controller import app, Controller, logger + logger.setLevel(log_level) controller = Controller(dispatch_method) sys.modules["fastchat.serve.controller"].controller = controller @@ -42,13 +44,14 @@ def create_controller_app( return app -def create_model_worker_app(**kwargs) -> Tuple[argparse.ArgumentParser, FastAPI]: +def create_model_worker_app(log_level: str = "INFO", **kwargs) -> Tuple[argparse.ArgumentParser, FastAPI]: import fastchat.constants fastchat.constants.LOGDIR = LOG_PATH - from fastchat.serve.model_worker import app, GptqConfig, AWQConfig, ModelWorker, worker_id + from fastchat.serve.model_worker import app, GptqConfig, AWQConfig, ModelWorker, worker_id, logger import argparse import threading import fastchat.serve.model_worker + logger.setLevel(log_level) # workaround to make program exit with Ctrl+c # it should be deleted after pr is merged by fastchat @@ -137,10 +140,14 @@ def create_model_worker_app(**kwargs) -> Tuple[argparse.ArgumentParser, FastAPI] def create_openai_api_app( controller_address: str, api_keys: List = [], + log_level: str = "INFO", ) -> FastAPI: import fastchat.constants fastchat.constants.LOGDIR = LOG_PATH from fastchat.serve.openai_api_server import app, CORSMiddleware, app_settings + from fastchat.utils import build_logger + logger = build_logger("openai_api", "openai_api.log") + logger.setLevel(log_level) app.add_middleware( CORSMiddleware, @@ -150,6 +157,7 @@ def create_openai_api_app( allow_headers=["*"], ) + sys.modules["fastchat.serve.openai_api_server"].logger = logger app_settings.controller_address = controller_address app_settings.api_keys = api_keys @@ -159,6 +167,9 @@ def create_openai_api_app( def _set_app_seq(app: FastAPI, q: Queue, run_seq: int): + if q is None or not isinstance(run_seq, int): + return + if run_seq == 1: @app.on_event("startup") async def on_startup(): @@ -177,15 +188,22 @@ def _set_app_seq(app: FastAPI, q: Queue, run_seq: int): q.put(run_seq) -def run_controller(q: Queue, run_seq: int = 1): +def run_controller(q: Queue, run_seq: int = 1, log_level: str ="INFO"): import uvicorn + import sys - app = create_controller_app(FSCHAT_CONTROLLER.get("dispatch_method")) + app = create_controller_app( + dispatch_method=FSCHAT_CONTROLLER.get("dispatch_method"), + log_level=log_level, + ) _set_app_seq(app, q, run_seq) host = FSCHAT_CONTROLLER["host"] port = FSCHAT_CONTROLLER["port"] - uvicorn.run(app, host=host, port=port) + if log_level == "ERROR": + sys.stdout = sys.__stdout__ + sys.stderr = sys.__stderr__ + uvicorn.run(app, host=host, port=port, log_level=log_level.lower()) def run_model_worker( @@ -193,8 +211,10 @@ def run_model_worker( controller_address: str = "", q: Queue = None, run_seq: int = 2, + log_level: str ="INFO", ): import uvicorn + import sys kwargs = get_model_worker_config(model_name) host = kwargs.pop("host") @@ -205,21 +225,28 @@ def run_model_worker( kwargs["controller_address"] = controller_address or fschat_controller_address() kwargs["worker_address"] = fschat_model_worker_address() - app = create_model_worker_app(**kwargs) + app = create_model_worker_app(log_level=log_level, **kwargs) _set_app_seq(app, q, run_seq) + if log_level == "ERROR": + sys.stdout = sys.__stdout__ + sys.stderr = sys.__stderr__ - uvicorn.run(app, host=host, port=port) + uvicorn.run(app, host=host, port=port, log_level=log_level.lower()) -def run_openai_api(q: Queue, run_seq: int = 3): +def run_openai_api(q: Queue, run_seq: int = 3, log_level: str = "INFO"): import uvicorn + import sys controller_addr = fschat_controller_address() - app = create_openai_api_app(controller_addr) # todo: not support keys yet. + app = create_openai_api_app(controller_addr, log_level=log_level) # TODO: not support keys yet. _set_app_seq(app, q, run_seq) host = FSCHAT_OPENAI_API["host"] port = FSCHAT_OPENAI_API["port"] + if log_level == "ERROR": + sys.stdout = sys.__stdout__ + sys.stderr = sys.__stderr__ uvicorn.run(app, host=host, port=port) @@ -239,13 +266,15 @@ def run_api_server(q: Queue, run_seq: int = 4): def run_webui(q: Queue, run_seq: int = 5): host = WEBUI_SERVER["host"] port = WEBUI_SERVER["port"] - while True: - no = q.get() - if no != run_seq - 1: - q.put(no) - else: - break - q.put(run_seq) + + if q is not None and isinstance(run_seq, int): + while True: + no = q.get() + if no != run_seq - 1: + q.put(no) + else: + break + q.put(run_seq) p = subprocess.Popen(["streamlit", "run", "webui.py", "--server.address", host, "--server.port", str(port)]) @@ -315,11 +344,18 @@ def parse_args() -> argparse.ArgumentParser: help="run webui.py server", dest="webui", ) + parser.add_argument( + "-q", + "--quiet", + action="store_true", + help="减少fastchat服务log信息", + dest="quiet", + ) args = parser.parse_args() return args, parser -def dump_server_info(after_start=False): +def dump_server_info(after_start=False, args=None): import platform import langchain import fastchat @@ -355,6 +391,7 @@ if __name__ == "__main__": mp.set_start_method("spawn") queue = Queue() args, parser = parse_args() + if args.all_webui: args.openai_api = True args.model_worker = True @@ -373,19 +410,23 @@ if __name__ == "__main__": args.api = False args.webui = False - dump_server_info() + dump_server_info(args=args) if len(sys.argv) > 1: logger.info(f"正在启动服务:") logger.info(f"如需查看 llm_api 日志,请前往 {LOG_PATH}") processes = {} + if args.quiet: + log_level = "ERROR" + else: + log_level = "INFO" if args.openai_api: process = Process( target=run_controller, name=f"controller({os.getpid()})", - args=(queue, len(processes) + 1), + args=(queue, len(processes) + 1, log_level), daemon=True, ) process.start() @@ -406,7 +447,7 @@ if __name__ == "__main__": process = Process( target=run_model_worker, name=f"model_worker({os.getpid()})", - args=(args.model_name, args.controller_address, queue, len(processes) + 1), + args=(args.model_name, args.controller_address, queue, len(processes) + 1, log_level), daemon=True, ) process.start() @@ -441,7 +482,7 @@ if __name__ == "__main__": no = queue.get() if no == len(processes): time.sleep(0.5) - dump_server_info(True) + dump_server_info(after_start=True, args=args) break else: queue.put(no) From 6c4ef26e9ac87448baa000a7bd1a7b03322d1610 Mon Sep 17 00:00:00 2001 From: imClumsyPanda Date: Fri, 1 Sep 2023 10:23:57 +0800 Subject: [PATCH 4/5] add RapidOCRPDFLoader and RapidOCRLoader (#1275) * add RapidOCRPDFLoader * update mypdfloader.py and requirements.txt * add myimgloader.py * add test samples * add TODO to mypdfloader * add loaders to KnowledgeFile class * add loaders to KnowledgeFile class --- document_loaders/__init__.py | 2 ++ document_loaders/myimgloader.py | 25 +++++++++++++++++++++ document_loaders/mypdfloader.py | 37 ++++++++++++++++++++++++++++++++ requirements.txt | 2 ++ requirements_api.txt | 2 ++ server/knowledge_base/utils.py | 8 +++++-- tests/samples/ocr_test.jpg | Bin 0 -> 8050 bytes tests/samples/ocr_test.pdf | Bin 0 -> 25816 bytes 8 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 document_loaders/__init__.py create mode 100644 document_loaders/myimgloader.py create mode 100644 document_loaders/mypdfloader.py create mode 100644 tests/samples/ocr_test.jpg create mode 100644 tests/samples/ocr_test.pdf diff --git a/document_loaders/__init__.py b/document_loaders/__init__.py new file mode 100644 index 0000000..a4d6b28 --- /dev/null +++ b/document_loaders/__init__.py @@ -0,0 +1,2 @@ +from .mypdfloader import RapidOCRPDFLoader +from .myimgloader import RapidOCRLoader \ No newline at end of file diff --git a/document_loaders/myimgloader.py b/document_loaders/myimgloader.py new file mode 100644 index 0000000..8648192 --- /dev/null +++ b/document_loaders/myimgloader.py @@ -0,0 +1,25 @@ +from typing import List +from langchain.document_loaders.unstructured import UnstructuredFileLoader + + +class RapidOCRLoader(UnstructuredFileLoader): + def _get_elements(self) -> List: + def img2text(filepath): + from rapidocr_onnxruntime import RapidOCR + resp = "" + ocr = RapidOCR() + result, _ = ocr(filepath) + if result: + ocr_result = [line[1] for line in result] + resp += "\n".join(ocr_result) + return resp + + text = img2text(self.file_path) + from unstructured.partition.text import partition_text + return partition_text(text=text, **self.unstructured_kwargs) + + +if __name__ == "__main__": + loader = RapidOCRLoader(file_path="../tests/samples/ocr_test.jpg") + docs = loader.load() + print(docs) diff --git a/document_loaders/mypdfloader.py b/document_loaders/mypdfloader.py new file mode 100644 index 0000000..71e063d --- /dev/null +++ b/document_loaders/mypdfloader.py @@ -0,0 +1,37 @@ +from typing import List +from langchain.document_loaders.unstructured import UnstructuredFileLoader + + +class RapidOCRPDFLoader(UnstructuredFileLoader): + def _get_elements(self) -> List: + def pdf2text(filepath): + import fitz + from rapidocr_onnxruntime import RapidOCR + import numpy as np + ocr = RapidOCR() + doc = fitz.open(filepath) + resp = "" + for page in doc: + # TODO: 依据文本与图片顺序调整处理方式 + text = page.get_text("") + resp += text + "\n" + + img_list = page.get_images() + for img in img_list: + pix = fitz.Pixmap(doc, img[0]) + img_array = np.frombuffer(pix.samples, dtype=np.uint8).reshape(pix.height, pix.width, -1) + result, _ = ocr(img_array) + if result: + ocr_result = [line[1] for line in result] + resp += "\n".join(ocr_result) + return resp + + text = pdf2text(self.file_path) + from unstructured.partition.text import partition_text + return partition_text(text=text, **self.unstructured_kwargs) + + +if __name__ == "__main__": + loader = RapidOCRPDFLoader(file_path="../tests/samples/ocr_test.pdf") + docs = loader.load() + print(docs) diff --git a/requirements.txt b/requirements.txt index e40f665..4271f3a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,6 +15,8 @@ SQLAlchemy==2.0.19 faiss-cpu accelerate spacy +PyMuPDF==1.22.5 +rapidocr_onnxruntime>=1.3.1 # uncomment libs if you want to use corresponding vector store # pymilvus==2.1.3 # requires milvus==2.1.3 diff --git a/requirements_api.txt b/requirements_api.txt index 58dbc0c..bdecf3c 100644 --- a/requirements_api.txt +++ b/requirements_api.txt @@ -16,6 +16,8 @@ faiss-cpu nltk accelerate spacy +PyMuPDF==1.22.5 +rapidocr_onnxruntime>=1.3.1 # uncomment libs if you want to use corresponding vector store # pymilvus==2.1.3 # requires milvus==2.1.3 diff --git a/server/knowledge_base/utils.py b/server/knowledge_base/utils.py index 8cab754..8582c9c 100644 --- a/server/knowledge_base/utils.py +++ b/server/knowledge_base/utils.py @@ -87,7 +87,8 @@ LOADER_DICT = {"UnstructuredHTMLLoader": ['.html'], "UnstructuredMarkdownLoader": ['.md'], "CustomJSONLoader": [".json"], "CSVLoader": [".csv"], - "PyPDFLoader": [".pdf"], + "RapidOCRPDFLoader": [".pdf"], + "RapidOCRLoader": ['.png', '.jpg', '.jpeg', '.bmp'], "UnstructuredFileLoader": ['.eml', '.msg', '.rst', '.rtf', '.txt', '.xml', '.doc', '.docx', '.epub', '.odt', @@ -196,7 +197,10 @@ class KnowledgeFile: print(f"{self.document_loader_name} used for {self.filepath}") try: - document_loaders_module = importlib.import_module('langchain.document_loaders') + if self.document_loader_name in []: + document_loaders_module = importlib.import_module('document_loaders') + else: + document_loaders_module = importlib.import_module('langchain.document_loaders') DocumentLoader = getattr(document_loaders_module, self.document_loader_name) except Exception as e: print(e) diff --git a/tests/samples/ocr_test.jpg b/tests/samples/ocr_test.jpg new file mode 100644 index 0000000000000000000000000000000000000000..70c199b7c73ddb2a9ae3467d238c5547d33f5e42 GIT binary patch literal 8050 zcmd6s2T&Bxm&b>kSwNDcMWRa1QG!Sk0RaU8VMRbPC~?6RkmwQx6%+}Q1SIF2VOLZ@ zSTYiJK@btyB`hN>aQyDB?ymkn9v zMh3voF5q+?&;uyQ$^U9+MtRn#XsM_uDXHjbXsBr!=@}Ur=ouK8nAupEm|2+_7+5%2 zSkJL@aBwg}xVSmlx!Kq`*#CM78O7NcN-8=kDmr#11}66ZvYqw-thB&5*<}hcA%L8f zjDnTybO3+?02$Spw|_bO&qhXm<|8!?Ege0>S%*dzfSinif}E1#FR#yfKRJ62P_k07 zU69kIK4;-XBlL(}{%Kl1t?<>_UJlDKyokbm|0p_oPA=~AJfdRa7cX5_R8m$^Ra4iy zrf*JGOStF0K#U+&w%40)v7>;GtpBF|l#+&yWe}FJ8XN$b9`KtDvx` z_Wz)bz~k92U2-`h9Kv$Hq^>&hFkm@c{gD z_!k!$K=Dsl|3daRT&!nY
Ylr(>Fk&%a-iGr1q>Vh0Ko2~_o)1z}j@=s~mucqbK z_RR!bAV=b(41joGKdXSMaEf&4tHi84(4O&Uj(MUF5af@s4i-8b?h_;mttobU>tm$rB zZ){mZv>DF(-Yvnemr*2C`VrRq4g?hO+i{fXh7pzS7_YSJJ6A6>$B94X7xhz+5Zip# zbCXtEMboB_C!@OM5q22mLnME=FCWY>F!SCCHZ_ zT_DUPgR7AgHeA?lPK4}K#}k2;MULaly(50=k?sER4fT||+WDX9^>f$T5}a>b=zEr{ zICs9rZWE>_`^BsmW5_=^M?5^vMs7}WPeh{A|(*l zz)5U%tonMqq^5=FRHj?im#G>r%lW)tKg79b8F!CovTo$Ot>G>^PXiphBETX~&TXCo z=c~0YA!NW8gm^SpSW0|pVW{Vgo8xW*uCGRh6c0wee^RC!c;2Oqr!MI^1;{`Q z&bpPrln*Wu$ftYln41vV?}j|Ht{VIM6x?c0OC^R{>#D(D+%F~P{9fNfGs7{2xCy2? z;q&^WaQtSU4o|*1&fW=C4?~T=k*+FeN{kQW-W9vC+cUJz0Zr`CSm8~j+h_}k(S&&v zZgLLujFx-N84};LCOfiU*c80)FE2Yqni*&b9ye&Z68d%3&HIncxj}PO^8uHg zS#7C4B=$YV%1nPsa-}v@RHdF`ZQ+;0Tir|SXkm7;!wdR|L41fMxIjq7=EmaYWfD%< zFsDGny;H!bCbP#%u}S6PpmZzv1?}TJxJUM2D z^kL)g!hGd-nI`NS34?6k`fJ}jMSoeiy1f66+P%9`roVhUMvDV{QRy{x!ib+nI@{Fv zu?=%n#5^AmcA5>raxcp*--dub=8@fdHXZz@Kr}o8`~dC}^$470xbC=9pkB1NBuTM+ zGg;fV8?)te&Jg}!?16y5B)8N@kI^|Y2z$ck@6=Z4yQx8qglUw0bxKxze=Q-T$BUyn zUMdJ%ehTyne0TAvDvAEMb4CbW7G!FFG(z&>9s&-_^`;b0Ih&bjJm z2*c$oEK8oU1@K@mms}mG^}7hdHQNf%0^BO)Pj^vCx+Gc@^_a7>~is|BL zYPOf)H(R5fWIWKYElL-gE3&VDNr{->ECOF0U4cJFr)^}m-92!mB{(K}O9jH%;DME$ z!!afY_vVw+=hrKki)lxJ9YD=|{@jUvb(}WQk|c8qlw2$qkRw2_5nB*~{eXH%U!@l+ z3xb?B_LA5VzdgPT%+I}EWz1BuP}O*wnwiZ*gg_CJm=n<-i@Y_{GSw#TVIw(J=trK; z$?se1-#kc#Uo=1k^1jkh6(Luj_R@heg3-Mv!g%vvnI(4c@@tv>ZJq0bCRc85)PK{{ z=;s(kTEe3>r3o+U^hc&t)i@56LS+UZv)$Lgw<}v316kWhgsoC?@?bFz?kMF@IJStJ zGnc=V3$#jdUl0x+w=ny*n$w+>N7dk$!o&1Fx!N=9IPMMmi8pahpi(Jn$uNqCAyqPp zAV9~*_cTiFBh8oYMuXZ?l0KMszq@{LVlEZ6MLoW0HW_59@L4_ReU6A<+Y}M|G0A)_ z2JZY>UR5-zOsq4SXa%O?H##822zC5e8-B49+wR)fiO+j>%l;>E2e301O*#cC7YA1r z3|skX1$P}Amtg?h51ScRiMWEj!iMgjlPW!)%*dl3MgQD1Uu%J6jd=Asp(T$ zuZuke=sXI8ewV581M8oWVI9M<`a_z_(B}vroR=RZNt7UYBKei%QHC%la3v`+=?&QR zfik!3fvLmcHz^}i*mpBu^?A6l{ZOU15*aV)kI68BOxnivn61jn+s-wKADcYr(v2_x zYbWx)5rs&0Zc)~nmK;JQB2j?B98y)(W^^)%Q4(uWJ_*;)+2abomEe#fhs3Fbea^8opBPjT|80~Vc(CFSZbfR3^ti4?$Y%{UgtEt+Um^1__W2Q zVu;6-*HEauki4Qop7x3^`-l8nfGiC_4}cWQ%r9KukuD#nYU-7^KU(l=mI_%vP7_X9 zI|YWm1_dzc&y<+tNl~KKPW_ z+(1i|!DN%zH_?!k8~=gk9J< zxb-GZ1(jII2X;H+%Z>4ipRhru;y2-NI4)pcV`{#doLu%WpG|~JJORjVV0Vx@ym19F1p-)i|p&8 zpY^yYE&fFh&kX^wdi+-0>p6RP)d(SQ(muYWu@&yxsL_x3aT{rE+8-4F za|2JbxrYk-G+CA7smqD6yY>St!;%7KQy(qPN%$?)Q)WAdG{S=`@gXY$wFEsKFzO(J zxw6BeaL(L`f3mWwkh`_%CF1Kyf!|_54Re0iCSwAy+x}x8_RLNz86S`CCO>lt!top_+BDbee%ce5Z&7qj=|ii-&^H`^ksWTnKf zo4~hUCUPqoBI%(@yLtWH(We0433Ss@YTBR`zimyRLGDvLDfJA=dU6W9wy<&G+(5?7 zIO#TWEg(hTAP*tEBpFNh=xE$}PPaZ`Gm-@x*yjETZJ@4S z@$tfz3`)S5XEvU1sB9LI(1!Hz;FLn&UP&D=Zo51AJo;d=rTWHVqP zv8f4f9Z$G}bM;=ftY0v?3wmE!nbRfM86Lgj2ZEP;s@>aKBbu`Plb*fo|RxsMs{ zneIl}$G?tlL7Bo;J)tZe*XA6i`?>`{d8McCPsRvRIoZ}lQ8XDmyk+*GBZi+-U+!+M zLz`rf#rTM1EwW7(_plPAYa=1)OPe*4&)?yjn@Q(ggF5v~!fI+xJ=)jzwPU-vw!1`O zFnb4*3Zdlb)Ra=18^N@=LT>a$a_wthQPFs-=c?}@1_fQF-~|;_-W&zCaum4^mWf9Z ze}=t^%gAy>I{EurF7CKfHirkA=H<#4sK&TR-phR8ofsU^u%5U-I-7|dX?JseKaVgb zD!1)bBlw zOs#2nKfK{Qn|KUVkHBimeLCWMg0INOSr zX4<-tM1HeP)1Cc9SbZlt?NaI%D`U=Ae6{avBSD!94 zLC*p|jDA*xNq70G=)QmH!jUnOT_V)tL)9O$^Y=zuDDoUY-bXl%&7<-Ou&fwy7tcSL zbtShd(Y?85AzlNc{HTcS>dh!-it5%~Z+^sP!(TF~%qPiN7SiEbI?SlRFXLk(z zL~;ZScs=j8W0 zU#ZpMuUu>_**J_%j-5QNY}`LmpR+fWuV|)XZ+|wC-FllUMIln&a|`kK$OvvesE*#s z!Hdn`0Fx`gFeXdP0GnNLPHar~Y$a21O#oMp?-A+iddS;u?mx;?r@-KLDioiBu)}C^ zE|46?!q4kDVP_hBYzDJdX7nFND5fjQ>3e4aI^2IOKMT{&Fcy5M@&~jE3%YKC4upD3 z!a<6r7yiXn5M2PSSe^ng-dL8InaR7flZr=Vb@sXzx$9TFJ0(V-MJI}EGael<3)-M_x)jY zQ%@g$nJt)g)S^bKH>axtnxVen`q=tI1NYUN1NUCuQ|D=i@xXa|PGq}|+qEuVCRtAV zjH3<5q#dlxFh;FSp;fE)D#Nt{9=oMM^B0&y)C5_H4gkA_{=ZYG&6Wjz^A6a0qyZlE zE<%l5rW$kPwtTE{=F;-3Nxr`Q3BUJ8>A(y%=xQG~t{V6!4X*|1pGJsz`%Pp0E<>uC{wPa*!AN}|47w$O??ww;##1-q zcd?l; zYTO1I~G#y zNRyYCkKazJ4qA#?6v6w&NNaj&$C8Z2ibLPqEzQk-bn;?&ts?QwD6#)bhnv>qSA;fL zM&KP3xY&##>Eo^L;2mEHX&6^Hdd;~`JYp&O@On9CRU?Il0Tqhte2@U_KxYkm!nXTi zQR=}@aVF4TTXy_5cUm7usH;8VJ1qW2P+oli%Tjapb`uU}rWJ&DXj77`u-&uqJySLT z9^rh1Qy>-*5`eoRfVQ0wPMJEyIoR#>x?_Z8sn-_q1y9p?Zd1CjXrZdyaEARkG&pqh zc83Da#GrlrH&Jb;*xfbZ+C#!;zGr$O&wf@^6tU>MgQRQwK)BB8D^HI!JcGINn57Y9 zCHyAF_8Ur9EI;jdp?wTyg_;}fJz&ua7d%8R#Tf9?c|#()@B-~BTYd%bG06bzTR)1W zA3IB-W29z(*zBjYUke@IFFgOv(BRXnCdl0oG5h}06=$O^f_6%WD}kI}U`=B|+l(N! z-)`6Y2zuChU6;}^ApiRf)D2liEvGD^I`F3(yB*tg2_cSmOZYynDOK`wI&+CtY54vV zo*km-%*3hG?#sn6Gs(DP9&esRy+YQ@| zwwV;|axdICuCk45BH4^Md|bh%g>6 zZ0cy(EzJD%{M@T0UioIn4@QMV!!u-jKhYBm#lp^=MS};Mhi0$%8^4$2@O9ma%&r1w zU$yO9>WA&KlPyCVxS|kum(HI+NXGc?xc7u4Emnv7Q%44M9nDQ`ft^tz#=>4m;iMS% zP|F@{okG%W86FUd;?W-5(!hARPja~f z>c86r_tm@Pl--riISUrm%ik6Sz*uakJqmhGq*WWAEuQ-va!&W*&j++FuGhS*;+h$6 zNj_?@{j>Q~{UXJ>S2aDjgx%h<#ijCkI5M^o;^Q^#3pT<6?rt0qGq&D;_7TB;8PY5r ziFLK6mPJP??~RTr{s)yE#PHEA2NOKQK%31LRk2S7lQiv~Imd=T*tNs=nIeUs-St?i z11PKPltj2}(#Ss36PFUWw3Hk1KHu^n-VLUDgBmaG^>Dn%dkwv+4}y`lEi|eW+0Mdh z)w1~1UkBR6t-RDO1+Yp+0A6_GELW|IGm5Q>&2Qlw^71woIIGd+?qq~h(+9qz)pmYu zkJ4hD){!6J-D@f zS%`Y|$BIwPD!yt?eR;)*!XsGtWL9wMkuvFO-E@}#e7k>aGmD>?=n?wC{L8qQ7g`+km9PCe3>TDeXBxlXU8#A1g;wKkHZ?fk*G*po+iL2S3=XKh7@tiQo5 zhi#;$Tig|W#QJh)7<4ufr^#d8C~u2U29p&-4$bl}2uUe8q$?fi7bTCet){BW8wq&4 z39Fl)xCL0~v;R*;)=P2%l?o6K@moz#aT=Z0S6F@1aliXbyB@pmb5E-^wa$O;LsOOt z<`ibTKn0KAodT|-aVPSi3PC7>5iV{vL9z~H*kn=)tP2rc4X$Kl6_TK|OscK=1}N`H zERr7K*74R+b1%b~YjG-UlK0Vi&9+s{5zi#llbF|)@FD%F33l;FEjF;+eC$_;1D1P# zG^t3dmDb*T@9`3AKl|A~i9e@pOn`h1ikg{j5O`vW-JTW3>UT%~ZW!vIE2#)Wr7Lb- zKd;jKme>1%RfdKUd@P!g!k)`5e{x zG;K(gBC?SF?3_?ywG-wz)}YO~k$b)^k(lb>b<5<9x0FGZaa+ogTlU6D+8AQyaAiB; z5REl`R|`HV9MHxJQ8;%kaeR3&F&&sR@8&<8uJClIFP6WQqVj*?5Q}2?LqY9>sn>-0xeqzg^cHKD^eg8V)nht-;7tzsa+dBL{f0I1^-6i+`r{kcfv;P34koi*p literal 0 HcmV?d00001 diff --git a/tests/samples/ocr_test.pdf b/tests/samples/ocr_test.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3a137ad1381578ddc4ced057aa764e2cbb51be9d GIT binary patch literal 25816 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 Zmd@+|`T;(HkWKG2o`99U$VA%de*vTI=;8nX literal 0 HcmV?d00001 From d6dce956825b8fe96643ca4f56af6ce9e79d4182 Mon Sep 17 00:00:00 2001 From: imClumsyPanda Date: Fri, 1 Sep 2023 14:15:13 +0800 Subject: [PATCH 5/5] bug fix --- server/knowledge_base/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/knowledge_base/utils.py b/server/knowledge_base/utils.py index 8582c9c..a8a9bcc 100644 --- a/server/knowledge_base/utils.py +++ b/server/knowledge_base/utils.py @@ -197,7 +197,7 @@ class KnowledgeFile: print(f"{self.document_loader_name} used for {self.filepath}") try: - if self.document_loader_name in []: + if self.document_loader_name in ["RapidOCRPDFLoader", "RapidOCRLoader"]: document_loaders_module = importlib.import_module('document_loaders') else: document_loaders_module = importlib.import_module('langchain.document_loaders')