يعد LLaMA 3 واحدًا من أكثر النماذج مفتوحة المصدر الواعدة بعد ميسترال، حيث يحل مجموعة واسعة من المهام. لقد كتبت سابقًا مدونة على Medium حول إنشاء LLM بأكثر من 2.3 مليون معلمة من الصفر باستخدام بنية LLaMA. الآن بعد أن تم إصدار LLaMA-3، سنقوم بإعادة إنشائه بطريقة أبسط.
لن نستخدم وحدة معالجة الرسومات (GPU) لهذه المدونة، ولكنك ستحتاج إلى 17 جيجابايت على الأقل من ذاكرة الوصول العشوائي (RAM) لأننا سنقوم بتحميل بعض الملفات التي يزيد حجمها عن 15 جيجابايت. إذا كانت هذه مشكلة بالنسبة لك، فيمكنك استخدام Kaggle كحل. نظرًا لأننا لا نحتاج إلى وحدة معالجة الرسومات، فإن Kaggle يقدم 30 جيجابايت من ذاكرة الوصول العشوائي أثناء استخدام نوى وحدة المعالجة المركزية فقط كمسرع.
إليك رابط المدونة الذي يرشدك إلى كيفية إنشاء أكثر من 2.3 مليون معلمة LLM من الصفر: 2.3+ مليون LLM معلمة من الصفر
الجزء الجيد هو أننا لن نستخدم برمجة كائنية التوجه (OOP)، بل فقط برمجة بايثون البسيطة. ومع ذلك، يجب أن يكون لديك فهم أساسي للشبكات العصبية وبنية المحولات. هذان هما المتطلبان الوحيدان اللازمان للمتابعة مع المدونة.
عنوان | وصلة |
---|---|
نظرية المحولات | رابط الفيديو |
نظرية الشبكات العصبية | رابط الفيديو |
أساسيات بايثون | رابط الفيديو |
قبل النظر في التفاصيل الفنية، أول شيء يجب أن تعرفه هو أن البنية الكاملة لـ LLaMA 3 هي نفس LLaMA 2. لذا، إذا لم تكن قد اطلعت على التفاصيل الفنية لـ LLaMA 3 حتى الآن، فلن يكون الأمر كذلك مشكلة بالنسبة لك لمتابعة هذه المدونة. حتى إذا لم يكن لديك فهم لبنية LLaMA 2، فلا تقلق، فسنلقي نظرة أيضًا على نظرة عامة رفيعة المستوى لتفاصيلها الفنية. تم تصميم هذه المدونة لك في كلتا الحالتين.
فيما يلي بعض النقاط الأساسية حول LLaMA 2 وLLaMA 3. إذا كنت على دراية ببنيتهما بالفعل:
ميزة | اللاما 3 | اللاما 2 |
---|---|---|
رمز مميز | Tiktoken (تم تطويره بواسطة OpenAI) | SentencePiece |
عدد المعلمات | 8 ب، 70 ب | 70ب، 13ب، 7ب |
بيانات التدريب | 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 لنماذج اللغات الكبيرة (LLMs) مثل ChatGPT. إنه مثل تخصيص وزن لكل فصل بناءً على أهميته. الفصول الأساسية للموضوع تحصل على أوزان أعلى، بينما تحصل الفصول الأقل أهمية على أوزان أقل.
لذا، قبل التعمق في الدراسة، عليك تعديل خطة دراستك بناءً على الأهمية المرجحة لكل فصل. يمكنك تخصيص المزيد من الوقت والجهد للفصول ذات الأوزان الأعلى، مما يضمن فهم المفاهيم الأساسية بدقة.
وبالمثل، فإن التسوية المسبقة باستخدام RMSNorm تساعد طلاب LLM على تحديد أولويات أجزاء النص الأكثر أهمية لفهم السياق والمعنى. فهو يعين أوزانًا أعلى للعناصر الأساسية وأوزانًا أقل للعناصر الأقل أهمية، مما يضمن أن يركز النموذج انتباهه على المكان الذي تشتد الحاجة إليه من أجل الفهم الدقيق. يمكن للقراء المهتمين استكشاف التنفيذ التفصيلي لـ RMSNorm هنا.
تقدم LLaMA وظيفة تنشيط SwiGLU، مستوحاة من PaLM.
تخيل أنك معلم تحاول شرح موضوع معقد لطلابك. لديك سبورة بيضاء كبيرة حيث يمكنك كتابة النقاط الرئيسية ورسم المخططات لجعل الأمور أكثر وضوحا. لكن في بعض الأحيان، قد لا تكون خط يدك أنيقًا جدًا، أو قد لا تكون مخططاتك مرسومة بشكل مثالي. وهذا يمكن أن يجعل من الصعب على طلابك فهم المادة.
الآن، تخيل لو كان لديك قلم سحري يقوم تلقائيًا بضبط حجم ونمط خط يدك بناءً على مدى أهمية كل نقطة. إذا كان هناك شيء مهم حقًا، فإن القلم يكتبه بشكل أكبر وأكثر وضوحًا، مما يجعله بارزًا. إذا كان الأمر أقل أهمية، يكتبه القلم بحجم أصغر، ولكن لا يزال مقروءًا. يشبه SwiGLU القلم السحري لنماذج اللغات الكبيرة (LLMs) مثل ChatGPT. قبل إنشاء النص، يقوم SwiGLU بضبط أهمية كل كلمة أو عبارة بناءً على صلتها بالسياق. تمامًا مثلما يضبط القلم السحري حجم وأسلوب كتابتك، يضبط SwiGLU تركيز كل كلمة أو عبارة.
لذلك، عندما يقوم LLM بإنشاء نص، فإنه يمكن أن يعطي أهمية أكبر للأجزاء المهمة، مما يجعلها أكثر وضوحًا ويضمن أنها تساهم بشكل أكبر في الفهم العام للنص. بهذه الطريقة، يساعد SwiGLU طلاب LLM على إنتاج نص أكثر وضوحًا وأسهل للفهم، تمامًا مثل الطريقة التي يساعدك بها القلم السحري في إنشاء تفسيرات أكثر وضوحًا لطلابك على السبورة البيضاء. يمكن العثور على مزيد من التفاصيل حول SwiGLU في الورقة المرتبطة.
التضمين الدوار، أو RoPE، هو نوع من تضمين الموضع المستخدم في LLaMA 3.
تخيل أنك في فصل دراسي، وتريد تخصيص مقاعد للطلاب لإجراء مناقشات جماعية. عادةً، يمكنك ترتيب المقاعد في صفوف وأعمدة، بحيث يكون لكل طالب موضع ثابت. ومع ذلك، في بعض الحالات، قد ترغب في إنشاء ترتيب جلوس أكثر ديناميكية حيث يمكن للطلاب التحرك والتفاعل بحرية أكبر.
يشبه ROPE ترتيبًا خاصًا للجلوس يسمح للطلاب بتدوير وتغيير أوضاعهم مع الحفاظ على مواقعهم النسبية لبعضهم البعض. وبدلاً من الثبات في مكان واحد، يستطيع الطلاب الآن التحرك بحركة دائرية، مما يسمح بتفاعلات أكثر مرونة.
في هذا السيناريو، يمثل كل طالب كلمة أو رمزًا مميزًا في تسلسل نصي، ويتوافق موضعه مع موضعه في التسلسل. تمامًا مثلما يسمح ROPE للطلاب بتدوير المواضع وتغييرها، يسمح ROPE للتضمين الموضعي للكلمات في تسلسل نصي بالتغيير ديناميكيًا بناءً على مواضعها النسبية لبعضها البعض. لذلك، عند معالجة النص، بدلاً من التعامل مع التضمينات الموضعية على أنها ثابتة وثابتة، يقدم ROPE جانبًا دوّارًا، مما يسمح بتمثيلات أكثر مرونة تلتقط العلاقات الديناميكية بين الكلمات في التسلسل. تساعد هذه المرونة نماذج مثل ChatGPT على فهم وإنشاء نص يتدفق بشكل طبيعي ويحافظ على التماسك بشكل أفضل، على غرار الطريقة التي يعزز بها ترتيب الجلوس الديناميكي المناقشات التفاعلية في الفصل الدراسي. يمكن للمهتمين بالتفاصيل الرياضية الرجوع إلى ورقة RoPE.
يستخدم LLaMA 3 تشفير زوج البايت (BPE) من مكتبة tiktoken التي قدمتها OpenAI، في حين يعتمد LLaMA 2 tokenizer 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، "قبل الميلاد": 2، "قرص مضغوط": 1، "دي": 1}. نضيف "de" إلى المفردات، ونجعلها {"a"، "b"، "c"، "d"، "e"، "bc"، "cd"، "de"}.
بعد ذلك، نجد "ab" باعتباره الزوج الأكثر شيوعًا. نقوم بدمج "ab" لتكوين وحدة الكلمات الفرعية "ab" ونقوم بتحديث عدد التكرارات إلى {"a": 0، "b": 1، "c": 1، "d": 1، "e": 0، "قبل الميلاد": 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، "قبل الميلاد": 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" "، "cde"}.
يمكن لهذه التقنية تحسين أداء LLMs والتعامل مع الكلمات النادرة والبعيدة عن المفردات. يتمثل الاختلاف الكبير بين 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 الخاص بنا. يمكنك إنشاء رمز مميز جديد أو الوصول إليه من هذا الرابط.
# 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
بعد ذلك، نحتاج إلى فهم الغرض الذي سيتم استخدام كل ملف من أجله.
نظرًا لأننا نهدف إلى الحصول على نسخة متماثلة تمامًا من اللاما-3، فهذا يعني أن نص الإدخال الخاص بنا يجب أن ينتج عنه مخرجات ذات معنى. على سبيل المثال، إذا كان الإدخال هو "لون الشمس؟"، فيجب أن يكون الإخراج "أبيض". يتطلب تحقيق ذلك تدريب LLM لدينا على مجموعة بيانات كبيرة، الأمر الذي يتطلب قوة حسابية عالية، مما يجعله غير ممكن بالنسبة لنا.
ومع ذلك، أصدرت Meta علنًا ملفات بنية llama-3 الخاصة بها، أو بعبارات أكثر تعقيدًا، أوزانها المُدربة مسبقًا، للاستخدام. لقد قمنا للتو بتنزيل هذه الملفات، مما يسمح لنا بتكرار بنيتها دون الحاجة إلى التدريب أو مجموعة بيانات كبيرة. كل شيء جاهز بالفعل، علينا فقط استخدام المكونات الصحيحة في الأماكن الصحيحة.
ألقِ نظرة على كل ملف من هذه الملفات وأهميته:
tokenizer.model - كما ناقشنا سابقًا، يستخدم LLaMA-3 رمز Byte Pair Encoding (BPE) من tiktoken، والذي تم تدريبه على مجموعة بيانات تحتوي على 15 تريليون رمز مميز - أكبر 7 مرات من مجموعة البيانات المستخدمة لـ LLaMA-2. لنقم بتحميل هذا الملف ونرى ما يحمله.
# 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
تُظهر سمة الطول إجمالي حجم المفردات، وهو العدد الفريد للأحرف في بيانات التدريب. نوع 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، بينما تمثل القيم صفوف الدمج بناءً على التردد.
الموحدة.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']
إذا كنت على دراية بهندسة المحولات، فستكون على دراية بالاستعلام والمصفوفات الرئيسية والمزيد. لاحقًا، سنستخدم هذه الطبقات/الأوزان لإنشاء مثل هذه المصفوفات داخل بنية 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+"
يمكنه استخراج الكلمات والاختصارات والأرقام (حتى ثلاثة أرقام) وتسلسلات الأحرف التي لا تحتوي على مسافات بيضاء من نص الإدخال، ويمكنك تخصيصها بناءً على متطلباتك. نحتاج إلى ترميز وظيفة رمزية بسيطة باستخدام 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" إليها. أولاً، يقوم بتشفير النص وتحويله إلى قيم رقمية. ثم يقوم بفك تشفيرها مرة أخرى إلى نص، مما يؤدي إلى ظهور عبارة "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)، إلى تحويلها إلى تضمينات لكل كلمة مميزة. وهذا يعني أن الرموز المميزة (17×1) ستصبح (17×4096)، حيث يكون لكل رمز تضمين مطابق يبلغ طوله 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 لإعلامها بذلك.
نحن نلعب