这篇长文解决的是「demo 容易、产品难」的落地断层:它把散落在论文与业界的做法收敛成七种可复用的模式。

最值得拿走的是它的组织框架——七种模式沿「性能 vs. 成本/风险」和「靠近数据 vs. 靠近用户」两条轴排布,评估(evals)是一切的起点,防御式 UX 和反馈飞轮则把可靠性责任延伸到产品层。最容易误用的地方在缓存与护栏:语义相似缓存看似省成本,实则在《碟中谍 2》变《碟中谍 3》这类案例上翻车;护栏不是加几句 prompt,而是结构、语法、安全、事实四层验证的组合。

中文读者为什么要读这篇:国内大量 LLM 落地讨论停留在 prompt 技巧,而这篇把评估数据集建设、混合检索、LoRA 系微调、缓存命中率、人机交互指南这些生产级议题串成完整体系,是少见的系统化参照系。叠加国内语境时,可关注国产开源模型与合规要求对微调、数据飞轮决策的影响。

读完后的行动:为你的 LLM 功能先建一组 20-50 条的任务专属 evals,再谈其他优化。

—— FDEChina编辑部 · 架构师视角

有一大类问题,想象起来容易、做出 demo 也容易,但要把它做成产品却极其困难。比如自动驾驶:让一辆车绕着街区自动行驶的 demo 很容易,但把它做成产品需要十年。——Karpathy(原文推文

这篇文章讨论的是把大语言模型(LLM)集成到系统与产品中的实用模式。我们会建立在学术研究、行业资源和从业者经验之上,把它们提炼成关键的理念与实践。

共有七种关键模式。它们沿着两条轴组织:一条轴是提升性能 vs. 降低成本/风险,另一条轴是更靠近数据 vs. 更靠近用户。

  • 评估(evals):度量性能
  • 检索增强生成(RAG):引入新鲜的、外部的知识
  • 微调(fine-tuning):让模型更擅长特定任务
  • 缓存(caching):降低延迟与成本
  • 护栏(guardrails):确保输出质量
  • 防御式 UX(Defensive UX):预见并优雅地管理错误
  • 收集用户反馈:构建我们的数据飞轮(data flywheel)

(另可参见这篇补充文章,了解如何把这些 LLM 模式匹配到潜在问题上。)

图:LLM 模式——从数据到用户,从防御到进攻(见各模式之间的联系)

模式一:评估(Evals)——度量性能

评估(evals)是用于评估模型在某个任务上表现如何的一组测量手段,包括基准数据(benchmark)与指标(metric)。引用一条 HackerNews 评论

团队有多重视 evals,是那些匆忙堆出垃圾的人和那些认真在这个领域做产品的人之间的主要分水岭。

为什么需要 evals?

Evals 让我们能够度量系统或产品做得怎么样,并发现任何回退(regression)。(一个系统或产品可以由多个组件构成,比如 LLM、prompt 模板、检索到的上下文,以及 temperature 这类参数。)一组有代表性的 evals 让我们朝着大规模度量系统变化迈进一步。没有 evals,我们就等于在盲飞,或者每次改动都得靠肉眼逐一检查 LLM 输出。

关于 evals 的更多内容

语言建模领域有许多基准,其中一些知名的包括:

  • MMLU:包含 57 个任务,涵盖初等数学、美国历史、计算机科学、法律等。要在其上表现良好,模型必须具备广博的世界知识与问题解决能力。
  • EleutherAI Eval:通过 zero/few-shot 设置在 200 个任务上测试模型的统一框架,纳入了大量 evals,包括 BigBench、MMLU 等。
  • HELM:不局限于具体任务和指标,而是跨领域对 LLM 做全面评估。指标包括准确性、校准、鲁棒性、公平性、偏见、毒性等;任务包括问答、信息检索、摘要、文本分类等。
  • AlpacaEval:自动评估框架,衡量一个强 LLM(如 GPT-4)在多大程度上偏好某个模型的输出而非参考模型的输出。指标包括胜率、偏见、延迟、价格、方差等。经 2 万条人工标注验证,与人工判断高度一致。

我们可以把指标分成两类:依赖上下文的与不依赖上下文的。

  • 依赖上下文(context-dependent):这类指标会把上下文考虑进来,通常是为某个特定任务提出的;把它们挪用到其他任务上需要一些调整。
  • 不依赖上下文(context-free):这类指标在评估生成结果时不 tied 到上下文,只把输出与给定的金标准参考答案做比较。由于与任务无关,它们更容易应用到各种任务上。

为了更好地理解这些指标(以及它们潜在的缺陷),我们来考察几个常用指标,如 BLEU、ROUGE、BERTScore 和 MoverScore。

BLEU(Bilingual Evaluation Understudy)是一种基于精确率(precision)的指标:它统计生成输出中同时出现在参考答案里的 n-gram 数量,再除以输出的总词数。它主要用于机器翻译,并因成本低而一直流行。

首先计算不同 n 值下的精确率:

precision_n = ( Σ_{p ∈ output} Σ_{n-gram ∈ p} Count_clip(n-gram) ) / ( Σ_{p ∈ output} Σ_{n-gram ∈ p} Count(n-gram) )

Count_clip(n-gram) 会按该 n-gram 在任一对应参考句中出现的最大次数进行截断:

Count_clip(n-gram) = min( 匹配到的 n-gram 数, max_{r ∈ R}( n-gram 在 r 中出现的次数 ) )

计算出各 n 下的精确率后,最终的 BLEU-N 得分取所有 precision_n 的几何平均。

然而,由于精确率只看 n-gram、不考虑生成输出的长度,一个只包含一个常见词(如停用词)的输出就能拿到完美精确率。这会产生误导,并鼓励模型输出更少的词来抬高 BLEU 分数。为了对抗这一点,引入了简短惩罚(brevity penalty,BP),惩罚过短的句子:

BP = 1                      若 |p| > |r|
   = e^(1 - |r|/|p|)        其他情况

于是最终公式为:

BLEU-N = BP · exp( Σ_{n=1}^{N} W_n · log(precision_n) )

ROUGE(Recall-Oriented Understudy for Gisting Evaluation):与 BLEU 相反,ROUGE 是面向召回率(recall)的。它统计参考答案中出现在输出里的词数,通常用于评估自动摘要任务。

ROUGE 有若干变体。ROUGE-N 与 BLEU 最相似,同样统计输出与参考之间匹配的 n-gram 数量:

ROUGE-N = ( Σ_{s_r ∈ references} Σ_{n-gram ∈ s_r} Count_match(n-gram) ) / ( Σ_{s_r ∈ references} Σ_{n-gram ∈ s_r} Count(n-gram) )

其他变体包括:

  • ROUGE-L:衡量输出与参考之间的最长公共子序列(LCS)。它考虑句子级结构相似性,聚焦于按顺序共同出现的最长 n-gram 序列。
  • ROUGE-S:衡量输出与参考之间的 skip-bigram。skip-bigram 是保持句子顺序的词对,不论两词之间夹着什么词。

BERTScore 是基于嵌入(embedding)的指标,用余弦相似度将生成输出中的每个 token 或 n-gram 与参考句进行比较。它由三部分组成:

  • 召回率:参考中每个 token 与其在生成输出中最近匹配之间的平均余弦相似度。
  • 精确率:生成输出中每个 token 与其在参考中最近匹配之间的平均余弦相似度。
  • F1:召回率与精确率的调和平均。
Recall_BERT  = (1/|r|) · Σ_{i ∈ r} max_{j ∈ p} iᵀj
Precision_BERT = (1/|p|) · Σ_{j ∈ p} max_{i ∈ r} iᵀj
BERTScore = F_BERT = 2 · P_BERT · R_BERT / ( P_BERT + R_BERT )

BERTScore 的价值在于它能处理同义词与改写。BLEU、ROUGE 这类更简单的指标依赖精确匹配,做不到这一点。BERTScore 已被证明在图像描述、机器翻译等任务上与人工判断有更好的相关性。

MoverScore 同样使用上下文化嵌入来计算生成输出与参考中 token 之间的距离。但与基于 token 一对一匹配(或称「硬对齐」)的 BERTScore 不同,MoverScore 允许多对一匹配(或称「软对齐」)。

图:BERTScore(左)vs. MoverScore(右)(来源)

MoverScore 能把一个序列中语义相关的词映射到另一个序列中的对应词。它通过求解一个带约束的优化问题来实现——找到把一段文本变换成另一段所需的最小「搬运量」。其思想是度量词语要把一个序列转换成另一个序列需要移动的距离。

然而,使用这些传统基准和指标存在若干陷阱。

第一,这些指标与人工判断的相关性很差。BLEU、ROUGE 等与人类对流畅度的评估甚至呈负相关;与人工充分性(adequacy)分数也只是中度或更低的相关。尤其 BLEU 与 ROUGE 在需要创造性和多样性的任务上相关性很低

第二,这些指标对更广泛任务的适应性往往很差。把为一个任务提出的指标挪用到另一个任务上并不总是明智的。例如,BLEU、ROUGE 这类精确匹配指标不适合抽象式摘要或对话这类任务:它们基于输出与参考之间的 n-gram 重叠,而对话任务的合理回答千差万别,一个与参考 n-gram 重叠为零的输出也可能是个好回答。

第三,这些指标的可复现性很差。即使是同一个指标,不同研究报告的方差也很大,可能源于人工判断收集方式或指标参数设置的不同。另一项针对 2000 项研究中 ROUGE 分数的调查发现,这些分数难以复现、难以比较,而且常常是错的——因为 evals 往往用未经测试、有错误的 ROUGE 实现来跑。

图:用 ROUGE 评估模型的若干维度(来源)

即便是 MMLU 这样的新基准,同一个模型也会因 eval 实现不同而得到差别很大的分数。Huggingface 对比了原始 MMLU 实现与 HELM、EleutherAI 的实现,发现同一道题在不同提供方那里的 prompt 可能不同。

图:同一道题在不同 MMLU 实现中的 prompt 不同(来源)

此外,三者的评估方式也各不相同:

  • 原始 MMLU:只比较选项(A、B、C、D)上的预测概率。
  • HELM:使用模型给出的下一个 token 概率分布,选概率最高的 token,即使它不是任何选项。
  • EleutherAI:计算完整答案序列(即字母后跟答案文本)的概率,然后选概率最高的答案。

图:同一道题在不同 MMLU 实现中的评估方式不同(来源)

结果是,即使是同一个 eval,绝对分数和模型排名也会随实现细节大幅波动。这意味着模型指标并不真正可比——即便是同一个 eval——除非实现在 prompt、分词这样的细枝末节上都完全一致。类似地,QLoRA 的作者发现 MMLU 过于敏感,并得出结论:「不要使用、报告或相信 MMLU 分数」

除了上述传统 evals,一个新兴趋势是使用一个强 LLM 作为无需参考答案的指标来评估其他 LLM 的生成结果。这意味着我们也许不再需要人工判断或金标准参考。

G-Eval 是一个把 LLM 与思维链(Chain-of-Thought, CoT)以及表单填写范式结合来评估 LLM 输出的框架。他们先向 LLM 提供任务介绍与评估标准,让它生成评估步骤的 CoT;然后,以评估新闻摘要的连贯性为例,把 prompt、CoT、新闻文章和摘要拼接起来,让 LLM 输出 1 到 5 之间的分数;最后,用 LLM 输出 token 的概率对分数做归一化,取其加权和作为最终结果。

图:G-Eval 概览(来源)

他们发现 GPT-4 作为评估者与人工判断的 Spearman 相关性很高(0.514),超过了之前所有方法。在连贯性、一致性、流畅性、相关性等方面也优于传统指标。在 topical chat 上,它在自然度、连贯性、吸引力、有据可依等多个维度上胜过 ROUGE-L、BLEU-4、BERTScore 等传统指标。

Vicuna 论文采用了类似方法。他们先定义八个类别(写作、角色扮演、抽取、推理、数学、编程、STEM、人文/社科),再为每个类别编写 10 个问题;接着从五个聊天机器人(LLaMA、Alpaca、ChatGPT、Bard、Vicuna)生成答案;最后请 GPT-4 按有用性、相关性、准确性、细节程度对答案质量打分。

总体上,他们发现 GPT-4 不仅打分稳定,还能对分数给出详细解释。在单答案打分范式下,GPT-4 与人类的一致率(85%)甚至高于人类彼此之间的一致率(81%)。这说明 GPT-4 的判断与人类评估者高度吻合。

QLoRA 也用 LLM 来评估另一个 LLM 的输出。他们请 GPT-4 将各模型与 gpt-3.5-turbo 在 Vicuna 基准上的表现做对比。给定 gpt-3.5-turbo 和另一个模型的回答后,GPT-4 被要求按 10 分制为两者打分并解释理由。他们还通过模型间的直接对比来衡量表现,把任务简化为包含平局的三分类打分。

为了验证自动评估的可靠性,他们收集了 Vicuna 基准上的人工判断。通过 Mechanical Turk,他们为与 gpt-3.5-turbo 的对比招募了 2 名标注者,为两两对比招募了 3 名标注者。结果发现人工与 GPT-4 对模型的排名基本一致,模型层面的 Spearman 等级相关为 0.55。这提供了又一个数据点,表明基于 LLM 的自动评估可以成为人工评估的一种省钱且合理的替代。

如何应用 evals?

构建扎实的 evals 应当是任何基于 LLM 的系统或产品(以及传统机器学习系统)的起点。

遗憾的是,BLEU、ROUGE 这类经典指标对抽象式摘要、对话这类更复杂的任务没有意义。而且我们已经看到,MMLU 这类基准(以及 ROUGE 这类指标)对实现和测量方式非常敏感。坦白说,除非你的 LLM 系统在准备学校考试,否则用 MMLU 做 eval 不太说得通

因此,与其直接使用现成基准,不如先收集一组任务特定的 evals(即 prompt、上下文、作为参考的期望输出)。这些 evals 会指导 prompt 工程、模型选择、微调等工作。随着系统更新,我们可以运行这些 evals,快速度量改进或回退。可以把它理解为评估驱动开发(Eval Driven Development, EDD)。

除了评估数据集,我们还需要有用的指标。它们把性能变化浓缩成一个可在不同评估轮次间比较的数字。如果能把问题简化,我们就可以选择更容易计算和解释的指标。

最简单的任务大概是分类:如果我们用 LLM 做类分类任务(如毒性检测、文档归类),或不带对话的抽取式问答,可以依赖标准分类指标,如召回率、精确率、PRAUC 等。如果任务没有唯一正确答案但有参考(如机器翻译、抽取式摘要),可以依赖基于匹配(BLEU、ROUGE)或语义相似度(BERTScore、MoverScore)的参考类指标。

但这些指标未必适用于抽象式摘要、对话等更开放的任务。而收集人工判断既慢又贵。因此我们可以选择依靠强 LLM 做自动评估。

相对人工判断通常噪声较大(标注者偏见各异),LLM 判断噪声较小(偏见更系统化)但偏见更强。不过既然我们知道这些偏见的存在,就可以针对性地缓解:

  • 位置偏见(position bias):LLM 倾向于偏好处在第一位的回答。缓解方法是交换顺序后对同一对回答评估两次;如果两种顺序下都偏好同一回答,记为胜出,否则记平局。
  • 冗长偏见(verbosity bias):LLM 倾向于偏好更长、更啰嗦的回答,即使后者更清晰、质量更高。一个可能的解法是保证参与比较的回答长度相近。
  • 自我增强偏见(self-enhancement bias):LLM 对自己的回答有轻微偏好。GPT-4 偏爱自己的胜率高 10%,Claude-v1 则高 25%。对策是不要用同一个 LLM 来做评估任务。

另一个技巧:与其让 LLM 直接评估(给出分数),不如给它一个参考并要求做比较,这有助于降噪。

最后,有时最好的 eval 就是人工评估,俗称 vibe check。(别与那个名字起得不幸的代码评估基准 HumanEval 混淆。)正如在 Latent Space 与 MosaicML 的播客(34 分钟处)中提到的:

基于 vibe 的评估不容低估。……我们的一个 eval 就是拿一堆 prompt,在模型训练过程中盯着答案看它们怎么变。老实说,我不认为这些评估指标里任何一个能真正捕捉我们关心的东西。我们有个 prompt 是「为 3 岁和 7 岁的孩子建议可以一起玩的游戏」,观察训练过程中这个答案的变化,比什么都更有价值。——Jonathan Frankle

另可参见这篇针对抽象式摘要 evals 的深度剖析,覆盖基于参考、上下文和偏好的指标,也讨论了幻觉(hallucination)检测。

模式二:检索增强生成(RAG)——引入外部知识

检索增强生成(Retrieval-Augmented Generation, RAG)从基础模型之外取回相关数据,用这些数据增强输入,提供更丰富的上下文以改进输出。

为什么用 RAG?

RAG 通过让模型立足于检索到的上下文来减少幻觉,从而提升事实性。此外,保持检索索引更新比持续预训练一个 LLM 便宜得多,这种成本效率让 RAG 成为向 LLM 提供新鲜数据的便捷途径。最后,如果需要更新或删除数据(比如有偏见或有毒的文档),更新检索索引比微调模型或用 prompt 约束 LLM 不生成有毒输出更直接。

简言之,RAG 把信息检索领域成熟且更简单的思想应用于支撑 LLM 生成。在 Sequoia 最近的一项调查中,88% 的受访者认为检索将成为其技术栈的关键组件。

关于 RAG 的更多内容

在深入 RAG 之前,先对文本嵌入(text embedding)建立基本认识会有帮助。(如果你已熟悉这个主题,可跳过本节。)

文本嵌入是文本数据的一种压缩、抽象的表示,任意长度的文本都可以表示为固定大小的数字向量。它通常从文本语料(如维基百科)中学习得到。可以把嵌入理解为文本的通用编码:相似的条目彼此接近,不相似的条目相距更远。

好的嵌入是指在下游任务(如检索相似条目)上表现良好的嵌入。Huggingface 的海量文本嵌入基准(MTEB)在分类、聚类、检索、摘要等多样任务上为各模型打分。

小提示:这里主要讨论文本嵌入,但嵌入可以有很多模态。例如 CLIP 是多模态的,把图像和文本嵌入到同一空间,让我们能找到与输入文本最相似的图像。我们也可以基于用户行为(如点击、购买)或图关系来嵌入商品。

RAG 源自开放域问答。Meta 的一篇早期论文显示,用 TF-IDF 检索相关文档并把它们作为上下文提供给语言模型(BERT),能提升开放域问答任务的表现。他们把每个任务转述成填空(cloze)陈述,再向语言模型查询缺失的 token。

随后,稠密段落检索(Dense Passage Retrieval, DPR)证明,用稠密嵌入(而非 TF-IDF 这类稀疏向量空间)做文档检索可以胜过 Lucene BM25 这样的强基线(top-5 准确率 65.2% 对 42.9%)。他们还表明,更高的检索精确率能转化为更高的端到端问答准确率,凸显了上游检索的重要性。

为了学习 DPR 嵌入,他们在既有问答对上微调了两个独立的基于 BERT 的编码器。段落编码器(E_p)把文本段落嵌入为向量,查询编码器(E_q)把问题嵌入为向量。查询嵌入随后用于检索与问题最相似的 k 个段落。

他们训练编码器使点积相似度成为一个好的排序函数,并以正样本段落的负对数似然作为损失函数进行优化。DPR 嵌入被优化为使问题与相关段落的向量内积最大,目标是学到一个让问题与其相关段落彼此靠近的向量空间。

推理时,他们离线地把所有段落嵌入(经 E_p)并索引到 FAISS。查询到来时给定一个问题,计算问题嵌入(经 E_q),通过近似最近邻取回 top k 个段落,交给语言模型(BERT)输出答案。

检索增强生成(RAG)这篇论文(本模式之名即来自它)指出了预训练 LLM 的缺点,包括:无法扩展或修订记忆、无法为生成结果提供洞见,以及幻觉。

为解决这些缺点,他们提出了 RAG(又称半参数模型,semi-parametric models)。稠密向量检索充当非参数组件,预训练 LLM 充当参数组件。他们复用 DPR 编码器初始化检索器并构建文档索引;生成器则用了 BART,一个 4 亿参数的 seq2seq 模型。

图:检索增强生成概览(来源)

推理时,他们把输入与检索到的文档拼接。LLM 基于原始输入、检索到的文档以及前 i-1 个 token 生成第 i 个 token。在生成方式上,他们提出了两种方法,差别在于检索到的段落如何参与生成输出。

第一种方法 RAG-Sequence 用同一篇文档生成完整序列。对 k 篇检索文档,生成器为每篇文档产出一个输出;然后把每个输出序列的概率做边缘化(把 k 篇文档各自的输出序列概率求和,并按各文档被检索到的概率加权),最后选概率最高的输出序列。

而 RAG-Token 可以基于不同文档生成不同 token。给定 k 篇检索文档,生成器先为每篇文档产生下一个输出 token 的分布,再边缘化(汇聚所有单个 token 分布),然后对下一个 token 重复这一过程。这意味着每次 token 生成都可以基于原始输入和已生成 token 检索不同的 k 篇相关文档,因此各文档可以有不同的检索概率,并对下一个生成的 token 有不同的贡献。

融合解码器(Fusion-in-Decoder, FiD)同样将检索与生成模型结合用于开放域问答。它支持两种检索方式:BM25(默认参数的 Lucene)和 DPR。FiD 之名来自它只在解码器中对检索文档做融合。

图:Fusion-in-Decoder 概览(来源)

对每篇检索到的段落,把标题和段落与问题拼接。这些拼接对在编码器中被独立处理。他们还在相应部分前加上 question:、title:、context: 等特殊 token。解码器则对这些检索段落的拼接结果做注意力。

由于编码器独立处理各段落,它可扩展到大量段落——每次只需对单个上下文做自注意力,因此计算量随检索段落数线性(而非二次方)增长,比 RAG-Token 之类的方案更可扩展。随后在解码时,解码器联合处理已编码的段落,使它能在多篇检索段落之间更好地聚合上下文。

检索增强 Transformer(Retrieval-Enhanced Transformer, RETRO)采用类似模式,组合了冻结的 BERT 检索器、可微分的编码器和分块交叉注意力(chunked cross-attention)来生成输出。不同之处在于:RETRO 在整个预训练阶段都做检索,而不只在推理时;并且它基于输入的分块来取回相关文档,实现生成过程中更细粒度、多次的检索,而非每个查询只检索一次。

对每个输入块(C_u),取回的 k 个块 RET(C_u) 被送入编码器,输出为编码后的邻居 E_u^j,其中 E_u^j = Encoder(RET(C_u)^j, H_u) ∈ R^(r × d_0)。这里每个块编码都通过交叉注意力层以 H_u(中间激活)和块 C_u 的激活为条件。简言之,检索块的编码取决于输入块的注意力激活。E_u^j 随后用于约束下一个块的生成。

图:RETRO 概览(来源)

检索时,RETRO 把输入序列切成 64 token 的块,然后找到与前一塊相似的文本,为当前块提供上下文。检索索引由两个连续的 token 块组成:N 与 F。前者是邻居块(64 token),用于计算 key;后者是原文档中的延续块(64 token)。

检索基于 BERT 嵌入上 L2 距离(欧氏距离)的近似 k 近邻(有趣的是,这与常见的余弦或点积相似度不同)。建立在 SCaNN 上的检索索引可以在 10ms 内查询 2T token 的数据库。

他们还演示了如何对现有基线模型做 RETRO-fit:冻结预训练权重,只训练分块交叉注意力和邻居编码器参数(对 7B 模型不到 10% 的权重),只需 600 万训练序列(预训练序列的 3%)即可为 Transformer 增强检索能力。RETRO-fitted 模型能够超过基线模型,并接近从零训练的 RETRO 的表现。

图:对预训练模型做 RETRO-fit 的性能(来源)

互联网增强语言模型(Internet-augmented LMs)提议用一个朴素的「现成」搜索引擎来增强 LLM。他们先通过 Google 搜索取回一组相关文档;由于检索文档往往很长(平均 2056 词),他们把它们切成每段六句话的段落;最后用 TF-IDF 对问题和段落做嵌入,用余弦相似度为每个查询排出最相关的段落。

图:互联网增强 LLM 概览(来源)

检索到的段落通过 few-shot prompting 来约束 LLM。他们沿用封闭式问答中常规的 k-shot prompting(k=15,只提供问答对),并扩展为附上证据段落,使每个上下文成为「证据、问题、答案」三元组。

生成器用的是 Gopher,一个在 300B token 上训练的 280B 参数模型。对每个问题,他们基于 50 篇检索段落各生成四个候选答案,最后用几种方法估计答案概率来选出最佳答案,包括直接推理、RAG、噪声信道推理和专家乘积(Product-of-Experts, PoE)。PoE 的表现始终最好。

RAG 也被应用于问答之外的任务,比如代码生成。CodeT5+ 可以作为独立生成器使用,但与 RAG 结合后,它在代码生成上显著超过类似模型。

为评估 RAG 对代码生成的影响,他们在三种设置下评估模型:

  • 基于检索:取回 top-1 代码样本作为预测。
  • 纯生成:仅基于解码器输出代码。
  • 检索增强:在通过解码器生成代码之前,把 top-1 代码样本附加到编码器输入。

图:CodeT5+ 中的 RAG 概览(来源)

作为一个定性示例,他们展示了检索到的代码提供了关键上下文(例如用 urllib3 发起 HTTP 请求),引导生成过程走向更正确的预测。相比之下,纯生成方法返回的输出只捕捉到「下载」和「压缩」的概念,是错误的。

如果我们没有查询-段落对的相关性(relevance)标注怎么办?没有它们,我们将无法训练把查询和文档嵌入到同一空间(相关性由内积表示)的双编码器(bi-encoder)。假设文档嵌入(Hypothetical document embeddings, HyDE)给出了一个解法。

图:HyDE 概览(来源)

给定一个查询,HyDE 先让 LLM(如 InstructGPT)生成一篇假设文档;然后由无监督编码器(如 Contriver)把该文档编码为嵌入向量;最后计算假设文档与语料库之间的内积,取回最相似的真实文档。

其预期是:编码器的稠密瓶颈充当有损压缩器,把无关的、非事实的细节在嵌入时排除掉。这将相关性建模问题从表示学习任务重构为生成任务。

如何应用 RAG

从构建 Obsidian-Copilot 的经验出发,我发现混合检索(传统搜索索引 + 基于嵌入的搜索)比单独任何一种都更好。在那里,我用语义搜索(e5-small-v2)补充经典检索(OpenSearch 上的 BM25)。

为什么不用纯嵌入搜索?虽然它在很多场景表现很好,但有些情况它会失手,比如:

  • 搜索人或物的名字(如 Eugene、Kaptir 2.0)。
  • 搜索缩写或短语(如 RAG、RLHF)。
  • 搜索 ID(如 gpt-3.5-turbo、titan-xlarge-v1.01)。

但关键词搜索也有局限:它只建模简单的词频,不捕捉语义或关联信息,因此处理同义词或上位词(表示泛化的词)时表现不佳。这正是与语义搜索互补的地方。

此外,有了传统搜索索引,我们可以用元数据精炼结果。例如用日期过滤器优先较新的文档,或把搜索收窄到特定时间段。如果是电商搜索,平均评分或类目过滤器也很有用。最后,元数据对下游排序也很方便,比如优先被引用更多的文档,或按销量加权商品。

至于嵌入,流行的做法似乎是用 text-embedding-ada-002。它的好处包括通过 API 使用很方便,不必维护自己的嵌入基础设施或自托管嵌入模型。不过,个人经验和业内轶事都表明,就检索而言还有更好的选择。

最早的嵌入方法包括 Word2vec 和 fastText。FastText 是一个开源、轻量的库,用户可以使用预训练嵌入,也可以训练新的嵌入模型。它自带 157 种语言的预训练嵌入,即使没有 GPU 也非常快,是我做早期概念验证的首选。

另一个好基线是 sentence-transformers。它让计算句子、段落甚至图像的嵌入变得简单,基于 BERT、RoBERTa 这些主力 Transformer,支持 100 多种语言。

最近,instructor 类模型展现出 SOTA 性能。这类模型在训练时把任务描述前缀加到文本前面;嵌入新文本时,只需描述任务即可得到任务特定的嵌入。(在我看来,这跟嵌入模型的指令微调没什么不同。)

一个例子是 E5 系列模型。做开放问答和信息检索时,只需给索引中的文档加 passage: 前缀,给查询加 query: 前缀。如果任务是对称的(如语义相似度、改写检索),或想把嵌入用作特征(如分类、聚类),只用 query: 前缀即可。

Instructor 模型更进一步,允许用户自定义前缀 prompt:「Represent the {domain} {task_type} for the {task_objective}:」,例如「Represent the Wikipedia document for retrieval:」(domain 和 task objective 可选)。这把 prompt tuning 的概念带进了文本嵌入领域。

最后,截至 8 月 1 日,MTEB 排行榜上最好的嵌入模型是阿里巴巴达摩院的 GTE 系列模型。榜首模型的大小是次优模型 e5-large-v2 的一半(0.67GB 对 1.34GB)。第二名 gte-base 模型大小仅 0.22GB,嵌入维度 768。(致谢 Nirant。)

要在规模上以低延迟检索文档,我们使用近似最近邻(Approximate Nearest Neighbors, ANN)。它以检索速度为优化目标,返回近似的(而非精确的)top k 个相似邻居,用少量精度损失换取大幅提速。

ANN 嵌入索引是让我们高效执行 ANN 搜索的数据结构。从高层看,它们在嵌入空间上构建分区,使我们可以快速缩放到查询向量所在的具体空间。一些流行技术包括:

评估 ANN 索引时要考虑的因素包括:

  • 召回率:与精确最近邻相比表现如何?
  • 延迟/吞吐:每秒能处理多少查询?
  • 内存占用:服务一个索引需要多少内存?
  • 新增条目的难易:能否不必重建全部文档索引就加入新条目(LSH 可以),还是必须重建索引(ScaNN 需要)?

没有任何一个框架在所有方面都胜过其他框架。因此,先定义你的功能与非功能需求,再做基准测试。就个人而言,我发现 ScaNN 在召回-延迟权衡上表现出色(见基准测试图)。

模式三:微调(Fine-tuning)——在特定任务上变得更强

微调(fine-tuning)是拿一个预训练模型(已用海量数据训练过),针对特定任务进一步打磨的过程。意图是利用模型在预训练中已经获得的知识,把它应用到特定任务上,通常涉及一个更小的、任务特定的数据集。

「微调」这个词用得比较宽泛,可以指若干概念:

  • 继续预训练(continued pre-training):用领域特定数据,在基座模型上沿用同样的预训练范式(下一 token 预测、掩码语言建模)。
  • 指令微调(instruction fine-tuning):在「指令-输出」对的样本上微调预训练(基座)模型,使其学会遵循指令、回答问题、当 waifu 等。
  • 单任务微调(single-task fine-tuning):把预训练模型打磨到某个狭窄具体的任务,如毒性检测或摘要,与 BERT、T5 类似。
  • 基于人类反馈的强化学习(RLHF):把指令微调与强化学习结合起来。它需要收集人类偏好(如两两比较),用于训练奖励模型;再通过近端策略优化(PPO)等 RL 技术,用奖励模型进一步微调经指令训练的 LLM。

本文主要聚焦单任务微调和指令微调。

为什么微调?

出于几个原因,微调开放 LLM 正成为使用第三方云端 LLM 之外越来越可行的替代方案。

性能与控制:微调可以提升现成基座模型的表现,甚至可能超过第三方 LLM。它还对 LLM 行为提供更强的控制,带来更稳健的系统或产品。总体而言,微调让我们能做出与简单套用第三方或开放 LLM 有差异化的产品。

模块化:单任务微调让我们能使用一支由较小模型组成的队伍,各自专精于自己的任务。借助这一设置,系统可以模块化为内容审核、抽取、摘要等各自的模型。而且,由于每个模型只需关注狭窄的任务集,我们可以绕开对齐税(alignment tax)——即在一个任务上微调会降低其他任务的表现。

减少依赖:通过微调并自托管自己的模型,我们可以减少专有数据(如 PII、内部文档与代码)暴露给外部 API 的法律顾虑;也绕开了第三方 LLM 带来的种种约束,如限流、高成本或过于严格的安全过滤。微调并自托管自己的 LLM,能确保数据不出内网,并按需扩展吞吐。

关于微调的更多内容

为什么需要微调基座模型?冒着过度简化的风险说:基座模型主要被优化为基于其训练语料预测下一个词,因此并不天然擅长遵循指令或回答问题。被问到问题时,它们往往回以更多问题。所以我们做指令微调,让它们学会恰当地回应。

不过,微调并非没有挑战。首先,我们需要大量示范数据。比如在 InstructGPT 论文中,他们用了 1.3 万条指令-输出样本做监督微调、3.3 万条输出比较做奖励建模、3.1 万条无人工标注的 prompt 作为 RLHF 的输入。

此外,微调伴随对齐税——这一过程可能导致某些关键任务表现下降。(毕竟没有免费午餐。)同一篇 InstructGPT 论文发现,RLHF 使其在公开 NLP 任务(如 SQuAD、HellaSwag、WMT 2015 法译英)上相对 GPT-3 基座出现表现回退。(一个变通办法是使用多个擅长狭窄任务的小型专用模型。)

微调与迁移学习(transfer learning)的概念相似。维基百科的定义是:「迁移学习是机器学习中的一门技术,把从一个任务学到的知识复用,以提升相关任务的表现。」几年前,迁移学习让我轻松地把在 ImageNet 上训练的 ResNet 模型应用于时尚商品分类并构建图像搜索。

ULMFit 是最早把迁移学习用于文本的论文之一。它确立了「自监督预训练(在无标注数据上)+ 微调(在有标注数据上)」的流程。他们使用 AWD-LSTM(AWS-LSTM),一种在各处门控上带 dropout 的 LSTM 变体。

图:ULMFit 概览(来源)

预训练(下一词预测)阶段,模型在 wikitext-103 上训练,该语料包含 2.86 万篇维基百科文章、1.03 亿词。随后是目标任务微调,用特定任务所属领域的数据微调语言模型。最后是分类器微调,给模型加上两个额外的线性块,在情感分析、问题分类、主题分类等目标分类任务上微调。

从那时起,「先预训练、后微调」的范式推动了语言建模的许多进展。BERT(仅编码器)在英文维基百科和 BooksCorpus 上以掩码语言建模和下一句预测做预训练,随后在任务特定的输入和标签上微调,用于单句分类、句对分类、单句标注和问答。

图:BERT 概览(来源)

GPT(仅解码器)先在 BooksCorpus 上通过下一 token 预测做预训练,随后对文本分类、文本蕴含、相似度和问答等任务做单任务微调。有趣的是,他们发现把语言建模作为辅助目标,有助于模型在训练中更好地泛化、更快收敛。

图:GPT 概览(来源)

T5(编码器-解码器)在 Colossal Clean Crawled Corpus(C4,2019 年 4 月 Common Crawl 的清洗版)上预训练,采用与 BERT 相同的去噪目标,即掩码语言建模。随后在文本分类、抽象式摘要、问答、机器翻译等任务上微调。

图:T5 概览(来源)

但与 ULMFit、BERT、GPT 为下游任务使用不同分类头不同,T5 把下游任务统一表示为文本到文本(text-to-text)。比如翻译任务的输入文本以「Translation English to German:」开头,而摘要任务可能以「Summarize:」或「TL;DR:」开头。这个前缀实质上成了一个超参数(prompt 工程的鼻祖?)。这一设计选择让他们能用一个微调后的模型横跨多种下游任务。

InstructGPT 把单任务微调的思想扩展为指令微调。基座模型是 GPT-3,在 Common Crawl、WebText、书籍、维基百科等互联网数据上预训练。接着在期望行为的示范(指令与输出)上做监督微调;然后在比较数据集上训练奖励模型;最后通过 PPO 针对奖励模型优化经指令训练的模型,最后这个阶段更多关注对齐而非特定任务表现。

图:InstructGPT 的微调步骤概览(来源)

接下来,我们从微调后的模型转向微调技术。

软提示调优(soft prompt tuning)在模型输入嵌入前拼接一个可训练张量,实质是造出一个「软提示」。与离散的文本提示不同,软提示可以通过反向传播学习,这意味着它能被微调以吸收来自任意数量有标注样本的信号。

接着是前缀调优(prefix tuning)。它不在模型输入上加软提示,而是把可训练参数拼接到所有 Transformer 块的隐藏状态前面。微调期间,语言模型原始参数保持冻结,只更新前缀参数。

图:前缀调优概览(来源)

论文显示,尽管只需更新 0.1% 的参数,却达到了与全量微调相当的表现;而且在数据有限且需要外推到新话题的设置下,它优于全量微调。一个假设是,训练更少的参数有助于在较小的目标数据集上减少过拟合。

还有适配器(adapter)技术。该方法在每个 Transformer 块中两次加入全连接网络层——注意力层之后和前馈网络层之后。在 GLUE 上,它每任务只需增加 3.6% 的参数,即可达到与全量微调相差 0.4% 以内的表现。

图:适配器概览(来源)

低秩适配(LoRA)把适配器设计为两个低秩矩阵的乘积。其灵感来自 Aghajanyan 等人的工作:预训练语言模型在适配特定任务时具有低内在维度,即使被随机投影到更小的子空间仍能高效学习。因此 LoRA 假设适配时的权重更新也具有低内在秩。

图:LoRA 概览(来源)

与前缀调优类似,他们发现 LoRA 胜过包括全量微调在内的多个基线。假设同样成立:LoRA 得益于降秩提供了隐式正则化;而更新所有权重的全量微调容易过拟合。

QLoRA 在 LoRA 思路上更进一步:微调时不用完整的 16 位模型,而用 4 位量化模型。它引入了若干创新,如 4 位 NormalFloat(用于量化模型)、双重量化(进一步节省内存)、分页优化器(GPU 显存不足时把数据转移到 CPU 内存以防 OOM 错误)。

图:QLoRA 概览(来源)

结果是,QLoRA 把微调一个 65B 模型的平均内存需求从超过 780GB 降到更可控的 48GB,且与 16 位全量微调基线相比,运行时或预测性能没有下降。

(趣事:在一次与 QLoRA 作者 Tim Dettmers 的聚会上,他打趣说双重量化「是个有点傻的点子,但效果完美」。嘿,能用就行。)

如何应用微调?

第一步是收集示范数据/标签。它们可以来自直接的任务,如文档分类、实体抽取或摘要,也可以是更复杂的,如问答或对话。收集这类数据的一些方式包括:

  • 通过专家或众包人工标注:贵且慢,但在良好指引下通常能带来更高质量的数据。
  • 通过用户反馈:可以简单到让用户选择描述商品的属性、对 LLM 回答点赞或点踩(如 ChatGPT),或记录用户选择下载哪些图片(如 Midjourney)。
  • 用 prompt 向许可宽松的更大开放模型提问:通过 prompt 工程,我们或许能从更大的模型(Falcon 40B Instruct)引出像样的示范数据,用于微调更小的模型。
  • 复用开源数据:如果你的任务可以框定为自然语言推理(NLI)任务,可以用 MNLI 数据微调一个模型做 NLI。然后,再在内部数据上继续微调该模型,把输入分类为蕴含、中立或矛盾。

注意:一些 LLM 条款禁止用户用其输出开发其他模型。

  • OpenAI 使用条款(第 2c-iii 条):不得使用服务输出开发与 OpenAI 竞争的模型。
  • LLaMA 2 社区许可协议(第 1b-v 条):不得使用 Llama 材料或其任何输出或结果来改进任何其他大语言模型(Llama 2 及其衍生作品除外)。

下一步是定义评估指标。我们在前面一节已经讨论过。

然后选择预训练模型。有多个许可宽松的开放 LLM 可选。排除 Llama 2(因为它并非完全开放商用),Falcon-40B 被认为是表现最好的模型。不过我发现它太重,在生产中做微调和部署都不方便。

我更倾向使用更小的模型,如 Falcon-7B。而且如果能简化并把任务框定得更窄,BERT(3.4 亿参数)、RoBERTa(3.55 亿参数)、BART(4.06 亿参数)是分类和自然语言推理任务的可靠之选。再往外,Flan-T5(7.7 亿和 30 亿参数版本)是翻译、抽象式摘要、标题生成等的可靠基线。

我们可能还需要更新模型架构,比如预训练模型的架构与任务不匹配时。例如,可能需要更新 BERT 或 T5 上的分类头以匹配我们的任务。提示:如果任务是简单的二分类,NLI 模型可以开箱即用——蕴含(entailment)映射为正、矛盾(contradiction)映射为负,而中立(neutral)标签可以用来表达不确定性。

接着选微调方法。LoRA 和 QLoRA 是好的起点。但如果你的微调强度更大,比如在新领域知识上继续预训练,你可能会发现全量微调是必要的。

最后是基本的超参数调优。总体上,大多数论文关注学习率、批大小和 epoch 数(见 LoRA、QLoRA)。如果用 LoRA,可能想调 rank 参数(不过 QLoRA 论文发现不同 rank 和 alpha 的结果相近)。其他超参数包括输入序列长度、损失类型(对比损失 vs. token 匹配)、数据配比(如预训练或示范数据的混合比例、正负样本的比例等)。

模式四:缓存(Caching)——降低延迟与成本

缓存(caching)是存储先前取回或计算过的数据的技术,这样未来对相同数据的请求就能更快地被服务。在 LLM 生成服务领域,流行的方法是以输入请求的嵌入为键缓存 LLM 响应;然后对每个新请求,如果收到语义相似度足够高的请求,就返回缓存的响应。

对一些从业者来说,这听起来像「等着出事的灾难」。我倾向于同意。因此我认为,采用这一模式的关键在于弄清楚如何安全地缓存,而不是只依赖语义相似度。

为什么缓存?

缓存能显著降低已服务过的响应的延迟。此外,通过免除对同一输入一次又一次地计算响应,我们可以减少 LLM 请求的数量,从而节省成本。还有一些用例不支持秒级延迟,预计算加缓存可能是服务这些用例的唯一途径。

关于缓存的更多内容

缓存是一种高速存储层,存放访问更频繁的那部分数据,让我们经由缓存而非数据的主存储(如搜索索引、关系数据库)更快地服务请求。总体上,缓存让先前取回或计算的数据得以高效复用。(更多关于缓存及其最佳实践的内容。)

LLM 缓存的一个例子是 GPTCache

图:GPTCache 概览(来源)

收到新请求时:

  • 嵌入生成器:用各种模型(如 OpenAI 的 text-embedding-ada-002、FastText、Sentence Transformers 等)把请求嵌入为向量。
  • 相似度评估器:经由向量库计算请求的相似度并给出距离度量。向量库可以是本地(FAISS、Hnswlib)或云端的;也可以通过模型来计算相似度。
  • 缓存存储:如果请求足够相似,取出缓存的响应并返回。
  • LLM:如果请求的相似度不够,就传给 LLM 生成结果。最后响应被返回,并缓存下来以备将来使用。

Redis 也分享过一个类似的例子,提到有的团队甚至预计算所有预期会收到的查询,然后设一个相似度阈值,决定哪些查询足够相似、可以返回缓存响应。

如何应用缓存?

我们应该从深入理解用户请求模式开始。这样才能深思熟虑地设计缓存,使它能被可靠地应用。

先看一个非 LLM 的例子。假设我们在为电商网站缓存商品价格。结账时,展示(可能过期的)缓存价格安全吗?大概不安全,因为顾客结账时看到的价格应与其最终支付的金额一致。缓存在这里不合适,因为我们需要保证顾客所见的一致性。

回到 LLM 响应。假设我们收到一个对《碟中谍 2》摘要的请求,它与《碟中谍 3》语义足够相似。如果我们基于语义相似度查缓存,就可能返回错误的响应。

我们还要考虑缓存对当前使用模式是否有效。一种量化方法是缓存命中率(直接由缓存服务的请求所占百分比)。如果使用模式是均匀随机的,缓存就需要频繁更新,维护缓存新鲜的投入可能抵消缓存带来的任何收益。反过来,如果使用服从幂律——少数独特请求占大多数流量(如搜索查询、商品浏览)——缓存就可能是有效策略。

除了语义相似度,我们还可以探索基于以下方式的缓存:

  • 条目 ID:适用于预计算商品评论摘要,或为整个电影三部曲生成一份摘要。
  • 条目 ID 对:比如生成两部电影的对比。虽然看起来是 O(N^2),实际上少部分组合驱动了大部分流量,比如同系列或同类型热门电影之间的对比。
  • 受约束的输入:如电影类型、导演或主演等变量。例如用户想找特定导演的电影,我们可以执行一个结构化查询,再让 LLM 把回答润色得更流畅。另一个例子是基于下拉选项生成代码——如果代码已经过验证可以运行,我们就可以缓存它以便可靠地复用。

另外,缓存不必只在运行中进行。如 Redis 所分享的,我们可以在服务之前离线或异步地预计算 LLM 生成结果。通过从缓存服务,我们把延迟从生成(通常数秒)转移到缓存查找(毫秒级)。批量预计算相比实时服务也有助于降低成本。

这里列出的方法可能不如对自然语言输入做语义缓存那么灵活,但我认为它在效率与可靠性之间取得了好的平衡。

模式五:护栏(Guardrails)——确保输出质量

在 LLM 语境下,护栏(guardrails)对 LLM 的输出做验证,确保输出不只是听起来不错,还在语法上正确、符合事实且不含有害内容;它也包括对对抗性输入的防范。

为什么需要护栏?

首先,它们帮助确保模型输出可靠、一致到可以在生产环境中使用。例如,我们可能要求输出符合特定的 JSON schema 以便机器可读,或者要求生成的代码可以执行。护栏可以胜任这类语法验证。

其次,它们提供额外的安全层,对 LLM 的输出保持质量控制。例如,要确认生成的内容是否适合对外服务,我们可能想检查输出是否有害、验证其事实准确性,或确保它与给定上下文连贯。

关于护栏的更多内容

一种方法是通过 prompt 控制模型响应。例如,Anthropic 分享过旨在引导模型生成有帮助、无害且诚实(HHH)响应的 prompt。他们发现,用 HHH prompt 做的 Python 微调比用 RLHF 微调表现更好。

图:HHH prompt 示例(来源)

更常见的做法是验证输出。一个例子是 Guardrails 包。它允许用户以 Pydantic 风格的验证为 LLM 输出添加结构、类型和质量要求;如果检查失败,它可以触发纠正动作,比如过滤违规输出或重新生成另一个响应。

大部分验证逻辑在 validators.py 里。看看它们如何实现很有意思。宽泛地说,其验证器分为以下几类:

  • 单输出值验证:包括确保输出 (i) 是预定义选项之一,(ii) 长度在某个范围内,(iii) 若为数值则落在期望区间内,(iv) 是完整的句子。
  • 语法检查:包括确保生成的 URL 有效且可访问,以及 Python 和 SQL 代码没有 bug。
  • 语义检查:验证输出与参考文档一致,或抽取式摘要与源文档高度吻合。这些检查可以通过余弦相似度或模糊匹配技术完成。
  • 安全检查:确保生成的输出不含不当语言,或翻译文本的质量足够高。

Nvidia 的 NeMo-Guardrails 遵循类似原则,但面向引导基于 LLM 的对话系统。它不聚焦语法护栏,而强调语义护栏,包括确保助手避开政治敏感话题、提供事实正确的信息,并能识别越狱(jailbreak)企图。

因此 NeMo 的路数有所不同:它不使用「检查某个值是否在列表中」或「检查代码是否有语法错误」这类更确定性的检查,而是大量依赖用另一个 LLM 来验证输出(受 SelfCheckGPT 启发)。

在其事实核查与防幻觉的示例中,他们让 LLM 自己检查最近的输出是否与给定上下文一致。事实核查时,基于从知识库检索到的文档询问 LLM 该回答是否为真。防幻觉时,由于没有知识库可用,他们让 LLM 生成多个备选补全,把这些作为上下文。其底层假设是:如果 LLM 生成的多个补全彼此矛盾,那么原始补全很可能是幻觉。

其审核(moderation)示例采用类似方法:响应经由一个 LLM 筛查有害与不道德内容。鉴于伦理与有害内容的微妙性,启发式方法和传统机器学习技术都不够用,因此需要 LLM 来更深入地理解对话的意图与结构。

除了用护栏验证 LLM 输出,我们还可以直接引导输出遵循特定语法。一个例子是微软的 Guidance。与 Guardrails 通过 prompt 施加 JSON schema 不同,Guidance 通过注入构成结构的 token 来强制 schema。

可以把 Guidance 想象成一门用于 LLM 交互与输出的领域特定语言(DSL)。它从 Handlebars(Web 应用中流行的模板语言,支持变量插值与逻辑控制)汲取灵感。

不过,Guidance 与一般模板语言的不同在于它是线性执行的,也就是说它维持生成 token 的顺序。因此,通过插入属于结构本身的 token——而不是指望 LLM 正确生成它们——Guidance 能够规定具体的输出格式。在示例中,他们展示了如何生成始终合法的 JSON生成带多个键的复杂输出格式确保 LLM 扮演正确的角色,以及让多个 agent 相互交互

他们还提出了一个叫 token healing 的概念,一个有助于避免分词引发的细微 bug 的实用特性。简单来说,它在 prompt 结束前把生成回退一个 token,然后限制第一个生成的 token 必须具有与 prompt 最后一个 token 匹配的前缀。这省去了编写 prompt 时对 token 边界的担忧。

如何应用护栏?

尽管业界的 LLM 护栏概念仍处于早期,有几个立刻可用且实用的策略可以考虑。

结构引导(structural guidance):尽可能使用 guidance。它直接控制输出,提供更精确的方法来确保输出符合特定的结构或格式。

语法护栏:包括检查分类输出是否在可接受选项的集合内、数值输出是否在期望范围内。如果生成 SQL,可以验证它没有语法错误,并确保查询中的所有列与 schema 匹配。生成代码(如 Python、JavaScript)时同理。

内容安全护栏:验证输出没有有害或不当内容。可以简单到对照脏话与不当词汇清单检查,或使用脏话检测模型。(对输出运行审核分类器很常见。)更复杂、更微妙的输出可以依赖 LLM 评估器。

语义/事实性护栏:确认输出在语义上与输入相关。假设我们基于电影梗概生成一份两句话的摘要,可以验证生成的摘要与原文是否语义相似,或让(另一个)LLM 确认摘要是否准确呈现了给定的梗概。

输入护栏:限制模型响应的输入类型,帮助降低模型因响应不当或对抗性 prompt 而生成有害内容的风险。例如,你若让 Midjourney 生成 NSFW 内容会得到报错。这可以简单到与字符串清单比对,或使用审核分类器。

图:Midjourney 上的输入护栏示例

模式六:防御式 UX(Defensive UX)——预见并优雅地处理错误

防御式 UX(Defensive UX)是一种设计策略,它承认坏事(如不准确或幻觉)可能在用户与机器学习或基于 LLM 的产品交互时发生。因此其意图是预先预见并管理这些问题,主要通过引导用户行为、防止误用、优雅地处理错误来实现。

为什么需要防御式 UX?

机器学习和 LLM 并不完美——它们可能产生不准确的输出。而且它们对相同输入的响应会随时间变化:比如搜索引擎因个性化而显示不同结果,或 LLM 在更有创造性、更高 temperature 的设置下生成多样的输出。这可能违背主张一致 UI 与可预测行为的一致性原则。

防御式 UX 能通过以下方式缓解上述问题:

  • 提升可及性:通过帮助用户理解 ML/LLM 功能如何工作及其局限,防御式 UX 让功能更平易近人、更友好。
  • 提升信任:当用户看到功能能优雅地处理困难场景、不产生有害输出时,他们更可能信任它。
  • 更好的 UX:通过设计系统与 UX 来处理模糊情形和错误,防御式 UX 为更顺畅、更愉快的用户体验铺路。

关于防御式 UX 的更多内容

要更深入了解防御式 UX,可以看微软、谷歌和苹果的人机协同(Human-AI)指南。

微软的《人机交互指南》(Guidelines for Human-AI Interaction)基于对 168 条潜在指南的调查,这些指南收集自内部与外部行业来源、学术文献和公开文章。在合并相似的指南、过滤掉过于宽泛或过于具体或非 AI 特有的指南,并经过一轮启发式评估后,他们收敛到 18 条指南。

图:贯穿用户旅程的人机交互指南(来源)

这些指南有特定的风格:每条都是 3-10 个词、以动词开头的简明行动规则,每条规则配有一句单行说明来消解潜在的歧义。它们按用户交互中可能的适用阶段组织:

  • 初始:明确系统能做什么(G1)、明确系统对能做的事能做到多好(G2)。
  • 交互中:基于情境把握服务时机(G3)、缓解社会偏见(G6)。
  • 出错时:支持高效撤销(G8)、支持高效纠正(G9)。
  • 长期:向用户行为学习(G13)、提供全局控制(G17)。

谷歌的 People + AI Guidebook 根植于谷歌产品团队与学术研究的数据和洞见。与微软以用户为中心组织指南不同,谷歌把指南组织成开发者需要牢记的概念。

它有 23 个模式,围绕产品开发过程中的常见问题分组,包括:

  • 如何开始以人为本的 AI:判断 AI 是否增加价值、尽早投资良好的数据实践(如 evals)。
  • 如何引导用户上手新的 AI 功能:让探索变得安全、锚定熟悉感、分阶段自动化。
  • 如何帮助用户建立对产品的信任:设定正确预期、保持透明、风险低时更多自动化。

苹果的《机器学习人机界面指南》(Human Interface Guidelines for Machine Learning)不同于学术文献和用户研究的自下而上方法,其主要来源是从业者的知识与经验。因此它没有太多参考文献或数据点,而是聚焦苹果长期以来的设计原则,形成了区别于前两者的独特视角。

这份文档聚焦苹果的设计原则如何应用于融入 ML 的产品,强调 UI 而非模型功能。它先请开发者思考 ML 在其应用中的角色,并从用户体验倒推。这包括思考 ML 是:

  • 关键还是补充:比如 Face ID 离不开 ML,但键盘没有 QuickType 也能用。
  • 主动还是被动:Siri 建议是主动的,而自动纠错是被动的。
  • 动态还是静态:推荐是动态的,而照片应用中的物体检测只随每次 iOS 更新改进。

然后它深入若干模式,分为系统的输入与输出两部分。输入聚焦显式反馈、隐式反馈、校准与纠正,指导 AI 产品如何请求和处理用户数据与交互。输出聚焦错误、多个选项、置信度、归因与局限,意图确保模型输出以可理解、有用的方式呈现。

三份指南的差异很有启发性。谷歌更强调训练数据与模型开发的考量,大概与其工程驱动的文化有关。微软更关注心智模型,可能是 HCI 学术研究的产物。苹果的做法则围绕提供无缝的 UX 展开,这一焦点大概受其文化价值观与原则的影响。

如何应用防御式 UX?

以下是基于上述指南的一些模式。(免责声明:我不是设计师。)

设定正确预期。这一原则在三份指南中保持一致:

  • 微软:明确系统对能做的事能做到多好(帮助用户理解 AI 系统可能出错的频率)。
  • 谷歌:设定正确预期(对用户透明,说明你的 AI 产品能做什么、不能做什么)。
  • 苹果:帮助人们建立现实的预期(在营销材料或功能的上下文中描述局限)。

这可以简单到在 AI 生成结果上方加一句简短免责声明(如 Bard 那样),或在应用的落地页上突出应用的局限(如 ChatGPT 那样)。

图:Google Bard 结果上的免责声明示例(注:nrows 不是有效参数)

通过对产品能力与局限保持透明,我们帮助用户校准对其功能与输出的预期。虽然短期内可能让用户少一些信任,但长期有助于培养信任——用户不容易高估我们的产品而随后感到失望。

支持高效撤销。这被明确列为微软第 8 条指南:支持高效撤销(让用户容易撤销或忽略不需要的 AI 系统服务)。

例如,用户在我们的网站上浏览时,一个聊天机器人弹出询问是否需要帮助,用户应当能轻松地关掉它。这确保聊天机器人不会碍事,尤其是在小屏设备上。类似地,GitHub Copilot 允许用户通过继续打字来方便地忽略代码建议。虽然这短期可能减少 AI 功能的使用,但长期避免了它变成骚扰、进而降低客户满意度。

提供归因(attribution)。三份指南都列出了这一点:

  • 微软:明确系统为何这样做(让用户能查看 AI 系统如此行为的解释)。
  • 谷歌:加入来自人的上下文(借助第三方来源的输入帮助用户评估你的推荐)。
  • 苹果:考虑使用归因帮助人们区分结果。

引用(citation)正日益成为常见的设计元素。以 BingChat 为例:我们发起查询时,它的回答包含引用,通常来自可信来源。这不仅显示了信息从何而来,也让用户能评估来源的质量。类似地,设想我们用 LLM 解释用户为什么可能喜欢某个商品:在 LLM 生成的解释旁边,可以附上一条真实评论的引文,或提及商品评分。

来自专家与社区的上下文也能增强用户信任。比如用户在寻找徒步路线推荐,提到某条路线受到相关社区的高度推荐会大有帮助。这不仅为推荐增值,也通过人与人的连接帮助用户校准信任。

图:通过社会认同(social proof)做归因的示例(来源)

最后,苹果的指南包含常见的归因描述,如「因为你读过非虚构类」「你读过的作者的新书」。这些描述不仅个性化了体验,也提供了上下文,增强用户理解与信任。

锚定熟悉感。向用户介绍新 AI 产品或功能时,用熟悉的 UX 模式和功能来引导会有帮助。这让用户更容易聚焦主要任务,并开始赢得客户对我们新产品的信任。要抵制用奇异的 UI 元素来炫耀新「魔法」功能的诱惑。

沿着同样的思路,随着 ChatGPT 的走红,对话式功能越来越常见:与文档聊天、与数据对话查询、对话购买日用品。但我怀疑聊天是不是大多数用户体验的正确 UX——相对于点击文字和图片这种熟悉的 UX,它太费劲了。

而且,用户投入越多,期望越高、越难满足。Netflix 分享过:用户对来自搜索这类显式动作的推荐有更高期望。一般来说,用户投入的精力越多(如聊天、搜索),他们的期望就越高。这与滚动浏览推荐栏或点击商品这类低投入交互形成对比。

因此,聊天虽然提供了更多灵活性,却也要求更多用户投入。此外,使用聊天框不够直观,缺少关于如何调整输出的示能(signifier)。总体上,我认为坚持熟悉且受约束的 UI 更容易让用户在我们的产品中导航;聊天只应作为次要或第三顺位的选择。

模式七:收集用户反馈——构建我们的数据飞轮

收集用户反馈让我们了解用户的偏好。对 LLM 产品而言,用户反馈有助于构建 evals、微调和护栏。想一想:数据——如用于预训练的语料、专家编写的示范、用于奖励建模的人类偏好——是 LLM 产品为数不多的护城河之一。因此在设计 UX 时,我们应有意识地思考如何收集用户反馈。

反馈可以是显式或隐式的。显式反馈是用户应我们产品的请求而提供的信息;隐式反馈是我们从用户交互中学到的、无需用户刻意提供的信息。

为什么收集用户反馈

用户反馈帮助我们的模型改进。通过了解用户喜欢什么、不喜欢什么、抱怨什么,我们可以改进模型以更好地满足他们的需求。它也让我们适应个体偏好。推荐系统就是典型例子:随着用户与条目交互,我们了解他们的好恶,并随时间更好地迎合其品味。

此外,反馈回路帮助我们评估系统的整体表现。evals 可以帮助度量模型/系统性能,而用户反馈提供了用户满意度与产品有效性的具体度量。

如何收集用户反馈

让用户容易提供反馈。三份指南对此都有呼应:

  • 微软:鼓励细粒度反馈(让用户能在与 AI 系统的日常交互中提供表达其偏好的反馈)。
  • 谷歌:让用户给予反馈(给用户实时教学、反馈和纠错的机会)。
  • 苹果:提供你的应用可用来改进其呈现内容与体验的可操作信息。

ChatGPT 就是这样一个例子。用户可以对回答点赞或点踩,也可以在回答真的很糟或没用时选择重新生成。这是关于人类偏好的有用反馈,随后可用于微调 LLM。

Midjourney 是另一个好例子。图像生成后,用户可以生成新一组图片(负反馈)、通过请求变体来调整某张图(正反馈),或放大并下载图片(强正反馈)。这使 Midjourney 能就生成的输出收集丰富的比较数据。

图:把收集用户反馈作为 UX 一部分的示例

也要考虑隐式反馈。隐式反馈随用户与我们的产品交互而产生。与显式反馈那种具体的回答不同,隐式反馈能提供关于用户行为与偏好的广泛数据。

类 Copilot 助手是典型例子。用户通过完全接受建议(强正反馈)、接受后做小改动(正反馈)或无视它(中性/负反馈)来表明建议是否有帮助。另外,他们可能修改引发代码生成的那条注释,暗示最初的代码生成没有满足他们的需求。

聊天机器人(如 ChatGPT 和 BingChat)是另一个例子。日使用量随时间如何变化?如果产品有粘性,说明用户喜欢它。还有,平均会话有多长?这可能不易解读:更长的对话是因为对话引人入胜、富有成果而更好?还是因为用户花了更久才得到所需而更糟?

其他常见的机器学习模式

除了上述七种模式,机器学习中还有其他与 LLM 系统和产品相关的模式,包括:

  • 数据飞轮(data flywheel):持续的数据收集改进模型,带来更好的用户体验;这反过来促进更多使用,产生更多数据以进一步评估和微调模型,形成良性循环。
  • 级联(cascade):与其把一个复杂任务整个丢给 LLM,不如简化并拆解它,让 LLM 只处理自己擅长的事,比如推理或优雅地表达。RAG 就是一例:与其依赖 LLM 基于内部知识检索和排序条目,不如用外部知识增强 LLM,聚焦于发挥 LLM 的推理能力。
  • 监控(monitoring):这有助于展示 AI 系统带来的价值(或没有价值)。有人分享过一个轶事:一个基于 LLM 的客服方案在生产环境跑了两周就停用了——A/B 测试显示,用 LLM 替代他们的客服团队时损失多了 12 倍!

(更多关于机器学习代码与系统设计模式的内容。)

另外,以下是其他人补充的观点:

  • 关注点分离/任务拆解——为不同子任务使用不同的 prompt 并串联起来,有助于注意力和可靠性(代价是延迟)。我们曾难以同时指定僵硬的输出结构和可变的响应内容,于是把任务拆开了。——Erick Enriquez
  • 还需要一些其他的:基于角色的访问控制——谁能访问什么;安全——如果我把数据库和 LLM 一起用,如何确保有正确的安全防护。——Krishna
  • 一致的输出格式:把输出设为 JSON 这样的标准化格式;工具增强(tool augmentation):把任务卸载给更专业、经过验证、可靠的模型。——Paul Tune
  • 安全:缓解缓存投毒、输入验证、缓解 prompt 注入、训练数据来源可追溯、输出不含带漏洞的代码、缓解针对工具(AI Agent)所用请求的恶意输入、缓解拒绝服务(对 LLM 做压力测试),等等。——Anderson Darario
  • 另一个 UX/UI 相关的:激励用户对生成的答案提供反馈(隐式或显式)。隐式的可以是 Copilot 幽灵文本那样,若以 TAB 接受即表示正反馈等。——Wen Yang
  • 很棒的清单。我会加上一致性检查(如自一致性采样)、任务的链式与拆解,以及多个模型输出的集成(ensembling)。这些我几乎每天都在用。——Dan White
  • 对构建分析类工具(LLM 作为从自然语言到编程语言的翻译器)来说,护栏极其相关。——m_voitko

结语

这是迄今为止我写过的最长的一篇文章。如果你读到了这里,谢谢你!希望阅读这些模式对你有所助益,也希望下面的 2×2 图说得通。

图:沿「数据到用户」与「防御到进攻」两条轴分布的 LLM 模式。

我们仍处在构建 LLM 系统与产品的非常早期的阶段。还有其他关键模式或资源吗?你发现什么有用、什么没用?我很想听听你的经验。欢迎联系我

参考资料

原文文末附有以下参考文献(按惯例保留原文):

  • Hendrycks, Dan, et al. “Measuring massive multitask language understanding.” arXiv preprint arXiv:2009.03300 (2020).
  • Gao, Leo, et al. “A Framework for Few-Shot Language Model Evaluation.” v0.0.1, Zenodo, (2021), doi:10.5281/zenodo.5371628.
  • Liang, Percy, et al. “Holistic evaluation of language models.” arXiv preprint arXiv:2211.09110 (2022).
  • Dubois, Yann, et al. “AlpacaFarm: A Simulation Framework for Methods That Learn from Human Feedback.” (2023)
  • Papineni, Kishore, et al. “Bleu: a method for automatic evaluation of machine translation.” Proceedings of the 40th annual meeting of the Association for Computational Linguistics. 2002.
  • Lin, Chin-Yew. “Rouge: A package for automatic evaluation of summaries.” Text summarization branches out. 2004.
  • Zhang, Tianyi, et al. “Bertscore: Evaluating text generation with bert.” arXiv preprint arXiv:1904.09675 (2019).
  • Zhao, Wei, et al. “MoverScore: Text generation evaluating with contextualized embeddings and earth mover distance.” arXiv preprint arXiv:1909.02622 (2019).
  • Sai, Ananya B., Akash Kumar Mohankumar, and Mitesh M. Khapra. “A survey of evaluation metrics used for NLG systems.” ACM Computing Surveys (CSUR) 55.2 (2022): 1-39.
  • Grusky, Max. “Rogue Scores.” Proceedings of the 61st Annual Meeting of the Association for Computational Linguistics (Volume 1: Long Papers). 2023.
  • Liu, Yang, et al. “Gpteval: Nlg evaluation using gpt-4 with better human alignment.” arXiv preprint arXiv:2303.16634 (2023).
  • Fourrier, Clémentine, et al. “What’s going on with the Open LLM Leaderboard?” (2023).
  • Zheng, Lianmin, et al. “Judging LLM-as-a-judge with MT-Bench and Chatbot Arena.” arXiv preprint arXiv:2306.05685 (2023).
  • Dettmers, Tim, et al. “Qlora: Efficient finetuning of quantized llms.” arXiv preprint arXiv:2305.14314 (2023).
  • Swyx et al. MPT-7B and The Beginning of Context=Infinity (2023).
  • Fradin, Michelle, Reeder, Lauren “The New Language Model Stack” (2023).
  • Radford, Alec, et al. “Learning transferable visual models from natural language supervision.” International conference on machine learning. PMLR, 2021.
  • Yan, Ziyou. “Search: Query Matching via Lexical, Graph, and Embedding Methods.” eugeneyan.com, (2021).
  • Petroni, Fabio, et al. “How context affects language models’ factual predictions.” arXiv preprint arXiv:2005.04611 (2020).
  • Karpukhin, Vladimir, et al. “Dense passage retrieval for open-domain question answering.” arXiv preprint arXiv:2004.04906 (2020).
  • Lewis, Patrick, et al. “Retrieval-augmented generation for knowledge-intensive nlp tasks.” Advances in Neural Information Processing Systems 33 (2020): 9459-9474.
  • Izacard, Gautier, and Edouard Grave. “Leveraging passage retrieval with generative models for open domain question answering.” arXiv preprint arXiv:2007.01282 (2020).
  • Borgeaud, Sebastian, et al. “Improving language models by retrieving from trillions of tokens.” International conference on machine learning. PMLR, (2022).
  • Lazaridou, Angeliki, et al. “Internet-augmented language models through few-shot prompting for open-domain question answering.” arXiv preprint arXiv:2203.05115 (2022).
  • Wang, Yue, et al. “Codet5+: Open code large language models for code understanding and generation.” arXiv preprint arXiv:2305.07922 (2023).
  • Gao, Luyu, et al. “Precise zero-shot dense retrieval without relevance labels.” arXiv preprint arXiv:2212.10496 (2022).
  • Yan, Ziyou. “Obsidian-Copilot: An Assistant for Writing & Reflecting.” eugeneyan.com, (2023).
  • Bojanowski, Piotr, et al. “Enriching word vectors with subword information.” Transactions of the association for computational linguistics 5 (2017): 135-146.
  • Reimers, Nils, and Iryna Gurevych. “Making Monolingual Sentence Embeddings Multilingual Using Knowledge Distillation.” Proceedings of the 2020 Conference on Empirical Methods in Natural Language Processing, Association for Computational Linguistics, (2020).
  • Wang, Liang, et al. “Text embeddings by weakly-supervised contrastive pre-training.” arXiv preprint arXiv:2212.03533 (2022).
  • Su, Hongjin, et al. “One embedder, any task: Instruction-finetuned text embeddings.” arXiv preprint arXiv:2212.09741 (2022).
  • Johnson, Jeff, et al. “Billion-Scale Similarity Search with GPUs.” IEEE Transactions on Big Data, vol. 7, no. 3, IEEE, 2019, pp. 535–47.
  • Malkov, Yu A., and Dmitry A. Yashunin. “Efficient and Robust Approximate Nearest Neighbor Search Using Hierarchical Navigable Small World Graphs.” IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 42, no. 4, IEEE, 2018, pp. 824–36.
  • Guo, Ruiqi, et al. “Accelerating Large-Scale Inference with Anisotropic Vector Quantization.” International Conference on Machine Learning, (2020)
  • Ouyang, Long, et al. “Training language models to follow instructions with human feedback.” Advances in Neural Information Processing Systems 35 (2022): 27730-27744.
  • Howard, Jeremy, and Sebastian Ruder. “Universal language model fine-tuning for text classification.” arXiv preprint arXiv:1801.06146 (2018).
  • Devlin, Jacob, et al. “Bert: Pre-training of deep bidirectional transformers for language understanding.” arXiv preprint arXiv:1810.04805 (2018).
  • Radford, Alec, et al. “Improving language understanding with unsupervised learning.” (2018).
  • Raffel, Colin, et al. “Exploring the limits of transfer learning with a unified text-to-text transformer.” The Journal of Machine Learning Research 21.1 (2020): 5485-5551.
  • Lester, Brian, Rami Al-Rfou, and Noah Constant. “The power of scale for parameter-efficient prompt tuning.” arXiv preprint arXiv:2104.08691 (2021).
  • Li, Xiang Lisa, and Percy Liang. “Prefix-tuning: Optimizing continuous prompts for generation.” arXiv preprint arXiv:2101.00190 (2021).
  • Houlsby, Neil, et al. “Parameter-efficient transfer learning for NLP.” International Conference on Machine Learning. PMLR, 2019.
  • Hu, Edward J., et al. “Lora: Low-rank adaptation of large language models.” arXiv preprint arXiv:2106.09685 (2021).
  • Dettmers, Tim, et al. “Qlora: Efficient finetuning of quantized llms.” arXiv preprint arXiv:2305.14314 (2023).
  • Williams, Adina, et al. “A Broad-Coverage Challenge Corpus for Sentence Understanding through Inference.” Proceedings of the 2018 Conference of the North American Chapter of the Association for Computational Linguistics: Human Language Technologies, Volume 1 (Long Papers), Association for Computational Linguistics, (2018).
  • GPTCache (2023).
  • Bai, Yuntao, et al. “Training a helpful and harmless assistant with reinforcement learning from human feedback.” arXiv preprint arXiv:2204.05862 (2022).
  • Guardrails (2023)
  • NeMo-Guardrails (2023)
  • Manakul, Potsawee, Adian Liusie, and Mark JF Gales. “Selfcheckgpt: Zero-resource black-box hallucination detection for generative large language models.” arXiv preprint arXiv:2303.08896 (2023).
  • Guidance (2023).
  • Amershi, Saleema, et al. “Guidelines for human-AI interaction.” Proceedings of the 2019 chi conference on human factors in computing systems. 2019.
  • People + AI Guidebook (2023).
  • Human Interface Guidelines for Machine Learning (2023).
  • Schendel, Zachary A., Faraz Farzin, and Siddhi Sundar. “A Human Perspective on Algorithmic Similarity.” Proceedings of the 14th ACM Conference on Recommender Systems. 2020.

延伸阅读:如果你想把这些模式放进 FDE 的交付语境中——比如评估与护栏如何对应方案验收、RAG 与微调如何对应知识库交付、防御式 UX 如何对应客户培训——可以进一步阅读站内的 Agent 主题AI Coding 主题FDE 能力模型,或在 指南目录中查找相关实践指南。

本文由 FDEChina 团队翻译自原文,转载已注明出处;如需引用请以原文为准。