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