跳到主要内容
Mac 本地部署大模型及 LangChain 应用实践 | 极客日志
Python AI 算法
Mac 本地部署大模型及 LangChain 应用实践 综述由AI生成 详细记录了在 Mac 本地部署 ChatGLM-6B 和 ChatGLM2-6B 大模型的过程,包括环境配置、模型下载、量化部署及 Web 演示启动。文章进一步介绍了如何使用 LangChain 框架接入本地模型,实现 LLMChain、RetrievalQA 检索增强生成及 SQLDatabaseChain 等功能,并分析了小参数量模型在处理复杂提示时的局限性。最后展示了 langchain-ChatGLM 的本地知识库问答实践,为开发者提供了在无专用 GPU 服务器情况下体验 AIGC 能力的完整参考方案。
灭霸 发布于 2025/2/6 更新于 2026/4/26 6 浏览介绍
随着 ChatGPT 的横空出世,国内互联网大厂、创业公司纷纷加入 AIGC 赛道,不断推出各种大模型。这些模型由于规模庞大、结构复杂,往往包含数十亿至数千亿的参数。在训练阶段,一般需要使用高效能的 GPU 集群训练数十天;在推理阶段,也需要高效能的 GPU 集群才能支撑一定量级的并发请求且实时返回。目前也有不少公司推出了规模相对较小但效果仍有一定优势的大模型,可以在消费级的单卡 GPU 上进行推理、甚至训练。本文尝试在普通的 Macbook Pro 上部署大模型开源方案,实现自然语言问答和对话等功能。
配置
所使用的 Macbook Pro 配置如下:
机型:Macbook Pro(14 英寸,2021 年)
芯片:Apple M1 Pro
内存:16G
系统:macOS Monterey,12.6.2
前置条件
首先默认本地已安装 macOS 的软件包管理工具 Homebrew。
Git
安装 Git:
brew install git
由于使用 git 命令下载的模型文件较大,因此还需要安装 Git Large File Storage:
brew install git-lfs
Conda
Conda 是一个依赖和环境管理工具,支持的语言包括 Python、R、Ruby 等,目前在 Python 语言生态中得到广泛应用。通过它可以创建、管理多个相互独立、隔离的 Python 环境。MiniConda 是 Conda 的免费、最小可用版本。下载并安装 MiniConda:
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-arm64.sh -b -p $HOME /miniconda
source ~/miniconda/bin/activate
ChatGLM-6B
介绍
ChatGLM-6B 是一个开源的、支持中英双语问答的对话语言模型,基于 General Language Model(GLM)架构,具有 62 亿参数。结合模型量化技术,用户可以在消费级的显卡上进行本地部署(INT4 量化级别下最低只需 6GB 显存)。ChatGLM-6B 使用了和 ChatGLM 相同的技术,针对中文问答和对话进行了优化。经过约 1T 标识符的中英双语训练,辅以监督微调、反馈自助、人类反馈强化学习等技术的加持,62 亿参数的 ChatGLM-6B 已经能生成相当符合人类偏好的回答。而 ChatGLM-6B-INT4 是 ChatGLM-6B 量化后的模型权重。具体的,ChatGLM-6B-INT4 对 ChatGLM-6B 中的 28 个 GLM Block 进行了 INT4 量化,没有对 Embedding 和 LM Head 进行量化。量化后的模型理论上 6G 显存(使用 CPU 即内存)即可推理,具有在嵌入式设备上运行的可能。
部署
创建并激活环境:
conda create --name chatglm python=3.9
conda activate chatglm
下载 ChatGLM-6B 源码:
cd ~/workspace/
git clone https://github.com/THUDM/ChatGLM-6B
安装依赖:
cd ~/workspace/ChatGLM-6B
pip install -r requirements.txt
下载 ChatGLM-6B INT4 量化的模型权重 ChatGLM-6B-INT4:
cd ~/workspace/models/
git lfs install
git clone https://huggingface.co/THUDM/chatglm-6b-int4
Macbook 直接加载量化后的模型可能出现提示——'clang: error: unsupported option '-fopenmp'',还需单独安装 OpenMP 依赖,此时会安装下面几个文件:/usr/local/lib/libomp.dylib, /usr/local/include/ompt.h, /usr/local/include/omp.h, /usr/local/include/omp-tools.h:
执行以下 Python 代码,从本地地址加载模型并进行推理,对'你好'和'如何读一本书'进行回答:
from transformers import AutoTokenizer, AutoModel
tokenizer = AutoTokenizer.from_pretrained("/Users/xxx/workspace/models/chatglm-6b-int4" , trust_remote_code=True )
model = AutoModel.from_pretrained("/Users/xxx/workspace/models/chatglm-6b-int4" , trust_remote_code=True ).float ()
model = model.eval ()
response, history = model.chat(tokenizer, "你好" , history=[])
print (response)
response, history = model.chat(tokenizer, "如何读一本书" , history=history)
print (response)
修改 ChatGLM-6B 源码目录下的 web_demo.py 文件的 7、8 两行,使用本地已下载的 INT4 量化的模型权重 ChatGLM-6B-INT4,并且不使用半精度(Mac 不支持)和 CUDA(无 GPU):
tokenizer = AutoTokenizer.from_pretrained("/Users/xxx/workspace/models/chatglm-6b-int4" , trust_remote_code=True )
model = AutoModel.from_pretrained("/Users/xxx/workspace/models/chatglm-6b-int4" , trust_remote_code=True ).float ()
ChatGLM2-6B
介绍 ChatGLM2-6B 是 ChatGLM-6B 的升级版本,在上下文长度和指令遵循能力上有所提升。
部署 以下步骤和 ChatGLM-6B 基本相同。首先创建并激活环境:
conda create --name chatglm2 python=3.9
conda activate chatglm2
cd ~/workspace/
git clone https://github.com/THUDM/ChatGLM2-6B
cd ~/workspace/ChatGLM2-6B
pip install -r requirements.txt
下载 ChatGLM2-6B INT4 量化的模型权重 ChatGLM2-6B-INT4:
cd ~/workspace/models/
git lfs install
git clone https://huggingface.co/THUDM/chatglm2-6b-int4
执行以下 Python 代码,从本地地址加载模型并进行推理,对'你好'和'如何读一本书'进行回答,代码与 ChatGLM 部分基本相同,仅更改模型地址:
from transformers import AutoTokenizer, AutoModel
tokenizer = AutoTokenizer.from_pretrained("/Users/xxx/workspace/models/chatglm2-6b-int4" , trust_remote_code=True )
model = AutoModel.from_pretrained("/Users/xxx/workspace/models/chatglm2-6b-int4" , trust_remote_code=True ).float ()
model = model.eval ()
response, history = model.chat(tokenizer, "你好" , history=[])
print (response)
response, history = model.chat(tokenizer, "如何读一本书" , history=history)
print (response)
对于 ChatGLM2-6B 源码目录下的 web_demo.py 的修改和启动和 ChatGLM-6B 部分类似,修改其中的 6、7 两行:
tokenizer = AutoTokenizer.from_pretrained("/Users/xxx/workspace/models/chatglm2-6b-int4" , trust_remote_code=True )
model = AutoModel.from_pretrained("/Users/xxx/workspace/models/chatglm2-6b-int4" , trust_remote_code=True ).float ()
LangChain
介绍 LangChain 是一个面向大语言模型的应用开发框架。如果将大语言模型比作人的大脑,那么可以将 LangChain 比作人的五官和四肢,它可以将外部数据源、工具和大语言模型连接在一起,既可以补充大语言模型的输入,也可以承接大语言模型的输出。LangChain 包含以下核心组件:
Model:表示大语言模型
Prompt:表示提示
Tool:表示工具
Chain:表示将 Model、Tool 等组件串联在一起,甚至可以递归地将其他 Chain 串联在一起
Agent:相对于 Chain 已固定执行链路,Agent 能够实现动态的执行链路
部署
安装依赖 在 chatglm2 环境下继续安装 LangChain 依赖:
注意,以上命令只是安装 LangChain 依赖的最小集,因为 LangChain 集成了多种模型、存储等工具,而这些工具的依赖并不会被安装,所以后续进一步使用这些工具时可能会报缺少特定依赖的错误,可以使用 pip 进行安装,或者直接使用 pip install 'langchain[all]' 安装 LangChain 的所有依赖,但比较耗时。
Model 继承 LangChain 的 LLM,接入 ChatGLM2,实现对话和问答,代码文件 chatglm_llm.py 如下所示:
from langchain.llms.base import LLM
from langchain.llms.utils import enforce_stop_tokens
from transformers import AutoTokenizer, AutoModel
from typing import List , Optional
class ChatGLM2 (LLM ):
max_token: int = 4096
temperature: float = 0.8
top_p = 0.9
tokenizer: object = None
model: object = None
history = []
def __init__ (self ):
super ().__init__()
@property
def _llm_type (self ) -> str :
return "ChatGLM2"
def load_model (self, model_path = None ):
self .tokenizer = AutoTokenizer.from_pretrained(model_path,trust_remote_code=True )
self .model = AutoModel.from_pretrained(model_path, trust_remote_code=True ).float ()
def _call (self,prompt:str , stop: Optional [List [str ]] = None ) -> str :
response, _ = self .model.chat(
self .tokenizer,
prompt,
history=self .history,
max_length=self .max_token,
temperature=self .temperature,
top_p=self .top_p)
if stop is not None :
response = enforce_stop_tokens(response, stop)
self .history = self .history + [[None , response]]
return response
if __name__ == "__main__" :
llm=ChatGLM2()
llm.load_model("/Users/xxx/workspace/models/chatglm2-6b-int4" )
print (llm._call("如何读一本书" ))
chatglm2_llm.py 的执行结果如图 6 所示。
Chain
LLMChain LLMChain 是最基础的 Chain,其引入一个提示模板将问题转化为提示输入模型,并输出模型的回答。
输入问题
拼接提示,根据提示模板将问题转化为提示
模型推理,输出答案
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
from chatglm2_llm import ChatGLM2
if __name__ == "__main__" :
llm = ChatGLM2()
llm.load_model("/Users/xxx/workspace/models/chatglm2-6b-int4" )
prompt = PromptTemplate(input_variables=["question" ], template="""
简洁和专业的来回答用户的问题。
如果无法从中得到答案,请说 '根据已知信息无法回答该问题' 或 '没有提供足够的相关信息',不允许在答案中添加编造成分,答案请使用中文。
问题是:{question}""" ,)
chain = LLMChain(llm=llm, prompt=prompt, verbose=True )
print (chain.run("如何读一本书" ))
其中模型采用自定义模型,接入本地部署的 ChatGLM2。
RetrievalQA 除了基础的链接提示和模型的 LLMChain 外,LangChain 还提供了其他多种 Chain,例如实现本地知识库功能的 RetrievalQA。
输入问题
向量化问题,和文本段向量化一致,将问题转化为向量
搜索相关文本段,从向量索引中搜索和问题相关的文本段
拼接提示,根据提示模板将问题和相关文本段转化为提示
模型推理,输出答案
代码文件 retrieval_qa_demo.py 如下所示:
from langchain.chains import RetrievalQA
from langchain.document_loaders import UnstructuredMarkdownLoader
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
from langchain.text_splitter import MarkdownTextSplitter
from langchain.vectorstores import Chroma
from chatglm2_llm import ChatGLM2
if __name__ == "__main__" :
loader = UnstructuredMarkdownLoader("/Users/xxx/workspace/docs/creative.md" )
documents = loader.load()
text_splitter = MarkdownTextSplitter(chunk_size=1000 , chunk_overlap=0 )
texts = text_splitter.split_documents(documents)
embeddings = HuggingFaceEmbeddings(model_name="/Users/xxx/workspace/models/text2vec-large-chinese" ,)
db = Chroma.from_documents(texts, embeddings)
llm = ChatGLM2()
llm.load_model("/Users/xxx/workspace/models/chatglm2-6b-int4" )
qa = RetrievalQA.from_chain_type(llm, chain_type="stuff" , retriever=db.as_retriever(), verbose=True )
print (qa.run("怎么创建程序化创意" ))
对于向量索引引擎,笔者使用 Chroma;对于大语言模型,笔者使用之前已定义的 ChatGLM2。对于问题和从向量索引返回的相关文本段,RetrievalQA 按下述提示模板拼接提示:
Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer.
{context}
Question: {question} Helpful Answer:
SQLDatabaseChain SQLDatabaseChain 能够通过模型自动生成 SQL 并执行,其实现原理包含多步:
输入问题
获取数据库 Schema,Schema 包含数据库所有表的建表语句和数据示例
拼接提示,根据提示模板将问题、数据库 Schema 转化为提示
模型推理,这一步预期模型根据问题、数据库 Schema 推理、输出的答案中包含查询 SQL,并从中提取出查询 SQL
执行查询 SQL,从数据库中获取查询结果
拼接提示,和上一次拼接的提示基本一致,只是其中的指示中包含了前两步已获取的查询 SQL、查询结果
模型推理,这一步预期模型根据问题、数据库 Schema、查询 SQL 和查询结果推理出最终的问题答案
代码文件 sql_database_chain_demo.py 如下所示:
from langchain import SQLDatabase, SQLDatabaseChain
from langchain.llms.fake import FakeListLLM
from chatglm2_llm import ChatGLM2
if __name__ == "__main__" :
llm = ChatGLM2()
llm.load_model("/Users/xxx/workspace/models/chatglm2-6b-int4" )
db = SQLDatabase.from_uri("sqlite:/Users/xxx/workspace/langchain-demo/Chinook.db" )
chain = SQLDatabaseChain.from_llm(llm, db, verbose=True )
print (chain.run("How many employees are there?" ))
其中,对于大语言模型,先尝试使用之前已定义的 ChatGLM2,后面会分析,从执行结果看,ChatGLM2-6B-INT4 和 ChatGLM2-6B 并不能输出符合格式的答案,从而无法进一步从中提取出查询 SQL,所以通过 FakeListLLM 直接使用固定的答案。对于数据库引擎,使用 SQLite3(Macbook 原生支持),对于数据库实例,使用 Chinook。
实际执行时,SQLDatabaseChain 首先根据问题和数据库 Schema 生成如下的提示:
You are a SQLite expert. Given an input question, first create a syntactically correct SQLite query to run, then look at the results of the query and return the answer to the input question. Unless the user specifies in the question a specific number of examples to obtain, query for at most 5 results using the LIMIT clause as per SQLite. You can order the results to return the most informative data in the database. Never query for all columns from a table. You must query only the columns that are needed to answer the question. Wrap each column name in double quotes (") to denote them as delimited identifiers. Pay attention to use only the column names you can see in the tables below. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which table. Pay attention to use date('now') function to get the current date, if the question involves "today". Use the following format: Question: Question here SQLQuery: SQL Query to run SQLResult: Result of the SQLQuery Answer: Final answer here Only use the following tables: {数据库 Schema,包含所有表的建表语句和数据示例,受限于篇幅,这里略去} Question: How many employees are there? SQLQuery:
若将提示输入 ChatGLM2(使用 ChatGLM2-6B-INT4),则无法返回预期的答案(答案合理、但不符合格式要求)。SQLDatabaseChain 的提示是针对 ChatGPT 逐步优化、确定的,因此适用于 ChatGPT,而 ChatGLM2-6B-INT4、ChatGLM2-6B 相对于 ChatGPT,模型规模较小,仅有 60 亿参数,对于上述的长文本提示无法给出预期的答案。由于没有 OpenAI 的 Token,因此示例代码通过 FakeListLLM 直接使用由 ChatGPT3.5 给出的答案。在获取查询 SQL 后,SQLDatabaseChain 会执行该 SQL 获取查询结果,并继续根据问题、数据库 Schema、查询 SQL 和查询结果生成如下的提示,若将提示输入 ChatGPT3.5,则可以续写'Answer',给出正确的答案。这里也通过 FakeListLLM 直接使用由 ChatGPT3.5 给出的答案,从而在本地跑通 SQLDatabaseChain 的流程。
Agent Agent 组合模型和各种工具,相对于 Chain 已固定执行链路,Agent 能够实现动态的执行链路。ReAct 架构是一个循环过程,对于问题,通过多次迭代,直至获取最终答案,而每次迭代包括如下几步:
将问题,各工具描述,之前每次迭代模型推理出的思考(Thought)、工具(Action)、工具输入(Action Input)、工具执行后的输出(Observation),以及期望模型输出格式,按照提示模板拼接出提示
将提示输入模型,由模型推理,输出进一步的思考(Thought)、工具(Action)、工具输入(Action Input)
使用模型给出的工具输入执行相应工具,获取工具输出(Observation)
继续第一步过程,直至获取最终答案跳出循环
LangChain 官方有个比较经典的实现 ReAct 架构的示例,其需要 OpenAI 和 SerpApi 的 Token,针对问题,使用 ChatGPT 进行多次推理,根据推理结果先使用搜索工具查询相关人的年龄,再使用计算器工具计算年龄的乘方,从而得到最终的答案。感兴趣的同学可以在本地执行示例代码体验,此处不再赘述。上述示例若使用本地部署的 ChatGLM2-6B-INT 作为大语言模型,则和在 SQLDatabaseChain 中遇到的问题相同,无法根据提示给出符合预期格式的答案。可见,虽然 LangChain 在设计上考虑了可扩展性,将 Model 以接口形式对外提供服务,屏蔽底层实现细节,但各种 Chain、Tool 和 Agent 中的提示模板还是针对 ChatGPT 进行了专门优化。
langchain-ChatGLM
介绍 langchain-ChatGLM 中使用的提示模板如下,其中'{question}'是提问的问题,'{context}'是将知识库中和问题相关的文本段用换行符拼接在一起:
已知信息: {context}
根据上述已知信息,简洁和专业的来回答用户的问题。如果无法从中得到答案,请说 '根据已知信息无法回答该问题' 或 '没有提供足够的相关信息',不允许在答案中添加编造成分,答案请使用中文。 问题是:{question}
部署 conda create --name langchain-chatglm python=3.9
conda activate langchain-chatglm
cd ~/workspace/
git clone https://github.com/imClumsyPan/langchain-ChatGLM
cd ~/workspace/langchain-ChatGLM
pip install -r requirements.txt
安装依赖的过程中,可能会因为缺少 Cmake、protobuf 和 swig 导致依赖 PyMuPDF 和 oonx 安装失败,因此对 Cmake、protobuf 和 swig 进行安装:
brew install cmake
brew install protobuf@3
brew install swig
langchain-ChatGLM 会使用模型进行自然语言文本的向量化,可以将这些模型下载到本地(若在 RetrievalQA 部分已下载,则无需再下载):
cd ~/workspace/models/
git lfs install
git clone https://huggingface.co/GanymedeNil/text2vec-large-chinese
修改 configs/model_config.py,修改第 19 行,设置文本向量化模型 text2vec 的本地地址:
"text2vec" : "/Users/xxx/workspace/models/text2vec-large-chinese" ,
修改第 46 行,设置 ChatGLM2-6B-INT4 的本地地址:
"local_model_path" : "/Users/xxx/workspace/models/chatglm2-6b-int4" ,
修改第 114 行,将大语言模型由 ChatGLM-6B 改为 ChatGLM-6B-INT4(实际使用的是 ChatGLM2-6B-INT4):
LLM_MODEL = "chatglm-6b-int4"
修改 model/loader/loader.py 第 147 行关于加载大语言模型的代码,删除或注释'to(self.llm_device)':
model = (
LoaderClass.from_pretrained(
checkpoint,
config=self .model_config,
trust_remote_code=True )
.float ()
)
实践中,'self.llm_device'的取值为'mps'(即使用并行处理),但若使用该设置,则会报错。准备本地知识库,笔者使用《超级汇川程序化创意产品手册》这一文档,将其以 Markdown 格式下载至本地,读者也可以使用该文档或其他文档。执行 cli_demo.py:
按提示先指定本地知识库,本地知识库同时支持目录和文件,对于目录,会扫描其中的文件。langchain-ChatGLM 会对文件内容进行切分、向量化并构建向量索引。随后可以提问和本地知识库相关的问题。langchain-ChatGLM 对问题进行向量化并从向量索引中寻找语义相关的知识库内容,将问题和知识库内容按提示模板拼接在一起后作为大语言模型的输入由其进行推理,给出最终的回答,同时也列出与问题相关的知识库内容。执行 webui.py:
结语 以上记录了在本地部署 ChatGLM-6B、ChatGLM2-6B、LangChain、langChain-ChatGLM 并进行推理的过程,不包含模型的微调。通过过程中的不断学习,对大语言模型及其周边生态、以及在多种场景下的应用,有了一定的了解。但将大语言模型应用在真实场景、发挥真正作用,还需要在语料搜集、模型微调、提示设计等方面针对业务特点进行不断的打磨。另外,本地部署仅为了快速体验,目前也有很多免费的 GPU 云资源可以申请,通过云厂商可以在 GPU 云资源上进行模型的微调和推理。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
随机西班牙地址生成器 随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online