AI Core cho lập trình viên/Nỗi đau của model đọc tuần tự trước khi có transformer
29/54
Bài 29 / 54~12 phútTransformer & attentionMiễn phí lượt xem

Nỗi đau của model đọc tuần tự trước khi có transformer

Trước transformer, model đọc văn bản từng token một: phụ thuộc xa bị quên dần, train không song song hoá được — hai nỗi đau attention sẽ giải quyết.

TL;DR: Trước transformer, model đọc tuần tự (RNN-style) đọc từng token một, mỗi bước nén toàn bộ những gì đã đọc vào một hidden state kích thước cố định. Cách nén này gây hai nỗi đau độc lập. Thứ nhất, thông tin ở xa phai dần: trạng thái cố định phải chứa lịch sử ngày càng dài, nên thông tin cũ bị pha loãng theo mỗi bước nén. Thứ hai, train không song song hoá được theo chiều dài chuỗi: bước t phải chờ bước t-1 xong, nên GPU dù nhiều nhân tính toán vẫn phần lớn ngồi chờ. Hai nỗi đau này là động lực trực tiếp khiến transformer ra đời, mở đầu bằng attention — chủ đề bài sau.

Một đội kỹ sư nội bộ tại một công ty fintech thử nghiệm một trợ lý tóm tắt cuộc gọi chăm sóc khách hàng (môi trường UAT, dữ liệu ẩn danh, chưa lên production), dùng kiến trúc đọc tuần tự kiểu cũ — cách nhiều hệ thống NLP vẫn dùng trước khi transformer phổ biến. Một bản ghi cuộc gọi dài 46 câu, khoảng 640 từ, được đưa vào để model tóm tắt thành 2-3 câu.

Câu đầu bản ghi nói rõ: "Chị Hạnh, chủ tài khoản doanh nghiệp mã KH-77821, gọi lên vì phát hiện một giao dịch lạ trừ 12 triệu đồng tối hôm qua." Hơn 30 câu tiếp theo xoay quanh xác minh danh tính, tra soát giao dịch, và nhắc tới một khách hàng khác trong ví dụ minh hoạ mà nhân viên tổng đài dùng để giải thích quy trình. Câu áp chót: "Vậy chị xác nhận muốn khoá thẻ tạm thời trong lúc chờ tra soát đúng không?" Bản tóm tắt model xuất ra gán nhầm yêu cầu khoá thẻ cho khách hàng trong ví dụ minh hoạ, không phải chị Hạnh — người mở đầu cuộc gọi. Lỗi này lặp lại ở nhiều bản ghi dài tương tự, không phải ca cá biệt.

Đây không phải lỗi thiếu dữ liệu train hay model "chưa đủ thông minh". Lỗi nằm sẵn trong cách kiến trúc đọc tuần tự xử lý chuỗi dài — nó nén dần lịch sử vào một trạng thái sức chứa hữu hạn, thông tin xuất hiện sớm buộc phải chia chỗ với mọi thứ xuất hiện sau. Bài này mổ đúng cơ chế đó, cùng một giới hạn thứ hai đi kèm.

Bài trước dạy LLM sinh văn bản bằng vòng lặp autoregressive: mỗi bước, model nhìn toàn bộ chuỗi token đã có, dự đoán phân phối xác suất cho token kế tiếp, sample một token, nối vào cuối, rồi lặp lại. Đó là câu trả lời cho model tạo output theo trình tự nào — vẫn đúng cho cả transformer. Bài này hỏi khác: bên trong một bước dự đoán, model đọc chuỗi đã có bằng cơ chế gì? Trước transformer, đó là đọc tuần tự, nén dần vào một trạng thái cố định — gây ra hai nỗi đau dưới đây.

1. Analogy — đọc một cuốn sách dày qua khe cửa sổ ghi chú

Tưởng tượng bạn phải tóm tắt một cuốn sách 400 trang, nhưng chỉ được ghi chú vào đúng một tấm thẻ nhớ 200 ô, không thêm thẻ mới được. Đọc xong mỗi trang, bạn phải sửa lại chính tấm thẻ đó: giữ vài ý cũ, xoá bớt để có chỗ cho ý mới. Tới trang 400, tấm thẻ vẫn chỉ 200 ô, nhưng phải đại diện cho toàn bộ 400 trang đã đọc.

Ý quan trọng ở trang 1 có sống sót tới trang 400 không? Mỗi lần sửa thẻ có rủi ro ghi đè, nên sau hàng trăm lần sửa liên tiếp, xác suất một ý ở trang 1 còn nguyên vẹn giảm dần theo cấp số nhân — không phải vì bạn cố quên, mà vì cơ chế "chỉ một thẻ, sửa liên tục" tự nó dẫn tới việc đó.

Ràng buộc thứ hai: bạn phải đọc tuần tự, không nhảy cóc. Muốn sửa thẻ sau khi đọc trang 50, bạn phải sửa xong thẻ ở trang 49 trước — gọi thêm 10 người đọc cũng vậy, cả 10 vẫn phải xếp hàng vì chỉ có một tấm thẻ để sửa tại một thời điểm.

Đọc sách qua một tấm thẻ nhớModel đọc tuần tự (RNN-style)
Tấm thẻ nhớ, đúng 200 ô, không thêm đượcHidden state h_t, kích thước vector cố định
Đọc xong 1 trang, sửa lại thẻ cũh_t được tính từ h_(t-1) và token mới, ghi đè trạng thái cũ
Ý ở trang 1 dần mờ sau hàng trăm lần sửaThông tin token đầu phai dần sau hàng trăm bước nén — Nỗi đau 1
Phải đọc xong trang 49 mới được sửa thẻ ở trang 50Phải tính xong h_(t-1) mới tính được h_t — Nỗi đau 2
Gọi thêm người đọc cũng không giúp — vẫn phải xếp hàngThêm GPU cũng không giúp — các bước theo thời gian vẫn phải tuần tự

2. Vì sao thông tin ở xa bị phai dần?

Model đọc tuần tự xử lý một chuỗi token bằng một công thức lặp lại y hệt ở mọi bước — cùng một hàm f, cùng một bộ trọng số, áp dụng liên tiếp:

h_0 <- trang thai rong (chua doc gi)
for t = 1 to N:
    h_t <- f(h_(t-1), token_t)     -- nen token moi vao trang thai cu
// h_N la trang thai cuoi cung, dai dien cho CA N token da doc
// Time: O(N) buoc tuan tu   Space: O(1) cho hidden state (kich thuoc co dinh)

Điểm mấu chốt nằm ở dòng comment cuối: h_t luôn có cùng một kích thước, bất kể t là 1 hay 1000. Ở t = 800, h_800 phải đại diện cho toàn bộ 800 token đã đọc, kích thước vector không hề tăng. Token đầu tiên không "giữ nguyên" một chỗ cố định trong h_t — nó bị trộn lẫn và ghi đè qua từng lần áp dụng f, giống tấm thẻ nhớ bị sửa 800 lần liên tiếp.

Mỗi lần áp dụng f thường nhân với một ma trận trọng số rồi ép qua hàm phi tuyến (như tanh). Lặp lại cùng phép biến đổi nhiều lần khiến đóng góp của token ở xa co lại theo cấp số nhân — quen thuộc dưới tên "vanishing gradient" khi nói về train, nhưng bản chất cũng là lý do tín hiệu của token xa bị pha loãng khi suy luận. Đây là hệ quả toán học tất yếu của việc nén một chuỗi dài tuỳ ý vào một vector kích thước cố định, không phải lỗi cài đặt.

Bốn hộp trạng thái ẩn cùng một bề rộng nhưng lần lượt phải nén 1, 2, k rồi cả N token; mỗi bước nhận thêm một token và chỉ tính được sau khi bước trước xong

Đây chính là cơ chế đứng sau lỗi tóm tắt sai ở đầu bài. token 1 mang thông tin "chị Hạnh mở đầu cuộc gọi" phải qua hơn 30 lần nén trước khi tới câu áp chót — tín hiệu về chị Hạnh pha loãng hơn nhiều so với khách hàng nhắc gần cuối, nên model gán nhầm người. Tăng hidden state chỉ làm chậm tốc độ phai, không xoá bỏ nó.

3. Vì sao train không song song hoá được?

Nỗi đau thứ hai độc lập với nỗi đau thứ nhất, nhưng cùng xuất phát từ một dòng công thức ở mục 2: h_t <- f(h_(t-1), token_t). Muốn tính h_t, bắt buộc phải có h_(t-1) trước.

GPU co hang nghin nhan tinh toan (core), nhung mot chuoi RNN chi cho phep:

step 1: h1 <- f(h0, token1)        // phai xong truoc khi step 2 bat dau
step 2: h2 <- f(h1, token2)        // cho step 1, khong the tinh som hon
step 3: h3 <- f(h2, token3)        // cho step 2
...
step N: hN <- f(h(N-1), tokenN)    // cho step N-1

// Time: O(N) buoc TUAN TU bat buoc, du co bao nhieu core cung vay
// Moi step chi la MOT phep nhan ma tran nho -- phan lon core GPU dung yen

Điểm dễ hiểu lầm: train theo batch nhiều câu cùng lúc vẫn song song bình thường — GPU tính h_t của câu A và câu B cùng lúc không vấn đề, vì hai câu độc lập. Nỗi đau nằm ở chiều thời gian, bên trong một câu: bước t của MỖI câu vẫn phải chờ bước t-1 của chính câu đó. Bản ghi 640 từ tách thành khoảng 800 token cần tối thiểu 800 bước tính tuần tự để ra h_800 — con số này không giảm được dù thêm bao nhiêu GPU, vì đây là chuỗi phụ thuộc, không phải khối lượng công việc chia nhỏ được.

Hệ quả thực tế: mỗi bước chỉ là một phép nhân ma trận nhỏ so với năng lực GPU hiện đại, nên phần lớn nhân tính toán ngồi không. Chuỗi càng dài, thời gian train càng kéo dài tuyến tính — không rút ngắn được bằng phần cứng mạnh hơn, chỉ bằng đổi kiến trúc.

4. Đào sâu — nguồn gốc động lực cho transformer

Hai nỗi đau ở mục 2 và 3 không phải suy diễn riêng của bài này — chúng là phần mở đầu của bài báo đặt nền móng cho transformer: Vaswani và cộng sự, "Attention Is All You Need" (arXiv:1706.03762). Nhóm tác giả mô tả các mô hình chuỗi thống trị thời điểm đó dựa trên mạng hồi quy hoặc tích chập phức tạp, rồi đề xuất kiến trúc mới dựa hoàn toàn trên attention, bỏ hẳn hồi quy lẫn tích chập — song song hoá được tốt hơn, ít thời gian train hơn đáng kể.

Điều này khớp đúng hai nỗi đau vừa mổ: bỏ hồi quy giải quyết nỗi đau mục 3 (bước t không còn chờ t-1); bỏ cơ chế nén tuần tự vào trạng thái cố định mở đường giải quyết nỗi đau mục 2 (token "nhìn" trực tiếp token khác ở bất kỳ khoảng cách nào). Cơ chế cụ thể — attention — là chủ đề bài sau.

5. Pitfall tổng hợp

Nhầm 1 — tăng kích thước hidden state là hết nghẽn:

✅ Tăng hidden state chỉ hoãn nỗi đau phụ thuộc xa, không gỡ bản chất nén-cố-định — chuỗi đầu vào luôn có thể dài hơn bất kỳ kích thước nào chọn trước (mục 2).

Nhầm 2 — RNN không song song được gì hết:

✅ RNN vẫn song song theo BATCH — nhiều câu chạy cùng lúc, nhiều GPU tính độc lập không vấn đề. Thứ không song song được là THEO CHIỀU DÀI CHUỖI: bên trong một câu, bước t phải chờ bước t-1 (mục 3).

6. Liên hệ các bài khác

7. Tóm tắt

  • Trước transformer, model đọc tuần tự (RNN-style) xử lý chuỗi bằng công thức lặp h_t <- f(h_(t-1), token_t), nén lịch sử vào một hidden state kích thước cố định.
  • Nỗi đau 1 — phụ thuộc xa phai dần: hidden state không tăng theo độ dài chuỗi, nên thông tin sớm bị ghi đè và pha loãng — hệ quả toán học của việc lặp lại cùng một phép biến đổi nhiều lần.
  • Nỗi đau 2 — train không song song hoá được: h_t phụ thuộc trực tiếp h_(t-1), nên các bước bên trong MỘT chuỗi bắt buộc tính tuần tự — batch vẫn song song được, chiều thời gian trong một câu thì không.
  • Hai nỗi đau độc lập nhưng cùng gốc: nén tuần tự vào trạng thái cố định — đúng động lực nêu trong bài báo gốc transformer (Vaswani et al., arXiv:1706.03762).
  • Giải pháp — mỗi token "nhìn thẳng" mọi token khác thay vì qua chuỗi nén trung gian — là attention, chủ đề bài sau.

8. Tự kiểm tra

Tự kiểm tra
Q1
Vì sao kích thước CỐ ĐỊNH của hidden state lại là nguyên nhân sâu xa khiến thông tin ở xa bị phai, chứ không đơn giản là "model quên"?

Hidden state giữ đúng một kích thước vector dù chuỗi dài bao nhiêu — không phải bộ nhớ mở rộng theo độ dài. Thông tin mới bị nén vào CÙNG không gian đó qua hàm `f` lặp lại, nên đóng góp của token ở xa co lại theo cấp số nhân qua từng lần áp dụng. Đây là hệ quả toán học tất yếu, không phải model "cố ý" quên như con người.

Q2
Nếu tăng kích thước hidden state lên gấp 10 lần, nỗi đau phụ thuộc xa có biến mất hoàn toàn không? Giải thích.

Không biến mất, chỉ dời xa hơn — hidden state lớn chịu được nhiều lần nén hơn trước khi pha loãng tới mức vô dụng, giống thẻ 2000 ô chịu sửa nhiều lần hơn thẻ 200 ô. Nhưng chuỗi đầu vào luôn có thể dài hơn bất kỳ kích thước cố định nào chọn trước — vấn đề nằm ở CƠ CHẾ nén, không phải một con số cụ thể.

Q3
Vì sao việc train theo batch nhiều câu cùng lúc KHÔNG giải quyết được nỗi đau song song hoá bên trong một chuỗi?

Batch parallelism và time-step parallelism là hai trục độc lập. GPU tính `h_t` của câu A và câu B song song vì hai chuỗi không phụ thuộc nhau. Nhưng bên trong MỘT câu, `h_t` cần `h_(t-1)` làm đầu vào trực tiếp — một chuỗi phụ thuộc dữ liệu thật, không phải khối lượng chia nhỏ được, nên thêm GPU không phá được.

Q4
Với một chuỗi 800 token, model đọc tuần tự cần tối thiểu bao nhiêu bước tính tuần tự để ra được `h_800`? Vì sao con số này không giảm được dù thêm GPU?

Tối thiểu 800 bước — đúng bằng độ dài chuỗi, vì mỗi `h_t` chỉ tính được sau khi `h_(t-1)` có giá trị. Thêm GPU giúp mỗi bước nhanh hơn nhưng không giảm SỐ bước bắt buộc — bước 800 vẫn chờ bước 799 xong dù phần cứng mạnh tới đâu, vì đây là giới hạn thuật toán, không phải giới hạn phần cứng.

Q5
Giải thích cơ chế khiến trợ lý tóm tắt cuộc gọi trong scenario đầu bài gán nhầm yêu cầu khoá thẻ cho sai khách hàng.

Thông tin "chị Hạnh mở đầu cuộc gọi, chủ tài khoản KH-77821" nằm ở token rất sớm, phải qua hơn 30 lần nén liên tiếp qua công thức h_t <- f(h_(t-1), token_t) trước khi tới câu áp chót nên đã phai nhiều. Khách hàng nhắc ở ví dụ minh hoạ gần câu áp chót hơn, tín hiệu còn "tươi" hơn — nên thắng, dù sai chủ thể.

Q6
Attention (bài sau) được mô tả là cơ chế cho mỗi token "nhìn thẳng" mọi token khác. Dựa vào hai nỗi đau vừa học, dự đoán vì sao thiết kế đó có thể giải quyết cả hai cùng lúc.

Nỗi đau 1 đến từ việc thông tin phải qua nhiều bước nén trung gian; nhìn thẳng không qua trung gian thì khoảng cách không còn quyết định độ phai. Nỗi đau 2 đến từ `h_t` phụ thuộc `h_(t-1)`; nếu "nhìn" của mỗi token không cần chờ token trước, phép tính chạy song song được. (Cơ chế chính xác — query, key, value — thuộc bài sau.)

Bài tiếp theo: Attention — truy vấn có trọng số mổ cơ chế cho mỗi token "nhìn thẳng" mọi token khác, thay vì đọc tuần tự qua trạng thái nén cố định như bài này.

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên

Bài tiếp theo

Query/Key/Value: attention là phép tra cứu mềm có trọng số