引言

如果将大语言模型比作大脑,那么「上下文窗口」(context window)就衡量着它的「记忆容量」。这个重要的参数代表模型一次性可以读取的最大文本长度。上下文窗口越大,模型在「思考」时能调用的素材就越多,理解对话和任务背景就越充分,由此生成的答复也就会更加准确和相关。

在过去两年的快速发展中,大语言模型的上下文窗口已经见证了长足的进步,也成为厂商之间比拼激烈的参数之一。以最流行的 GPT 系列模型为例,两年前的早期版本 GPT-3 还只能支持 2048 tokens 的上下文窗口。1 经过历次更新,目前版本的 GPT-3.5 Turbo 可以支持 16K [16,385 tokens],GPT-4 Turbo 可以支持 128K。

OpenAI 的主要竞争对手中,Anthropic 的 Claude 系列模型一向以长上下文支持为特色,在 2023 年 5 月就率先支持了 100K 上下文,今年初发布的 Claude 3 系列模型还进一步提升到了 200K。Google 的 Gemini 系列模型从 32K 起步,目前还在小范围内测的 1.5 Pro 则达到了 1M。

上个月,国内模型厂商月之暗面宣布实现将旗下 Kimi 智能助手的上下文窗口从 20 万字提高到 200 万字2,并开启申请内测。经过联系,我们获得了提前体验资格,并有机会直接向 Kimi 团队提出一些问题。

Kimi 团队告诉我们,之所以将长上下文作为一个优先研发方向,有一定战略上的考虑。首先,在 Kimi 团队看来,在通往通用人工智能(AGI)的过程中,无损的长上下文将会是一个很关键的基础技术;历史上所有的模型架构演进,本质上都是在提升有效的、无损的上下文长度。同时,他们认为,更长的上下文窗口可以进一步打开对 AI 应用场景的想象力。

效果实测

那么,Kimi 长上下文支持的效果究竟如何呢?

之前,国外厂商在介绍具有长上下文能力的模型时,经常会选择使用一种称为「大海捞针」(Needle in a Haystack, NIAH)的基准测试。这项测试的方法是:将随机事实或陈述(「针」)放在文本中,然后向模型提问,考察其能否从文本中找到那根「针」;然后,将针的位置(「深度」)不断后移,文本不断加长,从而得到一个成绩矩阵。Gemini 1.5Claude 3 的技术报告均援引了大海捞针的成绩。

Kimi 团队没有针对此次更新的长上下文能力做大海捞针测试。他们表示,这项测试刚出现时是个很好的测试指标,但随着这项测试的流行,任何模型只要稍加优化就可以做到「全绿」,也就是通过所有深度和长度组合条件下的测试,因此已经失去了对照意义;此外,大海捞针对大模型而言,实际上也是一项比较简单的任务,不能全面体现模型的能力。不过,Kimi 团队仍然表示对于相比竞品的效果很有信心。

因此,在测试和体验 Kimi 的长上下文能力时,我也没有采用基准测试的方法,而是根据过去想到过、但囿于上下文长度限制未能实现的几个需求,设计了几个具体场景来评估 Kimi 的表现。

市场分析

今年以来的 IPO 中,比较引起市场关注的事件肯定包括年初蜜雪冰城、古茗同时递表,随后也出现了大量分析和对比两家公司招股书的文章。招股书属于典型的篇幅冗长、结构固定的八股文本,本身就很适合交给 AI 辅助分析;但之前囿于长度限制,一份都难以容纳,更不用说对比。

首先从港交所下载两份文件(蜜雪冰城古茗),确认一下文件长度:

pdftotext mixue.pdf - | wc -m
# 369351
pdftotext goodme.pdf - | wc -m
# 420975

(这里的 pdftotext 命令用来从 PDF 中提取纯文本;你可以通过习惯的包管理器安装 poppler 来获得这个程序。)

即两份文件加起来字符数约 80 万。将文件上传到 Kimi,简单交代背景和一般要求。

首先问两者在品类和价格段上的区别,以及取得的成绩。这些一般会写在开头的序言部分和中间位置的「业务」一章。Kimi 找的也是比较全面的。

再看一个稍微进阶的例子:分析两家公司在供应链和物流方面的异同。

可以看到,古茗部分的总结是比较准确的,蜜雪冰城部分则稍有瑕疵:前半段的「门店网络广」并不属于「供应链」的特点,更合适的引用应当是其配送网络的相关数据。此外,原因总结部分提到业务模式和市场定位是正确的,但没有能进一步考虑这是为了适应不同类型主推饮品的需要。作为对比,晚点的文章就分析道,「蜜雪冰城的物流配送服务的特点是覆盖广、深入。[…] 古茗则拥有更强大的冷链物流能力,这和其菜单上的水果茶占比有关。」

(提示词中的「客观、中立」等要求是根据 Kimi 团队的提示添加的。如果没有这句话,Kimi 容易过于依赖公司自己的描述,导致回答有些「广告味」。另一个小瑕疵是,从这里开始,Kimi 也没有记住最初提示词中「标注页码」的要求,需要重新在后续提示词中要求一次。)

接下来关注招股书里比较重要的风险披露部分。这一部分可能是最能反映「天下文章一大抄」的,很多时候你能看到同样一段轱辘话在十几年间代代相传。但就是因为套话太多,那些真正原创的片段往往就更显重要。显然,几十页的篇幅靠人眼对比是很痛苦的,而 AI 显然比较擅长于此:

最后问的是两个法律角度会比较关心的问题:历史沿革和公司架构。

Kimi 的回答不是特别切题,主要问题在于过于宽泛地理解了「历史沿革」的含义——它在招股书语境下指的主要是集团公司历史上的设立、变更和股权交易情况,而不是「公司的发展史」(即使特别在提示词中做出了解释仍然如此)。公司架构方面则基本没有提供有效的信息,例如一般会关注的境外公司架构、老股东情况等都没有提及。

辅助编程

生成式 AI 辅助编程是一个非常热门的应用。但是,现有方案往往不能充分反映现有代码库的情况。即使是 GitHub Copilot 这样基于插件的方案,也只能从代码库中挑选一些片段发送给模型参考。更长的上下文窗口给送出更全的现有代码提供了可能。

这里以 GitHub 上的 oduwsdl/CarbonDate 项目为例,这是一个通过提取多种元信息,试图推测网页发布时间的有趣工具,我曾在之前的文章中以它为例说明如何从他人代码库中「取经」。下面来看看 Kimi 对它的理解程度如何。


插曲:如何让 Kimi 处理整个目录的文件

测试这个场景时遇到的一个小问题:虽然 Kimi 支持批量上传文件,但不支持上传文件夹,从而无法反映路径层级中包含的信息。因此,我写了一个简单的脚本,将指定目录中的所有文件(不含隐藏文件和隐藏目录中的文件)及其路径都汇总到一个 JSON 文件中(本例中的长度为 150 万字符,你可以自己下载测试),然后再输入给 Kimi:

#!/bin/bash

DIR=$1
echo "[" > output.json # open the outer array
find "$DIR" -type f \( ! -regex '.*/\..*' \) -print0 |\
while IFS= read -r -d $'\0' file; do
    jq -n \
        --arg path "$file" \
        --arg content "$(cat "$file")" \
        '{path: $path, content: $content}' >> output.json
    echo "," >> output.json
done
sed -i '' '$ s/,$//' output.json # remove the dangling comma
echo "]" >> output.json # close the outer array

(用 shell 是因为……我只会这个。)


首先问几个简单的问题:语言、依赖项、readme 文件内容,以及稍微进阶的工作原理分析。

应该说 Kimi 的回答总体上是令人满意的。特别是第 3 问,可以看出不仅充分理解了问题,而且能根据不同程度的可用信息做合理推断。反倒是第 1 问中的依赖项总结有些瑕疵,这本来可以通过查看 requirements.txt 的内容快速找到答案,但 Kimi 似乎更多依赖于 readme 中的信息,并且杂糅了一些本质上是运行要求(prerequisites)的说明。

接着再尝试一个编程任务:额外提供另一个搜索引擎 Kagi 的 API 文档,要求仿照现有模块写一个从 Kagi 搜索结果页提取网页发布日期的新模块,并分析需要修改哪些其他文件来整合这个新模块。

就生成的代码而言,基本的结构还是正确的,但很遗憾并没有充分理解新文档中提供的响应范例——其中明确指出搜索结果存放在响应的 data 数组中,而不是生成代码试图访问的 items 数组。至于第 2 问,关于 main.py 的修改建议是合适的,其余几项就与实际情况不符了,可能是过于宽泛地套用「Python 模块」相关语料的产物。

内容管理

少数派每年都会举行一次年度征文活动,历年获奖文章普遍被认为是最能代表少数派用户水平和风格的作品。从内容运营角度,我们希望能更好发挥这些文章的长尾价值,例如做更好的标签归类等;也希望从中得出一些观察和分析,进一步了解我们的作者,将以后的活动办得更好。但由于文章数量多、人力有限,过去这方面的努力做得不够。Kimi 的 200 万字上下文足够容纳 2016 年以来的所有获奖征文,这给了我用它来做些分析的动力。

先做一些数据准备工作。用 MarkDownload 工具批量将历年征文下载到本地,然后归类到按年份命名的文件夹中。然后,仍然用上一节提到的脚本,稍作修改,将所有 markdown 文件汇总到一个 2.75MB、字符数为 134.3 万的 JSON 文件中(你可以自己下载测试)。将它和对文件结构的简单介绍一起发送给 Kimi。

首先提几个简单的概括总结类问题:

其中:

  • 答得比较好的是第 3 问,对少数派用户特点的总结与我们的理解是一致的。
  • 第 1 问和第 2 问的开头答得也都不错,对于常见主题和风格的总结比较到位;但之后的举例和展开就显得过于宽泛,属于 AI 喜欢说的那种「正确的废话」。
  • 第 4 问则没有给出特别有效的趋势观察。

接下来是一个我比较期待的场景:借助 AI,基于对文章内容的分析自动提炼主题和标签,并且以一种结构化的格式输出。如果这是可行的,就能给内容维护的自动化带来不少便利。

遗憾的是,经过反复尝试,Kimi 虽然体现出一些可能性,但现阶段还不能稳定地给出可以直接使用的结果:

  • 在所有尝试中,无论如何在提示词中强调,Kimi 都不能完整输出全部 110 篇文章的分类结果,甚至宁愿在结尾颇具幽默感地在注释里「喃喃自语」: // ... (Continue with the remaining articles, ensuring a total of 110 objects in the array)。这里怀疑可能是网页版对输出长度有一些限制。
  • 至于结果本身,虽然结构符合要求,但所选的主题和标签都比较机械,大多数是用标题中的词语重组或改写而来,并不适合作为标签文本,也没有反映标题之后的正文内容。

还有不少情况下,Kimi 输出的 JSON 格式是错误的,例如把每行都用一对大括号包裹起来。诚然,如果是在生产环境下,肯定也需要在模型输出之后加一个有效性校验环节,但不能一步到位毕竟还是有些遗憾。


随后,我又以自己的个人博客归档(162 万字符,下载)为输入做了一些测试。

首先提问一些基础事实问题。Kimi 的表现很稳定,除了最后一篇从英文翻译的引用有点生硬,其他跟我自己会写出的问答也没什么区别。

接着还是尝试了总结和分析类的任务。其中,主题总结的结果跟我的印象是差不多的,但选择的示例文章则有一些瑕疵,特别是「生活经历」部分选择的后两篇其实是技术主题文章。

小结

就展示长上下文的潜力而言,Kimi 已经实现了有意义的第一步。概括起来,目前版本的优点在于,能够有效读取复杂文档,准确完成事实查询和要点提炼,可以满足从长篇幅或大批量文本中提取信息的需求。但不足在于,对于文档的深度理解和逻辑推理能力、以及在多轮对话中遵守用户指令的能力,还有较大提高空间。因此,对于需要深入理解特定领域知识的任务,Kimi 还无法完全满足需求,或至少需要一定程度的人工校验和调整。

支持长上下文也对 Kimi 的算力资源提出了更高的要求。事实上,就在这一功能开始测试后不久,Kimi 就经历了一次流量增加超预期的事件,不得不短时间内多次扩容应对迅速增加的需求。对此,Kimi 团队表示,200 万字上下文确实要求进一步大幅降低推理成本,目前正在全力解决。在商业化方面,用户关心较多的付费方案也将在今年初步确定。

此外值得一提的是,Kimi 的 200 万字支持推出后,不少其他国内模型厂商也相继跟进,宣布支持 500 万甚至 1000 万字的输入。但是,相关宣传中存在一些技术细节的显著缺失——支持读取并不代表模型能一次性处理;那些看起来更惊人的指标实际上是使用 RAG(retrieval augmented generation,检索增强生成)方案达到的效果。简单来说,RAG 是先对输入文档做一次「嵌入」处理(embedding,即将文档内容转储为能反映其特征的向量)。查询时,先通过向量运算找出与查询语句最相关的文档,再将这一小部分内容输入给模型生成回答。

因此,如果说上下文窗口是模型的记忆,那么 RAG 就像是为了减轻记忆负担而准备的辅助外脑。这么做的好处是绕过了上下文窗口的限制:无论输入的文本有多长,最终输入给模型的长度都是可控的,生成速度也自然比一次性处理完整输入更快,成本开销更低;但相应的代价就是检索的准确性和完整性相对较弱。

指出这点这并不是要否认 RAG 的价值。至少目前阶段,RAG 的特性仍然使它更胜任那些对成本和速度较为敏感的应用场景,也更容易实现规模化,不需要等待模型的进化就能接受更长的输入。但是,厂商和媒体在宣传和报道时,确实应当秉持更加严谨、诚实的态度,准确向用户描述和解释技术背景,而不是简单地强调一些数字。一个理性、知情的消费者群体,也是更有助于促进 AI 服务普及、开展良性竞争的。