使用LLM时,上下文始终是一个绕不过去的话题。上下文本质上来说可以看作是LLM的短期记忆,能够提供当前对话场景等关键信息。当然,与图灵机终究不可能有无限长的纸带一样,LLM也不可能保存无限长的上下文。虽然如今大多数LLM的上下文长度已经增长到了大多数人都够用的情况,但对于许多上下文高依赖的任务,例如小说续写、角色扮演对话、大型任务模拟(没错!说的就是你,游戏模拟),还是远远不够的。
另外一个相对不那么重要(存疑)的一点就是,大多数API都是按照tokens计费的,因此携带大量上下文的对话也自然会对钱包的伤害更高了。
此时再考虑到上下文保存的对话格式实际上是不那么信息密集的数据格式,于是以此为着力点,我们在本文中探讨一下上下文压缩的可能性。
一个很自然的想法是,不再提供LLM之前的对话作为上下文,而是先对之前的对话进行总结,再"喂"给LLM,这样LLM得到的就是经过高度精炼的上下文信息,因而也就能保存更多的记忆。
那么该怎么进行总结呢?~~总不能每次都由自己来人工概括吧?~~既然我们的主题是LLM,当然得把这份艰巨(无趣)的任务交给LLM啦!很自然地,我们会联想到第二篇博客提到的链式提示技术:由第二个LLM(下文称为工具LLM)来负责对第一个LLM(下文称为原LLM)的对话内容进行概括,然后将工具LLM的输出再作用于原LLM的下一轮对话中。
可这样也有个显而易见的问题:虽然原LLM可以获得精炼后的上下文,但也只不过是将原来处理上下文的任务转移给了工具LLM。虽然用链式提示可以做到每n轮对话让工具LLM概括一遍,再将这些概括内容拼接起来返回给原LLM,但这样的做法还是相当低效且耗费tokens的。
暂且停止对于链式提示的思考,转而考虑下面这个问题:能否只用长度为1的上下文(即LLM只能看到上一轮对话的内容)来维持连贯的对话呢?
既然如此,那么上一轮对话中就不仅需要包含对于用户询问的正常回答,还需要包含之前所有对话的概要信息。而这正是格式化输出发挥作用的地方。
你需要控制输出,用于在上下文长度为1时的情况,仍然能持续获得当前对话的大致场景。 输出分为两个部分, 第一部分是对用户问题的正常回答。 第二部分是对于这一次对话以及之前对话的总结,用来给下一轮对话作参考,如果能够看到上一轮对话输出的第二部分,那么总结需要同时概括上一轮对话输出第二部分的内容以及这一轮对话。
上面是我自己写的一个非常简陋的提示词,但在下面的实验中已经能取得比较好的效果:
Input:
福建的气候是什么样的?
Output:
第一部分回答: 福建属于亚热带季风气候,主要特点是温暖湿润、雨量充沛。具体特征包括: 1. 四季分明但冬季较短 2. 年平均气温17-21℃ 3. 年降水量1000-2000毫米 4. 夏季多台风影响 5. 沿海与内陆山区气候差异明显 第二部分对话总结: [当前对话]用户询问福建气候特征,已回复福建属于亚热带季风气候,并详细说明了温度、降水、台风影响等主要气候特点。 [历史对话]无
Input:
江西的呢?
Output:
第一部分回答: 江西的气候也属于亚热带季风气候,与福建相似但有差异: 1. 四季更分明,冬季比福建稍冷 2. 年平均气温16-19℃ 3. 年降水量1400-1900毫米 4. 春季多阴雨,夏季有伏旱 5. 地形影响明显,盆地比周边地区更热 第二部分对话总结: [当前对话]用户询问江西气候,已说明其亚热带季风气候特征,并与福建气候进行了对比,指出温度、降水等方面的差异。 [历史对话]上一轮讨论了福建气候特征,包括温暖湿润、多台风等特点。两省同属亚热带但存在差异。
Input:
两省谁更宜居?
Output:
第一部分回答: 从宜居性角度对比福建和江西: 福建优势: 1. 冬季更温暖(沿海地区基本无霜冻) 2. 空气质量整体较好(受海洋调节) 3. 经济更发达,基础设施完善 江西优势: 1. 夏季极端高温较少(山区凉爽) 2. 房价和生活成本较低 3. 四季分明的气候体验 建议选择标准: - 怕冷选福建沿海 - 预算有限选江西 - 喜欢分明四季选江西内陆 - 重视医疗教育选福州/厦门 第二部分对话总结: [当前对话]对比分析了福建与江西的宜居性,从气候舒适度、经济条件、生活成本等方面列出具体差异,并给出选择建议。 [历史对话]前两轮分别讨论了福建和江西的气候特征,已建立两省同属亚热带但存在细节差异的认知基础。本次宜居性比较延续了气候对比的主线。
可以看到,在使用本提示词后,LLM在正常回答用户输入之外,还能在上下文长度为1的情况下对先前对话与本次对话进行总结。这样总结时只需要在原有总结的基础上加上本轮对话内容,如此不断迭代。
理想情况下,使用结构化输出技巧,LLM的输入为用户提问与上一轮对话的总结部分,输出为正常回答与新一版总结,而只呈现给用户正常回答内容。这样用户很难觉察到实际在以消耗极少tokens的状态下运行,而理论上总结部分依然能保留非常久远的关键信息。
当然,刚才的提示词实际上只有控制上下文的部分,当提示词变得格外复杂,截然不同的两部分输出难免会互相影响,此时还是需要用链式提示技巧来让两个LLM互相协作完成任务。
第一个LLM,也就是原LLM,负责按照提示词处理用户任务,输出结果同时呈现给用户并传输给工具LLM。
第二个LLM,也就是工具LLM,负责概括原LLM本轮对话的内容,并结合自己之前概括的部分(如果有的话),输出同时传给原LLM作为上下文与作为自己下一轮对话的输入。
如此一来,两个LLM的输出都作为对方输入的一部分,在不断的自噬中消化对话内容作为上下文,真是个精美的衔尾蛇结构。
当然,以上的内容我并没有什么机会做细致的实验来验证成效,所做的依然只是理论上的探索。
让LLM来对对话进行概括也并非没有风险,即使向其告知概括的内容将作为上下文的一部分,也难免会在对话过程中遗漏关键信息。我们能尽力做的,只不过是精简原本对话上下文的冗余内容而已,还是难以违背信息熵的基本规律。当然,未来也有可能训练出专门用来处理上下文的大模型,那时候只需要输入主题与重点内容,再结合对话,大模型就能自动给原LLM提供最优化的信息,等等等等……
那么,本系列关于LLM奇思妙想的系列就差不多到此结束了。想来也是有些感慨,小时候玩游戏时遇到稍微智能一些的NPC都会格外惊喜,更别提还能和我谈天说地了。而如今,AI的快速发展让我都觉得有些恍惚。
我也不太确定自己是否真的喜欢AI,喜欢LLM,但我依然记得,当时给那如今看来已有些简陋的大模型敲下第一句问候时,那心中的兴奋与喜悦。