LLMs and Programming in the first days of 2024

Salvatore Sanfilippo

2024 年初的 LLM 與程式設計

首先我要說,這篇文章並非要對 LLMs(大型語言模型)進行回顧。2023 年對人工智慧而言顯然是特別的一年,再去重述這點似乎沒什麼意義。相反地,這篇文章想作為一名程式設計師的個人見證。自 ChatGPT 問世以來,之後又透過在本地端運行的 LLMs,我大量使用了這項新技術。目的在於加快我寫程式碼的能力,但這不是唯一的用途。同時也是為了不要把心智能量浪費在不值得費力的程式設計面向。無數小時花在搜尋某些奇特、智識上乏味面向的文件;費力去學習一個過度複雜、往往又沒什麼道理的 API;撰寫寫完就能用、幾個小時後就會丟棄的程式。這些都是我不想再做的事,尤其現在 Google 已經變成一片垃圾訊息的海洋,得在其中費力搜尋少數有用的東西。

同時,我在程式設計上當然不是新手。我有能力在沒有任何輔助的情況下寫程式,而且的確經常這麼做。隨著時間,我越來越常使用 LLMs 來撰寫高階程式碼,尤其是在 Python 上,而在 C 上則少得多。讓我對 LLMs 個人經驗印象深刻的是,我已經精確學會何時該用它們、何時使用反而只會拖慢我。我也學到,LLMs 有點像 Wikipedia 和散落在 YouTube 上的所有影片課程:它們能幫助那些有意願、有能力且有紀律的人,但對於已經落後的人,助益則微乎其微。我擔心至少在初期,它們只會讓原本就具優勢的人受益。

但讓我們一步一步來。

無所不知還是鸚鵡學舌?

這一波機器學習新穎與進展浪潮中最令人擔憂的現象之一,是 AI 專家承認自身知識有限的能力不足。人類發明了 neural networks(類神經網路),然後更關鍵地,發明了能自動最佳化 neural networks 參數的演算法。硬體已能訓練越來越大的模型,並利用關於待處理資料的統計知識(先驗)以及透過大量試誤進行連續逼近,發現了比其他架構表現更好的架構。但總體而言,neural networks 仍然相當不透明。

面對這種無法解釋 LLMs 某些新興能力的狀況,人們原本會期待科學家表現得更謹慎。相反地,許多人卻嚴重低估了 LLMs,聲稱它們終究不過是稍微進階一點的 Markov chains(馬可夫鏈),頂多只能吐出在訓練集中看過內容的極有限變化。接著,面對證據,這種鸚鵡的說法幾乎被全面收回。

同時,許多熱情的群眾則賦予 LLMs 現實中並不存在的超自然能力。遺憾的是,LLMs 頂多只能在訓練期間所見資料所代表的空間中進行內插:而這本身就已經很了不起了。實際上,它們內插的能力是有限的(但仍令人驚艷,也出乎意料)。唉,如果當今最大的 LLMs 能夠在它們所見過的所有程式碼所界定的空間中連續內插,那該有多好!即便它們無法產生真正的創新,也足以取代 99% 的程式設計師。現實則要謙遜得多,一如往常。LLM 無疑能夠撰寫出它未曾以完全相同形式見過的程式,展現出某種將訓練集中以一定頻率出現的不同想法加以融合的能力。同時也很清楚,這種能力目前有著深刻的限制,每當需要細緻的推理時,LLMs 就會慘敗。然而,它們代表了 AI 從誕生至今最偉大的成就。這點似乎無可否認。

愚笨卻無所不知

的確:LLMs 頂多只能進行粗淺的推理,常常不準確,很多時候還夾雜著關於不存在事實的幻覺。但它們擁有龐大的知識。在程式設計領域,以及其他擁有高品質資料的領域中,LLMs 就像什麼都知道一點的愚笨天才。和這樣的夥伴進行 pair programming(結對程式設計)會很可怕(對我而言,就算在最廣義的層面上,pair programming 也很可怕):他們會有荒謬的想法,而我們得不斷抗爭才能貫徹自己的想法。但如果這個博學的笨蛋任由我們使喚、有問必答,情況就不同了。當前的 LLMs 不會帶我們超越已知的道路,但如果我們想處理一個不太熟悉的主題,它們往往能把我們從一無所知拉拔到足以自行前進的程度。

在程式設計領域,或許直到二、三十年前,它們的能力還沒什麼吸引力。那時你只需要懂幾種程式語言、經典的演算法,以及那十個基礎的函式庫。剩下的得靠你自己補上——你自己的智慧、專業與設計能力。如果你具備這些要素,你就是能大抵無所不能的專業程式設計師。隨著時間,我們見證了框架、程式語言、各種函式庫的大爆炸。這種複雜度的爆炸往往完全不必要、也缺乏正當性,但事實就是如此。而在這樣的脈絡下,一個什麼都懂的笨蛋就是珍貴的盟友。

讓我舉個例子:我用 Keras 進行機器學習實驗至少持續了一年。後來因為各種原因,我轉用了 PyTorch。我已經知道什麼是 embedding(嵌入)或 residual network(殘差網路),但我不想一步步研讀 PyTorch 的文件(就像我當初學 Keras 那樣,那時 ChatGPT 還不存在)。有了 LLMs,用 Torch 撰寫 Python 程式碼變得非常容易。我只需要對想組裝的模型有清晰的想法,並提出正確的問題。

舉些例子

我說的不是那種簡單的事,像是:「嘿,類別 X 要做 Y 的方法是什麼?」如果只是為了這個,或許會讓人傾向同意那些對 LLMs 持懷疑態度的人。更複雜的模型所能做到的要精細得多。就在幾年前,這根本就是魔法。我可以對 GPT4 說:你看,這是我在 PyTorch 中實作的 neural network 模型。這些是我的批次。我想調整張量的大小,讓發出批次的函式與 neural network 的輸入相容,而且我想以這種特定方式來呈現。你能展示重塑所需的程式碼嗎?GPT4 寫出了程式碼,而我所要做的,只是在 Python CLI 中測試張量是否真的具有對我有用的維度,以及資料佈局是否正確。

再舉另一個例子。前陣子我得為某些基於 ESP32 的裝置實作 BLE 客戶端。研究一番後,我發現跨平台的 Bluetooth 程式設計綁定大致上都無法使用。解決方案很簡單,用 Objective C 並透過 macOS 的原生 API 來寫程式碼。於是,我同時得處理兩個問題:學習 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(我用於行編輯的函式庫之一),讓它能在多工模式下運作。

再舉一個例子,這次較少關於寫程式,更多是關於資料解讀。我想用在網路上找到的一個 convolutional neural network(卷積神經網路)來架設 Python 腳本,但它的文件相當缺乏。這個網路的好處是採用 ONNX 格式,所以我能輕易擷取輸入與輸出的清單及其指定名稱。我對這個 convnet 唯一知道的一件事是,它能偵測影像中的某些特徵。我不知道輸入影像的格式與尺寸,尤其是,網路的輸出遠比我想像的複雜(我以為它是 binary classifier(二元分類器):觀測到的影像是否正常或有問題?應該是兩個輸出,但實際上有數百個)。我一開始把 ONNX 網路的中繼資料輸出複製貼到 ChatGPT。我向助理說明我對這個網路所知甚少。ChatGPT 推測輸入是如何組織的,並認為輸出可能是經過正規化的方框,標示影像中對應潛在缺陷的區域,而其他輸出則表示這些缺陷的可能性。經過幾分鐘的來回對話後,我就得到了一個能夠進行網路推論的 Python 腳本,外加將原始影像轉換為適合輸入的張量所需的程式碼等等。讓我對那次對話印象深刻的是,ChatGPT 最後在觀察到一張測試影像上的原始輸出數值(基本上就是 logits)後,才「理解」了網路是如何運作的:一系列浮點數提供了脈絡,得以辨識確切的輸出細節、正規化方式、方框是以中心為基準還是指定左上角等等。

用完即丟的程式

我可以記錄像上面所述的數十個類似案例。但那沒什麼意義,因為故事大同小異地重複著。我遇到一個問題,需要快速知道某件事,而我*能夠驗證* LLM 是否在胡說八道。在這種情況下,我會利用 LLM 來加速我對知識的需求。

不過,也有些不同的情況是我會讓 LLM 寫完整段程式碼。例如,每當我需要撰寫或多或少用完即丟的程式時。像這個:

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

我需要視覺化一個小型 neural network 在學習過程中的損失曲線。我向 GPT4 展示了 PyTorch 程式在學習期間產生的 CSV 檔案格式,然後要求如果我在命令列上指定多個 CSV 檔案,我不再想要同一實驗的訓練與驗證損失曲線,而是想要不同實驗間驗證損失曲線的比較。上面就是 GPT4 產生的結果。總共只花了三十秒。

同樣地,我需要一個能讀取 AirBnB CSV 報表、並依月份與年份彙整我的公寓的程式。接著,考量清潔費用與每次預訂的夜數,對一年中不同月份的平均租金進行統計。這個程式對我非常有用。同時,撰寫它卻無聊至極:毫無有趣之處。所以我擷取了 CSV 檔案中一段不錯的內容,複製貼到 GPT4。我向 LLM 說明要解決的問題:程式一次就跑通了。我在下方完整展示給你看。

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 檔案中進行分組的程式。這(在我看來)顯示 LLMs 能夠在訓練集中不同程式所勾勒出的空間內,在一定程度上進行內插。

在我看來,撰寫這類程式會是不明智的時間運用。LLM 能替我完成這件事,無疑以相當直接的方式加快了我產出程式碼的能力:我可以專注於重要的事情。

當事情不如預期:系統程式設計

儘管我在 LLMs 與程式設計上有所斬獲,當我用 C 撰寫程式時,幾乎總是只把 LLMs 當作更方便的文件形式來使用。我是 system programming(系統程式設計)的專家,在那個脈絡下,LLMs 只有在具備複雜推理能力的情況下才會對我有用:在這種情境中,它們幾乎總是失敗。

讓我們從這個實驗的提示開始:

概述一個精巧、簡小但可運作的 bloom filter(布隆過濾器) C 語言實作的需求。聚焦於雜湊函式的重要面向。然後用高品質的 C 將其寫出來。

同時請考慮,你的範例實作應調整為可儲存 100,000 個元素,偽陽性機率至多 5%。加入的元素皆為以空字元結尾的字串。

以下是輸出:

#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 產生的實作並不怎麼出色。Bloom filter 相當普及。它並非什麼奇特的資料結構。然而,很明顯要寫出一個像樣的 bloom filter 需要更強的抽象能力:例如,如何找到一種有效的方式對同一個字串進行 N 次雜湊,並確保這些雜湊充分去相關。

然而,若你明確要求 GPT4 修改雜湊函式,使其產生 N 個去相關的輸出,其提出的解法就顯得可接受得多:

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;
}

如果它能自行想出這個點子,就會以不同的方式撰寫 bloom filter,使用單一雜湊函式一次設定 K 個位元。

GPT4 能夠在孤立的情況下,完成撰寫一個適當且更通用的雜湊函式的任務。但在撰寫 bloom filter 這個較大的專案中,它未能展現足夠的推理能力,反而吐出兩個不同卻相似的雜湊函式:這正是人們會從一篇寫得不好的 bloom filter 範例文章中所預期的結果。

當前 Large Language Models 推理能力的薄弱,加上該主題可能來源稀少,甚至更糟的是,可能存在許多低品質的來源,共同導致了這個微薄的成果。然而,這並非孤例。我少數幾次嘗試在演算法或 system programming 領域使用 LLMs 時,結果都極差,即使所期望的推理能力並不比 LLMs 在 Python 程式設計等脈絡下似乎已具備的能力更高。

同時,GPT-4 能夠(在另一個對話中)反編譯它所產生的函式,並精確理解其用途:

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

因此,斷言在 system programming 脈絡中毫無用處,當然是不準確的。

一件有趣且大致可預期的事是,在這些情況下,小模型與大模型之間的差異確實顯著。讓我們看看兩個本地模型的表現。

例如,Mixtral,也就是 Mistral 的 MoE(混合專家模型),對同一個問題回應如下:

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 在許多用途上是個優秀的模型,但就 LLMs 本已薄弱的推理能力而言,目前的規則無疑是:越大越好。

這是與本地 LLM deepseek-coder 的對話,該模型量化至 4 位元,因為我的 RAM 不足以用更高精度來運行模型。然而,作為一個擁有 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;
}

也許我使用的加鹽策略很差?我該怎麼改?

[DeepSeek 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,斷言它們對程式設計師毫無幫助都是極為草率的。

然而同時,過去幾個月我的經驗顯示,對於 system programming,如果你已經是經驗豐富的程式設計師,LLMs 幾乎從未提供可接受的解法。讓我再舉一個真實例子。我目前的專案 ggufflib,涉及撰寫一個讀寫 GGUF 格式檔案的函式庫,也就是 llama.cpp 載入量化模型時所使用的格式。起初為了理解 quantization(量化)編碼如何運作(出於速度原因,每個 quantization 的位元以花俏的方式儲存),我嘗試使用 ChatGPT,但後來決定逆向工程 llama.cpp 的程式碼:那要快得多。一個能像樣地協助 system programming 領域的程式設計師的 LLM,如果看到資料編碼的「struct」宣告與解碼函式,應該能夠重建資料格式的文件。llama.cpp 的函式小到足以完整放入 GPT4 的脈絡中,但輸出卻完全無用。在這些情況下,事情還是得像過去那樣做:紙和筆、閱讀程式碼,並觀察解碼器擷取的位元被記錄到哪裡。

讓我把上述的使用案例解釋得更清楚一點,這樣如果你想的話,也可以自己試試。我們有來自 llama.cpp 實作的這個結構。

// 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;

接著有這個用來執行反量化的函式:

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),等等。

順帶一提,這是我最後自己寫的程式碼:

    } 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 能替我做這件事,將會非常有用,而我敢打賭這只是幾個月內的事,因為這類任務不需要任何突破,只要稍微擴大規模就能達成。

綜觀全局

我很遺憾這麼說,但這是事實:當今大多數的程式設計不過是以稍微不同的形式反覆吐出同樣的東西。不需要高水準的推理。LLMs 還算擅長做這件事,雖然它們仍強烈受限於脈絡的最大長度。這真的應該讓程式設計師深思。寫這類程式值得嗎?當然,你會得到報酬,而且還不少,但如果 LLM 能代勞一部分,或許五到十年後那就不是最好的立足之地。

那麼,LLMs 究竟是否具備某種推理能力,還是全是虛張聲勢?或許有時它們看起來像在推理,只是因為如同符號學家會說的,「能指」給人一種其實並不存在的意義印象。那些與 LLMs 共事夠久、同時也接受其限制的人,則確信事實並非如此:它們融合以往所見事物的能力,遠遠超出隨機吐出詞彙的程度。儘管它們的訓練大多是在 pre-training(預訓練)期間、透過預測下一個 token(詞元)來完成的,這個目標迫使模型創造出某種形式的抽象模型。這個模型薄弱、零碎且不完美,但如果我們觀察到我們所觀察到的現象,它就必然存在。如果我們的數學確定性值得懷疑,且最頂尖的專家往往立場對立,那麼相信自己親眼所見似乎是明智的做法。

最後,如今不把 LLMs 用於程式設計還有什麼道理?向 LLMs 提出正確的問題是一項基本技能。越少練習,就越無法藉由 AI 來提升自己的工作。再者,培養描述問題的能力在與其他人溝通時也很有用。不只有 LLMs 有時聽不懂我們想說什麼。溝通不良是一大限制,而許多程式設計師儘管在自身專業領域非常能幹,溝通卻非常糟糕。而現在 Google 已經無法使用:即使只是把 LLMs 當作一種壓縮形式的文件來使用,也是個好主意。就我而言,我會繼續大量使用它們。我從來就不喜歡去學習某個冷僻通訊協定的細節,或某個想炫耀自己多厲害的人所寫的函式庫中迂迴曲折的方法。對我而言,那似乎像是「垃圾知識」。LLMs 每天都越來越幫我省去這一切。

原文由 Salvatore Sanfilippo 發布

本文章由 muse-spark-1.2-contributor 進行翻譯