يتنبّأ النموذج اللغوي بالـ token التالي. في هذا الفيديو نتتبّع هذا التنبّؤ عبر حلقة التوليد وصولًا إلى ردّ كامل في المحادثة. سنشاهد حلقة التوليد وهي تعمل، ونرى لماذا قد يختلف تشغيلان للـ prompt نفسه، وما وسائل التحكّم في ذلك، ثم نرى أن المحادثة في الحقيقة إكمالٌ لنصّها. بعد ذلك ننظر في ما يحمله فعلًا كل طلب إلى النموذج، وفي نافذة السياق، أي القدر الثابت من النص الذي يستطيع النموذج رؤيته في وقت واحد.
لنشاهد حلقة التوليد وهي تعمل، خطوة بخطوة. نبدأ من سياق جاهز: «The cat sat on the»
يتنبّأ النموذج بتوزيع احتمالي للـ token التالي، تمامًا كما فعل من قبل. أعلى احتمال هنا من نصيب mat، فهو الـ token التالي الأرجح.
لكن انتبهوا إلى أمر مهم: نحن لا نختار دائمًا الـ token الأرجح. بدلًا من ذلك، نسحب عيّنة (sampling)، أي نختار token عشوائيًّا لكن مرجَّحًا بالاحتمالات، فالـ tokens الأرجح تُختار أكثر من غيرها. هذه المرة وقع الاختيار على hat، مع أن mat كان أكثر احتمالًا. هذه العشوائية مقصودة، وسنرى بعد قليل لماذا هي مفيدة.
نضيف الـ token الذي اخترناه، hat، ومن الآن فصاعدًا يُعامَل تمامًا مثل أي token آخر في السياق.
الآن تتكرّر الحلقة. بعد إضافة hat إلى السياق، يتنبّأ النموذج بالـ token التالي من جديد. قد يكون نقطة، أو الـ token الذي يمثّل كلمة and، أو كلمة today.
نسحب عيّنة مرة أخرى. هذه المرة كانت العيّنة نقطة، فنضيفها، وبها تنتهي الجملة.
هذا هو المحرّك كله. نتنبّأ بتوزيع، ونسحب منه token، ونضيفه، ثم نكرّر. ولأننا نسحب عيّنة في كل خطوة، قد يؤدي الـ prompt نفسه إلى نصوص مختلفة في تشغيلات مختلفة.
لنرَ هذه العشوائية بمثال ملموس. نعطي النموذج الـ prompt نفسه تمامًا في كل مرة: «The cat sat on the»
نشغّله مرة فنحصل على تكملة واحدة: «The cat sat on the mat, the cat slept»
نشغّل الـ prompt نفسه مرة أخرى، وقد نحصل على نهاية مختلفة تمامًا.
وتشغيل ثالث يعطي تكملة أخرى. المُدخلات والإعدادات نفسها، لكن الـ tokens المسحوبة تختلف.
سحب العيّنات يمنحنا التنوّع، لكنه يصعّب المقارنة أيضًا. قد يعطي الـ prompt نفسه إجابات مختلفة، لذا لا يكفي تشغيل واحد للحكم على مدى موثوقيته. وهذا التنوّع مفيد عندما نريد عدة أسماء ممكنة، أو عدة تفسيرات، أو عدة طرق لحلّ مشكلة.
بما أن سحب العيّنات هو مصدر العشوائية، فلدينا بعض وسائل التحكّم في مقدارها. أهمّها درجة الحرارة (temperature)، وهي تعيد تشكيل التوزيع قبل أن نسحب منه. عند درجة حرارة 1، نستخدم احتمالات النموذج كما هي، دون تغيير.
اخفضوا درجة الحرارة، فيصبح التوزيع أكثر حدّة. يميل النموذج بقوة نحو خياره الأول، فتصبح مُخرَجاته أسهل في التوقّع. ارفعوا درجة الحرارة، فيصبح التوزيع أكثر تسطّحًا. وتزداد فرصة اختيار الـ tokens الأقل ترجيحًا، فقد تتنوّع المُخرَجات أكثر. والتنوّع الأكبر لا يعني بالضرورة إجابة أفضل.
ودرجة الحرارة ليست وسيلة التحكّم الوحيدة. الإعداد top-k يُبقي عددًا نختاره من الـ tokens الأعلى احتمالًا. والإعداد top-p يُبقي منها ما يكفي لبلوغ نسبة نختارها من مجموع الاحتمالات. وكلاهما يقلّص ما نسحب منه. ومن وسائل التحكّم الأخرى حدٌّ لعدد الـ tokens في المُخرَجات، وفي بعض الـ APIs، بذرة عشوائية (seed) تساعد على تكرار التشغيل. لكن الـ seed لا يضمن دائمًا مُخرَجات متطابقة. بالمناسبة، السبب أن الـ GPU يُجري كثيرًا من هذه الحسابات بالتوازي، وترتيب جمعه للأرقام قد يتغيّر من تشغيل إلى آخر. فتختلف النتيجة في آخر بضع خانات، وهذا يكفي أحيانًا لتغيير الـ token الذي يقع عليه الاختيار. سنعود إلى هذه الوسائل عندما نرسل الطلبات إلى النموذج بأنفسنا.
هذه هي الصورة الذهنية المهمة: درجة الحرارة تعيد تشكيل التوزيع، والإعدادان top-p و top-k يقتطعان ذيله، ثم نسحب منه token.
كيف يُنتج التنبّؤ بالـ token التالي قدرات مفيدة كالحساب أو الترجمة؟ لفهم ذلك، لننظر في ما يتطلّبه التنبّؤ بالـ token التالي فعلًا. خذوا الجملة: «if three pens cost six dollars, one pen costs» للإجابة عن سؤال كهذا بشكل موثوق، يحتاج النموذج إلى استخدام العلاقة بين الأعداد. والإجابة هنا اثنان.
في المثال «English cat, French»، التكملة الصحيحة هي chat. تعلّم العلاقات بين اللغات يساعد النموذج على إكمال نصوص كهذه.
وفي «the capital of Japan is»، التكملة المتوقّعة هي Tokyo. والحقائق التي تعلّمها في التدريب تساعده على التنبّؤ بهذه الإجابة.
كل هذه المهام يمكن صياغتها نصًّا يتنبّأ به النموذج. تعلّم الحساب والترجمة والحقائق عن العالم يساعد النموذج على التنبّؤ بهذه الأنواع من النصوص. وهدف التدريب نفسه، التنبّؤ بالـ token التالي، يمنحه سببًا لتعلّم كل هذه العلاقات. وهكذا يمكن أن تنشأ عدة قدرات مختلفة من هدف التدريب الواحد هذا. ثم يمكننا أن نختبر مدى نجاح النموذج في استخدام كل قدرة في مسائل جديدة.
حتى الآن، لا يفعل نموذجنا إلا إكمال النص. فكيف نصنع منه مساعد محادثة، من النوع الذي اشتهر بفضل ChatGPT؟ الفكرة أبسط مما تبدو عليه. نكتب المحادثة كلها كنصّ واحد طويل، مع علامات أدوار صغيرة، مثل user و assistant، تبيّن من قال ماذا.
نصّ المحادثة الكامل هذا، بكل رسائلها حتى الآن، يُدخَل إلى النموذج كسلسلة واحدة من الـ tokens. لا يوجد في الخلفية محرّك محادثة منفصل. إنه متنبّئ الـ token التالي نفسه، لكنه يقرأ الآن الحوار كله.
وللإجابة، يُكمل النموذج النص ببساطة مباشرة بعد آخر علامة assistant. هنا يتنبّأ بعبارة it's Tokyo، token تلو الآخر، تمامًا كما يُنهي أي جملة أخرى.
لكن على النموذج أيضًا أن يعرف متى يتوقّف. أثناء التدريب يتعلّم token خاصًا لنهاية الرسالة، ويُخرج هذا الـ token عندما يكتمل الردّ. عندما يظهر token التوقّف، ينتهي هذا الردّ. وفي محادثة عادية، تنتظر الواجهة بعد ذلك رسالة المستخدم التالية.
الآلية الأساسية إذن إكمالٌ لنصّ المحادثة المُعلَّم بعلامات الأدوار. المتنبّئ نفسه الذي نبنيه منذ البداية يقرأ المحادثة كاملة ويُكمل ردّ الـ assistant. علامات الأدوار تمنح المحادثة بنيتها. والتدريب يعلّم النموذج أيضًا كيف يردّ كمساعد.
لهذه النظرة الجديدة نتيجةٌ يسهل أن تفوتنا، فلننظر بدقّة في ما يصل إلى النموذج كلما أرسل الـ harness طلبًا. هذا هو الطلب الأول. يحتوي على الـ system prompt، وهو تعليمات الـ harness نفسه إلى النموذج، وبعده مهمّتكم، وعليها علامة دور المستخدم. يقرأ النموذج كل ذلك ويُكمل ردّ الـ assistant التالي. في هذا المثال، الردّ استدعاءُ أداة يطلب قراءة ملف الاختبار.
انظروا الآن إلى الطلب الثاني. إنه لا يقتصر على ما هو جديد. الـ system prompt ومهمّتكم يُرسَلان مرة أخرى، حرفيًا، وبعدهما فقط يأتي استدعاء النموذج للأداة، والنتيجة التي أعادها الـ harness، وكلٌّ منهما مُعلَّم بعلامة دوره. لا يحتفظ النموذج بسجلّ محادثة منفصل يسدّ به ما ينقص الطلب. الـ harness يرسل نصّ المحادثة الكامل حتى الآن من جديد، في كل مرة، كما تُظهر هذه الطلبات.
في الطلب الثالث يتّضح النمط. الجديد هو السطران الأخيران فقط، وكل ما فوقهما هو النص نفسه، يُرسَل للمرة الثالثة. هذه هي النقطة التي يجب تذكّرها. كل طلب يحمل النافذة الحالية كاملة، لا السطر الذي أضفتموه فقط. والجزء المكرَّر يكبر مع كل رسالة، إلى أن يلخّص الـ harness جزءًا منه أو يحذفه.
إرسال النص نفسه مرارًا يبدو إهدارًا، ولدى مزوِّد الخدمة حلّ لذلك. النموذج يعمل في السحابة، والحساب الذي أجراه على بداية نصّ سبق أن رآها يُحفظ لفترة قصيرة ويُعاد استخدامه. هذا هو التخزين المؤقت للـ prompt. عندما يبدأ طلب جديد ببادئة تطابق بادئة مخزَّنة، يعيد مزوِّد الخدمة استخدام حسابه لهذا الجزء، ويعالج الجزء الجديد. النص المكرَّر ما زال موجودًا في الطلب، لكن إعادة استخدام الحساب تقلّل وقت الانتظار وتكلفة معالجته.
التخزين المؤقت لا ينفع إلا مع بادئة لم تتغيّر. إذا اختلف أي شيء في جزء سابق من الطلب، مثل system prompt مختلف، أو رسالة سابقة عُدِّلت، أو مجموعة أدوات مختلفة، فلن يتطابق شيء من تلك النقطة فصاعدًا، ويعالج النموذج بقية الطلب من الصفر. وقد تنتهي صلاحية المخزَّن. ومدّة بقائه تعتمد على مزوِّد الخدمة والإعدادات. ودون بادئة مخزَّنة صالحة، على النموذج أن يعالج المُدخلات من جديد قبل أن يولّد الإجابة.
والآن، تذكّروا أن المحادثة كلها تُدخَل كسلسلة واحدة طويلة من الـ tokens. ومع استمرار المحادثة، تواصل هذه السلسلة النمو.
لكن النموذج لا يتعامل في آن واحد إلا مع عدد ثابت من الـ tokens. هذا الحدّ يُسمّى نافذة السياق. السلاسل الأطول تحتاج إلى ذاكرة وحوسبة أكثر. في نمط الـ attention الكامل الذي رأيناه، يمكن لكل موضع أن يستخدم كل المواضع التي قبله، فيزداد عدد المقارنات بسرعة.
كل ما يستخدمه النموذج للإجابة، التعليمات، والرسائل السابقة، وأي ملفات شاركتموها، كل ذلك يجب أن تتّسع له هذه النافذة. والردّ أيضًا يحتاج إلى مكان. ومع اقتراب الطلب من الحدّ، على الـ harness أن يقرّر ما يُبقيه، وما يلخّصه، وما يحذفه.
لنلخّص. يمثّل النموذج النص على شكل tokens ومتّجهات مُتعلَّمة، ثم يتنبّأ بتوزيع للـ token التالي.
التوليد حلقة تسحب token واحدًا من هذا التنبّؤ، وتضيفه، ثم تتنبّأ من جديد، ولهذا قد يختلف تشغيلان للـ prompt نفسه.
والمحادثة مجرد إكمال لنصّها المُعلَّم بالأدوار، يُقرأ داخل نافذة سياق محدودة.
وهذا يمنحنا طريقة لفهم ردود الوكيل: ما المعلومات التي كانت في النافذة، وماذا ولّد النموذج منها؟ وسنستخدم هذا التمييز عندما نقارن بين التشغيلات.