Transformers documentation

قوالب المحادثة

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.22.1).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

قوالب المحادثة

يتناول دليل أساسيات المحادثة كيفية تخزين سجلات المحادثة وتوليد النص من نماذج المحادثة باستخدام TextGenerationPipeline.

يُعدّ هذا الدليل موجَّهًا للمستخدمين الأكثر تقدّمًا، ويتناول الفئات والطرق الأساسية، فضلًا عن المفاهيم الرئيسية لفهم ما يحدث فعليًا عند محادثتك لنموذج.

الدور الحاسم المطلوب لفهم نماذج المحادثة هو هذا: جميع نماذج اللغة السببية، سواء كانت مدرَّبة على المحادثة أم لا، تستمر في توليد تسلسل من الرموز. عند تدريب نماذج اللغة السببية، يبدأ التدريب عادةً بـ «التدريب المسبق» على مجموعة ضخمة من النصوص، مما يُنشئ نموذجًا «أساسيًا». غالبًا ما يتم «ضبط» هذه النماذج الأساسية للمحادثة، أي تدريبها على بيانات مُنسّقة على شكل تسلسل من الرسائل. ومع ذلك، تبقى المحادثة مجرد تسلسل من الرموز! تُحوَّل قائمة قواميس role وcontent التي تمررها إلى نموذج المحادثة إلى تسلسل من الرموز، غالبًا باستخدام رموز تحكم مثل <|user|> أو <|assistant|> أو <|end_of_message|>، مما يتيح للنموذج رؤية بنية المحادثة. توجد العديد من الصيغ الممكنة للمحادثة، وقد تستخدم النماذج المختلفة صيغًا أو رموز تحكم مختلفة، حتى لو كانت مُضبوطة من النموذج الأساسي نفسه!

لا داعي للذعر، فأنت لا تحتاج إلى حفظ جميع صيغ المحادثة الممكنة لاستخدام نماذج المحادثة. تأتي نماذج المحادثة مع قوالب محادثة، تُبيّن كيف تتوقع أن تُنسّق المحادثات. يمكنك الوصول إلى هذه القوالب عبر طريقة apply_chat_template. دعنا ننظر إلى مثالين. كلا هذين النموذجين مُعدّلان من نفس النموذج الأساسي Mistral-7B:

Mistral
Zephyr
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.1")
chat = [
  {"role": "user", "content": "Hello, how are you?"},
  {"role": "assistant", "content": "I'm doing great. How can I help you today?"},
  {"role": "user", "content": "I'd like to show off how chat templating works!"},
]

tokenizer.apply_chat_template(chat, tokenize=False)
<s>[INST] Hello, how are you? [/INST]I'm doing great. How can I help you today?</s> [INST] I'd like to show off how chat templating works! [/INST]

يستخدم Mistral-7B-Instruct الرمزين [INST] و[/INST] للإشارة إلى بداية رسائل المستخدم ونهايتها، بينما يستخدم Zephyr-7B الرمزين <|user|> و<|assistant|> للإشارة إلى أدوار المتكلمين. ولهذا السبب تُعدّ قوالب المحادثة مهمة - فمع رموز التحكم الخاطئة، ستكون أداءات هذه النماذج أسوأ بشكل كبير.

باستخدام apply_chat_template

يجب أن تكون المدخلات إلى apply_chat_template منظمة على شكل قائمة من القواميس تحتوي على مفاتيح role وcontent. يحدد المفتاح role المتحدث، بينما يحتوي المفتاح content على الرسالة. الأدوار الشائعة هي:

  • user للرسائل الصادرة من المستخدم
  • assistant للرسائل الصادرة عن النموذج
  • system لتوجيهات كيفية تصرف النموذج (يتم وضعها عادةً في بداية المحادثة)

apply_chat_template يستقبل هذه القائمة ويعيد تسلسلاً منسقاً. اضبط tokenize=True إذا كنت ترغب في تحويل التسلسل إلى رموز.

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("HuggingFaceH4/zephyr-7b-beta")
model = AutoModelForCausalLM.from_pretrained("HuggingFaceH4/zephyr-7b-beta", device_map="auto", dtype=torch.bfloat16)

messages = [
    {"role": "system", "content": "You are a friendly chatbot who always responds in the style of a pirate",},
    {"role": "user", "content": "How many helicopters can a human eat in one sitting?"},
 ]
tokenized_chat = tokenizer.apply_chat_template(messages, tokenize=True, add_generation_prompt=True, return_tensors="pt").to(model.device)
print(tokenizer.decode(tokenized_chat["input_ids"][0]))
<|system|>
You are a friendly chatbot who always responds in the style of a pirate</s>
<|user|>
How many helicopters can a human eat in one sitting?</s>
<|assistant|>

مرّر المحادثة المُرمّزة إلى generate() لتوليد رد.

outputs = model.generate(**tokenized_chat, max_new_tokens=128)
print(tokenizer.decode(outputs[0]))
<|system|>
You are a friendly chatbot who always responds in the style of a pirate</s>
<|user|>
How many helicopters can a human eat in one sitting?</s>
<|assistant|>
Matey, I'm afraid I must inform ye that humans cannot eat helicopters. Helicopters are not food, they are flying machines. Food is meant to be eaten, like a hearty plate o' grog, a savory bowl o' stew, or a delicious loaf o' bread. But helicopters, they be for transportin' and movin' around, not for eatin'. So, I'd say none, me hearties. None at all.

تضيف بعض أدوات تقسيم النص رموزًا خاصة مثل <bos> و<eos>. يجب أن تتضمن قوالب المحادثة بالفعل جميع الرموز الخاصة اللازمة، وغالبًا ما يكون إضافة رموز خاصة إضافية غير صحيح أو متكررًا، مما يُضعف أداء النموذج. عند تنسيق النص باستخدام apply_chat_template(tokenize=False)، تأكد من تعيين add_special_tokens=False إذا كنت ستقوم بتقسيم النص لاحقًا لتجنب تكرار هذه الرموز. لا يُعد هذا مشكلة إذا كنت تستخدم apply_chat_template(tokenize=True)، مما يعني أنه الخيار الأكثر أمانًا عادةً!

add_generation_prompt

ربما لاحظتَ حجة add_generation_prompt في الأمثلة أعلاه. تضيف هذه الحجة رموزًا إلى نهاية المحادثة تشير إلى بداية رد من assistant. تذكّر: خلف كل تجريدات المحادثة، ما تزال نماذج المحادثة مجرد نماذج لغوية تستكمل تسلسلًا من الرموز! إذا شملت رموزًا تخبرها أنها الآن في رد من assistant، فستكتب ردًا صحيحًا، لكن إذا لم تشمل هذه الرموز، فقد يخلط النموذج ويفعل شيئًا غريبًا، مثل متابعة رسالة المستخدم بدلاً من الرد عليها!

لنلقِ نظرة على مثال لفهم ما يفعله add_generation_prompt فعليًا. أولًا، لننسّق محادثة دون add_generation_prompt:

tokenized_chat = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=False)
tokenized_chat
<|im_start|>user
Hi there!<|im_end|>
<|im_start|>assistant
Nice to meet you!<|im_end|>
<|im_start|>user
Can I ask a question?<|im_end|>

الآن، لننسّق نفس المحادثة باستخدام add_generation_prompt=True:

tokenized_chat = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
tokenized_chat
<|im_start|>user
Hi there!<|im_end|>
<|im_start|>assistant
Nice to meet you!<|im_end|>
<|im_start|>user
Can I ask a question?<|im_end|>
<|im_start|>assistant

عند تعيين add_generation_prompt=True، يُضاف <|im_start|>assistant في النهاية للإشارة إلى بداية رسالة assistant. وهذا يتيح للنموذج معرفة أن رد assistant هو التالي.

لا تتطلب جميع نماذج التوليد موجهات توليد، وبعض النماذج، مثل Llama، لا تحتوي على أي رموز خاصة قبل استجابة assistant. في هذه الحالات، لا يكون لـ add_generation_prompt أي تأثير.

continue_final_message

يتحكم معامل continue_final_message في ما إذا كان ينبغي مواصلة الرسالة الأخيرة في المحادثة بدلاً من بدء رسالة جديدة. يقوم بإزالة رموز نهاية التسلسل حتى يستمر النموذج في التوليد من الرسالة الأخيرة.

يُعدّ هذا مفيدًا لـ«تعبئة مسبقة» لاستجابة النموذج. في المثال أدناه، يولّد النموذج نصًا يستكمل سلسلة JSON بدلاً من بدء رسالة جديدة. وقد يكون ذلك مفيدًا للغاية لتحسين دقة اتباع التعليمات عندما تعرف كيفية بدء ردوده.

chat = [
    {"role": "user", "content": "Can you format the answer in JSON?"},
    {"role": "assistant", "content": '{"name": "'},
]

formatted_chat = tokenizer.apply_chat_template(chat, tokenize=True, return_dict=True, continue_final_message=True)
model.generate(**formatted_chat)

يجب ألا تستخدم add_generation_prompt و continue_final_message معًا. يضيف الأول رموزًا تبدأ رسالة جديدة، بينما يزيل الثاني رموز نهاية التسلسل. يؤدي استخدامهما معًا إلى إرجاع خطأ.

مرّر اسم الحقل كنص (string) لملء ذلك الحقل مسبقًا بدلاً من content. تقرأ نماذج الاستدلال استدلالها من حقل منفصل باسم reasoning_content. يؤدي ملء content مسبقًا إلى إغلاق كتلة الاستدلال قبل بدء التوليد، مما يمنع النموذج من المتابعة داخلها. أما ملء reasoning_content مباشرةً فيبقي الكتلة مفتوحة.

chat = [
    {"role": "user", "content": "Explain 1+1"},
    {"role": "assistant", "reasoning_content": "The user wants a simple addition. ", "content": ""},
]

formatted_chat = tokenizer.apply_chat_template(chat, tokenize=False, continue_final_message="reasoning_content")

يجب أن يكون الحقل المُسمّى موجودًا في الرسالة النهائية وأن يُشار إليه بواسطة قالب المحادثة. يتم رفع خطأ عند فشل أيٍّ من هذين الفحصين.

يُعيّن TextGenerationPipeline قيمة add_generation_prompt إلى True افتراضيًا لبدء رسالة جديدة. ومع ذلك، إذا كانت الرسالة الأخيرة في المحادثة تحمل دور assistant، فإنها تفترض أن الرسالة هي تعبئة مسبقة (prefill) وتتحول إلى continue_final_message=True. والسبب في ذلك أن معظم النماذج لا تدعم رسائل متتالية متعددة من المساعد. لتجاوز هذا السلوك، مرّر معامل continue_final_message صراحةً إلى خط الأنابيب.

تدريب النموذج

تدريب نموذج مع قالب محادثة هو طريقة جيدة للتأكد من أن القالب يطابق الرموز التي تم تدريب النموذج عليها. طبّق قالب المحادثة كخطوة ما قبل المعالجة على مجموعة البيانات الخاصة بك. اضبط add_generation_prompt=False لأن الرموز الإضافية لتحفيز استجابة المساعد ليست مفيدة أثناء التدريب.

يُظهر ما يلي مثالًا على معالجة مجموعة بيانات مسبقة باستخدام قالب محادثة.

from transformers import AutoTokenizer
from datasets import Dataset

tokenizer = AutoTokenizer.from_pretrained("HuggingFaceH4/zephyr-7b-beta")

chat1 = [
    {"role": "user", "content": "Which is bigger, the moon or the sun?"},
    {"role": "assistant", "content": "The sun."}
]
chat2 = [
    {"role": "user", "content": "Which is bigger, a virus or a bacterium?"},
    {"role": "assistant", "content": "A bacterium."}
]

dataset = Dataset.from_dict({"chat": [chat1, chat2]})
dataset = dataset.map(lambda x: {"formatted_chat": tokenizer.apply_chat_template(x["chat"], tokenize=False, add_generation_prompt=False)})
print(dataset['formatted_chat'][0])
<|user|>
Which is bigger, the moon or the sun?</s>
<|assistant|>
The sun.</s>

بعد هذه الخطوة، يمكنك المتابعة في اتباع وصفة التدريب لنماذج اللغة السببية باستخدام العمود formatted_chat.

Update on GitHub