LLaMA 3 เป็นหนึ่งในโมเดลโอเพ่นซอร์สที่มีแนวโน้มมากที่สุดรองจาก Mistral ซึ่งแก้ไขงานได้หลากหลาย ก่อนหน้านี้ฉันเคยเขียนบล็อกบน Medium เกี่ยวกับการสร้าง LLM ด้วยพารามิเตอร์มากกว่า 2.3 ล้านพารามิเตอร์ตั้งแต่เริ่มต้นโดยใช้สถาปัตยกรรม LLaMA ตอนนี้ LLaMA-3 เปิดตัวแล้ว เราจะสร้างมันขึ้นมาใหม่ในลักษณะที่เรียบง่ายยิ่งขึ้น
เราจะไม่ใช้ GPU สำหรับบล็อกนี้ แต่คุณต้องมี RAM อย่างน้อย 17 GB เนื่องจากเราจะโหลดไฟล์บางไฟล์ที่มีขนาดเกิน 15 GB หากนี่เป็นปัญหาสำหรับคุณ คุณสามารถใช้ Kaggle เป็นวิธีการแก้ปัญหาได้ เนื่องจากเราไม่ต้องการ GPU Kaggle จึงมี RAM ขนาด 30 GB ในขณะที่ใช้เฉพาะแกน CPU เป็นตัวเร่งความเร็ว
นี่คือลิงก์บล็อกที่จะแนะนำคุณเกี่ยวกับวิธีสร้าง LLM พารามิเตอร์ 2.3+ ล้านตั้งแต่เริ่มต้น: LLM พารามิเตอร์ 2.3+ ล้านตั้งแต่เริ่มต้น
ส่วนที่ดีคือเราจะไม่ใช้การเข้ารหัสการเขียนโปรแกรมเชิงวัตถุ (OOP) เพียงการเขียนโปรแกรม Python ธรรมดา อย่างไรก็ตาม คุณควรมีความเข้าใจพื้นฐานเกี่ยวกับโครงข่ายประสาทเทียมและสถาปัตยกรรม Transformer นี่เป็นข้อกำหนดเบื้องต้นสองประการเท่านั้นที่ต้องปฏิบัติตามพร้อมกับบล็อก
หัวข้อ | ลิงค์ |
---|---|
ทฤษฎีหม้อแปลงไฟฟ้า | ลิงค์วิดีโอ |
ทฤษฎีโครงข่ายประสาทเทียม | ลิงค์วิดีโอ |
พื้นฐานหลาม | ลิงค์วิดีโอ |
ก่อนที่จะดูรายละเอียดทางเทคนิค สิ่งแรกที่คุณต้องรู้คือสถาปัตยกรรมทั้งหมดของ LLaMA 3 นั้นเหมือนกับ LLaMA 2 ดังนั้น หากคุณยังไม่ได้อ่านรายละเอียดทางเทคนิคของ LLaMA 3 ก็จะไม่เป็นเช่นนั้น ปัญหาสำหรับคุณในการติดตามบล็อกนี้ แม้ว่าคุณจะไม่มีความเข้าใจเกี่ยวกับสถาปัตยกรรม LLaMA 2 แต่อย่ากังวล เราจะดูภาพรวมระดับสูงของรายละเอียดทางเทคนิคด้วย บล็อกนี้ออกแบบมาเพื่อคุณไม่ว่าจะด้วยวิธีใดก็ตาม
ต่อไปนี้เป็นประเด็นสำคัญบางประการเกี่ยวกับ LLaMA 2 และ LLaMA 3 หากคุณคุ้นเคยกับสถาปัตยกรรมของ LLaMA 2 แล้ว:
คุณสมบัติ | ลามะ 3 | ลามะ 2 |
---|---|---|
โทเค็นไนเซอร์ | Tiktoken (พัฒนาโดย OpenAI) | ประโยคชิ้น |
จำนวนพารามิเตอร์ | 8B, 70B | 70B, 13B, 7B |
ข้อมูลการฝึกอบรม | โทเค็น 15T | โทเค็น 2.2T |
ความยาวบริบท | โทเค็น 8192 | โทเค็น 4096 |
กลไกความสนใจ | ความสนใจแบบสอบถามแบบกลุ่ม | ความสนใจแบบสอบถามแบบกลุ่ม |
โมเดลที่ได้รับการปรับแต่งอย่างละเอียด | ใช่ | ใช่ |
ผลงาน | ดีกว่า Llama 2 ในเกณฑ์มาตรฐานทั้งหมด | ดีกว่า Llama 1 ในการวัดประสิทธิภาพส่วนใหญ่ |
ข้อกำหนดด้านการคำนวณ | สูงมาก (รุ่น 70B) | สูงมาก (รุ่น 70B) |
ความพร้อมใช้งาน | โอเพ่นซอร์ส | โอเพ่นซอร์ส |
การเรียนรู้การเสริมกำลังจากผลตอบรับของมนุษย์ | ใช่ | ใช่ |
จำนวนภาษาที่รองรับ | 30 ภาษา | 20 ภาษา |
เหมาะสำหรับ | ดีที่สุดสำหรับงานที่มีความต้องการมากขึ้น เช่น การใช้เหตุผล การเขียนโค้ด และการทดสอบความเชี่ยวชาญ | เหมาะสำหรับงานที่มีความต้องการมากขึ้น เช่น การใช้เหตุผล การเขียนโค้ด และการทดสอบความสามารถ |
การทำความเข้าใจสถาปัตยกรรมของ LLaMA 3 เป็นสิ่งสำคัญก่อนที่จะเริ่มเขียนโค้ด เพื่อความเข้าใจด้วยภาพที่ดีขึ้น ต่อไปนี้เป็นแผนภาพเปรียบเทียบระหว่าง vanilla Transformer, LLaMA 2/3 และ Mistral
มาดูส่วนประกอบที่สำคัญที่สุดของ LLaMA 3 พร้อมรายละเอียดเพิ่มเติมอีกเล็กน้อย:
ในแนวทาง LLaMA 3 ซึ่งเหมือนกับ LLaMA 2 เทคนิคที่เรียกว่า RMSNorm ใช้สำหรับการปรับอินพุตของเลเยอร์ย่อยของหม้อแปลงแต่ละตัวให้เป็นมาตรฐาน
ลองนึกภาพว่าคุณกำลังอ่านหนังสือสอบเล่มใหญ่ และคุณมีหนังสือเรียนเล่มใหญ่ที่เต็มไปด้วยบทต่างๆ แต่ละบทแสดงถึงหัวข้อที่แตกต่างกัน แต่บางบทมีความสำคัญต่อการทำความเข้าใจเนื้อหามากกว่าบทอื่นๆ ตอนนี้ ก่อนที่จะอ่านตำราเรียนทั้งเล่ม คุณต้องตัดสินใจประเมินความสำคัญของแต่ละบท คุณคงไม่อยากใช้เวลาเท่ากันในทุกบท คุณต้องการเน้นไปที่สิ่งที่สำคัญมากขึ้น นี่คือจุดที่การทำให้เป็นมาตรฐานล่วงหน้าโดยใช้ RMSNorm เข้ามามีบทบาทในโมเดลภาษาขนาดใหญ่ (LLM) เช่น ChatGPT มันเหมือนกับการให้น้ำหนักแก่แต่ละบทตามความสำคัญของบท บทที่เป็นพื้นฐานของวิชาจะมีน้ำหนักมากกว่า ส่วนบทที่มีความสำคัญน้อยกว่าจะมีน้ำหนักน้อยกว่า
ดังนั้น ก่อนที่จะศึกษาแบบเจาะลึก คุณจะต้องปรับแผนการศึกษาตามความสำคัญแบบถ่วงน้ำหนักของแต่ละบท คุณจัดสรรเวลาและความพยายามให้กับบทที่มีน้ำหนักมากกว่า เพื่อให้มั่นใจว่าคุณจะเข้าใจแนวคิดหลักได้อย่างละเอียด
ในทำนองเดียวกัน การทำให้เป็นมาตรฐานล่วงหน้าโดยใช้ RMSNorm ช่วยให้ LLM จัดลำดับความสำคัญว่าส่วนใดของข้อความมีความสำคัญต่อการทำความเข้าใจบริบทและความหมายมากกว่า โดยจะกำหนดน้ำหนักที่สูงกว่าให้กับองค์ประกอบสำคัญ และน้ำหนักที่ลดลงให้กับองค์ประกอบที่มีความสำคัญน้อยกว่า เพื่อให้มั่นใจว่าโมเดลจะมุ่งความสนใจไปที่จุดที่จำเป็นที่สุดเพื่อความเข้าใจที่แม่นยำ ผู้อ่านที่สนใจสามารถสำรวจการใช้งาน RMSNorm โดยละเอียดได้ที่นี่
LLaMA แนะนำฟังก์ชันการเปิดใช้งาน SwiGLU ซึ่งได้รับแรงบันดาลใจจาก PaLM
ลองนึกภาพคุณเป็นครูที่พยายามอธิบายหัวข้อที่ซับซ้อนให้นักเรียนฟัง คุณมีไวท์บอร์ดขนาดใหญ่สำหรับจดประเด็นสำคัญและวาดไดอะแกรมเพื่อทำให้สิ่งต่าง ๆ ชัดเจนยิ่งขึ้น แต่บางครั้งลายมือของคุณอาจไม่เรียบร้อยมาก หรือไดอะแกรมของคุณอาจวาดได้ไม่สมบูรณ์แบบ ซึ่งจะทำให้นักเรียนเข้าใจเนื้อหาได้ยากขึ้น
ทีนี้ ลองจินตนาการว่าคุณมีปากกาวิเศษที่จะปรับขนาดและรูปแบบของลายมือของคุณโดยอัตโนมัติตามความสำคัญของแต่ละจุดหรือไม่ หากมีบางสิ่งที่สำคัญจริงๆ ปากกาจะเขียนให้ใหญ่ขึ้นและชัดเจนยิ่งขึ้น ทำให้มันโดดเด่น หากมีความสำคัญน้อยกว่า ปากกาจะเขียนให้เล็กลงแต่ยังคงอ่านได้ SwiGLU เปรียบเสมือนปากกาวิเศษสำหรับโมเดลภาษาขนาดใหญ่ (LLM) เช่น ChatGPT ก่อนที่จะสร้างข้อความ SwiGLU จะปรับความสำคัญของแต่ละคำหรือวลีตามความเกี่ยวข้องกับบริบท เช่นเดียวกับปากกาวิเศษปรับขนาดและสไตล์การเขียนของคุณ SwiGLU ปรับการเน้นของแต่ละคำหรือวลี
ดังนั้น เมื่อ LLM สร้างข้อความ จะสามารถเพิ่มความโดดเด่นให้กับส่วนสำคัญๆ ได้มากขึ้น ทำให้มองเห็นได้ชัดเจนยิ่งขึ้น และช่วยให้มั่นใจว่ามีส่วนช่วยในการทำความเข้าใจเนื้อหาโดยรวมมากขึ้น ด้วยวิธีนี้ SwiGLU ช่วยให้ LLM สร้างข้อความที่ชัดเจนและเข้าใจง่ายขึ้น เช่นเดียวกับที่ปากกาวิเศษช่วยให้คุณสร้างคำอธิบายที่ชัดเจนยิ่งขึ้นสำหรับนักเรียนของคุณบนไวท์บอร์ด รายละเอียดเพิ่มเติมเกี่ยวกับ SwiGLU สามารถพบได้ในเอกสารที่เกี่ยวข้อง
การฝังแบบหมุนหรือ RoPE เป็นการฝังตำแหน่งประเภทหนึ่งใน LLaMA 3
ลองนึกภาพคุณอยู่ในห้องเรียน และต้องการกำหนดที่นั่งให้นักเรียนเพื่อสนทนากลุ่ม โดยปกติแล้ว คุณอาจจัดที่นั่งเป็นแถวและคอลัมน์ โดยให้นักเรียนแต่ละคนมีตำแหน่งที่แน่นอน อย่างไรก็ตาม ในบางกรณี คุณต้องการสร้างการจัดที่นั่งแบบไดนามิกมากขึ้น ซึ่งนักเรียนสามารถเดินไปรอบๆ และโต้ตอบได้อย่างอิสระมากขึ้น
ROPE เปรียบเสมือนการจัดที่นั่งแบบพิเศษที่ช่วยให้นักเรียนสามารถหมุนและเปลี่ยนตำแหน่งได้ โดยที่ยังคงรักษาตำแหน่งที่สัมพันธ์กันไว้ แทนที่จะยึดติดกับที่เดียว ตอนนี้นักเรียนสามารถเคลื่อนที่เป็นวงกลมได้ ซึ่งช่วยให้มีปฏิสัมพันธ์ที่ลื่นไหลมากขึ้น
ในสถานการณ์สมมตินี้ นักเรียนแต่ละคนแทนคำหรือโทเค็นในลำดับข้อความ และตำแหน่งของพวกเขาสอดคล้องกับตำแหน่งของพวกเขาในลำดับ เช่นเดียวกับวิธีที่ ROPE อนุญาตให้นักเรียนหมุนและเปลี่ยนตำแหน่ง ROPE อนุญาตให้การฝังตำแหน่งคำในลำดับข้อความเปลี่ยนแปลงแบบไดนามิกตามตำแหน่งที่สัมพันธ์กัน ดังนั้น เมื่อประมวลผลข้อความ แทนที่จะถือว่าการฝังตำแหน่งเป็นแบบคงที่และคงที่ ROPE นำเสนอลักษณะการหมุน ซึ่งช่วยให้การนำเสนอมีความยืดหยุ่นมากขึ้น ซึ่งจับความสัมพันธ์แบบไดนามิกระหว่างคำในลำดับ ความยืดหยุ่นนี้ช่วยให้โมเดลอย่าง ChatGPT เข้าใจได้ดีขึ้นและสร้างข้อความที่ลื่นไหลเป็นธรรมชาติและรักษาความสอดคล้องกัน คล้ายกับวิธีที่การจัดที่นั่งแบบไดนามิกส่งเสริมการสนทนาที่มีการโต้ตอบมากขึ้นในห้องเรียน ผู้ที่สนใจรายละเอียดทางคณิตศาสตร์สามารถดูเอกสาร RoPE ได้
LLaMA 3 ใช้การเข้ารหัสคู่ไบต์ (BPE) จากไลบรารี tiktoken ที่นำเสนอโดย OpenAI ในขณะที่โทเค็น LLaMA 2 BPE จะขึ้นอยู่กับไลบรารีประโยค มีความแตกต่างเล็กน้อยระหว่างพวกเขา แต่
ก่อนอื่น มาเรียนรู้ว่าจริงๆ แล้ว BPE คืออะไร
เริ่มต้นด้วยตัวอย่างง่ายๆ สมมติว่าเรามีคลังข้อความที่มีคำว่า "ab", "bc", "bcd" และ "cde" เราเริ่มต้นด้วยการเริ่มต้นคำศัพท์ของเราด้วยอักขระแต่ละตัวในคลังข้อความ ดังนั้นคำศัพท์เริ่มต้นของเราคือ {"a", "b", "c", "d", "e"}
ต่อไป เราจะคำนวณความถี่ของอักขระแต่ละตัวในคลังข้อความ สำหรับตัวอย่างของเรา ความถี่คือ: {"a": 1, "b": 3, "c": 3, "d": 2, "e": 1}
ตอนนี้เราเริ่มกระบวนการรวม เราทำซ้ำขั้นตอนต่อไปนี้จนกว่าคำศัพท์ของเราจะถึงขนาดที่ต้องการ:
ขั้นแรก เราจะค้นหาคู่ของอักขระที่ต่อเนื่องกันบ่อยที่สุด ในกรณีนี้ คู่ที่พบบ่อยที่สุดคือ "bc" โดยมีความถี่เป็น 2 จากนั้นเราจะรวมคู่นี้เพื่อสร้างหน่วยคำย่อยใหม่ "bc" หลังจากการรวมเข้าด้วยกัน เราจะอัปเดตการนับความถี่เพื่อให้สอดคล้องกับหน่วยคำย่อยใหม่ ความถี่ที่อัปเดตคือ {"a": 1, "b": 2, "c": 2, "d": 2, "e": 1, "bc": 2} เราเพิ่มหน่วยคำย่อยใหม่ "bc" ลงในคำศัพท์ของเรา ซึ่งตอนนี้กลายเป็น {"a", "b", "c", "d", "e", "bc"}
เราทำซ้ำกระบวนการ คู่ที่พบบ่อยที่สุดถัดไปคือ "cd" เรารวม "cd" เข้าด้วยกันเพื่อสร้างหน่วยคำย่อยใหม่ "cd" และอัปเดตจำนวนความถี่ ความถี่ที่อัปเดตคือ {"a": 1, "b": 2, "c": 1, "d": 1, "e": 1, "bc": 2, "cd": 2} เราเติม "cd" ลงในคำศัพท์ ผลลัพธ์ที่ได้คือ {"a", "b", "c", "d", "e", "bc", "cd"}
ดำเนินกระบวนการต่อไป คู่ที่พบบ่อยถัดไปคือ "de" เรารวม "de" เพื่อสร้างหน่วยคำย่อย "de" และอัปเดตความถี่เป็น {"a": 1, "b": 2, "c": 1, "d": 1, "e": 0, "bc": 2, "cd": 1, "de": 1} เราเติม "de" เข้าไปในคำศัพท์ ทำให้เป็น {"a", "b", "c", "d", "e", "bc", "cd", "de"}
ต่อไปเราจะพบว่า "ab" เป็นคู่ที่พบบ่อยที่สุด เรารวม "ab" เข้าด้วยกันเพื่อสร้างหน่วยคำย่อย "ab" และอัปเดตความถี่เป็น {"a": 0, "b": 1, "c": 1, "d": 1, "e": 0, "bc": 2, "cd": 1, "de": 1, "ab": 1}
เราเติม "ab" เข้าไปในคำศัพท์ ซึ่งจะกลายเป็น {"a", "b", "c", "d", "e", "bc", "cd", "de", "ab"}
จากนั้นคู่ที่พบบ่อยถัดไปคือ "bcd" เรารวม "bcd" เพื่อสร้างหน่วยคำย่อย "bcd" และอัปเดตความถี่เป็น {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "bc": 1, "cd": 0, "de": 1, "ab": 1, "bcd": 1} เราเพิ่ม "bcd" ลงในคำศัพท์ส่งผลให้ {"a", "b", "c", "d", "e", "bc", "cd", "de", "ab", "bcd" "}
สุดท้ายคู่ที่พบบ่อยที่สุดคือ "cde" เรารวม "cde" เพื่อสร้างหน่วยคำย่อย "cde" และอัปเดตความถี่เป็น {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "bc": 1, "cd": 0, "de": 0, "ab": 1, "bcd": 1, "cde": 1} เราเติม "cde" เข้าไปในคำศัพท์ ทำให้เป็น {"a", "b", "c", "d", "e", "bc", "cd", "de", "ab", "bcd" "," ซีดีอี"}.
เทคนิคนี้สามารถปรับปรุงประสิทธิภาพของ LLM และจัดการกับคำที่หายากและคำศัพท์ที่ไม่อยู่ในนั้น ข้อแตกต่างที่สำคัญระหว่าง TikToken BPE และประโยค BPE ก็คือ TikToken BPE ไม่ได้แบ่งคำออกเป็นส่วนเล็กๆ เสมอไปหากรู้จักทั้งคำแล้ว ตัวอย่างเช่น หากคำว่า "กอด" อยู่ในคำศัพท์ คำนั้นจะยังคงเป็นโทเค็นเดียวแทนที่จะแบ่งออกเป็น ["กอด", "การกอด"]
เราจะทำงานร่วมกับไลบรารี Python กลุ่มเล็กๆ แต่จะเป็นการดีกว่าถ้าติดตั้งไลบรารีเหล่านั้นเพื่อหลีกเลี่ยงไม่ให้พบข้อผิดพลาด "ไม่พบโมดูล"
!p ip install sentencepiece tiktoken torch blobfile matplotlib huggingface_hub
Requirement already satisfied: sentencepiece in /opt/conda/lib/python3.10/site-packages (0.2.0)
Requirement already satisfied: tiktoken in /opt/conda/lib/python3.10/site-packages (0.7.0)
Requirement already satisfied: torch in /opt/conda/lib/python3.10/site-packages (2.1.2+cpu)
Requirement already satisfied: blobfile in /opt/conda/lib/python3.10/site-packages (2.1.1)
Requirement already satisfied: matplotlib in /opt/conda/lib/python3.10/site-packages (3.7.5)
Requirement already satisfied: huggingface_hub in /opt/conda/lib/python3.10/site-packages (0.22.2)
Requirement already satisfied: regex>=2022.1.18 in /opt/conda/lib/python3.10/site-packages (from tiktoken) (2023.12.25)
Requirement already satisfied: requests>=2.26.0 in /opt/conda/lib/python3.10/site-packages (from tiktoken) (2.31.0)
Requirement already satisfied: filelock in /opt/conda/lib/python3.10/site-packages (from torch) (3.13.1)
Requirement already satisfied: typing-extensions in /opt/conda/lib/python3.10/site-packages (from torch) (4.9.0)
Requirement already satisfied: sympy in /opt/conda/lib/python3.10/site-packages (from torch) (1.12)
Requirement already satisfied: networkx in /opt/conda/lib/python3.10/site-packages (from torch) (3.2.1)
Requirement already satisfied: jinja2 in /opt/conda/lib/python3.10/site-packages (from torch) (3.1.2)
Requirement already satisfied: fsspec in /opt/conda/lib/python3.10/site-packages (from torch) (2024.2.0)
Requirement already satisfied: pycryptodomex~=3.8 in /opt/conda/lib/python3.10/site-packages (from blobfile) (3.20.0)
Requirement already satisfied: urllib3<3,>=1.25.3 in /opt/conda/lib/python3.10/site-packages (from blobfile) (1.26.18)
Requirement already satisfied: lxml~=4.9 in /opt/conda/lib/python3.10/site-packages (from blobfile) (4.9.4)
Requirement already satisfied: contourpy>=1.0.1 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (1.2.0)
Requirement already satisfied: cycler>=0.10 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (4.47.0)
Requirement already satisfied: kiwisolver>=1.0.1 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (1.4.5)
Requirement already satisfied: numpy<2,>=1.20 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (1.26.4)
Requirement already satisfied: packaging>=20.0 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (21.3)
Requirement already satisfied: pillow>=6.2.0 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (9.5.0)
Requirement already satisfied: pyparsing>=2.3.1 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (3.1.1)
Requirement already satisfied: python-dateutil>=2.7 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (2.9.0.post0)
Requirement already satisfied: pyyaml>=5.1 in /opt/conda/lib/python3.10/site-packages (from huggingface_hub) (6.0.1)
Requirement already satisfied: tqdm>=4.42.1 in /opt/conda/lib/python3.10/site-packages (from huggingface_hub) (4.66.1)
Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.10/site-packages (from python-dateutil>=2.7->matplotlib) (1.16.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /opt/conda/lib/python3.10/site-packages (from requests>=2.26.0->tiktoken) (3.3.2)
Requirement already satisfied: idna<4,>=2.5 in /opt/conda/lib/python3.10/site-packages (from requests>=2.26.0->tiktoken) (3.6)
Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/lib/python3.10/site-packages (from requests>=2.26.0->tiktoken) (2024.2.2)
Requirement already satisfied: MarkupSafe>=2.0 in /opt/conda/lib/python3.10/site-packages (from jinja2->torch) (2.1.3)
Requirement already satisfied: mpmath>=0.19 in /opt/conda/lib/python3.10/site-packages (from sympy->torch) (1.3.0)
หลังจากติดตั้งไลบรารีที่จำเป็นแล้ว เราจำเป็นต้องดาวน์โหลดไฟล์บางไฟล์ เนื่องจากเราจะจำลองสถาปัตยกรรมของ llama-3–8B คุณต้องมีบัญชีบน HuggingFace นอกจากนี้ เนื่องจาก llama-3 เป็นโมเดลที่มีรั้วรอบขอบชิด คุณต้องยอมรับข้อกำหนดและเงื่อนไขเพื่อเข้าถึงเนื้อหาของโมเดล
นี่คือขั้นตอน:
เมื่อคุณทำทั้งสองขั้นตอนเสร็จแล้ว ตอนนี้เราจะต้องดาวน์โหลดไฟล์บางไฟล์ มีสองทางเลือกในการทำเช่นนั้น:
(ตัวเลือกที่ 1: ด้วยตนเอง) ไปที่ไดเร็กทอรี llama-3–8B HF จากลิงก์นี้ และดาวน์โหลดไฟล์ทั้งสามไฟล์เหล่านี้ด้วยตนเอง
(ตัวเลือกที่ 2: การเขียนโค้ด) เราสามารถใช้ไลบรารี Hugging_face ที่เราติดตั้งไว้ก่อนหน้านี้ เพื่อดาวน์โหลดไฟล์เหล่านี้ทั้งหมด อย่างไรก็ตาม ก่อนอื่น เราต้องเข้าสู่ระบบ HuggingFace Hub ภายในสมุดบันทึกการทำงานของเราโดยใช้ HF Token ของเรา คุณสามารถสร้างโทเค็นใหม่หรือเข้าถึงได้จากลิงก์นี้
# Import the `notebook_login` function from the `huggingface_hub` module.
from huggingface_hub import notebook_login
# Execute the `notebook_login` function to log in to the Hugging Face Hub.
notebook_login ()
VBox(children=(HTML(value='<center> <imgnsrc=https://huggingface.co/front/assets/huggingface_logo-noborder.sv…
เมื่อคุณเรียกใช้เซลล์นี้ ระบบจะขอให้คุณป้อนโทเค็น หากมีข้อผิดพลาดระหว่างการเข้าสู่ระบบ ให้ลองอีกครั้ง แต่อย่าลืมยกเลิกการเลือกเพิ่มโทเค็นเป็นข้อมูลรับรอง git หลังจากนั้น เราเพียงแค่ต้องเรียกใช้โค้ด Python แบบง่ายๆ เพื่อดาวน์โหลดไฟล์สามไฟล์ที่เป็นแกนหลักของสถาปัตยกรรม llama-3–8B
# Import the necessary function from the huggingface_hub library
from huggingface_hub import hf_hub_download
# Define the repository information
repo_id = "meta-llama/Meta-Llama-3-8B"
subfolder = "original" # Specify the subfolder within the repository
# List of filenames to download
filenames = [ "params.json" , "tokenizer.model" , "consolidated.00.pth" ]
# Specify the directory where you want to save the downloaded files
save_directory = "llama-3-8B/" # Replace with your desired path
# Download each file
for filename in filenames :
hf_hub_download (
repo_id = repo_id , # Repository ID
filename = filename , # Name of the file to download
subfolder = subfolder , # Subfolder within the repository
local_dir = save_directory # Directory to save the downloaded file
)
original/params.json: 0%| | 0.00/211 [00:00<?, ?B/s]
original/tokenizer.model: 0%| | 0.00/2.18M [00:00<?, ?B/s]
original/consolidated.00.pth: 0%| | 0.00/16.1G [00:00<?, ?B/s]
เมื่อดาวน์โหลดไฟล์ทั้งหมดแล้ว เราจำเป็นต้องนำเข้าไลบรารีที่เราจะใช้ในบล็อกนี้
# File system paths
from pathlib import Path
# Tokenization library
import tiktoken
# BPE loading function
from tiktoken . load import load_tiktoken_bpe
# PyTorch library
import torch
# JSON handling
import json
# Plotting library
import matplotlib . pyplot as plt
ต่อไปเราต้องเข้าใจว่าแต่ละไฟล์จะใช้ทำอะไร
เนื่องจากเรามุ่งเป้าไปที่การจำลอง llama-3 อย่างแน่นอน นั่นหมายความว่าข้อความที่ป้อนของเราต้องให้ผลลัพธ์ที่มีความหมาย ตัวอย่างเช่น หากอินพุตของเราคือ "สีของดวงอาทิตย์คือ?" ผลลัพธ์จะต้องเป็น "สีขาว" การบรรลุเป้าหมายนี้ต้องอาศัยการฝึกอบรม LLM ของเราบนชุดข้อมูลขนาดใหญ่ ซึ่งต้องใช้พลังในการคำนวณสูง ทำให้เราเป็นไปไม่ได้
อย่างไรก็ตาม Meta ได้เปิดตัวไฟล์สถาปัตยกรรม llama-3 ต่อสาธารณะหรือในแง่ที่ซับซ้อนกว่านั้นคือน้ำหนักที่ฝึกไว้ล่วงหน้าสำหรับการใช้งาน เราเพิ่งดาวน์โหลดไฟล์เหล่านี้ ทำให้เราสามารถจำลองสถาปัตยกรรมของไฟล์เหล่านี้ได้โดยไม่จำเป็นต้องฝึกอบรมหรือใช้ชุดข้อมูลขนาดใหญ่ ทุกอย่างถูกจัดเตรียมไว้แล้ว เราแค่ต้องใช้ส่วนประกอบที่ถูกต้องในตำแหน่งที่เหมาะสม
ดูไฟล์แต่ละไฟล์เหล่านี้และความสำคัญ:
tokenizer.model - ดังที่เราได้พูดคุยกันก่อนหน้านี้ LLaMA-3 ใช้โทเค็นการเข้ารหัสคู่ไบต์ (BPE) จาก tiktoken ซึ่งได้รับการฝึกฝนบนชุดข้อมูลที่มีโทเค็น 15 ล้านล้านโทเค็น ซึ่งใหญ่กว่าชุดข้อมูลที่ใช้สำหรับ LLaMA-2 ถึง 7 เท่า ลองโหลดไฟล์นี้มาดูว่ามีอะไรบ้าง
# Loading the tokenizer from llama-3-8B
tokenizer_model = load_tiktoken_bpe ( "/kaggle/working/llama-3-8B/original/tokenizer.model" )
# Get the length of the tokenizer model
len ( tokenizer_model )
# OUTPUT: 128000
# Get the type of the `tokenizer_model` object.
type ( tokenizer_model )
# OUTPUT: dictionary
dict
แอ็ตทริบิวต์ length แสดงขนาดคำศัพท์ทั้งหมด ซึ่งเป็นจำนวนอักขระเฉพาะในข้อมูลการฝึก ประเภทของ tokenizer_model เป็นพจนานุกรม
# Printing the first 10 items of tokenizer model
dict ( list ( tokenizer_model . items ())[ 5600 : 5610 ])
{b'mitted': 5600,
b" $('#": 5601,
b' saw': 5602,
b' approach': 5603,
b'ICE': 5604,
b' saying': 5605,
b' anyone': 5606,
b'meta': 5607,
b'SD': 5608,
b' song': 5609}
เมื่อเราพิมพ์รายการสุ่ม 10 รายการจากนั้น คุณจะเห็นสตริงที่สร้างขึ้นโดยใช้อัลกอริธึม BPE คล้ายกับตัวอย่างที่เราพูดถึงก่อนหน้านี้ คีย์ที่แสดงถึงลำดับไบต์จากการฝึก BPE ในขณะที่ค่าแสดงถึงอันดับการผสานตามความถี่
consolidated.00.pth - มีพารามิเตอร์ที่เรียนรู้ (น้ำหนัก) ของ Llama-3–8B พารามิเตอร์เหล่านี้ประกอบด้วยข้อมูลเกี่ยวกับวิธีที่โมเดลเข้าใจและประมวลผลภาษา เช่น วิธีแสดงโทเค็น คำนวณความสนใจ ทำการแปลงฟีดไปข้างหน้า และปรับเอาต์พุตให้เป็นมาตรฐาน
# Loading a PyTorch model of LLaMA-3-8B
model = torch . load ( "/kaggle/working/llama-3-8B/original/consolidated.00.pth" )
# printing first 11 layers of the architecture
list ( model . keys ())[: 11 ]
['tok_embeddings.weight',
'layers.0.attention.wq.weight',
'layers.0.attention.wk.weight',
'layers.0.attention.wv.weight',
'layers.0.attention.wo.weight',
'layers.0.feed_forward.w1.weight',
'layers.0.feed_forward.w3.weight',
'layers.0.feed_forward.w2.weight',
'layers.0.attention_norm.weight',
'layers.0.ffn_norm.weight',
'layers.1.attention.wq.weight']
หากคุณคุ้นเคยกับสถาปัตยกรรม Transformer คุณจะรู้จักคิวรี เมทริกซ์คีย์ และอื่นๆ อีกมากมาย ต่อมา เราจะใช้เลเยอร์/น้ำหนักเหล่านี้เพื่อสร้างเมทริกซ์ดังกล่าวภายในสถาปัตยกรรมของ Llama-3
params.json- มีค่าพารามิเตอร์ต่างๆ เช่น:
# Opening the parameters JSON file
with open ( "/kaggle/working/llama-3-8B/original/params.json" , "r" ) as f :
config = json . load ( f )
# Printing the content
print ( config )
{'dim': 4096, 'n_layers': 32, 'n_heads': 32, 'n_kv_heads': 8, 'vocab_size': 128256, 'multiple_of': 1024, 'ffn_dim_multiplier': 1.3, 'norm_eps': 1e-05, 'rope_theta': 500000.0}
ค่าเหล่านี้จะช่วยให้เราจำลองสถาปัตยกรรม Llama-3 ได้โดยการระบุรายละเอียด เช่น จำนวนหัว ขนาดของเวกเตอร์ที่ฝัง และอื่นๆ
มาเก็บค่าเหล่านี้ไว้เพื่อที่เราจะได้ใช้ในภายหลัง
# Dimension
dim = config [ "dim" ]
# Layers
n_layers = config [ "n_layers" ]
# Heads
n_heads = config [ "n_heads" ]
# KV_heads
n_kv_heads = config [ "n_kv_heads" ]
# Vocabulary
vocab_size = config [ "vocab_size" ]
# Multiple
multiple_of = config [ "multiple_of" ]
# Multiplier
ffn_dim_multiplier = config [ "ffn_dim_multiplier" ]
# Epsilon
norm_eps = config [ "norm_eps" ]
# RoPE
rope_theta = torch . tensor ( config [ "rope_theta" ])
ตอนนี้เรามีโมเดลโทเค็น โมเดลสถาปัตยกรรมที่มีน้ำหนัก และพารามิเตอร์การกำหนดค่า เรามาเริ่มเขียนโค้ด Llama-3 ของเราตั้งแต่เริ่มต้นกันดีกว่า
สิ่งแรกสุดที่เราต้องทำคือแปลงข้อความอินพุตของเราเป็นโทเค็น และเพื่อให้บรรลุเป้าหมายนี้ อันดับแรกเราต้องสร้างโทเค็นพิเศษบางอย่างซึ่งจำเป็นในการจัดเตรียมเครื่องหมายที่มีโครงสร้างภายในข้อความโทเค็น ทำให้โทเค็นไนเซอร์สามารถรับรู้และจัดการเงื่อนไขเฉพาะได้ หรือคำแนะนำ
special_tokens = [
"<|begin_of_text|>" , # Marks the beginning of a text sequence.
"<|end_of_text|>" , # Marks the end of a text sequence.
"<|reserved_special_token_0|>" , # Reserved for future use.
"<|reserved_special_token_1|>" , # Reserved for future use.
"<|reserved_special_token_2|>" , # Reserved for future use.
"<|reserved_special_token_3|>" , # Reserved for future use.
"<|start_header_id|>" , # Indicates the start of a header ID.
"<|end_header_id|>" , # Indicates the end of a header ID.
"<|reserved_special_token_4|>" , # Reserved for future use.
"<|eot_id|>" , # Marks the end of a turn (in a conversational context).
] + [ f"<|reserved_special_token_ { i } |>" for i in range ( 5 , 256 - 5 )] # A large set of tokens reserved for future use.
ต่อไป เราจะกำหนดกฎสำหรับการแบ่งข้อความออกเป็นโทเค็นโดยการระบุรูปแบบที่แตกต่างกันเพื่อให้ตรงกับสตริงย่อยประเภทต่างๆ ในข้อความที่ป้อน นี่คือวิธีที่เราสามารถทำเช่นนั้นได้
# patterns based on which text will be break into tokens
tokenize_breaker = r"(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^rnp{L}p{N}]?p{L}+|p{N}{1,3}| ?[^sp{L}p{N}]+[rn]*|s*[rn]+|s+(?!S)|s+"
มันสามารถแยกคำ การย่อ ตัวเลข (สูงสุดสามหลัก) และลำดับของอักขระที่ไม่ใช่ช่องว่างจากข้อความที่ป้อน คุณสามารถปรับแต่งได้ตามความต้องการของคุณ เราจำเป็นต้องเขียนโค้ดฟังก์ชัน tokenizer อย่างง่ายโดยใช้ TikToken BPE ซึ่งรับอินพุตสามแบบ: tokenizer_model, tokenize_breaker และ special_tokens ฟังก์ชั่นนี้จะเข้ารหัส/ถอดรหัสข้อความที่เราป้อนตามลำดับ
# Initialize tokenizer with specified parameters
tokenizer = tiktoken . Encoding (
# make sure to set path to tokenizer.model file
name = "/kaggle/working/llama-3-8B/original/tokenizer.model" ,
# Define tokenization pattern string
pat_str = tokenize_breaker ,
# Assign BPE mergeable ranks from tokenizer_model of LLaMA-3
mergeable_ranks = tokenizer_model ,
# Set special tokens with indices
special_tokens = { token : len ( tokenizer_model ) + i for i , token in enumerate ( special_tokens )},
)
# Encode "hello world!" and decode tokens to string
tokenizer . decode ( tokenizer . encode ( "hello world!" ))
'hello world!'
เพื่อตรวจสอบว่าวิธีการทำงานของตัวเข้ารหัสของเราทำงานอย่างถูกต้อง เราจะส่ง "Hello World" เข้าไป ขั้นแรก จะเข้ารหัสข้อความและแปลงเป็นค่าตัวเลข จากนั้นจะถอดรหัสกลับเป็นข้อความ ส่งผลให้เกิด "สวัสดีชาวโลก!" นี่เป็นการยืนยันว่าฟังก์ชันทำงานอย่างถูกต้อง มาสร้างโทเค็นอินพุตของเรากันเถอะ
# input prompt
prompt = "the answer to the ultimate question of life, the universe, and everything is "
# Encode the prompt using the tokenizer and prepend a special token (128000)
tokens = [ 128000 ] + tokenizer . encode ( prompt )
print ( tokens ) # Print the encoded tokens
# Convert the list of tokens into a PyTorch tensor
tokens = torch . tensor ( tokens )
# Decode each token back into its corresponding string
prompt_split_as_tokens = [ tokenizer . decode ([ token . item ()]) for token in tokens ]
print ( prompt_split_as_tokens ) # Print the decoded tokens
[128000, 1820, 4320, 311, 279, 17139, 3488, 315, 2324, 11, 279, 15861, 11, 323, 4395, 374, 220]
['<|begin_of_text|>', 'the', ' answer', ' to', ' the', ' ultimate', ' question', ' of', ' life', ',', ' the', ' universe', ',', ' and', ' everything', ' is', ' ']
เราเข้ารหัสข้อความที่เราป้อน "คำตอบสำหรับคำถามสุดท้ายของชีวิต จักรวาล และทุกสิ่ง" เริ่มต้นด้วยโทเค็นพิเศษ
หากเราตรวจสอบความยาวของเวกเตอร์อินพุตของเรา มันจะเป็น:
# checking dimension of input vector and embedding vector from llama-3 architecture
print ( dim , len ( tokens ))
4096 17
เวกเตอร์อินพุตของเราซึ่งปัจจุบันมีขนาด (17x1) จำเป็นต้องแปลงเป็นการฝังสำหรับคำโทเค็นแต่ละคำ ซึ่งหมายความว่าโทเค็น (17x1) ของเราจะกลายเป็น (17x4096) โดยที่แต่ละโทเค็นมีการฝังที่สอดคล้องกันซึ่งมีความยาว 4096
# Define embedding layer with vocab size and embedding dimension
embedding_layer = torch . nn . Embedding ( vocab_size , dim )
# Copy pre-trained token embeddings to the embedding layer
embedding_layer . weight . data . copy_ ( model [ "tok_embeddings.weight" ])
# Get token embeddings for given tokens, converting to torch.bfloat16 format
token_embeddings_unnormalized = embedding_layer ( tokens ). to ( torch . bfloat16 )
# Print shape of resulting token embeddings
token_embeddings_unnormalized . shape
torch.Size([17, 4096])
การฝังเหล่านี้ไม่ได้ทำให้เป็นมาตรฐาน และจะมีผลกระทบร้ายแรงหากเราไม่ทำให้เป็นมาตรฐาน ในส่วนถัดไป เราจะทำการทำให้เป็นมาตรฐานกับเวกเตอร์อินพุตของเรา
เราจะทำให้เวกเตอร์อินพุตเป็นมาตรฐานโดยใช้สูตรเดียวกันกับที่เราได้เห็นก่อนหน้านี้สำหรับ RMSNorm เพื่อให้แน่ใจว่าอินพุตของเราได้รับการทำให้เป็นมาตรฐาน
# Calculating RMSNorm
def rms_norm ( tensor , norm_weights ):
# Calculate the mean of the square of tensor values along the last dimension
squared_mean = tensor . pow ( 2 ). mean ( - 1 , keepdim = True )
# Add a small value to avoid division by zero
normalized = torch . rsqrt ( squared_mean + norm_eps )
# Multiply normalized tensor by the provided normalization weights
return ( tensor * normalized ) * norm_weights
เราจะใช้น้ำหนักความสนใจจาก layer_0 เพื่อทำให้การฝังที่ไม่เป็นมาตรฐานของเราเป็นมาตรฐาน เหตุผลในการใช้ layer_0 คือตอนนี้เรากำลังสร้างเลเยอร์แรกของสถาปัตยกรรมหม้อแปลง LLaMA-3 ของเรา
# using RMS normalization and provided normalization weights
token_embeddings = rms_norm ( token_embeddings_unnormalized ,
model [ "layers.0.attention_norm.weight" ])
# Print the shape of the resulting token embeddings
token_embeddings . shape
torch.Size([17, 4096])
คุณอาจรู้อยู่แล้วว่ามิติจะไม่เปลี่ยนแปลงเพราะเราแค่ทำให้เวกเตอร์เป็นมาตรฐานเท่านั้นและไม่มีอะไรอื่นอีก
ขั้นแรก มาโหลดแบบสอบถาม คีย์ ค่า และเวกเตอร์เอาท์พุตจากโมเดลกันก่อน
# Print the shapes of different weights
print (
# Query weight shape
model [ "layers.0.attention.wq.weight" ]. shape ,
# Key weight shape
model [ "layers.0.attention.wk.weight" ]. shape ,
# Value weight shape
model [ "layers.0.attention.wv.weight" ]. shape ,
# Output weight shape
model [ "layers.0.attention.wo.weight" ]. shape
)
torch.Size([4096, 4096]) torch.Size([1024, 4096]) torch.Size([1024, 4096]) torch.Size([4096, 4096])
มิติข้อมูลระบุว่าน้ำหนักแบบจำลองที่เราดาวน์โหลดไม่ได้สำหรับแต่ละหัวแยกกัน แต่สำหรับหัวความสนใจหลายรายการเนื่องจากการใช้วิธีการ/การฝึกอบรมแบบขนาน อย่างไรก็ตาม เราสามารถแกะเมทริกซ์เหล่านี้เพื่อให้ใช้ได้กับหัวเดียวเท่านั้น
# Retrieve query weight for the first layer of attention
q_layer0 = model [ "layers.0.attention.wq.weight" ]
# Calculate dimension per head
head_dim = q_layer0 . shape [ 0 ] // n_heads
# Reshape query weight to separate heads
q_layer0 = q_layer0 . view ( n_heads , head_dim , dim )
# Print the shape of the reshaped query weight tensor
q_layer0 . shape
torch.Size([32, 128, 4096])
ในที่นี้ 32 คือจำนวนหัวความสนใจใน Llama-3, 128 คือขนาดของเวกเตอร์คิวรี และ 4096 คือขนาดของโทเค็นที่ฝัง เราสามารถเข้าถึงเมทริกซ์น้ำหนักแบบสอบถามของส่วนหัวแรกของเลเยอร์แรกได้โดยใช้:
# Extract the query weight for the first head of the first layer of attention
q_layer0_head0 = q_layer0 [ 0 ]
# Print the shape of the extracted query weight tensor for the first head
q_layer0_head0 . shape
torch.Size([128, 4096])
ในการค้นหาเวกเตอร์การสืบค้นสำหรับแต่ละโทเค็น เราจะคูณน้ำหนักการสืบค้นด้วยการฝังโทเค็น
# Matrix multiplication: token embeddings with transpose of query weight for first head
q_per_token = torch . matmul ( token_embeddings , q_layer0_head0 . T )
# Shape of resulting tensor: queries per token
q_per_token . shape
torch.Size([17, 128])
เวกเตอร์การสืบค้นไม่ทราบตำแหน่งของตนในข้อความแจ้งโดยธรรมชาติ ดังนั้นเราจะใช้ RoPE เพื่อแจ้งให้ทราบ
เราแยกกัน