在本系列文章中,我們從頭開始實作llama3。
Llama3的整體架構:
圖片
#Llama3的模型參數:
讓我們來看看這些參數在LlaMa 3模型中的實際數值。
圖片
#在實例化LlaMa類別時,變數max_seq_len定義了context -window。類別中還有其他參數,但這個參數與transformer模型的關係最為直接。這裡的max_seq_len是8K。
圖片
Transformer類別是一種定義了詞彙量和層數的模型。這裡的詞彙量是指模型能夠辨識和處理的單字(和標記)集合。 Attention layers指的是模型中使用的transformer block(attention和feed-forward layers的組合)。
圖片
根據這些數字,LlaMa 3的詞彙量為128K,這是相當大的。此外,它有32個transformer block。
[3] 特徵維度(Feature-dimension)和注意力頭(Attention-Heads)
特徵維度和attention-heads被引入到Self-Attention模組中。 Feature dimension指的是嵌入空間中tokens的向量大小(特徵維度是指輸入資料或嵌入向量的維度大小),而attention-heads包括驅動transformers中self-attention機制的QK-module。
圖片
隱藏維度是指在前饋神經網路(Feed Forward)中,隱藏層的維度大小。前饋神經網路通常包含一個或多個隱藏層,這些隱藏層的維度決定了網路的容量和複雜度。在Transformer模型中,前饋神經網路的隱藏層維度通常是特徵維度的某個倍數,以增加模型的表示能力。 LLama3中,隱藏維度是特徵維度的1.3倍。需要注意的是,隱藏層和隱藏維度是兩個概念。
更多的隱藏層數量允許網路在將它們投射回較小的輸出維度之前,內部創建和操縱更豐富的表示。
圖片
第一個矩陣是輸入特徵矩陣,透過Attention layer處理產生Attention Weighted features。在這張影像中,輸入特徵矩陣只有5 x 3的大小,但在真實的Llama 3模型中,它成長到了8K x 4096,這是巨大的。
接下來是Feed-Forward Network中的隱藏層,成長到5325,然後在最後一層回落到4096。
圖片
LlaMa 3結合了上述32個transformer block,輸出從一個block傳遞到下一個block,直到達到最後一個。
圖片
一旦我們啟動了上述所有部分,就是時候把它們整合在一起,看看它們是如何產生LlaMa效果的。
圖片
步驟1:首先我們有我們的輸入矩陣,大小為8K(context-window)x 128K(vocabulary-size)。這個矩陣經過嵌入處理,將這個高維矩陣轉換為低維。
步驟2:在這種情況下,這個低維度結果變成4096,這是我們之前看到的LlaMa模型中特徵的指定維度。
在神經網路中,升維和降維都是常見的操作,它們各自有不同的目的和效果。
升維通常是為了增加模型的容量,使其能夠捕捉更複雜的特徵和模式。當輸入資料被映射到一個更高維度的空間時,不同的特徵組合可以被模型更容易地區分。這在處理非線性問題時尤其有用,因為它可以幫助模型學習到更複雜的決策邊界 。
降維則是為了減少模型的複雜性和過度擬合的風險。透過減少特徵空間的維度,模型可以被迫學習更精煉和泛化的特徵表示。此外,降維可以作為一種正規化手段,有助於提升模型的泛化能力。在某些情況下,降維還可以減少計算成本和提高模型的運作效率 。
在實際應用中,升維後再降維的策略可以被視為一種特徵提取和變換的過程。在這個過程中,模型首先透過增加維度來探索資料的內在結構,然後透過降維來提取最有用的特徵和模式。這種方法可以幫助模型在維持足夠複雜性的同時,避免過度擬合訓練資料 。
步驟3:這個特徵透過Transformer block進行處理,首先由Attention layer處理,然後是FFN layer。 Attention layer橫向跨特徵處理,而FFN layer則縱向跨維度處理。
步驟4:步驟3為Transformer block的32層重複。最終,結果矩陣的維度與用於特徵維度的維度相同。
步驟5:最後,這個矩陣被轉換回原始的詞彙矩陣大小,即128K,以便模型可以選擇並映射詞彙中可用的單字。
這就是LlaMa 3在那些基準測試中取得高分並創造LlaMa 3效應的方式。
我們將容易搞混的幾個術語用簡短的語言總結一下:
這是模型在單次處理時能夠接受的最大token數。
在LlaMa 3-8B模型中,這個參數設定為8,000個tokens,即Context Window Size = 8K。這意味著模型在單次處理時可以考慮的最大token數量為8,000。這對於理解長文本或保持長期對話上下文非常關鍵。
這是模型能辨識的所有不同token的數量。這包括所有可能的單字、標點符號和特殊字元。模型的詞彙量是128,000,表示為Vocabulary-size = 128K。這意味著模型能夠識別和處理128,000種不同的tokens,這些tokens包括各種單字、標點符號和特殊字元。
Transformer模型中的一個主要元件。它主要負責透過學習輸入資料中哪些部分最重要(即「注意」哪些token)來處理輸入資料。一個模型可能有多個這樣的層,每層都試圖從不同的角度理解輸入資料。
LlaMa 3-8B模型包含32個處理層,即Number of Layers = 32。這些層包括多個Attention Layers及其他類型的網路層,每層都從不同角度處理和理解輸入資料。
包含多個不同層的模組,通常至少包含一個Attention Layer和一個Feed-Forward Network(前饋網路)。一個模型可以有多個transformer block,這些block順序連接,每個block的輸出都是下一個block的輸入。也可以稱呼transformer block為decoder layer。
在Transformer模型的脈絡中,通常我們說模型有“32層”,這可以等同於說模型有“32個Transformer blocks”。每個Transformer block通常包含一個自註意力層和一個前饋神經網路層,這兩個子層共同構成了一個完整的處理單元或「層」。
因此,當我們說模型有32個Transformer blocks時,實際上是在描述這個模型由32個這樣的處理單元組成,每個單元都有能力進行資料的自註意力處理和前饋網路處理。這種表述方式強調了模型的層級結構和其在每個層級上的處理能力。
總結來說,"32層"和"32個Transformer blocks"在描述Transformer模型結構時基本上是同義的,都指模型包含32次獨立的資料處理週期,每個週期都包括自註意力和前饋網路操作。
這是輸入token在模型中表示為向量時,每個向量的維度。
每個token在模型中轉換成一個含4096個特徵的向量,即Feature-dimension = 4096。這個高維度使得模型能夠捕捉更豐富的語意資訊和上下文關係。
在每個Attention Layer中,可以有多個Attention-Heads,每個head獨立地從不同的視角分析輸入資料。
每個Attention Layer包含32個獨立的Attention Heads,即Number of Attention Heads = 32。這些heads分別從不同的面向分析輸入數據,共同提供更全面的數據解析能力。
這通常指的是Feed-Forward Network中的層的寬度,即每層的神經元數量。通常,Hidden Dimensions會大於Feature-dimension,這允許模型在內部創建更豐富的資料表示。
在Feed-Forward Networks中,隱藏層的維度為5325,即Hidden Dimensions = 5325。這比特徵維度大,允許模型在內部層之間進行更深層的特徵轉換和學習。
Attention Layers 和 Attention-Heads 的關係:每個Attention Layer可以包含多個Attention-Heads。
數值關係:一個模型可能有多個transformer blocks,每個block包含一個Attention Layer和一個或多個其他層。每個Attention Layer可能有多個Attention-Heads。這樣,整個模型就在不同層和heads中進行複雜的資料處理。
下載Llama3模型的官方連結腳本:https://llama.meta.com/llama-downloads/
下面這段程式碼展示了如何使用tiktoken庫來載入和使用一個基於Byte Pair Encoding (BPE) 的分詞器。這個分詞器是為了處理文字數據,特別是在自然語言處理和機器學習模型中使用。
我們輸入hello world,看分詞器如何進行分詞。
from pathlib import Pathimport tiktokenfrom tiktoken.load import load_tiktoken_bpeimport torchimport jsonimport matplotlib.pyplot as plttokenizer_path = "Meta-Llama-3-8B/tokenizer.model"special_tokens = ["<|begin_of_text|>","<|end_of_text|>","<|reserved_special_token_0|>","<|reserved_special_token_1|>","<|reserved_special_token_2|>","<|reserved_special_token_3|>","<|start_header_id|>","<|end_header_id|>","<|reserved_special_token_4|>","<|eot_id|>",# end of turn] + [f"<|reserved_special_token_{i}|>" for i in range(5, 256 - 5)]mergeable_ranks = load_tiktoken_bpe(tokenizer_path)tokenizer = tiktoken.Encoding(name=Path(tokenizer_path).name,pat_str=r"(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+",mergeable_ranks=mergeable_ranks,special_tokens={token: len(mergeable_ranks) + i for i, token in enumerate(special_tokens)},)tokenizer.decode(tokenizer.encode("hello world!"))
圖片
查看載入的模型檔案中包含的前20個參數或權重的名稱。
model = torch.load("Meta-Llama-3-8B/consolidated.00.pth")print(json.dumps(list(model.keys())[:20], indent=4))
圖片
圖片
總的來說,這個輸出結果揭示了一個基於Transformer架構的深度學習模型的關鍵組成部分。這種模型廣泛用於自然語言處理任務,如文字分類、機器翻譯、問答系統等。每一層的結構幾乎相同,包括注意力機制、前饋網路和歸一化層,有助於模型捕捉複雜的輸入序列特徵。
查看Llama3模型的參數配置:
with open("Meta-Llama-3-8B/params.json", "r") as f:config = json.load(f)config
图片
我们使用这个配置来推断模型的细节,比如:
dim = config["dim"]n_layers = config["n_layers"]n_heads = config["n_heads"]n_kv_heads = config["n_kv_heads"]vocab_size = config["vocab_size"]multiple_of = config["multiple_of"]ffn_dim_multiplier = config["ffn_dim_multiplier"]norm_eps = config["norm_eps"]rope_theta = torch.tensor(config["rope_theta"])
图片
代码如下:
prompt = "the answer to the ultimate question of life, the universe, and everything is "tokens = [128000] + tokenizer.encode(prompt)print(tokens)tokens = torch.tensor(tokens)prompt_split_as_tokens = [tokenizer.decode([token.item()]) for token in tokens]print(prompt_split_as_tokens)
[128000, 1820, 4320, 311, 279, 17139, 3488, 315, 2324, 11, 279, 15861, 11, 323, 4395, 374, 220]['<|begin_of_text|>', 'the', ' answer', ' to', ' the', ' ultimate', ' question', ' of', ' life', ',', ' the', ' universe', ',', ' and', ' everything', ' is', ' ']
截止到目前,我们的[17x1]令牌现在变成了[17x4096],即长度为4096的17个嵌入(每个令牌一个)。
下图是为了验证我们输入的这句话,是17个token。
图片
代码如下:
embedding_layer = torch.nn.Embedding(vocab_size, dim)embedding_layer.weight.data.copy_(model["tok_embeddings.weight"])token_embeddings_unnormalized = embedding_layer(tokens).to(torch.bfloat16)token_embeddings_unnormalized.shape
图片
我们接着使用 RMS 归一化对嵌入进行归一化,也就是图中这个位置:
图片
使用公式如下:
图片
代码如下:
# def rms_norm(tensor, norm_weights):# rms = (tensor.pow(2).mean(-1, keepdim=True) + norm_eps)**0.5# return tensor * (norm_weights / rms)def rms_norm(tensor, norm_weights):return (tensor * torch.rsqrt(tensor.pow(2).mean(-1, keepdim=True) + norm_eps)) * norm_weights
这段代码定义了一个名为 rms_norm 的函数,它实现了对输入张量(tensor)的RMS(Root Mean Square,均方根)归一化处理。这个函数接受两个参数:tensor 和 norm_weights。tensor 是需要进行归一化处理的输入张量,而 norm_weights 是归一化时使用的权重。
函数的工作原理如下:
在进行归一化处理后,我们的数据形状仍然保持为 [17x4096],这与嵌入层的形状相同,只不过数据已经过归一化。
token_embeddings = rms_norm(token_embeddings_unnormalized, model["layers.0.attention_norm.weight"])token_embeddings.shape
图片
图片
接下来,我们介绍注意力机制的实现,也就是下图中的红框标注的位置:
图片
图片
计算 ( Q ) 和 ( K ) 的点积。
对点积结果进行缩放。
应用softmax函数得到注意力权重。
用注意力权重乘以值矩阵 ( V ) 得到输出矩阵 ( Z )。
这张图展示了Transformer模型中多头注意力机制的实现过程,从输入句子的嵌入开始,经过多头分割、注意力计算,最后拼接结果并生成输出。每个步骤都详细说明了如何从输入矩阵 ( X ) 生成最终的输出矩阵 ( Z )。
当我们从模型中加载查询(query)、键(key)、值(value)和输出(output)向量时,我们注意到它们的形状分别是 [4096x4096]、[1024x4096]、[1024x4096]、[4096x4096]
乍一看这很奇怪,因为理想情况下我们希望每个头的每个q、k、v和o都是单独的
print(model["layers.0.attention.wq.weight"].shape,model["layers.0.attention.wk.weight"].shape,model["layers.0.attention.wv.weight"].shape,model["layers.0.attention.wo.weight"].shape)
圖片
查詢(Query)權重矩陣 (wq.weight) 的形狀是 [4096, 4096]。鍵(Key)權重矩陣 (wk.weight) 的形狀是 [1024, 4096]。值(Value)權重矩陣 (wv.weight) 的形狀是 [1024, 4096]。輸出(Output)權重矩陣 (wo.weight) 的形狀是 [4096, 4096]。輸出結果顯示:查詢(Q)和輸出(O)權重矩陣的形狀是相同的,都是[4096, 4096]。這意味著對於查詢和輸出,輸入特徵和輸出特徵的維度都是4096。鍵(K)和值(V)權重矩陣的形狀也是相同的,都是[1024, 4096]。這顯示鍵和值的輸入特徵維度為4096,但輸出特徵維度被壓縮到了1024。這些權重矩陣的形狀反映了模型設計者如何設定注意力機制中不同部分的維度。特別是,鍵和值的維度被減小可能是為了減少計算複雜度和記憶體消耗,而保持查詢和輸出的較高維度可能是為了保留更多的資訊。這個設計選擇依賴於特定的模型架構和應用場景
讓我們用「我欣賞李鴻章」這個句子作為例子,來簡化解釋這個圖中的注意力機制的實現過程。輸入句子:首先,我們有句子「我欣賞李鴻章」。在處理這個句子之前,我們需要將句子中的每個字轉換成數學上可以處理的形式,也就是字向量。這個過程叫做詞嵌入(embedding)。
詞嵌入:每個詞,例如“我”、“欣賞”、“李鴻章”,都會被轉換成一個固定大小的向量。這些向量包含了詞的語義資訊。
分割成多個頭:為了讓模型能夠從不同的角度理解句子,我們將每個字的向量分割成多個部分,這裡有8個頭。每個頭都會關注句子的不同面向。
計算注意力:對於每個頭,我們都會計算一個叫做注意力的東西。這個過程涉及到三個步驟:以「我欣賞李鴻章」為例,如果我們想要專注於「欣賞」這個詞,那麼「欣賞」就是查詢,而其他詞例如「我」和「李鴻章」就是鍵,它們的向量就是值。
查詢(Q):這是我們想要尋找資訊的部分。鍵(K):這是包含資訊的部分。值(V):這是實際的資訊內容。拼接與輸出:計算完每個頭的注意力之後,我們將這些結果拼接起來,並透過一個權重矩陣Wo來產生最終的輸出。這個輸出將被用於下一層的處理或作為最終結果的一部分。
在圖中的註釋中提到的形狀問題,是關於如何在電腦中有效地儲存和處理這些向量的問題。在實際的程式碼實作中,為了提高效率,開發者可能會將多個頭的查詢、鍵、值向量打包在一起處理,而不是單獨處理每個頭。這樣可以利用現代計算機的平行處理能力,加快計算速度。
輸出結果顯示:
這些權重矩陣的形狀反映了模型設計者如何設定注意力機制中不同部分的維度。特別是,鍵和值的維度被減小可能是為了減少計算複雜度和記憶體消耗,而保持查詢和輸出的較高維度可能是為了保留更多的資訊。這個設計選擇依賴於特定的模型架構和應用場景
讓我們用「我欣賞李鴻章」這個句子作為例子,來簡化解釋這個圖中的注意力機制的實現過程。
查詢(Q):這是我們想要尋找資訊的部分。
鍵(K):這是包含資訊的部分。
值(V):這是實際的資訊內容。
在圖中的註釋中提到的形狀問題,是關於如何在電腦中有效地儲存和處理這些向量的問題。在實際的程式碼實作中,為了提高效率,開發者可能會將多個頭的查詢、鍵、值向量打包在一起處理,而不是單獨處理每個頭。這樣可以利用現代計算機的平行處理能力,加快計算速度。
我們繼續用句子「我欣賞李鴻章」來解釋WQ、WK、WV和WO這些權重矩陣的作用。
在Transformer模型中,每個單字都會透過單字嵌入轉換成向量。這些向量接下來會透過一系列的線性變換來計算注意力分數。這些線性變換就是透過權重矩陣WQ、WK、WV和WO來實現的。
在整個過程中,WQ、WK、WV和WO是透過訓練學習得到的,它們決定了模型如何將輸入的詞向量轉換成不同的表示,以及如何組合這些表示來得到最終的輸出。這些矩陣是Transformer模型中註意力機制的核心部分,它們使得模型能夠捕捉句子中不同單字之間的關係。
WQ(權重矩陣Q)、WK(權重矩陣K)、WV(權重矩陣V)和WO(權重矩陣O)這些矩陣是Transformer模型中的參數,它們是在模型訓練過程中透過反向傳播演算法和梯度下降等最佳化方法學習得到的。
讓我們來看看這個學習過程是如何進行的:
在本小节中,我们将从多个注意力头中展开查询向量,得到的形状是 [32x128x4096] 这里,32 是 llama3 中注意力头的数量,128 是查询向量的大小,而 4096 是令牌嵌入的大小。
q_layer0 = model["layers.0.attention.wq.weight"]head_dim = q_layer0.shape[0] // n_headsq_layer0 = q_layer0.view(n_heads, head_dim, dim)q_layer0.shape
图片
这段代码通过对模型中第一层的查询(Q)权重矩阵进行重塑(reshape),将其分解为多个注意力头的形式,从而揭示了32和128这两个维度。
之所以在这段代码中出现了32和128这两个维度,而在之前的代码段中没有,是因为这段代码通过重塑操作明确地将查询权重矩阵分解为多个注意力头,每个头具有自己的维度。32代表了模型中注意力头的数量,而128代表了分配给每个头的特征维度大小。这种分解是为了实现多头注意力机制,其中每个头可以独立地关注输入的不同部分,最终通过组合这些头的输出来提高模型的表达能力。
访问了第一层第一个头的查询(query)权重矩阵,这个查询权重矩阵的大小是 [128x4096]。
q_layer0_head0 = q_layer0[0]q_layer0_head0.shape
图片
在这里,你可以看到结果形状是 [17x128],这是因为我们有17个令牌,每个令牌都有一个长度为128的查询(每个令牌在一个头上方的查询)。
br
圖片
這段程式碼執行了一個矩陣乘法運算,將令牌嵌入(token_embeddings)與第一層第一個頭的查詢(query)權重矩陣(q_layer0_head0)的轉置(.T)相乘,以產生每個令牌的查詢向量(q_per_token)。
torch.matmul 是PyTorch中的矩陣乘法函數,它可以處理兩個張量的乘法。
token_embeddings 應該是形狀為 [17, 4096] 的張量,表示有17個令牌,每個令牌由4096維的嵌入向量表示。
q_layer0_head0 是第一層第一個頭的查詢權重矩陣,其原形為 [128, 4096]。 .T 是PyTorch中的轉置操作,將 q_layer0_head0 的形狀轉置為 [4096, 128]。
這樣,token_embeddings 和 q_layer0_head0.T 的矩陣乘法就是 [17, 4096] 和 [4096, 128] 的乘法,結果是形狀為 [17, 128] 的張量。
這行程式碼列印出 q_per_token 張量的形狀,確認其為 [ 17, 128]。
這表示對於輸入的每個令牌(共17個),我們現在都有了一個128維的查詢向量。這128維的查詢向量是透過將令牌嵌入與查詢權重矩陣相乘得到的,可以用於後續的注意力機制計算。
總之,這段程式碼透過矩陣乘法將每個令牌的嵌入向量轉換為查詢向量,為實現注意力機制的下一步做準備。每個令牌現在都有了一個與之對應的查詢向量,這些查詢向量將用於計算與其他令牌的注意力分數。
以上是手撕Llama3第1層: 從零開始實現llama3的詳細內容。更多資訊請關注PHP中文網其他相關文章!