TRL documentation

مدرب GRPO غير المتزامن

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

and get access to the augmented documentation experience

to get started

مدرب GRPO غير المتزامن

model badge

نظرة عامة

يُنفّذ AsyncGRPOTrainer خوارزمية GRPO نفسها، لكنه يفصل بين توليد التمرير (rollout) والتدريب. يعمل عامل في الخلفية على بثّ الاكتمالات (completions) بشكل مستمر من خادم vLLM بينما تستهلك حلقة التدريب هذه الاكتمالات، مما يؤدي إلى تداخل عمليات التوليد وتحديثات التدرّجات بدلاً من تعاقبها.

تمت مساهمة هذا المدرب بواسطة Amine Dirhoussi و Quentin Gallouédec.

البدء السريع

يُظهر هذا المثال كيفية تدريب نموذج باستخدام طريقة GRPO غير المتزامنة. نقوم بتدريب نموذج Qwen3.5 2B باستخدام الأوامر من مجموعة بيانات DeepMath-103K. يمكنك عرض البيانات في مجموعة البيانات هنا:

فيما يلي السكربت لتدريب النموذج. يحتاج المدرب إلى vLLM و kernels، ويحتاج accuracy_reward إلى math-verify، والذي يمكنك تثبيته باستخدام pip install trl[vllm,kernels,math_verify].

# train_async_grpo.py
from datasets import load_dataset
from trl import AsyncGRPOTrainer
from trl.rewards import accuracy_reward

if __name__ == "__main__":  # the rollout worker is a spawned process that re-imports this script
    dataset = load_dataset("trl-lib/DeepMath-103K", split="train")

    trainer = AsyncGRPOTrainer(
        model="Qwen/Qwen3.5-2B",
        reward_funcs=accuracy_reward,
        train_dataset=dataset,
    )
    trainer.train()

تعمل عملية التوليد على خادم vLLM، على وحدات معالجة رسومية مختلفة عن وحدات المعالجة الرسومية الخاصة بمدرّب النموذج. ابدأ تشغيله على وحدة معالجة رسومية 0:

CUDA_VISIBLE_DEVICES=0 VLLM_SERVER_DEV_MODE=1 vllm serve Qwen/Qwen3.5-2B \
    --weight-transfer-config '{"backend": "nccl"}' \
    --logprobs-mode processed_logprobs \
    --api-server-count 8 \
    --max-model-len 4096

ثم نفّذ السكربت على وحدة معالجة الرسوميات 1 باستخدام الأمر التالي:

CUDA_VISIBLE_DEVICES=1 python train_async_grpo.py

التوافق والقيود

بعض تركيبات المعاملات مقيدة عمداً في تطبيق AsyncGRPOTrainer الحالي:

  • تعمل عملية التوليد دائمًا على خادم vLLM منفصل، يُبدأ باستخدام vllm serve. لا يوجد وضع للتكامل (colocate).
  • يجب أن تكون الأوامر (Prompts) في مجموعة البيانات بصيغة حوارية. عند استخدام environment_factory، قد لا تحتوي مجموعة البيانات على عمود prompt، حيث يوفر reset() ذلك.
  • يجب أن تكون دوال المكافآت قابلة للاستدعاء بلغة بايثون. لا يتم دعم نماذج المكافآت المُعطاة كمعرّف نموذج أو كـ PreTrainedModel، راجع استخدام دالة مكافأة مخصصة.
  • يتطلب المدرب transformers>=5.2.0.
  • يستخدم التدريب المجزأ FSDP2. لا يتم دعم DeepSpeed، كما لا يتم دعم توازلي السياق والتسلسل (cp_size > 1 أو sp_size > 1 في parallelism_config).
  • use_liger_kernel=True غير مدعوم.
  • لا يتم دعم محولات PEFT المستهدفة lm_head.
  • لا يتم دعم النماذج التي تحتوي على نقاط تركيز انتباه (attention sinks). الطبقات غير المتعلقة بالانتباه الوحيدة المدعومة هي طبقات DeltaNet ذات البوابة (gated) في النماذج الهجينة (Qwen3.5, Qwen3.6, Qwen3-Next, …)، والتي تتطلب transformers>=5.15.0.
  • لا يتم دعم الصور، راجع تدريب نماذج اللغة البصرية.

تحمّل عملية التدريب نواة الانتباه من Hub عبر مكتبة kernels: kernels-community/aiter-flash-attn على وحدات معالجة الرسوميات AMD (ROCm)، وkernels-community/flash-attn4 على وحدات معالجة الرسوميات NVIDIA Blackwell، وkernels-community/flash-attn3 على وحدات معالجة الرسوميات NVIDIA الأخرى. النماذج التي تحدّ من قيم الانتباه (attn_logit_softcapping)، مثل Gemma 2، غير مدعومة على وحدات معالجة الرسوميات AMD أو على وحدات معالجة الرسوميات NVIDIA Blackwell.

النظر بعمق أكبر في طريقة GRPO غير المتزامنة

في GRPOTrainer، تكون عملية التوليد والتدريب متسلسلة: توليد دفعة، حساب الخسارة، تحديث الأوزان، ثم التكرار. حتى في وضع colocate، يجب أن ينتهي أحد المرحلتين قبل بدء الأخرى. أما في AsyncGRPOTrainer، فيقوم عامل التوليد (rollout worker) بتوليد الإكمالات وتقييمها في عملية خلفية، بينما يقوم حلقة التدريب بحساب الخسارة وتحديث النموذج. لفهم آلية عمله، يمكن تقسيمه إلى ثلاث خطوات رئيسية: توليد الإكمالات، حساب الميزة (advantage)، وحساب الخسارة. لا يوجد نموذج مرجعي، وبالتالي لا يوجد حد لانحراف كولباك-ليبلر (KL divergence) (وهو الافتراضي في GRPO، beta=0.0).

توليد الاكتمالات

يحتفظ عامل النشر (rollout worker) بحد أقصى max_concurrent_rollouts طلبات قيد التنفيذ إلى خادم vLLM. لكل موجه (prompt)، يولّد مجموعة من G G اكتمالات (num_generations) )، وحين اكتمال المجموعة بأكملها، يُقيّمها ويحسب مزاياها. تنتقل عينات التدريب الناتجة إلى طابور (queue) يسحب منه حلقة التدريب. لا تنتظر عملية التوليد إلا عندما تتقدم max_staleness خطوات على التدريب (انظر التزامن), )، ولا ينتظر خطو تدريب إلا عندما يكون الطابور فارغًا.

كل weight_sync_steps خطوات تدريب، يرسل المدرب أوزانه المحدّثة إلى خادم vLLM عبر NCCL، بحيث تأتي المكملات التالية من أحدث سياسة. لذلك، قد يأتي مكمل من سياسة أقدم بضع خطوات من السياسة قيد التدريب: أي عينة تتأخر بأكثر من max_staleness تحديثات أوزان عند سحبها من قبل المدرب تُستبعد.

تستخدم المقايير وبقية هذه الصفحة المصطلحات التالية للمراحل التي يمر بها الإكمال:

  • a مجموعة هي موجه واحدة مع G G منها. يتم حساب الميزة داخلها.
  • التوليد rollout هو محادثة كاملة واحدة يتم توليدها لموجّه، ويشمل كل استدعاء للأدوات. يتكوّن من دورة أو أكثر turns، حيث تكون كل دورة طلبًا واحدًا إلى vLLM.
  • يُحسب الخسارة على عينة: وهي الرموز (tokens) لتمريرة (rollout) مع ميزتها (advantage). يمكن لتمريرة متعددة الأدوار أن تنتج أكثر من عينة (انظر التمريرات متعددة الأدوار).
  • التُصَف row هي ما يمرّره كل عملية في دفعة مصغّرة واحدة: عدة عينات مجمّعة معًا (انظر تعبئة الشجرة). تحتوي الدفعة المصغّرة على صف واحد لكل عملية، وتُشكّل gradient_accumulation_steps دفعات مصغّرة خطوة واحدة step، أي خطوة المُحسِّن.

تأتي أعداد الرموز بثلاثة أنواع: الرموز المولَّدة هي ما أنتجه النموذج، والرموز المُرسَلة هي كل رُموز العينة (بما في ذلك الاستدعاء ونتائج الأدوات)، والرموز المدرَّبة هي التي يُحسب عليها الخسارة.

حساب الميزة

بالنسبة لكل من G G اكتمالات المجموعة، يكون المكافأة هي مجموع المكافآت من كل دالة مكافأة. يتم تطبيع الميزة داخل المجموعة، كما في GRPO: A^i,t=ri−mean(r)std(r)\hat{A}_{i,t} = \frac{r_i - \text{mean}(\mathbf{r})}{\text{std}(\mathbf{r})}

لا يوجد خيار scale_rewards: يتم دائمًا قياس الميزة (advantage) باستخدام الانحراف المعياري للمجموعة. يُستبعد أي إكمال (completion) عادت جميع دوال المكافأة فيه بقيمة None من حساب المتوسط والانحراف المعياري للمجموعة، ويحصل على ميزة تساوي صفرًا.

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

حساب الخسارة

لا تتطابق احتمالات السجلات (log-probabilities) في خادم vLLM تمامًا مع تلك الخاصة بمدرّب النموذج: نواة مختلفة، ودقة مختلفة، وسياسة متأخرة بضع خطوات (انظر التعامل مع عدم التطابق بين التدريب والاستدلال). يستخدم المدرّب احتمالات السجلات من العيّنة (sampler) كسياسة قديمة، لذا تغطي نسبة الأهمية الواحدة كلًا من عدم التطابق والتأخر: ri,t(θ)=πθ(oi,t∣q,oi,<t)πsampler(oi,t∣q,oi,<t). r_{i,t}(\theta) = \frac{\pi_\theta(o_{i,t} \mid q, o_{i,< t})}{\pi_{\text{sampler}}(o_{i,t} \mid q, o_{i,< t})}.

تُستبعد الرموز التي يقع نسبة خارج [vllm_importance_sampling_clip_min, vllm_importance_sampling_clip_max] من الخسارة. لمزيد من المعلومات، راجع Masked Importance Sampling.

بشكل افتراضي (score_centering=True), يستخدم المدرب مركزية الدرجات. لا تُزيل أوزان الأهمية وحدها عدم التطابق: فدرجة ∇θlog⁡πθ \nabla_\theta \log \pi_\theta لرمز مُسحَب لا تتوسط إلى صفر تحت المُسحِب μ \mu , لذلك، كل تحديث يسحب السياسة نحو المُسحِب أيضًا. تقوم مركزية الدرجات بطرح ذلك التوقع في كل موضع: ∇θL=−Eo∼μ[A^(f(rt)∇θlog⁡πθ(ot)−Ev∼μ[f(rv)∇θlog⁡πθ(v)])],rv=πθ(v)μ(v), \nabla_\theta \mathcal{L} = - \mathbb{E}_{o \sim \mu} \left[ \hat{A} \left( f(r_t) \nabla_\theta \log \pi_\theta(o_t) - \mathbb{E}_{v \sim \mu} \left[ f(r_v) \nabla_\theta \log \pi_\theta(v) \right] \right) \right], \quad r_v = \frac{\pi_\theta(v)}{\mu(v)},

حيث f(r)=r f(r) = r داخل [Cmin⁡,Cmax⁡] [C_{\min}, C_{\max}] و 0 0 خارجها. يتم حساب التوقع الداخلي على num_sampler_logprobs أكثر الرموز احتمالًا في كل موضع، والتي يطلبها عامل التمرين من الخادم.

مع score_centering=False, يستخدم المدرب هدف GRPO البديل المقصوص بدلاً من ذلك، مع نفس النسبة المقنّعة: L(θ)=−1∑i∣oi∣∑i∑t=1∣oi∣1[Cmin⁡≤ri,t(θ)≤Cmax⁡]min⁡(ri,t(θ)A^i,t, clip(ri,t(θ),1−ϵlow,1+ϵhigh)A^i,t), \mathcal{L}(\theta) = - \frac{1}{\sum_{i} |o_i|} \sum_{i} \sum_{t=1}^{|o_i|} \mathbb{1}\left[ C_{\min} \le r_{i,t}(\theta) \le C_{\max} \right] \min \left( r_{i,t}(\theta) \hat{A}_{i,t}, \, \text{clip}\left( r_{i,t}(\theta), 1 - \epsilon_{\text{low}}, 1 + \epsilon_{\text{high}} \right) \hat{A}_{i,t} \right),

حيث ϵlow \epsilon_{\text{low}} هو epsilon و ϵhigh \epsilon_{\text{high}} هو epsilon_high.

في الحالتين، يتم تطبيع الخسارة على عدد الرموز المدرَّبة في الدفعة الجزئية، مع احتسابها عبر جميع العمليات، ثم أخذ متوسطها على دفعات gradient_accumulation_steps الجزئية لكل خطوة.

المقاييس المسجّلة

أثناء التدريب، نسجّل المقاييس التالية:

  • num_tokens: إجمالي عدد الرموز المُرسَلة حتى الآن، سواء كانت أوامر أو استكمالات. عند استخدام حزم الأشجار، يُحتسب البادئة المشتركة بين عدة عينات مرة واحدة فقط.
  • completions/mean_length: متوسط طول المكملات المولّدة. عند استخدام الأدوات، تُحتسب الرموز غير المتعلقة بالأدوات فقط.
  • completions/min_length: الحد الأدنى لطول المكملات المولّدة. عند استخدام الأدوات، تُحتسب الرموز غير المرتبطة بالأدوات فقط.
  • completions/max_length: الحد الأقصى لطول المكملات المولّدة. عند استخدام الأدوات، يتم احتساب رموز غير الأدوات فقط.
  • completions/mean_terminated_length: متوسط طول الإكمالات المولّدة التي تنتهي بـ EOS. عند استخدام الأدوات، يتم احتساب رموز غير الأدوات فقط.
  • completions/min_terminated_length: الحد الأدنى لطول المكملات المولّدة التي تنتهي بـ EOS. عند استخدام الأدوات، يتم احتساب رموز غير الأدوات فقط.
  • completions/max_terminated_length: الحد الأقصى لطول المكملات المولّدة التي تنتهي بـ EOS. عند استخدام الأدوات، يتم احتساب رموز غير الأدوات فقط.
  • completions/clipped_ratio: نسبة الإكمالات المقطوعة (المُقصّاة) التي لا ينتهي دورها الأخير برمز EOS.
  • completions/duplicate_frac: نسبة التكرارات التي تُعيد تكرار تكرار سابق من مجموعتها رمزًا برمز. راجع التشبع والتكرارات.
  • tools/call_frequency: متوسط عدد استدعاءات الأدوات لكل إكمال، عبر كل تمرين، بما في ذلك المجموعات التي يتم حذفها لاحقًا. يُسجَّل فقط عندما يحتوي المدرب على أدوات.
  • tools/failure_frequency: نسبة استدعاءات الأدوات التي فشلت (لم يتم العثور على الأداة أو أثار استثناءً) مقارنةً بجميع استدعاءات الأدوات، بما في ذلك المجموعات التي يتم استبعادها لاحقًا. تكون القيمة NaN في نافذة تسجيل لا تحتوي على أي استدعاء للأداة، حيث يسجّل GRPO القيمة 0.0. يُسجَّل هذا المقياس فقط عندما يمتلك المدرب أدوات.
  • rewards/{reward_func_name}/mean: متوسط المكافأة من دالة مكافأة محددة، عبر كل إكمال قيّمته، بما في ذلك المجموعات التي يتم حذفها لاحقًا. عندما تمتلك البيئة المكافأة عبر get_reward، يكون {reward_func_name} اسم فئة البيئة.
  • rewards/{reward_func_name}/group_std: الانحراف المعياري للمكافأة من دالة مكافأة محددة، يُحسب داخل كل مجموعة ويُمتص على العينات المدرَّبة.
  • reward: متوسط المكافأة الإجمالي بعد جمع المكافآت عبر الدوال، على جميع الإكتمالات المُقيَّمة، بما في ذلك المجموعات التي يتم استبعادها لاحقًا. إنها مكافأة السياسة، لذا فهي القيمة التي يجب متابعتها: reward_kept تستثني المجموعات التي تكون جميع مكافآتها متساوية.
  • reward_group_std: الانحراف المعياري للمكافآت المجمّعة عبر الدوال، ويُحسب داخل كل مجموعة ثم يُمتص على العينات المدرَّبة. وهو القيمة التي يُقسَّم عليها الميزة (advantage)، على عكس reward_std في GRPO الذي يُحسب على الدفعة (batch) بأكملها.
  • reward_kept: متوسط المكافأة للعينات المدرَّبة، مع استبعاد المجموعات المُستبعَدة.
  • reward_dropped: متوسط المكافأة للمجموعات التي تم استبعادها، أي المكافأة التي علقوا عندها. راجع التشبع والتكرارات لقراءتها مع frac_reward_zero_std وreward.
  • frac_reward_zero_std: نسبة المجموعات التي تكون انحراف مكافأتها المعياري صفراً (جميع الإجابات صحيحة أو خاطئة)، أو التي تحتوي على أقل من إكمالين قابلين للتقييم. تُستبعد هذه المجموعات.
  • sampling/sampling_logp_difference/mean: متوسط الفرق المطلق بين احتمالات اللوغاريتم التي يعيدها العيّان (vLLM) وتلك التي يعيد حسابها نموذج التدريب، على الرموز المدرَّبة.
  • sampling/sampling_logp_difference/max: أكبر فرق مطلق من هذا النوع.
  • sampling/importance_sampling_ratio/min: أصغر نسبة أخذ عينات مهمة ri,t(θ) r_{i,t}(\theta) بين النموذج وأداة أخذ العينات، عبر الرموز المدرَّبة، قبل استبعاد الرموز الواقعة خارج [vllm_importance_sampling_clip_min, vllm_importance_sampling_clip_max] .
  • sampling/importance_sampling_ratio/mean: متوسط نسبة أخذ العينات المهمة.
  • sampling/importance_sampling_ratio/max: أكبر نسبة أخذ عينات أهمية من هذا النوع.
  • sampling/masked_mean: نسبة الرموز المدرَّبة التي تم استبعادها من الخسارة لأن نسبة أخذ العينة المهمة الخاصة بها تقع خارج [vllm_importance_sampling_clip_min, vllm_importance_sampling_clip_max].
  • sampling/sampler_mass: متوسط كتلة الاحتمال التي تغطيها الرموز الأكثر احتمالًا بعدد num_sampler_logprobs لمُعيّن العينات. يُسجَّل فقط عندما يكون score_centering=True.
  • entropy: متوسط إنتروبيا توقعات الرموز عبر الرموز المدرَّبة.
  • aux_loss: خسارة التوازن على الأحمال المساعدة لنموذج مزيج الخبراء، قبل أن تُضرب في router_aux_loss_coef وتُضاف إلى الخسارة. تُسجَّل فقط عندما يكون النموذج من نوع MoE وrouter_aux_loss_coef غير صفري.
  • clip_ratio/region_mean: نسبة احتمالات الرموز التي يُقصّ فيها هدف GRPO ليبقى ضمن منطقة الثقة: clip(ri,t(θ),1−ϵlow,1+ϵhigh) \text{clip}\left( r_{i,t}(\theta), 1 - \epsilon_\mathrm{low}, 1 + \epsilon_\mathrm{high} \right) . مع score_centering=True لا يُقصّ الخسارة، لذا فإن clip_ratio/* المقاييس تعدّ الرموز خارج منطقة الثقة.
  • clip_ratio/low_mean: متوسط نسبة احتمالات الرموز التي تم اقتطاعها عند الحد الأدنى لمنطقة الثقة: ri,t(θ)<1−ϵlowr_{i,t}(\theta) < 1 - \epsilon_\mathrm{low}.
  • clip_ratio/low_min: أصغر نسبة لكل إكمال من الرموز المقصوصة عند الحد الأدنى لمنطقة الثقة: ri,t(θ)<1−ϵlowr_{i,t}(\theta) < 1 - \epsilon_\mathrm{low}.
  • clip_ratio/high_mean: متوسط نسبة احتمالات الرموز التي تم اقتطاعها عند الحد الأعلى لمنطقة الثقة: ri,t(θ)>1+ϵhighr_{i,t}(\theta) > 1 + \epsilon_\mathrm{high}.
  • clip_ratio/high_max: أعلى نسبة لكل إكمال من الرموز المقصوصة عند الحد الأعلى لمنطقة الثقة: ri,t(θ)>1+ϵhighr_{i,t}(\theta) > 1 + \epsilon_\mathrm{high}.

تصف المقاييس الأخرى خط الأنابيب غير المتزامن.

تُسجَّل المقاييس مرة واحدة لكل نافذة تسجيل: كل ما تم تسجيله منذ آخر تسجيل، والتي تغطي آخر logging_steps خطوات مُحسِّن. على مدى النافذة:

  • يتم حساب المتوسط المقاس لكل تمرين أو عينة أو استدعاء، مثل reward وrollout/turns_mean وtools/latency_s، عن طريق أخذ متوسط جميع التمارين أو العينات أو الاستدعاءات ضمن النافذة. أما المعدل مثل rollout/generated_tokens_per_s فيُحسب بقسمة إجمالي الرموز على إجمالي الثواني.
  • وإلا، يُجمَّع الاسم الذي يحتوي الجزء الأخير منه على كلمة count: sample/dropped_stale_count هو عدد العينات المُهمَلة منذ آخر تسجيل، وليس منذ بداية التدريب.
  • يحتفظ الاسم الذي يحتوي الجزء الأخير منه على الكلمة max أو min، مثل sample/staleness_max أو completions/max_length، بالقيمة الأكبر أو الأصغر.
  • أي مقياس آخر هو متوسط القيم المسجلة في النافذة.

وحدات:

  • _s: ثوانٍ.
  • tokens_per_s: عدد الرموز في الثانية.
  • _frac: نسبة بين 0 و 1.
  • _per_step: الكمية في خطوة مُحسِّن واحدة، مُتوسّطة على الخطوات في النافذة.

مقاييس النشر

  • rollout/duration_s: الوقت الفعلي (Wall time) لتمريرة واحدة (rollout)، بدءًا من الإرسال (dispatch) وحتى تجمع حلقة التمريرة (rollout loop) لها.
  • rollout/generated_tokens_per_s: معدل الإنتاجية في التوليد، بوحدة الرموز المولّدة في الثانية، منذ التقرير السابق.
  • rollout/concurrency: عدد عمليات النشر الجارية إلى خادم vLLM.
  • rollout/concurrency_cap: الحد الحالي لعمليات النشر الجارية، والذي يتبع حمل الخادم. راجع التزامن.
  • rollout/turns_mean، rollout/turns_max: متوسط وأقصى عدد للجولات في كل تمريرة.
  • rollout/samples_per_rollout: عدد عينات التدريب لكل تمريرة. عند قيمة أعلى من 1.0، تم تفرع بعض التمريرات؛ وعند قيمة أقل من 1.0، لم تنتج بعض التمريرات أي رمز مدرَّب. راجع التمريرات متعددة الأدوار.
  • rollout/fork_frac: نسبة انتقالات الأدوار التي لا يبدأ فيها الموجه (prompt) بالرموز المحفوظة، مما يعني أن الدور بدأ عينة جديدة.
  • rollout/drift_tokens_mean، rollout/drift_tokens_max: عدد الرموز المحفوظة التي أعاد الفرع عرضها، مُتوسطًا على انتقالات الأدوار (يُحتسب الفرع الذي لا يتفرع على أنه 0). يتشارك العينة الجديدة الرموز قبل نقطة التفرع، لذا فإن هذا هو ما يضيفه كل فرع إلى الأمام.
  • rollout/score_queue_size: عدد المجموعات المكتملة التي تنتظر التقييم.
  • rollout/queue_size: عدد العينات المُقيَّمة التي تنتظر في طابور rollout، ويُقاس ذلك في النقطة التي يسحبها منها المدرب. راجع طابور rollout.
  • rollout/score_s: الوقت اللازم لتقييم مجموعة.
  • rollout/score_wait_s: المدة التي انتظرها مجموعة مكتملة قبل أن تُقيَّم.
  • rollout/score_block_s: الوقت الذي تم فيه حظر التوليد لأن طابور التقييم كان ممتلئًا.
  • rollout/backpressure_s: الوقت الذي انتظرت فيه عملية التوليد لبدء الجولة لأنها كانت متقدمة على التدريب بعدد خطوات يساوي max_staleness، محسوبًا كمتوسط على فترات الانتظار ضمن نافذة التسجيل. راجع التزامن.
  • rollout/vllm_retry_count: عدد طلبات vLLM التي تم إعادة إرسالها. وإلا فإن الخادم المتدهور سيبدو وكأنه بطء غير مبرر.
  • rollout/vllm_kv_cache_usage_frac: نسبة استخدام ذاكرة KV في vLLM (القيمة القصوى عبر المحركات)، مُتوسّطة على فترات الاستطلاع ضمن نافذة التسجيل.
  • rollout/vllm_running_requests: عدد الطلبات التي تقوم محركات الخادم بفك ترميزها (قيمة num_requests_running في vLLM، مجمّعة عبر المحركات). الفارق عن rollout/concurrency هو ما يتواجد في عملية خادم الواجهة البرمجية، أثناء بناء الاستجابات.
  • rollout/vllm_queued_requests: عدد الطلبات التي قبلها الخادم لكن لم تُجدول بعد (حقل num_requests_waiting في vLLM، مجمّعًا عبر المحركات).
  • rollout/vllm_preemption_count: عدد الطلبات التي أعاد الخادم تشغيلها (وهي num_preemptions_total في vLLM، مجمّعة عبر المحركات). يؤدي مزامنة الأوزان إلى إعادة تعيين ذاكرة التخزين المؤقت للخادم، مما يؤدي إلى إعادة تشغيل كل طلب قيد التنفيذ، لذا فإن هذا ليس إشارة ضغط.

مقاييس الأداة

  • tools/latency_s، tools/{tool_name}/latency_s: الوقت المستغرق لتنفيذ استدعاء أداة، على مستوى جميع الأدوات وعلى مستوى كل أداة على حدة.
  • tools/{tool_name}/call_count، tools/{tool_name}/failure_count: عدد الاستدعاءات والاستدعاءات الفاشلة لكل أداة.
  • tools/unknown_name_count: عدد الاستدعاءات لأداة غير موجودة. على عكس الأداة التي نُفِّذت وأثارت خطأً، فإن هذا خطأ في السياسة.
  • tools/parallel_calls_mean: متوسط عدد استدعاءات الأدوات في رسالة المساعد التي تستدعي الأدوات.
  • tools/loop_exhausted_frac: نسبة عمليات التمرين التي تم قطعها عند max_tool_calling_iterations بينما لا تزال تستدعي الأدوات، مقسومة على إجمالي عمليات التمرين، بما في ذلك عمليات التمرين التي لا تحتوي على أي استدعاء والمجموعات التي يتم استبعادها لاحقًا. يُسجَّل هذا المقياس فقط عندما يحتوي المدرب على أدوات.

يسجّل HarnessAsyncGRPOTrainer معظم هذه العناصر بنفس الأسماء. راجع التدريب على الهارنس للاطلاع على الفروقات.

مقاييس البيئة

  • env/reset_s: الوقت المستغرق في reset() للبيئة قبل بدء الجولة. يُسجَّل فقط عند توفير environment_factory.
  • env/reward_s: الوقت المستغرق في get_reward() للبيئة بعد انتهاء الجولة. يُسجَّل فقط عندما تُعرِّف البيئة get_reward.

مقاييس نموذجية

  • sample/forwarded_tokens_mean، sample/forwarded_tokens_max: متوسط وأقصى عدد من الرموز المحوّلة لكل عينة.
  • sample/trained_tokens_mean: متوسط عدد الرموز المدرَّبة لكل عينة.
  • sample/time_in_queue_s: المدة التي انتظرها العينة في طابور النشر قبل أن تُدرَّب عليها.
  • sample/staleness_mean، sample/staleness_max: عدد تحديثات الأوزان بين السياسة التي ولّدت العينة وتلك التي تُدرَّب عليها. تجمع مقاييس sampling/importance_sampling_ratio/* وsampling/sampling_logp_difference/* هذا القِدم مع عدم التطابق بين التدريب والاستدلال: نسبة بعيدة عن 1.0 أو فرق في الاحتمال اللوغاريتمي بعيد عن 0.0 عند انعدام القِدم يشير إلى عدم التطابق، على سبيل المثال فرق في نوع البيانات (dtype) مع الخادم.
  • sample/dropped_stale_count: عدد العينات التي تم إسقاطها لتجاوزها max_staleness.

مقاييس الدفعة

  • batch/forwarded_tokens_per_step، batch/trained_tokens_per_step: عدد الرموز المُرسَلة والمدرَّبة في كل خطوة، عبر جميع العمليات.
  • batch/samples_per_step، batch/groups_per_step: عدد العينات وعدد المجموعات المميزة في كل خطوة. يختلفان بعدد العينات في كل مجموعة.
  • batch/microbatches_per_step: عدد الدُفعات الجزئية في كل خطوة.
  • batch/masked_token_frac: نسبة الرموز المُرسَلة التي لم يتم تدريبها.
  • batch/samples_per_row، batch/row_tokens_mean، batch/row_tokens_max: عدد العينات في كل صف، والمتوسط والأقصى لعدد الرموز المُرسَلة في كل صف.
  • batch/row_fill_frac: نسبة استخدام الصفوف من token_budget.
  • batch/row_imbalance: نسبة تكلفة الانتباه للصف الأكثر تكلفة إلى متوسط التكلفة. العملية التي تحتوي على الصف الأكثر تكلفة تُبطئ العمليات الأخرى عند تنفيذ 1.0 هو توازن مثالي.
  • batch/packing_ratio: عدد الرموز قبل التعبئة لكل رمز مُمرَّر: المعامل الذي تُصغّر به التعبئة العملية الأمامية، وقيمته 1.0 عندما لا يوجد شيء مشترك.
  • batch/pad_frac: نسبة الحشو المضاف لجعل الصفوف بنفس الطول. لا يتسبب إلا في تكلفة نقل، حيث يُزال قبل التمرير الأمامي.
  • batch/dropped_oversize_count: عدد العينات التي تم استبعادها لتجاوزها token_budget.

مقاييس الأداء

  • perf/step_s: الوقت الفعلي (Wall time) للخطوة، بما في ذلك المُحسِّن (optimizer) ومزامنة الأوزان والوقت المُستغرق في انتظار العينات.
  • perf/fwd_bwd_s، perf/fwd_s: الوقت المستغرق في المرورين الأمامي والخلفي لخطوة، وفي المرور الأمامي فقط.
  • perf/optimizer_s: الوقت المستغرق في خطوة المُحسِّن.
  • perf/rollout_wait_s: الوقت الذي انتظره التدريب لأن طابور rollout كان فارغًا. راجع طابور rollout.
  • perf/weight_sync_s: مدة مزامنة الأوزان، مقسمة إلى perf/weight_sync_pause_s (انتظار إيقاف vLLM مؤقتًا)، وperf/weight_sync_barrier_s (انتظار العمليات الأخرى)، وperf/weight_sync_transfer_s (إرسال الأوزان).
  • perf/fwd_bwd_forwarded_tokens_per_s، perf/wall_clock_forwarded_tokens_per_s: عدد الرموز المُرسَلة في الثانية الواحدة، سواءً خلال مسارات الأمامية والخلفية أو خلال الخطوة بأكملها.
  • perf/wall_clock_trained_tokens_per_s: عدد الرموز المدربة في الثانية الواحدة، على مدار الخطوة بأكملها.
  • perf/fwd_bwd_mfu، perf/wall_clock_mfu: نسبة استغلال عمليات FLOPs للنموذج على أجهزة التدريب، سواءً عبر المرورين الأمامي والخلفي أو عبر الخطوة الكاملة. تستثني هذه المقاييس خادم vLLM. تُسجَّل فقط عندما يكون الحد الأقصى للأداء الحاسوبي لجهاز التدريب عند دقة التدريب معروفًا، وعندما يدعم تقدير FLOPs بنية النموذج (مثل Llama، Qwen2، Qwen3، Qwen3-MoE أو Qwen3-VL).

التخصيص

تهيئة خادم vLLM

يولّد المدرب باستخدام خادم vLLM، والذي يصل إليه عبر vllm_server_base_url (افتراضياً http://localhost:8000):

VLLM_SERVER_DEV_MODE=1 vllm serve <model_name> \
    --weight-transfer-config '{"backend": "nccl"}' \
    --logprobs-mode processed_logprobs \
    --api-server-count 8 \
    --max-model-len 4096
  • VLLM_SERVER_DEV_MODE=1 و --weight-transfer-config يكشفان نقاط النهاية التي يرسل من خلالها المُدرِّب أوزانه.
  • يجعل --logprobs-mode processed_logprobs الخادم يعيد احتمالات السجل (log-probabilities) للتوزيع الذي تم أخذ العينة منه، بعد تطبيق درجة الحرارة و top-k/top-p، وهو ما يُقارن به نسبة الأهمية (importance ratio).
  • يحافظ --api-server-count 8 على أعلى قيم الاحتمال اللوجاريتمي (log-probabilities) التي تجعل تكلفة تقديم طلبات توسيط الدرجات منخفضة (انظر التحذير أدناه).
  • --max-model-len هو الحد الأقصى لطول التسلسل الكلي (الموجّه والإكمال) الذي تتوقعه. تقلل القيمة المنخفضة من استخدام ذاكرة وحدة معالجة الرسوميات (GPU) على الخادم، مما يحرر مساحة أكبر لذاكرة KV cache ويزيد من الإنتاجية. نقطة بداية جيدة هي طول الموجّه مضافًا إليه max_completion_length من إعداداتك. عند استخدام الأدوات، يضيف كل دور ما يصل إلى max_completion_length رمزًا بالإضافة إلى نتائج الأدوات، لذا اضبطه على أطول محادثة تتوقعها. إذا كان طول موجّه الدور مضافًا إليه max_completion_length أطول من --max-model-len، يرفض الخادم الطلب ويتوقف التنفيذ مع خطأ الخادم. مع top_k أو top_p، يتوقف الخادم عن ذلك الدور بدلاً من ذلك عند وصوله إلى --max-model-len، ويرفض فقط الموجّه الذي يكون طوله بالفعل بهذا القدر. وهو أيضًا القيمة الافتراضية لـ token_budget.

تأكد من أن الخادم يستخدم وحدات معالجة رسومية مختلفة عن تلك التي يستخدمها المدرب، وإلا فقد تواجه أخطاء NCCL. يمكنك تحديد وحدات المعالجة الرسومية المراد استخدامها باستخدام متغير البيئة CUDA_VISIBLE_DEVICES.

تُعدّ تسلسلية أعلى قيم الاحتمال اللوغاريتمي (log-probabilities) عملاً يُنفَّذ على وحدة المعالجة المركزية (CPU) في خادم واجهة برمجة التطبيقات (API server) الخاص بـ vLLM، ومع خادم واجهة برمجة التطبيقات المفرد الافتراضي، يُقلّص الإعداد num_sampler_logprobs=8 من إنتاجية التوليد إلى النصف. ابدأ الخادم باستخدام --api-server-count 8 في إصدار vLLM 0.32 أو أحدث: فقد قُيِّس على بطاقة H100 واحدة مع نماذج تتراوح بين 1.5 مليار و7 مليارات معامل، فتكلفة أعلى 8 قيم للوغ-احتمالات لا تتجاوز 3% من إنتاجية التوليد. أما في إصدار vLLM 0.31، فإن نفس العلم (flag) لا يزال يُسبّب خسارة تتراوح بين 20% و40%، وقد تم إصلاح ذلك في المصدر الأصلي عبر vllm-project/vllm#58181.

التزامن

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

العتمة. يحدد max_staleness عدد خطوات التدريب التي يمكن أن تتقدم بها عملية التوليد على المدرب. يتتبع العامل هذا الحد بالرموز (tokens)، لأن عدد العينات في الخطوة يتغير كلما أصبحت الإكمالات أطول أو أقصر. بعد كل خطوة، يعدّ المدرب عدد الرموز التي استخدمها، ويسمح للعامل بالاحتفاظ بعدد من الرموز في الطابور أو قيد المعالجة يصل إلى max(max_staleness, 1) ضعف ذلك العدد. قبل الخطوة الأولى، يفترض أن كل صف ممتلئ. يبدأ العامل مجموعة جديدة من عمليات التمرير (rollouts) فقط طالما كان تحت ذلك الحد. في البداية، لا يعرف بعد عدد الرموز التي تنتجها عملية تمرير واحدة، لذا يبدأ مجموعة واحدة في كل مرة حتى تكتمل المجموعة الأولى. تُهمل أي عينة لا تزال أقدم من max_staleness خطوات.

تحميل vLLM. إرسال عدد كبير جدًا من الطلبات إلى vLLM في وقت واحد يجعله أبطأ: تمتلئ ذاكرة KV، ويتعين على vLLM إيقاف بعض الطلبات مؤقتًا وإعادة تنفيذها. لذلك يحافظ العامل أيضًا على حدّ حيّ لعدد عمليات التمرير (rollouts) التي تُنفَّذ في وقت واحد، ويُسجَّل ذلك باسم rollout/concurrency_cap، ويتحقق من /metrics الخاص بالخادم كل 5 ثوانٍ لضبطه. يبدأ الحدّ بعدد الطلبات التي تتسع لها ذاكرة KV عند الطول الأقصى (KV cache tokens / max_model_len)، حتى لا يُحمَّل الخادم بشكل زائد في البداية. طالما يبقى استخدام ذاكرة KV دون 60% ولا توجد طلبات في الانتظار، يرتفع الحدّ بنسبة 25%، وبحدٍّ أقصى مرة واحدة لكل دفعة من عمليات التمرير المكتملة تعادل قيمة الحدّ. عندما يتجاوز الاستخدام 80%، ينخفض الحدّ بنسبة 30%: تستمر الطلبات الجارية في التنفيذ، بينما تنتظر الطلبات الجديدة. إذا لم يكن لدى الخادم نقطة نهاية /metrics، يكون الحدّ هو max_concurrent_rollouts.

الموارد. max_concurrent_rollouts (الافتراضي 512) هو الحد الأقصى لعدد عمليات التمرين (rollouts) التي تعمل في وقت واحد. إنه الحد الوحيد الذي تحدده بنفسك، لأن المدرب لا يستطيع معرفة ما يمكن لبيئاتك وأدواتك التعامل معه. كل عملية تمرين تعمل تستخدم بيئة، ويمكنها تنفيذ استدعاءات أدوات متزامنة على مجموعة خيوط (thread pool) بهذا الحجم، وتبقي اتصالاً مفتوحًا مع خادم vLLM. اضبطه بما يتناسب مع ما يمكن لهذه المكونات التعامل معه، على سبيل المثال عدد الجلسات التي يقبلها خادم بيئتك:

from trl import AsyncGRPOConfig

training_args = AsyncGRPOConfig(
    ...,
    max_concurrent_rollouts=64,
)

إذا كانت منخفضة جداً، فإن عملية التوليد تتباطأ. يحتفظ كل مسار (rollout) بمكانه (slot) أثناء تشغيله للأدوات أو أثناء الانتظار بين الأدوار، وليس فقط أثناء توليد vLLM، لذا فإن المسارات التي تستدعي العديد من الأدوات تحتاج إلى مزيد من الأماكن للحفاظ على انشغال vLLM. إذا كانت أعلى بكثير من القيمة الافتراضية، فقد يبدأ خادم vLLM في إسقاط الاتصالات.

تُظهر المقاييس المسجّلة الحدّ الذي يُبطئ عملية التوليد:

  • rollout/backpressure_s أكبر من صفر: حدّ التقادم. التوليد يسبق التدريب، وهو أمر متوقع عندما يكون الاستدلال أسرع من التدريب.
  • rollout/concurrency عند rollout/concurrency_cap، أقل من max_concurrent_rollouts: حمل خادم vLLM.
  • rollout/concurrency عند max_concurrent_rollouts، مع انخفاض rollout/vllm_kv_cache_usage_frac وارتفاع perf/rollout_wait_s: الحد الأقصى. ارفع max_concurrent_rollouts إذا سمحت لك بيئاتك وأدواتك بذلك.

قائمة الانتظار للنشر

يدفع العامل العينات المُقيَّمة إلى طابور، بينما يسحب المدرب منها. أربعة مقاييس تصف ذلك الطابور:

  • rollout/queue_size: عدد العينات التي تنتظر في الطابور.
  • sample/time_in_queue_s: المدة التي انتظرها عينة واحدة قبل أن تُدرَّب عليها، وهي الجزء الزمني من قِدَمها.
  • perf/rollout_wait_s: المدة التي انتظر فيها التدريب بسبب فراغ الطابور.
  • rollout/backpressure_s: المدة التي انتظرت فيها عملية التوليد لأنها كانت متقدمة على التدريب بعدد خطوات يساوي max_staleness.

العمودان الأخيران هما صور مرآة لبعضهما. عند قراءتهما مع حجم الطابور، يخبرانك بأي جانب هو عنق الزجاجة:

  • الطابور شبه فارغ، وperf/rollout_wait_s مرتفع: مقيّد بالتوليد. المدرب يعاني من الجوع؛ راجع rollout/generated_tokens_per_s وrollout/concurrency وrollout/score_queue_size.
  • طابور يحتفظ بحوالي max_staleness خطوات من الرموز، وrollout/backpressure_s مرتفع: مقيّد بالمدرب. يتم كبح التوليد، ويتراوح sample/staleness_mean بالقرب من حدّه.
  • كلاهما مرتفع: حد max_staleness نفسه ضيق للغاية. عمليات التمرير الجارية تملأ هذا الحد قبل أن تصل عيناتها إلى الطابور، فينتظر المدرب بينما يتم كبح التوليد. ارفع max_staleness.
  • كلاهما قريب من الصفر: متوازن.

التشبع والتكرارات

يتعلّم السياسة من الفروقات بين عمليات النشر (rollouts) لمجموعة: حيث يُحسب كل ميزة (advantage) بالنسبة إلى متوسط مكافأة المجموعة. عندما تحصل جميع عمليات النشر في مجموعة على نفس المكافأة، فإنها جميعًا تحصل على ميزة تساوي صفرًا، وبالتالي تُستبعد المجموعة (انظر المقاييس المسجّلة) ولا تعلّم السياسة شيئًا. ومع تشبّع التشغيل، تنتهي مجموعات أكثر فأكثر بهذه الطريقة، إما لأن كل عملية نشر تحلّ الموجه (prompt) أو لأن لا واحدة منها تفعل. أربعة مقاييس تميّز بين هذه الحالات، وكل مقياس منها يجيب عن سؤاله الخاص، مما يتيح لك قراءتها مباشرةً من رسومها البيانية:

مقياسسؤال
frac_reward_zero_stdما هي نسبة المجموعات التي لا تحمل أي إشارة
reward_droppedحيث تجمّدت تلك المجموعات: متوسط مكافأتها. في حالة المكافآت 0/1، يمثل هذا الرقم نسبة المجموعات التي نجحت فيها جميع عمليات التمرير، لذا فإن 0.9 تعني أن معظمها تم حلّه، و0.1 تعني أن معظمها يفشل في كل عملية تمرير
rewardمدى أداء السياسة عبر كل مجموعة مُقيَّمة، بما في ذلك المجموعات المُستبعَدة
completions/duplicate_fracما إذا كانت عملية أخذ العينات لا تزال تعطي نتائج مختلفة لنفس الاستعلام

اقرأ الثلاثة الأولى معًا، مقابل نطاق مكافأتك:

frac_reward_zero_stdreward_droppedrewardتشخيصما يجب فعله
منخفضأي (ضوضاء على عدد قليل من المجموعات)أيسليم: معظم المجموعات تنتج تدرجًالا شيء
الصعودقريباً من أعلى النطاقالصعودالوصول إلى حد التشبع في القمة: أصبحت التلميحات سهلة للغاية بالنسبة للسياسة، وهو ما يحدث عادةً في نهاية تشغيل ناجحإيقاف التشغيل، أو الانتقال إلى موجهات أكثر صعوبة
مرتفعقرب نهاية المدىمنخفض ومسطحالانحشار في القاع: تكون الأوامر (Prompts) صعبة للغاية، أو لا تمنح دالة المكافأة أي تقدير على الإطلاقتحقق أولاً من completions/clipped_ratio، لأن الإكمال الذي يُقطع قبل إجابته يحصل على درجة صفر، ثم اقرأ بعض الإكمالات للتأكد من أن دالة المكافأة تفسرها بشكل صحيح
الصعودسقوطسقوطregression: مجموعات كانت تنجح سابقاً أصبحت تفشل معاًتحقق من completions/duplicate_frac وentropy للتحقق من الانهيار
مرتفعفي المنتصفأيمزيج: مع مكافآت 0/1، تكون بعض الموجهات سهلة جداً وبعضها الآخر صعب جداً. ومع مكافآت الجزئي، قد تعلق المجموعات أيضاً على نفس الدرجة الجزئيةقراءة عمليات التمرير

completions/duplicate_frac هو حصة التكرارات التي تُعيد تكرار تكرار سابق من مجموعتها رمزًا برمز، عبر جميع جولاتها. التكرارات المتطابقة تحصل على مكافآت متطابقة، لذا فإن التكرارات تدفع أيضًا frac_reward_zero_std إلى الارتفاع.

completions/duplicate_fracتشخيص
قريب من 0، مع خط أساسي صغير في المهام التي تكون إكمالاتها قصيرة بما يكفي لتكرارها بالصدفةسليم: يتم أخذ عينات من عمليات التمرير لمجموعة بشكل مستقل
يقفز نحو (num_generations - 1) / num_generationsلم يعد العيّان عشوائياً: تعود كل مجموعة كنسخ من تمريرة واحدة. على سبيل المثال، درجة حرارة تساوي صفرًا، أو محركات vLLM المتوازية على مستوى البيانات التي ترمّز النص نفسه لأنها تشترك في بذرة عيّان (يُرسل العامل بذرة فريدة مع كل طلب لمنع ذلك)
يتصاعد ببطء، مع انخفاض entropyالانهيار: تتقارب السياسة نحو إجابة واحدة لكل موجه

تُسجَّل هذه المقاييس عندما يُقيِّم العامل مجموعة، وليس عندما تُدرَّب عيناتها، لذا فهي تسبق التدريب بقليل وتشمل مجموعات تُستبعد عيناتها لاحقًا لتجاوزها max_staleness. تُستبعد المجموعة التي تعذَّر تقييم أيّ منها، لأنّ كل دالة مكافأة أعادت None، وتُحتسب ضمن frac_reward_zero_std، لكنها لا تملك مكافأة، لذا يستثنيها reward_dropped.

تعبئة الأشجار

تُجمَّع عينات الدفعة الجزئية (micro-batch) في صف واحد لكل عملية، دون أي حشو. الصف هو شجرة بادئات (prefix trie) وليس مجرد ربط متسلسل: فالرمز (token) المشترك بين عدة عينات يُمرَّر مرة واحدة فقط، بينما لا يزال الانتباه (attention) يُظهر لكل عينة تاريخها الخاص بدقة. تكرّس جميع عمليات التوليد (rollouts) ضمن مجموعة واحدة موجهها (prompt)، وتكرر كل جولة في عملية توليد متعددة الجولات المحادثة حتى تلك اللحظة، مما يُقلّص حجم العملية الأمامية (forward) بنسبة التعبئة (batch/packing_ratio). هذا تغيير في الترتيب فقط: فكل رمز مُدرَّب يحتفظ بهدفه الخاص وميزته (advantage) واحتمال السجل القديم (old log-probability)، ويتطابق احتمال سجله مع ما كان سيُنتج لو مُرِّرَت العينة بمفردها.

في جولتين من التنفيذ لموجّه واحد، حيث p هو الموجّه وa، b هما الاكتمالان:

1. Rollouts         r0:  p1 p2 p3 a1 a2                10 tokens
                    r1:  p1 p2 p3 b1 b2

2. Trie             S0 [p1 p2] ─┬─ S1 [p3 a1 a2]       p3 is not shared: a position predicts
                                └─ S2 [p3 b1 b2]       one token, and p3 predicts a1 or b1

3. Packed row       index         0  1 |  2  3  4 |  5  6  7
   (depth-first)    input_ids    p1 p2 | p3 a1 a2 | p3 b1 b2    8 tokens: everything but attention
                    position_ids  0  1 |  2  3  4 |  2  3  4    runs on 8 instead of 10
                    segment      S0    | S1       | S2

4. Attention        segment   queries    keys and values: the segment's path in the trie
   (one call)       S0        p1 p2      p1 p2              index 0 1
                    S1        p3 a1 a2   p1 p2 p3 a1 a2     index 0 1 2 3 4
                    S2        p3 b1 b2   p1 p2 p3 b1 b2     index 0 1 5 6 7

5. Loss             r0: a1 a2 predicted at index 2 3   each trained token is predicted at the index
                    r1: b1 b2 predicted at index 5 6   of the token before it in its rollout

بعض العواقب:

  • يُشارَك أي بادئة مشتركة: فمسارات التنفيذ لموجّه واحد، وكذلك عبر الموجّهات بادئة نظام مشتركة أو مخطط أداة مشترك. يُرسَل العينة إلى الصف الذي يحتوي بالفعل على مجموعته، حيث يوجد موجّهها مسبقًا. وعندما يمتلئ ذلك الصف، تُرسَل العينة إلى صف آخر إذا كان هناك عملية ما لا تزال تملك صفًا فارغًا، أو إذا لم يُسبب ذلك ارتفاع الحمولة على الصف الأكثر ازدحامًا، وهو ما يحدد زمن الدفعة الجزئية؛ وإلا تُغلق الدفعة الجزئية.
  • عدم مشاركة أي شيء يعني التعبئة العادية: إذ يصبح كل عينة في هذه الحالة مقطعًا مستقلًا، وهو ما يُعرف بالتعبئة العادية دون حشو.
  • يتم تكرار K/V فقط: يتم جمع البادئة المشتركة مرة واحدة لكل مقطع فرع، وهو ما يعادل تقريبًا ما يحتفظ به التدريب غير المُفكَّك، وهو صغير عند استخدام الانتباه الاستعلامي المُجمَّع.
  • تُؤيَّد هجائن Gated DeltaNet (Qwen3.5, Qwen3.6, Qwen3-Next, …): حيث تقرأ عملية الالتفاف الخاصة بها أسلاف كل رمز على حدة، وتبدأ قاعدة دلتا المقطّعة الخاصة بها كل مقطع من الحالة النهائية لوالدها.

token_budget يحدد حدّ الرموز في الصف، حيث يُحتسب الرمز المشترك مرة واحدة فقط. افتراضيًا، يكون هذا الحدّ هو max_model_len الخاص بالخادم، فلا يمكن لأي عيّنة أن تتجاوزه. عند عدم وجود أي عناصر مشتركة، يعمل التجميع الشجري بسرعة تقارب سرعة التجميع العادي بدون حشو، وتزداد السرعة تقريبًا مع نسبة التجميع (حوالي 2.8 إلى 3 أضعاف عند نسبة 3.4 على Qwen3-0.6B و1.7B، على H100). نسبة التجميع هي حاصل ضرب البادئة المشتركة في عدد العينات لكل صف، لذا فإن batch/samples_per_row هو المؤشر الرئيسي: عند اقترابه من 1، لا يمكن مشاركة أي شيء، والعنصر المؤثر هو token_budget. أما batch/row_fill_frac فيكتسب أهمية عندما تكون العينات طويلة: فعيّنة من 10 آلاف رمز لا تملأ ميزانية 32 ألف رمز بشكل جيد (ثلاث عيّنات تتسع، وأربع لا تتسع أبدًا، لذا غالبًا ما يحصل المخطط على عيّنيتين فقط ويصل امتلاء الصف إلى حوالي 77%)، بينما تملأ العينات المكونة من 1000 رمز الميزانية بشكل شبه مثالي.

احكم على حزم الشجرة من حيث الإنتاجية، batch/trained_tokens_per_step / perf/fwd_bwd_s. تحتوي الدفعة الجزئية المحزومة على عينات أكثر، لذا يرتفع perf/fwd_bwd_s وينخفض perf/fwd_bwd_mfu، الذي يُقسَّم على الرموز المُرسَلة: لا يُعد أيٌّ منهما تراجعاً. كما أن وجود عينات أكثر في كل صف يعني سحب المزيد من عمليات التوليد في كل خطوة، مما يجعل التشغيل المعتمد على التوليد أكثر اعتماداً عليه.

تتكوّن مقاييس الدفعة من: تحتوي خطوة واحدة على gradient_accumulation_steps × num_processes صفوف، لذا فإن batch/samples_per_step يساوي تقريباً ذلك العدد من مرات batch/samples_per_row، وكذلك الأمر بالنسبة للرموز. يتطابق الجانبان فقط حتى مستوى التباين بين الدفعات الجزئية للخطوة الواحدة، لأن مقاييس كل خطوة هي مجموع، بينما مقاييس كل صف هي متوسط.

أي شيء يجعل الصفوف أطول أو أقل يُزيل المشاركة. محادثة متعددة الأدوار لا تتفرع أبداً هي عينة واحدة طويلة، وrollout/samples_per_rollout يحدد عدد العينات التي تنتجها المحادثة.

عينة أعلى ك وأعلى با

يعيد Top-k و top-p تطبيع توزيع العينة على مجموعة مرشّحة محدودة بدلاً من المفردات الكاملة. تعكس احتمالات السجلات (log-probabilities) التي يعيدها الخادم ذلك (--logprobs-mode processed_logprobs)، لذا يجب على المُدرِّب (trainer) أن يُطبِّع على المجموعة نفسها، وإلا فستكون كل نسبة أهمية (importance ratio) منحازة: فاحتمال الرمز (token) ضمن المفردات الكاملة أقل منه ضمن مجموعة المرشحين التي سُحِب منها (DeepSeek-V3.2، القسم 3.1). عند استخدام top_p < 1 أو top_k > 0، يطلب العامل (worker) من الخادم مجموعة المرشحين لكل رمز مُسحوب، ثم يُعيد المُدرِّب تطبيع احتمالات السجلات الخاصة به عليها. يتطلب ذلك علمين إضافيين للخادم، ويجب تعيين top_k، لأن vLLM يحدّ حجم مجموعة المرشحين به:

VLLM_SERVER_DEV_MODE=1 vllm serve <model_name> \
    --weight-transfer-config '{"backend": "nccl"}' \
    --logprobs-mode processed_logprobs \
    --api-server-count 8 \
    --max-model-len 4096 \
    --return-sampling-mask \
    --enable-scale-out
from trl import AsyncGRPOConfig

training_args = AsyncGRPOConfig(
    ...,
    top_p=0.95,
    top_k=512,
)

يجعل --return-sampling-mask نظام vLLM تسجيل مجموعة المرشحين (vLLM 0.28 أو أحدث، على مشغّل نماذج V2 الخاص به: VLLM_USE_V2_MODEL_RUNNER=1 إذا رفض الخادم هذا العلم)، ويكشف --enable-scale-out نقطة النهاية التي تستقبل الرموز وتُخرجها والتي تُرجع هذه المجموعة. يتحقق العامل من كلا العَلَمَين على الخادم عند بدء التشغيل. لا يمكن لنسخة أقدم من vLLM إرجاع المجموعة، لذا يستمر المدرب في التطبيع على كامل قاموس الرموز ويصدر تحذيرًا بشأن النسبة المتحيزة.

تدريب المحوِّلات باستخدام PEFT

ندعم التكامل الوثيق مع مكتبة 🤗 PEFT، مما يتيح لك تدريب المُكيِّفات (adapters) ومشاركتها على Hub بدلاً من تدريب النموذج بأكمله.

from datasets import load_dataset
from peft import LoraConfig
from trl import AsyncGRPOTrainer
from trl.rewards import accuracy_reward

if __name__ == "__main__":
    dataset = load_dataset("trl-lib/DeepMath-103K", split="train")

    trainer = AsyncGRPOTrainer(
        model="Qwen/Qwen3.5-2B",
        reward_funcs=accuracy_reward,
        train_dataset=dataset,
        peft_config=LoraConfig(r=32, lora_alpha=64, target_modules="all-linear"),
    )
    trainer.train()

يجب أن تحصل كل مزامنة للأوزان على السياسة المحدّثة على الخادم. افتراضيًا، يدمج المدرب المُكيّف (adapter) في النموذج الأساسي ويرسل الأوزان المدمجة كاملة، وهو ما يعادل حوالي 15 غيغابايت لنموذج بحجم 7B في كل مزامنة. ابدأ خادم vLLM باستخدام --enable-lora وسيكتفي بإرسال المُكيّف فقط، أي حوالي 160 ميغابايت عند r=32:

VLLM_SERVER_DEV_MODE=1 VLLM_ALLOW_RUNTIME_LORA_UPDATING=1 vllm serve <model_name> \
    --weight-transfer-config '{"backend": "nccl"}' \
    --logprobs-mode processed_logprobs \
    --api-server-count 8 \
    --max-model-len 4096 \
    --enable-lora \
    --max-lora-rank 32 \
    --max-loras 6

يعرّف VLLM_ALLOW_RUNTIME_LORA_UPDATING=1 نقطة النهاية التي يرسل إليها المُدرِّب كل مُكيِّف جديد. احتفظ بـ --weight-transfer-config حتى عند استخدام --enable-lora: فمُحدِّد وضع المزامنة يُختار فقط عند بدء المُدرِّب، وحينها يكون الخادم قيد التشغيل بالفعل، وتُعدّ المزامنة المدمجة هي الوضع الاحتياطي.

يجب أن يكون --max-lora-rank أحد القيم التالية: 1, 8, 16, 32, 64, 128, 256, 320, 512. يحدد هذا الخيار أعلى رتبة يمكن للخادم تقديمها، وليس الرتبة التي سيقدمها بالضرورة، لذا فإن مُكيّف r=4 يعمل بشكل سليم تحت حد 8. يجب أن يكون --max-loras مساوياً على الأقل لـ max_staleness + 2: حيث يحتفظ المُدرّب بعدد من إصدارات المُكيّفات يساوي max_staleness + 1 مسجّلة، حتى يتمكن التمرين الذي بدأ تحت سياسة أقدم من الاكتمال تحت تلك السياسة بدلاً من تبديل السياسات في منتصف التوليد، كما يقوم كل مزامنة بتحميل الإصدار التالي قبل تفريغ أقدم إصدار. يتحقق المُدرّب من كلا القيمتين عند بدء التشغيل ويخبرك بالقيم التي يجب إعادة تشغيل الخادم بها.

يحمّل vLLM المُكيّفات من القرص بدلاً من الشبكة، لذا يكتب المُدرّب كل إصدار تحت <output_dir>/.vllm_lora/ ويمنح الخادم المسار. إذا كان الخادم يعمل على جهاز مختلف، فيجب أن يكون ذلك المجلد على نظام ملفات يمكن لكلٍّ منهما رؤيته.

<output_dir>/.vllm_lora/ هو ذاكرة تخزين مؤقتة للخدمة، وليس المكان الذي يُحفظ فيه المُكيّف الذي درّبتَه. فهو يحتفظ فقط بأحدث الإصدارات، ويُحذف كل إصدار بعد فترة وجيزة من خروجه من نافذة الصلاحية. نقاط التفتيش هي ما يحفظ المُكيّف الذي درّبتَه، لذا لا تُعطِلها: مع save_strategy="no"، تنتهي عملية تدريب تستغرق ساعات دون أن تترك أي أثر. كما يجب أن يكون output_dir قابلاً للقراءة من قِبل الخادم، وأن يستمر في الوجود بعد انتهاء المهمة، لذا لن تعمل المساحة المؤقتة المحلية على العقدة، رغم أنها كانت ستُبقي عمليات المزامنة بعيداً عن نظام الملفات المشترك البطيء.

لا يكون مزامنة المُكيِّف (Adapter) أسرع دائمًا. يولّد vLLM بطيئًا بحوالي 1.33 إلى 1.39 مرة لكل رمز عند تقديم مُكيِّف LoRA مقارنةً بتقديم الأوزان المدمجة، وعادةً ما يكون التوليد هو العامل المهيمن على الوقت الكلي (wall-clock) في التعلم المعزز. ما تحصل عليه مقابل ذلك هو توقف أقصر بكثير عند كل مزامنة، ونحو 1% من عرض نطاق المزامنة. إذا كان معدل إنتاجية التوليد هو عنق الزجاجة لديك، فإن المزامنة المدمجة غالبًا ما تبقى الخيار الأفضل.

استخدام دالة مكافأة مخصصة

يدعم AsyncGRPOTrainer استخدام دوال مكافأة مخصصة. لضمان التوافق، يجب أن تستوفي دالة المكافأة الخاصة بك المتطلبات التالية:

يمكن أن تكون دوال المكافآت إما دوال Python متزامنة قابلة للاستدعاء، أو دوال async def غير متزامنة. عند توفير عدة دوال مكافآت، تُنفَّذ بالتوازي: حيث تُستدعى الدوال غير المتزامنة معًا، بينما تُنفَّذ الدوال المتزامنة في خيوط منفصلة، مما يؤدي إلى تداخل زمن استجابتها.

  1. حجج الإدخال:

    • يجب أن تقبل الدالة ما يلي كحجج مُسمّاة:

      • prompts (تحتوي على الأوامر)،
      • completions (تحتوي على الاكتمالات المولّدة)،
      • completion_ids (تحتوي على المكملات المُرمَّزة)،
      • جميع أسماء الأعمدة (باستثناء prompt) التي قد يحتوي عليها مجموعة البيانات. على سبيل المثال، إذا كانت مجموعة البيانات تحتوي على عمود باسم ground_truth، فسيتم استدعاء الدالة مع ground_truth كحجة اسمية.

      أسهل طريقة للامتثال لهذا المتطلب هي استخدام **kwargs في توقيع الدالة.

    • المجموعة البيانات بصيغة حوارية، لذا prompts وcompletions قائمتان من قواميس الرسائل.

  2. القيمة المُعادَة: يجب أن تُعيد الدالة قائمة من الأرقام العشرية. يمثل كل رقم عشري المكافأة المقابلة لإكمال واحد.

تُنفَّذ دوال المكافآت في عامل التمرير (rollout worker)، وهو عملية منفصلة، لذا فإن كل ما يُمرَّر إليه يكون مُجمَّداً (pickled). يجب أن تكون كل دالة مكافأة، وأداة، وenvironment_factory (وأي شيء تغلق عليه) قابلة للتجميد: استخدم دالة على مستوى الوحدة (module-level function)، أو functools.partial، أو كائن فئة قابل للاستدعاء (callable class instance). تثير الدوال اللامبدا (Lambdas) والدوال المغلقة (closures) خطأً من نوع TypeError عند trainer.train(). هذا يختلف عن GRPOTrainer، حيث تُستدعى دوال المكافآت داخل العملية نفسها وتعمل الدوال المغلقة بشكل صحيح.

تعمل عملية النشر أيضًا مع CUDA_VISIBLE_DEVICES=""، لذا لا يمكنها استخدام وحدة معالجة الرسوميات. يتم تحميل نموذج المكافأة المدعوم بوحدة معالجة الرسوميات (مثل مُقيّم AutoModelForSequenceClassification) دون خطأ، لكنه يعود بصمت إلى وحدة المعالجة المركزية. حافظ على خفة دوال المكافأة (المُتحقق منها مثل accuracy_reward، أو فحوصات التنسيق أو الطول). إذا كنت بحاجة إلى نموذج مكافأة مدعوم بوحدة معالجة الرسوميات، فقم بتشغيله خلف محرك استنتاج خاص به (vLLM، TGI، …) على وحدات معالجة رسوميات منفصلة، واجعل دالة مكافأة قابلة للتسلسل تستدعيه عبر HTTP.

المثال 1: مكافأة الإكمالات الأطول

فيما يلي مثال على دالة مكافأة تكافئ الإكمالات الأطول:

def reward_func(completion_ids, **kwargs):
    """Reward function that assigns higher scores to longer completions (in terms of token count)."""
    return [float(len(ids)) for ids in completion_ids]

يمكنك اختباره على النحو التالي:

>>> prompts = [[{"role": "user", "content": "The sky is"}], [{"role": "user", "content": "The sun is"}]]  # not used in the reward function, but the trainer will pass it
>>> completions = [[{"role": "assistant", "content": " blue."}], [{"role": "assistant", "content": " in the sky."}]]  # not used in the reward function, but the trainer will pass it
>>> completion_ids = [[6303, 13], [304, 279, 12884, 13]]
>>> reward_func(prompts=prompts, completions=completions, completion_ids=completion_ids)
[2.0, 4.0]

المثال 2: مكافأة الإكمالات بصيغة محددة

فيما يلي مثال على دالة مكافأة تتحقق مما إذا كانت الإكمال (completion) يتبع تنسيقًا محددًا. استُوحِي هذا المثال من دالة format reward المستخدمة في ورقة DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning. تم تصميمها لتنسيق محادثة، حيث تتكون المحفزات (prompts) والإكمالات (completions) من رسائل منظمة.

import re

def format_reward_func(completions, **kwargs):
    """Reward function that checks if the completion has a specific format."""
    pattern = r"^<think>.*?</think><answer>.*?</answer>$"
    completion_contents = [completion[0]["content"] for completion in completions]
    matches = [re.match(pattern, content) for content in completion_contents]
    return [1.0 if match else 0.0 for match in matches]

يمكنك اختبار هذه الدالة على النحو التالي:

>>> prompts = [
...     [{"role": "user", "content": "What is the result of (1 + 2) * 4?"}],
...     [{"role": "user", "content": "What is the result of (3 + 1) * 2?"}],
... ]
>>> completions = [
...     [{"role": "assistant", "content": "<think>The sum of 1 and 2 is 3, which we multiply by 4 to get 12.</think><answer>(1 + 2) * 4 = 12</answer>"}],
...     [{"role": "assistant", "content": "The sum of 3 and 1 is 4, which we multiply by 2 to get 8. So (3 + 1) * 2 = 8."}],
... ]
>>> format_reward_func(prompts=prompts, completions=completions)
[1.0, 0.0]

المثال 3: مكافأة الاكتمالات بناءً على مرجع

فيما يلي مثال على دالة مكافأة تتحقق من صحة الإكمال. يستلهم هذا المثال دالة accuracy reward المستخدمة في ورقة DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning. يحتوي مجموعة البيانات على عمود باسم ground_truth.

import re

def reward_func(completions, ground_truth, **kwargs):
    # Regular expression to capture content inside \boxed{}
    matches = [re.search(r"\\boxed\{(.*?)\}", completion[0]["content"]) for completion in completions]
    contents = [match.group(1) if match else "" for match in matches]
    # Reward 1 if the content is the same as the ground truth, 0 otherwise
    return [1.0 if c == gt else 0.0 for c, gt in zip(contents, ground_truth)]

يمكنك اختبار هذه الدالة على النحو التالي:

>>> prompts = [
...     [{"role": "user", "content": "Solve the equation $2x + 3 = 7$."}],
...     [{"role": "user", "content": "Solve the equation $3x - 5 = 10$."}],
... ]
>>> completions = [
...     [{"role": "assistant", "content": r"The solution is \boxed{2}."}],
...     [{"role": "assistant", "content": r"The solution is \boxed{6}."}],
... ]
>>> ground_truth = ["2", "5"]
>>> reward_func(prompts=prompts, completions=completions, ground_truth=ground_truth)
[1.0, 0.0]

المثال 4: دوال المكافآت متعددة المهام

فيما يلي مثال على استخدام دوال مكافأة متعددة في AsyncGRPOTrainer. في هذا المثال، نعرّف دالتي مكافأة مخصصتين للمهام: math_reward_func وcoding_reward_func. تمنح math_reward_func مكافأة للمسائل الرياضية بناءً على صحتها، بينما تمنح coding_reward_func مكافأة لمسائل البرمجة بناءً على ما إذا كانت الحل يعمل.

from datasets import Dataset
from trl import AsyncGRPOTrainer

# Define a dataset that contains both math and coding problems
dataset = Dataset.from_list(
    [
        {"prompt": [{"role": "user", "content": "What is 2+2?"}], "task": "math"},
        {"prompt": [{"role": "user", "content": "Write a function that returns the sum of two numbers."}], "task": "code"},
        {"prompt": [{"role": "user", "content": "What is 3*4?"}], "task": "math"},
        {"prompt": [{"role": "user", "content": "Write a function that returns the product of two numbers."}], "task": "code"},
    ]
)

# Math-specific reward function
def math_reward_func(prompts, completions, task, **kwargs):
    rewards = []
    for prompt, completion, t in zip(prompts, completions, task):
        if t == "math":
            # Calculate math-specific reward
            correct = check_math_solution(prompt, completion)
            reward = 1.0 if correct else -1.0
            rewards.append(reward)
        else:
            # Return None for non-math tasks
            rewards.append(None)
    return rewards

# Coding-specific reward function
def coding_reward_func(prompts, completions, task, **kwargs):
    rewards = []
    for prompt, completion, t in zip(prompts, completions, task):
        if t == "code":
            # Calculate coding-specific reward
            works = test_code_solution(prompt, completion)
            reward = 1.0 if works else -1.0
            rewards.append(reward)
        else:
            # Return None for non-coding tasks
            rewards.append(None)
    return rewards

# Use both task-specific reward functions
if __name__ == "__main__":
    trainer = AsyncGRPOTrainer(
        model="Qwen/Qwen2.5-0.5B-Instruct",
        reward_funcs=[math_reward_func, coding_reward_func],
        train_dataset=dataset,
    )

    trainer.train()

في هذا المثال، صُمّمت math_reward_func وcoding_reward_func للعمل مع مجموعة بيانات مختلطة تحتوي على مسائل رياضية ومسائل برمجية. يُستخدم عمود task في مجموعة البيانات لتحديد دالة المكافأة التي يجب تطبيقها على كل مسألة. إذا لم تكن هناك دالة مكافأة ذات صلة لعينة في مجموعة البيانات، فإن دالة المكافأة ستعيد None، وسيواصل AsyncGRPOTrainer العمل بالدوال والمهام الصالحة. وهذا يتيح لـAsyncGRPOTrainer التعامل مع دوال مكافأة متعددة ذات نطاقات تطبيق مختلفة.

لاحظ أن AsyncGRPOTrainer سيتجاهل مكافآت None التي تُعاد من دوال المكافآت، وسيأخذ في الاعتبار فقط المكافآت التي تُعاد من الدوال ذات الصلة. وهذا يضمن تدريب النموذج على المهام ذات الصلة وتجاهل المهام التي لا توجد لها دالة مكافآت ذات صلة.

المثال 5: دوال المكافآت غير المتزامنة

يمكن أيضًا تعريف دوال المكافأة المخصصة كدوال async def غير متزامنة. وهذا مفيد إذا كانت مكافأتك تعتمد على عمليات إدخال/إخراج بطيئة (على سبيل المثال، استدعاء خدمة عن بُعد). عند تمرير عدة دوال مكافأة غير متزامنة، ينفذ AsyncGRPOTrainer هذه الدوال بشكل متوازٍ بحيث تتداخل أزمنه استجابتها.

فيما يلي مثال مبسّط لدالة مكافأة غير متزامنة تحاكي عملية تعتمد على الإدخال/الإخراج:

import asyncio

async def async_reward_func(prompts, completions, **kwargs):
    # Simulate an I/O-bound call (e.g., HTTP request, database lookup)
    await asyncio.sleep(0.01)
    # Simple toy reward: 1.0 if the completion is non-empty, else 0.0
    return [1.0 if completion else 0.0 for completion in completions]

تمرير دالة المكافأة إلى المدرب

لاستخدام دالة المكافأة المخصصة الخاصة بك، قم بتمريرها إلى AsyncGRPOTrainer على النحو التالي:

from trl import AsyncGRPOTrainer

trainer = AsyncGRPOTrainer(
    reward_funcs=reward_func,
    ...,
)

يمكنك تمرير عدة دوال مكافأة كقائمة؛ وقد تتضمن هذه القائمة دوالًا متزامنة وغير متزامنة على حد سواء:

from trl import AsyncGRPOTrainer

trainer = AsyncGRPOTrainer(
    reward_funcs=[reward_func, async_reward_func1, async_reward_func2],
    ...,
)

وستُحسب المكافأة على أنها مجموع المكافآت من كل دالة.

تخصيص حلقة النشر

تتم عمليات التوليد والمكافآت والمزايا والتصفية في عملية التمرير (rollout) داخل AsyncRolloutLoop. لتعديل إحدى هذه العمليات، أنشئ فئة فرعية من الحلقة، ثم تجاوز (override) الدالة الخاصة بالخطوة المعنية، وحدد الفئة الفرعية كقيمة لـ loop_cls في فئة فرعية من المدرب. لا يتغير منشئ المدرب (constructor).

الطريقةتم استدعاءافتراضي
filter_prompt(row_index, row, epoch)مرة واحدة لكل موجه، قبل التوليديقبل كل موجه
generate_one(prompt, tools, group_id)مرة واحدة لكل تمرينحلقة الأدوات متعددة الأدوار
generate_one_turn(prompt_ids)مرة واحدة لكل طلب أخذ عينةطلب واحد من vLLM باستخدام إعدادات أخذ العينات من التهيئة
compute_advantages(group)مرة واحدة لكل مجموعة، بعد جمع المكافآت في group.rewardsالمكافأة المعيارية داخل المجموعة
setup(), close()عند بدء الحلقات وتوقفهالا شيء

على سبيل المثال، منهج يتوقف عن أخذ عينات من الأوامر التي يحلها بالفعل، مع تحديد عتبة في فئة فرعية من التهيئة:

from dataclasses import dataclass

import numpy as np

from trl import AsyncGRPOConfig, AsyncGRPOTrainer
from trl.trainer.async_grpo_trainer import AsyncRolloutLoop


@dataclass
class CurriculumConfig(AsyncGRPOConfig):
    pass_rate_cap: float = 0.9


class CurriculumRolloutLoop(AsyncRolloutLoop):
    def filter_prompt(self, row_index, row, epoch):
        return self.pass_rates.get(row_index, 0.0) < self.config.pass_rate_cap

    async def setup(self):
        self.pass_rates = {}

    def compute_advantages(self, group):
        scored = group.rewards[~np.isnan(group.rewards)]
        if len(scored):
            self.pass_rates[group.row_index] = float(np.mean(scored > 0))
        return super().compute_advantages(group)


class CurriculumTrainer(AsyncGRPOTrainer):
    loop_cls = CurriculumRolloutLoop

تُجمع المكافآت عبر reward_funcs، والبيئات التي تُعرّف get_reward، والمكافأة reward التي يضبطها generate_one المخصص على Rollout الخاص به: لتقييم بواسطة قاضٍ، مرّر دالة مكافأة غير متزامنة، أو قيّم داخل generate_one. يُنفَّذ compute_advantages لكل مجموعة، بما في ذلك المجموعة التي تُستبعد لاحقًا لأن جميع مكافآتها المُقيَّمة متساوية؛ فالاستبعاد ينظر إلى المكافآت وليس إلى المزايا. يُبنى الحلقة في عملية مولّدة، لذا عرّف الفئة الفرعية على مستوى الوحدة، وأبقِ كود التدريب تحت if __name__ == "__main__":. أنشئ العملاء في setup، الذي يعمل على حلقة أحداث الحلقة، بدلاً من تجاوز __init__. يُتخطى أي موجه ترفضه filter_prompt لذلك العصر ويُحتسب ضمنه، كما هو الحال مع المجموعة المستبعدة. إذا رفضت كل موجهات العصر، ترمي الحلقة استثناءً.

الاستئناف من نقطة تفتيش

يعيد Trainer الأساسي التدريب عن طريق تجاوز الدُفعات التي تم تدريبها مسبقًا وإعادة تشغيلها، وهو ما لا ينطبق على طابور النشر المباشر، لذا يكون ignore_data_skip دائمًا True. وبدلاً من ذلك، يُحفظ فهرس أول موجه لم يتم تدريبه بعد في rollout_state.json جنبًا إلى جنب مع كل نقطة تحقق، ويُستعاد عند الاستئناف، بحيث يتقدم العامل بسرعة إلى ذلك الموجه دون إعادة تشغيل العينات. إنه موضع trained وليس موضع المولّد: يعمل العامل متقدمًا على التدريب بعمق طابور النشر، وتُفقد العينات المخزنة مؤقتًا عند انتهاء التشغيل، لذا فإن الاستئناف من موضع المولّد سيتجاوز موجهات تم توليدها لكن لم يتم تدريبها أبدًا. ولا يمكن إعادة تموضع مجموعات البيانات المتدفقة (IterableDataset)، لذا يعيد عاملها التشغيل من الموجه الأول عند الاستئناف.

تدريب الوكيل

يدعم GRPO غير المتزامن تدريب الوكلاء: حيث يستدعي النموذج الأدوات أثناء التوليد ويتعلم من النتيجة.

  • الأداة tool هي دالة بايثون بسيطة (متزامنة أو غير متزامنة) تُعرَض للنموذج. استخدم tools للاستدعاءات التي لا تعتمد على حالة (آلة حاسبة، بحث على الويب).
  • يُعدّ البيئة الشكل الأكثر شمولاً: فهو كائن ذو حالة يُنشأ من جديد لكل تمريرة، وتُعرض دواله العامة كأدوات، بالإضافة إلى خطاف دورة حياة reset وخطاف اختياري get_reward يتيح له امتلاك المكافأة. استخدم environment_factory عندما تحتاج إلى حالة لكل تمريرة، أو خطاف إعادة تعيين، أو مكافأة تملكها البيئة.

يمكنك دمجها — حيث يمكنك تمرير tools مستقلة جنبًا إلى جنب مع environment_factory.

الأدوات

يتوقع الحجة tools قائمة من دوال بايثون (متزامنة أو غير متزامنة) تُعرّف الأدوات المتاحة للوكيل:

from trl import AsyncGRPOTrainer

trainer = AsyncGRPOTrainer(
    tools=[tool1, tool2],
    ...,
)

يجب أن يكون كل أداة دالة بايثون قياسية مع حُجج وأنواع إرجاع مُعلَّمة بالنوع، إلى جانب سلسلة توثيق بنمط Google تصف غرضها وحُججها وقيمة الإرجاع. لمزيد من التفاصيل، راجد دليل تمرير الأدوات.

يستدعي النموذج أداةً باسمها، لذا تُسجَّل كل أداة تحت __name__ ويجب أن تكون دالة على مستوى الوحدة، مما يجعلها قابلةً للتسلسل (picklable) لعميل التمرين (rollout worker). تُنفَّذ الأدوات المتزامنة على مجموعة خيوط (thread pool) بحجم max_concurrent_rollouts، لذا لا تعيق الأداة البطيئة أيًّا من التمارين الأخرى الجارية. ضمن دور مساعد واحد، تُنفَّذ استدعاءات الأدوات بالترتيب الذي أصدرها به النموذج. وبالتالي، يمكن تنفيذ ما يصل إلى max_concurrent_rollouts من الأدوات المتزامنة في وقت واحد، لذا يجب أن تكون أي أداة تقرأ أو تكتب حالةً مشتركةً آمنةً من الخيوط (thread-safe).

يتطلب حلقة استدعاء الأداة أن يكون قالب المحادثة حافظًا على البادئة (فإضافة رسالة أداة يجب ألا تُغيّر طريقة عرض الرسائل السابقة). بالنسبة لعائلات النماذج المعروفة (مثل Qwen3 وDeepSeek-V3)، يقوم TRL تلقائيًا باستبدال قالب التدريب المُعدّل عند تمكين الأدوات. راجع قوالب المحادثة للحصول على القائمة الكاملة.

استخدم max_tool_calling_iterations في AsyncGRPOConfig لتحديد الحد الأقصى لعدد جولات استدعاء الأدوات. افتراضيًا، لا يوجد حد، ويتوقف التوليد عندما ينتج النموذج ردًا لا يتضمن أي استدعاءات للأدوات.

مثال:

from trl import AsyncGRPOTrainer

def multiply(a: int, b: int) -> int:
    """
    Multiplies two integers.

    Args:
        a: The first integer.
        b: The second integer.

    Returns:
        The product of the two integers.
    """
    return a * b

async def async_add(a: int, b: int) -> int:
    """
    Asynchronously adds two integers.

    Args:
        a: The first integer.
        b: The second integer.

    Returns:
        The sum of the two integers.
    """
    return a + b

trainer = AsyncGRPOTrainer(
    tools=[multiply, async_add],
    ...,
)

البيئات

يمكنك أيضًا توفير الأدوات من خلال environment_factory. في هذا الوضع، يقوم AsyncGRPOTrainer بإنشاء نسخة واحدة من البيئة لكل تمريرة (rollout) ويعرض الدوال العامة للبيئة كأدوات.

عند استخدام environment_factory، تمتلك البيئة البيانات: فلا يوجد train_dataset خارجي. في كل تمريرة، يُنتج reset() المهمة — إما عبر أخذ عينات ذاتية من مجموعة نصوص تحتفظ بها البيئة، أو عبر توليد حالة إجرائياً — ويعيد الموجه. يحدد max_steps طول التدريب. فيما يلي مثال مبسط لبيئة تأخذ عينات ذاتية من هدف وتعرض طريقة increment (مُعرَّضة كأداة).

import random

from trl import AsyncGRPOConfig, AsyncGRPOTrainer

class IncrementEnv:
    # Reserved methods
    def reset(self, **kwargs) -> str | None:  # required; called at the start of each rollout
        self.counter = 0
        self.target = random.randint(1, 6)  # self-sample the task
        return f"Increment the counter by {self.target}."  # returned string becomes the prompt

    def get_reward(self) -> float:  # optional: the environment scores itself from its own state
        return float(self.counter == self.target)

    # Public methods (exposed as tools)
    def increment(self, step: int) -> int:
        """
        Increment the internal counter.

        Args:
            step: Value to add to the counter.

        Returns:
            The updated counter value.
        """
        self.counter += step
        return self.counter

if __name__ == "__main__":
    trainer = AsyncGRPOTrainer(
        model="Qwen/Qwen3-0.6B",
        args=AsyncGRPOConfig(max_steps=1000, chat_template_kwargs={"enable_thinking": False}),
        environment_factory=IncrementEnv,
    )
    trainer.train()

تحتوي فئة البيئة على طريقتين محجوزتين: reset وget_reward. على عكس أي طرق عامة أخرى، لا تُعرَض هذه الطرق للنموذج كأدوات.

  • يُستدعى reset (إلزامي) عند بدء الجولة. يمكن أن يعيد None أو نصًا. عند إعادته نصًا، يكون ذلك النص هو موجه المستخدم.
  • get_reward (اختياري، متزامن أو غير متزامن) لا يستقبل أي حجة ويعيد قيمة من نوع float: حيث يقوم البيئة بتقييم الحلقة التي أكّدها للتو بناءً على حالته الداخلية (هل انتهت اللعبة بفوز؟ هل تم تخمين الكلمة؟). يُستدعى مرة واحدة لكل تمريرة مكتملة ويعمل كمصدر للمكافأة.

يعمل كلاهما على مجموعة خيوط أدوات العامل، لذا فإن البيئة البطيئة تؤخر نشرها الخاص فقط دون التأثير على العمليات الأخرى الجارية. يتم تسجيل مدتيهما على النحو التالي: env/reset_s وenv/reward_s.

بما أن البيئة تُعيد أخذ العينات من نفسها عند كل reset()، فقد لا تتشارك أعضاء مجموعة GRPO الـG نفس الحالة الأولية، مما يجعل خط الأساس للمجموعة أكثر ضوضاءً قليلاً. للحصول على حالات متطابقة داخل المجموعة، اجعل reset() يُشتق الحالة بشكل حتمي من مفتاح مشترك.

توفير مجموعة بيانات خارجية (اختياري)

بدلاً من السماح للبيئة بأخذ عينات ذاتية، يمكنك توفير train_dataset خارجي. على سبيل المثال، عندما تكون مهامك موجودة بالفعل في مجموعة بيانات، أو للتوجيه بين بيئات متعددة. في كل تمريرة تدريبية، يأخذ المدرب صفًا واحدًا، ويستخدم عموده "prompt" كموجّه، ويمرر أعمدة الصف إلى reset() كحجج باسم (حتى تقرأ البيئة المهمة من مجموعة البيانات).

from datasets import Dataset
from trl import AsyncGRPOTrainer

# Each row carries the prompt and the task data (here, the counter target to reach).
dataset = Dataset.from_dict(
    {
        "prompt": [[{"role": "user", "content": f"Increment the counter to {i}."}] for i in range(1, 7)],
        "target": list(range(1, 7)),
    }
)

class IncrementEnv:
    def reset(self, target, **kwargs) -> None:  # the row's `target` column arrives as a keyword argument
        self.counter = 0
        self.target = target

    def get_reward(self) -> float:
        return float(self.counter == self.target)

    def increment(self, step: int) -> int:  # exposed as a tool (see the full docstring above)
        self.counter += step
        return self.counter

if __name__ == "__main__":
    trainer = AsyncGRPOTrainer(
        model="Qwen/Qwen3-0.6B",
        train_dataset=dataset,  # the dataset owns the task; the "target" column reaches reset(target=...)
        environment_factory=IncrementEnv,
    )
    trainer.train()

المكافآت

لا تتعارض المكافآت التي يديرها البيئة مع المكافآت التي يديرها المدرب. مرّر reward_funcs للمكافآت التي يُعدّ حسابها من الإكمال أمراً طبيعياً (مثل فحص التنسيق على النص) و اترك البيئة تُعيد مكافأتها المعتمدة على حالتها عبر get_reward. تُجمع جميع المصادر بوزن 1.

def format_reward(completions, **kwargs):  # trainer-owned: scores the completion text
    ...

trainer = AsyncGRPOTrainer(
    model="Qwen/Qwen3-0.6B",
    args=AsyncGRPOConfig(max_steps=1000),
    reward_funcs=format_reward,          # trainer-owned: scores the completion
    environment_factory=IncrementEnv,    # env-owned: scores its internal state via `get_reward`
)

بيئات متعددة

لتدريب النموذج على مزيج من المهام في تشغيل واحد (مثل مهمة برمجة ولعبة)، مرر قاموسًا يربط أسماء البيئات بمصانعها بدلاً من دالة قابلة للاستدعاء واحدة. يختار كل تمرين بيئته عبر حقل environment في مجموعة البيانات، وتُعرض أدوات تلك البيئة فقط. يتجنب هذا تسريب أدوات غير ذات صلة (أداة move الخاصة باللعبة إلى مثال برمجي، والعكس صحيح). ما تزال كل بيئة تمتلك بياناتها وتعيد الموجه من reset()، لذا لا تحتاج مجموعة البيانات سوى عمود environment للتوجيه بينها.

import random

from datasets import Dataset
from trl import AsyncGRPOConfig, AsyncGRPOTrainer


class CodingEnv:
    def reset(self, **kwargs) -> str:
        n = random.randint(1, 20)
        return f"Compute the {n}th Fibonacci number."

    def run_code(self, code: str) -> str:  # exposed as a tool only for "coding" examples
        """Run the given Python code and return its stdout.

        Args:
            code: The Python source to execute.

        Returns:
            The captured standard output.
        """
        ...

class GameEnv:
    def reset(self, **kwargs) -> str:
        return "Reach the goal tile."

    def move(self, action: str) -> str:  # exposed as a tool only for "game" examples
        """Apply an action to the game and return the new observation.

        Args:
            action: One of "up", "down", "left", "right".

        Returns:
            The observation after the move.
        """
        ...

# The dataset only routes: each row picks the environment for that rollout
if __name__ == "__main__":
    dataset = Dataset.from_dict({"environment": ["coding", "game"] * 500})

    trainer = AsyncGRPOTrainer(
        model="Qwen/Qwen3-0.6B",
        args=AsyncGRPOConfig(chat_template_kwargs={"enable_thinking": False}),
        train_dataset=dataset,
        reward_funcs=reward_func,
        environment_factory={"coding": CodingEnv, "game": GameEnv},
    )
    trainer.train()

تُعاد استخدام أمثلة البيئة عبر الخطوات (مع إعادة الضبط بين الحلقات)، لذا يُدفع ثمن __init__ المكلف مرة واحدة بدلاً من كل خطوة. يُستهلك حقل environment من قِبل المدرب ولا يُمرَّر إلى reset.

التشغيلات متعددة الأدوار

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

يحصل كل عينة من عملية النشر على ميزة هذه العملية. وتُظهر rollout/fork_frac وrollout/drift_tokens_* كيفية تسوية الأدوار.

التكاملات مع البيئات

لتدريب وكلاء يعملون داخل إطارهم الخاص، مثل وكلاء البرمجة في بيئات Harbor المعزولة، راجع التدريب على الأطر في دليل OpenEnv.

تدريب نماذج اللغة البصرية

يمكن تدريب نماذج الرؤية واللغة (Qwen3.5, Qwen3.6, Qwen3-VL, …) على مجموعات بيانات نصية فقط. مرّر معرّف النموذج كما هو معتاد؛ لا يتغير أي شيء آخر.

برج الرؤية مجمّد. مجموعة البيانات النصية فقط لا تنتج رموز صور أبدًا، لذا لا يُختبر البرج أبدًا عبر مسار الأمامي. كل ما يقع خارج النموذج النصي (برج الرؤية، المُشغِّل متعدد الوسائط) يحمل requires_grad=False: فلا يتلقى تدرجات ولا حالة مُحسِّن، ويتخطى مزامنة الأوزان هذا الجزء، فيحتفظ الخادم بالقيم التي حمّلها من نقطة التفتيش. لا يزال البرج محمّلًا، لذا يستهلك ذاكرة وحدة معالجة الرسوميات لأوزانه.

لا تُدعَم الصور بعد. لا يتم تمرير الأوامر التي تحتوي على صور إلى خادم vLLM أو إلى مسار التقدّم في التدريب. كما يتطلب التدريب متعدد الوسائط مسار التعبئة الخالية من الحشو لبناء مواضع M-RoPE ثلاثية الأبعاد من شبكة الصورة، وهو ما لم يُنفَّذ هنا.

AsyncGRPOTrainer

فئة trl.AsyncGRPOTrainer

< >

( model: str | PreTrainedModelreward_funcs: collections.abc.Callable[..., list[float]] | list[collections.abc.Callable[..., list[float]]] | None = Noneargs: trl.trainer.async_grpo_config.AsyncGRPOConfig | None = Nonetrain_dataset: datasets.arrow_dataset.Dataset | datasets.iterable_dataset.IterableDataset | None = Noneprocessing_class: transformers.tokenization_utils_base.PreTrainedTokenizerBase | None = Nonecallbacks: list[transformers.trainer_callback.TrainerCallback] | None = Noneoptimizers: tuple[typing.Optional[torch.optim.Optimizer], typing.Optional[torch.optim.lr_scheduler.LambdaLR]] = (None, None)peft_config: PeftConfig | None = Nonetools: list[collections.abc.Callable] | None = Noneenvironment_factory: collections.abc.Callable[[], trl.trainer.async_grpo_trainer._SupportsReset] | dict[str, collections.abc.Callable[[], trl.trainer.async_grpo_trainer._SupportsReset]] | None = None )

المعاملات

  • model (str أو PreTrainedModel) — النموذج المراد تدريبه. يمكن أن يكون أحد ما يلي:
    • سلسلة نصية، تكون معرف النموذج لنموذج مُدرَّب مسبقًا مستضاف داخل مستودع نماذج على huggingface.co، أو مسارًا إلى مجلد يحتوي على أوزان النموذج المحفوظة باستخدام save_pretrained، مثل './my_model_directory/'. يتم تحميل النموذج مع البنية المُعلنة في ملفه الإعدادي: AutoModelForImageTextToText لنقطة تحقق لغة-رؤية (حيث يتم تحميل برج الرؤية الخاص به لكنه مجمّد، راجع تدريب نماذج اللغة-الرؤية)، وAutoModelForCausalLM في الحالات الأخرى.
    • كائن PreTrainedModel. يتم تبديل تنفيذ الانتباه فيه إلى واجهة الانتباه الشجري الخاصة بالمدرّب للتدريب المعبأ.

    يُستخدم اسم النموذج من ملف الإعداد لتحديد النموذج على خادم vLLM المستخدم للتوليد.

  • reward_funcs (RewardFunc | list[RewardFunc], اختياري) — دوال المكافأة المستخدمة لحساب المكافآت. لحساب المكافآت، نستدعي جميع دوال المكافأة باستخدام الأوامر (prompts) والإكمالات (completions) ثم نجمع المكافآت. يمكن تجاهلها عندما تُوفَّر المكافأة من خلال البيئة عبر environment_factory (انظر أدناه). يمكن أن تكون:
    • دالة مكافأة واحدة: تُزوَّد الدالة بالموجِّهات (prompts) والإكمالات المولَّدة، بالإضافة إلى أي أعمدة إضافية في مجموعة البيانات. يجب أن تُرجع قائمة بالمكافآت. يمكن أن تكون دوال المكافأة متزامنة أو غير متزامنة، كما يمكنها أن تُرجع None عندما لا تنطبق المكافأة على تلك العينات. وهذا مفيد في التدريب متعدد المهام حيث تنطبق دوال مكافأة مختلفة على أنواع مختلفة من العينات. عندما تُرجع دالة مكافأة None لعينة معينة، تُستبعد تلك الدالة من حساب المكافأة لتلك العينة. لمزيد من التفاصيل، راجع استخدام دالة مكافأة مخصصة.
    • قائمة من دوال المكافأة، حيث يكون كل عنصر دالة مكافأة كما هو موصوف أعلاه. تُجمع المكافآت من جميع الدوال.

    على عكس GRPOTrainer، تُحسب المكافآت في عملية فرعية مُنشأة، لذا يجب أن تكون كل دالة مكافأة (إلى جانب tools وenvironment_factory) قابلة للتسلسل (picklable): استخدم دالة على مستوى الوحدة (module-level function)، أو functools.partial، أو كائن فئة قابل للاستدعاء — فالدوال اللامدا (lambdas) والإغلاقات (closures) ستفشل عند بدء التشغيل. تُسجَّل الأدوات تحت اسم __name__ الخاص بها، لذا يجب أن تكون دوالاً على مستوى الوحدة. كما تعمل العملية الفرعية مع CUDA_VISIBLE_DEVICES=""، لذا يعمل نموذج المكافأة المدعوم بوحدة معالجة رسومية (GPU) على وحدة المعالجة المركزية (CPU) (بطيئاً)، وليس على وحدة معالجة الرسومات الخاصة بالمدرب.

  • args (AsyncGRPOConfig، اختياري) — إعدادات هذا المدرب. إذا كانت None، يتم استخدام إعداد افتراضي.
  • train_dataset (Dataset أو IterableDataset, اختياري) — مجموعة البيانات المستخدمة للتدريب. يجب أن تتضمن عمودًا "prompt". تُتجاهل أي أعمدة إضافية في مجموعة البيانات. يجب أن تكون العينات في حوارية صيغة: تحتوي كل عينة على رسائل منظمة (مثل الدور والمحتوى).

    يمكن حذفه فقط عند توفير environment_factory وامتلاك البيئة (أو توليدها إجرائيًا) للبيانات، مع إرجاع الأمر التوجيهي من طريقة reset() الخاصة بها. في هذه الحالة، يجب تعيين max_steps لتحديد مدة التدريب.

  • processing_class (PreTrainedTokenizerBase، اختياري) — فئة المعالجة المستخدمة لمعالجة البيانات. يجب ضبط جانب الحشو على “يسار”. إذا كانت None، يتم تحميل فئة المعالجة من اسم النموذج باستخدام from_pretrained. يجب ضبط رمز الحشو، tokenizer.pad_token. إذا لم تضبط فئة المعالجة رمز الحشو، سيتم استخدام tokenizer.eos_token كقيمة افتراضية.
  • callbacks (قائمة من TrainerCallback, اختياري) — قائمة من استدعاءات المراجعة (callbacks) لتخصيص حلقة التدريب. ستُضاف هذه الاستدعاءات إلى قائمة استدعاءات المراجعة الافتراضية المفصّلة هنا.

    إذا كنت ترغب في إزالة أحد المراجعات الافتراضية المستخدمة، فاستخدم طريقة remove_callback.

  • optimizers (tuple[torch.optim.Optimizer | None, torch.optim.lr_scheduler.LambdaLR | None]، اختياري، القيمة الافتراضية (None, None)) — مجموعة (tuple) تحتوي على المُحسِّن (optimizer) والمُجدوِل (scheduler) المطلوبين للاستخدام. ستكون القيمة الافتراضية هي نسخة من AdamW على النموذج الخاص بك، مع مُجدوِل يُحدده get_linear_schedule_with_warmup ويُتحكم به عبر args.
  • peft_config (PeftConfig، اختياري) — إعدادات PEFT المستخدمة لتغليف النموذج. إذا كانت None، فلن يتم تغليف النموذج. عند بدء تشغيل خادم vLLM باستخدام --enable-lora، يتم مزامنة مُكيّف LoRA معه كمُكيّف بدلاً من أوزان مدمجة؛ راجع قسم LoRA في توثيق AsyncGRPO.
  • tools (قائمة من Callable، اختياري) — قائمة من دوال الأدوات القابلة للاستدعاء (مزامنة أو غير مزامنة) التي يمكن للنموذج استدعاؤها أثناء التوليد. يجب أن تكون كل أداة دالة بايثون قياسية مع حُجج وقيم إرجاع مُعلَّمة الأنواع بشكل صحيح، وتعليق مستند بنمط Google يصف غرضها وحُججها وقيمة الإرجاع. لمزيد من التفاصيل، راجع: https://huggingface.co/docs/transformers/en/chat_extras#passing-tools. يستخدم النموذج اسم الدالة، وتلميحات الأنواع، وتعليق المستند لتحديد كيفية استدعائها. تأكد من أن قالب الدردشة الخاص بالنموذج يدعم استخدام الأدوات وأنه تم ضبطه الدقيق (fine-tuned) لاستدعاء الأدوات.
  • environment_factory (EnvironmentFactory أو dict[str, EnvironmentFactory], اختياري) — دالة قابلة للاستدعاء تنشئ وتعيد نسخة من البيئة، أو قاموس يربط أسماء البيئات بدوال قابلة للاستدعاء من هذا النوع. يجب أن تعرّف فئة البيئة طرقًا يمكن استدعاؤها كأدوات أثناء التوليد. يجب أن تلتزم كل طريقة بنفس المتطلبات الخاصة بـ tools الموصوفة أعلاه. كما يجب على البيئة أن تنفّذ دالة قابلة للاستدعاء باسم reset يمكن استخدامها لإعادة تعيين الحالة بين عمليات التوليد. يجب أن تعيد دالة reset إما None أو نصًا: عند إرجاع نص، يُضاف هذا النص إلى آخر رسالة مستخدم قبل التوليد. كما يمكن للبيئة أن تعرّف دالة get_reward لا تتخذ أي حجة وتعيد قيمة من نوع float: عند وجودها، تمتلك البيئة المكافأة، وتُستدعى get_reward مرة واحدة لكل تمريرة مكتملة لتقييمها من الحالة الداخلية للبيئة. تعمل كمصدر مكافأة إضافي (بوزن 1، ويُسجَّل باسم فئة البيئة) إلى جانب reward_funcs, الذي يصبح اختياريًا في هذه الحالة.

    باستخدام دالة قابلة للاستدعاء واحدة، يستخدم كل مثال نفس البيئة، مع وجود نسخة واحدة لكل تمرين حتى تبقى تفاعلاتها معزولة. أما عند استخدام قاموس، فيجب أن يحمل كل مثال حقل environment لاختيار بيئته بالاسم، ولا تُعرض في موجهه إلا أدوات تلك البيئة — مما يتيح لتمرين واحد خلط المهام (مثل بيئة برمجة وبيئة لعبة). هذه الميزة تجريبية وقد تتغير أو تُحذف في أي وقت دون إشعار مسبق.

مدرب لطريقة تحسين السياسة النسبية للمجموعات (GRPO). تم اقتراح هذا الخوارزمية في الأصل في الورقة DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models. هذا المدرب هو النسخة غير المتزامنة من GRPO، حيث يتم تفويض التوليد إلى خادم vLLM خارجي يعمل بشكل غير متزامن بالتوازي مع التدريب، مما يفصل التوليد (rollout) عن حلقة تحديث التدرج.

مثال:

>>> from trl import AsyncGRPOTrainer
>>> from trl.rewards import accuracy_reward
>>> from datasets import load_dataset

>>> dataset = load_dataset("trl-lib/DeepMath-103K", split="train")

>>> trainer = AsyncGRPOTrainer(
...     model="Qwen/Qwen2.5-0.5B-Instruct",
...     reward_funcs=accuracy_reward,
...     train_dataset=dataset,
... )
>>> trainer.train()

لتخصيص العبارات التي يتم أخذ عينات منها، أو كيفية توليد الجولة، أو كيفية حساب المزايا، قم بإنشاء فئة فرعية من AsyncRolloutLoop وقم بتعيينها كـ loop_cls للمدرّب. يقوم TRL ببناء الحلقة في

عملية النشر:

>>> class CurriculumTrainer(AsyncGRPOTrainer):
...     loop_cls = CurriculumRolloutLoop

تدريب

< >

( resume_from_checkpoint: str | bool | None = Nonetrial: optuna.Trial | dict[str, Any] | None = Noneignore_keys_for_eval: list[str] | None = None ) → ~trainer_utils.TrainOutput

المعاملات

  • resume_from_checkpoint (str أو bool، اختياري) — إذا كان str، فهو المسار المحلي لنقطة تفتيش محفوظة بواسطة نسخة سابقة من Trainer. وإذا كان bool ويساوي True، يتم تحميل آخر نقطة تفتيش في args.output_dir كما حفظتها نسخة سابقة من Trainer. إذا كانت موجودة، فسيستأنف التدريب من حالات النموذج/المُحسِّن/المُجدوِل المحمَّلة هنا.
  • trial (optuna.Trial أو dict[str, Any]، اختياري) — التجربة أو قاموس المعاملات الفائقة للبحث في المعاملات الفائقة.
  • ignore_keys_for_eval (list[str]، اختياري) — قائمة بالمفاتيح في مخرجات النموذج الخاص بك (إذا كان قاموسًا) التي يجب تجاهلها عند جمع التوقعات للتقييم أثناء التدريب.

يعيد

~trainer_utils.TrainOutput

كائن يحتوي على عدد الخطوات العالمية، وخسارة التدريب، والمقاييس.

نقطة الدخول الرئيسية للتدريب.

save_model

< >

( output_dir: str | None = None_internal_call: bool = False )

سيحفظ النموذج، بحيث يمكنك إعادة تحميله باستخدام from_pretrained().

سيتم الحفظ من العملية الرئيسية فقط.

push_to_hub

< >

( commit_message: str | None = 'End of training'blocking: bool = Truetoken: str | None = Nonerevision: str | None = None**kwargs )

المعاملات

  • commit_message (str، اختياري، الافتراضي "End of training") — الرسالة التي تُرسل عند الدفع.
  • blocking (bool، اختياري، الافتراضي True) — ما إذا كان ينبغي للدالة أن تُرجع فقط بعد اكتمال git push.
  • token (str، اختياري، القيمة الافتراضية None) — رمز (Token) يملك صلاحية الكتابة لتجاوز الحجج الأصلية الخاصة بـ Trainer.
  • revision (str، اختياري) — مراجعة git التي سيتم الالتزام منها. الافتراضي هو رأس فرع “main”.
  • kwargs (dict[str, Any]، اختياري) — معلمات إضافية تُمرَّر إلى ~Trainer.create_model_card.

ارفع self.model وself.processing_class إلى مركز النماذج 🤗 في المستودع self.args.hub_model_id.

AsyncGRPOConfig

فئة trl.AsyncGRPOConfig

< >

( output_dir: str | None = Noneper_device_train_batch_size: int = 8num_train_epochs: float = 3.0max_steps: int = -1learning_rate: float = 1e-06lr_scheduler_type: str = 'constant'lr_scheduler_kwargs: dict | str | None = Nonewarmup_steps: float = 0optim: transformers.training_args.OptimizerNames | str = 'adamw_torch_fused'optim_args: str | None = Noneweight_decay: float = 0.0adam_beta1: float = 0.9adam_beta2: float = 0.999adam_epsilon: float = 1e-08optim_target_modules: None | str | list[str] = Nonegradient_accumulation_steps: int = 1average_tokens_across_devices: bool = Truemax_grad_norm: float = 1.0label_smoothing_factor: float = 0.0bf16: bool | None = Nonefp16: bool = Falsebf16_full_eval: bool = Falsefp16_full_eval: bool = Falsetf32: bool | None = Nonegradient_checkpointing: bool = Truegradient_checkpointing_kwargs: dict[str, typing.Any] | str | None = Nonetorch_compile: bool = Falsetorch_compile_backend: str | None = Nonetorch_compile_mode: str | None = Noneuse_liger_kernel: bool = Falseliger_kernel_config: dict[str, bool] | None = Noneuse_cache: bool = Falseneftune_noise_alpha: float | None = Nonetorch_empty_cache_steps: int | None = Noneauto_find_batch_size: bool = Falselogging_strategy: transformers.trainer_utils.IntervalStrategy | str = 'steps'logging_steps: float = 1logging_first_step: bool = Falselog_on_each_node: bool = Truelogging_nan_inf_filter: bool = Trueinclude_num_input_tokens_seen: str | bool = 'no'log_level: str = 'passive'log_level_replica: str = 'warning'disable_tqdm: bool | None = Nonereport_to: None | str | list[str] = 'none'run_name: str | None = Noneproject: str = 'huggingface'trackio_space_id: str | None = Nonetrackio_bucket_id: str | None = Nonetrackio_static_space_id: typing.Union[str, NoneType, typing.Literal[False]] = Noneeval_strategy: transformers.trainer_utils.IntervalStrategy | str = 'no'eval_steps: float | None = Noneeval_delay: float = 0per_device_eval_batch_size: int = 8prediction_loss_only: bool = Falseeval_on_start: bool = Falseeval_do_concat_batches: bool = Trueeval_use_gather_object: bool = Falseeval_accumulation_steps: int | None = Noneinclude_for_metrics: list[str] = <factory>batch_eval_metrics: bool = Falsesave_only_model: bool = Falsesave_strategy: transformers.trainer_utils.SaveStrategy | str = 'steps'save_steps: float = 500save_on_each_node: bool = Falsesave_total_limit: int | None = Noneenable_jit_checkpoint: bool = Falsepush_to_hub: bool = Falsehub_token: str | None = Nonehub_private_repo: bool | None = Nonehub_model_id: str | None = Nonehub_strategy: transformers.trainer_utils.HubStrategy | str = 'every_save'hub_always_push: bool = Falsehub_revision: str | None = Noneload_best_model_at_end: bool = Falsemetric_for_best_model: str | None = Nonegreater_is_better: bool | None = Noneignore_data_skip: bool = Truerestore_callback_states_from_checkpoint: bool = Falsefull_determinism: bool = Falseseed: int = 42data_seed: int | None = Noneuse_cpu: bool = Falseaccelerator_config: dict | str | None = Noneparallelism_config: accelerate.parallelism_config.ParallelismConfig | None = Nonedataloader_drop_last: bool = Falsedataloader_num_workers: int = 0dataloader_pin_memory: bool = Truedataloader_persistent_workers: bool = Falsedataloader_prefetch_factor: int | None = Nonedataloader_multiprocessing_context: str | None = Nonedataloader_in_order: bool = Trueremove_unused_columns: bool = Falselabel_names: list[str] | None = Nonetrain_sampling_strategy: str = 'random'length_column_name: str = 'length'ddp_find_unused_parameters: bool | None = Noneddp_bucket_cap_mb: int | None = Noneddp_broadcast_buffers: bool | None = Noneddp_static_graph: bool | None = Noneddp_backend: str | None = Noneddp_timeout: int = 1800fsdp: str | None = Nonefsdp_config: dict[str, typing.Any] | str | None = Nonedeepspeed: dict | str | None = Nonedebug: str | list[transformers.debug_utils.DebugOption] = ''skip_memory_metrics: bool = Truedo_train: bool = Falsedo_eval: bool = Falsedo_predict: bool = Falseresume_from_checkpoint: str | None = Nonelocal_rank: int = -1model_init_kwargs: dict[str, typing.Any] | str | None = Nonedtype: str = 'float32'trust_remote_code: bool = Falserouter_aux_loss_coef: float | None = Nonenum_generations: int = 8max_completion_length: int = 2048temperature: float = 1.0top_p: float = 1.0top_k: int = 0min_p: float | None = Nonechat_template_kwargs: dict | str | None = Nonemax_tool_calling_iterations: int | None = Nonevllm_server_base_url: str = 'http://localhost:8000'vllm_server_timeout: float = 1800.0epsilon: float = 0.2epsilon_high: float | None = Nonescore_centering: bool = Truevllm_importance_sampling_clip_min: float = 0.5vllm_importance_sampling_clip_max: float = 5.0num_sampler_logprobs: int = 8token_budget: int | None = Nonemax_concurrent_rollouts: int = 512max_staleness: int = 4weight_sync_steps: int = 1rollout_worker_timeout: float = 300.0log_completions: bool = Falsenum_completions_to_print: int | None = None )

المعاملات التي تتحكم في النموذج

  • model_init_kwargs (dict[str, Any] أو str، اختياري) — حجج الكلمات المفتاحية لـ from_pretrained، تُستخدم عند إنشاء النموذج من مسار. تُستخدم قيمة revision أيضًا عند تحميل فئة المعالجة.
  • dtype (str, اختياري, الافتراضي هو "float32") — نوع البيانات الذي يُحمَّل به النموذج، وهو أحد "auto", "bfloat16", "float16" أو "float32". الافتراضي له "float32" لأن عدم التطابق بين التدريب والاستدلال الذي يُقاس عليه هذا المُدرِّب (Defeating the Training-Inference Mismatch via FP16) ) حساس لدقة المُدرِّب نفسه. إغلاق هذه الفجوة من البداية إلى النهاية يتطلب أيضًا تشغيل خادم vLLM بنفسvllm serve --dtype); يُسجَّل عدم التطابق كتحذير عند بدء التدريب. أي dtype في model_init_kwargs لها الأولوية.

    هذا يضبط فقط نوع البيانات (dtype) الذي تُحمَّل به الأوزان. عند استخدام FSDP مع الدقة المختلطة (الافتراضي هو bf16=True)، يقوم accelerate بترقية كل معامل قابل للتدريب إلى fp32 قبل التقسيم، لذا فإن الأوزان الرئيسية وحالة المُحسِّن تكون من نوع fp32 بغض النظر عما يقوله dtype، بينما يتم تنفيذ التقدّم والتراجع في bf16 أو fp16. ثم تُكتب نقاط التفتيش (Checkpoints) في نوع البيانات الخاص بالدقة المختلطة، وليس في fp32. بدون FSDP لا يحدث أي ترقية: استخدام dtype="bfloat16" مع bf16=True يحافظ على أوزان وحالات مُحسِّن من نوع bf16، وهو ما يمثل تدريبًا خالصًا في bf16.

  • trust_remote_code (bool, optional, defaults to False) — Whether to allow loading models and tokenizers that ship custom Python code from the Hub. Forwarded to from_pretrained and from_pretrained.
  • router_aux_loss_coef (float, اختياري) — معامل خسارة التوازن على الأحمال المساعدة لنماذج مزيج الخبراء (MoE)، تُضاف إلى خسارة التدريب بهذا الوزن. عند عدم تعيينه، يُستخدم القيمة المُعلنة في إعدادات النموذج. عيّن 0.0 لتعطيله. يفشل عند استخدامه مع نموذج غير MoE.

المعاملات التي تتحكم في التوليد

  • num_generations (int، اختياري، القيمة الافتراضية 8) — عدد الأجيال التي يتم أخذ عينات منها لكل موجه.
  • max_completion_length (int، اختياري، القيمة الافتراضية 2048) — الحد الأقصى لعدد الرموز المولّدة لكل طلب vLLM، وبالتالي لكل جولة في عملية نشر متعددة الجولات.
  • temperature (float، اختياري، القيمة الافتراضية 1.0) — درجة الحرارة للعينات. كلما زادت درجة الحرارة، زادت عشوائية المكملات.
  • top_p (float، اختياري، القيمة الافتراضية 1.0) — قيمة عشرية تتحكم في الاحتمال التراكمي لأعلى الرموز المراد أخذها في الاعتبار. يجب أن تكون ضمن المجال (0، 1]. اضبطها على 1.0 لاعتبار جميع الرموز. إذا كانت أقل من 1.0، فيجب ضبط top_k أيضًا، ويجب تشغيل خادم vLLM (>= 0.28) باستخدام --return-sampling-mask --enable-scale-out: حيث يعيد المدرب تطبيع احتمالاته اللوغاريتمية على مجموعة المرشحين التي تم أخذ كل رمز منها، تمامًا كما يفعل الخادم. أما إصدارات vLLM الأقدم فتحتفظ بالمفردات الكاملة.
  • top_k (int، اختياري، القيمة الافتراضية 0) — عدد رموز المفردات ذات أعلى احتمالات التي يجب الاحتفاظ بها لتصفية top-k. إذا كانت القيمة 0، يتم تعطيل تصفية top-k وتُعتبر جميع الرموز. أي قيمة أخرى تتطلب أعلام الخادم الموصوفة تحت top_p.
  • min_p (float, اختياري) — أدنى احتمال للرمز، والذي يُضرب في احتمال الرمز الأكثر احتمالاً. يجب أن يكون قيمة بين 0.0 و 1.0. القيم النموذجية تقع في نطاق 0.01-0.2. عند تجاوز 0.0، يجب ضبط top_k أيضاً، مع أعلام الخادم الموصوفة تحت top_p.
  • chat_template_kwargs (dict[str, Any]، اختياري) — معلمات إضافية بالكلمات المفتاحية تُمرَّر إلى دالة apply_chat_template عند توليد الإكمالات.
  • max_tool_calling_iterations (int, اختياري) — الحد الأقصى لعدد جولات استدعاء الأدوات عند تدريب وكيل. إذا كانت None، فلا يوجد حد، ويتوقف التوليد عندما يولّد النموذج ردًا في جولة دون أي استدعاءات للأدوات.

المعاملات التي تتحكم في خادم vLLM

  • vllm_server_base_url (str، اختياري، القيمة الافتراضية "http --//localhost:8000"): العنوان الأساسي لخادم vLLM المستخدم للتوليد (مثل "http://localhost:8000").
  • vllm_server_timeout (float, اختياري، القيمة الافتراضية 1800.0) — مهلة بالثواني للاتصالات مع خادم vLLM: انتظار جهوزيته عند الإقلاع، كل طلب توليد، وكل استدعاء مزامنة الأوزان. يرفع خطأ عند انتهاء المهلة في انتظار الخادم واستدعاءات مزامنة الأوزان؛ أما طلب التوليد الذي تتجاوز مدته المهلة فيُعاد.

المعاملات التي تتحكم في التدريب

  • epsilon (float، اختياري، القيمة الافتراضية 0.2) — قيمة إيبسلون للقص.
  • epsilon_high (float, اختياري) — قيمة إبسيلون الحد الأعلى للتقليم. إذا لم يتم تحديدها، فإن القيمة الافتراضية هي نفس قيمة الحد الأدنى المحددة في الحجة epsilon. يوصي الورق DAPO بقيمة 0.28.
  • score_centering (bool، اختياري، القيمة الافتراضية True) — ما إذا كان سيتم التدريب مع توسيط الدرجات (الورقة) بدلاً من الهدف المقطوع: الدرجة المرجّحة بأهمية كل رمز مُستخرج مطروحاً منها توقعها تحت مُستخرج vLLM، بحيث لا تسحب ميزة ثابتة السياسة نحو المُستخرج. يُؤخذ التوقع على num_sampler_logprobs من أكثر الرموز احتمالاً في كل موضع، والتي يبلغ المُستخرج عن احتمالاتها اللوغاريتمية.
  • vllm_importance_sampling_clip_min (float, اختياري، القيمة الافتراضية 0.5) — الحد الأدنى C_min لنسبة الأهمية بين السياسة ومُعيّن vLLM. يتم استبعاد الرموز التي تقع نسبة أهميتها خارج النطاق [vllm_importance_sampling_clip_min, vllm_importance_sampling_clip_max] من دالة الخسارة (أخذ عينات الأهمية المقنّعة).
  • vllm_importance_sampling_clip_max (float، اختياري، القيمة الافتراضية 5.0) — الحد الأعلى C_max لنسبة الأهمية.
  • num_sampler_logprobs (int، اختياري، القيمة الافتراضية 8) — عدد أعلى قيم الاحتمال اللوغاريتمي (log-probabilities) المطلوبة من مُعيّن vLLM لكل رمز مُولَّد، وتُستخدم في عملية توسيط الدرجات. كلما زاد العدد، غطّى كتلة احتمالات المُعيّن بشكل أكبر، لكنه تكلّف أكثر في التسلسل (serialization)؛ ويحدّ من قيمته خادم --max-logprobs.
  • token_budget (int، اختياري) — الحد الأقصى لعدد الرموز المُرسَلة في صف واحد (تمريرة أمامية لرتبة DP واحدة). يُشكّل TokenBudgetBatcher دفعات مصغّرة متوازنة من حيث تكلفة الانتباه، بحيث يبقى كل صف ضمن هذا الميزانية، مما يحدّ من ذروة الذاكرة بشكل مستقل عن عدد العينات (حيث يصبح عدد العينات لكل صف ديناميكيًا). تحدّ الميزانية من الرموز الفريدة وليس من الرموز الخام، لذا تُحتسب الرموز المشتركة بين عدة عينات مرة واحدة، كما تحدّ من حدود مصطلحات الخسارة للصف. إذا كانت None (الافتراضي)، يتم تعيينها إلى max_model_len لخادم vLLM (يتم الاستعلام عنه عند بدء التدريب) — وهو الحد الأقصى لطول الموجه + الإكمال — بحيث لا يمكن لأي عينة rollout تجاوز الميزانية أبدًا. العينة الأطول من token_budget لا تتسع في أي صف وتُستبعد مع تحذير.

المعاملات التي تتحكم في خط أنابيب النشر غير المتزامن

  • max_concurrent_rollouts (int، اختياري، القيمة الافتراضية 512) — الحد الأقصى لعدد عمليات التمرير (rollouts) التي تعمل في وقت واحد. يحتفظ كل منها ببيئة، وقد يشغّل استدعاءات أدوات متزامنة، ويحافظ على اتصال بخادم vLLM، لذا اضبطه بما يتناسب مع ما يمكن لهذه الموارد تحمّله. تحت هذا الحد، يتبع عدد عمليات التمرير الجارية استخدام ذاكرة KV في خادم vLLM وmax_staleness.
  • max_staleness (int، اختياري، القيمة الافتراضية 4) — الحد الأقصى لعدد خطوات تحديث الأوزان التي يمكن أن تتأخر عنها عينة التوليد عن النسخة الحالية من النموذج قبل أن تُستبعد. يتم التمسك بهذا الحد: لا تبدأ مجموعة جديدة من عمليات التوليد إلا عندما تتسع الرموز الموجودة في قائمة الانتظار والتي قيد المعالجة ضمن ما يستهلكه المدرب في max(max_staleness, 1) خطوات.
  • weight_sync_steps (int، اختياري، القيمة الافتراضية 1) — عدد خطوات التدريب بين مزامنات الأوزان إلى خادم vLLM.
  • rollout_worker_timeout (float, اختياري، القيمة الافتراضية 300.0) — عدد الثواني منذ آخر نبضة قلب من عامل التمرير (rollout worker) قبل أن يعتبر المدرب العامل متوقفًا عن الاستجابة ويُلغي العملية.

المعاملات التي تتحكم في التسجيل

  • log_completions (bool، اختياري، القيمة الافتراضية False) — ما إذا كان سيتم تسجيل عينة من أزواج (الموجّه، الإكمال) كل logging_steps خطوات.
  • num_completions_to_print (int, اختياري) — عدد الإكمالات التي سيتم طباعتها باستخدام rich. إذا كانت None، فسيتم تسجيل جميع الإكمالات.

فئة التهيئة الخاصة بـ AsyncGRPOTrainer.

تتضمن هذه الفئة فقط المعلمات الخاصة بالتدريب غير المتزامن لخوارزمية GRPO. للحصول على قائمة كاملة بحجج التدريب، يُرجى الرجوع إلى توثيق TrainingArguments. لاحظ أن القيم الافتراضية في هذه الفئة قد تختلف عن تلك الموجودة في TrainingArguments.

تتميز هذه المعلمات بقيم افتراضية مختلفة عن TrainingArguments:

  • logging_steps: القيمة الافتراضية هي 1 بدلاً من 500.
  • gradient_checkpointing: القيمة الافتراضية هي True بدلاً من False.
  • bf16: القيمة الافتراضية هي True إذا لم يتم تعيين fp16، بدلاً من False.
  • learning_rate: القيمة الافتراضية هي 1e-6 بدلاً من 5e-5.
  • lr_scheduler_type: القيمة الافتراضية هي constant بدلاً من linear (انظر أدناه).
  • ignore_data_skip: القيمة الافتراضية هي True بدلاً من False؛ حلقة التخطي وإعادة التشغيل الخاصة بـ Trainer الأساسي لا تنطبق على طابور rollout غير المتزامن.
  • remove_unused_columns: القيمة الافتراضية هي False بدلاً من True، ويجب أن تبقى False: حيث تُمرَّر جميع أعمدة مجموعة البيانات إلى دوال المكافأة.

مدة التدريب ومعدل التعلم في وضع المصالحة القائم على الرسائل: يمكن أن تتفرع محادثة متعددة الأدوار إلى عدد متغير من صفوف التدريب (حيث يبدأ إعادة كتابة المحادثة صفًا جديدًا)، لذا فإن عدد العينات، وبالتالي عدد خطوات المُحسِّن، لكل حقبة غير معروف مسبقًا. ونتيجةً لذلك:

  • num_train_epochs يحدد التدريب بعدد الدورات الكاملة على مجموعة بيانات prompt، حيث يُحسب العدد بناءً على عدد الأوامر المميزة التي تم التدريب عليها فعليًا. وهذا مستقل عن عدد الصفوف التي تنتجها الفروع، لذا فإن طلب N دورة يعني دائمًا التدريب على N مرور على البيانات. عند ترك max_steps غير مُعيَّن، يكون هذا هو شرط التوقف، بينما max_steps مجرد حد أمان.
  • max_steps، إذا تم تعيينه بشكل صريح (> 0)، يصبح شرط التوقف (حيث يُحدَّد التوقف بناءً على خطوات المُحسِّن بدلاً من الفترات) ويعطّل شرط التوقف القائم على الفترات.
  • lr_scheduler_type افتراضيًا constant لأن أفق التلاشي يُقاس بخطوات المُحسِّن، والتي لا يمكن معرفتها مسبقًا عندما يعتمد عدد الخطوات على معدل التفرع. لمعدل تعلّم متلاشٍ، اضبط جدولًا متلاشيًا معًا مع max_steps صريح.

AsyncRolloutLoop

فئة trl.trainer.async_grpo_trainer.AsyncRolloutLoop

< >

( config: AsyncGRPOConfigmodel_name: strdataset: Datasetreward_funcs: list[collections.abc.Callable[..., list[float]]]processing_class: PreTrainedTokenizerBaseeos_token_ids: list[int]rollout_buffer: Queuemodel_version_value: Synchronizedtoken_allowance: Synchronizedheartbeat_value: Synchronizedfailed_event: Eventexception_info_queue: Queuemetrics_queue: Queuetools: list[collections.abc.Callable] | None = Noneenvironment_factory: collections.abc.Callable[[], object] | dict[str, collections.abc.Callable[[], object]] | None = Nonescore_queue_maxsize: int = 16lora_name: str | None = Nonedataset_start_index: int = 0 )

يولّد التوليدات ويقيّمها لـ AsyncGRPOTrainer، في عملية توليد مستقلة.

يُنفّذ الإعداد الافتراضي خوارزمية GRPO. لتخصيص تشغيل، أنشئ فئة فرعية منه، وأعد تعريف الطرق التي تتغير فقط، ثم اضبط الفئة الفرعية كـ loop_cls للمدرّب. يمرّ كل موجه بالترتيب التالي:

  • filter_prompt(): ما إذا كان سيتم توليد أي شيء للموجّه.
  • generate_one(): تمريرة كاملة واحدة، تستدعي generate_one_turn() لكل طلب أخذ عينات.
  • يتم تقييم مجموعة من num_generations من عمليات النشر: حيث تكون مكافآتها هي مجموع reward_funcs الخاص بالمدرب، والبيئات التي تُعرّف get_reward، وRollout.reward. ثم يُنفَّذ compute_advantages() مرة واحدة لكل مجموعة.

ينشئ setup() ويحرر close() الموارد على حلقة الأحداث الخاصة بالحلقة، مثل عميل HTTP. يمكن لخطاف القراءة أن يقرأ self.config (يمكن لفئة فرعية من AsyncGRPOConfig أن تحمل حقولها الخاصة) وself.tokenizer. نظرًا لأن الحلقة تُبنى في عملية مولّدة، يجب أن تكون الفئة الفرعية قابلة للاستيراد (معرّفة على مستوى الوحدة).

filter_prompt

< >

( row_index: introw: dict[str, typing.Any]epoch: int ) → bool

المعاملات

  • row_index (int) — مؤشر الصف في مجموعة بيانات التدريب.
  • row (dict[str, Any]) — صف مجموعة البيانات.
  • epoch (int) — عدد مرات المرور الكاملة على مجموعة البيانات قبل هذه المرة.

يعيد

bool

True لتوليد num_generations من عمليات التمرير للموجّه، وFalse لتخطيها.

ما إذا كان سيتم التوليد لموجّه (prompt) معين. يتم تجاوز الموجّه المرفوض في هذه الحقبة (epoch) ويُحتسب كمستهلك، تمامًا كما هو الحال مع مجموعة مُهملَة. يتم قبول جميع الموجّهات افتراضيًا. يؤدي رفض جميع الموجّهات في حقبة كاملة إلى حدوث خطأ، لأنه لا يمكن لأي دفعة (batch) الوصول إلى المدرب (trainer) بعد ذلك.

generate_one

< >

( prompt: list[dict[str, str]]tools: list[collections.abc.Callable]group_id: int = 0 ) → Rollout

المعاملات

  • prompt (list[dict[str, Any]]) — رسائل الاستدعاء.
  • tools (list[Callable]) — الأدوات المتاحة لهذا التمرين: أدوات المدرب tools، بالإضافة إلى الطرق العامة للبيئة عند استخدام environment_factory.
  • group_id (int، اختياري، القيمة الافتراضية 0) — معرّف المجموعة التي ينتمي إليها التمرين.

يعيد

التنفيذ

النتيجة النهائية للتوزيع.

أنشئ تمريرة واحدة لموجّه.

بشكل افتراضي، يتبع حلقة الأدوات متعددة الأدوار النمط التالي: في كل دور، تُصيَّر المحادثة كاملةً باستخدام قالب الدردشة، ثم يُستخرج ردٌّ باستخدام generate_one_turn()، وتُنفَّذ استدعاءات الأدوات التي يحتويها، حتى يتوقف النموذج عن استدعاء الأدوات أو حتى بلوغ حد max_tool_calling_iterations. يمكنك تجاوز هذا السلوك لتوليد الردود بطريقة مختلفة، على سبيل المثال باستخدام إطار عمل وكيل. أعد كل طلب استخلاص ضمن Rollout.turns، إذ تُبنى تسلسلات التدريب من هذه الطلبات، واضبط Rollout.reward هنا لتقييم مسار التنفيذ.

generate_one_turn

< >

( prompt_ids: list[int] ) → TurnRecord

المعاملات

  • prompt_ids (list[int]) — معرّفات الرموز (Token ids) للموجّه الكامل.

يعيد

TurnRecord

الموجّه والرموز المُستخرجة مع احتمالاتها اللوغاريتمية. عند استخدام top_k أو top_p، يحتوي أيضاً على مجموعة المرشحين لكل رمز مستخرج، ومع num_sampler_logprobs، يحتوي على أعلى الاحتمالات اللوغاريتمية للمستخرج.

استخرج ردًا واحدًا من خادم vLLM، مع إعدادات أخذ العينات الخاصة بالتكوين (temperature، top_p، top_k، min_p، repetition_penalty وmax_completion_length).

compute_advantages

< >

( group: RolloutGroup ) → np.ndarray

المعاملات

  • group (RolloutGroup) — التوليدات الـnum_generations لموجّه واحد، مع مجموع مكافآتها في group.rewards.

يعيد

np.ndarray

ميزة واحدة لكل تمريرة، بترتيب group.rewards.

احسب ميزة واحدة لكل تمريرة ضمن مجموعة. افتراضياً، يتم توحيد كل مكافأة ضمن المجموعة؛ وتُمنح ميزة 0 للتمريرة التي تحتوي على مكافأة بقيمة NaN، وتُستبعد من حساب المتوسط والانحراف المعياري.

يُنفَّذ لكل مجموعة. تُستبعد المجموعة التي تكون مكافآتها المُقيَّمة جميعها متساوية، بغض النظر عن المزايا التي تُرجعها.

إعداد

< >

( )

أنشئ الموارد التي تحتاجها الخطافات الأخرى، مثل عميل HTTP. يعمل على حلقة الأحداث الخاصة بالحلقة قبل أول مطالبة. لا يفعل شيئًا افتراضيًا.

إغلاق

< >

( )

تحرير ما أنشأه setup(). يُنفَّذ عند توقف الحلقات، بما في ذلك عند حدوث فشل. لا يؤدي أي إجراء افتراضيًا.

التنفيذ

فئة trl.trainer.async_grpo_trainer.Rollout

< >

( completion: list[dict[str, str]]completion_ids: list[int]turns: list[trl.trainer.async_grpo_trainer.TurnRecord]tool_call_count: int = 0tool_failure_count: int = 0reward: float | None = Nonerollout_id: str = <factory> )

المعاملات

  • completion (list[dict[str, Any]]) — Messages generated after the prompt, in conversational format. Passed to the reward functions as completions.
  • completion_ids (list[int]) — معرّفات الرموز (Token ids) للإكمال المولّد. تُمرَّر إلى دوال المكافأة على أنها completion_ids.
  • turns (list[TurnRecord]) — طلبات العينة التي أنتجت التمرين، بالترتيب. تُبنى تسلسلات التدريب منها: يتم تدريب الرموز المُستخرجة، بينما يُعدّ باقي كل موجه سياقًا.
  • tool_call_count (int، اختياري، القيمة الافتراضية 0) — عدد استدعاءات الأدوات التي تمت خلال الجولة.
  • tool_failure_count (int، اختياري، القيمة الافتراضية 0) — عدد استدعاءات الأدوات التي فشلت.
  • reward (float، اختياري) — المكافأة المحسوبة أثناء التوليد، على سبيل المثال بواسطة مُقيِّم خارجي. تُضاف إلى مكافآت دوال المكافأة والبيئات. إذا كانت None، فلن تحصل هذه الجولة على أي مكافأة من هذا المصدر.
  • rollout_id (str، اختياري) — يحدد الـ rollout في صفوف التدريب الخاصة به، لأغراض التصحيح. القيمة الافتراضية هي معرّف سداسي عشوائي جديد.

نتيجة واحدة مكتملة، كما تُعاد بواسطة generate_one().

TurnRecord

فئة trl.trainer.async_grpo_trainer.TurnRecord

< >

( prompt_ids: list[int]output_ids: list[int]output_log_probs: list[float] = <factory>output_mask: list[int] | None = Noneoutput_sampling_mask: list[list[int]] | None = Noneoutput_sampler_logprobs: list[dict[int, float]] | None = None )

المعاملات

  • prompt_ids (list[int]) — معرّفات الرموز (Token ids) للموجّه الكامل المُرسَل لهذا الدور، بما في ذلك كل الأدوار السابقة في المحادثة.
  • output_ids (list[int]) — معرّفات الرموز التي ولّدها المُعيّن.
  • output_log_probs (list[float]، اختياري، القيمة الافتراضية []) — احتمالية السامبلر (log-probability) لكل رمز في output_ids. مطلوب عند تدريب أيٍّ منها.
  • output_mask (list[int]، اختياري) — قيمة واحدة لكل رمز في output_ids: 1 للتدريب على الرمز، و0 للاحتفاظ به كسياق فقط. إذا كانت None، يتم التدريب على كل رمز في المخرجات.
  • output_sampling_mask (list[list[int]]، اختياري) — قائمة واحدة لكل رمز في output_ids: معرّفات الرموز التي يمكن أن يسحب منها المُعيّن (sampler) تحت تقليص top_k / top_p. إذا كان None، فهي المفردات الكاملة.
  • output_sampler_logprobs (list[dict[int, float]], اختياري) — قائمة واحدة لكل رمز في output_ids: احتمالات السجل (log-probabilities) لأكثر الرموز احتمالًا من قبل العينة (sampler)، مفهرسة بمعرّف الرمز. يُستخدم في توسيط الدرجات (num_sampler_logprobs).

طلب أخذ عينة واحد: الأمر المُرسَل إلى مُولّد العينات والرموز التي أنتجها.

RolloutGroup

فئة trl.trainer.async_grpo_trainer.RolloutGroup

< >

( prompts: list[list[dict[str, str]]]reward_kwargs: dict[str, list[typing.Any]]completions: list[list[dict[str, str]]]completions_ids: list[list[int]]completions_sequences: list[list[trl.trainer.async_grpo_trainer.TrainingSequence]]tool_call_counts: list[int]tool_failure_counts: list[int]model_version: intgroup_id: introw_index: intenv_rewards: list[tuple[type, float] | None]rollout_rewards: list[float | None]queued_at: float = 0.0rewards: numpy.ndarray | None = None )

المعاملات

  • prompts (list[list[dict[str, Any]]]) — موجّه لكل تمرين.
  • reward_kwargs (dict[str, list[Any]]) — الأعمدة الأخرى في صف مجموعة بيانات الاستعلام، والتي تُمرَّر إلى دوال المكافأة كحجج مُسمّاة.
  • completions (list[list[dict[str, Any]]]) — رسائل الإكمال لكل تمرين.
  • completions_ids (list[list[int]]) — معرّفات رموز الإكمال لكل تمرين.
  • completions_sequences (list[list[TrainingSequence]]) — تسلسلات التدريب المبنية من كل تمرين. التمرين الذي تغيّر تاريخه المُعاد ترميزه بين الأدوار يعطي أكثر من تسلسل واحد.
  • tool_call_counts (list[int]) — عدد استدعاءات الأدوات في كل تمريرة.
  • tool_failure_counts (list[int]) — عدد استدعاءات الأدوات الفاشلة في كل تمريرة.
  • model_version (int) — إصدار النموذج عند بدء أول تمرين للمجموعة.
  • group_id (int) — معرّف المجموعة، يُحسب بدءًا من 0 في عملية النشر.
  • row_index (int) — مؤشر صف الموجه في مجموعة بيانات التدريب.
  • env_rewards (list[tuple[type, float] or None]) — بالنسبة إلى تمرين (rollout) تُعرّف بيئته get_reward، فهي فئة البيئة ومكافأتها؛ وإلا None.
  • rollout_rewards (list[float or None]) — Rollout.reward لكل عملية نشر.
  • queued_at (float، اختياري، القيمة الافتراضية 0.0) — time.monotonic() عند وضع المجموعة في قائمة الانتظار للتقييم.
  • rewards (np.ndarray، اختياري) — المكافأة الإجمالية لكل تمرين، مجمّعة على جميع مصادر المكافآت، وتكون NaN عندما لا تمنح أي مصدر تقييمًا لها. يتم تعيينها قبل استدعاء compute_advantages().

التوليدات num_generations لموجّه واحد، مُقيَّمة معًا. تحتوي كل قائمة على إدخال واحد لكل توليد.

Update on GitHub