动手点关注
干货不迷路
本文旨在让无大模型开发背景的工程师或者技术爱好者无痛理解大语言模型应用开发的理论和主流工具,因此会先从与LLM应用开发相关的基础概念谈起,并不刻意追求极致的严谨和完备,而是从直觉和本质入手,结合笔者调研整理及消化理解,帮助大家能够更容易的理解LLM技术全貌,大家可以基于本文衍生展开,结合自己感兴趣的领域深入研究。若有不准确或者错误的地方也希望大家能够留言指正。
本文体系完整,内容丰富,由于内容比较多,分多次连载 。
第一部分 基础概念
1.机器学习场景类别
2.机器学习类型(LLM相关)
3.深度学习的兴起
4.基础模型
第二部分 应用挑战
1.问题定义与基本思路
2.基本流程与相关技术
1)Tokenization与Embbeding
2)向量数据库
3)finetune(微调)
4)模型部署与推理
5)prompt
6)编排与集成
7)其它(预训练等)
第三部分 场景案例
常用参考
2.基本流程与相关技术
点此查看前面内容:
4)编排与集成
经过前面的介绍,我们对大模型应用开发的通常涉及到的重要构成组件都有了介绍,然而这些零散的组件需要有一根”线“将它们串联起来,最终变成用户可以感受到的LLM应用。这里编排集成服务便起到了这样的“线“的作用,它作为粘合剂和应用骨架,在整个应用构成中起到了提纲挈领的作用。
在本章你将学习到:
1)相关理论和应用开发范式
2)Langchain框架介绍
3)LlamaIndex框架介绍
4)Semantic Kernel框架介绍
5) 技术展望
相关理论和应用开发范式
在本节中,我们关注两个问题,首先是编排集成框架在LLM应用中的定位,解决的痛点、核心价值及功能构成。第二,对于LLM应用来讲,它的开发范式和传统开发有何不同,好的编排框架是如何帮助开发者正确构建LLM应用的。
应用开发范式
在前面我们指出了移动互联网时代APP与大模型时代APP在产品价值形态上的不同,我们也不难想到大模型应用开发相较于传统的软件开发也会有比较大的变化。
而这种 核心原因在于 传统的软件开发只和 代码有关, 一个传统的软件开发过程,只需要产品经理定义好 需求 ,开发人员按照需求 编码实现即可, 而保证APP是否达到预期 质量 的方式也比较简单,就 是严格全面的 测试 ,做到了 这 样,就能保证 发 布到生产的 应用是可靠的。 另外, 传统应用的特性不会随着外界的变化而变化,它的逻辑都被固化在代码中,只要代码(包含环境 ) 正确,应用就正常 。 因此,在那个DevOPS的 年代, 开发质量的核心就 是保证 开发代码到生产代码 的 一致性,相关技术也是围绕这一目的展开,经典的就是docker的出现 。
到了AI 1.0时代,一个AI应用就由单纯的代码变成了代码,模型及数据构成。因此,一个AI应用是否达到预期质量不仅与代码实现相关,更受到模型和数据的影响。另外,由于传统小模型本身的特性,其能力是单一稳定的,因此代码与模型的能力边界是相对清晰的。另一方面,由于小模型无法做到一次训练多次使用的目标,因为存在概念和数据的drift(详见:新书推荐:《MLOps工程实践》)需要高频训练,因此在这一阶段除了传统软件开发关注的问题外,需要做到模型训练到推理的正确性,消除其中的不一致,而这正是MLOPS关注的重点。
现在,进入了LLM应用时代,大模型其具备了几个核心特点,导致了开发范式的再度迭代。大模型相较于传统小模型最大的特点在于它具备了一定的通用性,通过Prompt就可以操纵大模型完成不同的任务。另一方面,大模型的性能表现以及训练微调成本,都使得模型训练变成了一个低频的动作,普通开发者无需自己训练模型,可能只需要不断升级替换更强大模型即可。而这样的结果都指向了一个问题,那就是代码和模型的能力边界是不稳定的。每一次大模型的更新都将会是外围代码的灾难。另一方面,操纵大模型实现某个功能成本极低,可能只是Prompt(自然语言)的调整,而对于通过代码实现相应功能来讲,落后了一个时代。而更重要的是, 随着整个应用的 闭环化, 一些固定 策略 代码效果远远不 如不断自我完善的 模型 。但大模型有大模型的问题,比如其输入输出的自然语言特征,带来的天然不精确的问题,其带来的就是系统可能的不稳定。
因此,当下LLMOPS 其关注重点不 再是模型训练推理本身,而更多朝着如何更好地围绕模型构建应用展开。
而这个时候,如何 处理好代码做什么, 模型做什么这一边界问题就变 得尤为重要 ,将有限的精力投入到正确的地方 以及充分理解大模型的特点,扬长避短,如 此这样才能开发出一款优质的LLM应用。
那么,具体怎么做才能达到这一目标呢?微软的CVP , Deputy CTO Sam Schillace曾在今年5月份发表了一篇博客:Early Lessons From GPT-4: The Schillace Laws,探讨了大模型开发的可能的最佳实践,且把它当作semantic-kernel的设计原则。它有9条内容:
1.如果模型能够完成某项任务,就不要编写相应的代码;模型会变得越来越好,而代码则不会。这个系统的总体目标是利用语言模型的能力来规划和理解意图,以构建具有极高效益的程序。很容易滑入更多命令式思维的状态,为程序的各个方面编写代码。要抵制这种诱惑 - 在你能够让模型可靠地完成某项任务的程度上,随着模型的发展,它会变得更好、更健壮。
2.牺牲代码实现的精确性换取通用性、再利用与大模型交互来弥补。在使用 LLM 进行编码时,正确的心态不是 "看看我们能让这只跳舞的熊做什么",而是尽可能多地利用系统。例如,我们可以建立非常通用的模式,如 "从数据库中建立一份报告 "或 "教授一年的课程",这些模式可以通过纯文本提示进行参数化,从而轻松产生极具价值和差异化的结果。
3.代码负责处理语法和流程,模型负责语义和意图。有很多不同的说法,但从根 本上说,模型更强于对意义和目标进行推理,而弱于执行具体的计算和流程。例如,高级模型编写代码来解决一般的数独问题很容易,但它们自己解决数独问题却很难。每种代码都有不同的优势,重要的是要针对正确的问题使用正确的代码。语法和语义之间的界限是这些程序的难点所在。
4.系统的脆弱性取决于其最脆弱的部分。无论哪种代码都是如此。因为我们追求的是灵活性和高利用率,所以最重要的是不要硬编码。尽可能的在Prompt中加入推理和灵活性,尽量少用命令式代码来使用 LLM。
5.聪明的问题,才能获取聪明的答案。LLM非常强大且“受过良好教育”,但它们缺乏上下文和主动性。如果你问他们一个简单或开放式的问题,你会得到一个简单或通用的答案。如果你想获得更多细节和改进,就必须更聪明的提问。这是对AI时代的“Garbage in, Garbage out”的回声。
6.存在不确定性时需要异常抛出。因为我们是在用精确性换取通用性,所以当模型的意图不确定时,我们需要依赖与用户的交互补偿。因此,当我们在程序中嵌套了一组提示,而其中一个提示的结果不确定(一种可能的方式)时,正确的做法是类似于异常抛出(Exception Throw)处理--将不确定性向上传递,直到可以澄清或与用户交互的层级。
7.文本是通用的传输协议。由于 LLM 擅长解析自然语言、意图和语义,因此文本是在Prompt、模块和基于 LLM 的服务之间传递指令的自然格式。自然语言在某些情况下上不够精确,也可以少量使用 XML 等结构化语言,但一般来说,在提示符之间传递自然语言的效果非常好,而且在大多数用途上也没有结构化语言那么脆弱。随着时间的推移,随着这些基于模型的程序越来越多,这是一种自然的 "未来证明(future proofing)",它将使不同的Prompt能够像人类一样相互理解。
8.你难,模型也难。在给模型布置一项具有挑战性的任务时,一个常见的模式就是让它 "大声推理(reason out loud)(一步步带步骤输出)"。这很有趣,但如果把提示作为程序的一部分来使用,就会出现问题,因为程序只需要推理的结果。不过,使用 meta prompt(参看: 一文探秘LLM应用开发(21)-Prompt(提示工程技术、重要性与挑战) ),将问题和详细答案给出,并要求提取答案,这样做效果相当好。这是一项对人来说更容易的认知任务 (可以想象只需要给某人一个通用任务:“阅读这个并找到答案”,并且在用户没有专业知识的情况下,这项任务可以在许多领域中完成,因为自然语言非常强大)。因此,在编写程序时,请记住,对人来说困难的事情很可能对模型也很困难,将模式分解成更容易的步骤通常会得到更稳定的结果。
9.注意“意识幻觉”,模型可以被用来对付它自己。我们很容易把 LLM 想象成 "大脑 "。但是,人类思维与模型之间存在着很大的差异。其中一个可以被利用的差异是,模型目前无法记住记住这一分钟到下一分钟的交互。因此,虽然我们永远不会要求人类在他们刚刚亲自编写的东西中查找错误或恶意代码,但我们可以对模型这么做。它可能会在两个地方犯同样的错误,但它不会对我们 "撒谎",因为它不知道这些代码从何而来。这意味着我们可以在某些地方 "利用模型来对付自己"--它可以用作代码的安全监控器、测试策略的组成部分、生成内容的内容过滤器等。
当然,LLM应用开发的最佳实践不仅限于这9条,它会在开发实践过程中不断丰富,而作为舞台中央的集成编排框架, 就更需要总结和沉淀,并将其体现在自己的框架中,从而 帮助开发者正确高效构建LLM应用,继而赢得自己的成功。
由于篇幅原因,在下一节中将介绍langchain框架的前世今生以及基本功能,欢迎关注。
点此查看合集: