
阿里妹導讀
本文基於實際場景,分享了作為開發者提高大模型響應效能的四個實用方法。
一、背景
1.1 序言
大模型的響應速度(首包和全文),直接影響使用者使用體驗。現階段來看,想要有較高的響應效能,我們可以選擇小尺寸模型,但意味著複雜場景下效果得不到保障;可以讓大模型“吐”更少的字,但意味著不能完成更多的任務。對於大模型來說,速度、效果、功能似乎是一個“不可能三角”。
本文主要分享我在實現大模型場景過程中,對於任務型應用,在保證一定效果的前提下,站在開發者角度如何提升響應速度和完成更多工的幾個思路。
任務型應用,主要指幫助使用者完成特定任務,例如預約餐館、預定機票、給出規劃等,與使用者的互動大多是結構化的,我們通常要求它輸出結構化資料以便進行下一步處理。相應地,生成式對話應用我們稱之為“閒聊”,其主要目的是進行自然的、開放式的互動,根據給定的對話歷史和上下文資訊生成連貫的自然語言回覆,而不是完成具體任務。
先說結論,後文再根據實際場景案例再做詳細說明:
-
prompt約束輸出結構,減少輸出token; -
分解任務,大小尺寸模型分工; -
流輸出,擷取資訊非同步/併發處理; -
提前約定,以短代號對映長結果;
1.2 場景案例描述
以基於大模型做一個行程規劃推薦應用的場景為例,使用者透過ASR語音輸入“幫我規劃五天的浙江行程,杭州玩兩天吧”,希望得到:
-
每天要遊覽的地點名; -
每個地點的簡短介紹; -
遊玩城市與天數計劃; -
地點的poi資訊(Point of Interest,泛指網際網路電子地圖中的點類資料,基本包含名稱、詳細地址、經緯度座標、地點類別四個屬性,以及營業時間、人均消費,圖片示例等資訊); -
前後地點的行駛距離與時間; -
json結構化結果,以便解析在頁面上展示;
分析這個場景需求,需要大模型輸出的任務有根據使用者意圖,生成推薦的地點名、地點的介紹、對應的遊玩城市與天數。需要呼叫外部API獲取的有,地點poi資訊、前後地點的行駛距離與時間。簡單設計後約定客戶端服務端這樣進行互動:
-
客戶端乘客語音輸入“幫我規劃五天的浙江行程,杭州玩兩天吧”,呼叫服務端介面;
# 請求
{
"query": "幫我規劃五天的浙江行程,杭州玩兩天吧",
"history": []
}
-
服務端內部根據使用者意圖,輸出一天的行程地點、遊玩計劃的JSON結構化資料。例如以下返回結果表示當天遊覽城市為杭州,一共規劃2天杭州、1天紹興、2天寧波。這一天去到地點A-E五個地方,且包含該地點的poi資訊。
{
"plan": {
"杭州": 2,
"紹興": 1,
"寧波": 2
},
"city": "杭州",
"locs": [
{
"name": "地點A",
"time": "上午",
"address": "xxxx",
"longtitude": 120.0000,
"latitude": 30.00000,
"pic_url": "http://xxx.jpg"
},
{
"name": "地點B"
},
{
"name": "地點C"
},
{
"name": "地點D"
},
{
"name": "地點E"
}
]
}
-
客戶端獲取到結果,渲染第一天的行程展示。

(圖1:車端渲染展示示意圖)
-
同時,根據遊玩城市和天數,客戶端將歷史行程新增為引數繼續呼叫服務端介面,獲取下一天不重複的行程。
# 請求
{
"query": "幫我規劃五天的浙江行程,杭州玩兩天吧",
"history": []
}
-
以此類推,直到遍歷完城市和天數意圖。
二、最佳化思路詳解與示例
2.1 思路1:prompt約束輸出結構,減少輸出token
全文響應時間和輸出token數量是正相關的。我們可以仔細考慮大模型輸出的資料結構,在prompt里加以約束與few shot,讓它輸出核心必要的字元,減少冗餘資訊的輸出,來減少大模型的全文響應時間。
舉個極端的反例,就“幫我規劃五天的浙江行程,杭州玩兩天吧”這個問題,讓大模型輸出遊玩計劃,以及一天的行程地點,返回這樣的結構:
{
"plan": [
{
"city": "杭州",
"days": 2
},
{
"city": "紹興",
"days": 1
},
{
"city": "寧波",
"days": 2
}
],
"city":"杭州",
"locs":{
"上午": "西湖",
"中餐": "知味觀",
"下午": "宋城景區",
"晚餐": "老頭兒油爆蝦",
"住宿": "杭州君悅酒店"
}
}
使用token計算器模型服務靈積-Token計算器 (aliyun.com),上述文字包含122tokens。這樣的結果存在很多冗餘資訊(空格符和換行符也佔token的!)有一些字元完全沒有必要讓大模型輸出。
我們來改造一下,在prompt里約定大模型類似這樣返回:
杭州:西湖,知味觀,宋城景區,老頭兒油爆蝦,杭州君悅酒店\n
plan:杭州2,紹興1,寧波2
“杭州”這個key表示當前城市,value地點順序即為依次遊覽的地方(沒有廣告費哈,通義千問給的)。plan中輸出城市與對應遊覽天數,把不必要的換行和空格可以去掉,這樣基本上不存在太多冗餘資訊。
因為我們在prompt里約束了返回的結構,也能很方便的解析出依次遊覽的5個地點以及遊玩城市及其對應的天數等這些所需要的內容,再進行計算只有33tokens了,沒有損失輸出資訊的同時大大減少輸出token量,也提高了不少響應效能。當然我舉的反例比較極端和誇張,但這樣的思路是完全可以移植到其他場景中的。
減少輸出token,是提升全文響應效能最直接的方式之一,可以提前在prompt中進行約束,讓儘量少的輸出token可以包含儘量多的資訊。
2.2 思路2:分解任務,大小尺寸模型分工
示例一,我們來繼續看行程規劃的需求,還需要有對於這個地點的簡短介紹。在上文中,我們基於qwen-plus輸出一天行程地點和整體計劃以保證效果。如果現在增加一個輸出地點介紹的任務,讓qwen-plus來做未免“大材小用”,還會平添大幾十token輸出,響應速度變得更慢。

(圖2:qwen-plus輸出地點與介紹)
輸出地點介紹這樣的簡單任務我們基於qwen-turbo就能完成。即qwen-plus規劃輸出地點後,再基於qwen-turbo生成該地點的簡單一句話簡介。

(圖3:qwen-plus輸出地點,qwen-turbo輸出介紹)
做個實際對比,相同的prompt下分別呼叫qwen-plus和turbo,各自設定seed與temperature,讓每次輸出內容儘量一致。可以看到qwen-turbo在輸出token較多的情況下響應時間還比qwen-plus快了0.4秒,效果上兩者都可接收。
prompt:用一句話介紹這個景點或餐飲店:宋城景區,不超過20字
模型
|
qwen-turbo
|
qwen-plus
|
輸出
|
宋城景區:重現宋代風貌,融合歷史、文化與娛樂的旅遊勝地。
|
宋城景區:穿越千年,夢迴大宋,感受宋代風情。
|
呼叫20次平均輸出tokens
|
18.3
|
16.0
|
呼叫20次平均響應時間
|
0.81s
|
1.20s
|
並且用qwen-turbo輸出景點一句話介紹可以非同步執行,不會影響到qwen-plus輸出規劃地點這一主線任務的時間。
遊玩計劃也可以用qwen-turbo來完成,但可能出現的情況是,使用者輸入“幫我規劃兩天雲南行程”,turbo輸出計劃:在昆明玩兩天,但plus輸出在麗江的一天行程,這樣就前後矛盾了。因此為了保證一致性,最終還是決定將遊玩計劃和當天行程地點一同使用qwen-plus輸出。
示例二,在利用qwen-vl提取圖片資訊場景中(如下圖所示),如果呼叫一次VL大模型一次性返回所有欄位的結構化資訊,如果遇上一些專案欄中文字很長,那麼整體返回的時間就會非常長。

(圖4:利用qwen-vl提取表單資訊)
我們可以考慮根據欄位的長度合理拆分任務,併發呼叫多次VL大模型,每次讓大模型輸出的字元各不相同,最後再把結果拼湊起來,這樣響應速度則只和輸出字數最多的那一欄(申訴說明)有關。當然這樣的方式會使得輸入token翻數倍,需要基於實際情況做最終取捨。
system_prompt_1 = '''
#任務要求
幫我識別出圖片中的運單號,最終只輸出運單號,
沒有運單號則輸出無,不要再輸出其他內容
'''
system_prompt_2 = '''
#任務要求
你是一個圖片識別專家,請提取圖中的資訊,
按順序輸出申訴人姓名,申訴角色,申訴編號,申訴日期,用逗號隔開
'''
system_prompt_3 = '''
#任務要求
你是一個圖片識別專家,請提取圖中的資訊,
只輸出申訴說明,不要再輸出其他內容
'''
system_prompt_4 = ...
總之,在需要大模型完成多個任務、輸出多種內容時,可以考慮合理分解任務,大小尺寸模型分工實現,但前提是分工不能產生前後內容衝突。
2.3 思路3:流輸出,擷取資訊非同步/併發處理
在上面兩個思路介紹中,行程規劃場景我們已經設計好了大模型要完成的任務、要輸出的資料格式,我們還需要呼叫外部的API介面,傳入引數地名來獲取該地的poi資訊。那是否需要等待大模型將5個地點都給出來後再呼叫查詢呢?
並不需要。我們讓輸出行程地點任務的大模型流式輸出,在迴圈接收響應的同時不斷進行正則匹配等方法即時提取出地點地名,便可以透過非同步/併發的手段去查詢poi介面以及呼叫qwen-turbo輸出一句話介紹。
如下圖所示流程,在得到所有結果並完成json組裝後,就可以按順序將seq 1、seq 2、seq 3…立即返回給客戶端,在螢幕上渲染展示給使用者,減少等待時間,提高了使用者體驗。

(圖4:輸出處理流程)
這個思路可以抽象為,我們將大模型的流式輸出,加上其他自定義邏輯,轉換成另一種流式輸出返回給使用者。

(圖5:流式輸出轉換)
2.4 思路4:提前約定,以短代號對映長結果
這個最佳化思路主要應用於讓大模型分類、做選擇題、輸出列表結果等場景,在prompt中我們向大模型提供若干個候選集,基於約束來讓大模型從候選集選出一個結果。
思路被啟發到的是以前專案中使用到的Protocol Buffers(protobuf)資料序列化格式,這個資料格式的思想是說我們提前約定好一個數據結構(.proto檔案),第一個欄位是經度,第二個欄位是緯度,第三個欄位是速度等等,傳輸的時候就不用再longtitude=xxx、latitude=xxx,直接根據預定義的結構編號讀寫資料,省去了許多動態解析的開銷,因此在各種需要高效、跨平臺的資料交換和儲存場景中都表現出色。
示例一:讓大模型做一道選擇題:
#角色
你是一位語言專家,請結合上下文語境,從備選項中選擇一個最適合將空白處補足的語句。
結果不允許編造,只能從備選項中返回一個,只用返回序號和語句,不必再回復其他資訊。
如果沒有合理的,請返回無法選擇。
#問題
xxxxxxxxx(___補足處____)xxxxxxxxxx
#備選項
1.xxxxxxxxx
2.xxxxxxxxx
3.xxxxxxxxx
4.xxxxxxxxx
5.xxxxxxxxx
6.xxxxxxxxx
7.xxxxxxxxx
你的回答:
大模型最終會輸出“2.xxxxxxx”。而我們使用流式輸出,獲取到前兩個tokon"2."時,就已經可以透過正則匹配出序號"2",從而得到後面的完整地址了(因為候選集是我們本地組裝出來的),不必再等待最後的結果,或者你在prompt中限制只輸出序號1/2/3/4也行。
import dashscope
import re
response = dashscope.Generation.call(model=model,
messages=messages,
temperature=0.01,
stream=True,
incremental_output=False,
result_format='message')
# 流式非增量輸出
for res in response:
content = res.output.choices[0].message.content
# 匹配無法選擇
if'無'in content:
return'無法選擇'
# 匹配數字
pattern = r"(\d+)\."
match = re.search(pattern, content)
if match:
number = match.group(1) # 輸出:2
return number
示例二,在資訊提取判斷場景,需要對內容進行多個維度的判斷,輸出yes或no以及no的原因,比如以下prompt片段:
#任務
你是簡歷篩選專家,你需要仔細分析【輸入簡歷】,嚴格依照【篩選標準】
對列舉的每條標準依次進行判斷是否符合。
#篩選標準
1.學歷要求:xxx
2.工作經驗:xxx
3.技術技能:xxx
4.語言能力:xxx
...
如果在輸出中,我們把每個篩選維度的中文當作key輸出,那會平白多出好幾十個字,完全沒有這個必要,因為可以用代號+字典的方式解決。我們再把以下提示詞片段加到上面的prompt後面,這樣輸出的json結果中,key為標準對應的序號1/2/3/4,我們在原生代碼中再把全稱等給解析出來,節省了大幾十個漢字的輸出。
#返回格式
結果以JSON格式返回,y表示符合,n表示不符合。
如果為n,緊跟n後給出判斷依據,但不要編造依據!此外不必再返回其他內容。
#返回示例,按照篩選標準對應的序號作為key
1: y
2: n(要求至少3年相關領域工作經驗,但申請者僅有1年經驗)
3: y
4: y
...
核心思路是,如果可以透過少量的輸出就能得到完整的結果,那就不必等待完整的輸出。
三、總結
本文基於實際場景,分享了作為開發者提高大模型響應效能的四個實用方法。這些思路具有廣泛的適用性,適用於多種場景。核心理念總結為:減少輸出token、選擇合適尺寸的模型以及採用流式輸出。
團隊:公共雲業務-技術服務部
一鍵部署通義千問對話模型
本方案結合通義千問和LangChain技術構建高效的對話模型,該模型基於自然語言處理技術提升語義理解和使用者互動體驗。它可以有效解決傳統對話模型在理解能力和互動效果上的侷限,使得使用者溝通更加自然流暢,被廣泛應用於聊天機器人、智慧客服和社交媒體等多種場景。
點選閱讀原文檢視詳情。