赞
踩
第三方商业大型语言模型(LLM)提供商,如OpenAI的GPT4,通过简单的API调用使LLM的使用更加容易。然而,由于数据隐私和合规等各种原因,我们可能仍需要在企业内部部署或私有模型推理。
开源LLM的普及让我们私有化部署大语言模型称为可能,从而减少了对这些第三方提供商的依赖。
当我们将开源模型托管在本地或云端时,专用的计算能力成为一个关键考虑因素。虽然GPU实例可能是最佳选择,但成本也很容易一飞冲天, 再加上现在一卡难求, 想跑模型也变成了一个不简单的事情。
在这个指南中,我们将探讨如何使用CPU在本地Python中运行开源并经过轻量化的LLM模型,用于检索增强生成(Retrieval-augmented generation, 也称为Document Q&A)。而且我们将在这个项目中利用最新、高性能的Llama 2聊天模型。
虽然LLM展现了出色的能力,但其运行所要求的计算和内存资源较高。为了应对这一问题,我们可以使用Quantization来压缩这些模型,以减少内存占用并加速推理计算过程,同时保持模型的性能和效果。
Quantization是一种将用于表示数字或值的位数减少的技术。在LLM的上下文中,它涉及通过将权重存储在较低精度的数据类型中来减少模型参数的精度。
由于它减小了模型大小,量化有助于在资源受限的设备上部署模型,例如仅有CPU但没有GPU的设备或嵌入式系统。
一种常见的方法是将模型权重从原始的16位浮点数值量化为较低精度的8位整数值。
从16位浮点到8位整数的权重Quantization
下图展示了我们将在本项目中构建的Document Q&A应用程序的架构。
Document Q&A 架构
我们将在此项目中运行Document Q&A的文件是曼联足球俱乐部的2022年公开年度报告,共177页。
数据来源:Manchester United Plc (2022). 2022 Annual Report 2022 on Form 20-F. https://ir.manutd.com/~/media/Files/M/Manutd-IR/documents/manu-20f-2022-09-24.pdf (CC0: Public Domain, as SEC content is public domain and free to use)
此示例的运行环境配备了AMD Ryzen 5 5600X 6核CPU,16GB 内存(DDR4 3600)。虽然它还装了一块RTX 3060TI 8G显卡,但在本示例中不会使用它,因为我们将讨论如何仅使用CPU来运行模型。
一下是构建这个后端应用时将使用的工具:
LangChain是一个目前流行的用于开发LLM驱动的应用程序框架,它提供了各种集成接口和数据连接接口,允许我们链式编排不同的模块,以创建高级用例,如聊天机器人、数据分析和Document Q&A。
C Transformers是提供一个Transformer模型的Python库,它使用GGML库C/C++绑定。说到这点,让我们首先了解一下GGML是什么。
ggml.ai团队开发的GGML库是一个为机器学习设计的Tensor Library,它可以在消费级硬件上高性能运行大模型。这是通过整数量化支持和内置优化算法实现的。
因此,LLM的GGML版本(以二进制格式量化过的模型)可以在CPU上高效地运行。鉴于我们在本项目中使用Python,我们将使用C Transformers库,其为GGML模型提供了Python绑定。
C Transformers支持一些常见的开源模型,包括目前流行的一些模型,如Llama、GPT4All-J、MPT和Falcon。
C Transformers支持的大语言模型
Sentence-transformers是一个Python库,提供了生成语句、文本和图像的嵌入(稠密向量表示 Dense Vector Representations)的简便方法。
它使用户能够生成100多种语言的关联语句,然后可以进行比较,找到具有相似含义的句子。
在这个项目中,我们将使用开源的all-MiniLM-L6-v2模型,因为它具有最佳的速度和出色的通用生成质量。
Facebook AI Similarity Search (FAISS) 是一个专为高效的稠密向量(Dense Vector)相似性搜索和聚和类而设计的库。
给定一组输入,我们可以使用FAISS对它们进行索引,并利用其强大的语义搜索算法在索引内搜索最相似的向量。
虽然它在传统意义上不是一个完整的向量存储(像数据库管理系统),但它以优化的方式处理向量的存储,以实现高效的相邻搜索。
在这个项目中,我们将使用Poetry来设置virtualenv并处理Python包管理,因为它易于使用且一致性较好。
之前使用过venv的话,我强烈推荐切换到Poetry,因为它可以使依赖管理更加高效和无缝。
开源LLM目前取得了巨大的进展,并且可以在Hugging Face的Open LLM排行榜上找到许多LLM。
基于以下考虑,我选择了最新的开源Llama-2–7B-Chat模型(GGML 8-bit)用于这个项目:
既然我们了解了各种组件,让我们逐步介绍如何构建Document Q&A应用程序。文中相关代码可以在此GitHub Repo中找到,所有依赖项都可以在requirements.txt文件中找到。
注意:由于已经有许多教程可供参考,我们不会展开讨论Document Q&A组件(例如文本分块、向量存储设置)的复杂性和细节。本文将专注于开源LLM和CPU推理方面。
在这一步骤中,将执行三个任务:
- # File: db_build.py
-
- from langchain.vectorstores import FAISS
- from langchain.text_splitter import RecursiveCharacterTextSplitter
- from langchain.document_loaders import PyPDFLoader, DirectoryLoader
- from langchain.embeddings import HuggingFaceEmbeddings
-
- # Load PDF file from data path
- loader = DirectoryLoader('data/',
- glob="*.pdf",
- loader_cls=PyPDFLoader)
- documents = loader.load()
-
- # Split text from PDF into chunks
- text_splitter = RecursiveCharacterTextSplitter(chunk_size=500,
- chunk_overlap=50)
- texts = text_splitter.split_documents(documents)
-
- # Load embeddings model
- embeddings = HuggingFaceEmbeddings(model_name='sentence-transformers/all-MiniLM-L6-v2',
- model_kwargs={'device': 'cpu'})
-
- # Build and persist FAISS vector store
- vectorstore = FAISS.from_documents(texts, embeddings)
- vectorstore.save_local('vectorstore/db_faiss')
运行上述Python脚本后,向量存储将在本地目录中生成并保存为’vectorstore/db_faiss’,已准备好进行语义搜索和检索。
考虑到我们使用的是Llama-2–7B-Chat模型,我们必须注意在这里使用的Prompt Template。
例如,OpenAI的GPT模型是为对话输入和消息输出而设计的。这意味着Prompt Template应该是类似于对话记录的格式(例如,分系统消息和用户消息)。
然而,在这里这样的模板就不适用用,因为我们的Llama 2模型没有专门针对这种类型的对话接口进行优化。相反,更适合使用经典的Prompt Template,如下所示:
- # File: prompts.py
-
- qa_template = """Use the following pieces of information to answer the user's question.
- If you don't know the answer, just say that you don't know, don't try to make up an answer.
- Context: {context}
- Question: {question}
- Only return the helpful answer below and nothing else.
- Helpful answer:
- """
注意:较小的LLM,如7B模型,对格式要求较为敏感。例如,当我修改Prompt Template的空格和缩进时,输出会稍有不同。
由于我们将在本地运行LLM,我们需要下载量化的Llama-2–7B-Chat模型的二进制文件。
我们可以访问TheBloke的Llama-2–7B-Chat GGML页面,该页面托管在Hugging Face上,然后下载名为llama-2–7b-chat.ggmlv3.q8_0.bin的GGML 8-bit量化文件。
HuggingFace上的Llama-2–7B-Chat-GGML page的文件和版本信息
下载的8-bit量化模型的.bin文件可以保存在项目的某个子文件夹下,例如/models。
模型卡页面还显示了每种量化格式的更多信息和详细说明:
Different quantized formats with details
注意:要下载C Transformers支持的其他GGML量化模型,请访问HuggingFace上TheBloke的主页,搜索您想要的模型,并查找名称以“-GGML”结尾的链接。
为了使用我们下载的GGML模型,我们将用C Transformers和LangChain之间的集成。具体来说,我们将使用LangChain中的C Transformers LLM Wrapper,该Wrapper为GGML模型提供了统一的接口。
- # File: llm.py
- from langchain.llms import CTransformers
-
- # Local CTransformers wrapper for Llama-2-7B-Chat
- llm = CTransformers(model='models/llama-2-7b-chat.ggmlv3.q8_0.bin', # Location of downloaded GGML model
- model_type='llama', # Model type Llama
- config={'max_new_tokens': 256,
- 'temperature': 0.01})
我们可以为LLM定义许多配置选项,例如最大Token数、top k、Temperature和Repetition penalty。
注意:我将Temperature设置为0.01而不是0,因为当Temperature恰好为零时,我得到了奇怪的返回(例如,一长串重复的字母E)。
有了Prompt Template和准备好的C Transformers LLM,我们编写三个函数来构建LangChain的RetrievalQA对象,以便我们能够执行Document Q&A任务。
- # File: utils.py
- from langchain import PromptTemplate
- from langchain.chains import RetrievalQA
- from langchain.embeddings import HuggingFaceEmbeddings
- from langchain.vectorstores import FAISS
-
- # Wrap prompt template in a PromptTemplate object
- def set_qa_prompt():
- prompt = PromptTemplate(template=qa_template,
- input_variables=['context', 'question'])
- return prompt
-
-
- # Build RetrievalQA object
- def build_retrieval_qa(llm, prompt, vectordb):
- dbqa = RetrievalQA.from_chain_type(llm=llm,
- chain_type='stuff',
- retriever=vectordb.as_retriever(search_kwargs={'k':2}),
- return_source_documents=True,
- chain_type_kwargs={'prompt': prompt})
- return dbqa
-
-
- # Instantiate QA object
- def setup_dbqa():
- embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2",
- model_kwargs={'device': 'cpu'})
- vectordb = FAISS.load_local('vectorstore/db_faiss', embeddings)
- qa_prompt = set_qa_prompt()
- dbqa = build_retrieval_qa(llm, qa_prompt, vectordb)
-
- return dbqa
接下来的步骤是将前面的各个组件合并到main.py脚本中。我们使用argparse模块,因为我们将从命令行传入用户查询到应用程序中。
考虑到我们将返回源文档,附加的代码将用于处理文档块,以获得更好的可视化显示。
为了评估CPU推理的速度,还使用了timeit模块。
- # File: main.py
- import argparse
- import timeit
-
- if __name__ == "__main__":
- parser = argparse.ArgumentParser()
- parser.add_argument('input', type=str)
- args = parser.parse_args()
- start = timeit.default_timer() # Start timer
-
- # Setup QA object
- dbqa = setup_dbqa()
-
- # Parse input from argparse into QA object
- response = dbqa({'query': args.input})
- end = timeit.default_timer() # End timer
-
- # Print document QA response
- print(f'\nAnswer: {response["result"]}')
- print('='*50) # Formatting separator
-
- # Process source documents for better display
- source_docs = response['source_documents']
- for i, doc in enumerate(source_docs):
- print(f'\nSource Document {i+1}\n')
- print(f'Source Text: {doc.page_content}')
- print(f'Document Name: {doc.metadata["source"]}')
- print(f'Page Number: {doc.metadata["page"]}\n')
- print('='* 50) # Formatting separator
-
- # Display time taken for CPU inference
- print(f"Time to retrieve response: {end - start}")
现在是时候对我们的应用程序进行测试了。在从项目目录加载virtualenv后,我们可以在命令行界面(CLI)中运行一个包含用户查询的命令。
例如,我们可以用以下命令询问阿迪达斯(曼彻斯特联全球技术赞助商)支付的最低保证金金额:
poetry run python main.py "How much is the minimum guarantee payable by adidas?"
注意:如果我们没有使用Poetry,可以省略前缀的
poetry run
。
Output from user query passed into document Q&A application | Image by author
输出结果显示,我们成功地得到了对用户查询的正确响应(即7.5亿英镑),同时还显示了与查询语义相似的相关文档片段。
总共31秒的应用程序启动和生成响应时间相当不错,考虑到我们是在本地运行在AMD Ryzen 5600X上(虽然它是一款优秀的CPU,但目前并不是市场上最好的)。
考虑到在GPU上运行LLM推理(例如直接在HuggingFace上)也可能需要几十秒,这个结果更加令人印象深刻。
现在我们已经构建了一个在CPU推理上运行LLM的Document Q&A应用程序,我们可以采取许多激动人心的步骤来推进这个项目。
我将在未来的几周内致力于撰写关于上述想法的文章和项目,所以请继续关注更多富有洞察力的生成式AI内容!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。