第一次LLM学习之旅
LLM出来之后,一直很想要一个自己的知识库系统。很早之前就选好了Obsidian当自己的知识库系统,把自己的各种知识收集进去。
Ob的一大特点就是所有东西都是直接装在目录里的,顺便可以当成一个目录管理器用。
这样做的意义在于,以后可以自己控制LLM处理这些文件的细节。
其实之前玩过本地的Stable-Diffustion
,效果非常好,已经是一个成熟的调包侠了,会自己从HuggingFace
上找model,然后找提示词网站来组装生成图。
但是对于语言大模型,部署的过程一直不太顺利。最开始我的选择是国产的ChatGlm-6B
。
但是在过程中多次出现各种跑不起来的情况,最后恍然大悟Windows不好使,还是得上Linux。
当时其实有Hype-V的Linux虚拟机,用来写我的eBPF毕设。
后面发现Hype-V里没法轻松把显卡挂进去,又对WSL有一种莫名的抗拒,于是索性没有往下搞。
最近突然心血来潮,又想来试试大模型有什么别的学习路径,这下发现了Ollama
这个好东西。
开端:Ollama,想做大模型界的Docker
如题所示,Ollama做了一些相当不错的封装。我把我心目中最终要的命令列在下面先,不妨来看看:
ollama pull qwen2
ollama list
ollama run qwen2
ollama ps
怎么样?如果你也是个Linux老鸟的话你会发现,ollama
这个命令和docker
似乎一模一样。
这也是ollama的追求,它似乎想做LLM时代的Docker。
当然,上面这四行命令中,最吸引人的当然是第一条和第三条。
我依然记得下载ChatGlm的时候,要去清华的网盘里把所有bin文件都点一遍下载。
虽然它给了压缩按钮,但是由于目录太大,它不让我压缩。
而ollama只需要一个pull,一个也许是你熟知的大模型就拉取到你本地了。
这个拉取过程支持断点续传,并且拉取的似乎也是一种分层模型,颇有docker中union filesystem的味道。
而ollama run更是惊艳,相比于其他llm那配置了半天还要自己写代码配置的方式,一行ollma run之后,一个对话框就出现了。
一个最简单的对话过程就开始,这和我们在网页上使用的各种什么大模型,没有任何区别。
当这个存在于终端里的对话框出现的时候,我突然意识到,这就是LLM本来的样子。
悟道:Transformer,也许仍然是个函数
在真正接触本地LLM之前,我一直有一个疑问,LLM到底是不是真正的通用人工智能?
但当我把本地LLM跑起来之后,我意识到这玩意远比我想象的要“结构简单”。
目前大多数LLM使用的是一种叫Transformer的架构。其实除了它还有一些别的架构,比如LSTM。
虽然我也不知道这些架构具体有哪些差异,但从结论上来看,Transformer解决了一个非常重要的问题:当神经网络层数增加时,对算力的需求不会陡增。
而这意味着,只要使用大量算力进行计算,效果就一定会变好。这在一定程度上解释了,为什么GPT-2到GPT-3有着如此大的提升——架构对了,怼算力就完事了。
至于Transformer究竟是什么?我只是个小小的工程师,这种专业问题还是问问搞AI的大佬吧。对于一个工程师来说,Transformer就是一个函数。我说的函数不是数学上那个函数,数学上的函数很好理解,毕竟啥都能当成函数。我说的是我们编程的那个函数。
def transformer(*args,**kwargs):
return sth
怎么样,是不是眼熟了许多?为什么要用这种描述呢?
这种认知意味着,当相关软件架构成熟后,任何工程师都可以将LLM作为自身软件的一部分。
但同时,由于LLM本身即是一个Transformer(这里我们暂时不考虑其他架构),我们只需单独处理它的输入和输出即可。
那么下一个问题是,这是一个怎样的函数?
在讨论这个问题前,让我们先来看看最常见的GPT模型,有没有想过GPT的全名是什么?
GPT全称Generative Pre-trained Transformer。从这个名字上我们可以看出来两件事。
首先,第一个问题在于这个Generative,与Artificial general intelligence(通用人工智能)不同,GPT很“谦虚”,但也很精确地描述了自己是什么,它不是AGI,而是单纯一个预训练过的大号Transformer。
这与我们上面得出来的结论是一致的。
其次,第二个问题在于这个pre-trained(预训练)。
这个过程本身其实很好理解,就是爬取网络上的各种知识给它,进行训练。
但这种训练意味着,这个函数的内部被改变了。
而函数内部的改变意味着训练前后,对于同一输入,给出的输出可能会大大不同,因为此时函数内部发生了变化。
现在让我们重新来审视这个“函数”。它应该长这个样:
def pre_train():
return parser
def transformer():
return parser(sth)
同时,这种预训练过程还导出了另一个结论——你有没有担心过AI替代人类这种问题?
现在你应该知道,这玩意根本做不到这种事,它就是个简单的函数,接受参数(我们一般管这个叫prompt,提示词),给出输出。更何况,它甚至不会上网自个找资料——因为它是预训练的,而不是实时的。
现在想必你会有一个疑问,能不能让他上网?网上的知识浩如烟海,要是能上网岂不是无敌了?
增强:RAG,给LLM插上翅膀
既然Transformer可以处理我们的问题,也可以处理网上的知识,那我们把网上的知识和我们的问题一起交给它不就好了。只不过,在获取网上的知识这一步,我们需要多一些步骤,把网上的知识处理成文本形式。
或者更近一步呢?也许不只是网上的知识,也许可以是本地的。也不是不只是文字的,也可以是图片,可以是文档甚至是更多可能的格式。
上面描述的过程,在LLM这里叫做RAG(Retrieval Augmented Generation,检索增强生成)。
简单来说,就是在使用大模型回答问题前,先帮大模型搜罗一部分资料,这种资料往往不被包含在大模型预训练时获取的部分,且这种资料一般有较强的实时性。
RAG应用一般有以下几个步骤:
- 加载数据,从上面提到的各类数据里加载进来
- 分词,由于数据众多,所以要依据一定规则将其分片
- 存储,分词后就可以建立索引,方便后续的提取
- 取值,从索引中找到数据,进行提取
- 生成,依据提取出来的数据和用户的问题一同输出给大模型,生成结果
以代码形式展示就是:
question = "?"
def RAG():
raw_documents = load()
chunks = split(raw_documents)
index = store(chunks)
prompt = retrieve(index)
result = generate(prompt + question)
return result
有了这种能力,相当于给大模型插上了翅膀,大模型的能力不仅仅局限于预训练时的数据,也对实时的数据有了处理能力。
扮演:Prompt,戏精上身,但更懂你
现在,是时候让LLM来帮忙干点活了,比如算算当下很火的数学题:3.9和3.11谁大?
为了防止一些非计算机领域或者不了解计算机版本号的同学get不到这个问题的精髓之处,还是稍微做些补充。从数学上来说,当然是3.90大于3.11,但在软件工程里,这种数字通常表示大版本+小版本,也就是3.09小于3.11。
当大家去问这个问题的时候,可能由于训练的预料不同得到不同的结果。
比如使用计算机知识语料进行训练的话,可能就是3.9<3.11。
但如果训练时专门对数学类问题进行过调优的话,就能很好地解释3.9>3.11。
倘若一个没有计算机知识的用户碰到了前一种大模型,就容易让人有种感觉:大模型好蠢啊。
但其实对人来来说,这个问题也未必是那么好回答的。
倘若网上冷不丁地弹出一个窗口问我这俩哪个大,我还真得楞一下,然后不知所措——因为我能意识到,这道题目似乎是少了条件的,他可没说是软件版本号还是数字比大小。
但如果是人在问我,那我的判断也许多一丝把握。
如果是我的工程师同学来问我,那十有八九是版本号了,如果是我的弟弟妹妹来问我,十有八九是写作业偷懒不想自己算了,如果是我的兄弟——那估计是看到了这个帖子,过来消遣洒家的。
看,为什么我可以分辨出这些?当然是我了解这些人,知道他们的特点,从而可以给他们回答。那为啥LLM不行呢?
回到那个网上冷不丁弹窗口的场景,LLM看到的就是一个这样的场景。
它既不知道你是谁,也不知道你在搞什么鸡毛,但是LLM的责任心(其实也没啥责任心,都是预训练留下的结果)让它必须回答。那能怎么办呢?
想想你当年上课的时候被突然抽起来回答问题的场景就好了——根据以前的经验,猜一个嘛,你大喊一声选C,老师说这是填空题。
这就是上面这个场景中LLM无法回答的原因,它并非真的不知道怎么回答,也不是随便乱猜。
只是你啥也没告诉它,所以它只能从过往的经验里,挑了个概率最大的告诉你,简称挑最大的答。
那我们可以做什么呢?简单!告诉它上下文就好了。如果你要问数学问题,你就告诉LLM:
数学上3.9和3.11谁更大?
如果你要问版本号,那你就说:
计算机版本号上3.9和3.11谁更大
以上,便是提示词(prompt)工程,通过提示词来诱导LLM给出我们期望的答案。
无论未来的LLM发展到什么地步,这种提示词工程都将存在下去,
因为人类生存的世界里,上下文永远是连续的。
但每次LLM启动时,它都会回到它诞生(预训练完成)的那一刻。
当我们追求通用能力的时候,精确性就会下降,反之亦然。
而GPT的开创意义就在这里,它选择了用最广泛,最Generative的预料来训练,然后在推理阶段根据大量的提示词来进行上下文的推理计算,得到的也是最接近人类逻辑的答案。
最好的提示方式是什么?想比与念一些莫名其妙地咒语,目前最佳的方式似乎是扮演
。
扮成一个老师、一个工程师或者猫娘(?)
感觉提示词展开又是一个大工程,网上已经有相当多的教程了,还是请读着自行搜索吧。
应用:Agent,人机交互的最优解
等等,总不能到最后AI都去写诗歌了,我们还在打工吧。
我们这么努力发展AI可不是为了养个爹,所以怎么让AI来帮我们干活呢?
那得先想想我们在干啥?写代码、画图表,一万个人心中也许有一万个想法。
但是通过上面的介绍,你应该明白——LLM其实什么也做不了,因为LLM就是LLM。它接受一个输入,给出一个输出,它不是人,甚至不是机器人。它不会帮你做出任何事。它唯一会的,就是根据你的输入,结合各种其他的给定,给出一个输出。这也是为什么在上面的很多地方,我会用函数来描述LLM,它只是处理数据,并不做任何实际操作。
不过话说回来,既然能是个函数,那也就意味着,它可以被“组装”进现有的软件体系中,作为软件的一部分。比如我们可以给出几个选项,然后迫使LLM从这几个选项中选出一个可能的,然后交给软件来执行。
当然,实际上我们要考虑的要更多,比如LLM如何取数据,取错了怎么办,或者是没照着选项怎么办,所以很多情况下,我们需要引入更多代码来解决这个问题。上面这种开发方式,就是当下火热的Agent模式。
以我个人的观点,这种开发模式中最重要的一点就是:给LLM配备工具。LLM本身是不会使用工具的,这和很多大家看到的能自适应一些环境的机器人不同。只有给LLM提供工具,并让LLM来选择使用什么工具、以何种方式使用工具,才有望让LLM真正为人类服务。现在我们再看LLM,你会发现,对话,也许只是它最最最简单的那个应用,Agent才是LLM的未来形态。
未来:LLM,钉子何在?
关于LLM我一直有一些暴论,其中一个也许大多数人和我持有一样的观点:LLM的输出是不可信的。
这是一件相当重要的事情,LLM和我们过去见过的计算机或其他较为稳定的控制系统不同,它不是稳定的。我这里对于稳定的定义要相对简单一些:在任何情况下,当输入一致的时候,输出也应该一致。但很显然,就目前的观察而言,有相当多的案例下,LLM的输出是不稳定的。
这意味着,你无法让它像其他人类设计出来的自动控制系统一样,在一些工业场景下工作,更何况LLM的内部还是个黑盒。这一点相当程度上制约了它的应用,这是一个概率问题,如果人类设计的系统可用性是99.99%,那LLM也许是个未知的概率。
我尝试搜索了下,LLM的输出为何做不到稳定,是否是因为加入了某些seed或是其他的随机数机制呢?很遗憾,我似乎找不到我能看懂的答案,如果哪天有大佬看到了这个问题,我很愿意接受大佬的指导。但我确信的是,如果LLM的输出做不到绝对稳定的话,我们就没法直接把工具直接交给LLM。
但是反过来,我们也可以换个思路。既然LLM有如此强大的处理能力,不妨让LLM“给出建议”就好了,最终的决策,还是由人来完成好了。这也是我目前做LLM应用的思路。
关于未来,谁知道呢?最近看了一篇文章,其中的观点是现代的LLM其实还处于一个初级的阶段,所以需要大量的提示词工程来帮助从LLM中获得想要的答案,如果未来有一天LLM发展到了某个阶段,就不需要这么多提示词了,似乎听着像是那么回事。
不过有另一个好消息是,许多程序员也许不用担心失业了。LLM不会让程序员失业,相反,它需要更多优秀的程序员去建设它。未来的LLM应当是触手可及的、人人可以去建设的,而不是高高在上的、遥不可及的。