Featured image of post Điều này nhằm 1 - game bài đổi thưởng tặng quà khởi nghiệp

Điều này nhằm 1 - game bài đổi thưởng tặng quà khởi nghiệp

Hãy tham gia ngay để nhận những phần thưởng hấp dẫn từ trò chơi bài đổi thưởng tặng quà khởi nghiệp.

Chaofa Yuan
Ngày 27 tháng 1 năm 2025
Khoảng 7 phút đọc
Chủ đề: hands-on-code, llms-zero-to-hero, transformer, LLM

Nội dung trang này:

    1. Bối cảnh
    1. Con đường nâng cấp
      1. ReLU
      1. GELU
      1. SwiGLU (SwishGLU)
      • 3.1 Hàm kích hoạt Swish
      • 3.2 Đơn vị cửa GLU (Gated Linear Unit)
      • 3.3 Biểu thức của SwiGLU
    1. Thực hiện mã nguồn FFN với swishGLU

Từ khi chatGPT ra đời vào cuối năm 22, các mô hình lớn (Large Language Model - LLM) thường sử dụng dạng Mô hình Ngôn ngữ Causual, thuộc phần Decoder của Transformers. Trong khối Decoder, có một tầng FFN (FeedForward), thường được coi là nơi lưu trữ kiến thức thông qua các tham số. FFN tiêu chuẩn thường trải qua quá trình tăng và giảm chiều, bao gồm hai ma trận trọng số, biểu diễn bằng công thức sau:

(1) FFN(x) = ReLU(xW₁ + b₁)W₂ + b₂

Trong đó x có kích thước (b,s,h), W₁ có kích thước (h,4h), W₂ có kích thước (4h,h). W₁ là ma trận tăng chiều (up), W₂ là ma trận giảm chiều (down).

Hàm kích hoạt được sử dụng để giúp mạng nơ-ron học được mối quan hệ phi tuyến phức tạp giữa đầu vào và đầu ra. Trong công thức (1), ReLU là một hàm kích hoạt phổ biến trong phiên bản gốc của Transformers, nhưng nó có thể được thay thế bằng các hàm khác như Gaussian Error Linear Unit (GELU) mà BERT đã sử dụng rộng rãi. Kể từ khi các mô hình lớn trở nên phổ biến và đặc biệt là sau khi PaLM được công bố, mọi người bắt đầu sử dụng swishGLU làm hàm kích hoạt chính và đây cũng là một điểm tối ưu hóa quan trọng.

Một đoạn mã dưới đây sẽ giúp bạn hiểu rõ hơn cách FFN hoạt động:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class FeedForward(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(config.n_embd, 4 * config.n_embd),
            # Hàm kích hoạt
            nn.ReLU(),
            # Có thể thay bằng nn.GELU()
            # Nếu dùng SwishGLU thì cách thực hiện sẽ khác, phần tiếp theo sẽ giải thích chi tiết về SwishGLU
            nn.Linear(4 * config.n_embd, config.n_embd),
            nn.Dropout(config.dropout)
        )
    
    def forward(self, x):
        return self.net(x)

2. Con đường nâng cấp

1. ReLU

ReLU là hàm kích hoạt được sử dụng rộng rãi nhất kể từ khi deep learning xuất hiện. Công thức rất đơn giản:

(2) ReLU(x) = max(0,x)

2. GELU

Từ GPT và BERT, GELU dường như đã trở thành lựa chọn thay thế cho ReLU trong thời đại mới. Công thức cụ thể như sau:

(3) GELU(x) = xP(X ≤ x) = xΦ(x)

Trong đó Φ(x) là hàm phân phối tích lũy của phân phối chuẩn, định nghĩa như sau:

(4) Φ(x) = ½[1 + erf(x/√2)]

Với erf là hàm lỗi (error function):

(5) erf(x) = (2/√π) ∫₀ˣ e^(-t²) dt

Tuy nhiên, do chi phí tính toán cao, nên có hai hàm sơ cấp được sử dụng để xấp xỉ (tuy nhiên, tính đến ngày 27 tháng 1 năm 2025, nhiều framework đã có khả năng tính chính xác hàm erf).

Chi tiết về các phương pháp xấp xỉ này có thể tham khảo bài viết của Su Shen về “Hai hàm sơ cấp xấp xỉ GELU”.

3. SwiGLU (SwishGLU)

SwiGLU (hoặc swishGLU, có thể sử dụng lẫn lộn) là sự kết hợp giữa hàm kích hoạt swish và đơn vị cửa GLU (Gated Linear Units). Do đó, cần phải giới thiệu riêng từng thành phần.

Điều đáng chú ý là từ mô hình T5 trở đi, nhiều mô hình (như PaLM) không còn sử dụng bias trong tầng FFN nữa. Điều này khiến công thức FFN trở thành:

(6) FFN(x) = ActiveFunction(xW₁)W₂

Lưu ý rằng công thức (6) khác với công thức (1) ở chỗ không có bias. Tuy nhiên, điều này phụ thuộc vào cách thực hiện của từng mô hình cụ thể.

3.1 Swish - Hàm kích hoạt

Swish là một hàm phi tuyến, công thức như sau:

(7) Swish = xσ(βx)

Trong đó β là một siêu tham số. Khi β=1, Swish trở thành SiLU (Sigmoid Linear Unit), và hầu hết các framework (như PyTorch, TensorFlow) đều sử dụng phiên bản cố định β=1 (nn.SiLU()).

Nếu sử dụng swish làm hàm kích hoạt, công thức FFN sẽ trở thành:

(8) FFN(W₁,W₂,x) = Swish(xW₁)W₂

Có hai ma trận có thể học, W₁ (kích thước h×4h) là ma trận tăng chiều, W₂ (kích thước 4h×h) là ma trận giảm chiều.

3.2 GLU - Đơn vị cửa

GLU (Gated Linear Units) là một cấu trúc cửa có tham số, sử dụng sigmoid để kiểm soát việc kích hoạt ở các chiều khác nhau. Công thức như sau:

(9) GLU(W,x,V,b,c) = (Wx+b) ⊗ σ(Vx+c)

Ở đây, những ai quen thuộc với LSTM hay GRU sẽ dễ dàng hiểu ngay. Lưu ý rằng bc (bias) không bắt buộc phải có.

So sánh công thức (9) với công thức (7), ta thấy W₁ tương ứng với W trong công thức (7), và Wgate tương ứng với V.

3.3 Biểu thức của SwiGLU

SwiGLU thay thế hàm cửa bằng swish, loại bỏ phần bias và thay thế một tầng Linear trong FFN bằng tầng GLU. Kết quả là có ba ma trận tham số có thể học: W₁, W₂, và W₃.

Công thức cuối cùng như sau:

(10) FFN(W₁,W₂,W₃,x) = W₂ ⋅ (W₁x ⊗ Swish(W₃x))

Hay viết theo cách khác:

(11) FFN(wup,wdown,wgate) = wdown ⋅ (wupx ⊗ Swish(wgatex))

Qua công thức này, chúng ta có thể hiểu rõ cách FFN hoạt động khi sử dụng swiGLU.

Trong phiên bản cơ bản của FFN (xem công thức 1), chỉ có wup và wdown với kích thước (h, 4h) và (4h, h), tổng số tham số là 8h².

Trong công thức (11), có ba ma trận, nếu muốn tổng số tham số vẫn là 8h², mỗi ma trận cần có kích thước 8h²/3. Do đó, wup và wgate sẽ có kích thước (h, 8h/3), và wdown sẽ có kích thước (8h/3, h).

Giả sử kích thước hidden_dimhidden_dim, kích thước lớp giữa (sau khi up) là mid_dim, logic tính toán như sau:

1
2
3
4
mid_dim = int(8 * hidden_dim / 3)
# multiple_of: đảm bảo kích thước lớp ẩn của SwiGLU là bội của lũy thừa lớn của 2
mid_dim = multiple_of * ((mid_dim + multiple_of - 1) // multiple_of)
# multiple_of thường được đặt là 256 trong các mô hình như LLaMA và GPT

Lưu ý rằng trong kiến trúc LLM, multiple_of là một tham số để tối ưu hiệu suất tính toán, thường được đặt là 256 hoặc các lũy thừa của 2 (như 128, 512, v.v.). Điều này nhằm:

  1. Tối ưu phần cứng: GPU/TPU xử lý tốt nhất với các kích thước là lũy thừa của 2.
  2. Đảm bảo căn chỉnh bộ nhớ: cải thiện tốc độ tính toán.
  3. Nâng cao hiệu suất tính toán song song: một số phép toán song song hoạt động hiệu quả hơn với các số nguyên tắc.

đăng ký jun88 3. Thực hiện mã nguồn FFN với swishGLU

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class FFNExpert(nn.Module):
    def __init__(self, hidden_dim, dropout):  # Con đường tiến hóa của LLM, FFN kích hoạt từ GELU -> SwiGLU
        super().__init__()
        # Có một con số bí ẩn là 8/3
        hidden_dim = hidden_dim
        # Có thể tối ưu thành bội của multiple_of
        mid_dim = hidden_dim * 8 // 3
        self.up = nn.Linear(hidden_dim, mid_dim, bias=False)
        self.down = nn.Linear(mid_dim, hidden_dim, bias=False)
        self.gate = nn.Linear(hidden_dim, mid_dim, bias=False)
        self.dropout = nn.Dropout(dropout)
    
    def forward(self, x):
        out = self.dropout(
            self.down(
                # Shape sau khi up là (b, s, mid_dim)
                # Shape của gate và up đều là (b, s, mid_dim)
                # Hai giá trị này nhân element-wise
                F.silu(
                    self.gate(x)
                ) * self.up(x)
            )
        )
        return out
Built with Hugo
Theme Stack thiết kế bởi Jimmy