Cửa sổ trượt
Cửa sổ trượt
Mỗi token có thật cần đọc lại toàn bộ những gì đứng trước nó? Cắt cho nó chỉ nhìn vài chục token gần nhất thì tiết kiệm được đúng bao nhiêu, và mất gì?
Ở self-attention bạn đã thấy mỗi truy vấn chấm với mọi khoá đứng trước nó. Trong mô hình sinh chữ, mặt nạ nhân quả chỉ chặn phần tương lai, nên trên chuỗi n token vẫn còn n(n+1)/2 ô được phép nhìn: chi phí tính toán bậc hai, và như bài bộ đệm KV đã đếm, bộ nhớ phải giữ lớn lên tuyến tính theo độ dài ngữ cảnh mà không bao giờ nhỏ lại.
Có một cách cắt rất thô: bắt mỗi token chỉ được đọc w token gần nhất, cửa sổ trượt theo vị trí. Nghe như tự làm mô hình kém đi, và điều đó có thể đúng, nhưng hai chuyện sau lại làm nó dùng được thật. Thứ nhất, bộ đệm KV chỉ còn phải giữ đúng cửa sổ, tức nó thôi lớn lên theo ngữ cảnh. Thứ hai, qua nhiều lớp thì tầm nhìn không hề bị khoá ở w. Sim dưới đây tính cả hai bằng số học phổ thông, bạn sửa cấu hình thì nó tính lại.
1 · Ma trận mặt nạ trên 20 token ✎ sửa được
| hàng i \ cột j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ô mở |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | ◆ | 1 | |||||||||||||||||||
| 1 | ✓ | ◆ | 2 | ||||||||||||||||||
| 2 | ✓ | ✓ | ◆ | 3 | |||||||||||||||||
| 3 | ✓ | ✓ | ✓ | ◆ | 4 | ||||||||||||||||
| 4 | ✓ | ✓ | ✓ | ✓ | ◆ | 5 | |||||||||||||||
| 5 | ✓ | ✓ | ✓ | ✓ | ✓ | ◆ | 6 | ||||||||||||||
| 6 | × | ✓ | ✓ | ✓ | ✓ | ✓ | ◆ | 6 | |||||||||||||
| 7 | × | × | ✓ | ✓ | ✓ | ✓ | ✓ | ◆ | 6 | ||||||||||||
| 8 | × | × | × | ✓ | ✓ | ✓ | ✓ | ✓ | ◆ | 6 | |||||||||||
| 9 | × | × | × | × | ✓ | ✓ | ✓ | ✓ | ✓ | ◆ | 6 | ||||||||||
| 10 | × | × | × | × | × | ✓ | ✓ | ✓ | ✓ | ✓ | ◆ | 6 | |||||||||
| 11 | × | × | × | × | × | × | ✓ | ✓ | ✓ | ✓ | ✓ | ◆ | 6 | ||||||||
| 12 | × | × | × | × | × | × | × | ✓ | ✓ | ✓ | ✓ | ✓ | ◆ | 6 | |||||||
| 13 | × | × | × | × | × | × | × | × | ✓ | ✓ | ✓ | ✓ | ✓ | ◆ | 6 | ||||||
| 14 | × | × | × | × | × | × | × | × | × | ✓ | ✓ | ✓ | ✓ | ✓ | ◆ | 6 | |||||
| 15 | × | × | × | × | × | × | × | × | × | × | ✓ | ✓ | ✓ | ✓ | ✓ | ◆ | 6 | ||||
| 16 | × | × | × | × | × | × | × | × | × | × | × | ✓ | ✓ | ✓ | ✓ | ✓ | ◆ | 6 | |||
| 17 | × | × | × | × | × | × | × | × | × | × | × | × | ✓ | ✓ | ✓ | ✓ | ✓ | ◆ | 6 | ||
| 18 | × | × | × | × | × | × | × | × | × | × | × | × | × | ✓ | ✓ | ✓ | ✓ | ✓ | ◆ | 6 | |
| 19 | × | × | × | × | × | × | × | × | × | × | × | × | × | × | ✓ | ✓ | ✓ | ✓ | ✓ | ◆ | 6 |
2 · Tầm nhìn hiệu dụng qua nhiều lớp ✎ sửa được
Mỗi lớp trộn thêm một cửa sổ nữa, nên qua L lớp thì tầm nhìn là L × (w - 1) + 1 token, xấp xỉ L × w. Trên ma trận đang vẽ: cửa sổ 6 qua 32 lớp muốn tới 161 token, còn chuỗi chỉ có 20 token nên con số bị kẹp về 20.
3 · Byte bộ đệm KV ở quy mô thật ✎ sửa được
2 (giữ cả K và V) × 8 đầu KV × 128 chiều × 2 byte = 4.096 byte cho mỗi token ở mỗi lớp. Nhân 32 lớp ra 128.0 KiB cho mỗi token. Phần còn lại chỉ là đếm xem phải giữ bao nhiêu token.
| ở ngữ cảnh 32.768 token | cửa sổ trượt | nhân quả đầy đủ | tỉ lệ |
|---|---|---|---|
| token phải giữ trong bộ đệm | 4.096 | 32.768 | 12.5% |
| byte bộ đệm KV | 512.0 MiB | 4.00 GiB | 12.5% |
| phép chú ý khi nạp cả ngữ cảnh | 125.831.168 | 536.887.296 | 23.4% |
- Nó đếm ô nào được nhìn và byte nào phải giữ. Nó không đo chất lượng. Một mặt nạ cắt 90% số ô vẫn hiện ra ở đây đẹp đẽ như nhau, dù mô hình có thể đã hỏng hẳn.
- Vì sao token neo lại quan trọng tới vậy là một kết quả thực nghiệm của người khác, không phải thứ số học này chứng minh được. Ở đây bạn chỉ thấy chúng tốn thêm bao nhiêu ô và bao nhiêu byte.
- Số token phải giữ được định nghĩa là số ô mở ở hàng cuối của ma trận, để bạn đếm được bằng mắt. Cách cấp phát thật còn phân trang, còn đệm thừa, còn chỗ cho lô nhiều câu, nên byte thực tế thường nhiều hơn con số này.
- Preset mang tên mô hình chỉ chép lại tham số công bố kèm nguồn và ngày. Cổng kiểm số canh phép tính, không canh chuyện mô hình ngoài kia có đúng cấu hình đó hay không.
Ba con số đáng nhìn kỹ
Cắt cửa sổ là cắt phép tính, và nó đo được chính xác. Ở trạng thái mở bài, ma trận vẽ 20 token với cửa sổ 6. Mặt nạ nhân quả đầy đủ mở 20 × 21 / 2 = 210 ô; cửa sổ 6 mở 6 × 7 / 2 + 14 × 6 = 21 + 84 = 105 ô, đúng một nửa. Công thức đóng ở đây dễ tự dựng lại: hàng i mở min(i + 1, w) ô, nên tổng chia làm hai đoạn, đoạn đầu là cái dốc w(w + 1)/2 cho tới khi cửa sổ đầy, đoạn sau là (n - w) hàng mỗi hàng đúng w ô. Đẩy cửa sổ lên bằng hoặc lớn hơn số token thì tỉ lệ về đúng 1, tức mặt nạ quay lại nhân quả đầy đủ, không phải gần giống mà là từng ô một.
Bộ đệm KV thôi lớn lên theo ngữ cảnh, đây là chỗ ăn tiền nhất. Nhìn bảng số 3. Với cấu hình 32 lớp, 8 đầu KV, 128 chiều mỗi đầu và 2 byte mỗi phần tử, mỗi token ở mỗi lớp tốn 2 × 8 × 128 × 2 = 4096 byte, nhân 32 lớp ra 128.0 KiB cho mỗi token. Ngữ cảnh 32768 token mà giữ hết thì thành 4.00 GiB chỉ riêng bộ đệm. Cửa sổ 4096 chỉ phải giữ 4096 token, tức 512.0 MiB, còn 12,5% và tiết kiệm 3.50 GiB. Quan trọng hơn con số đó: nếu bạn kéo độ dài ngữ cảnh lên nữa thì cột nhân quả đầy đủ tăng theo, còn cột cửa sổ trượt đứng nguyên. Phần phép chú ý khi nạp cả ngữ cảnh cũng tụt về 23,4%, vì bậc hai đã thành gần như tuyến tính.
Cửa sổ hẹp không phải là tầm nhìn hẹp, và đây là điểm phản trực giác nhất của bài. Ở lớp 1, token i trộn được thông tin của token i - (w - 1). Lớp 2 đọc các trạng thái đã trộn đó, nên nó với tới i - 2(w - 1). Cứ mỗi lớp lại vươn thêm một cửa sổ nữa, nên qua L lớp tầm nhìn hiệu dụng là L × (w - 1) + 1 token, xấp xỉ L × w. Kéo núm số lớp mà xem: cửa sổ 4096, một lớp với tới 4096 token, hai lớp 8191, bốn lớp 16.381, tám lớp đã là 32.761 token, tức gần trọn ngữ cảnh 32768. Từ lớp thứ chín trở đi con số bị kẹp ở độ dài chuỗi và sim nói thẳng là đã phủ hết. Trên ma trận nhỏ đang vẽ thì hiện tượng kẹp xảy ra ngay: 32 lớp cửa sổ 6 muốn với tới 161 token, mà chuỗi chỉ có 20.
Nói cho đúng, đó là giới hạn hình học của đường truyền thông tin, không phải lời bảo đảm. Nó cho biết xa nhất thì tín hiệu có thể đi tới đâu, chứ không nói tín hiệu đi qua tám lớp còn mạnh bao nhiêu, cũng không nói mô hình có học được cách dùng đường đi dài đó hay không. Sim này không đo được những chuyện ấy, và bạn nên nghi ngờ bất cứ ai nói L × w là ngữ cảnh hiệu dụng.
Vì sao phải giữ lại vài token đầu chuỗi
Bấm sang chế độ cửa sổ trượt cộng token neo. Bốn cột sát mép trái mở ở mọi hàng, và từ hàng 6 trở đi chúng đổi màu, vì lúc đó chúng đã nằm ngoài cửa sổ và chỉ còn neo giữ lại. Với 20 token, cửa sổ 6 và 4 token neo, số ô mở đi từ 105 lên 155, trong đó 50 ô có được là nhờ neo, và bộ đệm phải giữ 10 token thay vì 6. Ở quy mô thật thì 4 token neo thêm vào 4096 token cửa sổ chỉ đội bộ đệm từ 512.0 MiB lên 512.5 MiB, tức thêm nửa MiB.
Nửa MiB để làm gì? Đây là chỗ phải nói thật rõ ranh giới. Người ta phát hiện ra rằng nếu bỏ hẳn các token đầu chuỗi ra khỏi bộ đệm, chất lượng sinh chữ sụp một cách bất thường, sụp mạnh hơn nhiều so với mức mà việc mất vài token ở xa đáng gây ra. Cách giải thích thường gặp là softmax buộc tổng trọng số bằng 1, nên khi không có ô nào đáng để ý thật thì mô hình phải đổ phần trọng số dư đó vào đâu đó, và trong thực tế nó học cách đổ vào mấy token đầu chuỗi. Bỏ mất chỗ trút ấy thì phân bố chú ý méo hẳn. Giữ lại vài token neo là cách vá rất rẻ, và cái rẻ đó thì sim đo được.
Nhưng phần chất lượng thì sim không đo được. Nó đếm ô và đếm byte. Kết luận rằng 4 token neo cứu được chất lượng là kết quả thực nghiệm của người khác, đo bằng perplexity trên chuỗi rất dài, không phải thứ mà số học ở đây chứng minh. Cổng kiểm số của bài này canh phép tính: nếu cấu hình là như vậy thì số ô và số byte phải là như vậy. Nó không canh, và không thể canh, những khẳng định về mô hình ngoài kia.
Đọc preset cho đúng
Preset mang tên mô hình chỉ chép lại tham số đã công bố, kèm nguồn và ngày chép, và ngày đó hiện ngay dưới hàng preset. Lĩnh vực này đổi rất nhanh: một bản v0.2 có thể bỏ hẳn cửa sổ trượt, một mô hình mới có thể xen kẽ lớp cửa sổ với lớp toàn cục theo tỉ lệ riêng của nó. Nên hãy coi preset là ví dụ đã đóng băng thời điểm. Preset thứ tư ghi rõ là số minh hoạ cho một lớp kích thước, không phải cấu hình của mô hình nào cả, vì thà nói là minh hoạ còn hơn dán tên một mô hình lên những con số mình không kiểm được.
Vài điểm nhỏ nhưng hay bị bỏ qua:
- Cửa sổ trượt là chuyện của mặt nạ, không phải chuyện của trọng số. Cùng một mô hình có thể chạy với cửa sổ ở lúc suy luận dù nó không được huấn luyện như vậy, và ngược lại. Điều đó ảnh hưởng tới chất lượng, nhưng không ảnh hưởng tới phép đếm ở đây.
- Số token phải giữ trong sim được định nghĩa là số ô mở ở hàng cuối của ma trận, để bạn đếm được bằng mắt. Bộ nhớ thật còn phân trang, còn đệm thừa, còn phải chứa nhiều câu trong một lô, nên con số thực tế thường lớn hơn.
- Cửa sổ bằng 0 bị chặn. Cửa sổ 0 nghĩa là token không đọc được cả chính nó, hàng mặt nạ rỗng và softmax chia cho 0. Sim kẹp về 1 và báo cho bạn biết là nó đã kẹp, thay vì âm thầm sửa.
Cửa sổ trượt đổi tầm nhìn trực tiếp lấy hai thứ rất cụ thể: số phép chú ý tụt từ bậc hai về gần tuyến tính, và bộ đệm KV thôi lớn lên theo ngữ cảnh vì chỉ còn phải giữ đúng cửa sổ. Cái làm nó dùng được thật là chuyện xếp lớp: qua L lớp, tầm nhìn hiệu dụng lên tới khoảng L × w, nên cửa sổ 4096 với 32 lớp về mặt hình học đã phủ trọn ngữ cảnh 32768. Chỗ nó gãy là các token đầu chuỗi, nên người ta giữ lại vài token neo, và cái giá của mấy token đó nhỏ tới mức gần như miễn phí. Đếm được ô và byte không có nghĩa là đo được chất lượng: phần đó phải chạy thực nghiệm mới biết.
- 1Một chuỗi 8 token với mặt nạ nhân quả đầy đủ. Có bao nhiêu ô được phép nhìn, tức bao nhiêu phép chú ý phải tính?
- 2Mô hình có 32 lớp, mỗi lớp dùng cửa sổ trượt rộng 4096, ngữ cảnh 32768 token. Về mặt hình học, thông tin từ token đầu chuỗi có thể đi tới token cuối chuỗi hay không?
- 3Đang ở cửa sổ trượt 4096 trên ngữ cảnh 32768 với bộ đệm 512.0 MiB. Bật thêm 4 token neo đầu chuỗi thì chuyện gì xảy ra, và sim chứng minh được điều gì?