引言
这篇文章并不是对大型语言模型(LLMs)的全面回顾。很明显,2023年对人工智能而言是特别的一年,但再次强调这一点似乎毫无意义。相反,这篇文章旨在作为一个程序员个人的见证。自从ChatGPT问世,以及后来使用本地运行的LLMs,我广泛使用了这项新技术。目的是加速编写代码的能力,但这并非唯一目的。还有一个意图是不在编程的不值得花费精力的方面浪费精神能量。无数小时花在寻找关于特殊、智力上不感兴趣方面的文档;努力学习过于复杂的API,通常毫无充分理由;编写一些几小时后就会丢弃的立即可用程序。这些都是我不想做的事情,尤其是现在,谷歌成了一个满是垃圾的海洋,只为寻找少数有用的东西。
与此同时,我当然不是编程的新手。我能够在没有任何帮助的情况下编写代码,事实上我经常这样做。随着时间的推移,我越来越多地使用LLMs来编写高级代码,特别是在Python中,而在C语言中则少得多。我个人经历中关于LLMs的一点是,我已经学会了何时使用它们,以及它们的使用何时只会拖慢我的速度。我还了解到,LLMs有点像维基百科和YouTube上散布的所有视频课程:它们帮助那些有意愿、能力和纪律的人,但对那些落后的人只有边际效益。我担心至少最初,它们只会让已经具有优势的人受益。
但让我们一步一步来。
全知还是鹦鹉?
在这波机器学习的新潮流和进步中,最令人担忧的现象之一是AI专家们接受自己知识有限的能力有限。智人发明了神经网络,然后更为关键的是,发明了自动优化神经网络参数的算法。硬件变得足以训练越来越大的模型,并使用关于要处理的数据的统计知识(先验知识),通过大量的试错和逐步逼近,发现了比其他架构更有效的架构。但总的来说,神经网络仍然相当不透明。
面对这种无法解释LLMs某些新兴能力的无能为力,人们本来会期望更多的科学家谨慎。相反,许多人严重低估了LLMs,说它们毕竟不过是一些稍微先进的马尔可夫链,最多能够复述它们在训练集中所见内容的极其有限变化。然后这种鹦鹉论,面对证据,几乎被普遍撤回。
同时,许多热情的群众将超自然力量归因于LLMs,而这些力量实际上并不存在。不幸的是,LLMs最多只能在它们在训练期间看到的数据所代表的空间内插值:而这已经是很多了。实际上,它们的插值能力有限(但仍然令人惊讶,也出乎意料)。哦,如果今天最大的LLMs能够在它们所见代码的所有空间内连续插值!即使它们不能产生真正的新颖之物,它们也能够取代99%的程序员。现实更为谦逊,几乎总是如此。一个LLM当然能够编写程序,它在训练集中以一定频率出现的不同想法融合在一起。这种能力目前有很深的限制,而且每当需要微妙的推理时,LLMs都会失败。但它们代表了从AI的黎明到今天的最大成就。这似乎是不可否认的。
愚蠢但全知
没错,LLMs最多只能进行基本的推理,经常不准确,还常夹杂着关于不存在的事实的幻觉。但它们拥有广泛的知识。在编程领域,以及其他有质量数据的领域,LLMs就像愚蠢的学者,知道很多事情。和这样的合作伙伴进行配对编程会很糟糕(对我来说,配对编程在最广义上都很糟糕):他们会有无意义的想法,我们必须不断斗争来强加我们自己的想法。但如果这个博学的傻瓜听从我们的指令,回答我们提出的所有问题,情况就会改变。当前的LLMs不会引导我们走出知识的道路,但如果我们想要处理一个我们不太了解的主题,它们通常可以将我们从完全无知提升到我们知道足够多以便独立前进的地步。
在编程领域,也许直到二十或三十年前,它们的能力还不那么有趣。那时你只需要了解几种编程语言、经典算法,以及那十个基本库。其余的你必须自己补充,你自己的智慧、专业知识、设计技巧。如果你拥有这些元素,你就是一个能够做几乎所有事情的专家程序员。随着时间的推移,我们见证了框架、编程语言、各种库的爆炸式增长。这是一种经常完全不必要且无理由的复杂性增长,但事实就是如此。在这样的背景下,一个无所不知的傻瓜是一个宝贵的盟友。
让我举个例子:我的机器学习实验至少进行了一年,使用的是Keras。然后出于各种原因,我转而使用PyTorch。我已经知道什么是嵌入式或残差网络,但我不想逐步学习PyTorch的文档(就像我学习Keras时那样,那时ChatGPT还不存在)。有了LLMs,编写使用Torch的Python代码变得非常容易。我只需要对我想要组合的模型有清晰的想法,并提出正确的问题。
实战案例时间
我提到的并不是像“嘿,X类怎么实现Y功能?”这种简单的问题。如果仅仅如此,人们可能会倾向于同意那些对LLMs持怀疑态度的观点。更复杂的模型所能完成的任务要复杂得多。直到几年前,这些还被认为是纯粹的魔法。我可以告诉GPT4:看,这是我在PyTorch中实现的神经网络模型。这些是我的批次数据。我想调整张量的大小,以使产生批次的函数与神经网络的输入兼容,并且我想以这种特定方式表达它们。你能给我展示完成这种重塑所需的代码吗?GPT4编写了代码,我所要做的只是在Python CLI中测试张量是否真的具有对我有用的尺寸,以及数据布局是否正确。
还有一个例子。不久前,我需要为某些基于ESP32的设备实现一个BLE客户端。经过一番研究,我意识到多平台蓝牙编程绑定或多或少都不可用。解决方案很简单,就是使用Objective C和macOS的原生API编写代码。因此,我发现自己同时面临两个问题:一方面要学习Objective C的繁琐BLE API,这些API充满了我认为毫无意义的模式(我是个极简主义者,这种API与我所认为的“好设计”完全相反),另一方面还要回忆起如何用Objective C编程。我上一次用Objective C编写程序是十年前:我已经不记得事件循环、内存管理等细节了。
最终的结果是下面这段代码,虽然不太美观,但它完成了它需要做的事情。我在极短的时间内写完了它。否则这将是不可能的。
https://github.com/antirez/freakwan/blob/main/osx-bte-cli/SerialBTE.m
这段代码主要是通过在ChatGPT上剪切和粘贴我想做的事情,但不太清楚如何做,因此它们并没有正确工作。有了LLM向我解释问题是什么以及如何解决它。确实,LLM并没有写太多代码,但它显著加快了编写速度。如果没有ChatGPT,我能做到吗?当然可以,但最有趣的不是它会让我花费更长时间:事实是,我甚至不会尝试,因为这不值得。这一点至关重要。编写这样的程序,对我的项目来说是次要的,其努力与收益之间的比例是不合理的。此外,这比程序本身有更有用的次要附带效应:对于那个项目,我修改了linenoise(我的一款行编辑库),使其可以在多任务中工作。
另一个例子,这次更多关于数据解释而非代码编写。我想使用我在网上找到的一个卷积神经网络设置一个Python脚本,但它缺乏足够的文档。这个网络的一个优点是它采用ONNX格式,所以我可以轻松提取输入和输出列表及其分配的名称。我唯一知道的关于这个卷积网络的是:它在图像中检测某些特征。我不知道输入图像的格式和大小,特别是,网络的输出比我想象的要复杂得多(我原以为它是一个二元分类器:观察到的图像是否正常或存在问题?两个输出,但实际上有数百个)。我开始将ONNX网络元数据输出复制粘贴到ChatGPT。我向助手解释了我对网络的了解。ChatGPT推测输入是如何组织的,输出可能是标准化的框,指示与潜在缺陷对应的图像部分,以及其他指示这些缺陷可能性的输出。经过几分钟的交流,我得到了一个能够进行网络推断的Python脚本,以及将起始图像转换为适合输入的张量所需的代码等等。那次会话让我印象深刻的是,ChatGPT最终“理解”了网络的运作方式,当它在测试图像上观察到原始输出值(基本上是logits)时:一系列浮点数为识别确切的输出细节、标准化提供了上下文,无论是框居中还是指定左上角等等。
import pandas as pd
pd.set_option('display.max_rows', None)
df = pd.read_csv('listings.csv')
reservations = df[df['Type'] == 'Reservation']
reservations['Start Date'] = pd.to_datetime(reservations['Start Date'])
reservations['Year'] = reservations['Start Date'].dt.year
reservations['Month'] = reservations['Start Date'].dt.month
reservations['Nightly Rate'] = (reservations['Amount'] - reservations['Cleaning Fee']) / reservations['Nights']
all_listings = reservations['Listing'].unique()
all_years = reservations['Year'].unique()
all_months = range(1, 13)
index = pd.MultiIndex.from_product([all_listings, all_years, all_months], names=['Listing', 'Year', 'Month'])
all_data = pd.DataFrame(index=index).reset_index()
merged_data = pd.merge(all_data, reservations, on=['Listing', 'Year', 'Month'], how='left')
average_nightly_rates = merged_data.groupby(['Listing', 'Year', 'Month'])['Nightly Rate'].mean().fillna(0)
print(average_nightly_rates)
为了准确地理解如何对这些原本相当分散和混乱的数据进行分组,需要进行一些简单的推理:这不是LLM仅凭借重述它所看到的语言近似内容就能解决的问题。GPT4在其训练过程中肯定见过类似的程序,但几乎可以肯定它没有见过一个必须按照我所要求的确切方式对事物进行分组的程序,尤其是在具有特定格式的CSV文件中。这表明(在我看来),LLM能在一定程度上在训练集中不同程序所勾勒出的空间内进行插值。
在我看来,编写这类程序是对我的时间的不明智使用。LLM能够代替我完成这项工作,无疑加速了我以相当直接的方式产生代码的能力:我可以专注于重要的事项。
当事情不如预期:系统编程
尽管我在LLMs和编程方面取得了成功,但当我写C语言程序时,我几乎总是仅将LLMs用作更便捷的文档形式。作为系统编程方面的专家,只有在LLMs能够进行复杂推理时,它们才对我有用:在这种情况下,它们几乎总是失败的。
我们从这个实验的提示开始:
概述一个良好的、小型但可工作的C语言布隆过滤器实现的要求。重点关注散列函数的重要方面。然后用高质量的C语言编写它。
同时考虑到,你的示例实现应该能够存储100,000个元素,最大误判概率为5%。添加的元素是以空字符结尾的字符串。
以下是输出:
GPT4生成的实现并不完美。布隆过滤器相当普遍。它不是什么稀有的数据结构。然而,很明显,编写一个像样的布隆过滤器需要更强的抽象能力:例如,找到一个有效的方法来对同一个字符串进行N次散列,并确保散列足够去相关。
然而,如果你明确要求GPT4修改散列函数,以便产生N个去相关的输出,它提出的解决方案要更加可接受:
果它自己想出了这个想法,它会以不同的方式编写布隆过滤器,一次设置K个位,使用单个哈希函数。
GPT4能够独立完成编写适当且更通用哈希函数的任务。但在编写布隆过滤器的更大项目中,它未能显示出足够的推理能力,而是吐出了两个不同但相似的哈希函数:这是人们对布隆过滤器的糟糕文章示例所期望的。
大型语言模型当前的弱推理能力,加上该主题可能的资源匮乏,甚至更糟糕的是,潜在的许多低质量来源,共同导致了这种微薄的结果。然而,这并非个案。我尝试在算法或系统编程领域使用LLMs的几次,结果都非常糟糕,即使预期的推理能力不大于LLMs在Python编程等上下文中似乎拥有的能力。
同时,GPT-4能够反编译它生成的函数(在单独的会话中),并准确理解其目的:
https://chat.openai.com/share/f54b64d7-d19a-4be4-8f10-08553aa812f1
因此,声称在系统编程上下文中没有用途显然是不准确的。
有趣且预料之中的是,在这些情况下,小型和大型模型之间的差异确实显著。让我们看看两个本地模型的表现如何。
例如,Mistral的MoE Mixtral对同一个问题的回应如下:
显然,只在最后添加hash_id
是一个极其糟糕的解决方案。Mixtral是一个适用于许多用途的出色模型,但就LLMs已经较弱的推理能力而言,到目前为止的规则当然是:大型更好。
这是与本地LLM deepseek-coder的对话,由于我没有足够的RAM以更高精度使用该模型,因此将其量化为4位。然而,由于其拥有340亿个参数,它在这个问题上的推理能力似乎相当先进。以下是与llama.cpp进行的聊天记录:
我有一个哈希函数,应该为相同的数据输出N个不同的哈希。然而,我看到随着我改变hash_id的值,产生的哈希分布非常差:
unsigned int hash(const char* str, unsigned int hash_id) { unsigned int hash = 5381; int c; while ((c = *str++)) { hash = ((hash << 5) + hash) + c; } return hash + hash_id; }
也许我正在使用的加盐策略很差?我该如何改变它?
[Deep-seek coder的回复]
你实现中的一个可能问题是你只是简单地将hash_id
添加到哈希的末尾。这可能导致分布不佳,尤其是如果hash_id
太大或分布不均。
考虑使用位运算来混合hash_id
。这里有一个使用XOR的例子:
unsigned int hash(const char* str, unsigned int hash_id) {unsigned int hash = 5381; int c; while ((c = *str++)) { hash = ((hash << 5) + hash) ^ c + hash_id; // Using XOR to mix in the hash_id } return (hash ^ hash_id); // Mixing the hash_id one more time at the end
}
这种方法比起仅仅在最后添加hash_id
,更能有效地将其分布在整个哈希中,从而避免了分布不均的问题。[被省略…实际回复包含更多信息]
对于运行在我的MacBook M1 Max上的模型来说,这算是不错的表现。它还将求和与XOR混合使用。在这种情况下,模型显然受益于我提供了解决问题的线索,但重要的是,它识别出了问题的真正根源,并提出了有效的替代方案。像这样的东西并不是任何书籍、文档或Google搜索所能涵盖的。不管你怎么看待这个模型,认为它的结果是基础的、是插值的结果,或者不管你怎么理解它,这个模型已经进行了某种形式的推理,如果我们接受在这个特定案例中,识别问题的根源及其潜在解决方案作为推理的一部分。不管人们怎么看待LLMs,说它们对程序员没用是非常草率的。
然而,根据过去几个月的经验,对于系统编程来说,如果你已经是一位经验丰富的程序员,LLMs几乎从不提供可接受的解决方案。让我展示给你另一个现实世界的例子。我当前的项目ggufflib涉及编写一个读写GGUF格式文件的库,这是llama.cpp加载量化模型所使用的格式。最初,为了理解量化编码的工作原理(由于速度原因,每个量化的比特都以特殊的方式存储),我尝试使用ChatGPT,但后来我决定反向工程llama.cpp的代码,这样更快。如果LLM能看到数据编码的“结构”声明和解码函数,理论上它应该能够重建数据格式的文档。llama.cpp的函数足够小,可以完全适应GPT4的上下文,但输出完全无用。在这些情况下,我们采取的做法就像过去一样:用纸和笔,阅读代码,查看解码器提取的比特在哪里注册。
为了让你更好地理解上述用例,如果你愿意,可以自己尝试一下。我们有llama.cpp实现中的这个结构。
然后有这个用于执行解量化的函数:
void dequantize_row_q6_K(const block_q6_K * restrict x, float * restrict y, int k) {assert(k % QK_K == 0); const int nb = k / QK_K; for (int i = 0; i < nb; i++) { const float d = GGML_FP16_TO_FP32(x[i].d); const uint8_t * restrict ql = x[i].ql; const uint8_t * restrict qh = x[i].qh; const int8_t * restrict sc = x[i].scales; for (int n = 0; n < QK_K; n += 128) { for (int l = 0; l < 32; ++l) { int is = l/16; const int8_t q1 = (int8_t)((ql[l + 0] & 0xF) | (((qh[l] >> 0) & 3) << 4)) - 32; const int8_t q2 = (int8_t)((ql[l + 32] & 0xF) | (((qh[l] >> 2) & 3) << 4)) - 32; const int8_t q3 = (int8_t)((ql[l + 0] >> 4) | (((qh[l] >> 4) & 3) << 4)) - 32; const int8_t q4 = (int8_t)((ql[l + 32] >> 4) | (((qh[l] >> 6) & 3) << 4)) - 32; y[l + 0] = d * sc[is + 0] * q1; y[l + 32] = d * sc[is + 2] * q2; y[l + 64] = d * sc[is + 4] * q3; y[l + 96] = d * sc[is + 6] * q4; } y += 128; ql += 64; qh += 32; sc += 8; } }
}
如果我让GPT4写出所使用的格式的概要,它难以清晰地解释“ql”的下4位/上4位是如何根据权重位置存储块的。对于这篇博客文章,我还尝试让它编写一个展示数据存储方式的更简单的函数(也许它不能用文字解释,但可以用代码)。生成的函数在许多方面都是错误的,索引不正确,6位到8位的符号扩展也不正确(它只是将其强制转换为uint8_t),等等。
顺便说一下,这是我自己最终编写的代码:
在上述函数中,我省略了这段代码的实际贡献:长篇大论地记录了llama.cpp Q6_K编码使用的确切格式。现在,如果GPT能够为我完成这项工作,那将是极大的帮助,我敢打赌这只是时间问题,因为这类任务在没有任何技术突破的情况下,仅通过稍微扩展就可以实现。
看待问题的角度
我不得不承认,但这是事实:当今的大多数编程工作都在以略有不同的形式重复相同的事情。这些工作不需要高水平的推理能力。LLMs在这方面表现得相当好,尽管它们仍然受到上下文大小的极大限制。这应该让程序员反思:编写这种类型的程序是否真的值得?当然,你会因此获得报酬,而且相当可观,但如果LLM能够完成部分工作,那么在五到十年后,这可能不是最佳的职业选择。
然后,LLMs真的具有一些推理能力,还是这一切都只是虚张声势?也许有时候,它们看似在进行推理,只是因为,如符号学家所说,“标志”给人一种实际上并不存在的意义的印象。那些深入了解LLMs的人在接受它们的限制的同时,肯定知道事实并非如此:它们融合先前所见内容的能力远超过随机重复单词。尽管它们的训练主要在预训练阶段进行,以预测下一个词,这个目标迫使模型创建某种形式的抽象模型。这个模型虽然脆弱、零碎且不完美,但如果我们观察到我们所观察到的,它必须存在。如果我们的数学确定性是可疑的,而最伟大的专家们常常持有相反的立场,那么相信自己的眼睛似乎是明智的。
最后,今天不使用LLMs进行编程有什么意义呢?向LLMs提出正确的问题是一项基本技能。练习得越少,人们就越难借助AI提高他们的工作效率。此外,发展描述问题的能力在与其他人交流时也很有用。LLMs并不是唯一有时候不能理解我们想说什么的对象。沟通不畅是一个很大的限制,许多程序员尽管在他们特定领域非常有能力,但沟通能力非常差。现在Google已经不太可用:即使仅将LLMs用作压缩形式的文档也是一个好主意。对于我来说,我将继续广泛使用它们。我从来不喜欢学习那些晦涩的通信协议细节或某人为了展示他们的能力而编写的复杂库方法。对我来说,这些似乎是“垃圾知识”。LLMs正在帮助我每天都更多地避免这些问题。
如何学习大模型 AI ?
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
第一阶段(10天):初阶应用
该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。
- 大模型 AI 能干什么?
- 大模型是怎样获得「智能」的?
- 用好 AI 的核心心法
- 大模型应用业务架构
- 大模型应用技术架构
- 代码示例:向 GPT-3.5 灌入新知识
- 提示工程的意义和核心思想
- Prompt 典型构成
- 指令调优方法论
- 思维链和思维树
- Prompt 攻击和防范
- …
第二阶段(30天):高阶应用
该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。
- 为什么要做 RAG
- 搭建一个简单的 ChatPDF
- 检索的基础概念
- 什么是向量表示(Embeddings)
- 向量数据库与向量检索
- 基于向量检索的 RAG
- 搭建 RAG 系统的扩展知识
- 混合检索与 RAG-Fusion 简介
- 向量模型本地部署
- …
第三阶段(30天):模型训练
恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。
到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?
- 为什么要做 RAG
- 什么是模型
- 什么是模型训练
- 求解器 & 损失函数简介
- 小实验2:手写一个简单的神经网络并训练它
- 什么是训练/预训练/微调/轻量化微调
- Transformer结构简介
- 轻量化微调
- 实验数据集的构建
- …
第四阶段(20天):商业闭环
对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。
- 硬件选型
- 带你了解全球大模型
- 使用国产大模型服务
- 搭建 OpenAI 代理
- 热身:基于阿里云 PAI 部署 Stable Diffusion
- 在本地计算机运行大模型
- 大模型的私有化部署
- 基于 vLLM 部署大模型
- 案例:如何优雅地在阿里云私有部署开源大模型
- 部署一套开源 LLM 项目
- 内容安全
- 互联网信息服务算法备案
- …
学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。
如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。