作者 | Antirez? ? ? ? ?
首先我要明確,這篇文章并不旨在回顧大語言模型。顯而易見,2023 年對人工智能來說是不平凡的一年,再去強調(diào)這一點似乎沒有多大必要。這篇文章更多是作為一位程序員的個人體驗分享。
自從 ChatGPT 出現(xiàn),再到使用本地運行的大語言模型,我就開始廣泛應(yīng)用這項新技術(shù)。我的目標不僅僅是提高編碼效率,更重要的是,我不想在編程中那些無需過多精力投入的地方浪費時間。不愿意花費大量時間去查找某些專業(yè)且無趣的文檔,不想為了學習一些過于復(fù)雜且往往無需如此的 API 而勞心費力,也不想編寫那些幾小時后就會被我拋棄的臨時代碼。尤其是如今 Google 成了一個充斥著垃圾信息的海洋,我們只能在其中努力尋找那少數(shù)有用信息。
同時,我也不是編程領(lǐng)域的新手。我完全有能力在沒有任何輔助的情況下編寫代碼,而且我也常常這么做。隨著時間的推移,我越來越頻繁地借助大語言模型來編寫高級代碼,特別是 Python 代碼,而在 C 語言方面則相對少一些。在體驗大語言模型的過程中,我深刻認識到,應(yīng)該在何時使用它們,以及何時它們的使用反而會拖慢我的步伐。我還了解到,大語言模型有點類似于維基百科和 YouTube 上琳瑯滿目的視頻課程:它們對那些有意愿、有能力和自律的人大有裨益,但對于其他人來說,幫助有限。我擔心,至少在初始階段,它們更多的是惠及那些本就占據(jù)優(yōu)勢的人。 但我們還是一步一個腳印來吧!
全知全能還是鸚鵡學舌? ? 在機器學習新浪潮中,最讓人擔憂的是 AI 專家們難以接受自己知識的局限性。人類發(fā)明了神經(jīng)網(wǎng)絡(luò),更關(guān)鍵的是,還發(fā)明了一個自動優(yōu)化神經(jīng)網(wǎng)絡(luò)參數(shù)的算法。隨著硬件能力的提升,能夠訓練更大的模型,利用數(shù)據(jù)的統(tǒng)計知識(即先驗知識),通過不斷的嘗試和錯誤,逐漸找到了一些比其他架構(gòu)更有效的模型設(shè)計。但不管怎樣,神經(jīng)網(wǎng)絡(luò)本質(zhì)上還是相當復(fù)雜且不透明的。
面對大語言模型一些新的無法解釋的能力,謹慎的科學家們反而低估了它們。許多人認為,大語言模型不過是稍微高級點的馬爾科夫鏈 (Markov chains),最多只能重復(fù)訓練集里有限變化的內(nèi)容。然而,越來越多的證據(jù)表明,這種看法幾乎可能是大錯特錯的。 同時,很多吃瓜群眾過分夸大了大語言模型的能力,認為它們擁有現(xiàn)實中根本不存在的超自然力量。事實上,大語言模型最多只能在其訓練數(shù)據(jù)所代表的空間內(nèi)進行插值,即使如此,它們的這一能力也已經(jīng)相當驚人。真要是今天的大語言模型能夠在看過的所有代碼構(gòu)成的空間內(nèi)自如插值,它們即便不能創(chuàng)造真正的新穎事物,也足以取代 99% 的程序員。但現(xiàn)實情況要并沒有這么樂觀。大語言模型確實可以編寫一些它之前未曾見過的程序,展示出將不同思想的訓練數(shù)據(jù)融合的能力,但這種能力目前還有很大的限制,尤其是在需要細膩推理時,它們往往無法勝任。盡管如此,它們?nèi)源碇鴱娜斯ぶ悄苷Q生至今的最偉大成就,這一點似乎無庸置疑。
無知卻博學 ? 確實,大語言模型大體上只能進行初級的推理,這些推理經(jīng)常不準確,甚至會摻雜著一些關(guān)于不存在事實的幻覺。但它們卻擁有海量的知識。在編程領(lǐng)域,以及其他有高質(zhì)量數(shù)據(jù)的領(lǐng)域,大語言模型就像是理解力有限卻知識淵博的人。如果要和這樣的伙伴進行結(jié)對編程(對我來說,結(jié)對編程本身就是個痛苦),可能會很糟糕:它們可能提出一些荒謬的想法,我們需要不斷斗爭才能貫徹我們自己的想法。但如果這個博學的傻瓜能夠聽從我們的指令,回答我們提出的所有問題,那一切都會變得不同。現(xiàn)有的大語言模型雖然不能引領(lǐng)我們超越已知的路徑,但如果我們想探索一個不熟悉的領(lǐng)域,它們往往能夠幫我們從一無所知到掌握足夠的知識來獨立前行。 在編程領(lǐng)域,直到二十或三十年前,這些能力可能還不太引人注目。
那時,你需要掌握幾種編程語言、經(jīng)典算法以及那些基本的庫。其余的則依靠你自己的智慧、專業(yè)知識和設(shè)計能力。具備這些素質(zhì),你就能成為一名全能的專家級程序員。然而,隨著時間的推移,我們見證了框架、編程語言、各種庫的大量涌現(xiàn)。這種復(fù)雜性通常是不必要的,甚至無法自圓其說,但事實就是如此。在這樣的情況下,一個無所不知的“白癡”成了寶貴的盟友。 我來舉個例子:我對機器學習的實驗最初是用 Keras 進行的,持續(xù)了至少一年。后來因各種原因,我轉(zhuǎn)向了 PyTorch。我已經(jīng)了解什么是嵌入(Embedding)或殘差網(wǎng)絡(luò)(Residual Networks, ResNets),但我并不想深入研究 PyTorch 的文檔(就像我學習 Keras 那樣,那時 ChatGPT 還不存在)。有了大語言模型,用 Torch 編寫 Python 代碼變得非常容易。我只需清楚地了解我想要構(gòu)建的模型,并提出合適的問題。
應(yīng)用案例 ? 我要討論的不是那些簡單的問題,比如“嘿,類 X 是如何執(zhí)行 Y 操作的?”如果只是這些問題,那些對大語言模型保持懷疑態(tài)度的人可能就有理由了。但實際上,更復(fù)雜的模型能做的事情遠遠超出這些。幾年前,這些還被認為是不可思議的魔法。我可以這樣對 GPT4 下指令:看,這是我在 PyTorch 中實現(xiàn)的神經(jīng)網(wǎng)絡(luò)模型。這里是我的數(shù)據(jù)批次。我想調(diào)整這些張量的大小,以便它們能與神經(jīng)網(wǎng)絡(luò)輸入的要求相匹配,并且我希望以這種特別的方式展現(xiàn)它們。你能幫我寫出調(diào)整這些張量尺寸的代碼嗎?GPT4 幫我寫出了代碼,我接下來要做的,就是在 Python 命令行界面中測試這些張量是否真的符合我需要的大小,以及數(shù)據(jù)結(jié)構(gòu)是否正確。 再舉一個例子。不久前,我需要為基于 ESP32 的設(shè)備開發(fā)一個藍牙低能耗 (BLE) 客戶端。經(jīng)過研究后,我發(fā)現(xiàn)多平臺藍牙編程接口大都不好用。解決方法很簡單,就是用 Objective C 和 macOS 的原生 API 來編寫代碼。但這樣一來,我就面臨著兩個問題:一是學習 Objective C 中復(fù)雜的 BLE API,這些 API 充滿了我認為完全沒有必要的復(fù)雜設(shè)計(作為一個極簡主義者,這些設(shè)計與我所認為的“好設(shè)計”截然相反);二是回憶起怎樣使用 Objective C 編程。我上次使用 Objective C 編寫程序是十年前了,很多細節(jié),比如事件循環(huán)、內(nèi)存管理等,我都已經(jīng)記不清了。 最后的結(jié)果就是這段代碼,雖然它看起來不是很美觀,但它完成了它的任務(wù)。我在極短的時間內(nèi)就編寫完成了。否則根本不可能做到這一點。 https://github.com/antirez/freakwan/blob/main/osx-bte-cli/SerialBTE.m 這段代碼主要是通過在 ChatGPT 上復(fù)制粘貼我想實現(xiàn)但不太確定如何著手的功能來編寫的,因此最初它們并未能正確運行。
然后,大語言模型幫我指出了問題所在并告訴我如何解決。雖然大部分代碼不是由 LLM 直接編寫的,但它確實極大地加快了編程速度。不用 ChatGPT 我也能完成這個任務(wù)嗎?答案是肯定的,但更有趣的不僅是它節(jié)省了我很多時間:事實上,如果沒有 ChatGPT,我連嘗試的勇氣都沒有,因為那似乎并不值得。這一點至關(guān)重要。對于我的項目來說,編寫這樣一個不重要的程序的努力與其帶來的好處的比例本來是不劃算的。此外,這個過程產(chǎn)生了一個比程序本身更有用的副作用:為了這個項目,我對 linenoise(我用于行編輯的一個庫)進行了改造,使其可以在多路復(fù)用環(huán)境下運行。 這是另一個例子,更多地涉及到數(shù)據(jù)解釋而非代碼編寫。我打算用一個我在網(wǎng)上發(fā)現(xiàn)的卷積神經(jīng)網(wǎng)絡(luò) (Convolutional Neural Network) 設(shè)置一個 Python 腳本,但這個網(wǎng)絡(luò)缺乏詳細文檔。網(wǎng)絡(luò)的一大優(yōu)勢是它采用了 ONNX (Open Neural Network Exchange) 格式,這使我能夠輕松地識別出網(wǎng)絡(luò)的輸入和輸出以及它們對應(yīng)的名稱。我對這個卷積網(wǎng)絡(luò)了解的唯一一點是:它能識別圖像中的特定特征。但我不知道所需輸入圖像的格式和大小,更何況,網(wǎng)絡(luò)的輸出比我預(yù)想的要復(fù)雜得多(我原本以為它是一個二元分類器 (binary classifier),用于判斷觀察到的圖像是否正常或存在問題。
我原以為它只有兩個輸出,但實際上有數(shù)百個)。我首先把 ONNX 網(wǎng)絡(luò)的元數(shù)據(jù)輸出復(fù)制粘貼到 ChatGPT 中,并向助手闡述了我所知道的關(guān)于網(wǎng)絡(luò)的有限信息。ChatGPT 推測了輸入的組織方式,以及輸出可能是標準化后的框,用于指出圖像中潛在缺陷的部分,還有其他輸出表示這些缺陷的可能性。經(jīng)過幾分鐘的交流,我得到了一個能進行網(wǎng)絡(luò)推斷的 Python 腳本,以及將起始圖像轉(zhuǎn)換為適合輸入的張量等必要代碼。讓我印象深刻的是,當 ChatGPT 觀察到測試圖像上的原始輸出值(基本上是邏輯單元 (logits))時,它終于“理解”了網(wǎng)絡(luò)的運作方式:一系列浮點數(shù)為識別輸出的確切細節(jié)和標準化提供了上下文,比如框是否居中或指定了左上角等細節(jié)。
一次性程序 ? 我曾經(jīng)遇到過很多類似的情況,就像我之前敘述的那樣。但記錄這些并沒有太大意義,因為這些情況重復(fù)的故事基本相同。我的問題是,我需要迅速了解一些事情,特別是在大語言模型給出的回答可能是無稽之談時,我得能夠驗證這些信息的真實性。在這種情況下,我會利用大語言模型加快我的知識獲取速度。 但在其他情況下,我會讓大語言模型完全編寫代碼。舉個例子,當我需要編寫一個基本可以隨時丟棄的程序時。比如這個: 簡單語言模型示例程序 我需要可視化一個小型神經(jīng)網(wǎng)絡(luò)學習過程中的損失曲線(loss curve)。
我向 GPT4 展示了 PyTorch 程序在學習過程中生成的 CSV 文件格式,然后我提出了一個需求:如果我在命令行中指定了多個 CSV 文件,我不想再看到同一實驗的訓練和驗證損失曲線,而是想比較不同實驗的驗證損失曲線。上面就是 GPT4 生成的結(jié)果,總共用了三十秒。 類似地,我需要一個程序來讀取 AirBnB 的 CSV 報告,并按月份和年份對我的公寓進行分組。接著,它會考慮清潔成本和每次預(yù)訂的夜晚數(shù)量,統(tǒng)計不同月份的平均租金價格。這個程序?qū)ξ曳浅S杏茫帉懰謽O其無聊,因為過程中沒有什么新穎之處。因此,我把 CSV 文件的一部分復(fù)制粘貼到 GPT4 上,告訴大語言模型我要解決的問題。程序第一次嘗試就運行成功了,下面是完整的展示。
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)要準確地理解如何對這些原本分散和無序的數(shù)據(jù)進行分組,需要一些簡單的邏輯推理。這不是大語言模型僅憑重復(fù)它所見過的語言模式就能簡單解決的問題。雖然 GPT4 在訓練中肯定觀察到了類似的程序,但幾乎可以肯定它沒有看到一個程序需要以我所要求的方式,在具有特定格式的 CSV 文件中對數(shù)據(jù)進行分組。這證明了(至少在我看來),大語言模型能夠在其訓練集中不同程序所勾勒的范圍內(nèi)進行一定程度的創(chuàng)新和推理。 在我個人看來,編寫這類程序可能是對時間的不明智使用。一個大語言模型能替我完成這項工作,顯著提升了我編程的效率。這意味著,我能將精力集中在真正重要的事務(wù)上。
?
?
當事情不按計劃進行:系統(tǒng)編程的挑戰(zhàn) ? 在大語言模型和編程領(lǐng)域,我取得了一定的成功。但是,當我使用 C 語言編寫程序時,我通常只把 LLM 作為一種更便捷的文檔工具。作為一名系統(tǒng)編程專家,我發(fā)現(xiàn)只有在 LLM 能進行復(fù)雜的推理分析時,它們才對我有實際用處。而在這方面,LLM 經(jīng)常無法滿足期望。 讓我們看一個實驗例子: 試著概述一個高效、小型但完整的 C 語言實現(xiàn)的布隆過濾器(Bloom Filter)的要求,重點在于哈希(hashing)功能的關(guān)鍵要素。然后用優(yōu)質(zhì)的 C 語言編寫它。 另外,考慮到你的例子應(yīng)該可以存儲 100,000 個元素,并且誤報概率不超過 5%。添加的元素是以空字符結(jié)尾的字符串。 以下是輸出結(jié)果:
?
?
#includeGPT4 所生成的布隆過濾器實現(xiàn)上并不算很出色。布隆過濾器應(yīng)用廣泛,并不是什么稀奇古怪的數(shù)據(jù)結(jié)構(gòu)。然而,要編寫一個合格的布隆過濾器,需要更高層次的抽象思維。比如,需要找到一種有效的方法,對同一個字符串進行多次哈希處理,同時確保這些哈希值相互之間的獨立性。 但是,如果你明確指示 GPT4 改進哈希函數(shù),使之能產(chǎn)生多個互不相關(guān)的輸出,它提出的方案就顯得更加合理: ?#include #include #include #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. ", testStr); } else { printf("%s is definitely not in the set. ", testStr); } return 0; }
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; }? 如果 GPT4 能夠自行提出這種方案,它編寫的布隆過濾器將會有所不同,可能會使用單一的哈希函數(shù)一次性設(shè)置多個位。 GPT4 能夠獨立完成編寫合適且更通用哈希函數(shù)的任務(wù)。但在整個布隆過濾器的編寫過程中,它未能表現(xiàn)出足夠的推理能力。相反,它只是提供了兩種不同但相似的哈希函數(shù),這種結(jié)果更像是從一篇關(guān)于布隆過濾器的低質(zhì)量文章中看到的。 大語言模型在推理能力上的不足,加上可能缺乏高質(zhì)量的相關(guān)資料,甚至還可能存在大量低質(zhì)量的資料,共同造成了這種不盡人意的結(jié)果。
?
?
事實上,這并非個例。在算法或系統(tǒng)編程領(lǐng)域,我嘗試使用 LLM 的體驗通常都很差勁,哪怕在類似 Python 編程這樣的領(lǐng)域,LLM 表現(xiàn)出來的推理能力并不算高。 與此同時,GPT-4 不僅能反編譯它生成的函數(shù)(在另一個獨立會話中),還能精準地理解該函數(shù)的用途: https://chat.openai.com/share/f54b64d7-d19a-4be4-8f10-08553aa812f1 因此,認為 GPT-4 在系統(tǒng)編程領(lǐng)域無用的說法顯然是不恰當?shù)摹?有趣的是,這些情況下,小型與大型模型之間的差異顯著。來看看兩個本地模型的對比表現(xiàn)。 例如,Mistral 的專家模型 (MoE) Mixtral,對同樣的問題給出了以下回應(yīng): ?
?
?
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 對許多應(yīng)用而言是一個優(yōu)秀的模型,但對于大語言模型本就有限的推理能力來說,目前的規(guī)則顯然是:越大越好。 這是我與本地大型語言模型 deepseek-coder 的對話記錄。由于 RAM 有限,我只能將模型量化到 4 位來使用。盡管如此,作為一個擁有 340 億參數(shù)的龐大模型,它在這個問題上的推理能力似乎相當出色。以下是我用 llama.cpp 進行的聊天記錄:
?
?
我有一個哈希函數(shù),它應(yīng)該為相同的數(shù)據(jù)輸出 N 個不同的哈希。但是,我發(fā)現(xià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 的回復(fù)] 你的實現(xiàn)中可能存在的一個問題是,你只是簡單地在哈希的末尾添加了 hash_id。這可能會導(dǎo)致分布不均,尤其是當 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,從而使哈希分布更均勻。[省略……實際回復(fù)更詳盡] 在我的 MacBook M1 Max 上運行的這個模型表現(xiàn)得相當不錯。它成功地將求和和異或操作混合使用了。在這個案例中,模型得到了我提供的關(guān)于問題的線索的幫助,但最終是模型自己識別出了問題的真正所在,并提出了一個有效的解決方案。這樣的成就是任何書籍、文檔或 Google 搜索都無法達到的。不管你如何看待這個模型——無論是作為一個初級的、基于插值的結(jié)果,還是以其他方式——它確實展現(xiàn)了某種形式的推理能力。
在這個特定案例中,如果我們認為識別問題的根源和找到可能的解決方案是一種推理,那么它確實做到了這一點。但無論我們?nèi)绾慰创笳Z言模型,斷言它們對程序員無用是非常草率的。 然而,根據(jù)我過去幾個月的經(jīng)驗,對于系統(tǒng)編程來說,如果你已是資深程序員,大語言模型往往無法提供令人滿意的解決方案。我來舉一個真實世界中的例子。我的最新項目,ggufflib,涉及到開發(fā)一個讀寫 GGUF 格式文件的庫,而這正是 llama.cpp 用來加載量化模型的格式。起初,為了弄懂量化編碼的工作原理(因為速度原因,每個量化比特都以特殊方式存儲),我試過使用 ChatGPT,但最后我選擇了對 llama.cpp 代碼進行逆向工程,這樣更加迅速。一個能夠有效協(xié)助系統(tǒng)程序員的大語言模型,在看到數(shù)據(jù)編碼的結(jié)構(gòu)聲明和解碼函數(shù)后,應(yīng)該能夠重建數(shù)據(jù)格式的文檔。雖然 llama.cpp 的功能足夠簡短,可以完全放入 GPT4 的上下文中,但它的輸出卻毫無用處。在這些情況下,我們還是得回歸傳統(tǒng)方式:紙筆在手,細讀代碼,尋找解碼器提取的比特在哪里注冊。 為了讓你更好地理解上述案例,如果你感興趣,可以嘗試一下。這里有一個來自 llama.cpp 實現(xiàn)的結(jié)構(gòu)。 ?
?
?
// 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;? 然后是用于執(zhí)行去量化的函數(shù): ?
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 編寫關(guān)于使用格式的概述時,它難以清晰地說明“ql”中上下四位的數(shù)據(jù)塊是如何存儲的,這與權(quán)重位置有關(guān)。在撰寫這篇博客時,我還嘗試讓它編寫一個簡化版本的函數(shù)來展示數(shù)據(jù)的存儲方式(可能它難以用文字解釋,但可以通過代碼來表達)。然而,它生成的函數(shù)存在諸多問題,比如索引不正確,從 6 位到 8 位的符號擴展處理錯誤(僅僅是將其轉(zhuǎn)換為 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. } }? 從上述函數(shù)中,我移除了這段代碼的核心貢獻:即長篇注釋,詳細記錄了 llama.cpp 中 Q6_K 編碼使用的確切格式?,F(xiàn)在,如果 GPT 能夠幫我完成這一工作,那將非常有幫助。我相信這只是時間問題,因為這類任務(wù)在沒有技術(shù)突破的情況下也是可行的,只需適當?shù)臄U展即可。
?
?
重新審視編程工作 ? 不得不說,這是一個事實:現(xiàn)今的編程大多是在微調(diào)同樣的內(nèi)容,只是形式略有變化。這種工作并不需要太高的推理能力。大語言模型在這方面表現(xiàn)出色,盡管它們的能力仍然受限于上下文長度。這個現(xiàn)象應(yīng)該引起程序員的深思:真的值得去編寫這類程序嗎?雖然可以賺到不錯的收入,但如果大語言模型也能完成其中一部分工作,那么在未來五到十年,這可能并非最佳的職業(yè)發(fā)展方向。 再來看,大語言模型真的具備一定的推理能力,還是只是表面上的假象?有時候,它們似乎在進行推理,但這可能只是因為,像符號學家所說,使用的“符號”造成了一種實際上并不存在的意義錯覺。
足夠了解大語言模型的人會明白,事實并非如此:這些模型整合既有信息的能力,遠非簡單的詞匯重復(fù)。它們在預(yù)訓練期間的訓練主要是預(yù)測下一個 Token,這個過程迫使模型構(gòu)建了一種抽象的模型。雖然這個模型可能脆弱、零散且不完美,但從我們觀察到的現(xiàn)象來看,它確實存在。在數(shù)學確定性存在疑問,且領(lǐng)域內(nèi)頂尖專家意見分歧的情況下,相信自己的直覺似乎是明智之舉。
最后,今天還有什么理由不去使用大語言模型輔助編程呢? 正確地向大語言模型提問是一項關(guān)鍵技能。這項技能練習得越少,利用 AI 改善工作的能力就越弱。而且,無論是與大語言模型還是與人類交流,清晰描述問題同樣重要。溝通不暢是一個嚴重的障礙,很多程序員盡管在自己的專業(yè)領(lǐng)域很有能力,但在溝通上卻做得很糟糕?,F(xiàn)在,連 Google 都變得不那么好用了,所以即便是將大語言模型作為一種壓縮文檔的方式來使用,也是個不錯的主意。至于我,我將繼續(xù)大量使用它們。我從來不喜歡去深究某個晦澀的通訊協(xié)議的細節(jié),或者去理解由某些想要炫耀自己技術(shù)的人編寫的復(fù)雜庫方法。這些對我來說就像是"無用知識"。有了大語言模型,我就能免于這些困擾,每天都能感覺到它帶來的幫助。
審核編輯:黃飛
評論