人工智能
大语言模型和AI编程
tensorflow性能调优
CUDA 高性能计算
-
+
首页
大语言模型和AI编程
首先我要说的是,这篇文章并不是对法学硕士的回顾。显然,2023 年对于人工智能来说是特殊的一年:重申这一点似乎毫无意义。相反,这篇文章的目的是作为一名程序员个人的见证。自从 ChatGPT 出现以来,以及后来通过使用本地运行的法学硕士,我广泛使用了这项新技术。目标是提高我编写代码的能力,但这不是唯一的目的。还有一个目的是不要将精力浪费在不值得付出努力的编程方面。花费了无数的时间来寻找有关奇特的、智力上无趣的方面的文档;努力学习过于复杂的 API,通常没有充分的理由;编写立即可用的程序,但几个小时后我就会丢弃这些程序。这些都是我不想做的事情,尤其是现在,谷歌已经变成了垃圾邮件的海洋,我只能在其中寻找一些有用的东西。 同时,我当然不是编程新手。我能够在没有任何帮助的情况下编写代码,而且事实上,我经常这样做。随着时间的推移,我越来越多地使用法学硕士来编写高级代码,尤其是在 Python 中,而在 C 语言中则更少。我个人使用法学硕士的经历让我印象深刻的是,我准确地了解了何时使用它们以及何时使用它们只会让我慢下来。我还了解到,LLM有点像维基百科和YouTube上散布的所有视频课程:它们帮助那些有意愿、有能力、有纪律的人,但对那些落后的人来说却是边际效益。我担心至少在最初,它们只会让那些已经拥有优势的人受益。 但让我们一步一步来。 # 全知还是鹦鹉? 机器学习新浪潮中最令人担忧的现象之一是人工智能专家接受其有限知识的能力有限。智人发明了神经网络,更重要的是,发明了一种自动优化神经网络参数的算法。硬件已经能够训练越来越大的模型,并使用有关要处理的数据(先验)的统计知识,并通过大量的逐次逼近试验和错误,已经发现架构比其他架构工作得更好。但总而言之,神经网络仍然相当不透明。 面对无法解释法学硕士某些新兴能力的情况,我们预计科学家会更加谨慎。相反,许多人都严重低估了法学硕士,他们说毕竟它们只不过是某种先进的马尔可夫链,最多只能重现他们在训练集中看到的极其有限的变化。然后,在证据面前,鹦鹉的这个概念几乎被普遍撤回。 与此同时,许多热心群众将其归因于法学硕士具有现实中不存在的超自然能力。不幸的是,法学硕士最多只能在他们在训练期间看到的数据所表示的空间中进行插值:而这已经很多了。事实上,它们的插值能力是有限的(但仍然令人惊讶,也出乎意料)。哦,如果当今最大的法学硕士能够在他们见过的所有代码所包围的空间中连续插值就好了!即使他们无法创造出真正的新奇事物,他们也能够取代 99% 的程序员。现实更加温和,几乎总是如此。法学硕士当然有能力编写它从未见过的精确形式的程序,表现出一定的能力来融合训练集中以一定频率出现的不同想法。很明显,这种能力目前还存在很大的局限性,每当需要微妙的推理时,法学硕士就会遭遇灾难性的失败。然而,它们代表了人工智能从诞生到今天的最伟大成就。这似乎是不可否认的。 # 虽然愚蠢却无所不知 这是真的:法学硕士最多只能进行基本的推理,但推理往往不准确,很多时候还充满了对不存在的事实的幻觉。但他们拥有渊博的知识。在编程领域以及其他可以获得高质量数据的领域,法学硕士就像知道很多事情的愚蠢学者。与这样的合作伙伴进行结对编程是很糟糕的(对我来说,结对编程即使在最笼统的意义上也是很糟糕的):他们会有荒谬的想法,而我们必须不断地努力强加我们自己的想法。但如果这个博学的傻瓜任由我们支配并回答他们提出的所有问题,事情就会改变。目前的法学硕士不会带我们超越知识的道路,但如果我们想解决一个我们不太了解的主题,它们通常可以将我们从绝对无知提升到我们有足够知识可以自己前进的地步。 在编程领域,也许二十、三十年前他们的能力还没有引起人们的兴趣。那时你必须了解几种编程语言、经典算法和那十个基本库。其余的你必须添加你自己,你自己的智慧、专业知识、设计技能。如果你具备这些要素,那么你就是一名专家程序员,或多或少能够完成所有事情。随着时间的推移,我们见证了各种框架、编程语言和库的爆炸式增长。复杂性的激增通常是完全不必要和不合理的,但事实是事情就是这样。在这样的背景下,一个什么都知道的白痴是一个宝贵的盟友。 举个例子:我的机器学习实验是用Keras进行了至少一年的。后来由于种种原因,我转而使用PyTorch。我已经知道什么是嵌入或残差网络,但我不想一步步研究 PyTorch 的文档(就像我对 Keras 所做的那样,这是我在 ChatGPT 还不存在时学到的)。有了法学硕士,就可以非常轻松地编写使用 Torch 的 Python 代码。我只需要对我想要组合的模型有清晰的想法,并提出正确的问题。 # 举例的时间到了 我说的不是简单的事情,比如:“嘿,X 类做 Y 的方法是什么”?如果只是为了这一点,人们可能会同意那些对法学硕士持怀疑态度的人。更复杂的模型能够做的事情要复杂得多。直到几年前,这还是纯粹的魔法。我可以告诉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 并没有编写太多代码,但它也确实显着加快了编写速度。如果没有 ChatGPT 我能做到吗?当然是的,但最有趣的事情并不是我会花更长的时间:事实是我什至不会尝试,因为它不值得。这个事实至关重要。编写这样一个程序(次要于我的项目)的工作量和收益之间的比例会很不方便。此外,这比程序本身具有更有用的次要附带效果:对于该项目,我修改了 linenoise(我的行编辑库之一),以便它可以在多路复用中工作。 另一个例子,这次更少关于代码编写,更多关于数据解释。我想使用我在网上找到的卷积神经网络来设置一个 Python 脚本,但它的文档非常缺乏。该网络的优点是采用 ONNX 格式,因此我可以轻松提取输入和输出列表及其分配的名称。关于这个卷积网络,我只知道一件事:它检测到图像中的某些特征。我不知道输入图像的格式和大小,尤其是网络的输出比我想象的要复杂得多(我以为这是一个二元分类器:观察到的图像是好的还是有问题?两个输出,但是有数百)。我首先将 ONNX 网络元数据输出复制粘贴到 ChatGPT 中。我向助理解释了我对网络知之甚少的事情。ChatGPT 假设输入是如何组织的,输出可能是标准化的框,指示与潜在缺陷相对应的图像部分,而其他输出则指示这些缺陷的可能性。经过几分钟的来回,我有了一个能够进行网络推理的Python脚本,加上将起始图像转换为适合输入的张量的必要代码,等等。那次会议让我印象深刻的是,ChatGPT 在观察到测试图像上的原始输出值(基本上是 logits)后,终于“理解”了网络的运作方式:一系列浮点数提供了识别确切输出细节的上下文、归一化(如果框居中或指定了左上角)等等。 # 一次性程序 我可以记录上面提到的几十个这样的案例。这是毫无意义的,因为同样的故事以或多或少相同的方式重演。我有一个问题,我需要快速知道一些*我可以验证*的东西,如果法学硕士在给我胡说八道。好吧,在这种情况下,我使用法学硕士来加速我对知识的需求。 然而,在不同的情况下,我让LLM编写所有代码。例如,每当我需要编写或多或少的一次性程序时。像这个: https://github.com/antirez/simple-language-model/blob/main/plot.py 我需要可视化小型神经网络学习过程中的损失曲线。我在学习时向GPT4展示了PyTorch程序产生的CSV文件的格式,然后我要求如果我在命令行指定多个CSV文件,我就不再想要同一个实验的训练和验证损失曲线,而是不同实验的验证损失曲线的比较。以上是GPT4生成的结果。总共三十秒。 同样,我需要一个程序来读取 AirBnB CSV 报告并按月份和年份对我的公寓进行分组。然后,考虑到清洁费用,以及每次预订的住宿天数,它会统计一年中不同月份的平均租金价格。这个程序对我来说非常有用。同时,写它又是极其无聊的:没有什么有趣的。因此,我选取了 CSV 文件的一部分并在 GPT4 上进行了复制粘贴。我写信给法学硕士要解决的问题是:程序第一次尝试就成功了。下面我给大家完整展示一下。 ```python 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) ``` 要准确理解如何对数据进行分组(否则这些数据相当分散和无序),需要一些简单的推理:这不是一个法学硕士可以简单地通过反省近似于它所看到的语言的东西来解决的问题。GPT4 在训练期间肯定观察到了类似的程序,但几乎可以肯定没有看到任何程序必须按照我要求的确切方式对事物进行分组,并将其分组到具有该特定格式的 CSV 文件中。这表明(在我看来)法学硕士可以在某种程度上在训练集中不同程序概述的空间内进行插值。 在我看来,编写这种类型的程序是对我时间的不谨慎的利用。事实上,法学硕士可以为我做到这一点,这无疑提高了我以相当直接的方式编写代码的能力:我可以专注于重要的事情。 # 当事情没有按预期进行时:系统编程 尽管我在法学硕士和编程方面取得了成功,但当我用 C 语言编写程序时,我几乎总是最终使用法学硕士作为一种更方便的文档形式。我是系统编程方面的专家,在这种情况下,只有当法学硕士能够进行复杂的推理时,它们才会对我有用:在这种情况下,他们几乎总是失败。 我们先来说说这个实验的提示: 概述布隆过滤器的一个漂亮、小但有效的 C 实现的要求。重点关注哈希函数的重要方面。然后用高质量的 C 语言编写它。 另请考虑,您的示例实现的大小应可存储 100,000 个元素,误报概率最多为 5%。添加的元素是以 null 结尾的字符串。 这是输出: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #define N 100000 // Number of elements #define P 0.05 // False positive probability #define M (int)(-(N * log(P)) / (log(2) * log(2))) // Size of bit array #define K (int)(M / N * log(2)) // Number of hash functions 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)); } // Basic hash functions (for demonstration purposes) 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)); // Add more hash functions if needed } int is_in(char *str) { return getBit(hash1(str)) && getBit(hash2(str)); // Check more hash functions if used } 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 个去相关输出,那么它提出的解决方案显然更容易被接受: ```c 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 能够独立执行编写适当且更通用的哈希函数的任务。但在编写布隆过滤器的更大项目中,它未能表现出足够的推理能力,而是吐出了两个不同但相似的哈希函数:这就是人们从一篇写得不好的布隆过滤器文章示例中所期望的。 当前大型语言模型的推理能力较弱,加上该主题的资源可能稀缺,甚至更糟糕的是,可能存在许多低质量的资源,共同导致了这种微薄的结果。然而,这并不是一个孤立的案例。我有几次尝试在算法或系统编程领域使用法学硕士,结果非常差,即使预期的推理能力并不比法学硕士在Python编程等环境中似乎拥有的能力强。 同时,GPT-4能够反编译它产生的函数(在单独的会话中),并且也能够准确地理解其目的: https://chat.openai.com/share/f54b64d7-d19a-4be4-8f10-08553aa812f1 因此,说在系统编程上下文中没有用处肯定是不准确的。 一件有趣且令人期待的事情是,在这些情况下,较小模型和较大模型之间的差异确实很大。让我们看看两个本地模型的表现如何。 例如,Mistral 的MOE和 Mixtral 对同一问题的答复如下: ```c 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 位,因为我没有足够的 RAM 来以更高精度使用模型。然而,由于有 340 亿个参数,它在这个问题上的推理能力似乎相当先进。这是聊天记录(使用 llama.cpp 进行): > 我有一个哈希函数,它应该为相同的数据输出 N 个不同的哈希值。但是,当我更改 hash_id 的值时,我发现生成的哈希值分布非常差: ```c 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”添加到哈希的末尾。这可能会导致分布不良,特别是当“hash_id”太大或分布不均匀时。 考虑使用按位运算来混合“hash_id”。以下是 XOR 的示例: ```c 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”更好地在整个哈希中传播“hash_id”,这可能会导致分布不均匀。[剪掉……实际回复包含更多信息] 对于在我的 MacBook M1 Max 上运行的型号来说还不错。它还将总和与异或混合。在这种情况下,我提供了有关要解决的问题的线索,这一事实无疑对模型有所帮助,但模型确定了问题的真正根源是什么,并且提出的替代方案是有效的。类似上面的东西不在任何书籍、文档或 Google 搜索范围之内。作为原始的、插值的结果,或者无论你想如何理解它,模型已经执行了某种形式的推理,如果通过推理,在这种特定情况下,我们接受对问题根源及其潜在解决方案的识别。然而,想想法学硕士,说它们对程序员没有帮助是非常轻率的。 但与此同时,我过去几个月的经验表明,对于系统编程,如果您已经是一名经验丰富的程序员,法学硕士几乎永远不会提供可接受的解决方案。让我向您展示另一个现实世界的例子。我当前的项目 ggufflib 涉及编写一个读取和写入 GGUF 格式文件的库,这是 llama.cpp 加载量化模型的格式。最初,为了了解量化编码的工作原理(出于速度原因,每个量化的位都以奇特的方式存储),我尝试使用 ChatGPT,但后来我决定对 llama.cpp 的代码进行逆向工程:它要快得多。一个可以很好地帮助系统程序员的法学硕士,如果它看到数据编码“结构”声明和解码函数,应该能够重建数据格式文档。llama.cpp 的函数足够小,完全适合 GPT4 的上下文,但输出完全无用。在这些情况下,事情就像过去一样:纸和笔,阅读代码,并查看解码器提取的位被注册在哪里。 让我更好地解释上述用例,以便您可以自己尝试(如果您愿意)。我们从 llama.cpp 实现中得到了这个结构。 ```c // 6-bit quantization // weight is represented as x = a * q // 16 blocks of 16 elements each // Effectively 6.5625 bits per weight typedef struct { uint8_t ql[QK_K/2]; // quants, lower 4 bits uint8_t qh[QK_K/4]; // quants, upper 2 bits int8_t scales[QK_K/16]; // scales, quantized with 8 bits ggml_fp16_t d; // super-block scale } block_q6_K; ``` 然后有这个函数用于执行反量化: ```c 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 位上。对于这篇博文,我还尝试要求它编写一个更简单的函数来显示数据的存储方式(也许它无法用文字解释,但可以用代码解释)。生成的函数在很多方面都被破坏了,索引错误,6 位 -> 8 位符号扩展错误(它只是转换为 uint8_t),等等。 顺便说一句,这是我自己编写的代码: ```c } 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是否具有一定的推理能力,或者这只是虚张声势?也许有时,他们似乎只是因为,正如符号学家所说,“能指”给人留下了实际上并不存在的意义的印象。那些与法学硕士有足够工作经验的人,在接受他们的局限性的同时,肯定知道事实并非如此:他们融合以前所见内容的能力远远超出了随机反刍单词的能力。尽管他们的训练主要是在预训练期间进行的,但在预测下一个标记时,这个目标迫使模型创建某种形式的抽象模型。这个模型是脆弱的、不完整的、不完美的,但如果我们观察我们所观察到的东西,它就一定存在。如果我们的数学确定性值得怀疑,并且最伟大的专家常常持相反的立场,那么相信亲眼所见似乎是一种明智的方法。 最后,今天不使用法学硕士进行编程有何意义?向法学硕士提出正确的问题是一项基本技能。练习得越少,人工智能就越无法改进他们的工作。然后,在与其他人交谈时,培养问题的描述能力也很有用。法学硕士并不是唯一有时不明白我们想说的话的人。沟通不畅是一个很大的限制,许多程序员尽管在自己的特定领域非常有能力,但沟通却很差。现在谷歌已经无法使用:使用法学硕士,即使只是作为文档的压缩形式也是一个好主意。就我而言,我将继续广泛使用它们。我从来不喜欢学习晦涩的通信协议的细节,或者由想要展示自己有多优秀的人编写的库的复杂方法。对我来说这似乎是“垃圾知识”。法学硕士每天都在帮助我摆脱这一切。
jays
2024年1月14日 19:28
分享文档
收藏文档
上一篇
下一篇
微信扫一扫
复制链接
手机扫一扫进行分享
复制链接
关于 MrDoc
觅道文档MrDoc
是
州的先生
开发并开源的在线文档系统,其适合作为个人和小型团队的云笔记、文档和知识库管理工具。
如果觅道文档给你或你的团队带来了帮助,欢迎对作者进行一些打赏捐助,这将有力支持作者持续投入精力更新和维护觅道文档,感谢你的捐助!
>>>捐助鸣谢列表
微信
支付宝
QQ
PayPal
下载Markdown文件
分享
链接
类型
密码
更新密码