Chuyển đến nội dung
9/30Chương 9 trên 30

Attention và khối Transformer, suy ra từ phép trung bình

Bắt đầu từ bản tóm tắt rẻ nhất của ngữ cảnh — phép trung bình — đo lỗi của nó và để công thức attention tự xuất hiện.

Trên trang này

Bạn đến đây với một tokenizer từ Chương 7, một bảng embedding từ Chương 8, và objective đi kèm với chúng: với các token đã có, gán một xác suất cho token tiếp theo.

Phần còn thiếu là phần giữa. Để dự đoán token tt, model cần một vector tóm tắt mọi thứ trước nó, và không thứ gì bạn đã xây dựng tạo ra vector đó. embedding của token t1t-1 không phải là nó — đó là model bigram, và nó không thể biết câu đã bắt đầu bằng một câu hỏi. Nối tất cả embedding trước đó cũng không phải: số lượng của chúng thay đổi ở mỗi bước, và một ma trận trọng số cố định không thể nhận đầu vào có độ dài biến thiên.

Vậy: một vector có kích thước cố định, tóm tắt một số lượng vector biến thiên. Đó là toàn bộ vấn đề, và attention là thứ bạn nhận được khi giải nó theo cách lười nhất có thể rồi sửa hai thứ bị hỏng.

Câu trả lời mà lĩnh vực này từng có, và vì sao chúng ta không xây dựng nó

Liên kết đến mục: Câu trả lời mà lĩnh vực này từng có, và vì sao chúng ta không xây dựng nó

Từ năm 1997 đến khoảng 2017, bản tóm tắt là một trạng thái hồi quy: giữ một vector h\mathbf{h} và cập nhật nó ở mỗi token, ht=f(ht1,xt)\mathbf{h}_t = f(\mathbf{h}_{t-1}, \mathbf{x}_t). Kích thước cố định, đầu vào biến thiên, đúng chính xác về hình dạng.

Nó thất bại theo ba cách, và kiến trúc của chương này trả lời cả ba. Backpropagation qua TT bước nhân TT Jacobian, nên gradient biến mất hoặc bùng nổ — căn bệnh mà Chương 5 đã đo bên trong một node tanh\tanh duy nhất. LSTM1 được thiết kế chính xác để chống lại điều đó và đã đẩy phạm vi dùng được từ vài chục bước lên vài trăm, nhưng không thay đổi sự thật rằng thông tin từ token 5 đến token 500 chỉ bằng cách sống sót qua 495 lần cập nhật tuần tự. Toàn bộ nguồn phải vừa trong một vector: trong dịch sequence-to-sequence2, một encoder nén đầu vào vào trạng thái cuối cùng của nó. Bahdanau, Cho và Bengio đã đặt tên cho nút thắt đó và sửa nó vào năm 2014, ba năm trước transformer, bằng cách cho decoder lấy tổng có trọng số của tất cả trạng thái encoder với các trọng số do chính nó tính.3 Mọi thứ bên dưới là ý tưởng đó, được một sequence áp dụng lên chính nó, với recurrence bị xóa. Và cập nhật là tuần tự theo thiết kế: ht\mathbf{h}_t cần ht1\mathbf{h}_{t-1}, và một GPU có mười nghìn lõi cũng chẳng làm được gì với điều đó. Kiến trúc chiến thắng không hiển nhiên là thông minh hơn; nó là kiến trúc có bước tốn kém là phép nhân ma trận.

Inductive bias cổ điển còn lại, convolution — trượt một bộ lọc nhỏ trên toàn bộ đầu vào, để một feature được phát hiện ở đâu cũng được phát hiện ở mọi nơi — cũng không được xây ở đây; nó gần như đúng hoàn hảo cho ảnh và được giao cho một khóa thị giác. Cả recurrence lẫn convolution đều không xuất hiện lại sau trang này, đó là lý do không thứ nào có một chương riêng: Chương 1 đã hứa rằng những gì bị bỏ qua sẽ được nói rõ thay vì im lặng.

Hàm hiển nhiên nhất nhận một số lượng vector biến thiên và trả về một vector là trung bình:

ct=1ti=1txi\mathbf{c}_t = \frac{1}{t}\sum_{i=1}^{t} \mathbf{x}_i

Bao nhiêu đầu vào cũng được, kích thước đầu ra cố định, khả vi, miễn phí. Bảng embedding cộng phép trung bình này cộng một lớp tuyến tính ra vocabulary là một language model hoàn chỉnh trong mười lăm dòng. Nó cũng rất tệ, và nó tệ như thế nào chính là toàn bộ suy luận.

Corpus bên dưới là một megabyte Shakespeare, 1.115.394 ký tự, qua một byte-level BPE tokenizer kiểu đã xây trong Chương 7 với vocabulary 1024: 459.760 token ở mức 2,43 ký tự mỗi token, chia 90/10. Mọi model rộng 128, nhìn 128 token, và train 3000 bước AdamW tại 10310^{-3} với batch 64. Perplexity được đo trên phần held-out.4

modeltham sốvalidation perplexity
chỉ token hiện tại, hoàn toàn không có ngữ cảnh263.16859,71
cộng trung bình đồng đều của mọi thứ trước nó263.168248,07
cộng learned position embeddings279.552245,93
trung bình đồng đều được cộng vào token thay vì thay thế nó263.16860,45

Đọc hàng thứ hai hai lần. Trung bình hóa ngữ cảnh không giúp thêm một chút; nó làm model tệ hơn bốn lần so với việc hoàn toàn bỏ qua ngữ cảnh. Có hai lý do, cả hai đều chứng minh được chứ không chỉ là thực nghiệm.

Phép trung bình không thấy được thứ tự. Phép cộng giao hoán, nên xáo trộn cửa sổ vẫn để bản tóm tắt không đổi — không phải xấp xỉ:

order.pyPYTHON
A = torch.tril(torch.ones(T, T))
A = A / A.sum(1, keepdim=True)          # rows of the averaging matrix
y = x[torch.randperm(T)]                # the same tokens, shuffled
print((A[-1] @ x - A[-1] @ y).abs().max().item())
TEXT
2.9802322387695312e-08

Nhiễu floating-point trên một tổng được sắp lại: hai bản tóm tắt là cùng một vector. Một model mà góc nhìn duy nhất về ngữ cảnh là phép trung bình không thể phân biệt the dog bit the man với the man bit the dog. Hàng ba chứng minh việc này không sửa được bằng cách thêm vị trí vào đầu vào — một learned position embedding trên mỗi token trước khi lấy trung bình chỉ mua được 2,14 điểm trên 188. Các vị trí đi vào tổng, và tổng quên chúng.

Và phép trung bình nhấn chìm hiện tại. Ở vị trí 100, token hiện tại chỉ là một phần trăm của bản tóm tắt. Điều đó có một cách sửa rẻ mà bạn đã có: giữ token và cộng bản tóm tắt vào nó — một residual connection, từ Chương 6, và hàng bốn cho thấy nó làm gì. Khi việc pha loãng được sửa, trung bình đồng đều chẳng đóng góp gì cả: 60,45 so với baseline 59,71. Mọi token đều ở đó, được gán trọng số bằng nhau, và trọng số bằng nhau cũng như không có thông tin.

Vấn đề không phải là phép trung bình. Nó là trọng số.

Phép trung bình là phép nhân ma trận, và mask là một softmax

Liên kết đến mục: Phép trung bình là phép nhân ma trận, và mask là một softmax

Lấy trung bình trên một prefix đang lớn dần trông giống một vòng lặp. Nó là một phép nhân với ma trận tam giác dưới có các hàng cộng lại bằng một — và cũng, chính xác, là một softmax:

mechanics.pyPYTHON
loop = torch.stack([x[:t + 1].mean(0) for t in range(T)])   # the obvious version

A = torch.tril(torch.ones(T, T))
A = A / A.sum(1, keepdim=True)
mat = A @ x                                                  # the same thing

S = torch.zeros(T, T).masked_fill(torch.tril(torch.ones(T, T)) == 0, float("-inf"))
soft = F.softmax(S, dim=-1) @ x                              # and the same thing again
TEXT
loop vs matmul   max |diff| = 5.960464477539063e-08
loop vs softmax  max |diff| = 5.960464477539063e-08

the averaging matrix A (rows sum to 1, upper triangle is zero):
  1.000 0.000 0.000 0.000 0.000 0.000
  0.500 0.500 0.000 0.000 0.000 0.000
  0.333 0.333 0.333 0.000 0.000 0.000
  0.250 0.250 0.250 0.250 0.000 0.000
  0.200 0.200 0.200 0.200 0.200 0.000
  0.167 0.167 0.167 0.167 0.167 0.167

Ba thành phần được đặt tên của một transformer giờ đã ở trên màn hình. Tam giác là causal mask, bị ép bởi objective: nếu vị trí tt có thể nhìn thấy vị trí t+1t{+}1 thì câu trả lời đã nằm trong đầu vào — rò rỉ mà Chương 6 bảo bạn audit, chỉ là lần này nằm bên trong kiến trúc. softmax là cách mask được triển khai: đặt các entry bị cấm thành -\infty đưa chúng về đúng zero và chuẩn hóa phần còn lại, nên masking và chuẩn hóa là một thao tác. (Dùng -\infty, không phải -1e9: đó là giá trị mà masking có nghĩa, nó sống sót qua cast sang float16 dưới dạng -\infty, và nó tránh cho bạn phải quyết định hằng số mình chọn có đủ lớn cho miền giá trị hiện tại hay không — chính là chiếc hộp floating-point của Chương 2 đang hỏi một câu bạn không cần trả lời.) Và scores là tham số tự do. Trung bình đồng đều là thứ bạn có khi mọi score được phép đều là cùng một số; đặt bất kỳ số nào vào đó và softmax biến chúng thành trọng số hợp lệ.

Phần còn lại của chương này là một câu hỏi: những con số đó đến từ đâu?

Chúng không thể là các tham số trần. Một ma trận T×TT \times T được học sẽ giống hệt nhau cho mọi câu — nó có thể mã hóa “nhìn lùi bốn token” nhưng không bao giờ mã hóa “nhìn vào danh từ mà đại từ này tham chiếu”. Trọng số nối vị trí tt với vị trí ii phải phụ thuộc vào thứ ở cả hai vị trí, vì relevance là một quan hệ, không phải một thuộc tính: từ it không tự thân liên quan, nó liên quan đến thứ gì đó.

Hàm rẻ nhất của hai vector trả về một số là tích vô hướng từ Chương 1. Chấm điểm vị trí ii cho vị trí ttxtxi\mathbf{x}_t \cdot \mathbf{x}_i và cơ chế hoạt động — nhưng tệ, theo hai cách buộc mọi thứ còn lại. Tích vô hướng của một vector với chính nó là norm bình phương của nó, nên mọi token sẽ attention chủ yếu đến chính nó. Và quan hệ sẽ đối xứng: nếu it attention mạnh đến animal, thì animal cũng attention mạnh đến it, điều này sai với ngôn ngữ, nơi một tính từ cần danh từ của nó hơn rất nhiều so với danh từ cần tính từ.

Vì vậy hãy cho mỗi token hai vai trò, như hai ánh xạ tuyến tính được học từ nó: vị trí này đang tìm kiếm gì, qt=Wqxt\mathbf{q}_t = W_q\mathbf{x}_t, tức query; và nó đưa ra gì để được tìm thấy bởi, ki=Wkxi\mathbf{k}_i = W_k\mathbf{x}_i, tức key. Chấm điểm qtki\mathbf{q}_t \cdot \mathbf{k}_i và tính đối xứng biến mất, vì WqWkW_q \neq W_k: một token có thể quảng cáo một thứ và tìm kiếm một thứ khác.

Vẫn còn một thứ sai. Tổng có trọng số đang lấy trên chính các xi\mathbf{x}_i, buộc thứ được copy phải là thứ được match. Matching muốn các feature nhận diện token; copying muốn các feature hữu ích ở downstream. Vì vậy học một ánh xạ thứ ba, vi=Wvxi\mathbf{v}_i = W_v\mathbf{x}_i, tức value, và lấy tổng trên những thứ đó.

Công thức giờ chỉ là bookkeeping:

Attention(Q,K,V)=softmax ⁣(QKdk+M)V\mathrm{Attention}(Q, K, V) = \mathrm{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}} + M\right)V

với MM là causal mask, zero trên và dưới đường chéo, và -\infty phía trên. Trong code, nó là ba mươi dòng, hai mươi dòng trong đó là shapes:

attention.pyPYTHON
class Head(nn.Module):
    """One head of causal self-attention."""

    def __init__(self, d_model, d_head, block):
        super().__init__()
        self.q = nn.Linear(d_model, d_head, bias=False)      
        self.k = nn.Linear(d_model, d_head, bias=False)      
        self.v = nn.Linear(d_model, d_head, bias=False)      
        self.d_head = d_head
        self.register_buffer("mask", torch.tril(torch.ones(block, block)).bool())

    def forward(self, x):
        T = x.shape[1]
        q, k, v = self.q(x), self.k(x), self.v(x)
        s = q @ k.transpose(-2, -1) / math.sqrt(self.d_head)          
        s = s.masked_fill(~self.mask[:T, :T], float("-inf"))          
        w = F.softmax(s, dim=-1)                                      
        return w @ v                                                  

Score, mask, chuẩn hóa, trộn. Mọi thứ khác là một projection.

Phép chia cho căn bậc hai, và nó chống lại điều gì

Liên kết đến mục: Phép chia cho căn bậc hai, và nó chống lại điều gì

Gần như mọi giải thích về dk\sqrt{d_k} đều nói “để giữ softmax khỏi bão hòa”, điều đó đúng và chẳng giải thích gì. Lập luận là hai dòng phương sai từ Chương 2. Nếu các entry của q\mathbf{q}k\mathbf{k} độc lập, có mean zero và variance one, mỗi tích qjkjq_j k_j có variance one, và phương sai của các thứ độc lập thì cộng lại:

Var(qk)=j=1dkVar(qjkj)=dk\mathrm{Var}(\mathbf{q}\cdot\mathbf{k}) = \sum_{j=1}^{d_k}\mathrm{Var}(q_j k_j) = d_k

Vì vậy scores có độ lệch chuẩn dk\sqrt{d_k}. Đo trên hai mươi nghìn cặp ngẫu nhiên:

TEXT
     d     Var(q.k)         std   sqrt(d)
     4        3.975       1.994     2.000
    16       16.071       4.009     4.000
    64       64.249       8.016     8.000
   256      253.065      15.908    16.000
  1024     1015.562      31.868    32.000

Vì sao điều đó quan trọng: softmax nhạy với scale theo cách một lớp tuyến tính thì không. Nhân đôi đầu vào của một lớp tuyến tính thì nhân đôi đầu ra; nhân scores với mười trước softmax biến một hỗn hợp mềm thành một lựa chọn cứng. Một hàng gồm 64 scores, có và không có phép chia:

dkd_ktrọng số lớn nhất, chưa chiaentropytoken hiệu dụngtrọng số lớn nhất, đã chiaentropytoken hiệu dụng
40,2052,94419,00,0813,75842,9
160,4381,6925,40,0753,84946,9
640,4890,8742,40,0853,67339,4
2560,99990,00071,00,1433,54734,7
10241,00000,00001,00,1323,64438,3

“Token hiệu dụng” là exponential của entropy: hàng đó thật sự đang trung bình trên bao nhiêu vị trí. Không chia, tại dk=256d_k = 256, một head vừa khởi tạo attention đúng một token trong 64, được chọn không bởi gì ngoài mẫu ngẫu nhiên.

Điều đó tệ ở forward và còn tệ hơn ở backward, theo một hình dạng Chương 5 đã đo trên một tanh\tanh. Một softmax đã cam kết vào một entry gần như không có đạo hàm: đường chéo Jacobian của nó là wi(1wi)w_i(1-w_i), bằng zero ở cả hai đầu. Trên hai nghìn hàng ngẫu nhiên:

dkd_kiwi(1wi)\sum_i w_i(1-w_i) chưa chiađã chiahàng bão hòa (trọng số lớn nhất trên 0,99)
40,84270,95680,2 % → 0,0 %
640,29400,960917,9 % → 0,0 %
2560,14060,960949,1 % → 0,0 %
10240,06810,961170,4 % → 0,0 %

Tại dk=1024d_k = 1024, bảy trong mười hàng đã bị đóng băng trước khi training bắt đầu, và một head bắt đầu ở trạng thái đóng băng không thể học nên nhìn vào đâu. Khi chia, đại lượng này phẳng ở 0,96 tại mọi width và không thứ gì bão hòa.

Giờ đến phần không ai publish: nó có thay đổi perplexity cuối cùng không? Xóa phép chia và train, ở bốn head width:

head widthchưa chiachia cho dk\sqrt{d_k}chia cho dkd_k
bốn heads, dk=32d_k = 3237,2938,0737,89
một head, dk=128d_k = 12848,5146,1045,99
một head, dk=256d_k = 25665,3747,53
một head, dk=512d_k = 51267,0649,15
một head, dk=1024d_k = 102476,6959,17

Hai hàng đầu đến từ budget 3000 bước ở trên; ba hàng cuối là một run ngắn hơn — 1500 bước, batch 32, một head, không normalization trước các projection — với cả hai biến thể dưới cùng setting.

Tại dk=32d_k = 32 phép chia chẳng đáng gì và run không có nó còn nhỉnh hơn rất nhẹ. Đó không phải giấy phép để bỏ nó, vì tại 256 nó đáng 18 điểm perplexity và tại 1024 đáng 17. Cơ chế hiện rõ trong chính scores:

dkd_kscore std khi initsau 1500 bước, chưa chiasau 1500 bước, đã chiahàng bão hòa, chưa chiađã chia
25610,49121,672,1391,9 %0,8 %
51215,13836,852,6698,7 %1,3 %
102421,155147,463,4499,9 %16,5 %

Head không chia không phục hồi. Nó chạy mất kiểm soát: độ lệch chuẩn của scores đi từ 21 khi khởi tạo lên 5147, attention entropy rơi về zero, và 99,9 % hàng đặt hơn 0,99 trọng số của chúng lên một token duy nhất. Khi một head đã là bộ chọn cứng, gradient của nó gần như zero và không có gì kéo nó lại, nên sụp đổ là ổn định. Head đã chia nằm ở độ lệch chuẩn score 3,44 sau cùng training, tức một hỗn hợp mềm vẫn có thể thay đổi.

Vaswani và cộng sự nói đúng điều này và không hơn — họ nghi rằng các tích “trở nên lớn về độ lớn với các giá trị dkd_k lớn” và chia.5 Từ lớn gánh trọng lượng, và các bảng cho biết lớn bắt đầu ở đâu: không gì tại 32, mọi thứ tại 256.

Nhiều hơn một ý kiến, và hai phần ba không ai nói tới

Liên kết đến mục: Nhiều hơn một ý kiến, và hai phần ba không ai nói tới

Một head là một hàng softmax cho mỗi vị trí, nên nó giữ một câu trả lời cho “cái gì liên quan ở đây”. Dự đoán từ sau the trong the animal that crossed the wet street cần slot cú pháp, chủ ngữ và token trước đó cùng lúc, và một phân phối xác suất không thể tập trung ở ba nơi. Vì vậy chạy nhiều head song song, mỗi head có width dmodel/hd_{\text{model}}/h, nối lại, và trộn bằng một ma trận nữa WoW_o: bạn đã chia nhỏ width, không thêm vào nó.

Attention cũng làm đúng một việc — nó di chuyển thông tin giữa các vị trí. Mọi thao tác trong code phía trên là tuyến tính dọc theo trục feature, và Chương 5 đã chứng minh một chồng các ánh xạ tuyến tính là gì. Vì vậy mỗi block cũng mang một MLP nhỏ áp dụng độc lập cho từng vị trí, mở rộng width lên bốn lần rồi quay lại, với GELU ở giữa. Sự phân công này đáng ghi nhớ: attention trộn qua các vị trí, feed-forward network tính toán bên trong một vị trí.

Cái thang đầy đủ, mỗi hàng thêm một mảnh vào hàng phía trên:

modeltham sốvalidation perplexity
trung bình đồng đều, được cộng279.55260,45
một attention head, thay thế token328.70455,47
một attention head, được cộng328.70446,10
bốn heads thay vì một345.21643,21
cộng feed-forward network476.92839,87
cộng LayerNorm — block hoàn chỉnh477.69638,07

Trọng số được học thắng trọng số đồng đều 14 điểm perplexity, chính là toàn bộ lập luận của chương này trong một hàng. Bốn heads mua thêm 3 điểm với 16.512 tham số bổ sung. Và cùng một head đáng giá thêm 9 điểm khi được cộng hơn là thay thế: attention đưa thông tin vào, nó không quyết định một vị trí là gì.

Giờ là các tham số thật sự nằm ở đâu, điều thường gây ngạc nhiên cho những người chỉ từng nhìn sơ đồ:

widthheadsattentionfeed-forwardtổng mỗi block
128465.664 (33,2 %)131.712 (66,6 %)197.888
768122.360.064 (33,3 %)4.722.432 (66,6 %)7.085.568
40963267.112.960 (33,3 %)134.238.208 (66,7 %)201.367.552

Hai phần ba của mỗi transformer block là feed-forward network, ở mọi scale, vì attention có bốn ma trận d×dd \times d và MLP có tương đương tám. Dù một model biết gì, phần lớn tham số giữ tri thức đó nằm trong MLP per-position.

Residuals và LayerNorm, thừa hưởng từ Chương 6

Liên kết đến mục: Residuals và LayerNorm, thừa hưởng từ Chương 6

LayerNorm đã được xây và đo trong Chương 6, và chương này dùng nó đúng như đã để lại ở đó; residual connections đã được đặt tên và ablate ở đó, và được xây ở đây. Các hàng “được cộng, không thay thế” phía trên là residual connections, đáng 188 điểm perplexity cho phép trung bình và 9 cho một head. LayerNorm7 chuẩn hóa từng ví dụ theo các feature của nó, và Chương 6 đã đưa ra lý do vì sao nó chứ không phải BatchNorm sống sót ở đây — không phụ thuộc batch, không running statistics, giống hệt trong training và inference, không quan tâm sequence length — mỗi điểm trong đó trở thành yêu cầu khi bạn generate từng token một cho một người dùng, chính là nơi Chương 13 đi tới. Nó tốn 768 tham số và mua được 1,8 điểm perplexity.

block.pyPYTHON
class Block(nn.Module):
    def forward(self, x):
        x = x + self.att(self.ln1(x))     
        x = x + self.ff(self.ln2(x))      
        return x

Nhìn xem normalization nằm ở đâu: trên đầu vào của mỗi sub-layer, với residual path từ đầu vào đến đầu ra không bao giờ bị normalize. Đó là pre-norm. Paper năm 2017 làm ngược lại, x = LayerNorm(x + Att(x))post-norm, đặt một LayerNorm lên chính residual path.

Xiong và cộng sự giải thích khác biệt qua gradient lúc khởi tạo, thứ trong mạng post-norm bị scale rất tệ theo độ sâu — lý do transformer gốc cần learning-rate warmup mới train được.8 Mười hai block, 1000 bước, learning rate 3×1033 \times 10^{-3}:

TEXT
gradient norm per block at initialisation, before any step
  pre-norm    block 1 0.0498 ... block 12 0.0657   ratio last/first  1.32
  post-norm   block 1 0.0977 ... block 12 0.1613   ratio last/first  1.65

  pre-norm,  no warmup          perplexity   37.82
  pre-norm,  200-step warmup    perplexity   37.62
  post-norm, no warmup          perplexity  308.05
  post-norm, 200-step warmup    perplexity   37.88

Post-norm không có warmup tệ hơn tám lần, và post-norm có warmup khớp đúng pre-norm. Warmup không phải một best practice chung ở đây; nó là bản vá cho một cách bố trí normalization cụ thể, và di chuyển LayerNorm loại bỏ nhu cầu đó. Đó là lý do hầu như mọi model từ 2019 là pre-norm, và vì sao sơ đồ 2017 nên được đọc như lịch sử chứ không phải như specification.

Xóa position embeddings và model vẫn train; nó chỉ đơn giản không thể biết thứ gì ở đâu, và đó là một đối xứng chứ không phải lỗi training. Không có gì trong attention score nhắc đến chính tt hay ii, nên hoán vị đầu vào sẽ hoán vị đầu ra: self-attention là permutation-equivariant. Đó là sự mù thứ tự của phép trung bình trong một lớp ngụy trang tốt hơn — causal mask khôi phục một phần thứ tự, vì mỗi vị trí thấy một prefix khác nhau, nhưng bên trong một prefix mọi thứ tự đều như nhau.

Bốn cách đưa vị trí vào, train trên cửa sổ 64-token và đánh giá tại 64, 128 và 256 — vượt quá mọi độ dài chúng đã thấy:

positionsperplexity tại 64tại 128tại 256
hoàn toàn không có48,7952,6357,52
learned absolute embeddings38,63108,47181,94
fixed sinusoids42,9695,26152,25
RoPE44,1250,5284,84
ALiBi44,9543,5142,49

Learned absolute embeddings — một vector cho mỗi vị trí, cộng vào token — thắng ở độ dài đã train rồi rơi khỏi vách đá, vì vị trí 100 chưa từng có trong batch và embedding của nó vẫn là vector ngẫu nhiên ban đầu. Sinusoids, lựa chọn gốc, được tính chứ không học, từ sine và cosine ở các tần số cách nhau theo cấp số nhân; paper 2017 hy vọng điều đó extrapolate, và bảng cho thấy không — hàm được định nghĩa tại vị trí 200, nhưng model chưa từng học cách đọc nó ở đó. RoPE9 không thêm gì mà thay vào đó xoay query và key theo một góc tỷ lệ với vị trí, trong các lát hai chiều; vì xoay cả hai vế của một tích vô hướng như nhau sẽ để nó không đổi, score cuối cùng chỉ phụ thuộc vào tit - i, nên vị trí trở thành relative miễn phí và không có bảng nào để dùng hết. Nó suy giảm, nhưng nó vẫn suy giảm một cách có kiểm soát. ALiBi10 là kết quả đơn giản nhất và kỳ lạ nhất ở đây: một linear penalty trên score tỷ lệ với khoảng cách, với độ dốc khác nhau cho mỗi head. Perplexity của nó cải thiện khi cửa sổ vượt quá độ dài training, từ 44,95 xuống 42,49, vì penalty được định nghĩa ở mọi khoảng cách và mỗi head tiếp tục làm thứ nó đã được train để làm.

Bài học sống lâu hơn bảng: một kiến trúc không thể biểu diễn một thứ là vấn đề khác với một kiến trúc chưa từng học miền đó, và vấn đề thứ hai mới là thứ cắn bạn. Nó cũng là bộ máy đằng sau mọi thông báo “chúng tôi đã mở rộng context lên 128K” — gần như luôn là các phép rescale của một rotary encoding, và đó là lý do Chương 16 nói giới hạn context dịch chuyển thay vì biến mất.

Dropout được thừa hưởng theo cùng cách: nó xuất hiện trên attention weights sau softmax, trên đầu ra của mỗi sub-layer trước residual addition, và trên tổng embedding, làm đúng thứ Chương 6 đã mô tả. Trong các run pretraining lớn, nó thường được đặt về zero, vì một model thấy mỗi token một lần không ở vị thế để overfit.

Hai tensor trong layer có shape n×nn \times n, trong đó nn là số token: scores và weights sau softmax. Mọi thứ khác — mọi projection, toàn bộ MLP — tuyến tính theo nn.

Một attention layer, rộng 512, 8 heads, batch một, float32, trên GPU laptop. Chỉ đọc hai cột millisecond theo tỷ lệ của chúng: đó là wall clock trên một card laptop 8 GB bị throttle từ 1.785 MHz xuống dưới 300 MHz khi nóng, nên một cold run của cùng code này trả về nhanh hơn bảy đến mười lần và một run khi máy bận còn chậm hơn. Các cột megabyte là byte count của allocator và không dịch chuyển.

TEXT
  tokens   ms total    ms x4   ms projections   attn matrix MB    peak MB    MB x4
     128      2.246        -            1.324              0.5       14.6        -
     256      2.855     1.27            2.113              2.0       19.2     1.31
     512      5.761     2.02            3.105              8.0       34.4     1.79
    1024     16.414     2.85            4.008             32.0       89.1     2.59
    2048     51.573     3.14            9.989            128.0      296.1     3.32
    4096    225.432     4.37           20.176            512.0     1100.1     3.72
    8192    832.838     3.69           40.106           2048.0     4300.1     3.91
   16384   OUT OF MEMORY                                 8192.0

fitted exponent (log-log slope, last four rows):  time ~ n^1.91   memory ~ n^1.87

Các cột x4 là tỷ lệ so với hàng phía trên, và việc nhân đôi nn hội tụ đúng về 4 cho cả thời gian và bộ nhớ — 3,91 ở bước cuối so với lý thuyết 4. Cột projections là đối chứng: 4,0 ms tại 1024 token đến 40,1 ms tại 8192, hệ số mười cho hệ số tám. Tuyến tính, đúng như quảng cáo.

Rồi đến hàng cuối. Một attention layer, một sequence, không có model xung quanh nó, hết bộ nhớ trên GPU 8 GB tại 16.384 token — riêng score matrix đã là 8 GB, vì 8 heads nhân 16.384 nhân 16.384 nhân 4 byte. Không phải model; một tensor trung gian trong một layer.

Đó là sự thật vật lý nằm dưới ba chương sau. Nó là lý do context window có giới hạn ngay từ đầu, thứ Chương 16 biến thành một mức giá. Nó là lý do FlashAttention tồn tại, tính cùng kết quả theo tiles mà không bao giờ lưu ma trận — một tối ưu bộ nhớ trước khi là tối ưu tốc độ.11 Và nó là phép tính đằng sau giá của một prompt dài, thứ Chương 24 trả trong một agent loop — một vấn đề tách biệt với phát hiện khác của chương đó, rằng model cũng sử dụng context dài tệ hơn, điều nó đo và từ chối đổ lỗi cho công thức này.

Hiện chi tiết

Hai biến thể thu nhỏ cache, được đặt tên ở đây và tính tiền ở Chương 13.

Generation cache các keys và values của các token đã xử lý — một key và một value cho mỗi token, mỗi head mỗi layer. Multi-query attention12 giữ hh query projections nhưng một key projection và value projection duy nhất dùng chung cho mọi head, chia cache đó cho hh. Grouped-query attention13 nội suy: các head được gom nhóm, mỗi nhóm dùng chung một key và value, nên g=hg = h là attention thường và g=1g = 1 là multi-query. Gần như mọi open model từ 2023 dùng nó với 4 hoặc 8 groups. Không thứ nào tồn tại vì chất lượng; cả hai tồn tại vì kích thước cache đó, và Chương 13 làm phép tính biến nó thành “model nào vừa trong GPU của bạn”.

Hai hình dạng, và kích thước của một hình

Liên kết đến mục: Hai hình dạng, và kích thước của một hình

Paper năm 2017 mô tả một encoder-decoder: một stack đọc nguồn bằng attention không mask, stack thứ hai generate đích theo causal, và một loại attention thứ ba ở giữa nơi queries của decoder gặp keys của encoder. Điều đó đúng cho dịch thuật, nơi đầu vào và đầu ra là hai sequence.

Thứ chiến thắng là nửa decoder-only — một stack, causal xuyên suốt, đầu vào và đầu ra trong cùng một sequence — và lý do không phải là thanh lịch. “Dự đoán token tiếp theo” chạy trên bất kỳ văn bản nào, nên tập training là internet thay vì parallel corpus, và mọi thứ trở thành đúng một task đó: một bản dịch là một document chứa nguồn rồi đích, một câu hỏi và câu trả lời của nó là một document, một cuộc hội thoại với tool call ở giữa là một document. Chương 11 nói về cách thứ cuối cùng đó được chế tạo. Encoders không biến mất — một encoder thấy toàn bộ đầu vào cùng lúc, điều bạn muốn khi công việc là biểu diễn một văn bản thay vì tiếp tục nó, và đó là lý do retrieval embeddings của Chương 19 đến từ encoders chứ không phải từ model đang chat.

Với block đã định nghĩa, kích thước model là số học. Mỗi block, với width dd và mở rộng bốn lần: 4d2+4d4d^2 + 4d cho Wq,Wk,Wv,WoW_q, W_k, W_v, W_o với bias trên cả bốn, như GPT-2 có — bảng phía trên bỏ bias khỏi ba trong số chúng, nên ít hơn 2.304 mỗi block tại d=768d = 768; 8d2+5d8d^2 + 5d cho MLP; 4d4d cho hai LayerNorm — 12d2+13d12d^2 + 13d, cộng một bảng token V×dV \times d và, với absolute positions, nctx×dn_{\text{ctx}} \times d. Với hình dạng của GPT-2 small — d=768d = 768, 12 block, vocabulary 50.257, context 1024, output layer dùng chung trọng số với embedding:

TEXT
  token embeddings     50,257 x 768 = 38,597,376
  position embeddings   1,024 x 768 =    786,432
  one block                             7,087,872
  12 blocks                            85,054,464
  final LayerNorm         2 x 768 =        1,536
  total (weights tied)                124,439,808

Đó chính là kích thước đã publish của model đó. Công thức không phải xấp xỉ; nó là model. Cũng lưu ý rằng gần một phần ba model nhỏ là bảng embedding, đó là lý do kích thước vocabulary là một quyết định kiến trúc chứ không phải tiền xử lý — trade-off mà Chương 7 đã dựng lên.

Perplexity là một con số về corpus. Một head làm gì là câu hỏi khác, và một model train trên một megabyte Shakespeare là công cụ sai cho việc đó: điều trung thực cần nói về attention map của một model 500.000 tham số là nó hầu như không diễn giải được. Vậy: một ngôn ngữ nơi câu hỏi có đáp án đúng.

Minh họa kinh điển là the animal did not cross the street because it was too tired, trong đó it là animal, đối lại với …because it was too wet, nơi một từ chuyển referent sang street. Đây là Winograd schemas14 — các cặp câu giống hệt nhau trừ một từ, và từ đó quyết định đại từ tham chiếu đến gì.

Chúng cũng có thể giải bằng cách gian lận, phần mà các tutorial bỏ qua. Nếu hai ứng viên là một con vật và một địa điểm, tiredwet nhận diện referent theo category, và một model chỉ biết những từ nào xuất hiện cũng trả lời đúng mà không biết gì về thứ tự. Đo trên phiên bản task đó, với các cặp animal/place held-out:

TEXT
uniform causal average           held-out referent accuracy 100.0 %
one transformer block            held-out referent accuracy  91.7 %

Bag of words thắng transformer. Bất kỳ demonstration nào xây trên câu đó đều không chứng minh gì về attention.

Vậy hãy bịt lỗ hổng: rút cả hai ứng viên từ một pool mười sáu danh từ, bất kỳ danh từ nào cũng có thể xuất hiện ở bất kỳ slot nào, và chia tính từ theo role thay vì category — bốn từ làm it là crosser (tired, scared, slow, weak), bốn từ làm nó là crossed (wet, wide, busy, steep).

TEXT
the {x} did not cross the {y} because it was too {adj} , so the {ref} waited .

Train như một next-token predictor thông thường, chấm điểm một vị trí — từ sau so the — và xây held-out set từ các cặp danh từ mà thứ tự đảo ngược đã có trong training, nên bất kỳ thứ gì biết hai danh từ nào hiện diện nhưng không biết cái nào đến trước sẽ phải trả lời ngược.

modeltham sốheld-outgọi tên danh từ kia
chỉ token hiện tại5.7965,2 %5,2 %
trung bình causal đồng đều5.79627,9 %50,0 %
một head learned attention18.08435,4 %64,6 %
bốn heads22.24475,0 %15,6 %
một transformer block55.71692,7 %4,2 %
hai transformer blocks105.508100,0 %0,0 %

Ngẫu nhiên giữa hai danh từ hiện diện là 50 %. Trung bình đồng đều đạt 27,9 % và trả lời bằng danh từ sai của cặp đúng một nửa số lần — dấu hiệu của thứ biết những từ nào ở đó và không biết gì về thứ tự của chúng, đúng như shuffle test đã dự đoán ba phần trước.

Giờ đến map: attention tại vị trí phải gọi tên referent, trung bình trên bốn heads của mỗi block, cho hai câu chỉ khác nhau một từ. Một trung bình đồng đều sẽ đặt 0,067 lên mỗi token trong mười lăm token nhìn thấy được.

TEXT
the animal did not cross the street because it was too tired , so the animal waited .
  blk 1  the:0.00 animal:0.70 did:0.00 not:0.00 cross:0.00 the:0.00 street:0.06
         because:0.00 it:0.00 was:0.00 too:0.00 tired:0.00 ,:0.05 so:0.00 the:0.19
  blk 2  the:0.00 animal:0.00 did:0.00 not:0.00 cross:0.00 the:0.00 street:0.00
         because:0.00 it:0.00 was:0.00 too:0.00 tired:1.00 ,:0.00 so:0.00 the:0.00

the animal did not cross the street because it was too wet , so the street waited .
  blk 1  the:0.00 animal:0.70 did:0.00 not:0.00 cross:0.00 the:0.00 street:0.06
         because:0.00 it:0.00 was:0.00 too:0.00   wet:0.00 ,:0.05 so:0.00 the:0.19
  blk 2  the:0.00 animal:0.00 did:0.00 not:0.00 cross:0.03 the:0.00 street:0.49
         because:0.00 it:0.00 was:0.00 too:0.20   wet:0.03 ,:0.00 so:0.00 the:0.25

Block 1 giống hệt trong cả hai câu — 0,70 lên danh từ đầu tiên, bất kể tính từ là gì. Đó không phải thất bại mà là một chứng minh: trong layer đầu tiên, query tại một vị trí là hàm của chính token và index của vị trí đó, và the ở vị trí 14 là cùng token trong cả hai câu. Một head ở layer đầu không thể condition trên một từ nó chưa fetch. Vì vậy block 1 làm thứ hữu ích duy nhất có thể và kéo danh từ đầu tiên về phía trước.

Block 2 là nơi các câu tách ra, và cùng một hàng trên toàn bộ tám tính từ cho thấy quy tắc model đã tìm ra:

tính từblock 2 trên animaltrên streettrên tính từđáp án
tired, scared, slow, weak0,0000,0001,000animal
wet, wide, busy, steep0,0000,4910,00–0,03street

Với crosser-adjective, block thứ hai dành toàn bộ trọng số cho tính từ, vì đáp án đã nằm trong residual stream — block 1 đã đặt nó ở đó — và nó chỉ cần xác nhận. Với crossed-adjective, nó đi fetch danh từ kia thay vào đó. Đó là một circuit hai bước: một head đẩy một ứng viên về phía trước, một head ở layer sau đọc một token quyết định có giữ nó hay không. Composition qua các layer là cơ chế, và đó là lý do một block đạt 92,7 % còn hai block đạt 100 %.

Nó cũng là hình dạng của circuit được ghi chép tốt nhất trong các model thật. Induction heads — một previous-token head cấp dữ liệu cho một head ở layer kế tiếp để hoàn tất pattern [A][B] … [A] → [B] — là thứ công trình interpretability của Anthropic xác định đứng sau một phần lớn in-context learning, và chúng hình thành tại một thời điểm có thể nhận diện trong pretraining. Chương này không cố phân tích điều đó: nó được giao cho tài liệu tham khảo, với cả hai paper trong phần nguồn, vì đọc circuits ra khỏi một model thật là một lĩnh vực nghiên cứu chứ không phải một mục.

Cuối cùng là phần triển khai. Ba mươi dòng phía trên, với weights được copy từ chính PyTorch:

TEXT
ours vs nn.MultiheadAttention           max |diff| = 1.7881393432617188e-07
ours vs F.scaled_dot_product_attention  max |diff| = 1.7881393432617188e-07

1.8×1071.8 \times 10^{-7} trên outputs có mean magnitude 0,159: cùng một phép tính theo thứ tự khác, ở độ chính xác float32.

Bạn đã có kiến trúc mà mọi model trong phần còn lại của khóa học được xây từ đó, và nó nhỏ hơn danh tiếng của mình: một phép trung bình có trọng số với trọng số được học, một MLP per-position giữ hai phần ba tham số, hai normalization và hai addition, được xếp chồng.

Thứ bạn chưa có là một model biết bất kỳ điều gì, và chỉ xếp chồng sẽ không tự sửa được. Hai block trên corpus này đạt training perplexity 14,49 và validation perplexity 40,57, so với 18,77 và 38,07 của một block — nhiều capacity hơn, tốt hơn trên thứ nó đã thấy, tệ hơn trên thứ nó chưa thấy, chính là bảng của Chương 6 với một transformer trong đó. Khoảng cách giữa model này và những model mà Chương 14 đến 30 nói chuyện không nằm ở kiến trúc. Nó là cùng block đó, lặp nhiều lần hơn, trên lượng văn bản lớn hơn rất nhiều.

Điều đó biến nó thành một bài toán kế toán, và việc kế toán kỳ lạ hơn vẻ ngoài. Bao nhiêu văn bản, và mọi người lấy nó ở đâu? Bao nhiêu số học, và bạn ước tính nó trước khi tiền được tiêu như thế nào? Với một ngân sách cố định, tốt hơn là làm model lớn hơn hay cho nó xem nhiều dữ liệu hơn — và có câu trả lời đúng không, hay chỉ là mốt? Chương 10 trả lời cả ba bằng đo lường, và đặt giá lên dạng hữu ích rẻ nhất của câu hỏi: hôm nay tốn bao nhiêu để train một model như GPT-2 từ con số không?


Ba cách giải thích về tài liệu này tốt hơn phần này ở đúng mục đích của chúng, và chương này được viết để đọc song song với chúng. The Illustrated Transformer của Jay Alammar là bức tranh đẹp nhất về luồng dữ liệu từng được vẽ. The Annotated Transformer của Harvard NLP là paper 2017 với code chạy được xen kẽ từng dòng. Let's build GPT: from scratch, in code, spelled out của Andrej Karpathy xây cùng model trực tiếp trong hai giờ, và cái thang ablation phía trên là cùng một xương sống được đo trên một corpus khác. Với câu hỏi interpretability mà chương này chỉ chạm tới, các nguồn chính là Elhage và cộng sự, A Mathematical Framework for Transformer Circuits (2021) và Olsson và cộng sự, In-context Learning and Induction Heads (2022), đều từ nhóm interpretability của Anthropic.

  1. Hochreiter, S. và Schmidhuber, J. Long Short-Term Memory. Neural Computation 9(8), tr. 1735–1780 (1997).

  2. Sutskever, I., Vinyals, O. và Le, Q. V. Sequence to Sequence Learning with Neural Networks. arXiv:1409.3215 (2014). Encoder-decoder có context vector duy nhất là nút thắt.

  3. Bahdanau, D., Cho, K. và Bengio, Y. Neural Machine Translation by Jointly Learning to Align and Translate. arXiv:1409.0473 (2014). Attention, ba năm trước transformer.

  4. Perplexity là exponential của mean cross-entropy trên mỗi token, từ Chương 8. Mọi con số ở đây dùng cùng tokenizer và cùng validation split, đó là điều kiện duy nhất để hai perplexity có thể được so sánh.

  5. Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A. N., Kaiser, Ł. và Polosukhin, I. Attention Is All You Need. arXiv:1706.03762 (2017). Mục 3.2.1 là một câu về dk\sqrt{d_k} mà chương này dành cả một phần để đo.

  6. Shazeer, N., Mirhoseini, A., Maziarz, K., Davis, A., Le, Q., Hinton, G. và Dean, J. Outrageously Large Neural Networks: The Sparsely-Gated Mixture-of-Experts Layer. arXiv:1701.06538 (2017).

  7. Ba, J. L., Kiros, J. R. và Hinton, G. E. Layer Normalization. arXiv:1607.06450 (2016). Được giới thiệu và đo trong Chương 6; dùng ở đây không đổi.

  8. Xiong, R., Yang, Y., He, D., Zheng, K., Zheng, S., Xing, C., Zhang, H., Lan, Y., Wang, L. và Liu, T.-Y. On Layer Normalization in the Transformer Architecture. arXiv:2002.04745 (2020). Phân tích gradient phía sau pre-norm, và lập luận rằng warmup là một triệu chứng.

  9. Su, J., Lu, Y., Pan, S., Murtadha, A., Wen, B. và Liu, Y. RoFormer: Enhanced Transformer with Rotary Position Embedding. arXiv:2104.09864 (2021).

  10. Press, O., Smith, N. A. và Lewis, M. Train Short, Test Long: Attention with Linear Biases Enables Input Length Extrapolation. arXiv:2108.12409 (2021). Kết quả extrapolation được tái hiện phía trên.

  11. Dao, T., Fu, D. Y., Ermon, S., Rudra, A. và Ré, C. FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness. arXiv:2205.14135 (2022).

  12. Shazeer, N. Fast Transformer Decoding: One Write-Head is All You Need. arXiv:1911.02150 (2019).

  13. Ainslie, J., Lee-Thorp, J., de Jong, M., Zemlyanskiy, Y., Lebrón, F. và Sanghai, S. GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints. arXiv:2305.13245 (2023).

  14. Levesque, H. J., Davis, E. và Morgenstern, L. The Winograd Schema Challenge. KR (2012). Cấu trúc phía sau câu animal / street mà mọi attention tutorial đều dùng.

Sẵn sàng để LIA chọn giúp bạn chưa?

Xây dựng cùng mọi mô hình AI ở một nơi — bắt đầu miễn phí ngay hôm nay.