โคลน ChatGPT ที่สร้างด้วย C ขนาด 3000 ไบต์บนพื้นฐาน GPT-2 (2023)
(nicholas.carlini.com)- สร้างตัวรันอนุมาน GPT-2 ด้วย โค้ด C ราว 3000 ไบต์ เท่านั้น โดยครอบคลุมตั้งแต่การโหลดน้ำหนัก การทำโทเค็นไนซ์ การรัน Transformer ไปจนถึงการแปลงผลลัพธ์ในลำดับการทำงานเดียว
- แม้จะคงขนาดโค้ดให้เล็ก แต่ก็ยังมี KV caching, การคูณเมทริกซ์แบบรวดเร็ว และการทำงานขนานด้วย OMP แบบเลือกเปิดได้ ทำให้ GPT-2 Small สร้างคำตอบได้ภายในไม่กี่วินาทีบนเครื่องสมัยใหม่
- คุณภาพผลลัพธ์อยู่ในระดับที่ “แย่อย่างเห็นได้ชัดในเชิงวัตถุวิสัย” และยังมีข้อจำกัดเชิงปฏิบัติ เช่น การจัดการ UTF-8 และความต้องการหน่วยความจำเมื่อรันโมเดลขนาดใหญ่
- การอิมพลีเมนต์แบ่งเป็นส่วนของการคำนวณเมทริกซ์ เลเยอร์โครงข่ายประสาท Transformer, Byte Pair Encoding, I/O และการโหลดน้ำหนักกับ BPE ซึ่งแสดงโครงสร้างทั้งหมดของตัวรันอนุมานขนาดเล็ก
- แม้ GPT-2 จะเป็นโมเดลโอเพนซอร์สจากปี 2019 ที่อ่อนกว่า GPT-4 มาก แต่ชิ้นส่วนหลักที่ใช้รันโมเดลภาษาแบบสมัยใหม่ก็ยังสามารถถ่ายทอดออกมาเป็นโค้ด C ขนาดเล็กได้
ตัวรัน GPT-2 ที่ทำด้วย C ขนาด 3000 ไบต์
- โปรแกรมนี้เป็น อิมพลีเมนเตชัน GPT-2 แบบไม่มี dependency ที่อ่านเมทริกซ์น้ำหนักและไฟล์ BPE จากไฟล์ TensorFlow ต้นฉบับ
- อินพุตถูกแปลงเป็นโทเค็นด้วยตัวเข้ารหัส Byte Pair Encoding(BPE) แบบง่าย และเอาต์พุตจะถูกแปลงกลับเป็นสตริงด้วยตัวถอดรหัส BPE
- โครงสร้างภายในไล่ตั้งแต่แพ็กเกจพีชคณิตเชิงเส้นพื้นฐาน การคำนวณเมทริกซ์ สถาปัตยกรรม Transformer ไปจนถึงโค้ดอนุมาน
- โค้ดเผยแพร่อยู่บน GitHub
- GPT-2 Small สามารถสร้างคำตอบหนึ่งรายการได้ภายในไม่กี่วินาทีบนเครื่องสมัยใหม่
- อิมพลีเมนต์ KV caching
- ใช้การคูณเมทริกซ์ที่มีประสิทธิภาพ
- สามารถเปิดใช้การทำงานขนานด้วย OMP ได้ตามต้องการ
เงื่อนไขการรันและข้อจำกัด
- แม้จะสามารถใช้การอิมพลีเมนต์นี้สร้างโปรแกรมสนทนาแบบ ChatGPT ได้ แต่คุณภาพผลลัพธ์ยังไม่ดี
- การจัดการอักขระ UTF-8 มีพฤติกรรมเฉพาะบางอย่าง
- หากรันโมเดลขนาด XL พร้อม context ที่ยาว อาจต้องใช้ RAM ประมาณ 100GB
- หากใช้อินพุตแบบ ASCII ร่วมกับ GPT-2 Small ก็แทบจะรันได้เกือบทุกที่
การทำงานของ GPT-2 และ Transformer
- ChatGPT คือแอปพลิเคชันที่ใช้โมเดลภาษาเพื่อสนทนาได้เหมือนมนุษย์ และ GPT-4 ถูกอธิบายว่าเป็นโมเดลรุ่นใหม่ที่ใช้ขับเคลื่อน ChatGPT
- โปรแกรม C นี้สร้างพฤติกรรมคล้าย ChatGPT โดยใช้ GPT-2 ซึ่งเป็นโมเดลจากปี 2019
- GPT-2 เป็นโมเดลแมชชีนเลิร์นนิงตระกูล Transformer
- Transformer รับลำดับคำที่มีขนาดคงที่เป็นอินพุต แล้วทำนายคำถัดไป
- เมื่อทำขั้นตอนเดียวกันซ้ำ ก็สามารถสร้างลำดับที่มีความยาวตามต้องการได้
การคำนวณเมทริกซ์และการบีบโค้ดด้วยแมโคร
- เนื่องจากโครงข่ายประสาทประกอบด้วยการคำนวณเมทริกซ์ การอิมพลีเมนต์จึงเริ่มจาก struct Matrix ขั้นต่ำ
float* datint rows, cols
- การดำเนินการที่จำเป็นแบ่งใหญ่ ๆ ได้เป็นสองประเภท
- การคำนวณระหว่างเมทริกซ์กับค่าคงที่
- การคำนวณระหว่างเมทริกซ์กับเมทริกซ์
- ใช้แมโครของ C เพื่อลดโครงสร้างลูปที่ซ้ำกัน และสร้างหลายฟังก์ชันโดยเปลี่ยนแค่โอเปอเรเตอร์เฉพาะส่วน
#defineของ C มีลักษณะใกล้เคียงการแทนที่ข้อความอย่างง่าย จึงสามารถใส่ได้ไม่เพียงโอเปอเรเตอร์ทั่วไป แต่รวมถึงนิพจน์ที่มีเครื่องหมายอัฒภาคด้วย เพื่อช่วยลดขนาดโค้ด
การคูณเมทริกซ์แบบรวดเร็ว
- การคูณเมทริกซ์พื้นฐานเริ่มจากอิมพลีเมนเตชัน O(n³) แบบตรงไปตรงมา ที่ใช้ลูปซ้อนสามชั้น
- จากนั้นจึงปรับลูปให้คำนึงถึงแคชและรูปแบบการเข้าถึงหน่วยความจำ เพื่อให้อ่านและเขียนหน่วยความจำเดิมซ้ำได้มากขึ้น
- เวอร์ชันที่เร็วขึ้นจะเพิ่มค่า
jและkทีละ 4 และใช้ลูปk2,j2ภายใน - ในขั้นตอนอนุมาน มีการเพิ่มวิธีคูณเฉพาะบางส่วนของเมทริกซ์ A กับ B เพื่อใช้ผลที่คำนวณไปแล้วซ้ำ
การอิมพลีเมนต์เลเยอร์โครงข่ายประสาท
- เพื่อสร้าง Transformer จึงมีการอิมพลีเมนต์เลเยอร์โครงข่ายประสาทบางชนิดขึ้นเอง
- ฟังก์ชันกระตุ้น GELU ถูกอิมพลีเมนต์ด้วยแมโคร
- มีฟังก์ชันสำหรับจัดการส่วนล่างรูปสามเหลี่ยมของเมทริกซ์เพื่อทำ causal attention
- จำกัดเมทริกซ์ attention ให้มองเห็นเฉพาะอดีตโดยไม่มองโทเค็นในอนาคต
- LayerNorm ใช้ปรับค่าเฉลี่ยและความแปรปรวนของแต่ละเลเยอร์ให้เป็นมาตรฐาน
- ฟังก์ชัน Linear จะบวก bias แบบ tiling หลังการคูณเมทริกซ์
ตัว Transformer หลัก
- การอิมพลีเมนต์ Transformer จะทำลำดับต่อไปนี้ซ้ำในแต่ละเลเยอร์
- ผ่าน LayerNorm และ Linear เพื่อคำนวณ query, key, value พร้อมกัน
- แยก qkv ตามแต่ละ head
- คำนวณผลคูณของ query กับ key แล้วใช้การประมวลผลแบบ causal attention
- นำผล softmax ไปคูณกับเมทริกซ์ value
- รวมผลลัพธ์แล้วใช้ residual connection
- ผ่าน GELU และ Linear แล้วใช้ residual connection อีกครั้ง
- ในตอนท้าย จะผ่าน Final LayerNorm แล้วนำเอาต์พุตของตำแหน่งโทเค็นสุดท้ายไปคูณกับน้ำหนัก embedding เพื่อคำนวณตัวเลือกของโทเค็นถัดไป
วิธีทำ KV caching
- ในการอนุมานของ Transformer เมื่อสร้างโทเค็นหนึ่งตัวแล้ว ตอนสร้างโทเค็นถัดไปไม่จำเป็นต้องคำนวณทั้งฟังก์ชันใหม่ทั้งหมด
- หากนำผลส่วนใหญ่ที่คำนวณไว้จนถึงโทเค็นลำดับที่ N กลับมาใช้ซ้ำ การสร้างโทเค็นลำดับที่ N+1 จะต้องทำงานเพิ่มเพียงบางส่วน
- การอิมพลีเมนต์นี้ทำการจัดสรรทั้งหมดแบบเรียงต่อกันภายในบล็อกหน่วยความจำเดียวกัน
- ทำให้การคูณเมทริกซ์แต่ละครั้งใช้หน่วยความจำชุดเดิมเสมอ จึงไม่ต้องล้างหน่วยความจำเป็นศูนย์ในรอบถัดไป และสามารถเก็บผลก่อนหน้าไว้ได้
- ในรอบใหม่ จะคำนวณเฉพาะแถวลำดับที่ N+1 เท่านั้น
การอิมพลีเมนต์ Byte Pair Encoding
- โมเดลภาษาต้องการอินพุตขนาดคงที่ จึงยากที่จะจัดการคำจำนวนไม่สิ้นสุดแบบเป็นคำทั้งคำโดยตรง
- โมเดลระดับตัวอักษรต้องเรียนรู้ความหมายของทุกคำใหม่ตั้งแต่ต้น และยังมีปัญหาที่ทำให้ขนาด context ที่ใช้งานได้ลดลงตามความยาวเฉลี่ยของคำ
- โมเดลอย่าง GPT-2 ใช้ BPE เพื่อสร้างโทเค็นเป็นชิ้นส่วนของคำ
- คำที่พบบ่อยอาจกลายเป็นโทเค็นเดียว
- คำที่พบไม่บ่อยจะถูกแยกเป็นชิ้นเล็กลง
- ตัวอย่างเช่น
nicholasอาจถูกแบ่งเป็นnich,o,las
- อัลกอริทึม BPE โดยทั่วไปจะรวมคู่โทเค็นที่อยู่ติดกันซ้ำ ๆ
- แต่อิมพลีเมนเตชัน C นี้เลือกใช้วิธี recursive ที่อาจใช้เวลาแบบเลขชี้กำลังแทนอัลกอริทึมเวลาเชิงเส้น เพื่อลดขนาดโค้ด
- ค้นหารายการใน vocabulary ที่ตรงกับ prefix ของคำปัจจุบัน
- ทำ tokenization ของสตริงที่เหลือแบบ recursive
- เลือกการแบ่งโทเค็นที่ดีที่สุดโดยอิงจากความยาวและดัชนีใน vocabulary
การโหลดน้ำหนัก
- น้ำหนักของโครงข่ายประสาทต้องถูกอ่านจากดิสก์ โดยไฟล์ใช้รูปแบบ serial binary แบบแบนของ float 32 บิต
- โมเดล GPT-2 แต่ละขนาดใช้สถาปัตยกรรมเดียวกัน และน้ำหนักก็ถูกเก็บในลำดับเดียวกัน จึงสามารถอ่านเมทริกซ์ตามรูปร่างที่ถูกต้องตามลำดับได้เลย
- อย่างไรก็ตาม ลำดับการเก็บเลเยอร์ต่างจากที่คาดไว้
- หลังเลเยอร์ 0 และ 1 จะตามด้วย 10
- เพราะชื่อถูกเรียงตาม lexicographic order
- ในการเรียงสตริง
10จะมาก่อน2
- การอิมพลีเมนต์จึงใช้โค้ด permutation เพื่อแปลงลำดับนี้กลับเป็นลำดับเลเยอร์จริง
การโหลด BPE vocabulary
- ก่อนจะรัน BPE ต้องอ่านไฟล์ vocabulary จากดิสก์ก่อน
- ไฟล์ต้นฉบับเป็นรูปแบบที่ออกแบบมาให้ Python อ่าน ไม่ใช่รูปแบบที่ parse ได้ง่ายด้วยโค้ด C ขนาดเล็ก
- ไฟล์นี้ไม่ใช่รายการคำ แต่เป็น รายการการ merge ของ BPE
- ตัวอย่างเช่น แทนที่จะเก็บโทเค็น
Helloตรง ๆ ไฟล์จะเก็บในลักษณะว่าต้อง mergeHกับello
- ตัวอย่างเช่น แทนที่จะเก็บโทเค็น
- ไฟล์ใช้อีกรูปแบบการเข้ารหัสที่คล้าย UTF-8 แต่ไม่เหมือนกันเสียทีเดียว
- อักขระ ASCII ที่พิมพ์ได้จะถูกเก็บตรง ๆ
- อักขระที่พิมพ์ไม่ได้ในช่วง 0~31 จะถูกเข้ารหัสเป็น
188 + ค่าของอักขระ - ตัวอย่างเช่น ช่องว่างจะถูกเข้ารหัสเป็นโทเค็น
Ġ
- บนดิสก์
Ġอยู่ในรูป UTF-8 เป็น0xc4 0xa0จึงต้องมีการประมวลผลเพิ่มเติมเพื่อแปลงกลับเป็นช่องว่าง
สิ่งที่โค้ดขนาดเล็กนี้แสดงให้เห็น
- ความก้าวหน้าของแมชชีนเลิร์นนิงหลายสิบปีสามารถบีบอัดลงในโค้ดขนาดไม่กี่พันไบต์ได้
- หากไม่นับน้ำหนักโมเดลจริง องค์ประกอบที่จำเป็นสำหรับการรันโครงข่ายประสาทสมัยใหม่แทบไม่ขาดไปเลย
- แม้การอิมพลีเมนต์นี้จะถูกสร้างขึ้นเพื่อความสนุกเป็นหลัก แต่มันก็เป็นตัวอย่างที่แสดงให้เห็นว่าโครงข่ายประสาทสามารถทำงานได้จริงด้วยองค์ประกอบที่เรียบง่าย
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ยังไม่ได้ลองรันโค้ดเอง แต่สิ่งที่น่าประทับใจคือ ขนาดที่เล็ก
เมื่อนึกว่าโปรแกรม ELIZA ยุคแรก ๆ ยังใหญ่กว่านี้ ก็เท่ากับว่าในช่วง 4 ปีที่ผ่านมา เราสามารถยัดของแบบนี้ลงไปได้ในระดับไบต์แล้ว
ถ้าใครรู้ว่าเวทมนตร์ซ่อนอยู่ตรงไหนก็อยากให้ช่วยอธิบายหน่อย สงสัยว่าเป็น ฟังก์ชัน GELU หรือเป็นโมเดลที่ดาวน์โหลดผ่านสคริปต์ Bash กันแน่
ถามว่า
Who are you?มันตอบว่าI am Alice.และถ้าถามเรื่องคอมพิวเตอร์หรือความสามารถ ก็จะพูดซ้ำว่าI am a computer model trained by OpenAI. How can I help you?ถ้าขอให้อธิบายการบวก มันกลับอธิบายการคูณ และกับ
2+2หรือSum 2+2ก็แค่พูดตามเดิมเท่านั้นยังจำได้ตอนที่ GPT-2 ออกมาใหม่ ๆ แล้วได้ลองเล่น
ผมกับเพื่อน export log แชตออกมา แล้ว fine-tune GPT-2 ให้มันเลียนแบบบทสนทนาระหว่างเราสองคน ซึ่งตลกมาก แต่บางครั้งก็แม่นจนน่าขนลุก
สงสัยว่าการ ก้าวกระโดดครั้งใหญ่ จาก GPT-2 ไป GPT-3 เกิดจากอะไร เป็นโมเดลที่ใหญ่ขึ้น ข้อมูลที่มากขึ้น หรือทั้งสองอย่างกันแน่
รู้ว่า RLHF สร้างความต่างอย่างมาก แต่แม้แต่โมเดล GPT-3 พื้นฐาน ถ้าให้ตัวอย่างมากพอ ก็ใช้งานได้ค่อนข้างดีแค่ด้วย text completion
ไม่ค่อยแน่ใจเหมือนกัน แต่มีนิทานที่ GPT-2 เขียนให้ซึ่งผมชอบอยู่
https://deepdreams.stavros.io/episodes/the-princess-the-fair...
สงสัยว่าหน้านี้ทำด้วย GPT-2 หรือเปล่า
ข้อความที่ว่า “ส่วนใหญ่ทำขึ้นเพื่อความสนุก แต่ก็เป็นตัวอย่างที่ดีที่แสดงให้เห็นว่า neural network จริง ๆ แล้วเรียบง่ายแค่ไหน” น่าสนุกดี
ชู่ว อย่าไปบอกใครนะ ปัญญาประดิษฐ์ คือไสยศาสตร์ดำที่เอาไว้หาเงิน
GPT-2 ถูก instruction tuning ไว้จนใช้แชตจริงได้หรือเปล่า?
ถ้าไม่ใช่ ผมรู้สึกว่าการเรียกสิ่งนี้ว่า ChatGPT clone ก็ค่อนข้างฝืนมาก
โดยพื้นฐานแล้วแทบใช้งานไม่ได้ และแทบไม่เกี่ยวข้องอะไรนอกจากหยิบชื่อมาใช้ ถึงอย่างนั้นมันก็เป็นโปรแกรมที่คอมไพล์และรันได้จริง
เมื่อเห็นคนยกย่องประสิทธิภาพของโปรเจกต์ที่ผู้สร้างเองก็ยอมรับว่าทำงานได้ไม่ดีนัก ก็เหมือนว่าแก่นหลักคือการ ดึงความสนใจด้วย buzzword นั่นแหละ
ประโยคที่ว่า “ภาษาอื่น ๆ ที่มีมาโครดี ๆ เห็นหรือยัง Lisp ไม่ได้ดีกว่า C เสมอไปนะ!” ครั้งนี้ถือว่ายอมรับได้ เพราะเป็นมุกแซวขึ้นข้างบน
ถ้าไม่เห็นลิงก์โค้ด มันถูกฝังอยู่ในเนื้อหา: https://github.com/carlini/c-chat-gpt-2
เคยเห็น แชตบอตปัญญาประดิษฐ์ แบบคลาสสิกที่ดีกว่านี้ด้วย
https://www.cs.cmu.edu/afs/cs/project/ai-repository/ai/areas...
Splotch แก้นิดหน่อยก็ยังคอมไพล์บนระบบตระกูล Unix สมัยใหม่ได้ดี
สงสัยว่ามีใครลองรันในเครื่องตัวเองแล้วดูว่า GPT-2 ตัวนี้ให้ผลลัพธ์แบบไหนบ้างหรือยัง
แต่ก็ยังน่าสนใจพอสมควร และอยากลองแกะดูแล้วปรับแต่งเอง ผมอยากลองเล่น GPT-2 บนเครื่องตัวเองมาสักพักแล้ว
ผมไม่ได้ตรวจอุณหภูมิและ seed ในโค้ดโดยตรง และตอนนั้นส่วนใหญ่กำลังดูว่าทำไมถึง obfuscate
ต่อให้แกะ obfuscation ออก โค้ดก็คงไม่ได้ยาวขึ้นมากนัก และถ้าอยู่ราว ๆ 10,000 ตัวอักษร แค่ดูบนหน้าจอก็น่าประทับใจพอแล้ว
ทุกวันนี้ถ้าใช้ gptscript ก็สร้าง ChatGPT ของตัวเองได้อย่างรวดเร็ว
https://github.com/gptscript-ai/gptscript
GELU นี่เหมือนเวทมนตร์จริง ๆ:
UNARY(GELU, b / 2 * (1 + tanh(.7978845 * (b + .044715 * b * b * b))))นิยามคือ
GELU(x) := x * Φ(x)โดยที่ Φ(x) คือ ฟังก์ชันแจกแจงสะสมของการแจกแจงแบบเกาส์