給 ?大模型初學(xué)者? 的 LLaMA 3 核心技術(shù)剖析 原創(chuàng) 精華
編者按: 本文旨在帶領(lǐng)讀者深入了解 LLaMA 3 的核心技術(shù) —— 使用 RMSNorm 進(jìn)行預(yù)歸一化、SwiGLU 激活函數(shù)、旋轉(zhuǎn)編碼(RoPE)和字節(jié)對(duì)編碼(BPE)算法。RMSNorm 技術(shù)讓模型能夠識(shí)別文本中的重點(diǎn),SwiGLU 激活函數(shù)則如同“神筆”,讓模型生成的文本更加突出重點(diǎn)且易于理解;RoPE 賦予了模型處理序列中詞語(yǔ)位置的靈活性,而 BPE 算法則有效提升了模型處理長(zhǎng)文本的能力。
從開(kāi)發(fā)環(huán)境配置到項(xiàng)目邏輯梳理,各組件的介紹與構(gòu)建,再到模型組件的整合,本文將帶你一步步走過(guò)從文本數(shù)據(jù)分詞到創(chuàng)建嵌入、從注意力機(jī)制到多頭注意力實(shí)現(xiàn)的全過(guò)程。僅需具備一定的 Python 編程基礎(chǔ),并對(duì)神經(jīng)網(wǎng)絡(luò)和 Transformer 架構(gòu)有基本的認(rèn)識(shí),便能跟隨本文的指引,觀察 LLaMA 3 如何根據(jù)輸入生成輸出,見(jiàn)證它如何基于輸入生成連貫且有意義的文本。
本文作者提供了完整的代碼示例和詳細(xì)的指導(dǎo)文檔,無(wú)需 GPU ,僅需 17 GB RAM 即可開(kāi)始實(shí)踐。無(wú)論是希望深化理論認(rèn)識(shí)還是渴望實(shí)踐技能提升,本文都將是不可多得的優(yōu)質(zhì)內(nèi)容。
作者 | Fareed Khan
編譯 | 岳揚(yáng)
LLaMA 3[1] 是繼 Mistral[2] 之后最有前途的開(kāi)源模型之一,具備應(yīng)對(duì)多種任務(wù)的強(qiáng)大能力。早前,我曾發(fā)布過(guò)一篇文章,詳述了如何基于 LLaMA 架構(gòu),從頭構(gòu)建一個(gè)參數(shù)量超 230 萬(wàn)的大語(yǔ)言模型(LLM)。目前 LLaMA-3 已經(jīng)發(fā)布,我們將以更加簡(jiǎn)便的方法重新構(gòu)建這一模型。
在本篇博客中無(wú)需使用 GPU,但需要不少于 17 GB 的 RAM,因?yàn)槲覀儗⒓虞d數(shù)個(gè)超過(guò) 15 GB 的文件。
為便于實(shí)踐,我已在 GitHub 上創(chuàng)建了一個(gè)代碼倉(cāng)庫(kù)[3],內(nèi)含全部操作代碼及詳細(xì)指南的 notebook 文件,避免了逐行從本博客復(fù)制粘貼的繁瑣。
想了解如何從零構(gòu)建一個(gè)參數(shù)量超過(guò) 230 萬(wàn)的大語(yǔ)言模型(LLM)嗎?請(qǐng)參考這篇指南[4],帶你從零開(kāi)始踏入大模型創(chuàng)建之旅。
目錄
01 預(yù)備知識(shí)概覽
02 LLaMA 2 與 LLaMA 3 的區(qū)別
03 LLaMA 3 架構(gòu)探秘
- 使用 RMSNorm 進(jìn)行預(yù)歸一化
- SwiGLU 激活函數(shù)
- 旋轉(zhuǎn)編碼 (RoPE)
- 字節(jié)對(duì)編碼 (BPE) 算法
04 配置開(kāi)發(fā)環(huán)境
05 理清項(xiàng)目組織邏輯
06 對(duì)輸入數(shù)據(jù)進(jìn)行分詞
07 為每個(gè) token 創(chuàng)建嵌入
08 使用 RMSNorm 進(jìn)行歸一化
09 注意力頭(Query,Key,Values)
10 實(shí)現(xiàn) RoPE
11 實(shí)現(xiàn) Self Attention
12 實(shí)現(xiàn) Multi-Head Attention
13 實(shí)現(xiàn) SwiGLU 激活函數(shù)
14 整合上述模型組件
15 見(jiàn)證模型如何基于輸入生成輸出
01 預(yù)備知識(shí)概覽
本文不涉及面向?qū)ο缶幊蹋∣OP),僅需掌握簡(jiǎn)單的 Python 語(yǔ)法即可。不過(guò),要想順暢閱讀這篇博客,具備對(duì)神經(jīng)網(wǎng)絡(luò)和 Transformer 架構(gòu)的基本認(rèn)識(shí)是必不可少的前提。這兩點(diǎn)便是學(xué)習(xí)本文的全部要求。
02 LLaMA 2 與 LLaMA 3 的區(qū)別
在探討相關(guān)技術(shù)的細(xì)枝末節(jié)之前,需要了解的一點(diǎn)是,LLaMA 3的架構(gòu)設(shè)計(jì)與 LLaMA 2 相同。因此,即便你尚未深入了解 LLaMA 3 的技術(shù)細(xì)節(jié),閱讀這篇博客也沒(méi)有啥問(wèn)題。即便不了解 LLaMA 2 的架構(gòu)也別著急,我們也會(huì)對(duì)其技術(shù)細(xì)節(jié)進(jìn)行概述,盡力確保所有感興趣的讀者都能閱讀本文。
以下是有關(guān) LLaMA 2 與 LLaMA 3 的幾個(gè)核心要點(diǎn):
03 LLaMA 3 架構(gòu)探秘
在著手編寫(xiě)代碼之前,了解 LLaMA 3 架構(gòu)非常重要。為了幫助各種讀者更形象地理解這一概念,特附上一張對(duì)比圖,直觀展示原始 Transformer 架構(gòu)與 LLaMA 2/3 及 Mistral 之間的異同。
From Rajesh Kavadiki
現(xiàn)在,讓我們深入了解一下 LLaMA 3 中幾個(gè)核心要素的細(xì)節(jié):
3.1 使用 RMSNorm 進(jìn)行預(yù)歸一化
沿襲 LLaMA 2 的做法,LLaMA 3 采取了一項(xiàng)名為 RMSNorm 的技術(shù),來(lái)對(duì)每一個(gè) Transformer 子層的輸入數(shù)據(jù)進(jìn)行歸一化處理(normalizing)。
設(shè)想一下,你正在備戰(zhàn)一場(chǎng)大型考試,手邊是一本“大部頭”教科書(shū),內(nèi)容分成了多個(gè)章節(jié)。每一章都覆蓋了不同知識(shí)點(diǎn),但其中某些章節(jié)對(duì)理解整本書(shū)的主題更為重要。在通讀整本教科書(shū)之前,你決定評(píng)估一下每一章的重要性,你不想在每一章節(jié)上都花同樣多的時(shí)間,而是希望將精力更多地集中在那些核心章節(jié)上。
類(lèi)比到像 ChatGPT 這樣的大語(yǔ)言模型(LLMs),使用 RMSNorm 進(jìn)行預(yù)歸一化就如同依據(jù)內(nèi)容的重要性為各章節(jié)“加權(quán)”。對(duì)理解書(shū)籍主題至關(guān)重要的章節(jié)會(huì)獲得更高的“權(quán)重”,而次要章節(jié)則反之。
因此,在深入學(xué)習(xí)之前,我們會(huì)根據(jù)各章節(jié)的重要性調(diào)整學(xué)習(xí)計(jì)劃。在權(quán)重較高的章節(jié)上分配更多時(shí)間與精力,確保全面而深刻地掌握其核心概念。
《Root Mean Square Layer Normalization》(??https://arxiv.org/abs/1910.07467??)
與此類(lèi)似,采用 RMSNorm 技術(shù)進(jìn)行預(yù)歸一化能幫助大語(yǔ)言模型(LLMs)識(shí)別出文本中哪些部分對(duì)于理解上下文及其語(yǔ)義更為重要。它通過(guò)為關(guān)鍵要素賦予更高權(quán)重、非關(guān)鍵要素賦予較低權(quán)重的方式,引導(dǎo)模型將注意力集中于那些對(duì)上下文的準(zhǔn)確解讀最需要的地方。對(duì)此感興趣的讀者可在此處[5]深入了解 RMSNorm 的具體實(shí)現(xiàn)方式。
3.2 SwiGLU 激活函數(shù)
LLaMA 從 PaLM 模型中汲取靈感,引入了 SwiGLU 激活函數(shù)。
想象一下,假如你是一名教師,正試圖向?qū)W生解釋一個(gè)復(fù)雜的話(huà)題。有一塊大白板,你在上面寫(xiě)下了一些要點(diǎn),并繪制圖像盡量使得內(nèi)容講解更為清晰。但有時(shí),你的字跡可能不太工整,或者圖像可能畫(huà)得不夠完美。這會(huì)大大增加學(xué)生理解教材的難度。
假如你擁有一支“神筆”,它能夠根據(jù)每個(gè)要點(diǎn)的重要性自動(dòng)調(diào)整字的大小和樣式。如若某一要點(diǎn)非常重要,這支筆就會(huì)把它寫(xiě)得更大、更清晰,讓其更加顯眼。如果不那么重要,這支“神筆”就會(huì)把字寫(xiě)得小一些,但仍清晰可辨識(shí)。
SwiGLU 對(duì)于像 ChatGPT 這樣的大語(yǔ)言模型(LLMs)來(lái)說(shuō)就像那支“神筆”。在生成文本之前,SwiGLU 會(huì)根據(jù)每個(gè)單詞(word)或短語(yǔ)(phrase)與上下文的相關(guān)性(relevance)調(diào)整其重要性(importance)。就像“神筆”會(huì)調(diào)整你寫(xiě)字??時(shí)的字體大小和風(fēng)格一樣,SwiGLU 也會(huì)調(diào)整每個(gè)詞或短語(yǔ)的重要程度。
《SwiGLU: GLU Variants Improve Transformer》(??https://kikaben.com/swiglu-2020/??)
這樣,當(dāng)大語(yǔ)言模型生成文本時(shí),就可以更加突出重要部分,并確保這些內(nèi)容對(duì)文本的整體理解貢獻(xiàn)更大。這樣,SwiGLU 就能幫助大語(yǔ)言模型生成更清晰、更易于理解的文本,就像“神筆”能夠幫助你在白板上為學(xué)生創(chuàng)造更為清晰的內(nèi)容講解一樣。若想進(jìn)一步了解 SwiGLU 的相關(guān)細(xì)節(jié),請(qǐng)查閱相關(guān)論文[6]。
3.3 旋轉(zhuǎn)編碼 (RoPE)
旋轉(zhuǎn)編碼(Rotary Embeddings),簡(jiǎn)稱(chēng) RoPE ,是 LLaMA 3 中采用的一種位置編碼方式(position embedding)。
想象一下,你正在教室里組織小組討論,需要為學(xué)生們分配座位。常規(guī)做法是按行和列安排座位,每位學(xué)生都有一個(gè)固定的座位。但在某些情況下,我們可能希望設(shè)計(jì)一種更為靈活的座位布局安排,讓學(xué)生們能更自如地走動(dòng)與交流。
RoPE 就像一種特別的座位布局方案,它讓每位學(xué)生既能旋轉(zhuǎn)又能改變位置,同時(shí)還能保持與其他學(xué)生的相對(duì)位置不變。學(xué)生們不再受制于某一個(gè)固定的位置,而是能做圓周運(yùn)動(dòng)(circular motion),從而實(shí)現(xiàn)更加順暢的互動(dòng)。
在這種情況下,每一位學(xué)生都代表著文本序列里的一個(gè)詞語(yǔ)或 token ,他們的位置與他們?cè)谛蛄兄械奈恢孟鄬?duì)應(yīng)。如同 RoPE 讓學(xué)生們能旋轉(zhuǎn)與改變位置一樣,RoPE 也允許文本序列中各詞語(yǔ)的位置編碼(positional embeddings)能夠根據(jù)彼此間的相對(duì)位置進(jìn)行動(dòng)態(tài)調(diào)整。
因此,在處理文本的過(guò)程中,RoPE 并未簡(jiǎn)單地將位置編碼視作固定、靜態(tài)的(fixed and static)元素,而是巧妙地融入了旋轉(zhuǎn)(rotational)這一概念,使得表示方式更加靈活、多樣化,能夠更精準(zhǔn)地把握文本序列內(nèi)詞語(yǔ)間的變化關(guān)系。 這種靈活性賦予了 ChatGPT 等模型更強(qiáng)的能力,使其能更深刻地理解和生成自然流暢、邏輯連貫的文本內(nèi)容,就如同在教室中采用動(dòng)態(tài)座位布局(dynamic seating arrangement)能夠激發(fā)更多互動(dòng)式的討論一樣。若想深入了解其背后的數(shù)學(xué)原理,可查閱 RoPE 的相關(guān)論文[7]。
3.4 字節(jié)對(duì)編碼 (BPE) 算法
LLaMA 3 采用由 OpenAI 推出的 tiktoken 庫(kù)中的字節(jié)對(duì)編碼(Byte Pair Encoding, BPE),而 LLaMA 2 的 BPE 分詞機(jī)制基于 sentencepiece 庫(kù)。兩者雖有微妙差異,但目前的首要任務(wù)是理解 BPE 究竟是什么。
先從一個(gè)簡(jiǎn)單的例子開(kāi)始:假設(shè)有一個(gè)文本語(yǔ)料庫(kù)(text corpus),內(nèi)含 "ab", "bc", "bcd", 和 "cde" 這些詞語(yǔ)。我們將語(yǔ)料庫(kù)中所有單詞拆分為單個(gè)字符納入詞匯表,此時(shí)的詞匯表為 {"a", "b", "c", "d", "e"}。
接下來(lái),計(jì)算各字符在文本語(yǔ)料庫(kù)中的出現(xiàn)次數(shù)。在本例中,統(tǒng)計(jì)結(jié)果為 {"a": 1, "b": 3, "c": 3, "d": 2, "e": 1}。
- 隨后,進(jìn)入核心環(huán)節(jié) —— 合并階段(merging process)。重復(fù)執(zhí)行以下操作直至詞匯表達(dá)到預(yù)定規(guī)模:第一步,找出頻次最高的連續(xù)字符組合。在本例中,頻次最高的一對(duì)字符是“bc”,頻次為 2。然后,我們將這對(duì)字符合并,生成新的子詞單元(subword unit)“bc”。合并后,更新字符頻次,更新后的頻次為 {"a": 1, "b": 2, "c": 2, "d": 2, "e": 1, "bc": 2}。我們將新的子詞單元 “bc” 加入詞匯表,使之?dāng)U充至 {"a", "b", "c", "d", "e", "bc"}。
- 重復(fù)循環(huán)這一過(guò)程。下一個(gè)出現(xiàn)頻次最高的詞對(duì)是“cd”,將其合并生成新的子詞單元 “cd”,并同步更新頻次。更新后為 {"a": 1, "b": 2, "c": 1, "d": 1, "e": 1, "bc": 2, "cd": 2}。然后我們將 “cd” 加入詞匯表,得到 {"a", "b", "c", "d", "e", "bc", "cd"}。
- 延續(xù)此流程,下一個(gè)頻繁出現(xiàn)的詞對(duì)是 “de”,將其合并為子詞單元 “de”,并將頻次更新至 {"a": 1, "b": 2, "c": 1, "d": 1, "e": 0, "bc": 2, "cd": 1, "de": 1}。然后將 “de” 添加到詞匯表中,使其更新為 {"a", "b", "c", "d", "e", "bc", "cd", "de"}。
- 接下來(lái),我們發(fā)現(xiàn) “ab” 是出現(xiàn)頻次最高的詞對(duì),將其合并為子詞單元 “ab”,同步更新頻次為 {"a": 0, "b": 1, "c": 1, "d": 1, "e": 0, "bc": 2, "cd": 1, "de": 1, "ab": 1}。再將 “ab” 添加至詞匯表中,使其擴(kuò)容至 {"a", "b", "c", "d", "e", "bc", "cd", "de", "ab"}。
- 再往后,“bcd”成為了下一個(gè)出現(xiàn)頻次最高的詞對(duì),將其合并為子詞單元 “bcd”,更新頻次至 {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "bc": 1, "cd": 0, "de": 1, "ab": 1, "bcd": 1}。將 “bcd” 添入詞匯表,使其升級(jí)為 {"a", "b", "c", "d", "e", "bc", "cd", "de", "ab", "bcd"}。
- 最后,出現(xiàn)頻次最高的詞對(duì)是 “cde”,將其合并為子詞單元 “cde”,更新頻次至 {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "bc": 1, "cd": 0, "de": 0, "ab": 1, "bcd": 1, "cde": 1}。將 “cde” 添加入詞匯表,這樣詞匯表就變?yōu)榱?{"a", "b", "c", "d", "e", "bc", "cd", "de", "ab", "bcd", "cde"}。
此方法能夠顯著提升大語(yǔ)言模型(LLMs)的性能,同時(shí)能夠有效處理生僻詞及詞匯表之外的詞匯。TikToken BPE 與 sentencepiece BPE 的主要區(qū)別在于:TikToken BPE 不會(huì)盲目將已知的完整詞匯分割。 比如,若 “hugging” 已存在于詞匯表中,它會(huì)保持原樣,不會(huì)被拆解成 ["hug","ging"]。
04 環(huán)境配置
我們將使用到少量 Python 庫(kù),為了防止出現(xiàn) “no module found” 的錯(cuò)誤,建議運(yùn)行下面這行命令提前安裝好這些庫(kù)。
安裝完所需庫(kù)后,下一步是下載相關(guān)文件。因?yàn)槲覀円獜?fù)現(xiàn)的是 llama-3-8B 模型的架構(gòu),所以我們需要在 HuggingFace 平臺(tái)上注冊(cè)一個(gè)賬戶(hù)。另外,鑒于 llama-3 是一款使用受限的模型,訪問(wèn)模型內(nèi)容前需同意其使用條款。
具體步驟如下:
- 點(diǎn)擊此處[8]注冊(cè) HuggingFace 賬戶(hù)
- 點(diǎn)擊此處[9]同意 llama-3-8B 的使用條款
完成以上兩個(gè)步驟后,接下來(lái)就會(huì)下載一些必要的文件。要實(shí)現(xiàn)此目的,有兩條途徑可選:
- 手動(dòng)下載 (Option 1: Manual) :通過(guò)此鏈接[10]進(jìn)入 llama-3–8B 的 HuggingFace 目錄,逐一手動(dòng)下載以下三個(gè)文件。
LLaMA-3 配置文件下載
- 通過(guò)編程的方式下載 (options 2: Coding) :使用之前安裝的 hugging_face 庫(kù),我們可以一鍵下載所有必需的文件。不過(guò),在此之前,需要先用 HF Token 在當(dāng)前工作環(huán)境中登錄HuggingFace Hub。你可以創(chuàng)建一個(gè)新 token ,或直接通過(guò)此鏈接[11]獲取。
當(dāng)我們運(yùn)行該代碼單元時(shí),系統(tǒng)會(huì)要求我們輸入 token 。如若登錄過(guò)程中遇到問(wèn)題,請(qǐng)重試,但一定要記得取消選中 “token as git credential” 這一選項(xiàng)。隨后,我們僅需運(yùn)行一段簡(jiǎn)單的 Python 腳本代碼,便能順利下載 llama-3-8B 架構(gòu)的三個(gè)主要文件。
下載完所有必要文件后,我們需要導(dǎo)入本博客中將要使用的 Python 庫(kù)。
接下來(lái),我們需要了解所下載的每個(gè)文件的具體用途。
05 理清項(xiàng)目組織邏輯
由于我們的目標(biāo)是精確復(fù)制 llama-3 ,這意味著無(wú)論輸入的文本是什么,都應(yīng)當(dāng)獲得有實(shí)際意義的輸出。舉例來(lái)說(shuō),當(dāng)我們輸入“太陽(yáng)的顏色是什么?(the color of the sun is?)”這樣的問(wèn)題時(shí),期望的回答自然是“白色(white)”。然而,要達(dá)成這一目標(biāo),通常需要在海量數(shù)據(jù)集上訓(xùn)練大語(yǔ)言模型(LLMs),而這往往對(duì)計(jì)算資源的要求極高,因此對(duì)于我們來(lái)說(shuō)并不可行。
不過(guò),Meta 已經(jīng)公開(kāi)發(fā)布了他們的 llama-3 架構(gòu)文件(或者更確切地說(shuō),是他們預(yù)訓(xùn)練模型權(quán)重)供公眾使用。我們剛剛下載的正是這些文件,這意味著我們無(wú)需自行訓(xùn)練模型或搜集龐大的數(shù)據(jù)集,這樣就可以復(fù)制它們的架構(gòu)。一切準(zhǔn)備工作都已就緒,接下來(lái),我們只需確保在恰當(dāng)?shù)奈恢谜_地運(yùn)用這些組件。
現(xiàn)在,讓我們逐一了解這些文件及其各自的重要作用:
tokenizer.model —— 如前文所述,LLaMA-3 采用的是 tiktoken 庫(kù)中的字節(jié)對(duì)編碼(BPE)分詞技術(shù),這項(xiàng)技術(shù)是在一個(gè)包含了 15 萬(wàn)億個(gè) tokens 的超大數(shù)據(jù)集上訓(xùn)練得來(lái)的,比 LLaMA-2 使用的數(shù)據(jù)集足足大了7倍之多?,F(xiàn)在,讓我們加載這個(gè)文件,一探究竟,看看它背后藏著哪些奧秘。
??length?
?? 這一屬性代表的是詞匯表的總體規(guī)模,具體指代的是訓(xùn)練數(shù)據(jù)中的所有不同(唯一)字符的總數(shù)。而 ??tokenizer_model?
? 本身,則是一種字典類(lèi)型的數(shù)據(jù)結(jié)構(gòu)。
當(dāng)我們隨機(jī)抽取并展示其中的 10 項(xiàng)內(nèi)容,會(huì)注意到,這些內(nèi)容都是通過(guò) BPE 算法精心構(gòu)造的字符串,與我們之前探討的示例頗為相似。此處的字典鍵(key)代表著 BPE 算法訓(xùn)練過(guò)程中的字節(jié)序列(Byte sequences),而字典值(values)則反映了依據(jù)出現(xiàn)頻率確定的合并排序級(jí)別(merge ranks)。
consolidated.00.pth —— 這個(gè)文件內(nèi)藏玄機(jī),它保存了 Llama-3-8B 模型在訓(xùn)練過(guò)程中學(xué)到的所有參數(shù),即所謂的模型權(quán)重。這些參數(shù)深度揭示了模型的工作機(jī)制,比如它如何對(duì) tokens 進(jìn)行編碼、如何計(jì)算注意力權(quán)重、如何執(zhí)行前饋神經(jīng)網(wǎng)絡(luò)的轉(zhuǎn)換,以及最終如何對(duì)輸出結(jié)果進(jìn)行歸一化處理,等等。
對(duì)于熟悉 transformer 架構(gòu)的人來(lái)說(shuō),諸如查詢(xún)(query)、鍵(key)矩陣等概念一定不會(huì)陌生。稍后,我們?cè)?Llama-3 的體系結(jié)構(gòu)中借助這些模型層/權(quán)重構(gòu)建出相應(yīng)的矩陣。
params.json —— 這個(gè)文件內(nèi)容豐富,記錄了各類(lèi)參數(shù)的具體數(shù)值,包括但不限于:
這些數(shù)值將幫助我們一步步復(fù)制 Llama-3 架構(gòu),它們?cè)敿?xì)記錄了模型架構(gòu)中的關(guān)鍵參數(shù),比如注意力頭的數(shù)量、嵌入向量的維度等。
現(xiàn)在,讓我們妥善保存這些數(shù)據(jù)值,以備后續(xù)環(huán)節(jié)中調(diào)用使用。
有了分詞模型(tokenizer model)、載有關(guān)鍵權(quán)重的架構(gòu)模型(architecture model containing weights)以及詳盡的配置參數(shù)(configuration parameters)在手,萬(wàn)事俱備,只欠東風(fēng)?,F(xiàn)在,讓我們滿(mǎn)懷熱情,從最基礎(chǔ)的部分做起,動(dòng)手搭建屬于我們自己的 Llama-3 模型吧!
06 對(duì)輸入數(shù)據(jù)進(jìn)行分詞
該步驟的首要任務(wù)是將輸入的文本信息轉(zhuǎn)化為詞元形式,而這一步驟的關(guān)鍵在于,我們必須先生成一系列特殊詞元(token)。這些特殊詞元如同導(dǎo)航標(biāo),鑲嵌在分詞之后的文本中,它們賦予分詞器識(shí)別與處理特定條件或指令的能力,是整個(gè)流程中不可或缺的一環(huán)。
接下來(lái),我們可以通過(guò)設(shè)定不同的模式來(lái)識(shí)別輸入文本中各種類(lèi)型的子字符串,以此來(lái)制定文本分割規(guī)則。讓我們來(lái)看看具體操作方法。
此工具能夠從輸入文本中提取單詞(words)、縮寫(xiě)(contractions)、數(shù)字(numbers)(最多三位數(shù)) ,以及由非空白字符組成的字符序列,我們可以根據(jù)自身需求對(duì)其進(jìn)行個(gè)性化設(shè)置。
我們需利用 TikToken 的 BPE 算法來(lái)編寫(xiě)一個(gè)簡(jiǎn)易的分詞函數(shù),該函數(shù)接收三項(xiàng)參數(shù):t0okenizer_model 、 tokenize_breaker 和 special_tokens 。該函數(shù)會(huì)按照需求對(duì)輸入文本進(jìn)行相應(yīng)的編碼或解碼處理。
為了驗(yàn)證該編碼函數(shù)是否能夠正常工作,我們先以“Hello World”作為測(cè)試文本傳入該函數(shù)處理。首先,該函數(shù)將文本編碼,將其轉(zhuǎn)化為一系列數(shù)字。隨后,再將這些數(shù)字解碼回原始文本,最終得到 “hello world!” —— 這一過(guò)程證明了函數(shù)功能正常?,F(xiàn)在,讓我們開(kāi)始對(duì)輸入內(nèi)容進(jìn)行分詞處理。
我們從一個(gè)特殊詞元(譯者注:此處應(yīng)當(dāng)為??<|begin_of_text|>?
?)開(kāi)始,對(duì)輸入文本 “the answer to the ultimate question of life, the universe, and everything is ” 進(jìn)行編碼處理。
07 為每個(gè) token 創(chuàng)建嵌入
如果我們檢查輸入向量的長(zhǎng)度,其長(zhǎng)度應(yīng)為:
目前,輸入向量的維度為 (17x1) ,下一步需要將每個(gè)經(jīng)過(guò)分詞后的單詞轉(zhuǎn)換為其對(duì)應(yīng)的嵌入表征。這樣一來(lái),原本的 (17x1) token 將擴(kuò)展為 (17x4096) 維度的嵌入矩陣,即每個(gè) token 都將擁有一個(gè)長(zhǎng)度為 4096 的嵌入向量。
有一點(diǎn)需要注意,這些嵌入向量并未經(jīng)過(guò)歸一化處理,若不對(duì)其進(jìn)行歸一化處理,可能會(huì)產(chǎn)生嚴(yán)重負(fù)面影響。在下一節(jié),我們將著手對(duì)輸入向量進(jìn)行歸一化操作。
08 使用 RMSNorm 進(jìn)行歸一化
為了確保輸入向量完成歸一化,我們將采用前文的 RMSNorm 公式來(lái)進(jìn)行處理。
《Root Mean Square Layer Normalization》 (??https://arxiv.org/abs/1910.07467??)
我們將使用來(lái)自 layers_0 的注意力權(quán)重,對(duì)尚未歸一化的嵌入向量進(jìn)行歸一化處理。選擇 layer_0 的原因在于,我們正著手構(gòu)建 LLaMA-3 transformer 架構(gòu)的第一層。
由于我們僅對(duì)向量進(jìn)行歸一化處理,并不涉及其他操作,所以向量的維度并不會(huì)發(fā)生變化。
09 注意力頭(Query,Key,Values)
首先,我們從模型中加載 query、key、value 和 output 向量。
從向量的維度上可以看出,我們下載的模型權(quán)重并非為單獨(dú)的注意力頭設(shè)計(jì),因?yàn)椴捎昧瞬⑿刑幚砘虿⑿杏?xùn)練的方式,可以同時(shí)服務(wù)于多個(gè)注意力頭。不過(guò),我們能夠分解這些矩陣,讓它們只適用于單個(gè)注意力頭。
在此,??32?
?? 代表 LLama-3 中注意力頭的數(shù)量,??128?
?? 是查詢(xún)向量的維度大小,??4096?
? 則是 token 嵌入的維度大小。
我們可以通過(guò)以下方式,獲取到第一層中首個(gè)注意力頭的 query 權(quán)重矩陣:
要計(jì)算每個(gè) token 對(duì)應(yīng)的 query 向量,我們需要將該 token 的嵌入向量與 query 權(quán)重進(jìn)行相乘運(yùn)算。
由于 query 向量本身無(wú)法識(shí)別自己在提示詞文本中的具體位置,因此我們將借助 RoPE 技術(shù),讓這些向量能夠感知其所在位置。
10 實(shí)現(xiàn) RoPE
我們將 query 向量分成兩個(gè)一組,接著對(duì)每一組實(shí)施旋轉(zhuǎn)角度(rotational angle)的調(diào)整,以此來(lái)區(qū)分它們。
此處要處理的是一個(gè)大小為 [17x64x2] 的向量,其實(shí)質(zhì)是將每個(gè)提示詞內(nèi)的 128 個(gè)長(zhǎng)度單位(128-length) 的 query 信息,劃分成 64 對(duì)。每一個(gè) query 對(duì)都將依據(jù) m*θ 角度進(jìn)行旋轉(zhuǎn),這里的 m 即為 token 在序列中的位置。
為了實(shí)現(xiàn)向量的旋轉(zhuǎn)操作,我們會(huì)采用復(fù)數(shù)點(diǎn)積(the dot product of complex numbers)的計(jì)算方法。
完成分割過(guò)程后,接下來(lái)我們將對(duì)分割后的數(shù)據(jù)進(jìn)行頻率計(jì)算。
現(xiàn)在,我們已經(jīng)為每個(gè) token 的 query 部分賦予了對(duì)應(yīng)的復(fù)數(shù)值。接下來(lái),我們就可以把這些 query 轉(zhuǎn)換為復(fù)數(shù),再依據(jù)它們各自在序列中的位置,運(yùn)用點(diǎn)積運(yùn)算實(shí)現(xiàn)旋轉(zhuǎn)處理。
得到旋轉(zhuǎn)后的向量后,我們可以通過(guò)將之前的復(fù)數(shù)重新解釋為實(shí)數(shù),從而恢復(fù)到最初以配對(duì)形式表示的 query 向量。
現(xiàn)在,旋轉(zhuǎn)后的數(shù)據(jù)將進(jìn)行合并處理,然后得到一個(gè)全新的 query 向量(rotated query vector),其 shape 為 [17x128] 。此處的數(shù)字 17 代表 token 總數(shù),而 128 則是 query 向量的維度大小。
處理 key 向量的方式與 query 向量的處理方式相似,不過(guò)要記得,key 向量也是 128 維的。由于 key 向量的權(quán)重在 4 個(gè)注意力頭(head)間共享,以盡量減少運(yùn)算量,因此其權(quán)重?cái)?shù)量?jī)H是 query 向量的四分之一。就像 query 向量一樣,key 向量也會(huì)通過(guò)旋轉(zhuǎn)(rotated)來(lái)融入位置信息(positional information),以此增強(qiáng)模型對(duì)序列位置的理解。
現(xiàn)在,我們已經(jīng)獲得了每個(gè) token 對(duì)應(yīng)的旋轉(zhuǎn)查詢(xún)向量與鍵向量(rotated queries and keys),其大小均為 [17x128] 。
11 實(shí)現(xiàn) Self Attention
通過(guò)將 query 矩陣與 key 矩陣相乘,我們會(huì)得到一組 score(相似性分?jǐn)?shù)),這些 score 對(duì)應(yīng)著每個(gè) token 與其他 token 之間的關(guān)聯(lián)度。具體來(lái)說(shuō),這些 score 表示每個(gè) token 的 query 向量與其 key 向量之間的相互關(guān)系。
[17x17] 這個(gè) Shape 表示的是注意力分?jǐn)?shù)(qk_per_token),其中數(shù)字 17 指的是提示詞文本中包含的 token 數(shù)量。
我們有必要對(duì) query-key scores (譯者注:此處應(yīng)當(dāng)指在計(jì)算注意力權(quán)重時(shí),Query 矩陣和 Key 矩陣之間的匹配程度或相關(guān)性得分。)進(jìn)行屏蔽處理。在模型訓(xùn)練階段,為了確保模型僅利用歷史信息進(jìn)行預(yù)測(cè),我們會(huì)屏蔽掉未來(lái) token 的 query-key scores。這一策略導(dǎo)致我們?cè)谶M(jìn)行推理時(shí),會(huì)將所有未來(lái) token 的 query-key scores 值都設(shè)定為零。
現(xiàn)在,我們需對(duì)每個(gè) token 的 query 向量和 key 向量實(shí)施遮掩操作。接著,我們打算在此之上應(yīng)用 softmax 函數(shù),將得到的分?jǐn)?shù)轉(zhuǎn)化為概率值。這樣做有利于從模型的詞匯表(vocabulary)中挑選出可能性最高的 tokens 或 token 序列,進(jìn)而讓模型的預(yù)測(cè)結(jié)果更易于理解,也更適用于語(yǔ)言生成、分類(lèi)等應(yīng)用場(chǎng)景。
在 value 矩陣這里,自注意力機(jī)制告一段落。為了節(jié)約計(jì)算資源,類(lèi)似地,value 矩陣權(quán)重在每四個(gè)注意力頭中被共享。最終,value 權(quán)重矩陣呈現(xiàn)出的 shape 為 [8x128x4096]。
與 query 矩陣和 key 矩陣類(lèi)似,我們可以通過(guò)特定方法得到第一層及首個(gè)注意力頭的 value 矩陣。
通過(guò)值矩陣權(quán)重,我們計(jì)算出每一個(gè) token 的注意力值,最終得到一個(gè) shape 為 [17x128] 的矩陣。這里,17 指的是提示詞文本中的 token 總數(shù),128 則是單個(gè) token 的值向量維度。
要獲取最終的注意力矩陣,我們只需進(jìn)行如下所示的乘法操作:
我們現(xiàn)在已經(jīng)獲得了第一層和第一個(gè)注意力頭的注意力值,這實(shí)際上就是所謂的自注意力值(self attention)。
12 實(shí)現(xiàn) Multi-Head Attention
接下來(lái)將通過(guò)一個(gè)循環(huán)過(guò)程,對(duì)第一層中的所有注意力頭重復(fù)進(jìn)行上述的計(jì)算步驟。
現(xiàn)在,第一層的所有 32 個(gè)注意力頭中的 QKV 注意力矩陣都已被計(jì)算出來(lái),接下來(lái),這些注意力分?jǐn)?shù)將被整合進(jìn)一個(gè)大小為 [17x4096] 的大矩陣中。
在 layer 0 attention (譯者注:可能是整個(gè) Transformer 模型理解和處理序列這一過(guò)程的第一步)中,收尾步驟是使用權(quán)重矩陣(weight matrix)去乘以堆疊起來(lái)的 QKV 矩陣。
我們現(xiàn)在已經(jīng)得到了應(yīng)用注意力機(jī)制處理之后的嵌入值(embedding values),這些變化應(yīng)當(dāng)被添加至原先的詞元嵌入(token embeddings)上。
接下來(lái),我們會(huì)對(duì)嵌入值的變化進(jìn)行歸一化處理,然后將其送入前饋神經(jīng)網(wǎng)絡(luò)(feedforward neural network)中進(jìn)一步加工。
13 實(shí)現(xiàn) SwiGLU 激活函數(shù)
由于我們已經(jīng)對(duì)前文介紹的 SwiGLU 激活函數(shù)有所了解,現(xiàn)在我們將把之前探討的那個(gè)公式運(yùn)用到這里。
SwiGLU: GLU Variants Improve Transformer (??https://kikaben.com/swiglu-2020/??)
14 整合上述模型組件
現(xiàn)在一切準(zhǔn)備就緒,我們需要合并代碼,從而構(gòu)建另外 31 個(gè)模型層。
15 見(jiàn)證模型如何基于文本輸入生成輸出
現(xiàn)在,我們已經(jīng)得到了最終的嵌入表征,這是模型預(yù)測(cè)下一個(gè)詞元(token)的依據(jù)。這一嵌入的結(jié)構(gòu)與普通的詞元嵌入(token embeddings)一致,為 [17x4096] ,意味著由 17 個(gè) token 構(gòu)成,每個(gè) token 的嵌入向量維度為 4096 。
接下來(lái),我們可以把得到的嵌入表征轉(zhuǎn)換回具體的 token 值,完成從抽象表征到文本內(nèi)容的解碼過(guò)程。
在預(yù)測(cè)后續(xù)內(nèi)容時(shí),我們會(huì)使用上一個(gè) token 的嵌入表征作為依據(jù),以此推理出最可能的下一個(gè) token 值。
為了將一串 token IDs 轉(zhuǎn)換成可讀的文本,我們需要進(jìn)行生成文本的解碼過(guò)程,將 token IDs 對(duì)應(yīng)到具體的字符或詞匯上。
所以,我們輸入的是“the answer to the ultimate question of life, the universe, and everything is(“生命、宇宙以及萬(wàn)物的終極問(wèn)題的答案是”)”,而得到的模型輸出正是“42”,這正是正確答案。
各位讀者可以嘗試各種不同的提示詞文本,進(jìn)行各種各樣的實(shí)驗(yàn)。在整個(gè)程序中,只需修改這兩行代碼即可,其他部分可保持不變!
Thanks for reading! Hope you have enjoyed and learned new things from this blog!
Fareed Khan
MSc Data Science, I write on AI
??https://www.linkedin.com/in/fareed-khan-dev/??
END
參考資料
[1]??https://llama.meta.com/llama3/??
[3]??https://github.com/FareedKhan-dev/Building-llama3-from-scratch??
[5]??https://github.com/bzhangGo/rmsnorm/blob/master/rmsnorm_torch.py??
[6]??https://arxiv.org/pdf/2002.05202v1.pdf??
[7]??https://arxiv.org/pdf/2104.09864v4.pdf??
[8]??https://huggingface.co/join?next=%2Fmeta-llama%2FMeta-Llama-3-8B??
[9]??https://huggingface.co/meta-llama/Meta-Llama-3-8B??
[10]??https://huggingface.co/meta-llama/Meta-Llama-3-8B/tree/main/original??
[11]??https://huggingface.co/settings/tokens??
原文鏈接:
??https://levelup.gitconnected.com/building-llama-3-from-scratch-with-python-e0cf4dbbc306??
