赞
踩
作者:刘宜松 中国科学院高能物理研究所 硕士研究生
指导老师:庄建 英特尔边缘计算创新大使,中国科学院高能物理研究所 研究员
LLM大模型存在很多痛点,包括但不限于数据陈旧,无法和外部组件进行交互,本文旨在使用OpenVINO 2023,利用其新版本的特性加速Llama2模型。为Llama2定制化Prompt,并用LangChain实现可联网获得最新消息的辅助检索查询。在本文中,端到端教授大家基于OpenVINO+LangChain+LLama2实现具备互联网自动查询的AI助手,代码开源地址:
https://github.com/lewis430/langchain_openvino_llama2
OpenVINO 2023.1此版本推出的新功能可简化 AI 的部署和加速。此新版本为开发人员提供更多集成,最大限度减少代码更改.
OpenVINO提供了模型转换工具OVC,该工具正在取代我们众所周知的离线模型转换任务中的模型优化器(MO)工具。该工具以OpenVINO包形式提供,依靠内部模型前端来读取框架格式,不需要原始框架来执行模型转换。
将原来的AutoModelForCausalLM替换为OVModelForCausalLM即可实现模型转换
- -from transformers import AutoModelForCausalLM
-
- +from optimum.intel.openvino import OVModelForCausalLM
-
- from transformers import AutoTokenizer, pipeline
-
-
-
- model_id = "FlagAlpha/Llama2-Chinese-7b-Chat"
-
- -model = AutoModelForCausalLM.from_pretrained(model_id)
-
- +model = OVModelForCausalLM.from_pretrained(model_id, export=True)
因为我们是需要面向中文的,而由于Llama2本身的中文对齐较弱,我们需要采用中文指令集,对meta-llama/Llama-2-7b-chat-hf进行LoRA微调,使其具备较强的中文对话能力。同时也因为Llama2模型的获取需要向meta提供个人信息,所以选择更易获取的Llama2-Chinese-7b-Chat模型,以下是模型的Huggingface链接:
https://huggingface.co/FlagAlpha/Llama2-Chinese-7b-Chat
OpenVINO可用于从Hugging Face Hub加载优化模型,并创建管道以使用Hugging FaceAPI通过OpenVINO Runtime运行推理。这意味着我们只需要将AutoModelForXxx类替换为相应的OVModelForXxx类。就能实现模型格式的转换。
- model_dir = "llama-2-chat-7b/ov_model"
-
- ov_model = OVModelForCausalLM.from_pretrained('llama-2-chat-7b', export=True, compile=False)
-
- ov_model.half()
-
- ov_model.save_pretrained(model_dir)
指定其部署推理的设备为CPU,让模型在intel的CPU上进行推理。
ov_model=OVModelForCausalLM.from_pretrained(model_dir,device='cpu',ov_config=ov_config,config=AutoConfig.from_pretrained(model_dir,trust_remote_code=True),trust_remote_code=True)
LangChain是一个开源的框架,它可以让AI开发人员把像GPT-4这样的大型语言模型(LLM)和外部数据结合起来。它提供了Python或JavaScript的包。它是基于大语言模型这种预测能力的应用开发工具。无论你是想要做一个聊天机器人、个人助理、问答系统,或者自助代理等等,都可以帮助我们快速地实现想法。我可以拍胸脯地说,LangChain 作为新一代 AI 开发框架,必将受到程序员的追捧,点燃 AI 应用开发的新热潮。
LangChain的精髓是agent机制,学习了解 Agent 就像探索迷宫一样有趣。langchain 的 Agent 实在是非常聪明的设计。什么是 Agent,简单讲 Agent 利用 LLM 的理解推理能力来简化原本十分复杂的逻辑判断,决定什么问题由什么工具来解决,最后汇总成一个“答案”返回给用户。LangChain 把这个过程用一个非常经典的 Prompt 模板定义了出来,这个模板中包括四个环节:Thought(LLM 的思考过程和决定)、Action(要采取的行动以及要给他的输入)、Observation(执行 Action 后预计的输出结果)、Tools(Action 行动的调用函数)。
RapidAPI 是一个 API 市场,提供了数千个 API,可以帮助开发人员快速找到并使用需要的 API。它包括多个类别,如人工智能、云计算、区块链、金融、游戏等,每个类别下面都有大量的 API 接口可供选择。在 RapidAPI 的平台上,你可以搜索、筛选、订阅和使用这些 API 接口,还可以查看 API 的文档和使用示例,从而更好地了解和使用它们。
我们利用Rapid API平台上bing搜索引擎提供的api来拿到浏览器上的最新数据,以此来作为大模型的新的数据来源。
- url = "https://bing-web-search1.p.rapidapi.com/search"
-
- querystring= = {"q":query,"mkt":"en-us","safeSearch":"Off","textFormat":"Raw","freshness":"Day"}
-
- headers = {
-
- "X-BingApis-SDK": "true",
-
- "X-RapidAPI-Key": "Your Key",
-
- "X-RapidAPI-Host": "bing-web-search1.p.rapidapi.com"
-
- }
-
- response = requests.get(url, headers=headers, params=querystring)
LangChain对OpenAI的ChatGPT的支持是最好的。但是在某些对数据安全性要求比较高的场合 ,我们需要将数据放在局域网中的,数据是不能出网关的,所以要在局域网内部搭建本地模型,其实整个应用的成本最高的就是LLM的部署的硬件需求。所以最经济的一个方案就是一个局域网共同一个LLM,它是统一部署后通过API的形式给各个应用做支持,这样也保证了硬件的充分利用。同时将LLM和LangChain分开部署的好处还有就是灵活性,LangChain对硬件的要求不高,它只是做资源的整合,任何重存储和重计算的服务全部在远端部署,可以将LangChain部署在硬件条件不那么好的设备上。
我们可以通过LangChain来很方便的使用OpenAI的模型接口,同时LangChain内部很多逻辑代码都是基于ChatGPT这样一个出色的模型来设计的,当我们需要考虑使用自定义的本地模型时,需要做一个适配,其核心在于构建langchain.llms.base.LLM 的子类 CustomLLM 并重写_call 函数如下:
- class CustomLLM(LLM):
-
- def _call(
- self,prompt: str,stop: Optional[List[str]] = None,
- run_manager: Optional[CallbackManagerForLLMRun] = None,
- ) -> str:
-
- response = requests.post(f'http://127.0.0.1:8080/', {
-
- "ask": prompt
-
- })
-
- return response.text
因为LangChain的内部逻辑上基本是为了ChatGPT 量身定制的,内置的所有Prompt都是基于ChatGPT的参数量和聪明程度,但是对于我们本地部署的参数量小的Llama-2-7b来说就十分不契合了,LangChain默认的Agent中的Prompt模版没办法和Llama-2-7b起到很好的互动,虽然不是每次都失败,但失败的概率是很大的,约等于不可用,所以我们需要自己来定义Prompt来做一个LangChain和Llama-2-7b模型的适配。
本例子中的Prompt结构中background_infomation=角色扮演+网络检索结果+原始问题这三者拼接起来的。其中的“网络检索结果”和“原始问题”比较理解理解,而我发现不得不仿照LangChain 加上一个角色定义,让 Llama2在做推理决策时和汇总答案时扮演不同的角色,这样可以显著得提高回答的质量。
agent_template = """
你现在是一个{role}。这里是一些已知信息:
- {related_content}
-
- {background_infomation}
-
- {question_guide}:{input}
-
- {answer_format}
-
- """
LangChain的核心逻辑是Agent,而Agent 的执行逻辑其实非常简单,首先把问题和背景知识通过拼装到原始的 Prompt 中,然后发给 LLM ,让他返回一个做决策的“任务表”,然后由 OutputParser 处理这个任务表,它根据预设的格式,总结出LLM 需要做的下一步行动,也就是选择某个 Tool 继续执行,还是就此结束直接返回答案。
在本项目的实现中,我完全继承了 AgentOutptparser,它原则上是可以做到根据返回来多次向 LLM 询问“任务表”的,但因为Llama-2-7b理解力实在有限,不会再多次的询问了,最多问三次。
- class CustomOutputParser(AgentOutputParser):
-
- def parse(self, llm_output: str) -> Union[AgentAction, AgentFinish]:
-
- match = re.match(r'^[\s\w]*(DeepSearch)\(([^\)]+)\)', llm_output, re.DOTALL)
-
- if not match:
-
- return AgentFinish(
-
- return_values={"output": llm_output.strip()},
-
- log=llm_output,
-
- )
-
- else:
-
- action = match.group(1).strip()
-
- action_input = match.group(2).strip()
-
- return AgentAction(tool=action, tool_input=action_input.strip(" ").strip('"'), log=llm_output)
最后,我们在这里定义新的 CustomPromptTemplate 类,其实只要重写一个 format 函数就行,用它来继承了 StringPromptTemplate类,从而实现对控制推理决策和汇总答案汇总拼装到不同的 Prompt中。LangChain内部逻辑会自动把每次 LLM 返回作为参数传递给 format 函数。我只要判断哪是首次向 LLM 询问决策,哪是后续的拼装答案。
- if len(intermediate_steps) == 0:
-
- background_infomation = "\n"
-
- role = "傻瓜机器人"
-
- question_guide = "我现在有一个问题"
-
- answer_format = """请你只回答\"DeepSearch("搜索词")\",并将"搜索词"替换为你认为需要搜索的关键词,除此之外不要回答其他任何内容。\n\n下面请回答我上面提出的问题!"""
-
- else:
-
- # 根据 intermediate_steps 中的 AgentAction 拼装 background_infomation
-
- background_infomation = "\n\n你还有这些已知信息作为参考:\n\n"
-
- action, observation = intermediate_steps[0]
-
- background_infomation += f"{observation}\n"
-
- role = "聪明的 AI 助手"
-
- question_guide = "请根据这些已知信息回答我的问题"
-
- answer_format = ""
前面提到给LLM赋予角色,可以很好的提升回答质量,在这里我分别赋予了推理决策和汇总答案两个角色:“傻瓜机器人”和 “聪明的 AI 助手”,前者只需要做简单的分类任何,后者才是生成最终答案文本,我目测这样的设定成功率较高,你也可以尝试换成其他的角色,说不定有惊喜。
cpu型号:Intel(R) Xeon(R) Gold 6248R CPU @ 3.00GHz
运行环境:操作系统Ubuntu20.4,Gradio版本>=3.47.1
从运行效果可以看出,对于一个数据实时性要求高的专业问题,这个 Agent 完全可以做到对互联网检索的结果的理解并返回。整个流程是可以很好执行起来。但作为一个 Demo 还不能做到 100% 的高效回答和足够的智能,最大的挑战来自三个方面,即如果想要做到极致的话,要在这三个方面花更多精力:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。