理解和編碼LLMs的自注意力機制

Mondo 教育 更新 2024-03-04

在本文中,我們將介紹 Transformer 架構中使用的自注意力機制以及 GPT-4 和 LLAMA 模型等大型語言模型。 自我注意力和相關機制是 LLM 的核心組成部分,在使用 LLM 時了解這些機制非常重要。

本文還提供了使用 Python 和 PyTorch 從頭開始編寫自注意力機制的詳細指南,並演示了它的工作原理,幫助初學者和有經驗的從業者更深入地了解它在 LLM 中的作用。

本文的作者是 Sebastian Raschka,他是一名機器學習和人工智慧研究員,目前擔任 Lightning AI 的首席 AI 教育研究員,正在撰寫《從頭開始構建大型語言模型》一書。 (以下內容由OneFlow編譯發布,**請聯絡授權。 源語言:

** ahead of aiOneFlow 編譯

譯者 楊婷, 萬子霖

自我關注機制簡介

自從最初的Transformer“Attention is All You Need”發表以來,自注意力已經成為許多SOTA深度學習模型的基石,尤其是在自然語言處理(NLP)領域。 目前,自注意力機制被廣泛使用,因此了解它們的工作原理很重要。

原始 Transformer 架構,來自:

深度學習中的“注意力”概念源於改進遞迴神經網路 (RNN),使它們能夠處理更長的序列或句子,例如,將句子從一種語言翻譯成另一種語言。 我們通常不會選擇逐字翻譯,因為它忽略了每種語言特有的複雜語法結構和習語,導致翻譯不準確或毫無意義。

為了克服上述問題,引入了一種注意力機制,使模型能夠在每個時間步訪問所有序列元素。 注意力機制的關鍵是有選擇地確定哪些詞在給定的上下文中最重要。 2017 年,Transformer 架構引入了獨立的自注意力機制,徹底消除了對迴圈神經網路 (RNN) 等傳統結構的依賴。

為了簡潔起見,我將簡要討論背景動機部分,將文章重點放在自注意力機制的技術細節上,以便可以專注於實現。 )

注意力就是你所需要的一切“,顯示單詞”在多大程度上依賴於輸入序列中的注意力權重或關注其他單詞(顏色濃度與注意力權重值成正比“)。

我們可以將自我注意力視為一種機制,它通過包含有關輸入上下文的資訊來增強輸入的資訊內容。 換句話說,自注意力機制使模型能夠衡量輸入序列中不同元素的重要性,並動態調整它們對輸出的影響。 對於語言處理任務,自注意力機制的重要性尤為突出。 因為在自然語言中,單詞的含義會根據句子或文件的上下文而變化。

請注意,自我注意力機制有很多變化,但人們主要關心的是提高自我注意力機制的效率。 然而,大多數公司仍然使用Attention is All You Need引入的原始的標點產品注意力機制,因為自我注意力很少是大多數公司在訓練大型轉換器時的計算瓶頸。

因此,本文的重點放在原始的標度點積注意力機制(稱為自注意力機制)上,它仍然是當今最流行和廣泛使用的注意力機制。 但是,如果你對其他型別的注意力機制感興趣,可以參考一些研究綜述和最新的**,比如《2020年高效變形金剛:一項調查》、《2023年關於轉換器高效訓練的調查》,以及最近的《FlashAttention》和《FlashAttention-V2**

嵌入輸入語句

在介紹自注意力機制之前,先考慮乙個輸入句子:"life is short, eat dessert first"。我們想通過自我關注機制來處理它。 與其他處理文字的建模方法(例如遞迴或卷積神經網路)類似,首先我們需要建立乙個句子嵌入。

為簡單起見,這裡我們的字典 dc 僅限於輸入句子中出現的單詞。 在實踐中,我們通常會考慮訓練資料集中的所有單詞(詞彙量通常在 30,000 到 50,000 之間)。

輸入:

sentence = 'life is short, eat dessert first'dc = print(dc)
輸出:

接下來,我們使用字典為每個單詞分配乙個整數索引:

輸入:

import torchsentence_int = torch.tensor( [dc[s] for s in sentence.replace(',', '').split()]print(sentence_int)
輸出:

tensor([0, 4, 5, 2, 1, 3])
現在,使用輸入句子的整數向量表示,我們可以使用嵌入層將輸入編碼為實向量嵌入。 在這裡,我們將使用乙個微小的 3D 嵌入,以便每個輸入詞都表示為 3D 向量。

需要注意的是,嵌入尺寸通常在數百到數千個維度之間,例如 Llama 2 使用的嵌入尺寸為 4096。 這裡使用 3D 嵌入純粹是為了說明目的,這樣我們就可以檢查單個向量,而不必用數字填充整個頁面。

該句子由 6 個單詞組成,將產生 6 個 3 維嵌入:

輸入:

vocab_size = 50_000torch.manual_seed(123)embed = torch.nn.embedding(vocab_size, 3)embedded_sentence = embed(sentence_int).detach()print(embedded_sentence)print(embedded_sentence.shape)
輸出:

tensor([[0.3374, -0.1778, -0.3035], 0.1794, 1.8951, 0.4954], 0.2692, -0.0770, -1.0205], 0.2196, -0.3792, 0.7671], 0.5880, 0.3486, 0.6603], 1.1925, 0.6984, -1.4097]])torch.size([6, 3])
定義權重矩陣

接下來,我們將討論一種廣泛使用的自注意力機制,即縮放點積注意力機制,它是 Transformer 架構的重要組成部分。

自注意力機制利用三個權重矩陣(wq、wk 和 wv),在訓練過程中作為模型引數進行調整。 這些矩陣分別用於將輸入投影到序列的查詢、鍵和值元件中。

相應的查詢、鍵和值序列是通過權重矩陣 w 和嵌入輸入 x 之間的矩陣乘法獲得的:

查詢序列:q(i) = x(i)wq,其中 q(i) 表示查詢序列的第 i 個元素。

鍵序列:k(i) = x(i)wk,其中 k(i) 表示鍵序列的第 i 個元素。

值序列:v(i) = x(i)wv,其中 v(i) 表示值序列的第 i 個元素。

索引 i 是指輸入序列中的標記索引位置,長度為 t。

這裡,q(i) 和 k(i) 都是 dk 維向量。 投影矩陣 WQ 和 WK 的維數為 D DK,而 WV 的維數為 D DV。

需要注意的是,d 表示每個單詞向量 x 的大小。 )

由於我們正在計算查詢和鍵向量之間的點積,因此這兩個向量必須包含相同數量的元素 (dq = dk)。 在許多大型語言模型中,我們通常使用相同大小的值向量來確保 dq = dk = dv。 但是,值向量 v(i) 中確定上下文向量大小的元素數量可以是任意的。

所以,在下乙個演示中,我們選擇設定 dq=dk=2,將 dv 設定為 4,初始化投影矩陣如下:

輸入:

torch.manual_seed(123)d = embedded_sentence.shape[1]d_q, d_k, d_v = 2, 2, 4w_query = torch.nn.parameter(torch.rand(d, d_q))w_key = torch.nn.parameter(torch.rand(d, d_k))w_value = torch.nn.parameter(torch.rand(d, d_v))
(與前面的詞嵌入向量類似,維度 dq、dk、dv 通常要大得多,但是。

為了更直觀地說明,我們在這裡使用了較小的數字。 )

計算非歸一化注意力權重

現在,讓我們計算第二個輸入元素的注意力向量,其中第二個輸入元素充當查詢:

在**中,它看起來像這樣:

輸入:

x_2 = embedded_sentence[1]query_2 = x_2 @ w_querykey_2 = x_2 @ w_keyvalue_2 = x_2 @ w_valueprint(query_2.shape)print(key_2.shape)print(value_2.shape)
輸出:

torch.size([2])torch.size([2])torch.size([4])
然後,我們可以推廣此過程以計算所有輸入的剩餘關鍵和值元素,因為我們在計算非歸一化注意力權重的下一步中將需要它們:

輸入:

keys = embedded_sentence @ w_keyvalues = embedded_sentence @ w_valueprint("keys.shape:", keys.shape)print("values.shape:", values.shape)
輸出:

keys.shape: torch.size([6, 2])values.shape: torch.size([6, 4])
現在我們已經計算了所有必要的鍵和值,我們可以繼續下一步並計算非規範化注意力權重 (omega),如下圖所示

如上圖所示,我們將 i,j 計算為查詢序列和鍵序列之間的點積,即 i,j=q(i)k(j)。

例如,我們可以計算查詢和第 5 個輸入元素(對應於索引位置 4)的非規範化注意力權重,如下所示:

輸入:

omega_24 = query_2.dot(keys[4])print(omega_24)
(請注意,它是希臘字母“omega”,因此上面的**變數也使用相同的名稱。 )

輸出:

tensor(1.2903)
由於我們稍後將需要這些非規範化的注意力權重來計算實際的注意力權重,因此我們需要計算所有輸入標記的值,如上所示

輸入:

omega_2 = query_2 @ keys.tprint(omega_2)
輸出:

tensor([-0.6004, 3.4707, -1.5023, 0.4991, 1.2903, -1.3374])
計算注意力權重

自注意力機制的下一步是通過應用softmax函式來獲得標準化的注意力權重(alpha),從而對未歸一化的注意力權重進行歸一化。 此外,在通過 softmax 函式歸一化之前,您需要使用 1 進行縮放,如下所示:

通過 DK 進行縮放可確保權重向量大致相同歐幾里得長度。 這有助於防止注意力權重過小或過大,這可能導致數值不穩定,甚至影響模型在訓練期間收斂的能力。

在**中,我們可以按如下方式計算注意力權重:

輸入:

import torch.nn.functional as fattention_weights_2 = f.softmax(omega_2 / d_k**0.5, dim=0)print(attention_weights_2)
輸出:

tensor([0.0386, 0.6870, 0.0204, 0.0840, 0.1470, 0.0229])
自注意力機制的最後一步是計算上下文向量 z(2),它是原始查詢輸入 x(2) 的注意力加權版本,所有其他輸入元素都作為其上下文的注意力權重:

注意力權重特定於輸入元素。 在這裡,我們選擇輸入元素 x(2)。

在**中,它看起來像這樣:

輸入:

context_vector_2 = attention_weights_2 @ valuesprint(context_vector_2.shape)print(context_vector_2)
輸出:

torch.size([4])tensor([0.5313, 1.3607, 0.7891, 1.3110])
請注意,此輸出向量 (dv=4) 的維度高於原始輸入向量 (d=3) 的維度,因為我們之前指定了 dv>d; 但是,此處指定的嵌入大小 DV 是任意的。

自注意力機制

為了總結前面幾節中自我注意力機制的實現,我們可以用乙個簡潔的自我注意力類來總結之前的自我注意力:

輸入:

import torch.nn as nnclass selfattention(nn.module): def __init__(self, d_in, d_out_kq, d_out_v): super().init__(self.d_out_kq = d_out_kq self.w_query = nn.parameter(torch.rand(d_in, d_out_kq)) self.w_key = nn.parameter(torch.rand(d_in, d_out_kq)) self.w_value = nn.parameter(torch.rand(d_in, d_out_v)) def forward(self, x): keys = x @ self.w_key queries = x @ self.w_query values = x @ self.w_value attn_scores = queries @ keys.t # unnormalized attention weights attn_weights = torch.softmax( attn_scores / self.d_out_kq**0.5, dim=-1 ) context_vec = attn_weights @ values return context_vec
按照 PyTorch 的慣例,上面的 selfattention 類在 init 方法中初始化自注意力引數,並通過前向傳播計算所有輸入的注意力權重和上下文向量。 下面是如何使用此類的示例:

輸入:

torch.manual_seed(123)# reduce d_out_v from 4 to 1, because we h**e 4 headsd_in, d_out_kq, d_out_v = 3, 2, 4sa = selfattention(d_in, d_out_kq, d_out_v)print(sa(embedded_sentence))
輸出:

tensor([[0.1564, 0.1028, -0.0763, -0.0764], 0.5313, 1.3607, 0.7891, 1.3110], 0.3542, -0.1234, -0.2627, -0.3706], 0.0071, 0.3345, 0.0969, 0.1998], 0.1008, 0.4780, 0.2021, 0.3674], 0.5296, -0.2799, -0.4107, -0.6006]],grad_fn=)
檢視第二行,您可以看到它與上一節中上下文向量 2 中的值完全匹配:tensor([0.]5313, 1.3607, 0.7891, 1.3110])。

多頭注意力機制

在本文開頭的頂部圖中(為了在下面重複),您可以看到 Transformer 使用了乙個名為"多頭關注"模組。

原變壓器架構中的多頭注意力模組來了:

這個“長”注意力模組與我們上面描述的自我注意力機制(縮放點積注意力)有什麼關係?

在縮放點積注意中,輸入序列通過表示查詢、鍵和值的三個矩陣進行轉換。 在多頭注意力的上下文中,這三個矩陣可以被認為是乙個注意力頭。 下圖總結了我們之前討論和實現的這個單一注意力頭:

顧名思義,多頭注意力由多個這樣的頭組成,每個頭頭由查詢、鍵和值的矩陣組成。 這個概念類似於在卷積神經網路中使用多個卷積核,從而產生具有多個輸出通道的特徵圖(map)。

為了在 ** 中說明這一點,我們可以為前面的 selfattention 類編寫乙個 multiheadattentionwrapper 類:

class multiheadattentionwrapper(nn.module): def __init__(self, d_in, d_out_kq, d_out_v, num_heads): super().init__(self.heads = nn.modulelist( [selfattention(d_in, d_out_kq, d_out_v) for _ in range(num_heads)] def forward(self, x): return torch.cat([head(x) for head in self.heads], dim=-1)
在 selfattention 類中,d* 引數和以前一樣,唯一新增的輸入引數是注意力頭的數量:

d in:輸入特徵向量維度。

d out kq:查詢和金鑰輸出維度。

d out v:值輸出維度。

num heads:注意力頭的數量。

我們使用這些輸入引數初始化 selfAttention 類 num heads times 並在 pytorch 中使用 nnmodulelist 來儲存多個自注意力例項。

前向傳播需要每個自聚焦的頭部(儲存在 selfheads) 獨立應用於輸入 x,然後每個頭的結果沿最後乙個維度 (dim=-1) 連線起來。讓我們通過以下示例來了解這一點:

首先,假設我們有乙個單獨的以自我為中心的頭部,為了簡單起見,將輸出維度設定為 1:

輸入:

torch.manual_seed(123)d_in, d_out_kq, d_out_v = 3, 2, 1sa = selfattention(d_in, d_out_kq, d_out_v)print(sa(embedded_sentence))
輸出:

tensor([[0.0185], 0.4003], 0.1103], 0.0668], 0.1180], 0.1827]],grad_fn=)
現在,將其擴充套件到 4 個注意頭:

輸入:

torch.manual_seed(123)block_size = embedded_sentence.shape[1]mha = multiheadattentionwrapper( d_in, d_out_kq, d_out_v, num_heads=4)context_vecs = mha(embedded_sentence)print(context_vecs)print("context_vecs.shape:", context_vecs.shape)
輸出:

tensor([[0.0185, 0.0170, 0.1999, -0.0860], 0.4003, 1.7137, 1.3981, 1.0497], 0.1103, -0.1609, 0.0079, -0.2416], 0.0668, 0.3534, 0.2322, 0.1008], 0.1180, 0.6949, 0.3157, 0.2807], 0.1827, -0.2060, -0.2393, -0.3167]],grad_fn=)context_vecs.shape: torch.size([6, 4])
根據上面的輸出,您可以看到之前建立的單個自聚焦頭現在代表了上面輸出張量的第一列。

需要注意的是,多頭注意力的結果是乙個 6 個 4 維張量:我們有 6 個輸入標記和 4 個自聚焦頭,其中每個自聚焦頭返回乙個一維輸出。 在上面的“自注意力機制”部分,我們還生成了乙個 6 4 維張量,因為我們將輸出維度設定為 4 而不是 1。 在實踐中,如果我們可以調整自注意力類中的輸出嵌入大小,為什麼我們需要多個注意力頭? 增加單個自注意力頭的輸出維度與使用多個注意力頭之間的區別在於模型如何處理資料並從中學習。 雖然這兩種方法都提高了模型表示資料不同特徵或方面的能力,但它們之間存在根本差異。

例如,多頭注意力中的每個注意力頭都可以學習關注輸入序列的不同部分,從而捕獲資料中的方面或關係。 這種表示的多樣性對於看漲關注的成功至關重要。

多頭注意力可以更有效率,尤其是在平行計算方面。 每個磁頭都可以獨立處理,這使其成為當今硬體加速器(如 GPU 或 TPU)的理想選擇,這些加速器擅長並行處理。

簡而言之,多注意力頭的使用不僅增加了模型的容量,還增強了其學習資料中各種特徵和關係的能力。 例如,7B Llama 2 型號使用 32 個注意力頭。

交叉注意力機制

在上面的演示中,我們設定了 dq = d k = 2 和 d v = 4。 換言之,我們對查詢和鍵序列使用相同的維度。 雖然值矩陣 w v 通常選擇與查詢和鍵矩陣相同的維度(例如在 Pytorch 的 MultiHeadAttention 類中),但我們可以選擇任何大小的值維度。

由於維度有時難以跟蹤,因此下圖總結了我們到目前為止所介紹的所有內容,其中描述了單個注意力頭的各種張量大小。

先前實現的自注意力機制的另一張圖側重於矩陣維度。

上圖對應於變壓器中使用的自注意力機制。 我們還沒有討論交叉注意力,這是注意力機制的乙個特殊變體。

什麼是交叉注意力,它與自我注意力有何不同?

在自注意力機制中,我們處理相同的輸入序列,而在交叉注意力中,我們混合或組合兩個不同的輸入序列。 在上圖的原始 Transformer 架構中,左邊是編碼器模組返回的序列,右邊是解碼器部分正在處理的輸入序列。

需要注意的是,在交叉注意力中,兩個輸入序列 x 1 和 x 2 可能具有不同數量的元素,但它們的嵌入維度必須匹配。

下圖說明了什麼是交叉注意力。 如果我們設定 x 1 = x 2,則相當於自注。

注意:查詢通常來自解碼器,而鍵和值通常來自編碼器。 )

如何在**中實現這一點? 我們將採用並修改我們之前在“自我注意力機制”部分實現的自我注意力類,僅進行一些小的修改:

輸入:

class crossattention(nn.module): def __init__(self, d_in, d_out_kq, d_out_v): super().init__(self.d_out_kq = d_out_kq self.w_query = nn.parameter(torch.rand(d_in, d_out_kq)) self.w_key = nn.parameter(torch.rand(d_in, d_out_kq)) self.w_value = nn.parameter(torch.rand(d_in, d_out_v)) def forward(self, x_1, x_2): # x_2 is new queries_1 = x_1 @ self.w_query keys_2 = x_2 @ self.w_key # new values_2 = x_2 @ self.w_value # new attn_scores = queries_1 @ keys_2.t # new attn_weights = torch.softmax( attn_scores / self.d_out_kq**0.5, dim=-1) context_vec = attn_weights @ values_2 return context_vec
交叉注意力類與之前的自我注意力類的區別如下:

前向傳播接受兩個不同的輸入,x 1 和 x 2。 查詢來自 x 1,鍵和值來自 x 2。 這意味著注意力機制正在評估兩個不同輸入之間的相互作用。

注意力分數是通過計算查詢的點積(從 x 1 開始)和鍵(從 x 2 開始)來計算的。

與自我注意類似,每個上下文向量都是值的加權總和。 但是,在交叉注意力中,這些值來自第二個輸入 (x2),權重基於 x1 和 x2 之間的互動。

具體例子如下:

輸入:

torch.manual_seed(123)d_in, d_out_kq, d_out_v = 3, 2, 4crossattn = crossattention(d_in, d_out_kq, d_out_v)first_input = embedded_sentencesecond_input = torch.rand(8, d_in)print("first input shape:", first_input.shape)print("second input shape:", second_input.shape)
輸出:

first input shape: torch.size([6, 3])second input shape: torch.size([8, 3])
注意:在計算交叉注意力時,第乙個和第二個輸入標記(行)的數量不必相同:

輸入:

context_vectors = crossattn(first_input, second_input)print(context_vectors)print("output shape:", context_vectors.shape)
輸出:

tensor([[0.4231, 0.8665, 0.6503, 1.0042], 0.4874, 0.9718, 0.7359, 1.1353], 0.4054, 0.8359, 0.6258, 0.9667], 0.4357, 0.8886, 0.6678, 1.0311], 0.4429, 0.9006, 0.6775, 1.0460], 0.3860, 0.8021, 0.5985, 0.9250]],grad_fn=)output shape: torch.size([6, 4])
有很多關於語言轉換器的討論。 在最初的 Transformer 架構中,當我們從輸入句子轉換為輸出句子時,交叉注意力在語言翻譯的上下文中非常有用。 輸入句子表示乙個輸入序列,翻譯表示第二個輸入序列(兩個句子中的單詞數可以不同)。

另乙個使用交叉注意力的流行模型是 Stable Diffusion。 Stable Diffusion 在 U-NET 模型中使用交叉注意力,其中生成的影象與用於條件控制的文字提示之間存在互動,其原始(具有潛在擴散模型的高解像度影象合成)詳細介紹了穩定擴散模型,後來被 Stability AI 識別為啟用了現在流行的穩定擴散模型。

因果自注意機制

在本節中,我們將前面討論的自注意力機制調整為因果自注意力機制,特別是用於生成文字的類似 GPT(解碼器式)的 LLM。 這種因果的自我注意力機制通常也被稱為“蒙面的自我注意力”。 在原來的 Transformer 架構中,它對應於“Masked Multi-Head Attention”模組。 為簡單起見,本節僅討論單個注意力頭,但相同的概念也適用於多個注意力頭。

原 Transformer 架構中的因果自注意力模組(源自“注意力就是你所需要的一切”**

因果自注意力機制確保序列中某個位置的輸出僅基於前乙個位置的已知輸出,而不是基於未來位置。 簡而言之,它確保每個下乙個單詞的 ** 僅取決於前乙個單詞。 為了在類似 GPT 的 LLM 中實現這一點,對於每個處理過的令牌,我們遮蔽了現在在輸入文字中當前令牌之後的後續令牌。

下圖演示了如何將因果掩碼應用於注意力權重,以隱藏輸入文字中的未來標記。

我們將使用上一節中的未加權注意力分數和注意力權重來說明和實現因果自注意。 首先,讓我們快速看一下上一節的“自我注意力機制”部分中注意力分數的計算:

輸入:

torch.manual_seed(123)d_in, d_out_kq, d_out_v = 3, 2, 4w_query = nn.parameter(torch.rand(d_in, d_out_kq))w_key = nn.parameter(torch.rand(d_in, d_out_kq))w_value = nn.parameter(torch.rand(d_in, d_out_v))x = embedded_sentencekeys = x @ w_keyqueries = x @ w_queryvalues = x @ w_value# attn_scores are the "omegas", # the unnormalized attention weightsattn_scores = queries @ keys.t print(attn_scores)print(attn_scores.shape)
輸出:

tensor([[0.0613, -0.3491, 0.1443, -0.0437, -0.1303, 0.1076], 0.6004, 3.4707, -1.5023, 0.4991, 1.2903, -1.3374], 0.2432, -1.3934, 0.5869, -0.1851, -0.5191, 0.4730], 0.0794, 0.4487, -0.1807, 0.0518, 0.1677, -0.1197], 0.1510, 0.8626, -0.3597, 0.1112, 0.3216, -0.2787], 0.4344, -2.5037, 1.0740, -0.3509, -0.9315, 0.9265]],grad_fn=)torch.size([6, 6])
與前面的自注意力部分類似,上面的輸出是乙個 6 6 張量,其中包含 6 個輸入標記的這些相應的非歸一化注意力權重(也稱為注意力分數)。

之前,我們通過 softmax 函式計算了縮放的點積注意力,如下所示:

輸入:

attn_weights = torch.softmax(attn_scores / d_out_kq**0.5, dim=1)print(attn_weights)
輸出:

tensor([[0.1772, 0.1326, 0.1879, 0.1645, 0.1547, 0.1831], 0.0386, 0.6870, 0.0204, 0.0840, 0.1470, 0.0229], 0.1965, 0.0618, 0.2506, 0.1452, 0.1146, 0.2312], 0.1505, 0.2187, 0.1401, 0.1651, 0.1793, 0.1463], 0.1347, 0.2758, 0.1162, 0.1621, 0.1881, 0.1231], 0.1973, 0.0247, 0.3102, 0.1132, 0.0751, 0.2794]],grad_fn=)
上面的 6 6 輸出表示注意力權重,我們在前面的自我注意力部分也計算過。

現在,在類似 GPT 的 LLM 中,我們訓練模型從左到右讀取和生成標記(或單詞)。 如果我們有乙個訓練文字樣本,例如:"life is short eat dessert first",使用以下設定,箭頭右側單詞的上下文向量應僅包含其自身及其前面的單詞:

life" → "is"

life is" → "short"

life is short" → "eat"

life is short eat" → "desert"

life is short eat desert" → "first"

如下圖所示,在處理所有未來令牌的對角線上方的注意力權重矩陣上應用掩碼是實現上述設定的最簡單方法。 這樣,在建立上下文向量時,將不包括“未來”一詞,這些向量是根據輸入進行加權和建立的。

上圖表示應遮罩的對角線上方的注意力權重。

在 ** 中,我們可以使用 pytorch 的 -tril- 函式來做到這一點,該函式首先用於建立由 1 和 0 組成的掩碼:

輸入:

block_size = attn_scores.shape[0]mask_**= torch.tril(torch.ones(block_size, block_size))print(mask_**
輸出:

tensor([[1., 0., 0., 0., 0., 0.],1., 1., 0., 0., 0., 0.],1., 1., 1., 0., 0., 0.],1., 1., 1., 1., 0., 0.],1., 1., 1., 1., 1., 0.],1., 1., 1., 1., 1., 1.]]
接下來,我們將注意力權重乘以這個掩碼,將對角線以上的所有注意力權重歸零:

輸入:

masked_**= attn_weights*mask_**print(masked_**
輸出:

tensor([[0.1772, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], 0.0386, 0.6870, 0.0000, 0.0000, 0.0000, 0.0000], 0.1965, 0.0618, 0.2506, 0.0000, 0.0000, 0.0000], 0.1505, 0.2187, 0.1401, 0.1651, 0.0000, 0.0000], 0.1347, 0.2758, 0.1162, 0.1621, 0.1881, 0.0000], 0.1973, 0.0247, 0.3102, 0.1132, 0.0751, 0.2794]],grad_fn=)
雖然上述方法是遮蔽未來標記的一種方式,但請注意,每行中注意力權重的總和不再是 1。 為了解決這個問題,我們可以對行進行歸一化,使它們再次加起來為 1,這是注意力加權的標準約定:

輸入:

row_sums = masked_**sum(dim=1, keepdim=true)masked_**norm = masked_**/ row_sumsprint(masked_**norm)
輸出:

tensor([[1.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], 0.0532, 0.9468, 0.0000, 0.0000, 0.0000, 0.0000], 0.3862, 0.1214, 0.4924, 0.0000, 0.0000, 0.0000], 0.2232, 0.3242, 0.2078, 0.2449, 0.0000, 0.0000], 0.1536, 0.3145, 0.1325, 0.1849, 0.2145, 0.0000], 0.1973, 0.0247, 0.3102, 0.1132, 0.0751, 0.2794]],grad_fn=)
如您所見,現在每行的注意力權重加起來為 1。

在神經網路中,像 transformer 模型一樣對注意力權重進行歸一化有兩個主要優點:首先,歸一化注意力權重的總和 1 類似於概率分布,這有助於按比例解釋模型對輸入不同部分的注意力; 其次,將注意力權重的總和限制在1,有助於控制權重和梯度的規模,提高訓練的動態性。

更高效的掩模處理,無需重新歸一化

在上面寫的因果自注意力過程中,我們首先計算注意力分數,然後計算注意力權重,掩碼處理對角線以上的注意力權重,最後對注意力權重進行重新歸一化。 這些步驟總結如下:

以前實施的因果自注意過程。

或者,有一種更有效的方法可以達到相同的結果。 在這種方法中,我們得到注意力分數,並將對角線上方的值替換為負無窮大,然後再將這些值輸入softmax函式來計算注意力權重。 該過程總結如下:

實現因果自我關注是另一種更有效的選擇。

我們可以用以下方式在 Pytorch 中編寫這個過程,首先掩蓋上面的注意力分數:

輸入:

mask = torch.triu(torch.ones(block_size, block_size), diagonal=1)masked = attn_scores.masked_fill(mask.bool(),torch.inf)print(masked)
上面的**首先建立乙個蒙版,該蒙版在對角線下方為0,在對角線上方為1。 在這裡,火炬triu 保留矩陣的主對角線及其上方的元素,將其下方的元素歸零,從而保留上三角形。 相反,火炬tril(下三角形)保留了主對角線及其下方的元素。

然後,使用蒙版填充將角線上方的所有元素替換為 -torchINF(負無窮大),結果如下。

輸出:

tensor([[0.0613, -inf, -inf, -inf, -inf, -inf], 0.6004, 3.4707, -inf, -inf, -inf, -inf], 0.2432, -1.3934, 0.5869, -inf, -inf, -inf], 0.0794, 0.4487, -0.1807, 0.0518, -inf, -inf], 0.1510, 0.8626, -0.3597, 0.1112, 0.3216, -inf], 0.4344, -2.5037, 1.0740, -0.3509, -0.9315, 0.9265]],grad_fn=)
接下來,我們只需要像往常一樣應用 softmax 函式,然後我們得到歸一化和遮蔽的注意力權重。

輸入:

attn_weights = torch.softmax(masked / d_out_kq**0.5, dim=1)print(attn_weights)
輸出:

tensor([[1.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], 0.0532, 0.9468, 0.0000, 0.0000, 0.0000, 0.0000], 0.3862, 0.1214, 0.4924, 0.0000, 0.0000, 0.0000], 0.2232, 0.3242, 0.2078, 0.2449, 0.0000, 0.0000], 0.1536, 0.3145, 0.1325, 0.1849, 0.2145, 0.0000], 0.1973, 0.0247, 0.3102, 0.1132, 0.0751, 0.2794]],grad_fn=)
為什麼這樣做? 在最後一步應用 softmax 函式時,它會將輸入值轉換為概率分布。 當輸入中存在 -inf 時,softmax 有效地將其視為零概率。 由於 e (-inf) 趨於接近 0,因此這些位置對輸出概率沒有影響。

結論

本文提供了對自我注意力機制內部工作原理的逐步編纂。 基於此,我們深入研究了多頭注意力機制,它是大型語言模型轉換器的基本組成部分。

接下來,我們還對交叉注意力進行編碼,這是專門針對兩個不同序列的自注意力機制的變體。 最後,我們對因果自注意力進行編碼,這是在解碼器式語言大型模型(如 GPT 和 LLAMA)中生成連貫且上下文適當的序列的關鍵概念。

通過從頭開始編寫這些複雜的機制,我們希望幫助讀者更好地理解轉換器和語言模型中使用的自注意力機制及其內部工作原理。

請注意,本文中提供的**僅用於說明目的。 如果計畫實現自注意力來訓練大型語言模型,建議考慮使用閃爍注意力等優化實現,以減少記憶體占用和計算負載。 )

【語言大模型推理最高可加速度11倍】SiliconLLM是由SiliconLLM開發的一款高效、易用、可擴充套件的LLM推理加速引擎,旨在為使用者提供開箱即用的推理加速能力,大幅降低大模型部署成本,加速生成式AI產品的實現。 (如需技術合作交流,請新增微信:siliconflow01)。

siliconllm 的吞吐量最多提高 4 倍,延遲降低 4 倍。

資料中心+PCIe:SiliconLLM吞吐量提公升高達5倍; 消費卡場景:siliconllm吞吐量最多提公升3倍。

系統提示場景:siliconllm的吞吐量提高多達11倍。 MOE模型:推理 siliconllm 的吞吐量最多提高 10 倍。

歡迎來到明星,試試看onediff:

github.com/siliconflow/onediff

相關問題答案

    自媒體爆文的誕生,懂得創作和推薦機制

    在自我 的世界裡,每個創作者都渴望自己的作品成為 熱銷文章 無論是每天瀏覽新聞時看到的令人驚豔的閱讀文章,還是對自己內心的追求,都揭示了突發文章的重要性。.什麼是突發文章?首先,我們需要弄清楚什麼是突發性文章。一般情況下,如果一部作品的瀏覽量能達到萬以上,就可以稱為爆款文章。然而,許多創作者努力創作...

    智慧型和壓縮 LLM的智慧型水平和資料壓縮能力

    親愛的讀者,今天我們要 大型語言模型 LLMs 的智慧型水平與其資料壓縮能力之間的關係。您可能想知道為什麼模型的壓縮程度越高,它就越智慧型這背後有乙個深刻的邏輯。首先,讓我們從資料壓縮的角度來了解智慧型。智慧型可以被視為一種能力,使我們能夠以更有效的方式處理和理解資訊。在資料壓縮的背景下,智慧型系統...

    編碼器如何連線到電機

    編碼器和電機是現代工業中常用的兩種機器和裝置,它們可以以不同的方式連線在一起工作。編碼器是一種測量裝置,用於測量電機的旋轉位置或速度。而電機是一種將電能轉換為機械能的裝置。在實踐中,編碼器和電機通常直接或間接連線在一起。.直接連線到創意靈感中心 直接連線方法是最簡單的連線型別。編碼器的軸直接與電機的...

    體貼 理解和支援的力量

    在生活的方方面面,我們都可能面臨各種挑戰和壓力。無論是工作 家庭還是個人問題,它都會讓我們感到疲倦和痛苦。這個時候,我們往往需要別人的理解和支援,幫助我們度過難關。對於男性來說,面對壓力和挑戰,他們可能會感到無助和孤獨。他們可能想要乙個了解他們處境 關心他們的感受並給予他們支援和鼓勵的人。當男人要求...

    建築立面的理解和應用

    在建築工程中,標高是乙個非常重要的概念,它是衡量建築物高度的指標。無論是在設計 施工還是驗收階段,都需要精確測量和控制建築物的標高。然而,對於很多人來說,建築標高的概念可能還比較陌生。那麼,建築標高到底是什麼意思呢?應該如何理解和應用它?本文將為您詳細解答這些問題。首先,我們需要清楚什麼是建築層。簡...