LLM 工程師入門:生成式AI的簡易指南 原創
編者按: 大模型發展了近兩年,Baihai IDP公眾號也分享了近百篇LLM各環節的技術洞察,有前沿探討、有落地實踐、有應用經驗。但回頭來看,我們似乎從來沒有認真、從0開始探討過LLM的基本原理。
最近,一些企業客戶和伙伴來詢問,是否有LLM的從0到1的科普貼。他們說:
“雖然在很多場景中,LLM都已經滲透入我們的工作生活,但對其內部的運作機制,仍有很多謎團待解決。
在應用落地時,LLMs 這種“黑箱式”的運作模式,不僅使我們難以完全信任這些模型的輸出結果,也阻礙了我們對其進一步研究和優化的步伐。如果我們無法理解 LLMs 的工作原理,就很難評估它們的局限性,進而制定出有針對性的解決方案。”
因此,我們把這篇LLM基礎原理文章推薦給大家。
本文為希望深入了解生成式AI的開發者、技術愛好者、AI落地的領導者和研究者們編寫,以通俗易懂的語言,系統地剖析了大語言模型的內部結構和訓練流程,從 token、next token predictions,到馬爾可夫鏈、神經網絡等核心概念,循序漸進地揭示了 LLM 是如何生成文本的。
作者 | Miguel Grinberg
編譯 | 岳揚
毫無疑問,隨著大語言模型[1](LLMs)的新聞不斷出現在我們的日常生活,生成式人工智能[2](GenAI)已經成為了我們無法忽視的存在。或許你早已體驗過 ChatGPT[3] ,甚至把它當作日常生活的小助理了。
面對這場 GenAI 變革,許多人心中都有一個疑問:這些模型表面上的智能(intelligence)究竟源自何處?本文將試圖用淺顯易懂的語言,不涉及復雜數學公式,來揭秘生成式文本模型的工作原理,讓你認識到它們并非魔法,而是計算機算法的產物。
01 What Does An LLM Do?
首先,我要澄清人們對大語言模型工作原理的一個重大誤解。人們通常認為,這些模型能夠回答我們的問題或與我們進行對話,但實際上,它們所能做的是基于我們提供的文本輸入,預測下一個單詞(更準確地說,是下一個 token)。
現在,讓我們一步步揭開 LLMs 神秘面紗背后的“真面目”,從 token 開始探索。
1.1 Tokens
token 是大語言模型(LLM)處理文本時的基本單元。雖然我們可以簡單地將 token 視為單個單詞,但 LLM 的目的是以最高效的方式對文本進行編碼。因此在很多情況下,token 可能是比單個單詞短或長的字符序列。 標點符號和空格同樣以 token 的形式存在,它們可以單獨表示為一個 token,也可以與其他字符組合。
LLM 所使用的所有 token 構成了它的詞匯表(vocabulary),這個詞匯表能夠用來表達所有可能的文本內容。大語言模型通常采用 BPE(Byte Pair Encoding)[4]算法來根據輸入數據集創建 token 詞匯表。以 GPT-2 語言模型[5](開源模型,可供深入研究)為例,其詞匯表擁有 50,257 個 token。
每個 token 在 LLM 的詞匯表中都有一個獨一無二的標識符(通常是一個數字編號)。LLM 通過分詞器將常規文本字符串轉換為一系列 token 編號。 如果您對 Python 有所了解,并且想要嘗試對 token 進行操作,可以安裝 OpenAI 提供的 tiktoken 軟件包:
然后請在 Python 命令行中嘗試以下操作:
在本實驗中,我們可以觀察到,GPT-2 語言模型的詞匯表中,token 464 對應的是單詞"The",而 token 2068 則對應" quick",這個 token 包括了單詞之前的空格。在該模型中,句號由 token 13 表示。
由于 token 是通過算法來決定的,因此我們可能會遇到一些奇特的情況,比如 GPT-2 會將單詞"the"的以下三種形式編碼為不同的 token:
BPE 算法并不會總是將整個單詞直接轉化為一個 token。事實上,那些使用頻率較低的單詞不會被單獨表示為一個 token,而是需要通過多個 token 的組合來進行編碼。下文這個例子,展示了 GPT-2 模型是如何用一個由兩個 token 組成的序列來編碼某個單詞的:
1.2 Next Token Predictions
如上文所述,語言模型會根據給定文本預測之后可能出現的 token。如果用 Python 偽代碼來展示這個過程,下文就演示了如何使用這些模型來預測下一個 token:
這個函數將用戶輸入的提示詞轉換成的 token 列表作為模型輸入。這里假設每個單詞都是一個單獨的 token。為了簡單起見,此處使用了每個 token 的文字形式,但實際上,每個 token 都是以數字的形式傳遞給模型的。
該函數返回的是一種特有的數據結構,它為詞匯表中的每個 token 分配了一個在輸入文本后緊接著出現的概率。如果使用的是 GPT-2 模型,那么返回的將是一個包含 50,257 個浮點數的列表(list),列表中每個數字代表相應 token 緊接著文本內容出現的概率。
在上述案例中,可以設想訓練效果良好的語言模型會為"jumps"這個 token 分配一個較高的概率,以接續短語"The quick brown fox"[6]。同樣地,如果模型訓練得當,那么像"potato"這樣的隨機單詞接在這個短語后面的概率就會低很多,幾乎接近于0。
為了做出合理的預測,語言模型需要經過一個訓練過程。在訓練期間,模型會學習大量文本內容。訓練結束后,模型就能利用它從訓練文本中構建的特有數據結構,來計算給定 token 序列的下一個 token 的概率。
這與你的預期是否有所不同?我希望現在這個概念看起來不再那么神秘了。
1.3 生成長文本序列
由于模型只能預測下一個出現的 token,因此要想讓它生成完整的句子,就必須在 for 循環中多次運行模型。每一次循環迭代,都會根據返回的概率列表選擇一個新的token。這個新 token 會被加入到下一次循環迭代中模型的輸入序列中,如此循環往復,一直持續到生成足夠的文本為止。
下面是一個更完整的 Python 偽代碼示例,演示了這個過程:
generate_text() 函數需要用戶提供提示詞內容,比如可以是一個問題。
tokenize() 這個輔助函數負責將用戶的提示詞轉換成一系列 token,這個過程會用到 tiktoken 或類似的庫。在 for 循環中,get_token_predictions() 函數負責調用AI模型,獲取下一個 token 的概率列表,與上文案例中描述的過程相同。
select_next_token() 函數的作用是根據模型給出的下一個 token 的概率列表,從候選 token 中挑選出最合適的 token 來放入輸入序列。這個函數可以采取最簡單的方法,即選擇概率最高的 token ,這在機器學習中被稱為“greedy selection”。然而,為了增加生成文本的多樣性,該函數通常會采用更高級的策略,使用一個隨機數生成器來選擇 token,即使是在隨機選擇 token 的情況下,也會優先選擇那些概率較高的 token。通過這種方式,即便是給出相同的輸入提示詞,模型也能生成不同的文本響應。
為了進一步增加 token 選擇過程的靈活性,可以通過調整超參數來改變 LLMs 返回的概率分布。 這些超參數為傳遞給文本生成函數的參數,能夠幫助用戶控制 token 選擇過程的“greediness”(譯者注:模型在選擇下一個 token 時所表現出的傾向性,是傾向于選擇概率最高的token(即最可能的token),還是允許一些不太可能的token(即概率較低的token)被選中。)。如果你以前經常使用 LLMs,那么就很可能對 “temperature” 超參數比較熟悉。當 temperature 值較高時,token 的概率分布會被 flattened out(譯者注:模型會考慮更多的token,包括那些概率較低的token,使得概率分布更加均勻。),這樣做的結果是,之前不太可能被選中的 token 現在有更大的機會被選中,從而使生成的文本看起來更具創造性和新穎性。除了 temperature 之外,還有兩個超參數 top_p 和 top_k,它們分別用來控制在選擇過程中高概率 token 被選中的數量。 通過調整這些超參數,可以進一步影響文本生成的風格和多樣性。
一旦模型選定了下一個token,循環就會繼續迭代。此時,模型將接收到一個新的輸入序列,這個輸入序列的末尾添加了新 token(譯者注:上一次迭代選擇的 token)。num_tokens 參數決定了循環的迭代次數,也就是生成文本的長度。生成的文本可能會(并且經常)在句子中間結束,因為大語言模型(LLM)并沒有句子或段落的概念,它只是逐個處理 token。為了防止生成的文本內容在句子中間就結束了,我們可以將 num_tokens 參數視為一個最大數量值,而不是一個確切的 token 數量。在這種情況下,我們可以在模型生成一個句號 token 時結束循環。
如果你已經閱讀到此處,并且理解了前文的所有內容,那么恭喜你,你現在對 LLMs 的工作原理已經有了較為深入的理解。各位讀者是否對更多技術細節感興趣?在下一節中,我將介紹更多技術細節,同時盡量避免提及關于這項技術的復雜數學知識。
02 Model Training
不幸的是,要想不涉及數學知識就討論模型的訓練過程,實在是件不容易的事。接下來,我將首先向大家展示一種非常簡單的訓練方法。
我們的目標是預測 token 后面可能出現的其他 tokens,因此,訓練模型的簡單方法就是從訓練數據集中提取所有連續的 tokens 對,然后用這些數據來構建一個概率表(table of probabilities)。
讓我們用一個簡短的詞匯表(vocabulary)和數據集(dataset)來做這件事。假設模型的詞匯表有以下五個 token :
為了使這個例子簡明扼要,我們不把空格和標點符號算作 token。
我們使用以下三個句子組成的訓練數據集:
- I like apples
- I like bananas
- you like bananas
我們可以制作一個 5x5 的表格,每個單元格記錄的是該行 token 后面跟著該列 token 的頻次。表格如下:
該數據集中,“I like” 出現了兩次,“you like” 一次,“like apples” 一次,而 “like bananas” 則是兩次。
現在我們知道了訓練數據集中每對 tokens 的出現頻率,就可以推算出它們相互跟隨的概率。做法是將表格中每一行的數字轉換成概率。比如,表格里“like”這一行,我們看到它后面跟著 “apples” 一次,跟著 “bananas” 兩次。這意味著在“like”之后出現 “apples” 的概率是33.3%,而出現 “bananas” 的概率則是66.7%。
下面是計算出所有概率的完整表格。那些空白單元格代表的概率自然就是0%。
對于 “I”、“you” 和 “like” 這些行的概率計算比較輕松、直接,但 “apples” 和 “bananas” 這兩行就有點難辦了,因為數據集中并沒有 “apples” 和 “bananas” 后面跟著其他 token 的情況。這就好比在我們的訓練數據中出現了“缺口”。為了確保模型在遇到這種未訓練的情況時也能有所輸出,我決定將 “apples” 和 “bananas” 后續 token 的概率均勻分配給其他四個 tokens。這種做法可能會導致一些不太自然的輸出結果,但至少模型在處理這兩個 token 時不會卡殼。
訓練數據中的這種“缺口”問題不容小覷。在真正的 LLMs 中,由于訓練數據集規模非常龐大,我們不太可能遇到像本文這個簡單例子中這么明顯的缺口。但是,由于訓練數據的覆蓋面不足,那些較小的、較難發現的缺口確實是存在的,并且相當常見。 LLMs 在這些訓練不足的區域所做的 tokens 預測質量可能會不高,而且這些問題往往不易被察覺。這也是 LLMs 有時會出現“幻覺[7]”(即生成的文本雖然朗朗上口,但可能包含與事實不符的內容或前后矛盾之處。)的原因之一。
借助上面的概率表,你現在可以構思一下 get_token_predictions() 函數的實現方法。以下是用 Python 偽代碼表示的一種實現方式:
是不是比想象中還要簡單些呢?這個函數可以接收來自用戶提示詞的 tokens 序列。它提取出這個序列中的最后一個 token ,并找到概率表中對應的那一行。
假如你用 [‘you’, ‘like’] 作為 input tokens 調用這個函數,它會給出 “like” 對應的那一行,這使得 “apples” 有 33.3% 的可能性放入句子的下一部分,而 “bananas” 則有 66.7% 的可能性。根據這些概率值可以得知,上面提到的 select_next_token() 函數在三次中有一次會挑選 “apples”。
當 “apples” 被選為 “you like” 的后續詞時,我們得到了句子 “you like apples”。這個句子在訓練數據集中并不存在,但它卻是完全合理的。希望這個小例子能讓您開始意識到,這些模型是如何通過重新利用訓練中學到的 patterns(譯者注:在數據集中識別和學習的重復出現的 tokens 序列組合。) 和拼接不同的信息片段,來形成看似原創的想法或概念的。
2.1 上下文窗口(The Context Window)
在上一部分,我用來訓練那個微型語言模型的方法,稱為馬爾可夫鏈[8](譯者注:Markov chain,一種數學系統,用于描述一系列可能的事件,其中每個事件的發生概率只依賴于前一個事件。)。
這種技術存在的問題是,它只根據一個 token(輸入序列中的最后一個 token)來預測接下來的內容。在此之前的文本在決定如何延續文本內容時并不起作用,因此我們可以認為這種方法的上下文窗口僅限于一個 token,這是非常小的。由于上下文窗口太小,模型會不斷“忘記”自己的思路,從一個詞跳到下一個詞,顯得雜亂無章。
為了提高模型的預測能力,我們可以構建一個更大的概率表。如果想使用兩個 token 的上下文窗口,就需要在概率表中添加代表所有兩個 token 組合的新行。以前文的例子來說,五個 token 將會在概率表增加 25 行兩個 token 組合的新行,再加上原有的 5 個單 token 行。這次除了考慮兩個 token 的組合外,還需要考慮三個 token 的組合,因此必須再次訓練模型。在 get_token_predictions() 函數的每次循環迭代中,如果條件允許,將使用輸入序列的最后兩個 tokens 來在一個更大的概率表中找到與這兩個 tokens 相對應的行。
但是,兩個 tokens 的上下文窗口仍然不夠。為了讓生成的文本不僅在結構上能夠自洽,還能在一定程度上具備一些意義,需要一個更大的上下文窗口。如果沒有足夠大的上下文窗口,新生成的 token 就無法與之前的 tokens 所表達的概念(concepts)或想法(ideas)建立聯系。那么我們應該如何是好?將上下文窗口擴展到 3 個 tokens 會在概率表中增加 125 行新內容,但質量可能仍然不盡人意。我們又需要多大的上下文窗口呢?
OpenAI 的開源模型 GPT-2 使用的上下文窗口其大小為 1024 個 tokens。如果我們要用馬爾可夫鏈(Markov chains)實現這樣一個大小的上下文窗口,概率表的每一行都需要代表一個長度在 1 到 1024 個 tokens 之間的序列。以前文例子中使用的 5 個 tokens 的詞匯表為例,1024 個 token 長度的序列共有 5 種可能的 token 組合。我在 Python 中進行了計算:
\>\>\> pow(5, 1024) 55626846462680034577255817933310101605480399511558295763833185422180110870347954896357078975312775514101683493275895275128810854038836502721400309634442970528269449838300058261990253686064590901798039126173562593355209381270166265416453973718012279499214790991212515897719252957621869994522193843748736289511290126272884996414561770466127838448395124802899527144151299810833802858809753719892490239782222290074816037776586657834841586939662825734294051183140794537141608771803070715941051121170285190347786926570042246331102750604036185540464179153763503857127117918822547579033069472418242684328083352174724579376695971173152319349449321466491373527284227385153411689217559966957882267024615430273115634918212890625
這個行數多得嚇人!這還只是整個概率表的一小部分,因為我們還需要長度從 1023 個 tokens 到 1 個 token 的所有序列,以確保在模型輸入中沒有足夠 token 時,較短的 token 序列也能被處理。雖然馬爾可夫鏈很有趣,但它們也確實存在很大的可擴展性問題。
而且,1024 個 tokens 的上下文窗口現在也已經不算特別大了。在 GPT-3 中,上下文窗口增加到了 2048 個 tokens,然后在 GPT-3.5 中增加到了 4096 個 token。GPT-4 最開始擁有 8192 個 tokens 的上下文窗口,后來增加到了 32K,再后來又增加到了 128K(沒錯,128,000 個 tokens !)。現在,具有 1M 或更大上下文窗口的模型也開始出現了,更大的上下文窗口允許模型更好地理解和利用之前輸入的文本信息,從而在生成新的文本時保持更高的連貫性和準確性。
總之,馬爾可夫鏈讓我們以正確的方式思考文本生成問題,但它們也存在一些重大問題,使得我們無法將其視為一個可行的解決方案。
2.2 從馬爾可夫鏈到神經網絡
顯然,我們必須放棄使用概率表的想法,因為一個合理上下文窗口的概率表需要的 RAM 大得驚人。我們可以采取的替代方案是用函數(function)來代替表格(table),這個函數是通過算法生成的,而不是存儲在一個大表格中。這正是神經網絡擅長的領域。
神經網絡是一種特殊的函數,它接受 inputs ,對其進行計算,并返回 output 。對于語言模型來說,input 代表提示詞轉換的 tokens,output 是對下一個 token 的預測概率列表。
我說過神經網絡是一種“特殊”的函數,是因為除了函數的邏輯之外,它們對 input 進行的計算還受到一系列外部定義參數的控制。一開始,神經網絡的參數是未知的,因此,函數產生的輸出是完全無用的。神經網絡的訓練過程包括尋找參數,找到使函數在對訓練數據集進行評估時表現最佳的參數,其假設是,如果函數在訓練數據上表現良好,那么它也會在其他數據上表現良好。
在訓練過程中,神經網絡的參數會通過反向傳播算法[9],以極小的增量進行迭代調整。這個過程涉及大量的數學計算,因此在這里不會詳細展開。每次調整后,神經網絡的預測能力都會略有提升。更新參數后,神經網絡會再次評估其在訓練數據集上的表現,并根據評估結果進行下一輪的調整。這個過程會一直持續下去,直到神經網絡在訓練數據集上對下一個 token 的預測達到令人滿意的效果。
下面幫助各位讀者了解一下神經網絡的規模,GPT-2 模型大約有 15 億個參數,而 GPT-3 將參數數量增加到 1750 億,GPT-4 據說有大約 1.76 萬億個參數。使用目前這一代硬件來訓練這樣大規模的神經網絡需要相當長的時間,通常是幾周甚至幾個月。
有趣的是,由于參數數量眾多,而且都是通過長時間的迭代過程計算出來的,沒有人類干預,因此很難理解模型是如何工作的。訓練好的大語言模型就像一個黑盒子,非常難以調試,因為模型的大部分“思維”都隱藏在參數中。即使是那些訓練它們的人,也很難解釋其內部的工作原理。
2.3 模型層、Transformers 和注意力機制
你可能非常想知道,神經網絡函數內部究竟進行了哪些神奇的運算,使得它們在這些經過精心調優的參數的幫助下,能夠接收 input tokens 列表,并以某種方式“猜”出下一個 token 的合理概率。
神經網絡就像是一套復雜的操作鏈條?,鏈條的每個鏈環都被稱為一個“模型層”。第一層接收 inputs (譯者注:神經網絡接收的初始數據。),對其進行某種形式的轉換。經過轉換的 input 接著進入下一層,再次進行轉換。這一過程一直持續到數據最終到達最后一層,并在這里進行最后一次轉換,從而產生最終的模型輸出。
機器學習專家們設計了各種類型的模型層,它們能夠對輸入數據執行數學轉換。同時,他們還找到了模型層的不同組織方式和分組方法,以達到預期的效果。有些模型層是通用的,適用于多種類型的輸入數據;而有些模型層則是專門為處理特定類型的數據而設計的,比如圖像數據或大語言模型中的經過分詞后的文本。
在大語言模型的文本生成場景中,目前最流行的神經網絡架構是 Transformer[10]。使用這種架構設計的 LLM(大語言模型)被稱為 GPT,即 Generative Pre-Trained Transformers[11]。
Transformer 模型的獨特之處在于它們執行的一種稱為 Attention[12] 的模型層計算方式,這使得它們能夠從上下文窗口中的 tokens 中推斷出它們之間的關系(relationships)和模式(patterns),隨后將這些關系和模式反映在下一個 token 的預測概率中。
Attention 機制最初被用于語言翻譯場景,是一種找出輸入序列中哪些 tokens 對理解其意義最為重要的方法。這種機制使現代翻譯器能夠通過關注(或集中“注意力”于)重要的單詞或tokens,從根本上“理解”一個句子。
03 Do LLMs Have Intelligence?
您可能已經開始思考,大語言模型(LLMs)在生成文本時是否展現出某種智能了?
我個人并不認為 LLMs 具備推理或創造原創思想的能力,但這并不意味著它們一無是處。由于 LLMs 對上下文窗口中的 tokens 進行了巧妙的計算,所以 LLMs 能夠識別出用戶提示詞中存在的 patterns,并將它們與訓練期間學到的類似 patterns 相匹配。 它們生成的文本大部分都是由零碎的訓練數據組成的,但它們將單詞(實際上是 tokens)拼接在一起的方式非常復雜,在很多情況下,它們生成的模型輸出結果既有原創感,又非常有用。
鑒于 LLM 容易產生幻覺,我不會信任未經人工驗證的情況下,任何直接將 LLMs 的 output 發送給用戶的工作流程。
在未來的幾個月或幾年里,是否會有更大的 LLM 出現?它們能夠實現真正的智能嗎?由于 GPT 架構的諸多限制,我覺得這是不太可能實現的,但誰知道呢,也許隨著未來創新成果的不斷涌現,我們會實現這一目標。
04 The End
感謝您與我一起堅持到最后!我希望本文已經能夠激發你的學習興趣,讓你決定繼續學習大模型相關知識,并最終面對那些令人畏懼的數學知識,如果您想詳細了解每一個細節的話,就無法回避這些數學知識。在這種情況下,我強烈推薦 Andrej Karpathy 的《Neural Networks: Zero to Hero》[13]系列視頻。
Thanks for reading!
Hope you have enjoyed and learned new things from this blog!
About the authors
Hi, my name is Miguel. I'm a software engineer, but also tinker with photography and filmmaking when I have time. I was born in Buenos Aires, Argentina, but I lived most of my adult life in Portland, Oregon, USA, the place that comes to mind when I think of home. As of 2018 I'm living in Ireland.
END
本期互動內容 ??
?通過閱讀本文,你能夠理解為什么 LLMs 會產生“幻覺”或者不可靠的輸出嗎?你認為造成這些問題的原因是什么?
??文中鏈接??
[1]??https://en.wikipedia.org/wiki/Large_language_model??
[2]??https://en.wikipedia.org/wiki/Generative_artificial_intelligence??
[3]??https://chat.openai.com/??
[4]??https://en.wikipedia.org/wiki/Byte_pair_encoding??
[5]??https://github.com/openai/gpt-2??
[6]??https://en.wikipedia.org/wiki/The_quick_brown_fox_jumps_over_the_lazy_dog??
[7]??https://en.wikipedia.org/wiki/Hallucination_(artificial_intelligence)??
[8]??https://en.wikipedia.org/wiki/Markov_chain??
[9]??https://en.wikipedia.org/wiki/Backpropagation??
[10]??https://en.wikipedia.org/wiki/Transformer_(deep_learning_architecture)??
[11]??https://en.wikipedia.org/wiki/Generative_pre-trained_transformer??
[12]??https://en.wikipedia.org/wiki/Attention_(machine_learning)??
[13]??https://karpathy.ai/zero-to-hero.html??
原文鏈接:
??https://blog.miguelgrinberg.com/post/how-llms-work-explained-without-math??
