自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

從Language Model到Chat Application:對話接口的設(shè)計(jì)與實(shí)現(xiàn)

發(fā)布于 2024-3-27 12:01
瀏覽
1收藏

01、前言

從2022年底chatGPT的一炮走紅開始,基于大語言模型的對話應(yīng)用如雨后春筍一般全面開花。剛剛過去的2023年是千帆競發(fā)的一年,在這一年里我們見證了百模大戰(zhàn),開源模型社區(qū)可謂繁榮昌盛:從llama到它的無數(shù)變體、qwen系列的完整中文大模型生態(tài)構(gòu)建、Mixtral等多模態(tài)的成功嘗試、再到llava等視覺大語言模型的蓄勢待發(fā)。在語言模型上,我們已經(jīng)有了十分豐富的選擇,這些模型在RTP-LLM上都得到了較好的支持,可以高效地完成推理。

在最早設(shè)計(jì)的RTP-LLM推理引擎中,我們認(rèn)為llm本質(zhì)都是語言模型,因此提供的只有語言模型調(diào)用方式,將所有請求簡化為輸入一個string,輸出一個string的模式。然而,從語言模型到chat應(yīng)用之間仍然有一個gap:輸入prompt的拼寫。text-in-text-out的設(shè)計(jì)可以簡化引擎開發(fā),但是prompt拼寫的難題就被丟給了用戶。實(shí)際上,對于某些模型,比如chatglm3,如果不加干預(yù),使用text-in-text-out的模式是無法正確進(jìn)行chat推理的,詳細(xì)原因我們后面會展開解釋。

從前端的需求側(cè)來看,text-in-text-out模式也無法滿足日益增長的chat應(yīng)用需求。讓每個用戶都學(xué)習(xí)甚至實(shí)現(xiàn)一遍chat prompt的拼寫既浪費(fèi)人力、又會提高錯誤率,因此,在LLM推理引擎層面實(shí)現(xiàn)chat接口的需求迫在眉睫。

眾所周知,openai作為最早推出chatGPT服務(wù)的開創(chuàng)式廠家,他們定義好的“openai接口”也是業(yè)界普遍采用的chat接口事實(shí)標(biāo)準(zhǔn),不但定義了基礎(chǔ)的多輪對話能力,還提供了funciton call、多模態(tài)輸入等多種功能。那么要在推理引擎實(shí)現(xiàn)chat能力,openai接口就是最合適的格式。本文就來聊一聊,在實(shí)現(xiàn)openai chat接口中遇到的種種問題。

02、開源實(shí)現(xiàn)大賞

在介紹我們的方案之前,我們先來看一看業(yè)界的其他框架是怎么實(shí)現(xiàn)chat能力的。這里討論的對象不僅限于openai接口的實(shí)現(xiàn),凡是實(shí)現(xiàn)多輪對話能力的,都在討論范圍之內(nèi)。提供chat能力的核心需求是如何將多輪對話按照模型訓(xùn)練時的格式渲染成模型的input id。這聽起來是個很簡單的事情,但是隨著模型類型不斷擴(kuò)張,各種五花八門的實(shí)現(xiàn)方式要做到正確卻并不容易,更不用說如果加上function call,問題就變得更加復(fù)雜。

2.1 huggingface tokenizer

hugging face將所有的LLM抽象成了text-generation pipeline,由Model和Tokenizer兩部分組成。其中,tokenizer需要繼承PreTrainedTokenizer類進(jìn)行實(shí)現(xiàn),該類提供了apply_chat_template方法,可以將多輪對話的dict轉(zhuǎn)換為input id或者prompt。

具體到實(shí)現(xiàn)上,該方法需要tokenizer在config中配置chat_template,這個template大概長這樣:

{% for message in messages %}
{{‘<|im_start|>’ + message[‘role’] + ‘\n’ + message[‘content’] + ‘<|im_end|>’ + ‘\n’}}
{% endfor %}
{% if add_generation_prompt %}
{{ ‘<|im_start|>assistant\n’ }}
{% endif %}

相信聰明的你看一眼就知道這玩意是啥語法了。有了這個模板,就可以把這樣的messages

[
{“role”: “user”, “content”: “Hi there!”},
{“role”: “assistant”, “content”: “Nice to meet you!”},
{“role”: “user”, “content”: “Can I ask a question?”}
]

拼成這樣的prompt:

<|im_start|>user
Hi there!<|im_end|>
<|im_start|>assistant
Nice to meet you!<|im_end|>
<|im_start|>user
Can I ask a question?<|im_end|>
如果想進(jìn)一步詳細(xì)了解,可以參考官方文檔

https://huggingface.co/docs/transformers/main/chat_templating

這個設(shè)計(jì)看起來簡單高效,非常美好。對很多模型來說,它也確實(shí)好用。那么我們馬上就來看一個失敗的例子。

2.2 chatglm

在chatglm3 官方repo的tokenizer config中,我們可以看到它定義了chat_template:

圖片

very good, 讓我們跑一下試試:

圖片

不對啊,這模型根本不說人話。

那么問題出在哪里呢?我們來看看拼好的prompt:

圖片

看起來像模像樣。但是如果對結(jié)果id逐個進(jìn)行detokenize,馬上就漏出了馬腳:

圖片

原來,由于chatglm3 tokenizer實(shí)現(xiàn)的問題,諸如[gMASK]、<|user|>等特殊token,在tokenize時會被錯誤地分割成多個token而非一個。而chatglm3的tokenizer實(shí)際上實(shí)現(xiàn)了一個非標(biāo)準(zhǔn)的build_chat_input接口,能正確處理多輪對話的id渲染。

通過這個例子可以看到,chat template 不一定靠譜。

2.3 qwen

qwen系列模型并沒有在tokenizer config里提供chat_template字段,使用默認(rèn)模板渲染的結(jié)果不難想象當(dāng)然是錯的。實(shí)際上,qwen和chatglm類似,自己實(shí)現(xiàn)了非標(biāo)準(zhǔn)的chat接口和渲染方法make_context,邏輯僅對自己的模型生效。對于單一模型來說當(dāng)然沒問題,但是并不能做成通用邏輯。

這時已經(jīng)不難發(fā)現(xiàn),開源模型有著五花八門的prompt拼寫方式。很多支持多模型的開源框架都號稱提供了openai格式的chat接口,那么來看看兼容多模型的開源框架做得如何。

2.4 vllm

vllm可以說是開源推理框架界的一哥,feature list里寫著支持openai接口。先翻翻代碼:

...

@app.post(“/v1/chat/completions”)
async def create_chat_completion(request: ChatCompletionRequest,
raw_request: Request):
try:
prompt = tokenizer.apply_chat_template(
conversation=request.messages,
tokenize=False,
add_generation_prompt=request.add_generation_prompt)

result_generator = engine.generate(prompt, sampling_params, request_id,
token_ids)

直接就無條件信賴chat template??雌饋砭筒惶孔V,讓我們起個qwen的服務(wù)

python3 -m vllm.entrypoints.openai.api_server --model Qwen/Qwen-7B-Chat --trust-remote-code

找個前端接上試試:

圖片

第一句話似乎是對的,但是顯然,沒有正確處理eos和stop words。約等于沒法用。

vllm還提供了手動指定chat_template文件的能力,但是這樣一來就對用戶有一定的使用門檻,做不到開箱即用;二來沒有解決tokenizer無法tokenize special token的問題。

2.5 llama.cpp

作為一個cpu first并且支持多種異構(gòu)加速方式的框架,llama.cpp在開源社區(qū)的呼聲也很高。

它的配套項(xiàng)目llama-cpp-python(https://github.com/abetlen/llama-cpp-python) 也在readme的開頭就強(qiáng)調(diào)了自己支持openai compatible server。

again,先看看代碼實(shí)現(xiàn):在llama_cpp/llama_chat_format.py中定義了一個ChatFormatter類,并針對不同的模型單獨(dú)寫了適配,以qwen為例的話:

@register_chat_format("qwen")
def format_qwen(
    messages: List[llama_types.ChatCompletionRequestMessage],
    **kwargs: Any,
) -> ChatFormatterResponse:
    _roles = dict(user="<|im_start|>user", assistant="<|im_start|>assistant")
    system_message="You are a helpful assistant."
    system_template="<|im_start|>system\n{system_message}"
    system_message=system_template.format(system_message=system_message)
    _messages = _map_roles(messages, _roles)
    _messages.append((_roles["assistant"], None))
    _sep = "<|im_end|>"
    _prompt = _format_chatml(system_message, _messages, _sep)
    _sep2 = "<|endoftext|>"
    return ChatFormatterResponse(prompt=_prompt,stop=_sep2)

看起來像模像樣,那么實(shí)際跑一下試試看。llama-cpp的運(yùn)行略微麻煩,需要先轉(zhuǎn)換模型為gguf模式然后運(yùn)行。這里只展示一下加載命令:

/opt/conda310/bin/python -m llama_cpp.server --model /mnt/nas1/gguf/qwen-14b-chat-f16.gguf  --n_gpu_layers 128 --host 0.0.0.0 --chat_format qwen

然后接上前端:

圖片

……總之是哪里不對。

看起來,主流開源推理框架提供的openai接口很難說得上能用。

2.6 llama-factory 和 fastchat

山窮水盡,峰回路轉(zhuǎn),在一次跟訓(xùn)練同學(xué)的交流中,發(fā)現(xiàn)有個做finetune的庫llama-factory寫的模板還不錯:

https://github.com/hiyouga/LLaMA-Factory/blob/5a207bb7230789ddefba932095de83002d01c005/src/llmtuner/data/template.py
這個template的設(shè)計(jì)十分干凈,沒有多余依賴;對于eos、special token的處理也十分到位,并且提供了proerty可供訪問,而且已經(jīng)適配了主流開源模型。

另外,還有個開源框架fast chat,它也提供了一些chat prompt的渲染模板,適配的模型更多,缺點(diǎn)是只拼了string,無法處理tokenizer的問題。

https://github.com/lm-sys/FastChat/blob/main/fastchat/conversation.py
測試了幾個模型的input id渲染結(jié)果發(fā)現(xiàn)均符合預(yù)期,于是決定直接拿過來用。雖然它們也不能解決所有問題,但可以省去很多模型的適配工作。

03、RTP-LLM的實(shí)現(xiàn)方案

了解了現(xiàn)狀之后,我們就希望能開發(fā)一個all in one、適配主流模型、功能豐富且開箱即用的chat接口。綜合整理多種模型的實(shí)現(xiàn)之后,我們設(shè)計(jì)了如下的縫合方案:

用戶指定template類型
前文提到,我們從開源項(xiàng)目里抄了一些適配規(guī)則。對于這部分規(guī)則模板,可以通過環(huán)境變量MODEL_TEMPLATE_TYPE指定使用。因?yàn)槠浔仨氾@示指定,并且完成度較高,而且還能解決tokenizer的問題,我們給了它最高優(yōu)先級。

chat_template
如果模型的tokenizer config中帶了chat_template屬性,那么用它作為除了指定模板以外渲染prompt的首選依據(jù)。這樣做有兩個考量:

一部分開源模型,如01ai的Yi-6B/34B系列,是用了llama的模型結(jié)構(gòu)+自己的chat_template。依靠chat_template屬性,無需額外設(shè)置即可自動獲得正確的渲染結(jié)果。

如果有用戶希望自己定義chat接口的prompt拼寫方式,那么chat_template也是最簡單的方式,也是業(yè)界的標(biāo)準(zhǔn)做法。用戶如果自己定義了拼寫模板,在導(dǎo)出checkpoint時設(shè)置了chat_template,那么應(yīng)當(dāng)起效。

qwen和多模態(tài):特殊處理
對于qwen系列模型,為了支持function,我們單獨(dú)寫了適配邏輯,在下一個section會詳細(xì)講解。同樣,對于多模態(tài)模型,因?yàn)樾枰幚韴D片,處理邏輯更復(fù)雜,我們也單獨(dú)寫了渲染邏輯。這些模型

其他模型:根據(jù)model type再次查找模板
這條規(guī)則和1類似,只不過是根據(jù)model type查找模板,而不是額外指定的環(huán)境變量。這樣可以完成原始版llama、baichuan等模型的支持。

保底:default chat template
如果以上的所有規(guī)則都不能找到合適的渲染方法,那么執(zhí)行兜底策略,使用chatML的方式拼寫prompt。

實(shí)現(xiàn)了以上方案后,用戶在啟動服務(wù)時,無需額外指定任何參數(shù),即可自動得到一個好用的openai chat接口;同時又保留了配置能力,可以一鍵套用常見的開源模板,也可以滿足用戶自帶模板的高級要求。

04、function call 的處理

4.1 基本邏輯

通過llm調(diào)用外部函數(shù)是一個重要的發(fā)展趨勢,qwen的全系列也支持用ReAct模板返回函數(shù)調(diào)用并根據(jù)函數(shù)返回給出最終結(jié)果。ReAct模板的prompt大概長這樣:

"<|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
Answer the following questions as best you can. You have access to the following APIs:

get_current_weather: Call this tool to interact with the get_current_weather API. What is the get_current_weather API useful for? Get the current weather in a given location. Parameters: {"type": "object", "properties": {"location": {"type": "string", "description": "The city and state, e.g. San Francisco, CA"}, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}}, "required": ["location"]}

Use the following format:

Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [get_current_weather]
Action Input: the input to the action
Observation: the result of the action
... (this Thought/Action/Action Input/Observation can be repeated zero or more times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question

Begin!

Question: 杭州市余杭區(qū)天氣如何?<|im_end|>
<|im_start|>assistant


具體的拼寫邏輯比較復(fù)雜,就不展開了。這里比較重要的是,如何處理response的問題。

當(dāng)遇到模型吐出\nAction: 和\nAction Input: 的組合時,我們就知道結(jié)果需要返回函數(shù)調(diào)用了。這個parse邏輯不復(fù)雜,但是LLM往往都是流式返回的結(jié)果,而在模型吐字的過程中,框架并不知道它會不會吐出來一個函數(shù)調(diào)用。

讓我們再去先看看開源實(shí)現(xiàn):

qwen官方的openai接口示例

if request.stream:
    if request.functions:
        raise HTTPException(
            status_code=400,
            detail="Invalid request: Function calling is not yet implemented for stream mode.",
        )

偷懶了,直接不允許流式返回和function call同時存在。

再看看chatglm的官方示例:

def contains_custom_function(value: str) -> bool:
    return value and 'get_' in value

這位更是高手,直接假設(shè)function call一定是get_開頭的。

至于其他開源框架,當(dāng)前大部分沒有不支持返回function call。

4.2 實(shí)現(xiàn)方法

最終的實(shí)現(xiàn)其實(shí)也很簡單,在模型吐字時留上一小塊buffer不返回,如果沒有\(zhòng)nAction: 那就繼續(xù)返回;如果遇到這個string,則說明模型可能要輸出function call,在此收集輸出知道遇到eos或者作為stop word 的\nObservation:,然后再把buffer一次性parse成函數(shù)并返回。

實(shí)際上,不同模型實(shí)現(xiàn)function call還有很多其他方式。由于qwen的規(guī)模最為完整,并且訓(xùn)練時也對function call做過align,所以目前我們的框架只支持了使用qwen進(jìn)行function call。未來也會繼續(xù)探索function的不同定義方式。

05、實(shí)戰(zhàn)篇:用chat接口構(gòu)建應(yīng)用

搞定服務(wù)之后,現(xiàn)在我們來實(shí)戰(zhàn)構(gòu)建一些基于chat接口的應(yīng)用。

首先,參照RTP-LLM的文檔啟動,以啟動任意size的qwen為例

export MODEL_TYPE=qwen
export CHECKPOINT_PATH=/path/to/model
export START_PORT=50233

python3 -m maga_transformer.start_server

這里的例子均使用qwen-14b模型完成。

5.1 langchain 文本信息結(jié)構(gòu)化輸出

這個例子展示RTP-LLM提供的openai接口返回function call的能力。這個例子中 langchain中對openai function設(shè)計(jì)了一類單獨(dú)的chain抽象,這里我們來看一個結(jié)構(gòu)化抽取實(shí)體的例子:

# 配置qwen服務(wù)域名為openai endpoint
import os
os.environ["OPENAI_API_KEY"] = "xxxx" # you can use any string for key
os.environ["OPENAI_API_BASE"] = "http://localhost:50233"

# langchain打印每一步的完整信息
from langchain.globals import set_debug, set_verbose
set_debug(True)
set_verbose(True)

from langchain_core.pydantic_v1 import BaseModel, Field
from langchain.chains.openai_functions import create_structured_output_chain
from langchain_community.chat_models import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate

# 定義一個Dog對象
class Dog(BaseModel):
    """Identifying information about a dog."""
    name: str = Field(..., description="The dog's name")
    color: str = Field(..., description="The dog's color")

# 定義prompt模板:用function call提取{input}中的對象。
llm = ChatOpenAI(model="anything-you-like", temperature=0.2)
prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "You are an algorithm for extracting information into structured formats, and respond with function call."),
        ("user", "extract information from the following input: {input}"),
    ]
)

# 構(gòu)建chain并調(diào)用,輸出解析的結(jié)果。output是一個`Dog`對象。
chain = create_structured_output_chain(Dog, llm, prompt)
output = chain.run("John had a dog named Harry, who was a brown beagle that loved chicken")
print(str(output))

運(yùn)行就可以得到解析好的結(jié)果

name='Harry' color='brown'

5.2 用llamaindex實(shí)現(xiàn)RAG

下面來看一個基礎(chǔ)的RAG例子,用llamaindex配合幾行代碼即可實(shí)現(xiàn)帶搜索增強(qiáng)的對話系統(tǒng)。在這個例子里,我們克隆一個chatglm3的官方github repo到本地,對目錄里的所有文檔做索引,并進(jìn)行增強(qiáng)對話。這里的例子不設(shè)計(jì)function call,所以理論上所有模型都能使用。

import os
os.environ["OPENAI_API_KEY"] = "xxxx" # you can use any string for key
os.environ["OPENAI_API_BASE"] = "http://localhost:50233"

from llama_index.readers import SimpleDirectoryReader, JSONReader, PDFReader
from llama_index.embeddings import HuggingFaceEmbedding
from llama_index import VectorStoreIndex, ServiceContext
from llama_index.llms import OpenAI

# 先從huggingface上拉一個embedding模型,給文本召回用
embed_model = HuggingFaceEmbedding(model_name="sentence-transformers/all-MiniLM-L6-v2")
service_context = ServiceContext.from_defaults(
    llm=llm, embed_model=embed_model
)
llm = OpenAI()

# 從本地目錄加載所有文檔,并建立向量索引
documents = SimpleDirectoryReader("/home/wangyin.yx/workspace/ChatGLM3").load_data()
index = VectorStoreIndex.from_documents(documents, service_context=service_context)

# 進(jìn)行對話查詢
query_engine = index.as_query_engine()
response = query_engine.query("如何在mac上部署chatglm?")
print(response)

運(yùn)行即可得到如下帶搜索增強(qiáng)的返回:

針對搭載 Apple Silicon 或 AMD GPU 的 Mac,可以借助 MPS 后端,在 GPU 上運(yùn)行 ChatGLM3-6B。參照 Apple 的 官方說明以安裝 PyTorch-Nightly(正確的版本號應(yīng)為 2.x.x.dev2023xxxx,而非 2.x.x)。目前 MacOS 只支持從本地加載模型。將代碼中的模型加載方式改為從本地加載,并使用 mps 后端,即可在 Mac 上部署 ChatGLM。


model=AutoModel.from_pretrained("your local path", trust_remote_code=True).to('mps')



## 06、總結(jié)

使用RTP-LLM的openai兼容接口,使得調(diào)用開源模型一鍵構(gòu)建chat應(yīng)用變得非常容易。同時,框架也提供了足夠豐富的配置項(xiàng),用戶可以適配多種方式train出來的模型。

#### 相關(guān)資料

[01]chatglm3 官方repo的tokenizer config
https://huggingface.co/THUDM/chatglm3-6b/blob/main/tokenizer_config.json
[02] llama-cpp-python

https://github.com/abetlen/llama-cpp-pytho
文末添加:
本文轉(zhuǎn)載自阿里技術(shù),作者:網(wǎng)隱
原文鏈接:https://mp.weixin.qq.com/s/DfMJVZnqFpsubKJ60H8s7g

標(biāo)簽
已于2024-3-27 12:03:30修改
收藏 1
回復(fù)
舉報(bào)
回復(fù)
相關(guān)推薦