平时做技术实践时,很多问题不是概念不会,而是细节没串起来。拿“ChatGLM-6B+LangChain环境部署与采用实践”来说,它看着像小点,放到项目里常会牵出环境、配置、兼容性和维护成本。下面按实际采用顺序,把思路、关键写法和容易踩坑的地方讲清楚,便于大家直接对照操作。
目标:原始采用ChatGLM-6B可接受的文字长度有限,打算结合LangChain实现长文本生成摘要.方法:step1:自定义一个GLM继承LangChain中的langchain.llms.base.LLM,load自己的模型.step2:采用LangChain的mapreduce的方法,对文本分块,做摘要,输出结果.采用的机器资源:T4显卡(16G显存)附参考资料:ChatGLM-6B:ModelScope: ChatGLM-6BLangChain:LangChain: summarizationLangChain: summarize notebook
- glm环境准备在指定的python环境下确定安装好以下依赖:
# 安装pytorch
conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia
# 安装modelscope
pip install modelscope==1.4.3 -f https://modelscope.oss-cn-beijing.aliyuncs.com/releases/repo.html
# 安装transformers
pip install protobuf==3.20.0 transformers==4.26.1 icetk cpm_kernels
# 安装charset-normalizer
python -m pip install charset-normalizer==2.1.0
# 安装langchain
pip install langchain
模型文件下载到/data/THUDM/ch@tglm-6b/下
可以使用以下代码先下载到临时目录,然后mv到自定义目录下:
from modelscope.utils.constant import Tasks
from modelscope.pipelines import pipeline
pipe = pipeline(task=Tasks.ch@t, model='ZhipuAI/ChatGLM-6B', model_revision='v1.0.7')
- ChatGLM-6B + LangChain
2.1 继承langchain.llms.base.LLM新建GLM类重写_call方法:加载自己的模型,同时限制只输出结果(ch@tglm原输出不是直接str,langchain中要求模型得到必须是str的结果:“”“LLM wrapper should take in a prompt and return a string.”“”)具体代码:
from langchain import LLMChain
from langchain.text_splitter import CharacterTextSplitter
from langchain.chains.mapreduce import MapReduceChain
from langchain.prompts import PromptTemplate
from langchain.llms.base import LLM
from transformers import AutoTokenizer, AutoModel, AutoConfig
from typing import Any, Dict, List, Mapping, Optional, Tuple, Union
from torch.mps import empty_cache
import torch
class GLM(LLM):
max_token: int = 2048
temperature: float = 0.8
top_p = 0.9
tokenizer: object = None
model: object = None
history_len: int = 1024
def __init__(self):
super().__init__()
@property
def _llm_type(self) -> str:
return "GLM"
def load_model(self, llm_device="gpu",model_name_or_path=None):
model_config = AutoConfig.from_pretrained(model_name_or_path, trust_remote_code=True)
self.tokenizer = AutoTokenizer.from_pretrained(model_name_or_path,trust_remote_code=True)
self.model = AutoModel.from_pretrained(model_name_or_path, config=model_config, trust_remote_code=True).half().cuda()
def _call(self,prompt:str,history:List[str] = [],stop: Optional[List[str]] = None):
response, _ = self.model.ch@t(
self.tokenizer,prompt,
history=history[-self.history_len:] if self.history_len > 0 else [],
max_length=self.max_token,temperature=self.temperature,
top_p=self.top_p)
return response
2.2 实例化llm对象&加载模型
import sys
modelpath = "/data/THUDM/ch@tglm-6b/"
sys.path.append(modelpath)
llm = GLM()
llm.load_model(model_name_or_path = modelpath)
2.3 配合langchain输出
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.docstore.document import Document
from langchain.chains.summarize import load_summarize_chain
with open("正府工作报告.txt") as f:
report_2023 = f.read()
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_text(report_2023)
docs = [Document(page_content=t) for t in texts]
prompt_template = """对下面的文字做精简的摘要:
{text}
"""
PROMPT = PromptTemplate(template=prompt_template, input_variables=["text"])
chain = load_summarize_chain(llm, chain_type="map_reduce", return_intermediate_steps=True, map_prompt=PROMPT, combine_prompt=PROMPT)
summ = chain({"input_documents": docs}, return_only_outputs=True)
print(summ['output_text'])
其中正府工作报告.txt来自于2023年正府工作报告_中国正府网:(链接已移除)
2.4 输出这篇文章介绍了中国在过去五年中的经济发展成就,正府采取多项措施应对有效需求不足的问题、兼容汽车消费、推进保交楼稳民生工作、加强环境保护和生态修复工作等。正府还出台了增值税留抵退税额度、降低带宽利息等减轻企业负担的措施。文章介绍了中国正府的宏观经济正策目标,包括推进中国式现代化、实现经济发展质量和数量的提升、改善民生、稳定社会大局等。新冠疫情防控正策包括疫苗迭代升级和新药研制、保障群众就医用药需求、重点做好老年人、儿童、患基础性疾病群体的疫情防控和医疗救治等。正府将着力扩大国内需求、加快建设现代化产业体系、深化国资国企改革、保护民营企业产权和企业家权益,鼓励兼容民营经济和民营企业发展壮大,稳定市场预期和提振市场信心。
实战2框架地址:(链接已移除)
langchain+ChatGLM-6B试用什么是langchain在这个场景下,ChatGLM-6B大家都知道了,是清华大学推出的有62亿参数的开源大语言模型。那么langchain是什么?langchain是一个基于语言模型的应用程序开发框架,它具有以下特点
数据感知:将语言模型与其他数据源连接在一起自主性:允许语言模型与其环境进行交互langchain框架是基于以上原则设计的。
结合项目来看,因为这些特点,langchain能够完成针对特定文件的问答、聊天机器人、评估、数据增强生成等工作。
如何部署github地址为:基于本地知识的 ChatGLM 应用实现
部署指南为:安装
从实现思路看,因为我换了新机器,我个人遇到了【build wheel for xxx时提示找不到cl.exe】的问题,解决方法是:
结合项目来看,装个Visual Studio,随后在Visual Studio Installer里安装组件【MSVC v142 - VS 2019 C++ x64/x86生成工具】。注意要选择这个版本,像我之前就装了MSVC v143结果build报错了。安装完成重启之后如果还报这个错,就找到cl.exe所在的目录,把它添加到环境变量Path里,这个目录大致是这样的:D:\VS\2019\VC\Tools\MSVC\14.29.30133\bin\Hostx64\x64如何采用实际处理时,安装完成后,执行python webui.py即可开始采用,访问本地的127.0.0.1:7860即可采用web版的界面。
从实现思路看,首先,进入模型设置,选一个LLM模型和Embedding模型。设置好的能够用ch@tglm-6b,差点的就要砍精度了。Embedding模型主要是文本转向量的模型,这个我研究不多。
在这个场景下,然后来到知识库测试界面,先新建知识库,同时为其上传文档或者文档所在文件夹,等待程序将文本上下文分割并解析为向量之后进行保存好后,测试问题。这一步主要是调整知识相关度阈值,默认Score=0会回复所有检索到的知识出处,这显然是不精准的。
实际处理时,确定好阈值后,在model_config里进行修改,这里还能够顺便修改一下prompt模板,重启程序以令其生效;
L 在对话中,设置好数据库之后提问即可。
缺陷举例而言,我写了这样一个Q&A:
如何进行性能优化?
使用Unity的FrameDebugger查看每帧的DrawCall和使用到的模型。
但是喂给ChatGLM-6B后提供的回答会类似于:
如何进行性能优化?
1. 了解业务需求:明确优化的目标和范围
2. 设计测试用例:针对目标场景进行性能模拟,以确定系统的性能表现
云云。
结合项目来看,借助这张图能够看到,这个框架的主体其实是跟LLM无关的,它只是比对和匹配文本向量的相似性,检出最相似的问句的上下文打包发给LLM进行润色。
实际处理时,在我的理解里,这个玩意儿不会真的把本地的数据更新进自己的模型内部,只是每次提问的时候,预先把可能存在答案的上下文作为prompt一起提交了而已,这里在model_config.py的PROMPT_TEMPLATE里也能看到。
所以它会触发以下问题:
结合项目来看,1 当这个问题同时不“独特”的时候,就会出现例子中所示的答案的杂糅。因为是打包上下文之后发给LLM润色,显然LLM理解的“性能优化”指的是一种普适性的答案,它自发的把自身语料训练出来的结果与本地知识库中项目所独有的结果进行混合,得到了似是而非的东西。2 当知识库中的问题重复度很高的时候,或者问题过于宽泛导致命中过多,也会出现本地知识库答案的杂糅。此时问题和各个文本向量的相似性很高,回答会串味,比如,询问“如何进行性能优化”之后,就会连同“性能优化怎么查看”、“性能优化的指标是多少”等等一块得到进行回答。3 因为是打包上下文发给LLM润色,所以这个“打包”可能会把答案截断,或者囊括了并非本问题的答案的上下文,会造成回答串味或不全。
实际处理时,轻松的说,对于我来说我同时不需什么语料的润色、帮助我提取有用信息,因为我给的文档本身就是最好的答案,所以我需的是精确的检索。这种截断上下文发给LLM的架构的三个缺陷是我无法忍受的:
- 落到代码里,本地知识库干预力度不够,由于真正需的知识在上下下文,而上下文是以Prompt的形式嵌入其中的,导致独特性不够的问题,大模型给出的答案会很偏向语料训练结果。
- 结合项目来看,ChatGLM-6B的中文能力过于羸弱,逻辑能力过于差劲,有时候无法判别出两个相似问题的区别。
- 上下文截断过于粗暴,对于长答案兼容不佳。langchain+ChatGPT落到代码里,其他尝试之后我转变思维,不再尝试让LLM模型去即时回答问题,而是让LLM即时判定问句是否一致,再针对同义问句匹配相同的回答。所以我个人在家又搭建了一个langchain+ChatGLM-6B的本地知识对话模型,但这个模型跟前一个模型的区别在于,我会写好一个问题答案对:
Q:你是谁?
A:我是弱智小助理。
实际处理时,当我提问“你是什么人”的时候,内部会采用LLM模型去一个个比对这句话和各个问题答案对中的问题部分是否属于同一个意思,此处它就会比对“你是谁”和“你是什么人”是不是同一个意思。Prompt会写成这个样子:
实际处理时,f"{original_question}\n{input_question}\n判断上述两句话是不是一个意思,如果是,则回答1;反之回答0。"这里再次暴露了ChatGLM-6B的羸弱,明明要求回答0或者1即可,往往会画蛇添足地说“是的,这两句是一个意思”,此外它对同义句的判定也有极大的问题,这里我们后面说。
在这个场景下,随后我又换了个方式,好吧,不要求你按格式回答了,你直接生成多几个同义句,我用文本向量按最近距离匹配好了。
从实现思路看,我寻思Judge可能确实有点难为它了,那么generate应该没问题吧,结果还是让我大跌眼镜,对于“如何进行性能优化的流程”的问题,ChatGLM-6B给出的回复是:
事实上,我想看到的是这样的句子:
- 怎么发起性能优化流程?
- 性能优化的流程是怎么样的?
- 我该如何启动性能优化流程?
- 性能优化流程是如何发起的?

(你要不看看你在说什么?.jpg
实际处理时,因为我一直在用ChatGPT帮助生成代码,所以我测试了一下ChatGPT,发现ChatGPT生成的同义句居然还不错,虽然有时候会带上人称,比如”你如何才能启动性能优化“。
实际处理时,事实证明,尽管ChatGPT之后,LLM人人均有不下ChatGPT-3之勇,但真用起来,那还是ChatGPT好使。
如何采用OpenAI API入口:Introduction - OpenAI API
结合项目来看,每个ChatGPT Plus用户每月有5刀的免费采用额度,而根据采用的GPT模型不同,收费也不同:
落到代码里,模型 迅速版(每千token) 完整版(每千token)GPT-4 8K Context 0.03$ 0.06$GPT-4 32K Context 0.06$ 0.12$GPT-3.5-Turbo 0.002$ -选择采用GPT-3.5的原因绝对不是便宜(迫真),而是因为它速度快,且为对话专门优化过。而同义句的生成就也不涉及大量的知识和专业性内容,所以直接用它。
采用的方法就很轻松:
p
rompt_base_templet = """请为下面这段文字生成至少5个的意思完全相同的中文问句,句子之间用回车分隔开:{question}"""
gpt_engine = "text-davinci-003"
max_tokens = 300
temperature = 0
# 获取同义问句
def get_synonymous_question(question : str) -> list:
openai.api_key = api_key
prompt = prompt_base_templet + question
response = openai.Completion.create(
engine=gpt_engine,
prompt=prompt,
max_tokens=max_tokens,
temperature=temperature,
n = output_num
)
generate_text = response.choices[0].text.strip()
return generate_text.split('\n')从实现思路看,感觉一句话基本大约不会超过60token,所以5个中文问句大致就是300个token。temperature设为0是为了防止回答过于发散。
框架理解这一步时,框架整体参考了imClumsyPanda/langchain-ChatGLM 的实现,包括怎么用gradio新建webui之类的,但轻量化了很多,因为我同时不需内嵌LLM,也不需对问句进行分词(都整句话直接转换成向量了)。
这个框架的运行结果是:
- 读取问题答案对
- 把问题整理出来发给OpenAI API生成同义句
- 把同义句转换为Document,把答案和原问句编制到metadata里
- 用embedding model将同义句转化为向量
- 用FAISS匹配最符合输入的问句
- 把结果中的metadata筛一次,合并同义句产生的答案
- 得到合并筛选之后的答案

理解这一步时,值得注意的是,该方法只实现了一半的数据安全,其问题还是要提交到服务器上的。所以如果有涉密需求,还是得手动编写同义句字典进行搭建数据库。
改进
允许上传图片和链接
允许使用不经过OpenAI的同义句字典
允许下载本地知识和字典
一些个人的想法理解这一步时,故,本质上而言,该方案同时不能算“对话”系统,因为LLM并没有在即时输入端参与,而是在本地知识上传后离线参与。
从实现思路看,写完之后我在思索,这玩意儿和Ctrl-F有什么区别,有没有一种可能,我直接Ctrl+F搜索“性能优化”也能找到我要的内容呢?所以这是个伪命题?
从实现思路看,后面想了想,如果知识库很少,问题单一的情况下确实是这样的没错。但随着知识库的增大,问题的keyword也在增多,单个keyword对应的答案内容开始急剧上升,假如后面有这些问题:
使用RenderDoc怎么指导性能优化?
怎么用Unity Frame Debugger优化性能?
如何优化冗余的资源实现性能提升?
……
结合项目来看,这样的话,Ctrl+F的实用性就大打折扣了。此外,keyword也可能会改头换面,比如“性能优化”实际上能够这样问“优化XX的性能表现”,所以我认为这个方案仍有有较大的用武之地。基于本地知识的问答机器人langchain-ChatGLM

- linux环境部署清华大学大模型最新版 ch@tglm2-6b 图文教程
- macbook安装环境ch@tglm2-6b的详细过程
- 基于本地知识的问答机器人langchain-ChatGLM 大语言模型实现方法详解
- ChatGPT平替- ChatGLM多用户同时行访问部署过程
- ChatGPT平替-ChatGLM环境搭建与部署运行效果
- ChatGLM 集成LangChain工具详解
- 基于prompt tuning v2训练好一个垂直领域的ch@tglm-6b
- 从实现思路看,最简化ChatGLM2-6b小白部署教程【windows系统,16G内存即可,对显卡无要求,CPU运行】
