作者 | 崔皓
审核 | 重楼
摘要
本文探讨了大模型在本地部署的实践方法,重点介绍了OpenAI的多模态、Function call 和 AI assistance 功能给AI开发者带来的便利。文中着重讨论了使用开源大模型,如Llama2和ChatGLM,来应对成本和数据安全性挑战的策略。
文章以LocalGPT为例,详细阐述了其安装、使用及代码实现,为读者提供了关于如何将大模型成功集成到自己的应用系统中的宝贵经验。
开篇
近年来,大模型的发展可谓日新月异,尤其是OpenAI发布的多模态、Function call以及AI assistance功能,为广大AI开发者带来了前所未有的便捷和创新空间。这些功能提升了模型的智能性和多样性。然而,随之而来的是一些企业对数据安全性的担忧,以及调用GPT模型所需成本的问题。
面对这些挑战,部分企业开始转向开源的大模型,如Llama2和ChatGLM,在控制成本的同时,也保障了数据的私密性和安全性。然而,采用开源模型同样面临着新的问题:如何有效搭建这些模型,并将其顺利接入到自己的应用系统中呢?这一过程中的技术挑战和实际操作,将是我们接下来探讨的重点。
本文将以开源架构LocalGPT为例,为您揭开本地部署大模型的神秘面纱,带您深入了解其背后的具体技术和实施步骤。
大模型架构与工具
谈到大模型本地化部署,市面上有很多流行的工具和架构,例如:Ollama、Chatchat和LocalGPT。Ollama是一种支持运行开源大型语言模型的工具,如Llama 2,它通过一个Modelfile将模型权重、配置和数据打包成单一程序包。这种方式优化了设置和配置细节,包括GPU的使用,丰富的模型库,与langchain代码实现无缝对接。本地化运行比较流程化。
Chatchat则是一种基于本地知识库的问答应用,它利用LangChain的理念,旨在建立一套对中文场景与开源模型支持友好、可离线运行的知识库问答解决方案。它受到了多个项目的启发,通过使用FastChat接入多种模型,并依托于LangChain框架提供API调用服务。Chatchat的特点在于对中文支持的优化,这使得它特别受到中国企业本地化部署的青睐。
LocalGPT则是一个开源项目,允许用户在不妥协隐私的情况下与文档进行对话。它特别注重数据的安全性,确保所有数据都在用户的计算机上处理。LocalGPT支持多种开源模型,包括HF、GPTQ、GGML和GGUF等,并提供多种嵌入选项。它的一个显著特点是一旦下载了LLM,就可以重复使用而无需重复下载。LocalGPT的代码简单,非常适合进行学习和研究。通过对它的学习,我们可以理解如何创建基于RAG的企业知识库。
虽然工具和架构各有特色,但是LocalGPT以简单的代码与清晰的思路成为程序员学习大模型本地部署的首选,后面的内容我们将以LocalGPT的源代码为基础来介绍它的实现与使用。
LocalGPT安装与使用
LocalGPT是一个开源项目,它允许用户在保护隐私的前提下与自己的文档进行对话。这一项目在GitHub上获得了17.5k的星标,所有操作均在本地完成,确保了数据的完全安全。LocalGPT的特点包括绝对隐私保护、支持多种开源模型、多样化的嵌入选择、一次下载后多次使用的大型语言模型、记录聊天历史以及提供用于构建RAG应用程序的API。此外,LocalGPT还提供了两种图形用户界面,支持多平台(包括CUDA、CPU和MPS)。
在技术细节实现方面,LocalGPT通过选择合适的本地模型和利用LangChain的能力,可以在本地运行整个RAG流程,而且性能合理。ingest.py利用LangChain工具解析文档并在本地使用InstructorEmbeddings创建嵌入,然后将结果存储在本地的Chroma向量数据库中。run_localGPT.py使用本地的大型语言模型来理解问题并创建答案,答案的上下文是通过相似性搜索从本地向量存储中提取的。用户可以将此本地大型语言模型替换为HuggingFace中的任何其他模型,只要确保所选模型符合HF格式。
LocalGPT安装
1.环境设置
使用git克隆仓库:
安装conda进行虚拟环境管理。创建并激活一个新的虚拟环境。
2.使用pip安装依赖项
为了设置环境以运行代码,首先安装所有要求的依赖:
3.安装LLAMA-CPP
LocalGPT使用LlamaCpp-Python用于GGML(需要llama-cpp-python <=0.1.76)和GGUF(llama-cpp-python >=0.1.83)模型。
如果想在llama-cpp中使用BLAS或Metal,您可以设置适当的标志:
支持NVIDIA GPU,使用cuBLAS
# 示例:cuBLAS
支持Apple Metal (M1/M2),使用
导入知识库文档
将要上传的文件放在SOURCE_DOCUMENTS文件夹中。可以在SOURCE_DOCUMENTS文件夹中放置多个文件夹,代码会递归地读取文件。
支持的文件格式方面,LocalGPT目前支持以下文件格式。LocalGPT使用LangChain加载这些文件格式。constants.py中的代码使用DOCUMENT_MAP字典将文件格式映射到相应的加载器。为了添加对另一种文件格式的支持,只需在此字典中添加文件格式和LangChain中的相应加载器即可。
提取文件信息
如果系统上设置cuda,运行以下命令以摄取所有数据。
输出如下:
使用device_type参数来指定特定设备。在CPU上运行:
在M1/M2上运行:
使用help获取支持设备的完整列表。
这将创建一个名为DB的新文件夹,并将其用于新创建的向量存储。您可以摄取任意多的文档,所有文档都将累积在本地嵌入数据库中。如果您想从一个空数据库开始,删除DB并重新摄取文档。
注意:当您第一次运行时,需要互联网连接下载嵌入模型(默认为Instructor Embedding)。在随后的运行中,不会有数据离开您的本地环境,您可以在没有互联网连接的情况下摄取数据。
对知识库提问
请运行以下命令进行聊天(默认情况下,它将在cuda上运行)。
可以像ingest.py一样指定设备类型。
这将加载摄取的向量存储和嵌入模型。您将看到一个提示:
> Enter a query:
输入您的问题后,按回车键。LocalGPT将根据系统的硬件花费一些时间。您将得到如下响应。
一旦生成了答案,您可以再提出另一个问题,而无需重新运行脚本,只需再次等待提示。
注意:当您第一次运行时,需要互联网连接下载LLM(默认为TheBloke/Llama-2-7b-Chat-GGUF)。在此之后,您可以关闭互联网连接,脚本推理仍然会工作。没有数据离开您的本地环境。
输入exit以结束脚本。
GPU和VRAM要求
下表是根据不同模型的大小(亿级参数)的VRAM要求。表中的估计值不包括嵌入模型使用的VRAM——这将额外使用2GB至7GB的VRAM,具体取决于模型。
我使用了4060笔记本版本的显卡(8G显存)进行的测试,大模型回应的速度在1分钟左右,如果使用更好的显卡这个速度应该还会提升。
LocalGPT 程序结构
在了解了LocalGPT的安装和使用之后,我们来对核心代码进行解析。如下图所示,在项目代码中我们将重要代码部分使用红色框出,并给大家介绍:
- SOURCE_DOCUMENTS 目录下面是用来存放需要上传的PDF文件,如果要将知识库所需的文件上传,必须先将文件放到这个目录,然后再执行ingest.py 程序进行嵌入操作。
- Constants.py文件用来对程序常量进行定义,包括文件目录,支持的文件类型,大模型的ID和名字等。
- Ingest.py 是用来嵌入文件到向量库的代码文件,也是后面我们要介绍的重点。
- Requirements.txt 文件用来保存安装所需要的依赖包。
- Run_localGPT.py 文件是用来执行问答回应的代码文件,在文件嵌入之后就通过它来向知识库提问。后面会着重介绍。
- 另外localGPT_UI.py 和run_localGPT_API.py,前者是提供UI界面的问答程序,后者是提供问答服务的API。
Ingest.py 代码解析
它的主要功能是从指定的源目录加载文档,将它们切割为适合处理的大小,并为这些文档片段创建嵌入,最后将它们存储在一个本地数据库中。这是一个文档处理和嵌入生成的完整流程,主要用于构建和管理大型文档的知识库。
1. 主要函数及其功能
load_single_document(file_path: str) -> Document:加载单个文档。根据文件路径和扩展名,使用相应的加载类来加载文档。
load_document_batch(filepaths):加载一批文档。它创建一个线程池来并行加载多个文档。
load_documents(source_dir: str) -> list[Document]:从源目录加载所有文档。它列出目录中的所有文件,并根据文件扩展名决定是否加载。
split_documents(documents: list[Document]) -> tuple[list[Document], list[Document]]:将文档分成两组,一组是普通文本文档,另一组是Python代码文档。
main(device_type):主函数,负责初始化日志记录,加载文档,分割文档,创建嵌入,并将结果持久化。
2. 函数调用关系
main() 是程序的入口点。它首先调用 load_documents() 来加载源目录中的所有文档。
在 load_documents() 内部,它通过调用 load_document_batch() 来加载文档批次。每个文档批次由 load_single_document() 加载。
加载并返回所有文档后,main() 调用 split_documents() 将文档分为文本和Python文档。
main() 还创建了文档的嵌入,并将这些嵌入以及原始文档数据存储在一个本地的Chroma数据库中。
我们将几个重要的函数展开给大家介绍如下:
load_single_document 函数的目的是加载一个文件路径指定的文档。下面是对关键代码的解释:
1. file_extension = os.path.splitext(file_path)[1]:
将文件路径分割为文件名和扩展名。os.path.splitext 是Python的一个标准库函数,用于分割文件名和其扩展名。这里 [1] 表示提取分割后的第二部分,即文件的扩展名。
2. loader_class = DOCUMENT_MAP.get(file_extension):
-在 DOCUMENT_MAP 字典中查找与文件扩展名对应的加载类。DOCUMENT_MAP 应该是一个预定义的字典,其键为文件扩展名,值为相应的加载类。
3. if loader_class:
这是一个条件判断。它检查是否找到了与文件扩展名对应的加载类。如果找到了(即 loader_class 不为空),则执行下一步;否则,执行 else 部分的代码。
如果找到了匹配的加载类,这行代码将创建该类的实例。它将文件路径作为参数传递给该类的构造函数,从而初始化一个新的加载器对象。if 条件不满足时执行的,即如果没有找到匹配的加载类,会引发一个 ValueError 异常,表明无法确定文件的文档类型。
说了文件加载的函数再来看看Ingest.py的主函数, main 函数实现文件加载到嵌入的流程。它从源目录加载文档,将它们分割成更小的片段,并为这些片段创建嵌入,最后将这些信息存储到本地数据库中。下面是对关键代码的解释:
1. documents = load_documents(SOURCE_DIRECTORY):
调用 load_documents 函数,从 SOURCE_DIRECTORY 指定的目录中加载所有文档。这个函数返回一个文档对象的列表。
2. text_documents, python_documents = split_documents(documents):
调用 split_documents 函数,将加载的文档分成两类:一类是普通文本文档,另一类是Python代码文档。这有助于后续根据文档类型采取不同的处理策略。
3. 创建 RecursiveCharacterTextSplitter 实例:
text_splitter 和 python_splitter 是用于分割文档的实例。text_splitter 用于普通文本,而 python_splitter 专门用于Python代码。它们通过设置 chunk_size 和 chunk_overlap 来定义文档切割的大小和重叠部分。
4. texts = text_splitter.split_documents(text_documents):
使用 text_splitter 对普通文本文档进行分割,结果存储在 texts 列表中。
5. texts.extend(python_splitter.split_documents(python_documents)):
使用 python_splitter 对Python代码文档进行分割,并将这些分割后的文档追加到 texts 列表中。
6. embeddings = HuggingFaceInstructEmbeddings(...):
创建 HuggingFaceInstructEmbeddings 的实例,用于生成文档片段的嵌入。这里使用了模型名称和设备类型作为参数。
7. db = Chroma.from_documents(...):
使用 Chroma 类的 from_documents 方法创建一个数据库实例。它接受分割后的文档片段、嵌入、持久化目录和客户端设置作为参数。
8. db.persist():
调用 persist 方法将数据库中的数据持久化到本地存储。
run_localGPT.py代码解析
这个代码实现文件现了一个基于大型语言模型的问答系统,涵盖了从模型加载到问答处理的整个流程。首先,它通过 load_model 函数根据设备类型和模型标识符加载和配置适合文本生成的模型。接着,在 main 函数中,系统初始化嵌入模型和向量存储,设置了一个基于模板的问答链,用于处理用户查询并生成回答。此外,系统提供了一个交互式界面,允许用户输入问题并接收相应的答案,同时还能展示回答的相关文档,这一功能的启用取决于用户的选择。整个系统通过精心的配置和日志记录,为用户提供了一个高效、可定制的问答体验。接下来,我们围绕两个重要函数load_model和main 函数展开说明。
Load_model函数
load_model 的函数,用于加载和配置用于文本生成的大型语言模型(LLM)。这里是对函数中关键部分的解释:
1. 函数参数:
device_type:指定运行模型的设备类型(如 "cuda" 或 "cpu")。
model_id:指定要从HuggingFace模型库中加载的模型的标识符。
model_basename (可选):如果使用量化模型,则指定模型的基本名称。
2. 模型加载逻辑:
如果 model_basename 不为 None,则根据模型文件名的不同,选择不同的加载方式:
- 对于文件名包含 ".ggml" 的量化模型,使用 LlamaCpp 加载。
- 对于文件名包含 ".safetensors" 的量化模型,使用AutoGPTQForCausalLM 加载。
如果 device_type 为 "cuda",则使用 AutoModelForCausalLM 加载完整的模型。
如果不符合上述条件,使用 LlamaTokenizer 和 LlamaForCausalLM 加载模型。
3. 模型配置:
使用 GenerationConfig 来加载模型的配置,以避免警告。
创建 pipeline 对象用于文本生成,配置包括模型、分词器、最大长度、温度、top_p 参数等。
4. 返回值:
函数返回一个 HuggingFacePipeline 对象,该对象封装了配置好的文本生成管道。
Load_model函数顾名思义是用来装载大模型的,在run_localGPT.py的main函数中会调用到Load_mode,接下来我们来看main函数的主要内容。
main 函数实现了一个基于大型语言模型(LLM)的信息检索和问答任务。函数首先根据设备类型加载一个嵌入模型,这可能是 HuggingFaceInstructEmbeddings 或 HuggingFaceEmbeddings。接着,它加载了之前由 inget.py 脚本创建的向量存储,这是用于文档检索的关键部分。使用 load_model 函数,函数加载了本地的LLM,支持多种不同的LLM。然后,它设置了一个问答检索链,使用预定义的提示模板和对话缓存来处理用户的查询。用户可以通过命令行输入查询,函数将使用LLM和文档检索系统来生成答案,并选择性地显示回答的源文档。这个过程既是交互式的,也是动态的,允许用户以自然语言的形式探索信息和知识库。整个流程展示了如何将嵌入模型、大型语言模型和文档检索相结合,实现一个功能丰富的问答系统。
main 函数构建了一个基于大型语言模型(LLM)的信息检索和问答系统。以下是对代码的关键部分的解释:
1. 初始化和设置:
Embeddings:加载用于文档嵌入的模型,这里使用的是 HuggingFaceInstructEmbeddings。
db: 加载由 inget.py 脚本创建的向量存储 Chroma,它用于后续的文档检索任务。
2. 问答链设置:
template: 定义一个提示模板,用于格式化问答的上下文、历史记录和问题。
prompt: 创建一个 PromptTemplate 实例,它将用于生成向语言模型提交的最终提示。
memory: 实例化 ConversationBufferMemory,用于在问答过程中保持对话历史的缓存。
3. 加载语言模型:
llm: 使用 load_model 函数加载本地的大型语言模型,这个函数支持不同类型的模型和设备。
4. 问答逻辑实现:
qa: 实例化一个 RetrievalQA 对象,它结合了LLM和文档检索器来实现问答功能。
在一个循环中,系统接收用户输入的查询,使用 qa 对象处理这些查询,并输出相应的答案。
5. 交互和显示:
用户可以通过命令行输入问题,系统根据用户输入的查询提供答案。
如果设置了 show_sources 标志,系统还会显示生成答案时参考的源文档。
constants.py 代码解析
说完了Ingest.py和run_localGPT.py,大家对localGPT的主要功能有所了解了,实际上Ingest.py用来执行文档的嵌入和存储,run_localGPT.py用来执行具体的问答工作,利用事先存储的向量库进行问题的搜索,然后通过大模型进行回应。不过这两块代码中始终没有出现具体的大模型,这个定义保存在constants.py 文件中,该文件中除了定义了调用的模型之外,还定了加载文件的类型以及嵌入的方式。代码如下:
一般而言,模型的信息包括MODEL_ID的MODEL_BASENAME,这块的信息可以通过Hugging Face 查找获得。Hugging Face是一家专注于自然语言处理(NLP)的AI研究公司,提供大量预训练模型和工具,广泛用于文本理解和生成任务。最重要的是它提供了一个大模型的平台,让广大网友可以上传自己调优的模型。
如下图所示,可以登陆huggingface.co网站,在首页的搜索框中输入想要的模型,它会自动模糊匹配相关的模型。
在选择模型之后,会进入如下页面,通过模型详情页面的“Files and versions”tab页,可以看到模型的ID,图中的ID为“TheBloke/Llama-2-7B-Chat-GGML”,同时可以看到需要下载的模型文件。这里选择bin 文件,如果模型存在多个下载文件,选择第一个,本例中我们选择“llama-2-7b-chat.ggmlv3.q4_0.bin”,这样load_model函数会自动从huggingface 上下载定义好的模型文件。
总结
本文展示了大模型本地部署的全过程,包括模型选择、配置、以及与应用的集成。通过对LocalGPT的细致解读,文章不仅展示了技术的实用性,也为有意采用大模型的企业和开发者提供了一条清晰的实施路径。这些深入的分析和实例指导,对于那些寻求在数据安全性和成本效益之间找到平衡点的企业来说,尤为宝贵。
作者介绍
崔皓,51CTO社区编辑,资深架构师,拥有18年的软件开发和架构经验,10年分布式架构经验。