作为开发者,我如何提高任务型大模型应用的响应性能

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
简介: 本文基于实际场景,分享了作为开发者提高大模型响应性能的四个实用方法。

1.背景

1.1 序言


大模型的响应速度(首包和全文),直接影响用户使用体验。现阶段来看,想要有较高的响应性能,我们可以选择小尺寸模型,但意味着复杂场景下效果得不到保障;可以让大模型“吐”更少的字,但意味着不能完成更多的任务。对于大模型来说,速度、效果、功能似乎是一个“不可能三角”。


本文主要分享我在实现大模型场景过程中,对于任务型应用,在保证一定效果的前提下,站在开发者角度如何提升响应速度和完成更多任务的几个思路。


任务型应用,主要指帮助用户完成特定任务,例如预约餐馆、预定机票、给出规划等,与用户的交互大多是结构化的,我们通常要求它输出结构化数据以便进行下一步处理。相应地,生成式对话应用我们称之为“闲聊”,其主要目的是进行自然的、开放式的互动,根据给定的对话历史和上下文信息生成连贯的自然语言回复,而不是完成具体任务。


先说结论,后文再根据实际场景案例再做详细说明:

  1. prompt约束输出结构,减少输出token;
  2. 分解任务,大小尺寸模型分工;
  3. 流输出,截取信息异步/并发处理;
  4. 提前约定,以短代号映射长结果;


1.2 场景案例描述


以基于大模型做一个行程规划推荐应用的场景为例,用户通过ASR语音输入“帮我规划五天的浙江行程,杭州玩两天吧”,希望得到:


  1. 每天要游览的地点名;
  2. 每个地点的简短介绍;
  3. 游玩城市与天数计划;
  4. 地点的poi信息(Point of Interest,泛指互联网电子地图中的点类数据,基本包含名称、详细地址、经纬度坐标、地点类别四个属性,以及营业时间、人均消费,图片示例等信息);
  5. 前后地点的行驶距离与时间;
  6. json结构化结果,以便解析在页面上展示;


分析这个场景需求,需要大模型输出的任务有根据用户意图,生成推荐的地点名、地点的介绍、对应的游玩城市与天数。需要调用外部API获取的有,地点poi信息、前后地点的行驶距离与时间。简单设计后约定客户端服务端这样进行交互:


a.客户端乘客语音输入“帮我规划五天的浙江行程,杭州玩两天吧”,调用服务端接口;

# 请求
{
  "query": "帮我规划五天的浙江行程,杭州玩两天吧",
  "history": []
}


b.服务端内部根据用户意图,输出一天的行程地点、游玩计划的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"
    }
  ]
}


c.客户端获取到结果,渲染第一天的行程展示。


image.png

d.同时,根据游玩城市和天数,客户端将历史行程添加为参数继续调用服务端接口,获取下一天不重复的行程。

# 请求
{
  "query": "帮我规划五天的浙江行程,杭州玩两天吧",
  "history": []
}

e.以此类推,直到遍历完城市和天数意图。



2.优化思路详解与示例

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输出,响应速度变得更慢。


image.png

输出地点介绍这样的简单任务我们基于qwen-turbo就能完成。即qwen-plus规划输出地点后,再基于qwen-turbo生成该地点的简单一句话简介。


image.png


做个实际对比,相同的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大模型一次性返回所有字段的结构化信息,如果遇上一些项目栏中文字很长,那么整体返回的时间就会非常长。


image.png

我们可以考虑根据字段的长度合理拆分任务,并发调用多次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...立即返回给客户端,在屏幕上渲染展示给用户,减少等待时间,提高了用户体验。


image.png

这个思路可以抽象为,我们将大模型的流式输出,加上其他自定义逻辑,转换成另一种流式输出返回给用户。


image.png



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
...

核心思路是,如果可以通过少量的输出就能得到完整的结果,那就不必等待完整的输出。


3.总结


本文基于实际场景,分享了作为开发者提高大模型响应性能的四个实用方法。这些思路具有广泛的适用性,适用于多种场景。核心理念总结为:减少输出token、选择合适尺寸的模型以及采用流式输出。





来源  |  阿里云开发者公众号
作者  |  舟谨

相关文章
|
存储 编译器
深入解析i++和++i的区别及性能影响
在我们编写代码时,经常需要对变量进行自增操作。这种情况下,我们通常会用到两种常见的操作符:i++和++i。最近在阅读博客时,我偶然看到了有关i++和++i性能的讨论。之前我一直在使用它们,但从未从性能的角度考虑过,这让我突然产生了兴趣。尽管它们看起来相似,但它们之间存在微妙而重要的区别。在本文中,我们将详细解释i++和++i之间的区别,以及它们对代码性能的影响。
396 1
深入解析i++和++i的区别及性能影响
|
24天前
|
缓存 算法 JavaScript
_.isEqual 方法在处理大型对象时的性能如何?
【10月更文挑战第29天】`_.isEqual` 方法在处理大型对象时性能存在一定的挑战,但通过其自身的优化机制以及结合适当的优化策略,仍然能够在许多场景下满足对大型复杂对象进行深度比较的需求。在实际使用中,需要根据具体情况综合考虑性能和功能的平衡,以选择最合适的比较方法。
|
7天前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习中的兼容性函数:原理、类型与未来趋势
深度学习中的兼容性函数:原理、类型与未来趋势
|
2月前
|
JSON 计算机视觉 数据格式
LangChain-17 FunctionCalling 利用大模型对函数进行回调 扩展大模型的额外的能力 比如实现加减乘除等功能
LangChain-17 FunctionCalling 利用大模型对函数进行回调 扩展大模型的额外的能力 比如实现加减乘除等功能
44 4
|
4月前
|
存储 监控 Serverless
函数计算发布功能问题之用户在使用主流函数计算产品的日志服务时可能会遇到使用成本的问题如何解决
函数计算发布功能问题之用户在使用主流函数计算产品的日志服务时可能会遇到使用成本的问题如何解决
|
5月前
|
自然语言处理 API 开发工具
PAI如何处理不同编程语言的混合任务?
【7月更文挑战第1天】PAI如何处理不同编程语言的混合任务?
114 57
|
5月前
|
机器学习/深度学习 自然语言处理 数据挖掘
RouteLLM:高效LLM路由框架,可以动态选择优化成本与响应质量的平衡
新框架提出智能路由选择在强弱语言模型间,利用用户偏好的学习来预测强模型胜率,基于成本阈值做决策。在大规模LLMs部署中,该方法显著降低成本而不牺牲响应质量。研究显示,经过矩阵分解和BERT等技术训练的路由器在多个基准上提升性能,降低强模型调用,提高APGR。通过数据增强,如MMLU和GPT-4评审数据,路由器在GSM8K、MMLU等测试中展现出色的性能提升和成本效率。未来将测试更多模型组合以验证迁移学习能力。该框架为LLMs部署提供了成本-性能优化的解决方案。
148 2
|
7月前
|
缓存 Java Android开发
构建高效的Android应用:内存优化策略解析
【5月更文挑战第25天】在移动开发领域,性能优化一直是一个不断探讨和精进的课题。特别是对于资源受限的Android设备来说,合理的内存管理直接关系到应用的流畅度和用户体验。本文深入分析了Android内存管理的机制,并提出了几种实用的内存优化技巧。通过代码示例和实践案例,我们旨在帮助开发者识别和解决内存瓶颈,从而提升应用性能。
|
7月前
|
自然语言处理
LLM上下文窗口突破200万!无需架构变化+复杂微调,轻松扩展8倍
【5月更文挑战第12天】LongRoPE研究突破LLM上下文窗口限制,无需架构变更和复杂微调,实现8倍扩展至2048万个token。该方法利用位置嵌入非均匀性,通过高效搜索和优化初始化,适用于处理长文本任务,对模型性能影响小。但可能需要较多计算资源,且2048万的长度是否足够所有任务尚待探讨。[论文链接](https://arxiv.org/abs/2402.13753)
172 1
|
6月前
|
机器学习/深度学习 人工智能 分布式计算
PAI底层支持多种计算框架
PAI底层支持多种计算框架:
122 0