作者:劉宜松、莊建
LLM大模型存在很多痛點,包括但不限於資料陳舊、無法和外部元件互動等,本文旨在使用 OpenVINO 2023.1新版本的特性加速Llama2模型,為Llama2客製化Prompt,並用LangChain 實現可連網取得最新消息的輔助搜尋功能。
本文將端對端教大家以OpenVINO結合LangChain與Llama2實現具備網際網路自動查詢的AI小助手,程式碼下載網址:https://github.com/lewis430/langchain_openvino_llama2
OpenVINO 2023.1新特性
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模型;該模型的Hugging Face連結為:https://huggingface.co/FlagAlpha/Llama2-Chinese-7b-Chat
模型轉換為 ONNX 格式
OpenVINO可用於從Hugging Face Hub載入最佳化模型,並創建管道以使用Hugging Face API透過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
指定其部署推論的設備為 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
LangChain是一個開源的框架,可以讓AI開發人員把像是GPT-4這樣的大型語言模型和外部資料結合起來,它提供了Python或JavaScript套件,是基於大語言模型這種預測能力的應用開發工具。無論你是想要做一個聊天機器人、個人助理、問答系統,或者自助代理等等,都可以幫助我們快速地實現想法。筆者可以拍胸脯說,LangChain作為新一代AI開發框架,必將受到程式設計師的歡迎,點燃AI應用開發的新熱潮。
Rapid API
Rapid API是一個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)
CustomLLM
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
自定義Prompt
因為LangChain的內部邏輯上基本是為了ChatGPT量身訂做,內建的所有Prompt都是根據 ChatGPT的參數量和聰明程度,但是對於我們本地部署、參數量小的Llama-2-7b來說就十分不契合,LangChain預設Agent中的Prompt模版無法和Llama-2-7b良好互動,雖然不是每次都失敗,但失敗機率很高,幾乎等於不可用,所以我們需要自己來定義Prompt,進行LangChain 和Llama-2-7b模型的配接。
本範例的Prompt結構中background_information是由「角色扮演」、「網路搜尋結果」、「原始問題」三者拼接而成。其中的「網路搜尋」和「原始問題」比較容易理解,而筆者發現不得不仿照LangChain加上一個角色定義,讓Llama2在做推論決策和匯總答案時扮演不同的角色,這樣可以顯著提高回答的品質。
agent_template = """
你現在是一個{role}。這裡是一些已知資訊:
{related_content}
{background_infomation}
{question_guide}:{input}
{answer_format}
"""
CustomOutputParser
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)
Custom Prompt Template和Custom Search Tool
最後,我們在這裡定義新的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 Xeon Gold 6248R CPU @ 3.00GHz
- 執行環境:作業系統Ubuntu22.04,Gradio 版本>=3.47.1
從執行效果可以看出,對於一個資料即時性要求高的專業問題,這個Agent完全可以做到對網際網路搜尋結果的理解並回覆,整個流程可以妥善執行。但作為一個Demo還不能做到 100%的高效率回答和足夠的智慧,最大的挑戰來自三個方面,即如果想要做到極致的話,要在這三個方面花更多精力:
- Prompt的提煉:筆者覺得還是要設計一個更好的、配接Llama2的Prompt,確保LLM指令回應的成功率。
- 模型推論能力:在硬體允許的情況下,換成參數更多、推論能力更強的大模型。
- 網路搜尋的parser:這裡筆者是基於Bing search API 做網際網路搜尋,直接回覆結果,但還是有不少廣告等垃圾資訊,這些資訊干擾讓這個機器人顯得不那麼聰明。
(本文作者劉宜松為中國科學院高能物理研究所碩士研究生,莊建為英特爾邊緣運算創新大使、中國科學院高能物理研究所研究員)
- OpenVINO 2025.0來了…一起繼續玩生成式AI吧! - 2025/02/26
- LLM性能再強化 OpenVINO 2024.3隆重發佈! - 2024/08/21
- OpenVINO全新GenAI API:幾行程式碼就能快速建立GenAI App! - 2024/08/14
訂閱MakerPRO知識充電報
與40000位開發者一同掌握科技創新的技術資訊!