TRL documentation
تكامل OpenEnv لتدريب نماذج اللغة الكبيرة مع بيئات
تكامل OpenEnv لتدريب نماذج اللغة الكبيرة مع بيئات
OpenEnv هو إطار عمل مفتوح المصدر لتعريف بيئات التعلم المعزز (RL) وسير العمل الوكيلية وتنفيذها والتفاعل معها. يوفر واجهات برمجة تطبيقات (APIs) موحدة للتفاعل مع البيئات، ويدعم تشغيل البيئات كخوادم خلفية (عبر WebSocket أو التنفيذ المُحاوِر). يمكنك العثور على مجموعة من بيئات OpenEnv الجاهزة للاستخدام على Hugging Face Hub.
يتناول هذا الدليل كيفية دمج OpenEnv مع TRL. لمزيد من المعلومات حول OpenEnv نفسه، راجع وثائق OpenEnv.
أمثلة جاهزة للاستخدام من OpenEnv:
grpo_echo(الأدنى)،grpo_catch،grpo_wordle،grpo_sudoku،grpo_multi_env،grpo_browsergym،grpo_carla، وasync_grpo_harbor(محلي و HF Jobs).
متى تستخدم البيئات
يمكن استخدام GRPOTrainer لتدريب الوكلاء. وفي مهام الوكلاء، يدعم وضعين: tools، حيث يمكن للنموذج استدعاء دوال خارجية لكن كل استدعاء يكون بلا حالة ومستقلًا، وenvironments، الذي يحافظ على الحالة عبر الأدوار، مما يتيح تفاعلًا متعدد الأدوار حقيقيًا حيث تُشكّل إجراءات الوكيل الملاحظات المستقبلية. استخدم البيئات عندما تكون الاستمرارية مهمة — على سبيل المثال، التنقل في لعبة، أو تصفح صفحة ويب، أو أي مهمة تعتمد فيها ما يراه الوكيل تاليًا على ما قام به سابقًا.
اختيار تكامل بيئة
يُعد OpenEnv المسار الأصلي الموثّق هنا. كما أن تكاملين إضافيين — OpenReward وHarbor — يلتزمان بعقد environment_factory نفسه، ويمكن استبدال أحدهما بالآخر على مستوى TRL. راجع مقارنة تكاملات البيئة في دليل GRPO لاختيار التكامل الذي يناسب نظامك البيئي لمهمتك.
التثبيت
تُستضاف بيئات OpenEnv على شكل مساحات Hugging Face Spaces، وهي أيضًا مستودعات Git يمكن تثبيتها عبر pip:
# Echo environment
pip install "openenv-echo-env @ git+https://huggingface.co/spaces/openenv/echo_env"
# Wordle (TextArena) environment
pip install "openenv-textarena @ git+https://huggingface.co/spaces/openenv/wordle"
# Catch (OpenSpiel) environment
pip install "openenv-openspiel-env @ git+https://huggingface.co/spaces/openenv/openspiel_env"يُثبّت هذا الأمر عميل البيئة (مثل EchoEnv) الذي يتواصل مع خادم البيئة عن بُعد عبر WebSocket، إلى جانب نماذج الإجراءات والملاحظات وجميع التبعيات المطلوبة (بما في ذلك openenv).
يمكنك العثور على أمر التثبيت لأي بيئة على صفحة مساحة HF الخاصة بها. انقر على قائمة ⋮ (النقاط الثلاث) واختر “Use this Space” لعرض تعليمات التثبيت.
يمكنك أيضًا تثبيت الحزمة الأساسية من PyPI باستخدام
pip install "openenv>=0.3.1"، لكن لاحظ أنه قد يلزم تثبيت تبعيات محددة للبيئة بشكل منفصل.
لأغراض التطوير، يمكنك استنساخ مستودع OpenEnv وتثبيته محليًا:
git clone https://github.com/openenv/OpenEnv.git
cd OpenEnv/envs/echo_env
pip install -e .تتضمن كل سكريبت بيئة في TRL بيانات وصفية للمعتمدات المضمنة (PEP 723) بحيث يمكنك أيضًا تشغيلها مباشرةً باستخدام uv:
uv run examples/grpo_echo/grpo_echo.pyيقوم هذا تلقائيًا بتثبيت حزمة البيئة المطلوبة في بيئة افتراضية معزولة.
البدء السريع
أسرع طريقة لفهم التكامل هي مثال كامل. يقوم سكربت echo.py بتدريب نموذج باستخدام بيئة Echo، والتي تمنح مكافآت للإكمالات بناءً على طول نصها:
from datasets import Dataset
from echo_env import EchoEnv
from echo_env.models import EchoAction
from trl import GRPOConfig, GRPOTrainer
ENV_URL = "https://openenv-echo-env.hf.space"
class EchoToolEnv:
def __init__(self):
self.env = EchoEnv(base_url=ENV_URL)
self.reward = 0.0
def reset(self, **kwargs) -> str | None:
self.reward = 0.0
return None
def echo(self, message: str) -> str:
"""
Echo the message back from the environment.
Args:
message: The message to echo
Returns:
The echoed message.
"""
observation = self.env.step(EchoAction(message=message))
self.reward = observation.observation.reward
return observation.observation.echoed_message
def reward_func(environments, **kwargs):
return [env.reward for env in environments]
dataset = Dataset.from_dict(
{"prompt": [[{"role": "user", "content": "Try to echo 'Hello World!' in the environment."}]] * 64}
)
trainer = GRPOTrainer(
model="Qwen/Qwen3-0.6B",
train_dataset=dataset,
reward_funcs=reward_func,
args=GRPOConfig(
chat_template_kwargs={"enable_thinking": False},
log_completions=True,
),
environment_factory=EchoToolEnv,
)
trainer.train()هذا كل شيء. فيما يلي ما يحدث في الخلفية:
environment_factory=EchoToolEnv: ينشئ المدرب نسخة واحدة منEchoToolEnvلكل عملية توليد (مرّر الفئة، وليس نسخة).- يتم استدعاء
reset()في بداية كل حلقة لتبديد الحالة. يعيد سلسلة ملاحظة (أوNone). - اكتشاف الأدوات: يكتشف المُدرِّب جميع الدوال العامة على كائن البيئة (وهنا
echo()) ويعرضها كأدوات لاستدعاء الدوال. يجب أن تحتوي كل دالة على تعليق توثيقي مناسب مع حُجج مُنمَّطة، يستخدمها المُدرِّب لبناء مخطط الأداة. - حلقة متعددة الأدوار: يقوم المدرب بإنشاء إكمال، وتحليل استدعاءات الأدوات، وتنفيذ
echo()، وإضافة النتيجة، ثم إنشاء إكمال جديد، حتى يتوقف النموذج عن استدعاء الأدوات أو يتم الوصول إلىmax_completion_length. - دالة المكافأة: تقرأ
env.rewardمن كل نسخة بيئة بعد الحلقة (قبل إعادة ضبط البيئة).
# Run the example
python examples/grpo_echo/grpo_echo.py
# Customize model and environment URL
python examples/grpo_echo/grpo_echo.py --model Qwen/Qwen3-0.6B --env-host https://openenv-echo-env.hf.spaceفيما يلي منحنى المكافأة من التدريب:
يمكنك استكشاف المزيد من أمثلة OpenEnv الجاهزة للاستخدام في الملاحظة الموجودة في أعلى هذه الصفحة.
كيف يعمل environment_factory
يدعم GRPOTrainer في TRL التدريب في بيئات تفاعلية من خلال الحجة environment_factory. عند توفير هذه الحجة، يتولى المدرب تلقائيًا معالجة حلقة استدعاء الأدوات متعددة الأدوار: فهو يولّي الاكتمالات، ويحلل استدعاءات الأدوات، وينفذها مقابل البيئة، ويعيد النتائج إلى النموذج. كل ذلك دون الحاجة إلى أي كود مخصص لعمليات النشر.
متطلبات فئة Environment
يجب أن تتبع فئة البيئة الخاصة بك هذه القواعد:
__init__(self)(اختياري): إذا تم توفيره، يجب ألا يستقبل أي حُجج. استخدمه لتهيئة الحالة أو العملاء. إذا كنت بحاجة إلى تكوين خارجي (مثل عنوان URL)، فالتقطه من النطاق المحيط أو من متغيرات على مستوى الوحدة.reset(self, **kwargs): تُستدعى في بداية كل حلقة. تستقبل جميع أعمدة مجموعة البيانات كحجج بكلمات مفتاحية. أعد ملاحظة نصية (أوNoneفي حال عدم وجود ملاحظة أولية).- طرق الأدوات: أي طريقة عامة (لا تبدأ بـ
_) باستثناءresetتُعرَض تلقائيًا كأداة. يجب أن تحتوي كل طريقة أداة على تعليق توثيقي يتضمن أوصافًا لـArgs:، حيث يستخدم المدرب هذه الأوصاف لتوليد مخطط الأداة للنموذج.
نصائح لفئات البيئة
- الحالة الخاصة بالمكافأة: يمكنك تخزين أي حالة تريدها على مثيل البيئة (مثل
self.reward،self.done، إلخ) والوصول إليها في دالة المكافأة الخاصة بك عبر معاملenvironments. راجع دليل البدء السريع لمثال على هذا النمط. - معالجة الأخطاء: إذا أثار أسلوب أداة استثناءً (مثل
ValueError("Game over."))، يلتقط المدرب ذلك ويعيد رسالة الخطأ إلى النموذج كاستجابة للأداة. هذه هي الطريقة الموصى بها للإشارة إلى أن إجراءً ما غير صالح أو أن الحلقة انتهت.
ENV_URL = "https://my-env.hf.space"
class MyEnv:
def __init__(self):
self.client = MyClient(base_url=ENV_URL) # captured from enclosing scope
self.reward = 0.0
def reset(self, **kwargs) -> str | None:
self.reward = 0.0
return "Initial observation for the model"
def my_tool(self, arg1: str, arg2: int) -> str:
"""
Description of what this tool does.
Args:
arg1: Description of arg1
arg2: Description of arg2
Returns:
The result message.
"""
self.reward = 1.0
return "Tool result"يجب أن تكون الأدوات طرق فردية بأسماء وصفية وبارامترات مُنمّطة (مثل
guess(word: str)،move(direction: str)). لا نوصي باستخدام طرق عامة مثلstep(action)، لأن النموذج يحتاج إلى أسماء أدوات ذات معنى ووصف للبارامترات ليتعلم استدعاء الأدوات.
دوال المكافأة
تستقبل دوال المكافآت معامل environments (وهو قائمة من حالات البيئة)، مما يتيح لك الوصول إلى أي حالة مخزنة خلال الحلقة:
def reward_func(environments, **kwargs) -> list[float]:
return [env.reward for env in environments]لمزيد من المعلومات حول دوال المكافأة، راجع GRPO - دوال مكافأة مخصصة.
نصائح حول دوال المكافأة
بعض الأمور التي وجدناها مفيدة عند العمل مع بيئات OpenEnv وخوارزمية GRPO:
- تأكد من اختلاف المجموعات. يقارن GRPO الإكمالات داخل مجموعة واحدة، لذا فإن الترتيب النسبي أهم من القيم المطلقة، والمجموعة التي تحصل فيها جميع الإكمالات على نفس المكافأة لا تُعطي أي إشارة. المكافأة الثنائية (1.0 عند النجاح، 0.0 في غير ذلك) تكون واضحة عندما ينجح النموذج في بعض الأحيان. عندما يكون نجاحه نادرًا، أضف نقاطًا جزئية: مثال سودوكو يضيف نسبة الخلايا المملوءة إلى مكافأة الحل، ومثال ووردل يضيف عدد الخلايا الخضراء في أفضل تخمين.
- افحص الحالة النهائية، لا المسار. عندما يكون ذلك ممكنًا، اترك البيئة هي التي تحكم على النتيجة (مثلًا: «هل حلّ النموذج اللغز؟») بدلًا من التحقق مما إذا كان قد اتبع تسلسلًا محددًا من الإجراءات. ومنح النموذج حرية اكتشاف استراتيجياته الخاصة.
- اختبر مكافأتك قبل التدريب. شغّل بعض الحلقات يدويًا (انظر دفتر ملاحظات مثال Wordle) للتأكد من أن البيئة تعيد مكافآت معقولة. إذا لم يتمكن نموذج كفء من تحقيق نتيجة أعلى من خط الأساس العشوائي، فقد تحتاج إشارة المكافأة إلى ضبط.
max_completion_length في الحلقات متعددة الأدوار
يحدد معامل max_completion_length إجمالي عدد الرموز عبر المحادثة متعددة الأدوار بأكملها (جميع توليدات النموذج + نتائج الأدوات مجتمعة)، وليس توليدًا واحدًا فقط. في البيئات التي تتضمن أدوارًا كثيرة (مثل لعبة سودوكو التي تتضمن عشرات الحركات)، قد تحتاج إلى زيادته:
args = GRPOConfig(
max_completion_length=4096, # default is usually 256-1024, increase for long episodes
# ...
)إذا كانت الحلقات تُختصر (يتوقف النموذج في منتصف اللعبة)، فمن المرجح أن يكون هذا هو السبب.
تشغيلات طويلة ضد مساحة
يمكن أن تُغلق مساحة (Space) اتصالات WebSocket التي تبقى مفتوحة لساعات (لقد لاحظنا ذلك تقريبًا كل ساعتين). بعد ذلك، تعيد عملاء OpenEnv حتى الإصدار 0.11.0 الاتصال بجلسة بيئة جديدة، وإذا كان هناك لعبة قيد التنفيذ، فإن استدعاء step() التالي يتم على لعبة لم يُعاد ضبطها من قِبل العميل، دون أي خطأ. منذ الإصدار 0.12.0 من OpenEnv، يرمي العملاء SessionLostError بدلاً من ذلك، مما يؤدي إلى فشل اللعبة. للحفاظ على هذه الانقطاعات بعيدًا عن التدريب، افتح عميلًا جديدًا في reset()، بحيث تستمر كل اتصال لمدة حلقة واحدة (episode)، واضبط MAX_CONCURRENT_ENVS أعلى قليلاً من عدد الألعاب التي تُلعب في وقت واحد، نظرًا لأن الجلسات القديمة تُحرَّر بشكل غير متزامن:
def reset(self, **kwargs) -> str | None:
self.client.close()
self.client = TextArenaEnv(base_url=env_url).sync()
result = self.client.reset()
...مثال متقدم: Wordle
لندرّب نموذجًا للعب Wordle باستخدام بيئة TextArena. يوضح هذا التفاعل متعدد الأدوار، ومعالجة الملاحظات التراكمية، وتقسيم المكافأة إلى عدة مصطلحات، وإنهاء الحلقة عبر الاستثناءات.
يتناول دليل OpenEnv Wordle خط الأنابيب بأكمله: بيانات SFT، وإحماء SFT، وخطوة GRPO هذه، والتقييم، مع ذكر العتاد والتكاليف. نسخة دفتر الملاحظات لهذا المثال هي
grpo_wordle.ipynb.
بيئة TextArena
TextArena هي مجموعة مفتوحة المصدر من الألعاب النصية التنافسية المصممة لتقييم مهارات الاستدلال في نماذج اللغة الكبيرة (LLMs) باستخدام ألعاب نصية مثل Wordle وSnake وTic-Tac-Toe وغيرها.

لماذا لعبة ووردل؟
يُعدّ Wordle معيارًا مرجعيًا جيدًا لتعلّم المعزز القائم على البيئة، لأنه يتطلب الاستدلال على الملاحظات، وهو نصّي بحت، كما يمكن للنماذج التي تحتوي على 1 مليار معامل أن تتحسّن في أدائها عليه.
كيف يعمل Wordle؟ Wordle هي لعبة تخمين كلمات حيث يجب على اللاعب تخمين كلمة من 5 أحرف في 6 محاولات. بعد كل تخمين، يوفر البيئة تغذية راجعة حرفًا بحرف:
G U E S S X G Y X XX = غير موجودة في الكلمة، G = الموقع الصحيح (أخضر)، Y = الموقع خاطئ (أصفر). هنا، “U” صحيحة وفي مكانها، و”E” موجودة في الكلمة لكن في مكان خاطئ.
ابدأ من مرحلة الإحماء الخاصة بالتدريب الإشرافي (SFT)
نادرًا ما يفوز نموذج Qwen3-1.7B بأي لعبة، لذا لا يوجد ما يتعلمه منه خوارزمية GRPO انطلاقًا من النموذج الأساسي: مجموعة من الألعاب لم يُفز بأيٍّ منها لا تُوفّر أي إشارة. يبدأ المثال من openenv/Qwen3-1.7B-Wordle-SFT، الذي تم تدريبه لمدة دورة واحدة على 1,000 لعبة لعبها محلل باستخدام نفس صيغة استدعاء الأدوات، مع استدلال موجز قبل كل تخمين.
فئة Environment
تغلّف فئة WordleEnv عميل TextArena، وتعرض guess() كأداة، وتحافظ على ما يحتاجه المكافأة:
import re
from textarena_env import TextArenaAction, TextArenaEnv
FEEDBACK = re.compile(r"Feedback:\n([A-Z ]+)\n([GYX ]+)")
class WordleEnv:
def __init__(self):
self.client = TextArenaEnv(base_url="https://<user>-wordle.hf.space")
def reset(self, **kwargs) -> str | None:
result = self.client.reset()
self._last_full_feedback = result.observation.messages[0].content
self.done = False
self.won = False
self.num_guesses = 0
self.best_greens = 0
self.invalid_moves = 0
return self._last_full_feedback
def guess(self, guess: str) -> str:
"""
Make a guess in the Wordle environment.
Args:
guess: The guessed word, formatted as '[abcde]'
Returns:
The feedback message from the environment.
"""
if self.done:
raise ValueError("Game over.")
result = self.client.step(TextArenaAction(message=guess))
_full_feedback = result.observation.messages[0].content
feedback = _full_feedback[len(self._last_full_feedback):]
self._last_full_feedback = _full_feedback
if "invalid move" in feedback.lower():
self.invalid_moves += 1
else:
self.num_guesses += 1
match = FEEDBACK.search(feedback)
if match:
self.best_greens = max(self.best_greens, match.group(2).count("G"))
self.won = result.done and result.reward >= 1.0
self.done = result.done
return feedbackاختيارات التصميم الرئيسية:
- يعيد
reset()رسالة اللعبة الأولية كأول ملاحظة يراها النموذج. guess()هو الأداة الوحيدة. يستدعي النموذج هذه الدالة في كل جولة بكلمة من خمسة أحرف.- تقطيع الملاحظات التراكمية: تُعيد TextArena سجل اللعبة الكامل في كل جولة. نقوم بتقطيع الجزء الجديد فقط لتجنب تكرار السياق.
- استثناء عند الانتهاء: إذا حاول النموذج التخمين بعد انتهاء اللعبة، يرمي
guess()استثناءً من نوعValueError. يلتقط المدرب هذا الاستثناء ويعيد"Game over."إلى النموذج كاستجابة للأداة، ويحسبه فيtools/failure_frequency.
دوال المكافأة والتدريب
تدفع مكافأة البيئة الخاصة نفسها مكافأة الفوز بنفس القيمة بعد تخمين واحد أو ستة، وتدفع جزاءً جزئياً فقط في نهاية اللعبة. عند تفعيل التفكير، يمكن لسياسة التعلم أن تتعلم التفكير بشكل أقل واستخدام جميع التخمينات الست للوصول إلى النهاية. ثلاثة شروط للمكافأة تمنع ذلك:
from datasets import Dataset
from trl import GRPOConfig, GRPOTrainer
def reward_win(environments, **kwargs) -> list[float]:
return [1.0 + 0.5 * (1 - env.num_guesses / 6) if env.won else 0.0 for env in environments]
def reward_partial(environments, **kwargs) -> list[float]:
return [0.0 if env.won else 0.1 * env.best_greens for env in environments]
def reward_invalid(environments, **kwargs) -> list[float]:
return [-0.1 * env.invalid_moves for env in environments]
prompt = """You are an expert Wordle solver with deep knowledge of English vocabulary...
Use the tool `guess` to make a guess."""
dataset = Dataset.from_dict({"prompt": [[{"role": "user", "content": prompt}]] * 600})
trainer = GRPOTrainer(
model="openenv/Qwen3-1.7B-Wordle-SFT",
reward_funcs=[reward_win, reward_partial, reward_invalid],
train_dataset=dataset,
args=GRPOConfig(
use_vllm=True,
vllm_mode="colocate",
chat_template_kwargs={"enable_thinking": True},
max_completion_length=4096,
max_tool_calling_iterations=12,
vllm_importance_sampling_mode="token_truncate",
num_generations=16,
per_device_train_batch_size=8,
gradient_accumulation_steps=8,
learning_rate=1e-6,
lr_scheduler_type="constant",
max_steps=150,
),
environment_factory=WordleEnv,
)
trainer.train()كلما جاء الفوز أسرع، زادت قيمته (1.42 في تخمين واحد، و1.0 في ستة)، بينما يُحتسب خسارة على أساس أفضل تخمين في اللعبة سواء انتهت أم لا، وتكلّف كل حركة غير صالحة 0.1. تسجّل TRL كل حدّ على حدة، لذا يرتفع rewards/reward_win/mean مع معدل الفوز.
تشغيل المثال
يلعب كل خطوة 64 لعبة، بجلسة بيئة واحدة لكل منها. انسخ مساحة openenv/wordle واضبط متغير MAX_CONCURRENT_ENVS=64 على نسختك.
وضع التجميع (وحدة معالجة رسومية واحدة، موصى به)
python examples/grpo_wordle/grpo_wordle.py --vllm-mode colocate --env-url https://<user>-wordle.hf.space
يُشغّل هذا الأمر vLLM في نفس العملية الخاصة بالتدريب، مما يتطلب بطاقة GPU واحدة فقط. في تجربتنا، استغرق تنفيذ 150 خطوة حوالي ساعتين و15 دقيقة على بطاقة A100 بسعة 80GB.
وضع الخادم (2+ وحدات معالجة رسومية، قابل للتوسع)
# Terminal 1: Start vLLM inference server
CUDA_VISIBLE_DEVICES=0 VLLM_SERVER_DEV_MODE=1 vllm serve openenv/Qwen3-1.7B-Wordle-SFT --host 0.0.0.0 --port 8000 \
--weight-transfer-config '{"backend": "nccl"}' \
--logprobs-mode processed_logprobs \
--max-logprobs -1
# Terminal 2: Run GRPO training with OpenEnv
CUDA_VISIBLE_DEVICES=1 python examples/grpo_wordle/grpo_wordle.py --vllm-mode server --vllm-server-url http://localhost:8000 \
--env-url https://<user>-wordle.hf.spaceالنتائج
من تشغيلنا، مع تشغيل تدريب واحد لكل نموذج، على نفس الكلمات الثلاثين المُستخدمة بطريقة الطمع:
| النموذج | انتصارات | التخمينات لكل فوز | تخمينات غير صالحة |
|---|---|---|---|
| Qwen3-1.7B | 0/30 | - | 24 |
| + تدريب الإشراف | 10/30 | 3.2 | 41 |
| + SFT + GRPO (النموذج) | 20/30 | 4.1 | 3 |
أزال GRPO في الغالب الكلمات المكررة والمختلقة، وفاز بمزيد من الألعاب. استغرقت معظم الانتصارات الجديدة من 4 إلى 6 تخمينات، مما أدى إلى ارتفاع عدد التخمينات لكل فوز. تأتي الكلمات الثلاثون من بين 156 اسمًا مستعارًا تستمد منه TextArena أسرارها، وقد شاهدت النماذج هذه الكلمات أيضًا أثناء التدريب، ومن المتوقع أن يحقق تشغيل جديد بضع انتصارات إضافية (فقد فاز تشغيلان SFT بنفس الوصفة بـ 10 و13 لعبة على التوالي). شاهد النماذج الثلاثة وهي تلعب في Wordle Arena، واطلع على منحنيات التدريب على Trackio.
التدريب متعدد البيئات
يمكنك تدريب نموذج واحد عبر بيئات متعددة في الوقت نفسه. وهذا مفيد عندما تريد أن يتعلم النموذج مهارات مختلفة بالتوازي. على سبيل المثال، لعب لعبة Wordle (الاستدلال اللغوي) ولعبة Catch (الاستدلال المكاني) في نفس دورة التدريب.
الفكرة الأساسية هي إنشاء فئة بيئة ميتا تغلف بيئات متعددة وتوجّه كل عينة إلى البيئة الصحيحة باستخدام عمود في مجموعة البيانات.
كيف يعمل
- أضف عمودًا باسم
"env"(أو ما يشابهه) إلى مجموعة بياناتك لتحديد البيئة التي ينتمي إليها كل عينة. - في
reset(**kwargs)، اقرأkwargs["env"]لاختيار البيئة النشطة لذلك الحِلق. - اعرض الأدوات من جميع البيئات؛ حيث يكتشف المدرب جميع الدوال العامة.
- استخدم دوال مكافأة منفصلة لكل بيئة، مع إرجاع
Noneللعينات التي لا تنتمي إلى تلك البيئة. يتعامل TRL مع قيمNoneباستخدامnansum/nanmean.
مثال: Wordle + Catch
يُدرّب السكربت grpo_multi_env.py على Wordle وCatch في آنٍ واحد:
class MultiEnv:
def __init__(self):
self._wordle_client = None
self._catch_client = None
self.active = None
self.reward = 0.0
self.done = False
def reset(self, **kwargs) -> str | None:
self.active = kwargs.get("env", "wordle")
self.reward = 0.0
self.done = False
if self.active == "wordle":
if self._wordle_client is not None:
try:
self._wordle_client.close()
except Exception:
pass
self._wordle_client = TextArenaEnv(base_url=WORDLE_URL)
result = self._wordle_client.reset()
self._last_full_feedback = result.observation.messages[0].content
self.reward = 0.0
return self._last_full_feedback
elif self.active == "catch":
if self._catch_client is not None:
try:
self._catch_client.close()
except Exception:
pass
self._catch_client = OpenSpielEnv(base_url=CATCH_URL)
result = self._catch_client.reset()
self.done = result.observation.done
return _format_catch_obs(result.observation.info_state)
# Wordle tool
def guess(self, guess: str) -> str:
"""Make a guess in the Wordle environment. ..."""
...
# Catch tools
def move(self, direction: str) -> str:
"""Move the paddle left or right. ..."""
...
def stay(self) -> str:
"""Do nothing and let the ball fall one step. ..."""
...الأنماط الرئيسية:
- تهيئة العميل بشكل كسول: أنشئ العملاء في
reset()وليس في__init__()لتجنب اتصالات WebSocket غير الضرورية. - الإغلاق قبل إعادة الفتح: أغلق العميل السابق قبل إنشاء عميل جديد لتجنب أخطاء سعة الخادم.
- توجيه
kwargs: يتم تمرير العمود"env"من مجموعة البيانات إلىreset()كحجة اسمية. - تُعرض جميع الأدوات في وقت واحد: يرى النموذج
guessوmoveوstayكأدوات متاحة بغض النظر عن البيئة النشطة. إذا استدعى النموذج أداة خاطئة (مثلmoveأثناء لعبة Wordle)، فإن الطريقة ترميValueErrorيلتقطها المدرب بسلاسة. عملياً، يتعلم النماذج استخدام الأدوات الصحيحة بناءً على موجه النظام.
دوال المكافأة لكل بيئة
تعيد كل دالة مكافأة None للعينات القادمة من بيئات أخرى:
def wordle_reward(environments, **kwargs) -> list[float | None]:
return [env.reward if env.active == "wordle" else None for env in environments]
def catch_reward(environments, **kwargs) -> list[float | None]:
rewards = []
for env in environments:
if env.active != "catch":
rewards.append(None)
elif env.done:
rewards.append(max(env.reward, 0.0))
else:
rewards.append(0.0)
return rewardsتحوّل TRL القيمة None إلى nan داخليًا، وتستخدم nansum/nanmean للتجميع، بحيث يُقيَّم كل عينة فقط بواسطة دالة المكافأة ذات الصلة بها.
مجموعة بيانات مع توجيه البيئة
n = 500
dataset = Dataset.from_dict({
"prompt": (
[[{"role": "user", "content": wordle_prompt}]] * n
+ [[{"role": "user", "content": catch_prompt}]] * n
),
"env": ["wordle"] * n + ["catch"] * n,
})تشغيل مثال البيئة المتعددة
python examples/grpo_multi_env/grpo_multi_env.py \
--wordle-url https://openenv-wordle.hf.space \
--catch-url https://openenv-openspiel-env.hf.space \
--vllm-mode colocate \
--gradient-accumulation-steps 4 \
--num-generations 8عند التدريب عبر بيئات متعددة، راقِ مقاييس كل دالة مكافأة على حدة (
train/reward_func_0،train/reward_func_1، إلخ) بدلاً من المقياس المدمجtrain/reward. يتناوب المقياس المدمج بين البيئات وقد يبدو مليئاً بالضوضاء.
تشغيل البيئات
عند استخدام environment_factory، يتصل المدرب بخادم البيئة تلقائيًا. كل ما عليك فعله هو التأكد من أن الخادم قيد التشغيل. هناك ثلاث طرق لتشغيل خادم بيئة OpenEnv:
الاتصال بمساحة Hugging Face عن بُعد (الأبسط)
تتضمن معظم سكربتات الأمثلة افتراضيًا مساحة مستضافة (لا حاجة إلى إعداد مسبق):
env = EchoEnv(base_url="https://openenv-echo-env.hf.space")للتدريب، انسخ المساحة إلى حسابك الخاص لتجنب مشاكل التزامن. يفتح المدرب اتصالات WebSocket متزامنة بعدد N (اتصال واحد لكل عملية توليد)، وقد لا تدعم المساحات المشتركة ذلك. راجع تزامن الخادم للتفاصيل.
حاوية Docker (موصى بها للإنتاج)
docker run -d -p 8001:8000 --platform linux/amd64 registry.hf.space/openenv-echo-env:latest
ثم اربط:
env = EchoEnv(base_url="http://0.0.0.0:8001")نُعيّن المنفذ 8001 إلى 8000 لنُبقي المنفذ 8000 متاحًا لخادم vLLM.
يمكنك أيضًا بدء تشغيل الحاوية برمجياً:
env = EchoEnv.from_docker_image("registry.hf.space/openenv-echo-env:latest")يمكنك العثور على صورة Docker لأي مساحة على Hub: افتح صفحة المساحة ← ⋮ (النقاط الثلاث) ← “تشغيل محليًا.”
عملية بايثون محلية (لأغراض التطوير)
hf download openenv/echo_env --repo-type=space --local-dir=echo_env python -m uvicorn echo_env.src.envs.echo_env.server.app:app --host 0.0.0.0 --port 8001
ثم اربط:
env = EchoEnv(base_url="http://0.0.0.0:8001")لمزيد من التفاصيل، راجع دليل OpenEnv.
فهرس البيئات
أفضل طريقة لاستكشاف الكتالوج الحالي للبيئات المُدارة هي زيارة الكتالوج الرسمي لـ OpenEnv.
لإنشاء بيئتك الخاصة، راجد دليل بناء بيئتك الخاصة باستخدام OpenEnv. البيئات مدمجة بشكل وثيق مع Hub، لذا يمكنك دفع بيئات جديدة ليتمكن المجتمع من إعادة استخدامها.
تزامن الخادم
عند استخدام environment_factory، يقوم المدرب بإنشاء N من حالات البيئة (حالة واحدة لكل عملية توليد)، حيث تفتح كل حالة اتصال WebSocket بالخادم. بشكل افتراضي، تسمح خوادم OpenEnv بجلسة متزامنة واحدة فقط، مما قد يؤدي إلى حدوث أعطال أثناء التدريب.
لدعم التدريب المتوازي، قم بإعداد الخادم للتوازي:
- في ملف البيئة الخاص بك، اعلن عن دعم الجلسات المتزامنة:
SUPPORTS_CONCURRENT_SESSIONS: bool = True- في تطبيق الخادم الخاص بك، اضبط حد التزامن:
app = create_app(
create_my_environment,
MyAction,
MyObservation,
max_concurrent_envs=64, # match or exceed generation_batch_size
)يجب أن يكون
max_concurrent_envs≥generation_batch_size(والذي يكون افتراضيًاper_device_train_batch_size × gradient_accumulation_steps). على سبيل المثال، معgradient_accumulation_steps=64وحجم دفعة 1، تحتاج إلى 64 جلسة متزامنة على الأقل.
environment_factory مقابل rollout_func
يدعم GRPOTrainer نهجين للتدريب القائم على البيئة:
environment_factory(الموصى به): تُعرّف فئة بيئة تتضمن طرقًا للأدوات، ويتولى المدرب معالجة التوليد، وتحليل استدعاءات الأدوات، والحلقة متعددة الأدوار تلقائيًا. هذا هو النهج المستخدم في جميع أنحاء هذا الدليل.rollout_func: تكتب حلقة التوليد والتفاعل مع البيئة بالكامل بنفسك. يمنحك هذا تحكماً كاملاً في كيفية إنتاج الإكمالات، وكيفية تنفيذ الأدوات، وكيفية حساب المكافآت.
استخدم rollout_func عندما لا يناسب environment_factory حالتك الخاصة. على سبيل المثال، خوادم الوكلاء الخارجية حيث تملك الخادم الخارجي حلقة التوليد وتدير بروتوكول تفاعل الوكيل مع البيئة الخاص به.
التدريب على الهارنسز: تدريب وكلاء برمجة حقيقيين (Harbor)
التكاملات المذكورة أعلاه هي صندوق أبيض: حيث يتولى TRL إدارة حلقة المحادثة متعددة الأدوار بنفسه. فهو يستخرج كل جولة، ويحلل استدعاءات الأدوات، وينفذها، ثم يعيد النتائج مرة أخرى.
بعض الوكلاء لا يمكن تشغيلهم بهذه الطريقة لأنهم يملكون حلقة تحكم خاصة بهم. فإطار عمل وكيل برمجي إنتاجي مثل opencode أو claude-code يمتلك مخططًا خاصًا به، ومجموعة أدوات، وإدارة سياق، وشروط إيقاف. أنت تريد تدريب ذلك الوكيل بالتحديد، وليس إعادة تنفيذ له.
لذلك، يوفّر TRL مسارًا تجريبيًا صندوقًا أسود (يملك الحلقة) مبنيًا على experimental.async_grpo.AsyncGRPOTrainer: يقود HarnessAsyncGRPOTrainer مصنع جلسات OpenEnv. تأتي الوكلاء وبيئاتهم المعزولة من Harbor، وتُقدَّم عبر OpenEnv باستخدام openenv harbor serve، بحيث يمكن تدريب أي وكيل تثبّته Harbor بالطريقة نفسها: تختار واحدًا منها باستخدام --harness. راجع examples/async_grpo_harbor لمثال كامل، بما في ذلك مُشغّل Hugging Face Jobs.
يتطلب هذا المسار
openenv>=0.7.0، وهو الإصدار الأول الذي يتضمن واجهةTrainingTraceلالتقاط البيانات:pip install "openenv>=0.7.0".
كيف يعمل
لا يقوم TRL بعينة كل جولة هنا. يعمل الوكيل حتى اكتماله بشكل مستقل، ثم يقرأ TRL ما قام به:
- يبدأ خادم OpenEnv Harbor الوكيل على مهمة Harbor داخل بيئة معزولة (على سبيل المثال E2B). تمر كل استدعاءات النموذج التي يجريها الوكيل عبر وكيل التقاط الخاص بالخادم إلى خادم vLLM الخاص بك، والذي يسجّل معرّفات رموز المحفّز والإكمال الخاصة بالمحرّك نفسه، وقيم logprobs المُستخرجة، وقناع الخسارة لكل رمز.
- عند توقف الوكيل، يقوم مُتحقّق المهمة الخاص بتقييم النتيجة، وتستقبل TRL
TrainingTraceالمُتحقَّق منه من OpenEnv وتبني صفوف التدريب من معرّفات الرموز والأقنعة الخاصة به. لا تعيد TRL ترميز الموجهات المُلتقطة. - تتدرّب GRPO على تلك الصفوف. يتم نشر المكافأة إلى كل رمز مُدرَّب من خلال الميزة النسبية للمجموعة، ثم تُزامَن الأوزان الجديدة مرة أخرى مع خادم vLLM نفسه.
تعمل كل عملية نشر (rollout) في جلسة معزولة خاصة بها، مع بيئة اختبار (sandbox) خاصة بها وجلسة التقاط خاصة بها. وتكمن أهمية العزل في أمرين: يجب على الوكيل (proxy) التقاط رموز (tokens) تلك العملية تحديدًا، ولا يجب أن تتداخل عملية نشر مع أخرى. يتحكم الإعداد max_concurrent_rollouts في عدد عمليات النشر التي تعمل في الوقت نفسه، ويجب أن يكون MAX_CONCURRENT_ENVS الخاص بالخادم مساويًا على الأقل لـ max_concurrent_rollouts + 1 (حيث يحتفظ المصنع باتصال إضافي واحد لبيانات وصف المهمة).
التوصيل
HarnessAsyncGRPOTrainer يستقبل النموذج والإعدادات ومجموعة البيانات بنفس طريقة experimental.async_grpo.AsyncGRPOTrainer، مع مصنع جلسات بدلاً من reward_funcs وtools وenvironment_factory. في غياب harness_adapter، يملك الوكيل حلقة عمله. يبني المدرب مصنع الجلسات بسياسة عينات خاصة به، لذا تمرر له دالة قابلة للاستدعاء مثل partial(HarborSessionFactory, ...) بدلاً من مصنع جاهز. تُرجع كل جلسة TrainingTrace من OpenEnv تحتوي على رموز المحرك الدقيقة، وقيم logprobs السلوكية، وأقنعة التعبئة المسبقة. rollout_reward_fn اختيارية؛ في غيابها، يستخدم TRL مكافأة المُتحقق من الجلسة.
from functools import partial
from harbor_env.harness import HarborSessionFactory
from trl.experimental.async_grpo import AsyncGRPOConfig
from trl.experimental.async_grpo.openenv_harness import HarnessAsyncGRPOTrainer
factory = partial(
HarborSessionFactory,
"http://localhost:8200", # the OpenEnv Harbor server
split="<hf-dataset>", # a Harbor task dataset the server serves
harness="mini-swe-agent", # or "opencode", "claude-code", ...
sandbox="e2b",
llm_url=vllm_url,
model=model,
)
config = AsyncGRPOConfig(num_generations=8, max_concurrent_rollouts=8, vllm_server_base_url=vllm_url)
trainer = HarnessAsyncGRPOTrainer(
model=model,
harness_session_factory=factory, # called by the trainer with its sampling policy
args=config,
train_dataset=dataset,
rollout_reward_fn=my_reward, # outcome -> float | None
)
trainer.train()ما تحتاج إلى تعريفه
يملك OpenEnv مسؤولية التحقق من الالتقاط واختيار الرموز. يقوم TRL بتحويل قناع التسلسل الكامل الخاص بالمُنتِج إلى قناع الإكمال الداخلي الخاص به، ثم يجمع الصفوف الناتجة ويطبّق GRPO. ولا يعيد بناء الرموز أو يستنتج أيّ الاستدعاءات تنتمي إلى الوكيل.
rollout_reward_fn : كيفية تقييم تمرير
Callable[[HarnessRolloutOutcome], float | None]
لا يعرف TRL ما الذي يعني النجاح في مهمتك، لذا تقوم بتحويل كل مسار مكتمل إلى مكافأة قياسية. تستقبل الدالة HarnessRolloutOutcome تصف ما قام به الوكيل:
env_reward(float | None): المكافأة من مُتحقِّق المهمة، أوNoneعندما تعذَّر تقييم الجولة.completion(list[dict]): نص المحادثة النهائي.trace(list[TraceEntry]): سجلات الالتقاط المحددة من OpenEnv، بما في ذلك الأدوار المقنّعة بالصفر.tool_call_countوtool_failure_count(int): عدد استدعاءات الأدوات التي أجراها الوكيل، وعدد الاستدعاءات التي بدت كفشل.tool_calls_by_name(dict[str, int]): عدد الاستدعاءات لكل أداة، على سبيل المثال{"bash": 3, "edit": 2}.timed_out(bool): ما إذا كان الوكيل قد نفد ميزانيته الزمنية.
أعد float، أو أعد None لوضع علامة على أن الجولة غير قابلة للتقييم، بحيث تُستبعد من خط الأساس للمجموعة بدلاً من احتسابها كقيمة صفرية. إذا لم تقم بتمرير هذه الدالة، فسيتم استخدام env_reward الخام كما هو. يحتفظ مثال Harbor بصحة المُتحقق كإشارة رئيسية، ويضيف مكافأة صغيرة لكفاءة استخدام الأدوات فقط عند حل المهمة.
لماذا لا نكتفي باستخدام المُتحقِّق؟ إنَّ المُتحقِّق هو دور البيئة، ويجيب عن سؤال واحد، وهو «ما مدى صحة النتيجة»، مما يجعله نظيفًا وقابلًا لإعادة الاستخدام في التقييم. أما المكافأة التي تُدرَّب عليها فهي قرار منفصل خاص بوقت التدريب (تحويل الدرجة إلى ثنائية، معاقبة السلوك المتدهور، استبعاد عمليات التمرير غير القابلة للتقييم). كما أنَّها تحتاج إلى إشارات لا يراها المُتحقِّق أبدًا، إذ أنَّ المُتحقِّق يفحص النتيجة النهائية فقط، بينما يستلم
rollout_reward_fnأيضًا المسار (عدد الأدوات،timed_out، الأثر). على سبيل المثال، قد تمر عملية تمرير ببعض الاختبارات دون أن تشغّلbashإطلاقًا؛ ولا يمكن لـrollout_reward_fnسوى هو أن يرى ذلك ويعاقب عليه.
تأتي أهلية الرموز من OpenEnv
session.fetch_training_trace() يعيد TrainingTrace مُتحققًا منه. لكل جولة node_id فريد، وprompt_token_ids، وcompletion_token_ids، وper_token_logps، وloss_mask إلزامي يغطي النص التوجيهي والإكمال معًا. قيم المواضع في النص التوجيهي تساوي صفرًا. تُحفظ أقنعة الإكمال الجزئية، بما في ذلك قيم الصفر داخل رد المساعد.
على سبيل المثال، يُدرَّب على الرموز 20 و22 عند استخدام الاستدعاء [10, 11] والإكمال [20, 21, 22] والقناع [0, 0, 1, 0, 1]. يبقى الرمز 21 سياقًا. يُحتفظ بالإكمال المقنّع بالكامل كسياق ولأغراض المحاسبة.
يستثني المُنتِج الاستدعاءات المساعدة والمحاولات المُعادتها المُتخلَّص منها. لا توجد معلمات train_turn_fn أو agent_turn_fn في هذه الواجهة البرمجية. يجب على التكاملات القائمة نقل عملية الاختيار إلى أقنعة OpenEnv الخاصة بها. يبقى مُتحقِّق المهمة مستقلاً عن أهلية الرموز.
تفشل الالتقاطات غير الصالحة عند حد OpenEnv وتوقف التدريب. تبقى أخطاء النقل غير قابلة للتقييم. تبدأ السجلات المُعاد كتابتها صفوفًا مستقلة، مع الحفاظ على الرموز المسحوبة وقيم logprobs. لا تزال الصفوف المتعددة تتشارك مكافأة rollout؛ ولا يغيّر هذا الواجهة طريقة وزن GRPO.
المتطلبات
يملك خادم OpenEnv Harbor مجموعة البيانات والبيئات المعزولة. ويحتاج إلى بيانات اعتماد خلفية البيئة المعزولة (على سبيل المثال E2B_API_KEY):
MAX_CONCURRENT_ENVS=9 openenv harbor serve --dataset <hf-dataset> --port 8200 --capture-port 8300 --expose gradio
يجب أن يعرض خادم vLLM استدعاء الأدوات ومعرفات الرموز الفعلية، وأن يفعّل مزامنة الأوزان عبر NCCL حتى يصل الوكيل دائمًا إلى السياسة الحالية. يجب أن تستخدم عمليات التوليد وتحديثات الأوزان خادم vLLM نفسه:
vllm serve <model> \
--enable-auto-tool-choice --tool-call-parser <parser> \
--logprobs-mode processed_logprobs \
--return-tokens-as-token-ids \
--weight-transfer-config '{"backend":"nccl"}'Update on GitHubتقع تدريبات إدارة الحلقة تحت
trl.experimentalوقد تتغير واجهتها البرمجية. يثبّت المثال نسخة OpenEnv التي تم الاختبار معها؛ راجع README الخاص بها للحصول على الإعداد الدقيق.
