Home 【翻译】Redis之父最新文章《2024年初的LLMs与编程》
Post
Cancel

【翻译】Redis之父最新文章《2024年初的LLMs与编程》

英文原文:http://antirez.com/news/140

Redis 之父的最新文章,非常详细地分享他在工作中使用 LLM 编程的体验,我们可以通过此文一窥超级大佬是如何使用大模型的。

这篇文章很我让ChatGPT提取了摘要:

  • 在2023年,人工智能尤其是可以在设备上本地使用的LLMs取得了显著的进步。作者广泛利用这项技术来加速他的编程能力。
  • 起初,作者主要使用LLMs来处理文档和避免在编程中的无聊/不感兴趣的部分。然而,随着时间的推移,他学会了何时使用LLMs最有帮助,以及何时它们可能会减慢他的速度。
  • LLMs在推理和插值方面的能力有限,它们不能超越它们被训练的内容。虽然它们不能替代人类程序员,但如果适当使用,它们可以是有用的辅助工具。
  • 作者提供了几个例子,展示了LLMs如何帮助他更快地编写代码,例如在框架之间切换、用一种不熟悉的语言编程,或分析他不完全理解的网络输出时。
  • 当任务虽然无聊但对作者的目标有用时,LLMs也适用于编写一次性/临时脚本。
  • 系统编程需要更强的推理能力,这通常是LLMs所缺乏的。作者提供了一个例子,其中LLMs在提供相关代码时,仍然难以解释一个低级别的数据格式。
  • 总之,LLMs最好被用作程序员的辅助工具而不是替代品。有了经验,人们可以学习如何以及何时有效地利用它们来处理不同的编程任务。但它们的能力仍然有限,并且可能不会扩展到所有领域,比如系统编程。

翻译工作只有Claude-2-100K可以完成

正文:

我想先说,这篇文章的目的不是对LLM进行回顾。很明显,2023年对人工智能来说是一个非凡的一年:重复这一点似乎毫无意义。相反,本文旨在成为一个个人程序员的见证。自ChatGPT问世以来, Later by使用本地运行的LLM,我已大量使用这种新技术。 目标是加速我编写代码的能力,但这并不是唯一的目的。 还有意图是不要浪费在编程中不值得投入精力的方面上的心智能量。 在特殊的、智力上不感兴趣的方面上搜索文档所花费的无数小时; 学习过于复杂的API的努力,往往没有充分的理由; 写立即可用的程序,我会在几个小时后丢弃。 这些都是我不想做的事情,特别是现在,谷歌已经成为海洋垃圾,难以找到几件有用的东西。

与此同时,我在编程方面肯定不是新手。 我有能力在没有任何帮助的情况下编写代码,而且我确实经常这样做。 随着时间的推移,我越来越倾向于使用LLM来编写高级代码,特别是在Python中,而在C语言中要少得多。 LLM给我留下深刻印象的是,我已经准确地了解到何时使用它们何时它们的使用只会拖慢我。 我还了解到,LLM有点像维基百科和Youtube上散布的所有视频课程:它们帮助那些有意愿、能力和纪律性的人,但对于那些落伍的人来说效果很小。 我担心,至少在最初,它们只会使那些已经具有优势的人受益。

但是让我们一步一步来。

全知或鹦鹉?

在机器学习领域出现这种新奇和进步的浪潮中,最令人担忧的现象之一是AI专家接受他们有限知识的有限能力。 人类发明了神经网络,然后更重要的是,发明了一种算法来自动优化神经网络的参数。 硬件已经能够训练越来越大的模型,并利用有关要处理的数据的统计知识(先验知识),通过大量的试错法逐步逼近,发现了比其他模型工作得更好的体系结构。 但总的来说,神经网络仍然相当模糊。

面对无法解释LLM某些新出现的能力的无能为力,人们本应该更加谨慎。 相反,许多科学家严重低估了LLM,说毕竟它们只是稍微高级一些的马尔可夫链,最多只能吐出它们在训练集中见过的极端有限的变体。 然后,在证据面前,这种鹦鹉的观念几乎被普遍推翻。

与此同时,热情的群众赋予LLM现实中不存在的超自然力量。 不幸的是,LLM最多只能在它们在训练期间看到的数 据表示的空间中进行插值:这已经很多了。 事实上,它们的插值能力是有限的(但仍然令人惊叹,也出人意料)。 哦,要是当今最大的LLM能连续插值在它们见过的所有代码所围绕的空间中该多好! 即使它们无法产生真正的新颖性,它们也能取代99%的程序员。 现实要谦虚得多,几乎总是如此。 LLM肯定能编写它没有以完全相同的形式见过的程序,展示了将训练数据集中以某种频率出现的不同想法混合在一起的某种能力。 同样明显的是,这种能力目前有深刻的局限性,每当需要细致的推理时,LLM就会以灾难性的方式失败。 然而,它们代表了从黎明到今天的AI的最大成就。 这似乎是不可否认的。

愚蠢但无所不知

的确:LLM最多只能进行最初级的推理,往往不准确,许多时候夹杂着关于不存在的事实的幻觉。 但它们拥有广泛的知识。 在编程领域以及其他可获得高质量数据的领域,LLM就像愚蠢的神童,知道很多事情。 和这样的伙伴进行配对编程(对我来说,配对编程在最一般的术语中就是可怕的):他们会有荒谬的想法,我们不得不持续斗争才能实现自己的想法。 但如果这位博学的傻子供我们使 用,回答所有问他的问题,事情就变了。 当前的LLM不会把我们带进知识的道路之外,但如果我们想处理一个我们不太熟悉的主题,它们通常可以把我们从绝对的无知中提升到我们能够自主前进的地步。

在编程领域,也许直到20或30年前,它们的能力仍然非常有限的兴趣。 那时你只需要知道几种编程语言、经典算法和那10个基本库。 其余的你必须自己添加,你自己的智力、专业知识、设计技能。 如果你有这些原料,你就是一名专家程序员,基本上能做任何事情。 随着时间的推移,我们见证了框架、编程语言、各种库的爆炸式增长。 复杂性的爆炸往往完全没有必要和理由,但事实就是如此。 在这样一个背景下,一个知道一切的白痴就是一位宝贵的盟友。

让我举个例子:我在机器学习上的实验至少进行了一年,使用Keras。 然后出于各种原因,我切换到了PyTorch。 我已经知道embedding或残差网络是什么,但我不想逐步学习PyTorch的文档(就像我在ChatGPT还不存在时所做的那样学习Keras)。 用LLM,编写使用Torch的Python代码变得非常容易。 我只需要对要组装的模型有清晰的想法,并提出正确的问题。

举个例子

我不是在说简单的事情,比如:“嘿,X类的Y方法是什么?” 如果仅仅是为了那个,你可能会倾向于同意那些对LLM持怀疑态度的人。 目前更复杂的模型能做的要精细得多。 在几年前,这将是纯粹的魔法。 我可以告诉GPT4:看,这是我在PyTorch中实现的神经网络模型。 这是我的批处理。 我想调整张量的大小,以便向神经网络发出批处理的函数与输入兼容,我想以这种特定的方式表示事物。 您能向我展示需要进行重新塑形的代码吗? GPT4编写代码,我所要做的就是在Python CLI中测试张量是否真的具有对我有用的维度,数据布局是否正确。

这里有另一个例子。 不久前,我需要为某些基于ESP32的设备实现BLE客户端。经过一些研究后,我意识到跨平台的蓝牙编程绑定或多或少都无法使用。 解决方案很简单,使用macOS的本机API用Objective C编写代码。 所以,我同时要处理两个问题:学习Objective C冗长的BLE 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格式,所以我可以轻松提取输入和输出列表以及分配给它们的名称。 我只知道关于这个convnet的一件事:它在图像中检测某些特征。 我不知道输入图像的格式和大小,尤其是网络的输出要比我想象的复杂得多(我认为它是一个二进制分类器:观察到的图像正常还是有问题? 两个输出,但有数百个输出)。 我从将ONNX网络元数据输出复制粘贴到ChatGPT开始。 我向助手解释关于网络的微乎其微的知识。 ChatGPT假设输入如何组织,输出可能是指示图像中潜在缺陷部分的规范化框,以及这些缺陷的可能性。经过几分钟的反复,我有了一个Python脚本,能够对网络进行推理,以及将起始图像转换为适合输入的张量所需的代码等等。 关于那次会话令我惊讶的是,ChatGPT在测试图像上观察到原始输出值(logit,基本上)后才最终“理解”网络的功能:一系列浮点数提供了识别精确输出细节、规范化、框是否居中或指定左上角等的上下文。

一次性程序

我可以记录数十起上面描述的这样的案例。 这是毫无意义的,因为它只是以多少相同的方式重复同样的故事。 我遇到一个问题,我需要快速了解某事,只要LLM不在胡说八道,我就可以验证它。 好吧,在这种情况下,我使用LLM加速获取知识。

然而,在不同的情况下,我会让LLM编写所有的代码。 例如,每当我需要编写一个或多或少一次性的程序时。 像这个:

https://github.com/antirez/simple-language-model/blob/main/plot.py

我需要可视化PyTorch程序在学习期间生成的CSV文件的损失曲线。 我向GPT4展示了CSV文件格式,然后如果我在命令行中指定多个CSV文件,我不再需要相同实验的训练和验证损失曲线,而是不同实验的验证损失曲线的比较。 以上是GPT4生成的结果。 总共30秒。

类似地,我需要一个程序来读取AirBnB CSV报告并根据月份和年份对我的公寓进行分组。 然后,考虑到清洁费用和每个预订的夜晚数量,它将对一年不同月份的平均租金价格进行统计。 对我来说,这个程序非常有用。 同时,编写它是无聊透顶:没有什么有趣的。 所以我把漂亮的CSV文件片段复制粘贴到GPT4上。 我向LLM解释要解决的问题:程序在第一次尝试时就可以工作。 我在下面完整展示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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能为我做到这一点,肯定会直接加速我的代码产生能力:我可以专注于最重要的事情。

当事情没有按它们应有的方式发展时:系统编程

尽管在编程方面我与LLM非常成功,但在我编写C程序时,我几乎总是只将LLM视为更方便的文档形式。 我是系统编程专家,在那种情况下,LLM对我有用只有当它们能进行复杂的推理时:在这种情况下,它们几乎总是失败。

让我们从这个实验的提示开始:

概述一个漂亮的、小但可工作的C实现的布隆过滤器的要求。 关注散列函数的重要方面。 然后写它,用高质概述一个漂亮的、小但可工作的C实现的布隆过滤器的要求。关注散列函数的重要方面。 然后用高质量的C语言编写它。

还要考虑你的示例实现应该大小为100,000个元素,假阳性概率最高为5%。 添加的元素是null结尾的字符串。

这里是GPT4的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define N 100000  // 元素个数 
#define P 0.05    // 假阳性概率
#define M (int)(-(N * log(P)) / (log(2) * log(2)))  // 位数组大小
#define K (int)(M / N * log(2))  // 哈希函数个数

unsigned char bitArray[M / 8 + 1];

void setBit(int n) {
    bitArray[n / 8] |= 1 << (n % 8); 
}

int getBit(int n) {
    return bitArray[n / 8] & (1 << (n % 8));
}

// 基本哈希函数(用于演示)
int hash1(char *str) {
    int hash = 5381;
    int c;
    while ((c = *str++))
        hash = ((hash << 5) + hash) + c;
    return abs(hash % M); 
}

int hash2(char *str) {
    unsigned long hash = 0;
    int c;
    while ((c = *str++))
        hash = c + (hash << 6) + (hash << 16) - hash;
    return abs(hash % M);
}

void add(char *str) {
    setBit(hash1(str));
    setBit(hash2(str));
    // 如果需要可以添加更多哈希函数
}

int is_in(char *str) {
    return getBit(hash1(str)) && getBit(hash2(str));
    // 如果使用了更多哈希函数要检查它们
}

int main() {
    char *testStr = "testString";
    add(testStr);

    if (is_in(testStr)) {
        printf("%s is probably in the set.\n", testStr);
    } else {
        printf("%s is definitely not in the set.\n", testStr);
    }

    return 0;
}

GPT4产生的实现并不算太出色。布隆过滤器相当广泛使用。这并不是一个如此难懂的数据结构。但是,编写一个体面的布隆过滤器需要更高的抽象能力:例如,有效地在同一个字符串上散列N次的问题,并确保散列充分解关联。

然而,如果你明确要求GPT4修改散列函数,以便它产生N个解耦输出,它的建议解决方案要可接受得多:

1
2
3
4
5
6
7
8
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; 
    }
    return hash % BF_SIZE;
}

如果它自己想到了这个主意,它会以不同的方式编写布隆过滤器,使用单个散列函数设置K个位。

GPT4能够隔离地执行为编写适当而更通用的散列函数的任务。 但是在编写布隆过滤器这个更大的项目中,它未能展示出足够的推理能力,而是吐出两个不同但相似的散列函数:这正是人们期望从关于布隆过滤器的糟糕文章例子中获得的。

当前大型语言模型的弱推理能力,加上可能的该主题资源稀缺,更糟糕的是,可能有许多低质量的资源,共同导致了这种贫乏的结果。 然而,这并非个别案例。 在算法或系统编程领域,我试图使用LLM的次数很少,结果非常糟糕,即使预期的推理能力也不大于LLM在Python编程环境中所展示的。

与此同时,GPT-4有能力在单独的会话中反编译它产生的函数,并精确理解其目的:

https://chat.openai.com/share/f54b64d7-d19a-4be4-8f10-08553aa812f1

因此,说在系统编程环境中LLM完全没有用是非常鲁莽的言论。

一个有趣且可以预料的事情是,在这些情况下,模型规模之间的差异确实很大。 让我们看看两个本地模型的表现。

例如,Mixtral,Mistral的MoE,对同样的问题回复如下:

1
2
3
4
5
6
7
8
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;
}

显然,仅在末尾添加hash_id是一个极差的解决方案。 Mixtral是一个用于许多用途的出色模型,但就LLM目前甚至较弱的推理能力而言,迄今为止的规律肯定是:越大越好。

这是我与本地LLM deepseek-coder的聊天,由于内存不足,我将其量化为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的示例:

1
2
3
4
5
6
7
8
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; // 使用XOR混合hash_id
    }
    return (hash ^ hash_id); // 在末尾再混合一次hash_id
}

这将比简单地添加它更好地传播您的散列中的hash_id,这可以导致分布不那么均匀。

在我的MacBook M1 Max上运行,这对于一个模型来说并不坏。 在这种情况下,提示我问题的线索肯定帮助了模型,但模型本身确定了实际问题所在,并提出了有效的替代方案。 这样的事情不是任何书籍、文档或谷歌搜索所能达到的。 并且,无论你如何看待LLM,说它们对程序员没有帮助是非常鲁莽的。

与此同时,然而,过去几个月的经验表明,对于系统编程,如果你已经是经验丰富的程序员,LLM几乎从不提供可接受的解决方案。 让我举一个真实例子。 我当前的项目ggufflib涉及编写读取和写入GGUF格式文件的库,这是llama.cpp加载量化模型的格式。 最初,为了理解量化编码如何工作(出于速度原因,每个量化的位以花哨的方式存储),我试图使用ChatGPT,但后来决定逆向工程llama.cpp的代码:速度快得多。 如果LLM看到数据编码“struct”声明和解码函数,它应该能够重构数据格式文档。 llama.cpp的函数小到足以完全适合GPT4的上下文中,然而输出完全无用。 在这些情况下,就像过去一样做事:纸和笔,阅读代码,并看看解码器提取位的位置。

让我更好地解释一下上述用例,以便你可以自己尝试,如果你愿意的话。 我们有来自llama.cpp实现的这个结构。

// 6比特量化 // 权重表示为x = a * q // 16个块,每个块16个元素 // 每个权重有效6.5625位 typedef struct { uint8_t ql[QK_K/2]; // 量化,低4位 uint8_t qh[QK_K/4]; // 量化,高2位 int8_t scales[QK_K/16]; // 缩放,用8位量化 ggml_fp16_t d; // 超级块缩放 } block_q6_K;

然后有这个函数,用于执行反量化:

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;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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位上存储的块的清晰解释,这取决于权重的位置。 对于这篇博文,我还要求它编写一个更简单的函数,以显示数据的存储方式(也许它无法用文字解释,但可以用代码)。 产生的函数在许多方面都有问题,索引是错误的,6位到8位的符号扩展是错误的(它只是转换为uint8_t),等等。

顺便说一下,这是我自己最终编写的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
} else if (tensor->type == GGUF_TYPE_Q6_K) {
    uint8_t *block = (uint8_t*)tensor->weights_data;
    uint64_t i = 0; // i-th weight to dequantize.
    while(i < tensor->num_weights) {
        float super_scale = from_half(*((uint16_t*)(block+128+64+16)));
        uint8_t *L = block;
        uint8_t *H = block+128;
        int8_t *scales = (int8_t*)block+128+64;
        for (int cluster = 0; cluster < 2; cluster++) {
            for (uint64_t j = 0; j < 128; j++) {
                f[i] = (super_scale * scales[j/16]) *
                       ((int8_t)
                        ((((L[j%64] >> (j/64*4)) & 0xF) |
                         (((H[j%32] >> (j/32*2)) & 3) << 4)))-32);
                i++;
                if (i == tensor->num_weights) return f;
            }
            L += 64;
            H += 32;
            scales += 8;
        }
        block += 128+64+16+2; // Go to the next block.
    }    }

从上面的函数中,我删除了这段代码的实际贡献:对llama.cpp Q6_K编码使用的精确格式的长注释。 现在,如果GPT能为我做到这一点,我敢打赌这只是时间问题,因为这种任务在没有任何突破的情况下就可以达到,只需要一点扩展。

把事情放在正确的位置

我不得不说,这是事实:当今的编程大多数都是以略有不同的形式重复同样的事情。这并不需要高层次的推理。LLM(大语言模型)在做这件事方面表现很好,尽管它们仍受限于上下文的最大大小。这真的应该让程序员深思。编写这类程序是否值得?当然,你可以获得报酬,而且非常可观,但如果LLM能做其中的一部分,那么在五到十年内这可能不是最好的发展方向。

那么,LLM是否具有某些推理能力,或者这全部都是在虚张声势?有时,它们看似能推理,仅仅是因为,正如符号学家所说,“符号”给人一种实际上并不存在的意义的印象。那些在接受LLM的局限性的同时,又足够了解LLM的人可以肯定:事情不可能如此:它们将以前见过的东西融合在一起的能力,远远超出了随机组合词语。尽管它们的训练在预训练阶段主要是通过预测下一个词符号来完成的,但这个目标迫使模型形成某种形式的抽象模型。这个模型是软弱的、破碎的和不完善的,但如果我们观察到我们观察到的,这个模型就必须存在。如果我们的数学确定性是充满疑问的,最伟大的专家们往往持相反立场,那么相信自己亲眼所见似乎是一种明智的做法。

最后,今天不使用LLM进行编程有什么意义?向LLM提出正确的问题是一项基本技能。这方面的练习越少,就越难以通过AI改进自己的工作。此外,对问题进行描述性表达的能力在与其他人交流时也很有用。LLM不是唯一可能不理解我们要说什么的。即使在自己的专业领域非常能干,也有许多程序员交流得很糟糕。而且现在谷歌搜索也毫无用处:即使只是将LLM用作文档的压缩形式,使用LLM也是一个好主意。就我个人而言,我将继续大规模使用它们。我从未喜欢学习一个晦涩通信协议或一个想显示自己多强的程序员编写的复杂库的细节。在我看来,这些都像“垃圾知识”。LLM每天都在这个方面帮助我越来越多。

This post is licensed under CC BY 4.0 by the author.