Bản sao ChatGPT dựa trên GPT-2 được triển khai bằng 3000 byte C (2023)
(nicholas.carlini.com)- Chỉ với mã C khoảng 3000 byte, trình suy luận GPT-2 xử lý trọn vẹn một luồng từ nạp trọng số, token hóa, chạy Transformer đến chuyển đổi đầu ra
- Dù giữ kích thước mã nhỏ, chương trình vẫn tạo câu trả lời từ GPT-2 Small trong vài giây trên máy hiện đại nhờ KV caching, phép nhân ma trận nhanh và tùy chọn song song hóa OMP
- Chất lượng đầu ra ở mức “khách quan mà nói là khá tệ”, và vẫn còn các hạn chế thực tế như xử lý UTF-8 cùng yêu cầu bộ nhớ khi chạy mô hình lớn
- Phần triển khai được chia thành phép toán ma trận, các lớp mạng nơ-ron, Transformer, Byte Pair Encoding, I/O, nạp trọng số và BPE, qua đó cho thấy toàn bộ cấu trúc của một trình suy luận nhỏ
- GPT-2 là mô hình mã nguồn mở năm 2019 yếu hơn GPT-4 rất nhiều, nhưng các thành phần cốt lõi để chạy mô hình ngôn ngữ hiện đại vẫn có thể được biểu diễn bằng mã C nhỏ gọn
Trình chạy GPT-2 viết bằng 3000 byte C
- Chương trình này là một bản triển khai GPT-2 không phụ thuộc thư viện, đọc các ma trận trọng số và tệp BPE từ tệp TensorFlow gốc
- Đầu vào được token hóa bằng bộ mã hóa Byte Pair Encoding(BPE) đơn giản, còn đầu ra được chuyển lại thành chuỗi bằng bộ giải mã BPE
- Cấu trúc bên trong đi từ gói đại số tuyến tính cơ bản, phép toán ma trận, kiến trúc Transformer đến mã suy luận
- Mã được công bố trên GitHub
- GPT-2 Small tạo một câu trả lời trong khoảng vài giây trên máy hiện đại
- Có triển khai KV caching
- Sử dụng phép nhân ma trận hiệu quả
- Có thể bật tùy chọn song song hóa OMP
Điều kiện chạy và giới hạn
- Với bản triển khai này có thể tạo một chương trình hội thoại giống ChatGPT, nhưng chất lượng đầu ra không tốt
- Việc xử lý ký tự UTF-8 có một số điểm đặc biệt
- Nếu chạy mô hình cỡ XL với độ dài ngữ cảnh dài, có thể cần khoảng 100GB RAM
- Với đầu vào ASCII kết hợp GPT-2 Small thì có thể chạy gần như ở mọi nơi
Cách GPT-2 và Transformer hoạt động
- ChatGPT là một ứng dụng cho phép trò chuyện với mô hình ngôn ngữ như với con người, còn GPT-4 được giới thiệu là mô hình mới nhất vận hành ChatGPT
- Chương trình C này triển khai hành vi tương tự ChatGPT bằng GPT-2, một mô hình từ năm 2019
- GPT-2 là mô hình học máy thuộc họ Transformer
- Transformer nhận một chuỗi từ có kích thước cố định làm đầu vào và dự đoán từ tiếp theo
- Lặp lại cùng quy trình có thể tạo ra chuỗi có độ dài tùy ý
Phép toán ma trận và nén dựa trên macro
- Vì mạng nơ-ron được cấu thành từ các phép toán ma trận, phần triển khai bắt đầu với struct Matrix tối thiểu
float* datint rows, cols
- Các phép toán cần thiết chủ yếu thuộc hai loại
- Phép toán ma trận-hằng số
- Phép toán ma trận-ma trận
- Macro C được dùng để giảm các cấu trúc vòng lặp lặp lại, chỉ thay toán tử cụ thể để sinh ra nhiều hàm
#definetrong C gần giống phép thay thế đơn giản, nên có thể đưa cả biểu thức gồm dấu chấm phẩy, không chỉ toán tử thông thường, vào tham số macro để giảm kích thước mã
Phép nhân ma trận nhanh
- Phép nhân ma trận cơ bản bắt đầu từ bản triển khai O(n³) đơn giản dùng ba vòng lặp lồng nhau
- Xét đến đặc tính cache và truy cập bộ nhớ, các vòng lặp được thay đổi để đọc và ghi lặp lại cùng vùng nhớ
- Bản triển khai nhanh tăng
jvàkmỗi lần 4 đơn vị, rồi dùng các vòng lặpk2,j2bên trong - Ở bước suy luận, để tái sử dụng một phần kết quả đã tính, có thêm cách chỉ nhân một phần của ma trận A với B
Triển khai các lớp mạng nơ-ron
- Để tạo Transformer, một số lớp mạng nơ-ron được triển khai trực tiếp
- Hàm kích hoạt GELU được triển khai bằng macro
- Có hàm xử lý phần tam giác dưới của ma trận cho causal attention
- Giới hạn ma trận attention để không nhìn thấy token tương lai mà chỉ nhìn quá khứ
- LayerNorm chuẩn hóa trung bình và phương sai của mỗi lớp
- Hàm Linear cộng bias theo kiểu lát gạch sau phép nhân ma trận
Phần lõi Transformer
- Bản triển khai Transformer lặp lại luồng sau ở mỗi lớp
- Qua LayerNorm và Linear để tính query, key, value cùng lúc
- Chia qkv theo từng head
- Tính tích của query và key, rồi áp dụng xử lý causal attention
- Nhân kết quả softmax với ma trận value
- Gom kết quả và áp dụng residual connection
- Qua GELU và Linear, rồi lại áp dụng residual connection
- Cuối cùng, sau LayerNorm cuối, nhân đầu ra tại vị trí token cuối với trọng số embedding để tính các ứng viên token tiếp theo
Cách KV caching hoạt động
- Trong suy luận Transformer, sau khi tạo một token, không cần tính lại toàn bộ hàm để tạo token tiếp theo
- Nếu tái sử dụng phần lớn kết quả đã tính đến token thứ N, thì việc tạo token thứ N+1 chỉ cần thêm một phần công việc
- Bản triển khai thực hiện mọi phép cấp phát tuần tự trong cùng một khối bộ nhớ
- Mỗi phép nhân ma trận luôn dùng cùng vùng nhớ, nhờ đó ở vòng lặp tiếp theo không cần khởi tạo bộ nhớ về 0 mà vẫn giữ kết quả trước đó
- Ở vòng lặp mới, chỉ hàng thứ N+1 được tính
Triển khai Byte Pair Encoding
- Mô hình ngôn ngữ cần đầu vào có kích thước cố định, nên khó xử lý trực tiếp vô số từ theo đơn vị từ
- Mô hình cấp ký tự phải học ý nghĩa của mọi từ từ đầu, đồng thời làm giảm kích thước ngữ cảnh hiệu dụng theo độ dài từ trung bình
- Các mô hình như GPT-2 dùng BPE để tạo token từ các mảnh từ
- Từ phổ biến có thể trở thành một token
- Từ hiếm được tách thành các mảnh nhỏ hơn
- Ví dụ
nicholascó thể được chia thànhnich,o,las
- Thuật toán BPE thông thường lặp lại việc gộp các cặp token liền kề
- Bản triển khai C này dùng phương pháp đệ quy có thể tốn thời gian theo cấp số nhân thay cho thuật toán thời gian tuyến tính, nhằm giảm kích thước mã
- Tìm mục trong vocabulary khớp với prefix của từ hiện tại
- Token hóa đệ quy phần chuỗi còn lại
- Chọn cách token hóa tốt nhất dựa trên độ dài và chỉ số trong vocabulary
Nạp trọng số
- Trọng số mạng nơ-ron phải được đọc từ đĩa, và tệp là định dạng tuần tự hóa nhị phân phẳng của float 32-bit
- Các kích thước mô hình GPT-2 dùng cùng kiến trúc và trọng số cũng được lưu theo cùng thứ tự, nên chỉ cần đọc lần lượt các ma trận có hình dạng đúng
- Thứ tự lưu lớp khác với kỳ vọng
- Sau lớp 0, 1 là lớp 10
- Vì tên được sắp xếp theo lexicographic order
- Trong sắp xếp chuỗi,
10đứng trước2
- Bản triển khai dùng mã hoán vị để chuyển thứ tự này về thứ tự lớp thực tế
Nạp vocabulary BPE
- Để chạy BPE, trước tiên cần đọc tệp vocabulary từ đĩa
- Tệp gốc có định dạng để đọc bằng Python, không phải định dạng dễ phân tích bằng mã C nhỏ
- Tệp không phải danh sách từ mà là danh sách gộp BPE
- Ví dụ, thay vì lưu trực tiếp token
Hello, nó lưu theo kiểu cần gộpHvàello
- Ví dụ, thay vì lưu trực tiếp token
- Tệp dùng một encoding giống UTF-8 nhưng không hoàn toàn giống
- Các ký tự ASCII in được được lưu nguyên dạng
- Các ký tự không in được trong phạm vi 0~31 được mã hóa thành
188 + giá trị ký tự - Ví dụ, dấu cách được mã hóa thành token
Ġ
Ġtrên đĩa là0xc4 0xa0trong UTF-8, nên cần xử lý riêng để đổi nó lại thành dấu cách
Điều mà đoạn mã nhỏ cho thấy
- Có thể nén nhiều thập kỷ phát triển học máy vào vài nghìn byte mã
- Ngoài trọng số mô hình thực tế, hầu như không thiếu các yếu tố cần thiết để chạy một mạng nơ-ron hiện đại
- Bản triển khai này chủ yếu được tạo ra cho vui, nhưng là ví dụ cho thấy mạng nơ-ron thực ra có thể được chạy bằng các thành phần đơn giản
1 bình luận
Ý kiến trên Hacker News
Tôi chưa tự chạy thử mã, nhưng ấn tượng ở chỗ kích thước nhỏ
Nghĩ đến việc các chương trình ELIZA thuở đầu còn lớn hơn, có thể nói trong 4 năm qua ta đã có thể nhồi nhét thứ như thế này đến từng byte
Nếu ai biết phép màu nằm ở đâu thì mong giải thích giúp. Tôi tò mò không biết là ở hàm GELU, hay ở mô hình được tải xuống bằng script Bash
Với
Who are you?nó trả lờiI am Alice., còn khi hỏi về máy tính hay chức năng thì lặp lạiI am a computer model trained by OpenAI. How can I help you?Khi yêu cầu giải thích phép cộng thì nó đưa ra giải thích về phép nhân, còn
2+2haySum 2+2thì chỉ nhắc lại y nguyênTôi còn nhớ lúc nghịch GPT-2 khi nó mới ra mắt
Tôi xuất log chat với một người bạn, fine-tune GPT-2 rồi để nó bắt chước cuộc trò chuyện giữa hai chúng tôi; vừa cực kỳ buồn cười, vừa đôi lúc chính xác đến rợn người
Tôi tò mò không biết bước nhảy vọt từ GPT-2 lên GPT-3 là nhờ điều gì. Do mô hình lớn hơn, dữ liệu nhiều hơn, hay cả hai
Tôi biết RLHF đã tạo ra khác biệt lớn, nhưng ngay cả mô hình GPT-3 nền tảng cũng khá hữu ích chỉ với khả năng hoàn thành văn bản nếu được đưa đủ ví dụ
Không rõ nữa, nhưng có vài truyện cổ tích tôi thích do GPT-2 viết
https://deepdreams.stavros.io/episodes/the-princess-the-fair...
Tôi tự hỏi liệu nó có được tạo bằng GPT-2 trên trang này không
Đoạn “phần lớn chỉ làm cho vui, nhưng đây là ví dụ hay cho thấy mạng nơ-ron thực ra đơn giản đến mức nào” khá thú vị
Suỵt, đừng nói với ai nhé. Trí tuệ nhân tạo là ma thuật hắc ám dùng để kiếm tiền
GPT-2 có được instruction tuning nên mới dùng được cho chat thực tế à?
Nếu không thì gọi thứ này là bản nhái ChatGPT có vẻ khá gượng ép
Về cơ bản là không dùng được, và gần như chẳng liên quan gì ngoài việc mượn cái tên. Dù vậy nó vẫn là một chương trình biên dịch và chạy được
Nhìn các phản ứng đánh giá cao hiệu năng của một dự án mà chính tác giả cũng thừa nhận là không hoạt động tử tế, rốt cuộc có vẻ cốt lõi là lôi kéo sự chú ý bằng từ khóa thịnh hành
Câu “Các ngôn ngữ có macro tử tế đang nhìn thấy chưa. Lisp không phải lúc nào cũng hơn C đâu!” lần này có thể chấp nhận được. Vì đó là trò đùa hướng lên trên
Nếu bạn chưa thấy link mã nguồn thì nó nằm lẫn trong phần nội dung: https://github.com/carlini/c-chat-gpt-2
Tôi từng thấy các chatbot trí tuệ nhân tạo cổ điển còn tốt hơn
https://www.cs.cmu.edu/afs/cs/project/ai-repository/ai/areas...
Splotch chỉ cần sửa chút là biên dịch tốt trên các hệ Unix-like hiện đại
Không biết có ai đã chạy cục bộ để xem GPT-2 này tạo ra đầu ra kiểu gì chưa
Dù vậy vẫn khá thú vị, và tôi muốn tự xem bên trong rồi chỉnh thử. Tôi đã muốn nghịch GPT-2 cục bộ một thời gian rồi
Tôi chưa trực tiếp kiểm tra temperature và seed trong mã, chủ yếu đang xem vì sao họ lại obfuscate
Dù gỡ obfuscation thì mã chắc cũng không dài khủng khiếp; nếu khoảng 10.000 ký tự thì chỉ nhìn trên màn hình thôi cũng đủ ấn tượng rồi
Dạo này dùng gptscript là có thể nhanh chóng tự triển khai ChatGPT của riêng mình
https://github.com/gptscript-ai/gptscript
GELU thật sự trông như phép màu:
UNARY(GELU, b / 2 * (1 + tanh(.7978845 * (b + .044715 * b * b * b))))Định nghĩa là
GELU(x) := x * Φ(x), trong đó Φ(x) là hàm phân phối tích lũy của phân phối Gauss